132 std::cout <<
"=== Hold rules ===" << std::endl;
134 g_dir = fs::temp_directory_path() /
"daq_hold_rules_test";
135 fs::remove_all(
g_dir);
136 fs::create_directories(
g_dir);
141 const char* bad[] = {
"0",
"-1",
"abc",
"1e3",
"999999999999",
"",
"1000x",
" ",
"+5"};
142 bool all_rejected =
true;
143 for (
const char*
b : bad)
145 all_rejected =
false;
146 check(all_rejected,
"parseHoldMs rejects 0, signs, junk, partial numbers and overflow");
151 "parseHoldMs enforces the wire ceiling");
158 check(svc.
init(cfg_path),
"service initialised from the test config");
163 check(kFlow != State::UNKNOWN && kFire != State::UNKNOWN,
"test states resolved");
171 check(!
ok,
"a client-supplied duration is refused for the fire state");
172 check(why ==
"duration not permitted for this state",
"the refusal says why: " + why);
176 "the REFUSED fire hold did not transition — a replayed frame cannot start a burn");
182 const auto t0 = Clock::now();
183 check(svc.
transitionTo(kFlow, 1500, &why),
"a duration IS honoured for the flow state");
186 const long long total =
187 std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
188 check(
back >= 0,
"the hold expired back into the return state on its own");
191 check(total >= 1490 && total <= 1600,
192 "the hold ran for the REQUESTED window, not the configured default (" +
193 std::to_string(total) +
" ms)");
199 check(!svc.
transitionTo(kFlow, 9999, &why),
"a duration over max_ms is refused");
200 check(why.rfind(
"duration exceeds max", 0) == 0,
201 "the refusal names the ceiling: " + why);
206 "a duration aimed at a non-holdable state is refused");
207 check(why ==
"state is not holdable",
"the refusal says why: " + why);
212 const auto t0 = Clock::now();
213 check(svc.
transitionTo(kFlow),
"the flow state is enterable with no duration at all");
215 const long long total =
216 std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
217 check(total >= 990 && total <= 1100,
218 "it used [flow].duration_ms (" + std::to_string(total) +
" ms)");
229 FlowCfg{
"Flow Test",
true,
"Press Standby", 1000, 5000,
"Main Valve", 0.5, 0.0});
231 check(svc.init(cfg_path),
"service initialised with a gated flow state");
234 check(svc.transitionTo(kStandby),
"reached the hub");
240 check(listener.
start(),
"bound the actuator UDP port to watch the wire");
242 const auto t0 = Clock::now();
243 check(svc.transitionTo(kFlow, 1000),
"gated hold accepted");
245 const long long total =
246 std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
248 check(total >= 1480 && total <= 1620,
249 "state was held for requested + open-stagger (" + std::to_string(total) +
" ms)");
251 std::this_thread::sleep_for(std::chrono::milliseconds(50));
253 const auto b = listener.
bursts();
258 const long long main_close =
261 check(up_open >= 0 && main_open >= 0 && main_close >= 0,
262 "saw the upstream open, the main open and the main close on the wire");
264 const long long stagger = (up_open >= 0 && main_open >= 0) ? main_open - up_open : -1;
265 check(stagger >= 400 && stagger <= 700,
266 "the main opened ~500 ms AFTER the upstream, not with it (" +
267 std::to_string(stagger) +
" ms)");
271 const long long main_ms = (main_open >= 0 && main_close >= 0) ? main_close - main_open : -1;
272 check(main_ms >= 980 && main_ms <= 1150,
273 "the main valve was open for the REQUESTED 1000 ms (" + std::to_string(main_ms) +
282 FlowCfg{
"Flow Test",
true,
"Press Standby", 1000, 5000,
"Main Valve", 0.5, 0.3});
284 check(svc.init(cfg_path),
"service initialised with a two-sided stagger");
287 check(svc.transitionTo(kStandby),
"reached the hub");
289 const auto t0 = Clock::now();
290 check(svc.transitionTo(kFlow, 1000),
"two-sided gated hold accepted");
292 const long long total =
293 std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
296 check(total >= 1180 && total <= 1320,
297 "hold = requested + open - close stagger (" + std::to_string(total) +
" ms)");
303 writeConfig(
FlowCfg{
"Flow Test",
true,
"Press Standby", 200, 5000,
"Main Valve", 0.0, 2.0});
305 check(svc.init(cfg_path),
"service initialised with an oversized close stagger");
308 check(svc.transitionTo(kStandby),
"reached the hub");
309 const auto t0 = Clock::now();
310 check(svc.transitionTo(kFlow, 200),
"hold accepted");
312 const long long total =
313 std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
314 check(
back >= 0 && total < 1000,
"clamped to a minimal hold instead of underflowing (" +
315 std::to_string(total) +
" ms)");
322 check(svc.init(cfg_path),
"service initialised with the flow state renamed");
324 check(renamed != State::UNKNOWN,
"the renamed state resolved");
327 check(svc.transitionTo(renamed, 400, &why),
328 "the renamed state is still the flow state — nothing resolved it by name");
335 check(svc.init(cfg_path),
"service initialised with a broken return target");
339 "a hold whose return target is unresolvable is refused");
340 check(why ==
"state is not holdable",
"refused as not holdable: " + why);
344 "the rig never entered the state, so its valve never opened");
351 check(svc.init(cfg_path),
"service initialised with no state marked is_flow");
355 "with nothing flagged, a supplied duration has no state to apply to");
356 check(why ==
"state is not holdable",
"refused as not holdable: " + why);
359 fs::remove_all(
g_dir);
360 std::cout << (
g_failures == 0 ?
"\nPASSED" :
"\nFAILED") << std::endl;
long long firstCommand(const std::vector< Burst > &bursts, uint8_t ch, uint8_t hw, long long after_ms=-1)
When ch was first commanded to hw at or after after_ms.
static long long waitForState(SequencerService &svc, State want, int timeout_ms)
Wait for the service to land in want; returns ms elapsed, or -1 on timeout.