feat(scheduler-core): implement simulation-mode startup (task 11.1)
Add sim.h/sim.c implementing AC 13 steps 1-4: open+validate ReplayHeader, locate the most recent SnapshotEntry at/before --sim-start-ts, load it via xce_sched_core_load_snapshot(), and set the virtual clock anchors. Wire all seven simulation CLI flags into main.c's task-5.2 mode-selection branch. Event loop itself (step 5) deferred to task 11.2. Fixes two real bugs found during TDD: a SnapshotEntry_get_ts() use-after-free and a pend_updates NULL-deref in xce_sched_core_fini() for bare-core callers. 27/27 scheduler suite green, full project build clean.
This commit is contained in:
parent
16d756aca3
commit
5d587ecf32
7 changed files with 1366 additions and 21 deletions
528
src/scheduler/testsuite/test_sim_startup.c
Normal file
528
src/scheduler/testsuite/test_sim_startup.c
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
/*
|
||||
* test_sim_startup.c - contract test for simulation-mode startup
|
||||
* (scheduler-core spec, task 11.1, Req 5 AC 13).
|
||||
*
|
||||
* Observable under test (task 11.1's own text):
|
||||
* - starting simulation against a valid fixture file reaches a
|
||||
* ready-to-run state with the virtual clock reporting the snapshot's
|
||||
* timestamp.
|
||||
* - starting against a file missing its initial snapshot entry fails with
|
||||
* a clear error before any event is processed.
|
||||
*
|
||||
* Fixture strategy: rather than hand-constructing capnp bytes, this file
|
||||
* builds REAL, valid `.replay` fixtures via the SAME production code path
|
||||
* normal-mode recording already uses (sched_record_start()/
|
||||
* sched_record_stop(), task 9.2) against a small real xce_scheduler_t
|
||||
* fixture -- same technique test_record_lifecycle.c already establishes.
|
||||
* The "missing initial snapshot entry" fixture is then derived from a valid
|
||||
* one by truncating it to exactly its first message (the ReplayHeader),
|
||||
* discovered by decoding that one message and reading the resulting file
|
||||
* offset -- no hand-rolled capnp byte construction needed for that case
|
||||
* either. The "first message is not a valid ReplayHeader" and "first data
|
||||
* message is not a SnapshotEntry" fixtures DO hand-construct one wrong-
|
||||
* shaped message directly (same technique test_e2e_capnp.c/
|
||||
* test_record_lifecycle.c already use for building capnp messages by hand),
|
||||
* since there is no production code path that ever writes a malformed
|
||||
* stream to reuse.
|
||||
*
|
||||
* White-box #include of ../src/core.c, ../src/core_clock.c (task 11.1's own
|
||||
* code under test, sim.c, needs both -- xce_sched_core_init/fini and
|
||||
* xce_core_now()) PLUS ../src/snapshot.c, ../src/snapshot_wire.c,
|
||||
* ../src/snapshot_load.c (task 8.1's real save/load, needed both to BUILD
|
||||
* the fixture via sched_record_start() and to exercise sim.c's own
|
||||
* xce_sched_core_load_snapshot() call) PLUS ../src/recording.c (real
|
||||
* sched_record_start()/sched_record_stop(), used ONLY to build fixtures --
|
||||
* this file never asserts anything about recording.c's own behavior, that
|
||||
* is test_record_lifecycle.c's job) PLUS ../src/sim.c (the code under
|
||||
* test). No driver.c/plugin.c/core_events.c/core_session.c/decision.c: this
|
||||
* file never drives a real scheduling session (task 11.1 stops at
|
||||
* "ready-to-run", not the event loop), so none of those are needed.
|
||||
*
|
||||
* Stubs below mirror the exact same link-satisfying set
|
||||
* test_record_lifecycle.c/test_driver_session_ordering.c already establish
|
||||
* for this core.c/core_clock.c/snapshot.c/snapshot_wire.c/snapshot_load.c/
|
||||
* recording.c combination (kill_sched_*, sched_resolve_job_picker's real
|
||||
* reimplementation -- schedobj.c is not linked).
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "controller.capnp.h"
|
||||
#include "kvmgr.h"
|
||||
#include "scheduler.h"
|
||||
#include "xstring.h"
|
||||
|
||||
/* Pull in the real functions under test into this TU (white-box #include,
|
||||
* same convention as test_record_lifecycle.c). */
|
||||
#include "../src/core.c"
|
||||
#include "../src/core_clock.c"
|
||||
#include "../src/recording.c"
|
||||
#include "../src/sim.c"
|
||||
#include "../src/snapshot.c"
|
||||
#include "../src/snapshot_load.c"
|
||||
#include "../src/snapshot_wire.c"
|
||||
|
||||
void kill_sched_job(void* arg) { (void)arg; }
|
||||
void kill_sched_queue(void* arg) { (void)arg; }
|
||||
void kill_sched_worker(void* arg) { (void)arg; }
|
||||
void kill_sched_user(void* arg) { (void)arg; }
|
||||
void kill_sched_project(void* arg) { (void)arg; }
|
||||
void kill_sched_resval(xce_val_t* val) { (void)val; }
|
||||
|
||||
/* plugin.c is not linked into this TU (no session is ever driven, so no
|
||||
* real plugin unregistration ever happens) -- same stub every other
|
||||
* core.c-including test file in this directory establishes for
|
||||
* xce_sched_core_init()'s kvmgr datakiller. */
|
||||
void plugin_unregister(void* arg) { (void)arg; }
|
||||
|
||||
/* Real reimplementation of schedobj.c's sched_resolve_job_picker -- same
|
||||
* rationale/algorithm as test_record_lifecycle.c's own copy (schedobj.c is
|
||||
* not linked). Both the FIXTURE core (built via a real xce_scheduler_t,
|
||||
* needs a resolvable picker for sched_record_start()'s own snapshot to be
|
||||
* well-formed) and sim.c's own core (which never registers a picker, per
|
||||
* sim.h's documented scope boundary -- this will legitimately return
|
||||
* -EINVAL/-ENOENT for that core, which xce_sched_core_load_snapshot()
|
||||
* gracefully WARNs and skips) share this one implementation. */
|
||||
int sched_resolve_job_picker(xce_sched_core_t* core, xce_sched_plugin_t** out)
|
||||
{
|
||||
xce_sched_plugin_t* plugin = NULL;
|
||||
int rc = 0;
|
||||
|
||||
if ((core == NULL) || (out == NULL)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*out = NULL;
|
||||
|
||||
if (core->job_pick_plugin == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
lock(core->plugins, RTTI_LOCK_READ);
|
||||
rc = key_find(core->plugins, core->job_pick_plugin, (void**)&plugin);
|
||||
unlock(core->plugins);
|
||||
|
||||
if (rc <= 0) {
|
||||
return (rc < 0) ? rc : -ENOENT;
|
||||
}
|
||||
|
||||
if ((plugin == NULL) || (plugin->picker_iface == NULL)
|
||||
|| (plugin->picker_iface->next_job == NULL)
|
||||
|| (plugin->picker_iface->begin_session == NULL)
|
||||
|| (plugin->picker_iface->end_session == NULL)
|
||||
|| (plugin->picker_iface->on_dispatch == NULL)
|
||||
|| (plugin->picker_iface->on_pend == NULL)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*out = plugin;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Fixture helpers */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static void make_tmp_dir(char* out, size_t out_len, const char* suffix)
|
||||
{
|
||||
char tmpl[PATH_MAX];
|
||||
|
||||
snprintf(tmpl, sizeof(tmpl), "/tmp/sched_simstart%s_XXXXXX", suffix);
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(tmpl));
|
||||
TEST_ASSERT_TRUE(strlen(tmpl) < out_len);
|
||||
strcpy(out, tmpl);
|
||||
}
|
||||
|
||||
static int stub_picker_begin_session(void* picker)
|
||||
{
|
||||
(void)picker;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static xce_sched_job_t* stub_picker_next_job(void* picker)
|
||||
{
|
||||
(void)picker;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int stub_picker_end_session(void* picker)
|
||||
{
|
||||
(void)picker;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_picker_jobid_noop(void* picker, uint64_t jobid)
|
||||
{
|
||||
(void)picker;
|
||||
(void)jobid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_picker_queue_update_noop(void* picker, xce_sched_queue_t* q)
|
||||
{
|
||||
(void)picker;
|
||||
(void)q;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_picker_job_noop(void* picker, xce_sched_job_t* job)
|
||||
{
|
||||
(void)picker;
|
||||
(void)job;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_picker_job_state_change_noop(void* picker, xce_sched_job_t* job,
|
||||
uint32_t state)
|
||||
{
|
||||
(void)picker;
|
||||
(void)job;
|
||||
(void)state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static xce_job_picker_iface_t g_stub_picker_iface
|
||||
= { .init = NULL,
|
||||
.fini = NULL,
|
||||
.begin_session = stub_picker_begin_session,
|
||||
.next_job = stub_picker_next_job,
|
||||
.end_session = stub_picker_end_session,
|
||||
.on_dispatch = stub_picker_jobid_noop,
|
||||
.on_pend = stub_picker_jobid_noop,
|
||||
.on_queue_update = stub_picker_queue_update_noop,
|
||||
.on_job_push = stub_picker_job_noop,
|
||||
.on_job_state_change = stub_picker_job_state_change_noop,
|
||||
.on_job_update = stub_picker_job_noop,
|
||||
.on_config = NULL,
|
||||
.on_job_rusage = NULL };
|
||||
|
||||
static xce_sched_plugin_t g_stub_picker_plugin
|
||||
= { .name = "test_sim_startup_picker",
|
||||
.picker_iface = &g_stub_picker_iface };
|
||||
|
||||
/* Builds a small, real xce_scheduler_t fixture (same pattern
|
||||
* test_record_lifecycle.c's fixture_init() establishes) purely to drive
|
||||
* sched_record_start()/sched_record_stop() and produce a REAL, valid
|
||||
* `.replay` file this test's OWN code under test (sim.c) then consumes. */
|
||||
static void build_fixture_scheduler(xce_scheduler_t* sched, char* record_dir)
|
||||
{
|
||||
int rc;
|
||||
|
||||
memset(sched, 0, sizeof(*sched));
|
||||
mylock_init(&(sched->record_lock));
|
||||
|
||||
rc = xce_sched_core_init(&(sched->core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
lock(sched->core.plugins, RTTI_LOCK_WRITE);
|
||||
rc = key_add(sched->core.plugins, (void*)g_stub_picker_plugin.name,
|
||||
(void*)&g_stub_picker_plugin);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, rc);
|
||||
unlock(sched->core.plugins);
|
||||
|
||||
sched->core.job_pick_plugin = g_stub_picker_plugin.name;
|
||||
sched->record_dir = record_dir;
|
||||
}
|
||||
|
||||
/* Produces a valid `.replay` fixture (ReplayHeader + initial SnapshotEntry)
|
||||
* at `out_path` via the real sched_record_start()/sched_record_stop(); the
|
||||
* chosen snapshot timestamp is returned via `out_ts`. */
|
||||
static void write_valid_fixture(char* out_path, size_t out_path_len,
|
||||
uint64_t* out_ts, const char* suffix)
|
||||
{
|
||||
char record_dir[PATH_MAX];
|
||||
xce_scheduler_t sched;
|
||||
int rc;
|
||||
|
||||
make_tmp_dir(record_dir, sizeof(record_dir), suffix);
|
||||
build_fixture_scheduler(&sched, record_dir);
|
||||
|
||||
rc = sched_record_start(&sched, "fixture.replay");
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_TRUE(strlen(sched.record_file_path) < out_path_len);
|
||||
strcpy(out_path, sched.record_file_path);
|
||||
*out_ts = sched.record_start_ts;
|
||||
|
||||
rc = sched_record_stop(&sched);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
}
|
||||
|
||||
/* Truncates `src_path` down to just its first capnp message (the
|
||||
* ReplayHeader) and writes the result to `dst_path` -- simulates a replay
|
||||
* file whose stream ends immediately after the header, i.e. "missing
|
||||
* initial snapshot entry". */
|
||||
static void truncate_to_header_only(const char* src_path, const char* dst_path)
|
||||
{
|
||||
FILE* src;
|
||||
FILE* dst;
|
||||
struct capn c;
|
||||
long header_end;
|
||||
char* buf;
|
||||
size_t n;
|
||||
|
||||
src = fopen(src_path, "r");
|
||||
TEST_ASSERT_NOT_NULL(src);
|
||||
TEST_ASSERT_EQUAL_INT(0, capn_init_fp(&c, src, 0));
|
||||
capn_free(&c);
|
||||
header_end = ftell(src);
|
||||
TEST_ASSERT_TRUE(header_end > 0);
|
||||
rewind(src);
|
||||
|
||||
buf = (char*)malloc((size_t)header_end);
|
||||
TEST_ASSERT_NOT_NULL(buf);
|
||||
n = fread(buf, 1, (size_t)header_end, src);
|
||||
TEST_ASSERT_EQUAL_UINT((size_t)header_end, n);
|
||||
fclose(src);
|
||||
|
||||
dst = fopen(dst_path, "w");
|
||||
TEST_ASSERT_NOT_NULL(dst);
|
||||
TEST_ASSERT_EQUAL_UINT((size_t)header_end,
|
||||
fwrite(buf, 1, (size_t)header_end, dst));
|
||||
fclose(dst);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* Hand-builds a single capnp message shaped like `SnapshotEntry`
|
||||
* (word_count=1, pointer_count=1) but with all-default/empty field
|
||||
* contents, and writes it standalone to `path` -- used to simulate a
|
||||
* malformed "first message is not ReplayHeader" fixture (ReplayHeader is
|
||||
* shaped (1,5); a lone (1,1)-shaped message can never be mistaken for one
|
||||
* by sim.c's shape check). */
|
||||
static void write_lone_snapshotentry_message(const char* path)
|
||||
{
|
||||
struct capn c;
|
||||
struct capn_segment* cs;
|
||||
SnapshotData_ptr sdata;
|
||||
SnapshotEntry_ptr snap;
|
||||
FILE* f;
|
||||
int rc;
|
||||
|
||||
capn_init_malloc(&c);
|
||||
cs = capn_root(&c).seg;
|
||||
|
||||
sdata = new_SnapshotData(cs);
|
||||
snap = new_SnapshotEntry(cs);
|
||||
SnapshotEntry_set_ts(snap, 42ULL);
|
||||
SnapshotEntry_set_data(snap, sdata);
|
||||
capn_setp(capn_root(&c), 0, snap.p);
|
||||
|
||||
f = fopen(path, "w");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
rc = write_capnp_message(f, &c);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
fclose(f);
|
||||
capn_free(&c);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Test 1: starting simulation against a valid fixture file reaches a */
|
||||
/* ready-to-run state with the virtual clock reporting the snapshot's */
|
||||
/* timestamp -- this task's own primary Observable. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static void
|
||||
test_valid_fixture_reaches_ready_state_with_frozen_virtual_clock(void)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
uint64_t snapshot_ts = 0;
|
||||
sched_sim_args_t args;
|
||||
xce_sched_sim_driver_t sim;
|
||||
int rc;
|
||||
|
||||
write_valid_fixture(path, sizeof(path), &snapshot_ts, "_valid");
|
||||
TEST_ASSERT_TRUE(snapshot_ts > 0);
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.replay_path = path;
|
||||
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
|
||||
args.sim_speed = 0; /* fast/frozen -- xce_core_now() stays pinned to
|
||||
* virtual_clock_base regardless of real elapsed
|
||||
* time, giving this test a deterministic,
|
||||
* non-flaky assertion. */
|
||||
|
||||
rc = sched_sim_driver_start(&sim, &args);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* Step 4's Observable: the virtual clock reports the snapshot's own
|
||||
* timestamp, exactly (frozen -- speed_num=0). */
|
||||
TEST_ASSERT_EQUAL_UINT64(snapshot_ts, xce_core_now(&(sim.core)));
|
||||
|
||||
/* Direct inspection of the anchors Step 4 documents setting. */
|
||||
TEST_ASSERT_EQUAL_UINT64(
|
||||
snapshot_ts, atomic_load_explicit(&(sim.core.virtual_clock_base),
|
||||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_UINT64(0ULL, atomic_load_explicit(&(sim.core.speed_num),
|
||||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_UINT64(1ULL, atomic_load_explicit(&(sim.core.speed_den),
|
||||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_UINT64(0ULL, sim.sim_speed_num);
|
||||
TEST_ASSERT_EQUAL_UINT64(1ULL, sim.sim_speed_den);
|
||||
|
||||
/* "Ready-to-run" -- no session is active, load_snapshot()'s own
|
||||
* precondition would otherwise have been violated. */
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load(&(sim.core.session_active)));
|
||||
|
||||
TEST_ASSERT_NOT_NULL(sim.replay_file);
|
||||
|
||||
printf("test_valid_fixture_reaches_ready_state_with_frozen_virtual_clock:"
|
||||
" PASS\n");
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Test 2: starting against a file missing its initial snapshot entry */
|
||||
/* fails with a clear error before any event is processed -- this task's */
|
||||
/* own second Observable. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static void test_missing_initial_snapshot_entry_fails_cleanly(void)
|
||||
{
|
||||
char valid_path[PATH_MAX];
|
||||
char header_only_path[PATH_MAX + 32];
|
||||
uint64_t snapshot_ts = 0;
|
||||
sched_sim_args_t args;
|
||||
xce_sched_sim_driver_t sim;
|
||||
int rc;
|
||||
|
||||
write_valid_fixture(valid_path, sizeof(valid_path), &snapshot_ts,
|
||||
"_hdronly_src");
|
||||
snprintf(header_only_path, sizeof(header_only_path),
|
||||
"%s.header_only.replay", valid_path);
|
||||
truncate_to_header_only(valid_path, header_only_path);
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.replay_path = header_only_path;
|
||||
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
|
||||
args.sim_speed = 1;
|
||||
|
||||
rc = sched_sim_driver_start(&sim, &args);
|
||||
TEST_ASSERT_TRUE(rc < 0);
|
||||
|
||||
printf("test_missing_initial_snapshot_entry_fails_cleanly: PASS\n");
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Test 3: a replay file whose FIRST message is not a valid ReplayHeader */
|
||||
/* is rejected (Req 5 AC 13 step 1) -- distinct code path from test 2. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static void test_first_message_not_replayheader_fails_cleanly(void)
|
||||
{
|
||||
char dir[PATH_MAX];
|
||||
char path[PATH_MAX + 32];
|
||||
sched_sim_args_t args;
|
||||
xce_sched_sim_driver_t sim;
|
||||
int rc;
|
||||
|
||||
make_tmp_dir(dir, sizeof(dir), "_badhdr");
|
||||
snprintf(path, sizeof(path), "%s/bad_header.replay", dir);
|
||||
write_lone_snapshotentry_message(path);
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.replay_path = path;
|
||||
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
|
||||
args.sim_speed = 1;
|
||||
|
||||
rc = sched_sim_driver_start(&sim, &args);
|
||||
TEST_ASSERT_TRUE(rc < 0);
|
||||
|
||||
printf("test_first_message_not_replayheader_fails_cleanly: PASS\n");
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Test 4: a missing replay file fails with a clear error (Req 5 AC 13 */
|
||||
/* step 1, "the file is missing, unreadable"). */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static void test_missing_replay_file_fails_cleanly(void)
|
||||
{
|
||||
sched_sim_args_t args;
|
||||
xce_sched_sim_driver_t sim;
|
||||
int rc;
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.replay_path = "/nonexistent/path/does_not_exist.replay";
|
||||
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
|
||||
args.sim_speed = 1;
|
||||
|
||||
rc = sched_sim_driver_start(&sim, &args);
|
||||
TEST_ASSERT_TRUE(rc < 0);
|
||||
|
||||
printf("test_missing_replay_file_fails_cleanly: PASS\n");
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Test 5: NULL argument guards. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static void test_null_arguments_return_einval(void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
sched_sim_args_t args;
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.replay_path = "/tmp/does-not-matter.replay";
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_sim_driver_start(NULL, &args));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_sim_driver_start(&sim, NULL));
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.replay_path = NULL;
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_sim_driver_start(&sim, &args));
|
||||
|
||||
printf("test_null_arguments_return_einval: PASS\n");
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Test 6: sched_sim_driver_run() -- design.md's literal entry point -- */
|
||||
/* calls start() then fails loudly with -ENOSYS (task 11.2's event loop */
|
||||
/* does not exist yet), never silently returning success. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static void test_run_starts_then_fails_enosys_pending_event_loop(void)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
uint64_t snapshot_ts = 0;
|
||||
sched_sim_args_t args;
|
||||
xce_sched_sim_driver_t sim;
|
||||
int rc;
|
||||
|
||||
write_valid_fixture(path, sizeof(path), &snapshot_ts, "_run");
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.replay_path = path;
|
||||
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
|
||||
args.sim_speed = 0;
|
||||
|
||||
rc = sched_sim_driver_run(&sim, &args);
|
||||
TEST_ASSERT_EQUAL_INT(-ENOSYS, rc);
|
||||
|
||||
/* Startup itself still genuinely succeeded -- state is real and
|
||||
* inspectable, matching sched_sim_driver_start()'s own contract. */
|
||||
TEST_ASSERT_EQUAL_UINT64(snapshot_ts, xce_core_now(&(sim.core)));
|
||||
|
||||
printf("test_run_starts_then_fails_enosys_pending_event_loop: PASS\n");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_valid_fixture_reaches_ready_state_with_frozen_virtual_clock);
|
||||
RUN_TEST(test_missing_initial_snapshot_entry_fails_cleanly);
|
||||
RUN_TEST(test_first_message_not_replayheader_fails_cleanly);
|
||||
RUN_TEST(test_missing_replay_file_fails_cleanly);
|
||||
RUN_TEST(test_null_arguments_return_einval);
|
||||
RUN_TEST(test_run_starts_then_fails_enosys_pending_event_loop);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue