Created sensor cache class

Created sensor cache and sensor interface that needs to be
implemented by sensors.

Tested:
- Sensors created by sensor cache are stored and reused if
  there is try to access same sensor multiple times.
- All other units tests are passing

Change-Id: I552b2016bca4688e1b2a223297587826af256b54
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
diff --git a/meson.build b/meson.build
index 42bd59f..8d0b018 100644
--- a/meson.build
+++ b/meson.build
@@ -67,9 +67,10 @@
     'telemetry',
     [
         'src/main.cpp',
+        'src/persistent_json_storage.cpp',
         'src/report.cpp',
         'src/report_manager.cpp',
-        'src/persistent_json_storage.cpp',
+        'src/sensor_cache.cpp',
     ],
     dependencies: [
         boost,
diff --git a/src/interfaces/sensor.hpp b/src/interfaces/sensor.hpp
new file mode 100644
index 0000000..dbeb158
--- /dev/null
+++ b/src/interfaces/sensor.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <tuple>
+
+namespace interfaces
+{
+
+class Sensor
+{
+  public:
+    struct Id
+    {
+        Id(std::string_view type, std::string_view service,
+           std::string_view path) :
+            type(type),
+            service(service), path(path)
+        {}
+
+        std::string type;
+        std::string service;
+        std::string path;
+
+        bool operator<(const Id& other) const
+        {
+            return std::tie(type, service, path) <
+                   std::tie(other.type, other.service, other.path);
+        }
+    };
+
+    virtual ~Sensor() = default;
+
+    virtual Id id() const = 0;
+};
+
+} // namespace interfaces
diff --git a/src/sensor_cache.cpp b/src/sensor_cache.cpp
new file mode 100644
index 0000000..3b9c83d
--- /dev/null
+++ b/src/sensor_cache.cpp
@@ -0,0 +1,19 @@
+#include "sensor_cache.hpp"
+
+SensorCache::SensorsContainer::iterator SensorCache::findExpiredSensor(
+    SensorCache::SensorsContainer::iterator begin)
+{
+    return std::find_if(begin, sensors.end(),
+                        [](const auto& item) { return item.second.expired(); });
+}
+
+void SensorCache::cleanupExpiredSensors()
+{
+    auto begin = sensors.begin();
+
+    for (auto it = findExpiredSensor(begin); it != sensors.end();
+         it = findExpiredSensor(begin))
+    {
+        begin = sensors.erase(it);
+    }
+}
diff --git a/src/sensor_cache.hpp b/src/sensor_cache.hpp
new file mode 100644
index 0000000..3e19721
--- /dev/null
+++ b/src/sensor_cache.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "interfaces/sensor.hpp"
+
+#include <boost/container/flat_map.hpp>
+#include <boost/system/error_code.hpp>
+
+#include <iostream>
+#include <memory>
+#include <string_view>
+
+class SensorCache
+{
+  public:
+    template <class SensorType, class... Args>
+    std::shared_ptr<SensorType> makeSensor(std::string_view service,
+                                           std::string_view path,
+                                           Args&&... args)
+    {
+        cleanupExpiredSensors();
+
+        auto id = SensorType::makeId(service, path);
+        auto it = sensors.find(id);
+
+        if (it == sensors.end())
+        {
+            auto sensor = std::make_shared<SensorType>(
+                std::move(id), std::forward<Args>(args)...);
+
+            sensors[sensor->id()] = sensor;
+
+            return sensor;
+        }
+
+        return std::static_pointer_cast<SensorType>(it->second.lock());
+    }
+
+  private:
+    using SensorsContainer =
+        boost::container::flat_map<interfaces::Sensor::Id,
+                                   std::weak_ptr<interfaces::Sensor>>;
+
+    SensorsContainer sensors;
+
+    SensorsContainer::iterator findExpiredSensor(SensorsContainer::iterator);
+    void cleanupExpiredSensors();
+};
diff --git a/tests/meson.build b/tests/meson.build
index 32baaad..08a6e6c 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -25,7 +25,12 @@
         'telemetry-ut',
         [
             '../src/persistent_json_storage.cpp',
+            '../src/report.cpp',
+            '../src/report_manager.cpp',
+            '../src/sensor_cache.cpp',
             'src/test_persistent_json_storage.cpp',
+            'src/test_sensor_cache.cpp',
+            'src/utils/generate_unique_mock_id.cpp',
         ],
         dependencies: [
             boost,
@@ -35,6 +40,6 @@
             phosphor_logging,
             sdbusplus,
         ],
-        include_directories: '../src',
+        include_directories: ['../src', 'src']
     )
 )
diff --git a/tests/src/mocks/json_storage.hpp b/tests/src/mocks/json_storage_mock.hpp
similarity index 100%
rename from tests/src/mocks/json_storage.hpp
rename to tests/src/mocks/json_storage_mock.hpp
diff --git a/tests/src/mocks/sensor_mock.hpp b/tests/src/mocks/sensor_mock.hpp
new file mode 100644
index 0000000..9e8d7f3
--- /dev/null
+++ b/tests/src/mocks/sensor_mock.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "interfaces/sensor.hpp"
+#include "utils/generate_unique_mock_id.hpp"
+
+#include <gmock/gmock.h>
+
+class SensorMock : public interfaces::Sensor
+{
+  public:
+    explicit SensorMock(Id sensorId) : mockSensorId(sensorId)
+    {
+        ON_CALL(*this, id()).WillByDefault(testing::Invoke([this] {
+            return this->mockSensorId;
+        }));
+    }
+
+    static Id makeId(std::string_view service, std::string_view path)
+    {
+        return Id("SensorMock", service, path);
+    }
+
+    MOCK_CONST_METHOD0(id, Id());
+
+    const uint64_t mockId = generateUniqueMockId();
+
+    Id mockSensorId = Id("SensorMock", "", "");
+};
diff --git a/tests/src/test_sensor_cache.cpp b/tests/src/test_sensor_cache.cpp
new file mode 100644
index 0000000..5be9a0d
--- /dev/null
+++ b/tests/src/test_sensor_cache.cpp
@@ -0,0 +1,109 @@
+#include "mocks/sensor_mock.hpp"
+#include "sensor_cache.hpp"
+
+#include <initializer_list>
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+
+class TestSensorCache : public Test
+{
+  public:
+    SensorCache sut;
+};
+
+auto sensorIdEq(interfaces::Sensor::Id id)
+{
+    return AllOf(Field(&interfaces::Sensor::Id::type, Eq(id.type)),
+                 Field(&interfaces::Sensor::Id::service, Eq(id.service)),
+                 Field(&interfaces::Sensor::Id::path, Eq(id.path)));
+}
+
+struct IdParam
+{
+    IdParam() = default;
+    IdParam(std::string_view service, std::string_view path) :
+        service(service), path(path)
+    {}
+
+    std::string service;
+    std::string path;
+};
+
+class TestSensorCacheP :
+    public TestSensorCache,
+    public WithParamInterface<std::vector<IdParam>>
+{
+  public:
+    void SetUp() override
+    {
+        auto vec = GetParam();
+        ASSERT_THAT(vec, SizeIs(param.size()));
+        std::copy(vec.begin(), vec.end(), param.begin());
+    }
+
+    template <size_t index>
+    const IdParam& id() const
+    {
+        static_assert(index < std::tuple_size_v<decltype(param)>);
+        return param[index];
+    }
+
+  private:
+    std::array<IdParam, 2> param;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    UniqueIds, TestSensorCacheP,
+    Values(std::vector<IdParam>({IdParam("service1", "path1"),
+                                 IdParam("service1", "path2")}),
+           std::vector<IdParam>({IdParam("service1", "path1"),
+                                 IdParam("service2", "path1")}),
+           std::vector<IdParam>({IdParam("service1", "path1"),
+                                 IdParam("service2", "path2")})));
+
+TEST_P(TestSensorCacheP, shouldReturnDifferentSensorWhenIdsAreDifferent)
+{
+    auto sensor1 =
+        sut.makeSensor<NiceMock<SensorMock>>(id<0>().service, id<0>().path);
+    auto sensor2 =
+        sut.makeSensor<NiceMock<SensorMock>>(id<1>().service, id<1>().path);
+
+    ASSERT_THAT(sensor1.get(), Not(Eq(sensor2.get())));
+    ASSERT_THAT(sensor1->mockId, Not(Eq(sensor2->mockId)));
+}
+
+TEST_F(TestSensorCache, shouldReturnSameSensorWhenSensorWithSameIdStillExists)
+{
+    auto sensor1 =
+        sut.makeSensor<NiceMock<SensorMock>>("sensor-service", "sensor-path");
+    auto sensor2 =
+        sut.makeSensor<NiceMock<SensorMock>>("sensor-service", "sensor-path");
+
+    ASSERT_THAT(sensor1.get(), Eq(sensor2.get()));
+    ASSERT_THAT(sensor1->mockId, Eq(sensor2->mockId));
+}
+
+TEST_F(TestSensorCache, shouldReturnDifferentSensorWhenPreviousSensorExpired)
+{
+    auto mockId1 =
+        sut.makeSensor<NiceMock<SensorMock>>("sensor-service", "sensor-path")
+            ->mockId;
+    auto mockId2 =
+        sut.makeSensor<NiceMock<SensorMock>>("sensor-service", "sensor-path")
+            ->mockId;
+
+    ASSERT_THAT(mockId2, Not(Eq(mockId1)));
+}
+
+TEST_F(TestSensorCache, shouldCreateSensorWithCorrespondingId)
+{
+    auto id =
+        sut.makeSensor<NiceMock<SensorMock>>("sensor-service", "sensor-path")
+            ->id();
+
+    auto expected = SensorMock::makeId("sensor-service", "sensor-path");
+
+    ASSERT_THAT(id, sensorIdEq(expected));
+}
diff --git a/tests/src/utils/generate_unique_mock_id.cpp b/tests/src/utils/generate_unique_mock_id.cpp
new file mode 100644
index 0000000..f3e3f01
--- /dev/null
+++ b/tests/src/utils/generate_unique_mock_id.cpp
@@ -0,0 +1,7 @@
+#include "generate_unique_mock_id.hpp"
+
+uint64_t generateUniqueMockId()
+{
+    static uint64_t id = 0u;
+    return id++;
+}
diff --git a/tests/src/utils/generate_unique_mock_id.hpp b/tests/src/utils/generate_unique_mock_id.hpp
new file mode 100644
index 0000000..6e5192c
--- /dev/null
+++ b/tests/src/utils/generate_unique_mock_id.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <cstdint>
+
+uint64_t generateUniqueMockId();