/* * test_core_isolation.c - regression guard for task 4.1's Observable * (scheduler-core spec, Req 5.1/5.2): "the core structure compiles and * links with zero symbols from the networking/API libraries; a build-time * or link-time check confirms this" -- and the accompanying constraint * "no wall-clock time call appears anywhere in the extracted core's * source." * * Two layers, same genre as test_picker_core_loop_boundary.c / * test_picker_pendreason_factory_audit.c (source-text scans of the real, * current on-disk files, not a frozen snapshot): * * 1. Source-text scan of core.h/core.c: no #include of client.h, * netio.h, xceapi.h, delay.h, idgen.h, or xce/cluster.h (the * "Allowed Dependencies" list in design.md names exactly these as * forbidden); no call-site substring matching a symbol exported only * by those libraries; no wall-clock call (time()/clock_gettime()/ * gettimeofday()) anywhere in core.c. * * 2. The literal build-time/link-time proof the Observable asks for, * not achievable by a text scan alone: core.c is compiled in * isolation into its own static library (meson.build's * sched_core_isolation_lib, this test's XCE_CORE_LIB_PATH), and this * test inspects that archive's UNDEFINED SYMBOL TABLE via nm(1) -- * proving what core.c's *compiled code actually calls*, independent * of what any header happens to transitively declare (xce/types.h, * used by literally every file in this directory, itself includes * netio.h for unrelated reasons -- so a "no forbidden #include" * check alone would either false-positive on that pre-existing, * harmless transitive include, or have to special-case it; the nm * check sidesteps that entirely by checking *usage*, not * *visibility*). * * Why nm on a static_library() rather than the full xce_scheduler * executable: the executable links the whole monolithic libxce_dep, so * every symbol from netio/xceapi/client/delay/idgen would appear in ITS * undefined-symbol table regardless of whether core.c itself ever calls * one -- that would prove nothing. Compiling core.c alone into its own * archive (sched_core_isolation_lib, meson.build) and inspecting THAT * object's undefined symbols isolates exactly what this one translation * unit references. */ #include #include #include #include #ifndef XCE_CORE_H_PATH #error \ "XCE_CORE_H_PATH must be defined by meson.build to core.h's absolute path" #endif #ifndef XCE_CORE_C_PATH #error \ "XCE_CORE_C_PATH must be defined by meson.build to core.c's absolute path" #endif #ifndef XCE_CORE_LIB_PATH #error \ "XCE_CORE_LIB_PATH must be defined by meson.build to the absolute path of core.c's isolated static_library() archive" #endif /* Forbidden headers (design.md "Allowed Dependencies", Req 5 AC 3): the * core 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 ", "#include ", "#include ", "#include ", "#include ", "#include ", }; #define NUM_FORBIDDEN_INCLUDES \ (sizeof(FORBIDDEN_INCLUDES) / sizeof(FORBIDDEN_INCLUDES[0])) /* Forbidden call-site symbols: exported only by netio.c/xceapi.c/client.c/ * delay.c/idgen.c (verified against those headers' own extern lists) -- * none of these are legitimately callable from a core with zero controller * I/O, network, or timer dependency. */ 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])) /* Wall-clock calls (Req 5 AC "no wall-clock time call appears anywhere in * the extracted core's source"). */ static const char* const FORBIDDEN_WALLCLOCK[] = { "time(", "clock_gettime(", "gettimeofday(", }; #define NUM_FORBIDDEN_WALLCLOCK \ (sizeof(FORBIDDEN_WALLCLOCK) / sizeof(FORBIDDEN_WALLCLOCK[0])) 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; } void setUp(void) { return; } void tearDown(void) { return; } /* Sanity check: both 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_core_source_paths_are_readable(void) { char* h = read_whole_file(XCE_CORE_H_PATH); char* c = read_whole_file(XCE_CORE_C_PATH); TEST_ASSERT_NOT_NULL_MESSAGE(h, XCE_CORE_H_PATH); TEST_ASSERT_NOT_NULL_MESSAGE(c, XCE_CORE_C_PATH); free(h); free(c); } static void test_core_never_includes_forbidden_headers(void) { char* h = read_whole_file(XCE_CORE_H_PATH); char* c = read_whole_file(XCE_CORE_C_PATH); size_t i; TEST_ASSERT_NOT_NULL(h); TEST_ASSERT_NOT_NULL(c); for (i = 0; i < NUM_FORBIDDEN_INCLUDES; i++) { char msg[256]; snprintf(msg, sizeof(msg), "core.h directly includes forbidden header '%s'", FORBIDDEN_INCLUDES[i]); TEST_ASSERT_NULL_MESSAGE(strstr(h, FORBIDDEN_INCLUDES[i]), msg); snprintf(msg, sizeof(msg), "core.c directly includes forbidden header '%s'", FORBIDDEN_INCLUDES[i]); TEST_ASSERT_NULL_MESSAGE(strstr(c, FORBIDDEN_INCLUDES[i]), msg); } free(h); free(c); } static void test_core_never_calls_forbidden_symbols(void) { char* h = read_whole_file(XCE_CORE_H_PATH); char* c = read_whole_file(XCE_CORE_C_PATH); size_t i; TEST_ASSERT_NOT_NULL(h); TEST_ASSERT_NOT_NULL(c); for (i = 0; i < NUM_FORBIDDEN_SYMBOLS; i++) { char msg[256]; snprintf(msg, sizeof(msg), "core.h references forbidden networking/API symbol '%s'", FORBIDDEN_SYMBOLS[i]); TEST_ASSERT_NULL_MESSAGE(strstr(h, FORBIDDEN_SYMBOLS[i]), msg); snprintf(msg, sizeof(msg), "core.c references forbidden networking/API symbol '%s'", FORBIDDEN_SYMBOLS[i]); TEST_ASSERT_NULL_MESSAGE(strstr(c, FORBIDDEN_SYMBOLS[i]), msg); } free(h); free(c); } static void test_core_never_calls_wallclock_functions(void) { char* c = read_whole_file(XCE_CORE_C_PATH); size_t i; TEST_ASSERT_NOT_NULL(c); for (i = 0; i < NUM_FORBIDDEN_WALLCLOCK; i++) { char msg[256]; snprintf(msg, sizeof(msg), "core.c calls wall-clock function '%s' -- the core must " "access time exclusively through a future xce_core_now() " "(scheduler-core spec, task 4.5)", FORBIDDEN_WALLCLOCK[i]); TEST_ASSERT_NULL_MESSAGE(strstr(c, FORBIDDEN_WALLCLOCK[i]), msg); } free(c); } /* * test_core_object_has_no_forbidden_undefined_symbols - the literal * build-time/link-time proof: core.c, compiled in isolation into * sched_core_isolation_lib (meson.build), must not reference (as an * undefined external symbol in the compiled object) anything matching the * forbidden prefixes above. This is independent of what any header * transitively declares -- it reflects only what core.c's compiled code * actually calls. */ static void test_core_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_CORE_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: " 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. */ 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), "core.c's compiled object has an undefined " "reference to '%s', matching forbidden " "networking/API prefix '%s' -- core.c must never " "link networking/API library symbols (Req 5.1/" "5.2)", 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_CORE_LIB_PATH -- " "the archive path is likely wrong (this test would otherwise pass " "vacuously)"); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_core_source_paths_are_readable); RUN_TEST(test_core_never_includes_forbidden_headers); RUN_TEST(test_core_never_calls_forbidden_symbols); RUN_TEST(test_core_never_calls_wallclock_functions); RUN_TEST(test_core_object_has_no_forbidden_undefined_symbols); return UNITY_END(); }