~aleteoryx/muditaos

3043c063eca7e112c71323e03b45dc296ba58230 — Onufry Pajaczek 3 years ago f56b631
[BH-1515] Bedtime reminder notification issue

AbstractAlarmAction execute method takes Record as paremeter to pass it
to action
Bedtime abstraction extended to keep own EvetContainer and hadle needed
actions(push,clear ... and so on)

Fixed an issue where cpp file was added by #include directive
New fuction added to test/CMakeLists.txt to
add manually prepared target to test siute
googletest-service-time-alarm-operations source moved to library and
than linked in to googletest-service-time-bell-alarm-operations
32 files changed, 410 insertions(+), 204 deletions(-)

M module-apps/apps-common/ApplicationCommon.cpp
M module-apps/apps-common/ApplicationCommonPopupBlueprints.cpp
M module-apps/apps-common/CMakeLists.txt
R {products/BellHybrid/alarms/include/popups => module-apps/apps-common/popups/data}/BedtimeReminderPopupRequestParams.hpp
M module-services/service-time/AlarmOperations.cpp
M module-services/service-time/AlarmOperations.hpp
M module-services/service-time/tests/CMakeLists.txt
M module-services/service-time/tests/tests-AlarmOperations.cpp
A module-services/service-time/tests/tests-AlarmOperations.hpp
M products/BellHybrid/alarms/BellAlarmHandler.cpp
M products/BellHybrid/alarms/CMakeLists.txt
M products/BellHybrid/alarms/include/AbstractAlarmAction.hpp
M products/BellHybrid/alarms/src/actions/FrontlightAction.cpp
M products/BellHybrid/alarms/src/actions/FrontlightAction.hpp
M products/BellHybrid/alarms/src/actions/NotifyGUIAction.cpp
M products/BellHybrid/alarms/src/actions/NotifyGUIAction.hpp
M products/BellHybrid/alarms/src/actions/NotifyGUIBedtimeReminderAction.cpp
M products/BellHybrid/alarms/src/actions/NotifyGUIBedtimeReminderAction.hpp
M products/BellHybrid/alarms/src/actions/PlayAudioActions.cpp
M products/BellHybrid/alarms/src/actions/PlayAudioActions.hpp
M products/BellHybrid/apps/application-bell-bedtime/presenter/BellBedtimeWindowPresenter.hpp
M products/BellHybrid/apps/common/CMakeLists.txt
M products/BellHybrid/apps/common/include/common/data/BatteryUtils.hpp
M products/BellHybrid/apps/common/include/common/models/BedtimeModel.hpp
M products/BellHybrid/apps/common/include/common/popups/BedtimeNotificationWindow.hpp
M products/BellHybrid/apps/common/src/popups/BedtimeNotificationWindow.cpp
M products/BellHybrid/services/appmgr/include/appmgr/messages/AlarmMessage.hpp
M products/BellHybrid/services/time/AlarmOperations.cpp
M products/BellHybrid/services/time/include/time/AlarmOperations.hpp
M products/BellHybrid/services/time/tests/CMakeLists.txt
M products/BellHybrid/services/time/tests/test-BellAlarmOperations.cpp
M test/CMakeLists.txt
M module-apps/apps-common/ApplicationCommon.cpp => module-apps/apps-common/ApplicationCommon.cpp +1 -1
@@ 2,7 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ApplicationCommon.hpp"
#include "GuiTimer.hpp"    // for GuiTimer
#include "GuiTimer.hpp"            // for GuiTimer
#include "Timers/TimerFactory.hpp" // for Timer
#include "status-bar/Time.hpp"
#include "messages/AppSwitchWindowPopupMessage.hpp"

M module-apps/apps-common/ApplicationCommonPopupBlueprints.cpp => module-apps/apps-common/ApplicationCommonPopupBlueprints.cpp +14 -0
@@ 7,6 7,7 @@
#include "service-db/Settings.hpp"
#include "service-db/agents/settings/SystemSettings.hpp"
#include "popups/data/PopupData.hpp"
#include "popups/data/BedtimeReminderPopupRequestParams.hpp"

namespace app
{


@@ 132,6 133,19 @@ namespace app
                                  std::make_unique<gui::AlarmPopupRequestParams>(popupParams));
                return true;
            });

        popupBlueprint.registerBlueprint(
            ID::BedtimeNotification, [&](gui::popup::ID id, std::unique_ptr<gui::PopupRequestParams> &params) {
                auto popupParams = dynamic_cast<gui::BedtimeReminderPopupRequestParams *>(params.get());
                if (popupParams == nullptr) {
                    return false;
                }

                switchWindowPopup(gui::popup::resolveWindowName(id),
                                  params->getDisposition(),
                                  std::make_unique<gui::BedtimeReminderPopupRequestParams>(popupParams->eventRecord));
                return true;
            });
    }

    std::optional<gui::popup::Blueprint> ApplicationCommon::popupBlueprintFallback(gui::popup::ID id)

M module-apps/apps-common/CMakeLists.txt => module-apps/apps-common/CMakeLists.txt +1 -0
@@ 75,6 75,7 @@ target_sources(apps-common
        widgets/TimeSetFmtSpinner.hpp
        actions/AlarmRingingData.hpp
        actions/AlarmTriggeredAction.hpp
        popups/data/BedtimeReminderPopupRequestParams.hpp

        models/SongContext.hpp
        models/SongsRepository.hpp

R products/BellHybrid/alarms/include/popups/BedtimeReminderPopupRequestParams.hpp => module-apps/apps-common/popups/data/BedtimeReminderPopupRequestParams.hpp +6 -2
@@ 1,16 1,20 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <apps-common/popups/data/PopupRequestParams.hpp>
#include <Tables/Record.hpp>

namespace gui
{
    class BedtimeReminderPopupRequestParams : public PopupRequestParams
    {
      public:
        BedtimeReminderPopupRequestParams() : PopupRequestParams{gui::popup::ID::BedtimeNotification}
        BedtimeReminderPopupRequestParams(Record record)
            : PopupRequestParams{gui::popup::ID::BedtimeNotification}, eventRecord{record}
        {}

        Record eventRecord;
    };
} // namespace gui

M module-services/service-time/AlarmOperations.cpp => module-services/service-time/AlarmOperations.cpp +30 -14
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "AlarmOperations.hpp"


@@ 128,18 128,13 @@ namespace alarms

    void AlarmOperationsCommon::turnOffRingingAlarm(const std::uint32_t id, OnTurnOffRingingAlarm callback)
    {
        auto found = findSingleEventById(ongoingSingleEvents, id);
        if (found == ongoingSingleEvents.end()) {
            LOG_ERROR("Trying to turn off nonexisting event");
        bool ongoingEventsResult = turnOffAlarmIfFoundInOngoingEvents(id);
        bool bedtimeEventsResult = turnOffAlarmIfFoundInBedtime(id);
        if (not ongoingEventsResult and not bedtimeEventsResult) {
            LOG_ERROR("Trying to turn off nonexisting event ID %d", static_cast<int>(id));
            callback(false);
            return;
        }
        switchAlarmExecution(*(*found), false);
        ongoingSingleEvents.erase(found);

        processOngoingEvents();

        handleActiveAlarmsCountChange();
        callback(true);
    }



@@ 338,6 333,26 @@ namespace alarms
        }
    }

    bool AlarmOperationsCommon::turnOffAlarmIfFoundInOngoingEvents(std::uint32_t id)
    {
        auto found = findSingleEventById(ongoingSingleEvents, id);
        if (found == ongoingSingleEvents.end()) {
            return false;
        }

        switchAlarmExecution(*(*found), false);
        ongoingSingleEvents.erase(found);
        processOngoingEvents();
        handleActiveAlarmsCountChange();

        return true;
    }

    bool AlarmOperationsCommon::turnOffAlarmIfFoundInBedtime(const std::uint32_t id)
    {
        return false;
    }

    void AlarmOperationsCommon::onAlarmTurnedOff(const std::shared_ptr<AlarmEventRecord> &event,
                                                 alarms::AlarmType alarmType)
    {}


@@ 433,15 448,16 @@ namespace alarms
        callback(std::move(snoozedEvents));
    }

    auto findSingleEventById(std::vector<std::unique_ptr<SingleEventRecord>> &events, const std::uint32_t id)
        -> std::vector<std::unique_ptr<SingleEventRecord>>::iterator
    auto findSingleEventById(EventsContainer<SingleEventRecord> &events, const std::uint32_t id)
        -> EventsContainer<SingleEventRecord>::iterator
    {
        return std::find_if(events.begin(), events.end(), [id](const std::unique_ptr<SingleEventRecord> &event) {
            return id == event->parent->ID;
        });
    }
    auto findSnoozedEventById(std::vector<std::unique_ptr<SnoozedAlarmEventRecord>> &events, const std::uint32_t id)
        -> std::vector<std::unique_ptr<SnoozedAlarmEventRecord>>::iterator

    auto findSnoozedEventById(EventsContainer<SnoozedAlarmEventRecord> &events, const std::uint32_t id)
        -> EventsContainer<SnoozedAlarmEventRecord>::iterator
    {
        return std::find_if(events.begin(), events.end(), [id](const std::unique_ptr<SnoozedAlarmEventRecord> &event) {
            return id == event->parent->ID;

M module-services/service-time/AlarmOperations.hpp => module-services/service-time/AlarmOperations.hpp +12 -8
@@ 15,6 15,8 @@

namespace alarms
{
    template <typename T> using EventsContainer = std::vector<std::unique_ptr<T>>;

    class IAlarmOperations
    {
      public:


@@ 114,14 116,17 @@ namespace alarms
        AlarmHandlerFactory alarmHandlerFactory;

        // Events we are waiting for (on one timepoint)
        std::vector<std::unique_ptr<SingleEventRecord>> nextSingleEvents;
        std::vector<std::unique_ptr<SingleEventRecord>> ongoingSingleEvents;
        std::vector<std::unique_ptr<SnoozedAlarmEventRecord>> snoozedSingleEvents;
        EventsContainer<SingleEventRecord> nextSingleEvents;
        EventsContainer<SingleEventRecord> ongoingSingleEvents;
        EventsContainer<SnoozedAlarmEventRecord> snoozedSingleEvents;

        alarms::AlarmType getAlarmEventType(const SingleEventRecord &event);
        virtual void handleAlarmEvent(const std::shared_ptr<AlarmEventRecord> &event,
                                      alarms::AlarmType alarmType,
                                      bool newStateOn);
        virtual bool turnOffAlarmIfFoundInBedtime(std::uint32_t id);
        bool turnOffAlarmIfFoundInOngoingEvents(std::uint32_t id);
        void switchAlarmExecution(const SingleEventRecord &singleAlarmEvent, bool newStateOn);

      private:
        GetCurrentTime getCurrentTimeCallback;


@@ 139,7 144,6 @@ namespace alarms
                                     std::vector<AlarmEventRecord> records,
                                     OnGetAlarmsProcessed handledCallback);
        void checkAndUpdateCache(AlarmEventRecord record);
        void switchAlarmExecution(const SingleEventRecord &singleAlarmEvent, bool newStateOn);
        void processEvents(TimePoint now);
        void processOngoingEvents();
        void processNextEventsQueue(const TimePoint now);


@@ 160,8 164,8 @@ namespace alarms
            IAlarmOperations::GetCurrentTime getCurrentTimeCallback) const override;
    };

    auto findSingleEventById(std::vector<std::unique_ptr<SingleEventRecord>> &events, const std::uint32_t id)
        -> std::vector<std::unique_ptr<SingleEventRecord>>::iterator;
    auto findSnoozedEventById(std::vector<std::unique_ptr<SnoozedAlarmEventRecord>> &events, const std::uint32_t id)
        -> std::vector<std::unique_ptr<SnoozedAlarmEventRecord>>::iterator;
    auto findSingleEventById(EventsContainer<SingleEventRecord> &events, const std::uint32_t id)
        -> EventsContainer<SingleEventRecord>::iterator;
    auto findSnoozedEventById(EventsContainer<SnoozedAlarmEventRecord> &events, const std::uint32_t id)
        -> EventsContainer<SnoozedAlarmEventRecord>::iterator;
} // namespace alarms

M module-services/service-time/tests/CMakeLists.txt => module-services/service-time/tests/CMakeLists.txt +19 -10
@@ 19,16 19,25 @@ add_catch2_executable(
        module-db
)

add_gtest_executable(
    NAME 
        service-time-alarm-operations
    SRCS
        tests-AlarmOperations.cpp
    LIBS
add_library(service-time-alarm-operations-source OBJECT
    tests-AlarmOperations.cpp)

target_link_libraries(service-time-alarm-operations-source
    PUBLIC
        module-sys
        service-time
    INCLUDE
        $<TARGET_PROPERTY:service-time,INCLUDE_DIRECTORIES>
    DEFS
        COMMON_ALARM_OPERATIONS_TEST
        gtest_main
        gmock
        log-api)

target_include_directories(service-time-alarm-operations-source
    PUBLIC
        $<TARGET_PROPERTY:service-time,INCLUDE_DIRECTORIES>)

add_executable(googletest-service-time-alarm-operations)
target_link_libraries(googletest-service-time-alarm-operations
    PUBLIC
    service-time-alarm-operations-source
)

add_gtest_manually_prepared_target(NAME googletest-service-time-alarm-operations)

M module-services/service-time/tests/tests-AlarmOperations.cpp => module-services/service-time/tests/tests-AlarmOperations.cpp +10 -111
@@ 1,123 1,22 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <AlarmOperations.hpp>
#include <AlarmRepository.hpp>
#include <service-time/AlarmHandler.hpp>

#include <module-db/Interface/AlarmEventRecord.hpp>

#include <gtest/gtest.h>
#include <gmock/gmock.h>

class MockAlarmHandler : public alarms::AlarmHandler
{
  public:
    MOCK_METHOD(bool, handle, (const AlarmEventRecord &record), ());
    MOCK_METHOD(bool, handleOff, (const AlarmEventRecord &record), ());
};

class MockAlarmEventsRepository : public alarms::AbstractAlarmEventsRepository
{
  public:
    std::vector<AlarmEventRecord> nextRecords;

    MOCK_METHOD(void,
                getAlarmEvent,
                (const std::uint32_t alarmId, const alarms::OnGetAlarmEventCallback &callback),
                ());
    MOCK_METHOD(void,
                getAlarmEvents,
                (std::uint32_t offset, std::uint32_t limit, const alarms::OnGetAlarmEventsCallback &callback),
                ());
    MOCK_METHOD(void, toggleAll, (const bool toggle, const alarms::OnToggleAll &callback), ());

    auto addAlarmEvent(const AlarmEventRecord &alarmEvent, const alarms::OnAddAlarmEventCallback &callback) -> void
    {
        addSingleEvent(alarmEvent);
        callback({true});
    }

    auto updateAlarmEvent(const AlarmEventRecord &alarmEvent, const alarms::OnAddAlarmEventCallback &callback) -> void
    {
        nextRecords.erase(std::remove_if(nextRecords.begin(),
                                         nextRecords.end(),
                                         [&alarmEvent](const AlarmEventRecord &ae) { return ae.ID == alarmEvent.ID; }),
                          nextRecords.end());
        addSingleEvent(alarmEvent);
        callback({true});
    }
    auto removeAlarmEvent(const std::uint32_t alarmId, const alarms::OnRemoveAlarmEventCallback &callback) -> void
    {
        nextRecords.erase(std::remove_if(nextRecords.begin(),
                                         nextRecords.end(),
                                         [&alarmId](const AlarmEventRecord &ae) { return ae.ID == alarmId; }),
                          nextRecords.end());
        callback({true});
    }

    void getAlarmEventsInRange(std::uint32_t offset,
                               std::uint32_t limit,
                               const alarms::OnGetAlarmEventsInRangeCallback &callback)
    {
        callback({nextRecords, nextRecords.size()});
    }

    void getAlarmEnabledEvents(const alarms::OnGetAlarmEventsCallback &callback)
    {
        std::vector<AlarmEventRecord> result;
        for (const auto &rec : nextRecords) {
            if (rec.enabled) {
                result.push_back(rec);
            }
        }
        callback({result});
    }

    void addSingleEvent(const AlarmEventRecord record)
    {
        nextRecords.push_back(record);
    }
};

/// Time point from string with current test machine time shift
TimePoint TimePointFromStringWithShift(std::string timeString)
{
    const auto timeToCalc = TimePointFromString("2000-01-01 12:00:00");
    const auto fromTimeT  = std::chrono::system_clock::to_time_t(timeToCalc);
    const auto fromLocal  = std::localtime(&fromTimeT);
    fromLocal->tm_hour    = 12;
    fromLocal->tm_min     = 0;
    auto time             = TimePointFloorMinutes(std::chrono::system_clock::from_time_t(std::mktime(fromLocal)));
    auto currentTimeShift = timeToCalc - time;

    return TimePointFromString(timeString.c_str()) - currentTimeShift;
}

alarms::IAlarmOperations::GetCurrentTime timeInjector = []() {
    return TimePointFromStringWithShift("2022-11-11 05:00:00");
};
alarms::IAlarmOperations::OnUpdateAlarmProcessed universalBoolCallback = [](bool success) { EXPECT_EQ(success, true); };

class AlarmOperationsFixture : public ::testing::Test
#include "tests-AlarmOperations.hpp"
namespace
{
  protected:
    std::unique_ptr<alarms::IAlarmOperations> getMockedAlarmOperations(
        std::unique_ptr<MockAlarmEventsRepository> &alarmRepo);
};
    alarms::IAlarmOperations::OnUpdateAlarmProcessed universalBoolCallback = [](bool success) {
        EXPECT_EQ(success, true);
    };
    alarms::IAlarmOperations::GetCurrentTime timeInjector = []() {
        return TimePointFromStringWithShift("2022-11-11 05:00:00");
    };
} // namespace

#if defined COMMON_ALARM_OPERATIONS_TEST
std::unique_ptr<alarms::IAlarmOperations> AlarmOperationsFixture::getMockedAlarmOperations(
    std::unique_ptr<MockAlarmEventsRepository> &alarmRepo)
{
    return std::make_unique<alarms::AlarmOperationsCommon>(std::move(alarmRepo), timeInjector);
}
#endif

constexpr auto defRRule    = "";
constexpr auto defMusic    = "";
constexpr auto defEnabled  = true;
constexpr auto defSnooze   = 15;

TEST_F(AlarmOperationsFixture, getEnabledEvents)
{

A module-services/service-time/tests/tests-AlarmOperations.hpp => module-services/service-time/tests/tests-AlarmOperations.hpp +110 -0
@@ 0,0 1,110 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <service-time/AlarmHandler.hpp>
#include <module-db/Interface/AlarmEventRecord.hpp>
#include <AlarmOperations.hpp>
#include <AlarmRepository.hpp>

class MockAlarmHandler : public alarms::AlarmHandler
{
  public:
    MOCK_METHOD(bool, handle, (const AlarmEventRecord &record), ());
    MOCK_METHOD(bool, handleOff, (const AlarmEventRecord &record), ());
};

class MockAlarmEventsRepository : public alarms::AbstractAlarmEventsRepository
{
  public:
    std::vector<AlarmEventRecord> nextRecords;

    MOCK_METHOD(void,
                getAlarmEvent,
                (const std::uint32_t alarmId, const alarms::OnGetAlarmEventCallback &callback),
                ());
    MOCK_METHOD(void,
                getAlarmEvents,
                (std::uint32_t offset, std::uint32_t limit, const alarms::OnGetAlarmEventsCallback &callback),
                ());
    MOCK_METHOD(void, toggleAll, (const bool toggle, const alarms::OnToggleAll &callback), ());

    auto addAlarmEvent(const AlarmEventRecord &alarmEvent, const alarms::OnAddAlarmEventCallback &callback) -> void
    {
        addSingleEvent(alarmEvent);
        callback({true});
    }

    auto updateAlarmEvent(const AlarmEventRecord &alarmEvent, const alarms::OnAddAlarmEventCallback &callback) -> void
    {
        nextRecords.erase(std::remove_if(nextRecords.begin(),
                                         nextRecords.end(),
                                         [&alarmEvent](const AlarmEventRecord &ae) { return ae.ID == alarmEvent.ID; }),
                          nextRecords.end());
        addSingleEvent(alarmEvent);
        callback({true});
    }
    auto removeAlarmEvent(const std::uint32_t alarmId, const alarms::OnRemoveAlarmEventCallback &callback) -> void
    {
        nextRecords.erase(std::remove_if(nextRecords.begin(),
                                         nextRecords.end(),
                                         [&alarmId](const AlarmEventRecord &ae) { return ae.ID == alarmId; }),
                          nextRecords.end());
        callback({true});
    }

    void getAlarmEventsInRange(std::uint32_t offset,
                               std::uint32_t limit,
                               const alarms::OnGetAlarmEventsInRangeCallback &callback)
    {
        callback({nextRecords, nextRecords.size()});
    }

    void getAlarmEnabledEvents(const alarms::OnGetAlarmEventsCallback &callback)
    {
        std::vector<AlarmEventRecord> result;
        for (const auto &rec : nextRecords) {
            if (rec.enabled) {
                result.push_back(rec);
            }
        }
        callback({result});
    }

    void addSingleEvent(const AlarmEventRecord record)
    {
        nextRecords.push_back(record);
    }
};

namespace
{
    /// Time point from string with current test machine time shift
    TimePoint TimePointFromStringWithShift(std::string timeString)
    {
        const auto timeToCalc = TimePointFromString("2000-01-01 12:00:00");
        const auto fromTimeT  = std::chrono::system_clock::to_time_t(timeToCalc);
        const auto fromLocal  = std::localtime(&fromTimeT);
        fromLocal->tm_hour    = 12;
        fromLocal->tm_min     = 0;
        auto time             = TimePointFloorMinutes(std::chrono::system_clock::from_time_t(std::mktime(fromLocal)));
        auto currentTimeShift = timeToCalc - time;

        return TimePointFromString(timeString.c_str()) - currentTimeShift;
    }

} // namespace

class AlarmOperationsFixture : public ::testing::Test
{
  protected:
    virtual std::unique_ptr<alarms::IAlarmOperations> getMockedAlarmOperations(
        std::unique_ptr<MockAlarmEventsRepository> &alarmRepo);
};

constexpr auto defRRule   = "";
constexpr auto defMusic   = "";
constexpr auto defEnabled = true;
constexpr auto defSnooze  = 15;

M products/BellHybrid/alarms/BellAlarmHandler.cpp => products/BellHybrid/alarms/BellAlarmHandler.cpp +1 -1
@@ 17,7 17,7 @@ namespace alarms
        auto result{true};
        if (record.enabled) {
            for (const auto &action : actions) {
                result &= action->execute();
                result &= action->execute(record.ID);
            }
        }
        return result;

M products/BellHybrid/alarms/CMakeLists.txt => products/BellHybrid/alarms/CMakeLists.txt +0 -1
@@ 20,7 20,6 @@ target_sources(alarms
        include/AlarmSoundPaths.hpp
        include/popups/AlarmActivatedPopupRequestParams.hpp
        include/popups/AlarmDeactivatedPopupRequestParams.hpp
        include/popups/BedtimeReminderPopupRequestParams.hpp
)

target_include_directories(alarms

M products/BellHybrid/alarms/include/AbstractAlarmAction.hpp => products/BellHybrid/alarms/include/AbstractAlarmAction.hpp +7 -4
@@ 1,15 1,18 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <Tables/Record.hpp>
#include <cstdint>

namespace alarms
{
    class AbstractAlarmAction
    {
      public:
        virtual ~AbstractAlarmAction() = default;
        virtual bool execute()         = 0;
        virtual bool turnOff()         = 0;
        virtual ~AbstractAlarmAction()      = default;
        virtual bool execute(Record record) = 0;
        virtual bool turnOff()              = 0;
    };
} // namespace alarms

M products/BellHybrid/alarms/src/actions/FrontlightAction.cpp => products/BellHybrid/alarms/src/actions/FrontlightAction.cpp +8 -8
@@ 18,7 18,7 @@ namespace alarms
        {
          public:
            explicit ManualFrontlightAction(sys::Service &service);
            bool execute() override;
            bool execute(Record record) override;
            bool turnOff() override;

          private:


@@ 31,7 31,7 @@ namespace alarms
        {
          public:
            explicit LinearProgressFrontlightAction(sys::Service &service);
            bool execute() override;
            bool execute(Record record) override;
            bool turnOff() override;

          private:


@@ 60,7 60,7 @@ namespace alarms
                                                                    service::ServiceProxy{service.weak_from_this()}}
    {}

    bool FrontlightAction::execute()
    bool FrontlightAction::execute([[maybe_unused]] Record record)
    {
        std::string settingString;



@@ 84,7 84,7 @@ namespace alarms
        case SettingsDependency::None:
            break;
        }
        return pimpl->execute();
        return pimpl->execute(record);
    }

    bool FrontlightAction::turnOff()


@@ 109,7 109,7 @@ namespace alarms
    ManualFrontlightAction::ManualFrontlightAction(sys::Service &service) : service{service}
    {}

    bool ManualFrontlightAction::execute()
    bool ManualFrontlightAction::execute([[maybe_unused]] Record record)
    {
        auto params = prepareParameters();
        service.bus.sendUnicast(std::make_shared<sevm::ScreenLightControlMessage>(


@@ 136,7 136,7 @@ namespace alarms
        settings.init(service::ServiceProxy{service.weak_from_this()});
    }

    bool LinearProgressFrontlightAction::execute()
    bool LinearProgressFrontlightAction::execute([[maybe_unused]] Record record)
    {
        const auto params = prepareParameters();
        service.bus.sendUnicast(std::make_shared<sevm::ScreenLightSetAutoProgressiveModeParams>(params),


@@ 155,9 155,9 @@ namespace alarms
        return screen_light_control::LinearProgressModeParameters{
            .startBrightnessValue = 0.0f,
            .functions            = {screen_light_control::functions::LinearProgressFunction{.target   = 10.0f,
                                                                                  .duration = firstTargetDuration},
                                                                                             .duration = firstTargetDuration},
                          screen_light_control::functions::LinearProgressFunction{.target   = 100.0f,
                                                                                  .duration = secondTargetDuration}},
                                                                                             .duration = secondTargetDuration}},
            .brightnessHysteresis = 0.0f};
    }


M products/BellHybrid/alarms/src/actions/FrontlightAction.hpp => products/BellHybrid/alarms/src/actions/FrontlightAction.hpp +1 -1
@@ 29,7 29,7 @@ namespace alarms
        FrontlightAction(sys::Service &service,
                         Mode mode,
                         SettingsDependency settingsDependency = SettingsDependency::None);
        bool execute() override;
        bool execute(Record record) override;
        bool turnOff() override;

      private:

M products/BellHybrid/alarms/src/actions/NotifyGUIAction.cpp => products/BellHybrid/alarms/src/actions/NotifyGUIAction.cpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotifyGUIAction.hpp"


@@ 10,7 10,7 @@ namespace alarms
{
    NotifyGUIAction::NotifyGUIAction(sys::Service &service) : service{service}
    {}
    bool NotifyGUIAction::execute()
    bool NotifyGUIAction::execute([[maybe_unused]] Record record)
    {
        return service.bus.sendUnicast(std::make_shared<app::actions::AlarmTriggeredAction>(), service::name::appmgr);
    }

M products/BellHybrid/alarms/src/actions/NotifyGUIAction.hpp => products/BellHybrid/alarms/src/actions/NotifyGUIAction.hpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 13,7 13,7 @@ namespace alarms
    {
      public:
        explicit NotifyGUIAction(sys::Service &service);
        bool execute() override;
        bool execute(Record record) override;
        bool turnOff() override;

      private:

M products/BellHybrid/alarms/src/actions/NotifyGUIBedtimeReminderAction.cpp => products/BellHybrid/alarms/src/actions/NotifyGUIBedtimeReminderAction.cpp +5 -3
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotifyGUIBedtimeReminderAction.hpp"


@@ 10,10 10,12 @@ namespace alarms
{
    NotifyGUIBedtimeReminderAction::NotifyGUIBedtimeReminderAction(sys::Service &service) : service{service}
    {}
    bool NotifyGUIBedtimeReminderAction::execute()

    bool NotifyGUIBedtimeReminderAction::execute(Record record)
    {
        return service.bus.sendUnicast(std::make_shared<BedtimeNotification>(), service::name::appmgr);
        return service.bus.sendUnicast(std::make_shared<BedtimeNotification>(record), service::name::appmgr);
    }

    bool NotifyGUIBedtimeReminderAction::turnOff()
    {
        return true;

M products/BellHybrid/alarms/src/actions/NotifyGUIBedtimeReminderAction.hpp => products/BellHybrid/alarms/src/actions/NotifyGUIBedtimeReminderAction.hpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 13,7 13,7 @@ namespace alarms
    {
      public:
        explicit NotifyGUIBedtimeReminderAction(sys::Service &service);
        bool execute() override;
        bool execute(Record record) override;
        bool turnOff() override;

      private:

M products/BellHybrid/alarms/src/actions/PlayAudioActions.cpp => products/BellHybrid/alarms/src/actions/PlayAudioActions.cpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "AlarmSoundPaths.hpp"


@@ 35,7 35,7 @@ namespace alarms
        const auto msg       = std::make_shared<service::AudioStopRequest>(stopPlaybackVec);
        return service.bus.sendUnicast(msg, service::audioServiceName);
    }
    bool PlayAudioAction::execute()
    bool PlayAudioAction::execute([[maybe_unused]] Record record)
    {
        const auto tone = settings.getValue(toneSetting, settings::SettingsScope::Global);
        std::optional<std::chrono::minutes> duration{};

M products/BellHybrid/alarms/src/actions/PlayAudioActions.hpp => products/BellHybrid/alarms/src/actions/PlayAudioActions.hpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 19,7 19,7 @@ namespace alarms
    {
      public:
        bool turnOff() override;
        bool execute() override;
        bool execute(Record record) override;
        explicit PlayAudioAction(sys::Service &service,
                                 const std::filesystem::path &tonesDirPath,
                                 std::string_view toneSetting,

M products/BellHybrid/apps/application-bell-bedtime/presenter/BellBedtimeWindowPresenter.hpp => products/BellHybrid/apps/application-bell-bedtime/presenter/BellBedtimeWindowPresenter.hpp +2 -2
@@ 30,8 30,8 @@ namespace app::bell_bedtime
    class View
    {
      public:
        virtual ~View() noexcept                  = default;
        virtual void exit()                       = 0;
        virtual ~View() noexcept = default;
        virtual void exit()      = 0;
    };

    class AbstractBedtimePresenter : public BasePresenter<View>

M products/BellHybrid/apps/common/CMakeLists.txt => products/BellHybrid/apps/common/CMakeLists.txt +1 -0
@@ 128,6 128,7 @@ target_link_libraries(application-bell-common
        module-gui
        bell::db
        Microsoft.GSL::GSL
        bell::alarms
        )

if (${ENABLE_TESTS})

M products/BellHybrid/apps/common/include/common/data/BatteryUtils.hpp => products/BellHybrid/apps/common/include/common/data/BatteryUtils.hpp +1 -0
@@ 7,6 7,7 @@
#include <algorithm>
#include <optional>
#include <string>
#include <array>

#include <Units.hpp>


M products/BellHybrid/apps/common/include/common/models/BedtimeModel.hpp => products/BellHybrid/apps/common/include/common/models/BedtimeModel.hpp +1 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

M products/BellHybrid/apps/common/include/common/popups/BedtimeNotificationWindow.hpp => products/BellHybrid/apps/common/include/common/popups/BedtimeNotificationWindow.hpp +4 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 23,11 23,14 @@ namespace gui

        app::ApplicationCommon *app;
        Icon *icon = nullptr;
        Record currentEventRecord;
        bool onInput(const InputEvent &inputEvent) override;
        void returnToPreviousWindow();
        void buildInterface() override;

      public:
        bool handleSwitchData(gui::SwitchData *data) override;

        explicit BedtimeNotificationWindow(app::ApplicationCommon *app);
        void onBeforeShow(ShowMode mode, SwitchData *data) override;
    };

M products/BellHybrid/apps/common/src/popups/BedtimeNotificationWindow.cpp => products/BellHybrid/apps/common/src/popups/BedtimeNotificationWindow.cpp +23 -1
@@ 1,15 1,17 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <audio/AudioMessage.hpp>
#include <apps-common/popups/Popups.hpp>
#include <apps-common/popups/data/PopupRequestParams.hpp>
#include <popups/data/BedtimeReminderPopupRequestParams.hpp>
#include <common/popups/BedtimeNotificationWindow.hpp>
#include <gui/input/InputEvent.hpp>
#include <gui/widgets/Icon.hpp>
#include <i18n/i18n.hpp>
#include <purefs/filesystem_paths.hpp>
#include <service-appmgr/Controller.hpp>
#include <service-time/AlarmServiceAPI.hpp>

namespace gui
{


@@ 42,6 44,25 @@ namespace gui
        icon->image->setMargins(Margins(0, icon::image_top_margin, 0, icon::image_bottom_margin));
        icon->text->setFont(style::window::font::verybiglight);
    }

    bool BedtimeNotificationWindow::handleSwitchData(SwitchData *data)
    {
        if (data == nullptr) {
            LOG_ERROR("Received null pointer");
            return false;
        }

        auto *eventData = dynamic_cast<gui::BedtimeReminderPopupRequestParams *>(data);
        if (eventData == nullptr) {
            LOG_ERROR("eventData is null pointer");
            return false;
        }

        currentEventRecord = eventData->eventRecord;

        return true;
    }

    void BedtimeNotificationWindow::onBeforeShow(ShowMode mode, [[maybe_unused]] SwitchData *data)
    {
        WindowWithTimer::onBeforeShow(mode, data);


@@ 55,6 76,7 @@ namespace gui

    void BedtimeNotificationWindow::returnToPreviousWindow()
    {
        alarms::AlarmServiceAPI::requestTurnOffRingingAlarm(app, currentEventRecord.ID);
        detachTimerIfExists();
        app::manager::Controller::sendAction(
            application,

M products/BellHybrid/services/appmgr/include/appmgr/messages/AlarmMessage.hpp => products/BellHybrid/services/appmgr/include/appmgr/messages/AlarmMessage.hpp +8 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 6,7 6,7 @@
#include <Service/Message.hpp>
#include <popups/AlarmActivatedPopupRequestParams.hpp>
#include <popups/AlarmDeactivatedPopupRequestParams.hpp>
#include <popups/BedtimeReminderPopupRequestParams.hpp>
#include <popups/data/BedtimeReminderPopupRequestParams.hpp>
#include <service-appmgr/Actions.hpp>
#include <service-appmgr/messages/ActionRequest.hpp>



@@ 39,12 39,16 @@ class AlarmDeactivated : public sys::DataMessage, public app::manager::actions::
class BedtimeNotification : public sys::DataMessage, public app::manager::actions::ConvertibleToAction
{
  public:
    BedtimeNotification() : sys::DataMessage{MessageType::MessageTypeUninitialized}
    BedtimeNotification(Record record) : sys::DataMessage{MessageType::MessageTypeUninitialized}, eventRecord{record}
    {}

    [[nodiscard]] auto toAction() const -> std::unique_ptr<app::manager::ActionRequest> override
    {
        return std::make_unique<app::manager::ActionRequest>(
            sender, app::manager::actions::ShowPopup, std::make_unique<gui::BedtimeReminderPopupRequestParams>());
            sender,
            app::manager::actions::ShowPopup,
            std::make_unique<gui::BedtimeReminderPopupRequestParams>(eventRecord));
    }

    Record eventRecord;
};

M products/BellHybrid/services/time/AlarmOperations.cpp => products/BellHybrid/services/time/AlarmOperations.cpp +52 -2
@@ 152,6 152,18 @@ namespace alarms
          bedtime(std::move(bedtimeSettingsProvider))
    {}

    bool AlarmOperations::turnOffAlarmIfFoundInBedtime(const std::uint32_t id)
    {
        if (not bedtime.isEventInContainer(id)) {
            return false;
        }

        switchAlarmExecution(*(*bedtime.findEvent(id)), false);
        bedtime.removeAllEvents();

        return true;
    }

    void AlarmOperations::minuteUpdated(TimePoint now)
    {
        /**


@@ 201,8 213,10 @@ namespace alarms
    void AlarmOperations::processBedtime(TimePoint now)
    {
        if (bedtime.decide(now)) {
            auto bedtimeEvent           = std::make_shared<AlarmEventRecord>();
            bedtimeEvent.get()->enabled = true;

            auto bedtimeEvent = bedtime.createEvent();
            bedtime.pushEvent(bedtimeEvent);

            handleAlarmEvent(bedtimeEvent, alarms::AlarmType::BedtimeReminder, true);
        }
    }


@@ 282,7 296,9 @@ namespace alarms

        handleAlarmEvent(event, alarms::AlarmType::PreWakeUpChime, false);
        handleAlarmEvent(event, alarms::AlarmType::PreWakeUpFrontlight, false);
        handleAlarmEvent(event, alarms::AlarmType::BedtimeReminder, false);
    }

    bool AlarmOperations::isBedtimeAllowed() const
    {
        return ongoingSingleEvents.empty() && snoozedSingleEvents.empty() && not preWakeUp.isActive();


@@ 336,6 352,40 @@ namespace alarms
        return activated && isTimeForBed(now, time);
    }

    auto Bedtime::createEvent() -> std::shared_ptr<AlarmEventRecord>
    {
        auto bedtimeEvent     = std::make_shared<AlarmEventRecord>();
        bedtimeEvent->enabled = true;
        bedtimeEvent->ID      = nextFreeId;
        nextFreeId++;

        return bedtimeEvent;
    }

    void Bedtime::pushEvent(const std::shared_ptr<AlarmEventRecord> &bedtimeEvent)
    {
        auto bedtimeSingleEvent    = std::make_unique<SingleEventRecord>();
        bedtimeSingleEvent->parent = bedtimeEvent;

        bedtimeSingleEvents.push_back(std::move(bedtimeSingleEvent));
    }

    bool Bedtime::isEventInContainer(std::uint32_t id)
    {
        return findEvent(id) == bedtimeSingleEvents.end() ? false : true;
    }

    EventsContainer<SingleEventRecord>::iterator Bedtime::findEvent(uint32_t id)
    {
        return findSingleEventById(bedtimeSingleEvents, id);
    }

    void Bedtime::removeAllEvents()
    {
        bedtimeSingleEvents.clear();
        nextFreeId = resonableIdOffset;
    }

    auto Bedtime::isTimeForBed(const TimePoint &now, const time_t &bedtime) -> bool
    {
        auto time_tNow    = TimePointToTimeT(now);

M products/BellHybrid/services/time/include/time/AlarmOperations.hpp => products/BellHybrid/services/time/include/time/AlarmOperations.hpp +10 -0
@@ 80,10 80,18 @@ namespace alarms
      public:
        explicit Bedtime(std::unique_ptr<AbstractBedtimeSettingsProvider> &&settingsProvider);
        auto decide(TimePoint now) -> bool;
        auto createEvent() -> std::shared_ptr<AlarmEventRecord>;
        void pushEvent(const std::shared_ptr<AlarmEventRecord> &bedtimeEvent);
        bool isEventInContainer(std::uint32_t id);
        EventsContainer<SingleEventRecord>::iterator findEvent(std::uint32_t id);
        void removeAllEvents();

      private:
        auto isTimeForBed(const TimePoint &now, const time_t &bedtime) -> bool;
        static constexpr auto resonableIdOffset = 1000;
        uint32_t nextFreeId{resonableIdOffset};

        EventsContainer<SingleEventRecord> bedtimeSingleEvents;
        const std::unique_ptr<AbstractBedtimeSettingsProvider> settingsProvider;
    };



@@ 96,6 104,8 @@ namespace alarms
                        std::unique_ptr<SnoozeChimeSettingsProvider> &&snoozeChimeSettingsProvider,
                        std::unique_ptr<AbstractBedtimeSettingsProvider> &&BedtimeModel);

        bool turnOffAlarmIfFoundInBedtime(const std::uint32_t id) override;

      private:
        void minuteUpdated(TimePoint now) override;
        void stopAllSnoozedAlarms() override;

M products/BellHybrid/services/time/tests/CMakeLists.txt => products/BellHybrid/services/time/tests/CMakeLists.txt +1 -0
@@ 4,6 4,7 @@ add_gtest_executable(
    SRCS
        test-BellAlarmOperations.cpp
    LIBS
        service-time-alarm-operations-source
        module-sys
        bell::time
    INCLUDE

M products/BellHybrid/services/time/tests/test-BellAlarmOperations.cpp => products/BellHybrid/services/time/tests/test-BellAlarmOperations.cpp +23 -18
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <AlarmOperations.hpp>


@@ 8,15 8,16 @@
#include <products/BellHybrid/services/time/include/time/AlarmOperations.hpp>

#include <gtest/gtest.h>

// Included a .cpp file with AlarmOperations tests for the common part (the base class' implementation).
// It needs to be built into this test in order to test the possible regression introduced by
// the Bell-specific AlarmOperations class.
// Generally, this should not be solved this way in other cases.
#include <service-time/tests/tests-AlarmOperations.cpp>

#include "tests/tests-AlarmOperations.hpp"
namespace
{
    alarms::IAlarmOperations::OnUpdateAlarmProcessed universalBoolCallback = [](bool success) {
        EXPECT_EQ(success, true);
    };
    alarms::IAlarmOperations::GetCurrentTime timeInjector = []() {
        return TimePointFromStringWithShift("2022-11-11 05:00:00");
    };

    class MockedPreWakeUpSettingsProvider : public alarms::PreWakeUpSettingsProvider
    {
      public:


@@ 95,17 96,21 @@ namespace
                                                         std::move(snoozeChimeSettingsProvider),
                                                         std::move(bedtimeSettingsProvider));
    }
} // namespace

// This function replaces the `getMockedAlarmOperations` function from `service-time/tests/tests-AlarmOperations.cpp`
std::unique_ptr<alarms::IAlarmOperations> AlarmOperationsFixture::getMockedAlarmOperations(
    std::unique_ptr<MockAlarmEventsRepository> &alarmRepo)
{
    return ::getMockedAlarmOperations(alarmRepo,
                                      std::make_unique<MockedPreWakeUpSettingsProvider>(),
                                      std::make_unique<MockedSnoozeChimeSettingsProvider>(),
                                      std::make_unique<MockedBedtimeModel>());
}
    class BellSpecificAlarmOperationsFixture : public AlarmOperationsFixture
    {
      protected:
        std::unique_ptr<alarms::IAlarmOperations> getMockedAlarmOperations(
            std::unique_ptr<MockAlarmEventsRepository> &alarmRepo) override
        {
            return ::getMockedAlarmOperations(alarmRepo,
                                              std::make_unique<MockedPreWakeUpSettingsProvider>(),
                                              std::make_unique<MockedSnoozeChimeSettingsProvider>(),
                                              std::make_unique<MockedBedtimeModel>());
        }
    };

} // namespace

TEST(PreWakeUp, TooEarlyForPreWakeUp)
{

M test/CMakeLists.txt => test/CMakeLists.txt +49 -1
@@ 94,7 94,7 @@ function(add_gtest_executable)
    set(_TESTNAME "googletest-${_TEST_ARGS_NAME}")

    if(NOT _TEST_ARGS_SRCS)
	message(FATAL_ERROR "You must provide test sources for ${_TESTNAME}")
        message(FATAL_ERROR "You must provide test sources for ${_TESTNAME}")
    endif(NOT _TEST_ARGS_SRCS)

    get_directory_property(_TEST_ENTITY TEST_ENTITY)


@@ 244,4 244,52 @@ function(add_test_entity)
    add_dependencies(check-${_ARGS_NAME} unittests-${_ARGS_NAME})
endfunction()

function(add_gtest_manually_prepared_target)
    cmake_parse_arguments(
        _TEST_ARGS
        ""
        "NAME"
        ""
        ${ARGN}
    )

    if(NOT _TEST_ARGS_NAME)
        message(FATAL_ERROR "You must provide a test name")
    endif(NOT _TEST_ARGS_NAME)

    target_compile_options(${_TEST_ARGS_NAME} PUBLIC "-fsanitize=address")
    target_link_options(${_TEST_ARGS_NAME} PUBLIC "-fsanitize=address")

    # disable logs in unit tests
    if (NOT ${ENABLE_TEST_LOGS})
        target_sources(${_TEST_ARGS_NAME} PRIVATE ${ROOT_TEST_DIR}/mock-logs.cpp)
        target_sources(${_TEST_ARGS_NAME} PRIVATE ${ROOT_TEST_DIR}/mock-freertos-tls.cpp)
    endif (NOT ${ENABLE_TEST_LOGS})

    set(_TEST_LABELS "")
    if(_TEST_ARGS_USE_FS)
        enable_test_filesystem()
    endif()

    target_compile_options(${_TEST_ARGS_NAME} PUBLIC "-pthread")
    target_link_options(${_TEST_ARGS_NAME} PUBLIC "-pthread")

    add_dependencies(unittests ${_TEST_ARGS_NAME})
    add_dependencies(check ${_TEST_ARGS_NAME})

    get_directory_property(_TEST_ENTITY TEST_ENTITY)
    if(_TEST_ENTITY)
        add_dependencies(unittests-${_TEST_ENTITY} ${_TEST_ARGS_NAME})
        list(APPEND _TEST_LABELS ${_TEST_ENTITY})
    endif()

    gtest_add_tests(
        TARGET ${_TEST_ARGS_NAME}
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
        TEST_LIST _TEST_LIST
    )

    set_tests_properties(${_TEST_LIST} PROPERTIES LABELS ${_TEST_LABELS})
endfunction()

set(_CATCH_DISCOVER_TESTS_SCRIPT ${_CATCH_DISCOVER_TESTS_SCRIPT} PARENT_SCOPE)