diff --git a/include/ECFMP/ECFMP.h b/include/ECFMP/ECFMP.h index 1a977b9..0f23aee 100644 --- a/include/ECFMP/ECFMP.h +++ b/include/ECFMP/ECFMP.h @@ -9,6 +9,7 @@ #include "ECFMP/eventbus/EventListener.h" #include "ECFMP/flightinformationregion/FlightInformationRegion.h" #include "ECFMP/flowmeasure/AirportFilter.h" +#include "ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h" #include "ECFMP/flowmeasure/EventFilter.h" #include "ECFMP/flowmeasure/FlowMeasure.h" #include "ECFMP/flowmeasure/FlowMeasureFilters.h" diff --git a/include/ECFMP/Sdk.h b/include/ECFMP/Sdk.h index 0e35575..fc73df6 100644 --- a/include/ECFMP/Sdk.h +++ b/include/ECFMP/Sdk.h @@ -1,4 +1,5 @@ #pragma once +#include "api/ElementCollectionTypes.h" namespace ECFMP { namespace EventBus { @@ -19,6 +20,23 @@ namespace ECFMP::Plugin { public: virtual ~Sdk() = default; + /** + * All the flight information regions that are currently loaded. + */ + [[nodiscard]] virtual auto FlightInformationRegions() const noexcept + -> std::shared_ptr = 0; + + /** + * All the events that are currently loaded. + */ + [[nodiscard]] virtual auto Events() const noexcept -> std::shared_ptr = 0; + + /** + * All the flow measures that are currently loaded. + */ + [[nodiscard]] virtual auto FlowMeasures() const noexcept + -> std::shared_ptr = 0; + /** * The event bus for the SDK, which can be used to subscribe to events. */ diff --git a/include/ECFMP/SdkEvents.h b/include/ECFMP/SdkEvents.h index f222300..c801068 100644 --- a/include/ECFMP/SdkEvents.h +++ b/include/ECFMP/SdkEvents.h @@ -1,28 +1,40 @@ #pragma once +#include "ECFMP/api/ElementCollectionTypes.h" namespace ECFMP::FlowMeasure { class FlowMeasure; }// namespace ECFMP::FlowMeasure namespace ECFMP::Plugin { + + // Events + using EventsUpdatedEvent = struct EventsUpdatedEvent { + std::shared_ptr events; + }; + + // Flow measures + using FlowMeasuresUpdatedEvent = struct FlowMeasureUpdatedEvent { + std::shared_ptr flowMeasures; + }; + using FlowMeasureNotifiedEvent = struct FlowMeasureNotifiedEvent { - const FlowMeasure::FlowMeasure& measure; + std::shared_ptr flowMeasure; }; using FlowMeasureActivatedEvent = struct FlowMeasureActivatedEvent { - const FlowMeasure::FlowMeasure& measure; + std::shared_ptr flowMeasure; }; using FlowMeasureExpiredEvent = struct FlowMeasureExpiredEvent { - const FlowMeasure::FlowMeasure& measure; + std::shared_ptr flowMeasure; }; using FlowMeasureWithdrawnEvent = struct FlowMeasureWithdrawnEvent { - const FlowMeasure::FlowMeasure& measure; + std::shared_ptr flowMeasure; }; using FlowMeasureReissuedEvent = struct FlowMeasureReissuedEvent { - const FlowMeasure::FlowMeasure& original; - const FlowMeasure::FlowMeasure& reissued; + std::shared_ptr original; + std::shared_ptr reissued; }; }// namespace ECFMP::Plugin diff --git a/include/ECFMP/SdkFactory.h b/include/ECFMP/SdkFactory.h index 7c753a1..88085c4 100644 --- a/include/ECFMP/SdkFactory.h +++ b/include/ECFMP/SdkFactory.h @@ -55,7 +55,7 @@ namespace ECFMP::Plugin { * * Throws an SdkConfigurationException if the configuration is bad. */ - [[nodiscard]] auto Instance() -> std::unique_ptr; + [[nodiscard]] auto Instance() -> std::shared_ptr; private: SdkFactory(); diff --git a/include/ECFMP/eventbus/EventBus.h b/include/ECFMP/eventbus/EventBus.h index fdd03d4..9107eff 100644 --- a/include/ECFMP/eventbus/EventBus.h +++ b/include/ECFMP/eventbus/EventBus.h @@ -1,5 +1,7 @@ #pragma once -#include "eventbus/InternalEventStream.h" +#include "../../src/eventbus/EventDispatcherFactory.h" +#include "../../src/eventbus/EventStream.h" +#include "../../src/eventbus/SubscriptionFlags.h" #include #include #include @@ -10,6 +12,12 @@ namespace ECFMP::EventBus { class EventBus { public: + explicit EventBus(const std::shared_ptr& dispatcherFactory) + : dispatcherFactory(dispatcherFactory) + { + assert(dispatcherFactory != nullptr && "Dispatcher factory cannot be null"); + } + virtual ~EventBus() = default; /** * Subscribes the given listener to the event stream. @@ -24,7 +32,15 @@ namespace ECFMP::EventBus { void Subscribe(std::shared_ptr> listener, std::shared_ptr> filter) { - GetStream().Subscribe(listener, filter); + if (listener == nullptr) { + throw std::invalid_argument("Listener cannot be null"); + } + + Subscribe(EventSubscription{ + dispatcherFactory->CreateDispatcher(listener, EventDispatchMode::Euroscope), + listener, + filter, + {EventDispatchMode::Euroscope, false}}); }; /** @@ -41,7 +57,15 @@ namespace ECFMP::EventBus { std::shared_ptr> listener, std::shared_ptr> filter ) { - GetStream().SubscribeOnce(listener, filter); + if (listener == nullptr) { + throw std::invalid_argument("Listener cannot be null"); + } + + Subscribe(EventSubscription{ + dispatcherFactory->CreateDispatcher(listener, EventDispatchMode::Euroscope), + listener, + filter, + {EventDispatchMode::Euroscope, true}}); }; /** @@ -55,18 +79,28 @@ namespace ECFMP::EventBus { return GetStream().template HasListenerOfType(); } + protected: + template + void Subscribe(const EventSubscription& subscription) + { + GetStream().Subscribe(subscription); + } + + // Factory for dispatchers + std::shared_ptr dispatcherFactory; + private: template - auto GetStream() -> InternalEventStream& + auto GetStream() -> EventStream& { auto lock = std::lock_guard(mutex); const auto index = std::type_index(typeid(EventType)); if (!streams.contains(index)) { - streams.insert({index, std::any(std::make_shared>())}); + streams.insert({index, std::any(std::make_shared>())}); } - return *std::any_cast>>(streams.at(index)); + return *std::any_cast>>(streams.at(index)); } // Protects the streams map diff --git a/include/ECFMP/eventbus/EventFilter.h b/include/ECFMP/eventbus/EventFilter.h index 8dc7e33..e420675 100644 --- a/include/ECFMP/eventbus/EventFilter.h +++ b/include/ECFMP/eventbus/EventFilter.h @@ -7,6 +7,6 @@ namespace ECFMP::EventBus { { public: virtual ~EventFilter() = default; - virtual bool ShouldProcess(const EventType&) = 0; + virtual bool ShouldProcess(const EventType& event) = 0; }; }// namespace ECFMP::EventBus diff --git a/include/ECFMP/eventbus/EventListener.h b/include/ECFMP/eventbus/EventListener.h index 8db814d..9e86b75 100644 --- a/include/ECFMP/eventbus/EventListener.h +++ b/include/ECFMP/eventbus/EventListener.h @@ -6,6 +6,6 @@ namespace ECFMP::EventBus { { public: virtual ~EventListener() = default; - virtual void OnEvent(const EventType&) = 0; + virtual void OnEvent(const EventType& event) = 0; }; }// namespace ECFMP::EventBus diff --git a/include/ECFMP/eventbus/EventStream.h b/include/ECFMP/eventbus/EventStream.h deleted file mode 100644 index cdbeb30..0000000 --- a/include/ECFMP/eventbus/EventStream.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once -#include -#include -#include - -namespace ECFMP::EventBus { - - template - class EventListener; - template - class EventFilter; - - template - struct EventSubscription { - std::shared_ptr> listener; - std::shared_ptr> filter; - bool once; - }; - - template - class EventStream - { - public: - virtual ~EventStream() = default; - - /** - * Subscribes the given listener to the event stream. - */ - void Subscribe(std::shared_ptr> listener) - { - Subscribe(listener, nullptr); - }; - - virtual void - Subscribe(std::shared_ptr> listener, std::shared_ptr> filter) - { - if (listener == nullptr) { - throw std::invalid_argument("listener cannot be null"); - } - - auto guard = std::lock_guard(mutex); - subscriptions.push_back(EventSubscription{listener, filter, false}); - }; - - /** - * Subscribes the given listener to the event stream, but only for the next event. - */ - virtual void SubscribeOnce(std::shared_ptr> listener) - { - SubscribeOnce(listener, nullptr); - } - - virtual void SubscribeOnce( - std::shared_ptr> listener, std::shared_ptr> filter - ) - { - if (listener == nullptr) { - throw std::invalid_argument("listener cannot be null"); - } - - auto guard = std::lock_guard(mutex); - subscriptions.push_back(EventSubscription{listener, filter, true}); - }; - - protected: - std::mutex mutex; - - // All subscriptions to this event stream. - std::vector> subscriptions; - }; -}// namespace ECFMP::EventBus diff --git a/include/ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h b/include/ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h new file mode 100644 index 0000000..3bba377 --- /dev/null +++ b/include/ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h @@ -0,0 +1,27 @@ +#pragma once + +namespace ECFMP::FlowMeasure { + + /** + * Contains information about a canonical flow measure. + */ + class CanonicalFlowMeasureInfo + { + public: + CanonicalFlowMeasureInfo(const std::string& identifier); + [[nodiscard]] auto Identifier() const noexcept -> const std::string&; + [[nodiscard]] auto Revision() const noexcept -> int; + [[nodiscard]] auto IsAfter(const CanonicalFlowMeasureInfo& other) const noexcept -> bool; + + private: + [[nodiscard]] static auto ParseIdentifier(const std::string& identifier) -> std::string; + [[nodiscard]] static auto ParseRevision(const std::string& identifier) -> int; + + // The identifier of the canonical flow measure + std::string identifier; + + // The revision of the canonical flow measure + int revision; + }; + +}// namespace ECFMP::FlowMeasure diff --git a/include/ECFMP/flowmeasure/FlowMeasure.h b/include/ECFMP/flowmeasure/FlowMeasure.h index 021bb3d..b7f041e 100644 --- a/include/ECFMP/flowmeasure/FlowMeasure.h +++ b/include/ECFMP/flowmeasure/FlowMeasure.h @@ -13,6 +13,7 @@ namespace ECFMP { namespace ECFMP::FlowMeasure { class FlowMeasureFilters; class Measure; + class CanonicalFlowMeasureInfo; /** * Represents the status of the flow measure @@ -111,5 +112,8 @@ namespace ECFMP::FlowMeasure { * Returns a collection of filters that apply to the flow measure. */ [[nodiscard]] virtual auto Filters() const noexcept -> const FlowMeasureFilters& = 0; + + // Information about the canonical nature of the flow measure + [[nodiscard]] virtual auto CanonicalInformation() const noexcept -> const CanonicalFlowMeasureInfo& = 0; }; }// namespace ECFMP::FlowMeasure diff --git a/include/mock/FlowMeasureMock.h b/include/mock/FlowMeasureMock.h index 6b64780..9a251a8 100644 --- a/include/mock/FlowMeasureMock.h +++ b/include/mock/FlowMeasureMock.h @@ -11,6 +11,10 @@ namespace ECFMP::Mock::FlowMeasure { MOCK_METHOD(std::shared_ptr, Event, (), (const, noexcept, override)); MOCK_METHOD(const std::string&, Identifier, (), (const, noexcept, override)); MOCK_METHOD(const std::string&, Reason, (), (const, noexcept, override)); + MOCK_METHOD( + const ECFMP::FlowMeasure::CanonicalFlowMeasureInfo&, CanonicalInformation, (), + (const, noexcept, override) + ); MOCK_METHOD(const std::chrono::system_clock::time_point&, StartTime, (), (const, noexcept, override)); MOCK_METHOD(const std::chrono::system_clock::time_point&, EndTime, (), (const, noexcept, override)); MOCK_METHOD(const std::chrono::system_clock::time_point&, WithdrawnAt, (), (const, override)); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b2523e..2d7b6a1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ SET(PROJECT_NAME flow_plugin_sdk_src) set(CMAKE_CXX_STANDARD 20) -set(src_api api/ApiDataDownloader.cpp api/ApiDataDownloader.h api/ApiDataScheduler.cpp api/ApiDataScheduler.h api/FlightInformationRegionDataParser.cpp api/FlightInformationRegionDataParser.h api/InternalApiElementCollection.h api/InternalApiElementCollection.h api/ApiElementIterator.cpp api/InternalElementCollectionTypes.h api/EventDataParser.cpp api/EventDataParser.h api/FlowMeasureDataParser.cpp api/FlowMeasureDataParser.h api/FlowMeasureMeasureParserInterface.h api/FlowMeasureFilterParser.cpp api/FlowMeasureFilterParser.h api/ApiDataParser.cpp api/ApiDataParser.h api/ApiDataDownloadedEvent.h api/FlightInformationRegionDataParserInterface.h api/EventDataParserInterface.h api/FlowMeasureDataParserInterface.h ../include/ECFMP/api/StringIdentifierApiElementCollection.h api/InternalStringIdentifierApiElementCollection.h) +set(src_api api/ApiDataDownloader.cpp api/ApiDataDownloader.h api/ApiDataScheduler.cpp api/ApiDataScheduler.h api/FlightInformationRegionDataParser.cpp api/FlightInformationRegionDataParser.h api/InternalApiElementCollection.h api/InternalApiElementCollection.h api/ApiElementIterator.cpp api/InternalElementCollectionTypes.h api/EventDataParser.cpp api/EventDataParser.h api/FlowMeasureDataParser.cpp api/FlowMeasureDataParser.h api/FlowMeasureMeasureParserInterface.h api/FlowMeasureFilterParser.cpp api/FlowMeasureFilterParser.h api/ApiDataParser.cpp api/ApiDataParser.h api/ApiDataDownloadedEvent.h api/FlightInformationRegionDataParserInterface.h api/EventDataParserInterface.h api/FlowMeasureDataParserInterface.h ../include/ECFMP/api/StringIdentifierApiElementCollection.h api/InternalStringIdentifierApiElementCollection.h api/FlowMeasureMeasureParser.cpp api/FlowMeasureMeasureParser.h eventbus/InternalEventBusFactory.cpp) set(src_date date/ParseDateStrings.cpp date/ParseDateStrings.h) @@ -14,15 +14,15 @@ set(src_flight_information_regions set(src_events event/ConcreteEvent.cpp event/ConcreteEvent.h event/ConcreteEventParticipant.cpp "event/ConcreteEventParticipant.h" - event/Event.cpp ../include/ECFMP/eventbus/EventStream.h ../include/ECFMP/eventbus/EventListener.h ../include/ECFMP/eventbus/EventFilter.h) + event/Event.cpp eventbus/EventStream.h ../include/ECFMP/eventbus/EventListener.h ../include/ECFMP/eventbus/EventFilter.h) set(src_eventbus - ../include/ECFMP/eventbus/EventStream.h + eventbus/EventStream.h ../include/ECFMP/eventbus/EventListener.h - ../include/ECFMP/eventbus/EventFilter.h eventbus/InternalEventStream.h ../include/ECFMP/eventbus/EventBus.h eventbus/InternalEventBus.h) + ../include/ECFMP/eventbus/EventFilter.h ../include/ECFMP/eventbus/EventBus.h eventbus/InternalEventBus.h eventbus/EventDispatcher.h eventbus/SynchronousEventDispatcher.h eventbus/AsynchronousEventDispatcher.h eventbus/EuroscopeEventDispatcher.h eventbus/PendingEuroscopeEvents.cpp eventbus/PendingEuroscopeEvents.h eventbus/EventDispatcherFactory.h eventbus/SubscriptionFlags.h) set(src_flowmeasures - flowmeasure/ConcreteFlowMeasure.cpp flowmeasure/ConcreteFlowMeasure.h flowmeasure/ConcreteAirportFilter.cpp flowmeasure/ConcreteAirportFilter.h flowmeasure/ConcreteEventFilter.cpp flowmeasure/ConcreteEventFilter.h flowmeasure/ConcreteLevelRangeFilter.cpp flowmeasure/ConcreteLevelRangeFilter.h flowmeasure/ConcreteRouteFilter.cpp flowmeasure/ConcreteRouteFilter.h flowmeasure/ConcreteMeasure.cpp flowmeasure/ConcreteMeasure.h flowmeasure/ConcreteMeasureFactory.cpp flowmeasure/ConcreteMeasureFactory.h flowmeasure/ConcreteFlowMeasureFilters.cpp flowmeasure/ConcreteFlowMeasureFilters.h api/FlowMeasureFilterParserInterface.h ../include/ECFMP/flowmeasure/MultipleLevelFilter.h flowmeasure/ConcreteMultipleLevelFilter.cpp flowmeasure/ConcreteMultipleLevelFilter.h ../include/mock/MultipleLevelFilterMock.h ../include/ECFMP/flowmeasure/RangeToDestinationFilter.h flowmeasure/ConcreteRangeToDestinationFilter.cpp flowmeasure/ConcreteRangeToDestinationFilter.h) + flowmeasure/ConcreteFlowMeasure.cpp flowmeasure/ConcreteFlowMeasure.h flowmeasure/ConcreteAirportFilter.cpp flowmeasure/ConcreteAirportFilter.h flowmeasure/ConcreteEventFilter.cpp flowmeasure/ConcreteEventFilter.h flowmeasure/ConcreteLevelRangeFilter.cpp flowmeasure/ConcreteLevelRangeFilter.h flowmeasure/ConcreteRouteFilter.cpp flowmeasure/ConcreteRouteFilter.h flowmeasure/ConcreteMeasure.cpp flowmeasure/ConcreteMeasure.h flowmeasure/ConcreteMeasureFactory.cpp flowmeasure/ConcreteMeasureFactory.h flowmeasure/ConcreteFlowMeasureFilters.cpp flowmeasure/ConcreteFlowMeasureFilters.h api/FlowMeasureFilterParserInterface.h ../include/ECFMP/flowmeasure/MultipleLevelFilter.h flowmeasure/ConcreteMultipleLevelFilter.cpp flowmeasure/ConcreteMultipleLevelFilter.h ../include/mock/MultipleLevelFilterMock.h ../include/ECFMP/flowmeasure/RangeToDestinationFilter.h flowmeasure/ConcreteRangeToDestinationFilter.cpp flowmeasure/ConcreteRangeToDestinationFilter.h flowmeasure/CanonicalFlowMeasureInfo.cpp ../include/ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h flowmeasure/FlowMeasureStatusUpdates.cpp flowmeasure/FlowMeasureStatusUpdates.h) set(src_log log/LogDecorator.cpp log/LogDecorator.h log/LogDecorator.h log/NullLogger.cpp log/NullLogger.h) @@ -30,9 +30,11 @@ set(src_pch pch.h pch.cpp) set(src_plugin - plugin/ConcreteSdk.h - plugin/ConcreteSdk.cpp - plugin/SdkFactory.cpp ../include/ECFMP/SdkEvents.h) + plugin/InternalSdk.h + plugin/InternalSdk.cpp + plugin/SdkFactory.cpp ../include/ECFMP/SdkEvents.h plugin/InternalSdkEvents.h) + +set(src_time time/Clock.cpp time/Clock.h) set(ALL_FILES ${src_api} @@ -43,7 +45,8 @@ set(ALL_FILES ${src_flight_information_regions} ${src_log} ${src_pch} - ${src_plugin} api/FlowMeasureMeasureParser.cpp api/FlowMeasureMeasureParser.h) + ${src_plugin} + ${src_time}) add_library(${PROJECT_NAME} STATIC ${ALL_FILES}) set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32" JSON_MultipleHeaders "ON") diff --git a/src/api/ApiDataDownloader.cpp b/src/api/ApiDataDownloader.cpp index ec2ad77..110239d 100644 --- a/src/api/ApiDataDownloader.cpp +++ b/src/api/ApiDataDownloader.cpp @@ -21,7 +21,7 @@ namespace ECFMP::Api { } ApiDataDownloader::~ApiDataDownloader() = default; - void ApiDataDownloader::DownloadData() + void ApiDataDownloader::OnEvent(const Plugin::ApiDataDownloadRequiredEvent& event) { // Get the API response logger->Info("Downloading data"); diff --git a/src/api/ApiDataDownloader.h b/src/api/ApiDataDownloader.h index 09c6fa6..9dea978 100644 --- a/src/api/ApiDataDownloader.h +++ b/src/api/ApiDataDownloader.h @@ -1,6 +1,8 @@ #pragma once +#include "ECFMP/eventbus/EventListener.h" #include "eventbus/InternalEventBus.h" #include "nlohmann/json_fwd.hpp" +#include "plugin/InternalSdkEvents.h" namespace ECFMP { namespace EventBus { @@ -19,7 +21,7 @@ namespace ECFMP::Api { /** * Downloads API data and disseminates it to any listeners. */ - class ApiDataDownloader + class ApiDataDownloader : public EventBus::EventListener { public: explicit ApiDataDownloader( @@ -28,7 +30,7 @@ namespace ECFMP::Api { ); ~ApiDataDownloader(); - void DownloadData(); + void OnEvent(const Plugin::ApiDataDownloadRequiredEvent& event) override; private: // HTTP client for getting data diff --git a/src/api/ApiDataScheduler.cpp b/src/api/ApiDataScheduler.cpp index f5bc58e..ccf95c9 100644 --- a/src/api/ApiDataScheduler.cpp +++ b/src/api/ApiDataScheduler.cpp @@ -1,79 +1,37 @@ #include "ApiDataScheduler.h" -#include "ApiDataDownloader.h" -#include -#include -#include -#include +#include "time/Clock.h" namespace ECFMP::Api { struct ApiDataScheduler::Impl { - explicit Impl(std::unique_ptr downloader) - : downloader(std::move(downloader)), schedulerThread(std::make_unique(&Impl::Scheduler, this)) + explicit Impl(std::shared_ptr eventBus) : eventBus(std::move(eventBus)) {} - void Scheduler() - { - std::unique_lock lock(conditionMutex); - std::chrono::system_clock::time_point lastRuntime = std::chrono::system_clock::time_point::min(); - while (true) { - // Wait until the next runtime, or until we are interrupted - conditionVariable.wait_until(lock, lastRuntime + runInterval, [this]() { - return !running; - }); - - // Check we are still running - if (!running) { - break; - } - - // Do a download - downloader->DownloadData(); - lastRuntime = std::chrono::system_clock::now(); - } - } - - /** - * Notify the thread that we're done, and stop. - */ - void Stop() - { - std::unique_lock lock(conditionMutex); - running = false; - lock.unlock(); - - conditionVariable.notify_one(); - schedulerThread->join(); - } - // The interval for downloading const std::chrono::seconds runInterval = std::chrono::seconds(90); - // Are we running - bool running = true; + // The last time we ran + std::chrono::system_clock::time_point lastRuntime = std::chrono::system_clock::time_point::min(); - // Used for waiting the thread when required - std::condition_variable conditionVariable; - - // Used to grant access to the condition variable - std::mutex conditionMutex; - - // Does the scheduling - std::unique_ptr schedulerThread; - - // For downloading data. - std::unique_ptr downloader; + // For publishing events + std::shared_ptr eventBus; }; - ApiDataScheduler::ApiDataScheduler(std::unique_ptr downloader) - : impl(std::make_unique(std::move(downloader))) + ApiDataScheduler::ApiDataScheduler(std::shared_ptr eventBus) + : impl(std::make_unique(std::move(eventBus))) { - assert(impl->downloader && "Downloader not set in ApiDataScheduler"); + assert(impl->eventBus && "Event bus not set in ApiDataScheduler"); } - ApiDataScheduler::~ApiDataScheduler() + ApiDataScheduler::~ApiDataScheduler() = default; + + void ApiDataScheduler::OnEvent(const Plugin::EuroscopeTimerTickEvent& event) { - impl->Stop(); - }; + const auto now = Time::TimeNow(); + if (impl->lastRuntime + impl->runInterval < now) { + impl->lastRuntime = now; + impl->eventBus->OnEvent({}); + } + } }// namespace ECFMP::Api diff --git a/src/api/ApiDataScheduler.h b/src/api/ApiDataScheduler.h index 080b652..3eac3be 100644 --- a/src/api/ApiDataScheduler.h +++ b/src/api/ApiDataScheduler.h @@ -1,4 +1,7 @@ #pragma once +#include "ECFMP/eventbus/EventListener.h" +#include "eventbus/InternalEventBus.h" +#include "plugin/InternalSdkEvents.h" namespace ECFMP::Api { @@ -7,11 +10,12 @@ namespace ECFMP::Api { /** * Responsible for downloading API data on a schedule. */ - class ApiDataScheduler + class ApiDataScheduler : public EventBus::EventListener { public: - explicit ApiDataScheduler(std::unique_ptr downloader); - ~ApiDataScheduler(); + explicit ApiDataScheduler(std::shared_ptr eventBus); + ~ApiDataScheduler() override; + void OnEvent(const Plugin::EuroscopeTimerTickEvent& eventType) override; private: struct Impl; diff --git a/src/api/EventDataParser.cpp b/src/api/EventDataParser.cpp index 3e01473..d7a7b43 100644 --- a/src/api/EventDataParser.cpp +++ b/src/api/EventDataParser.cpp @@ -1,4 +1,5 @@ #include "EventDataParser.h" +#include "ECFMP/SdkEvents.h" #include "ECFMP/event/EventParticipant.h" #include "ECFMP/log/Logger.h" #include "InternalApiElementCollection.h" @@ -9,9 +10,13 @@ namespace ECFMP::Api { - EventDataParser::EventDataParser(std::shared_ptr logger) : logger(std::move(logger)) + EventDataParser::EventDataParser( + std::shared_ptr logger, std::shared_ptr eventBus + ) + : logger(std::move(logger)), eventBus(std::move(eventBus)) { assert(this->logger != nullptr && "Logger cannot be null"); + assert(this->eventBus != nullptr && "EventBus cannot be null"); } auto EventDataParser::ParseEvents(const nlohmann::json& data, const InternalFlightInformationRegionCollection& firs) @@ -52,6 +57,7 @@ namespace ECFMP::Api { } logger->Debug("Finished updating events"); + eventBus->OnEvent({events}); return events; } diff --git a/src/api/EventDataParser.h b/src/api/EventDataParser.h index 63ce9ec..1791508 100644 --- a/src/api/EventDataParser.h +++ b/src/api/EventDataParser.h @@ -1,5 +1,6 @@ #pragma once #include "EventDataParserInterface.h" +#include "eventbus/InternalEventBus.h" #include "nlohmann/json_fwd.hpp" namespace ECFMP::Log { @@ -10,7 +11,9 @@ namespace ECFMP::Api { class EventDataParser : public EventDataParserInterface { public: - explicit EventDataParser(std::shared_ptr logger); + explicit EventDataParser( + std::shared_ptr logger, std::shared_ptr eventBus + ); [[nodiscard]] auto ParseEvents(const nlohmann::json& data, const InternalFlightInformationRegionCollection& firs) -> std::shared_ptr override; @@ -24,5 +27,8 @@ namespace ECFMP::Api { // A logger, for logging things std::shared_ptr logger; + + // EventBus + std::shared_ptr eventBus; }; }// namespace ECFMP::Api diff --git a/src/api/FlightInformationRegionDataParser.cpp b/src/api/FlightInformationRegionDataParser.cpp index 24a0bd8..e49b3a5 100644 --- a/src/api/FlightInformationRegionDataParser.cpp +++ b/src/api/FlightInformationRegionDataParser.cpp @@ -1,15 +1,20 @@ #include "FlightInformationRegionDataParser.h" #include "ECFMP/log/Logger.h" #include "InternalElementCollectionTypes.h" +#include "eventbus/InternalEventBus.h" #include "flightinformationregion/ConcreteFlightInformationRegion.h" #include "nlohmann/json.hpp" +#include "plugin/InternalSdkEvents.h" namespace ECFMP::Api { - FlightInformationRegionDataParser::FlightInformationRegionDataParser(std::shared_ptr logger) - : logger(std::move(logger)) + FlightInformationRegionDataParser::FlightInformationRegionDataParser( + std::shared_ptr logger, std::shared_ptr eventBus + ) + : logger(std::move(logger)), eventBus(std::move(eventBus)) { assert(this->logger && "logger not set in FlightInformationRegionDataParser"); + assert(this->eventBus && "eventBus not set in FlightInformationRegionDataParser"); } auto FlightInformationRegionDataParser::ParseFirs(const nlohmann::json& data) @@ -36,6 +41,7 @@ namespace ECFMP::Api { } logger->Debug("Finished updating FIRs"); + eventBus->OnEvent({firs}); return firs; } diff --git a/src/api/FlightInformationRegionDataParser.h b/src/api/FlightInformationRegionDataParser.h index 135ae4b..0025614 100644 --- a/src/api/FlightInformationRegionDataParser.h +++ b/src/api/FlightInformationRegionDataParser.h @@ -5,6 +5,9 @@ #include "nlohmann/json_fwd.hpp" namespace ECFMP { + namespace EventBus { + class InternalEventBus; + }// namespace EventBus namespace Log { class Logger; }// namespace Log @@ -17,7 +20,9 @@ namespace ECFMP::Api { class FlightInformationRegionDataParser : public FlightInformationRegionDataParserInterface { public: - FlightInformationRegionDataParser(std::shared_ptr logger); + FlightInformationRegionDataParser( + std::shared_ptr logger, std::shared_ptr eventBus + ); [[nodiscard]] auto ParseFirs(const nlohmann::json& data) -> std::shared_ptr override; @@ -27,5 +32,8 @@ namespace ECFMP::Api { // A logger, for logging things std::shared_ptr logger; + + // EventBus + std::shared_ptr eventBus; }; }// namespace ECFMP::Api diff --git a/src/api/FlowMeasureDataParser.cpp b/src/api/FlowMeasureDataParser.cpp index 7a08446..1649008 100644 --- a/src/api/FlowMeasureDataParser.cpp +++ b/src/api/FlowMeasureDataParser.cpp @@ -1,4 +1,5 @@ #include "FlowMeasureDataParser.h" +#include "ECFMP/SdkEvents.h" #include "ECFMP/flightinformationregion/FlightInformationRegion.h" #include "ECFMP/flowmeasure/FlowMeasure.h" #include "ECFMP/log/Logger.h" @@ -9,17 +10,21 @@ #include "date/ParseDateStrings.h" #include "flowmeasure/ConcreteFlowMeasure.h" #include "nlohmann/json.hpp" +#include "plugin/InternalSdkEvents.h" namespace ECFMP::Api { FlowMeasureDataParser::FlowMeasureDataParser( std::unique_ptr filterParser, - std::unique_ptr measureParser, std::shared_ptr logger + std::unique_ptr measureParser, std::shared_ptr logger, + std::shared_ptr eventBus ) - : filterParser(std::move(filterParser)), measureParser(std::move(measureParser)), logger(std::move(logger)) + : filterParser(std::move(filterParser)), measureParser(std::move(measureParser)), logger(std::move(logger)), + eventBus(std::move(eventBus)) { assert(this->filterParser != nullptr && "FlowMeasureDataParser::FlowMeasureDataParser: filterParser is null"); assert(this->measureParser != nullptr && "FlowMeasureDataParser::FlowMeasureDataParser: measureParser is null"); assert(this->logger != nullptr && "FlowMeasureDataParser::FlowMeasureDataParser: logger is null"); + assert(this->eventBus != nullptr && "FlowMeasureDataParser::FlowMeasureDataParser: eventBus is null"); } auto FlowMeasureDataParser::ParseFlowMeasures( @@ -89,6 +94,10 @@ namespace ECFMP::Api { flowMeasures->Add(flowMeasure); } + logger->Debug("FlowMeasureDataParser::OnEvent: parsed flow measures"); + eventBus->OnEvent({flowMeasures}); + eventBus->OnEvent({flowMeasures}); + return flowMeasures; } diff --git a/src/api/FlowMeasureDataParser.h b/src/api/FlowMeasureDataParser.h index 0d3e26a..1970592 100644 --- a/src/api/FlowMeasureDataParser.h +++ b/src/api/FlowMeasureDataParser.h @@ -1,6 +1,7 @@ #pragma once #include "ECFMP/flowmeasure/FlowMeasure.h" #include "FlowMeasureDataParserInterface.h" +#include "eventbus/InternalEventBus.h" #include "nlohmann/json_fwd.hpp" namespace ECFMP::Log { @@ -17,7 +18,8 @@ namespace ECFMP::Api { public: FlowMeasureDataParser( std::unique_ptr filterParser, - std::unique_ptr measureParser, std::shared_ptr logger + std::unique_ptr measureParser, std::shared_ptr logger, + std::shared_ptr eventBus ); ~FlowMeasureDataParser() override = default; [[nodiscard]] auto ParseFlowMeasures( @@ -45,6 +47,9 @@ namespace ECFMP::Api { // Logger std::shared_ptr logger; + + // Event bus + std::shared_ptr eventBus; }; }// namespace ECFMP::Api diff --git a/src/api/InternalStringIdentifierApiElementCollection.h b/src/api/InternalStringIdentifierApiElementCollection.h index 8c32c92..78c772c 100644 --- a/src/api/InternalStringIdentifierApiElementCollection.h +++ b/src/api/InternalStringIdentifierApiElementCollection.h @@ -13,5 +13,11 @@ namespace ECFMP::Api { const auto uniqueLock = std::unique_lock(this->lock); this->elements[static_cast>(element)->Id()] = element; } + + [[nodiscard]] auto GetUnderlyingCollection() const noexcept + -> const std::unordered_map>& + { + return this->elements; + } }; }// namespace ECFMP::Api diff --git a/src/eventbus/AsynchronousEventDispatcher.h b/src/eventbus/AsynchronousEventDispatcher.h new file mode 100644 index 0000000..522e4bb --- /dev/null +++ b/src/eventbus/AsynchronousEventDispatcher.h @@ -0,0 +1,38 @@ +#pragma once +#include "ECFMP/eventbus/EventListener.h" +#include "EventDispatcher.h" +#include +#include + +namespace ECFMP::EventBus { + + template + class AsynchronousEventDispatcher : public EventDispatcher + { + public: + explicit AsynchronousEventDispatcher(std::shared_ptr> listener) : listener(listener) + { + assert(listener != nullptr && "Listener cannot be null"); + } + + /** + * Dispatch the event asynchronously. + */ + void Dispatch(const EventType& event) override + { + // Copy the listener to ensure it is not destroyed before the event is dispatched + auto listenerCopy = listener; + static_cast(std::async( + std::launch::async, + [listenerCopy](const auto& event) { + listenerCopy->OnEvent(event); + }, + event + )); + }; + + private: + // The event listener for this dispatcher + std::shared_ptr> listener; + }; +}// namespace ECFMP::EventBus diff --git a/src/eventbus/EuroscopeEventDispatcher.h b/src/eventbus/EuroscopeEventDispatcher.h new file mode 100644 index 0000000..e2f03ef --- /dev/null +++ b/src/eventbus/EuroscopeEventDispatcher.h @@ -0,0 +1,39 @@ +#pragma once +#include "ECFMP/eventbus/EventListener.h" +#include "EventDispatcher.h" +#include "PendingEuroscopeEvents.h" +#include + +namespace ECFMP::EventBus { + + template + class EuroscopeEventDispatcher : public EventDispatcher + { + public: + explicit EuroscopeEventDispatcher( + std::shared_ptr> listener, + const std::shared_ptr& pendingEuroscopeEvents + ) + : listener(listener), pendingEuroscopeEvents(pendingEuroscopeEvents) + { + assert(listener != nullptr && "Listener cannot be null"); + assert(pendingEuroscopeEvents != nullptr && "Pending ES events cannot be null"); + } + + void Dispatch(const EventType& event) override + { + // Copy the listener to make sure it doesn't get deleted before the event is dispatched + auto listenerCopy = this->listener; + pendingEuroscopeEvents->AddEvent([listenerCopy, event]() -> void { + listenerCopy->OnEvent(event); + }); + }; + + private: + // The event listener for this dispatcher + std::shared_ptr> listener; + + // The pending ES events + std::shared_ptr pendingEuroscopeEvents; + }; +}// namespace ECFMP::EventBus diff --git a/src/eventbus/EventDispatcher.h b/src/eventbus/EventDispatcher.h new file mode 100644 index 0000000..4195140 --- /dev/null +++ b/src/eventbus/EventDispatcher.h @@ -0,0 +1,14 @@ +#pragma once + +namespace ECFMP::EventBus { + /** + * A class responsible for dispatching events to their listeners. + */ + template + class EventDispatcher + { + public: + virtual ~EventDispatcher() = default; + virtual void Dispatch(const EventType& event) = 0; + }; +}// namespace ECFMP::EventBus diff --git a/src/eventbus/EventDispatcherFactory.h b/src/eventbus/EventDispatcherFactory.h new file mode 100644 index 0000000..cf01b90 --- /dev/null +++ b/src/eventbus/EventDispatcherFactory.h @@ -0,0 +1,41 @@ +#pragma once +#include "AsynchronousEventDispatcher.h" +#include "EuroscopeEventDispatcher.h" +#include "EventDispatcher.h" +#include "PendingEuroscopeEvents.h" +#include "SubscriptionFlags.h" +#include "SynchronousEventDispatcher.h" +#include + +namespace ECFMP::EventBus { + class EventDispatcherFactory + { + public: + explicit EventDispatcherFactory(const std::shared_ptr& pendingEuroscopeEvents) + : pendingEuroscopeEvents(pendingEuroscopeEvents) + { + assert(pendingEuroscopeEvents != nullptr && "pendingEuroscopeEvents cannot be null"); + } + virtual ~EventDispatcherFactory() = default; + + template + [[nodiscard]] auto CreateDispatcher(std::shared_ptr> listener, EventDispatchMode mode) + -> std::shared_ptr> + { + switch (mode) { + case EventDispatchMode::Sync: + return std::make_shared>(listener); + case EventDispatchMode::Async: + return std::make_shared>(listener); + case EventDispatchMode::Euroscope: + return std::make_shared>(listener, pendingEuroscopeEvents); + default: + throw std::runtime_error("Unknown dispatch mode"); + } + }; + + private: + // A place that stores events that should be dispatched on the Euroscope thread. + std::shared_ptr pendingEuroscopeEvents; + }; +}// namespace ECFMP::EventBus diff --git a/src/eventbus/EventStream.h b/src/eventbus/EventStream.h new file mode 100644 index 0000000..e2763dd --- /dev/null +++ b/src/eventbus/EventStream.h @@ -0,0 +1,128 @@ +#pragma once +#include "ECFMP/eventbus/EventFilter.h" +#include "ECFMP/eventbus/EventListener.h" +#include "SubscriptionFlags.h" +#include "eventbus/EventDispatcher.h" +#include +#include +#include +#include + +namespace ECFMP::EventBus { + + template + struct EventSubscription { + + EventSubscription( + std::shared_ptr> dispatcher, + std::shared_ptr> rawListener, std::shared_ptr> filter, + SubscriptionFlags flags + ) + : dispatcher(std::move(dispatcher)), rawListener(std::move(rawListener)), filter(std::move(filter)), + flags(flags) + { + assert(this->dispatcher != nullptr && "Dispatcher cannot be null"); + assert(this->rawListener != nullptr && "Listener cannot be null"); + } + + std::shared_ptr> dispatcher; + std::shared_ptr> rawListener; + std::shared_ptr> filter; + + SubscriptionFlags flags; + }; + + template + class EventStream + { + public: + virtual ~EventStream() = default; + + /** + * Subscribes the given listener to the event stream. + */ + void Subscribe(const EventSubscription& subscription) + { + auto guard = std::lock_guard(mutex); + subscriptions.push_back(subscription); + }; + + void OnEvent(const EventType& event) + { + auto guard = std::lock_guard(this->mutex); + + // Remove all subscriptions that are only valid for one event. + this->subscriptions.erase( + std::remove_if( + this->subscriptions.begin(), this->subscriptions.end(), + [&event](const EventSubscription& subscription) { + // If the subscription filter doesn't pass, then we don't want to call the listener + // but also don't want to remove the subscription. + if (subscription.filter != nullptr && !subscription.filter->ShouldProcess(event)) { + return false; + } + + // Dispatch the event to the subscription + subscription.dispatcher->Dispatch(event); + + // For any called listeners that are only valid for one event, remove them. + return subscription.flags.once; + } + ), + this->subscriptions.end() + ); + }; + + /** + * A method for testing only, shouldn't normally be used. + */ + template + [[nodiscard]] auto HasListenerOfType() -> bool + { + auto guard = std::lock_guard(this->mutex); + return std::any_of( + this->subscriptions.begin(), this->subscriptions.end(), + [](const EventSubscription& subscription) { + try { + static_cast(dynamic_cast(*subscription.rawListener)); + return true; + } + catch (std::bad_cast&) { + return false; + } + } + ); + } + + /** + * A method for testing only, shouldn't normally be used. + */ + template + [[nodiscard]] auto HasListenerForSubscription(const SubscriptionFlags& expectedFlags) -> bool + { + auto guard = std::lock_guard(this->mutex); + return std::any_of( + this->subscriptions.begin(), this->subscriptions.end(), + [&expectedFlags](const EventSubscription& subscription) { + try { + static_cast(dynamic_cast(*subscription.rawListener)); + + return subscription.flags.once == expectedFlags.once + && subscription.flags.dispatchMode == expectedFlags.dispatchMode; + return true; + } + catch (std::bad_cast&) { + return false; + } + } + ); + } + + protected: + // Mutex for the streams + std::mutex mutex; + + // All subscriptions to this event stream. + std::vector> subscriptions; + }; +}// namespace ECFMP::EventBus diff --git a/src/eventbus/InternalEventBus.h b/src/eventbus/InternalEventBus.h index 997eef1..28417e6 100644 --- a/src/eventbus/InternalEventBus.h +++ b/src/eventbus/InternalEventBus.h @@ -5,37 +5,125 @@ namespace ECFMP::EventBus { class InternalEventBus : public EventBus { public: + explicit InternalEventBus(const std::shared_ptr& dispatcherFactory) + : EventBus(dispatcherFactory) + {} + template void OnEvent(const EventType& event) { - auto lock = std::lock_guard(pendingMutex); - pendingEvents.push_back([event, this]() { - GetStream().OnEvent(event); - }); + GetStream().OnEvent(event); } /** - * Processes all pending events. - * - * This method should be called from the EuroScope thread. - * - * This is necessary because we're expecting our users to use EuroScope classes such as FlightPlan, - * which can only be used on the ES thread. + * Subscribes the given listener to the event stream, asynchronously. */ - void ProcessPendingEvents() + template + void SubscribeAsync(std::shared_ptr> listener) { - auto lock = std::lock_guard(pendingMutex); - for (const auto& event: pendingEvents) { - event(); + SubscribeAsync(listener, nullptr); + }; + + template + void SubscribeAsync( + std::shared_ptr> listener, std::shared_ptr> filter + ) + { + if (listener == nullptr) { + throw std::invalid_argument("Listener cannot be null"); } - pendingEvents.clear(); - } - private: - // Protects the pending events - std::mutex pendingMutex; + Subscribe(EventSubscription{ + dispatcherFactory->CreateDispatcher(listener, EventDispatchMode::Async), + listener, + filter, + {EventDispatchMode::Async, false}}); + }; + + /** + * Subscribes the given listener to the event stream, asynchronously, once. + */ + template + void SubscribeAsyncOnce(std::shared_ptr> listener) + { + SubscribeAsyncOnce(listener, nullptr); + }; + + template + void SubscribeAsyncOnce( + std::shared_ptr> listener, std::shared_ptr> filter + ) + { + if (listener == nullptr) { + throw std::invalid_argument("Listener cannot be null"); + } - // Events that are waiting to be processed by the EuroScope thread. - std::vector> pendingEvents; + Subscribe(EventSubscription{ + dispatcherFactory->CreateDispatcher(listener, EventDispatchMode::Async), + listener, + filter, + {EventDispatchMode::Async, true}}); + }; + + /** + * Subscribes the given listener to the event stream, synchronously. + */ + template + void SubscribeSync(std::shared_ptr> listener) + { + SubscribeSync(listener, nullptr); + }; + + template + void SubscribeSync( + std::shared_ptr> listener, std::shared_ptr> filter + ) + { + if (listener == nullptr) { + throw std::invalid_argument("Listener cannot be null"); + } + + Subscribe(EventSubscription{ + dispatcherFactory->CreateDispatcher(listener, EventDispatchMode::Sync), + listener, + filter, + {EventDispatchMode::Sync, false}}); + }; + + /** + * Subscribes the given listener to the event stream, once, synchronously. + */ + template + void SubscribeSyncOnce(std::shared_ptr> listener) + { + SubscribeSyncOnce(listener, nullptr); + }; + + template + void SubscribeSyncOnce( + std::shared_ptr> listener, std::shared_ptr> filter + ) + { + if (listener == nullptr) { + throw std::invalid_argument("Listener cannot be null"); + } + + Subscribe(EventSubscription{ + dispatcherFactory->CreateDispatcher(listener, EventDispatchMode::Sync), + listener, + filter, + {EventDispatchMode::Sync, true}}); + }; + + /** + * Checks whether a specified type of listener is registered for a specified type of event. + * + * Can be used for test assertions. + */ + template + [[nodiscard]] auto HasListenerForSubscription(const SubscriptionFlags& flags) -> bool + { + return GetStream().template HasListenerForSubscription(flags); + } }; }// namespace ECFMP::EventBus diff --git a/src/eventbus/InternalEventBusFactory.cpp b/src/eventbus/InternalEventBusFactory.cpp new file mode 100644 index 0000000..35b7607 --- /dev/null +++ b/src/eventbus/InternalEventBusFactory.cpp @@ -0,0 +1,16 @@ +#include "eventbus/InternalEventBusFactory.h" +#include "eventbus/PendingEuroscopeEvents.h" +#include "plugin/InternalSdkEvents.h" + +namespace ECFMP::EventBus { + [[nodiscard]] auto MakeEventBus() -> std::shared_ptr + { + auto pendingEuroscopeEvents = std::make_shared(); + auto eventDispatcherFactory = std::make_shared(pendingEuroscopeEvents); + + auto eventBus = std::make_shared(eventDispatcherFactory); + eventBus->SubscribeSync(pendingEuroscopeEvents); + + return eventBus; + } +}// namespace ECFMP::EventBus diff --git a/src/eventbus/InternalEventBusFactory.h b/src/eventbus/InternalEventBusFactory.h new file mode 100644 index 0000000..35b306d --- /dev/null +++ b/src/eventbus/InternalEventBusFactory.h @@ -0,0 +1,7 @@ +#pragma once + +#include "eventbus/InternalEventBus.h" + +namespace ECFMP::EventBus { + [[nodiscard]] auto MakeEventBus() -> std::shared_ptr; +}// namespace ECFMP::EventBus diff --git a/src/eventbus/InternalEventStream.h b/src/eventbus/InternalEventStream.h deleted file mode 100644 index 6ba9019..0000000 --- a/src/eventbus/InternalEventStream.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once -#include "ECFMP/eventbus/EventFilter.h" -#include "ECFMP/eventbus/EventListener.h" -#include "ECFMP/eventbus/EventStream.h" - -namespace ECFMP::EventBus { - - template - class InternalEventStream : public EventStream - { - public: - void OnEvent(const EventType& event) - { - auto guard = std::lock_guard(this->mutex); - - // Remove all subscriptions that are only valid for one event. - this->subscriptions.erase( - std::remove_if( - this->subscriptions.begin(), this->subscriptions.end(), - [&event](const EventSubscription& subscription) { - // If the subscription filter doesn't pass, then we don't want to call the listener - // but also don't want to remove the subscription. - if (subscription.filter != nullptr && !subscription.filter->ShouldProcess(event)) { - return false; - } - - // Call the listener. - subscription.listener->OnEvent(event); - - // For any called listeners that are only valid for one event, remove them. - return subscription.once; - } - ), - this->subscriptions.end() - ); - }; - - /** - * A method for testing only, shouldn't normally be used. - */ - template - [[nodiscard]] auto HasListenerOfType() -> bool - { - auto guard = std::lock_guard(this->mutex); - return std::any_of( - this->subscriptions.begin(), this->subscriptions.end(), - [](const EventSubscription& subscription) { - try { - static_cast(dynamic_cast(*subscription.listener)); - return true; - } - catch (std::bad_cast&) { - return false; - } - } - ); - } - }; -}// namespace ECFMP::EventBus diff --git a/src/eventbus/PendingEuroscopeEvents.cpp b/src/eventbus/PendingEuroscopeEvents.cpp new file mode 100644 index 0000000..1548007 --- /dev/null +++ b/src/eventbus/PendingEuroscopeEvents.cpp @@ -0,0 +1,20 @@ +#include "PendingEuroscopeEvents.h" + +namespace ECFMP::EventBus { + + void PendingEuroscopeEvents::AddEvent(const std::function& event) + { + std::lock_guard lock(pendingEventsMutex); + pendingEvents.push_back(event); + } + + void PendingEuroscopeEvents::OnEvent(const Plugin::EuroscopeTimerTickEvent& timerTickEvent) + { + std::lock_guard lock(pendingEventsMutex); + for (const auto& event: pendingEvents) { + event(); + } + + pendingEvents.clear(); + } +}// namespace ECFMP::EventBus diff --git a/src/eventbus/PendingEuroscopeEvents.h b/src/eventbus/PendingEuroscopeEvents.h new file mode 100644 index 0000000..3a997f5 --- /dev/null +++ b/src/eventbus/PendingEuroscopeEvents.h @@ -0,0 +1,24 @@ +#pragma once +#include "ECFMP/eventbus/EventListener.h" +#include "EventStream.h" +#include "plugin/InternalSdkEvents.h" + +namespace ECFMP::EventBus { + + /** + * Stores pending ES events + */ + class PendingEuroscopeEvents : public EventListener + { + public: + void AddEvent(const std::function& event); + void OnEvent(const Plugin::EuroscopeTimerTickEvent& timerTickEvent) override; + + private: + // Mutex for the pending events + std::mutex pendingEventsMutex; + + // The pending events + std::vector> pendingEvents; + }; +}// namespace ECFMP::EventBus diff --git a/src/eventbus/SubscriptionFlags.h b/src/eventbus/SubscriptionFlags.h new file mode 100644 index 0000000..1d28a1b --- /dev/null +++ b/src/eventbus/SubscriptionFlags.h @@ -0,0 +1,27 @@ +#pragma once + +namespace ECFMP::EventBus { + /** + * The dispatch mode for events + */ + enum EventDispatchMode + { + // Event should be dispatched synchronously + Sync = 0, + + // Event should be dispatched asynchronously + Async = 1, + + // Event should be retained and later dispatched when the ES thread comes around + Euroscope = 2 + }; + + using SubscriptionFlags = struct SubscriptionFlags { + // How to dispatch events to the subscription + EventDispatchMode dispatchMode; + + // Whether we should only run this subscription once + bool once; + }; + +}// namespace ECFMP::EventBus diff --git a/src/eventbus/SynchronousEventDispatcher.h b/src/eventbus/SynchronousEventDispatcher.h new file mode 100644 index 0000000..e97f794 --- /dev/null +++ b/src/eventbus/SynchronousEventDispatcher.h @@ -0,0 +1,26 @@ +#pragma once +#include "ECFMP/eventbus/EventListener.h" +#include "EventDispatcher.h" +#include + +namespace ECFMP::EventBus { + + template + class SynchronousEventDispatcher : public EventDispatcher + { + public: + explicit SynchronousEventDispatcher(std::shared_ptr> listener) : listener(listener) + { + assert(listener != nullptr && "Listener cannot be null"); + } + + void Dispatch(const EventType& event) override + { + listener->OnEvent(event); + }; + + private: + // The event listener for this dispatcher + std::shared_ptr> listener; + }; +}// namespace ECFMP::EventBus diff --git a/src/flowmeasure/CanonicalFlowMeasureInfo.cpp b/src/flowmeasure/CanonicalFlowMeasureInfo.cpp new file mode 100644 index 0000000..a44fc7f --- /dev/null +++ b/src/flowmeasure/CanonicalFlowMeasureInfo.cpp @@ -0,0 +1,43 @@ +#include "ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h" + +namespace ECFMP::FlowMeasure { + CanonicalFlowMeasureInfo::CanonicalFlowMeasureInfo(const std::string& identifier) + : identifier(ParseIdentifier(identifier)), revision(ParseRevision(identifier)) + {} + + auto CanonicalFlowMeasureInfo::Identifier() const noexcept -> const std::string& + { + return identifier; + } + + auto CanonicalFlowMeasureInfo::Revision() const noexcept -> int + { + return revision; + } + + auto CanonicalFlowMeasureInfo::IsAfter(const CanonicalFlowMeasureInfo& other) const noexcept -> bool + { + return identifier == other.identifier && revision > other.revision; + } + + auto CanonicalFlowMeasureInfo::ParseIdentifier(const std::string& identifier) -> std::string + { + return identifier.substr(0, identifier.find_last_of('-')); + } + + auto CanonicalFlowMeasureInfo::ParseRevision(const std::string& identifier) -> int + { + auto revisionSeparator = identifier.find_last_of('-'); + if (revisionSeparator == std::string::npos) { + return 0; + } + + auto revision = identifier.substr(revisionSeparator + 1); + try { + return std::stoi(revision); + } + catch (std::invalid_argument&) { + return 0; + } + } +}// namespace ECFMP::FlowMeasure diff --git a/src/flowmeasure/ConcreteFlowMeasure.cpp b/src/flowmeasure/ConcreteFlowMeasure.cpp index 69d4163..6c03238 100644 --- a/src/flowmeasure/ConcreteFlowMeasure.cpp +++ b/src/flowmeasure/ConcreteFlowMeasure.cpp @@ -1,5 +1,6 @@ #include "ConcreteFlowMeasure.h" #include "ECFMP/flightinformationregion/FlightInformationRegion.h" +#include "ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h" #include "ECFMP/flowmeasure/FlowMeasureFilters.h" #include "ECFMP/flowmeasure/Measure.h" @@ -12,7 +13,8 @@ namespace ECFMP::FlowMeasure { const std::vector>& notifiedFirs, std::unique_ptr measure, std::unique_ptr filters ) - : id(id), event(std::move(event)), identifier(std::move(identifier)), reason(std::move(reason)), + : id(id), event(std::move(event)), identifier(std::move(identifier)), + canonicalInformation(std::make_unique(this->identifier)), reason(std::move(reason)), startTime(startTime), endTime(endTime), withdrawnTime(withdrawnTime), status(status), notifiedFirs(notifiedFirs), measure(std::move(measure)), filters(std::move(filters)) { @@ -105,4 +107,9 @@ namespace ECFMP::FlowMeasure { } ) != notifiedFirs.end(); } + + auto ConcreteFlowMeasure::CanonicalInformation() const noexcept -> const CanonicalFlowMeasureInfo& + { + return *canonicalInformation; + } }// namespace ECFMP::FlowMeasure diff --git a/src/flowmeasure/ConcreteFlowMeasure.h b/src/flowmeasure/ConcreteFlowMeasure.h index 1c2d90e..e242dbf 100644 --- a/src/flowmeasure/ConcreteFlowMeasure.h +++ b/src/flowmeasure/ConcreteFlowMeasure.h @@ -22,6 +22,7 @@ namespace ECFMP::FlowMeasure { [[nodiscard]] auto Id() const noexcept -> int override; [[nodiscard]] auto Event() const noexcept -> std::shared_ptr override; [[nodiscard]] auto Identifier() const noexcept -> const std::string& override; + [[nodiscard]] auto CanonicalInformation() const noexcept -> const CanonicalFlowMeasureInfo& override; [[nodiscard]] auto Reason() const noexcept -> const std::string& override; [[nodiscard]] auto StartTime() const noexcept -> const std::chrono::system_clock::time_point& override; [[nodiscard]] auto EndTime() const noexcept -> const std::chrono::system_clock::time_point& override; @@ -48,6 +49,9 @@ namespace ECFMP::FlowMeasure { // Identifier for the measure std::string identifier; + // Canonical identifier for the measure + std::unique_ptr canonicalInformation; + // Why the measure was enacted std::string reason; @@ -72,5 +76,4 @@ namespace ECFMP::FlowMeasure { // The filters std::unique_ptr filters; }; - }// namespace ECFMP::FlowMeasure diff --git a/src/flowmeasure/FlowMeasureStatusUpdates.cpp b/src/flowmeasure/FlowMeasureStatusUpdates.cpp new file mode 100644 index 0000000..3cff37b --- /dev/null +++ b/src/flowmeasure/FlowMeasureStatusUpdates.cpp @@ -0,0 +1,74 @@ +#include "FlowMeasureStatusUpdates.h" +#include "ECFMP/SdkEvents.h" +#include "ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h" +#include "ECFMP/log/Logger.h" +#include "eventbus/InternalEventBus.h" + +namespace ECFMP::FlowMeasure { + + FlowMeasureStatusUpdates::FlowMeasureStatusUpdates( + std::shared_ptr eventBus, std::shared_ptr logger + ) + : eventBus(std::move(eventBus)), logger(std::move(logger)) + { + assert(this->eventBus != nullptr && "The event bus cannot be null."); + assert(this->logger != nullptr && "The logger cannot be null."); + } + + void FlowMeasureStatusUpdates::OnEvent(const Plugin::InternalFlowMeasuresUpdatedEvent& event) + { + for (const auto& measurePair: event.flowMeasures->GetUnderlyingCollection()) { + const auto measure = measurePair.second; + + auto canonicalInformation = measure->CanonicalInformation(); + + // Never seen it before, so we can only broadcast a state. + if (!canonicalFlowMeasures.contains(canonicalInformation.Identifier())) { + canonicalFlowMeasures[canonicalInformation.Identifier()] = measure; + BroadcastStatusUpdate(measure); + return; + } + + // Switch the measure we have stored out for the new one + auto previousMeasure = canonicalFlowMeasures[canonicalInformation.Identifier()]; + canonicalFlowMeasures[canonicalInformation.Identifier()] = measure; + + // If the new measure is after the old one, we need to reissue the measure + if (canonicalInformation.IsAfter(previousMeasure->CanonicalInformation())) { + eventBus->OnEvent({previousMeasure, measure}); + } + + // If the status has changed, we need to broadcast the new status + if (previousMeasure->Status() != measure->Status()) { + BroadcastStatusUpdate(measure); + } + } + } + + void FlowMeasureStatusUpdates::BroadcastStatusUpdate(const std::shared_ptr& measure) + { + switch (measure->Status()) { + case MeasureStatus::Notified: + WriteLogMessage(*measure, "Notified"); + eventBus->OnEvent({measure}); + break; + case MeasureStatus::Active: + WriteLogMessage(*measure, "Activated"); + eventBus->OnEvent({measure}); + break; + case MeasureStatus::Withdrawn: + WriteLogMessage(*measure, "Withdrawn"); + eventBus->OnEvent({measure}); + break; + case MeasureStatus::Expired: + WriteLogMessage(*measure, "Expired"); + eventBus->OnEvent({measure}); + break; + } + } + + void FlowMeasureStatusUpdates::WriteLogMessage(const FlowMeasure& measure, const std::string& status) + { + logger->Info("Flow measure " + measure.Identifier() + " now has a status of " + status + "."); + } +}// namespace ECFMP::FlowMeasure diff --git a/src/flowmeasure/FlowMeasureStatusUpdates.h b/src/flowmeasure/FlowMeasureStatusUpdates.h new file mode 100644 index 0000000..25d3c46 --- /dev/null +++ b/src/flowmeasure/FlowMeasureStatusUpdates.h @@ -0,0 +1,43 @@ +#pragma once +#include "ECFMP/eventbus/EventListener.h" +#include "plugin/InternalSdkEvents.h" + +namespace ECFMP { + namespace EventBus { + class InternalEventBus; + }// namespace EventBus + namespace Log { + class Logger; + }// namespace Log +}// namespace ECFMP + +namespace ECFMP::FlowMeasure { + + class FlowMeasure; + + /** + * Handles status updates for flow measures. + */ + class FlowMeasureStatusUpdates : public EventBus::EventListener + { + public: + FlowMeasureStatusUpdates( + std::shared_ptr eventBus, std::shared_ptr logger + ); + void OnEvent(const Plugin::InternalFlowMeasuresUpdatedEvent& event) override; + + private: + void BroadcastStatusUpdate(const std::shared_ptr& measure); + void WriteLogMessage(const FlowMeasure& measure, const std::string& status); + + // The event bus + std::shared_ptr eventBus; + + // The logger + std::shared_ptr logger; + + // The canonical flow measures + std::unordered_map> canonicalFlowMeasures; + }; + +}// namespace ECFMP::FlowMeasure diff --git a/src/plugin/ConcreteSdk.cpp b/src/plugin/ConcreteSdk.cpp deleted file mode 100644 index 41f2bea..0000000 --- a/src/plugin/ConcreteSdk.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "ConcreteSdk.h" -#include "eventbus/InternalEventBus.h" - -namespace ECFMP::Plugin { - ConcreteSdk::ConcreteSdk(std::shared_ptr apiScheduler, std::shared_ptr eventBus) - : apiScheduler(std::move(apiScheduler)), eventBus(std::move(eventBus)) - { - assert(this->apiScheduler && "Api scheduler not set in ConcreteSdk"); - assert(this->eventBus && "Event bus not set in ConcreteSdk"); - } - - void ConcreteSdk::Destroy() - { - // Shutdown the API data downloader - apiScheduler.reset(); - } - - auto ConcreteSdk::EventBus() const noexcept -> EventBus::EventBus& - { - return *eventBus; - } - - void ConcreteSdk::OnEuroscopeTimerTick() - { - eventBus->ProcessPendingEvents(); - } -}// namespace ECFMP::Plugin diff --git a/src/plugin/ConcreteSdk.h b/src/plugin/ConcreteSdk.h deleted file mode 100644 index 627272d..0000000 --- a/src/plugin/ConcreteSdk.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include "ECFMP/Sdk.h" - -namespace ECFMP { - namespace Api { - class ApiDataScheduler; - } - namespace EventBus { - class InternalEventBus; - } -}// namespace ECFMP - -namespace ECFMP::Plugin { - - class ConcreteSdk : public Sdk - { - public: - ConcreteSdk(std::shared_ptr apiScheduler, std::shared_ptr eventBus); - ~ConcreteSdk() override = default; - - [[nodiscard]] auto EventBus() const noexcept -> EventBus::EventBus& override; - void OnEuroscopeTimerTick() override; - - void Destroy() override; - - private: - // Schedules API downloads - this kinda keeps itself to itself, so we dont need to enforce types here - // which makes things easier for testing - std::shared_ptr apiScheduler; - - // The event bus for the SDK, which can be used to subscribe to events. - std::shared_ptr eventBus; - }; -}// namespace ECFMP::Plugin diff --git a/src/plugin/InternalSdk.cpp b/src/plugin/InternalSdk.cpp new file mode 100644 index 0000000..e20e79b --- /dev/null +++ b/src/plugin/InternalSdk.cpp @@ -0,0 +1,66 @@ +#include "InternalSdk.h" +#include "InternalSdkEvents.h" +#include "eventbus/InternalEventBus.h" + +namespace ECFMP::Plugin { + InternalSdk::InternalSdk(std::shared_ptr eventBus) + : eventBus(std::move(eventBus)), + flightInformationRegions(std::make_shared()), + events(std::make_shared()), + flowMeasures(std::make_shared()) + { + assert(this->eventBus && "Event bus not set in ConcreteSdk"); + } + + void InternalSdk::Destroy() + { + eventBus.reset(); + } + + auto InternalSdk::EventBus() const noexcept -> EventBus::EventBus& + { + return *eventBus; + } + + void InternalSdk::OnEuroscopeTimerTick() + { + eventBus->OnEvent(EuroscopeTimerTickEvent{}); + } + + auto InternalSdk::FlightInformationRegions() const noexcept + -> std::shared_ptr + { + auto lock = std::lock_guard(mutex); + return flightInformationRegions; + } + + void InternalSdk::OnEvent(const FlightInformationRegionsUpdatedEvent& event) + { + auto lock = std::lock_guard(mutex); + flightInformationRegions = event.firs; + } + + auto InternalSdk::Events() const noexcept -> std::shared_ptr + { + auto lock = std::lock_guard(mutex); + return events; + } + + void InternalSdk::OnEvent(const EventsUpdatedEvent& event) + { + auto lock = std::lock_guard(mutex); + events = event.events; + } + + auto InternalSdk::FlowMeasures() const noexcept -> std::shared_ptr + { + auto lock = std::lock_guard(mutex); + return flowMeasures; + } + + void InternalSdk::OnEvent(const FlowMeasuresUpdatedEvent& eventType) + { + auto lock = std::lock_guard(mutex); + flowMeasures = eventType.flowMeasures; + } +}// namespace ECFMP::Plugin diff --git a/src/plugin/InternalSdk.h b/src/plugin/InternalSdk.h new file mode 100644 index 0000000..822b3a8 --- /dev/null +++ b/src/plugin/InternalSdk.h @@ -0,0 +1,58 @@ +#pragma once +#include "ECFMP/Sdk.h" +#include "ECFMP/SdkEvents.h" +#include "ECFMP/api/ElementCollectionTypes.h" +#include "ECFMP/eventbus/EventListener.h" +#include "InternalSdkEvents.h" +#include "api/InternalElementCollectionTypes.h" + +namespace ECFMP { + namespace Api { + class ApiDataScheduler; + } + namespace EventBus { + class InternalEventBus; + } +}// namespace ECFMP + +namespace ECFMP::Plugin { + + class InternalSdk : public Sdk, + public EventBus::EventListener, + public EventBus::EventListener, + public EventBus::EventListener + { + public: + InternalSdk(std::shared_ptr eventBus); + ~InternalSdk() override = default; + + [[nodiscard]] auto FlightInformationRegions() const noexcept + -> std::shared_ptr override; + [[nodiscard]] auto Events() const noexcept -> std::shared_ptr override; + [[nodiscard]] auto FlowMeasures() const noexcept -> std::shared_ptr override; + + [[nodiscard]] auto EventBus() const noexcept -> EventBus::EventBus& override; + void OnEuroscopeTimerTick() override; + void OnEvent(const FlightInformationRegionsUpdatedEvent& event) override; + void OnEvent(const EventsUpdatedEvent& eventType) override; + void OnEvent(const FlowMeasuresUpdatedEvent& eventType) override; + + void Destroy() override; + + private: + // The event bus for the SDK, which can be used to subscribe to events. + std::shared_ptr eventBus; + + // The flight information regions that are currently loaded. + std::shared_ptr flightInformationRegions; + + // The events that are currently loaded. + std::shared_ptr events; + + // The flow measures that are currently loaded. + std::shared_ptr flowMeasures; + + // Locks the class for returning the flight information regions. + mutable std::mutex mutex; + }; +}// namespace ECFMP::Plugin diff --git a/src/plugin/InternalSdkEvents.h b/src/plugin/InternalSdkEvents.h new file mode 100644 index 0000000..d4f3e74 --- /dev/null +++ b/src/plugin/InternalSdkEvents.h @@ -0,0 +1,22 @@ +#pragma once +#include "api/InternalElementCollectionTypes.h" + +namespace ECFMP::Plugin { + + // API + using ApiDataDownloadRequiredEvent = struct ApiDataDownloadRequiredEvent { + }; + + // FIRs + using FlightInformationRegionsUpdatedEvent = struct FlightInformationRegionsUpdatedEvent { + std::shared_ptr firs; + }; + + using InternalFlowMeasuresUpdatedEvent = struct InternalFlowMeasuresUpdatedEvent { + std::shared_ptr flowMeasures; + }; + + // EuroScope + using EuroscopeTimerTickEvent = struct EuroscopeTimerTickEvent { + }; +}// namespace ECFMP::Plugin diff --git a/src/plugin/SdkFactory.cpp b/src/plugin/SdkFactory.cpp index 53e2a20..11ae257 100644 --- a/src/plugin/SdkFactory.cpp +++ b/src/plugin/SdkFactory.cpp @@ -1,8 +1,10 @@ #include "ECFMP/SdkFactory.h" -#include "ConcreteSdk.h" +#include "ECFMP/SdkEvents.h" #include "ECFMP/flightinformationregion/FlightInformationRegion.h" #include "ECFMP/http/HttpClient.h" #include "ECFMP/log/Logger.h" +#include "InternalSdk.h" +#include "InternalSdkEvents.h" #include "api/ApiDataDownloadedEvent.h" #include "api/ApiDataDownloader.h" #include "api/ApiDataParser.h" @@ -12,29 +14,45 @@ #include "api/FlowMeasureDataParser.h" #include "api/FlowMeasureFilterParser.h" #include "api/FlowMeasureMeasureParser.h" -#include "eventbus/InternalEventBus.h" +#include "eventbus/InternalEventBusFactory.h" +#include "flowmeasure/FlowMeasureStatusUpdates.h" #include "log/LogDecorator.h" #include "log/NullLogger.h" namespace ECFMP::Plugin { struct SdkFactory::SdkFactoryImpl { - auto CreateApiDataScheduler() -> std::shared_ptr + void CreateApiDataScheduler() { // Set up data listeners - // TODO: Test for checking the registration. auto apiDataParser = std::make_shared( - std::make_shared(GetLogger()), - std::make_shared(GetLogger()), + std::make_shared(GetLogger(), GetEventBus()), + std::make_shared(GetLogger(), GetEventBus()), std::make_shared( std::make_unique(GetLogger()), - std::make_unique(GetLogger()), GetLogger() + std::make_unique(GetLogger()), GetLogger(), GetEventBus() ), GetLogger() ); - GetEventBus()->Subscribe(apiDataParser); + GetEventBus()->SubscribeAsync(apiDataParser); - return std::make_shared( - std::make_unique(std::move(httpClient), GetEventBus(), GetLogger()) + // Set up data downloader + auto dataDownloader = + std::make_shared(std::move(httpClient), GetEventBus(), GetLogger()); + GetEventBus()->SubscribeAsync(dataDownloader); + + // Set up data scheduler + auto dataScheduler = std::make_shared(GetEventBus()); + GetEventBus()->SubscribeSync(dataScheduler); + } + + void RegisterEventListeners() + { + // Create the API data scheduler + this->CreateApiDataScheduler(); + + // Flow measure status updates - powers the event-driven nature. + GetEventBus()->SubscribeAsync( + std::make_shared(GetEventBus(), GetLogger()) ); } @@ -59,7 +77,7 @@ namespace ECFMP::Plugin { auto GetEventBus() -> std::shared_ptr { if (!eventBus) { - eventBus = std::make_shared(); + eventBus = EventBus::MakeEventBus(); } return eventBus; @@ -108,14 +126,21 @@ namespace ECFMP::Plugin { return *this; } - auto SdkFactory::Instance() -> std::unique_ptr + auto SdkFactory::Instance() -> std::shared_ptr { if (!impl->httpClient) { throw SdkConfigurationException("No http client provided"); } - return std::make_unique( - std::shared_ptr(impl->CreateApiDataScheduler()), impl->GetEventBus() - ); + // Set up other event listeners + impl->RegisterEventListeners(); + + // Set up the SDK + auto sdk = std::make_shared(impl->GetEventBus()); + impl->GetEventBus()->SubscribeAsync(sdk); + impl->GetEventBus()->SubscribeAsync(sdk); + impl->GetEventBus()->SubscribeAsync(sdk); + + return std::move(sdk); } }// namespace ECFMP::Plugin diff --git a/src/time/Clock.cpp b/src/time/Clock.cpp new file mode 100644 index 0000000..7ba53c6 --- /dev/null +++ b/src/time/Clock.cpp @@ -0,0 +1,25 @@ +#include "Clock.h" + +namespace ECFMP::Time { + + std::chrono::system_clock::time_point testNow = std::chrono::system_clock::time_point::min(); + + auto TimeNow() -> std::chrono::system_clock::time_point + { + if (testNow != std::chrono::system_clock::time_point::min()) { + return testNow; + } + + return std::chrono::system_clock::now(); + } + + void SetTestNow(const std::chrono::system_clock::time_point& now) + { + testNow = now; + } + + void UnsetTestNow() + { + testNow = std::chrono::system_clock::time_point::min(); + } +}// namespace ECFMP::Time diff --git a/src/time/Clock.h b/src/time/Clock.h new file mode 100644 index 0000000..a586873 --- /dev/null +++ b/src/time/Clock.h @@ -0,0 +1,7 @@ +#pragma once + +namespace ECFMP::Time { + [[nodiscard]] auto TimeNow() -> std::chrono::system_clock::time_point; + void SetTestNow(const std::chrono::system_clock::time_point& now); + void UnsetTestNow(); +}// namespace ECFMP::Time diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 11d4c00..b705111 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,7 +15,7 @@ set(test__api api/ApiDataDownloaderTest.cpp api/FlightInformationRegionDataParserTest.cpp api/ApiElementIteratorTest.cpp api/InternalApiElementCollectionTest.cpp api/FlowMeasureMeasureParserTest.cpp api/FlowMeasureFilterParserTest.cpp - api/ApiDataParserTest.cpp api/InternalStringIdentifierApiElementCollectionTest.cpp) + api/ApiDataParserTest.cpp api/InternalStringIdentifierApiElementCollectionTest.cpp api/ApiDataSchedulerTest.cpp) set(test__date @@ -27,8 +27,8 @@ set(test__event api/EventDataParserTest.cpp) set(test__eventbus - eventbus/InternalEventStreamTest.cpp - eventbus/InternalEventBusTest.cpp) + eventbus/EventStreamTest.cpp + eventbus/InternalEventBusTest.cpp ../src/eventbus/InternalEventBusFactory.h eventbus/EventDispatcherFactoryTest.cpp) set(test__flightinformationregion flightinformationregion/ConcreteFlightInformationRegionTest.cpp @@ -42,7 +42,7 @@ set(test__flowmeasure flowmeasure/ConcreteMeasureTest.cpp flowmeasure/ConcreteFlowMeasureTest.cpp flowmeasure/ConcreteFlowMeasureFiltersTest.cpp - api/FlowMeasureDataParserTest.cpp flowmeasure/ConcreteRangeToDestinationFilterTest.cpp) + api/FlowMeasureDataParserTest.cpp flowmeasure/ConcreteRangeToDestinationFilterTest.cpp flowmeasure/CanonicalFlowMeasureInfoTest.cpp flowmeasure/FlowMeasureStatusUpdatesTest.cpp) set(test__log log/LogDecoratorTest.cpp @@ -60,7 +60,7 @@ set(test__pch set(test__plugin plugin/SdkFactoryTest.cpp - plugin/ConcreteSdkTest.cpp) + plugin/InternalSdkTest.cpp) set(test__other main.cpp diff --git a/test/api/ApiDataDownloaderTest.cpp b/test/api/ApiDataDownloaderTest.cpp index 6be738b..18171f2 100644 --- a/test/api/ApiDataDownloaderTest.cpp +++ b/test/api/ApiDataDownloaderTest.cpp @@ -1,6 +1,7 @@ #include "api/ApiDataDownloader.h" #include "ECFMP/http/HttpClient.h" #include "api/ApiDataDownloadedEvent.h" +#include "eventbus/InternalEventBusFactory.h" #include "mock/MockHttpClient.h" #include "mock/MockLogger.h" #include "nlohmann/json.hpp" @@ -35,12 +36,11 @@ namespace ECFMPTest::Api { public: ApiDataDownloaderTest() : mockEventHandler(std::make_shared(nlohmann::json{{"abc", "def"}})), - eventBus(std::make_shared()), - logger(std::make_shared>()), + eventBus(ECFMP::EventBus::MakeEventBus()), logger(std::make_shared>()), httpClient(std::make_unique>()) { // Add mock listener to event bus - eventBus->Subscribe(mockEventHandler); + eventBus->SubscribeSync(mockEventHandler); } [[nodiscard]] auto MakeDownloader() -> ECFMP::Api::ApiDataDownloader @@ -64,7 +64,7 @@ namespace ECFMPTest::Api { EXPECT_CALL(*logger, Error).Times(1); - MakeDownloader().DownloadData(); + MakeDownloader().OnEvent({}); EXPECT_EQ(0, mockEventHandler->GetCallCount()); } @@ -77,7 +77,7 @@ namespace ECFMPTest::Api { EXPECT_CALL(*logger, Error).Times(1); - MakeDownloader().DownloadData(); + MakeDownloader().OnEvent({}); EXPECT_EQ(0, mockEventHandler->GetCallCount()); } @@ -90,7 +90,7 @@ namespace ECFMPTest::Api { EXPECT_CALL(*logger, Error).Times(1); - MakeDownloader().DownloadData(); + MakeDownloader().OnEvent({}); EXPECT_EQ(0, mockEventHandler->GetCallCount()); } @@ -105,9 +105,8 @@ namespace ECFMPTest::Api { EXPECT_CALL(*logger, Error).Times(0); - MakeDownloader().DownloadData(); + MakeDownloader().OnEvent({}); - eventBus->ProcessPendingEvents(); EXPECT_EQ(1, mockEventHandler->GetCallCount()); } }// namespace ECFMPTest::Api diff --git a/test/api/ApiDataParserTest.cpp b/test/api/ApiDataParserTest.cpp index 1072624..1b791e4 100644 --- a/test/api/ApiDataParserTest.cpp +++ b/test/api/ApiDataParserTest.cpp @@ -7,9 +7,7 @@ #include "api/InternalElementCollectionTypes.h" #include "log/NullLogger.h" #include "gmock/gmock.h" -#include #include -#include namespace ECFMPTest::Api { class MockEventDataParser : public ECFMP::Api::EventDataParserInterface diff --git a/test/api/ApiDataSchedulerTest.cpp b/test/api/ApiDataSchedulerTest.cpp new file mode 100644 index 0000000..9cf2475 --- /dev/null +++ b/test/api/ApiDataSchedulerTest.cpp @@ -0,0 +1,67 @@ +#include "api/ApiDataScheduler.h" +#include "ECFMP/eventbus/EventListener.h" +#include "eventbus/InternalEventBusFactory.h" +#include "time/Clock.h" + +namespace ECFMPTest::Api { + + class ApiDataDownloadRequiredEventListener + : public ECFMP::EventBus::EventListener + { + public: + void OnEvent(const ECFMP::Plugin::ApiDataDownloadRequiredEvent& eventType) override + { + callCount++; + } + + int callCount = 0; + }; + + class ApiDataSchedulerTest : public testing::Test + { + public: + ApiDataSchedulerTest() + : listener(std::make_shared()), + eventBus(ECFMP::EventBus::MakeEventBus()), + apiDataScheduler(std::make_shared(eventBus)) + { + eventBus->SubscribeSync(listener); + } + + void TearDown() override + { + ECFMP::Time::UnsetTestNow(); + } + + std::shared_ptr listener = + std::make_shared(); + std::shared_ptr eventBus; + std::shared_ptr apiDataScheduler; + }; + + TEST_F(ApiDataSchedulerTest, ItPerformsUpdateOnFirstTick) + { + apiDataScheduler->OnEvent({}); + EXPECT_EQ(1, listener->callCount); + } + + TEST_F(ApiDataSchedulerTest, ItPerformsUpdateIfOneNotPerformedFor90Seconds) + { + ECFMP::Time::SetTestNow(std::chrono::system_clock::now()); + apiDataScheduler->OnEvent({}); + ECFMP::Time::SetTestNow(ECFMP::Time::TimeNow() + std::chrono::seconds(91)); + apiDataScheduler->OnEvent({}); + + EXPECT_EQ(2, listener->callCount); + } + + TEST_F(ApiDataSchedulerTest, ItDoesntPerformUpdateIfOnePerformedRecently) + { + ECFMP::Time::SetTestNow(std::chrono::system_clock::now()); + apiDataScheduler->OnEvent({}); + ECFMP::Time::SetTestNow(ECFMP::Time::TimeNow() + std::chrono::seconds(89)); + apiDataScheduler->OnEvent({}); + + EXPECT_EQ(1, listener->callCount); + } +}// namespace ECFMPTest::Api diff --git a/test/api/EventDataParserTest.cpp b/test/api/EventDataParserTest.cpp index 935a681..3c2b6cb 100644 --- a/test/api/EventDataParserTest.cpp +++ b/test/api/EventDataParserTest.cpp @@ -1,17 +1,46 @@ #include "api/EventDataParser.h" +#include "ECFMP/SdkEvents.h" #include "ECFMP/flightinformationregion/FlightInformationRegion.h" #include "api/InternalElementCollectionTypes.h" #include "date/ParseDateStrings.h" +#include "eventbus/InternalEventBusFactory.h" #include "flightinformationregion/ConcreteFlightInformationRegion.h" #include "mock/MockLogger.h" #include "nlohmann/json.hpp" #include "nlohmann/json_fwd.hpp" namespace ECFMPTest::Api { + + class MockEventsUpdatedEventHandler : public ECFMP::EventBus::EventListener + { + public: + explicit MockEventsUpdatedEventHandler(int expectedItemCount) : expectedItemCount(expectedItemCount) + {} + + void OnEvent(const ECFMP::Plugin::EventsUpdatedEvent& event) override + { + callCount++; + EXPECT_EQ(expectedItemCount, event.events->Count()); + } + + [[nodiscard]] auto GetCallCount() const -> int + { + return callCount; + } + + private: + int expectedItemCount; + + int callCount = 0; + }; + class EventDataParserTest : public testing::Test { public: - EventDataParserTest() : mockLogger(std::make_shared>()), parser(mockLogger) + EventDataParserTest() + : mockEventHandler(std::make_shared(2)), + eventBus(ECFMP::EventBus::MakeEventBus()), + mockLogger(std::make_shared>()), parser(mockLogger, eventBus) { firs.Add(std::make_shared( 1, "EGTT", "London" @@ -19,8 +48,12 @@ namespace ECFMPTest::Api { firs.Add(std::make_shared( 2, "EGPX", "Scottish" )); + + eventBus->SubscribeSync(mockEventHandler); } + std::shared_ptr mockEventHandler; + std::shared_ptr eventBus; ECFMP::Api::InternalFlightInformationRegionCollection firs; std::shared_ptr> mockLogger; ECFMP::Api::EventDataParser parser; @@ -29,16 +62,19 @@ namespace ECFMPTest::Api { TEST_F(EventDataParserTest, ItReturnsNullptrIfDataNotObject) { EXPECT_EQ(nullptr, parser.ParseEvents(nlohmann::json::array(), firs)); + EXPECT_EQ(0, mockEventHandler->GetCallCount()); } TEST_F(EventDataParserTest, ItReturnsNullptrIfIfDataDoesNotContainEvents) { EXPECT_EQ(nullptr, parser.ParseEvents(nlohmann::json{{"not_events", "abc"}}, firs)); + EXPECT_EQ(0, mockEventHandler->GetCallCount()); } TEST_F(EventDataParserTest, ItReturnsNullptrIfIfEventsNotArray) { EXPECT_EQ(nullptr, parser.ParseEvents(nlohmann::json{{"events", "abc"}}, firs)); + EXPECT_EQ(0, mockEventHandler->GetCallCount()); } TEST_F(EventDataParserTest, ItParsesEvents) @@ -123,6 +159,8 @@ namespace ECFMPTest::Api { EXPECT_EQ(1203536, event2Participant2->Cid()); EXPECT_EQ("EGCC", event2Participant2->OriginAirport()); EXPECT_EQ("EGPH", event2Participant2->DestinationAirport()); + + EXPECT_EQ(1, mockEventHandler->GetCallCount()); } TEST_F(EventDataParserTest, ItParsesEventsWithNoParticipants) @@ -163,6 +201,8 @@ namespace ECFMPTest::Api { EXPECT_EQ(ECFMP::Date::TimePointFromDateString("2022-04-17T13:16:00Z"), event2->Start()); EXPECT_EQ(ECFMP::Date::TimePointFromDateString("2022-04-18T13:17:00Z"), event2->End()); EXPECT_EQ("def", event2->VatcanCode()); + + EXPECT_EQ(1, mockEventHandler->GetCallCount()); } TEST_F(EventDataParserTest, ItParsesEventsWithNoVatcanCode) @@ -227,11 +267,12 @@ namespace ECFMPTest::Api { EXPECT_EQ(ECFMP::Date::TimePointFromDateString("2022-04-17T13:16:00Z"), event2->Start()); EXPECT_EQ(ECFMP::Date::TimePointFromDateString("2022-04-18T13:17:00Z"), event2->End()); EXPECT_EQ("", event2->VatcanCode()); + + EXPECT_EQ(1, mockEventHandler->GetCallCount()); } TEST_F(EventDataParserTest, ItParsesEventsWithNoParticipantOriginOrDestination) { - // TODO: Add event participation and fix this test const auto eventData = nlohmann::json::array( {{{"id", 1}, {"flight_information_region_id", 1}, @@ -308,6 +349,8 @@ namespace ECFMPTest::Api { EXPECT_EQ(1203536, event2Participants[1]->Cid()); EXPECT_EQ("", event2Participants[1]->OriginAirport()); EXPECT_EQ("", event2Participants[1]->DestinationAirport()); + + EXPECT_EQ(1, mockEventHandler->GetCallCount()); } using BadEventDataCheck = struct BadEventDataCheck { @@ -322,7 +365,9 @@ namespace ECFMPTest::Api { { public: EventDataParserBadDataTest() - : mockLogger(std::make_shared>()), parser(mockLogger) + : mockEventHandler(std::make_shared(1)), + eventBus(ECFMP::EventBus::MakeEventBus()), + mockLogger(std::make_shared>()), parser(mockLogger, eventBus) { firs.Add(std::make_shared( 1, "EGTT", "London" @@ -330,8 +375,12 @@ namespace ECFMPTest::Api { firs.Add(std::make_shared( 2, "EGPX", "Scottish" )); + + eventBus->SubscribeSync(mockEventHandler); } + std::shared_ptr mockEventHandler; + std::shared_ptr eventBus; ECFMP::Api::InternalFlightInformationRegionCollection firs; std::shared_ptr> mockLogger; ECFMP::Api::EventDataParser parser; @@ -830,5 +879,8 @@ namespace ECFMPTest::Api { const auto event1 = events->Get(1); EXPECT_NE(nullptr, event1); EXPECT_EQ(1, event1->Id()); + + // Check event handler is called + EXPECT_EQ(1, mockEventHandler->GetCallCount()); } }// namespace ECFMPTest::Api diff --git a/test/api/FlightInformationRegionDataParserTest.cpp b/test/api/FlightInformationRegionDataParserTest.cpp index 993c772..700fd64 100644 --- a/test/api/FlightInformationRegionDataParserTest.cpp +++ b/test/api/FlightInformationRegionDataParserTest.cpp @@ -1,17 +1,53 @@ #include "api/FlightInformationRegionDataParser.h" -#include "ECFMP/flightinformationregion/FlightInformationRegion.h" +#include "ECFMP/eventbus/EventListener.h" #include "api/InternalElementCollectionTypes.h" +#include "eventbus/InternalEventBusFactory.h" #include "mock/MockLogger.h" #include "nlohmann/json.hpp" +#include "plugin/InternalSdkEvents.h" +#include namespace ECFMPTest::Api { - class FlightInformationRegionDataParserTest : public testing::Test + + class MockFlightInformationRegionsUpdatedEventHandler + : public ECFMP::EventBus::EventListener { public: - FlightInformationRegionDataParserTest() - : mockLogger(std::make_shared>()), parser(mockLogger) + explicit MockFlightInformationRegionsUpdatedEventHandler(int expectedItemCount) + : expectedItemCount(expectedItemCount) {} + void OnEvent(const ECFMP::Plugin::FlightInformationRegionsUpdatedEvent& event) override + { + callCount++; + EXPECT_EQ(expectedItemCount, event.firs->Count()); + } + + [[nodiscard]] auto GetCallCount() const -> int + { + return callCount; + } + + private: + int expectedItemCount; + + int callCount = 0; + }; + + class FlightInformationRegionDataParserTest : public testing::Test + { + public: + FlightInformationRegionDataParserTest() + : mockEventHandler(std::make_shared(2)), + eventBus(ECFMP::EventBus::MakeEventBus()), + mockLogger(std::make_shared>()), parser(mockLogger, eventBus) + { + // Add mock listener to event bus + eventBus->SubscribeSync(mockEventHandler); + } + + std::shared_ptr mockEventHandler; + std::shared_ptr eventBus; std::shared_ptr> mockLogger; ECFMP::Api::FlightInformationRegionDataParser parser; }; @@ -19,16 +55,19 @@ namespace ECFMPTest::Api { TEST_F(FlightInformationRegionDataParserTest, ItReturnsNullptrIfDataNotObject) { EXPECT_EQ(nullptr, parser.ParseFirs(nlohmann::json::array())); + EXPECT_EQ(0, mockEventHandler->GetCallCount()); } TEST_F(FlightInformationRegionDataParserTest, ItReturnsNullptrIfDataDoesNotContainFirs) { EXPECT_EQ(nullptr, parser.ParseFirs(nlohmann::json{{"not_flight_information_regions", "abc"}})); + EXPECT_EQ(0, mockEventHandler->GetCallCount()); } TEST_F(FlightInformationRegionDataParserTest, ItReturnsNullptrIfFirsNotArray) { EXPECT_EQ(nullptr, parser.ParseFirs(nlohmann::json{{"flight_information_regions", "abc"}})); + EXPECT_EQ(0, mockEventHandler->GetCallCount()); } TEST_F(FlightInformationRegionDataParserTest, ItParsesFirs) @@ -60,6 +99,9 @@ namespace ECFMPTest::Api { EXPECT_EQ(2, fir2->Id()); EXPECT_EQ("EGPX", fir2->Identifier()); EXPECT_EQ("Scottish", fir2->Name()); + + // Check event handler is called + EXPECT_EQ(1, mockEventHandler->GetCallCount()); } using BadFlightInformationRegionDataCheck = struct BadFlightInformationRegionDataCheck { @@ -75,9 +117,16 @@ namespace ECFMPTest::Api { { public: FlightInformationRegionDataParserBadDataTest() - : mockLogger(std::make_shared>()), parser(mockLogger) - {} - + : mockEventHandler(std::make_shared(1)), + eventBus(ECFMP::EventBus::MakeEventBus()), + mockLogger(std::make_shared>()), parser(mockLogger, eventBus) + { + // Add mock listener to event bus + eventBus->SubscribeSync(mockEventHandler); + } + + std::shared_ptr mockEventHandler; + std::shared_ptr eventBus; std::shared_ptr> mockLogger; ECFMP::Api::FlightInformationRegionDataParser parser; }; @@ -144,5 +193,8 @@ namespace ECFMPTest::Api { EXPECT_EQ(1, fir1->Id()); EXPECT_EQ("EGTT", fir1->Identifier()); EXPECT_EQ("London", fir1->Name()); + + // Check event handler is called + EXPECT_EQ(1, mockEventHandler->GetCallCount()); } }// namespace ECFMPTest::Api diff --git a/test/api/FlowMeasureDataParserTest.cpp b/test/api/FlowMeasureDataParserTest.cpp index cd44a0d..4887e05 100644 --- a/test/api/FlowMeasureDataParserTest.cpp +++ b/test/api/FlowMeasureDataParserTest.cpp @@ -1,4 +1,5 @@ #include "api/FlowMeasureDataParser.h" +#include "ECFMP/SdkEvents.h" #include "ECFMP/flowmeasure/FlowMeasure.h" #include "ECFMP/flowmeasure/Measure.h" #include "ECFMP/flowmeasure/MultipleLevelFilter.h" @@ -9,12 +10,14 @@ #include "api/InternalElementCollectionTypes.h" #include "date/ParseDateStrings.h" #include "event/ConcreteEvent.h" +#include "eventbus/InternalEventBusFactory.h" #include "flightinformationregion/ConcreteFlightInformationRegion.h" #include "flowmeasure/ConcreteAirportFilter.h" #include "flowmeasure/ConcreteFlowMeasureFilters.h" #include "flowmeasure/ConcreteMeasure.h" #include "mock/MockLogger.h" #include "nlohmann/json.hpp" +#include "plugin/InternalSdkEvents.h" namespace ECFMPTest::Api { @@ -43,6 +46,55 @@ namespace ECFMPTest::Api { ); }; + class MockFlowMeasuresUpdatedEventHandler + : public ECFMP::EventBus::EventListener + { + public: + explicit MockFlowMeasuresUpdatedEventHandler(int expectedItemCount) : expectedItemCount(expectedItemCount) + {} + + void OnEvent(const ECFMP::Plugin::FlowMeasuresUpdatedEvent& event) override + { + callCount++; + EXPECT_EQ(expectedItemCount, event.flowMeasures->Count()); + } + + [[nodiscard]] auto GetCallCount() const -> int + { + return callCount; + } + + private: + int expectedItemCount; + + int callCount = 0; + }; + + class MockInternalFlowMeasuresUpdatedEventHandler + : public ECFMP::EventBus::EventListener + { + public: + explicit MockInternalFlowMeasuresUpdatedEventHandler(int expectedItemCount) + : expectedItemCount(expectedItemCount) + {} + + void OnEvent(const ECFMP::Plugin::InternalFlowMeasuresUpdatedEvent& event) override + { + callCount++; + EXPECT_EQ(expectedItemCount, event.flowMeasures->Count()); + } + + [[nodiscard]] auto GetCallCount() const -> int + { + return callCount; + } + + private: + int expectedItemCount; + + int callCount = 0; + }; + struct FlowMeasureDataParserTestCase { std::string description; nlohmann::json data; @@ -67,8 +119,15 @@ namespace ECFMPTest::Api { filterParserRaw = filterParser.get(); auto measureParser = std::make_unique(); measureParserRaw = measureParser.get(); + + eventBus = ECFMP::EventBus::MakeEventBus(); + mockEventHandler = std::make_shared(1); + mockEventHandlerInternal = std::make_shared(1); + eventBus->SubscribeSync(mockEventHandler); + eventBus->SubscribeSync(mockEventHandlerInternal); + parser = std::make_unique( - std::move(filterParser), std::move(measureParser), std::make_shared() + std::move(filterParser), std::move(measureParser), std::make_shared(), eventBus ); firs.Add(std::make_shared( @@ -84,6 +143,9 @@ namespace ECFMPTest::Api { )); } + std::shared_ptr mockEventHandler; + std::shared_ptr mockEventHandlerInternal; + std::shared_ptr eventBus; MockFlowMeasureFilterParser* filterParserRaw; MockFlowMeasureMeasureParser* measureParserRaw; ECFMP::Api::InternalEventCollection events; @@ -143,6 +205,10 @@ namespace ECFMPTest::Api { ); EXPECT_EQ(testCase.expectedNotifiedFirIds, notifiedFirIds); + + // Check that the event was published + EXPECT_EQ(1, mockEventHandler->GetCallCount()); + EXPECT_EQ(1, mockEventHandlerInternal->GetCallCount()); } INSTANTIATE_TEST_SUITE_P( @@ -271,8 +337,15 @@ namespace ECFMPTest::Api { filterParserRaw = filterParser.get(); auto measureParser = std::make_unique(); measureParserRaw = measureParser.get(); + + eventBus = ECFMP::EventBus::MakeEventBus(); + mockEventHandler = std::make_shared(0); + mockEventHandlerInternal = std::make_shared(0); + eventBus->SubscribeSync(mockEventHandler); + eventBus->SubscribeSync(mockEventHandlerInternal); + parser = std::make_unique( - std::move(filterParser), std::move(measureParser), std::make_shared() + std::move(filterParser), std::move(measureParser), std::make_shared(), eventBus ); firs.Add(std::make_shared( @@ -288,6 +361,9 @@ namespace ECFMPTest::Api { )); } + std::shared_ptr mockEventHandler; + std::shared_ptr mockEventHandlerInternal; + std::shared_ptr eventBus; MockFlowMeasureFilterParser* filterParserRaw; MockFlowMeasureMeasureParser* measureParserRaw; ECFMP::Api::InternalEventCollection events; @@ -329,12 +405,15 @@ namespace ECFMPTest::Api { else { EXPECT_EQ(0, flowMeasures->Count()); } + + // Check that the event handler was called + EXPECT_EQ(1, mockEventHandler->GetCallCount()); + EXPECT_EQ(1, mockEventHandlerInternal->GetCallCount()); } INSTANTIATE_TEST_SUITE_P( FlowMeasureDataParserBadDataTest, FlowMeasureDataParserBadDataTest, testing::Values( - FlowMeasureDataParserBadDataTestCase{"data_not_object", nlohmann::json::array()}, FlowMeasureDataParserBadDataTestCase{ "missing_id", nlohmann::json{ @@ -707,4 +786,72 @@ namespace ECFMPTest::Api { return info.param.description; } ); + + class FlowMeasureDataParserBizarreDataTest : public testing::Test + { + public: + void SetUp() override + { + // Create the mock parsers + auto filterParser = std::make_unique(); + filterParserRaw = filterParser.get(); + auto measureParser = std::make_unique(); + measureParserRaw = measureParser.get(); + + eventBus = ECFMP::EventBus::MakeEventBus(); + mockEventHandler = std::make_shared(0); + mockEventHandlerInternal = std::make_shared(0); + eventBus->SubscribeSync(mockEventHandler); + eventBus->SubscribeSync(mockEventHandlerInternal); + + parser = std::make_unique( + std::move(filterParser), std::move(measureParser), std::make_shared(), eventBus + ); + + firs.Add(std::make_shared( + 1, "EGTT", "London" + )); + firs.Add(std::make_shared( + 2, "EGPX", "Scottish" + )); + + events.Add(std::make_shared( + 1, "Test event", now, plusOneHour, firs.Get(1), "abc", + std::vector>{} + )); + } + + std::shared_ptr mockEventHandler; + std::shared_ptr mockEventHandlerInternal; + std::shared_ptr eventBus; + MockFlowMeasureFilterParser* filterParserRaw; + MockFlowMeasureMeasureParser* measureParserRaw; + ECFMP::Api::InternalEventCollection events; + ECFMP::Api::InternalFlightInformationRegionCollection firs; + std::unique_ptr parser; + }; + + TEST_F(FlowMeasureDataParserBizarreDataTest, ItReturnsNullptrIfJsonNotObject) + { + auto result = parser->ParseFlowMeasures(nlohmann::json::array(), events, firs); + ASSERT_EQ(nullptr, result); + EXPECT_EQ(0, mockEventHandler->GetCallCount()); + EXPECT_EQ(0, mockEventHandlerInternal->GetCallCount()); + } + + TEST_F(FlowMeasureDataParserBizarreDataTest, ItReturnsNullptrIfJsonDoesntContainFlowMeasuresKey) + { + auto result = parser->ParseFlowMeasures(nlohmann::json{{"foo", "bar"}}, events, firs); + ASSERT_EQ(nullptr, result); + EXPECT_EQ(0, mockEventHandler->GetCallCount()); + EXPECT_EQ(0, mockEventHandlerInternal->GetCallCount()); + } + + TEST_F(FlowMeasureDataParserBizarreDataTest, ItReturnsNullptrIfFlowMeasuresKeyIsNotArray) + { + auto result = parser->ParseFlowMeasures(nlohmann::json{{"flow_measures", "bar"}}, events, firs); + ASSERT_EQ(nullptr, result); + EXPECT_EQ(0, mockEventHandler->GetCallCount()); + EXPECT_EQ(0, mockEventHandlerInternal->GetCallCount()); + } }// namespace ECFMPTest::Api diff --git a/test/eventbus/EventDispatcherFactoryTest.cpp b/test/eventbus/EventDispatcherFactoryTest.cpp new file mode 100644 index 0000000..e8646cc --- /dev/null +++ b/test/eventbus/EventDispatcherFactoryTest.cpp @@ -0,0 +1,83 @@ +#include "eventbus/EventDispatcherFactory.h" +#include "eventbus/PendingEuroscopeEvents.h" +#include "eventbus/SubscriptionFlags.h" + +namespace ECFMPTest::EventBus { + + class MockEventDispatcherListener : public ECFMP::EventBus::EventListener + { + public: + explicit MockEventDispatcherListener(int expectedEvent) : expectedEvent(expectedEvent) + {} + + void OnEvent(const int& event) override + { + callCount++; + EXPECT_EQ(event, expectedEvent); + } + + int expectedEvent; + int callCount = 0; + }; + + class EventDispatcherFactoryTest : public testing::Test + { + public: + EventDispatcherFactoryTest() + : pendingEvents(std::make_shared()), + eventDispatcherFactory(std::make_shared(pendingEvents)) + {} + + [[nodiscard]] static auto CreateListener() -> std::shared_ptr + { + return std::make_shared(5); + } + + static void WaitForEventsToProcess() + { + const auto start = std::chrono::system_clock::now(); + while (std::chrono::system_clock::now() - start < std::chrono::seconds(2)) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } + + std::shared_ptr pendingEvents; + std::shared_ptr eventDispatcherFactory; + }; + + TEST_F(EventDispatcherFactoryTest, ItMakesASynchronousDispatcher) + { + auto listener = CreateListener(); + auto dispatcher = + eventDispatcherFactory->CreateDispatcher(listener, ECFMP::EventBus::EventDispatchMode::Sync); + EXPECT_NE(nullptr, dispatcher); + EXPECT_EQ(0, listener->callCount); + dispatcher->Dispatch(5); + EXPECT_EQ(1, listener->callCount); + } + + TEST_F(EventDispatcherFactoryTest, ItMakesAEuroscopeDispatcher) + { + auto listener = CreateListener(); + auto dispatcher = + eventDispatcherFactory->CreateDispatcher(listener, ECFMP::EventBus::EventDispatchMode::Euroscope); + EXPECT_NE(nullptr, dispatcher); + EXPECT_EQ(0, listener->callCount); + dispatcher->Dispatch(5); + EXPECT_EQ(0, listener->callCount); + pendingEvents->OnEvent({}); + EXPECT_EQ(1, listener->callCount); + } + + TEST_F(EventDispatcherFactoryTest, ItMakesAnAsynchronousDispatcher) + { + auto listener = CreateListener(); + auto dispatcher = + eventDispatcherFactory->CreateDispatcher(listener, ECFMP::EventBus::EventDispatchMode::Async); + EXPECT_NE(nullptr, dispatcher); + EXPECT_EQ(0, listener->callCount); + dispatcher->Dispatch(5); + WaitForEventsToProcess(); + EXPECT_EQ(1, listener->callCount); + } +}// namespace ECFMPTest::EventBus diff --git a/test/eventbus/InternalEventStreamTest.cpp b/test/eventbus/EventStreamTest.cpp similarity index 52% rename from test/eventbus/InternalEventStreamTest.cpp rename to test/eventbus/EventStreamTest.cpp index 074ac06..8cc6a9c 100644 --- a/test/eventbus/InternalEventStreamTest.cpp +++ b/test/eventbus/EventStreamTest.cpp @@ -1,5 +1,7 @@ -#include "eventbus/InternalEventStream.h" +#include "eventbus/EventStream.h" #include "ECFMP/eventbus/EventListener.h" +#include "eventbus/SubscriptionFlags.h" +#include "eventbus/SynchronousEventDispatcher.h" namespace ECFMP::EventBus { @@ -38,72 +40,67 @@ namespace ECFMP::EventBus { bool shouldProcess; }; - class InternalEventStreamTest : public testing::Test + class EventStreamTest : public testing::Test { public: - InternalEventStream eventStream; + EventStream eventStream; }; - TEST_F(InternalEventStreamTest, OnEventWithNoSubscriptionsDoesNotThrow) + TEST_F(EventStreamTest, OnEventWithNoSubscriptionsDoesNotThrow) { ASSERT_NO_THROW(eventStream.OnEvent(1)); } - TEST_F(InternalEventStreamTest, ItThrowsOnSubscribeIfListenerIsNull) - { - ASSERT_THROW(eventStream.Subscribe(nullptr), std::invalid_argument); - } - - TEST_F(InternalEventStreamTest, ItThrowsOnSubscribeOnceIfListenerIsNull) - { - ASSERT_THROW(eventStream.SubscribeOnce(nullptr), std::invalid_argument); - } - - TEST_F(InternalEventStreamTest, OnEventWithOneSubscriptionCallsListener) + TEST_F(EventStreamTest, OnEventWithOneSubscriptionCallsListener) { auto listener = std::make_shared(1); - eventStream.Subscribe(listener); + auto dispatcher = std::make_shared>(listener); + eventStream.Subscribe({dispatcher, listener, nullptr, {ECFMP::EventBus::EventDispatchMode::Sync, false}}); eventStream.OnEvent(1); EXPECT_EQ(1, listener->callCount); } - TEST_F(InternalEventStreamTest, OnEventWithOneSubscriptionCallsListenerMultipleTimes) + TEST_F(EventStreamTest, OnEventWithOneSubscriptionCallsListenerMultipleTimes) { auto listener = std::make_shared(1); - eventStream.Subscribe(listener); + auto dispatcher = std::make_shared>(listener); + eventStream.Subscribe({dispatcher, listener, nullptr, {ECFMP::EventBus::EventDispatchMode::Sync, false}}); eventStream.OnEvent(1); eventStream.OnEvent(1); EXPECT_EQ(2, listener->callCount); } - TEST_F(InternalEventStreamTest, OnEventWithOneSubscriptionOnceCallsListenerOnce) + TEST_F(EventStreamTest, OnEventWithOneSubscriptionOnceCallsListenerOnce) { auto listener = std::make_shared(1); - eventStream.SubscribeOnce(listener); + auto dispatcher = std::make_shared>(listener); + eventStream.Subscribe({dispatcher, listener, nullptr, {ECFMP::EventBus::EventDispatchMode::Sync, true}}); eventStream.OnEvent(1); eventStream.OnEvent(1); EXPECT_EQ(1, listener->callCount); } - TEST_F(InternalEventStreamTest, OnEventWithOneSubscriptionAndFilterCallsListener) + TEST_F(EventStreamTest, OnEventWithOneSubscriptionAndFilterCallsListener) { auto listener = std::make_shared(1); + auto dispatcher = std::make_shared>(listener); auto filter = std::make_shared(1, true); - eventStream.Subscribe(listener, filter); + eventStream.Subscribe({dispatcher, listener, filter, {ECFMP::EventBus::EventDispatchMode::Sync, false}}); eventStream.OnEvent(1); EXPECT_EQ(1, listener->callCount); EXPECT_EQ(1, filter->callCount); } - TEST_F(InternalEventStreamTest, OnEventWithOneSubscriptionAndFilterCallsListenerMultipleTimes) + TEST_F(EventStreamTest, OnEventWithOneSubscriptionAndFilterCallsListenerMultipleTimes) { auto listener = std::make_shared(1); + auto dispatcher = std::make_shared>(listener); auto filter = std::make_shared(1, true); - eventStream.Subscribe(listener, filter); + eventStream.Subscribe({dispatcher, listener, filter, {ECFMP::EventBus::EventDispatchMode::Sync, false}}); eventStream.OnEvent(1); eventStream.OnEvent(1); @@ -111,22 +108,24 @@ namespace ECFMP::EventBus { EXPECT_EQ(2, filter->callCount); } - TEST_F(InternalEventStreamTest, OnEventWithOneSubscriptionAndFilterDoesNotCallListener) + TEST_F(EventStreamTest, OnEventWithOneSubscriptionAndFilterDoesNotCallListener) { auto listener = std::make_shared(1); + auto dispatcher = std::make_shared>(listener); auto filter = std::make_shared(1, false); - eventStream.Subscribe(listener, filter); + eventStream.Subscribe({dispatcher, listener, filter, {ECFMP::EventBus::EventDispatchMode::Sync, false}}); eventStream.OnEvent(1); EXPECT_EQ(0, listener->callCount); EXPECT_EQ(1, filter->callCount); } - TEST_F(InternalEventStreamTest, OnEventWithOneSubscriptionAndFilterDoesNotCallListenerMultipleTimes) + TEST_F(EventStreamTest, OnEventWithOneSubscriptionAndFilterDoesNotCallListenerMultipleTimes) { auto listener = std::make_shared(1); + auto dispatcher = std::make_shared>(listener); auto filter = std::make_shared(1, false); - eventStream.Subscribe(listener, filter); + eventStream.Subscribe({dispatcher, listener, filter, {ECFMP::EventBus::EventDispatchMode::Sync, false}}); eventStream.OnEvent(1); eventStream.OnEvent(1); @@ -134,11 +133,12 @@ namespace ECFMP::EventBus { EXPECT_EQ(2, filter->callCount); } - TEST_F(InternalEventStreamTest, OnEventWithOneOnceSubscriptionAndFilterCallsListenerOnce) + TEST_F(EventStreamTest, OnEventWithOneOnceSubscriptionAndFilterCallsListenerOnce) { auto listener = std::make_shared(1); + auto dispatcher = std::make_shared>(listener); auto filter = std::make_shared(1, true); - eventStream.SubscribeOnce(listener, filter); + eventStream.Subscribe({dispatcher, listener, filter, {ECFMP::EventBus::EventDispatchMode::Sync, true}}); eventStream.OnEvent(1); eventStream.OnEvent(1); @@ -146,11 +146,12 @@ namespace ECFMP::EventBus { EXPECT_EQ(1, filter->callCount); } - TEST_F(InternalEventStreamTest, OnEventWithOneOnceSubscriptionAndFilterDoesNotCallListenerOnce) + TEST_F(EventStreamTest, OnEventWithOneOnceSubscriptionAndFilterDoesNotCallListenerOnce) { auto listener = std::make_shared(1); + auto dispatcher = std::make_shared>(listener); auto filter = std::make_shared(1, false); - eventStream.SubscribeOnce(listener, filter); + eventStream.Subscribe({dispatcher, listener, filter, {ECFMP::EventBus::EventDispatchMode::Sync, true}}); eventStream.OnEvent(1); eventStream.OnEvent(1); diff --git a/test/eventbus/InternalEventBusTest.cpp b/test/eventbus/InternalEventBusTest.cpp index c39c103..6ce749d 100644 --- a/test/eventbus/InternalEventBusTest.cpp +++ b/test/eventbus/InternalEventBusTest.cpp @@ -1,6 +1,9 @@ -#include "eventbus/InternalEventBus.h" #include "ECFMP/eventbus/EventFilter.h" #include "ECFMP/eventbus/EventListener.h" +#include "eventbus/InternalEventBusFactory.h" +#include "plugin/InternalSdkEvents.h" +#include +#include namespace ECFMPTest::EvenBus { @@ -42,68 +45,195 @@ namespace ECFMPTest::EvenBus { class InternalEventBusTest : public ::testing::Test { public: - ECFMP::EventBus::InternalEventBus eventBus; + InternalEventBusTest() : eventBus(ECFMP::EventBus::MakeEventBus()) + {} + + static void WaitForEventsToProcess() + { + const auto start = std::chrono::system_clock::now(); + while (std::chrono::system_clock::now() - start < std::chrono::seconds(2)) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } + + std::shared_ptr eventBus; }; TEST_F(InternalEventBusTest, OnEventWithNoSubscriptionsDoesNotThrow) { - ASSERT_NO_THROW(eventBus.OnEvent(1)); + ASSERT_NO_THROW(eventBus->OnEvent(1)); + } + + TEST_F(InternalEventBusTest, ItThrowsOnSubscribeEuroscopeIfListenerIsNull) + { + ASSERT_THROW(eventBus->Subscribe(nullptr), std::invalid_argument); + } + + TEST_F(InternalEventBusTest, ItThrowsOnSubscribeOnceEuroscopeIfListenerIsNull) + { + ASSERT_THROW(eventBus->SubscribeOnce(nullptr), std::invalid_argument); + } + + TEST_F(InternalEventBusTest, ItSubscribesAListenerEuroscopeForMultipleCalls) + { + auto listener = std::make_shared(1); + eventBus->Subscribe(listener); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent({}); + EXPECT_EQ(listener->callCount, 3); + } + + TEST_F(InternalEventBusTest, ItSubscribesAListenerEuroscopeWithFilterForMultipleCalls) + { + auto listener = std::make_shared(1); + auto filter = std::make_shared(1, true); + eventBus->Subscribe(listener, filter); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent({}); + EXPECT_EQ(listener->callCount, 3); + EXPECT_EQ(filter->callCount, 3); + } + + TEST_F(InternalEventBusTest, ItSubscribesAListenerEuroscopeOnce) + { + auto listener = std::make_shared(1); + eventBus->SubscribeOnce(listener); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent({}); + EXPECT_EQ(listener->callCount, 1); + } + + TEST_F(InternalEventBusTest, ItSubscribesAListenerEuroscopeOnceWithFilter) + { + auto listener = std::make_shared(1); + auto filter = std::make_shared(1, true); + eventBus->SubscribeOnce(listener, filter); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent({}); + EXPECT_EQ(listener->callCount, 1); + EXPECT_EQ(filter->callCount, 1); } - TEST_F(InternalEventBusTest, ItThrowsOnSubscribeIfListenerIsNull) + TEST_F(InternalEventBusTest, ItThrowsOnSubscribeSyncIfListenerIsNull) { - ASSERT_THROW(eventBus.Subscribe(nullptr), std::invalid_argument); + ASSERT_THROW(eventBus->SubscribeSync(nullptr), std::invalid_argument); } - TEST_F(InternalEventBusTest, ItThrowsOnSubscribeOnceIfListenerIsNull) + TEST_F(InternalEventBusTest, ItThrowsOnSubscribeOnceSyncIfListenerIsNull) { - ASSERT_THROW(eventBus.SubscribeOnce(nullptr), std::invalid_argument); + ASSERT_THROW(eventBus->SubscribeSyncOnce(nullptr), std::invalid_argument); } - TEST_F(InternalEventBusTest, ItSubscribesAListenerForMultipleCalls) + TEST_F(InternalEventBusTest, ItSubscribesAListenerSyncForMultipleCalls) { auto listener = std::make_shared(1); - eventBus.Subscribe(listener); - eventBus.OnEvent(1); - eventBus.OnEvent(1); - eventBus.OnEvent(1); - eventBus.ProcessPendingEvents(); + eventBus->SubscribeSync(listener); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); EXPECT_EQ(listener->callCount, 3); } - TEST_F(InternalEventBusTest, ItSubscribesAListenerWithFilterForMultipleCalls) + TEST_F(InternalEventBusTest, ItSubscribesAListenerSyncWithFilterForMultipleCalls) { auto listener = std::make_shared(1); auto filter = std::make_shared(1, true); - eventBus.Subscribe(listener, filter); - eventBus.OnEvent(1); - eventBus.OnEvent(1); - eventBus.OnEvent(1); - eventBus.ProcessPendingEvents(); + eventBus->SubscribeSync(listener, filter); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); EXPECT_EQ(listener->callCount, 3); EXPECT_EQ(filter->callCount, 3); } - TEST_F(InternalEventBusTest, ItSubscribesAListenerOnce) + TEST_F(InternalEventBusTest, ItSubscribesAListenerSyncOnce) { auto listener = std::make_shared(1); - eventBus.SubscribeOnce(listener); - eventBus.OnEvent(1); - eventBus.OnEvent(1); - eventBus.OnEvent(1); - eventBus.ProcessPendingEvents(); + eventBus->SubscribeSyncOnce(listener); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); EXPECT_EQ(listener->callCount, 1); } - TEST_F(InternalEventBusTest, ItSubscribesAListenerOnceWithFilter) + TEST_F(InternalEventBusTest, ItSubscribesAListenerSyncOnceWithFilter) { auto listener = std::make_shared(1); auto filter = std::make_shared(1, true); - eventBus.SubscribeOnce(listener, filter); - eventBus.OnEvent(1); - eventBus.OnEvent(1); - eventBus.OnEvent(1); - eventBus.ProcessPendingEvents(); + eventBus->SubscribeSyncOnce(listener, filter); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + EXPECT_EQ(listener->callCount, 1); + EXPECT_EQ(filter->callCount, 1); + } + + TEST_F(InternalEventBusTest, ItThrowsOnSubscribeAsyncIfListenerIsNull) + { + ASSERT_THROW(eventBus->SubscribeAsync(nullptr), std::invalid_argument); + } + + TEST_F(InternalEventBusTest, ItThrowsOnSubscribeOnceAsyncIfListenerIsNull) + { + ASSERT_THROW(eventBus->SubscribeAsyncOnce(nullptr), std::invalid_argument); + } + + TEST_F(InternalEventBusTest, ItSubscribesAListenerAsyncForMultipleCalls) + { + auto listener = std::make_shared(1); + eventBus->SubscribeAsync(listener); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + + WaitForEventsToProcess(); + EXPECT_EQ(listener->callCount, 3); + } + + TEST_F(InternalEventBusTest, ItSubscribesAListenerAsyncWithFilterForMultipleCalls) + { + auto listener = std::make_shared(1); + auto filter = std::make_shared(1, true); + eventBus->SubscribeAsync(listener, filter); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + + WaitForEventsToProcess(); + EXPECT_EQ(listener->callCount, 3); + EXPECT_EQ(filter->callCount, 3); + } + + TEST_F(InternalEventBusTest, ItSubscribesAListenerAsyncOnce) + { + auto listener = std::make_shared(1); + eventBus->SubscribeAsyncOnce(listener); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + + WaitForEventsToProcess(); + EXPECT_EQ(listener->callCount, 1); + } + + TEST_F(InternalEventBusTest, ItSubscribesAListenerAsyncOnceWithFilter) + { + auto listener = std::make_shared(1); + auto filter = std::make_shared(1, true); + eventBus->SubscribeSyncOnce(listener, filter); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + eventBus->OnEvent(1); + + WaitForEventsToProcess(); EXPECT_EQ(listener->callCount, 1); EXPECT_EQ(filter->callCount, 1); } diff --git a/test/flowmeasure/CanonicalFlowMeasureInfoTest.cpp b/test/flowmeasure/CanonicalFlowMeasureInfoTest.cpp new file mode 100644 index 0000000..a90629d --- /dev/null +++ b/test/flowmeasure/CanonicalFlowMeasureInfoTest.cpp @@ -0,0 +1,90 @@ +#include "ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h" + +namespace ECFMPTest::FlowMeasure { + class CanonicalFlowMeasureInfoTest : public ::testing::Test + { + }; + + TEST_F(CanonicalFlowMeasureInfoTest, ItHasACanonicalIdentifierOriginalRevision) + { + const auto identifier = "EGTT02A"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + ASSERT_EQ("EGTT02A", canonicalFlowMeasureInfo.Identifier()); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItHasACanonicalRevisionOriginalRevision) + { + const auto identifier = "EGTT02A"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + ASSERT_EQ(0, canonicalFlowMeasureInfo.Revision()); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItHasACanonicalIdentifierFirstRevision) + { + const auto identifier = "EGTT02A-1"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + ASSERT_EQ("EGTT02A", canonicalFlowMeasureInfo.Identifier()); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItHasACanonicalRevisionFirstRevision) + { + const auto identifier = "EGTT02A-1"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + ASSERT_EQ(1, canonicalFlowMeasureInfo.Revision()); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItHasACanonicalIdentifierThirteenthRevision) + { + const auto identifier = "EGTT02A-13"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + ASSERT_EQ("EGTT02A", canonicalFlowMeasureInfo.Identifier()); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItHasACanonicalRevisionThirteenthRevision) + { + const auto identifier = "EGTT02A-13"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + ASSERT_EQ(13, canonicalFlowMeasureInfo.Revision()); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItHasACanonicalIdentifierBadRevision) + { + const auto identifier = "EGTT02A-AB"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + ASSERT_EQ("EGTT02A", canonicalFlowMeasureInfo.Identifier()); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItHasACanonicalRevisionBadRevision) + { + const auto identifier = "EGTT02A-AB"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + ASSERT_EQ(0, canonicalFlowMeasureInfo.Revision()); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItIsCanonicallyAfterAnotherMeasureWithSameIdentifierAndLowerRevision) + { + const auto identifier = "EGTT02A-1"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + const auto otherIdentifier = "EGTT02A-0"; + const auto otherCanonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(otherIdentifier); + EXPECT_TRUE(canonicalFlowMeasureInfo.IsAfter(otherCanonicalFlowMeasureInfo)); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItIsNotCanonicallyAfterAnotherMeasureWithSameIdentifierAndHigherRevision) + { + const auto identifier = "EGTT02A-1"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + const auto otherIdentifier = "EGTT02A-2"; + const auto otherCanonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(otherIdentifier); + EXPECT_FALSE(canonicalFlowMeasureInfo.IsAfter(otherCanonicalFlowMeasureInfo)); + } + + TEST_F(CanonicalFlowMeasureInfoTest, ItIsNotCanonicallyAfterAnotherMeasureWithDifferentIdentifierAndLowerRevision) + { + const auto identifier = "EGTT02A-1"; + const auto canonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(identifier); + const auto otherIdentifier = "EGTT02B-0"; + const auto otherCanonicalFlowMeasureInfo = ECFMP::FlowMeasure::CanonicalFlowMeasureInfo(otherIdentifier); + EXPECT_FALSE(canonicalFlowMeasureInfo.IsAfter(otherCanonicalFlowMeasureInfo)); + } +}// namespace ECFMPTest::FlowMeasure diff --git a/test/flowmeasure/ConcreteFlowMeasureTest.cpp b/test/flowmeasure/ConcreteFlowMeasureTest.cpp index 2fb73b6..060be57 100644 --- a/test/flowmeasure/ConcreteFlowMeasureTest.cpp +++ b/test/flowmeasure/ConcreteFlowMeasureTest.cpp @@ -1,5 +1,6 @@ #include "flowmeasure/ConcreteFlowMeasure.h" #include "ECFMP/flightinformationregion/FlightInformationRegion.h" +#include "ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h" #include "ECFMP/flowmeasure/MultipleLevelFilter.h" #include "ECFMP/flowmeasure/RangeToDestinationFilter.h" #include "event/ConcreteEvent.h" @@ -105,6 +106,29 @@ namespace ECFMPTest::FlowMeasure { EXPECT_EQ("EGTT01A", measure1.Identifier()); } + TEST_F(ConcreteFlowMeasureTest, ItHasACanonicalIdentifierForRevisedMeasure) + { + auto measure2 = ECFMP::FlowMeasure::ConcreteFlowMeasure( + 1, event, "EGTT02B-5", "Reason", startTime, endTime, withdrawnTime, + ECFMP::FlowMeasure::MeasureStatus::Active, {fir1}, + std::make_unique(ECFMP::FlowMeasure::MeasureType::Prohibit), + std::make_unique( + std::list>{ + std::make_shared( + std::set{"EGLL"}, ECFMP::FlowMeasure::AirportFilterType::Departure + )}, + std::list>{}, + std::list>{}, + std::list>{}, + std::list>{}, + std::list>{} + ) + ); + + EXPECT_EQ("EGTT02B", measure2.CanonicalInformation().Identifier()); + EXPECT_EQ(5, measure2.CanonicalInformation().Revision()); + } + TEST_F(ConcreteFlowMeasureTest, ItHasAReason) { EXPECT_EQ("Reason", measure1.Reason()); diff --git a/test/flowmeasure/FlowMeasureStatusUpdatesTest.cpp b/test/flowmeasure/FlowMeasureStatusUpdatesTest.cpp new file mode 100644 index 0000000..6785ed6 --- /dev/null +++ b/test/flowmeasure/FlowMeasureStatusUpdatesTest.cpp @@ -0,0 +1,575 @@ +#include + +#include "../../include/mock/FlowMeasureMock.h" +#include "ECFMP/SdkEvents.h" +#include "ECFMP/eventbus/EventListener.h" +#include "ECFMP/flowmeasure/CanonicalFlowMeasureInfo.h" +#include "ECFMP/flowmeasure/FlowMeasure.h" +#include "api/InternalStringIdentifierApiElementCollection.h" +#include "eventbus/InternalEventBusFactory.h" +#include "flowmeasure/FlowMeasureStatusUpdates.h" +#include "log/NullLogger.h" +#include "gmock/gmock.h" + +namespace ECFMPTest::FlowMeasure { + + template + class FlowMeasureStatusUpdatesEventListener : public ECFMP::EventBus::EventListener + { + public: + FlowMeasureStatusUpdatesEventListener(std::shared_ptr expectedMeasure) + : expectedMeasure(std::move(expectedMeasure)) + {} + + void OnEvent(const EventType& event) override + { + callCount++; + EXPECT_EQ(event.flowMeasure, expectedMeasure); + } + + void AssertCalledOnce() + { + EXPECT_EQ(callCount, 1); + } + + void AssertNotCalled() + { + EXPECT_EQ(callCount, 0); + } + + private: + int callCount = 0; + + std::shared_ptr expectedMeasure; + }; + + class FlowMeasureReissuedEventListener + : public ECFMP::EventBus::EventListener + { + public: + FlowMeasureReissuedEventListener( + std::shared_ptr expectedOriginal, + std::shared_ptr expectedReissued + ) + : expectedOriginal(std::move(expectedOriginal)), expectedReissued(std::move(expectedReissued)) + {} + + void OnEvent(const ECFMP::Plugin::FlowMeasureReissuedEvent& event) override + { + EXPECT_EQ(event.original, expectedOriginal); + EXPECT_EQ(event.reissued, expectedReissued); + callCount++; + } + + void AssertCalledOnce() + { + ASSERT_EQ(callCount, 1); + } + + void AssertNotCalled() + { + ASSERT_EQ(callCount, 0); + } + + private: + int callCount = 0; + + std::shared_ptr expectedOriginal; + std::shared_ptr expectedReissued; + }; + + class FlowMeasureStatusUpdatesTest : public ::testing::Test + { + public: + FlowMeasureStatusUpdatesTest() + : eventBus(ECFMP::EventBus::MakeEventBus()), logger(std::make_shared()), + flowMeasureStatusUpdates(std::make_shared(eventBus, logger)) + {} + + [[nodiscard]] static auto + GetMeasureMock(const std::string& identifier, const ECFMP::FlowMeasure::MeasureStatus status) + -> std::shared_ptr + { + auto measure = std::make_shared(); + auto canonicalInformation = std::make_shared(identifier); + + ON_CALL(*measure, CanonicalInformation) + .WillByDefault(testing::Invoke( + [canonicalInformation]() -> const ECFMP::FlowMeasure::CanonicalFlowMeasureInfo& { + return *canonicalInformation; + } + )); + ON_CALL(*measure, Status).WillByDefault(testing::Return(status)); + ON_CALL(*measure, Identifier).WillByDefault(testing::Invoke([identifier]() -> const std::string& { + return identifier; + })); + + return measure; + } + + protected: + std::shared_ptr eventBus; + std::shared_ptr logger; + std::shared_ptr flowMeasureStatusUpdates; + }; + + TEST_F(FlowMeasureStatusUpdatesTest, ItBroadcastsNotifiedEventFirstTimeCanonicalMeasureSeen) + { + // Set up the measure and collection + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Notified); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure1 + ); + auto listenerActivated = + std::make_shared>( + measure1 + ); + auto listenerWithdrawn = + std::make_shared>( + measure1 + ); + auto listenerExpired = + std::make_shared>(measure1 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertCalledOnce(); + listenerActivated->AssertNotCalled(); + listenerWithdrawn->AssertNotCalled(); + listenerExpired->AssertNotCalled(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItBroadcastsActivatedEventFirstTimeCanonicalMeasureSeen) + { + // Set up the measure and collection + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Active); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure1 + ); + auto listenerActivated = + std::make_shared>( + measure1 + ); + auto listenerWithdrawn = + std::make_shared>( + measure1 + ); + auto listenerExpired = + std::make_shared>(measure1 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertCalledOnce(); + listenerWithdrawn->AssertNotCalled(); + listenerExpired->AssertNotCalled(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItBroadcastsWithdrawnEventFirstTimeCanonicalMeasureSeen) + { + // Set up the measure and collection + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Withdrawn); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure1 + ); + auto listenerActivated = + std::make_shared>( + measure1 + ); + auto listenerWithdrawn = + std::make_shared>( + measure1 + ); + auto listenerExpired = + std::make_shared>(measure1 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertNotCalled(); + listenerWithdrawn->AssertCalledOnce(); + listenerExpired->AssertNotCalled(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItBroadcastsExpiredEventFirstTimeCanonicalMeasureSeen) + { + // Set up the measure and collection + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Expired); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure1 + ); + auto listenerActivated = + std::make_shared>( + measure1 + ); + auto listenerWithdrawn = + std::make_shared>( + measure1 + ); + auto listenerExpired = + std::make_shared>(measure1 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertNotCalled(); + listenerWithdrawn->AssertNotCalled(); + listenerExpired->AssertCalledOnce(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItBroadcastsReissuedEventOnCanonicalUpodate) + { + // Set up the measure and collection + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Expired); + auto measure2 = GetMeasureMock("EGTT01A-1", ECFMP::FlowMeasure::MeasureStatus::Expired); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + + // Set up event listeners + auto listenerReissued = std::make_shared(measure1, measure2); + eventBus->SubscribeSync(listenerReissued); + + // Run event and check no first-time broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerReissued->AssertNotCalled(); + + // Add the second measure and check the reissued event is broadcast + flowMeasures->Add(measure2); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerReissued->AssertCalledOnce(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItDoesntBroadcastReissuedEventOnCanonicalUpodate) + { + // Set up the measure and collection + auto measure1 = GetMeasureMock("EGTT01A-1", ECFMP::FlowMeasure::MeasureStatus::Expired); + auto measure2 = GetMeasureMock("EGTT01A0", ECFMP::FlowMeasure::MeasureStatus::Expired); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + + // Set up event listeners + auto listenerReissued = std::make_shared(measure1, measure2); + eventBus->SubscribeSync(listenerReissued); + + // Run event and check no first-time broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerReissued->AssertNotCalled(); + + // Add the second measure and check the reissued event is still not broadcast + flowMeasures->Add(measure2); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerReissued->AssertNotCalled(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItTransitionsAMeasureFromNotifiedToActivated) + { + // Set up the measure and collection, then call the event to emplant the measure + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Notified); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + + // Create the second measure + auto measure2 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Active); + flowMeasures->Add(measure2); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure2 + ); + auto listenerActivated = + std::make_shared>( + measure2 + ); + auto listenerWithdrawn = + std::make_shared>( + measure2 + ); + auto listenerExpired = + std::make_shared>(measure2 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertCalledOnce(); + listenerWithdrawn->AssertNotCalled(); + listenerExpired->AssertNotCalled(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItTransitionsAMeasureFromNotifiedToExpired) + { + // Set up the measure and collection, then call the event to emplant the measure + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Notified); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + + // Create the second measure + auto measure2 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Expired); + flowMeasures->Add(measure2); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure2 + ); + auto listenerActivated = + std::make_shared>( + measure2 + ); + auto listenerWithdrawn = + std::make_shared>( + measure2 + ); + auto listenerExpired = + std::make_shared>(measure2 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertNotCalled(); + listenerWithdrawn->AssertNotCalled(); + listenerExpired->AssertCalledOnce(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItTransitionsAMeasureFromNotifiedToAWithdrawn) + { + // Set up the measure and collection, then call the event to emplant the measure + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Notified); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + + // Create the second measure + auto measure2 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Withdrawn); + flowMeasures->Add(measure2); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure2 + ); + auto listenerActivated = + std::make_shared>( + measure2 + ); + auto listenerWithdrawn = + std::make_shared>( + measure2 + ); + auto listenerExpired = + std::make_shared>(measure2 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertNotCalled(); + listenerWithdrawn->AssertCalledOnce(); + listenerExpired->AssertNotCalled(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItTransitionsAMeasureFromActiveToExpired) + { + // Set up the measure and collection, then call the event to emplant the measure + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Active); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + + // Create the second measure + auto measure2 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Expired); + flowMeasures->Add(measure2); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure2 + ); + auto listenerActivated = + std::make_shared>( + measure2 + ); + auto listenerWithdrawn = + std::make_shared>( + measure2 + ); + auto listenerExpired = + std::make_shared>(measure2 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertNotCalled(); + listenerWithdrawn->AssertNotCalled(); + listenerExpired->AssertCalledOnce(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItTransitionsAMeasureFromActiveToWithdrawn) + { + // Set up the measure and collection, then call the event to emplant the measure + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Active); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + + // Create the second measure + auto measure2 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Withdrawn); + flowMeasures->Add(measure2); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure2 + ); + auto listenerActivated = + std::make_shared>( + measure2 + ); + auto listenerWithdrawn = + std::make_shared>( + measure2 + ); + auto listenerExpired = + std::make_shared>(measure2 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertNotCalled(); + listenerWithdrawn->AssertCalledOnce(); + listenerExpired->AssertNotCalled(); + } + + TEST_F(FlowMeasureStatusUpdatesTest, ItTransitionsAMeasureThroughMultipleStatuses) + { + // Set up the measure and collection, then call the event to emplant the measure + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Notified); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + + // Create the second and third measure + auto measure2 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Active); + flowMeasures->Add(measure2); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + + auto measure3 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Withdrawn); + flowMeasures->Add(measure3); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure3 + ); + auto listenerActivated = + std::make_shared>( + measure3 + ); + auto listenerWithdrawn = + std::make_shared>( + measure3 + ); + auto listenerExpired = + std::make_shared>(measure3 + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertNotCalled(); + listenerWithdrawn->AssertCalledOnce(); + listenerExpired->AssertNotCalled(); + } +}// namespace ECFMPTest::FlowMeasure diff --git a/test/plugin/ConcreteSdkTest.cpp b/test/plugin/ConcreteSdkTest.cpp deleted file mode 100644 index 5482e96..0000000 --- a/test/plugin/ConcreteSdkTest.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "plugin/ConcreteSdk.h" -#include "ECFMP/eventbus/EventListener.h" -#include "api/ApiDataDownloader.h" -#include "eventbus/InternalEventBus.h" -#include "mock/MockHttpClient.h" - -namespace ECFMPTest::Plugin { - - class MockEventListener : public ECFMP::EventBus::EventListener - { - public: - void OnEvent(const int&) override - { - callCount++; - } - - int callCount = 0; - }; - - class ConcreteSdkTest : public testing::Test - { - public: - ConcreteSdkTest() - : eventBus(std::make_shared()), testPtr(std::make_shared(5)), - instance(std::shared_ptr(testPtr), eventBus) - {} - - std::shared_ptr eventBus; - std::shared_ptr testPtr; - ECFMP::Plugin::ConcreteSdk instance; - }; - - TEST_F(ConcreteSdkTest, OnEuroscopeTimerTickProcessesEvents) - { - auto mockEventListener = std::make_shared(); - eventBus->Subscribe(mockEventListener); - eventBus->OnEvent(5); - EXPECT_EQ(0, mockEventListener->callCount); - instance.OnEuroscopeTimerTick(); - EXPECT_EQ(1, mockEventListener->callCount); - } - - TEST_F(ConcreteSdkTest, ItHasAnEventBus) - { - auto mockEventListener = std::make_shared(); - instance.EventBus().Subscribe(mockEventListener); - eventBus->OnEvent(5); - instance.OnEuroscopeTimerTick(); - EXPECT_EQ(1, mockEventListener->callCount); - } -}// namespace ECFMPTest::Plugin diff --git a/test/plugin/InternalSdkTest.cpp b/test/plugin/InternalSdkTest.cpp new file mode 100644 index 0000000..074a0fe --- /dev/null +++ b/test/plugin/InternalSdkTest.cpp @@ -0,0 +1,89 @@ +#include "plugin/InternalSdk.h" +#include "ECFMP/eventbus/EventListener.h" +#include "eventbus/InternalEventBusFactory.h" +#include "mock/MockHttpClient.h" + +namespace ECFMPTest::Plugin { + + template + class MockEventListener : public ECFMP::EventBus::EventListener + { + public: + void OnEvent(const EventType&) override + { + callCount++; + } + + int callCount = 0; + }; + + class ConcreteSdkTest : public testing::Test + { + public: + ConcreteSdkTest() : eventBus(ECFMP::EventBus::MakeEventBus()), instance(eventBus) + {} + + std::shared_ptr eventBus; + ECFMP::Plugin::InternalSdk instance; + }; + + TEST_F(ConcreteSdkTest, OnEuroscopeTimerTickTriggersEvent) + { + auto mockEventListener = std::make_shared>(); + eventBus->SubscribeSync(mockEventListener); + EXPECT_EQ(0, mockEventListener->callCount); + instance.OnEuroscopeTimerTick(); + EXPECT_EQ(1, mockEventListener->callCount); + } + + TEST_F(ConcreteSdkTest, ItHasAnEventBus) + { + auto mockEventListener = std::make_shared>(); + instance.EventBus().Subscribe(mockEventListener); + eventBus->OnEvent(5); + instance.OnEuroscopeTimerTick(); + EXPECT_EQ(1, mockEventListener->callCount); + } + + TEST_F(ConcreteSdkTest, ItHasADefaultFlightInformationRegionCollection) + { + EXPECT_EQ(0, instance.FlightInformationRegions()->Count()); + } + + TEST_F(ConcreteSdkTest, ItUpdatesFlightInformationRegionsOnEvent) + { + auto newFirs = std::make_shared(); + auto event = ECFMP::Plugin::FlightInformationRegionsUpdatedEvent{newFirs}; + instance.OnEvent(event); + + EXPECT_EQ(newFirs, instance.FlightInformationRegions()); + } + + TEST_F(ConcreteSdkTest, ItHasADefaultEventCollection) + { + EXPECT_EQ(0, instance.Events()->Count()); + } + + TEST_F(ConcreteSdkTest, ItUpdatesEventsOnEvent) + { + auto newEvents = std::make_shared(); + auto event = ECFMP::Plugin::EventsUpdatedEvent{newEvents}; + instance.OnEvent(event); + + EXPECT_EQ(newEvents, instance.Events()); + } + + TEST_F(ConcreteSdkTest, ItHasADefaultFlowMeasureCollection) + { + EXPECT_EQ(0, instance.FlowMeasures()->Count()); + } + + TEST_F(ConcreteSdkTest, ItUpdatesFlowMeasuresOnEvent) + { + auto newFlowMeasures = std::make_shared(); + auto event = ECFMP::Plugin::FlowMeasuresUpdatedEvent{newFlowMeasures}; + instance.OnEvent(event); + + EXPECT_EQ(newFlowMeasures, instance.FlowMeasures()); + } +}// namespace ECFMPTest::Plugin diff --git a/test/plugin/SdkFactoryTest.cpp b/test/plugin/SdkFactoryTest.cpp index 3989f55..032a48d 100644 --- a/test/plugin/SdkFactoryTest.cpp +++ b/test/plugin/SdkFactoryTest.cpp @@ -1,9 +1,18 @@ #include "ECFMP/SdkFactory.h" +#include "ECFMP/SdkEvents.h" #include "ECFMP/eventbus/EventBus.h" #include "api/ApiDataDownloadedEvent.h" +#include "api/ApiDataDownloader.h" #include "api/ApiDataParser.h" +#include "api/ApiDataScheduler.h" +#include "eventbus/InternalEventBus.h" +#include "eventbus/PendingEuroscopeEvents.h" +#include "eventbus/SubscriptionFlags.h" +#include "flowmeasure/FlowMeasureStatusUpdates.h" #include "mock/MockHttpClient.h" #include "mock/MockLogger.h" +#include "plugin/InternalSdk.h" +#include "plugin/InternalSdkEvents.h" namespace ECFMPTest::Plugin { class SdkFactoryTest : public testing::Test @@ -16,6 +25,14 @@ namespace ECFMPTest::Plugin { http2(std::make_unique>()) {} + [[nodiscard]] static auto InternalBus(const std::shared_ptr sdk) + -> ECFMP::EventBus::InternalEventBus& + { + return const_cast( + static_cast(sdk->EventBus()) + ); + } + std::unique_ptr> logger; std::unique_ptr> logger2; std::unique_ptr> http; @@ -71,7 +88,97 @@ namespace ECFMPTest::Plugin { { const auto instance = ECFMP::Plugin::SdkFactory::Build().WithHttpClient(std::move(http)).Instance(); auto hasListener = - instance->EventBus().HasListenerOfType(); + InternalBus(instance) + .HasListenerForSubscription( + {ECFMP::EventBus::EventDispatchMode::Async, false} + ); + EXPECT_TRUE(hasListener); + instance->Destroy(); + } + + TEST_F(SdkFactoryTest, ItRegistersSdkForFirUpdateEvents) + { + const auto instance = ECFMP::Plugin::SdkFactory::Build().WithHttpClient(std::move(http)).Instance(); + auto hasListener = + InternalBus(instance) + .HasListenerForSubscription< + ECFMP::Plugin::InternalSdk, ECFMP::Plugin::FlightInformationRegionsUpdatedEvent>( + {ECFMP::EventBus::EventDispatchMode::Async, false} + ); + EXPECT_TRUE(hasListener); + instance->Destroy(); + } + + TEST_F(SdkFactoryTest, ItRegistersSdkForEventsUpdateEvents) + { + const auto instance = ECFMP::Plugin::SdkFactory::Build().WithHttpClient(std::move(http)).Instance(); + auto hasListener = + InternalBus(instance) + .HasListenerForSubscription( + {ECFMP::EventBus::EventDispatchMode::Async, false} + ); + EXPECT_TRUE(hasListener); + instance->Destroy(); + } + + TEST_F(SdkFactoryTest, ItRegistersSdkForFlowMeasureUpdateEvents) + { + const auto instance = ECFMP::Plugin::SdkFactory::Build().WithHttpClient(std::move(http)).Instance(); + auto hasListener = InternalBus(instance) + .HasListenerForSubscription< + ECFMP::Plugin::InternalSdk, ECFMP::Plugin::FlowMeasuresUpdatedEvent>( + {ECFMP::EventBus::EventDispatchMode::Async, false} + ); + EXPECT_TRUE(hasListener); + instance->Destroy(); + } + + TEST_F(SdkFactoryTest, ItRegistersPendingEuroscopeEventsForTimerTickEvents) + { + const auto instance = ECFMP::Plugin::SdkFactory::Build().WithHttpClient(std::move(http)).Instance(); + auto hasListener = + InternalBus(instance) + .HasListenerForSubscription< + ECFMP::EventBus::PendingEuroscopeEvents, ECFMP::Plugin::EuroscopeTimerTickEvent>( + {ECFMP::EventBus::EventDispatchMode::Sync, false} + ); + EXPECT_TRUE(hasListener); + instance->Destroy(); + } + + TEST_F(SdkFactoryTest, ItRegistersApiDataSchedulerForTimerTickEvents) + { + const auto instance = ECFMP::Plugin::SdkFactory::Build().WithHttpClient(std::move(http)).Instance(); + auto hasListener = InternalBus(instance) + .HasListenerForSubscription< + ECFMP::Api::ApiDataScheduler, ECFMP::Plugin::EuroscopeTimerTickEvent>( + {ECFMP::EventBus::EventDispatchMode::Sync, false} + ); + EXPECT_TRUE(hasListener); + instance->Destroy(); + } + + TEST_F(SdkFactoryTest, ItRegistersApiDataDownloaderForDownloadRequiredEvents) + { + const auto instance = ECFMP::Plugin::SdkFactory::Build().WithHttpClient(std::move(http)).Instance(); + auto hasListener = InternalBus(instance) + .HasListenerForSubscription< + ECFMP::Api::ApiDataDownloader, ECFMP::Plugin::ApiDataDownloadRequiredEvent>( + {ECFMP::EventBus::EventDispatchMode::Async, false} + ); + EXPECT_TRUE(hasListener); + instance->Destroy(); + } + + TEST_F(SdkFactoryTest, ItRegistersFlowMeasureStatusUpdatesForUpdatedEvent) + { + const auto instance = ECFMP::Plugin::SdkFactory::Build().WithHttpClient(std::move(http)).Instance(); + auto hasListener = InternalBus(instance) + .HasListenerForSubscription< + ECFMP::FlowMeasure::FlowMeasureStatusUpdates, + ECFMP::Plugin::InternalFlowMeasuresUpdatedEvent>( + {ECFMP::EventBus::EventDispatchMode::Async, false} + ); EXPECT_TRUE(hasListener); instance->Destroy(); }