myxce/src/scheduler/testsuite/test_sim_isolation.c
Rongsong Shen 5ca48206b2
test(scheduler-sim-decision-trace): close validation coverage gaps; sync design.md
Feature validation round 1: tests for trace off keeping recall (1.5),
no logging from REPL trace sources (8.7), reconstructed records in the
trace file (8.1) and first in continue mode (7.3), output after trace
off matching an untraced run (10.4), and tracing enabled only from the
REPL (11.1). design.md now reflects the REPL-side file split, the added
API, the dropped file tag and the test files; duplicate trace
declarations removed from repl_internal.h.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 17:33:26 +00:00

1171 lines
45 KiB
C

/*
* test_sim_isolation.c - regression guard for task 11.3's Observable
* (scheduler-core spec, Req 5 AC 17, "Simulation isolation"): "a build-time
* or structural check confirms no controller-facing symbol is reachable
* from the simulation driver's compiled object" -- plus the AC's own
* "confirmed by the struct definition itself, not runtime checks alone"
* clause. Scope: sim.h + sim.c + sim_loop.c + sim_picker.c + sim_record.c +
* repl.c/repl.h/repl_break.c/repl_info.c/repl_internal.h/repl_params.c
* together (task 11.1 + task 11.2's sibling file + task 11.5's sibling
* file, the --sim-output writer + task 12.1's REPL files + task 12.2's own
* repl_break.c/repl_info.c/repl_internal.h + task 12.3's own repl_params.c,
* each an 800-line-cap split of repl.c -- see repl_internal.h's own "Why a
* split at all" section -- + bug-1567's own sim_picker.c, the identical
* split applied to sim_loop.c once IT hit the same cap) -- NOT a re-audit
* of core.c (task 4.1's own territory, test_core_isolation.c) and NOT
* plugin.c/builtin_fcfs.c (shared normal-mode infra, out of this task's
* boundary). sim_verify.c (task 11.4) is likewise out of scope here -- it
* is never compiled into sched_sim_isolation_lib below and carries no
* isolation-sensitive logic of its own (pure struct comparison, no I/O);
* extending this guard to it was not part of task 11.4's own text and is
* left as-is. repl.c/repl.h were added per task 12.1's own text (extending
* this exact 3-file precedent to a 5-file one); repl_break.c/repl_info.c/
* repl_internal.h extended it again (task 12.2, to an 8-file one);
* repl_params.c extends it once more (task 12.3, to a 9-file one);
* sim_picker.c extends it a final time (bug-1567, to a 10-file one) -- all
* touch xce_sched_sim_driver_t directly or read/write stdin/stdout, the
* same category of driver-level code this guard already covers.
*
* Three layers, extending test_core_isolation.c's two-layer pattern (same
* genre: source-text scans of the real, current on-disk files, not a frozen
* snapshot):
*
* 1. Source-text scan of sim.h/sim.c/sim_loop.c/sim_record.c/repl.c/
* repl.h/repl_break.c/repl_info.c/repl_internal.h: no #include of
* client.h/netio.h/xceapi.h/delay.h/idgen.h/xce/cluster.h; no call-site
* substring matching a symbol exported only by those libraries (the
* exact FORBIDDEN_SYMBOLS list test_core_isolation.c already
* established, reused verbatim here, not reinvented).
*
* 2. A structural check test_core_isolation.c has no equivalent of: Req 5
* AC 17 explicitly requires "the xce_sched_sim_driver_t shall contain
* no xcecli_ctx_t, no xce_cluster_config_t, no capnp socket" --
* enforced by the STRUCT DEFINITION ITSELF, not merely by absence of
* the type names anywhere in the file (sim.h's own boundary comment,
* just above the struct, legitimately NAMES xcecli_ctx_t and
* xce_cluster_config_t in prose while documenting this exact
* requirement -- see strip_c_comments()'s doc comment below for why
* that makes a naive whole-file scan wrong). This layer extracts ONLY
* the `typedef struct { ... } xce_sched_sim_driver_t;` body text and
* scans that narrower region for forbidden field types.
*
* 3. The literal build-time/link-time proof the Observable asks for, not
* achievable by a text scan alone: sim.c AND sim_loop.c together (not
* just one -- task 11.2 added sim_loop.c as a sibling file to task
* 11.1's sim.c, and repeating core.c's siblings' own "never
* mechanically covered" gap for the sim driver's own second file would
* be exactly the mistake this task exists to avoid) are compiled in
* isolation into their own static library (meson.build's
* sched_sim_isolation_lib, this test's XCE_SIM_LIB_PATH), and this test
* inspects that archive's UNDEFINED SYMBOL TABLE via nm(1) -- proving
* what the compiled code ACTUALLY calls, independent of what any
* header happens to transitively declare. Same "compile alone, inspect
* undefined symbols" technique and same rationale
* test_core_isolation.c's own header comment gives for why nm on a
* static_library() (not the full xce_scheduler executable, which links
* the whole monolithic libxce_dep and would prove nothing about this
* one translation unit).
*
* Deliberately NOT covered here: the wall-clock ban (FORBIDDEN_WALLCLOCK in
* test_core_isolation.c) -- that constraint is scoped to core.c/core.h
* specifically (Req 5 AC 3), not to driver-level code. sim.c's
* sim_set_virtual_clock() and sim_loop.c's sim_advance_virtual_clock() both
* legitimately call clock_gettime() to seed/advance the core's virtual-clock
* anchors from the driver side -- their own doc comments already explain why
* this does not violate core.c's isolation boundary. Applying
* FORBIDDEN_WALLCLOCK here would incorrectly fail on that legitimate,
* documented behavior.
*/
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unity/unity.h>
#ifndef XCE_SIM_H_PATH
#error "XCE_SIM_H_PATH must be defined by meson.build to sim.h's absolute path"
#endif
#ifndef XCE_SIM_C_PATH
#error "XCE_SIM_C_PATH must be defined by meson.build to sim.c's absolute path"
#endif
#ifndef XCE_SIM_LOOP_C_PATH
#error \
"XCE_SIM_LOOP_C_PATH must be defined by meson.build to sim_loop.c's absolute path"
#endif
#ifndef XCE_SIM_PICKER_C_PATH
#error \
"XCE_SIM_PICKER_C_PATH must be defined by meson.build to sim_picker.c's absolute path"
#endif
#ifndef XCE_SIM_RECORD_C_PATH
#error \
"XCE_SIM_RECORD_C_PATH must be defined by meson.build to sim_record.c's absolute path"
#endif
#ifndef XCE_SIM_REPL_C_PATH
#error \
"XCE_SIM_REPL_C_PATH must be defined by meson.build to repl.c's absolute path"
#endif
#ifndef XCE_SIM_REPL_H_PATH
#error \
"XCE_SIM_REPL_H_PATH must be defined by meson.build to repl.h's absolute path"
#endif
#ifndef XCE_SIM_REPL_BREAK_C_PATH
#error \
"XCE_SIM_REPL_BREAK_C_PATH must be defined by meson.build to repl_break.c's absolute path"
#endif
#ifndef XCE_SIM_REPL_INFO_C_PATH
#error \
"XCE_SIM_REPL_INFO_C_PATH must be defined by meson.build to repl_info.c's absolute path"
#endif
#ifndef XCE_SIM_REPL_INTERNAL_H_PATH
#error \
"XCE_SIM_REPL_INTERNAL_H_PATH must be defined by meson.build to repl_internal.h's absolute path"
#endif
#ifndef XCE_SIM_REPL_PARAMS_C_PATH
#error \
"XCE_SIM_REPL_PARAMS_C_PATH must be defined by meson.build to repl_params.c's absolute path"
#endif
/* Task 5.1 (scheduler-sim-decision-trace, Req 11.3): the REPL trace
* sources and the renderer's private header. */
#if !defined(XCE_SIM_REPL_TRACE_C_PATH) \
|| !defined(XCE_SIM_REPL_TRACE_STORE_C_PATH) \
|| !defined(XCE_SIM_REPL_TRACE_RENDER_C_PATH) \
|| !defined(XCE_SIM_REPL_TRACE_RENDER_OUTCOME_C_PATH) \
|| !defined(XCE_SIM_REPL_TRACE_RENDER_H_PATH) \
|| !defined(XCE_SIM_REPL_TRACE_EMIT_C_PATH)
#error \
"XCE_SIM_REPL_TRACE_*_PATH must be defined by meson.build to the absolute paths of the repl_trace*.c sources and repl_trace_render.h"
#endif
#ifndef XCE_SIM_LIB_PATH
#error \
"XCE_SIM_LIB_PATH must be defined by meson.build to the absolute path of the sched_sim_isolation_lib static_library() archive (its sources are listed in src/scheduler/meson.build)"
#endif
/* Forbidden headers (design.md "Allowed Dependencies", same list
* test_core_isolation.c uses for core.h/core.c, reused verbatim): the
* simulation driver must never directly include any of these. */
static const char* const FORBIDDEN_INCLUDES[] = {
"#include \"client.h\"", "#include \"netio.h\"",
"#include \"xceapi.h\"", "#include \"delay.h\"",
"#include \"idgen.h\"", "#include \"xce/cluster.h\"",
"#include <client.h>", "#include <netio.h>",
"#include <xceapi.h>", "#include <delay.h>",
"#include <idgen.h>", "#include <xce/cluster.h>",
};
#define NUM_FORBIDDEN_INCLUDES \
(sizeof(FORBIDDEN_INCLUDES) / sizeof(FORBIDDEN_INCLUDES[0]))
/* Forbidden call-site symbols -- the exact FORBIDDEN_SYMBOLS list
* test_core_isolation.c already established (verified against
* netio.c/xceapi.c/client.c/delay.c/idgen.c's own extern lists), reused
* verbatim here per this task's own instruction to reuse it, not
* reinvent it. */
static const char* const FORBIDDEN_SYMBOLS[] = {
"xcecli_",
"endpoint_",
"iomgr_",
"idgen_",
"delaymgr_",
"delay_task",
"xce_connect_cluster",
"xce_reconnect_",
"xce_parse_peers",
"xce_cluster_config_validate",
"socket_connect",
"create_ssl_context",
"initialize_openssl",
"cleanup_openssl",
"io_fsm_start",
};
#define NUM_FORBIDDEN_SYMBOLS \
(sizeof(FORBIDDEN_SYMBOLS) / sizeof(FORBIDDEN_SYMBOLS[0]))
/* Forbidden xce_sched_sim_driver_t field TYPES (Req 5 AC 17's own literal
* wording: "shall contain no xcecli_ctx_t, no xce_cluster_config_t, no
* capnp socket"). xcecli_ctx_t/xce_cluster_config_t are named directly by
* the requirement; "capnp socket" maps to netio.h's own connection-carrying
* types (endpoint_t/iomgr_t/io_fsm_t) -- confirmed by inspecting netio.h: no
* distinct "capnp socket" type exists in this codebase, these are the only
* live-socket types netio.h defines. Note xcecli_ctx_t/endpoint_t/iomgr_t
* would also be caught by FORBIDDEN_SYMBOLS' "xcecli_"/"endpoint_"/"iomgr_"
* prefixes above (layer 1) -- listed again here anyway so this check stands
* on its own as the AC-17-literal "struct definition itself" proof, not
* merely inheriting layer 1's coverage. xce_cluster_config_t is NOT caught
* by FORBIDDEN_SYMBOLS (that list has "xce_cluster_config_validate", a
* different string, not a substring of "xce_cluster_config_t" or vice
* versa) -- this is the one genuinely new check this layer adds. */
static const char* const STRUCT_FORBIDDEN_TYPES[] = {
"xcecli_ctx_t", "xce_cluster_config_t", "endpoint_t", "iomgr_t", "io_fsm_t",
};
#define NUM_STRUCT_FORBIDDEN_TYPES \
(sizeof(STRUCT_FORBIDDEN_TYPES) / sizeof(STRUCT_FORBIDDEN_TYPES[0]))
/* Markers bracketing xce_sched_sim_driver_t's own definition in sim.h (see
* extract_struct_body() below). sim.h defines a SECOND `typedef struct {`
* later in the file (sched_sim_args_t) -- extract_struct_body() searches for
* the LAST "typedef struct {" strictly before the end marker, which
* correctly resolves to THIS struct's own opening brace, not the later
* unrelated one. */
static const char* const STRUCT_START_MARKER = "typedef struct {";
static const char* const STRUCT_END_MARKER = "} xce_sched_sim_driver_t;";
#define STRUCT_BODY_BUF_SIZE 4096
static char* read_whole_file(const char* path)
{
FILE* f = fopen(path, "rb");
if (f == NULL) {
return NULL;
}
if (fseek(f, 0, SEEK_END) != 0) {
fclose(f);
return NULL;
}
long len = ftell(f);
if (len < 0) {
fclose(f);
return NULL;
}
rewind(f);
char* buf = (char*)malloc((size_t)len + 1);
if (buf == NULL) {
fclose(f);
return NULL;
}
size_t nread = fread(buf, 1, (size_t)len, f);
fclose(f);
buf[nread] = '\0';
return buf;
}
/*
* strip_c_comments - returns a heap copy of `src` with every block comment
* (slash-star ... star-slash) and line comment (slash-slash to end of line)
* replaced by a single space, leaving string and character literal CONTENTS
* untouched (so a literal containing a comment-marker-shaped sequence is
* never misparsed as a comment start). Necessary because
* sim.h's own boundary doc comment legitimately names xcecli_ctx_t and
* xce_cluster_config_t in prose while documenting Req 5 AC 17 (see this
* file's own header comment, layer 2) -- a raw-text scan (fine for
* test_core_isolation.c, whose core.h/core.c comments never name a
* forbidden symbol) would false-positive on that legitimate documentation.
* Comments never produce compiled symbols either, so stripping them first
* keeps every source-text scan below consistent with what the nm(1)
* link-time proof (layer 3) actually measures: real code, not prose.
* Returns NULL on allocation failure.
*/
static char* strip_c_comments(const char* src)
{
size_t len = strlen(src);
char* out = (char*)malloc(len + 1);
size_t oi = 0;
size_t i = 0;
if (out == NULL) {
return NULL;
}
while (i < len) {
if ((src[i] == '/') && ((i + 1) < len) && (src[i + 1] == '*')) {
i += 2;
while (((i + 1) < len)
&& !((src[i] == '*') && (src[i + 1] == '/'))) {
i++;
}
i += 2;
out[oi++] = ' ';
continue;
}
if ((src[i] == '/') && ((i + 1) < len) && (src[i + 1] == '/')) {
i += 2;
while ((i < len) && (src[i] != '\n')) {
i++;
}
out[oi++] = ' ';
continue;
}
if (src[i] == '"') {
out[oi++] = src[i++];
while ((i < len) && (src[i] != '"')) {
if ((src[i] == '\\') && ((i + 1) < len)) {
out[oi++] = src[i++];
}
out[oi++] = src[i++];
}
if (i < len) {
out[oi++] = src[i++];
}
continue;
}
if (src[i] == '\'') {
out[oi++] = src[i++];
while ((i < len) && (src[i] != '\'')) {
if ((src[i] == '\\') && ((i + 1) < len)) {
out[oi++] = src[i++];
}
out[oi++] = src[i++];
}
if (i < len) {
out[oi++] = src[i++];
}
continue;
}
out[oi++] = src[i++];
}
out[oi] = '\0';
return out;
}
/*
* extract_struct_body - isolates xce_sched_sim_driver_t's own
* `{ ... }` field-listing text from `stripped_h` (already comment-stripped
* sim.h text) into out_buf. Fails the currently-running test (via Unity's
* TEST_ASSERT_* longjmp) if either bracketing marker cannot be found, or if
* the extracted text overflows out_sz -- the same "fail loud on a stale/
* broken assumption" discipline test_core_isolation.c's own
* XCE_CORE_*_PATH #error guards and any_undefined_seen check establish.
*/
static void extract_struct_body(const char* stripped_h, char* out_buf,
size_t out_sz)
{
const char* end_pos = strstr(stripped_h, STRUCT_END_MARKER);
const char* start_pos = NULL;
const char* scan;
size_t body_len;
TEST_ASSERT_NOT_NULL_MESSAGE(
end_pos, "sim.h: could not locate '} xce_sched_sim_driver_t;' -- "
"has the struct been renamed or removed?");
for (scan = stripped_h; scan < end_pos;) {
const char* hit = strstr(scan, STRUCT_START_MARKER);
if ((hit == NULL) || (hit >= end_pos)) {
break;
}
start_pos = hit;
scan = hit + 1;
}
TEST_ASSERT_NOT_NULL_MESSAGE(
start_pos, "sim.h: could not locate the 'typedef struct {' opening "
"xce_sched_sim_driver_t's own definition");
body_len = (size_t)(end_pos - start_pos);
TEST_ASSERT_TRUE_MESSAGE(
body_len < out_sz,
"sim.h: xce_sched_sim_driver_t's struct body text overflowed the "
"fixed scan buffer -- grow STRUCT_BODY_BUF_SIZE");
memcpy(out_buf, start_pos, body_len);
out_buf[body_len] = '\0';
}
static void assert_no_forbidden_includes(const char* stripped,
const char* label)
{
size_t i;
for (i = 0; i < NUM_FORBIDDEN_INCLUDES; i++) {
char msg[256];
snprintf(msg, sizeof(msg), "%s directly includes forbidden header '%s'",
label, FORBIDDEN_INCLUDES[i]);
TEST_ASSERT_NULL_MESSAGE(strstr(stripped, FORBIDDEN_INCLUDES[i]), msg);
}
}
static void assert_no_forbidden_symbols(const char* stripped, const char* label)
{
size_t i;
for (i = 0; i < NUM_FORBIDDEN_SYMBOLS; i++) {
char msg[256];
snprintf(msg, sizeof(msg),
"%s references forbidden networking/API/controller symbol "
"'%s'",
label, FORBIDDEN_SYMBOLS[i]);
TEST_ASSERT_NULL_MESSAGE(strstr(stripped, FORBIDDEN_SYMBOLS[i]), msg);
}
}
/*
* Requirement 8.7 (scheduler-sim-decision-trace): the REPL trace sources
* must never write trace content to the scheduler's regular log. They may
* use stdio (the REPL prints), so this is narrower than
* test_core_isolation.c's trace no-I/O scan: no logging header and no
* logging call. utils.h (which the REPL needs) pulls in tlog.h and defines
* the TRACE/DBG/INFO/WARN/ERROR macros, so the calls are what is checked.
* Names match as a call on identifier boundaries, not as a member call,
* with string literals blanked first.
*/
static const char* const REPL_TRACE_LOG_INCLUDES[] = {
"<tlog.h>", "\"tlog.h\"", "\"xcelog.h\"", "<xcelog.h>", "<syslog.h>",
};
static const char* const REPL_TRACE_LOG_CALLS[] = {
"TRACE", "DEBUG", "DBG", "INFO",
"WARN", "ERROR", "FATAL", "SCHEDULER_LOG",
"CONTROLLER_LOG", "XCECD_LOG", "XCECD_WORKER_LOG",
"syslog", "vsyslog", "openlog",
};
/* identifier prefixes: any call starting with one is a log call */
static const char* const REPL_TRACE_LOG_PREFIXES[] = {
"tlog",
"xce_log",
"xce_init_xcecd",
};
static int is_ident_char(char c)
{
return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))
|| ((c >= '0') && (c <= '9')) || (c == '_');
}
/* Blanks the contents of string and char literals in place, except on
* #include lines. */
static void blank_string_literals(char* t)
{
size_t i = 0;
int in_include = 0;
while (t[i] != '\0') {
if ((t[i] == '#') && (strncmp(t + i, "#include", 8) == 0)) {
in_include = 1;
} else if (t[i] == '\n') {
in_include = 0;
}
if (((t[i] == '"') || (t[i] == '\'')) && !in_include) {
char q = t[i++];
while ((t[i] != '\0') && (t[i] != q) && (t[i] != '\n')) {
if ((t[i] == '\\') && (t[i + 1] != '\0')) {
t[i++] = ' ';
}
t[i++] = ' ';
}
if (t[i] == q) {
i++;
}
continue;
}
i++;
}
}
/* Offset of the first call to `name` (or, when `prefix`, to any
* identifier starting with `name`) in `t`, or -1. A member call
* (`x.name(` / `x->name(`) and a longer identifier do not count. */
static long find_call(const char* t, const char* name, int prefix)
{
size_t n = strlen(name);
const char* p = t;
for (; (p = strstr(p, name)) != NULL; p += n) {
const char* e = p + n;
if ((p > t)
&& (is_ident_char(p[-1]) || (p[-1] == '.')
|| ((p[-1] == '>') && (p - t >= 2) && (p[-2] == '-')))) {
continue;
}
if (prefix) {
while (is_ident_char(*e)) {
e++;
}
} else if (is_ident_char(*e)) {
continue;
}
while ((*e == ' ') || (*e == '\t')) {
e++;
}
if (*e == '(') {
return (long)(p - t);
}
}
return -1;
}
static void assert_repl_trace_source_never_logs(const char* stripped,
const char* label)
{
char* t = strdup(stripped);
char msg[512];
size_t i;
TEST_ASSERT_NOT_NULL(t);
for (i = 0; i < sizeof(REPL_TRACE_LOG_INCLUDES) / sizeof(char*); i++) {
snprintf(msg, sizeof(msg), "%s includes logging header %s (Req 8.7)",
label, REPL_TRACE_LOG_INCLUDES[i]);
TEST_ASSERT_NULL_MESSAGE(strstr(t, REPL_TRACE_LOG_INCLUDES[i]), msg);
}
blank_string_literals(t);
for (i = 0; i < sizeof(REPL_TRACE_LOG_CALLS) / sizeof(char*); i++) {
snprintf(msg, sizeof(msg),
"%s calls logging macro/function '%s' -- trace content "
"must never reach the scheduler log (Req 8.7)",
label, REPL_TRACE_LOG_CALLS[i]);
TEST_ASSERT_EQUAL_INT_MESSAGE(
-1, find_call(t, REPL_TRACE_LOG_CALLS[i], 0), msg);
}
for (i = 0; i < sizeof(REPL_TRACE_LOG_PREFIXES) / sizeof(char*); i++) {
snprintf(msg, sizeof(msg),
"%s calls a '%s*' logging function -- trace content must "
"never reach the scheduler log (Req 8.7)",
label, REPL_TRACE_LOG_PREFIXES[i]);
TEST_ASSERT_EQUAL_INT_MESSAGE(
-1, find_call(t, REPL_TRACE_LOG_PREFIXES[i], 1), msg);
}
free(t);
}
/* Offset (>= from) of the next plain assignment `name = ...` (not `==`,
* not a longer identifier) in comment-stripped `t`, or -1. Member
* assignments (`x->name =`, `x.name =`) count. */
static long find_assignment(const char* t, const char* name, long from)
{
size_t n = strlen(name);
const char* p = t + from;
for (; (p = strstr(p, name)) != NULL; p += n) {
const char* e = p + n;
if (((p > t) && is_ident_char(p[-1])) || is_ident_char(*e)) {
continue;
}
while ((*e == ' ') || (*e == '\t') || (*e == '\n')) {
e++;
}
if ((e[0] == '=') && (e[1] != '=')) {
return (long)(p - t);
}
}
return -1;
}
/* True when a string literal in comment-stripped `t` contains `word`
* (case-insensitive). */
static int literal_mentions(const char* t, const char* word)
{
size_t n = strlen(word);
size_t i = 0;
while (t[i] != '\0') {
if (t[i] == '\'') { /* skip a char literal */
i++;
while ((t[i] != '\0') && (t[i] != '\'') && (t[i] != '\n')) {
i += ((t[i] == '\\') && (t[i + 1] != '\0')) ? 2 : 1;
}
i += (t[i] == '\'');
continue;
}
if (t[i] == '"') {
size_t j = i + 1;
while ((t[j] != '\0') && (t[j] != '"') && (t[j] != '\n')) {
if (strncasecmp(t + j, word, n) == 0) {
return 1;
}
j += ((t[j] == '\\') && (t[j + 1] != '\0')) ? 2 : 1;
}
i = j + (t[j] == '"');
continue;
}
i++;
}
return 0;
}
static int dirname_of(const char* path, char* out, size_t out_sz)
{
const char* slash = strrchr(path, '/');
size_t n;
if (slash == NULL) {
return 0;
}
n = (size_t)(slash - path);
if (n + 1 > out_sz) {
return 0;
}
memcpy(out, path, n);
out[n] = '\0';
return 1;
}
void setUp(void) { return; }
void tearDown(void) { return; }
/* Sanity check: all three source paths baked in by meson.build must
* actually be readable -- without this, a stale path would make every scan
* below silently pass on zero bytes of text (the exact false-confidence
* failure mode this whole test exists to avoid). */
static void test_sim_source_paths_are_readable(void)
{
char* h = read_whole_file(XCE_SIM_H_PATH);
char* c = read_whole_file(XCE_SIM_C_PATH);
char* loop = read_whole_file(XCE_SIM_LOOP_C_PATH);
char* picker = read_whole_file(XCE_SIM_PICKER_C_PATH);
char* record = read_whole_file(XCE_SIM_RECORD_C_PATH);
char* repl_c = read_whole_file(XCE_SIM_REPL_C_PATH);
char* repl_h = read_whole_file(XCE_SIM_REPL_H_PATH);
char* repl_break_c = read_whole_file(XCE_SIM_REPL_BREAK_C_PATH);
char* repl_info_c = read_whole_file(XCE_SIM_REPL_INFO_C_PATH);
char* repl_int_h = read_whole_file(XCE_SIM_REPL_INTERNAL_H_PATH);
char* repl_params_c = read_whole_file(XCE_SIM_REPL_PARAMS_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(h, XCE_SIM_H_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(c, XCE_SIM_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(loop, XCE_SIM_LOOP_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(picker, XCE_SIM_PICKER_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(record, XCE_SIM_RECORD_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(repl_c, XCE_SIM_REPL_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(repl_h, XCE_SIM_REPL_H_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(repl_break_c, XCE_SIM_REPL_BREAK_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(repl_info_c, XCE_SIM_REPL_INFO_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(repl_int_h, XCE_SIM_REPL_INTERNAL_H_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(repl_params_c, XCE_SIM_REPL_PARAMS_C_PATH);
free(h);
free(c);
free(loop);
free(picker);
free(record);
free(repl_c);
free(repl_h);
free(repl_break_c);
free(repl_info_c);
free(repl_int_h);
free(repl_params_c);
}
static void test_sim_never_includes_forbidden_headers(void)
{
char* h_raw = read_whole_file(XCE_SIM_H_PATH);
char* c_raw = read_whole_file(XCE_SIM_C_PATH);
char* loop_raw = read_whole_file(XCE_SIM_LOOP_C_PATH);
char* picker_raw = read_whole_file(XCE_SIM_PICKER_C_PATH);
char* record_raw = read_whole_file(XCE_SIM_RECORD_C_PATH);
char* repl_c_raw = read_whole_file(XCE_SIM_REPL_C_PATH);
char* repl_h_raw = read_whole_file(XCE_SIM_REPL_H_PATH);
char* repl_break_c_raw = read_whole_file(XCE_SIM_REPL_BREAK_C_PATH);
char* repl_info_c_raw = read_whole_file(XCE_SIM_REPL_INFO_C_PATH);
char* repl_int_h_raw = read_whole_file(XCE_SIM_REPL_INTERNAL_H_PATH);
char* repl_params_c_raw = read_whole_file(XCE_SIM_REPL_PARAMS_C_PATH);
char* repl_rollup_c_raw = read_whole_file(XCE_SIM_REPL_ROLLUP_C_PATH);
char* repl_fmt_c_raw = read_whole_file(XCE_SIM_REPL_FMT_C_PATH);
char* h;
char* c;
char* loop;
char* picker;
char* record;
char* repl_c;
char* repl_h;
char* repl_break_c;
char* repl_info_c;
char* repl_int_h;
char* repl_params_c;
char* repl_rollup_c;
char* repl_fmt_c;
TEST_ASSERT_NOT_NULL(h_raw);
TEST_ASSERT_NOT_NULL(c_raw);
TEST_ASSERT_NOT_NULL(loop_raw);
TEST_ASSERT_NOT_NULL(picker_raw);
TEST_ASSERT_NOT_NULL(record_raw);
TEST_ASSERT_NOT_NULL(repl_c_raw);
TEST_ASSERT_NOT_NULL(repl_h_raw);
TEST_ASSERT_NOT_NULL(repl_break_c_raw);
TEST_ASSERT_NOT_NULL(repl_info_c_raw);
TEST_ASSERT_NOT_NULL(repl_int_h_raw);
TEST_ASSERT_NOT_NULL(repl_params_c_raw);
TEST_ASSERT_NOT_NULL(repl_rollup_c_raw);
TEST_ASSERT_NOT_NULL(repl_fmt_c_raw);
h = strip_c_comments(h_raw);
c = strip_c_comments(c_raw);
loop = strip_c_comments(loop_raw);
picker = strip_c_comments(picker_raw);
record = strip_c_comments(record_raw);
repl_c = strip_c_comments(repl_c_raw);
repl_h = strip_c_comments(repl_h_raw);
repl_break_c = strip_c_comments(repl_break_c_raw);
repl_info_c = strip_c_comments(repl_info_c_raw);
repl_int_h = strip_c_comments(repl_int_h_raw);
repl_params_c = strip_c_comments(repl_params_c_raw);
repl_rollup_c = strip_c_comments(repl_rollup_c_raw);
repl_fmt_c = strip_c_comments(repl_fmt_c_raw);
TEST_ASSERT_NOT_NULL(h);
TEST_ASSERT_NOT_NULL(c);
TEST_ASSERT_NOT_NULL(loop);
TEST_ASSERT_NOT_NULL(picker);
TEST_ASSERT_NOT_NULL(record);
TEST_ASSERT_NOT_NULL(repl_c);
TEST_ASSERT_NOT_NULL(repl_h);
TEST_ASSERT_NOT_NULL(repl_break_c);
TEST_ASSERT_NOT_NULL(repl_info_c);
TEST_ASSERT_NOT_NULL(repl_int_h);
TEST_ASSERT_NOT_NULL(repl_params_c);
TEST_ASSERT_NOT_NULL(repl_rollup_c);
TEST_ASSERT_NOT_NULL(repl_fmt_c);
assert_no_forbidden_includes(h, "sim.h");
assert_no_forbidden_includes(c, "sim.c");
assert_no_forbidden_includes(loop, "sim_loop.c");
assert_no_forbidden_includes(picker, "sim_picker.c");
assert_no_forbidden_includes(record, "sim_record.c");
assert_no_forbidden_includes(repl_c, "repl.c");
assert_no_forbidden_includes(repl_h, "repl.h");
assert_no_forbidden_includes(repl_break_c, "repl_break.c");
assert_no_forbidden_includes(repl_info_c, "repl_info.c");
assert_no_forbidden_includes(repl_int_h, "repl_internal.h");
assert_no_forbidden_includes(repl_params_c, "repl_params.c");
assert_no_forbidden_includes(repl_rollup_c, "repl_rollup.c");
assert_no_forbidden_includes(repl_fmt_c, "repl_fmt.c");
free(h_raw);
free(c_raw);
free(loop_raw);
free(picker_raw);
free(record_raw);
free(repl_c_raw);
free(repl_h_raw);
free(repl_break_c_raw);
free(repl_info_c_raw);
free(repl_int_h_raw);
free(repl_params_c_raw);
free(repl_rollup_c_raw);
free(repl_fmt_c_raw);
free(h);
free(c);
free(loop);
free(picker);
free(record);
free(repl_c);
free(repl_h);
free(repl_break_c);
free(repl_info_c);
free(repl_int_h);
free(repl_params_c);
free(repl_rollup_c);
free(repl_fmt_c);
}
static void test_sim_never_calls_forbidden_symbols(void)
{
char* h_raw = read_whole_file(XCE_SIM_H_PATH);
char* c_raw = read_whole_file(XCE_SIM_C_PATH);
char* loop_raw = read_whole_file(XCE_SIM_LOOP_C_PATH);
char* picker_raw = read_whole_file(XCE_SIM_PICKER_C_PATH);
char* record_raw = read_whole_file(XCE_SIM_RECORD_C_PATH);
char* repl_c_raw = read_whole_file(XCE_SIM_REPL_C_PATH);
char* repl_h_raw = read_whole_file(XCE_SIM_REPL_H_PATH);
char* repl_break_c_raw = read_whole_file(XCE_SIM_REPL_BREAK_C_PATH);
char* repl_info_c_raw = read_whole_file(XCE_SIM_REPL_INFO_C_PATH);
char* repl_int_h_raw = read_whole_file(XCE_SIM_REPL_INTERNAL_H_PATH);
char* repl_params_c_raw = read_whole_file(XCE_SIM_REPL_PARAMS_C_PATH);
char* repl_rollup_c_raw = read_whole_file(XCE_SIM_REPL_ROLLUP_C_PATH);
char* repl_fmt_c_raw = read_whole_file(XCE_SIM_REPL_FMT_C_PATH);
char* h;
char* c;
char* loop;
char* picker;
char* record;
char* repl_c;
char* repl_h;
char* repl_break_c;
char* repl_info_c;
char* repl_int_h;
char* repl_params_c;
char* repl_rollup_c;
char* repl_fmt_c;
TEST_ASSERT_NOT_NULL(h_raw);
TEST_ASSERT_NOT_NULL(c_raw);
TEST_ASSERT_NOT_NULL(loop_raw);
TEST_ASSERT_NOT_NULL(picker_raw);
TEST_ASSERT_NOT_NULL(record_raw);
TEST_ASSERT_NOT_NULL(repl_c_raw);
TEST_ASSERT_NOT_NULL(repl_h_raw);
TEST_ASSERT_NOT_NULL(repl_break_c_raw);
TEST_ASSERT_NOT_NULL(repl_info_c_raw);
TEST_ASSERT_NOT_NULL(repl_int_h_raw);
TEST_ASSERT_NOT_NULL(repl_params_c_raw);
TEST_ASSERT_NOT_NULL(repl_rollup_c_raw);
TEST_ASSERT_NOT_NULL(repl_fmt_c_raw);
h = strip_c_comments(h_raw);
c = strip_c_comments(c_raw);
loop = strip_c_comments(loop_raw);
picker = strip_c_comments(picker_raw);
record = strip_c_comments(record_raw);
repl_c = strip_c_comments(repl_c_raw);
repl_h = strip_c_comments(repl_h_raw);
repl_break_c = strip_c_comments(repl_break_c_raw);
repl_info_c = strip_c_comments(repl_info_c_raw);
repl_int_h = strip_c_comments(repl_int_h_raw);
repl_params_c = strip_c_comments(repl_params_c_raw);
repl_rollup_c = strip_c_comments(repl_rollup_c_raw);
repl_fmt_c = strip_c_comments(repl_fmt_c_raw);
TEST_ASSERT_NOT_NULL(h);
TEST_ASSERT_NOT_NULL(c);
TEST_ASSERT_NOT_NULL(loop);
TEST_ASSERT_NOT_NULL(picker);
TEST_ASSERT_NOT_NULL(record);
TEST_ASSERT_NOT_NULL(repl_c);
TEST_ASSERT_NOT_NULL(repl_h);
TEST_ASSERT_NOT_NULL(repl_break_c);
TEST_ASSERT_NOT_NULL(repl_info_c);
TEST_ASSERT_NOT_NULL(repl_int_h);
TEST_ASSERT_NOT_NULL(repl_params_c);
TEST_ASSERT_NOT_NULL(repl_rollup_c);
TEST_ASSERT_NOT_NULL(repl_fmt_c);
assert_no_forbidden_symbols(h, "sim.h");
assert_no_forbidden_symbols(c, "sim.c");
assert_no_forbidden_symbols(loop, "sim_loop.c");
assert_no_forbidden_symbols(picker, "sim_picker.c");
assert_no_forbidden_symbols(record, "sim_record.c");
assert_no_forbidden_symbols(repl_c, "repl.c");
assert_no_forbidden_symbols(repl_h, "repl.h");
assert_no_forbidden_symbols(repl_break_c, "repl_break.c");
assert_no_forbidden_symbols(repl_info_c, "repl_info.c");
assert_no_forbidden_symbols(repl_int_h, "repl_internal.h");
assert_no_forbidden_symbols(repl_params_c, "repl_params.c");
assert_no_forbidden_symbols(repl_rollup_c, "repl_rollup.c");
assert_no_forbidden_symbols(repl_fmt_c, "repl_fmt.c");
free(h_raw);
free(c_raw);
free(loop_raw);
free(picker_raw);
free(record_raw);
free(repl_c_raw);
free(repl_h_raw);
free(repl_break_c_raw);
free(repl_info_c_raw);
free(repl_int_h_raw);
free(repl_params_c_raw);
free(repl_rollup_c_raw);
free(repl_fmt_c_raw);
free(h);
free(c);
free(loop);
free(picker);
free(record);
free(repl_c);
free(repl_h);
free(repl_break_c);
free(repl_info_c);
free(repl_int_h);
free(repl_params_c);
free(repl_rollup_c);
free(repl_fmt_c);
}
/*
* test_sim_trace_repl_sources_are_isolated - the REPL trace sources
* (control, store, renderer, emitter) are part of the simulation driver:
* each one must be readable, include no forbidden header and name no
* controller-facing symbol. They may use stdio (the REPL prints), unlike
* the core trace sources test_core_isolation.c scans.
*/
static void test_sim_trace_repl_sources_are_isolated(void)
{
static const char* const paths[] = {
XCE_SIM_REPL_TRACE_C_PATH,
XCE_SIM_REPL_TRACE_STORE_C_PATH,
XCE_SIM_REPL_TRACE_RENDER_C_PATH,
XCE_SIM_REPL_TRACE_RENDER_OUTCOME_C_PATH,
XCE_SIM_REPL_TRACE_RENDER_H_PATH,
XCE_SIM_REPL_TRACE_EMIT_C_PATH,
};
size_t i;
for (i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
char* raw = read_whole_file(paths[i]);
char* stripped;
TEST_ASSERT_NOT_NULL_MESSAGE(raw, paths[i]);
stripped = strip_c_comments(raw);
TEST_ASSERT_NOT_NULL_MESSAGE(stripped, paths[i]);
TEST_ASSERT_TRUE_MESSAGE(stripped[0] != '\0', paths[i]);
assert_no_forbidden_includes(stripped, paths[i]);
assert_no_forbidden_symbols(stripped, paths[i]);
assert_repl_trace_source_never_logs(stripped, paths[i]);
free(raw);
free(stripped);
}
}
/* The log scan itself must be able to fail: each class of planted log
* call or include is a hit, and the same names inside literals or longer
* identifiers are not. */
static void test_repl_trace_log_scan_detects_and_ignores(void)
{
char ok_text[] = "static const char* s = \"WARN(x) tlog_info(\";\n"
"void f(void) { MY_INFO(1); p->ERROR(2); "
"sched_trace_rec_free(0); fprintf(stdout, \"x\"); }\n";
char hit_mac[] = "void f(void) { WARN (\"x\"); }\n";
char hit_tlog[] = "void f(void) { tlog_debug3(\"x\"); }\n";
char hit_sl[] = "void f(void) { SCHEDULER_LOG(INFO, \"x\"); }\n";
blank_string_literals(ok_text);
blank_string_literals(hit_mac);
blank_string_literals(hit_tlog);
blank_string_literals(hit_sl);
TEST_ASSERT_EQUAL_INT(-1, find_call(ok_text, "WARN", 0));
TEST_ASSERT_EQUAL_INT(-1, find_call(ok_text, "tlog", 1));
TEST_ASSERT_EQUAL_INT(-1, find_call(ok_text, "INFO", 0));
TEST_ASSERT_EQUAL_INT(-1, find_call(ok_text, "ERROR", 0));
TEST_ASSERT_NOT_EQUAL(-1, find_call(hit_mac, "WARN", 0));
TEST_ASSERT_NOT_EQUAL(-1, find_call(hit_tlog, "tlog", 1));
TEST_ASSERT_NOT_EQUAL(-1, find_call(hit_sl, "SCHEDULER_LOG", 0));
}
/*
* test_trace_enable_is_repl_only - Requirement 11.1: no command-line
* option, configuration setting or administrative request enables
* tracing. Structural proof over every scheduler source and header:
* - the only assignments to `trace_ctl` (the core's published trace
* settings, the one switch the trace hooks test) are in the REPL trace
* sources repl_trace.c / repl_trace_emit.c, plus core.c's reset, which
* must store NULL;
* - no non-REPL source carries a string literal naming "trace" (an
* option, config key or request name would be one), other than an
* #include of the trace headers. The REPL files (repl*.c/h) are the
* interactive command surface and are exempt from this half.
*/
static void scan_trace_enable_dir(const char* dir, int* nfiles)
{
DIR* d = opendir(dir);
struct dirent* e;
TEST_ASSERT_NOT_NULL_MESSAGE(d, dir);
while ((e = readdir(d)) != NULL) {
size_t n = strlen(e->d_name);
char path[1024];
char msg[1200];
char* raw;
char* t;
long at;
int is_repl, writer_ok, i;
if ((n < 3) || ((strcmp(e->d_name + n - 2, ".c") != 0)
&& (strcmp(e->d_name + n - 2, ".h") != 0))) {
continue;
}
snprintf(path, sizeof(path), "%s/%s", dir, e->d_name);
raw = read_whole_file(path);
TEST_ASSERT_NOT_NULL_MESSAGE(raw, path);
t = strip_c_comments(raw);
TEST_ASSERT_NOT_NULL_MESSAGE(t, path);
(*nfiles)++;
is_repl = (strncmp(e->d_name, "repl", 4) == 0);
writer_ok = (strcmp(e->d_name, "repl_trace.c") == 0)
|| (strcmp(e->d_name, "repl_trace_emit.c") == 0);
for (at = find_assignment(t, "trace_ctl", 0); at >= 0;
at = find_assignment(t, "trace_ctl", at + 1)) {
const char* rhs = strchr(t + at, '=') + 1;
while ((*rhs == ' ') || (*rhs == '\t')) {
rhs++;
}
snprintf(msg, sizeof(msg),
"%s assigns trace_ctl -- only the REPL trace sources may "
"enable tracing (Req 11.1)",
path);
TEST_ASSERT_TRUE_MESSAGE(
writer_ok
|| ((strcmp(e->d_name, "core.c") == 0)
&& (strncmp(rhs, "NULL", 4) == 0)),
msg);
}
if (!is_repl) {
/* drop #include lines, then look for "trace" in a literal */
for (i = 0; t[i] != '\0'; i++) {
if ((t[i] == '#') && (strncmp(t + i, "#include", 8) == 0)) {
while ((t[i] != '\0') && (t[i] != '\n')) {
t[i++] = ' ';
}
if (t[i] == '\0') {
break;
}
}
}
snprintf(msg, sizeof(msg),
"%s has a string literal naming \"trace\" -- tracing "
"must not be reachable from an option, config key or "
"request (Req 11.1)",
path);
TEST_ASSERT_FALSE_MESSAGE(literal_mentions(t, "trace"), msg);
}
free(raw);
free(t);
}
closedir(d);
}
static void test_trace_enable_is_repl_only(void)
{
char src_dir[1024];
char inc_dir[1024];
char sub[1100];
int nfiles = 0;
TEST_ASSERT_TRUE(dirname_of(XCE_SIM_C_PATH, src_dir, sizeof(src_dir)));
TEST_ASSERT_TRUE(dirname_of(XCE_SIM_H_PATH, inc_dir, sizeof(inc_dir)));
scan_trace_enable_dir(src_dir, &nfiles);
snprintf(sub, sizeof(sub), "%s/plugins", src_dir);
scan_trace_enable_dir(sub, &nfiles);
scan_trace_enable_dir(inc_dir, &nfiles);
snprintf(sub, sizeof(sub), "%s/sched", inc_dir);
scan_trace_enable_dir(sub, &nfiles);
/* a guard against scanning an empty or wrong directory */
TEST_ASSERT_GREATER_THAN_INT(50, nfiles);
}
/*
* test_sim_driver_struct_has_no_forbidden_fields - Req 5 AC 17's
* "confirmed by the struct definition itself, not runtime checks alone"
* clause. Scans ONLY xce_sched_sim_driver_t's own `{ ... }` field-listing
* text (extract_struct_body(), on comment-stripped sim.h) for forbidden
* field types -- narrower and more literal than the whole-file scans above,
* and immune to sim.h's own boundary comment naming these same types in
* prose (comments are stripped before extraction).
*/
static void test_sim_driver_struct_has_no_forbidden_fields(void)
{
char* h_raw = read_whole_file(XCE_SIM_H_PATH);
char* h;
char struct_body[STRUCT_BODY_BUF_SIZE];
size_t i;
TEST_ASSERT_NOT_NULL(h_raw);
h = strip_c_comments(h_raw);
TEST_ASSERT_NOT_NULL(h);
extract_struct_body(h, struct_body, sizeof(struct_body));
for (i = 0; i < NUM_STRUCT_FORBIDDEN_TYPES; i++) {
char msg[768];
snprintf(msg, sizeof(msg),
"xce_sched_sim_driver_t (sim.h) has a field referencing "
"forbidden controller-facing type '%s' -- Req 5 AC 17 "
"requires zero xcecli_ctx_t/xce_cluster_config_t/capnp-"
"socket fields, enforced by the struct definition itself",
STRUCT_FORBIDDEN_TYPES[i]);
TEST_ASSERT_NULL_MESSAGE(strstr(struct_body, STRUCT_FORBIDDEN_TYPES[i]),
msg);
}
free(h_raw);
free(h);
}
/*
* test_sim_object_has_no_forbidden_undefined_symbols - the literal
* build-time/link-time proof: sim.c + sim_loop.c + sim_picker.c +
* sim_record.c + repl.c + repl_break.c + repl_info.c + repl_params.c,
* compiled together in isolation into sched_sim_isolation_lib
* (meson.build), must not reference
* (as an undefined external symbol in the compiled archive) anything
* matching the forbidden prefixes above. Independent of what any header
* transitively declares -- reflects only what this driver's compiled code
* actually
* calls. Same technique and same any_undefined_seen vacuous-pass guard as
* test_core_isolation.c's test_core_object_has_no_forbidden_undefined_symbols().
*/
static void test_sim_object_has_no_forbidden_undefined_symbols(void)
{
char cmd[4096];
char line[1024];
FILE* p;
int any_undefined_seen = 0;
snprintf(cmd, sizeof(cmd), "nm --format=posix -u \"%s\" 2>&1",
XCE_SIM_LIB_PATH);
p = popen(cmd, "r");
TEST_ASSERT_NOT_NULL_MESSAGE(p, "failed to popen() nm(1)");
while (fgets(line, sizeof(line), p) != NULL) {
char symbol[512];
char type;
size_t i;
/* posix format for an undefined symbol: "<name> U" (no
* value/size columns). Skip anything that doesn't parse as
* exactly that -- e.g. a blank line, or an archive member header
* some nm versions print (this archive has one member object per
* source in sched_sim_isolation_lib, src/scheduler/meson.build). */
if (sscanf(line, "%511s %c", symbol, &type) != 2) {
continue;
}
if (type != 'U') {
continue;
}
any_undefined_seen = 1;
for (i = 0; i < NUM_FORBIDDEN_SYMBOLS; i++) {
if (strncmp(symbol, FORBIDDEN_SYMBOLS[i],
strlen(FORBIDDEN_SYMBOLS[i]))
== 0) {
char msg[768];
snprintf(msg, sizeof(msg),
"sim.c/sim_loop.c's compiled object has an "
"undefined reference to '%s', matching forbidden "
"controller-facing prefix '%s' -- the simulation "
"driver must never link networking/API/client "
"library symbols (Req 5 AC 17)",
symbol, FORBIDDEN_SYMBOLS[i]);
pclose(p);
TEST_FAIL_MESSAGE(msg);
}
}
}
pclose(p);
/* If nm produced no undefined symbols at all, either the archive path
* is wrong or nm itself failed silently -- either way this test would
* be vacuously passing without having checked anything real. */
TEST_ASSERT_TRUE_MESSAGE(
any_undefined_seen,
"nm reported zero undefined symbols for XCE_SIM_LIB_PATH -- the "
"archive path is likely wrong (this test would otherwise pass "
"vacuously)");
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_sim_source_paths_are_readable);
RUN_TEST(test_sim_never_includes_forbidden_headers);
RUN_TEST(test_sim_never_calls_forbidden_symbols);
RUN_TEST(test_sim_trace_repl_sources_are_isolated);
RUN_TEST(test_repl_trace_log_scan_detects_and_ignores);
RUN_TEST(test_trace_enable_is_repl_only);
RUN_TEST(test_sim_driver_struct_has_no_forbidden_fields);
RUN_TEST(test_sim_object_has_no_forbidden_undefined_symbols);
return UNITY_END();
}