ESPSomfy-RTS/test/test_radioscheduler/test_radioscheduler.cpp
cjkas 29d642b6f4 refactor: drop unused radio API
Transceiver::flushTx() had no caller; suspendTx() is the only flush.
RadioScheduler::hasRoom() was only ever asked about a normal job, so it no
longer takes a priority flag.  The tests check the reserved stop slots
through submit() instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:11:35 +02:00

512 lines
19 KiB
C++

#include <unity.h>
#include "RadioScheduler.h"
static const uint32_t REMOTE_A = 0x111111;
static const uint32_t REMOTE_B = 0x222222;
void setUp() {}
void tearDown() {}
static somfy_tx_job_t makeJob(uint32_t remoteAddress, somfy_commands cmd = somfy_commands::Up) {
somfy_tx_job_t job;
job.frame.remoteAddress = remoteAddress;
job.frame.cmd = cmd;
job.repeats = 2;
return job;
}
static uint32_t submit(RadioScheduler &s, uint32_t remoteAddress, uint32_t now = 0, bool priority = false, uint16_t delayMs = 0) {
somfy_tx_job_t job = makeJob(remoteAddress, priority ? somfy_commands::My : somfy_commands::Up);
return s.submit(job, priority, delayMs, now);
}
// Takes the next job and sends all of it, as the radio task does. Returns its
// ticket, 0 when none is due.
static uint32_t runNext(RadioScheduler &s, uint32_t now = 0) {
somfy_tx_job_t job;
uint32_t waitMs;
if(!s.next(now, job, waitMs)) return 0;
if(!s.start(job.seq, now)) return job.seq;
s.ended(job.seq, true, now);
return job.seq;
}
static void assertEvent(RadioScheduler &s, tx_event_t type, uint32_t ticket) {
somfy_tx_event_t evt;
TEST_ASSERT_TRUE_MESSAGE(s.popEvent(evt), "expected another event");
TEST_ASSERT_EQUAL_UINT8((uint8_t)type, (uint8_t)evt.type);
TEST_ASSERT_EQUAL_UINT32(ticket, evt.seq);
}
static void assertNoEvents(RadioScheduler &s) {
somfy_tx_event_t evt;
TEST_ASSERT_FALSE(s.popEvent(evt));
}
static void discardEvents(RadioScheduler &s) {
somfy_tx_event_t evt;
while(s.popEvent(evt)) {}
}
/*********************************************************************
* Order
********************************************************************/
static void test_jobs_go_out_in_the_order_they_came() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A);
const uint32_t b = submit(s, REMOTE_B);
const uint32_t c = submit(s, REMOTE_A);
TEST_ASSERT_NOT_EQUAL(0, a);
TEST_ASSERT_EQUAL_UINT32(a, runNext(s));
TEST_ASSERT_EQUAL_UINT32(b, runNext(s));
TEST_ASSERT_EQUAL_UINT32(c, runNext(s));
TEST_ASSERT_EQUAL_UINT32(0, runNext(s));
}
static void test_tickets_are_distinct_and_set_in_the_job() {
RadioScheduler s;
somfy_tx_job_t job = makeJob(REMOTE_A);
const uint32_t a = s.submit(job, false, 0, 0);
TEST_ASSERT_EQUAL_UINT32(a, job.seq);
TEST_ASSERT_NOT_EQUAL(a, submit(s, REMOTE_A));
}
static void test_nothing_waiting_sleeps_until_woken() {
RadioScheduler s;
somfy_tx_job_t job;
uint32_t waitMs = 0;
TEST_ASSERT_FALSE(s.next(0, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(RADIO_WAIT_FOREVER, waitMs);
}
/*********************************************************************
* Stops (priority jobs)
********************************************************************/
static void test_stop_goes_ahead_of_other_remotes() {
RadioScheduler s;
const uint32_t b1 = submit(s, REMOTE_B);
const uint32_t b2 = submit(s, REMOTE_B);
const uint32_t stop = submit(s, REMOTE_A, 0, true);
TEST_ASSERT_EQUAL_UINT32(stop, runNext(s));
TEST_ASSERT_EQUAL_UINT32(b1, runNext(s));
TEST_ASSERT_EQUAL_UINT32(b2, runNext(s));
}
static void test_stop_queues_behind_its_own_remote() {
// Overtaking the remote's waiting frame would send the rolling codes out of
// order and the motor would reject the older one.
RadioScheduler s;
const uint32_t up = submit(s, REMOTE_A);
const uint32_t b = submit(s, REMOTE_B);
const uint32_t stop = submit(s, REMOTE_A, 0, true);
TEST_ASSERT_EQUAL_UINT32(up, runNext(s));
TEST_ASSERT_EQUAL_UINT32(b, runNext(s));
TEST_ASSERT_EQUAL_UINT32(stop, runNext(s));
}
static void test_stop_goes_ahead_once_its_remote_is_on_air() {
RadioScheduler s;
const uint32_t up = submit(s, REMOTE_A);
const uint32_t b = submit(s, REMOTE_B);
somfy_tx_job_t job;
uint32_t waitMs;
TEST_ASSERT_TRUE(s.next(0, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(up, job.seq);
// The remote's frame is already going out so the stop can't overtake it.
const uint32_t stop = submit(s, REMOTE_A, 0, true);
s.start(up, 0);
s.ended(up, true, 0);
TEST_ASSERT_EQUAL_UINT32(stop, runNext(s));
TEST_ASSERT_EQUAL_UINT32(b, runNext(s));
}
static void test_stops_go_out_in_the_order_they_came() {
RadioScheduler s;
const uint32_t back = submit(s, REMOTE_B);
const uint32_t stopA = submit(s, REMOTE_A, 0, true);
const uint32_t stopC = submit(s, 0x333333, 0, true);
TEST_ASSERT_EQUAL_UINT32(stopA, runNext(s));
TEST_ASSERT_EQUAL_UINT32(stopC, runNext(s));
TEST_ASSERT_EQUAL_UINT32(back, runNext(s));
}
static void test_second_stop_of_a_remote_stays_behind_the_first() {
RadioScheduler s;
const uint32_t back = submit(s, REMOTE_B);
const uint32_t first = submit(s, REMOTE_A, 0, true);
const uint32_t second = submit(s, REMOTE_A, 0, true);
TEST_ASSERT_EQUAL_UINT32(first, runNext(s));
TEST_ASSERT_EQUAL_UINT32(back, runNext(s));
TEST_ASSERT_EQUAL_UINT32(second, runNext(s));
}
/*********************************************************************
* Room
********************************************************************/
static void test_normal_jobs_leave_the_reserved_slots() {
RadioScheduler s;
for(uint8_t i = 0; i < TX_JOB_QUEUE_SIZE - TX_RESERVED_SLOTS; i++)
TEST_ASSERT_NOT_EQUAL(0, submit(s, REMOTE_B));
TEST_ASSERT_FALSE(s.hasRoom());
TEST_ASSERT_EQUAL_UINT32(0, submit(s, REMOTE_B));
TEST_ASSERT_NOT_EQUAL(0, submit(s, REMOTE_A, 0, true));
}
static void test_stops_use_the_reserved_slots() {
RadioScheduler s;
for(uint8_t i = 0; i < TX_JOB_QUEUE_SIZE - TX_RESERVED_SLOTS; i++) submit(s, REMOTE_B);
for(uint8_t i = 0; i < TX_RESERVED_SLOTS; i++)
TEST_ASSERT_NOT_EQUAL(0, submit(s, REMOTE_A, 0, true));
TEST_ASSERT_EQUAL_UINT32(0, submit(s, REMOTE_A, 0, true));
}
static void test_a_refused_job_has_no_ticket() {
RadioScheduler s;
for(uint8_t i = 0; i < TX_JOB_QUEUE_SIZE - TX_RESERVED_SLOTS; i++) submit(s, REMOTE_B);
somfy_tx_job_t job = makeJob(REMOTE_A);
job.seq = 99;
TEST_ASSERT_EQUAL_UINT32(0, s.submit(job, false, 0, 0));
TEST_ASSERT_EQUAL_UINT32(0, job.seq);
}
static void test_taking_a_job_frees_its_slot() {
RadioScheduler s;
for(uint8_t i = 0; i < TX_JOB_QUEUE_SIZE - TX_RESERVED_SLOTS; i++) submit(s, REMOTE_B);
runNext(s);
TEST_ASSERT_TRUE(s.hasRoom());
}
/*********************************************************************
* Delayed jobs
********************************************************************/
static void test_delayed_job_waits_until_due() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A, 1000, false, 500);
somfy_tx_job_t job;
uint32_t waitMs;
TEST_ASSERT_FALSE(s.next(1000, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(500, waitMs);
TEST_ASSERT_FALSE(s.next(1400, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(100, waitMs);
TEST_ASSERT_TRUE(s.next(1500, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(a, job.seq);
}
static void test_delayed_job_holds_up_the_jobs_behind_it() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A, 0, false, 500);
const uint32_t b = submit(s, REMOTE_B);
TEST_ASSERT_EQUAL_UINT32(0, runNext(s, 100));
TEST_ASSERT_EQUAL_UINT32(a, runNext(s, 500));
TEST_ASSERT_EQUAL_UINT32(b, runNext(s, 500));
}
static void test_stop_overtakes_a_delayed_job_of_another_remote() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A, 0, false, 500);
const uint32_t stop = submit(s, REMOTE_B, 100, true);
TEST_ASSERT_EQUAL_UINT32(stop, runNext(s, 100));
// The stop went off air at 100 so the delay starts again from there.
TEST_ASSERT_EQUAL_UINT32(0, runNext(s, 500));
TEST_ASSERT_EQUAL_UINT32(a, runNext(s, 600));
}
static void test_delay_counts_from_the_job_ahead_going_off_air() {
// The stop at the target is queued just before the favorite and is on air
// longer than the favorite's delay.
RadioScheduler s;
const uint32_t stop = submit(s, REMOTE_A, 0, true);
const uint32_t setMy = submit(s, REMOTE_A, 0, false, 200);
somfy_tx_job_t job;
uint32_t waitMs;
TEST_ASSERT_TRUE(s.next(0, job, waitMs));
TEST_ASSERT_TRUE(s.start(stop, 0));
s.ended(stop, true, 250);
TEST_ASSERT_FALSE(s.next(350, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(100, waitMs);
TEST_ASSERT_TRUE(s.next(450, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(setMy, job.seq);
}
static void test_delay_ignores_a_job_that_ended_before_it_was_queued() {
RadioScheduler s;
submit(s, REMOTE_B, 0);
runNext(s, 50);
const uint32_t a = submit(s, REMOTE_A, 100, false, 200);
somfy_tx_job_t job;
uint32_t waitMs;
TEST_ASSERT_FALSE(s.next(100, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(200, waitMs);
TEST_ASSERT_TRUE(s.next(300, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(a, job.seq);
}
static void test_delay_after_a_job_ended_across_millis_wrap() {
RadioScheduler s;
const uint32_t stop = submit(s, REMOTE_A, 0xFFFFFF00u, true);
const uint32_t setMy = submit(s, REMOTE_A, 0xFFFFFF00u, false, 0x100);
somfy_tx_job_t job;
uint32_t waitMs;
s.next(0xFFFFFF00u, job, waitMs);
s.start(stop, 0xFFFFFF00u);
s.ended(stop, true, 0x80);
TEST_ASSERT_FALSE(s.next(0x100, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(0x80, waitMs);
TEST_ASSERT_TRUE(s.next(0x180, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(setMy, job.seq);
}
static void test_stop_waits_behind_its_remotes_delayed_job() {
// The favorite's long press holds a rolling code, so the stop can't go first.
RadioScheduler s;
const uint32_t setMy = submit(s, REMOTE_A, 0, false, 500);
const uint32_t stop = submit(s, REMOTE_A, 100, true);
TEST_ASSERT_EQUAL_UINT32(0, runNext(s, 100));
TEST_ASSERT_EQUAL_UINT32(setMy, runNext(s, 500));
TEST_ASSERT_EQUAL_UINT32(stop, runNext(s, 500));
}
static void test_delay_across_millis_wrap() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A, 0xFFFFFF00u, false, 0x200);
somfy_tx_job_t job;
uint32_t waitMs;
TEST_ASSERT_FALSE(s.next(0xFFFFFF00u, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(0x200, waitMs);
TEST_ASSERT_FALSE(s.next(0x50, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(0xB0, waitMs);
TEST_ASSERT_TRUE(s.next(0x100, job, waitMs));
TEST_ASSERT_EQUAL_UINT32(a, job.seq);
}
/*********************************************************************
* Outcomes
********************************************************************/
static void test_job_reports_started_then_finished() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A);
somfy_tx_job_t job;
uint32_t waitMs;
s.next(10, job, waitMs);
TEST_ASSERT_TRUE(s.start(a, 20));
TEST_ASSERT_TRUE(s.keepSending(a));
s.ended(a, true, 300);
somfy_tx_event_t evt;
TEST_ASSERT_TRUE(s.popEvent(evt));
TEST_ASSERT_EQUAL_UINT8((uint8_t)tx_event_t::started, (uint8_t)evt.type);
TEST_ASSERT_EQUAL_UINT32(a, evt.seq);
TEST_ASSERT_EQUAL_UINT32(20, evt.at);
TEST_ASSERT_EQUAL_UINT32(REMOTE_A, evt.remoteAddress);
TEST_ASSERT_EQUAL_UINT8((uint8_t)somfy_commands::Up, (uint8_t)evt.cmd);
TEST_ASSERT_TRUE(s.popEvent(evt));
TEST_ASSERT_EQUAL_UINT8((uint8_t)tx_event_t::finished, (uint8_t)evt.type);
TEST_ASSERT_EQUAL_UINT32(300, evt.at);
assertNoEvents(s);
}
static void test_cut_short_job_reports_aborted() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A);
somfy_tx_job_t job;
uint32_t waitMs;
s.next(0, job, waitMs);
s.start(a, 0);
s.ended(a, false, 0);
assertEvent(s, tx_event_t::started, a);
assertEvent(s, tx_event_t::aborted, a);
assertNoEvents(s);
}
static void test_flush_drops_every_waiting_job_once() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A);
const uint32_t stop = submit(s, REMOTE_B, 0, true);
const uint32_t delayed = submit(s, REMOTE_A, 0, false, 5000);
s.flush(0);
assertEvent(s, tx_event_t::dropped, stop);
assertEvent(s, tx_event_t::dropped, a);
assertEvent(s, tx_event_t::dropped, delayed);
assertNoEvents(s);
TEST_ASSERT_EQUAL_UINT32(0, runNext(s, 5000));
}
static void test_flush_cuts_short_the_job_on_air() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A);
somfy_tx_job_t job;
uint32_t waitMs;
s.next(0, job, waitMs);
s.start(a, 0);
s.flush(0);
TEST_ASSERT_FALSE(s.keepSending(a));
s.ended(a, false, 0);
assertEvent(s, tx_event_t::started, a);
assertEvent(s, tx_event_t::aborted, a);
assertNoEvents(s);
}
static void test_job_flushed_before_it_starts_is_dropped() {
// The radio task took it off the queue, then a flush came before it got the
// radio.
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A);
somfy_tx_job_t job;
uint32_t waitMs;
s.next(0, job, waitMs);
s.flush(0);
TEST_ASSERT_FALSE(s.start(a, 0));
assertEvent(s, tx_event_t::dropped, a);
assertNoEvents(s);
}
static void test_every_job_gets_one_outcome_across_a_flush() {
RadioScheduler s;
uint32_t tickets[6];
for(uint8_t i = 0; i < 3; i++) tickets[i] = submit(s, REMOTE_A + i);
somfy_tx_job_t job;
uint32_t waitMs;
s.next(0, job, waitMs);
s.start(job.seq, 0);
s.flush(0);
// Jobs queued after the flush are unaffected by it.
for(uint8_t i = 3; i < 6; i++) tickets[i] = submit(s, REMOTE_B + i);
s.ended(job.seq, s.keepSending(job.seq), 0);
while(runNext(s)) {}
uint8_t outcomes[6] = {};
somfy_tx_event_t evt;
while(s.popEvent(evt)) {
if(evt.type == tx_event_t::started) continue;
for(uint8_t i = 0; i < 6; i++)
if(tickets[i] == evt.seq) outcomes[i]++;
}
for(uint8_t i = 0; i < 6; i++) TEST_ASSERT_EQUAL_UINT8(1, outcomes[i]);
}
static void test_no_outcome_is_lost_when_events_arent_collected() {
// loop() collects the events. If it stalls, jobs are refused rather than
// their outcomes thrown away.
RadioScheduler s;
uint32_t accepted = 0;
for(uint32_t i = 0; i < 200; i++) {
if(!submit(s, REMOTE_A)) break;
accepted++;
runNext(s);
}
TEST_ASSERT_TRUE(accepted > 0);
TEST_ASSERT_TRUE(accepted < 200);
TEST_ASSERT_FALSE(s.hasRoom());
uint32_t started = 0, finished = 0;
somfy_tx_event_t evt;
while(s.popEvent(evt)) {
if(evt.type == tx_event_t::started) started++;
else if(evt.type == tx_event_t::finished) finished++;
}
TEST_ASSERT_EQUAL_UINT32(accepted, started);
TEST_ASSERT_EQUAL_UINT32(accepted, finished);
TEST_ASSERT_TRUE(s.hasRoom());
TEST_ASSERT_NOT_EQUAL(0, submit(s, REMOTE_A));
}
static void test_flush_reports_every_drop_when_events_arent_collected() {
RadioScheduler s;
uint32_t tickets[64];
uint8_t n = 0;
// Outcomes of jobs already sent take up part of the event ring.
for(uint8_t i = 0; i < 20; i++) {
tickets[n] = submit(s, REMOTE_A);
TEST_ASSERT_NOT_EQUAL(0, tickets[n]);
n++;
runNext(s);
}
// Queue as many as are taken.
for(uint32_t t; n < 64 && (t = submit(s, REMOTE_B, 0, true)) != 0;) tickets[n++] = t;
TEST_ASSERT_TRUE(n > 20);
s.flush(0);
uint8_t outcomes[64] = {};
somfy_tx_event_t evt;
while(s.popEvent(evt)) {
if(evt.type == tx_event_t::started) continue;
for(uint8_t i = 0; i < n; i++)
if(tickets[i] == evt.seq) outcomes[i]++;
}
for(uint8_t i = 0; i < n; i++) TEST_ASSERT_EQUAL_UINT8(1, outcomes[i]);
}
static void test_unknown_ticket_is_ignored() {
RadioScheduler s;
TEST_ASSERT_FALSE(s.keepSending(0));
TEST_ASSERT_FALSE(s.keepSending(42));
TEST_ASSERT_FALSE(s.start(42, 0));
s.ended(42, true, 0);
assertNoEvents(s);
}
/*********************************************************************
* Suspend
********************************************************************/
static void test_suspend_flushes_and_refuses_jobs() {
RadioScheduler s;
const uint32_t a = submit(s, REMOTE_A);
s.suspend(TX_SUSPEND_SCAN, 0);
TEST_ASSERT_TRUE(s.suspended());
assertEvent(s, tx_event_t::dropped, a);
TEST_ASSERT_FALSE(s.hasRoom());
TEST_ASSERT_EQUAL_UINT32(0, submit(s, REMOTE_A));
TEST_ASSERT_EQUAL_UINT32(0, submit(s, REMOTE_A, 0, true));
assertNoEvents(s);
}
static void test_resume_needs_every_reason_cleared() {
RadioScheduler s;
s.suspend(TX_SUSPEND_SCAN, 0);
s.suspend(TX_SUSPEND_OTA, 0);
s.resume(TX_SUSPEND_SCAN);
TEST_ASSERT_TRUE(s.suspended());
TEST_ASSERT_EQUAL_UINT32(0, submit(s, REMOTE_A));
s.resume(TX_SUSPEND_OTA);
TEST_ASSERT_FALSE(s.suspended());
TEST_ASSERT_NOT_EQUAL(0, submit(s, REMOTE_A));
}
/*********************************************************************
* Repeater frames
********************************************************************/
static void test_repeater_frames_report_nothing() {
RadioScheduler s;
somfy_tx_job_t job = makeJob(REMOTE_A);
job.raw = true;
const uint32_t r = s.submit(job, false, 0, 0);
TEST_ASSERT_NOT_EQUAL(0, r);
TEST_ASSERT_EQUAL_UINT32(r, runNext(s));
submit(s, REMOTE_B);
job = makeJob(REMOTE_A);
job.raw = true;
s.submit(job, false, 0, 0);
s.flush(0);
somfy_tx_event_t evt;
TEST_ASSERT_TRUE(s.popEvent(evt));
TEST_ASSERT_EQUAL_UINT32(REMOTE_B, evt.remoteAddress);
assertNoEvents(s);
}
static void test_repeater_frame_doesnt_hold_back_a_stop() {
// It isn't one of our remotes so it has no rolling code of ours to keep in
// order.
RadioScheduler s;
somfy_tx_job_t job = makeJob(REMOTE_A);
job.raw = true;
const uint32_t r = s.submit(job, false, 0, 0);
const uint32_t stop = submit(s, REMOTE_A, 0, true);
TEST_ASSERT_EQUAL_UINT32(stop, runNext(s));
TEST_ASSERT_EQUAL_UINT32(r, runNext(s));
discardEvents(s);
}
int main(int argc, char **argv) {
(void)argc;
(void)argv;
UNITY_BEGIN();
RUN_TEST(test_jobs_go_out_in_the_order_they_came);
RUN_TEST(test_tickets_are_distinct_and_set_in_the_job);
RUN_TEST(test_nothing_waiting_sleeps_until_woken);
RUN_TEST(test_stop_goes_ahead_of_other_remotes);
RUN_TEST(test_stop_queues_behind_its_own_remote);
RUN_TEST(test_stop_goes_ahead_once_its_remote_is_on_air);
RUN_TEST(test_stops_go_out_in_the_order_they_came);
RUN_TEST(test_second_stop_of_a_remote_stays_behind_the_first);
RUN_TEST(test_normal_jobs_leave_the_reserved_slots);
RUN_TEST(test_stops_use_the_reserved_slots);
RUN_TEST(test_a_refused_job_has_no_ticket);
RUN_TEST(test_taking_a_job_frees_its_slot);
RUN_TEST(test_delayed_job_waits_until_due);
RUN_TEST(test_delayed_job_holds_up_the_jobs_behind_it);
RUN_TEST(test_stop_overtakes_a_delayed_job_of_another_remote);
RUN_TEST(test_delay_counts_from_the_job_ahead_going_off_air);
RUN_TEST(test_delay_ignores_a_job_that_ended_before_it_was_queued);
RUN_TEST(test_delay_after_a_job_ended_across_millis_wrap);
RUN_TEST(test_stop_waits_behind_its_remotes_delayed_job);
RUN_TEST(test_delay_across_millis_wrap);
RUN_TEST(test_job_reports_started_then_finished);
RUN_TEST(test_cut_short_job_reports_aborted);
RUN_TEST(test_flush_drops_every_waiting_job_once);
RUN_TEST(test_flush_cuts_short_the_job_on_air);
RUN_TEST(test_job_flushed_before_it_starts_is_dropped);
RUN_TEST(test_every_job_gets_one_outcome_across_a_flush);
RUN_TEST(test_no_outcome_is_lost_when_events_arent_collected);
RUN_TEST(test_flush_reports_every_drop_when_events_arent_collected);
RUN_TEST(test_unknown_ticket_is_ignored);
RUN_TEST(test_suspend_flushes_and_refuses_jobs);
RUN_TEST(test_resume_needs_every_reason_cleared);
RUN_TEST(test_repeater_frames_report_nothing);
RUN_TEST(test_repeater_frame_doesnt_hold_back_a_stop);
return UNITY_END();
}