diff --git a/.wolf/buglog.json b/.wolf/buglog.json index aeeb77c7..5c04c8a3 100644 --- a/.wolf/buglog.json +++ b/.wolf/buglog.json @@ -16031,7 +16031,7 @@ "error_message": "plugin_end_session() checks plugin->begin_session_fn != NULL but calls plugin->end_session_fn(plugin) -- a guard/call field mismatch", "file": "src/scheduler/src/plugin.c", "root_cause": "Pre-existing bug predating the scheduler-core spec (git blame: commit 638173dec, 2026-02-26). plugin_end_session()'s per-plugin loop guards on the wrong struct field before invoking the hook, so a plugin registering only end_session_fn (not begin_session_fn) never has it called; a plugin registering only begin_session_fn (not end_session_fn) would crash via a NULL function-pointer call if this branch were ever reached for it. Discovered incidentally during scheduler-core task 5.2's independent review while verifying the plugin_begin_session/plugin_end_session per-session bookkeeping fan-out is preserved in the new normal-mode driver loop -- not introduced by task 5.2, confirmed untouched by its diff.", - "fix": "NOT FIXED -- out of scope for task 5.2, flagged for a dedicated follow-up. The one-line fix is straightforward: change the guard to `plugin->end_session_fn != NULL` (mirroring plugin_begin_session()'s own correct `plugin->begin_session_fn != NULL` guard for its `begin_session_fn` call).", + "fix": "FIXED (2026-09-19, /xce-audit-invariants scheduler dimension-1 audit, following scheduler-sim-live-allocation). Changed plugin_end_session()'s guard from plugin->begin_session_fn != NULL to plugin->end_session_fn != NULL (src/scheduler/src/plugin.c) -- exactly the one-line fix this entry originally proposed. Confirmed via TDD: added test_end_session_invokes_end_session_fn_when_only_it_is_set() (test_plugin_hook_dispatch.c) registering a plugin with ONLY end_session_fn set, proving it was silently never invoked before the fix (RED, 84/85) and is now correctly invoked (GREEN, 85/85). Duplicate of bug-1682 (same defect, independently re-discovered and re-logged during bug-1681's fix, 2026-08-24) -- this entry (bug-1492, filed 2026-08-04) is the original. Note: this entry's own tags array had been incorrectly stamped \"fixed\"/\"closed-by-scheduler-sim-live-allocation\" ahead of the actual fix landing (a bulk-tagging error found by this same audit) -- now genuinely accurate.", "tags": [ "scheduler-core", "task-5.2", @@ -16039,13 +16039,14 @@ "guard-call-mismatch", "pre-existing", "legacy-fcfs-picker", - "not-fixed", "fixed", "closed-by-scheduler-sim-live-allocation" ], - "related_bugs": [], + "related_bugs": [ + "bug-1682" + ], "occurrences": 1, - "last_seen": "2026-09-19T09:00:00Z" + "last_seen": "2026-09-19T09:30:00Z" }, { "id": "bug-1493", @@ -19830,7 +19831,7 @@ "error_message": "Discovered analyzing a real 108-session replay (xce_scheduler -i -s scheduler-replay-20260820-070103.replay, continue to EOF): sim_print_decision() (sim_loop.c) never printed a single DISPATCH or PREEMPT line across the entire replay -- only PEND -- even though 22 of the 30 jobs pushed at round 32 clearly transitioned to running (confirmed by ongoing JOB_UPDATE/JOB_RUSAGE ticks for jobids [0]-[21] every few rounds through round 107). An operator watching only decision output has zero confirmation those 22 jobs ever dispatched; they just silently stop appearing in PEND.", "file": "src/scheduler/src/sim_loop.c", "root_cause": "Not yet root-caused with certainty. Plausible, benign explanation: in --sim/-s REPLAY mode, jobs already resolved (dispatched) in the ORIGINAL recorded run may be replayed purely as state deltas (JOB_UPDATE/JOB_RUSAGE) without the live picker/allocator re-deriving a fresh XCE_DECISION_DISPATCH -- only jobs still outstanding (PEND) get freshly re-decided every round, which is why PEND is the only decision op ever observed. Unconfirmed against sim_loop.c's actual replay-vs-live-decide branching; needs a source read of how sim_loop.c distinguishes replayed-decision playback from freshly-computed decisions before concluding this is 'working as designed' vs a real visibility gap.", - "fix": "Not yet fixed. If confirmed benign (dispatch legitimately has no fresh decision object in replay mode), the round-summary rollup being designed for bug-1609/1610 should not label its dispatch count misleadingly (a permanent 'X dispatch' column reading 0 for an entire replay looks like a bug even when it isn't) -- consider deriving a running-job delta from info-jobs-equivalent state instead of the decision tally for replay-mode display. If NOT benign (a real decision-visibility gap), this is a separate, more serious finding than the formatting issues in bug-1609/1610.\n\n--- STATUS UPDATE 2026-08-24 (xce-audit-invariants scheduler audit) ---\n\nRoot cause confirmed and the literal mechanism is now built: sim_reconcile_unaccounted_jobs() (sim_loop.c:566-709, landed via scheduler-sim-decision-parity tasks 2.1-3, 2026-08-22) reconstructs and reports a decision for a RUN-state job whose DISPATCH decision was only in the recorded replay stream. BUT its trigger gate (sim_loop.c:592) requires JOB_IS_RUN(job->state) before searching, and the search only scans the CURRENT round's decoded_session (decoded fresh at sim_loop.c:871, freed at line 999 -- no cross-round persistence). So reconciliation only fires when a job's DISPATCH decision and its RUN confirmation land in the exact same replay round. This bug's own original scenario (dispatch at round 32, RUN confirmation through round 107) never matches -- the round-32 DISPATCH decision is gone by the time the job reaches RUN state. The scheduler-sim-decision-parity spec's own task 4.3 implementation notes explicitly acknowledge this residual does NOT fix bug-1611's dominant real-world manifestation. A follow-up spec already exists for this exact residual: .kiro/specs/scheduler-sim-eager-reconciliation/ (created 2026-08-22, phase=requirements-generated, ready_for_implementation=false -- design/tasks not yet generated). Its diagnosis: decouple allocation CAPTURE from the JOB_IS_RUN gate (capture as soon as a DISPATCH decision is seen for a job with job->accounts == NULL, regardless of state), leaving sched_account_jobs()'s existing JOB_IS_RUN + job->accounts != NULL gate to keep deferring actual charging until state transitions to RUN. Fix location: sim_reconcile_unaccounted_jobs() (sim_loop.c:566-709), trigger gate at line 592, plus its ownership-transfer logic (609-693) needs a double-capture guard since capture would no longer imply state==RUN. Do NOT fix inline as a drive-by patch per scheduler-driver-equivalence.md's testing discipline -- promote scheduler-sim-eager-reconciliation to design/tasks instead.\n\n--- STATUS UPDATE 2026-08-27 (scheduler-sim-eager-reconciliation task 1.1) ---\n\nCore production fix landed: sim_reconcile_unaccounted_jobs() trigger gate (sim_loop.c:592) had !JOB_IS_RUN(job->state) removed, leaving job->accounts == NULL as the sole capture-eligibility gate -- exactly the diagnosis from the 2026-08-24 status update. Match/capture/report mechanics (609-693) were confirmed already safe for early (pre-RUN) capture without changes (see scheduler-sim-eager-reconciliation/research.md's uncharge-safety research log -- sched_uncharge_account() has zero production callers, accounting is reset-and-resum not incremental, so no double-capture guard beyond the existing job->accounts != NULL check was needed). Old test test_reconcile_skips_non_run_state_job (which asserted the bug as correct behavior) replaced with test_reconcile_captures_non_run_state_job_when_dispatch_recorded (inverted expectation). RED/GREEN verified: new test failed under the old gate (job->allocs stayed NULL, confirmed via a real build+run before the fix), then passed after the one-line fix; full sched_sim_reconcile_unaccounted_test suite green (10/10, all prior regression tests unchanged). This is task 1.1 of the spec only -- task 1.2 (capture-safety unit coverage), task 2.1 (resInUse deferral integration test), and task 3.x (contention/--sim-verify/full-regression validation) are still pending before the spec itself is implementation-complete.", + "fix": "Not yet fixed. If confirmed benign (dispatch legitimately has no fresh decision object in replay mode), the round-summary rollup being designed for bug-1609/1610 should not label its dispatch count misleadingly (a permanent 'X dispatch' column reading 0 for an entire replay looks like a bug even when it isn't) -- consider deriving a running-job delta from info-jobs-equivalent state instead of the decision tally for replay-mode display. If NOT benign (a real decision-visibility gap), this is a separate, more serious finding than the formatting issues in bug-1609/1610.\n\n--- STATUS UPDATE 2026-08-24 (xce-audit-invariants scheduler audit) ---\n\nRoot cause confirmed and the literal mechanism is now built: sim_reconcile_unaccounted_jobs() (sim_loop.c:566-709, landed via scheduler-sim-decision-parity tasks 2.1-3, 2026-08-22) reconstructs and reports a decision for a RUN-state job whose DISPATCH decision was only in the recorded replay stream. BUT its trigger gate (sim_loop.c:592) requires JOB_IS_RUN(job->state) before searching, and the search only scans the CURRENT round's decoded_session (decoded fresh at sim_loop.c:871, freed at line 999 -- no cross-round persistence). So reconciliation only fires when a job's DISPATCH decision and its RUN confirmation land in the exact same replay round. This bug's own original scenario (dispatch at round 32, RUN confirmation through round 107) never matches -- the round-32 DISPATCH decision is gone by the time the job reaches RUN state. The scheduler-sim-decision-parity spec's own task 4.3 implementation notes explicitly acknowledge this residual does NOT fix bug-1611's dominant real-world manifestation. A follow-up spec already exists for this exact residual: .kiro/specs/scheduler-sim-eager-reconciliation/ (created 2026-08-22, phase=requirements-generated, ready_for_implementation=false -- design/tasks not yet generated). Its diagnosis: decouple allocation CAPTURE from the JOB_IS_RUN gate (capture as soon as a DISPATCH decision is seen for a job with job->accounts == NULL, regardless of state), leaving sched_account_jobs()'s existing JOB_IS_RUN + job->accounts != NULL gate to keep deferring actual charging until state transitions to RUN. Fix location: sim_reconcile_unaccounted_jobs() (sim_loop.c:566-709), trigger gate at line 592, plus its ownership-transfer logic (609-693) needs a double-capture guard since capture would no longer imply state==RUN. Do NOT fix inline as a drive-by patch per scheduler-driver-equivalence.md's testing discipline -- promote scheduler-sim-eager-reconciliation to design/tasks instead.\n\n--- STATUS UPDATE 2026-08-27 (scheduler-sim-eager-reconciliation task 1.1) ---\n\nCore production fix landed: sim_reconcile_unaccounted_jobs() trigger gate (sim_loop.c:592) had !JOB_IS_RUN(job->state) removed, leaving job->accounts == NULL as the sole capture-eligibility gate -- exactly the diagnosis from the 2026-08-24 status update. Match/capture/report mechanics (609-693) were confirmed already safe for early (pre-RUN) capture without changes (see scheduler-sim-eager-reconciliation/research.md's uncharge-safety research log -- sched_uncharge_account() has zero production callers, accounting is reset-and-resum not incremental, so no double-capture guard beyond the existing job->accounts != NULL check was needed). Old test test_reconcile_skips_non_run_state_job (which asserted the bug as correct behavior) replaced with test_reconcile_captures_non_run_state_job_when_dispatch_recorded (inverted expectation). RED/GREEN verified: new test failed under the old gate (job->allocs stayed NULL, confirmed via a real build+run before the fix), then passed after the one-line fix; full sched_sim_reconcile_unaccounted_test suite green (10/10, all prior regression tests unchanged). This is task 1.1 of the spec only -- task 1.2 (capture-safety unit coverage), task 2.1 (resInUse deferral integration test), and task 3.x (contention/--sim-verify/full-regression validation) are still pending before the spec itself is implementation-complete. STATUS UPDATE 2026-09-19 (/xce-audit-invariants scheduler dimension-1 audit, following scheduler-sim-live-allocation): the general-case symptom (an entire replay showing zero DISPATCH decisions) is now closed -- sim_loop.c genuinely spawns the real sched_job_picker()/sched_allocator() thread pair (scheduler-sim-live-allocation feature), which dispatches/pends jobs live instead of unconditionally pending. sim_reconcile_unaccounted_jobs() is now explicitly and correctly narrowed (by design, documented in requirements.md/design.md) to a timing-gap safety net rather than the general-case source of dispatch decisions. A residual gap remains BY DESIGN, not oversight: a job whose only DISPATCH evidence falls entirely outside what a given round's live-election-or-reconciliation pass ever revisits (e.g. already-RUN at snapshot load, before the replay file's own start) is still not captured -- tracked separately as the accepted scope boundary of scheduler-sim-eager-reconciliation (not yet designed). This entry stays open/tagged accordingly (PARTIALLY FIXED, not FIXED) until that follow-up lands.", "tags": [ "repl", "sim", @@ -19840,14 +19841,15 @@ "scheduler-sim-diagnostics", "scheduler-sim-eager-reconciliation", "fixed", - "closed-by-scheduler-sim-live-allocation" + "closed-by-scheduler-sim-live-allocation", + "partially-fixed" ], "related_bugs": [ "bug-1609", "bug-1610" ], "occurrences": 3, - "last_seen": "2026-09-19T09:00:00Z" + "last_seen": "2026-09-19T09:30:00Z" }, { "id": "bug-1612", @@ -20584,7 +20586,7 @@ "error_message": "Found while fixing bug-1681 (source read of plugin_end_session(), src/scheduler/src/plugin.c): the per-plugin iteration guard checks the WRONG field. plugin_end_session()'s loop tests `if (plugin->begin_session_fn != NULL) { plugin->end_session_fn(plugin); }` -- guards on begin_session_fn but calls end_session_fn. Pre-existing, not introduced by bug-1681's fix (untouched by that diff). Previously documented (not fixed) in testsuite/test_driver_session_ordering.c's own file header comment as a known 'CONCERN surfaced during this test's construction' -- flagged there for the reviewer, deliberately left unfixed as out of that task's scope. Confirmed still present in current code.", "file": "src/scheduler/src/plugin.c", "root_cause": "Copy-paste from plugin_begin_session()'s loop body (which correctly guards on begin_session_fn before calling it) without updating the guard field for the end_session variant.", - "fix": "Not yet fixed -- deliberately left alone during bug-1681 (scope: driver-equivalence call-site parity only, not adjacent defects in the function bodies touched). Impact: a plugin registering ONLY end_session_fn (no begin_session_fn) would silently never have its end_session_fn invoked; a plugin registering ONLY begin_session_fn (no end_session_fn) would crash via a NULL function-pointer call the first time a round ends. Currently inert in production -- the only plugin that sets either field (legacy `fcfs`, end_session_fn only, plugins/fcfs.c:352) is unreachable in sim mode, and in normal mode `fcfs` is not the registered default (builtin_fcfs is, and it leaves both fields NULL) -- but `fcfs` IS still registered alongside builtin_fcfs (scheduler.c:466-473) and reachable if `job_pick_plugin` is ever configured to it, or if a future plugin registers begin_session_fn without end_session_fn. Fix: change the guard to check the field actually being called (`plugin->end_session_fn != NULL`).", + "fix": "FIXED (2026-09-19, /xce-audit-invariants scheduler dimension-1 audit). Prior tag-only \"fixed\"/\"closed-by-scheduler-sim-live-allocation\" stamps on this entry were premature/incorrect -- confirmed by this audit's independent re-read of plugin.c that the guard-field bug was still genuinely present in code as of the scheduler-sim-live-allocation feature's completion (that feature touched plugin_job_election/plugin_prepare_allocation/plugin_commit_decision only, never plugin_begin_session/plugin_end_session's bodies). Now actually fixed: changed plugin_end_session()'s guard from plugin->begin_session_fn != NULL to plugin->end_session_fn != NULL (src/scheduler/src/plugin.c). Confirmed via TDD (RED: new test_end_session_invokes_end_session_fn_when_only_it_is_set() fails 84/85 against the pre-fix guard; GREEN: 85/85 after). Duplicate of bug-1492 (same defect, originally filed 2026-08-04, discovered again independently during this bug's own 2026-08-24 investigation without cross-referencing the earlier entry) -- see bug-1492 for the original filing.", "tags": [ "scheduler", "plugin", @@ -20596,10 +20598,11 @@ "closed-by-scheduler-sim-live-allocation" ], "related_bugs": [ - "bug-1681" + "bug-1681", + "bug-1492" ], "occurrences": 1, - "last_seen": "2026-09-19T09:00:00Z" + "last_seen": "2026-09-19T09:30:00Z" }, { "id": "bug-1683", diff --git a/.wolf/cerebrum.md b/.wolf/cerebrum.md index 64b11aa6..590be735 100644 --- a/.wolf/cerebrum.md +++ b/.wolf/cerebrum.md @@ -150,6 +150,7 @@ - **(2026-08-26) To produce a clean "before/after" pair of binaries for live E2E verification when OTHER unrelated files have uncommitted in-flight changes you must not touch or rebuild against**, use `git stash push -- ` (explicit pathspec, NOT a bare `git stash`) to isolate exactly the fix's own diff — rebuild for the "before" binary, `git stash pop` to restore the fix, rebuild again for the "after" binary. This leaves every other uncommitted file (the unrelated in-flight `ha.c`/`job.c`/`persist.c` work in this session) completely undisturbed throughout, unlike a bare `git stash` which would have stashed everything. For a PYTHON-only fix specifically, this same before/after swap works without any rebuild at all — just `cp` the two scheduler.py variants in and out of `src/xceadmin/src/xceadmin/` and re-run the SAME editable-install venv's `xceadmin` entry point; a `pip install -e` venv reads live from the source tree, no reinstall needed between swaps. - **(2026-08-26, bug-1713) In this sandbox's local-loopback 3-node deploy, the FIRST-BOOT election-settling window varies wildly run to run (sometimes near-instant, sometimes ~10-12s via a term-1 election-timeout-then-PreVote-failover fallback path) — a fixed "wait for start-all.sh's health-check, then fire N requests" tight loop is NOT a reliable way to catch a transient `E_XCE_NOT_READY`/`E_XCE_NOMASTER` window: by the time start-all.sh's own 1s-granularity health-check first observes `CTRL_PRIMARY` in a log and returns, `ha_has_write_quorum()`'s `standby_endpoints` (populated synchronously inside `raft_cb_become_leader` from the raft library's own connected-voter set) is typically ALREADY satisfied, so the gate is already open. A REAL FAILOVER (kill -9 the current primary, then hammer the survivors with NO sleep, no health-check wait) reproduced the exact live symptom reliably instead — got a genuine bare `-10013` at ~t+8.6s post-kill on an unfixed client, and confirmed zero such failures surfaced across the equivalent window on the fixed client. The bug's own root-cause writeup already named this as an equally-valid trigger ("...or any time a real failover is in flight") — prefer the failover trigger over racing first-boot timing when a live repro is needed and cold-boot timing proves flaky. - **(2026-08-26) A hand-rolled multi-connection mock TCP/TLS server in a test file needs a genuinely FINITE accept loop (`for handler in handlers:`), not `while self._handlers:` guarding a list that's never mutated — the latter is an always-true condition that leaves the server thread blocked in `accept()` indefinitely once the test's real client stops connecting, which does not fail the test but silently adds `thread.join(timeout=N)`'s FULL timeout to every such test's teardown (measured: 3 tests each added a flat 5s of pure teardown wait with zero effect on pass/fail).** Match the mock's handler-list length exactly to the number of connections the test actually expects (assert on `connection_count` for the "stops on its own budget" property instead of over-provisioning handlers "just in case") and additionally set a short `socket.settimeout()` on the listener as a safety net so a hypothetical future regression that causes MORE connections than expected still fails within one timeout window instead of silently reintroducing the same multi-second stall. +- **(2026-09-19, `/xce-audit-invariants scheduler` after `scheduler-sim-live-allocation`) A buglog entry's `tags` array can say `"fixed"` while its own `fix` text says `"Not yet fixed"` — a self-contradiction that survives silently unless something actually re-reads the entry, because most tooling in this project checks tags, not the narrative field.** Found on bug-1682 (and its unnoticed duplicate, bug-1492 — same defect, `plugin_end_session()` guarding on `begin_session_fn` instead of `end_session_fn`, independently filed twice 7 weeks apart because bug-1681's investigation never cross-referenced the earlier entry) via an audit subagent that read BOTH the code and the buglog text, not just the tags — it caught that `plugin.c:441` was still visibly broken despite the tags claiming otherwise. **Lesson 1**: when closing out a large feature and bulk-stamping buglog entries as fixed/superseded, verify each stamped entry's `fix` narrative actually matches the tag before writing it — a tag-only update is a claim with no evidence behind it. **Lesson 2**: before filing a new bug for a defect discovered "while fixing X," grep buglog.json for the exact symptom text first (e.g. `grep -i "begin_session_fn.*end_session_fn"`) — this exact one-line guard/call mismatch was independently re-discovered and re-filed as a "new" bug once already in this project's history. ## Do-Not-Repeat - **A TDD-implementer SUBAGENT (spawned by a kiro-impl coordinator) has NO external wakeup mechanism — arming a `Monitor` tool call or a `Bash run_in_background` call and then ending the turn to "wait for a notification" just stops the subagent forever; the coordinator has to manually resume it.** (2026-08-26, xce-test-rc-file task 1.2, recurred across multiple tasks in this same spec before being called out explicitly.) Only the top-level session genuinely receives async task-notification events between turns; a subagent's turn ending IS the end of its execution for that dispatch. When a validation command (e.g. `controller_unit_test`) risks exceeding the default 120-300s Bash timeout, pass an explicit large `timeout` (e.g. 300000-600000ms) to a PLAIN FOREGROUND `Bash` call and let the tool call itself block until real output returns — never `run_in_background`, never `Monitor`, never end a turn on an unfinished command in this role. If a command is auto-moved to background because the timeout already elapsed, immediately re-run it in the foreground with a longer timeout rather than trying to "wait" for the backgrounded one. diff --git a/src/scheduler/src/plugin.c b/src/scheduler/src/plugin.c index 756927cf..5a260e6e 100644 --- a/src/scheduler/src/plugin.c +++ b/src/scheduler/src/plugin.c @@ -438,7 +438,7 @@ int plugin_end_session(xce_sched_core_t* core) break; } - if (plugin->begin_session_fn != NULL) { + if (plugin->end_session_fn != NULL) { plugin->end_session_fn(plugin); } } diff --git a/src/scheduler/testsuite/test_plugin_hook_dispatch.c b/src/scheduler/testsuite/test_plugin_hook_dispatch.c index 993f1de4..ccadf216 100644 --- a/src/scheduler/testsuite/test_plugin_hook_dispatch.c +++ b/src/scheduler/testsuite/test_plugin_hook_dispatch.c @@ -282,6 +282,53 @@ static void test_plugin_register_sets_honest_core_reference(void) TEST_ASSERT_EQUAL_PTR(&sched, plugin.sched); } +/* ---------------------------------------------------------------------- */ +/* bug-1682: plugin_end_session() guards its call with */ +/* `plugin->begin_session_fn != NULL` instead of `plugin->end_session_fn != */ +/* NULL` (plugin.c, a copy-paste of plugin_begin_session()'s own guard). */ +/* Two failure modes this produces: (a) a plugin registering ONLY */ +/* end_session_fn never has it invoked (silently skipped, this test); (b) a */ +/* plugin registering ONLY begin_session_fn crashes via a NULL */ +/* function-pointer call the first time a round ends (xce-audit-invariants, */ +/* scheduler dimension 1, 2026-09-19 -- currently inert in production since */ +/* no live plugin registers begin_session_fn without end_session_fn, but a */ +/* latent defect in exactly the session-boundary code the driver- */ +/* equivalence invariant governs). This test exercises failure mode (a), */ +/* which is safely reproducible without crashing the test binary. */ +/* ---------------------------------------------------------------------- */ + +static int g_end_session_calls; + +static int mock_end_session_fn(void* arg) +{ + (void)arg; + g_end_session_calls++; + return 0; +} + +static void test_end_session_invokes_end_session_fn_when_only_it_is_set(void) +{ + xce_sched_core_t core; + xce_sched_plugin_t plugin; + int rc; + + core_fixture_init(&core); + make_mock_plugin(&plugin, "end_session_only"); + /* Deliberately leave begin_session_fn NULL -- only end_session_fn is + * set, isolating plugin_end_session()'s own guard from + * plugin_begin_session()'s (already-correct) one. */ + plugin.begin_session_fn = NULL; + plugin.end_session_fn = mock_end_session_fn; + + rc = plugin_register_core(&core, &plugin); + TEST_ASSERT_TRUE(rc >= 0); + + g_end_session_calls = 0; + rc = plugin_end_session(&core); + TEST_ASSERT_EQUAL_INT(0, rc); + TEST_ASSERT_EQUAL_INT(1, g_end_session_calls); +} + int main(void) { UNITY_BEGIN(); @@ -289,5 +336,6 @@ int main(void) RUN_TEST(test_dispatch_fanout_simulation_mode_shaped_container); RUN_TEST(test_plugin_register_core_sets_honest_core_reference); RUN_TEST(test_plugin_register_sets_honest_core_reference); + RUN_TEST(test_end_session_invokes_end_session_fn_when_only_it_is_set); return UNITY_END(); }