Skip to content
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 @@ -284,6 +327,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
42 changes: 32 additions & 10 deletions tests/test_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ int tf_skip_count = 0;
#include <string.h>
#include <signal.h>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#include <winsock2.h> /* #798 follow-up: socket-isolation re-exec probe */
#else
#include <unistd.h>
Expand Down Expand Up @@ -894,6 +896,36 @@ 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_SANITIZED_BUILD) && CBM_SANITIZED_BUILD
const int sanitized = 1;
#else
const int sanitized = 0;
#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", 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 @@ -905,16 +937,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