Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions scripts/msan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,27 @@ ulimit -s 262144 2>/dev/null || true
export CBM_THREAD_STACK_MB="${CBM_THREAD_STACK_MB:-256}"
export LD_LIBRARY_PATH="$MSAN_PREFIX/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

expected_build_config=$'sanitized=1 test_seams=1\n'
build_config_marker="__cbm_build_config_end__"
captured_build_config=""
actual_build_config=""
build_config_probe_ok=0
if captured_build_config="$(./build/msan/test-runner --build-config && printf '%s' "$build_config_marker")"; then
build_config_probe_ok=1
actual_build_config="${captured_build_config%"$build_config_marker"}"
else
actual_build_config="$captured_build_config"
fi
if [ "$build_config_probe_ok" -ne 1 ] ||
[ "$actual_build_config" != "$expected_build_config" ]; then
reported_build_config="${actual_build_config//$'\n'/\\n}"
printf '%s\n' 'ERROR: build config mismatch for build/msan/test-runner' >&2
printf '%s\n' ' expected: sanitized=1 test_seams=1' >&2
printf ' reported: %s\n' "${reported_build_config:-<empty>}" >&2
printf '%s\n' 'Refusing to run suites; check sanitizer flags and CBM_SANITIZED_BUILD wiring.' >&2
exit 1
fi

echo "=== MSan lane: $(clang --version | head -1) ==="

# KNOWN RED — THREE DISTINCT CAUSES, whitelisted per cause (O10)
Expand Down
46 changes: 45 additions & 1 deletion scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ source "$ROOT/scripts/path-safety.sh"
MAKE_ARGS=()
BUILD_DIR="build/c"
SANITIZE_GIVEN=0
SANITIZE_VALUE=""
prev_arg=""
for arg in "$@"; do
case "$arg" in
Expand All @@ -154,7 +155,11 @@ for arg in "$@"; do
--tsan) ;; # already handled
--suites|--suites=*) ;; # already handled (value skipped via prev_arg below)
BUILD_DIR=*) BUILD_DIR="${arg#BUILD_DIR=}"; MAKE_ARGS+=("$arg") ;;
SANITIZE=*) SANITIZE_GIVEN=1; MAKE_ARGS+=("$arg") ;;
SANITIZE=*)
SANITIZE_GIVEN=1
SANITIZE_VALUE="${arg#SANITIZE=}"
MAKE_ARGS+=("$arg")
;;
*=*)
if [[ "${prev_arg:-}" != "--suites" ]]; then
MAKE_ARGS+=("$arg") # forward any VAR=VAL to make
Expand All @@ -174,6 +179,42 @@ if [ "$SANITIZE_GIVEN" -eq 0 ] && [ "${MSYSTEM:-}" = "CLANGARM64" ]; then
MAKE_ARGS+=("SANITIZE=-fsanitize=undefined -fsanitize-trap=undefined -fstack-protector-strong -fno-omit-frame-pointer")
fi

EXPECTED_SANITIZED=1
if [ "$SANITIZE_GIVEN" -eq 1 ]; then
case "$SANITIZE_VALUE" in
*[![:space:]]*) EXPECTED_SANITIZED=1 ;;
*) EXPECTED_SANITIZED=0 ;;
esac
fi

# Refuse to run suites when the built runner disagrees with the lane that
# produced it. Exact output keeps missing, duplicate, or future unhandled
# fields fail-closed instead of silently weakening the gate.
assert_test_runner_build_config() {
local runner="$1"
local expected_sanitized="$2"
local expected="sanitized=$expected_sanitized test_seams=1"
local marker="__cbm_build_config_end__"
local captured=""
local actual=""
local probe_ok=0

if captured="$("$runner" --build-config && printf '%s' "$marker")"; then
probe_ok=1
actual="${captured%"$marker"}"
else
actual="$captured"
fi
if [ "$probe_ok" -ne 1 ] || [ "$actual" != "$expected"$'\n' ]; then
local reported="${actual//$'\n'/\\n}"
printf 'ERROR: build config mismatch for %s\n' "$runner" >&2
printf ' expected: %s\n' "$expected" >&2
printf ' reported: %s\n' "${reported:-<empty>}" >&2
printf '%s\n' 'Refusing to run suites; check sanitizer flags and CBM_SANITIZED_BUILD wiring.' >&2
return 1
fi
}

print_env "test.sh"

# ── TSan mode (--tsan): the data-race gate ──
Expand All @@ -182,6 +223,7 @@ print_env "test.sh"
if [ "$TSAN" -eq 1 ]; then
echo "=== test.sh: TSan leg (make test-tsan) ==="
make -j"$NPROC" -f Makefile.cbm "$BUILD_DIR/test-runner-tsan" ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
assert_test_runner_build_config "$BUILD_DIR/test-runner-tsan" 1
make -f Makefile.cbm test-tsan ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
exit "$?"
fi
Expand All @@ -192,6 +234,7 @@ fi
if [ -n "$SUITES" ]; then
echo "=== test.sh: ITERATION mode — suites: $SUITES (incremental build) ==="
make -j"$NPROC" -f Makefile.cbm "$BUILD_DIR/test-runner" ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
assert_test_runner_build_config "$BUILD_DIR/test-runner" "$EXPECTED_SANITIZED"
# shellcheck disable=SC2086 # suite list is deliberately word-split
"$BUILD_DIR/test-runner" $SUITES
exit "$?"
Expand Down Expand Up @@ -275,6 +318,7 @@ BUILD_DIR="$BUILD_DIR" scripts/clean.sh
# pass/fail/skip totals aggregate to the same numbers as the sequential run).
# CBM_TEST_SEQUENTIAL=1 restores the single-process runner.
make -j"$NPROC" -f Makefile.cbm "$BUILD_DIR/test-runner" ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
assert_test_runner_build_config "$BUILD_DIR/test-runner" "$EXPECTED_SANITIZED"
if [ "${CBM_TEST_SEQUENTIAL:-0}" = "1" ]; then
make -f Makefile.cbm test ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
else
Expand Down
36 changes: 26 additions & 10 deletions tests/test_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ int tf_skip_count = 0;
#include "foundation/log.h" /* crash-durable worker log probe */
#include "foundation/mem.h" /* cbm_mem_init — worker budget */
#include "foundation/platform.h" /* cbm_file_exists — blocking-git marker */
#include "foundation/sanitized.h" /* CBM_SANITIZED — --build-config contract */
#include "daemon/runtime.h" /* bounded worker response probe */
#include "daemon/ipc.h" /* Windows private-lock re-exec probe */
#include "daemon/version_cohort.h" /* Windows crash-turnover re-exec probe */
Expand Down Expand Up @@ -817,6 +818,31 @@ extern void suite_dump_verify_io(void);
extern void cbm_kind_in_set_free_cache(void);

int main(int argc, char **argv) {
int blocking_git_rc = tf_maybe_run_blocking_git_probe(argc, argv);
if (blocking_git_rc >= 0) {
return blocking_git_rc;
}
/* Installation tests use this executable as a structurally real candidate.
* Mirror the production binary's minimal verification contract. */
if (argc == 2 && strcmp(argv[1], "--version") == 0) {
(void)puts("codebase-memory-mcp test-runner");
return 0;
}
if (argc == 2 && strcmp(argv[1], "--build-config") == 0) {
#ifdef _WIN32
if (_setmode(cbm_fileno(stdout), _O_BINARY) == -1) {
fprintf(stderr, "failed to set build-config stdout to binary mode\n");
return 2;
}
#endif
#if defined(CBM_ENABLE_TEST_SEAMS) && CBM_ENABLE_TEST_SEAMS
const int test_seams = 1;
#else
const int test_seams = 0;
#endif
(void)printf("sanitized=%d test_seams=%d\n", CBM_SANITIZED, test_seams);
return 0;
}
/* Skip the multi-hundred-MB executable-image hash that computes the exact
* build fingerprint: it is tens of seconds per spawned worker/daemon under
* ASan on constrained CI runners and the sole cause of the daemon-family
Expand All @@ -828,16 +854,6 @@ int main(int argc, char **argv) {
(void)cbm_setenv("CBM_TEST_BUILD_FINGERPRINT",
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", 1);
}
int blocking_git_rc = tf_maybe_run_blocking_git_probe(argc, argv);
if (blocking_git_rc >= 0) {
return blocking_git_rc;
}
/* Installation tests use this executable as a structurally real candidate.
* Mirror the production binary's minimal verification contract. */
if (argc == 2 && strcmp(argv[1], "--version") == 0) {
(void)puts("codebase-memory-mcp test-runner");
return 0;
}
int mcp_idxfailclosed_rc = tf_maybe_run_mcp_idxfailclosed_probe(argc, argv);
if (mcp_idxfailclosed_rc >= 0) {
return mcp_idxfailclosed_rc;
Expand Down
Loading