run.sh (10947B)
1 #!/usr/bin/env bash 2 # test/elf/run.sh — ELF object-file fidelity tests, on the shared corpus 3 # harness (test/lib/kit_corpus.sh). 4 # 5 # Scope: read/write/roundtrip of relocatable ELF .o files only. Linker 6 # and exe behavior live in test/link/ — do not duplicate them here. 7 # 8 # Layers — each is a SEQUENTIAL corpus (its own kit_corpus_run, single lane, 9 # no opt axis), NOT a parallel lane within a case: 10 # 11 # A. test/elf/unit/*.c hand-built ObjBuilder roundtrip tests. Each .c 12 # links against build/libkit.a, runs, exits 0 13 # on success. (lane A) 14 # B. test/elf/cases/*.c clang-oracle structural roundtrip tests: 15 # clang -c case.c -> golden.o 16 # kit-roundtrip golden.o -> rt.o 17 # normalized readelf diff golden.o vs rt.o. 18 # Honors per-case NN_name.targets tuple 19 # applicability (engine SKIP-NA) and per-case 20 # NN_name.cflags extra compiler flags. (lane B) 21 # C. test/elf/bad/*.elf malformed inputs; expect kit-roundtrip to 22 # fail (nonzero, non-signal) with a substring from 23 # <name>.expect on stderr. (lane C) 24 # 25 # Tools detected: clang, llvm-readelf/readelf, python3. Missing tools cause the 26 # dependent layer's cases to be SKIPped, not failed. Set KIT_TEST_ALLOW_SKIP=1 27 # to allow the harness to exit 0 with skips; otherwise any skip makes the run 28 # fail (so CI catches a silently degraded run). 29 # 30 # Parallelism: 31 # default run in parallel with a capped CPU-count default. 32 # KIT_TEST_JOBS=N run up to N cases per layer concurrently. 33 # KIT_TEST_JOBS=auto same as the default. 34 # KIT_ELF_PARALLEL=0 force serial dispatch. 35 # Every lane hook writes only under $KIT_WORK and records only via kit_*, so the 36 # console summary is identical serial or parallel; per-case command output for 37 # a failure is surfaced by the engine in deterministic index order. 38 39 set -u 40 41 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 42 TEST_DIR="$ROOT/test/elf" 43 BUILD_DIR="$ROOT/build/test" 44 LIB_AR="$ROOT/build/libkit.a" 45 NORMALIZE="$TEST_DIR/normalize.py" 46 47 export KIT_LIB_DIR="$ROOT/test/lib" 48 # shellcheck source=../lib/kit_corpus.sh 49 . "$ROOT/test/lib/kit_corpus.sh" 50 51 mkdir -p "$BUILD_DIR" 52 53 KIT_BIN="${KIT:-$ROOT/build/kit}" 54 PAR="${KIT_ELF_PARALLEL:-1}" 55 56 # ----- tool detection ---------------------------------------------------- 57 58 have_clang=0 59 have_llvm_readelf=0 60 have_python3=0 61 62 command -v clang >/dev/null 2>&1 && have_clang=1 63 command -v llvm-readelf >/dev/null 2>&1 && have_llvm_readelf=1 64 command -v readelf >/dev/null 2>&1 && have_llvm_readelf=1 65 command -v python3 >/dev/null 2>&1 && have_python3=1 66 67 # ----- locate kit-roundtrip ------------------------------------------- 68 # Built as a Make target (mk/test.mk) so it picks up libkit.a changes. 69 70 ROUNDTRIP_BIN="$BUILD_DIR/kit-roundtrip" 71 roundtrip_ok=0 72 [ -x "$ROUNDTRIP_BIN" ] && roundtrip_ok=1 73 74 # ----- Layer A toolchain wiring ------------------------------------------ 75 76 UNIT_CC="${CC:-clang}" 77 UNIT_SYSROOT_FLAGS=() 78 unit_sysroot="$(xcrun --show-sdk-path 2>/dev/null || true)" 79 [ -n "$unit_sysroot" ] && UNIT_SYSROOT_FLAGS=(-isysroot "$unit_sysroot") 80 UNIT_EXTRA_CFLAGS=() 81 UNIT_EXTRA_LDFLAGS=() 82 if [ -n "${KIT_ELF_UNIT_CFLAGS:-}" ]; then 83 # shellcheck disable=SC2206 84 UNIT_EXTRA_CFLAGS=(${KIT_ELF_UNIT_CFLAGS}) 85 fi 86 if [ -n "${KIT_ELF_UNIT_LDFLAGS:-}" ]; then 87 # shellcheck disable=SC2206 88 UNIT_EXTRA_LDFLAGS=(${KIT_ELF_UNIT_LDFLAGS}) 89 fi 90 91 # ----- Layer B target wiring --------------------------------------------- 92 # Map (KIT_TEST_ARCH, KIT_TEST_OBJ) (defaults aa64+elf) to the clang 93 # `--target=` triple the Layer B golden objects are compiled against. 94 # kit-roundtrip then detects the input's e_machine / Mach-O cputype and 95 # constructs a matching KitTargetSpec, so the readelf/objdump diff stays 96 # apples-to-apples per target. 97 case "${KIT_TEST_OBJ:-elf}" in 98 elf) 99 case "${KIT_TEST_ARCH:-aa64}" in 100 aa64|aarch64|arm64) CLANG_TARGET="aarch64-linux-gnu" ;; 101 x64|x86_64|amd64) CLANG_TARGET="x86_64-linux-gnu" ;; 102 rv64|riscv64) CLANG_TARGET="riscv64-linux-gnu" ;; 103 *) printf 'unknown KIT_TEST_ARCH=%s\n' "${KIT_TEST_ARCH}" >&2; exit 2 ;; 104 esac 105 ;; 106 macho) 107 case "${KIT_TEST_ARCH:-aa64}" in 108 aa64|aarch64|arm64) CLANG_TARGET="arm64-apple-macos" ;; 109 x64|x86_64|amd64) CLANG_TARGET="x86_64-apple-macos" ;; 110 *) printf 'KIT_TEST_OBJ=macho: unsupported arch %s\n' "${KIT_TEST_ARCH}" >&2; exit 2 ;; 111 esac 112 ;; 113 *) printf 'unknown KIT_TEST_OBJ=%s\n' "${KIT_TEST_OBJ}" >&2; exit 2 ;; 114 esac 115 116 CUR_TUPLE="${KIT_TEST_ARCH:-aa64}-${KIT_TEST_OBJ:-elf}" 117 118 # ----- header summary ---------------------------------------------------- 119 120 printf 'kit ELF test harness\n' 121 printf ' clang: %s\n' "$([ $have_clang -eq 1 ] && echo yes || echo no)" 122 printf ' llvm-readelf: %s\n' "$([ $have_llvm_readelf -eq 1 ] && echo yes || echo no)" 123 printf ' python3: %s\n' "$([ $have_python3 -eq 1 ] && echo yes || echo no)" 124 printf ' kit-roundtrip: %s\n' "$([ $roundtrip_ok -eq 1 ] && echo found || echo "MISSING — run \"make $ROUNDTRIP_BIN\"")" 125 printf ' target: %s (tuple %s)\n' "$CLANG_TARGET" "$CUR_TUPLE" 126 printf '\n' 127 128 # ----- Layer A lane: unit/*.c -------------------------------------------- 129 # Build a native binary against libkit.a, run it, expect exit 0. All 130 # artifacts (binary + logs) live under $KIT_WORK for parallel safety. 131 kit_lane_A() { 132 local name="unit/$KIT_NAME" 133 if [ ! -f "$LIB_AR" ]; then 134 kit_skip "$name" "build/libkit.a missing"; return 135 fi 136 137 local bin="$KIT_WORK/$KIT_BASE" 138 local build_log="$KIT_WORK/build.log" 139 local out_log="$KIT_WORK/out.log" 140 141 if ! "$UNIT_CC" -std=c11 -Wall -Wextra -Werror \ 142 "${UNIT_EXTRA_CFLAGS[@]}" \ 143 "${UNIT_SYSROOT_FLAGS[@]}" \ 144 -I"$ROOT/include" -I"$ROOT/src" -I"$ROOT/test" \ 145 "$KIT_SRC" "$LIB_AR" "${UNIT_EXTRA_LDFLAGS[@]}" -o "$bin" \ 146 2> "$build_log"; then 147 kit_fail "$name" "build failed; see $build_log" 148 return 149 fi 150 151 if "$bin" > "$out_log" 2>&1; then 152 kit_pass "$name" 153 else 154 kit_fail "$name" 155 sed 's/^/ | /' "$out_log" 156 fi 157 } 158 159 # ----- Layer B lane: cases/*.c ------------------------------------------- 160 # clang -c -> golden.o ; kit-roundtrip -> rt.o ; normalized readelf diff. 161 # Whole-case .targets applicability is handled by the engine (SKIP-NA) via 162 # KIT_TARGETS_EXT. Per-case .cflags are read here, KIT_WORK-confined. 163 kit_lane_B() { 164 local name="cases/$KIT_NAME" 165 166 # Per-case skip reasons (missing tooling). 167 if [ $roundtrip_ok -ne 1 ]; then kit_skip "$name" "kit-roundtrip not built"; return; fi 168 if [ $have_clang -ne 1 ]; then kit_skip "$name" "clang missing"; return; fi 169 if [ $have_llvm_readelf -ne 1 ]; then kit_skip "$name" "llvm-readelf missing"; return; fi 170 if [ $have_python3 -ne 1 ]; then kit_skip "$name" "python3 missing"; return; fi 171 172 # Per-case extra compiler flags: NN_name.cflags alongside the .c passes 173 # additional flags (e.g. -ffunction-sections, -x c++). Tokenized on shell 174 # whitespace, matching the original harness convention. 175 local extra_cflags=() cflags_file tok 176 cflags_file="${KIT_SRC%.c}.cflags" 177 if [ -f "$cflags_file" ]; then 178 for tok in $(cat "$cflags_file"); do 179 [ -n "$tok" ] && extra_cflags+=("$tok") 180 done 181 fi 182 183 if ! clang --target="$CLANG_TARGET" -c -O0 "${extra_cflags[@]}" \ 184 "$KIT_SRC" -o "$KIT_WORK/golden.o" 2> "$KIT_WORK/clang.log"; then 185 kit_skip "$name" "clang -c failed (cross-compile not configured?)" 186 return 187 fi 188 189 if ! "$ROUNDTRIP_BIN" "$KIT_WORK/golden.o" "$KIT_WORK/rt.o" 2> "$KIT_WORK/roundtrip.log"; then 190 kit_fail "$name" "roundtrip failed" 191 sed 's/^/ | /' "$KIT_WORK/roundtrip.log" 192 return 193 fi 194 195 # Structural diff: readelf normalized. 196 python3 "$NORMALIZE" readelf "$KIT_WORK/golden.o" > "$KIT_WORK/golden.readelf" 2> /dev/null || true 197 python3 "$NORMALIZE" readelf "$KIT_WORK/rt.o" > "$KIT_WORK/rt.readelf" 2> /dev/null || true 198 if ! diff -u "$KIT_WORK/golden.readelf" "$KIT_WORK/rt.readelf" > "$KIT_WORK/readelf.diff"; then 199 kit_fail "$name" "readelf diff" 200 head -n 40 "$KIT_WORK/readelf.diff" | sed 's/^/ | /' 201 return 202 fi 203 204 kit_pass "$name" 205 } 206 207 # ----- Layer C lane: bad/*.elf ------------------------------------------- 208 # Expect kit-roundtrip to fail (nonzero, non-signal) and emit a substring 209 # from <name>.expect on stderr. 210 kit_lane_C() { 211 local name="bad/$KIT_NAME" 212 if [ $roundtrip_ok -ne 1 ]; then 213 kit_skip "$name" "kit-roundtrip not built"; return 214 fi 215 216 local rc=0 217 "$ROUNDTRIP_BIN" "$KIT_SRC" "$KIT_WORK/out.o" \ 218 > "$KIT_WORK/stdout.log" 2> "$KIT_WORK/stderr.log" || rc=$? 219 220 if [ $rc -eq 0 ]; then 221 kit_fail "$name" "expected nonzero exit, got 0"; return 222 fi 223 if [ $rc -ge 128 ]; then 224 kit_fail "$name" "terminated by signal $((rc - 128)) — segfault?"; return 225 fi 226 227 local expect_file expect 228 expect_file="${KIT_SRC%.elf}.expect" 229 if [ ! -f "$expect_file" ]; then 230 kit_fail "$name" "missing $expect_file"; return 231 fi 232 expect="$(cat "$expect_file")" 233 if grep -qF -- "$expect" "$KIT_WORK/stderr.log"; then 234 kit_pass "$name" 235 else 236 kit_fail "$name" "stderr did not contain: $expect" 237 sed 's/^/ | /' "$KIT_WORK/stderr.log" 238 fi 239 } 240 241 # ----- drive the three layers -------------------------------------------- 242 243 printf 'Layer A — unit tests\n' 244 KIT_LABEL=test-elf KIT_BUILD_DIR="$BUILD_DIR/elf/unit" \ 245 KIT_CORPUS_GLOBS="$TEST_DIR/unit/*.c" KIT_CORPUS_EXT=c KIT_SIDECAR_DIR="$TEST_DIR/unit" \ 246 KIT_LANES="A" KIT_OPT_LEVELS="" KIT_TUPLES="$CUR_TUPLE" KIT_TARGETS_EXT="" \ 247 KIT_PARALLELIZABLE="$PAR" kit_corpus_run 248 printf '\n' 249 250 printf 'Layer B — clang-oracle cases\n' 251 KIT_LABEL=test-elf KIT_BUILD_DIR="$BUILD_DIR/elf/cases" \ 252 KIT_CORPUS_GLOBS="$TEST_DIR/cases/*.c" KIT_CORPUS_EXT=c KIT_SIDECAR_DIR="$TEST_DIR/cases" \ 253 KIT_LANES="B" KIT_OPT_LEVELS="" KIT_TUPLES="$CUR_TUPLE" KIT_TARGETS_EXT=".targets" \ 254 KIT_PARALLELIZABLE="$PAR" kit_corpus_run 255 printf '\n' 256 257 printf 'Layer C — negative read_elf inputs\n' 258 KIT_LABEL=test-elf KIT_BUILD_DIR="$BUILD_DIR/elf/bad" \ 259 KIT_CORPUS_GLOBS="$TEST_DIR/bad/*.elf" KIT_CORPUS_EXT=elf KIT_SIDECAR_DIR="$TEST_DIR/bad" \ 260 KIT_LANES="C" KIT_OPT_LEVELS="" KIT_TUPLES="$CUR_TUPLE" KIT_TARGETS_EXT="" \ 261 KIT_PARALLELIZABLE="$PAR" kit_corpus_run 262 printf '\n' 263 264 # ----- summary ----------------------------------------------------------- 265 266 kit_summary test-elf 267 kit_exit