Files
codebase-memory-mcp/tests/test_gitignore_checked.c
Martin Vogel a245618a64 wip(ci): checkpoint hybrid test-impact implementation
Preserve the current selector, frozen-input inventory, result codec,
coverage collection, runner filtering, and isolated Windows venue work.
Component-level regression proofs are recorded in the PR handover.

The live classified provider, public execution integration, artifact
admission, fresh oracle/shadow evidence, and full platform gates remain
incomplete. This checkpoint does not enable or claim verified narrowing.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-03 23:55:18 +02:00

953 lines
43 KiB
C

/* Independent tests for the frozen exact-byte checked-ignore API.
* No loader, global hooks, allocation pressure, timing races or admission claims. */
#include "test_framework.h"
#include "discover/discover.h"
#include "discover/gitignore_checked.h"
#include "foundation/arena.h"
#include "foundation/platform.h"
#include <limits.h>
#include <stdint.h>
/* Existing legacy symbol; discover.h currently declares the bool wrapper only. */
extern int cbm_gitignore_match_result(const cbm_gitignore_t *, const char *, bool);
#define GIC_CHECK(expression) \
do { \
if (!(expression)) { \
fprintf(stderr, "checked-ignore assertion %s:%d: %s\n", __FILE__, __LINE__, \
#expression); \
goto done; \
} \
} while (0)
typedef struct {
CBMArena input;
cbm_ignore_checked_t *owner[3];
} gic_fixture;
static void gic_init(gic_fixture *f) {
memset(f, 0, sizeof(*f));
cbm_arena_init(&f->input);
}
static int gic_finish(gic_fixture *f, int result) {
for (size_t i = 0; i < 3; i++)
cbm_ignore_checked_free(f->owner[i]);
cbm_arena_destroy(&f->input);
return result;
}
static cbm_ignore_checked_limits_t gic_limits(void) {
return (cbm_ignore_checked_limits_t){.max_bytes = 4 * 1024 * 1024,
.max_work = 100000000,
.max_patterns = 1024,
.max_arena_bytes = 4 * 1024 * 1024,
.max_depth = CBM_IGNORE_CHECKED_DEPTH_MAX};
}
static cbm_ignore_checked_control_t gic_control(void) {
return (cbm_ignore_checked_control_t){.deadline_ms = UINT64_MAX};
}
static bool gic_status(cbm_ignore_checked_status_t actual, cbm_ignore_checked_status_t expected,
const cbm_ignore_checked_error_t *error) {
if (actual == expected && error->status == expected &&
(expected == CBM_IGNORE_CHECKED_LIMIT || error->cap == CBM_IGNORE_CAP_NONE))
return true;
fprintf(stderr, "checked-ignore status actual=%d expected=%d error=%d cap=%d offset=%zu\n",
(int)actual, (int)expected, (int)error->status, (int)error->cap, error->byte_offset);
return false;
}
static bool gic_open(cbm_ignore_checked_t **owner, const cbm_ignore_checked_limits_t *limits) {
cbm_ignore_checked_error_t error;
memset(&error, 0xa5, sizeof(error));
bool ok =
gic_status(cbm_ignore_checked_open(limits, owner, &error), CBM_IGNORE_CHECKED_OK, &error) &&
*owner;
if (!ok) {
cbm_ignore_checked_free(*owner);
*owner = NULL;
}
return ok;
}
static bool gic_parse(cbm_ignore_checked_t *owner, const void *bytes, size_t length, size_t count,
const cbm_ignore_program_t **program) {
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_checked_parse_result_t out = {0};
cbm_ignore_checked_error_t error;
cbm_ignore_checked_status_t status =
cbm_ignore_checked_parse(owner, bytes, length, &control, &out, &error);
if (!gic_status(status, CBM_IGNORE_CHECKED_OK, &error) || !out.program ||
out.pattern_count != count)
return false;
*program = out.program;
return true;
}
static bool gic_match(cbm_ignore_checked_t *owner, const cbm_ignore_program_t *program,
const char *path, bool directory, cbm_ignore_decision_t expected) {
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_checked_error_t error;
cbm_ignore_decision_t decision = CBM_IGNORE_UNAVAILABLE;
cbm_ignore_checked_status_t status = cbm_ignore_checked_match(
owner, program, path, strlen(path), directory, &control, &decision, &error);
return gic_status(status, CBM_IGNORE_CHECKED_OK, &error) && decision == expected;
}
static bool gic_usage_equal(const cbm_ignore_checked_usage_t *a,
const cbm_ignore_checked_usage_t *b) {
return a->bytes_reserved == b->bytes_reserved && a->work_used == b->work_used &&
a->patterns_reserved == b->patterns_reserved &&
a->arena_requested_bytes == b->arena_requested_bytes &&
a->terminal_status == b->terminal_status;
}
typedef struct {
const char *patterns, *path;
bool directory;
size_t count;
cbm_ignore_decision_t expected;
} gic_semantic;
static bool gic_semantic_one(const gic_semantic *row) {
/* Legacy controls run first, so a broken fixture is not a feature failure. */
cbm_gitignore_t *legacy = cbm_gitignore_parse(row->patterns);
if (!legacy)
return false;
int old = cbm_gitignore_match_result(legacy, row->path, row->directory);
bool old_bool = cbm_gitignore_matches(legacy, row->path, row->directory);
cbm_gitignore_free(legacy);
if (old != (int)row->expected || old_bool != (row->expected == CBM_IGNORE_IGNORED))
return false;
cbm_ignore_checked_t *owner = NULL;
cbm_ignore_checked_limits_t limits = gic_limits();
const cbm_ignore_program_t *program = NULL;
bool ok = gic_open(&owner, &limits) &&
gic_parse(owner, row->patterns, strlen(row->patterns), row->count, &program) &&
gic_match(owner, program, row->path, row->directory, row->expected);
cbm_ignore_checked_free(owner);
return ok;
}
TEST(gic_a_semantics_and_legacy_parity) {
const gic_semantic rows[] = {
{"", "foo", false, 0, CBM_IGNORE_NO_OPINION},
{"#comment\n\n \t\r\n!\n/\n!/\n", "foo", false, 0, CBM_IGNORE_NO_OPINION},
{"*.tmp \t\r\n!keep.tmp\r\n", "keep.tmp", false, 2, CBM_IGNORE_REINCLUDED},
{"*.tmp \t\r\n!keep.tmp\r\n", "deep/x.tmp", false, 2, CBM_IGNORE_IGNORED},
{" #literal\n", " #literal", false, 1, CBM_IGNORE_IGNORED},
{"a\rb", "a\rb", false, 1, CBM_IGNORE_IGNORED},
{"x\nx\n!x\nx", "x", false, 4, CBM_IGNORE_IGNORED},
{"!x", "x", false, 1, CBM_IGNORE_REINCLUDED},
{"/build", "src/build", true, 1, CBM_IGNORE_NO_OPINION},
{"/build", "build", true, 1, CBM_IGNORE_IGNORED},
{"doc/frotz", "src/doc/frotz", false, 1, CBM_IGNORE_NO_OPINION},
{"doc/frotz", "doc/frotz", false, 1, CBM_IGNORE_IGNORED},
{"build/", "deep/build", true, 1, CBM_IGNORE_IGNORED},
{"build/", "build", false, 1, CBM_IGNORE_NO_OPINION},
{"build/", "build/x", false, 1, CBM_IGNORE_NO_OPINION},
{"**/build", "a/b/build", true, 1, CBM_IGNORE_IGNORED},
{"a/**/b", "a/b", false, 1, CBM_IGNORE_IGNORED},
{"logs/**", "logs/a/b", false, 1, CBM_IGNORE_IGNORED},
{"a**b", "a/x/b", false, 1, CBM_IGNORE_IGNORED},
{"/a*b", "a/x/b", false, 1, CBM_IGNORE_NO_OPINION},
{"file?.txt", "file12.txt", false, 1, CBM_IGNORE_NO_OPINION},
{"file[0-9].txt", "file3.txt", false, 1, CBM_IGNORE_IGNORED},
{"[a", "a", false, 1, CBM_IGNORE_IGNORED},
{"[]", "a", false, 1, CBM_IGNORE_NO_OPINION},
{"[!]", "z", false, 1, CBM_IGNORE_IGNORED},
{"[^a]", "a", false, 1, CBM_IGNORE_NO_OPINION},
{"src[.-0]test.c", "src/test.c", false, 1, CBM_IGNORE_NO_OPINION},
{"src[!x]test.c", "src/test.c", false, 1, CBM_IGNORE_NO_OPINION},
{"src[/]test.c", "src/test.c", false, 1, CBM_IGNORE_NO_OPINION},
{"\\#literal", "\\#literal", false, 1, CBM_IGNORE_IGNORED},
{"\\!literal", "!literal", false, 1, CBM_IGNORE_NO_OPINION},
{"tail\\ \t\r\n", "tail\\", false, 1, CBM_IGNORE_IGNORED},
{"/x", "./x", false, 1, CBM_IGNORE_NO_OPINION},
{"a/b", "a//b", false, 1, CBM_IGNORE_NO_OPINION},
{"\x80", "\x80", false, 1, CBM_IGNORE_IGNORED},
{"[\x80-z]", "A", false, 1, CHAR_MIN < 0 ? CBM_IGNORE_IGNORED : CBM_IGNORE_NO_OPINION},
{"*", "", false, 1, CBM_IGNORE_IGNORED},
};
for (size_t i = 0; i < sizeof(rows) / sizeof(rows[0]); i++) {
if (!gic_semantic_one(&rows[i])) {
fprintf(stderr, "checked-ignore semantic row=%zu\n", i);
return 1;
}
}
PASS();
}
/* A latched failure leaves every accounting field and earlier program intact;
* neither an ordinary parse nor a match may turn it into successful no-opinion. */
static bool gic_terminal(cbm_ignore_checked_t *owner, const cbm_ignore_program_t *old_program,
cbm_ignore_checked_status_t expected) {
cbm_ignore_checked_usage_t before, after;
if (!cbm_ignore_checked_usage(owner, &before) || before.terminal_status != expected)
return false;
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_checked_parse_result_t out = {.program = old_program, .pattern_count = 77};
cbm_ignore_checked_error_t error;
if (!gic_status(cbm_ignore_checked_parse(owner, "x", 1, &control, &out, &error), expected,
&error) ||
out.program || out.pattern_count)
return false;
if (old_program) {
cbm_ignore_decision_t decision = CBM_IGNORE_IGNORED;
if (!gic_status(cbm_ignore_checked_match(owner, old_program, "x", 1, false, &control,
&decision, &error),
expected, &error) ||
decision != CBM_IGNORE_UNAVAILABLE)
return false;
}
return cbm_ignore_checked_usage(owner, &after) && gic_usage_equal(&before, &after);
}
static bool gic_bad_span(cbm_ignore_checked_t *owner, const cbm_ignore_program_t *old_program,
unsigned kind) {
cbm_ignore_checked_usage_t before, after;
if (!cbm_ignore_checked_usage(owner, &before))
return false;
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_checked_error_t error;
cbm_ignore_checked_status_t status;
if (kind == 0) {
const unsigned char input[] = {'x', '\n', 0, 'y'};
cbm_ignore_checked_parse_result_t out = {.program = old_program, .pattern_count = 77};
status = cbm_ignore_checked_parse(owner, input, sizeof(input), &control, &out, &error);
if (out.program || out.pattern_count)
return false;
} else {
const char missing[] = {'x', 'y'};
const char embedded[] = {'x', 0, 'y', 0};
cbm_ignore_decision_t decision = CBM_IGNORE_IGNORED;
status = cbm_ignore_checked_match(owner, old_program, kind == 1 ? missing : embedded,
kind == 1 ? 1 : 3, false, &control, &decision, &error);
if (decision != CBM_IGNORE_UNAVAILABLE)
return false;
}
uint64_t reserved = kind == 1 ? 2 : 4;
return cbm_ignore_checked_usage(owner, &after) &&
after.bytes_reserved == before.bytes_reserved + reserved &&
gic_status(status, CBM_IGNORE_CHECKED_UNSUPPORTED, &error) &&
gic_terminal(owner, old_program, CBM_IGNORE_CHECKED_UNSUPPORTED);
}
TEST(gic_b_exact_spans_and_input_lifetime) {
gic_fixture f;
gic_init(&f);
int result = 1;
cbm_ignore_checked_limits_t limits = gic_limits();
const cbm_ignore_program_t *program = NULL, *empty = NULL;
GIC_CHECK(gic_open(&f.owner[0], &limits));
GIC_CHECK(gic_parse(f.owner[0], NULL, 0, 0, &empty));
GIC_CHECK(gic_match(f.owner[0], empty, "", false, CBM_IGNORE_NO_OPINION));
unsigned char *input = cbm_arena_alloc(&f.input, 4);
GIC_CHECK(input);
memcpy(input, "a\nbX", 4);
GIC_CHECK(gic_parse(f.owner[0], input, 3, 2, &program));
memset(input, 0xa5, 4);
cbm_arena_destroy(&f.input);
cbm_arena_init(&f.input);
GIC_CHECK(gic_match(f.owner[0], program, "a", false, CBM_IGNORE_IGNORED));
GIC_CHECK(gic_match(f.owner[0], program, "b", false, CBM_IGNORE_IGNORED));
GIC_CHECK(gic_match(f.owner[0], program, "bX", false, CBM_IGNORE_NO_OPINION));
cbm_ignore_checked_free(f.owner[0]);
f.owner[0] = NULL;
for (unsigned kind = 0; kind < 3; kind++) {
GIC_CHECK(gic_open(&f.owner[0], &limits));
GIC_CHECK(gic_parse(f.owner[0], "x", 1, 1, &program));
GIC_CHECK(gic_match(f.owner[0], program, "x", false, CBM_IGNORE_IGNORED));
GIC_CHECK(gic_bad_span(f.owner[0], program, kind));
cbm_ignore_checked_free(f.owner[0]);
f.owner[0] = NULL;
}
result = 0;
done:
return gic_finish(&f, result);
}
typedef struct {
size_t calls, stop;
} gic_counter;
static bool gic_cancel(void *context) {
gic_counter *counter = context;
counter->calls++;
return counter->stop && counter->calls >= counter->stop;
}
static bool gic_invalid_parse_call(cbm_ignore_checked_t *owner, const cbm_ignore_program_t *program,
unsigned kind, cbm_ignore_checked_control_t *control,
cbm_ignore_checked_error_t *error,
cbm_ignore_checked_status_t *status) {
if (kind == 3)
control->deadline_ms = 0;
cbm_ignore_checked_parse_result_t out = {.program = program, .pattern_count = 77};
*status = cbm_ignore_checked_parse(kind == 0 ? NULL : owner, kind == 1 ? NULL : "x", 1,
kind == 2 ? NULL : control, kind == 4 ? NULL : &out, error);
return kind == 4 || (!out.program && out.pattern_count == 0);
}
static bool gic_invalid_match_call(cbm_ignore_checked_t *owner, const cbm_ignore_program_t *program,
const cbm_ignore_program_t *foreign, unsigned kind,
cbm_ignore_checked_control_t *control,
cbm_ignore_checked_error_t *error,
cbm_ignore_checked_status_t *status) {
if (kind == 11)
control->deadline_ms = 0;
const cbm_ignore_program_t *chosen = program;
if (kind == 5)
chosen = foreign;
if (kind == 6)
chosen = NULL;
cbm_ignore_decision_t decision = CBM_IGNORE_IGNORED;
*status = cbm_ignore_checked_match(kind == 12 ? NULL : owner, chosen, kind == 7 ? NULL : "x",
kind == 8 ? SIZE_MAX : 1, false, kind == 9 ? NULL : control,
kind == 10 ? NULL : &decision, error);
return kind == 10 || decision == CBM_IGNORE_UNAVAILABLE;
}
static bool gic_preflight(cbm_ignore_checked_t *owner, const cbm_ignore_program_t *program,
const cbm_ignore_program_t *foreign, unsigned kind) {
cbm_ignore_checked_usage_t before, after;
if (!cbm_ignore_checked_usage(owner, &before))
return false;
cbm_ignore_checked_control_t control = gic_control();
gic_counter counter = {0};
control.cancelled = gic_cancel;
control.context = &counter;
cbm_ignore_checked_error_t error;
cbm_ignore_checked_status_t status;
bool cleared =
kind < 5 ? gic_invalid_parse_call(owner, program, kind, &control, &error, &status)
: gic_invalid_match_call(owner, program, foreign, kind, &control, &error, &status);
return cleared && gic_status(status, CBM_IGNORE_CHECKED_INVALID, &error) &&
counter.calls == 0 && cbm_ignore_checked_usage(owner, &after) &&
gic_usage_equal(&before, &after);
}
static bool gic_invalid_open(unsigned kind) {
cbm_ignore_checked_limits_t limits = gic_limits();
if (kind == 0)
limits.max_bytes = 0;
if (kind == 1)
limits.max_work = 0;
if (kind == 2)
limits.max_patterns = 0;
if (kind == 3)
limits.max_arena_bytes = 0;
if (kind == 4)
limits.max_depth = 0;
if (kind == 5)
limits.max_depth = CBM_IGNORE_CHECKED_DEPTH_MAX + 1;
if (kind == 6)
limits.max_patterns = (size_t)INT_MAX + 1;
cbm_ignore_checked_t *owner = NULL;
cbm_ignore_checked_error_t error;
cbm_ignore_checked_status_t status =
cbm_ignore_checked_open(kind == 7 ? NULL : &limits, kind == 8 ? NULL : &owner, &error);
bool ok = gic_status(status, CBM_IGNORE_CHECKED_INVALID, &error) && !owner;
cbm_ignore_checked_free(owner);
return ok;
}
static bool gic_optional_errors(cbm_ignore_checked_t *retained) {
cbm_ignore_checked_t *owner = NULL;
cbm_ignore_checked_limits_t limits = gic_limits();
if (cbm_ignore_checked_open(&limits, &owner, NULL) != CBM_IGNORE_CHECKED_OK || !owner) {
cbm_ignore_checked_free(owner);
return false;
}
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_checked_parse_result_t out = {0};
cbm_ignore_decision_t decision = CBM_IGNORE_UNAVAILABLE;
bool ok =
cbm_ignore_checked_parse(owner, "x", 1, &control, &out, NULL) == CBM_IGNORE_CHECKED_OK &&
out.program && out.pattern_count == 1;
if (ok)
ok = cbm_ignore_checked_match(owner, out.program, "x", 1, false, &control, &decision,
NULL) == CBM_IGNORE_CHECKED_OK &&
decision == CBM_IGNORE_IGNORED;
cbm_ignore_checked_free(owner);
owner = retained; /* Retained ownership remains in the caller's fixture. */
cbm_ignore_checked_error_t error;
return gic_status(cbm_ignore_checked_open(NULL, &owner, &error), CBM_IGNORE_CHECKED_INVALID,
&error) &&
!owner && ok;
}
TEST(gic_c_ownership_preflight_and_terminal) {
gic_fixture f;
gic_init(&f);
int result = 1;
cbm_ignore_checked_limits_t limits = gic_limits();
const cbm_ignore_program_t *program = NULL, *foreign = NULL;
GIC_CHECK(gic_open(&f.owner[0], &limits) && gic_open(&f.owner[1], &limits));
memset(&limits, 0, sizeof(limits)); /* Owners must retain their copied caps. */
GIC_CHECK(gic_parse(f.owner[0], "x", 1, 1, &program));
GIC_CHECK(gic_parse(f.owner[1], "x", 1, 1, &foreign));
GIC_CHECK(gic_match(f.owner[0], program, "x", false, CBM_IGNORE_IGNORED));
for (unsigned kind = 0; kind < 9; kind++)
GIC_CHECK(gic_invalid_open(kind));
for (unsigned kind = 0; kind < 13; kind++)
GIC_CHECK(gic_preflight(f.owner[0], program, foreign, kind));
GIC_CHECK(gic_match(f.owner[0], program, "x", false, CBM_IGNORE_IGNORED));
GIC_CHECK(gic_match(f.owner[1], foreign, "x", false, CBM_IGNORE_IGNORED));
GIC_CHECK(gic_optional_errors(f.owner[1]));
GIC_CHECK(gic_bad_span(f.owner[0], program, 0));
GIC_CHECK(gic_preflight(f.owner[0], program, foreign, 5));
GIC_CHECK(gic_terminal(f.owner[0], program, CBM_IGNORE_CHECKED_UNSUPPORTED));
GIC_CHECK(gic_match(f.owner[1], foreign, "x", false, CBM_IGNORE_IGNORED));
cbm_ignore_checked_usage_t usage;
memset(&usage, 0xa5, sizeof(usage));
GIC_CHECK(!cbm_ignore_checked_usage(NULL, &usage));
GIC_CHECK(usage.bytes_reserved == 0 && usage.work_used == 0 && usage.patterns_reserved == 0 &&
usage.arena_requested_bytes == 0 && usage.terminal_status == CBM_IGNORE_CHECKED_OK);
GIC_CHECK(!cbm_ignore_checked_usage(f.owner[1], NULL));
cbm_ignore_checked_free(NULL);
result = 0;
done:
return gic_finish(&f, result);
}
static const char gic_rules0[] = "*.tmp\n!keep.tmp\n";
static const char gic_rules1[] = "file?\n";
static const char *const gic_paths[] = {"drop.tmp", "keep.tmp", "file1", "other", "drop.tmp"};
static const cbm_ignore_decision_t gic_decisions[] = {CBM_IGNORE_IGNORED, CBM_IGNORE_REINCLUDED,
CBM_IGNORE_IGNORED, CBM_IGNORE_NO_OPINION,
CBM_IGNORE_IGNORED};
typedef struct {
cbm_ignore_checked_status_t status;
cbm_ignore_cap_t cap;
cbm_ignore_checked_usage_t usage;
} gic_replay_result;
static bool gic_replay_parse(cbm_ignore_checked_t *owner, size_t step,
const cbm_ignore_program_t **program,
cbm_ignore_checked_status_t *status,
cbm_ignore_checked_error_t *error) {
const char *rules = step == 0 ? gic_rules0 : gic_rules1;
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_checked_parse_result_t out = {.pattern_count = 77};
*status = cbm_ignore_checked_parse(owner, rules, strlen(rules), &control, &out, error);
if (*status != CBM_IGNORE_CHECKED_OK)
return !out.program && out.pattern_count == 0;
*program = out.program;
return out.program && out.pattern_count == (step == 0 ? 2 : 1);
}
static bool gic_replay_match(cbm_ignore_checked_t *owner, const cbm_ignore_program_t **program,
size_t step, cbm_ignore_checked_status_t *status,
cbm_ignore_checked_error_t *error) {
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_decision_t decision = CBM_IGNORE_IGNORED;
size_t row = step - 2;
*status = cbm_ignore_checked_match(owner, program[row == 2 ? 1 : 0], gic_paths[row],
strlen(gic_paths[row]), false, &control, &decision, error);
return decision ==
(*status == CBM_IGNORE_CHECKED_OK ? gic_decisions[row] : CBM_IGNORE_UNAVAILABLE);
}
static bool gic_replay(const cbm_ignore_checked_limits_t *limits, gic_replay_result *out) {
cbm_ignore_checked_t *owner = NULL;
if (!gic_open(&owner, limits))
return false;
const cbm_ignore_program_t *program[2] = {NULL, NULL};
cbm_ignore_checked_error_t error = {0};
cbm_ignore_checked_status_t status = CBM_IGNORE_CHECKED_OK;
bool ok = true;
size_t arena_after_parse = 0;
for (size_t step = 0; step < 7 && ok && status == CBM_IGNORE_CHECKED_OK; step++) {
cbm_ignore_checked_usage_t before, after;
ok = cbm_ignore_checked_usage(owner, &before);
if (!ok)
break;
ok = step < 2 ? gic_replay_parse(owner, step, &program[step], &status, &error)
: gic_replay_match(owner, program, step, &status, &error);
ok = ok && error.status == status && cbm_ignore_checked_usage(owner, &after);
if (!ok)
break;
if (step == 1)
arena_after_parse = after.arena_requested_bytes;
if (step >= 2)
ok = after.arena_requested_bytes == arena_after_parse;
if (status == CBM_IGNORE_CHECKED_LIMIT && error.cap == CBM_IGNORE_CAP_BYTES)
ok = ok && before.bytes_reserved == after.bytes_reserved;
}
out->status = status;
out->cap = error.cap;
ok = ok && cbm_ignore_checked_usage(owner, &out->usage);
if (status != CBM_IGNORE_CHECKED_OK)
ok = ok && gic_terminal(owner, program[0], status);
cbm_ignore_checked_free(owner);
return ok;
}
static bool gic_quota_pair(const gic_replay_result *full, cbm_ignore_cap_t cap) {
cbm_ignore_checked_limits_t limits = gic_limits();
if (cap == CBM_IGNORE_CAP_BYTES)
limits.max_bytes = full->usage.bytes_reserved;
if (cap == CBM_IGNORE_CAP_PATTERNS)
limits.max_patterns = full->usage.patterns_reserved;
if (cap == CBM_IGNORE_CAP_ARENA)
limits.max_arena_bytes = full->usage.arena_requested_bytes;
if (cap == CBM_IGNORE_CAP_WORK)
limits.max_work = full->usage.work_used;
gic_replay_result exact = {0}, short_cap = {0};
if (!gic_replay(&limits, &exact) || exact.status != CBM_IGNORE_CHECKED_OK ||
exact.cap != CBM_IGNORE_CAP_NONE || !gic_usage_equal(&full->usage, &exact.usage))
return false;
if (cap == CBM_IGNORE_CAP_BYTES)
limits.max_bytes--;
if (cap == CBM_IGNORE_CAP_PATTERNS)
limits.max_patterns--;
if (cap == CBM_IGNORE_CAP_ARENA)
limits.max_arena_bytes--;
if (cap == CBM_IGNORE_CAP_WORK)
limits.max_work--;
return gic_replay(&limits, &short_cap) && short_cap.status == CBM_IGNORE_CHECKED_LIMIT &&
short_cap.cap == cap && short_cap.usage.terminal_status == CBM_IGNORE_CHECKED_LIMIT &&
short_cap.usage.bytes_reserved <= limits.max_bytes &&
short_cap.usage.patterns_reserved <= limits.max_patterns &&
short_cap.usage.arena_requested_bytes <= limits.max_arena_bytes &&
short_cap.usage.work_used <= limits.max_work;
}
TEST(gic_d_aggregate_quota_boundaries) {
cbm_ignore_checked_limits_t limits = gic_limits();
gic_replay_result full = {0};
ASSERT_TRUE(gic_replay(&limits, &full));
ASSERT_EQ(full.status, CBM_IGNORE_CHECKED_OK);
uint64_t expected_bytes = sizeof(gic_rules0) - 1 + sizeof(gic_rules1) - 1;
for (size_t i = 0; i < 5; i++)
expected_bytes += strlen(gic_paths[i]) + 1;
ASSERT_EQ(full.usage.bytes_reserved, expected_bytes);
ASSERT_EQ(full.usage.patterns_reserved, 3);
ASSERT_GT(full.usage.work_used, 1);
ASSERT_GT(full.usage.arena_requested_bytes, 1);
ASSERT_TRUE(gic_quota_pair(&full, CBM_IGNORE_CAP_BYTES));
ASSERT_TRUE(gic_quota_pair(&full, CBM_IGNORE_CAP_PATTERNS));
ASSERT_TRUE(gic_quota_pair(&full, CBM_IGNORE_CAP_ARENA));
ASSERT_TRUE(gic_quota_pair(&full, CBM_IGNORE_CAP_WORK));
PASS();
}
static bool gic_match_limit(cbm_ignore_checked_t *owner, const cbm_ignore_program_t *program,
const char *path, cbm_ignore_cap_t cap) {
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_checked_error_t error;
cbm_ignore_decision_t decision = CBM_IGNORE_IGNORED;
cbm_ignore_checked_status_t status = cbm_ignore_checked_match(
owner, program, path, strlen(path), false, &control, &decision, &error);
return gic_status(status, CBM_IGNORE_CHECKED_LIMIT, &error) && error.cap == cap &&
decision == CBM_IGNORE_UNAVAILABLE &&
gic_terminal(owner, program, CBM_IGNORE_CHECKED_LIMIT);
}
static bool gic_depth_case(unsigned depth, bool earlier) {
cbm_ignore_checked_limits_t limits = gic_limits();
limits.max_depth = depth;
cbm_ignore_checked_t *owner = NULL;
const cbm_ignore_program_t *program = NULL;
const char *rules = earlier ? "a\n!*a" : "*a";
bool ok = gic_open(&owner, &limits) &&
gic_parse(owner, rules, strlen(rules), earlier ? 2 : 1, &program);
if (ok && depth == 1)
ok = gic_match_limit(owner, program, "a", CBM_IGNORE_CAP_DEPTH);
else if (ok)
ok = gic_match(owner, program, "a", false,
earlier ? CBM_IGNORE_REINCLUDED : CBM_IGNORE_IGNORED);
cbm_ignore_checked_free(owner);
return ok;
}
static bool gic_attempt_case(bool earlier) {
char pattern[96], path[64];
size_t n = 0;
if (earlier) {
memcpy(pattern, "*\n!", 3);
n = 3;
}
for (size_t i = 0; i < 20; i++) {
memcpy(pattern + n, "a**", 3);
n += 3;
}
pattern[n++] = 'X';
pattern[n] = 0;
memset(path, 'a', sizeof(path) - 1);
path[sizeof(path) - 1] = 0;
cbm_gitignore_t *legacy = cbm_gitignore_parse(pattern);
if (!legacy)
return false;
int old = cbm_gitignore_match_result(legacy, path, false);
cbm_gitignore_free(legacy);
if (old != (earlier ? 1 : 0))
return false;
cbm_ignore_checked_t *owner = NULL;
const cbm_ignore_program_t *program = NULL;
cbm_ignore_checked_limits_t limits = gic_limits();
bool ok = gic_open(&owner, &limits) &&
gic_parse(owner, pattern, n, earlier ? 2 : 1, &program) &&
gic_match_limit(owner, program, path, CBM_IGNORE_CAP_ATTEMPT);
cbm_ignore_checked_free(owner);
return ok;
}
static bool gic_max_depth_boundary(void) {
char pattern[2 * CBM_IGNORE_CHECKED_DEPTH_MAX + 1];
char path[CBM_IGNORE_CHECKED_DEPTH_MAX];
size_t groups = CBM_IGNORE_CHECKED_DEPTH_MAX - 1;
for (size_t i = 0; i < groups; i++) {
pattern[2 * i] = '*';
pattern[2 * i + 1] = 'a';
path[i] = 'a';
}
pattern[2 * groups] = 0;
path[groups] = 0;
cbm_ignore_checked_limits_t limits = gic_limits();
cbm_ignore_checked_t *owner = NULL;
const cbm_ignore_program_t *program = NULL;
bool ok = gic_open(&owner, &limits) && gic_parse(owner, pattern, 2 * groups, 1, &program) &&
gic_match(owner, program, path, false, CBM_IGNORE_IGNORED);
cbm_ignore_checked_free(owner);
owner = NULL;
if (!ok)
return false;
limits.max_depth--;
ok = gic_open(&owner, &limits) && gic_parse(owner, pattern, 2 * groups, 1, &program) &&
gic_match_limit(owner, program, path, CBM_IGNORE_CAP_DEPTH);
cbm_ignore_checked_free(owner);
return ok;
}
TEST(gic_e_depth_attempt_and_no_partial_decision) {
const gic_semantic ordinary = {"a**Z", "a/x/Z", false, 1, CBM_IGNORE_IGNORED};
ASSERT_TRUE(gic_semantic_one(&ordinary));
ASSERT_TRUE(gic_max_depth_boundary());
ASSERT_TRUE(gic_depth_case(2, false));
ASSERT_TRUE(gic_depth_case(2, true));
ASSERT_TRUE(gic_depth_case(1, false));
ASSERT_TRUE(gic_depth_case(1, true));
ASSERT_TRUE(gic_attempt_case(false));
ASSERT_TRUE(gic_attempt_case(true));
PASS();
}
enum { GIC_LONG = 8193 };
typedef struct {
char *patterns, *path;
size_t pattern_length;
cbm_ignore_decision_t expected;
} gic_long_input;
static bool gic_long_make(gic_fixture *f, unsigned kind, gic_long_input *out) {
out->patterns = cbm_arena_alloc(&f->input, GIC_LONG + 3);
out->path = cbm_arena_alloc(&f->input, GIC_LONG + 1);
if (!out->patterns || !out->path)
return false;
memset(out->patterns, 'a', GIC_LONG + 3);
memset(out->path, 'a', GIC_LONG);
out->path[GIC_LONG] = 0;
out->pattern_length = GIC_LONG;
out->expected = CBM_IGNORE_IGNORED;
if (kind == 1) {
out->patterns[0] = '[';
out->patterns[GIC_LONG + 1] = ']';
out->pattern_length = GIC_LONG + 2;
out->path[1] = 0;
}
if (kind == 2) {
out->patterns[0] = 'x';
out->pattern_length = 1;
for (size_t i = 1; i < GIC_LONG; i += 2)
out->path[i] = '/';
out->expected = CBM_IGNORE_NO_OPINION;
}
out->patterns[out->pattern_length] = 0;
return true;
}
typedef struct {
size_t polls;
uint64_t work;
bool cancelled;
} gic_observation;
static bool gic_long_operation(cbm_ignore_checked_t *owner, const cbm_ignore_program_t *program,
const gic_long_input *input, bool parsing,
const cbm_ignore_checked_control_t *control,
const cbm_ignore_program_t **parsed,
cbm_ignore_checked_status_t *status) {
cbm_ignore_checked_error_t error;
if (parsing) {
cbm_ignore_checked_parse_result_t out = {.program = program, .pattern_count = 77};
*status = cbm_ignore_checked_parse(owner, input->patterns, input->pattern_length, control,
&out, &error);
if (*status == CBM_IGNORE_CHECKED_OK) {
*parsed = out.program;
return error.status == *status && out.program && out.pattern_count == 1;
}
return error.status == *status && !out.program && out.pattern_count == 0;
}
cbm_ignore_decision_t decision = CBM_IGNORE_IGNORED;
*status = cbm_ignore_checked_match(owner, program, input->path, strlen(input->path), false,
control, &decision, &error);
return error.status == *status &&
decision ==
(*status == CBM_IGNORE_CHECKED_OK ? input->expected : CBM_IGNORE_UNAVAILABLE);
}
static bool gic_long_run(const gic_long_input *input, bool parsing, size_t stop,
gic_observation *observation) {
cbm_ignore_checked_limits_t limits = gic_limits();
cbm_ignore_checked_t *owner = NULL;
const cbm_ignore_program_t *program = NULL;
bool ok = gic_open(&owner, &limits);
if (ok)
ok = parsing ? gic_parse(owner, "keep", 4, 1, &program)
: gic_parse(owner, input->patterns, input->pattern_length, 1, &program);
cbm_ignore_checked_usage_t before = {0}, after = {0};
ok = ok && cbm_ignore_checked_usage(owner, &before);
gic_counter counter = {.stop = stop};
cbm_ignore_checked_control_t control = gic_control();
control.cancelled = gic_cancel;
control.context = &counter;
cbm_ignore_checked_status_t status = CBM_IGNORE_CHECKED_INVALID;
const cbm_ignore_program_t *parsed = NULL;
if (ok)
ok = gic_long_operation(owner, program, input, parsing, &control, &parsed, &status);
ok = ok && cbm_ignore_checked_usage(owner, &after);
if (ok && status == CBM_IGNORE_CHECKED_OK)
ok = (!stop || counter.calls < stop) && after.terminal_status == CBM_IGNORE_CHECKED_OK;
else if (ok)
ok = status == CBM_IGNORE_CHECKED_CANCELLED && stop && counter.calls >= stop &&
gic_terminal(owner, program, CBM_IGNORE_CHECKED_CANCELLED);
observation->polls = counter.calls;
observation->work = after.work_used - before.work_used;
observation->cancelled = status == CBM_IGNORE_CHECKED_CANCELLED;
/* Necessary aggregate bound only. This does not observe individual gaps;
* the callback never reenters usage or any other owner operation. */
if (ok)
ok = counter.calls > 0 && counter.calls <= UINT64_MAX / 4096 &&
observation->work <= (uint64_t)counter.calls * 4096;
if (ok && parsing && status == CBM_IGNORE_CHECKED_OK)
ok = gic_match(owner, parsed, input->path, false, input->expected);
size_t polls_before_free = counter.calls;
cbm_ignore_checked_free(owner);
return ok && counter.calls == polls_before_free;
}
static bool gic_cancel_sweep(const gic_long_input *input, bool parsing) {
gic_observation normal = {0};
if (!gic_long_run(input, parsing, 0, &normal) || normal.cancelled || normal.work <= 4096 ||
!normal.polls || normal.polls >= SIZE_MAX - 1)
return false;
const size_t stops[] = {1, normal.polls / 2 + 1, normal.polls, normal.polls + 1};
bool cancelled = false;
for (size_t i = 0; i < 4; i++) {
gic_observation sample = {0};
if (!gic_long_run(input, parsing, stops[i], &sample))
return false;
cancelled = cancelled || sample.cancelled;
}
return cancelled;
}
static bool gic_expired(cbm_ignore_checked_t *owner, const cbm_ignore_program_t *program,
bool parsing) {
cbm_ignore_checked_control_t control = gic_control();
control.deadline_ms = 1;
cbm_ignore_checked_error_t error;
cbm_ignore_checked_status_t status;
if (cbm_now_ms() <= 1)
return false;
if (parsing) {
cbm_ignore_checked_parse_result_t out = {.program = program, .pattern_count = 77};
status = cbm_ignore_checked_parse(owner, "x", 1, &control, &out, &error);
if (out.program || out.pattern_count)
return false;
} else {
cbm_ignore_decision_t decision = CBM_IGNORE_IGNORED;
status =
cbm_ignore_checked_match(owner, program, "x", 1, false, &control, &decision, &error);
if (decision != CBM_IGNORE_UNAVAILABLE)
return false;
}
return gic_status(status, CBM_IGNORE_CHECKED_DEADLINE, &error) &&
gic_terminal(owner, program, CBM_IGNORE_CHECKED_DEADLINE);
}
TEST(gic_f_cancellation_deadline_and_poll_evidence) {
gic_fixture f;
gic_init(&f);
int result = 1;
gic_long_input input;
GIC_CHECK(gic_long_make(&f, 0, &input));
GIC_CHECK(gic_cancel_sweep(&input, true));
GIC_CHECK(gic_cancel_sweep(&input, false));
for (unsigned kind = 1; kind < 3; kind++) {
gic_observation sample = {0};
GIC_CHECK(gic_long_make(&f, kind, &input));
GIC_CHECK(gic_long_run(&input, false, 0, &sample));
GIC_CHECK(!sample.cancelled && sample.work > 4096);
}
cbm_ignore_checked_limits_t limits = gic_limits();
const cbm_ignore_program_t *program[3] = {NULL, NULL, NULL};
for (size_t i = 0; i < 3; i++) {
GIC_CHECK(gic_open(&f.owner[i], &limits));
GIC_CHECK(gic_parse(f.owner[i], "x", 1, 1, &program[i]));
GIC_CHECK(gic_match(f.owner[i], program[i], "x", false, CBM_IGNORE_IGNORED));
}
GIC_CHECK(gic_expired(f.owner[0], program[0], true));
GIC_CHECK(gic_expired(f.owner[1], program[1], false));
GIC_CHECK(gic_match(f.owner[2], program[2], "x", false, CBM_IGNORE_IGNORED));
result = 0;
done:
return gic_finish(&f, result);
}
static bool gic_owner_oom(size_t owner_request) {
cbm_ignore_checked_limits_t limits = gic_limits();
limits.max_arena_bytes = owner_request;
cbm_ignore_checked_t *owner = NULL;
cbm_ignore_checked_error_t error;
cbm_ignore_checked_status_t status =
cbm_ignore_checked_open_oom_for_tests(&limits, &owner, &error);
bool ok = gic_status(status, CBM_IGNORE_CHECKED_OOM, &error) && !owner;
cbm_ignore_checked_free(owner);
if (!ok || !owner_request)
return false;
limits.max_arena_bytes--;
status = cbm_ignore_checked_open_oom_for_tests(&limits, &owner, &error);
cbm_ignore_checked_status_t wanted =
owner_request == 1 ? CBM_IGNORE_CHECKED_INVALID : CBM_IGNORE_CHECKED_LIMIT;
ok = gic_status(status, wanted, &error) &&
(wanted != CBM_IGNORE_CHECKED_LIMIT || error.cap == CBM_IGNORE_CAP_ARENA) && !owner;
cbm_ignore_checked_free(owner);
return ok;
}
static bool gic_parse_oom(cbm_ignore_parse_fault_t fault, cbm_ignore_checked_usage_t *before,
cbm_ignore_checked_usage_t *usage) {
cbm_ignore_checked_t *owner = NULL;
cbm_ignore_checked_limits_t limits = gic_limits();
if (!gic_open(&owner, &limits))
return false;
const cbm_ignore_program_t *old_program = NULL;
if (!gic_parse(owner, "keep", 4, 1, &old_program) || !cbm_ignore_checked_usage(owner, before)) {
cbm_ignore_checked_free(owner);
return false;
}
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_checked_error_t error;
cbm_ignore_checked_parse_result_t out = {.program = old_program, .pattern_count = 77};
const char input[] = "one\ntwo\n";
cbm_ignore_checked_status_t status = cbm_ignore_checked_parse_fault_for_tests(
owner, input, sizeof(input) - 1, &control, fault, &out, &error);
bool ok = gic_status(status, CBM_IGNORE_CHECKED_OOM, &error) && !out.program &&
out.pattern_count == 0 && cbm_ignore_checked_usage(owner, usage) &&
usage->bytes_reserved == before->bytes_reserved + sizeof(input) - 1 &&
usage->patterns_reserved == before->patterns_reserved + 2 &&
usage->arena_requested_bytes > before->arena_requested_bytes &&
usage->terminal_status == CBM_IGNORE_CHECKED_OOM &&
gic_terminal(owner, old_program, CBM_IGNORE_CHECKED_OOM);
cbm_ignore_checked_free(owner);
return ok;
}
static bool gic_unreached_and_invalid_faults(cbm_ignore_checked_t *owner,
const cbm_ignore_program_t *program) {
cbm_ignore_checked_control_t control = gic_control();
gic_counter counter = {0};
control.cancelled = gic_cancel;
control.context = &counter;
cbm_ignore_checked_usage_t before, after;
if (!cbm_ignore_checked_usage(owner, &before))
return false;
cbm_ignore_checked_parse_result_t out = {.program = program, .pattern_count = 77};
cbm_ignore_checked_error_t error;
cbm_ignore_checked_status_t status = cbm_ignore_checked_parse_fault_for_tests(
owner, "x", 1, &control, (cbm_ignore_parse_fault_t)999, &out, &error);
if (!gic_status(status, CBM_IGNORE_CHECKED_INVALID, &error) || out.program ||
out.pattern_count || counter.calls || !cbm_ignore_checked_usage(owner, &after) ||
!gic_usage_equal(&before, &after))
return false;
status = cbm_ignore_checked_parse_fault_for_tests(owner, NULL, 0, &control,
CBM_IGNORE_PARSE_FAULT_PATTERN, &out, &error);
if (!gic_status(status, CBM_IGNORE_CHECKED_OK, &error) || !out.program || out.pattern_count ||
!gic_match(owner, out.program, "x", false, CBM_IGNORE_NO_OPINION))
return false;
status = cbm_ignore_checked_parse_fault_for_tests(owner, "z", 1, &control,
CBM_IGNORE_PARSE_FAULT_NONE, &out, &error);
return gic_status(status, CBM_IGNORE_CHECKED_OK, &error) && out.program &&
out.pattern_count == 1 &&
gic_match(owner, out.program, "z", false, CBM_IGNORE_IGNORED) &&
gic_match(owner, program, "one", false, CBM_IGNORE_IGNORED);
}
static bool gic_oom_quota_first(cbm_ignore_parse_fault_t fault, size_t owner_request) {
cbm_ignore_checked_limits_t limits = gic_limits();
limits.max_arena_bytes = owner_request;
cbm_ignore_checked_t *owner = NULL;
if (!gic_open(&owner, &limits))
return false;
cbm_ignore_checked_control_t control = gic_control();
cbm_ignore_checked_error_t error;
cbm_ignore_checked_parse_result_t out = {.pattern_count = 77};
cbm_ignore_checked_status_t status =
cbm_ignore_checked_parse_fault_for_tests(owner, "x", 1, &control, fault, &out, &error);
cbm_ignore_checked_usage_t usage;
bool ok = gic_status(status, CBM_IGNORE_CHECKED_LIMIT, &error) &&
error.cap == CBM_IGNORE_CAP_ARENA && !out.program && out.pattern_count == 0 &&
cbm_ignore_checked_usage(owner, &usage) &&
usage.arena_requested_bytes == owner_request &&
gic_terminal(owner, NULL, CBM_IGNORE_CHECKED_LIMIT);
cbm_ignore_checked_free(owner);
return ok;
}
TEST(gic_g_call_local_simulated_oom) {
gic_fixture f;
gic_init(&f);
int result = 1;
cbm_ignore_checked_limits_t limits = gic_limits();
cbm_ignore_checked_usage_t opened, normal, program_failure, pattern_failure;
cbm_ignore_checked_usage_t program_before, pattern_before;
const cbm_ignore_program_t *program = NULL;
GIC_CHECK(gic_open(&f.owner[0], &limits));
GIC_CHECK(cbm_ignore_checked_usage(f.owner[0], &opened));
GIC_CHECK(gic_parse(f.owner[0], "keep", 4, 1, &program));
GIC_CHECK(gic_parse(f.owner[0], "one\ntwo\n", 8, 2, &program));
GIC_CHECK(gic_match(f.owner[0], program, "one", false, CBM_IGNORE_IGNORED));
GIC_CHECK(cbm_ignore_checked_usage(f.owner[0], &normal));
GIC_CHECK(gic_owner_oom(opened.arena_requested_bytes));
GIC_CHECK(gic_parse_oom(CBM_IGNORE_PARSE_FAULT_PROGRAM, &program_before, &program_failure));
GIC_CHECK(gic_parse_oom(CBM_IGNORE_PARSE_FAULT_PATTERN, &pattern_before, &pattern_failure));
GIC_CHECK(gic_usage_equal(&program_before, &pattern_before));
GIC_CHECK(program_failure.arena_requested_bytes > program_before.arena_requested_bytes);
GIC_CHECK(pattern_failure.arena_requested_bytes > program_failure.arena_requested_bytes);
GIC_CHECK(pattern_failure.arena_requested_bytes <= normal.arena_requested_bytes);
GIC_CHECK(program_failure.work_used > opened.work_used && pattern_failure.work_used > 0);
GIC_CHECK(gic_oom_quota_first(CBM_IGNORE_PARSE_FAULT_PROGRAM, opened.arena_requested_bytes));
GIC_CHECK(gic_oom_quota_first(CBM_IGNORE_PARSE_FAULT_PATTERN, opened.arena_requested_bytes));
GIC_CHECK(gic_unreached_and_invalid_faults(f.owner[0], program));
/* Failed calls in other owners cannot poison this retained real program. */
GIC_CHECK(gic_match(f.owner[0], program, "two", false, CBM_IGNORE_IGNORED));
result = 0;
done:
return gic_finish(&f, result);
}
SUITE(gitignore_checked) {
RUN_TEST(gic_a_semantics_and_legacy_parity);
RUN_TEST(gic_b_exact_spans_and_input_lifetime);
RUN_TEST(gic_c_ownership_preflight_and_terminal);
RUN_TEST(gic_d_aggregate_quota_boundaries);
RUN_TEST(gic_e_depth_attempt_and_no_partial_decision);
RUN_TEST(gic_f_cancellation_deadline_and_poll_evidence);
RUN_TEST(gic_g_call_local_simulated_oom);
}