kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit c89c279bfba06ba19c63e497e07187f08da8ab1f
parent 9b2f7411de60da7bddda1376723fbcf9df2157f2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 17 Jun 2026 15:38:56 -0700

test: Tier 1 test set with kitchen sink tests

Diffstat:
Mmk/flags.mk | 7++++---
Mmk/test.mk | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Atest/TESTMAP.md | 24++++++++++++++++++++++++
Mtest/lib/kit_corpus.sh | 39++++++++++++++++++++++++++++++++++++++-
Atest/tier1/README.md | 17+++++++++++++++++
Atest/tier1/frontend/README.md | 30++++++++++++++++++++++++++++++
Atest/tier1/frontend/run-kitchen.sh | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/tier1/kitchen/frontend/c_kitchen.c | 44++++++++++++++++++++++++++++++++++++++++++++
Atest/tier1/kitchen/frontend/c_kitchen.expected | 1+
Atest/tier1/kitchen/frontend/pp_kitchen.c | 15+++++++++++++++
Atest/tier1/kitchen/frontend/pp_kitchen.expected | 2++
Atest/tier1/kitchen/frontend/pp_kitchen.h | 2++
Atest/tier1/kitchen/frontend/toy_kitchen.expected | 1+
Atest/tier1/kitchen/frontend/toy_kitchen.toy | 30++++++++++++++++++++++++++++++
Atest/tier1/kitchen/frontend/wasm_kitchen.expected | 1+
Atest/tier1/kitchen/frontend/wasm_kitchen.wat | 30++++++++++++++++++++++++++++++
Atest/tier1/kitchen/objlink/asm/aa64_cond.expected.hex | 1+
Atest/tier1/kitchen/objlink/asm/aa64_cond.s | 7+++++++
Atest/tier1/kitchen/objlink/asm/rv64_li_ret.expected.txt | 2++
Atest/tier1/kitchen/objlink/asm/rv64_li_ret.hex | 1+
Atest/tier1/kitchen/objlink/asm/x64_mov_ret.expected.txt | 2++
Atest/tier1/kitchen/objlink/asm/x64_mov_ret.hex | 1+
Atest/tier1/kitchen/objlink/link/archive_member.c | 7+++++++
Atest/tier1/kitchen/objlink/link/helper.c | 3+++
Atest/tier1/kitchen/objlink/link/main.c | 17+++++++++++++++++
Atest/tier1/kitchen/objlink/object/kitchen_object.c | 19+++++++++++++++++++
Atest/tier1/manifests/system/debug-dwarf.txt | 7+++++++
Atest/tier1/manifests/system/dist.txt | 4++++
Atest/tier1/manifests/system/runtime-headers.txt | 7+++++++
Atest/tier1/manifests/system/tools.txt | 4++++
Atest/tier1/objlink/README.md | 25+++++++++++++++++++++++++
Atest/tier1/objlink/asm.sh | 33+++++++++++++++++++++++++++++++++
Atest/tier1/objlink/common.sh | 44++++++++++++++++++++++++++++++++++++++++++++
Atest/tier1/objlink/link.sh | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/tier1/objlink/object.sh | 38++++++++++++++++++++++++++++++++++++++
Atest/tier1/objlink/run.sh | 25+++++++++++++++++++++++++
Atest/tier1/run_logged.sh | 24++++++++++++++++++++++++
Atest/tier1/system/README.md | 30++++++++++++++++++++++++++++++
Atest/tier1/system/debug-dwarf.sh | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/tier1/system/dist.sh | 38++++++++++++++++++++++++++++++++++++++
Atest/tier1/system/runtime-headers.sh | 33+++++++++++++++++++++++++++++++++
Atest/tier1/system/tools.sh | 38++++++++++++++++++++++++++++++++++++++
42 files changed, 986 insertions(+), 6 deletions(-)

diff --git a/mk/flags.mk b/mk/flags.mk @@ -68,15 +68,16 @@ FORCE: $(BUILD_CONFIG): FORCE @mkdir -p $(dir $@) - @{ \ + @tmp="$@.$$$$.tmp"; \ + { \ printf '%s\n' 'RELEASE=$(RELEASE)'; \ printf '%s\n' 'HOST_OPTFLAGS=$(HOST_OPTFLAGS)'; \ printf '%s\n' 'HOST_MODE_CPPFLAGS=$(HOST_MODE_CPPFLAGS)'; \ printf '%s\n' 'HOST_MODE_CFLAGS=$(HOST_MODE_CFLAGS)'; \ printf '%s\n' 'HOST_MODE_LDFLAGS=$(HOST_MODE_LDFLAGS)'; \ printf '%s\n' 'AUTO_INIT_CFLAGS=$(AUTO_INIT_CFLAGS)'; \ - } > $@.tmp - @if ! cmp -s $@.tmp $@; then mv $@.tmp $@; else rm -f $@.tmp; fi + } > "$$tmp"; \ + if ! cmp -s "$$tmp" "$@"; then mv "$$tmp" "$@"; else rm -f "$$tmp"; fi # --- kit version / build-id (single source of truth: top-level VERSION) ----- # Captured once per `make` invocation and injected only into driver/version.o diff --git a/mk/test.mk b/mk/test.mk @@ -201,9 +201,101 @@ DEFAULT_TEST_TARGETS = \ bootstrap \ test-bootstrap-toy -.PHONY: test $(TEST_TARGETS) windows-ucrt-sysroots hosted-glibc-images +TIER1_TEST_TARGETS = \ + test-t1-harness \ + test-t1-build \ + test-t1-driver \ + test-t1-core-api \ + test-t1-pp \ + test-t1-c-front \ + test-t1-cg \ + test-t1-opt \ + test-t1-arch \ + test-t1-asm-disas \ + test-t1-obj \ + test-t1-link-jit \ + test-t1-rt \ + test-t1-debug \ + test-t1-emu-interp \ + test-t1-wasm \ + test-t1-tools \ + test-t1-dist \ + test-t1-gram -test: $(DEFAULT_TEST_TARGETS) +TIER1_PARALLEL_TARGETS = $(filter-out test-t1-build,$(TIER1_TEST_TARGETS)) + +T1_LOG = test/tier1/run_logged.sh + +.PHONY: test test-full test-tier1 $(TIER1_TEST_TARGETS) \ + $(TEST_TARGETS) windows-ucrt-sysroots hosted-glibc-images + +test: test-tier1 + +test-tier1: + @$(MAKE) --no-print-directory test-t1-build + @$(MAKE) --no-print-directory -j$(NCPU) $(TIER1_PARALLEL_TARGETS) + +test-full: $(DEFAULT_TEST_TARGETS) + +test-t1-harness: + @$(T1_LOG) harness bash test/lib/kit_corpus_selftest.sh + +test-t1-build: + @$(T1_LOG) build $(MAKE) --no-print-directory lib bin rt + +test-t1-driver: + @$(T1_LOG) driver env KIT=$(abspath $(BIN)) sh test/driver/run.sh + +test-t1-core-api: + @$(T1_LOG) core-api $(MAKE) --no-print-directory test-hash test-release-index test-abi-classify + +test-t1-pp: bin + @$(T1_LOG) pp env KIT=$(abspath $(BIN)) test/tier1/frontend/run-kitchen.sh pp + +test-t1-c-front: bin $(PARSE_RUNNER) $(LINK_EXE_RUNNER) $(JIT_RUNNER) + @$(T1_LOG) c-front env KIT=$(abspath $(BIN)) test/tier1/frontend/run-kitchen.sh c + +test-t1-cg: bin + @$(T1_LOG) cg $(MAKE) --no-print-directory test-cg-api test-ir-recorder + @$(T1_LOG) toy env KIT=$(abspath $(BIN)) test/tier1/frontend/run-kitchen.sh toy + +test-t1-opt: + @$(T1_LOG) opt $(MAKE) --no-print-directory test-opt-tiny-inline test-opt-inline test-opt-zero-arg + +test-t1-arch: + @$(T1_LOG) arch $(MAKE) --no-print-directory test-isa test-aa64-inline test-x64-inline test-rv64-inline test-arm32-inline + +test-t1-asm-disas: $(ASM_RUNNER) + @$(T1_LOG) asm-disas test/tier1/objlink/asm.sh + +test-t1-obj: $(ROUNDTRIP_BIN) + @$(T1_LOG) obj test/tier1/objlink/object.sh + @$(T1_LOG) ar $(MAKE) --no-print-directory test-ar + +test-t1-link-jit: $(LINK_EXE_RUNNER) + @$(T1_LOG) link test/tier1/objlink/link.sh + @$(T1_LOG) jit-relax $(MAKE) --no-print-directory test-link-jit-tls-relax + +test-t1-rt: + @$(T1_LOG) rt test/tier1/system/runtime-headers.sh + +test-t1-debug: + @$(T1_LOG) debug test/tier1/system/debug-dwarf.sh + +test-t1-emu-interp: + @$(T1_LOG) emu-interp $(MAKE) --no-print-directory test-emu test-interp test-interp-emu + +test-t1-wasm: bin + @$(T1_LOG) wasm env KIT=$(abspath $(BIN)) test/tier1/frontend/run-kitchen.sh wasm + +test-t1-tools: + @$(T1_LOG) tools test/tier1/system/tools.sh + +test-t1-dist: + @$(T1_LOG) dist test/tier1/system/dist.sh + +test-t1-gram: + @$(T1_LOG) gram $(MAKE) --no-print-directory test-driver-gram # Unit-test binary build rules: two regimes (public vs internal interface). include mk/test_unit.mk diff --git a/test/TESTMAP.md b/test/TESTMAP.md @@ -0,0 +1,24 @@ +# Test selection map + +Use Tier 1 first for a fast subsystem verdict, then the focused suite for the +area you changed. + +| Changed paths | First run | Focused follow-up | +| --- | --- | --- | +| `lang/cpp/**`, `test/pp/**` | `make test-t1-pp` | `make test-pp` | +| `lang/c/**`, `test/parse/**` | `make test-t1-c-front` | `make test-parse` | +| `src/cg/**`, `include/kit.h`, `lang/toy/**` | `make test-t1-cg` | `make test-cg-api test-toy` | +| `src/opt/**` | `make test-t1-opt` | `make test-opt` | +| `src/arch/**` | `make test-t1-arch test-t1-asm-disas` | `make test-isa test-asm` plus the touched arch inline target | +| `src/asm/**`, `driver/cmd/as.c`, `driver/cmd/disas.c` | `make test-t1-asm-disas` | `make test-asm` | +| `src/obj/**`, object format readers/writers | `make test-t1-obj` | `make test-elf test-macho test-coff` | +| `src/link/**`, JIT image mapping | `make test-t1-link-jit` | `make test-link` | +| `rt/**` | `make test-t1-rt` | `make test-rt-runtime test-rt-backtrace` | +| `src/debug/**`, `src/dbg/**` | `make test-t1-debug` | `make test-debug test-dwarf test-dbg` | +| `src/emu/**`, `src/interp/**` | `make test-t1-emu-interp` | `make test-emu test-emu-unit test-interp` | +| `lang/wasm/**`, `src/arch/wasm/**` | `make test-t1-wasm` | `make test-wasm test-wasm-target` | +| `driver/cmd/**`, byte utility tools | `make test-t1-tools` | matching `test-driver-*` target | +| `src/dist/**`, `src/api/cas.c`, `src/api/package.c` | `make test-t1-dist` | `make test-dist test-selfdist` | +| `src/gram/**`, `driver/cmd/gram.c`, `test/gram/**` | `make test-t1-gram` | `make test-gram test-driver-gram` | +| `test/lib/**`, `mk/test.mk` | `make test-t1-harness test-tier1` | affected suite | +| `Makefile`, `mk/**`, public headers | `make test-t1-build test-tier1` | affected suite | diff --git a/test/lib/kit_corpus.sh b/test/lib/kit_corpus.sh @@ -18,6 +18,11 @@ # KIT_LABEL summary label (e.g. "toy") # KIT_BUILD_DIR scratch root; per-item workdirs live under it # KIT_CORPUS_GLOBS space-separated globs of case source files +# KIT_CORPUS_LIST optional exact manifest of cases to run. Entries are +# newline-separated, with blank lines and # comments +# ignored. Relative entries are resolved from the first +# corpus glob's directory; bare basenames may omit +# KIT_CORPUS_EXT. # KIT_CORPUS_EXT extension stripped for the basename (e.g. "toy","c","s") # KIT_SIDECAR_DIR dir holding sidecars (default: dir of the first glob) # KIT_LANES active lane ids, space-separated (runner derives from KIT_TEST_PATHS) @@ -98,8 +103,40 @@ kit_queue_e() { # ---- discovery ------------------------------------------------------------- kit_corpus_discover() { KIT_CASES=() - local g f base + local g f base manifest_base entry path shopt -s nullglob + if [ -n "${KIT_CORPUS_LIST:-}" ]; then + if [ ! -f "$KIT_CORPUS_LIST" ]; then + echo "$KIT_LABEL: KIT_CORPUS_LIST not found: $KIT_CORPUS_LIST" >&2 + exit 2 + fi + set -- $KIT_CORPUS_GLOBS + manifest_base=$(dirname "$1") + while IFS= read -r entry || [ -n "$entry" ]; do + entry="${entry%%#*}" + entry="${entry#"${entry%%[![:space:]]*}"}" + entry="${entry%"${entry##*[![:space:]]}"}" + [ -z "$entry" ] && continue + case "$entry" in + /*) path="$entry" ;; + *) path="$manifest_base/$entry" ;; + esac + if [ ! -e "$path" ] && [ -n "$KIT_CORPUS_EXT" ]; then + path="$path.$KIT_CORPUS_EXT" + fi + if [ ! -e "$path" ]; then + echo "$KIT_LABEL: manifest case not found: $entry" >&2 + exit 2 + fi + base=$(basename "$path") + [ -n "$KIT_CORPUS_EXT" ] && base="${base%.$KIT_CORPUS_EXT}" + if [ -n "${KIT_TEST_FILTER:-}" ]; then + case "$base" in *"$KIT_TEST_FILTER"*) ;; *) continue ;; esac + fi + KIT_CASES+=("$path") + done < "$KIT_CORPUS_LIST" + return + fi for g in $KIT_CORPUS_GLOBS; do for f in $g; do base=$(basename "$f") diff --git a/test/tier1/README.md b/test/tier1/README.md @@ -0,0 +1,17 @@ +# Tier 1 tests + +Tier 1 is the fast subsystem sentinel layer. Each target should answer one +coarse question: is this subsystem basically alive? + +Rules: + +- Prefer one kitchen-sink case over a broad corpus. +- Prefer dedicated fixtures under `test/tier1/kitchen/` over sampling old + corpora. `KIT_CORPUS_LIST` remains available for focused Tier 2 probes. +- Keep output in `build/test/tier1/logs/`. +- Keep heavy platform, ecosystem, selfhost, and full corpus sweeps in Tier 2/3. +- If a Tier 1 sentinel fails, run the matching focused suite from + `test/TESTMAP.md`. + +`make test` runs Tier 1 in parallel. The previous broad default is available as +`make test-full`. diff --git a/test/tier1/frontend/README.md b/test/tier1/frontend/README.md @@ -0,0 +1,30 @@ +# Frontend Tier 1 Kitchen Sentinels + +These fixtures are dedicated kitchen-sink cases for the frontend Tier 1 suite. +They are intended as quick sentinels, not replacements for the full suites. + +- `pp_kitchen.c`: object/function macros, token paste, conditional inclusion, + stringify, variadics, and local include resolution. +- `c_kitchen.c`: declarations, struct/union/enum, arrays, pointers, loop, + switch, object emission, and `kit run`. +- `toy_kitchen.toy`: tuple data, function pointer call, switch, JIT run, and + interpreter run. +- `wasm_kitchen.wat`: memory, mutable global, loop/control flow, call, and + `kit run` through the Wasm frontend. + +Run one suite with: + +```sh +test/tier1/frontend/run-kitchen.sh pp +test/tier1/frontend/run-kitchen.sh c +test/tier1/frontend/run-kitchen.sh toy +test/tier1/frontend/run-kitchen.sh wasm +``` + +Run all frontend sentinels with: + +```sh +test/tier1/frontend/run-kitchen.sh all +``` + +The runner logs each fixture under `build/test/tier1/frontend/<suite>/`. diff --git a/test/tier1/frontend/run-kitchen.sh b/test/tier1/frontend/run-kitchen.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -u + +usage() { + printf 'usage: %s all|pp|c|parse|toy|wasm\n' "$0" >&2 +} + +if [ "$#" -gt 1 ]; then + usage + exit 2 +fi + +suite="${1:-all}" +script_dir=$(cd "$(dirname "$0")" && pwd) +root=$(cd "$script_dir/../../.." && pwd) +fixture_root="$root/test/tier1/kitchen/frontend" +log_root="${KIT_TIER1_LOG_DIR:-$root/build/test/tier1/frontend}" +KIT="${KIT:-$root/build/kit}" + +expected_rc() { + tr -d '[:space:]' < "$1" +} + +check_rc() { + local label="$1" + local got="$2" + local want="$3" + want=$((want & 255)) + if [ "$got" -ne "$want" ]; then + printf '%s: expected rc %d, got %d\n' "$label" "$want" "$got" >&2 + return 1 + fi +} + +normalize_tokens() { + tr '\n' ' ' < "$1" | tr -s '[:space:]' ' ' | sed -e 's/^ //' -e 's/ $//' +} + +run_pp() { + local src="$fixture_root/pp_kitchen.c" + local expected="$fixture_root/pp_kitchen.expected" + local actual="$1/pp_kitchen.actual" + local exp_norm="$1/pp_kitchen.expected.norm" + local act_norm="$1/pp_kitchen.actual.norm" + + [ -x "$KIT" ] || { printf 'missing kit binary: %s\n' "$KIT" >&2; return 2; } + "$KIT" cc -E -I "$fixture_root" "$src" -o "$actual" + normalize_tokens "$expected" > "$exp_norm" + normalize_tokens "$actual" > "$act_norm" + diff -u "$exp_norm" "$act_norm" +} + +run_c() { + local src="$fixture_root/c_kitchen.c" + local expected + local run_rc + local obj="$1/c_kitchen.o" + local dump="$1/c_kitchen.dump" + expected=$(expected_rc "$fixture_root/c_kitchen.expected") + + [ -x "$KIT" ] || { printf 'missing kit binary: %s\n' "$KIT" >&2; return 2; } + "$KIT" cc -O0 -c "$src" -o "$obj" + "$KIT" objdump -h -t "$obj" > "$dump" + grep -Eq '(^|[[:space:]])main($|[[:space:]])' "$dump" + "$KIT" run "$src" + run_rc=$? + check_rc "c/kitchen" "$run_rc" "$expected" +} + +run_toy() { + local src="$fixture_root/toy_kitchen.toy" + local expected + local run_rc + expected=$(expected_rc "$fixture_root/toy_kitchen.expected") + + [ -x "$KIT" ] || { printf 'missing kit binary: %s\n' "$KIT" >&2; return 2; } + "$KIT" run "$src" + run_rc=$? + check_rc "toy/kitchen-jit" "$run_rc" "$expected" + "$KIT" run --no-jit "$src" + run_rc=$? + check_rc "toy/kitchen-interp" "$run_rc" "$expected" +} + +run_wasm() { + local src="$fixture_root/wasm_kitchen.wat" + local expected + local run_rc + expected=$(expected_rc "$fixture_root/wasm_kitchen.expected") + + [ -x "$KIT" ] || { printf 'missing kit binary: %s\n' "$KIT" >&2; return 2; } + "$KIT" run -e test_main "$src" + run_rc=$? + check_rc "wasm/kitchen-jit" "$run_rc" "$expected" +} + +run_suite() { + local name="$1" + local log_dir="$log_root/$name" + local log="$log_dir/kitchen.log" + local suite_rc + mkdir -p "$log_dir" + + if "run_$name" "$log_dir" > "$log" 2>&1; then + printf 'PASS tier1-frontend/%s %s\n' "$name" "$log" + return 0 + else + suite_rc=$? + fi + + printf 'FAIL tier1-frontend/%s %s\n' "$name" "$log" >&2 + tail -n 80 "$log" >&2 || true + return "$suite_rc" +} + +case "$suite" in + all) suites="pp c toy wasm" ;; + pp|c|toy|wasm) suites="$suite" ;; + parse) suites="c" ;; + *) usage; exit 2 ;; +esac + +rc=0 +for s in $suites; do + run_suite "$s" || rc=1 +done + +exit "$rc" diff --git a/test/tier1/kitchen/frontend/c_kitchen.c b/test/tier1/kitchen/frontend/c_kitchen.c @@ -0,0 +1,44 @@ +struct Pair { + int a; + int b; +}; + +union Box { + int i; + unsigned u; +}; + +enum Mode { + MODE_ADD = 1, + MODE_MUL = 2 +}; + +static int apply(enum Mode mode, struct Pair p) { + switch (mode) { + case MODE_ADD: + return p.a + p.b; + case MODE_MUL: + return p.a * p.b; + default: + return 0; + } +} + +int main(void) { + struct Pair p = {.a = 6, .b = 7}; + union Box box = {.u = 40u}; + int values[3] = {1, 2, 3}; + int *cursor = values; + int counter = 3; + int total = apply(MODE_ADD, p); + + for (int i = 0; i < 3; ++i) { + total += cursor[i]; + } + + if (total == 19 && box.i == 40) { + total += counter; + } + + return total == 22 ? 42 : 1; +} diff --git a/test/tier1/kitchen/frontend/c_kitchen.expected b/test/tier1/kitchen/frontend/c_kitchen.expected @@ -0,0 +1 @@ +42 diff --git a/test/tier1/kitchen/frontend/pp_kitchen.c b/test/tier1/kitchen/frontend/pp_kitchen.c @@ -0,0 +1,15 @@ +#include "pp_kitchen.h" + +#define OBJ 7 +#define ADD(a, b) ((a) + (b)) +#define STR(x) #x +#define CAT(a, b) a ## b +#define PICK(flag) flag +#define VA(first, ...) first + __VA_ARGS__ + +#if defined(OBJ) && OBJ == 7 +int CAT(pp_, value) = ADD(T1_HEADER_ADD(OBJ), VA(1, 2, 3)); +const char *pp_text = STR(kitchen sink); +#else +int pp_value = -1; +#endif diff --git a/test/tier1/kitchen/frontend/pp_kitchen.expected b/test/tier1/kitchen/frontend/pp_kitchen.expected @@ -0,0 +1,2 @@ +int pp_value = ((((7) + 5)) + (1 + 2, 3)); +const char *pp_text = "kitchen sink"; diff --git a/test/tier1/kitchen/frontend/pp_kitchen.h b/test/tier1/kitchen/frontend/pp_kitchen.h @@ -0,0 +1,2 @@ +#define T1_HEADER_VALUE 5 +#define T1_HEADER_ADD(x) ((x) + T1_HEADER_VALUE) diff --git a/test/tier1/kitchen/frontend/toy_kitchen.expected b/test/tier1/kitchen/frontend/toy_kitchen.expected @@ -0,0 +1 @@ +42 diff --git a/test/tier1/kitchen/frontend/toy_kitchen.toy b/test/tier1/kitchen/frontend/toy_kitchen.toy @@ -0,0 +1,30 @@ +tuple Pair { + i64, + i64, +} + +fn add2(x: i64): i64 { + return x + 2; +} + +fn __user_main(): i64 { + let p: Pair = Pair { 10, 20 }; + let fp: *fn(i64): i64 = add2; + var total: i64 = p.0 + p.1; + + switch 2 { + 1 { + total = total + 1; + } + 2 { + total = total + fp(10); + } + default { + total = 0; + } + } + + return total; +} + +fn main(): i32 { return __user_main() as i32; } diff --git a/test/tier1/kitchen/frontend/wasm_kitchen.expected b/test/tier1/kitchen/frontend/wasm_kitchen.expected @@ -0,0 +1 @@ +42 diff --git a/test/tier1/kitchen/frontend/wasm_kitchen.wat b/test/tier1/kitchen/frontend/wasm_kitchen.wat @@ -0,0 +1,30 @@ +(module + (memory 1) + (global $g (mut i32) (i32.const 5)) + (func $add (param $x i32) (result i32) + local.get $x + global.get $g + i32.add) + (func (export "test_main") (result i32) + (local $i i32) + (local $sum i32) + (loop $again + local.get $sum + local.get $i + i32.add + local.set $sum + local.get $i + i32.const 1 + i32.add + local.tee $i + i32.const 4 + i32.lt_s + br_if $again) + i32.const 8 + local.get $sum + i32.store + i32.const 8 + i32.load + i32.const 31 + i32.add + call $add)) diff --git a/test/tier1/kitchen/objlink/asm/aa64_cond.expected.hex b/test/tier1/kitchen/objlink/asm/aa64_cond.expected.hex @@ -0,0 +1 @@ +5f4000f1ff0740b1e4079f1ae5139fda4100839ae6b4889a diff --git a/test/tier1/kitchen/objlink/asm/aa64_cond.s b/test/tier1/kitchen/objlink/asm/aa64_cond.s @@ -0,0 +1,7 @@ +.text + cmp x2, #16 + cmn sp, #1, lsl #12 + cset w4, ne + csetm x5, eq + csel x1, x2, x3, eq + csinc x6, x7, x8, lt diff --git a/test/tier1/kitchen/objlink/asm/rv64_li_ret.expected.txt b/test/tier1/kitchen/objlink/asm/rv64_li_ret.expected.txt @@ -0,0 +1,2 @@ +0: li a0, 7 +4: ret diff --git a/test/tier1/kitchen/objlink/asm/rv64_li_ret.hex b/test/tier1/kitchen/objlink/asm/rv64_li_ret.hex @@ -0,0 +1 @@ +1305700067800000 diff --git a/test/tier1/kitchen/objlink/asm/x64_mov_ret.expected.txt b/test/tier1/kitchen/objlink/asm/x64_mov_ret.expected.txt @@ -0,0 +1,2 @@ +0: movl $42, %eax +5: ret diff --git a/test/tier1/kitchen/objlink/asm/x64_mov_ret.hex b/test/tier1/kitchen/objlink/asm/x64_mov_ret.hex @@ -0,0 +1 @@ +b82a000000c3 diff --git a/test/tier1/kitchen/objlink/link/archive_member.c b/test/tier1/kitchen/objlink/link/archive_member.c @@ -0,0 +1,7 @@ +__attribute__((noinline)) int tier1_archive_value(void) { + return 7; +} + +__attribute__((noinline)) int tier1_archive_dead_value(void) { + return 101; +} diff --git a/test/tier1/kitchen/objlink/link/helper.c b/test/tier1/kitchen/objlink/link/helper.c @@ -0,0 +1,3 @@ +__attribute__((noinline)) int tier1_helper_value(int x) { + return x + 30; +} diff --git a/test/tier1/kitchen/objlink/link/main.c b/test/tier1/kitchen/objlink/link/main.c @@ -0,0 +1,17 @@ +extern int tier1_helper_value(int x); +extern int tier1_archive_value(void); + +int tier1_live_data = 5; +int *tier1_live_data_ref = &tier1_live_data; + +__attribute__((noinline)) static int tier1_live_local(void) { + return tier1_helper_value(*tier1_live_data_ref) + tier1_archive_value(); +} + +__attribute__((noinline)) static int tier1_dead_local(void) { + return 99; +} + +int test_main(void) { + return tier1_live_local() == 42 ? 0 : tier1_dead_local(); +} diff --git a/test/tier1/kitchen/objlink/object/kitchen_object.c b/test/tier1/kitchen/objlink/object/kitchen_object.c @@ -0,0 +1,19 @@ +int tier1_common; +long tier1_common_long; + +__attribute__((weak)) int tier1_weak_hook(void) { + return 11; +} + +__attribute__((visibility("hidden"))) int tier1_hidden_add(int x) { + return x + 3; +} + +static const char tier1_message[] = "objlink"; +int tier1_data __attribute__((section(".data.tier1"))) = 19; +int *tier1_data_ref = &tier1_data; + +int tier1_object_entry(void) { + return tier1_hidden_add(tier1_common) + tier1_weak_hook() + + tier1_message[2] + *tier1_data_ref + (int)tier1_common_long; +} diff --git a/test/tier1/manifests/system/debug-dwarf.txt b/test/tier1/manifests/system/debug-dwarf.txt @@ -0,0 +1,7 @@ +# Debug/DWARF Tier 1 sentinel steps. +make:bin +make:test-dwarf +make:test-debug +driver:objdump-dwarf/01-dwarf-all +driver:dbg/repl-help + diff --git a/test/tier1/manifests/system/dist.txt b/test/tier1/manifests/system/dist.txt @@ -0,0 +1,4 @@ +# Local distribution primitive Tier 1 sentinel targets. +test-driver-cas +test-driver-pkg + diff --git a/test/tier1/manifests/system/runtime-headers.txt b/test/tier1/manifests/system/runtime-headers.txt @@ -0,0 +1,7 @@ +# Runtime header triples covered by test-rt-headers. +aarch64-linux-gnu +x86_64-linux-gnu +riscv64-linux-gnu +aarch64-apple-darwin +x86_64-apple-darwin + diff --git a/test/tier1/manifests/system/tools.txt b/test/tier1/manifests/system/tools.txt @@ -0,0 +1,4 @@ +# Cheap driver-tool Tier 1 sentinel targets. +test-driver-tools +test-driver-strings +test-driver-objdump diff --git a/test/tier1/objlink/README.md b/test/tier1/objlink/README.md @@ -0,0 +1,25 @@ +# Objlink Tier 1 + +This directory runs dedicated kitchen-sink fixtures for object, link, arch, and +asm sentinels. It does not sample the broader corpora. + +Fixtures live under `test/tier1/kitchen/objlink/`: + +- `asm/`: direct `asm-runner` encode/decode checks for aa64, x64, and rv64. +- `object/`: one C object with common symbols, weak/hidden symbols, data + relocations, and per-function/per-data sections; it is round-tripped with + `kit-roundtrip` and compared through the ELF normalizer. +- `link/`: a tiny multi-object program linked through `link-exe-runner` with an + archive member and `--gc-sections`, then inspected as an executable image. + +Run all: + +```sh +test/tier1/objlink/run.sh +``` + +Prerequisites are the same harness binaries used by the full suites: + +```sh +make build/test/asm-runner build/test/kit-roundtrip build/test/link-exe-runner +``` diff --git a/test/tier1/objlink/asm.sh b/test/tier1/objlink/asm.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -eu + +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +. "$ROOT/test/tier1/objlink/common.sh" + +KITCHEN="$ROOT/test/tier1/kitchen/objlink/asm" +WORK="$(t1_work_dir "$ROOT" asm)" +LOG_DIR="$(t1_log_dir "$ROOT")" +ASM_RUNNER="$ROOT/build/test/asm-runner" + +mkdir -p "$WORK" "$LOG_DIR" +t1_require_exe "$ASM_RUNNER" 'run: make build/test/asm-runner' + +echo "tier1 asm: aa64 encode" +KIT_TEST_ARCH=aa64 t1_run "$LOG_DIR/asm-aa64-encode.log" \ + "$ASM_RUNNER" --encode "$KITCHEN/aa64_cond.s" "$WORK/aa64_cond.hex" +t1_diff "$KITCHEN/aa64_cond.expected.hex" "$WORK/aa64_cond.hex" \ + "$WORK/aa64_cond.diff" + +echo "tier1 asm: x64 decode" +KIT_TEST_ARCH=x64 t1_run "$LOG_DIR/asm-x64-decode.log" \ + "$ASM_RUNNER" --decode "$KITCHEN/x64_mov_ret.hex" "$WORK/x64_mov_ret.txt" +t1_diff "$KITCHEN/x64_mov_ret.expected.txt" "$WORK/x64_mov_ret.txt" \ + "$WORK/x64_mov_ret.diff" + +echo "tier1 asm: rv64 decode" +KIT_TEST_ARCH=rv64 t1_run "$LOG_DIR/asm-rv64-decode.log" \ + "$ASM_RUNNER" --decode "$KITCHEN/rv64_li_ret.hex" "$WORK/rv64_li_ret.txt" +t1_diff "$KITCHEN/rv64_li_ret.expected.txt" "$WORK/rv64_li_ret.txt" \ + "$WORK/rv64_li_ret.diff" + +echo "tier1 asm: ok" diff --git a/test/tier1/objlink/common.sh b/test/tier1/objlink/common.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +t1_root() { + cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd +} + +t1_log_dir() { + local root="$1" + printf '%s\n' "$root/build/test/tier1/objlink/logs" +} + +t1_work_dir() { + local root="$1" name="$2" + printf '%s\n' "$root/build/test/tier1/objlink/$name" +} + +t1_require_exe() { + local path="$1" hint="$2" + if [ ! -x "$path" ]; then + echo "missing executable: $path" >&2 + echo "$hint" >&2 + return 2 + fi +} + +t1_clang_triple() { + case "${1:-aa64}" in + aa64|aarch64|arm64) printf 'aarch64-linux-gnu\n' ;; + x64|x86_64|amd64) printf 'x86_64-linux-gnu\n' ;; + rv64|riscv64) printf 'riscv64-linux-gnu\n' ;; + *) echo "unsupported KIT_TEST_ARCH=$1" >&2; return 2 ;; + esac +} + +t1_run() { + local log="$1" + shift + "$@" >"$log" 2>&1 +} + +t1_diff() { + local expected="$1" actual="$2" diff_file="$3" + diff -u "$expected" "$actual" >"$diff_file" 2>&1 +} diff --git a/test/tier1/objlink/link.sh b/test/tier1/objlink/link.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -eu + +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +. "$ROOT/test/tier1/objlink/common.sh" + +ARCH="${KIT_TEST_ARCH:-aa64}" +TRIPLE="$(t1_clang_triple "$ARCH")" +KITCHEN="$ROOT/test/tier1/kitchen/objlink/link" +WORK="$(t1_work_dir "$ROOT" link)" +LOG_DIR="$(t1_log_dir "$ROOT")" +LINK_EXE_RUNNER="$ROOT/build/test/link-exe-runner" +AR_BIN="$(command -v llvm-ar 2>/dev/null || command -v ar 2>/dev/null || true)" +READELF_BIN="$(command -v llvm-readelf 2>/dev/null || command -v readelf 2>/dev/null || true)" + +mkdir -p "$WORK" "$LOG_DIR" +t1_require_exe "$LINK_EXE_RUNNER" 'run: make build/test/link-exe-runner' +[ -n "$AR_BIN" ] || { echo "llvm-ar/ar not found" >&2; exit 2; } +[ -n "$READELF_BIN" ] || { echo "llvm-readelf/readelf not found" >&2; exit 2; } +command -v clang >/dev/null 2>&1 || { echo "clang not found" >&2; exit 2; } + +CFLAGS=( + --target="$TRIPLE" -O1 -ffreestanding -fno-stack-protector + -fno-PIC -fno-pie -fcommon -ffunction-sections -fdata-sections +) + +echo "tier1 link: compile $ARCH objects" +t1_run "$LOG_DIR/link-main-clang.log" \ + clang "${CFLAGS[@]}" -c "$KITCHEN/main.c" -o "$WORK/main.o" +t1_run "$LOG_DIR/link-helper-clang.log" \ + clang "${CFLAGS[@]}" -c "$KITCHEN/helper.c" -o "$WORK/helper.o" +t1_run "$LOG_DIR/link-archive-clang.log" \ + clang "${CFLAGS[@]}" -c "$KITCHEN/archive_member.c" -o "$WORK/archive_member.o" +t1_run "$LOG_DIR/link-start-clang.log" \ + clang "${CFLAGS[@]}" -c "$ROOT/test/link/harness/start.c" -o "$WORK/start.o" + +echo "tier1 link: archive" +t1_run "$LOG_DIR/link-ar.log" "$AR_BIN" rcs "$WORK/libtier1.a" "$WORK/archive_member.o" + +echo "tier1 link: link executable" +KIT_TEST_ARCH="$ARCH" KIT_TEST_OBJ=elf t1_run "$LOG_DIR/link-exe.log" \ + "$LINK_EXE_RUNNER" --gc-sections -o "$WORK/tier1.exe" \ + "$WORK/main.o" "$WORK/helper.o" "$WORK/start.o" \ + --archive "$WORK/libtier1.a" + +echo "tier1 link: inspect executable" +"$READELF_BIN" -h "$WORK/tier1.exe" >"$WORK/tier1.readelf" 2>"$LOG_DIR/link-readelf.log" +grep -Eq 'Type:[[:space:]]+EXEC|Type:[[:space:]]+DYN' "$WORK/tier1.readelf" +grep -Eq 'Entry point address:[[:space:]]+0x[0-9a-fA-F]+' "$WORK/tier1.readelf" + +echo "tier1 link: ok" diff --git a/test/tier1/objlink/object.sh b/test/tier1/objlink/object.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -eu + +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +. "$ROOT/test/tier1/objlink/common.sh" + +ARCH="${KIT_TEST_ARCH:-aa64}" +TRIPLE="$(t1_clang_triple "$ARCH")" +KITCHEN="$ROOT/test/tier1/kitchen/objlink/object" +WORK="$(t1_work_dir "$ROOT" object)" +LOG_DIR="$(t1_log_dir "$ROOT")" +ROUNDTRIP="$ROOT/build/test/kit-roundtrip" +NORMALIZE="$ROOT/test/elf/normalize.py" + +mkdir -p "$WORK" "$LOG_DIR" +t1_require_exe "$ROUNDTRIP" 'run: make build/test/kit-roundtrip' +command -v clang >/dev/null 2>&1 || { echo "clang not found" >&2; exit 2; } +command -v python3 >/dev/null 2>&1 || { echo "python3 not found" >&2; exit 2; } +{ command -v llvm-readelf >/dev/null 2>&1 || command -v readelf >/dev/null 2>&1; } || { + echo "llvm-readelf/readelf not found" >&2 + exit 2 +} + +echo "tier1 object: compile $ARCH" +t1_run "$LOG_DIR/object-clang.log" \ + clang --target="$TRIPLE" -c -O0 -fcommon -ffunction-sections \ + -fdata-sections "$KITCHEN/kitchen_object.c" -o "$WORK/kitchen.o" + +echo "tier1 object: roundtrip" +t1_run "$LOG_DIR/object-roundtrip.log" \ + "$ROUNDTRIP" "$WORK/kitchen.o" "$WORK/kitchen.rt.o" + +echo "tier1 object: normalize diff" +python3 "$NORMALIZE" readelf "$WORK/kitchen.o" >"$WORK/kitchen.readelf" 2>"$LOG_DIR/object-readelf-a.log" || true +python3 "$NORMALIZE" readelf "$WORK/kitchen.rt.o" >"$WORK/kitchen.rt.readelf" 2>"$LOG_DIR/object-readelf-b.log" || true +t1_diff "$WORK/kitchen.readelf" "$WORK/kitchen.rt.readelf" "$WORK/kitchen.diff" + +echo "tier1 object: ok" diff --git a/test/tier1/objlink/run.sh b/test/tier1/objlink/run.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -eu + +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +LOG_DIR="$ROOT/build/test/tier1/logs" +mkdir -p "$LOG_DIR" + +run_one() { + local name="$1" + shift + local log="$LOG_DIR/objlink-$name.log" + printf 'T1 objlink-%-8s -> %s\n' "$name" "$log" + if "$@" >"$log" 2>&1; then + printf 'T1 objlink-%-8s OK\n' "$name" + else + local rc=$? + printf 'T1 objlink-%s failed; tail of %s:\n' "$name" "$log" >&2 + tail -100 "$log" >&2 || true + return "$rc" + fi +} + +run_one asm "$ROOT/test/tier1/objlink/asm.sh" +run_one object "$ROOT/test/tier1/objlink/object.sh" +run_one link "$ROOT/test/tier1/objlink/link.sh" diff --git a/test/tier1/run_logged.sh b/test/tier1/run_logged.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -u + +if [ "$#" -lt 2 ]; then + echo "usage: run_logged.sh <label> <command> [args...]" >&2 + exit 2 +fi + +label="$1" +shift + +root="$(cd "$(dirname "$0")/../.." && pwd)" +log_dir="$root/build/test/tier1/logs" +log="$log_dir/$label.log" +mkdir -p "$log_dir" + +"$@" >"$log" 2>&1 +rc=$? +if [ "$rc" -ne 0 ]; then + printf 'T1 %s failed; tail of %s:\n' "$label" "$log" >&2 + tail -100 "$log" >&2 || true + exit "$rc" +fi +printf 'T1 %-18s OK\n' "$label" diff --git a/test/tier1/system/README.md b/test/tier1/system/README.md @@ -0,0 +1,30 @@ +# Tier 1 System Sentinels + +These scripts are thin wrappers around existing cheap make targets or driver +scripts. They are intended to catch broad system regressions quickly without +pulling in cross-provisioning, VM, ecosystem, libc, or release suites. + +Each wrapper redirects command output into +`build/test/tier1/logs/system/<area>/` and prints the log path. On failure it +tails the captured log. + +## Coverage + +- `tools.sh` runs existing cheap tool-driver sentinels: `test-driver-tools` + (`xxd`, `cmp`, `hash`, `disas`, `mc`, `compress`, `image`), + `test-driver-strings`, and `test-driver-objdump`. +- `runtime-headers.sh` runs `test-rt-headers`, compiling `test/rt/smoke.c` + against the runtime headers for the cheap configured target triples. +- `debug-dwarf.sh` runs DWARF reader and debug producer unit targets, one + committed `objdump --dwarf` golden, and the lightweight `kit dbg` help + transcript. +- `dist.sh` runs the local CAS and package driver suites through + `test-driver-cas` and `test-driver-pkg`. + +## Deferred + +The intentionally deferred suites are the heavier runtime execution and +symbolization sweeps (`test-rt-runtime`, `test-rt-backtrace`), full debugger +transcript matrix (`test-dbg`), self-distribution/update flow +(`test-selfdist`), release artifact smoke, libc containers, ecosystem builds, +and port/selfhost/cross-provisioning. diff --git a/test/tier1/system/debug-dwarf.sh b/test/tier1/system/debug-dwarf.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# Tier 1 system sentinel for debug producer/consumer paths and a tiny dbg smoke. + +set -u + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../../.." && pwd) +manifest="$repo_root/test/tier1/manifests/system/debug-dwarf.txt" +log_dir="${KIT_TIER1_LOG_DIR:-$repo_root/build/test/tier1/logs/system/debug-dwarf}" + +mkdir -p "$log_dir" + +run_step() { + name=$1 + shift + log="$log_dir/$name.log" + printf 'tier1/debug-dwarf: %s\n' "$name" + if "$@" > "$log" 2>&1; then + printf ' ok: %s\n' "$log" + return 0 + fi + rc=$? + printf ' FAIL: %s (exit %s)\n' "$log" "$rc" >&2 + tail -n 80 "$log" >&2 + return "$rc" +} + +run_objdump_dwarf() { + name=objdump-dwarf + log="$log_dir/$name.log" + actual="$log_dir/$name.actual" + expected="$repo_root/test/objdump/dwarf/cases/01-dwarf-all.expected" + case_sh="$repo_root/test/objdump/dwarf/cases/01-dwarf-all.sh" + + printf 'tier1/debug-dwarf: %s\n' "$name" + ( + cd "$log_dir" || exit 1 + KIT="$repo_root/build/kit" sh "$case_sh" > "$actual" + ) > "$log" 2>&1 + rc=$? + if [ "$rc" -eq 0 ]; then + diff -u "$expected" "$actual" >> "$log" 2>&1 + rc=$? + fi + if [ "$rc" -eq 0 ]; then + printf ' ok: %s\n' "$log" + return 0 + fi + printf ' FAIL: %s (exit %s)\n' "$log" "$rc" >&2 + tail -n 80 "$log" >&2 + return "$rc" +} + +if [ ! -f "$manifest" ]; then + printf 'tier1/debug-dwarf: manifest missing: %s\n' "$manifest" >&2 + exit 2 +fi + +run_step bin make -C "$repo_root" bin || exit $? +run_step test-dwarf make -C "$repo_root" test-dwarf || exit $? +run_step test-debug make -C "$repo_root" test-debug || exit $? +run_objdump_dwarf || exit $? +run_step dbg-repl-help env KIT="$repo_root/build/kit" DBG_CASE=repl-help \ + sh "$repo_root/test/dbg/run.sh" diff --git a/test/tier1/system/dist.sh b/test/tier1/system/dist.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# Tier 1 system sentinel for local distribution primitives. + +set -u + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../../.." && pwd) +manifest="$repo_root/test/tier1/manifests/system/dist.txt" +log_dir="${KIT_TIER1_LOG_DIR:-$repo_root/build/test/tier1/logs/system/dist}" + +mkdir -p "$log_dir" + +run_step() { + name=$1 + shift + log="$log_dir/$name.log" + printf 'tier1/dist: %s\n' "$name" + if "$@" > "$log" 2>&1; then + printf ' ok: %s\n' "$log" + return 0 + fi + rc=$? + printf ' FAIL: %s (exit %s)\n' "$log" "$rc" >&2 + tail -n 80 "$log" >&2 + return "$rc" +} + +if [ ! -f "$manifest" ]; then + printf 'tier1/dist: manifest missing: %s\n' "$manifest" >&2 + exit 2 +fi + +while IFS= read -r target || [ -n "$target" ]; do + target=${target%%#*} + target=$(printf '%s' "$target" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + [ -n "$target" ] || continue + run_step "$target" make -C "$repo_root" "$target" || exit $? +done < "$manifest" diff --git a/test/tier1/system/runtime-headers.sh b/test/tier1/system/runtime-headers.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Tier 1 system sentinel for runtime headers across the cheap target set. + +set -u + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../../.." && pwd) +manifest="$repo_root/test/tier1/manifests/system/runtime-headers.txt" +log_dir="${KIT_TIER1_LOG_DIR:-$repo_root/build/test/tier1/logs/system/runtime-headers}" + +mkdir -p "$log_dir" + +run_step() { + name=$1 + shift + log="$log_dir/$name.log" + printf 'tier1/runtime-headers: %s\n' "$name" + if "$@" > "$log" 2>&1; then + printf ' ok: %s\n' "$log" + return 0 + fi + rc=$? + printf ' FAIL: %s (exit %s)\n' "$log" "$rc" >&2 + tail -n 80 "$log" >&2 + return "$rc" +} + +if [ ! -f "$manifest" ]; then + printf 'tier1/runtime-headers: manifest missing: %s\n' "$manifest" >&2 + exit 2 +fi + +run_step test-rt-headers make -C "$repo_root" test-rt-headers diff --git a/test/tier1/system/tools.sh b/test/tier1/system/tools.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# Tier 1 system sentinel for cheap driver utility tools. + +set -u + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../../.." && pwd) +manifest="$repo_root/test/tier1/manifests/system/tools.txt" +log_dir="${KIT_TIER1_LOG_DIR:-$repo_root/build/test/tier1/logs/system/tools}" + +mkdir -p "$log_dir" + +run_step() { + name=$1 + shift + log="$log_dir/$name.log" + printf 'tier1/tools: %s\n' "$name" + if "$@" > "$log" 2>&1; then + printf ' ok: %s\n' "$log" + return 0 + fi + rc=$? + printf ' FAIL: %s (exit %s)\n' "$log" "$rc" >&2 + tail -n 80 "$log" >&2 + return "$rc" +} + +if [ ! -f "$manifest" ]; then + printf 'tier1/tools: manifest missing: %s\n' "$manifest" >&2 + exit 2 +fi + +while IFS= read -r target || [ -n "$target" ]; do + target=${target%%#*} + target=$(printf '%s' "$target" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + [ -n "$target" ] || continue + run_step "$target" make -C "$repo_root" "$target" || exit $? +done < "$manifest"