/* test_select.c — Phase 1 TDD skeletons for select-expression AST / CSE. * * Compile but fail until Phases 3-4 implement the full select pipeline. * The UNIT_TEST macro must be defined before including resreq.h so that * sel_optimize_test_reset() is visible. */ #define UNIT_TEST 1 #include #include #include #include #include #include #include "resreq.h" #include "test_resmgr_fixture.h" #include "utils.h" #include "xstring.h" /* ── fake_scheduler_t ───────────────────────────────────────────────────── * * Phase 1 stub. Phase 6 replaces these wrappers with equivalents that * reference real xce_scheduler_t fields — the atomic field layout and * predicate semantics are stable. */ typedef struct { _Atomic int session_active; _Atomic int eval_in_progress; _Atomic int staging_merge_complete; } fake_scheduler_t; static int test_sched_session_active(fake_scheduler_t *s) { return atomic_load_explicit(&s->session_active, memory_order_acquire); } static int test_sched_eval_in_progress(fake_scheduler_t *s) { return atomic_load_explicit(&s->eval_in_progress, memory_order_acquire); } static int test_staging_merge_complete(fake_scheduler_t *s) { return atomic_load_explicit(&s->staging_merge_complete, memory_order_acquire); } /* ── 1-1: Parse tests ─────────────────────────────────────────────────────*/ static void test_select_parse_valid_expr(void) { /* parse_resreq_primary with a well-formed select expression must * succeed (rc >= 0) and produce a non-NULL root node. */ char * errmsg = NULL; xce_primary_resreq_t resreq; /* resmgr fixture is created for teardown symmetry; Phase 2 threads it * into parse_resreq_primary — for Phase 1 we use the 3-arg signature. */ var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "type==linux", resmgr, &errmsg); /* Phase 4: expect success */ TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_select_parse_unknown_resource(void) { /* A resource name that is not present in the resmgr must produce a * parse error (rc < 0). */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "unknownres==linux", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_select_parse_type_mismatch(void) { /* Comparing a BOOL resource with a string literal is a type mismatch * and must be rejected. "gpu" is registered as RES_TYPE_BOOL in the * fixture; "gpu==true" supplies a string literal, not a bare bool. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "gpu==true", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } /* ── 1-2: Overflow guard tests ────────────────────────────────────────────*/ static void test_select_input_len_overflow(void) { /* Expressions longer than SELECT_MAX_INPUT_LEN (8192) must be rejected * immediately — before any parsing work is done. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); const int limit = 8192 + 1; char * longexpr = malloc((size_t)limit + 1); TEST_ASSERT_NOT_NULL(longexpr); memset(longexpr, 'a', (size_t)limit); longexpr[limit] = '\0'; int rc = parse_resreq_primary(&resreq, longexpr, resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); free(longexpr); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_select_depth_overflow(void) { /* Nesting deeper than SELECT_MAX_DEPTH (64) must be rejected. * Build 65 levels of '!' prefix to exceed the limit. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); const int max_depth = 65; /* Each level is one '!' character; add "gpu" plus NUL at the end. */ char *deepexpr = malloc((size_t)max_depth + 4); TEST_ASSERT_NOT_NULL(deepexpr); int pos = 0; for (int i = 0; i < max_depth; i++) deepexpr[pos++] = '!'; deepexpr[pos++] = 'g'; deepexpr[pos++] = 'p'; deepexpr[pos++] = 'u'; deepexpr[pos] = '\0'; int rc = parse_resreq_primary(&resreq, deepexpr, resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); free(deepexpr); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } /* ── 1-3: CSE / hash-consing tests ───────────────────────────────────────*/ static void test_select_cse_commutative_same_ptr(void) { /* When CSE is enabled, A&&B and B&&A must be canonicalised to the same * interned pointer because && is commutative. */ sel_node_table_init(); sel_optimize_set(1); xce_select_node_t *a = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_GPU_IDX, 0); xce_select_node_t *b = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_MEM_IDX, 0); TEST_ASSERT_NOT_NULL(a); TEST_ASSERT_NOT_NULL(b); xce_select_node_t *ab = makeSelectNode(SEL_AND, (uintptr_t)a, (uintptr_t)b); xce_select_node_t *ba = makeSelectNode(SEL_AND, (uintptr_t)b, (uintptr_t)a); TEST_ASSERT_NOT_NULL(ab); TEST_ASSERT_NOT_NULL(ba); /* Phase 4: pointer equality guaranteed by commutativity sort + CSE */ TEST_ASSERT_EQUAL_PTR(ab, ba); sel_optimize_test_reset(); } static void test_select_cse_same_args_same_ptr(void) { /* Two makeSelectNode calls with identical type and operands must return * the same interned pointer. */ sel_node_table_init(); sel_optimize_set(1); double num = 100.0; uintptr_t bits; memcpy(&bits, &num, sizeof(bits)); xce_select_node_t *n1 = makeSelectNode(SEL_NUM, bits, 0); xce_select_node_t *n2 = makeSelectNode(SEL_NUM, bits, 0); TEST_ASSERT_NOT_NULL(n1); TEST_ASSERT_NOT_NULL(n2); /* Phase 4: same pointer from CSE table */ TEST_ASSERT_EQUAL_PTR(n1, n2); sel_optimize_test_reset(); } /* ── 1-4: Eval tests ────────────────────────────────────────────────────── * * In Phase 1 these tests verify parse structure (AST node types), not * runtime evaluation values. Full eval contract is exercised in Phase 4e * once eval_select_simple is implemented. */ static void test_eval_numeric_gt(void) { /* "mem>50" must parse to a SEL_GT root node. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_GT, resreq.select->root->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_eval_numeric_lt(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "mem<200", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_LT, resreq.select->root->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_eval_numeric_eq(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "mem==100", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_EQ, resreq.select->root->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_eval_string_eq(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "type==linux", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_EQ, resreq.select->root->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_eval_bool_standalone(void) { /* A bare resource name of type BOOL must parse as SEL_RESNAME with * res_type == RES_TYPE_BOOL — no comparison operator needed. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "gpu", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_RESNAME, resreq.select->root->type); TEST_ASSERT_EQUAL_INT(RES_TYPE_BOOL, resreq.select->root->u.resname.res_type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_eval_not(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "!gpu", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_NOT, resreq.select->root->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_eval_and_shortcircuit(void) { /* "gpu && mem>50" must produce a SEL_AND root with non-NULL left and * right children. Short-circuit semantics are verified in Phase 4e. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "gpu && mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_AND, resreq.select->root->type); TEST_ASSERT_NOT_NULL(resreq.select->root->u.binop.left); TEST_ASSERT_NOT_NULL(resreq.select->root->u.binop.right); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_eval_or_shortcircuit(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "gpu || type==linux", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_OR, resreq.select->root->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } /* ── 4b-3: Ordered comparison type validation tests ─────────────────────────*/ static void test_ordered_cmp_nonnum_string_resource_fails(void) { /* "type>5" where "type" is RES_TYPE_STRING must be rejected. * Ordered comparisons are only valid on numeric resources. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "type>5", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_ordered_cmp_nonnum_bool_resource_fails(void) { /* "gpu>0" where "gpu" is RES_TYPE_BOOL must be rejected. * Ordered comparisons are only valid on numeric resources. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "gpu>0", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_ordered_cmp_string_rhs_fails(void) { /* "mem>linux" where "mem" is RES_TYPE_NUMERIC but RHS is a string literal * must be rejected. Ordered comparisons require a numeric literal. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "mem>linux", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_ordered_cmp_numeric_resource_succeeds(void) { /* Ordered comparisons on a numeric resource with a numeric literal * must succeed. "mem>50" and "mem<200" are tested separately in * test_eval_numeric_gt/lt; this test validates GE and LE variants. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "mem>=100", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_GE, resreq.select->root->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } /* ── Phase 4b: EQ type-validation tests ──────────────────────────────────*/ static void test_4b_num_str_mismatch_eq_fails(void) { /* "mem==linux" where "mem" is RES_TYPE_NUMERIC and "linux" is a string * literal must be rejected: numeric resource vs string literal is a * type mismatch for equality comparisons. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "mem==linux", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_4b_str_num_coercion_eq_succeeds(void) { /* "type==5" where "type" is RES_TYPE_STRING and "5" is a SEL_NUM literal. * The numeric literal must be coerced to the string "5" so the comparison * is accepted (STRING × NUM → coerce to STRING × STR("5")). */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "type==5", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_EQ, resreq.select->root->type); TEST_ASSERT_NOT_NULL(resreq.select->root->u.binop.left); TEST_ASSERT_NOT_NULL(resreq.select->root->u.binop.right); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_4b_true_resname_migration_error(void) { /* A bare "true" token in resname_leaf position must be rejected with a * migration diagnostic (legacy boolean syntax is no longer accepted). */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "true", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_4b_false_resname_migration_error(void) { /* A bare "false" token in resname_leaf position must be rejected with a * migration diagnostic (legacy boolean syntax is no longer accepted). */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "false", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_4b_bool_num_eq_accepted(void) { /* "gpu==1" where "gpu" is RES_TYPE_BOOL and "1" is SEL_NUM. * BOOL × NUM equality is accepted per design (numeric 0/1 maps to * false/true for boolean resources). */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "gpu==1", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_EQ, resreq.select->root->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_4b_num_str_mismatch_ne_fails(void) { /* "mem!=linux" where "mem" is RES_TYPE_NUMERIC and "linux" is a string * literal must be rejected for NE just as for EQ. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "mem!=linux", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_4b_bool_str_ne_fails(void) { /* "gpu!=true" where "gpu" is RES_TYPE_BOOL and "true" is a string literal * must be rejected: boolean resource cannot be compared with a string. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "gpu!=true", resmgr, &errmsg); TEST_ASSERT_TRUE(rc < 0); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_4b_str_num_coercion_ne_succeeds(void) { /* "type!=5" where "type" is RES_TYPE_STRING and "5" is SEL_NUM. * The numeric literal must be coerced to "5" and the NE comparison accepted. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "type!=5", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); TEST_ASSERT_EQUAL_INT(SEL_NE, resreq.select->root->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } /* ── 1-5: GC tests ────────────────────────────────────────────────────────*/ static void test_gc_sweep_unreachable_freed(void) { /* After a sweep with no live jobs, every node in the table is * unreachable. Re-interning the same arguments must return a freshly * allocated pointer — the old node was freed and removed from the table. * * The pointer-inequality assertion captures the contract; it becomes * meaningful once Phase 4f wires the mark-and-sweep to the intern table. */ sel_node_table_init(); sel_optimize_set(1); double val = 99.0; uintptr_t bits; memcpy(&bits, &val, sizeof(bits)); xce_select_node_t *node1 = makeSelectNode(SEL_NUM, bits, 0); TEST_ASSERT_NOT_NULL(node1); /* No live jobs → all nodes are unreachable */ gc_clear_marks(); gc_sweep_select_nodes(NULL, 0); /* Re-intern: node1 was freed; a new node must be allocated */ xce_select_node_t *node2 = makeSelectNode(SEL_NUM, bits, 0); TEST_ASSERT_NOT_NULL(node2); /* Phase 4f: node2 must be a distinct allocation from the freed node1 */ TEST_ASSERT_TRUE(node2 != node1); sel_optimize_test_reset(); } static void test_gc_sweep_reachable_retained(void) { /* A node referenced by a live job must survive GC with its node_id * unchanged. In Phase 1 the xce_sched_job_t struct is opaque and the * live-job marking path is not yet implemented, so this test validates * the weaker invariant: a node that has just been created has a stable * node_id when no sweep has modified it. * * Phase 6 extends this test to pass a real live_jobs array so that the * mark pass actually retains the node. */ sel_node_table_init(); sel_optimize_set(1); double val = 42.0; uintptr_t bits; memcpy(&bits, &val, sizeof(bits)); xce_select_node_t *node = makeSelectNode(SEL_NUM, bits, 0); TEST_ASSERT_NOT_NULL(node); uint32_t old_id = node->node_id; /* node_id is assigned at intern time and must not change before a sweep */ TEST_ASSERT_EQUAL_UINT32(old_id, node->node_id); sel_optimize_test_reset(); } static void test_gc_reintern_after_sweep(void) { /* After a sweep removes a node, re-creating the same expression must * succeed — the intern table must accept the new node without * stale-entry confusion. */ sel_node_table_init(); sel_optimize_set(1); xce_select_node_t *n1 = makeSelectNode(SEL_TRUE, 0, 0); TEST_ASSERT_NOT_NULL(n1); gc_clear_marks(); gc_sweep_select_nodes(NULL, 0); /* sweep all — no live jobs */ /* Re-intern: must not crash and must return a valid (non-NULL) node */ xce_select_node_t *n2 = makeSelectNode(SEL_TRUE, 0, 0); TEST_ASSERT_NOT_NULL(n2); sel_optimize_test_reset(); } /* ── 1-6: GC predicate contract tests ──────────────────────────────────── * * Verify the three scheduler-state predicates used by the GC idle-phase * guard (Phase 4f-4). Each test drives the fake_scheduler_t atomics into * a specific state and asserts the expected predicate return value. */ static void test_gc_predicate_idle(void) { /* When the scheduler is idle, all three predicates must report safe * values: session_active == 0, eval_in_progress == 0, * staging_merge_complete == 1. */ fake_scheduler_t sched; memset(&sched, 0, sizeof(sched)); atomic_store_explicit(&sched.staging_merge_complete, 1, memory_order_release); TEST_ASSERT_EQUAL_INT(0, test_sched_session_active(&sched)); TEST_ASSERT_EQUAL_INT(0, test_sched_eval_in_progress(&sched)); TEST_ASSERT_EQUAL_INT(1, test_staging_merge_complete(&sched)); } static void test_gc_predicate_session_active_blocks(void) { /* session_active == 1 indicates an active scheduling session; GC must * not run. The predicate must return 1. */ fake_scheduler_t sched; memset(&sched, 0, sizeof(sched)); atomic_store_explicit(&sched.session_active, 1, memory_order_release); atomic_store_explicit(&sched.staging_merge_complete, 1, memory_order_release); TEST_ASSERT_EQUAL_INT(1, test_sched_session_active(&sched)); } static void test_gc_predicate_eval_in_progress_blocks(void) { /* eval_in_progress == 1 means host evaluation is underway; GC must * not run. The predicate must return 1. */ fake_scheduler_t sched; memset(&sched, 0, sizeof(sched)); atomic_store_explicit(&sched.eval_in_progress, 1, memory_order_release); atomic_store_explicit(&sched.staging_merge_complete, 1, memory_order_release); TEST_ASSERT_EQUAL_INT(1, test_sched_eval_in_progress(&sched)); } static void test_gc_predicate_staging_not_complete_blocks(void) { /* staging_merge_complete == 0 means the staging area has not been * merged into the live tree yet; GC must not run. The predicate * must return 0. */ fake_scheduler_t sched; memset(&sched, 0, sizeof(sched)); /* staging_merge_complete stays 0 (zeroed by memset) */ TEST_ASSERT_EQUAL_INT(0, test_staging_merge_complete(&sched)); } /* ── Phase 4f: recompact_node_ids tests ────────────────────────────────── * * 4f-5: recompact_node_ids must renumber live nodes to dense 0..N-1 and * reset sel_node_next_id so subsequent allocations continue from N. */ static void test_recompact_ids_dense(void) { /* Create 3 distinct SEL_NUM nodes; after recompact their node_ids must * be 0, 1, 2 in insertion order. */ sel_node_table_init(); sel_optimize_set(1); double v1 = 1.0, v2 = 2.0, v3 = 3.0; uintptr_t b1, b2, b3; memcpy(&b1, &v1, sizeof(b1)); memcpy(&b2, &v2, sizeof(b2)); memcpy(&b3, &v3, sizeof(b3)); xce_select_node_t *n1 = makeSelectNode(SEL_NUM, b1, 0); xce_select_node_t *n2 = makeSelectNode(SEL_NUM, b2, 0); xce_select_node_t *n3 = makeSelectNode(SEL_NUM, b3, 0); TEST_ASSERT_NOT_NULL(n1); TEST_ASSERT_NOT_NULL(n2); TEST_ASSERT_NOT_NULL(n3); recompact_node_ids(); /* IDs must be dense starting from 0 */ TEST_ASSERT_EQUAL_UINT32(0, n1->node_id); TEST_ASSERT_EQUAL_UINT32(1, n2->node_id); TEST_ASSERT_EQUAL_UINT32(2, n3->node_id); sel_optimize_test_reset(); } static void test_recompact_resets_counter(void) { /* After sweeping all nodes and reinternng 1, recompact must reset * sel_node_next_id to 1 so the next new node gets id == 1 (not a * stale large value). */ sel_node_table_init(); sel_optimize_set(1); /* Create and sweep two nodes to advance the counter */ double va = 100.0, vb = 200.0; uintptr_t ba, bb; memcpy(&ba, &va, sizeof(ba)); memcpy(&bb, &vb, sizeof(bb)); makeSelectNode(SEL_NUM, ba, 0); makeSelectNode(SEL_NUM, bb, 0); gc_clear_marks(); gc_sweep_select_nodes(NULL, 0); /* remove all — no live jobs */ /* Reintern exactly one node */ double vc = 300.0; uintptr_t bc; memcpy(&bc, &vc, sizeof(bc)); xce_select_node_t *n_live = makeSelectNode(SEL_NUM, bc, 0); TEST_ASSERT_NOT_NULL(n_live); recompact_node_ids(); /* The 1 live node gets id 0 */ TEST_ASSERT_EQUAL_UINT32(0, n_live->node_id); /* The NEXT new node must get id 1 (counter reset to 1 by recompact) */ double vd = 400.0; uintptr_t bd; memcpy(&bd, &vd, sizeof(bd)); xce_select_node_t *n_new = makeSelectNode(SEL_NUM, bd, 0); TEST_ASSERT_NOT_NULL(n_new); TEST_ASSERT_EQUAL_UINT32(1, n_new->node_id); sel_optimize_test_reset(); } static void test_recompact_empty_table(void) { /* recompact on an empty table must not crash and must leave the * counter at 0. */ sel_node_table_init(); sel_optimize_set(1); gc_clear_marks(); gc_sweep_select_nodes(NULL, 0); /* sweep any residual nodes */ recompact_node_ids(); /* After recompact the first new node must get id == 0 */ double vx = 999.0; uintptr_t bx; memcpy(&bx, &vx, sizeof(bx)); xce_select_node_t *n = makeSelectNode(SEL_NUM, bx, 0); TEST_ASSERT_NOT_NULL(n); TEST_ASSERT_EQUAL_UINT32(0, n->node_id); sel_optimize_test_reset(); } /* ── sel_optimize_level write-once CAS (task 1-2) ────────────────────────*/ static void test_optimize_set_once(void) { sel_optimize_set(1); TEST_ASSERT_EQUAL_INT(1, sel_get_optimize_level()); /* Second call must not overwrite — write-once CAS */ sel_optimize_set(0); TEST_ASSERT_EQUAL_INT(1, sel_get_optimize_level()); sel_optimize_test_reset(); } static void test_optimize_reset_restores_unset(void) { sel_optimize_set(2); TEST_ASSERT_EQUAL_INT(2, sel_get_optimize_level()); sel_optimize_test_reset(); TEST_ASSERT_EQUAL_INT(-1, sel_get_optimize_level()); /* After reset, a new set must succeed */ sel_optimize_set(0); TEST_ASSERT_EQUAL_INT(0, sel_get_optimize_level()); sel_optimize_test_reset(); } /* ── 4c-1: Commutativity: GT is non-commutative ──────────────────────────*/ static void test_cse_gt_not_commutative(void) { /* In CSE mode, makeSelectNode(SEL_GT, A, B) and makeSelectNode(SEL_GT, B, A) * must return DIFFERENT interned pointers — GT is not commutative. */ sel_optimize_test_reset(); /* ensure clean state */ sel_node_table_init(); sel_optimize_set(1); xce_select_node_t *a = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_MEM_IDX, 0); double val = 50.0; uintptr_t vbits; memcpy(&vbits, &val, sizeof(vbits)); xce_select_node_t *b = makeSelectNode(SEL_NUM, vbits, 0); TEST_ASSERT_NOT_NULL(a); TEST_ASSERT_NOT_NULL(b); xce_select_node_t *ab = makeSelectNode(SEL_GT, (uintptr_t)a, (uintptr_t)b); xce_select_node_t *ba = makeSelectNode(SEL_GT, (uintptr_t)b, (uintptr_t)a); TEST_ASSERT_NOT_NULL(ab); TEST_ASSERT_NOT_NULL(ba); /* GT is NOT commutative — must yield distinct interned pointers */ TEST_ASSERT_TRUE(ab != ba); sel_optimize_test_reset(); } /* ── 4c-2/3: Flatten pass tests ─────────────────────────────────────────*/ static void test_flatten_noop_left_assoc(void) { /* An already left-associative AND chain must not be re-ordered. * "(type==linux && gpu) && mem>50" is left-assoc; root stays SEL_AND with * left=SEL_AND after the flatten pass. */ sel_optimize_test_reset(); /* ensure clean state */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); int rc = parse_resreq_primary(&resreq, "(type==linux && gpu) && mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); xce_select_node_t *root = resreq.select->root; /* Already left-assoc: root = AND(AND(...), ...) */ TEST_ASSERT_EQUAL_INT(SEL_AND, root->type); TEST_ASSERT_EQUAL_INT(SEL_AND, root->u.binop.left->type); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_flatten_right_assoc_disabled(void) { /* In level==0, a right-associative AND chain must be flattened to * left-associative by sel_flatten_pass. * Build AND(type==linux, AND(gpu, mem>50)) manually, apply one pass. * Result must be AND(AND(type==linux, gpu), mem>50). */ sel_optimize_test_reset(); /* ensure clean state */ sel_optimize_set(0); /* Build leaves */ xce_select_node_t *type_node = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_TYPE_IDX, (uintptr_t)RES_TYPE_STRING); double val = 50.0; uintptr_t vbits; memcpy(&vbits, &val, sizeof(vbits)); xce_select_node_t *mem_node = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_MEM_IDX, (uintptr_t)RES_TYPE_NUMERIC); xce_select_node_t *num_node = makeSelectNode(SEL_NUM, vbits, 0); xce_select_node_t *gpu_node = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_GPU_IDX, (uintptr_t)RES_TYPE_BOOL); TEST_ASSERT_NOT_NULL(type_node); TEST_ASSERT_NOT_NULL(mem_node); TEST_ASSERT_NOT_NULL(num_node); TEST_ASSERT_NOT_NULL(gpu_node); /* inner = AND(gpu, mem>50) — right child */ xce_select_node_t *gt_node = makeSelectNode(SEL_GT, (uintptr_t)mem_node, (uintptr_t)num_node); xce_select_node_t *inner = makeSelectNode(SEL_AND, (uintptr_t)gpu_node, (uintptr_t)gt_node); /* root = AND(type==linux, AND(gpu, mem>50)) — right-assoc */ xce_select_node_t *root = makeSelectNode(SEL_AND, (uintptr_t)type_node, (uintptr_t)inner); TEST_ASSERT_NOT_NULL(gt_node); TEST_ASSERT_NOT_NULL(inner); TEST_ASSERT_NOT_NULL(root); /* Apply flatten pass */ int changed = 0; xce_select_node_t *new_root = sel_flatten_pass(root, &changed); TEST_ASSERT_NOT_NULL(new_root); TEST_ASSERT_EQUAL_INT(1, changed); /* Result must be left-assoc: AND(AND(A, B), C) */ TEST_ASSERT_EQUAL_INT(SEL_AND, new_root->type); TEST_ASSERT_EQUAL_INT(SEL_AND, new_root->u.binop.left->type); /* The right child must NOT be an AND (it should be a leaf or comparison) */ TEST_ASSERT_TRUE(new_root->u.binop.right->type != SEL_AND); /* Cleanup: new_root is a fresh tree in level==0 */ sel_free_tree(new_root); sel_optimize_test_reset(); } static void test_flatten_right_assoc_cse_same_ptr(void) { /* In CSE mode (level==1), sel_flatten_pass on AND(A, AND(B, C)) must * produce a left-associative result: the root is AND with a left child * that is also AND. * * Note on pointer equality: CSE commutativity sorting reorders operands * by pointer value, so AND(A, AND(B,C)) after flatten may differ in its * internal B/C ordering from a freshly-built AND(AND(A,B), C). We verify * the structural shape (left-assoc) rather than pointer identity. */ sel_optimize_test_reset(); /* ensure clean state */ sel_node_table_init(); sel_optimize_set(1); xce_select_node_t *a = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_TYPE_IDX, 0); xce_select_node_t *b = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_GPU_IDX, 0); xce_select_node_t *c = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_MEM_IDX, 0); TEST_ASSERT_NOT_NULL(a); TEST_ASSERT_NOT_NULL(b); TEST_ASSERT_NOT_NULL(c); /* Build right-assoc: AND(A, AND(B, C)) */ xce_select_node_t *bc_node = makeSelectNode(SEL_AND, (uintptr_t)b, (uintptr_t)c); xce_select_node_t *right_assoc = makeSelectNode(SEL_AND, (uintptr_t)a, (uintptr_t)bc_node); TEST_ASSERT_NOT_NULL(bc_node); TEST_ASSERT_NOT_NULL(right_assoc); /* In CSE mode, commutativity sort may place bc_node on either left or right * depending on pointer ordering. Verify that one child is the AND node. */ int bc_is_left = (right_assoc->u.binop.left == bc_node); int bc_is_right = (right_assoc->u.binop.right == bc_node); TEST_ASSERT_TRUE(bc_is_left || bc_is_right); /* Flatten: whichever child is AND must be re-associated */ int changed = 0; xce_select_node_t *flattened = sel_flatten_pass(right_assoc, &changed); TEST_ASSERT_NOT_NULL(flattened); /* After flatten: root is AND and one of its two direct children is AND * (the newly formed inner node), making it left-associative in structure. * Because CSE may re-sort again, we check that the result is structurally * an AND of (AND-child, leaf) regardless of which side each ends up on. */ TEST_ASSERT_EQUAL_INT(SEL_AND, flattened->type); int left_is_and = (flattened->u.binop.left->type == SEL_AND); int right_is_and = (flattened->u.binop.right->type == SEL_AND); /* Exactly one child should be an AND (the inner pair) */ TEST_ASSERT_TRUE(left_is_and || right_is_and); TEST_ASSERT_TRUE(changed == 1 || changed == 0); /* changed is set if re-assoc */ /* In CSE mode, re-building the same expression using the flattened tree's * own children must return the already-interned pointer. */ xce_select_node_t *and_child = left_is_and ? flattened->u.binop.left : flattened->u.binop.right; xce_select_node_t *leaf_child = left_is_and ? flattened->u.binop.right : flattened->u.binop.left; xce_select_node_t *inner_l = and_child->u.binop.left; xce_select_node_t *inner_r = and_child->u.binop.right; xce_select_node_t *rebuilt_inner = makeSelectNode(SEL_AND, (uintptr_t)inner_l, (uintptr_t)inner_r); xce_select_node_t *rebuilt_outer = makeSelectNode(SEL_AND, (uintptr_t)rebuilt_inner, (uintptr_t)leaf_child); TEST_ASSERT_NOT_NULL(rebuilt_inner); TEST_ASSERT_NOT_NULL(rebuilt_outer); /* CSE must return the already-interned pointer for the same expression */ TEST_ASSERT_EQUAL_PTR(and_child, rebuilt_inner); TEST_ASSERT_EQUAL_PTR(flattened, rebuilt_outer); sel_optimize_test_reset(); } static void test_flatten_deep_nested_disabled(void) { /* In level==0, build AND(A, AND(B, AND(C, D))) — 3 levels of right-nesting. * A do-while loop applying sel_flatten_pass must produce a fully * left-associative result: AND(AND(AND(A,B),C),D). */ sel_optimize_test_reset(); /* ensure clean state from any prior failure */ sel_optimize_set(0); xce_select_node_t *a = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_TYPE_IDX, (uintptr_t)RES_TYPE_STRING); xce_select_node_t *b = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_GPU_IDX, (uintptr_t)RES_TYPE_BOOL); xce_select_node_t *c = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_MEM_IDX, (uintptr_t)RES_TYPE_NUMERIC); xce_select_node_t *d = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_NCPUS_IDX, (uintptr_t)RES_TYPE_NUMERIC); TEST_ASSERT_NOT_NULL(a); TEST_ASSERT_NOT_NULL(b); TEST_ASSERT_NOT_NULL(c); TEST_ASSERT_NOT_NULL(d); /* AND(C, D) */ xce_select_node_t *cd = makeSelectNode(SEL_AND, (uintptr_t)c, (uintptr_t)d); /* AND(B, AND(C, D)) */ xce_select_node_t *bcd = makeSelectNode(SEL_AND, (uintptr_t)b, (uintptr_t)cd); /* AND(A, AND(B, AND(C, D))) */ xce_select_node_t *root = makeSelectNode(SEL_AND, (uintptr_t)a, (uintptr_t)bcd); TEST_ASSERT_NOT_NULL(cd); TEST_ASSERT_NOT_NULL(bcd); TEST_ASSERT_NOT_NULL(root); /* Apply flatten in a loop until no more changes */ int flat_changed; do { flat_changed = 0; root = sel_flatten_pass(root, &flat_changed); TEST_ASSERT_NOT_NULL(root); } while (flat_changed); /* Verify fully left-assoc: AND(AND(AND(A,B),C),D) */ TEST_ASSERT_EQUAL_INT(SEL_AND, root->type); xce_select_node_t *lv1 = root->u.binop.left; TEST_ASSERT_EQUAL_INT(SEL_AND, lv1->type); xce_select_node_t *lv2 = lv1->u.binop.left; TEST_ASSERT_EQUAL_INT(SEL_AND, lv2->type); /* lv2->left must be a RESNAME leaf (A) */ TEST_ASSERT_EQUAL_INT(SEL_RESNAME, lv2->u.binop.left->type); sel_free_tree(root); sel_optimize_test_reset(); } static void test_flatten_or_right_assoc_disabled(void) { /* Same as test_flatten_right_assoc_disabled but with OR. */ sel_optimize_test_reset(); /* ensure clean state */ sel_optimize_set(0); xce_select_node_t *a = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_TYPE_IDX, (uintptr_t)RES_TYPE_STRING); xce_select_node_t *b = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_GPU_IDX, (uintptr_t)RES_TYPE_BOOL); xce_select_node_t *c = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_MEM_IDX, (uintptr_t)RES_TYPE_NUMERIC); TEST_ASSERT_NOT_NULL(a); TEST_ASSERT_NOT_NULL(b); TEST_ASSERT_NOT_NULL(c); /* OR(A, OR(B, C)) — right-assoc */ xce_select_node_t *inner = makeSelectNode(SEL_OR, (uintptr_t)b, (uintptr_t)c); xce_select_node_t *root = makeSelectNode(SEL_OR, (uintptr_t)a, (uintptr_t)inner); TEST_ASSERT_NOT_NULL(inner); TEST_ASSERT_NOT_NULL(root); int changed = 0; xce_select_node_t *new_root = sel_flatten_pass(root, &changed); TEST_ASSERT_NOT_NULL(new_root); TEST_ASSERT_EQUAL_INT(1, changed); /* Result: OR(OR(A, B), C) — left-assoc */ TEST_ASSERT_EQUAL_INT(SEL_OR, new_root->type); TEST_ASSERT_EQUAL_INT(SEL_OR, new_root->u.binop.left->type); TEST_ASSERT_TRUE(new_root->u.binop.right->type != SEL_OR); sel_free_tree(new_root); sel_optimize_test_reset(); } static void test_flatten_mixed_ops_no_change(void) { /* AND(A, OR(B, C)) — the inner node is OR, outer is AND. * Different operators: flatten must NOT re-associate. * Root stays AND with right=OR, and changed must be 0. */ sel_optimize_test_reset(); /* ensure clean state */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); /* "type==linux && (mem>50 || gpu)" parses as AND(EQ, OR(GT, RESNAME)) * which has different operators at the AND and OR levels — no flattening. */ int rc = parse_resreq_primary(&resreq, "type==linux && (mem>50 || gpu)", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(resreq.select); TEST_ASSERT_NOT_NULL(resreq.select->root); xce_select_node_t *root = resreq.select->root; /* Root must be AND */ TEST_ASSERT_EQUAL_INT(SEL_AND, root->type); /* Right child must be OR — not re-associated by flatten */ TEST_ASSERT_EQUAL_INT(SEL_OR, root->u.binop.right->type); /* Apply flatten — must be a no-op for mixed operators */ int changed = 0; xce_select_node_t *new_root = sel_flatten_pass(root, &changed); TEST_ASSERT_NOT_NULL(new_root); /* No re-association occurred */ TEST_ASSERT_EQUAL_INT(0, changed); TEST_ASSERT_EQUAL_INT(SEL_AND, new_root->type); TEST_ASSERT_EQUAL_INT(SEL_OR, new_root->u.binop.right->type); /* new_root == root in the no-change case (same pointer) */ TEST_ASSERT_EQUAL_PTR(root, new_root); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } /* ── 4d: const-fold transform tests ────────────────────────────────────────── * * All tests call sel_const_fold_pass() directly on manually-built AST nodes. * The stub returns the input node unchanged, so every type assertion FAILS — * that is the expected TDD RED state. * * Memory contract (level==0): * After the real implementation: the pass frees consumed subtrees and * returns a new (or surviving child) node. sel_free_tree(result) is * always correct — it frees exactly what was returned. * Stub case: result == input; sel_free_tree frees the whole tree. * * Memory contract (level==1, CSE): * Do NOT call sel_free_tree; call sel_optimize_test_reset() to tear down * the intern table and free all interned nodes. */ /* 4d-1: EQ(resname, any) → SEL_TRUE */ static void test_constfold_eq_any_gives_true(void) { sel_optimize_set(0); xce_select_node_t *resname = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_TYPE_IDX, (uintptr_t)RES_TYPE_STRING); xce_select_node_t *any = makeSelectNode(SEL_ANY, 0, 0); xce_select_node_t *eq = makeSelectNode(SEL_EQ, (uintptr_t)resname, (uintptr_t)any); TEST_ASSERT_NOT_NULL(resname); TEST_ASSERT_NOT_NULL(any); TEST_ASSERT_NOT_NULL(eq); xce_select_node_t *result = sel_const_fold_pass(eq); TEST_ASSERT_NOT_NULL(result); /* Phase 4d: EQ(resname, any) must fold to SEL_TRUE */ TEST_ASSERT_EQUAL_INT(SEL_TRUE, result->type); sel_free_tree(result); sel_optimize_test_reset(); } /* 4d-1: NE(resname, any) → SEL_FALSE */ static void test_constfold_ne_any_gives_false(void) { sel_optimize_set(0); xce_select_node_t *resname = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_TYPE_IDX, (uintptr_t)RES_TYPE_STRING); xce_select_node_t *any = makeSelectNode(SEL_ANY, 0, 0); xce_select_node_t *ne = makeSelectNode(SEL_NE, (uintptr_t)resname, (uintptr_t)any); TEST_ASSERT_NOT_NULL(resname); TEST_ASSERT_NOT_NULL(any); TEST_ASSERT_NOT_NULL(ne); xce_select_node_t *result = sel_const_fold_pass(ne); TEST_ASSERT_NOT_NULL(result); /* Phase 4d: NE(resname, any) must fold to SEL_FALSE */ TEST_ASSERT_EQUAL_INT(SEL_FALSE, result->type); sel_free_tree(result); sel_optimize_test_reset(); } /* 4d-2: AND(SEL_TRUE, X) → X (identity law) */ static void test_constfold_true_and_x_gives_x(void) { sel_optimize_set(0); xce_select_node_t *t_node = makeSelectNode(SEL_TRUE, 0, 0); xce_select_node_t *x_node = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_GPU_IDX, (uintptr_t)RES_TYPE_BOOL); xce_select_node_t *and_node = makeSelectNode(SEL_AND, (uintptr_t)t_node, (uintptr_t)x_node); TEST_ASSERT_NOT_NULL(t_node); TEST_ASSERT_NOT_NULL(x_node); TEST_ASSERT_NOT_NULL(and_node); xce_select_node_t *result = sel_const_fold_pass(and_node); TEST_ASSERT_NOT_NULL(result); /* Phase 4d: TRUE && X must reduce to X (SEL_RESNAME) */ TEST_ASSERT_EQUAL_INT(SEL_RESNAME, result->type); sel_free_tree(result); sel_optimize_test_reset(); } /* 4d-2: OR(SEL_FALSE, X) → X (identity law) */ static void test_constfold_false_or_x_gives_x(void) { sel_optimize_set(0); xce_select_node_t *f_node = makeSelectNode(SEL_FALSE, 0, 0); xce_select_node_t *x_node = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_GPU_IDX, (uintptr_t)RES_TYPE_BOOL); xce_select_node_t *or_node = makeSelectNode(SEL_OR, (uintptr_t)f_node, (uintptr_t)x_node); TEST_ASSERT_NOT_NULL(f_node); TEST_ASSERT_NOT_NULL(x_node); TEST_ASSERT_NOT_NULL(or_node); xce_select_node_t *result = sel_const_fold_pass(or_node); TEST_ASSERT_NOT_NULL(result); /* Phase 4d: FALSE || X must reduce to X (SEL_RESNAME) */ TEST_ASSERT_EQUAL_INT(SEL_RESNAME, result->type); sel_free_tree(result); sel_optimize_test_reset(); } /* 4d-3: AND(SEL_FALSE, X) → SEL_FALSE (annihilation law) */ static void test_constfold_false_and_x_gives_false(void) { sel_optimize_set(0); xce_select_node_t *f_node = makeSelectNode(SEL_FALSE, 0, 0); xce_select_node_t *x_node = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_GPU_IDX, (uintptr_t)RES_TYPE_BOOL); xce_select_node_t *and_node = makeSelectNode(SEL_AND, (uintptr_t)f_node, (uintptr_t)x_node); TEST_ASSERT_NOT_NULL(f_node); TEST_ASSERT_NOT_NULL(x_node); TEST_ASSERT_NOT_NULL(and_node); xce_select_node_t *result = sel_const_fold_pass(and_node); TEST_ASSERT_NOT_NULL(result); /* Phase 4d: FALSE && X must annihilate to SEL_FALSE */ TEST_ASSERT_EQUAL_INT(SEL_FALSE, result->type); sel_free_tree(result); sel_optimize_test_reset(); } /* 4d-3: OR(SEL_TRUE, X) → SEL_TRUE (annihilation law) */ static void test_constfold_true_or_x_gives_true(void) { sel_optimize_set(0); xce_select_node_t *t_node = makeSelectNode(SEL_TRUE, 0, 0); xce_select_node_t *x_node = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_GPU_IDX, (uintptr_t)RES_TYPE_BOOL); xce_select_node_t *or_node = makeSelectNode(SEL_OR, (uintptr_t)t_node, (uintptr_t)x_node); TEST_ASSERT_NOT_NULL(t_node); TEST_ASSERT_NOT_NULL(x_node); TEST_ASSERT_NOT_NULL(or_node); xce_select_node_t *result = sel_const_fold_pass(or_node); TEST_ASSERT_NOT_NULL(result); /* Phase 4d: TRUE || X must annihilate to SEL_TRUE */ TEST_ASSERT_EQUAL_INT(SEL_TRUE, result->type); sel_free_tree(result); sel_optimize_test_reset(); } /* 4d-4: NOT(SEL_TRUE) → SEL_FALSE (complement law) */ static void test_constfold_not_true_gives_false(void) { sel_optimize_set(0); xce_select_node_t *t_node = makeSelectNode(SEL_TRUE, 0, 0); xce_select_node_t *not_node = makeSelectNode(SEL_NOT, (uintptr_t)t_node, 0); TEST_ASSERT_NOT_NULL(t_node); TEST_ASSERT_NOT_NULL(not_node); xce_select_node_t *result = sel_const_fold_pass(not_node); TEST_ASSERT_NOT_NULL(result); /* Phase 4d: !TRUE must fold to SEL_FALSE */ TEST_ASSERT_EQUAL_INT(SEL_FALSE, result->type); sel_free_tree(result); sel_optimize_test_reset(); } /* 4d-4: NOT(SEL_FALSE) → SEL_TRUE (complement law) */ static void test_constfold_not_false_gives_true(void) { sel_optimize_set(0); xce_select_node_t *f_node = makeSelectNode(SEL_FALSE, 0, 0); xce_select_node_t *not_node = makeSelectNode(SEL_NOT, (uintptr_t)f_node, 0); TEST_ASSERT_NOT_NULL(f_node); TEST_ASSERT_NOT_NULL(not_node); xce_select_node_t *result = sel_const_fold_pass(not_node); TEST_ASSERT_NOT_NULL(result); /* Phase 4d: !FALSE must fold to SEL_TRUE */ TEST_ASSERT_EQUAL_INT(SEL_TRUE, result->type); sel_free_tree(result); sel_optimize_test_reset(); } /* 4d-1 (CSE): folding the same interned EQ(resname, any) must produce the * same interned SEL_TRUE singleton — const-fold in CSE mode must return an * interned pointer, not a fresh allocation. */ static void test_constfold_cse_eq_any_singleton(void) { sel_optimize_test_reset(); sel_node_table_init(); sel_optimize_set(1); xce_select_node_t *resname = makeSelectNode(SEL_RESNAME, (uintptr_t)TEST_RES_TYPE_IDX, (uintptr_t)RES_TYPE_STRING); xce_select_node_t *any = makeSelectNode(SEL_ANY, 0, 0); xce_select_node_t *eq1 = makeSelectNode(SEL_EQ, (uintptr_t)resname, (uintptr_t)any); TEST_ASSERT_NOT_NULL(resname); TEST_ASSERT_NOT_NULL(any); TEST_ASSERT_NOT_NULL(eq1); /* CSE pre-condition: same args → same interned pointer */ xce_select_node_t *eq2 = makeSelectNode(SEL_EQ, (uintptr_t)resname, (uintptr_t)any); TEST_ASSERT_EQUAL_PTR(eq1, eq2); /* Phase 4d: both folds must yield SEL_TRUE and the same singleton */ xce_select_node_t *fold1 = sel_const_fold_pass(eq1); xce_select_node_t *fold2 = sel_const_fold_pass(eq2); TEST_ASSERT_NOT_NULL(fold1); TEST_ASSERT_NOT_NULL(fold2); TEST_ASSERT_EQUAL_INT(SEL_TRUE, fold1->type); TEST_ASSERT_EQUAL_INT(SEL_TRUE, fold2->type); TEST_ASSERT_EQUAL_PTR(fold1, fold2); sel_optimize_test_reset(); } /* ── Phase 4e: Evaluation with Memoization ──────────────────────────────── * * Tests for eval_select_simple (level==0, no ctx) and eval_select (CSE mode, * memoized via select_eval_ctx_t). These tests verify actual evaluation * results, not just AST structure. */ /* Helper: fill a stack-allocated host_eval_ctx_t */ static void init_test_host(host_eval_ctx_t *host, double *numvals, const char **strvals, int num_res) { host->num_res = num_res; host->numvals = numvals; host->strvals = strvals; } /* Five-resource host arrays matching test_resmgr_fixture: * idx 0: type (STRING) * idx 1: mem (NUMERIC) * idx 2: gpu (BOOL) * idx 3: ncpus (NUMERIC) * idx 4: arch (STRING) */ static void test_p4e_simple_null_root(void) { /* eval_select_simple(NULL, host) → 1 (vacuously true) */ double numvals[5] = {0}; const char *strvals[5] = {NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(NULL, &host)); } static void test_p4e_simple_bool_true(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 0.0, 1.0, 0.0, 0.0}; /* gpu=1 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "gpu", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_bool_false(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 0.0, 0.0, 0.0, 0.0}; /* gpu=0 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "gpu", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(0, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_numeric_gt_true(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 100.0, 0.0, 0.0, 0.0}; /* mem=100 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_numeric_gt_false(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 30.0, 0.0, 0.0, 0.0}; /* mem=30 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(0, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_numeric_lt_true(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 100.0, 0.0, 0.0, 0.0}; /* mem=100 < 200 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "mem<200", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_numeric_ge_boundary(void) { /* mem>=100 with mem=100: boundary case must be true */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 100.0, 0.0, 0.0, 0.0}; const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "mem>=100", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_numeric_le_boundary(void) { /* mem<=100 with mem=100: boundary case must be true */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 100.0, 0.0, 0.0, 0.0}; const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "mem<=100", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_numeric_eq_true(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 100.0, 0.0, 0.0, 0.0}; const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "mem==100", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_numeric_eq_false(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 50.0, 0.0, 0.0, 0.0}; const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "mem==100", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(0, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_numeric_ne_true(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 100.0, 0.0, 0.0, 0.0}; const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "mem!=50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_string_eq_true(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 0.0, 0.0, 0.0, 0.0}; const char *strvals[5] = {"linux", NULL, NULL, NULL, NULL}; /* type="linux" */ host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "type==linux", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_string_eq_false(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 0.0, 0.0, 0.0, 0.0}; const char *strvals[5] = {"windows", NULL, NULL, NULL, NULL}; /* type≠linux */ host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "type==linux", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(0, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_not_inverts_true(void) { /* !gpu with gpu=0.0 → NOT false = 1 */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 0.0, 0.0, 0.0, 0.0}; /* gpu=0 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "!gpu", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_not_inverts_false(void) { /* !gpu with gpu=1.0 → NOT true = 0 */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 0.0, 1.0, 0.0, 0.0}; /* gpu=1 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "!gpu", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(0, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_and_both_true(void) { char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 100.0, 1.0, 0.0, 0.0}; /* mem=100, gpu=1 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "gpu && mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_and_short_circuit_false(void) { /* gpu=0: AND short-circuits without evaluating mem>50 */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 100.0, 0.0, 0.0, 0.0}; /* gpu=0 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "gpu && mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(0, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_or_short_circuit_true(void) { /* gpu=1: OR short-circuits without evaluating mem>50 (which would be false) */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_optimize_set(0); double numvals[5] = {0.0, 30.0, 1.0, 0.0, 0.0}; /* gpu=1, mem=30 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "gpu || mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(resreq.select->root, &host)); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_simple_sel_true_node(void) { /* A SEL_TRUE leaf always evaluates to 1 */ sel_optimize_set(0); xce_select_node_t *node = makeSelectNode(SEL_TRUE, 0, 0); TEST_ASSERT_NOT_NULL(node); double numvals[1] = {0}; const char *strvals[1] = {NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 1); TEST_ASSERT_EQUAL_INT(1, eval_select_simple(node, &host)); sel_free_tree(node); sel_optimize_test_reset(); } static void test_p4e_simple_sel_false_node(void) { /* A SEL_FALSE leaf always evaluates to 0 */ sel_optimize_set(0); xce_select_node_t *node = makeSelectNode(SEL_FALSE, 0, 0); TEST_ASSERT_NOT_NULL(node); double numvals[1] = {0}; const char *strvals[1] = {NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 1); TEST_ASSERT_EQUAL_INT(0, eval_select_simple(node, &host)); sel_free_tree(node); sel_optimize_test_reset(); } /* ── Phase 4e: ctx lifecycle and memoized eval ─────────────────────────── */ static void test_p4e_ctx_init_allocs_non_null(void) { sel_optimize_set(1); select_eval_ctx_t *ctx = select_eval_ctx_init(64); TEST_ASSERT_NOT_NULL(ctx); TEST_ASSERT_NOT_NULL(ctx->bool_bits); TEST_ASSERT_NOT_NULL(ctx->num_vals); TEST_ASSERT_NOT_NULL(ctx->str_vals); select_eval_ctx_fini(ctx); sel_optimize_test_reset(); } static void test_p4e_ctx_node_count_matches(void) { sel_optimize_set(1); select_eval_ctx_t *ctx = select_eval_ctx_init(100); TEST_ASSERT_NOT_NULL(ctx); TEST_ASSERT_EQUAL_INT(100, (int)ctx->node_count); TEST_ASSERT_EQUAL_INT(100, (int)ctx->bool_bits_cap); TEST_ASSERT_EQUAL_INT(100, (int)ctx->num_vals_cap); TEST_ASSERT_EQUAL_INT(100, (int)ctx->str_vals_cap); select_eval_ctx_fini(ctx); sel_optimize_test_reset(); } static void test_p4e_ctx_reset_sets_host(void) { sel_optimize_set(1); select_eval_ctx_t *ctx = select_eval_ctx_init(64); TEST_ASSERT_NOT_NULL(ctx); double numvals[5] = {0}; const char *strvals[5] = {NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); select_eval_ctx_reset(ctx, &host); TEST_ASSERT_EQUAL_PTR(&host, ctx->host); select_eval_ctx_fini(ctx); sel_optimize_test_reset(); } static void test_p4e_memoized_null_root(void) { /* eval_select(NULL, ctx) → 1 regardless of ctx state */ sel_optimize_set(1); select_eval_ctx_t *ctx = select_eval_ctx_init(64); TEST_ASSERT_NOT_NULL(ctx); double numvals[5] = {0}; const char *strvals[5] = {NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); select_eval_ctx_reset(ctx, &host); TEST_ASSERT_EQUAL_INT(1, eval_select(NULL, ctx)); select_eval_ctx_fini(ctx); sel_optimize_test_reset(); } static void test_p4e_memoized_numeric_gt_true(void) { /* eval_select (memoized) gives same correct result as eval_select_simple */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_node_table_init(); sel_optimize_set(1); double numvals[5] = {0.0, 100.0, 0.0, 0.0, 0.0}; /* mem=100 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); select_eval_ctx_t *ctx = select_eval_ctx_init(256); TEST_ASSERT_NOT_NULL(ctx); select_eval_ctx_reset(ctx, &host); TEST_ASSERT_EQUAL_INT(1, eval_select(resreq.select->root, ctx)); select_eval_ctx_fini(ctx); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } static void test_p4e_memoized_second_call_same(void) { /* Calling eval_select twice with the same ctx must return the same value. * The second call hits the bool_bits cache instead of re-evaluating. */ char * errmsg = NULL; xce_primary_resreq_t resreq; var resmgr = make_test_resmgr(); sel_node_table_init(); sel_optimize_set(1); double numvals[5] = {0.0, 100.0, 1.0, 0.0, 0.0}; /* mem=100, gpu=1 */ const char *strvals[5] = {NULL, NULL, NULL, NULL, NULL}; host_eval_ctx_t host; init_test_host(&host, numvals, strvals, 5); int rc = parse_resreq_primary(&resreq, "gpu && mem>50", resmgr, &errmsg); TEST_ASSERT_TRUE(rc >= 0); select_eval_ctx_t *ctx = select_eval_ctx_init(256); TEST_ASSERT_NOT_NULL(ctx); select_eval_ctx_reset(ctx, &host); int result1 = eval_select(resreq.select->root, ctx); int result2 = eval_select(resreq.select->root, ctx); TEST_ASSERT_EQUAL_INT(1, result1); TEST_ASSERT_EQUAL_INT(result1, result2); select_eval_ctx_fini(ctx); kill_primary_resreq(&resreq); FREEUP(errmsg); free_test_resmgr(resmgr); sel_optimize_test_reset(); } /* ── Phase 6 prep: gc_clear_marks / gc_mark_select_tree exported ──────────*/ static void test_gc_clear_marks_exported(void) { sel_optimize_test_reset(); sel_optimize_set(1); sel_node_table_init(); xce_select_node_t* n1 = makeSelectNode(SEL_NUM, (uintptr_t)1.0, 0); xce_select_node_t* n2 = makeSelectNode(SEL_NUM, (uintptr_t)2.0, 0); TEST_ASSERT_NOT_NULL(n1); TEST_ASSERT_NOT_NULL(n2); /* manually mark n1 */ n1->gc_mark = 1; /* gc_clear_marks must reset all gc_mark bits */ gc_clear_marks(); TEST_ASSERT_EQUAL_INT(0, n1->gc_mark); /* gc_mark_select_tree must set gc_mark on n2 */ gc_mark_select_tree(n2); TEST_ASSERT_EQUAL_INT(1, n2->gc_mark); /* sweep preserves n2 (marked), removes n1 (unmarked) */ gc_sweep_select_nodes(NULL, 0); TEST_ASSERT_EQUAL_INT(1, n2->gc_mark); sel_node_table_fini(); sel_optimize_test_reset(); } /* ── Top-level runner ─────────────────────────────────────────────────────*/ void test_select(void) { printf("\n>> select expression AST / CSE test\n\n"); xstring_table_init(139); RUN_TEST(test_optimize_set_once); RUN_TEST(test_optimize_reset_restores_unset); RUN_TEST(test_select_parse_valid_expr); RUN_TEST(test_select_parse_unknown_resource); RUN_TEST(test_select_parse_type_mismatch); RUN_TEST(test_select_input_len_overflow); RUN_TEST(test_select_depth_overflow); RUN_TEST(test_select_cse_commutative_same_ptr); RUN_TEST(test_select_cse_same_args_same_ptr); RUN_TEST(test_eval_numeric_gt); RUN_TEST(test_eval_numeric_lt); RUN_TEST(test_eval_numeric_eq); RUN_TEST(test_eval_string_eq); RUN_TEST(test_eval_bool_standalone); RUN_TEST(test_eval_not); RUN_TEST(test_eval_and_shortcircuit); RUN_TEST(test_eval_or_shortcircuit); RUN_TEST(test_ordered_cmp_nonnum_string_resource_fails); RUN_TEST(test_ordered_cmp_nonnum_bool_resource_fails); RUN_TEST(test_ordered_cmp_string_rhs_fails); RUN_TEST(test_ordered_cmp_numeric_resource_succeeds); /* ── Phase 4b: EQ type-validation tests ─────────────────────────────── */ RUN_TEST(test_4b_num_str_mismatch_eq_fails); RUN_TEST(test_4b_str_num_coercion_eq_succeeds); RUN_TEST(test_4b_true_resname_migration_error); RUN_TEST(test_4b_false_resname_migration_error); RUN_TEST(test_4b_bool_num_eq_accepted); RUN_TEST(test_4b_num_str_mismatch_ne_fails); RUN_TEST(test_4b_bool_str_ne_fails); RUN_TEST(test_4b_str_num_coercion_ne_succeeds); RUN_TEST(test_gc_sweep_unreachable_freed); RUN_TEST(test_gc_sweep_reachable_retained); RUN_TEST(test_gc_reintern_after_sweep); RUN_TEST(test_gc_predicate_idle); RUN_TEST(test_gc_predicate_session_active_blocks); RUN_TEST(test_gc_predicate_eval_in_progress_blocks); RUN_TEST(test_gc_predicate_staging_not_complete_blocks); /* ── Phase 4f: recompact tests ───────────────────────────────────── */ RUN_TEST(test_recompact_ids_dense); RUN_TEST(test_recompact_resets_counter); RUN_TEST(test_recompact_empty_table); /* ── Phase 4c tests ───────────────────────────────────────────────── */ RUN_TEST(test_cse_gt_not_commutative); RUN_TEST(test_flatten_noop_left_assoc); RUN_TEST(test_flatten_right_assoc_disabled); RUN_TEST(test_flatten_right_assoc_cse_same_ptr); RUN_TEST(test_flatten_deep_nested_disabled); RUN_TEST(test_flatten_or_right_assoc_disabled); RUN_TEST(test_flatten_mixed_ops_no_change); /* ── Phase 4d tests ───────────────────────────────────────────────── */ RUN_TEST(test_constfold_eq_any_gives_true); RUN_TEST(test_constfold_ne_any_gives_false); RUN_TEST(test_constfold_true_and_x_gives_x); RUN_TEST(test_constfold_false_or_x_gives_x); RUN_TEST(test_constfold_false_and_x_gives_false); RUN_TEST(test_constfold_true_or_x_gives_true); RUN_TEST(test_constfold_not_true_gives_false); RUN_TEST(test_constfold_not_false_gives_true); RUN_TEST(test_constfold_cse_eq_any_singleton); /* ── Phase 4e tests ───────────────────────────────────────────────── */ RUN_TEST(test_p4e_simple_null_root); RUN_TEST(test_p4e_simple_bool_true); RUN_TEST(test_p4e_simple_bool_false); RUN_TEST(test_p4e_simple_numeric_gt_true); RUN_TEST(test_p4e_simple_numeric_gt_false); RUN_TEST(test_p4e_simple_numeric_lt_true); RUN_TEST(test_p4e_simple_numeric_ge_boundary); RUN_TEST(test_p4e_simple_numeric_le_boundary); RUN_TEST(test_p4e_simple_numeric_eq_true); RUN_TEST(test_p4e_simple_numeric_eq_false); RUN_TEST(test_p4e_simple_numeric_ne_true); RUN_TEST(test_p4e_simple_string_eq_true); RUN_TEST(test_p4e_simple_string_eq_false); RUN_TEST(test_p4e_simple_not_inverts_true); RUN_TEST(test_p4e_simple_not_inverts_false); RUN_TEST(test_p4e_simple_and_both_true); RUN_TEST(test_p4e_simple_and_short_circuit_false); RUN_TEST(test_p4e_simple_or_short_circuit_true); RUN_TEST(test_p4e_simple_sel_true_node); RUN_TEST(test_p4e_simple_sel_false_node); RUN_TEST(test_p4e_ctx_init_allocs_non_null); RUN_TEST(test_p4e_ctx_node_count_matches); RUN_TEST(test_p4e_ctx_reset_sets_host); RUN_TEST(test_p4e_memoized_null_root); RUN_TEST(test_p4e_memoized_numeric_gt_true); RUN_TEST(test_p4e_memoized_second_call_same); /* ── Phase 6 prep: exported GC mark primitives ───────────────────── */ RUN_TEST(test_gc_clear_marks_exported); xstring_table_fini(); }