/* * test_repl_trace_wiring.c - the trace commands wired into the REPL * (scheduler-sim-decision-trace spec, task 5.2; design.md "ReplIntegration", * "SimIntegration" (repl.h gates), "ReplTraceControl", "System Flows"; * Requirements 1.11, 7.3, 7.4, 8.4, 8.6, 10.4, 10.5). * * Links every scheduler source except main.c and repl.c, and includes * repl.c here so its static help printer, dispatcher and quit path are * reachable. The sim driver and REPL state are zeroed fixtures with a small * fixture job table; nothing is started. * * Covers: * - help: a trailing Trace section listing the trace commands, `info * trace` among the State Inspection lines, one description column for * every line including the new ones (1.11); * - routing: `trace` gets the raw line, so a command longer than the * tokenizer's 16 tokens / 64 characters is not truncated; `info trace` * reaches the store and the `info` usage line lists it (1.11, 10.5); * - `set output` rejects a trace file still open for writing, including * one kept open by the session in progress and a hard link to it, and * leaves the output file unchanged (8.4); * - quit and end of input shut tracing down (8.6), and only after the * picker and allocator threads are stopped (bug-1925); * - the continue-mode rollup reports a traced round on its own, with the * range-flush idle rule, and never starts a collapsed range from it * (7.3, 7.4); * - the repl.h gates forward only when the REPL is interactive. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "kvmgr.h" #include "sched/trace.h" #include "utils.h" #include "../src/repl.c" #define PEND_JOB MAKE_JOBID(1, 2, 1000, 0x0, (uint64_t)42) static xce_sched_sim_driver_t g_sim; static xce_sched_repl_state_t g_rs; static xce_sched_job_t g_pend_job; static char g_dir[64]; static char g_cwd[4096]; static int g_report_fd = -1; /* bug-1925: what the first repl_trace_shutdown() call saw (-1: not called * yet; 0: no thread live; 1: a picker/allocator thread still live), and how * many stand-in threads have been cancelled. */ static int g_shutdown_saw_live = -1; static int g_standins_started = 0; static int g_standins_exited = 0; /* stdout capture */ static int g_cap_fd = -1; static int g_cap_saved = -1; static char g_cap_path[64]; void setUp(void) { kvmgr_params_t p = { .key_type = KVMGR_KEY_INT, .nslots = 59, .delta = 16, .datakiller = NULL }; memset(&g_sim, 0, sizeof(g_sim)); memset(&g_rs, 0, sizeof(g_rs)); g_shutdown_saw_live = -1; g_standins_started = 0; g_standins_exited = 0; g_rs.units_mode = XCE_REPL_UNITS_BOTH; g_rs.mode = XCE_REPL_MODE_STEP; g_sim.interactive = true; g_sim.repl_state = &g_rs; g_sim.core.jobs = makeInstance(&xkvmgr_type, &p); TEST_ASSERT_NOT_NULL(g_sim.core.jobs); TEST_ASSERT_NOT_NULL(getcwd(g_cwd, sizeof(g_cwd))); snprintf(g_dir, sizeof(g_dir), "/tmp/xce_trace_wiring_XXXXXX"); TEST_ASSERT_NOT_NULL(mkdtemp(g_dir)); TEST_ASSERT_EQUAL_INT(0, chdir(g_dir)); } static void fixture_fini(void) { repl_trace_shutdown(&g_sim); free(g_rs.collapse_pend_jobids); g_rs.collapse_pend_jobids = NULL; free(g_rs.collapse_decided_jobids); /* bug-1929 */ g_rs.collapse_decided_jobids = NULL; repl_round_reset_decided(&g_rs); if (g_sim.record_file != NULL) { fclose(g_sim.record_file); g_sim.record_file = NULL; } if (g_sim.core.jobs != NULL) { destroy(g_sim.core.jobs); g_sim.core.jobs = NULL; } } void tearDown(void) { fixture_fini(); g_sim.repl_state = NULL; unlink("a.trace"); unlink("b.trace"); unlink("rec.replay"); TEST_ASSERT_EQUAL_INT(0, chdir(g_cwd)); rmdir(g_dir); } /* ---- helpers ------------------------------------------------------------- */ static void cap_begin(void) { snprintf(g_cap_path, sizeof(g_cap_path), "/tmp/xce_trace_wire_out_XXXXXX"); g_cap_fd = mkstemp(g_cap_path); TEST_ASSERT_TRUE(g_cap_fd >= 0); fflush(stdout); g_cap_saved = dup(STDOUT_FILENO); TEST_ASSERT_TRUE(g_cap_saved >= 0); TEST_ASSERT_TRUE(dup2(g_cap_fd, STDOUT_FILENO) >= 0); } /* Ends the capture and returns what was printed (caller frees). */ static char* cap_end(void) { off_t len; char* buf; fflush(stdout); dup2(g_cap_saved, STDOUT_FILENO); close(g_cap_saved); len = lseek(g_cap_fd, 0, SEEK_CUR); TEST_ASSERT_TRUE(len >= 0); buf = (char*)calloc(1, (size_t)len + 1); TEST_ASSERT_NOT_NULL(buf); TEST_ASSERT_TRUE(lseek(g_cap_fd, 0, SEEK_SET) == 0); if (len > 0) { TEST_ASSERT_TRUE(read(g_cap_fd, buf, (size_t)len) == len); } close(g_cap_fd); unlink(g_cap_path); return buf; } /* Dispatches `line` as the REPL would and returns its output. */ static char* dispatch(const char* line) { cap_begin(); TEST_ASSERT_FALSE(repl_dispatch(&g_sim, line)); return cap_end(); } static void dispatch_quiet(const char* line) { free(dispatch(line)); } static char* help_text(void) { cap_begin(); repl_print_help(); return cap_end(); } static void write_file(const char* path, const char* text) { FILE* f = fopen(path, "w"); TEST_ASSERT_NOT_NULL(f); fputs(text, f); fclose(f); } static char* read_file(const char* path) { FILE* f = fopen(path, "r"); char* buf; long len; TEST_ASSERT_NOT_NULL(f); TEST_ASSERT_EQUAL_INT(0, fseek(f, 0, SEEK_END)); len = ftell(f); TEST_ASSERT_TRUE(len >= 0); rewind(f); buf = (char*)calloc(1, (size_t)len + 1); TEST_ASSERT_NOT_NULL(buf); if (len > 0) { TEST_ASSERT_EQUAL_size_t((size_t)len, fread(buf, 1, (size_t)len, f)); } fclose(f); return buf; } /* The column at which the description of the help line whose command field * is exactly `field` begins (the field starts the line after indentation). */ static int desc_column(const char* text, const char* field) { size_t flen = strlen(field); const char* p = text; while ((p = strstr(p, field)) != NULL) { const char* ls = p; const char* d = p + flen; while ((ls > text) && (*(ls - 1) == ' ')) { ls--; } if (((ls == text) || (*(ls - 1) == '\n')) && (*d == ' ')) { while ((ls > text) && (*(ls - 1) != '\n')) { ls--; } while (*d == ' ') { d++; } return (int)(d - ls); } p += flen; } TEST_FAIL_MESSAGE(field); return -1; } /* A traced decision record for `jobid` (DECISION kind, no entries). */ static xce_sched_trace_rec_t* new_rec(uint64_t jobid) { xce_sched_trace_ctl_t ctl; xce_sched_trace_rec_t* rec; memset(&ctl, 0, sizeof(ctl)); ctl.all = true; ctl.level = XCE_TRACE_LEVEL_SUMMARY; ctl.line_bound = 1000; ctl.session_no = 3; ctl.position = 1; rec = sched_trace_rec_new(&ctl, jobid, XCE_TRACE_REC_DECISION); TEST_ASSERT_NOT_NULL(rec); return rec; } typedef struct { xce_decision_t d; xce_job_alloc_t alloc; } fixture_decision_t; static void dispatch_of(fixture_decision_t* f, uint64_t jobid) { memset(f, 0, sizeof(*f)); f->alloc.jobid = jobid; f->d.op = XCE_DECISION_DISPATCH; f->d.u.alloc = &(f->alloc); f->d.trace = new_rec(jobid); } static void add_pend_job(void) { memset(&g_pend_job, 0, sizeof(g_pend_job)); g_pend_job.jobid = PEND_JOB; g_pend_job.state = JOB_STAT_PEND; TEST_ASSERT_TRUE( key_add(g_sim.core.jobs, (void*)g_pend_job.jobid, &g_pend_job) >= 0); } /* An active, non-idle collapsed range over rounds 3-4 with one dispatch * per round and no pend job. */ static void open_dispatch_range(void) { g_rs.mode = XCE_REPL_MODE_CONTINUE; g_rs.collapse_active = true; g_rs.collapse_start_round = 3; g_rs.collapse_end_round = 4; g_rs.collapse_dispatch_count = 1; } /* Runs the round-boundary rollup for round 5 with the given counts. */ static char* rollup_round5(int ndispatch, bool traced) { g_rs.session_count = 5; g_rs.last_n_dispatch = ndispatch; g_rs.last_n_preempt = 0; g_rs.trace.round_traced = traced; cap_begin(); repl_round_rollup_process(&g_sim); return cap_end(); } /* ---- help (1.11) --------------------------------------------------------- */ static void test_help_trace_section_is_last_and_lists_trace_commands(void) { char* h = help_text(); char* snap = strstr(h, "\nSnapshot & Parameters:\n"); char* trace = strstr(h, "\nTrace:\n"); const char* cmds[] = { "trace on all", "trace on job ", "trace off", "trace status" }; size_t i; TEST_ASSERT_NOT_NULL(snap); TEST_ASSERT_NOT_NULL_MESSAGE(trace, "help must have a Trace section"); TEST_ASSERT_TRUE_MESSAGE(snap < trace, "Trace must follow Snapshot"); TEST_ASSERT_NULL_MESSAGE(strstr(trace + 1, "\n\n"), "Trace must be the last section"); for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) { char* pos = strstr(h, cmds[i]); TEST_ASSERT_NOT_NULL_MESSAGE(pos, cmds[i]); TEST_ASSERT_TRUE_MESSAGE(pos > trace, cmds[i]); } TEST_ASSERT_NOT_NULL(strstr(trace, "level summary|full")); TEST_ASSERT_NOT_NULL(strstr(trace, "lines N")); TEST_ASSERT_NOT_NULL(strstr(trace, "file PATH")); free(h); } static void test_help_info_trace_in_state_inspection(void) { char* h = help_text(); char* state = strstr(h, "\nState Inspection:\n"); char* snap = strstr(h, "\nSnapshot & Parameters:\n"); char* info = strstr(h, "info trace "); TEST_ASSERT_NOT_NULL(state); TEST_ASSERT_NOT_NULL(snap); TEST_ASSERT_NOT_NULL_MESSAGE(info, "help must list `info trace`"); TEST_ASSERT_TRUE((state < info) && (info < snap)); free(h); } static void test_help_one_description_column_with_trace_lines(void) { char* h = help_text(); const char* fields[] = { "step [N]", "break [key=value ...]", "info limit |all", "info trace ", "set units raw|human|both", "trace on all [OPTIONS]", "trace on job ... [OPTIONS]", "level summary|full", "lines N", "file PATH", "trace off", "trace status", }; int col0 = desc_column(h, fields[0]); size_t i; for (i = 1; i < sizeof(fields) / sizeof(fields[0]); i++) { TEST_ASSERT_EQUAL_INT_MESSAGE(col0, desc_column(h, fields[i]), fields[i]); } free(h); } /* ---- routing (1.11, 10.5) ------------------------------------------------ */ /* 30 job ids and a path longer than 63 bytes: far past the tokenizer's * 16 tokens / 64 characters, so only the raw line can carry them. */ static void test_trace_routed_with_raw_line(void) { char line[2048]; char path[256]; size_t n; int i; char* out; xce_sched_trace_ctl_t* ctl; snprintf(path, sizeof(path), "%s/a_trace_file_with_a_name_well_beyond_sixty_four_chars.trace", g_dir); n = (size_t)snprintf(line, sizeof(line), "trace on job"); for (i = 0; i < 30; i++) { n += (size_t)snprintf(line + n, sizeof(line) - n, " 1-2-1000-%d", 100 + i); } snprintf(line + n, sizeof(line) - n, " level full lines 200 file %s", path); out = dispatch(line); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "[sim] trace: enabled"), out); free(out); ctl = repl_trace_settings_ctl(g_rs.trace.next); TEST_ASSERT_NOT_NULL(ctl); TEST_ASSERT_TRUE(ctl->nsel >= 30); TEST_ASSERT_EQUAL_INT(XCE_TRACE_LEVEL_FULL, ctl->level); TEST_ASSERT_EQUAL_UINT32(200, ctl->line_bound); TEST_ASSERT_EQUAL_STRING(path, repl_trace_settings_path(g_rs.trace.next)); out = dispatch("trace status"); TEST_ASSERT_NOT_NULL(strstr(out, "[sim] trace status")); free(out); out = dispatch("trace off"); TEST_ASSERT_NOT_NULL(strstr(out, "[sim] trace: disabled")); TEST_ASSERT_NULL(g_rs.trace.next); free(out); unlink(path); } static void test_info_trace_routed_and_listed_in_usage(void) { char* out = dispatch("info trace"); TEST_ASSERT_NOT_NULL_MESSAGE( strstr(out, "[sim] info trace: usage: info trace"), out); free(out); out = dispatch("info trace 1-2-1000-77"); TEST_ASSERT_NULL_MESSAGE(strstr(out, "unrecognized subcommand"), out); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "[sim] info trace:"), out); free(out); out = dispatch("info"); TEST_ASSERT_EQUAL_STRING( "[sim] info: usage: info [arg]\n", out); free(out); } /* ---- set output (8.4) ---------------------------------------------------- */ static void assert_set_output_rejected(const char* path) { char want[256]; FILE* before = g_sim.record_file; char* out; char* content; char line[256]; snprintf(want, sizeof(want), "[sim] set output: %s is an open trace file\n", path); snprintf(line, sizeof(line), "set output %s", path); out = dispatch(line); TEST_ASSERT_EQUAL_STRING(want, out); free(out); TEST_ASSERT_TRUE(g_sim.record_file == before); /* output unchanged */ content = read_file("a.trace"); TEST_ASSERT_EQUAL_STRING("keep\n", content); /* not truncated */ free(content); } static void test_set_output_rejects_open_trace_file(void) { write_file("a.trace", "keep\n"); g_sim.record_file = fopen("rec.replay", "w"); TEST_ASSERT_NOT_NULL(g_sim.record_file); dispatch_quiet("trace on all file a.trace"); TEST_ASSERT_NOT_NULL(repl_trace_settings_file(g_rs.trace.next)); assert_set_output_rejected("a.trace"); TEST_ASSERT_EQUAL_INT(0, link("a.trace", "b.trace")); assert_set_output_rejected("b.trace"); } /* A file a command replaced during the session stays open, and rejected, * until the session ends (8.4 with 8.5). */ static void test_set_output_rejects_file_kept_open_by_session(void) { write_file("a.trace", "keep\n"); dispatch_quiet("trace on all file a.trace"); repl_trace_session_begin(&g_sim); dispatch_quiet("trace off"); TEST_ASSERT_NULL(g_rs.trace.next); assert_set_output_rejected("a.trace"); repl_trace_session_end(&g_sim); TEST_ASSERT_FALSE(repl_trace_file_conflicts(&g_sim, "a.trace")); } /* ---- quit and end of input (8.6, bug-1925) ------------------------------ */ /* Linked with -Wl,--wrap=repl_trace_shutdown: notes whether a picker or * allocator thread was still live when the trace settings were released. * The allocator reads core->trace_ctl unlocked on every job pop * (sched_allocator()), so a live one could use the ctl after the free. */ void __real_repl_trace_shutdown(xce_sched_sim_driver_t* sim); void __wrap_repl_trace_shutdown(xce_sched_sim_driver_t* sim) { if (g_shutdown_saw_live < 0) { g_shutdown_saw_live = (sim->allocator_thread_live || (__atomic_load_n(&g_standins_exited, __ATOMIC_SEQ_CST) < g_standins_started)) ? 1 : 0; } __real_repl_trace_shutdown(sim); } static void standin_exited(void* arg) { (void)arg; __atomic_add_fetch(&g_standins_exited, 1, __ATOMIC_SEQ_CST); } /* Stands in for the picker or allocator of a session paused at the REPL: * parked in a cancellation point, as the real ones are in cbuf_get/put. */ static void* standin_thread(void* arg) { (void)arg; pthread_cleanup_push(standin_exited, NULL); for (;;) { pause(); } pthread_cleanup_pop(0); return NULL; } /* Gives the child a live picker and allocator, as a session paused at a * breakpoint has (sim_loop.c sets allocator_thread_live once both run). */ static void start_standin_threads(void) { if ((pthread_create(&(g_sim.core.jobpicker), NULL, standin_thread, NULL) != 0) || (pthread_create(&(g_sim.core.allocator), NULL, standin_thread, NULL) != 0)) { _exit(98); } g_standins_started = 2; g_sim.allocator_thread_live = true; } /* Runs in the child at exit(), after the quit path ran: reports whether * tracing was shut down ('N' if not) and, with threads, whether they were * stopped before it ('L' if one was still live), then frees the fixture so * the child exits clean. */ static void report_at_exit(void) { char ok = ((g_rs.trace.next == NULL) && (g_rs.trace.cur == NULL) && (g_sim.core.trace_ctl == NULL)) ? 'Y' : 'N'; if ((ok == 'Y') && (g_shutdown_saw_live != 0)) { ok = 'L'; } (void)!write(g_report_fd, &ok, 1); close(g_report_fd); fixture_fini(); } static void assert_child_shuts_down(bool eof, bool threads) { int fds[2]; pid_t pid; int status = 0; char got = '?'; write_file("a.trace", ""); dispatch_quiet("trace on all file a.trace"); repl_trace_session_begin(&g_sim); TEST_ASSERT_NOT_NULL(g_sim.core.trace_ctl); TEST_ASSERT_EQUAL_INT(0, pipe(fds)); fflush(stdout); pid = fork(); TEST_ASSERT_TRUE(pid >= 0); if (pid == 0) { int devnull = open("/dev/null", O_RDWR); close(fds[0]); g_report_fd = fds[1]; dup2(devnull, STDIN_FILENO); dup2(devnull, STDOUT_FILENO); if (threads) { start_standin_threads(); } atexit(report_at_exit); if (eof) { repl_command_loop(&g_sim); /* readline() hits EOF */ } else { (void)repl_dispatch(&g_sim, "quit"); } _exit(99); /* both paths must exit(0) */ } close(fds[1]); TEST_ASSERT_EQUAL_INT(1, (int)read(fds[0], &got, 1)); close(fds[0]); TEST_ASSERT_EQUAL_INT(pid, waitpid(pid, &status, 0)); TEST_ASSERT_TRUE(WIFEXITED(status)); TEST_ASSERT_EQUAL_INT(0, WEXITSTATUS(status)); TEST_ASSERT_NOT_EQUAL_MESSAGE( 'L', got, "trace settings released while threads were live"); TEST_ASSERT_EQUAL_CHAR_MESSAGE('Y', got, "tracing still active at exit"); } static void test_quit_shuts_tracing_down(void) { assert_child_shuts_down(false, false); } static void test_eof_shuts_tracing_down(void) { assert_child_shuts_down(true, false); } /* bug-1925: `quit` at a breakpoint mid-session, picker and allocator live. */ static void test_quit_stops_threads_before_trace_shutdown(void) { assert_child_shuts_down(false, true); } static void test_eof_stops_threads_before_trace_shutdown(void) { assert_child_shuts_down(true, true); } /* ---- continue-mode rollup (7.3, 7.4) ------------------------------------- */ /* An outcome identical to the active range would extend it, but a traced * round flushes the range and is reported on its own. */ static void test_rollup_traced_round_emitted_alone_after_flush(void) { char* out; char* range; char* alone; open_dispatch_range(); out = rollup_round5(1, true); range = strstr(out, "[sim] round 3-4 (2 round(s)"); alone = strstr(out, "[sim] round 5-5 (1 round(s)"); TEST_ASSERT_NOT_NULL_MESSAGE(range, out); TEST_ASSERT_NOT_NULL_MESSAGE(alone, out); TEST_ASSERT_TRUE(range < alone); TEST_ASSERT_NOT_NULL(strstr(alone, "1 dispatch, 0 pend, 0 preempt")); TEST_ASSERT_FALSE_MESSAGE(g_rs.collapse_active, "a traced round must not start a range"); TEST_ASSERT_NULL(g_rs.collapse_pend_jobids); free(out); } /* A traced round with no dispatch, preempt or pend job is idle, and is * reported even though it is a single round. */ static void test_rollup_traced_idle_round_reported_as_idle(void) { char* out = rollup_round5(0, true); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "[sim] round 5-5 (1 round(s)"), out); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "): idle\n"), out); TEST_ASSERT_FALSE(g_rs.collapse_active); free(out); } /* The flush idle rule, not the allocation-failure fallback: a pend-only * traced round is not idle. */ static void test_rollup_traced_pend_only_round_not_idle(void) { char* out; add_pend_job(); out = rollup_round5(0, true); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "0 dispatch, 1 pend, 0 preempt"), out); TEST_ASSERT_NULL_MESSAGE(strstr(out, "idle"), out); TEST_ASSERT_FALSE(g_rs.collapse_active); free(out); } /* The round after a traced one starts a fresh range as before; an * untraced round is unaffected. */ static void test_rollup_untraced_round_after_traced_starts_range(void) { char* out; open_dispatch_range(); free(rollup_round5(1, true)); g_rs.session_count = 6; g_rs.trace.round_traced = false; /* reset by the next session begin */ cap_begin(); repl_round_rollup_process(&g_sim); out = cap_end(); TEST_ASSERT_EQUAL_STRING("", out); TEST_ASSERT_TRUE(g_rs.collapse_active); TEST_ASSERT_EQUAL_UINT64(6, g_rs.collapse_start_round); free(out); } /* ---- repl.h gates -------------------------------------------------------- */ static void test_gates_noop_when_not_interactive(void) { fixture_decision_t f; char* out; dispatch_quiet("trace on all"); g_sim.interactive = false; dispatch_of(&f, PEND_JOB); cap_begin(); xce_repl_trace_session_begin(&g_sim); xce_repl_check_trace(&g_sim, &f.d); xce_repl_check_trace_only(&g_sim, &f.d); xce_repl_trace_session_end(&g_sim); out = cap_end(); TEST_ASSERT_EQUAL_STRING("", out); TEST_ASSERT_NULL(g_sim.core.trace_ctl); /* 11.3: never published */ TEST_ASSERT_NULL(g_rs.trace.cur); TEST_ASSERT_FALSE(g_rs.trace.session_in_progress); TEST_ASSERT_NOT_NULL(f.d.trace); /* not consumed */ sched_trace_rec_free(f.d.trace); free(out); } static void test_gates_forward_when_interactive(void) { fixture_decision_t f; fixture_decision_t t; char* out; dispatch_quiet("trace on all"); xce_repl_trace_session_begin(&g_sim); TEST_ASSERT_NOT_NULL(g_sim.core.trace_ctl); TEST_ASSERT_TRUE(g_rs.trace.session_in_progress); dispatch_of(&f, PEND_JOB); dispatch_of(&t, PEND_JOB); t.d.op = XCE_DECISION_TRACE; cap_begin(); xce_repl_check_trace(&g_sim, &f.d); xce_repl_check_trace_only(&g_sim, &t.d); out = cap_end(); TEST_ASSERT_NULL(f.d.trace); /* moved to the store */ TEST_ASSERT_NULL(t.d.trace); TEST_ASSERT_TRUE(strlen(out) > 0); TEST_ASSERT_TRUE(g_rs.trace.round_traced); free(out); /* no record: nothing printed */ cap_begin(); xce_repl_check_trace(&g_sim, &f.d); out = cap_end(); TEST_ASSERT_EQUAL_STRING("", out); free(out); xce_repl_trace_session_end(&g_sim); TEST_ASSERT_NULL(g_sim.core.trace_ctl); TEST_ASSERT_FALSE(g_rs.trace.session_in_progress); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_help_trace_section_is_last_and_lists_trace_commands); RUN_TEST(test_help_info_trace_in_state_inspection); RUN_TEST(test_help_one_description_column_with_trace_lines); RUN_TEST(test_trace_routed_with_raw_line); RUN_TEST(test_info_trace_routed_and_listed_in_usage); RUN_TEST(test_set_output_rejects_open_trace_file); RUN_TEST(test_set_output_rejects_file_kept_open_by_session); RUN_TEST(test_quit_shuts_tracing_down); RUN_TEST(test_eof_shuts_tracing_down); RUN_TEST(test_quit_stops_threads_before_trace_shutdown); RUN_TEST(test_eof_stops_threads_before_trace_shutdown); RUN_TEST(test_rollup_traced_round_emitted_alone_after_flush); RUN_TEST(test_rollup_traced_idle_round_reported_as_idle); RUN_TEST(test_rollup_traced_pend_only_round_not_idle); RUN_TEST(test_rollup_untraced_round_after_traced_starts_range); RUN_TEST(test_gates_noop_when_not_interactive); RUN_TEST(test_gates_forward_when_interactive); return UNITY_END(); }