feat(scheduler-sim-discrete-clock): remove the REPL speed parameter (task 2.2)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-29 04:53:20 +00:00
parent 3a6b503f07
commit 05320e777b
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
8 changed files with 257 additions and 147 deletions

View file

@ -15,18 +15,19 @@
* reloaded core state (snapshot_ts, the pushed job) matches what the
* child's session actually produced.
*
* The remaining tests cover `set speed`/`set output`/`set verify`/
* `info config` and the auto-generated `snapshot` filename, at the same
* The remaining tests cover `set output`/`set verify`/`info config`, the
* removed `set speed` parameter (scheduler-sim-discrete-clock, Req 3.1-3.5:
* it prints a removal message and changes nothing) and the auto-generated
* `snapshot` filename, at the same
* "unit-level precision where possible, integration-level proof for the
* one Observable that needs it" split test_repl_execution_control.c's own
* header comment establishes.
*
* `set units raw|human|both` (scheduler-sim-repl-verbosity spec, task 7.1,
* Requirement 6.2/6.3) is added below, alongside the existing `set speed`/
* `set output`/`set verify` tests, per design.md's File Structure Plan
* Requirement 6.2/6.3) is added below, alongside the existing `set output`/
* `set verify` tests, per that spec's design.md File Structure Plan
* ("test_repl_snapshot_params.c -- extended with `set units raw|human|both`
* parsing/validation tests ... alongside its existing `set speed`/`set
* output`/`set verify` tests"). THIS SLICE IS RED-PHASE ONLY: neither
* parsing/validation tests ... alongside its existing `set ...` tests"). THIS SLICE IS RED-PHASE ONLY: neither
* xce_sched_repl_state_s's `units_mode` field nor repl_cmd_set()'s `units`
* branch exists yet (task 7.2, a separate later task, adds both) -- see
* that test group's own header comment further below for the exact
@ -880,42 +881,195 @@ static void test_snapshot_auto_generates_filename_from_event_index(void)
}
/* ---------------------------------------------------------------------- */
/* Test: `set speed <N>` mutates BOTH the driver-level shadow copy AND the */
/* atomic core.speed_num/speed_den pair xce_core_now() actually reads. */
/* Test: `set speed` (with or without a value) prints the removal message, */
/* hands control back to the prompt and leaves the simulation clock and */
/* the core speed atoms untouched (scheduler-sim-discrete-clock, Req 3.1, */
/* 3.2, 1.5). */
/* ---------------------------------------------------------------------- */
static void test_set_speed_updates_shadow_and_core_atomics(void)
#define SET_SPEED_REMOVED_MSG \
"[sim] set speed: the speed parameter was removed; the simulator " \
"clock moves only at recorded timestamps"
static xce_sched_sim_driver_t g_speed_sim;
static bool g_speed_handled;
static const char* g_speed_line;
static void run_set_speed_line(void)
{
g_speed_handled = repl_cmd_set(&g_speed_sim, g_speed_line);
}
static void assert_set_speed_removed(const char* line)
{
char* captured;
memset(&g_speed_sim, 0, sizeof(g_speed_sim));
atomic_store_explicit(&(g_speed_sim.core.virtual_clock_base), 5000ULL,
memory_order_release);
atomic_store_explicit(&(g_speed_sim.core.real_clock_base), 0ULL,
memory_order_release);
atomic_store_explicit(&(g_speed_sim.core.speed_num), 0ULL,
memory_order_release);
atomic_store_explicit(&(g_speed_sim.core.speed_den), 1ULL,
memory_order_release);
g_speed_line = line;
g_speed_handled = true;
captured = capture_stdout_around(run_set_speed_line);
TEST_ASSERT_FALSE_MESSAGE(g_speed_handled,
"`set speed` must return to the prompt");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(captured, SET_SPEED_REMOVED_MSG),
"`set speed` must print the removal "
"message");
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "usage"),
"`set speed` must not fall through to the "
"`set` usage message");
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "speed set to"),
"`set speed` must not report a new speed");
TEST_ASSERT_EQUAL_UINT64_MESSAGE(5000ULL,
xce_core_now(&(g_speed_sim.core)),
"`set speed` must not move the "
"simulation clock");
TEST_ASSERT_EQUAL_UINT64(
0ULL, atomic_load_explicit(&(g_speed_sim.core.speed_num),
memory_order_acquire));
TEST_ASSERT_EQUAL_UINT64(
1ULL, atomic_load_explicit(&(g_speed_sim.core.speed_den),
memory_order_acquire));
TEST_ASSERT_EQUAL_UINT64(
5000ULL, atomic_load_explicit(&(g_speed_sim.core.virtual_clock_base),
memory_order_acquire));
free(captured);
}
static void test_set_speed_prints_removal_message_and_changes_nothing(void)
{
assert_set_speed_removed("set speed 7");
assert_set_speed_removed("set speed");
printf("test_set_speed_prints_removal_message_and_changes_nothing: "
"PASS\n");
}
/* ---------------------------------------------------------------------- */
/* Test: the bare `set` usage message no longer offers a speed setting */
/* (Req 3.3) and still lists output/verify/units (Req 3.5). */
/* ---------------------------------------------------------------------- */
static xce_sched_sim_driver_t g_usage_sim;
static void run_set_bare(void) { (void)repl_cmd_set(&g_usage_sim, "set"); }
static void test_set_usage_omits_speed(void)
{
char* captured;
memset(&g_usage_sim, 0, sizeof(g_usage_sim));
captured = capture_stdout_around(run_set_bare);
TEST_ASSERT_NOT_NULL(strstr(captured, "[sim] set: usage: "
"set output <FILE>|off | "
"set verify on|off | "
"set units raw|human|both"));
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "speed"),
"the `set` usage message must not mention "
"speed");
free(captured);
printf("test_set_usage_omits_speed: PASS\n");
}
/* ---------------------------------------------------------------------- */
/* Test: a scripted interactive REPL run (`set speed 7`, `info config`, */
/* `quit`) keeps reading after `set speed`, prints the removal message and */
/* the config line, and exits 0 through `quit` (Req 3.1, 3.2, 3.4). */
/* ---------------------------------------------------------------------- */
static void child_run_repl_script(const char* replay_path,
const char* script_path,
const char* capture_path)
{
xce_sched_sim_driver_t sim;
bool handled;
sched_sim_args_t args;
if (freopen(script_path, "r", stdin) == NULL) {
_exit(120);
}
if (freopen(capture_path, "w", stdout) == NULL) {
_exit(121);
}
memset(&sim, 0, sizeof(sim));
memset(&args, 0, sizeof(args));
args.replay_path = replay_path;
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
args.interactive = true;
handled = repl_cmd_set(&sim, "set speed 7");
TEST_ASSERT_FALSE_MESSAGE(handled, "`set` never hands control back");
TEST_ASSERT_EQUAL_UINT64(7ULL, sim.sim_speed_num);
TEST_ASSERT_EQUAL_UINT64(1ULL, sim.sim_speed_den);
TEST_ASSERT_EQUAL_UINT64(
7ULL, 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));
(void)sched_sim_driver_run(&sim, &args);
/* speed=0 (freeze / as-fast-as-possible) is a legitimate value, unlike
* `step`'s N (which rejects <= 0). */
handled = repl_cmd_set(&sim, "set speed 0");
TEST_ASSERT_FALSE(handled);
TEST_ASSERT_EQUAL_UINT64(0ULL, sim.sim_speed_num);
TEST_ASSERT_EQUAL_UINT64(
0ULL, atomic_load_explicit(&(sim.core.speed_num), memory_order_acquire));
/* Unreachable in a correct implementation -- "quit" calls exit(0). */
fflush(stdout);
_exit(111);
}
/* Malformed input must be rejected AND leave state unchanged. */
handled = repl_cmd_set(&sim, "set speed abc");
TEST_ASSERT_FALSE(handled);
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0ULL, sim.sim_speed_num,
"a malformed `set speed` argument must "
"not change the current speed");
static void test_scripted_repl_set_speed_then_info_config_then_quit(void)
{
char replay_path[PATH_MAX];
char aux_dir[PATH_MAX];
char script_path[PATH_MAX];
char capture_path[PATH_MAX];
FILE* f;
pid_t pid;
int status;
char* captured;
char* cfg_line;
printf("test_set_speed_updates_shadow_and_core_atomics: PASS\n");
f = write_header_and_snapshot(replay_path, sizeof(replay_path),
"_setspeedscript");
TEST_ASSERT_NOT_NULL(f);
append_queue_update_event(f, 1000ULL, "normal");
append_replay_session(f, 2000ULL);
append_end_of_stream_marker(f);
fclose(f);
make_tmp_dir(aux_dir, sizeof(aux_dir), "_setspeedscriptaux");
snprintf(script_path, sizeof(script_path), "%s/script.txt", aux_dir);
snprintf(capture_path, sizeof(capture_path), "%s/capture.out", aux_dir);
write_text_file(script_path, "set speed 7\ninfo config\nquit\n");
pid = fork();
TEST_ASSERT_TRUE_MESSAGE(pid >= 0, "fork() must succeed");
if (pid == 0) {
child_run_repl_script(replay_path, script_path, capture_path);
_exit(112); /* unreachable */
}
TEST_ASSERT_EQUAL_INT_MESSAGE(pid, waitpid(pid, &status, 0),
"waitpid must reap the REPL child");
TEST_ASSERT_TRUE_MESSAGE(WIFEXITED(status),
"the REPL child must exit normally via quit's "
"exit(0)");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, WEXITSTATUS(status),
"quit's exit(0) is the only correct exit "
"path");
captured = read_whole_file(capture_path);
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(captured, SET_SPEED_REMOVED_MSG),
"the scripted `set speed 7` must print the "
"removal message");
cfg_line = strstr(captured, "[sim] config:");
TEST_ASSERT_NOT_NULL_MESSAGE(cfg_line,
"the REPL must keep reading after `set "
"speed` and run `info config`");
TEST_ASSERT_NULL_MESSAGE(strstr(cfg_line, "speed="),
"`info config` must not report a speed item");
free(captured);
printf("test_scripted_repl_set_speed_then_info_config_then_quit: "
"PASS\n");
}
/* ---------------------------------------------------------------------- */
@ -1041,15 +1195,13 @@ static void test_set_output_opens_writes_preamble_and_off_closes(void)
/* */
/* Direct struct inspection (rs->units_mode) observes the setting rather */
/* than routing through `info config`: today `info config` reports only */
/* speed/output/verify/events_processed/current_ts/sessions
/* output/verify/events_processed/current_ts/sessions
* (test_info_config_reports_current_parameters below), it does not report
* units_mode, and design.md's Traceability table never lists `info config`
* as a Resource Value Formatter dependency -- extending it is out of this
* task's scope (that belongs to task 8, the table-layout task). Direct
* struct access is the same technique
* test_set_speed_updates_shadow_and_core_atomics/
* test_set_verify_toggles_flag already use for their own driver-state
* fields (sim.sim_speed_num, sim.verify) above.
* struct access is the same technique test_set_verify_toggles_flag
* already uses for its own driver-state field (sim.verify) above.
*/
static void test_set_units_raw_human_both_update_display_mode(void)
@ -1122,8 +1274,7 @@ static void test_set_units_invalid_value_rejected_like_existing_pattern(void)
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(captured, "set units"),
"an unrecognized `set units` value must be rejected with a "
"units-specific usage message -- matching `set speed`'s "
"\"[sim] set speed: usage: set speed <N>\" and `set verify`'s "
"units-specific usage message -- matching `set verify`'s "
"\"[sim] set verify: usage: set verify on|off\" per-key "
"usage-error pattern (repl_params.c), NOT the generic \"[sim] set: "
"unrecognized parameter 'units'\" message that fires today for an "
@ -1132,8 +1283,8 @@ static void test_set_units_invalid_value_rejected_like_existing_pattern(void)
TEST_ASSERT_EQUAL_MESSAGE(
XCE_REPL_UNITS_BOTH, g_units_invalid_sim.repl_state->units_mode,
"a rejected `set units` value must leave the current display mode "
"unchanged -- same discipline `set speed`/`set verify`'s own "
"malformed-input tests already establish for their own fields");
"unchanged -- same discipline `set verify`'s own "
"malformed-input test already establishes for its own field");
free(captured);
printf("test_set_units_invalid_value_rejected_like_existing_pattern: "
@ -1141,9 +1292,9 @@ static void test_set_units_invalid_value_rejected_like_existing_pattern(void)
}
/* ---------------------------------------------------------------------- */
/* Test: `info config` reports speed/output/verify/events_processed/ */
/* current_ts/sessions, both while output is off and while it is active */
/* with a known path. */
/* Test: `info config` reports output/verify/events_processed/current_ts/ */
/* sessions and no speed item (scheduler-sim-discrete-clock, Req 3.4), */
/* both while output is off and while it is active with a known path. */
/* ---------------------------------------------------------------------- */
static xce_sched_sim_driver_t g_cfg_sim;
@ -1169,7 +1320,9 @@ static void test_info_config_reports_current_parameters(void)
g_cfg_sim.record_file = NULL;
captured = capture_stdout_around(run_info_config);
TEST_ASSERT_NOT_NULL(strstr(captured, "speed=3"));
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "speed="),
"info config must not report a speed item "
"(the REPL speed parameter was removed)");
TEST_ASSERT_NOT_NULL(strstr(captured, "output=off"));
TEST_ASSERT_NOT_NULL(strstr(captured, "verify=on"));
TEST_ASSERT_NOT_NULL(strstr(captured, "events_processed=7"));
@ -1209,7 +1362,8 @@ static void test_help_lists_snapshot_and_set_commands(void)
char* captured = capture_stdout_around(run_print_help);
TEST_ASSERT_NOT_NULL(strstr(captured, "snapshot"));
TEST_ASSERT_NOT_NULL(strstr(captured, "set speed"));
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "speed"),
"help must not offer a speed setting");
TEST_ASSERT_NOT_NULL(strstr(captured, "set output"));
TEST_ASSERT_NOT_NULL(strstr(captured, "set verify"));
TEST_ASSERT_NOT_NULL(strstr(captured, "info config"));
@ -1224,7 +1378,9 @@ int main(void)
RUN_TEST(test_snapshot_written_file_is_loadable_by_fresh_sim_start);
RUN_TEST(test_snapshot_auto_generates_filename_from_event_index);
RUN_TEST(test_set_speed_updates_shadow_and_core_atomics);
RUN_TEST(test_set_speed_prints_removal_message_and_changes_nothing);
RUN_TEST(test_set_usage_omits_speed);
RUN_TEST(test_scripted_repl_set_speed_then_info_config_then_quit);
RUN_TEST(test_set_verify_toggles_flag);
RUN_TEST(test_set_output_opens_writes_preamble_and_off_closes);
RUN_TEST(test_set_units_raw_human_both_update_display_mode);