#include "RuntimeEvent.h" #include "RuntimeEventCoalescingQueue.h" #include "RuntimeEventDispatcher.h" #include "RuntimeEventQueue.h" #include "RuntimeEventType.h" #include "RuntimeEventPayloads.h" #include "RuntimeEventTestHarness.h" #include #include #include #include #include namespace { int gFailures = 0; void Expect(bool condition, const char* message) { if (condition) return; std::cerr << "FAIL: " << message << "\n"; ++gFailures; } void TestRuntimeEventTypeNames() { Expect(RuntimeEventTypeName(RuntimeEventType::Unknown) == "Unknown", "unknown event type has a stable name"); Expect(RuntimeEventTypeName(RuntimeEventType::OscCommitRequested) == "OscCommitRequested", "control event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::RuntimeMutationAccepted) == "RuntimeMutationAccepted", "runtime event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::ShaderBuildPrepared) == "ShaderBuildPrepared", "shader build event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::RenderSnapshotPublished) == "RenderSnapshotPublished", "render event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::BackendStateChanged) == "BackendStateChanged", "backend event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::QueueDepthChanged) == "QueueDepthChanged", "telemetry event name is stable"); } void TestRuntimeEventPayloadTypes() { OscCommitRequestedEvent oscCommit; oscCommit.routeKey = "layer-1\namount"; oscCommit.layerKey = "layer-1"; oscCommit.parameterKey = "amount"; oscCommit.generation = 42; Expect(RuntimeEventPayloadType(oscCommit) == RuntimeEventType::OscCommitRequested, "OSC commit payload maps to OSC commit event type"); Expect(oscCommit.generation == 42, "OSC commit payload keeps generation"); RuntimeMutationEvent acceptedMutation; acceptedMutation.action = "SetLayerShader"; acceptedMutation.accepted = true; acceptedMutation.shaderBuildRequested = true; Expect(RuntimeEventPayloadType(acceptedMutation) == RuntimeEventType::RuntimeMutationAccepted, "accepted mutation payload maps to accepted event type"); Expect(acceptedMutation.shaderBuildRequested, "mutation payload carries shader build follow-up"); RuntimeMutationEvent rejectedMutation; rejectedMutation.accepted = false; rejectedMutation.errorMessage = "Unknown layer."; Expect(RuntimeEventPayloadType(rejectedMutation) == RuntimeEventType::RuntimeMutationRejected, "rejected mutation payload maps to rejected event type"); Expect(rejectedMutation.errorMessage == "Unknown layer.", "mutation payload carries rejection error"); ShaderBuildEvent preparedBuild; preparedBuild.phase = RuntimeEventShaderBuildPhase::Prepared; preparedBuild.inputWidth = 1920; preparedBuild.inputHeight = 1080; Expect(RuntimeEventPayloadType(preparedBuild) == RuntimeEventType::ShaderBuildPrepared, "shader build payload maps by phase"); Expect(preparedBuild.inputWidth == 1920 && preparedBuild.inputHeight == 1080, "shader build payload carries input dimensions"); RenderResetEvent appliedReset; appliedReset.scope = RuntimeEventRenderResetScope::TemporalHistoryAndFeedback; appliedReset.applied = true; Expect(RuntimeEventPayloadType(appliedReset) == RuntimeEventType::RenderResetApplied, "render reset payload maps applied state"); Expect(appliedReset.scope == RuntimeEventRenderResetScope::TemporalHistoryAndFeedback, "render reset payload carries reset scope"); SubsystemWarningEvent warning; warning.subsystem = "VideoBackend"; warning.warningKey = "late-frame"; Expect(RuntimeEventPayloadType(warning) == RuntimeEventType::SubsystemWarningRaised, "warning payload maps to raised event by default"); warning.cleared = true; Expect(RuntimeEventPayloadType(warning) == RuntimeEventType::SubsystemWarningCleared, "warning payload maps to cleared event when marked cleared"); } void TestRuntimeEventEnvelope() { const auto createdAt = std::chrono::steady_clock::now(); OscCommitRequestedEvent oscCommit; oscCommit.routeKey = "layer-1\namount"; oscCommit.layerKey = "layer-1"; oscCommit.parameterKey = "amount"; oscCommit.generation = 7; RuntimeEvent event = MakeRuntimeEvent(oscCommit, "ControlServices", 12, createdAt); Expect(event.type == RuntimeEventType::OscCommitRequested, "runtime event infers type from payload"); Expect(event.sequence == 12, "runtime event stores sequence"); Expect(event.source == "ControlServices", "runtime event stores source"); Expect(event.createdAt == createdAt, "runtime event stores creation time"); Expect(event.HasPayload(), "runtime event reports payload presence"); Expect(event.PayloadMatchesType(), "runtime event payload matches inferred type"); const auto* payload = std::get_if(&event.payload); Expect(payload && payload->generation == 7, "runtime event stores typed payload in variant"); event.type = RuntimeEventType::RuntimeMutationAccepted; Expect(!event.PayloadMatchesType(), "runtime event detects mismatched payload and type"); RuntimeEvent empty; Expect(!empty.HasPayload(), "default runtime event has no payload"); Expect(empty.PayloadMatchesType(), "default runtime event unknown type matches empty payload"); RuntimeMutationEvent acceptedMutation; acceptedMutation.accepted = true; acceptedMutation.action = "SetLayerBypass"; RuntimeEvent acceptedEvent = MakeRuntimeEvent(acceptedMutation, "RuntimeCoordinator", 13, createdAt); Expect(acceptedEvent.type == RuntimeEventType::RuntimeMutationAccepted, "runtime event handles payloads with dynamic accepted mapping"); RuntimeMutationEvent rejectedMutation; rejectedMutation.accepted = false; rejectedMutation.action = "SetLayerBypass"; RuntimeEvent rejectedEvent = MakeRuntimeEvent(rejectedMutation, "RuntimeCoordinator", 14, createdAt); Expect(rejectedEvent.type == RuntimeEventType::RuntimeMutationRejected, "runtime event handles payloads with dynamic rejected mapping"); } void TestRuntimeEventQueue() { RuntimeEventQueue queue(2); const auto createdAt = std::chrono::steady_clock::now() - std::chrono::milliseconds(5); RuntimeStateBroadcastRequestedEvent firstPayload; firstPayload.reason = "first"; RuntimeStateBroadcastRequestedEvent secondPayload; secondPayload.reason = "second"; RuntimeStateBroadcastRequestedEvent thirdPayload; thirdPayload.reason = "third"; Expect(queue.Push(MakeRuntimeEvent(firstPayload, "test", 1, createdAt)), "queue accepts first event"); Expect(queue.Push(MakeRuntimeEvent(secondPayload, "test", 2, createdAt)), "queue accepts second event"); Expect(!queue.Push(MakeRuntimeEvent(thirdPayload, "test", 3, createdAt)), "queue rejects event when capacity is full"); RuntimeEventQueueMetrics fullMetrics = queue.GetMetrics(std::chrono::steady_clock::now()); Expect(fullMetrics.depth == 2, "queue metrics report depth"); Expect(fullMetrics.capacity == 2, "queue metrics report capacity"); Expect(fullMetrics.droppedCount == 1, "queue metrics report dropped count"); Expect(fullMetrics.oldestEventAgeMilliseconds >= 0.0, "queue metrics report oldest event age"); RuntimeEvent event; Expect(queue.TryPop(event), "queue pops first event"); Expect(event.sequence == 1, "queue preserves FIFO order"); std::vector drained = queue.Drain(); Expect(drained.size() == 1, "queue drains remaining event"); Expect(drained[0].sequence == 2, "drained event preserves FIFO order"); Expect(queue.Depth() == 0, "queue is empty after drain"); } void TestRuntimeEventDispatcher() { RuntimeEventDispatcher dispatcher(4); int allHandlerCount = 0; int broadcastHandlerCount = 0; int failureHandlerCount = 0; dispatcher.SubscribeAll([&](const RuntimeEvent& event) { Expect(event.sequence != 0, "dispatcher assigns sequence before all-handler dispatch"); ++allHandlerCount; }); dispatcher.Subscribe(RuntimeEventType::RuntimeStateBroadcastRequested, [&](const RuntimeEvent& event) { Expect(event.type == RuntimeEventType::RuntimeStateBroadcastRequested, "dispatcher invokes type-specific handler for matching event"); ++broadcastHandlerCount; }); dispatcher.Subscribe(RuntimeEventType::RuntimeStateBroadcastRequested, [&](const RuntimeEvent&) { ++failureHandlerCount; throw std::runtime_error("test handler failure"); }); RuntimeStateBroadcastRequestedEvent broadcast; broadcast.reason = "test"; Expect(dispatcher.PublishPayload(broadcast, "test"), "dispatcher publishes payload"); Expect(dispatcher.QueueDepth() == 1, "dispatcher reports queued depth"); RuntimeEventDispatchResult result = dispatcher.DispatchPending(); Expect(result.dispatchedEvents == 1, "dispatcher reports dispatched event count"); Expect(result.handlerInvocations == 3, "dispatcher reports handler invocation count"); Expect(result.handlerFailures == 1, "dispatcher catches and reports handler failures"); Expect(allHandlerCount == 1, "dispatcher invoked all-handler"); Expect(broadcastHandlerCount == 1, "dispatcher invoked type-specific handler"); Expect(failureHandlerCount == 1, "dispatcher invoked failing handler"); Expect(dispatcher.QueueDepth() == 0, "dispatcher queue is empty after dispatch"); RuntimeEvent mismatched = MakeRuntimeEvent(broadcast, "test"); mismatched.type = RuntimeEventType::ShaderBuildRequested; Expect(!dispatcher.Publish(mismatched), "dispatcher rejects mismatched event type and payload"); RuntimeEventDispatcher tinyDispatcher(1); Expect(tinyDispatcher.PublishPayload(broadcast, "test"), "tiny dispatcher accepts first event"); Expect(!tinyDispatcher.PublishPayload(broadcast, "test"), "tiny dispatcher rejects event when queue is full"); RuntimeEventQueueMetrics metrics = tinyDispatcher.GetQueueMetrics(); Expect(metrics.droppedCount == 1, "dispatcher exposes queue drop metrics"); } void TestRuntimeEventCoalescingQueue() { RuntimeEventCoalescingQueue queue(2); const auto createdAt = std::chrono::steady_clock::now() - std::chrono::milliseconds(5); OscValueReceivedEvent first; first.routeKey = "layer-1\namount"; first.layerKey = "layer-1"; first.parameterKey = "amount"; first.valueJson = "0.1"; first.generation = 1; OscValueReceivedEvent replacement = first; replacement.valueJson = "0.9"; replacement.generation = 2; OscValueReceivedEvent otherRoute; otherRoute.routeKey = "layer-2\namount"; otherRoute.layerKey = "layer-2"; otherRoute.parameterKey = "amount"; otherRoute.valueJson = "0.3"; otherRoute.generation = 3; OscValueReceivedEvent overflow; overflow.routeKey = "layer-3\namount"; overflow.layerKey = "layer-3"; overflow.parameterKey = "amount"; overflow.valueJson = "0.4"; overflow.generation = 4; Expect(queue.Push(MakeRuntimeEvent(first, "ControlServices", 1, createdAt)), "coalescing queue accepts first keyed event"); Expect(queue.Push(MakeRuntimeEvent(replacement, "ControlServices", 2, std::chrono::steady_clock::now())), "coalescing queue replaces matching keyed event"); Expect(queue.Push(MakeRuntimeEvent(otherRoute, "ControlServices", 3, createdAt)), "coalescing queue accepts second keyed event"); Expect(!queue.Push(MakeRuntimeEvent(overflow, "ControlServices", 4, createdAt)), "coalescing queue rejects new key when full"); RuntimeEventCoalescingQueueMetrics metrics = queue.GetMetrics(std::chrono::steady_clock::now()); Expect(metrics.depth == 2, "coalescing queue metrics report unique-key depth"); Expect(metrics.capacity == 2, "coalescing queue metrics report capacity"); Expect(metrics.coalescedCount == 1, "coalescing queue metrics report coalesced count"); Expect(metrics.droppedCount == 1, "coalescing queue metrics report dropped count"); Expect(metrics.oldestEventAgeMilliseconds >= 0.0, "coalescing queue metrics report oldest event age"); std::vector drained = queue.Drain(); Expect(drained.size() == 2, "coalescing queue drains unique events"); Expect(drained[0].sequence == 2, "coalescing queue keeps latest replacement event"); Expect(drained[1].sequence == 3, "coalescing queue preserves first-seen key order"); const auto* latestPayload = std::get_if(&drained[0].payload); Expect(latestPayload && latestPayload->valueJson == "0.9", "coalescing queue keeps latest payload value"); Expect(queue.Depth() == 0, "coalescing queue is empty after drain"); } void TestRuntimeEventCoalescingCustomKey() { RuntimeEventCoalescingQueue queue(4, [](const RuntimeEvent& event) { return std::string(RuntimeEventTypeName(event.type)); }); RuntimeStateBroadcastRequestedEvent first; first.reason = "parameter"; RuntimeStateBroadcastRequestedEvent second; second.reason = "reload"; Expect(queue.Push(MakeRuntimeEvent(first, "RuntimeCoordinator", 10)), "custom-key coalescing queue accepts first event"); Expect(queue.Push(MakeRuntimeEvent(second, "RuntimeCoordinator", 11)), "custom-key coalescing queue coalesces second event by type"); std::vector drained = queue.Drain(); Expect(drained.size() == 1, "custom-key coalescing queue drains one coalesced event"); Expect(drained[0].sequence == 11, "custom-key coalescing queue keeps latest event"); const auto* payload = std::get_if(&drained[0].payload); Expect(payload && payload->reason == "reload", "custom-key coalescing queue keeps latest typed payload"); } void TestRuntimeEventTestHarness() { RuntimeEventTestHarness harness; RuntimeStateBroadcastRequestedEvent broadcast; broadcast.reason = "parameter"; RuntimeEventDispatchResult firstDispatch = harness.PublishAndDispatch(broadcast, "RuntimeCoordinator"); Expect(firstDispatch.dispatchedEvents == 1, "test harness publishes and dispatches payloads"); Expect(harness.SeenCount() == 1, "test harness records dispatched events"); Expect(harness.SeenCount(RuntimeEventType::RuntimeStateBroadcastRequested) == 1, "test harness counts seen events by type"); const RuntimeEvent* seenBroadcast = harness.LastSeen(RuntimeEventType::RuntimeStateBroadcastRequested); Expect(seenBroadcast && seenBroadcast->source == "RuntimeCoordinator", "test harness returns last seen event by type"); harness.ClearSeen(); Expect(harness.SeenCount() == 0, "test harness clears seen events"); OscValueReceivedEvent first; first.routeKey = "layer-1\namount"; first.layerKey = "layer-1"; first.parameterKey = "amount"; first.valueJson = "0.1"; first.generation = 1; OscValueReceivedEvent replacement = first; replacement.valueJson = "0.8"; replacement.generation = 2; Expect(harness.PublishCoalesced(first, "ControlServices", 20), "test harness accepts first coalesced payload"); Expect(harness.PublishCoalesced(replacement, "ControlServices", 21), "test harness accepts replacement coalesced payload"); RuntimeEventDispatchResult coalescedDispatch = harness.FlushCoalescedAndDispatch(); Expect(coalescedDispatch.dispatchedEvents == 1, "test harness dispatches one coalesced event"); Expect(harness.SeenCount(RuntimeEventType::OscValueReceived) == 1, "test harness records coalesced event"); const RuntimeEvent* seenOsc = harness.LastSeen(RuntimeEventType::OscValueReceived); const auto* seenPayload = seenOsc ? std::get_if(&seenOsc->payload) : nullptr; Expect(seenPayload && seenPayload->valueJson == "0.8", "test harness keeps latest coalesced payload"); } } int main() { TestRuntimeEventTypeNames(); TestRuntimeEventPayloadTypes(); TestRuntimeEventEnvelope(); TestRuntimeEventQueue(); TestRuntimeEventDispatcher(); TestRuntimeEventCoalescingQueue(); TestRuntimeEventCoalescingCustomKey(); TestRuntimeEventTestHarness(); if (gFailures != 0) { std::cerr << gFailures << " RuntimeEventType test failure(s).\n"; return 1; } std::cout << "RuntimeEventType tests passed.\n"; return 0; }