sel_key_cmp_fn compared keys with memcmp on the whole sel_node_key_t struct, including the 4-byte padding between type and op1. The stack- allocated search key in gc_sweep_select_nodes can have different padding bytes than the heap-allocated copy stored by key_add, causing key_del to miss the entry and leave a dangling pointer in the CSE intern table. Replace memcmp with field-by-field comparison on type, op1, op2. Also fix GC sweep tests that asserted pointer inequality after re-intern: calloc may lawfully reuse the freed address, so pointer inequality does not reliably indicate a successful sweep. Track the swept node's node_id before freeing and assert the re-interned node gets a fresh id. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2452 lines
81 KiB
C
2452 lines
81 KiB
C
/* 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 <pthread.h>
|
||
#include <stdatomic.h>
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#include <unity/unity.h>
|
||
|
||
#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_valid_any(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==any", 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 fresh
|
||
* node — the old node was freed and removed from the table.
|
||
*
|
||
* We track node_id rather than pointer equality: calloc may lawfully
|
||
* reuse the freed address, so pointer-inequality is not a reliable
|
||
* indicator that GC swept correctly. */
|
||
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);
|
||
uint32_t node1_id = node1->node_id;
|
||
|
||
/* 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.
|
||
* node_id is monotonically assigned — a fresh intern produces a
|
||
* different id even if the heap address happens to be reused. */
|
||
xce_select_node_t *node2 = makeSelectNode(SEL_NUM, bits, 0);
|
||
TEST_ASSERT_NOT_NULL(node2);
|
||
TEST_ASSERT_NOT_EQUAL(node1_id, node2->node_id);
|
||
|
||
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();
|
||
|
||
double d1 = 1.0, d2 = 2.0;
|
||
uintptr_t b1, b2;
|
||
memcpy(&b1, &d1, sizeof(b1));
|
||
memcpy(&b2, &d2, sizeof(b2));
|
||
xce_select_node_t* n1 = makeSelectNode(SEL_NUM, b1, 0);
|
||
xce_select_node_t* n2 = makeSelectNode(SEL_NUM, b2, 0);
|
||
TEST_ASSERT_NOT_NULL(n1);
|
||
TEST_ASSERT_NOT_NULL(n2);
|
||
uint32_t n1_id = n1->node_id;
|
||
|
||
/* 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);
|
||
|
||
/* n1 was swept — re-interning must produce a fresh node_id.
|
||
* Pointer inequality would be unreliable because calloc may
|
||
* lawfully reuse the freed address. */
|
||
xce_select_node_t* n1_again = makeSelectNode(SEL_NUM, b1, 0);
|
||
TEST_ASSERT_NOT_NULL(n1_again);
|
||
TEST_ASSERT_NOT_EQUAL(n1_id, n1_again->node_id);
|
||
|
||
sel_node_table_fini();
|
||
sel_optimize_test_reset();
|
||
}
|
||
|
||
/* ── Task 8: sel_alloc_thread_id ownership check ─────────────────────────*/
|
||
|
||
static void
|
||
test_sel_register_alloc_thread(void)
|
||
{
|
||
sel_optimize_test_reset(); /* resets sel_alloc_thread_id to 0 */
|
||
sel_optimize_set(1);
|
||
sel_node_table_init();
|
||
|
||
/* Should not crash when unregistered (id=0 means skip check) */
|
||
xce_select_node_t* n = makeSelectNode(SEL_TRUE, 0, 0);
|
||
TEST_ASSERT_NOT_NULL(n);
|
||
|
||
/* Register current thread */
|
||
sel_register_alloc_thread();
|
||
|
||
/* makeSelectNode from same thread must still work */
|
||
xce_select_node_t* n2 = makeSelectNode(SEL_FALSE, 0, 0);
|
||
TEST_ASSERT_NOT_NULL(n2);
|
||
|
||
gc_clear_marks();
|
||
gc_sweep_select_nodes(NULL, 0);
|
||
sel_node_table_fini();
|
||
sel_optimize_test_reset();
|
||
}
|
||
|
||
/* ── Task 8: wrong-thread ownership guard test ───────────────────────────*/
|
||
|
||
/* shared state for the wrong-thread sweep test */
|
||
static int g_wrong_thread_sweep_ran = 0;
|
||
|
||
static void* wrong_thread_sweep(void* arg)
|
||
{
|
||
(void)arg;
|
||
/* calling gc_sweep from the wrong thread must hit the ownership guard
|
||
* and return without sweeping — nodes must remain intact */
|
||
gc_sweep_select_nodes(NULL, 0);
|
||
g_wrong_thread_sweep_ran = 1;
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
test_sel_wrong_thread_guard(void)
|
||
{
|
||
sel_optimize_test_reset();
|
||
sel_optimize_set(1);
|
||
sel_node_table_init();
|
||
|
||
/* Register THIS thread as the allocator */
|
||
sel_register_alloc_thread();
|
||
|
||
/* Allocate a node that should survive the wrong-thread sweep */
|
||
xce_select_node_t* n = makeSelectNode(SEL_TRUE, 0, 0);
|
||
TEST_ASSERT_NOT_NULL(n);
|
||
|
||
/* Spawn a different thread to call gc_sweep — guard must block it */
|
||
pthread_t tid;
|
||
g_wrong_thread_sweep_ran = 0;
|
||
int rc = pthread_create(&tid, NULL, wrong_thread_sweep, NULL);
|
||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||
pthread_join(tid, NULL);
|
||
TEST_ASSERT_EQUAL_INT(1, g_wrong_thread_sweep_ran);
|
||
|
||
/* Node must still exist (wrong-thread sweep was blocked) */
|
||
/* GC from correct thread: mark then sweep normally */
|
||
gc_clear_marks();
|
||
gc_sweep_select_nodes(NULL, 0);
|
||
|
||
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_valid_any);
|
||
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);
|
||
|
||
/* ── Task 8: sel_alloc_thread_id ownership check ─────────────────── */
|
||
RUN_TEST(test_sel_register_alloc_thread);
|
||
RUN_TEST(test_sel_wrong_thread_guard);
|
||
|
||
xstring_table_fini();
|
||
}
|