commit ee3139b5876283352651f76efaaa65a93bdfcf10 parent d60e3e32916334532d527101871f8f429019786a Author: Ryan Sepassi <rsepassi@gmail.com> Date: Wed, 6 May 2026 13:02:28 -0700 AX: tests own their Makefile + helpers; fixtures normalized to NNN- Move the test infrastructure into tests/, leaving the top-level Makefile bootstrap-only: - tests/Makefile holds the legacy build rules (M1pp/hex2pp/scheme1/cc, mes-libc, tcc-boot2/tcc-tcc/tcc-tcc-tcc) and the suite dispatch. Top-level sets REPO_ROOT and `include`s it; `make -C tests` standalone detects the missing REPO_ROOT and re-execs the parent. - tests/lib-runner.sh: shared helpers (discover, report, fail, show_diff, compare_runtime, read_expected). Sourced by tests/run-suite.sh. - scripts/{run-tests,boot-run-tests,boot-build-p1,boot-build-p1pp, boot-build-cc}.sh moved under tests/ with the boot- prefix dropped. - scripts/seed-accept{,-boot34,-boot5}.sh collapsed into one tests/seed-accept.sh with mode arg (kernel | boot34 | boot5). - Test fixtures normalized to NNN- prefix: cc-cg / cc-libc / cc-lex / cc-pp / cc-util / M1pp migrated NN- → NNN-, tests/P1 (was unprefixed) got sequential NNN- prefixes assigned alphabetically. - Golden-file convention: .expected-toks renamed to .expected in cc-lex / cc-pp; the suite contract still differentiates by pipeline. - tests/README.md documents the per-suite contract, naming rule, and how to add fixtures. Diffstat:
418 files changed, 2151 insertions(+), 1975 deletions(-)
diff --git a/Makefile b/Makefile @@ -274,287 +274,6 @@ CLOC_FILES := \ cloc: @sh scripts/count-lines.sh $(CLOC_FILES) -# ── Tests container image (boot2-busybox; AX-bound) ────────────────────── - -IMAGE_STAMP := build/$(ARCH)/.image -IMAGE_STAMPS := $(foreach a,$(ALL_ARCHES),build/$(a)/.image) - -image: $(IMAGE_STAMP) - -$(IMAGE_STAMPS): build/%/.image: scripts/Containerfile.busybox - mkdir -p $(@D) - podman build --platform $(PLATFORM_$*) -t boot2-busybox:$* \ - -f scripts/Containerfile.busybox scripts/ - @touch $@ - -# ── Test infrastructure (KEEP for now; AX moves to tests/Makefile) ─────── -# -# All rules below this line build artifacts under the legacy layout -# (build/<arch>/{tools,M1pp,hex2pp,scheme1,cc,vendor,tcc-*}/...) used by -# scripts/run-tests.sh + scripts/boot-run-tests.sh. They are NOT part of -# the bootN.sh chain. The plan is to move them out of this Makefile in -# phase AX (see docs/PLAN.md). Kept here unchanged so `make test` keeps -# working between phases. - -PODMAN = podman run --rm --pull=never --platform $(PLATFORM_$(1)) \ - --tmpfs /tmp:size=512M \ - -e ARCH=$(1) \ - -v $(CURDIR):/work -w /work boot2-busybox:$(1) - -# Stage 1: vendored hex0-seed -> M0/hex2-0/catm in the container. -build/%/tools/M0 build/%/tools/catm build/%/tools/hex2-0 &: \ - scripts/mk-seed-tools.sh build/%/.image \ - vendor/seed/%/hex0-seed vendor/seed/%/hex0.hex0 \ - vendor/seed/%/hex1.hex0 vendor/seed/%/hex2.hex1 \ - vendor/seed/%/catm.hex2 vendor/seed/%/M0.hex2 \ - vendor/seed/%/ELF.hex2 - $(call PODMAN,$*) sh scripts/mk-seed-tools.sh - -tools: $(foreach a,$(ALL_ARCHES),build/$(a)/tools/M0) - -# Pre-pruned P1 backend tables. Generated once from P1/gen/p1_gen.py and -# committed under P1/P1-<arch>.M1; routine builds skip the prune step. -P1_PRUNE_SRCS := M1pp/M1pp.P1 hex2pp/hex2pp.P1 $(wildcard tests/P1/*.P1) - -tables: $(foreach a,$(ALL_ARCHES),P1/P1-$(a).M1) - -build/%/P1/P1.M1: $(wildcard P1/gen/*.py) - mkdir -p $(@D) - python3 P1/gen/p1_gen.py --arch $* --out $@ - -.SECONDARY: $(foreach a,$(ALL_ARCHES),build/$(a)/P1/P1.M1) - -P1/P1-%.M1: build/%/P1/P1.M1 scripts/prune-p1-table.sh $(P1_PRUNE_SRCS) - sh scripts/prune-p1-table.sh $< $@ $(P1_PRUNE_SRCS) - -# Legacy per-arch programs (used by tests). -M1PP_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/M1pp/M1pp) -HEX2PP_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/hex2pp/hex2pp) -SCHEME1_SRC := scheme1/scheme1.P1pp -SCHEME1_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/scheme1/scheme1) - -CC_SRCS := scheme1/prelude.scm cc/cc.scm cc/main.scm -CC_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/cc/cc.scm) - -P1_BUILD_DEPS = scripts/lint.sh scripts/boot-build-p1.sh \ - build/%/.image build/%/tools/M0 \ - vendor/seed/%/ELF.hex2 P1/P1-%.M1 - -P1PP_BUILD_DEPS = scripts/boot-build-p1pp.sh \ - build/%/.image \ - build/%/M1pp/M1pp build/%/hex2pp/hex2pp \ - vendor/seed/%/ELF.hex2 \ - P1/P1-%.M1pp P1/P1.M1pp P1/P1pp.P1pp - -ALPINE_GCC_IMAGES := $(foreach a,$(ALL_ARCHES),build/$(a)/.image-alpine-gcc) - -$(ALPINE_GCC_IMAGES): build/%/.image-alpine-gcc: scripts/Containerfile.alpine-gcc - mkdir -p $(@D) - podman build --platform $(PLATFORM_$*) \ - -t boot2-alpine-gcc:$* \ - -f scripts/Containerfile.alpine-gcc scripts/ - @touch $@ - -ALPINE_GCC = podman run --rm --pull=never --platform $(PLATFORM_$(1)) \ - --tmpfs /tmp:size=128M \ - -e ARCH=$(1) \ - -v $(CURDIR):/work -w /work boot2-alpine-gcc:$(1) - -$(M1PP_BINS): build/%/M1pp/M1pp: M1pp/M1pp.P1 $(P1_BUILD_DEPS) - ARCH=$* sh scripts/lint.sh M1pp/M1pp.P1 - $(call PODMAN,$*) sh scripts/boot-build-p1.sh M1pp/M1pp.P1 $@ - -$(HEX2PP_BINS): build/%/hex2pp/hex2pp: hex2pp/hex2pp.P1 $(P1_BUILD_DEPS) - ARCH=$* sh scripts/lint.sh hex2pp/hex2pp.P1 - $(call PODMAN,$*) sh scripts/boot-build-p1.sh hex2pp/hex2pp.P1 $@ - -$(SCHEME1_BINS): build/%/scheme1/scheme1: $(SCHEME1_SRC) $(P1PP_BUILD_DEPS) - $(call PODMAN,$*) sh scripts/boot-build-p1pp.sh $@ $(SCHEME1_SRC) - -$(CC_BINS): build/%/cc/cc.scm: $(CC_SRCS) build/%/.image build/%/tools/catm - mkdir -p $(@D) - $(call PODMAN,$*) build/$*/tools/catm $@ $(CC_SRCS) - -# ── tcc-boot2 chain (tests-only; AX-bound) ─────────────────────────────── - -TCC_TARGET_aarch64 := ARM64 -TCC_TARGET_amd64 := X86_64 -TCC_TARGET_riscv64 := RISCV64 -TCC_TARGET ?= $(TCC_TARGET_$(ARCH)) -TCC_VENDOR := build/$(ARCH)/vendor/tcc -TCC_FLAT := $(TCC_VENDOR)/tcc.flat.c -TCC_FLATS := $(foreach a,$(ALL_ARCHES),build/$(a)/vendor/tcc/tcc.flat.c) - -TCC_BOOT2_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/tcc-boot2/tcc-boot2) -TCC_BOOT2_P1PPS := $(foreach a,$(ALL_ARCHES),build/$(a)/tcc-boot2/tcc.flat.P1pp) - -LIBC_FLATS := $(foreach a,$(ALL_ARCHES),build/$(a)/vendor/mes-libc/libc.flat.c) -LIBC_P1PPS := $(foreach a,$(ALL_ARCHES),build/$(a)/vendor/mes-libc/libc.P1pp) - -$(TCC_FLATS): build/%/vendor/tcc/tcc.flat.c: scripts/stage1-flatten.sh - sh scripts/stage1-flatten.sh --arch $* - -build/%/vendor/tcc/stdarg-bridge.h: build/%/vendor/tcc/tcc.flat.c - -LIBC_VENDOR_SRCS := $(shell find vendor/mes-libc -type f \( -name '*.c' -o -name '*.h' \) 2>/dev/null) \ - $(wildcard vendor/mes-libc/patches/*.before) \ - $(wildcard vendor/mes-libc/patches/*.after) - -$(LIBC_FLATS): build/%/vendor/mes-libc/libc.flat.c: \ - scripts/libc-flatten.sh build/%/vendor/tcc/stdarg-bridge.h $(LIBC_VENDOR_SRCS) - sh scripts/libc-flatten.sh --arch $* - -CC_DEBUG ?= 0 -CC_TRACE_EMIT ?= 0 - -$(LIBC_P1PPS): build/%/vendor/mes-libc/libc.P1pp: \ - build/%/vendor/mes-libc/libc.flat.c \ - build/%/scheme1/scheme1 build/%/cc/cc.scm \ - scripts/boot-build-cc.sh build/%/.image - $(call PODMAN,$*) env CC_LIB=libc__ \ - CC_DEBUG=$(CC_DEBUG) CC_TRACE_EMIT=$(CC_TRACE_EMIT) \ - sh scripts/boot-build-cc.sh $< $@ - -$(TCC_BOOT2_P1PPS): build/%/tcc-boot2/tcc.flat.P1pp: \ - build/%/vendor/tcc/tcc.flat.c build/%/scheme1/scheme1 build/%/cc/cc.scm \ - scripts/boot-build-cc.sh build/%/.image - $(call PODMAN,$*) env CC_LIB=tcc__ \ - CC_DEBUG=$(CC_DEBUG) CC_TRACE_EMIT=$(CC_TRACE_EMIT) \ - sh scripts/boot-build-cc.sh build/$*/vendor/tcc/tcc.flat.c $@ - -$(TCC_BOOT2_BINS): build/%/tcc-boot2/tcc-boot2: \ - build/%/tcc-boot2/tcc.flat.P1pp build/%/vendor/mes-libc/libc.P1pp \ - P1/entry-libc.P1pp P1/elf-end.P1pp \ - $(P1PP_BUILD_DEPS) - $(call PODMAN,$*) env WORK_SUBPATH=tcc-boot2/tcc-boot2 \ - sh scripts/boot-build-p1pp.sh $@ \ - P1/entry-libc.P1pp build/$*/vendor/mes-libc/libc.P1pp \ - $< P1/elf-end.P1pp - -# ── tcc-gcc: same flatten, stock gcc (tests-only sanity check) ─────────── - -TCC_GCC_BIN := build/$(ARCH)/tcc-gcc/tcc-gcc -TCC_GCC_IMAGE := build/$(ARCH)/.image-alpine-gcc -TCC_GCC_HARNESS := tcc-gcc/$(ARCH)/start.S tcc-gcc/$(ARCH)/sys_stubs.c - -$(TCC_GCC_BIN): $(TCC_FLAT) build/$(ARCH)/vendor/mes-libc/libc.flat.c \ - $(TCC_GCC_HARNESS) scripts/build-tcc-gcc.sh $(TCC_GCC_IMAGE) - mkdir -p $(@D) - podman run --rm --pull=never --platform $(PLATFORM_$(ARCH)) \ - -e ARCH=$(ARCH) \ - -v $(CURDIR):/work -w /work boot2-alpine-gcc:$(ARCH) \ - sh scripts/build-tcc-gcc.sh $@ $(TCC_FLAT) \ - build/$(ARCH)/vendor/mes-libc/libc.flat.c - -# ── tcc-cc / tcc-libc / tcc-tcc / tcc-tcc-tcc test infrastructure ──────── - -HOST_CC ?= cc - -TCC_HARNESS_ARCHES := aarch64 amd64 riscv64 - -HOST_CC_TARGET_aarch64 := aarch64-linux-gnu -HOST_CC_TARGET_amd64 := x86_64-linux-gnu -HOST_CC_TARGET = $(HOST_CC_TARGET_$(ARCH)) - -ifeq ($(ARCH),aarch64) -TCC_ASM_DEPS := build/$(ARCH)/tcc-boot2/tcc-boot2 build/$(ARCH)/.image -TCC_ASM = $(call PODMAN,$(ARCH)) build/$(ARCH)/tcc-boot2/tcc-boot2 -nostdlib -c -o $(1) $(2) -else ifeq ($(ARCH),riscv64) -TCC_ASM_DEPS := build/$(ARCH)/.image-alpine-gcc -TCC_ASM = $(call ALPINE_GCC,$(ARCH)) cc -c -o $(1) -x assembler $(2) -else -TCC_ASM_DEPS := -TCC_ASM = $(HOST_CC) -target $(HOST_CC_TARGET) -c -o $(1) -x assembler $(2) -endif - -TCC_CC_START := build/$(ARCH)/tcc-cc/start.o -TCC_CC_MEM := build/$(ARCH)/tcc-cc/mem.o -TCC_CC_TCC_INCLUDE = $(TCC_VENDOR)/$(TCC_PKG)/include -TCC_CC_TCC_LIBDIR = $(TCC_VENDOR)/$(TCC_PKG)/lib - -ifeq ($(ARCH),amd64) -TCC_CC_VA_LIST := build/$(ARCH)/tcc-cc/va_list.o -else -TCC_CC_VA_LIST := -endif - -TCC_LIBC_DIR := build/$(ARCH)/tcc-libc -TCC_LIBC_START := $(TCC_LIBC_DIR)/start.o -TCC_LIBC_SYS_STUBS := $(TCC_LIBC_DIR)/sys_stubs.o -TCC_LIBC_MEM := $(TCC_LIBC_DIR)/mem.o -TCC_LIBC_LIBC := $(TCC_LIBC_DIR)/libc.o - -TCC_TCC_BIN := build/$(ARCH)/tcc-tcc/tcc-tcc -TCC_TCC_TCC_BIN := build/$(ARCH)/tcc-tcc-tcc/tcc-tcc-tcc - -$(TCC_CC_START): tcc-cc/$(ARCH)/start.S $(TCC_ASM_DEPS) - mkdir -p $(@D) - $(call TCC_ASM,$@,$<) - -$(TCC_CC_MEM): tcc-cc/mem.c \ - build/$(ARCH)/tcc-boot2/tcc-boot2 \ - build/$(ARCH)/.image - mkdir -p $(@D) - $(call PODMAN,$(ARCH)) \ - build/$(ARCH)/tcc-boot2/tcc-boot2 \ - -nostdlib -I $(TCC_CC_TCC_INCLUDE) -c -o $@ $< - -build/amd64/tcc-cc/va_list.o: \ - build/amd64/tcc-boot2/tcc-boot2 \ - build/amd64/.image build/amd64/vendor/tcc/tcc.flat.c - mkdir -p $(@D) - $(call PODMAN,amd64) \ - build/amd64/tcc-boot2/tcc-boot2 \ - -nostdlib -I build/amd64/vendor/tcc/$(TCC_PKG)/include \ - -D TCC_TARGET_X86_64=1 \ - -c -o $@ build/amd64/vendor/tcc/$(TCC_PKG)/lib/va_list.c - -$(TCC_LIBC_START): tcc-libc/$(ARCH)/start.S $(TCC_ASM_DEPS) - mkdir -p $(@D) - $(call TCC_ASM,$@,$<) - -$(TCC_LIBC_SYS_STUBS): tcc-libc/$(ARCH)/sys_stubs.S $(TCC_ASM_DEPS) - mkdir -p $(@D) - $(call TCC_ASM,$@,$<) - -$(TCC_LIBC_MEM): tcc-cc/mem.c \ - build/$(ARCH)/tcc-boot2/tcc-boot2 \ - build/$(ARCH)/.image - mkdir -p $(@D) - $(call PODMAN,$(ARCH)) \ - build/$(ARCH)/tcc-boot2/tcc-boot2 \ - -nostdlib -I $(TCC_CC_TCC_INCLUDE) -c -o $@ $< - -$(TCC_LIBC_LIBC): build/$(ARCH)/vendor/mes-libc/libc.flat.c \ - build/$(ARCH)/tcc-boot2/tcc-boot2 \ - build/$(ARCH)/.image - mkdir -p $(@D) - $(call PODMAN,$(ARCH)) \ - build/$(ARCH)/tcc-boot2/tcc-boot2 \ - -nostdlib -I $(TCC_CC_TCC_INCLUDE) \ - -include $(TCC_CC_TCC_INCLUDE)/stdarg.h \ - -c -o $@ $< - -$(TCC_TCC_BIN): scripts/boot-build-tcc-tcc.sh \ - $(TCC_FLAT) \ - build/$(ARCH)/tcc-boot2/tcc-boot2 \ - $(TCC_LIBC_START) $(TCC_LIBC_SYS_STUBS) \ - $(TCC_LIBC_MEM) $(TCC_LIBC_LIBC) \ - build/$(ARCH)/.image - mkdir -p $(@D) - $(call PODMAN,$(ARCH)) \ - sh scripts/boot-build-tcc-tcc.sh $@ - -$(TCC_TCC_TCC_BIN): scripts/boot-build-tcc-tcc.sh \ - $(TCC_FLAT) \ - $(TCC_TCC_BIN) \ - $(TCC_LIBC_START) $(TCC_LIBC_SYS_STUBS) \ - $(TCC_LIBC_MEM) $(TCC_LIBC_LIBC) \ - build/$(ARCH)/.image - mkdir -p $(@D) - $(call PODMAN,$(ARCH)) \ - sh scripts/boot-build-tcc-tcc.sh $@ $(TCC_TCC_BIN) - # ── Native tools (opt-in dev-loop helpers) ─────────────────────────────── NATIVE_TOOLS := build/native-tools/M1 build/native-tools/hex2 \ @@ -574,101 +293,12 @@ build/native-tools/m1pp: scripts/build-native-tools.sh M1pp/M1pp.c build/native-tools/hex2pp: scripts/build-native-tools.sh hex2pp/hex2pp.c sh scripts/build-native-tools.sh hex2pp -# ── test target (proxies to scripts/run-tests.sh) ──────────────────────── - -SUITE ?= - -ifeq ($(origin ARCH),file) - ARCH_FILTER := - TEST_ARCHES := $(ALL_ARCHES) -else - ARCH_FILTER := $(ARCH) - TEST_ARCHES := $(ARCH) -endif - -TEST_M1PP_DEPS := $(foreach a,$(TEST_ARCHES), \ - build/$(a)/.image build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \ - vendor/seed/$(a)/ELF.hex2) - -TEST_P1_DEPS := $(foreach a,$(TEST_ARCHES), \ - build/$(a)/.image build/$(a)/tools/M0 P1/P1-$(a).M1 \ - build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp vendor/seed/$(a)/ELF.hex2) - -TEST_SCHEME1_DEPS := $(foreach a,$(TEST_ARCHES), \ - build/$(a)/.image build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \ - build/$(a)/scheme1/scheme1 build/$(a)/tools/catm) - -TEST_CC_UNIT_DEPS := $(foreach a,$(TEST_ARCHES), \ - build/$(a)/.image build/$(a)/tools/catm \ - build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \ - build/$(a)/scheme1/scheme1) - -TEST_CC_DEPS := $(TEST_CC_UNIT_DEPS) \ - $(foreach a,$(TEST_ARCHES),build/$(a)/cc/cc.scm) - -TEST_CC_LIBC_DEPS := $(TEST_CC_DEPS) \ - $(foreach a,$(TEST_ARCHES),build/$(a)/vendor/mes-libc/libc.P1pp) \ - P1/entry-libc.P1pp P1/elf-end.P1pp - -TEST_TCC_CC_DEPS := build/$(ARCH)/.image \ - $(TCC_TCC_BIN) $(TCC_TCC_TCC_BIN) \ - $(TCC_CC_START) $(TCC_CC_MEM) $(TCC_CC_VA_LIST) - -TEST_TCC_LIBC_DEPS := build/$(ARCH)/.image \ - $(TCC_TCC_BIN) $(TCC_TCC_TCC_BIN) \ - $(TCC_LIBC_START) $(TCC_LIBC_SYS_STUBS) $(TCC_LIBC_MEM) $(TCC_LIBC_LIBC) \ - $(TCC_CC_VA_LIST) - -test: -ifeq ($(SUITE),) - @$(MAKE) --no-print-directory test SUITE=m1pp - @$(MAKE) --no-print-directory test SUITE=p1 - @$(MAKE) --no-print-directory test SUITE=scheme1 - @$(MAKE) --no-print-directory test SUITE=cc-util - @$(MAKE) --no-print-directory test SUITE=cc-lex - @$(MAKE) --no-print-directory test SUITE=cc-pp - @$(MAKE) --no-print-directory test SUITE=cc-cg - @$(MAKE) --no-print-directory test SUITE=cc - @$(MAKE) --no-print-directory test SUITE=cc-libc -else ifeq ($(SUITE),m1pp) - @$(MAKE) --no-print-directory $(TEST_M1PP_DEPS) - sh scripts/run-tests.sh --suite=m1pp $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) -else ifeq ($(SUITE),p1) - @$(MAKE) --no-print-directory $(TEST_P1_DEPS) - sh scripts/run-tests.sh --suite=p1 $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) -else ifeq ($(SUITE),scheme1) - @$(MAKE) --no-print-directory $(TEST_SCHEME1_DEPS) - sh scripts/run-tests.sh --suite=scheme1 $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) -else ifeq ($(filter $(SUITE),cc-util cc-lex cc-pp cc-cg),$(SUITE)) - @$(MAKE) --no-print-directory $(TEST_CC_UNIT_DEPS) - sh scripts/run-tests.sh --suite=$(SUITE) $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) -else ifeq ($(SUITE),cc) - @$(MAKE) --no-print-directory $(TEST_CC_DEPS) - sh scripts/run-tests.sh --suite=cc $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) -else ifeq ($(SUITE),cc-libc) - @$(MAKE) --no-print-directory $(TEST_CC_LIBC_DEPS) - sh scripts/run-tests.sh --suite=cc-libc $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) -else ifeq ($(SUITE),cc-ext) - @$(MAKE) --no-print-directory $(TEST_CC_LIBC_DEPS) - sh scripts/run-tests.sh --suite=cc-ext $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) -else ifeq ($(SUITE),tcc-cc) - @if [ -z "$(filter $(ARCH),$(TCC_HARNESS_ARCHES))" ]; then \ - echo "tcc-cc supports ARCH in {$(TCC_HARNESS_ARCHES)} only (got '$(ARCH)')" >&2; exit 2; \ - fi - @$(MAKE) --no-print-directory ARCH=$(ARCH) $(TEST_TCC_CC_DEPS) - @s2=0; s3=0; \ - sh scripts/run-tests.sh --suite=tcc-cc --arch=$(ARCH) --stage=2 $(NAMES) || s2=$$?; \ - sh scripts/run-tests.sh --suite=tcc-cc --arch=$(ARCH) --stage=3 $(NAMES) || s3=$$?; \ - [ $$s2 -eq 0 ] && [ $$s3 -eq 0 ] -else ifeq ($(SUITE),tcc-libc) - @if [ -z "$(filter $(ARCH),$(TCC_HARNESS_ARCHES))" ]; then \ - echo "tcc-libc supports ARCH in {$(TCC_HARNESS_ARCHES)} only (got '$(ARCH)')" >&2; exit 2; \ - fi - @$(MAKE) --no-print-directory ARCH=$(ARCH) $(TEST_TCC_LIBC_DEPS) - @s2=0; s3=0; \ - sh scripts/run-tests.sh --suite=tcc-libc --arch=$(ARCH) --stage=2 $(NAMES) || s2=$$?; \ - sh scripts/run-tests.sh --suite=tcc-libc --arch=$(ARCH) --stage=3 $(NAMES) || s3=$$?; \ - [ $$s2 -eq 0 ] && [ $$s3 -eq 0 ] -else - @echo "unknown SUITE='$(SUITE)' (m1pp | p1 | scheme1 | cc-util | cc-lex | cc-pp | cc-cg | cc | cc-libc | cc-ext | tcc-cc | tcc-libc)" >&2; exit 2 -endif +# ── Test infrastructure (suites + their build deps) ────────────────────── +# tests/Makefile owns the legacy test-build rules (M1pp/hex2pp/scheme1/cc +# self-hosted binaries, mes-libc, tcc-boot2, tcc-tcc/tcc-tcc-tcc) and the +# `test` / `image` / `tools` / `tables` targets. ARCH/DRIVER and per-arch +# metadata above are visible to the include. REPO_ROOT signals to +# tests/Makefile that it is being included from the top-level (rather +# than invoked standalone via `make -C tests`). +REPO_ROOT := $(CURDIR) +include tests/Makefile diff --git a/docs/PLAN.md b/docs/PLAN.md @@ -361,37 +361,47 @@ land" wording); audit found that wrong (AT.4 is a pure refactor). --- -### AX. tests/ Makefile +### [DONE] AX. tests/ Makefile **Goal.** Tests are a separate concern with their own Makefile. Top-level build is decoupled from test dispatch. -**Touch list.** -- New: `tests/Makefile`. Targets per suite (`test-cc`, `test-cc-libc`, - `test-cc-cg`, `test-cc-lex`, `test-cc-pp`, `test-cc-util`, - `test-M1pp`, `test-p1`, `test-scheme1`, `test-cc-ext`, `test-tcc-cc`, - `test-tcc-libc`) plus aggregate `test`. -- New: `tests/lib-runner.sh` (or fold into `scripts/lib-test.sh`). - Helpers: `discover_fixtures`, `run_diff_text`, `run_diff_bytes`, - `report_pass_fail`. Refactor `scripts/boot-run-tests.sh` (818 lines) - to call these. -- Top-level `Makefile`: `include tests/Makefile`. Drop the test-dispatch - branch (lines 680–732 currently). `make test` proxies to the tests - Makefile. -- Collapse `scripts/seed-accept{,-boot34,-boot5}.sh` into one - parameterized script under `tests/` (or `scripts/`, but called from - `tests/Makefile`). -- Test-naming consistency: pick one of `NNN-`, `NN-`, or unprefixed and - apply across `tests/cc`, `tests/cc-libc`, `tests/M1pp`, `tests/p1`. - Rename freely. -- Golden-file convention: pick one of `.expected` (stdout) / - `.expected-exit` (exit code) / `.expected-bytes` (binary). Rename - freely. -- New: `tests/README.md` documenting the per-suite contract. - -**Validation.** `make test` from repo root green on default arch. -`make -C tests test-cc` works in isolation. `tests/Makefile` is -self-contained. +**Status note.** Landed as a single phase. Per-suite split happened via +`SUITE=<name>` dispatch (kept to minimize churn) rather than per-target +phony names like `test-cc`; `make test SUITE=cc` and `make test` (all) +both resolve through `tests/Makefile`. The standalone `make -C tests` +entry detects a missing `REPO_ROOT` and re-execs the top-level Makefile +with goal forwarding. + +**Touch list (landed).** +- `tests/Makefile`: includes the legacy test-build rules (M1pp/hex2pp/ + scheme1/cc/tcc-boot2/tcc-tcc/tcc-tcc-tcc/mes-libc) and the suite + dispatch. Top-level `Makefile` sets `REPO_ROOT := $(CURDIR)` and + `include tests/Makefile`. +- `tests/lib-runner.sh`: `discover`, `report`, `show_diff`, `fail`, + `compare_runtime`, `read_expected`. Sourced by `tests/run-suite.sh`. +- Moved `scripts/{run-tests,boot-run-tests,boot-build-p1,boot-build-p1pp, + boot-build-cc,seed-accept*}.sh` → `tests/{run,run-suite,build-p1, + build-p1pp,build-cc,seed-accept}.sh`. The three seed-accept scripts + collapsed into one with a `kernel | boot34 | boot5` mode arg. +- `boot-build-p1{,pp}.sh` retirement was scoped to "move under tests/" + rather than full deletion: they are still pure transformations the + test path needs (single P1pp → ELF), and the bootN.sh chain doesn't + expose an equivalent. +- Test fixtures normalized to `NNN-` prefix everywhere. `cc-cg`, + `cc-libc`, `cc-lex`, `cc-pp`, `cc-util`, `M1pp` migrated NN- → NNN-; + `tests/P1` (was unprefixed) got sequential NNN- prefixes assigned + alphabetically. +- Golden-file convention unified to `.expected` (primary output) / + `.expected-exit` (exit code). `.expected-toks` renamed → `.expected` + in `cc-lex`/`cc-pp`; the suite contract still differentiates by + pipeline (lex/pp pipelines emit token streams to stdout). +- `tests/README.md` documents the per-suite contract, fixture-naming + rule, and how to add fixtures. + +**Validation.** `make test` from repo root green on the default arch. +`make -C tests <target>` delegates back to the parent. `tests/Makefile` +is self-contained when `REPO_ROOT` is exported. --- diff --git a/docs/SEED-VIRTIO-BLK.md b/docs/SEED-VIRTIO-BLK.md @@ -282,7 +282,8 @@ order; no dual paths in the tree at any commit boundary. `scripts/extract-dump.sh`. `dump_tmpfs_blk` runs unconditionally from `sys_exit_final` before PSCI off. 4. **Acceptance.** Run `tier1-gate.sh`, `tier2-gate.sh`, - `seed-accept.sh`, `seed-accept-boot34.sh`, `seed-accept-boot5.sh`. + `tests/seed-accept.sh`, `tests/seed-accept.sh boot34`, + `tests/seed-accept.sh boot5`. All must produce byte-identical artifacts to the prior (cpio+dumpfs) tree at `HEAD~1`. Expect boot5 to surface any off-by-one in the directory table fastest (≈3900 tmpfs entries). diff --git a/scripts/boot-build-cc.sh b/scripts/boot-build-cc.sh @@ -1,52 +0,0 @@ -#!/bin/sh -## boot-build-cc.sh — in-container .c -> .P1pp via scheme1 + cc.scm. -## -## Pure transformation. Caller (the Makefile) ensures every fixed-path -## input below already exists: the per-arch scheme1 ELF and the catm'd -## cc.scm source. Mirrors boot-build-p1pp.sh's contract: env-driven, -## one thing only, no host work. -## -## Env: ARCH=aarch64|amd64|riscv64 -## CC_DEBUG=1 (optional) — pass --cc-debug to cc.scm so it prints -## per-phase heap usage on stderr. -## CC_TRACE_EMIT=1 (optional) — pass --cc-trace-emit so cc.scm -## wraps every emitted function with a `%trace(<mangled>)` -## call at entry. Pair with libp1pp's %trace macro and -## libp1pp__trace runtime helper (in P1/P1pp.P1pp) to -## produce a stderr line per function entry at runtime. -## CC_LIB=PFX (optional) — compile in library mode (cc.scm -## --lib=PFX). Skips cc.scm's auto-emitted entry -## stub and trailing :ELF_end so the output catm's -## into a larger link, and namespaces anonymous -## string labels as PFX+"cc__str_N" to avoid -## collisions with other cc.scm outputs in the -## same chain. The catm caller then prepends -## P1/entry-{plain,libc}.P1pp and appends -## P1/elf-end.P1pp exactly once each. -## Usage: boot-build-cc.sh <src.c> <out.P1pp> - -set -eu - -: "${ARCH:?ARCH must be set}" -[ "$#" -eq 2 ] || { echo "usage: ARCH=<arch> $0 <src> <out>" >&2; exit 2; } - -SRC=$1 -OUT=$2 - -SCHEME1_BIN=build/$ARCH/scheme1/scheme1 -CC_SRC=build/$ARCH/cc/cc.scm - -[ -x "$SCHEME1_BIN" ] || { echo "missing $SCHEME1_BIN" >&2; exit 1; } -[ -e "$CC_SRC" ] || { echo "missing $CC_SRC" >&2; exit 1; } -[ -e "$SRC" ] || { echo "missing $SRC" >&2; exit 1; } - -mkdir -p "$(dirname "$OUT")" - -# Build cc-flag list once. Order doesn't matter to cc-main but -# stays stable for log readability. -set -- -[ "${CC_DEBUG:-0}" = "1" ] && set -- "$@" --cc-debug -[ "${CC_TRACE_EMIT:-0}" = "1" ] && set -- "$@" --cc-trace-emit -[ -n "${CC_LIB:-}" ] && set -- "$@" "--lib=$CC_LIB" - -"$SCHEME1_BIN" "$CC_SRC" "$@" "$SRC" "$OUT" diff --git a/scripts/boot-build-p1.sh b/scripts/boot-build-p1.sh @@ -1,90 +0,0 @@ -#!/bin/sh -## boot-build-p1.sh — in-container .P1/.M1 -> ELF. -## -## Pure transformation. Caller (the Makefile) ensures every fixed-path -## input below already exists. Only the variable per-call inputs (source, -## output binary) come in as args. -## -## Pipeline: -## cat <P1/P1-$ARCH.M1> <src> -> /tmp/combined.M1 -## M0 /tmp/combined.M1 -> /tmp/prog.hex2 -## catm /tmp/elf.hex2 /tmp/prog.hex2 -> /tmp/linked.hex2 -## hex2-0 /tmp/linked.hex2 -> $OUT -## -## Stages through /tmp because the stage0 tools do one syscall per byte; -## virtiofs round-trips would dominate otherwise. -## -## Per-call intermediates (combined.M1, prog.hex2, linked.hex2) land at -## build/$ARCH/.work/<src-without-ext>/, mirroring the source path under -## the repo root (e.g. tests/P1/00-hello.P1 -> build/aarch64/.work/ -## tests/P1/00-hello/). A one-line sidecar at <out>.workdir records -## that path so tooling (scripts/disasm-elf.sh) can find the artifacts -## from the binary alone. -## -## Env: ARCH=aarch64|amd64|riscv64 -## Usage: boot-build-p1.sh <src> <out> - -set -eu - -# Per-stage tracing is always on. Stage0 tools (M0, hex2-0) print -# nothing on success and almost nothing on failure, so we narrate which -# step is running, snapshot intermediates to $WORK after each one, and -# print a clear FAIL banner so the user knows where it died. -ARCH_LBL=${ARCH:-?} -CURRENT_STEP= -trap 'rc=$? -if [ "$rc" -ne 0 ] && [ -n "$CURRENT_STEP" ]; then - echo "[p1 $ARCH_LBL] FAIL at: $CURRENT_STEP (exit $rc)" >&2 - if [ -n "${WORK:-}" ]; then - echo "[p1 $ARCH_LBL] partial intermediates in $WORK" >&2 - fi -fi' EXIT - -trace() { - label=$1; path=$2 - sz=$(wc -c < "$path" 2>/dev/null || echo "?") - printf '[p1 %s] %s (%s bytes) %s\n' "$ARCH_LBL" "$label" "$sz" "$path" >&2 -} - -step() { - CURRENT_STEP=$1 - printf '[p1 %s] >> %s\n' "$ARCH_LBL" "$CURRENT_STEP" >&2 -} - -: "${ARCH:?ARCH must be set}" -[ "$#" -eq 2 ] || { echo "usage: ARCH=<arch> $0 <src> <out>" >&2; exit 2; } - -SRC=$1 -OUT=$2 - -TABLE=P1/P1-$ARCH.M1 -ELF_HDR=vendor/seed/$ARCH/ELF.hex2 -TOOLS=build/$ARCH/tools -NAME=${SRC%.*} -WORK=build/$ARCH/.work/$NAME -mkdir -p "$WORK" "$(dirname "$OUT")" - -step "cat: P1 table + $SRC -> combined.M1" -cat "$TABLE" "$SRC" > /tmp/combined.M1 -cp /tmp/combined.M1 "$WORK/combined.M1" -trace "combined.M1" /tmp/combined.M1 - -step "M0: combined.M1 -> prog.hex2" -"$TOOLS/M0" /tmp/combined.M1 /tmp/prog.hex2 -cp /tmp/prog.hex2 "$WORK/prog.hex2" -trace "prog.hex2" /tmp/prog.hex2 - -step "catm: ELF header + prog.hex2 -> linked.hex2" -cp "$ELF_HDR" /tmp/elf.hex2 -"$TOOLS/catm" /tmp/linked.hex2 /tmp/elf.hex2 /tmp/prog.hex2 -cp /tmp/linked.hex2 "$WORK/linked.hex2" -trace "linked.hex2" /tmp/linked.hex2 - -step "hex2-0: linked.hex2 -> $OUT" -"$TOOLS/hex2-0" /tmp/linked.hex2 /tmp/prog.bin -cp /tmp/prog.bin "$OUT" -chmod 0700 "$OUT" -trace "$OUT" "$OUT" - -printf '%s\n' "$WORK" > "$OUT.workdir" -CURRENT_STEP= diff --git a/scripts/boot-build-p1pp.sh b/scripts/boot-build-p1pp.sh @@ -1,126 +0,0 @@ -#!/bin/sh -## boot-build-p1pp.sh — in-container .P1pp -> ELF via the new chain. -## -## Pure transformation. Caller (the Makefile) ensures every fixed-path -## input below already exists, including the per-arch self-hosted M1pp -## ELF binary (build/$ARCH/M1pp/M1pp) and hex2pp ELF binary -## (build/$ARCH/hex2pp/hex2pp). Both of those are built once via the -## seed M0+hex2 chain (boot-build-p1.sh); after that point the seed -## tools no longer participate in any user/test pipeline. -## -## Pipeline (new chain — no M0/hex2 anywhere): -## cat <P1-$ARCH.M1pp> <P1.M1pp> <P1pp.P1pp> <srcs...> -> /tmp/combined.M1pp -## M1pp /tmp/combined.M1pp -> /tmp/expanded.hex2pp -## cat $ELF_HDR /tmp/expanded.hex2pp -> /tmp/linked.hex2pp -## hex2pp /tmp/linked.hex2pp $OUT -## -## $ELF_HDR is vendor/seed/$ARCH/ELF.hex2, which supplies the -## :ELF_base / :_start / :ELF_end framing. hex2pp accepts the hex2 -## `LABEL>OTHER` subtraction syntax as a synonym for its own -## `LABEL-OTHER`, so the vendor headers assemble unchanged. -## -## libp1pp (P1/P1pp.P1pp) is concatenated unconditionally so portable -## sources can use %fn, the control-flow macros, and libp1pp routines -## (sys_*, print*, parse_*, fmt_*, memcpy/memcmp, bump allocator, panic, -## %assert_*) without per-program plumbing. hex2pp has no link-time DCE, -## so programs that don't reference any libp1pp routine still pay a -## fixed code-size tax (~a few KB). -## -## Multiple <srcs> are concatenated in the order given. This is how -## libc-using executables compose: a typical chain is -## P1/entry-libc.P1pp build/$ARCH/vendor/mes-libc/libc.P1pp client.P1pp P1/elf-end.P1pp -## with libc.P1pp / client.P1pp produced by cc.scm --lib=PFX so they -## omit the entry stub and trailing :ELF_end (those come from the -## fixed fragments instead). For a single-TU exec, pass exactly one -## source built without --lib= and the fragments are unnecessary. -## -## Per-call intermediates land at build/$ARCH/.work/<work-subpath>/. -## <work-subpath> defaults to the first src's path with extension -## stripped — fine for single-source builds (scheme1, p1 tests). For -## catm chains where the first src is a wrapper (e.g. P1/entry-libc.P1pp -## or a generated build/.../*.P1pp), the caller MUST set WORK_SUBPATH -## explicitly so the work dir mirrors the logical primary source path -## (e.g. tests/cc-libc/00-exit). A one-line sidecar at <out>.workdir -## records the resolved work dir so tooling (scripts/disasm-elf.sh) can -## locate the artifacts from the binary alone. -## -## Env: ARCH=aarch64|amd64|riscv64 -## WORK_SUBPATH=<repo-relative-path-without-ext> — overrides the -## work-dir name; required when the first src isn't -## the logical primary source. -## Usage: boot-build-p1pp.sh <out> <srcs...> - -set -eu - -# Per-stage tracing is always on. M1pp / hex2pp print little on success -# and bail fast on error, so we narrate which step is running, snapshot -# intermediates to $WORK before exiting, and print a clear FAIL banner -# on error so the user knows where it died. -ARCH_LBL=${ARCH:-?} -CURRENT_STEP= -trap 'rc=$? -if [ "$rc" -ne 0 ] && [ -n "$CURRENT_STEP" ]; then - echo "[p1pp $ARCH_LBL] FAIL at: $CURRENT_STEP (exit $rc)" >&2 - if [ -n "${WORK:-}" ]; then - echo "[p1pp $ARCH_LBL] partial intermediates in $WORK" >&2 - fi -fi' EXIT - -trace() { - label=$1; path=$2 - sz=$(wc -c < "$path" 2>/dev/null || echo "?") - printf '[p1pp %s] %s (%s bytes) %s\n' "$ARCH_LBL" "$label" "$sz" "$path" >&2 -} - -step() { - CURRENT_STEP=$1 - printf '[p1pp %s] >> %s\n' "$ARCH_LBL" "$CURRENT_STEP" >&2 -} - -: "${ARCH:?ARCH must be set}" -[ "$#" -ge 2 ] || { echo "usage: ARCH=<arch> $0 <out> <srcs...>" >&2; exit 2; } - -OUT=$1 -shift - -BACKEND=P1/P1-$ARCH.M1pp -FRONTEND=P1/P1.M1pp -LIBP1PP=P1/P1pp.P1pp -ELF_HDR=vendor/seed/$ARCH/ELF.hex2 -M1PP_BIN=build/$ARCH/M1pp/M1pp -HEX2PP_BIN=build/$ARCH/hex2pp/hex2pp -if [ -n "${WORK_SUBPATH:-}" ]; then - NAME=$WORK_SUBPATH -else - NAME=${1%.*} -fi -WORK=build/$ARCH/.work/$NAME -mkdir -p "$WORK" "$(dirname "$OUT")" - -# Snapshot each intermediate to $WORK as soon as it's produced, so a -# failure leaves the most-recent good artifact on disk for triage. With -# the trap above, the user sees both the failing stage and where to -# look. -step "cat: combined.M1pp <- backend + frontend + libp1pp + $#" -cat "$BACKEND" "$FRONTEND" "$LIBP1PP" "$@" > /tmp/combined.M1pp -cp /tmp/combined.M1pp "$WORK/combined.M1pp" -trace "combined.M1pp" /tmp/combined.M1pp - -step "M1pp: combined.M1pp -> expanded.hex2pp" -"$M1PP_BIN" /tmp/combined.M1pp /tmp/expanded.hex2pp -cp /tmp/expanded.hex2pp "$WORK/expanded.hex2pp" -trace "expanded.hex2pp" /tmp/expanded.hex2pp - -step "cat: linked.hex2pp <- ELF header + expanded.hex2pp" -cat "$ELF_HDR" /tmp/expanded.hex2pp > /tmp/linked.hex2pp -cp /tmp/linked.hex2pp "$WORK/linked.hex2pp" -trace "linked.hex2pp" /tmp/linked.hex2pp - -step "hex2pp: linked.hex2pp -> $OUT" -"$HEX2PP_BIN" -B 0x600000 /tmp/linked.hex2pp /tmp/prog.bin -cp /tmp/prog.bin "$OUT" -chmod 0700 "$OUT" -trace "$OUT" "$OUT" - -printf '%s\n' "$WORK" > "$OUT.workdir" -CURRENT_STEP= diff --git a/scripts/boot-run-tests.sh b/scripts/boot-run-tests.sh @@ -1,818 +0,0 @@ -#!/bin/sh -## boot-run-tests.sh — in-container suite runner. -## -## One invocation handles every requested fixture in a suite for $ARCH, -## instead of the host re-entering the container per fixture. The host -## runner (scripts/run-tests.sh) starts one podman process per arch and -## lets this script do all the build / execute / diff work. -## -## PASS/FAIL lines stream to stdout in the same format the host expects; -## the host greps them to update its totals, then prints the final -## summary itself. Fixture-name discovery happens here when no names -## are passed (so the host can stay agnostic about each suite's layout), -## except for m1pp: scripts/lint.sh runs python on the host, so the -## host preflights lint and passes the explicit kept list down. -## -## Env: ARCH=aarch64|amd64|riscv64 -## Usage: boot-run-tests.sh --suite=<m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc|cc-libc|cc-ext|tcc-cc|tcc-libc> [name ...] - -set -eu - -: "${ARCH:?ARCH must be set}" - -SUITE= -NAMES= - -while [ "$#" -gt 0 ]; do - case "$1" in - --suite) shift; SUITE=$1 ;; - --suite=*) SUITE=${1#--suite=} ;; - --) shift; while [ "$#" -gt 0 ]; do NAMES="$NAMES $1"; shift; done; break ;; - -*) echo "$0: unknown flag '$1'" >&2; exit 2 ;; - *) NAMES="$NAMES $1" ;; - esac - shift -done - -case "$SUITE" in - m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc|cc-libc|cc-ext|tcc-cc|tcc-libc) ;; - "") echo "$0: --suite required" >&2; exit 2 ;; - *) echo "$0: unknown suite '$SUITE'" >&2; exit 2 ;; -esac - -CC_EXTRA_FLAGS= -[ "${CC_TRACE_EMIT:-0}" = "1" ] && CC_EXTRA_FLAGS="$CC_EXTRA_FLAGS --cc-trace-emit" -[ "${CC_DEBUG:-0}" = "1" ] && CC_EXTRA_FLAGS="$CC_EXTRA_FLAGS --cc-debug" - -discover() { - dir=$1; ext=$2 - ls "$dir" 2>/dev/null \ - | sed -n "s/^\\([^_][^.]*\\)\\.$ext\$/\\1/p" \ - | sort -u -} - -report() { - label=$1; status=$2 - echo " $status $label" -} - -show_diff() { - expected=$1; actual=$2 - echo " --- expected ---" - printf '%s\n' "$expected" | sed 's/^/ /' - echo " --- actual ---" - printf '%s\n' "$actual" | sed 's/^/ /' -} - -# fail <label> [<heading>] [<log-file>] -# Emit a FAIL row plus an optional indented heading and indented log -# contents. Lets every suite handle a failed sub-step without re-running -# the failing command to capture its stderr. -fail() { - label=$1 - report "$label" FAIL - [ -n "${2:-}" ] && echo " $2" || : - if [ -n "${3:-}" ] && [ -e "${3:-}" ]; then - sed 's/^/ /' "$3" >&2 || true - fi -} - -## --- m1pp suite --------------------------------------------------------- -## -## Single check: run M1pp against tests/M1pp/<name>.M1pp, diff its text -## output against tests/M1pp/<name>.expected. The suite tests macro -## expansion only — assembling the result through hex2pp is the job of -## the p1 / cc-* suites, where the input is a complete program. -run_m1pp_suite() { - if [ -z "$NAMES" ]; then - NAMES=$(discover tests/M1pp M1pp) - fi - for name in $NAMES; do - expected=tests/M1pp/$name.expected - m1pp_src=tests/M1pp/$name.M1pp - - if [ ! -e "$m1pp_src" ]; then - echo " SKIP $name (no .M1pp)" - continue - fi - if [ ! -e "$expected" ]; then - echo " SKIP $name (no .expected)" - continue - fi - expected_content=$(cat "$expected") - - label="[$ARCH] $name" - outfile=build/$ARCH/tests/M1pp/$name.hex2pp - mkdir -p "$(dirname "$outfile")" - rm -f "$outfile" - "./build/$ARCH/M1pp/M1pp" "$m1pp_src" "$outfile" >/dev/null 2>&1 || true - if [ -e "$outfile" ]; then - actual=$(cat "$outfile") - else - actual= - fi - - if [ "$actual" != "$expected_content" ]; then - report "$label" FAIL - show_diff "$expected_content" "$actual" - continue - fi - - report "$label" PASS - done -} - -## --- p1 suite ----------------------------------------------------------- - -run_p1_suite() { - if [ -z "$NAMES" ]; then - NAMES=$(discover tests/P1 P1pp) - fi - for name in $NAMES; do - pp_src=tests/P1/$name.P1pp - expected=tests/P1/$name.expected - if [ ! -e "$expected" ]; then echo " SKIP $name (no .expected)"; continue; fi - if [ ! -e "$pp_src" ]; then echo " SKIP $name (no .P1pp)"; continue; fi - expected_content=$(cat "$expected") - - label="[$ARCH] $name" - bin=build/$ARCH/tests/P1/$name - log=build/$ARCH/.work/tests/P1/$name/build.log - mkdir -p "$(dirname "$bin")" "$(dirname "$log")" - if ! sh scripts/boot-build-p1pp.sh "$bin" "$pp_src" \ - >"$log" 2>&1; then - fail "$label" "" "$log" - continue - fi - actual=$("./$bin" 2>&1 || true) - if [ "$actual" = "$expected_content" ]; then - report "$label" PASS - else - report "$label" FAIL - show_diff "$expected_content" "$actual" - fi - done -} - -## --- scheme1 suite ------------------------------------------------------ - -run_scheme1_suite() { - if [ -z "$NAMES" ]; then - NAMES=$(discover tests/scheme1 scm) - fi - for name in $NAMES; do - fixture=tests/scheme1/$name.scm - expected_stdout_file=tests/scheme1/$name.expected - expected_exit_file=tests/scheme1/$name.expected-exit - - if [ ! -e "$fixture" ]; then echo " SKIP $name (no .scm)"; continue; fi - if [ -e "$expected_stdout_file" ]; then - expected_stdout=$(cat "$expected_stdout_file") - else - expected_stdout= - fi - if [ -e "$expected_exit_file" ]; then - expected_exit=$(cat "$expected_exit_file") - else - expected_exit=0 - fi - - label="[$ARCH] $name" - bin=build/$ARCH/scheme1/scheme1 - if [ ! -x "$bin" ]; then - report "$label" FAIL - echo " (missing $bin -- run 'make scheme1 ARCH=$ARCH')" >&2 - continue - fi - - tmp_stdout=$(mktemp) - if sh scripts/boot-run-scheme1.sh "$fixture" >"$tmp_stdout" 2>&1; then - actual_exit=0 - else - actual_exit=$? - fi - actual_stdout=$(cat "$tmp_stdout") - rm -f "$tmp_stdout" - - if [ "$actual_stdout" = "$expected_stdout" ] \ - && [ "$actual_exit" = "$expected_exit" ]; then - report "$label" PASS - else - report "$label" FAIL - if [ "$actual_stdout" != "$expected_stdout" ]; then - show_diff "$expected_stdout" "$actual_stdout" - fi - if [ "$actual_exit" != "$expected_exit" ]; then - echo " exit: expected $expected_exit, got $actual_exit" - fi - fi - done -} - -## --- cc-* suites -------------------------------------------------------- - -_cc_check() { - lbl=$1; exp_out=$2; exp_exit=$3; act_out=$4; act_exit=$5 - if [ "$act_out" = "$exp_out" ] && [ "$act_exit" = "$exp_exit" ]; then - report "$lbl" PASS - else - report "$lbl" FAIL - if [ "$act_out" != "$exp_out" ]; then show_diff "$exp_out" "$act_out"; fi - if [ "$act_exit" != "$exp_exit" ]; then - echo " exit: expected $exp_exit, got $act_exit" - fi - fi -} - -# _cc_unit_suite <suite-name> <expected-ext> <layer-list> -_cc_unit_suite() { - suite=$1; ext=$2; layers=$3 - [ -n "$NAMES" ] || NAMES=$(discover tests/$suite scm) - for name in $NAMES; do - fixture=tests/$suite/$name.scm - [ -e "$fixture" ] || { echo " SKIP $name (no .scm)"; continue; } - if [ -e "tests/$suite/$name.$ext" ]; then - expout=$(cat "tests/$suite/$name.$ext") - else - expout= - fi - if [ -e "tests/$suite/$name.expected-exit" ]; then - expexit=$(cat "tests/$suite/$name.expected-exit") - else - expexit=0 - fi - tmp=$(mktemp) - # Wrap in `sh -c` so catm failure doesn't abort under set -e and - # scheme1 still runs (matches host runner's prior behavior). - if sh -c " - build/$ARCH/tools/catm /tmp/cc-test.scm $layers $fixture - exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm - " >"$tmp" 2>&1; then - act_exit=0 - else - act_exit=$? - fi - act_out=$(cat "$tmp"); rm -f "$tmp" - _cc_check "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit" - done -} - -run_cc_util_suite() { - _cc_unit_suite cc-util expected "scheme1/prelude.scm cc/cc.scm" -} - -# _cc_pipeline_suite <suite-name> <expected-ext> <layers> -_cc_pipeline_suite() { - suite=$1; ext=$2; layers=$3 - [ -n "$NAMES" ] || NAMES=$(discover tests/$suite c) - for name in $NAMES; do - fixture=tests/$suite/$name.c - [ -e "$fixture" ] || { echo " SKIP $name (no .c)"; continue; } - if [ -e "tests/$suite/$name.$ext" ]; then - expout=$(grep -v '^;;' "tests/$suite/$name.$ext" || true) - else - expout= - fi - if [ -e "tests/$suite/$name.expected-exit" ]; then - expexit=$(cat "tests/$suite/$name.expected-exit") - else - expexit=0 - fi - tmp=$(mktemp) - if sh -c " - build/$ARCH/tools/catm /tmp/cc-test.scm $layers - exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm $fixture - " >"$tmp" 2>&1; then - act_exit=0 - else - act_exit=$? - fi - if [ "$expexit" != "0" ]; then - act_out= - else - act_out=$(cat "$tmp") - fi - rm -f "$tmp" - _cc_check "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit" - done -} - -run_cc_lex_suite() { - _cc_pipeline_suite cc-lex expected-toks \ - "scheme1/prelude.scm cc/cc.scm tests/cc-lex/_run-lex.scm" -} - -# Two passes: .c fixtures via the lex+pp pipeline; the lone .scm fixture -# (22-initial-defines) covers the -D mechanism the driver doesn't expose, -# so it stays on the unit-suite path. NAMES is restored between passes -# because _cc_pipeline_suite may have populated it via discovery. -run_cc_pp_suite() { - saved=$NAMES - _cc_pipeline_suite cc-pp expected-toks \ - "scheme1/prelude.scm cc/cc.scm tests/cc-pp/_run-pp.scm" - NAMES=$saved - _cc_unit_suite cc-pp expected \ - "scheme1/prelude.scm cc/cc.scm" -} - -# _cc_runtime_suite <suite-name> <fixture-ext> <layers> [<fixture-as-arg?>] -_cc_runtime_suite() { - suite=$1; fext=$2; layers=$3; arg_pass=${4:-0} - [ -n "$NAMES" ] || NAMES=$(discover tests/$suite "$fext") - for name in $NAMES; do - fixture=tests/$suite/$name.$fext - [ -e "$fixture" ] || { echo " SKIP $name (no .$fext)"; continue; } - - if [ -e tests/$suite/$name.expected ]; then - expout=$(cat tests/$suite/$name.expected) - else - expout= - fi - if [ -e tests/$suite/$name.expected-exit ]; then - expexit=$(cat tests/$suite/$name.expected-exit) - else - expexit=0 - fi - - elf=build/$ARCH/tests/$suite/$name - workdir=build/$ARCH/.work/tests/$suite/$name - p1pp=$workdir/$name.P1pp - mkdir -p "$(dirname "$elf")" "$workdir" - - if [ "$arg_pass" = "1" ]; then - cmd=" - build/$ARCH/tools/catm /tmp/cc-test.scm $layers - exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm $fixture - " - else - cmd=" - build/$ARCH/tools/catm /tmp/cc-test.scm $layers $fixture - exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm - " - fi - label="[$ARCH] $suite/$name" - cg_log=$workdir/cg.log - if ! sh -c "$cmd" >"$p1pp" 2>"$cg_log"; then - fail "$label" "cg emission failed:" "$cg_log" - continue - fi - - p1pp_log=$workdir/p1pp.log - if ! WORK_SUBPATH=tests/$suite/$name \ - sh scripts/boot-build-p1pp.sh "$elf" "$p1pp" \ - >"$p1pp_log" 2>&1; then - fail "$label" "P1pp assemble failed:" "$p1pp_log" - continue - fi - - tmp=$(mktemp) - if "./$elf" >"$tmp" 2>&1; then - act_exit=0 - else - act_exit=$? - fi - act_out=$(cat "$tmp"); rm -f "$tmp" - _cc_check "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit" - done -} - -run_cc_cg_suite() { - _cc_runtime_suite cc-cg scm \ - "scheme1/prelude.scm cc/cc.scm" 0 -} - -run_cc_suite() { - [ -n "$NAMES" ] || NAMES=$(discover tests/cc c) - for name in $NAMES; do - src=tests/cc/$name.c - [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } - if [ -e tests/cc/$name.expected ]; then - expout=$(cat tests/cc/$name.expected) - else - expout= - fi - if [ -e tests/cc/$name.expected-exit ]; then - expexit=$(cat tests/cc/$name.expected-exit) - else - expexit=0 - fi - elf=build/$ARCH/tests/cc/$name - workdir=build/$ARCH/.work/tests/cc/$name - p1pp=$workdir/$name.P1pp - label="[$ARCH] cc/$name" - mkdir -p "$(dirname "$elf")" "$workdir" - - cc_log=$workdir/cc.log - # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split. - if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \ - "$src" "$p1pp" >"$cc_log" 2>&1; then - fail "$label" "cc compile failed:" "$cc_log" - continue - fi - - p1pp_log=$workdir/p1pp.log - if ! WORK_SUBPATH=tests/cc/$name \ - sh scripts/boot-build-p1pp.sh "$elf" "$p1pp" \ - >"$p1pp_log" 2>&1; then - fail "$label" "P1pp assemble failed:" "$p1pp_log" - continue - fi - - tmp=$(mktemp) - if "./$elf" >"$tmp" 2>&1; then - act_exit=0 - else - act_exit=$? - fi - act_out=$(cat "$tmp"); rm -f "$tmp" - _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit" - done -} - -## --- cc-libc suite ------------------------------------------------------ -## -## Mirrors run_cc_suite but links the prepended libc.P1pp into every -## fixture. Targeted red-green TDD on the cc.scm + libc combination — -## each .c is small (one feature: printf with %d, malloc round-trip, -## getenv lookup, …) so a failure isolates the bug to one symbol path. -run_cc_libc_suite() { - [ -n "$NAMES" ] || NAMES=$(discover tests/cc-libc c) - for name in $NAMES; do - src=tests/cc-libc/$name.c - [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } - if [ -e tests/cc-libc/$name.expected ]; then - expout=$(cat tests/cc-libc/$name.expected) - else - expout= - fi - if [ -e tests/cc-libc/$name.expected-exit ]; then - expexit=$(cat tests/cc-libc/$name.expected-exit) - else - expexit=0 - fi - elf=build/$ARCH/tests/cc-libc/$name - workdir=build/$ARCH/.work/tests/cc-libc/$name - client_p1pp=$workdir/$name.client.P1pp - label="[$ARCH] cc-libc/$name" - mkdir -p "$(dirname "$elf")" "$workdir" - - # Compile the client TU in lib mode so it doesn't emit its - # own :p1_main / :ELF_end and namespaces its anonymous string - # labels under app__cc__str_N — distinct from libc.P1pp's - # libc__cc__str_N. - cc_log=$workdir/cc.log - # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split. - if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \ - --lib=app__ "$src" "$client_p1pp" \ - >"$cc_log" 2>&1; then - fail "$label" "cc compile failed:" "$cc_log" - continue - fi - - # catm chain: entry-libc supplies :p1_main (calls __libc_init - # then main), libc.P1pp supplies the libc routines, the client - # supplies :main, elf-end supplies the :ELF_end terminator. - p1pp_log=$workdir/p1pp.log - if ! WORK_SUBPATH=tests/cc-libc/$name \ - sh scripts/boot-build-p1pp.sh "$elf" \ - P1/entry-libc.P1pp build/$ARCH/vendor/mes-libc/libc.P1pp \ - "$client_p1pp" P1/elf-end.P1pp \ - >"$p1pp_log" 2>&1; then - fail "$label" "P1pp assemble failed:" "$p1pp_log" - continue - fi - - tmp=$(mktemp) - if "./$elf" >"$tmp" 2>&1; then - act_exit=0 - else - act_exit=$? - fi - act_out=$(cat "$tmp"); rm -f "$tmp" - _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit" - done -} - -## --- cc-ext suite ------------------------------------------------------- -## -## External C subset coverage via the vendored c-testsuite single-exec -## fixtures (vendor/c-testsuite/single-exec/NNNNN.c). This complements -## tests/cc, which is hand-curated; the goal here is breadth, to surface -## bugs in cc.scm against programs we did not write ourselves. -## -## Fixture spec: each program returns 0 on success and non-zero on -## failure; the .expected file pins stdout+stderr. We honest-report: -## PASS = runs + matches expected + exit 0, anything else = FAIL. -## Compile/assemble errors count as FAIL too — every regression in the -## supported subset shows up. As cc.scm grows the FAIL count drops. -## -## Pipeline switches on the .tags file: tests with `needs-libc` are -## linked through the same chain as the cc-libc suite (entry-libc + -## mes-libc + client + elf-end, with --lib=app__ to namespace string -## labels); plain tests use the bare cc -> P1pp -> ELF flow. -## -## Selection: with no name args, runs every fixture. Otherwise the args -## are basenames (e.g. 00001) under vendor/c-testsuite/single-exec/. -run_cc_ext_suite() { - dir=vendor/c-testsuite/single-exec - [ -n "$NAMES" ] || NAMES=$(discover "$dir" c) - for name in $NAMES; do - src=$dir/$name.c - tags=$dir/$name.c.tags - expected=$dir/$name.c.expected - label="[$ARCH] cc-ext/$name" - - [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } - - needs_libc=0 - if [ -e "$tags" ] && grep -q '^needs-libc$' "$tags"; then - needs_libc=1 - fi - - expout= - [ -e "$expected" ] && expout=$(cat "$expected") - expexit=0 - - elf=build/$ARCH/tests/cc-ext/$name - workdir=build/$ARCH/.work/tests/cc-ext/$name - client_p1pp=$workdir/$name.P1pp - mkdir -p "$(dirname "$elf")" "$workdir" - - cc_log=$workdir/cc.log - if [ "$needs_libc" = "1" ]; then - # Lib mode: client TU compiled with --lib=app__ so it doesn't - # emit its own :p1_main / :ELF_end and namespaces anonymous - # string labels under app__cc__str_N (libc supplies its own). - # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split. - if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \ - --lib=app__ "$src" "$client_p1pp" \ - >"$cc_log" 2>&1; then - fail "$label" "cc compile failed:" "$cc_log" - continue - fi - else - # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split. - if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \ - "$src" "$client_p1pp" \ - >"$cc_log" 2>&1; then - fail "$label" "cc compile failed:" "$cc_log" - continue - fi - fi - - p1pp_log=$workdir/p1pp.log - if [ "$needs_libc" = "1" ]; then - # catm chain matches run_cc_libc_suite: entry-libc supplies - # :p1_main (calls __libc_init then main), libc.P1pp the libc - # routines, the client :main, elf-end the :ELF_end terminator. - if ! WORK_SUBPATH=tests/cc-ext/$name \ - sh scripts/boot-build-p1pp.sh "$elf" \ - P1/entry-libc.P1pp build/$ARCH/vendor/mes-libc/libc.P1pp \ - "$client_p1pp" P1/elf-end.P1pp \ - >"$p1pp_log" 2>&1; then - fail "$label" "P1pp assemble failed:" "$p1pp_log" - continue - fi - else - if ! WORK_SUBPATH=tests/cc-ext/$name \ - sh scripts/boot-build-p1pp.sh "$elf" "$client_p1pp" \ - >"$p1pp_log" 2>&1; then - fail "$label" "P1pp assemble failed:" "$p1pp_log" - continue - fi - fi - - tmp=$(mktemp) - if "./$elf" >"$tmp" 2>&1; then - act_exit=0 - else - act_exit=$? - fi - act_out=$(cat "$tmp"); rm -f "$tmp" - _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit" - done -} - -## --- tcc-cc suite ------------------------------------------------------- -## -## Runs the plain tests/cc fixtures through a self-built tcc. STAGE -## selects the compiler — STAGE=2 uses tcc-tcc (twice-compiled, built -## by tcc-boot2 which was itself built by cc.scm), STAGE=3 uses -## tcc-tcc-tcc (thrice-compiled, built by tcc-tcc — the README -## endpoint, the first tcc whose machine code an actual tcc emitted). -## start.o / mem.o come from the tcc-cc tree (cross-asm and -## tcc-boot2-built respectively); they don't change between stages. -run_tcc_cc_suite() { - case "$ARCH" in - aarch64) tcc_target=ARM64; tcc_banner='AArch64' ;; - amd64) tcc_target=X86_64; tcc_banner='x86_64' ;; - riscv64) tcc_target=RISCV64; tcc_banner='riscv64' ;; - *) - echo " FAIL [$ARCH] tcc-cc" - echo " tcc-cc supports ARCH in {aarch64, amd64, riscv64} only" >&2 - return - ;; - esac - - case "${STAGE:-2}" in - 2) tcc=build/$ARCH/tcc-tcc/tcc-tcc; stage_tag=stage2 ;; - 3) tcc=build/$ARCH/tcc-tcc-tcc/tcc-tcc-tcc; stage_tag=stage3 ;; - *) - echo " FAIL [$ARCH] tcc-cc" - echo " unknown STAGE='$STAGE' (expected 2 or 3)" >&2 - return - ;; - esac - start=build/$ARCH/tcc-cc/start.o - mem=build/$ARCH/tcc-cc/mem.o - tcc_include=build/$ARCH/vendor/tcc/tcc-0.9.26-1147-gee75a10c/include - # x86_64 only: __va_start / __va_arg intrinsics for variadic - # functions. Other arches lower va_arg without out-of-line helpers. - if [ "$ARCH" = "amd64" ]; then - va_list=build/$ARCH/tcc-cc/va_list.o - else - va_list= - fi - if [ ! -x "$tcc" ]; then - echo " FAIL [$ARCH] tcc-cc" - echo " missing $tcc -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 - return - fi - if [ ! -e "$start" ]; then - echo " FAIL [$ARCH] tcc-cc" - echo " missing $start -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 - return - fi - if [ ! -e "$mem" ]; then - echo " FAIL [$ARCH] tcc-cc" - echo " missing $mem -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 - return - fi - if [ -n "$va_list" ] && [ ! -e "$va_list" ]; then - echo " FAIL [$ARCH] tcc-cc" - echo " missing $va_list -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 - return - fi - if ! "$tcc" -version 2>/dev/null | grep "$tcc_banner" >/dev/null; then - echo " FAIL [$ARCH] tcc-cc" - echo " $tcc is not a $tcc_banner-targeted tcc; rebuild with TCC_TARGET=$tcc_target" >&2 - return - fi - - [ -n "$NAMES" ] || NAMES=$(discover tests/cc c) - for name in $NAMES; do - src=tests/cc/$name.c - [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } - if [ -e tests/cc/$name.expected ]; then - expout=$(cat tests/cc/$name.expected) - else - expout= - fi - if [ -e tests/cc/$name.expected-exit ]; then - expexit=$(cat tests/cc/$name.expected-exit) - else - expexit=0 - fi - - elf=build/$ARCH/tests/tcc-cc/$stage_tag/$name - workdir=build/$ARCH/.work/tests/tcc-cc/$stage_tag/$name - label="[$ARCH] tcc-cc[$stage_tag]/$name" - mkdir -p "$(dirname "$elf")" "$workdir" - - tcc_log=$workdir/tcc.log - # shellcheck disable=SC2086 # $va_list is intentionally word-split (may be empty). - if ! "$tcc" -nostdlib -I "$tcc_include" \ - "$start" "$mem" $va_list "$src" -o "$elf" \ - >"$tcc_log" 2>&1; then - fail "$label" "tcc compile/link failed:" "$tcc_log" - continue - fi - - tmp=$(mktemp) - if "./$elf" >"$tmp" 2>&1; then - act_exit=0 - else - act_exit=$? - fi - act_out=$(cat "$tmp"); rm -f "$tmp" - _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit" - done -} - -## --- tcc-libc suite ----------------------------------------------------- -## -## End-to-end "tcc as a real compiler" check, run through a self-built -## tcc. STAGE selects the compiler — STAGE=2 uses tcc-tcc (twice- -## compiled), STAGE=3 uses tcc-tcc-tcc (thrice-compiled, README -## endpoint). tcc-boot2 already compiled mes-libc into libc.o; for each -## tests/cc-libc fixture, the selected tcc compiles + links it against -## start.o per-arch entry stub: __libc_init then main then exit -## sys_stubs.o per-arch raw-syscall sys_* implementations -## mem.o mem* compiler-builtin runtime (memcpy/memmove/memset/memcmp) -## libc.o tcc-boot2-built mes-libc -## and runs the resulting ELF natively in the per-arch container. -run_tcc_libc_suite() { - case "$ARCH" in - aarch64) tcc_target=ARM64; tcc_banner='AArch64' ;; - amd64) tcc_target=X86_64; tcc_banner='x86_64' ;; - riscv64) tcc_target=RISCV64; tcc_banner='riscv64' ;; - *) - echo " FAIL [$ARCH] tcc-libc" - echo " tcc-libc supports ARCH in {aarch64, amd64, riscv64} only" >&2 - return - ;; - esac - - case "${STAGE:-2}" in - 2) tcc=build/$ARCH/tcc-tcc/tcc-tcc; stage_tag=stage2 ;; - 3) tcc=build/$ARCH/tcc-tcc-tcc/tcc-tcc-tcc; stage_tag=stage3 ;; - *) - echo " FAIL [$ARCH] tcc-libc" - echo " unknown STAGE='$STAGE' (expected 2 or 3)" >&2 - return - ;; - esac - start=build/$ARCH/tcc-libc/start.o - sys_stubs=build/$ARCH/tcc-libc/sys_stubs.o - mem=build/$ARCH/tcc-libc/mem.o - libc=build/$ARCH/tcc-libc/libc.o - tcc_include=build/$ARCH/vendor/tcc/tcc-0.9.26-1147-gee75a10c/include - # x86_64 only: __va_start / __va_arg intrinsics for variadic - # functions. mes-libc's printf family hits this directly. - if [ "$ARCH" = "amd64" ]; then - va_list=build/$ARCH/tcc-cc/va_list.o - else - va_list= - fi - for f in "$tcc" "$start" "$sys_stubs" "$mem" "$libc"; do - if [ ! -e "$f" ]; then - echo " FAIL [$ARCH] tcc-libc" - echo " missing $f -- run 'make test SUITE=tcc-libc ARCH=$ARCH'" >&2 - return - fi - done - if [ -n "$va_list" ] && [ ! -e "$va_list" ]; then - echo " FAIL [$ARCH] tcc-libc" - echo " missing $va_list -- run 'make test SUITE=tcc-libc ARCH=$ARCH'" >&2 - return - fi - if ! "$tcc" -version 2>/dev/null | grep "$tcc_banner" >/dev/null; then - echo " FAIL [$ARCH] tcc-libc" - echo " $tcc is not a $tcc_banner-targeted tcc; rebuild with TCC_TARGET=$tcc_target" >&2 - return - fi - - [ -n "$NAMES" ] || NAMES=$(discover tests/cc-libc c) - for name in $NAMES; do - src=tests/cc-libc/$name.c - [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } - if [ -e tests/cc-libc/$name.expected ]; then - expout=$(cat tests/cc-libc/$name.expected) - else - expout= - fi - if [ -e tests/cc-libc/$name.expected-exit ]; then - expexit=$(cat tests/cc-libc/$name.expected-exit) - else - expexit=0 - fi - - elf=build/$ARCH/tests/tcc-libc/$stage_tag/$name - workdir=build/$ARCH/.work/tests/tcc-libc/$stage_tag/$name - label="[$ARCH] tcc-libc[$stage_tag]/$name" - mkdir -p "$(dirname "$elf")" "$workdir" - - tcc_log=$workdir/tcc.log - # shellcheck disable=SC2086 # $va_list is intentionally word-split (may be empty). - if ! "$tcc" -nostdlib -I "$tcc_include" \ - "$start" "$sys_stubs" "$mem" "$libc" $va_list "$src" -o "$elf" \ - >"$tcc_log" 2>&1; then - fail "$label" "tcc compile/link failed:" "$tcc_log" - continue - fi - - tmp=$(mktemp) - if "./$elf" >"$tmp" 2>&1; then - act_exit=0 - else - act_exit=$? - fi - act_out=$(cat "$tmp"); rm -f "$tmp" - _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit" - done -} - -case "$SUITE" in - m1pp) run_m1pp_suite ;; - p1) run_p1_suite ;; - scheme1) run_scheme1_suite ;; - cc-util) run_cc_util_suite ;; - cc-lex) run_cc_lex_suite ;; - cc-pp) run_cc_pp_suite ;; - cc-cg) run_cc_cg_suite ;; - cc) run_cc_suite ;; - cc-libc) run_cc_libc_suite ;; - cc-ext) run_cc_ext_suite ;; - tcc-cc) run_tcc_cc_suite ;; - tcc-libc) run_tcc_libc_suite ;; -esac diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh @@ -1,177 +0,0 @@ -#!/bin/sh -## run-tests.sh — host-side dispatcher for the unified test runner. -## -## All build/run/diff work happens inside the container via -## scripts/boot-run-tests.sh: this script just starts one podman -## process per requested arch and aggregates the per-arch -## PASS/FAIL totals. Prior versions of this runner re-entered the -## container per fixture (and per build/run within a fixture); now -## a whole arch's suite is one podman invocation. -## -## The one bit of work that stays on the host is the lint preflight -## for the m1pp and p1 suites: scripts/lint.sh runs python, which the -## busybox container doesn't carry. Names that fail lint are reported -## here (FAIL + diagnostic) and excluded from the in-container batch. -## -## Suites: -## m1pp tests/M1pp/<name>.M1pp — m1pp expander parity test. -## p1 tests/P1/<name>.P1pp — built via boot-build-p1pp.sh -## and run; stdout diffed. -## tests/P1/<name>.P1 — raw P1, built via -## boot-build-p1.sh (no expander). -## scheme1 tests/scheme1/<name>.scm — run by per-arch scheme1. -## cc-util tests/cc-util/<name>.scm — scheme1 prelude+util byte-diff. -## cc-lex tests/cc-lex/<name>.c — lex pipeline byte-diff. -## cc-pp tests/cc-pp/<name>.c — pp pipeline byte-diff (+ .scm). -## cc-cg tests/cc-cg/<name>.scm — cg emit -> P1pp -> ELF -> run. -## cc tests/cc/<name>.c — cc -> P1pp -> ELF -> run. -## cc-ext vendor/c-testsuite/single-exec/<name>.c — broad subset -## coverage from the upstream -## c-testsuite. needs-libc tests -## link the mes-libc chain (same -## as cc-libc); plain tests use -## the bare cc pipeline. Any -## compile/assemble/runtime error -## counts as FAIL. -## tcc-cc tests/cc/<name>.c — tcc-boot2 -> ELF -> run. -## tcc-libc tests/cc-libc/<name>.c — tcc-boot2 builds mes-libc into -## libc.o, then compiles + links -## each fixture against it -> run. -## -## All three arches by default; --arch restricts to one. -## -## Usage: scripts/run-tests.sh --suite <suite> [--arch ARCH] [name ...] - -set -eu - -SUITE= -ARCH= -NAMES= -STAGE= - -while [ "$#" -gt 0 ]; do - case "$1" in - --suite) shift; SUITE=$1 ;; - --suite=*) SUITE=${1#--suite=} ;; - --arch) shift; ARCH=$1 ;; - --arch=*) ARCH=${1#--arch=} ;; - --stage) shift; STAGE=$1 ;; - --stage=*) STAGE=${1#--stage=} ;; - --) shift; while [ "$#" -gt 0 ]; do NAMES="$NAMES $1"; shift; done; break ;; - -*) echo "$0: unknown flag '$1'" >&2; exit 2 ;; - *) NAMES="$NAMES $1" ;; - esac - shift -done - -case "$SUITE" in - m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc|cc-libc|cc-ext|tcc-cc|tcc-libc) ;; - "") echo "$0: --suite required (m1pp | p1 | scheme1 | cc-util | cc-lex | cc-pp | cc-cg | cc | cc-libc | cc-ext | tcc-cc | tcc-libc)" >&2; exit 2 ;; - *) echo "$0: unknown suite '$SUITE'" >&2; exit 2 ;; -esac - -REPO=$(cd "$(dirname "$0")/.." && pwd) -cd "$REPO" - -platform_of() { - case "$1" in - aarch64) echo linux/arm64 ;; - amd64) echo linux/amd64 ;; - riscv64) echo linux/riscv64 ;; - *) echo "$0: unknown arch '$1'" >&2; return 1 ;; - esac -} - -run_in_container() { - arch=$1; shift - podman run --rm --pull=never --platform "$(platform_of "$arch")" \ - --tmpfs /tmp:size=512M \ - -e "ARCH=$arch" \ - -e "CC_TRACE_EMIT=${CC_TRACE_EMIT:-0}" \ - -e "CC_DEBUG=${CC_DEBUG:-0}" \ - -e "STAGE=${STAGE:-}" \ - -v "$REPO":/work -w /work \ - "boot2-busybox:$arch" "$@" -} - -if [ -z "$ARCH" ]; then - ARCHES="aarch64 amd64 riscv64" -else - ARCHES=$ARCH -fi - -PASS=0 -FAIL=0 - -# Lint preflight: lint.sh uses python (host-only). Discover the -# fixture set if --names was empty, lint each raw fixture (.M1pp / .P1), -# emit a host-side FAIL + diagnostic for any miss, write the kept name -# list to $keep_file. FAIL line goes to stdout to interleave with the -# container's PASS/FAIL output; the FAIL counter updates in-scope. -# -# Suite layout: -# m1pp: tests/M1pp/<name>.M1pp (no raw .M1 fixtures any more) -# p1: tests/P1/<name>.P1pp (lint skipped — expander output) -# tests/P1/<name>.P1 (lint runs) -lint_preflight() { - arch=$1; keep_file=$2; dir=$3; raw_ext=$4; pp_ext=$5 - : > "$keep_file" - if [ -z "$NAMES" ]; then - raw=$(ls "$dir" 2>/dev/null \ - | sed -n "s/^\([^_][^.]*\)\.${raw_ext}\$/\1/p") - pp=$(ls "$dir" 2>/dev/null \ - | sed -n "s/^\([^_][^.]*\)\.${pp_ext}\$/\1/p") - all=$(printf '%s\n%s\n' "$raw" "$pp" | sort -u | tr '\n' ' ') - else - all=$NAMES - fi - for name in $all; do - raw_src=$dir/$name.$raw_ext - if [ -e "$raw_src" ] \ - && ! ARCH=$arch sh scripts/lint.sh "$raw_src" >/dev/null 2>&1; then - echo " FAIL [$arch] $name" - ARCH=$arch sh scripts/lint.sh "$raw_src" 2>&1 \ - | sed 's/^/ /' >&2 || true - FAIL=$((FAIL + 1)) - else - printf '%s ' "$name" >> "$keep_file" - fi - done -} - -for arch in $ARCHES; do - case "$SUITE" in - m1pp) preflight_args="tests/M1pp M1 M1pp" ;; - p1) preflight_args="tests/P1 P1 P1pp" ;; - *) preflight_args= ;; - esac - if [ -n "$preflight_args" ]; then - keep_file=$(mktemp) - # shellcheck disable=SC2086 # $preflight_args is intentionally word-split. - lint_preflight "$arch" "$keep_file" $preflight_args - names=$(cat "$keep_file") - rm -f "$keep_file" - # Skip the container call only when the user gave names AND - # all of them failed lint. With no names, an empty kept set - # would mean nothing to run anyway. - names_trimmed=$(echo "$names" | tr -d ' \t\n') - if [ -z "$names_trimmed" ]; then - continue - fi - else - names=$NAMES - fi - - out=$(mktemp) - # shellcheck disable=SC2086 # $names is intentionally word-split. - run_in_container "$arch" sh scripts/boot-run-tests.sh \ - --suite="$SUITE" $names | tee "$out" - p=$(grep -c '^ PASS ' "$out" 2>/dev/null || true) - f=$(grep -c '^ FAIL ' "$out" 2>/dev/null || true) - PASS=$((PASS + ${p:-0})) - FAIL=$((FAIL + ${f:-0})) - rm -f "$out" -done - -echo "$PASS passed, $FAIL failed" -[ "$FAIL" -eq 0 ] diff --git a/scripts/seed-accept-boot34.sh b/scripts/seed-accept-boot34.sh @@ -1,69 +0,0 @@ -#!/bin/sh -## seed-accept-boot34.sh — acceptance: run boot3 (then optionally boot4) -## under DRIVER=seed and assert byte-identical outputs vs build/aarch64/ -## podman/bootN/'s podman-built artefacts. -## -## Prereqs (build first): -## - build/aarch64/podman/boot{0..6}/ (run ./scripts/boot.sh aarch64 -## under DRIVER=podman to populate these — boot6/Image becomes the -## tcc-built seed kernel) -## -## What it does: -## 1. DRIVER=seed scripts/boot3.sh aarch64 — one qemu boot, scheme1 -## drives cc.scm → tcc0 from a generated run.scm. -## 2. cmp -s build/$ARCH/seed/boot3/tcc0 vs build/$ARCH/podman/boot3/tcc0; -## fail on diff. -## 3. If $WITH_BOOT4=1, repeat for boot4 (tcc0 → tcc1 → tcc2 → tcc3, -## with tcc2 == tcc3 fixed-point asserted). -## -## Usage: scripts/seed-accept-boot34.sh -## WITH_BOOT4=1 scripts/seed-accept-boot34.sh - -set -eu - -ARCH=aarch64 -ROOT=$(cd "$(dirname "$0")/.." && pwd) -cd "$ROOT" - -PODMAN=build/$ARCH/podman -SEED=build/$ARCH/seed -KERNEL=$PODMAN/boot6/Image -[ -f "$KERNEL" ] || { echo "missing $KERNEL — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } -[ -x $PODMAN/boot3/tcc0 ] || { echo "$PODMAN/boot3/tcc0 missing — run scripts/boot3.sh aarch64" >&2; exit 1; } - -echo "[seed-accept-boot34] boot3: DRIVER=seed scripts/boot3.sh $ARCH" -DRIVER=seed scripts/boot3.sh $ARCH - -if ! cmp -s $SEED/boot3/tcc0 $PODMAN/boot3/tcc0; then - s_seed=$(wc -c < $SEED/boot3/tcc0) - s_ref=$(wc -c < $PODMAN/boot3/tcc0) - echo "[seed-accept-boot34] boot3 FAIL: tcc0 differs (seed=$s_seed podman=$s_ref)" >&2 - exit 3 -fi -echo "[seed-accept-boot34] boot3 PASS — tcc0 byte-identical vs podman" - -if [ "${WITH_BOOT4:-0}" != 1 ]; then - exit 0 -fi - -[ -x $PODMAN/boot4/tcc3 ] || { echo "$PODMAN/boot4/tcc3 missing — run scripts/boot4.sh aarch64 under podman first" >&2; exit 1; } - -echo "[seed-accept-boot34] boot4: DRIVER=seed scripts/boot4.sh $ARCH" -DRIVER=seed scripts/boot4.sh $ARCH - -fail=0 -# All boot4 outputs — including the intermediate crt1.o / libc.a / -# libtcc1.a — must match podman byte-for-byte. The strip-file-prefix -# tcc patch (simple-patches/tcc-0.9.26/) drops the /work/in/[tcc-lib/] -# mount prefix from STT_FILE entries, so seed's flat-basename staging -# and podman's /work/in/ mounts produce identical .o relocations. -for f in tcc3 hello crt1.o libc.a libtcc1.a; do - if ! cmp -s $SEED/boot4/$f $PODMAN/boot4/$f; then - s_seed=$(wc -c < $SEED/boot4/$f) - s_ref=$(wc -c < $PODMAN/boot4/$f) - echo "[seed-accept-boot34] boot4 DIFF $f: seed=$s_seed podman=$s_ref" >&2 - fail=1 - fi -done -[ $fail -eq 0 ] || exit 4 -echo "[seed-accept-boot34] boot4 PASS — tcc3/hello/crt1.o/libc.a/libtcc1.a byte-identical vs podman" diff --git a/scripts/seed-accept-boot5.sh b/scripts/seed-accept-boot5.sh @@ -1,55 +0,0 @@ -#!/bin/sh -## seed-accept-boot5.sh — acceptance: run boot5 under DRIVER=seed and -## assert byte-identical outputs vs build/aarch64/podman/boot5/'s -## podman-built artefacts. Mirrors scripts/seed-accept-boot34.sh. -## -## Prereqs (build first): -## - build/aarch64/podman/boot{0..6}/ (run ./scripts/boot.sh aarch64 -## under DRIVER=podman to populate these — boot6/Image becomes the -## tcc-built seed kernel) -## -## What it does: -## 1. DRIVER=seed scripts/boot5.sh aarch64 — one qemu boot, scheme1 -## drives ~1300 (run "tcc" …) calls from a generated run.scm. -## 2. cmp -s each output (libc.a, crt1.o, crti.o, crtn.o, hello) vs -## the podman reference at build/$ARCH/podman/boot5/; fail on diff. -## -## Usage: scripts/seed-accept-boot5.sh - -set -eu - -ARCH=aarch64 -ROOT=$(cd "$(dirname "$0")/.." && pwd) -cd "$ROOT" - -PODMAN=build/$ARCH/podman -SEED=build/$ARCH/seed -KERNEL=$PODMAN/boot6/Image -[ -f "$KERNEL" ] || { echo "missing $KERNEL — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } -[ -d $PODMAN/boot5 ] || { echo "$PODMAN/boot5 missing — run scripts/boot5.sh aarch64" >&2; exit 1; } -for f in libc.a crt1.o crti.o crtn.o hello; do - [ -e $PODMAN/boot5/$f ] || { echo "$PODMAN/boot5/$f missing — run scripts/boot5.sh aarch64" >&2; exit 1; } -done - -echo "[seed-accept-boot5] DRIVER=seed scripts/boot5.sh $ARCH" -DRIVER=seed scripts/boot5.sh $ARCH - -fails=0 -for f in libc.a crt1.o crti.o crtn.o hello; do - seed_size=$(wc -c < $SEED/boot5/$f) - ref_size=$(wc -c < $PODMAN/boot5/$f) - if cmp -s $SEED/boot5/$f $PODMAN/boot5/$f; then - echo "[seed-accept-boot5] $f: byte-identical ($seed_size bytes)" - else - echo "[seed-accept-boot5] $f: DIFF (seed=$seed_size podman=$ref_size)" - fails=$((fails + 1)) - fi -done - -if [ $fails -eq 0 ]; then - echo "[seed-accept-boot5] PASS — all 5 outputs byte-identical" - exit 0 -else - echo "[seed-accept-boot5] FAIL — $fails outputs differ" >&2 - exit 4 -fi diff --git a/scripts/seed-accept.sh b/scripts/seed-accept.sh @@ -1,171 +0,0 @@ -#!/bin/sh -## seed-accept.sh — Tier-2 acceptance for boot0/1/2 on seed-kernel. -## -## Loads the boot2-built scheme1 as /init in the seed kernel, runs a -## .scm driver that: -## 1. Logs "hello" to stdout (sys_write fd=1 → UART). -## 2. Spawns child-prog (=catm from boot2) via clone+execve to -## concatenate two files A + B → C in the in-memory tmpfs. -## 3. waitids the child, reads C back, prints it to stdout. -## 4. exit_group(0). -## -## End-to-end exercise of every Tier-1 syscall (read/write/openat/close/ -## brk/exit_group) plus the three Tier-2 ones (clone/execve/waitid). -## -## Verifies the transcript contains the expected log lines and that -## sys_exit_or_resume_parent saw the child exit cleanly. -## -## Usage: scripts/seed-accept.sh - -set -eu - -ARCH=aarch64 -ROOT=$(cd "$(dirname "$0")/.." && pwd) -cd "$ROOT" - -PODMAN=build/$ARCH/podman -KERNEL=$PODMAN/boot6/Image -EXTRACT=seed-kernel/scripts/extract-blk.sh -SCHEME1=$PODMAN/boot2/scheme1 -CATM=$PODMAN/boot2/catm -PRELUDE=scheme1/prelude.scm - -[ -f "$KERNEL" ] || { echo "missing $KERNEL — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } -[ -x "$SCHEME1" ] || { echo "missing $SCHEME1 — run boot2 first" >&2; exit 1; } -[ -x "$CATM" ] || { echo "missing $CATM — run boot2 first" >&2; exit 1; } - -OUTDIR=$ROOT/build/$ARCH/seed-accept -rm -rf "$OUTDIR"; mkdir -p "$OUTDIR" - -STAGE=$(mktemp -d -t seed-accept.XXXXXX) -trap 'rm -rf "$STAGE"' EXIT - -# ─── driver.scm — the in-VM acceptance program ──────────────────────── -cat > "$STAGE/driver.scm" <<'SCM' -;; driver.scm — Tier-2 acceptance for seed-kernel. -(write-string stdout "scheme1: hello from acceptance driver\n") -(write-string stdout "scheme1: spawning child-prog (catm) C <- A + B\n") - -(let ((r (run "child-prog" "C" "A" "B"))) - (if (car r) - (begin - (write-string stdout "scheme1: child returned\n")) - (begin - (write-string stdout "scheme1: spawn FAILED\n") - (exit 1)))) - -(let ((rp (open-input "C"))) - (if (car rp) - (let* ((p (cdr rp)) - (rb (read-all p))) - (if (car rb) - (begin - (write-string stdout "scheme1: read C: [") - (write-bytes stdout (cdr rb)) - (write-string stdout "]\n")) - (write-string stdout "scheme1: read C FAILED\n")) - (close p)) - (write-string stdout "scheme1: open C FAILED\n"))) - -(write-string stdout "scheme1: ALL-OK\n") -(exit 0) -SCM - -# ─── Combine prelude + driver via host catm — this matches the chain's -# own boot-run-scheme1.sh wrapper, so the .scm shape is identical -# to what scheme1 expects everywhere. ────────────────────────────── -cat "$PRELUDE" "$STAGE/driver.scm" > "$STAGE/combined.scm" - -# ─── Two demo input files. catm reads them from the in-VM tmpfs. ────── -printf 'Hello, ' > "$STAGE/A" -printf 'seed-kernel!\n' > "$STAGE/B" - -# ─── Stage the cpio: /init=scheme1, /child-prog=catm, plus inputs. ──── -mkdir -p "$STAGE/cpio" -cp "$SCHEME1" "$STAGE/cpio/init"; chmod +x "$STAGE/cpio/init" -cp "$CATM" "$STAGE/cpio/child-prog"; chmod +x "$STAGE/cpio/child-prog" -cp "$STAGE/combined.scm" "$STAGE/cpio/combined.scm" -cp "$STAGE/A" "$STAGE/cpio/A" -cp "$STAGE/B" "$STAGE/cpio/B" - -NAMES='init -child-prog -combined.scm -A -B' - -( cd "$STAGE/cpio" && printf '%s\n' "$NAMES" | cpio -o -H newc 2>/dev/null ) > "$STAGE/initramfs.cpio" -sz=$(wc -c < "$STAGE/initramfs.cpio") -pad=$(( (512 - sz % 512) % 512 )) -if [ "$pad" -gt 0 ]; then - head -c "$pad" /dev/zero >> "$STAGE/initramfs.cpio" -fi -mv "$STAGE/initramfs.cpio" "$STAGE/in.img" -truncate -s 256M "$STAGE/out.img" - -TRANSCRIPT=$OUTDIR/transcript.txt -echo "[seed-accept] booting scheme1 + driver.scm on seed-kernel" -qemu-system-aarch64 \ - -machine virt,gic-version=3,accel=hvf -cpu host -m 2048M \ - -nographic -no-reboot \ - -global virtio-mmio.force-legacy=false \ - -kernel "$KERNEL" \ - -drive file="$STAGE/in.img",if=none,format=raw,id=hd0,readonly=on \ - -device virtio-blk-device,drive=hd0 \ - -drive file="$STAGE/out.img",if=none,format=raw,id=hd1 \ - -device virtio-blk-device,drive=hd1 \ - -append "init combined.scm" \ - > "$TRANSCRIPT" 2>&1 & -QPID=$! -( sleep 240; kill -9 $QPID 2>/dev/null ) </dev/null >/dev/null 2>&1 & -WATCHER=$! -wait $QPID 2>/dev/null || true -kill $WATCHER 2>/dev/null || true - -# Extract files (we want C from the tmpfs). -if ! "$EXTRACT" "$OUTDIR" "$STAGE/out.img" >/dev/null 2>&1; then - echo "[seed-accept] FAIL: extract-blk failed (kernel didn't reach exit?)" >&2 - tail -60 "$TRANSCRIPT" >&2 - exit 3 -fi - -# ─── Verify ─────────────────────────────────────────────────────────── -fail=0 -for needle in \ - 'scheme1: hello from acceptance driver' \ - 'scheme1: spawning child-prog' \ - 'scheme1: child returned' \ - 'scheme1: read C: \[Hello, seed-kernel!' \ - 'scheme1: ALL-OK' \ - 'exit_group(0)' -do - if ! grep -q "$needle" "$TRANSCRIPT"; then - echo "[seed-accept] MISSING in transcript: $needle" >&2 - fail=1 - fi -done - -if [ ! -f "$OUTDIR/C" ]; then - echo "[seed-accept] MISSING extracted file: $OUTDIR/C" >&2 - fail=1 -elif ! diff -q "$OUTDIR/C" - <<EOF >/dev/null -Hello, seed-kernel! -EOF -then - echo "[seed-accept] C differs from expected:" >&2 - od -c "$OUTDIR/C" | head -3 >&2 - fail=1 -fi - -if [ $fail -ne 0 ]; then - echo "[seed-accept] FAIL — see $TRANSCRIPT" >&2 - exit 4 -fi - -echo "" -echo "=== driver log (excerpt from transcript) ===" -grep '^scheme1:' "$TRANSCRIPT" || true -echo "===========================================" -echo "" -echo "[seed-accept] PASS — scheme1 + .scm + child-prog cycle complete" -echo "[seed-accept] artifacts in $OUTDIR/" diff --git a/tests/M1pp/01-passthrough.M1pp b/tests/M1pp/001-passthrough.M1pp diff --git a/tests/M1pp/01-passthrough.expected b/tests/M1pp/001-passthrough.expected diff --git a/tests/M1pp/02-defs.M1pp b/tests/M1pp/002-defs.M1pp diff --git a/tests/M1pp/02-defs.expected b/tests/M1pp/002-defs.expected diff --git a/tests/M1pp/03-builtins.M1pp b/tests/M1pp/003-builtins.M1pp diff --git a/tests/M1pp/03-builtins.expected b/tests/M1pp/003-builtins.expected diff --git a/tests/M1pp/04-expr-ops.M1pp b/tests/M1pp/004-expr-ops.M1pp diff --git a/tests/M1pp/04-expr-ops.expected b/tests/M1pp/004-expr-ops.expected diff --git a/tests/M1pp/05-int-atoms.M1pp b/tests/M1pp/005-int-atoms.M1pp diff --git a/tests/M1pp/05-int-atoms.expected b/tests/M1pp/005-int-atoms.expected diff --git a/tests/M1pp/06-paste.M1pp b/tests/M1pp/006-paste.M1pp diff --git a/tests/M1pp/06-paste.expected b/tests/M1pp/006-paste.expected diff --git a/tests/M1pp/07-rescan.M1pp b/tests/M1pp/007-rescan.M1pp diff --git a/tests/M1pp/07-rescan.expected b/tests/M1pp/007-rescan.expected diff --git a/tests/M1pp/08-select.M1pp b/tests/M1pp/008-select.M1pp diff --git a/tests/M1pp/08-select.expected b/tests/M1pp/008-select.expected diff --git a/tests/M1pp/09-args.M1pp b/tests/M1pp/009-args.M1pp diff --git a/tests/M1pp/09-args.expected b/tests/M1pp/009-args.expected diff --git a/tests/M1pp/10-full-parity.M1pp b/tests/M1pp/010-full-parity.M1pp diff --git a/tests/M1pp/10-full-parity.expected b/tests/M1pp/010-full-parity.expected diff --git a/tests/M1pp/11-local-labels.M1pp b/tests/M1pp/011-local-labels.M1pp diff --git a/tests/M1pp/11-local-labels.expected b/tests/M1pp/011-local-labels.expected diff --git a/tests/M1pp/12-braced-args.M1pp b/tests/M1pp/012-braced-args.M1pp diff --git a/tests/M1pp/12-braced-args.expected b/tests/M1pp/012-braced-args.expected diff --git a/tests/M1pp/13-parenless-control.M1pp b/tests/M1pp/013-parenless-control.M1pp diff --git a/tests/M1pp/13-parenless-control.expected b/tests/M1pp/013-parenless-control.expected diff --git a/tests/M1pp/13-parenless.M1pp b/tests/M1pp/013-parenless.M1pp diff --git a/tests/M1pp/13-parenless.expected b/tests/M1pp/013-parenless.expected diff --git a/tests/M1pp/14-str-builtin.M1pp b/tests/M1pp/014-str-builtin.M1pp diff --git a/tests/M1pp/14-str-builtin.expected b/tests/M1pp/014-str-builtin.expected diff --git a/tests/M1pp/14-str-paste.M1pp b/tests/M1pp/014-str-paste.M1pp diff --git a/tests/M1pp/14-str-paste.expected b/tests/M1pp/014-str-paste.expected diff --git a/tests/M1pp/15-struct.M1pp b/tests/M1pp/015-struct.M1pp diff --git a/tests/M1pp/15-struct.expected b/tests/M1pp/015-struct.expected diff --git a/tests/M1pp/16-enum.M1pp b/tests/M1pp/016-enum.M1pp diff --git a/tests/M1pp/16-enum.expected b/tests/M1pp/016-enum.expected diff --git a/tests/M1pp/18-tight-paren-call.M1pp b/tests/M1pp/018-tight-paren-call.M1pp diff --git a/tests/M1pp/18-tight-paren-call.expected b/tests/M1pp/018-tight-paren-call.expected diff --git a/tests/M1pp/19-one-line-macro.M1pp b/tests/M1pp/019-one-line-macro.M1pp diff --git a/tests/M1pp/19-one-line-macro.expected b/tests/M1pp/019-one-line-macro.expected diff --git a/tests/M1pp/20-multiline-header.M1pp b/tests/M1pp/020-multiline-header.M1pp diff --git a/tests/M1pp/20-multiline-header.expected b/tests/M1pp/020-multiline-header.expected diff --git a/tests/M1pp/21-mid-line-directive.M1pp b/tests/M1pp/021-mid-line-directive.M1pp diff --git a/tests/M1pp/21-mid-line-directive.expected b/tests/M1pp/021-mid-line-directive.expected diff --git a/tests/M1pp/22-paste-across-newlines.M1pp b/tests/M1pp/022-paste-across-newlines.M1pp diff --git a/tests/M1pp/22-paste-across-newlines.expected b/tests/M1pp/022-paste-across-newlines.expected diff --git a/tests/M1pp/24-empty-body.M1pp b/tests/M1pp/024-empty-body.M1pp diff --git a/tests/M1pp/24-empty-body.expected b/tests/M1pp/024-empty-body.expected diff --git a/tests/M1pp/25-frame-locals.M1pp b/tests/M1pp/025-frame-locals.M1pp diff --git a/tests/M1pp/25-frame-locals.expected b/tests/M1pp/025-frame-locals.expected diff --git a/tests/M1pp/27-string-emit.M1pp b/tests/M1pp/027-string-emit.M1pp diff --git a/tests/M1pp/27-string-emit.expected b/tests/M1pp/027-string-emit.expected diff --git a/tests/M1pp/28-select-cond-from-macro.M1pp b/tests/M1pp/028-select-cond-from-macro.M1pp diff --git a/tests/M1pp/28-select-cond-from-macro.expected b/tests/M1pp/028-select-cond-from-macro.expected diff --git a/tests/M1pp/29-string-escapes.M1pp b/tests/M1pp/029-string-escapes.M1pp diff --git a/tests/M1pp/29-string-escapes.expected b/tests/M1pp/029-string-escapes.expected diff --git a/tests/M1pp/30-struct-comma-fields.M1pp b/tests/M1pp/030-struct-comma-fields.M1pp diff --git a/tests/M1pp/30-struct-comma-fields.expected b/tests/M1pp/030-struct-comma-fields.expected diff --git a/tests/M1pp/31-string-via-macro.M1pp b/tests/M1pp/031-string-via-macro.M1pp diff --git a/tests/M1pp/31-string-via-macro.expected b/tests/M1pp/031-string-via-macro.expected diff --git a/tests/Makefile b/tests/Makefile @@ -0,0 +1,409 @@ +# tests/Makefile — test infrastructure (suites + their build deps). +# +# Two ways in: +# 1. `include tests/Makefile` from the top-level Makefile (REPO_ROOT +# already set). All test/build rules are emitted; the top-level's +# `test`, `image`, etc. targets resolve here. +# 2. `make -C tests <target>` standalone. We detect the missing +# REPO_ROOT and re-exec the top-level Makefile, forwarding goals. +# +# Suites: +# test SUITE=<name> one suite (defaults to all) +# test aggregate (m1pp, p1, scheme1, cc-util, +# cc-lex, cc-pp, cc-cg, cc, cc-libc) +# test SUITE=tcc-cc ARCH=... self-built tcc through cc fixtures +# test SUITE=tcc-libc ARCH=... self-built tcc through cc-libc fixtures +# +# Suite-specific build targets (image, tools, M1pp, hex2pp, scheme1, cc, +# tcc-boot2/tcc-tcc/tcc-tcc-tcc, mes-libc) are kept here too so the +# top-level Makefile is bootstrap-only. + +ifndef REPO_ROOT +# ── Standalone invocation: delegate to top-level. ──────────────────────── +# `make -C tests <goals>` lands here. Forward to ../Makefile. +.PHONY: __forward $(MAKECMDGOALS) +__forward: + @$(MAKE) -C .. $(if $(MAKECMDGOALS),$(MAKECMDGOALS),test) +$(MAKECMDGOALS): __forward ; +.DEFAULT_GOAL := __forward +else +# ── Included from top-level: emit rules. ───────────────────────────────── + +# ── tests-only container images (boot2-busybox, boot2-alpine-gcc) ──────── + +IMAGE_STAMP := build/$(ARCH)/.image +IMAGE_STAMPS := $(foreach a,$(ALL_ARCHES),build/$(a)/.image) + +.PHONY: image +image: $(IMAGE_STAMP) + +$(IMAGE_STAMPS): build/%/.image: scripts/Containerfile.busybox + mkdir -p $(@D) + podman build --platform $(PLATFORM_$*) -t boot2-busybox:$* \ + -f scripts/Containerfile.busybox scripts/ + @touch $@ + +ALPINE_GCC_IMAGES := $(foreach a,$(ALL_ARCHES),build/$(a)/.image-alpine-gcc) + +$(ALPINE_GCC_IMAGES): build/%/.image-alpine-gcc: scripts/Containerfile.alpine-gcc + mkdir -p $(@D) + podman build --platform $(PLATFORM_$*) \ + -t boot2-alpine-gcc:$* \ + -f scripts/Containerfile.alpine-gcc scripts/ + @touch $@ + +PODMAN = podman run --rm --pull=never --platform $(PLATFORM_$(1)) \ + --tmpfs /tmp:size=512M \ + -e ARCH=$(1) \ + -v $(CURDIR):/work -w /work boot2-busybox:$(1) + +ALPINE_GCC = podman run --rm --pull=never --platform $(PLATFORM_$(1)) \ + --tmpfs /tmp:size=128M \ + -e ARCH=$(1) \ + -v $(CURDIR):/work -w /work boot2-alpine-gcc:$(1) + +# ── Stage1 seed tools (M0/catm/hex2-0): vendored hex0-seed in container ── + +build/%/tools/M0 build/%/tools/catm build/%/tools/hex2-0 &: \ + scripts/mk-seed-tools.sh build/%/.image \ + vendor/seed/%/hex0-seed vendor/seed/%/hex0.hex0 \ + vendor/seed/%/hex1.hex0 vendor/seed/%/hex2.hex1 \ + vendor/seed/%/catm.hex2 vendor/seed/%/M0.hex2 \ + vendor/seed/%/ELF.hex2 + $(call PODMAN,$*) sh scripts/mk-seed-tools.sh + +.PHONY: tools +tools: $(foreach a,$(ALL_ARCHES),build/$(a)/tools/M0) + +# ── P1 backend tables (committed under P1/P1-<arch>.M1) ────────────────── + +P1_PRUNE_SRCS := M1pp/M1pp.P1 hex2pp/hex2pp.P1 $(wildcard tests/P1/*.P1) + +.PHONY: tables +tables: $(foreach a,$(ALL_ARCHES),P1/P1-$(a).M1) + +build/%/P1/P1.M1: $(wildcard P1/gen/*.py) + mkdir -p $(@D) + python3 P1/gen/p1_gen.py --arch $* --out $@ + +.SECONDARY: $(foreach a,$(ALL_ARCHES),build/$(a)/P1/P1.M1) + +P1/P1-%.M1: build/%/P1/P1.M1 scripts/prune-p1-table.sh $(P1_PRUNE_SRCS) + sh scripts/prune-p1-table.sh $< $@ $(P1_PRUNE_SRCS) + +# ── Per-arch self-hosted M1pp / hex2pp / scheme1 / cc binaries ─────────── + +M1PP_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/M1pp/M1pp) +HEX2PP_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/hex2pp/hex2pp) +SCHEME1_SRC := scheme1/scheme1.P1pp +SCHEME1_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/scheme1/scheme1) + +CC_SRCS := scheme1/prelude.scm cc/cc.scm cc/main.scm +CC_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/cc/cc.scm) + +P1_BUILD_DEPS = scripts/lint.sh tests/build-p1.sh \ + build/%/.image build/%/tools/M0 \ + vendor/seed/%/ELF.hex2 P1/P1-%.M1 + +P1PP_BUILD_DEPS = tests/build-p1pp.sh \ + build/%/.image \ + build/%/M1pp/M1pp build/%/hex2pp/hex2pp \ + vendor/seed/%/ELF.hex2 \ + P1/P1-%.M1pp P1/P1.M1pp P1/P1pp.P1pp + +$(M1PP_BINS): build/%/M1pp/M1pp: M1pp/M1pp.P1 $(P1_BUILD_DEPS) + ARCH=$* sh scripts/lint.sh M1pp/M1pp.P1 + $(call PODMAN,$*) sh tests/build-p1.sh M1pp/M1pp.P1 $@ + +$(HEX2PP_BINS): build/%/hex2pp/hex2pp: hex2pp/hex2pp.P1 $(P1_BUILD_DEPS) + ARCH=$* sh scripts/lint.sh hex2pp/hex2pp.P1 + $(call PODMAN,$*) sh tests/build-p1.sh hex2pp/hex2pp.P1 $@ + +$(SCHEME1_BINS): build/%/scheme1/scheme1: $(SCHEME1_SRC) $(P1PP_BUILD_DEPS) + $(call PODMAN,$*) sh tests/build-p1pp.sh $@ $(SCHEME1_SRC) + +$(CC_BINS): build/%/cc/cc.scm: $(CC_SRCS) build/%/.image build/%/tools/catm + mkdir -p $(@D) + $(call PODMAN,$*) build/$*/tools/catm $@ $(CC_SRCS) + +# ── tcc-boot2 chain (cc.scm-built tcc + mes-libc, tests-only) ──────────── + +TCC_TARGET_aarch64 := ARM64 +TCC_TARGET_amd64 := X86_64 +TCC_TARGET_riscv64 := RISCV64 +TCC_TARGET ?= $(TCC_TARGET_$(ARCH)) +TCC_VENDOR := build/$(ARCH)/vendor/tcc +TCC_FLAT := $(TCC_VENDOR)/tcc.flat.c +TCC_FLATS := $(foreach a,$(ALL_ARCHES),build/$(a)/vendor/tcc/tcc.flat.c) + +TCC_BOOT2_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/tcc-boot2/tcc-boot2) +TCC_BOOT2_P1PPS := $(foreach a,$(ALL_ARCHES),build/$(a)/tcc-boot2/tcc.flat.P1pp) + +LIBC_FLATS := $(foreach a,$(ALL_ARCHES),build/$(a)/vendor/mes-libc/libc.flat.c) +LIBC_P1PPS := $(foreach a,$(ALL_ARCHES),build/$(a)/vendor/mes-libc/libc.P1pp) + +$(TCC_FLATS): build/%/vendor/tcc/tcc.flat.c: scripts/stage1-flatten.sh + sh scripts/stage1-flatten.sh --arch $* + +build/%/vendor/tcc/stdarg-bridge.h: build/%/vendor/tcc/tcc.flat.c + +LIBC_VENDOR_SRCS := $(shell find vendor/mes-libc -type f \( -name '*.c' -o -name '*.h' \) 2>/dev/null) \ + $(wildcard vendor/mes-libc/patches/*.before) \ + $(wildcard vendor/mes-libc/patches/*.after) + +$(LIBC_FLATS): build/%/vendor/mes-libc/libc.flat.c: \ + scripts/libc-flatten.sh build/%/vendor/tcc/stdarg-bridge.h $(LIBC_VENDOR_SRCS) + sh scripts/libc-flatten.sh --arch $* + +CC_DEBUG ?= 0 +CC_TRACE_EMIT ?= 0 + +$(LIBC_P1PPS): build/%/vendor/mes-libc/libc.P1pp: \ + build/%/vendor/mes-libc/libc.flat.c \ + build/%/scheme1/scheme1 build/%/cc/cc.scm \ + tests/build-cc.sh build/%/.image + $(call PODMAN,$*) env CC_LIB=libc__ \ + CC_DEBUG=$(CC_DEBUG) CC_TRACE_EMIT=$(CC_TRACE_EMIT) \ + sh tests/build-cc.sh $< $@ + +$(TCC_BOOT2_P1PPS): build/%/tcc-boot2/tcc.flat.P1pp: \ + build/%/vendor/tcc/tcc.flat.c build/%/scheme1/scheme1 build/%/cc/cc.scm \ + tests/build-cc.sh build/%/.image + $(call PODMAN,$*) env CC_LIB=tcc__ \ + CC_DEBUG=$(CC_DEBUG) CC_TRACE_EMIT=$(CC_TRACE_EMIT) \ + sh tests/build-cc.sh build/$*/vendor/tcc/tcc.flat.c $@ + +$(TCC_BOOT2_BINS): build/%/tcc-boot2/tcc-boot2: \ + build/%/tcc-boot2/tcc.flat.P1pp build/%/vendor/mes-libc/libc.P1pp \ + P1/entry-libc.P1pp P1/elf-end.P1pp \ + $(P1PP_BUILD_DEPS) + $(call PODMAN,$*) env WORK_SUBPATH=tcc-boot2/tcc-boot2 \ + sh tests/build-p1pp.sh $@ \ + P1/entry-libc.P1pp build/$*/vendor/mes-libc/libc.P1pp \ + $< P1/elf-end.P1pp + +# ── tcc-gcc: same flatten, stock gcc (sanity check) ────────────────────── + +TCC_GCC_BIN := build/$(ARCH)/tcc-gcc/tcc-gcc +TCC_GCC_IMAGE := build/$(ARCH)/.image-alpine-gcc +TCC_GCC_HARNESS := tcc-gcc/$(ARCH)/start.S tcc-gcc/$(ARCH)/sys_stubs.c + +$(TCC_GCC_BIN): $(TCC_FLAT) build/$(ARCH)/vendor/mes-libc/libc.flat.c \ + $(TCC_GCC_HARNESS) scripts/build-tcc-gcc.sh $(TCC_GCC_IMAGE) + mkdir -p $(@D) + podman run --rm --pull=never --platform $(PLATFORM_$(ARCH)) \ + -e ARCH=$(ARCH) \ + -v $(CURDIR):/work -w /work boot2-alpine-gcc:$(ARCH) \ + sh scripts/build-tcc-gcc.sh $@ $(TCC_FLAT) \ + build/$(ARCH)/vendor/mes-libc/libc.flat.c + +# ── tcc-cc / tcc-libc / tcc-tcc / tcc-tcc-tcc test infrastructure ──────── + +HOST_CC ?= cc + +TCC_HARNESS_ARCHES := aarch64 amd64 riscv64 + +HOST_CC_TARGET_aarch64 := aarch64-linux-gnu +HOST_CC_TARGET_amd64 := x86_64-linux-gnu +HOST_CC_TARGET = $(HOST_CC_TARGET_$(ARCH)) + +ifeq ($(ARCH),aarch64) +TCC_ASM_DEPS := build/$(ARCH)/tcc-boot2/tcc-boot2 build/$(ARCH)/.image +TCC_ASM = $(call PODMAN,$(ARCH)) build/$(ARCH)/tcc-boot2/tcc-boot2 -nostdlib -c -o $(1) $(2) +else ifeq ($(ARCH),riscv64) +TCC_ASM_DEPS := build/$(ARCH)/.image-alpine-gcc +TCC_ASM = $(call ALPINE_GCC,$(ARCH)) cc -c -o $(1) -x assembler $(2) +else +TCC_ASM_DEPS := +TCC_ASM = $(HOST_CC) -target $(HOST_CC_TARGET) -c -o $(1) -x assembler $(2) +endif + +TCC_CC_START := build/$(ARCH)/tcc-cc/start.o +TCC_CC_MEM := build/$(ARCH)/tcc-cc/mem.o +TCC_CC_TCC_INCLUDE = $(TCC_VENDOR)/$(TCC_PKG)/include +TCC_CC_TCC_LIBDIR = $(TCC_VENDOR)/$(TCC_PKG)/lib + +ifeq ($(ARCH),amd64) +TCC_CC_VA_LIST := build/$(ARCH)/tcc-cc/va_list.o +else +TCC_CC_VA_LIST := +endif + +TCC_LIBC_DIR := build/$(ARCH)/tcc-libc +TCC_LIBC_START := $(TCC_LIBC_DIR)/start.o +TCC_LIBC_SYS_STUBS := $(TCC_LIBC_DIR)/sys_stubs.o +TCC_LIBC_MEM := $(TCC_LIBC_DIR)/mem.o +TCC_LIBC_LIBC := $(TCC_LIBC_DIR)/libc.o + +TCC_TCC_BIN := build/$(ARCH)/tcc-tcc/tcc-tcc +TCC_TCC_TCC_BIN := build/$(ARCH)/tcc-tcc-tcc/tcc-tcc-tcc + +$(TCC_CC_START): tcc-cc/$(ARCH)/start.S $(TCC_ASM_DEPS) + mkdir -p $(@D) + $(call TCC_ASM,$@,$<) + +$(TCC_CC_MEM): tcc-cc/mem.c \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + build/$(ARCH)/.image + mkdir -p $(@D) + $(call PODMAN,$(ARCH)) \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + -nostdlib -I $(TCC_CC_TCC_INCLUDE) -c -o $@ $< + +build/amd64/tcc-cc/va_list.o: \ + build/amd64/tcc-boot2/tcc-boot2 \ + build/amd64/.image build/amd64/vendor/tcc/tcc.flat.c + mkdir -p $(@D) + $(call PODMAN,amd64) \ + build/amd64/tcc-boot2/tcc-boot2 \ + -nostdlib -I build/amd64/vendor/tcc/$(TCC_PKG)/include \ + -D TCC_TARGET_X86_64=1 \ + -c -o $@ build/amd64/vendor/tcc/$(TCC_PKG)/lib/va_list.c + +$(TCC_LIBC_START): tcc-libc/$(ARCH)/start.S $(TCC_ASM_DEPS) + mkdir -p $(@D) + $(call TCC_ASM,$@,$<) + +$(TCC_LIBC_SYS_STUBS): tcc-libc/$(ARCH)/sys_stubs.S $(TCC_ASM_DEPS) + mkdir -p $(@D) + $(call TCC_ASM,$@,$<) + +$(TCC_LIBC_MEM): tcc-cc/mem.c \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + build/$(ARCH)/.image + mkdir -p $(@D) + $(call PODMAN,$(ARCH)) \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + -nostdlib -I $(TCC_CC_TCC_INCLUDE) -c -o $@ $< + +$(TCC_LIBC_LIBC): build/$(ARCH)/vendor/mes-libc/libc.flat.c \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + build/$(ARCH)/.image + mkdir -p $(@D) + $(call PODMAN,$(ARCH)) \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + -nostdlib -I $(TCC_CC_TCC_INCLUDE) \ + -include $(TCC_CC_TCC_INCLUDE)/stdarg.h \ + -c -o $@ $< + +$(TCC_TCC_BIN): scripts/boot-build-tcc-tcc.sh \ + $(TCC_FLAT) \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + $(TCC_LIBC_START) $(TCC_LIBC_SYS_STUBS) \ + $(TCC_LIBC_MEM) $(TCC_LIBC_LIBC) \ + build/$(ARCH)/.image + mkdir -p $(@D) + $(call PODMAN,$(ARCH)) \ + sh scripts/boot-build-tcc-tcc.sh $@ + +$(TCC_TCC_TCC_BIN): scripts/boot-build-tcc-tcc.sh \ + $(TCC_FLAT) \ + $(TCC_TCC_BIN) \ + $(TCC_LIBC_START) $(TCC_LIBC_SYS_STUBS) \ + $(TCC_LIBC_MEM) $(TCC_LIBC_LIBC) \ + build/$(ARCH)/.image + mkdir -p $(@D) + $(call PODMAN,$(ARCH)) \ + sh scripts/boot-build-tcc-tcc.sh $@ $(TCC_TCC_BIN) + +# ── Suite dispatch (proxies to tests/run.sh) ───────────────────────────── + +SUITE ?= + +ifeq ($(origin ARCH),file) + ARCH_FILTER := + TEST_ARCHES := $(ALL_ARCHES) +else + ARCH_FILTER := $(ARCH) + TEST_ARCHES := $(ARCH) +endif + +TEST_M1PP_DEPS := $(foreach a,$(TEST_ARCHES), \ + build/$(a)/.image build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \ + vendor/seed/$(a)/ELF.hex2) + +TEST_P1_DEPS := $(foreach a,$(TEST_ARCHES), \ + build/$(a)/.image build/$(a)/tools/M0 P1/P1-$(a).M1 \ + build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp vendor/seed/$(a)/ELF.hex2) + +TEST_SCHEME1_DEPS := $(foreach a,$(TEST_ARCHES), \ + build/$(a)/.image build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \ + build/$(a)/scheme1/scheme1 build/$(a)/tools/catm) + +TEST_CC_UNIT_DEPS := $(foreach a,$(TEST_ARCHES), \ + build/$(a)/.image build/$(a)/tools/catm \ + build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \ + build/$(a)/scheme1/scheme1) + +TEST_CC_DEPS := $(TEST_CC_UNIT_DEPS) \ + $(foreach a,$(TEST_ARCHES),build/$(a)/cc/cc.scm) + +TEST_CC_LIBC_DEPS := $(TEST_CC_DEPS) \ + $(foreach a,$(TEST_ARCHES),build/$(a)/vendor/mes-libc/libc.P1pp) \ + P1/entry-libc.P1pp P1/elf-end.P1pp + +TEST_TCC_CC_DEPS := build/$(ARCH)/.image \ + $(TCC_TCC_BIN) $(TCC_TCC_TCC_BIN) \ + $(TCC_CC_START) $(TCC_CC_MEM) $(TCC_CC_VA_LIST) + +TEST_TCC_LIBC_DEPS := build/$(ARCH)/.image \ + $(TCC_TCC_BIN) $(TCC_TCC_TCC_BIN) \ + $(TCC_LIBC_START) $(TCC_LIBC_SYS_STUBS) $(TCC_LIBC_MEM) $(TCC_LIBC_LIBC) \ + $(TCC_CC_VA_LIST) + +.PHONY: test +test: +ifeq ($(SUITE),) + @$(MAKE) --no-print-directory test SUITE=m1pp + @$(MAKE) --no-print-directory test SUITE=p1 + @$(MAKE) --no-print-directory test SUITE=scheme1 + @$(MAKE) --no-print-directory test SUITE=cc-util + @$(MAKE) --no-print-directory test SUITE=cc-lex + @$(MAKE) --no-print-directory test SUITE=cc-pp + @$(MAKE) --no-print-directory test SUITE=cc-cg + @$(MAKE) --no-print-directory test SUITE=cc + @$(MAKE) --no-print-directory test SUITE=cc-libc +else ifeq ($(SUITE),m1pp) + @$(MAKE) --no-print-directory $(TEST_M1PP_DEPS) + sh tests/run.sh --suite=m1pp $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) +else ifeq ($(SUITE),p1) + @$(MAKE) --no-print-directory $(TEST_P1_DEPS) + sh tests/run.sh --suite=p1 $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) +else ifeq ($(SUITE),scheme1) + @$(MAKE) --no-print-directory $(TEST_SCHEME1_DEPS) + sh tests/run.sh --suite=scheme1 $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) +else ifeq ($(filter $(SUITE),cc-util cc-lex cc-pp cc-cg),$(SUITE)) + @$(MAKE) --no-print-directory $(TEST_CC_UNIT_DEPS) + sh tests/run.sh --suite=$(SUITE) $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) +else ifeq ($(SUITE),cc) + @$(MAKE) --no-print-directory $(TEST_CC_DEPS) + sh tests/run.sh --suite=cc $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) +else ifeq ($(SUITE),cc-libc) + @$(MAKE) --no-print-directory $(TEST_CC_LIBC_DEPS) + sh tests/run.sh --suite=cc-libc $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) +else ifeq ($(SUITE),cc-ext) + @$(MAKE) --no-print-directory $(TEST_CC_LIBC_DEPS) + sh tests/run.sh --suite=cc-ext $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) +else ifeq ($(SUITE),tcc-cc) + @if [ -z "$(filter $(ARCH),$(TCC_HARNESS_ARCHES))" ]; then \ + echo "tcc-cc supports ARCH in {$(TCC_HARNESS_ARCHES)} only (got '$(ARCH)')" >&2; exit 2; \ + fi + @$(MAKE) --no-print-directory ARCH=$(ARCH) $(TEST_TCC_CC_DEPS) + @s2=0; s3=0; \ + sh tests/run.sh --suite=tcc-cc --arch=$(ARCH) --stage=2 $(NAMES) || s2=$$?; \ + sh tests/run.sh --suite=tcc-cc --arch=$(ARCH) --stage=3 $(NAMES) || s3=$$?; \ + [ $$s2 -eq 0 ] && [ $$s3 -eq 0 ] +else ifeq ($(SUITE),tcc-libc) + @if [ -z "$(filter $(ARCH),$(TCC_HARNESS_ARCHES))" ]; then \ + echo "tcc-libc supports ARCH in {$(TCC_HARNESS_ARCHES)} only (got '$(ARCH)')" >&2; exit 2; \ + fi + @$(MAKE) --no-print-directory ARCH=$(ARCH) $(TEST_TCC_LIBC_DEPS) + @s2=0; s3=0; \ + sh tests/run.sh --suite=tcc-libc --arch=$(ARCH) --stage=2 $(NAMES) || s2=$$?; \ + sh tests/run.sh --suite=tcc-libc --arch=$(ARCH) --stage=3 $(NAMES) || s3=$$?; \ + [ $$s2 -eq 0 ] && [ $$s3 -eq 0 ] +else + @echo "unknown SUITE='$(SUITE)' (m1pp | p1 | scheme1 | cc-util | cc-lex | cc-pp | cc-cg | cc | cc-libc | cc-ext | tcc-cc | tcc-libc)" >&2; exit 2 +endif + +endif # REPO_ROOT diff --git a/tests/P1/argc_exit.P1pp b/tests/P1/000-argc_exit.P1pp diff --git a/tests/P1/argc_exit.expected b/tests/P1/000-argc_exit.expected diff --git a/tests/P1/cmpset.P1pp b/tests/P1/001-cmpset.P1pp diff --git a/tests/P1/cmpset.expected b/tests/P1/001-cmpset.expected diff --git a/tests/P1/double.P1pp b/tests/P1/002-double.P1pp diff --git a/tests/P1/double.expected b/tests/P1/002-double.expected diff --git a/tests/P1/ext-macros.P1pp b/tests/P1/003-ext-macros.P1pp diff --git a/tests/P1/ext-macros.expected b/tests/P1/003-ext-macros.expected diff --git a/tests/P1/hello.P1pp b/tests/P1/004-hello.P1pp diff --git a/tests/P1/hello.expected b/tests/P1/004-hello.expected diff --git a/tests/P1/large-addi.P1pp b/tests/P1/005-large-addi.P1pp diff --git a/tests/P1/large-addi.expected b/tests/P1/005-large-addi.expected diff --git a/tests/P1/lea-slot.P1pp b/tests/P1/006-lea-slot.P1pp diff --git a/tests/P1/lea-slot.expected b/tests/P1/006-lea-slot.expected diff --git a/tests/P1/loop-tag-scoping.P1pp b/tests/P1/007-loop-tag-scoping.P1pp diff --git a/tests/P1/loop-tag-scoping.expected b/tests/P1/007-loop-tag-scoping.expected diff --git a/tests/P1/memcpy-call.P1pp b/tests/P1/008-memcpy-call.P1pp diff --git a/tests/P1/memcpy-call.expected b/tests/P1/008-memcpy-call.expected diff --git a/tests/P1/p1-aliasing.P1pp b/tests/P1/009-p1-aliasing.P1pp diff --git a/tests/P1/p1-aliasing.expected b/tests/P1/009-p1-aliasing.expected diff --git a/tests/P1/p1-call.P1pp b/tests/P1/010-p1-call.P1pp diff --git a/tests/P1/p1-call.expected b/tests/P1/010-p1-call.expected diff --git a/tests/P1/ptr-arith.P1pp b/tests/P1/011-ptr-arith.P1pp diff --git a/tests/P1/ptr-arith.expected b/tests/P1/011-ptr-arith.expected diff --git a/tests/P1/sub-word-mem.P1pp b/tests/P1/012-sub-word-mem.P1pp diff --git a/tests/P1/sub-word-mem.expected b/tests/P1/012-sub-word-mem.expected diff --git a/tests/P1/switch-case.P1pp b/tests/P1/013-switch-case.P1pp diff --git a/tests/P1/switch-case.expected b/tests/P1/013-switch-case.expected diff --git a/tests/P1/sys_calls.P1pp b/tests/P1/014-sys_calls.P1pp diff --git a/tests/P1/sys_calls.expected b/tests/P1/014-sys_calls.expected diff --git a/tests/P1/unops.P1pp b/tests/P1/015-unops.P1pp diff --git a/tests/P1/unops.expected b/tests/P1/015-unops.expected diff --git a/tests/P1/wide-imm.P1pp b/tests/P1/016-wide-imm.P1pp diff --git a/tests/P1/wide-imm.expected b/tests/P1/016-wide-imm.expected diff --git a/tests/README.md b/tests/README.md @@ -0,0 +1,110 @@ +# tests/ + +Test suites for the boot2 chain. The top-level `Makefile` includes +`tests/Makefile`, so `make test` from the repo root resolves here. You +can also `make -C tests test-…`; that delegates back to the parent. + +## Layout + +``` +tests/ +├── Makefile # suite + build-dep rules (included from top-level) +├── run.sh # host-side dispatcher (per-arch podman, lint preflight) +├── run-suite.sh # in-container suite driver (one invocation per arch) +├── lib-runner.sh # shared helpers sourced by run-suite.sh +├── build-p1.sh # in-container .P1 -> ELF (M0 + hex2 chain) +├── build-p1pp.sh # in-container .P1pp -> ELF (self-hosted M1pp/hex2pp) +├── build-cc.sh # in-container .c -> .P1pp (scheme1 + cc.scm) +├── seed-accept.sh # seed-driver acceptance (kernel | boot34 | boot5) +├── M1pp/ # macro-expander parity tests +├── P1/ # P1pp pipeline + standalone .P1 tests +├── scheme1/ # scheme1 interpreter tests +├── cc-util/ # cc.scm prelude + util byte-diff +├── cc-lex/ # cc.scm lex pipeline byte-diff +├── cc-pp/ # cc.scm pp pipeline byte-diff +├── cc-cg/ # cc.scm cg emit -> P1pp -> ELF -> run +├── cc/ # full cc.scm: .c -> P1pp -> ELF -> run +├── cc-libc/ # cc + mes-libc link -> ELF -> run +└── simple-patches/ # patch-application tests +``` + +`vendor/c-testsuite/single-exec/` (outside `tests/`) is the input set +for the `cc-ext` suite. + +## Running + +```sh +make test # all suites, default ARCH +make test SUITE=cc # one suite +make test SUITE=cc NAMES='001 042' # filter by fixture-name prefix +make test SUITE=cc-libc ARCH=amd64 # one arch +make test SUITE=tcc-cc ARCH=amd64 STAGE=2 # tcc-built test runners +``` + +`make image` builds the per-arch `boot2-busybox` container used by all +podman-driven suites. + +## Per-suite contract + +Every suite picks fixtures by `<name>.<input-ext>` under its directory +and compares against `<name>.expected` (for the suite's primary output) +and/or `<name>.expected-exit` (for the runtime exit code). Missing +expected files default to empty stdout / exit 0. + +Names start with a 3-digit prefix (`NNN-`) so listings stay stable as +suites grow. + +| Suite | Fixture | Goldens | Pipeline | +|-----------|-------------------|--------------------------------------|--------------------------------------------------------------------| +| `m1pp` | `<name>.M1pp` | `.expected` | `M1pp <fixture> > out`; diff `out` vs `.expected` | +| `p1` | `<name>.P1pp` | `.expected` | `build-p1pp.sh` → ELF → run; diff stdout | +| `scheme1` | `<name>.scm` | `.expected`, `.expected-exit` | `scheme1 <fixture>`; diff stdout + exit | +| `cc-util` | `<name>.scm` | `.expected`, `.expected-exit` | `catm prelude+cc.scm+<fixture>` → `scheme1`; diff stdout + exit | +| `cc-lex` | `<name>.c` | `.expected`, `.expected-exit` | lex pipeline; diff stdout (`;;` lines stripped) | +| `cc-pp` | `<name>.c`/`.scm` | `.expected`, `.expected-exit` | pp pipeline (.c) or unit (.scm); diff stdout | +| `cc-cg` | `<name>.scm` | `.expected`, `.expected-exit` | cg emit → `build-p1pp.sh` → ELF → run; diff stdout + exit | +| `cc` | `<name>.c` | `.expected`, `.expected-exit` | `cc.scm` → `build-p1pp.sh` → ELF → run; diff stdout + exit | +| `cc-libc` | `<name>.c` | `.expected`, `.expected-exit` | `cc.scm` (lib mode) + entry-libc + libc.P1pp → ELF; diff | +| `cc-ext` | `<name>.c` | `<name>.c.expected`, `<name>.c.tags` | upstream c-testsuite fixtures; `needs-libc` tag forces libc link | +| `tcc-cc` | `tests/cc/...` | (reuses cc/) | self-built tcc → ELF → run; STAGE=2 (tcc-tcc) or 3 (tcc-tcc-tcc) | +| `tcc-libc`| `tests/cc-libc/...`| (reuses cc-libc/) | self-built tcc + per-arch start/sys_stubs/libc.o → ELF → run | + +The `cc-pp` row covers two fixture passes inside one suite: `.c` +fixtures go through the lex+pp pipeline; the lone `.scm` fixture +(`022-initial-defines.scm` at time of writing) drives the unit-suite +path because it exercises `-D` initial-define plumbing the .c fixture +driver doesn't expose. + +## Exit semantics + +- A suite returns 0 only if every fixture PASSed. +- A fixture FAILs if any sub-step (compile, assemble, run) errors, or + if stdout or exit doesn't match the golden. +- Compile/assemble failures dump the relevant `.log` from `build/<arch> + /.work/tests/<suite>/<name>/` for triage; the binary itself is not + retained when assembly fails. + +## Adding a fixture + +1. Pick the lowest unused `NNN-` index in the target suite. +2. Drop `<NNN>-<name>.<input-ext>` plus `.expected` (and + `.expected-exit` if the program is supposed to exit non-zero). +3. Run `make test SUITE=<suite> NAMES=<NNN>-<name>` to verify. + +## seed-driver acceptance + +`tests/seed-accept.sh` is separate from `make test`. It runs the +seed-kernel under QEMU and checks byte-equivalence against podman-built +artifacts: + +```sh +tests/seed-accept.sh # full Tier-2 (default mode=kernel) +tests/seed-accept.sh boot34 # boot3/4 byte-eq vs podman +WITH_BOOT4=1 tests/seed-accept.sh boot34 +tests/seed-accept.sh boot5 # boot5 byte-eq vs podman +``` + +ARCH is fixed to `aarch64` since that's the only seed-driver-complete +arch today. Prereq for every mode: `./scripts/boot.sh aarch64` has run +under the default `DRIVER=podman` so `build/aarch64/podman/boot{0..6}/` +is populated. diff --git a/tests/build-cc.sh b/tests/build-cc.sh @@ -0,0 +1,52 @@ +#!/bin/sh +## tests/build-cc.sh — in-container .c -> .P1pp via scheme1 + cc.scm. +## +## Pure transformation. Caller (the Makefile) ensures every fixed-path +## input below already exists: the per-arch scheme1 ELF and the catm'd +## cc.scm source. Mirrors tests/build-p1pp.sh's contract: env-driven, +## one thing only, no host work. +## +## Env: ARCH=aarch64|amd64|riscv64 +## CC_DEBUG=1 (optional) — pass --cc-debug to cc.scm so it prints +## per-phase heap usage on stderr. +## CC_TRACE_EMIT=1 (optional) — pass --cc-trace-emit so cc.scm +## wraps every emitted function with a `%trace(<mangled>)` +## call at entry. Pair with libp1pp's %trace macro and +## libp1pp__trace runtime helper (in P1/P1pp.P1pp) to +## produce a stderr line per function entry at runtime. +## CC_LIB=PFX (optional) — compile in library mode (cc.scm +## --lib=PFX). Skips cc.scm's auto-emitted entry +## stub and trailing :ELF_end so the output catm's +## into a larger link, and namespaces anonymous +## string labels as PFX+"cc__str_N" to avoid +## collisions with other cc.scm outputs in the +## same chain. The catm caller then prepends +## P1/entry-{plain,libc}.P1pp and appends +## P1/elf-end.P1pp exactly once each. +## Usage: tests/build-cc.sh <src.c> <out.P1pp> + +set -eu + +: "${ARCH:?ARCH must be set}" +[ "$#" -eq 2 ] || { echo "usage: ARCH=<arch> $0 <src> <out>" >&2; exit 2; } + +SRC=$1 +OUT=$2 + +SCHEME1_BIN=build/$ARCH/scheme1/scheme1 +CC_SRC=build/$ARCH/cc/cc.scm + +[ -x "$SCHEME1_BIN" ] || { echo "missing $SCHEME1_BIN" >&2; exit 1; } +[ -e "$CC_SRC" ] || { echo "missing $CC_SRC" >&2; exit 1; } +[ -e "$SRC" ] || { echo "missing $SRC" >&2; exit 1; } + +mkdir -p "$(dirname "$OUT")" + +# Build cc-flag list once. Order doesn't matter to cc-main but +# stays stable for log readability. +set -- +[ "${CC_DEBUG:-0}" = "1" ] && set -- "$@" --cc-debug +[ "${CC_TRACE_EMIT:-0}" = "1" ] && set -- "$@" --cc-trace-emit +[ -n "${CC_LIB:-}" ] && set -- "$@" "--lib=$CC_LIB" + +"$SCHEME1_BIN" "$CC_SRC" "$@" "$SRC" "$OUT" diff --git a/tests/build-p1.sh b/tests/build-p1.sh @@ -0,0 +1,90 @@ +#!/bin/sh +## tests/build-p1.sh — in-container .P1/.M1 -> ELF. +## +## Pure transformation. Caller (the Makefile) ensures every fixed-path +## input below already exists. Only the variable per-call inputs (source, +## output binary) come in as args. +## +## Pipeline: +## cat <P1/P1-$ARCH.M1> <src> -> /tmp/combined.M1 +## M0 /tmp/combined.M1 -> /tmp/prog.hex2 +## catm /tmp/elf.hex2 /tmp/prog.hex2 -> /tmp/linked.hex2 +## hex2-0 /tmp/linked.hex2 -> $OUT +## +## Stages through /tmp because the stage0 tools do one syscall per byte; +## virtiofs round-trips would dominate otherwise. +## +## Per-call intermediates (combined.M1, prog.hex2, linked.hex2) land at +## build/$ARCH/.work/<src-without-ext>/, mirroring the source path under +## the repo root (e.g. tests/P1/00-hello.P1 -> build/aarch64/.work/ +## tests/P1/00-hello/). A one-line sidecar at <out>.workdir records +## that path so tooling (scripts/disasm-elf.sh) can find the artifacts +## from the binary alone. +## +## Env: ARCH=aarch64|amd64|riscv64 +## Usage: tests/build-p1.sh <src> <out> + +set -eu + +# Per-stage tracing is always on. Stage0 tools (M0, hex2-0) print +# nothing on success and almost nothing on failure, so we narrate which +# step is running, snapshot intermediates to $WORK after each one, and +# print a clear FAIL banner so the user knows where it died. +ARCH_LBL=${ARCH:-?} +CURRENT_STEP= +trap 'rc=$? +if [ "$rc" -ne 0 ] && [ -n "$CURRENT_STEP" ]; then + echo "[p1 $ARCH_LBL] FAIL at: $CURRENT_STEP (exit $rc)" >&2 + if [ -n "${WORK:-}" ]; then + echo "[p1 $ARCH_LBL] partial intermediates in $WORK" >&2 + fi +fi' EXIT + +trace() { + label=$1; path=$2 + sz=$(wc -c < "$path" 2>/dev/null || echo "?") + printf '[p1 %s] %s (%s bytes) %s\n' "$ARCH_LBL" "$label" "$sz" "$path" >&2 +} + +step() { + CURRENT_STEP=$1 + printf '[p1 %s] >> %s\n' "$ARCH_LBL" "$CURRENT_STEP" >&2 +} + +: "${ARCH:?ARCH must be set}" +[ "$#" -eq 2 ] || { echo "usage: ARCH=<arch> $0 <src> <out>" >&2; exit 2; } + +SRC=$1 +OUT=$2 + +TABLE=P1/P1-$ARCH.M1 +ELF_HDR=vendor/seed/$ARCH/ELF.hex2 +TOOLS=build/$ARCH/tools +NAME=${SRC%.*} +WORK=build/$ARCH/.work/$NAME +mkdir -p "$WORK" "$(dirname "$OUT")" + +step "cat: P1 table + $SRC -> combined.M1" +cat "$TABLE" "$SRC" > /tmp/combined.M1 +cp /tmp/combined.M1 "$WORK/combined.M1" +trace "combined.M1" /tmp/combined.M1 + +step "M0: combined.M1 -> prog.hex2" +"$TOOLS/M0" /tmp/combined.M1 /tmp/prog.hex2 +cp /tmp/prog.hex2 "$WORK/prog.hex2" +trace "prog.hex2" /tmp/prog.hex2 + +step "catm: ELF header + prog.hex2 -> linked.hex2" +cp "$ELF_HDR" /tmp/elf.hex2 +"$TOOLS/catm" /tmp/linked.hex2 /tmp/elf.hex2 /tmp/prog.hex2 +cp /tmp/linked.hex2 "$WORK/linked.hex2" +trace "linked.hex2" /tmp/linked.hex2 + +step "hex2-0: linked.hex2 -> $OUT" +"$TOOLS/hex2-0" /tmp/linked.hex2 /tmp/prog.bin +cp /tmp/prog.bin "$OUT" +chmod 0700 "$OUT" +trace "$OUT" "$OUT" + +printf '%s\n' "$WORK" > "$OUT.workdir" +CURRENT_STEP= diff --git a/tests/build-p1pp.sh b/tests/build-p1pp.sh @@ -0,0 +1,126 @@ +#!/bin/sh +## tests/build-p1pp.sh — in-container .P1pp -> ELF via the new chain. +## +## Pure transformation. Caller (the Makefile) ensures every fixed-path +## input below already exists, including the per-arch self-hosted M1pp +## ELF binary (build/$ARCH/M1pp/M1pp) and hex2pp ELF binary +## (build/$ARCH/hex2pp/hex2pp). Both of those are built once via the +## seed M0+hex2 chain (tests/build-p1.sh); after that point the seed +## tools no longer participate in any user/test pipeline. +## +## Pipeline (new chain — no M0/hex2 anywhere): +## cat <P1-$ARCH.M1pp> <P1.M1pp> <P1pp.P1pp> <srcs...> -> /tmp/combined.M1pp +## M1pp /tmp/combined.M1pp -> /tmp/expanded.hex2pp +## cat $ELF_HDR /tmp/expanded.hex2pp -> /tmp/linked.hex2pp +## hex2pp /tmp/linked.hex2pp $OUT +## +## $ELF_HDR is vendor/seed/$ARCH/ELF.hex2, which supplies the +## :ELF_base / :_start / :ELF_end framing. hex2pp accepts the hex2 +## `LABEL>OTHER` subtraction syntax as a synonym for its own +## `LABEL-OTHER`, so the vendor headers assemble unchanged. +## +## libp1pp (P1/P1pp.P1pp) is concatenated unconditionally so portable +## sources can use %fn, the control-flow macros, and libp1pp routines +## (sys_*, print*, parse_*, fmt_*, memcpy/memcmp, bump allocator, panic, +## %assert_*) without per-program plumbing. hex2pp has no link-time DCE, +## so programs that don't reference any libp1pp routine still pay a +## fixed code-size tax (~a few KB). +## +## Multiple <srcs> are concatenated in the order given. This is how +## libc-using executables compose: a typical chain is +## P1/entry-libc.P1pp build/$ARCH/vendor/mes-libc/libc.P1pp client.P1pp P1/elf-end.P1pp +## with libc.P1pp / client.P1pp produced by cc.scm --lib=PFX so they +## omit the entry stub and trailing :ELF_end (those come from the +## fixed fragments instead). For a single-TU exec, pass exactly one +## source built without --lib= and the fragments are unnecessary. +## +## Per-call intermediates land at build/$ARCH/.work/<work-subpath>/. +## <work-subpath> defaults to the first src's path with extension +## stripped — fine for single-source builds (scheme1, p1 tests). For +## catm chains where the first src is a wrapper (e.g. P1/entry-libc.P1pp +## or a generated build/.../*.P1pp), the caller MUST set WORK_SUBPATH +## explicitly so the work dir mirrors the logical primary source path +## (e.g. tests/cc-libc/000-exit). A one-line sidecar at <out>.workdir +## records the resolved work dir so tooling (scripts/disasm-elf.sh) can +## locate the artifacts from the binary alone. +## +## Env: ARCH=aarch64|amd64|riscv64 +## WORK_SUBPATH=<repo-relative-path-without-ext> — overrides the +## work-dir name; required when the first src isn't +## the logical primary source. +## Usage: tests/build-p1pp.sh <out> <srcs...> + +set -eu + +# Per-stage tracing is always on. M1pp / hex2pp print little on success +# and bail fast on error, so we narrate which step is running, snapshot +# intermediates to $WORK before exiting, and print a clear FAIL banner +# on error so the user knows where it died. +ARCH_LBL=${ARCH:-?} +CURRENT_STEP= +trap 'rc=$? +if [ "$rc" -ne 0 ] && [ -n "$CURRENT_STEP" ]; then + echo "[p1pp $ARCH_LBL] FAIL at: $CURRENT_STEP (exit $rc)" >&2 + if [ -n "${WORK:-}" ]; then + echo "[p1pp $ARCH_LBL] partial intermediates in $WORK" >&2 + fi +fi' EXIT + +trace() { + label=$1; path=$2 + sz=$(wc -c < "$path" 2>/dev/null || echo "?") + printf '[p1pp %s] %s (%s bytes) %s\n' "$ARCH_LBL" "$label" "$sz" "$path" >&2 +} + +step() { + CURRENT_STEP=$1 + printf '[p1pp %s] >> %s\n' "$ARCH_LBL" "$CURRENT_STEP" >&2 +} + +: "${ARCH:?ARCH must be set}" +[ "$#" -ge 2 ] || { echo "usage: ARCH=<arch> $0 <out> <srcs...>" >&2; exit 2; } + +OUT=$1 +shift + +BACKEND=P1/P1-$ARCH.M1pp +FRONTEND=P1/P1.M1pp +LIBP1PP=P1/P1pp.P1pp +ELF_HDR=vendor/seed/$ARCH/ELF.hex2 +M1PP_BIN=build/$ARCH/M1pp/M1pp +HEX2PP_BIN=build/$ARCH/hex2pp/hex2pp +if [ -n "${WORK_SUBPATH:-}" ]; then + NAME=$WORK_SUBPATH +else + NAME=${1%.*} +fi +WORK=build/$ARCH/.work/$NAME +mkdir -p "$WORK" "$(dirname "$OUT")" + +# Snapshot each intermediate to $WORK as soon as it's produced, so a +# failure leaves the most-recent good artifact on disk for triage. With +# the trap above, the user sees both the failing stage and where to +# look. +step "cat: combined.M1pp <- backend + frontend + libp1pp + $#" +cat "$BACKEND" "$FRONTEND" "$LIBP1PP" "$@" > /tmp/combined.M1pp +cp /tmp/combined.M1pp "$WORK/combined.M1pp" +trace "combined.M1pp" /tmp/combined.M1pp + +step "M1pp: combined.M1pp -> expanded.hex2pp" +"$M1PP_BIN" /tmp/combined.M1pp /tmp/expanded.hex2pp +cp /tmp/expanded.hex2pp "$WORK/expanded.hex2pp" +trace "expanded.hex2pp" /tmp/expanded.hex2pp + +step "cat: linked.hex2pp <- ELF header + expanded.hex2pp" +cat "$ELF_HDR" /tmp/expanded.hex2pp > /tmp/linked.hex2pp +cp /tmp/linked.hex2pp "$WORK/linked.hex2pp" +trace "linked.hex2pp" /tmp/linked.hex2pp + +step "hex2pp: linked.hex2pp -> $OUT" +"$HEX2PP_BIN" -B 0x600000 /tmp/linked.hex2pp /tmp/prog.bin +cp /tmp/prog.bin "$OUT" +chmod 0700 "$OUT" +trace "$OUT" "$OUT" + +printf '%s\n' "$WORK" > "$OUT.workdir" +CURRENT_STEP= diff --git a/tests/cc-cg/00-fn-empty.scm b/tests/cc-cg/00-fn-empty.scm @@ -1,10 +0,0 @@ -;; tests/cc-cg/00-fn-empty.scm — minimal direct-cg test. -;; Models: int main(void) { return 0; } -;; Runtime: exits 0. - -(let ((cg (cg-init))) - (cg-fn-begin cg "main" '() %t-i32) - (cg-push-imm cg %t-i32 0) - (cg-return cg) - (cg-fn-end cg) - (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/00-fn-empty.expected-exit b/tests/cc-cg/000-fn-empty.expected-exit diff --git a/tests/cc-cg/000-fn-empty.scm b/tests/cc-cg/000-fn-empty.scm @@ -0,0 +1,10 @@ +;; tests/cc-cg/000-fn-empty.scm — minimal direct-cg test. +;; Models: int main(void) { return 0; } +;; Runtime: exits 0. + +(let ((cg (cg-init))) + (cg-fn-begin cg "main" '() %t-i32) + (cg-push-imm cg %t-i32 0) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/01-return-imm.expected-exit b/tests/cc-cg/001-return-imm.expected-exit diff --git a/tests/cc-cg/01-return-imm.scm b/tests/cc-cg/001-return-imm.scm diff --git a/tests/cc-cg/02-one-param.expected-exit b/tests/cc-cg/002-one-param.expected-exit diff --git a/tests/cc-cg/02-one-param.scm b/tests/cc-cg/002-one-param.scm diff --git a/tests/cc-cg/03-two-params.expected-exit b/tests/cc-cg/003-two-params.expected-exit diff --git a/tests/cc-cg/03-two-params.scm b/tests/cc-cg/003-two-params.scm diff --git a/tests/cc-cg/04-binop-add.expected-exit b/tests/cc-cg/004-binop-add.expected-exit diff --git a/tests/cc-cg/04-binop-add.scm b/tests/cc-cg/004-binop-add.scm diff --git a/tests/cc-cg/05-load-binop-store.expected-exit b/tests/cc-cg/005-load-binop-store.expected-exit diff --git a/tests/cc-cg/05-load-binop-store.scm b/tests/cc-cg/005-load-binop-store.scm diff --git a/tests/cc-cg/06-if.expected-exit b/tests/cc-cg/006-if.expected-exit diff --git a/tests/cc-cg/06-if.scm b/tests/cc-cg/006-if.scm diff --git a/tests/cc-cg/07-ifelse.expected-exit b/tests/cc-cg/007-ifelse.expected-exit diff --git a/tests/cc-cg/07-ifelse.scm b/tests/cc-cg/007-ifelse.scm diff --git a/tests/cc-cg/08-while-break.expected-exit b/tests/cc-cg/008-while-break.expected-exit diff --git a/tests/cc-cg/08-while-break.scm b/tests/cc-cg/008-while-break.scm diff --git a/tests/cc-cg/09-call.expected-exit b/tests/cc-cg/009-call.expected-exit diff --git a/tests/cc-cg/09-call.scm b/tests/cc-cg/009-call.scm diff --git a/tests/cc-cg/10-string.expected-exit b/tests/cc-cg/010-string.expected-exit diff --git a/tests/cc-cg/10-string.scm b/tests/cc-cg/010-string.scm diff --git a/tests/cc-cg/11-global-var.expected-exit b/tests/cc-cg/011-global-var.expected-exit diff --git a/tests/cc-cg/11-global-var.scm b/tests/cc-cg/011-global-var.scm diff --git a/tests/cc-cg/13-call-5args.expected-exit b/tests/cc-cg/013-call-5args.expected-exit diff --git a/tests/cc-cg/13-call-5args.scm b/tests/cc-cg/013-call-5args.scm diff --git a/tests/cc-cg/14-take-addr.expected-exit b/tests/cc-cg/014-take-addr.expected-exit diff --git a/tests/cc-cg/14-take-addr.scm b/tests/cc-cg/014-take-addr.scm diff --git a/tests/cc-cg/15-char-roundtrip.expected-exit b/tests/cc-cg/015-char-roundtrip.expected-exit diff --git a/tests/cc-cg/15-char-roundtrip.scm b/tests/cc-cg/015-char-roundtrip.scm diff --git a/tests/cc-cg/16-short-roundtrip.expected-exit b/tests/cc-cg/016-short-roundtrip.expected-exit diff --git a/tests/cc-cg/16-short-roundtrip.scm b/tests/cc-cg/016-short-roundtrip.scm diff --git a/tests/cc-cg/17-int-roundtrip.expected-exit b/tests/cc-cg/017-int-roundtrip.expected-exit diff --git a/tests/cc-cg/17-int-roundtrip.scm b/tests/cc-cg/017-int-roundtrip.scm diff --git a/tests/cc-cg/18-sext-narrow.expected-exit b/tests/cc-cg/018-sext-narrow.expected-exit diff --git a/tests/cc-cg/18-sext-narrow.scm b/tests/cc-cg/018-sext-narrow.scm diff --git a/tests/cc-cg/19-zext-narrow.expected-exit b/tests/cc-cg/019-zext-narrow.expected-exit diff --git a/tests/cc-cg/19-zext-narrow.scm b/tests/cc-cg/019-zext-narrow.scm diff --git a/tests/cc-cg/20-promote-sign.expected-exit b/tests/cc-cg/020-promote-sign.expected-exit diff --git a/tests/cc-cg/20-promote-sign.scm b/tests/cc-cg/020-promote-sign.scm diff --git a/tests/cc-cg/21-preinc.expected-exit b/tests/cc-cg/021-preinc.expected-exit diff --git a/tests/cc-cg/21-preinc.scm b/tests/cc-cg/021-preinc.scm diff --git a/tests/cc-cg/22-postinc.expected-exit b/tests/cc-cg/022-postinc.expected-exit diff --git a/tests/cc-cg/22-postinc.scm b/tests/cc-cg/022-postinc.scm diff --git a/tests/cc-cg/23-cmpd-simple.expected-exit b/tests/cc-cg/023-cmpd-simple.expected-exit diff --git a/tests/cc-cg/23-cmpd-simple.scm b/tests/cc-cg/023-cmpd-simple.scm diff --git a/tests/cc-cg/24-cmpd-ptr.expected-exit b/tests/cc-cg/024-cmpd-ptr.expected-exit diff --git a/tests/cc-cg/24-cmpd-ptr.scm b/tests/cc-cg/024-cmpd-ptr.scm diff --git a/tests/cc-cg/25-deref-postinc.expected-exit b/tests/cc-cg/025-deref-postinc.expected-exit diff --git a/tests/cc-cg/25-deref-postinc.scm b/tests/cc-cg/025-deref-postinc.scm diff --git a/tests/cc-cg/28-ternary.expected-exit b/tests/cc-cg/028-ternary.expected-exit diff --git a/tests/cc-cg/28-ternary.scm b/tests/cc-cg/028-ternary.scm diff --git a/tests/cc-cg/29-land.expected-exit b/tests/cc-cg/029-land.expected-exit diff --git a/tests/cc-cg/29-land.scm b/tests/cc-cg/029-land.scm diff --git a/tests/cc-cg/30-lor.expected-exit b/tests/cc-cg/030-lor.expected-exit diff --git a/tests/cc-cg/30-lor.scm b/tests/cc-cg/030-lor.scm diff --git a/tests/cc-cg/36-struct-load.expected-exit b/tests/cc-cg/036-struct-load.expected-exit diff --git a/tests/cc-cg/36-struct-load.scm b/tests/cc-cg/036-struct-load.scm diff --git a/tests/cc-cg/37-struct-store.expected-exit b/tests/cc-cg/037-struct-store.expected-exit diff --git a/tests/cc-cg/37-struct-store.scm b/tests/cc-cg/037-struct-store.scm diff --git a/tests/cc-cg/38-arrow.expected-exit b/tests/cc-cg/038-arrow.expected-exit diff --git a/tests/cc-cg/38-arrow.scm b/tests/cc-cg/038-arrow.scm diff --git a/tests/cc-cg/40-array-index.expected-exit b/tests/cc-cg/040-array-index.expected-exit diff --git a/tests/cc-cg/40-array-index.scm b/tests/cc-cg/040-array-index.scm diff --git a/tests/cc-cg/49-init-scalar-global.expected-exit b/tests/cc-cg/049-init-scalar-global.expected-exit diff --git a/tests/cc-cg/49-init-scalar-global.scm b/tests/cc-cg/049-init-scalar-global.scm diff --git a/tests/cc-cg/50-init-addr.expected-exit b/tests/cc-cg/050-init-addr.expected-exit diff --git a/tests/cc-cg/50-init-addr.scm b/tests/cc-cg/050-init-addr.scm diff --git a/tests/cc-cg/51-init-array-list.expected-exit b/tests/cc-cg/051-init-array-list.expected-exit diff --git a/tests/cc-cg/51-init-array-list.scm b/tests/cc-cg/051-init-array-list.scm diff --git a/tests/cc-cg/52-init-array-str.expected-exit b/tests/cc-cg/052-init-array-str.expected-exit diff --git a/tests/cc-cg/52-init-array-str.scm b/tests/cc-cg/052-init-array-str.scm diff --git a/tests/cc-cg/53-init-struct-pos.expected-exit b/tests/cc-cg/053-init-struct-pos.expected-exit diff --git a/tests/cc-cg/53-init-struct-pos.scm b/tests/cc-cg/053-init-struct-pos.scm diff --git a/tests/cc-cg/54-init-struct-desig.expected-exit b/tests/cc-cg/054-init-struct-desig.expected-exit diff --git a/tests/cc-cg/54-init-struct-desig.scm b/tests/cc-cg/054-init-struct-desig.scm diff --git a/tests/cc-cg/57-block-static.expected-exit b/tests/cc-cg/057-block-static.expected-exit diff --git a/tests/cc-cg/57-block-static.scm b/tests/cc-cg/057-block-static.scm diff --git a/tests/cc-cg/58-fnptr-tab.expected-exit b/tests/cc-cg/058-fnptr-tab.expected-exit diff --git a/tests/cc-cg/58-fnptr-tab.scm b/tests/cc-cg/058-fnptr-tab.scm diff --git a/tests/cc-cg/63-do-while.expected-exit b/tests/cc-cg/063-do-while.expected-exit diff --git a/tests/cc-cg/63-do-while.scm b/tests/cc-cg/063-do-while.scm diff --git a/tests/cc-cg/64-switch.expected-exit b/tests/cc-cg/064-switch.expected-exit diff --git a/tests/cc-cg/64-switch.scm b/tests/cc-cg/064-switch.scm diff --git a/tests/cc-cg/65-goto.expected-exit b/tests/cc-cg/065-goto.expected-exit diff --git a/tests/cc-cg/65-goto.scm b/tests/cc-cg/065-goto.scm diff --git a/tests/cc-cg/66-vararg-call.expected-exit b/tests/cc-cg/066-vararg-call.expected-exit diff --git a/tests/cc-cg/66-vararg-call.scm b/tests/cc-cg/066-vararg-call.scm diff --git a/tests/cc-cg/67-fnptr-call.expected-exit b/tests/cc-cg/067-fnptr-call.expected-exit diff --git a/tests/cc-cg/67-fnptr-call.scm b/tests/cc-cg/067-fnptr-call.scm diff --git a/tests/cc-cg/68-ptr-cmp.expected-exit b/tests/cc-cg/068-ptr-cmp.expected-exit diff --git a/tests/cc-cg/68-ptr-cmp.scm b/tests/cc-cg/068-ptr-cmp.scm diff --git a/tests/cc-cg/69-vararg-recv.expected-exit b/tests/cc-cg/069-vararg-recv.expected-exit diff --git a/tests/cc-cg/69-vararg-recv.scm b/tests/cc-cg/069-vararg-recv.scm diff --git a/tests/cc-cg/70-struct-ret-1word.expected-exit b/tests/cc-cg/070-struct-ret-1word.expected-exit diff --git a/tests/cc-cg/70-struct-ret-1word.scm b/tests/cc-cg/070-struct-ret-1word.scm diff --git a/tests/cc-cg/71-struct-ret-2word.expected-exit b/tests/cc-cg/071-struct-ret-2word.expected-exit diff --git a/tests/cc-cg/71-struct-ret-2word.scm b/tests/cc-cg/071-struct-ret-2word.scm diff --git a/tests/cc-cg/72-cg-snapshot-rewind.expected-exit b/tests/cc-cg/072-cg-snapshot-rewind.expected-exit diff --git a/tests/cc-cg/72-cg-snapshot-rewind.scm b/tests/cc-cg/072-cg-snapshot-rewind.scm diff --git a/tests/cc-cg/73-struct-ret-3word.expected-exit b/tests/cc-cg/073-struct-ret-3word.expected-exit diff --git a/tests/cc-cg/73-struct-ret-3word.scm b/tests/cc-cg/073-struct-ret-3word.scm diff --git a/tests/cc-cg/80-uneg-canonical.expected-exit b/tests/cc-cg/080-uneg-canonical.expected-exit diff --git a/tests/cc-cg/80-uneg-canonical.scm b/tests/cc-cg/080-uneg-canonical.scm diff --git a/tests/cc-cg/81-ubnot-canonical.expected-exit b/tests/cc-cg/081-ubnot-canonical.expected-exit diff --git a/tests/cc-cg/81-ubnot-canonical.scm b/tests/cc-cg/081-ubnot-canonical.scm diff --git a/tests/cc-cg/82-uadd-wrap-canonical.expected-exit b/tests/cc-cg/082-uadd-wrap-canonical.expected-exit diff --git a/tests/cc-cg/82-uadd-wrap-canonical.scm b/tests/cc-cg/082-uadd-wrap-canonical.scm diff --git a/tests/cc-cg/83-signed-shr-by-unsigned.expected-exit b/tests/cc-cg/083-signed-shr-by-unsigned.expected-exit diff --git a/tests/cc-cg/83-signed-shr-by-unsigned.scm b/tests/cc-cg/083-signed-shr-by-unsigned.scm diff --git a/tests/cc-cg/84-bool-promotes-int.expected-exit b/tests/cc-cg/084-bool-promotes-int.expected-exit diff --git a/tests/cc-cg/84-bool-promotes-int.scm b/tests/cc-cg/084-bool-promotes-int.scm diff --git a/tests/cc-cg/85-i32-u32-eq.expected-exit b/tests/cc-cg/085-i32-u32-eq.expected-exit diff --git a/tests/cc-cg/85-i32-u32-eq.scm b/tests/cc-cg/085-i32-u32-eq.scm diff --git a/tests/cc-lex/00-empty.c b/tests/cc-lex/000-empty.c diff --git a/tests/cc-lex/00-empty.expected-toks b/tests/cc-lex/000-empty.expected diff --git a/tests/cc-lex/01-keywords.c b/tests/cc-lex/001-keywords.c diff --git a/tests/cc-lex/01-keywords.expected-toks b/tests/cc-lex/001-keywords.expected diff --git a/tests/cc-lex/02-integers.c b/tests/cc-lex/002-integers.c diff --git a/tests/cc-lex/02-integers.expected-toks b/tests/cc-lex/002-integers.expected diff --git a/tests/cc-lex/03-strings.c b/tests/cc-lex/003-strings.c diff --git a/tests/cc-lex/03-strings.expected-toks b/tests/cc-lex/003-strings.expected diff --git a/tests/cc-lex/04-chars.c b/tests/cc-lex/004-chars.c diff --git a/tests/cc-lex/04-chars.expected-toks b/tests/cc-lex/004-chars.expected diff --git a/tests/cc-lex/05-comments.c b/tests/cc-lex/005-comments.c diff --git a/tests/cc-lex/05-comments.expected-toks b/tests/cc-lex/005-comments.expected diff --git a/tests/cc-lex/06-line-splice.c b/tests/cc-lex/006-line-splice.c diff --git a/tests/cc-lex/06-line-splice.expected-toks b/tests/cc-lex/006-line-splice.expected diff --git a/tests/cc-lex/07-punctuators.c b/tests/cc-lex/007-punctuators.c diff --git a/tests/cc-lex/07-punctuators.expected-toks b/tests/cc-lex/007-punctuators.expected diff --git a/tests/cc-lex/08-digraphs.c b/tests/cc-lex/008-digraphs.c diff --git a/tests/cc-lex/08-digraphs.expected-toks b/tests/cc-lex/008-digraphs.expected diff --git a/tests/cc-lex/09-kw-vs-ident.c b/tests/cc-lex/009-kw-vs-ident.c diff --git a/tests/cc-lex/09-kw-vs-ident.expected-toks b/tests/cc-lex/009-kw-vs-ident.expected diff --git a/tests/cc-lex/10-nl-tokens.c b/tests/cc-lex/010-nl-tokens.c diff --git a/tests/cc-lex/10-nl-tokens.expected-toks b/tests/cc-lex/010-nl-tokens.expected diff --git a/tests/cc-lex/11-trigraphs.c b/tests/cc-lex/011-trigraphs.c diff --git a/tests/cc-lex/11-trigraphs.expected-toks b/tests/cc-lex/011-trigraphs.expected diff --git a/tests/cc-lex/12-program.c b/tests/cc-lex/012-program.c diff --git a/tests/cc-lex/12-program.expected-toks b/tests/cc-lex/012-program.expected diff --git a/tests/cc-lex/13-keywords-all.c b/tests/cc-lex/013-keywords-all.c diff --git a/tests/cc-lex/13-keywords-all.expected-toks b/tests/cc-lex/013-keywords-all.expected diff --git a/tests/cc-lex/14-reject-float.c b/tests/cc-lex/014-reject-float.c diff --git a/tests/cc-lex/14-reject-float.expected-toks b/tests/cc-lex/014-reject-float.expected diff --git a/tests/cc-lex/14-reject-float.expected-exit b/tests/cc-lex/014-reject-float.expected-exit diff --git a/tests/cc-lex/15-reject-multichar.c b/tests/cc-lex/015-reject-multichar.c diff --git a/tests/cc-lex/15-reject-multichar.expected-toks b/tests/cc-lex/015-reject-multichar.expected diff --git a/tests/cc-lex/15-reject-multichar.expected-exit b/tests/cc-lex/015-reject-multichar.expected-exit diff --git a/tests/cc-libc/00-exit.c b/tests/cc-libc/000-exit.c diff --git a/tests/cc-libc/00-exit.expected-exit b/tests/cc-libc/000-exit.expected-exit diff --git a/tests/cc-libc/01-write-syscall.c b/tests/cc-libc/001-write-syscall.c diff --git a/tests/cc-libc/01-write-syscall.expected b/tests/cc-libc/001-write-syscall.expected diff --git a/tests/cc-libc/02-write-libc.c b/tests/cc-libc/002-write-libc.c diff --git a/tests/cc-libc/02-write-libc.expected b/tests/cc-libc/002-write-libc.expected diff --git a/tests/cc-libc/03-fputs-stdout.c b/tests/cc-libc/003-fputs-stdout.c diff --git a/tests/cc-libc/03-fputs-stdout.expected b/tests/cc-libc/003-fputs-stdout.expected diff --git a/tests/cc-libc/04-printf-literal.c b/tests/cc-libc/004-printf-literal.c diff --git a/tests/cc-libc/04-printf-literal.expected b/tests/cc-libc/004-printf-literal.expected diff --git a/tests/cc-libc/05-printf-int.c b/tests/cc-libc/005-printf-int.c diff --git a/tests/cc-libc/05-printf-int.expected b/tests/cc-libc/005-printf-int.expected diff --git a/tests/cc-libc/06-puts.c b/tests/cc-libc/006-puts.c diff --git a/tests/cc-libc/06-puts.expected b/tests/cc-libc/006-puts.expected diff --git a/tests/cc-libc/07-malloc-roundtrip.c b/tests/cc-libc/007-malloc-roundtrip.c diff --git a/tests/cc-libc/07-malloc-roundtrip.expected b/tests/cc-libc/007-malloc-roundtrip.expected diff --git a/tests/cc-libc/09-exit-code.c b/tests/cc-libc/009-exit-code.c diff --git a/tests/cc-libc/09-exit-code.expected-exit b/tests/cc-libc/009-exit-code.expected-exit diff --git a/tests/cc-libc/10-file-roundtrip.c b/tests/cc-libc/010-file-roundtrip.c diff --git a/tests/cc-libc/10-file-roundtrip.expected b/tests/cc-libc/010-file-roundtrip.expected diff --git a/tests/cc-libc/11-fseek.c b/tests/cc-libc/011-fseek.c diff --git a/tests/cc-libc/11-fseek.expected b/tests/cc-libc/011-fseek.expected diff --git a/tests/cc-libc/12-unlink.c b/tests/cc-libc/012-unlink.c diff --git a/tests/cc-libc/12-unlink.expected b/tests/cc-libc/012-unlink.expected diff --git a/tests/cc-libc/13-printf-string.c b/tests/cc-libc/013-printf-string.c diff --git a/tests/cc-libc/13-printf-string.expected b/tests/cc-libc/013-printf-string.expected diff --git a/tests/cc-libc/14-printf-mixed.c b/tests/cc-libc/014-printf-mixed.c diff --git a/tests/cc-libc/14-printf-mixed.expected b/tests/cc-libc/014-printf-mixed.expected diff --git a/tests/cc-libc/15-strops.c b/tests/cc-libc/015-strops.c diff --git a/tests/cc-libc/15-strops.expected b/tests/cc-libc/015-strops.expected diff --git a/tests/cc-libc/16-malloc-free.c b/tests/cc-libc/016-malloc-free.c diff --git a/tests/cc-libc/16-malloc-free.expected b/tests/cc-libc/016-malloc-free.expected diff --git a/tests/cc-libc/17-atoi.c b/tests/cc-libc/017-atoi.c diff --git a/tests/cc-libc/17-atoi.expected b/tests/cc-libc/017-atoi.expected diff --git a/tests/cc-libc/18-printf-int-promo.c b/tests/cc-libc/018-printf-int-promo.c diff --git a/tests/cc-libc/18-printf-int-promo.expected b/tests/cc-libc/018-printf-int-promo.expected diff --git a/tests/cc-pp/00-noop.c b/tests/cc-pp/000-noop.c diff --git a/tests/cc-pp/00-noop.expected-toks b/tests/cc-pp/000-noop.expected diff --git a/tests/cc-pp/01-obj-macro.c b/tests/cc-pp/001-obj-macro.c diff --git a/tests/cc-pp/01-obj-macro.expected-toks b/tests/cc-pp/001-obj-macro.expected diff --git a/tests/cc-pp/02-obj-macro-multi-tok.c b/tests/cc-pp/002-obj-macro-multi-tok.c diff --git a/tests/cc-pp/02-obj-macro-multi-tok.expected-toks b/tests/cc-pp/002-obj-macro-multi-tok.expected diff --git a/tests/cc-pp/03-fn-macro.c b/tests/cc-pp/003-fn-macro.c diff --git a/tests/cc-pp/03-fn-macro.expected-toks b/tests/cc-pp/003-fn-macro.expected diff --git a/tests/cc-pp/04-fn-macro-2args.c b/tests/cc-pp/004-fn-macro-2args.c diff --git a/tests/cc-pp/04-fn-macro-2args.expected-toks b/tests/cc-pp/004-fn-macro-2args.expected diff --git a/tests/cc-pp/05-variadic.c b/tests/cc-pp/005-variadic.c diff --git a/tests/cc-pp/05-variadic.expected-toks b/tests/cc-pp/005-variadic.expected diff --git a/tests/cc-pp/06-stringize.c b/tests/cc-pp/006-stringize.c diff --git a/tests/cc-pp/06-stringize.expected-toks b/tests/cc-pp/006-stringize.expected diff --git a/tests/cc-pp/07-paste.c b/tests/cc-pp/007-paste.c diff --git a/tests/cc-pp/07-paste.expected-toks b/tests/cc-pp/007-paste.expected diff --git a/tests/cc-pp/08-nested-expansion.c b/tests/cc-pp/008-nested-expansion.c diff --git a/tests/cc-pp/08-nested-expansion.expected-toks b/tests/cc-pp/008-nested-expansion.expected diff --git a/tests/cc-pp/09-hideset-self.c b/tests/cc-pp/009-hideset-self.c diff --git a/tests/cc-pp/09-hideset-self.expected-toks b/tests/cc-pp/009-hideset-self.expected diff --git a/tests/cc-pp/10-if-defined.c b/tests/cc-pp/010-if-defined.c diff --git a/tests/cc-pp/10-if-defined.expected-toks b/tests/cc-pp/010-if-defined.expected diff --git a/tests/cc-pp/11-if-arith.c b/tests/cc-pp/011-if-arith.c diff --git a/tests/cc-pp/11-if-arith.expected-toks b/tests/cc-pp/011-if-arith.expected diff --git a/tests/cc-pp/12-ifdef-ifndef.c b/tests/cc-pp/012-ifdef-ifndef.c diff --git a/tests/cc-pp/12-ifdef-ifndef.expected-toks b/tests/cc-pp/012-ifdef-ifndef.expected diff --git a/tests/cc-pp/13-elif-chain.c b/tests/cc-pp/013-elif-chain.c diff --git a/tests/cc-pp/13-elif-chain.expected-toks b/tests/cc-pp/013-elif-chain.expected diff --git a/tests/cc-pp/14-nested-if.c b/tests/cc-pp/014-nested-if.c diff --git a/tests/cc-pp/14-nested-if.expected-toks b/tests/cc-pp/014-nested-if.expected diff --git a/tests/cc-pp/15-undef.c b/tests/cc-pp/015-undef.c diff --git a/tests/cc-pp/15-undef.expected-toks b/tests/cc-pp/015-undef.expected diff --git a/tests/cc-pp/16-error.c b/tests/cc-pp/016-error.c diff --git a/tests/cc-pp/16-error.expected-exit b/tests/cc-pp/016-error.expected-exit diff --git a/tests/cc-pp/17-include-rejected.c b/tests/cc-pp/017-include-rejected.c diff --git a/tests/cc-pp/17-include-rejected.expected-exit b/tests/cc-pp/017-include-rejected.expected-exit diff --git a/tests/cc-pp/18-builtin-stdc.c b/tests/cc-pp/018-builtin-stdc.c diff --git a/tests/cc-pp/18-builtin-stdc.expected-toks b/tests/cc-pp/018-builtin-stdc.expected diff --git a/tests/cc-pp/19-pragma-dropped.c b/tests/cc-pp/019-pragma-dropped.c diff --git a/tests/cc-pp/19-pragma-dropped.expected-toks b/tests/cc-pp/019-pragma-dropped.expected diff --git a/tests/cc-pp/20-cexpr-ops.c b/tests/cc-pp/020-cexpr-ops.c diff --git a/tests/cc-pp/20-cexpr-ops.expected-toks b/tests/cc-pp/020-cexpr-ops.expected diff --git a/tests/cc-pp/21-undefined-id-zero.c b/tests/cc-pp/021-undefined-id-zero.c diff --git a/tests/cc-pp/21-undefined-id-zero.expected-toks b/tests/cc-pp/021-undefined-id-zero.expected diff --git a/tests/cc-pp/22-initial-defines.expected-exit b/tests/cc-pp/022-initial-defines.expected-exit diff --git a/tests/cc-pp/22-initial-defines.scm b/tests/cc-pp/022-initial-defines.scm diff --git a/tests/cc-pp/30-define-end-to-end.c b/tests/cc-pp/030-define-end-to-end.c diff --git a/tests/cc-pp/30-define-end-to-end.expected-toks b/tests/cc-pp/030-define-end-to-end.expected diff --git a/tests/cc-pp/31-string-concat.c b/tests/cc-pp/031-string-concat.c diff --git a/tests/cc-pp/31-string-concat.expected-toks b/tests/cc-pp/031-string-concat.expected diff --git a/tests/cc-pp/32-builtin-date-time.c b/tests/cc-pp/032-builtin-date-time.c diff --git a/tests/cc-pp/32-builtin-date-time.expected-toks b/tests/cc-pp/032-builtin-date-time.expected diff --git a/tests/cc-pp/33-builtin-stdc-version-hosted.c b/tests/cc-pp/033-builtin-stdc-version-hosted.c diff --git a/tests/cc-pp/33-builtin-stdc-version-hosted.expected-toks b/tests/cc-pp/033-builtin-stdc-version-hosted.expected diff --git a/tests/cc-pp/34-builtin-file-line.c b/tests/cc-pp/034-builtin-file-line.c diff --git a/tests/cc-pp/34-builtin-file-line.expected-toks b/tests/cc-pp/034-builtin-file-line.expected diff --git a/tests/cc-pp/35-stringize-str-char.c b/tests/cc-pp/035-stringize-str-char.c diff --git a/tests/cc-pp/35-stringize-str-char.expected-toks b/tests/cc-pp/035-stringize-str-char.expected diff --git a/tests/cc-pp/36-empty-arg.c b/tests/cc-pp/036-empty-arg.c diff --git a/tests/cc-pp/36-empty-arg.expected-toks b/tests/cc-pp/036-empty-arg.expected diff --git a/tests/cc-pp/37-vararg-empty.c b/tests/cc-pp/037-vararg-empty.c diff --git a/tests/cc-pp/37-vararg-empty.expected-toks b/tests/cc-pp/037-vararg-empty.expected diff --git a/tests/cc-pp/38-arg-prescan-stringize.c b/tests/cc-pp/038-arg-prescan-stringize.c diff --git a/tests/cc-pp/38-arg-prescan-stringize.expected-toks b/tests/cc-pp/038-arg-prescan-stringize.expected diff --git a/tests/cc-pp/39-arg-prescan-paste.c b/tests/cc-pp/039-arg-prescan-paste.c diff --git a/tests/cc-pp/39-arg-prescan-paste.expected-toks b/tests/cc-pp/039-arg-prescan-paste.expected diff --git a/tests/cc-pp/40-ifdef-active-typedef-fnmacro.c b/tests/cc-pp/040-ifdef-active-typedef-fnmacro.c diff --git a/tests/cc-pp/40-ifdef-active-typedef-fnmacro.expected-toks b/tests/cc-pp/040-ifdef-active-typedef-fnmacro.expected diff --git a/tests/cc-pp/41-ifndef-defined-else-typedef-fnmacro.c b/tests/cc-pp/041-ifndef-defined-else-typedef-fnmacro.c diff --git a/tests/cc-pp/41-ifndef-defined-else-typedef-fnmacro.expected-toks b/tests/cc-pp/041-ifndef-defined-else-typedef-fnmacro.expected diff --git a/tests/cc-pp/42-ccscm-stdarg-gate.expected-exit b/tests/cc-pp/042-ccscm-stdarg-gate.expected-exit diff --git a/tests/cc-pp/42-ccscm-stdarg-gate.scm b/tests/cc-pp/042-ccscm-stdarg-gate.scm diff --git a/tests/cc-pp/50-pp-divzero.c b/tests/cc-pp/050-pp-divzero.c diff --git a/tests/cc-pp/50-pp-divzero.expected-exit b/tests/cc-pp/050-pp-divzero.expected-exit diff --git a/tests/cc-pp/50-stringize-char-special.c b/tests/cc-pp/050-stringize-char-special.c diff --git a/tests/cc-pp/50-stringize-char-special.expected-toks b/tests/cc-pp/050-stringize-char-special.expected diff --git a/tests/cc-pp/51-pp-shortcircuit-lor.c b/tests/cc-pp/051-pp-shortcircuit-lor.c diff --git a/tests/cc-pp/51-pp-shortcircuit-lor.expected-toks b/tests/cc-pp/051-pp-shortcircuit-lor.expected diff --git a/tests/cc-pp/52-digraph-hash-directive.c b/tests/cc-pp/052-digraph-hash-directive.c diff --git a/tests/cc-pp/52-digraph-hash-directive.expected-toks b/tests/cc-pp/052-digraph-hash-directive.expected diff --git a/tests/cc-pp/52-pp-shortcircuit-land.c b/tests/cc-pp/052-pp-shortcircuit-land.c diff --git a/tests/cc-pp/52-pp-shortcircuit-land.expected-toks b/tests/cc-pp/052-pp-shortcircuit-land.expected diff --git a/tests/cc-pp/53-line-directive-effect.c b/tests/cc-pp/053-line-directive-effect.c diff --git a/tests/cc-pp/53-line-directive-effect.expected-toks b/tests/cc-pp/053-line-directive-effect.expected diff --git a/tests/cc-pp/53-pp-shortcircuit-cond.c b/tests/cc-pp/053-pp-shortcircuit-cond.c diff --git a/tests/cc-pp/53-pp-shortcircuit-cond.expected-toks b/tests/cc-pp/053-pp-shortcircuit-cond.expected diff --git a/tests/cc-pp/54-elif-after-else.c b/tests/cc-pp/054-elif-after-else.c diff --git a/tests/cc-pp/54-elif-after-else.expected-exit b/tests/cc-pp/054-elif-after-else.expected-exit diff --git a/tests/cc-util/00-bv-prefix.expected-exit b/tests/cc-util/000-bv-prefix.expected-exit diff --git a/tests/cc-util/00-bv-prefix.scm b/tests/cc-util/000-bv-prefix.scm diff --git a/tests/cc-util/01-bv-find.expected-exit b/tests/cc-util/001-bv-find.expected-exit diff --git a/tests/cc-util/01-bv-find.scm b/tests/cc-util/001-bv-find.scm diff --git a/tests/cc-util/02-bv-slice-cat.expected-exit b/tests/cc-util/002-bv-slice-cat.expected-exit diff --git a/tests/cc-util/02-bv-slice-cat.scm b/tests/cc-util/002-bv-slice-cat.scm diff --git a/tests/cc-util/03-bv-fixnum.expected-exit b/tests/cc-util/003-bv-fixnum.expected-exit diff --git a/tests/cc-util/03-bv-fixnum.scm b/tests/cc-util/003-bv-fixnum.scm diff --git a/tests/cc-util/04-alist.expected-exit b/tests/cc-util/004-alist.expected-exit diff --git a/tests/cc-util/04-alist.scm b/tests/cc-util/004-alist.scm diff --git a/tests/cc-util/05-list-preds.expected-exit b/tests/cc-util/005-list-preds.expected-exit diff --git a/tests/cc-util/05-list-preds.scm b/tests/cc-util/005-list-preds.scm diff --git a/tests/cc-util/06-min3-align.expected-exit b/tests/cc-util/006-min3-align.expected-exit diff --git a/tests/cc-util/06-min3-align.scm b/tests/cc-util/006-min3-align.scm diff --git a/tests/cc-util/07-buf.expected-exit b/tests/cc-util/007-buf.expected-exit diff --git a/tests/cc-util/07-buf.scm b/tests/cc-util/007-buf.scm diff --git a/tests/cc-util/08-make-namer.expected-exit b/tests/cc-util/008-make-namer.expected-exit diff --git a/tests/cc-util/08-make-namer.scm b/tests/cc-util/008-make-namer.scm diff --git a/tests/cc-util/09-die-noloc.expected b/tests/cc-util/009-die-noloc.expected diff --git a/tests/cc-util/09-die-noloc.expected-exit b/tests/cc-util/009-die-noloc.expected-exit diff --git a/tests/cc-util/09-die-noloc.scm b/tests/cc-util/009-die-noloc.scm diff --git a/tests/cc-util/10-die-no-irritants.expected b/tests/cc-util/010-die-no-irritants.expected diff --git a/tests/cc-util/10-die-no-irritants.expected-exit b/tests/cc-util/010-die-no-irritants.expected-exit diff --git a/tests/cc-util/10-die-no-irritants.scm b/tests/cc-util/010-die-no-irritants.scm diff --git a/tests/cc-util/11-write-bv-fd.expected b/tests/cc-util/011-write-bv-fd.expected diff --git a/tests/cc-util/11-write-bv-fd.expected-exit b/tests/cc-util/011-write-bv-fd.expected-exit diff --git a/tests/cc-util/11-write-bv-fd.scm b/tests/cc-util/011-write-bv-fd.scm diff --git a/tests/cc-util/12-slurp-fd.expected-exit b/tests/cc-util/012-slurp-fd.expected-exit diff --git a/tests/cc-util/12-slurp-fd.scm b/tests/cc-util/012-slurp-fd.scm diff --git a/tests/cc-util/13-slurp-large.expected-exit b/tests/cc-util/013-slurp-large.expected-exit diff --git a/tests/cc-util/13-slurp-large.scm b/tests/cc-util/013-slurp-large.scm diff --git a/tests/lib-runner.sh b/tests/lib-runner.sh @@ -0,0 +1,72 @@ +## tests/lib-runner.sh — shared helpers for test suite drivers. +## +## Sourced by tests/run-suite.sh (in-container suite driver) and may be +## sourced by other test entry points. Pure-shell, no side effects on +## source. Callers set $ARCH and $NAMES (optional fixture filter) before +## invoking the helpers. + +# discover <dir> <ext> +# List basenames under <dir> matching <name>.<ext>, excluding leading +# underscore (reserved for harness fragments like _run-pp.scm). +discover() { + dir=$1; ext=$2 + ls "$dir" 2>/dev/null \ + | sed -n "s/^\\([^_][^.]*\\)\\.$ext\$/\\1/p" \ + | sort -u +} + +report() { + label=$1; status=$2 + echo " $status $label" +} + +show_diff() { + expected=$1; actual=$2 + echo " --- expected ---" + printf '%s\n' "$expected" | sed 's/^/ /' + echo " --- actual ---" + printf '%s\n' "$actual" | sed 's/^/ /' +} + +# fail <label> [<heading>] [<log-file>] +# Emit a FAIL row plus an optional indented heading and indented log +# contents. Lets every suite handle a failed sub-step without re-running +# the failing command to capture its stderr. +fail() { + label=$1 + report "$label" FAIL + [ -n "${2:-}" ] && echo " $2" || : + if [ -n "${3:-}" ] && [ -e "${3:-}" ]; then + sed 's/^/ /' "$3" >&2 || true + fi +} + +# compare_runtime <label> <expected-stdout> <expected-exit> <actual-stdout> <actual-exit> +# PASS iff stdout and exit both match; otherwise FAIL with a diff and/or +# exit-code mismatch line. +compare_runtime() { + lbl=$1; exp_out=$2; exp_exit=$3; act_out=$4; act_exit=$5 + if [ "$act_out" = "$exp_out" ] && [ "$act_exit" = "$exp_exit" ]; then + report "$lbl" PASS + else + report "$lbl" FAIL + if [ "$act_out" != "$exp_out" ]; then show_diff "$exp_out" "$act_out"; fi + if [ "$act_exit" != "$exp_exit" ]; then + echo " exit: expected $exp_exit, got $act_exit" + fi + fi +} + +# read_expected <path> [<default>] +# Print contents of <path> if it exists; else <default> (empty if +# omitted). Trailing-newline behavior follows cat: every suite already +# compares with `[ "$a" = "$b" ]` after $(cat …) so the strip-trailing- +# newline of $() is uniform. +read_expected() { + path=$1; default=${2:-} + if [ -e "$path" ]; then + cat "$path" + else + printf '%s' "$default" + fi +} diff --git a/tests/run-suite.sh b/tests/run-suite.sh @@ -0,0 +1,774 @@ +#!/bin/sh +## tests/run-suite.sh — in-container suite runner. +## +## One invocation handles every requested fixture in a suite for $ARCH, +## instead of the host re-entering the container per fixture. The host +## runner (tests/run.sh) starts one podman process per arch and lets +## this script do all the build / execute / diff work. +## +## PASS/FAIL lines stream to stdout in the same format the host expects; +## the host greps them to update its totals, then prints the final +## summary itself. Fixture-name discovery happens here when no names +## are passed (so the host can stay agnostic about each suite's layout), +## except for m1pp: scripts/lint.sh runs python on the host, so the +## host preflights lint and passes the explicit kept list down. +## +## Env: ARCH=aarch64|amd64|riscv64 +## Usage: tests/run-suite.sh --suite=<m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc|cc-libc|cc-ext|tcc-cc|tcc-libc> [name ...] + +set -eu + +: "${ARCH:?ARCH must be set}" + +. "$(dirname "$0")/lib-runner.sh" + +SUITE= +NAMES= + +while [ "$#" -gt 0 ]; do + case "$1" in + --suite) shift; SUITE=$1 ;; + --suite=*) SUITE=${1#--suite=} ;; + --) shift; while [ "$#" -gt 0 ]; do NAMES="$NAMES $1"; shift; done; break ;; + -*) echo "$0: unknown flag '$1'" >&2; exit 2 ;; + *) NAMES="$NAMES $1" ;; + esac + shift +done + +case "$SUITE" in + m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc|cc-libc|cc-ext|tcc-cc|tcc-libc) ;; + "") echo "$0: --suite required" >&2; exit 2 ;; + *) echo "$0: unknown suite '$SUITE'" >&2; exit 2 ;; +esac + +CC_EXTRA_FLAGS= +[ "${CC_TRACE_EMIT:-0}" = "1" ] && CC_EXTRA_FLAGS="$CC_EXTRA_FLAGS --cc-trace-emit" +[ "${CC_DEBUG:-0}" = "1" ] && CC_EXTRA_FLAGS="$CC_EXTRA_FLAGS --cc-debug" + +## --- m1pp suite --------------------------------------------------------- +## +## Single check: run M1pp against tests/M1pp/<name>.M1pp, diff its text +## output against tests/M1pp/<name>.expected. The suite tests macro +## expansion only — assembling the result through hex2pp is the job of +## the p1 / cc-* suites, where the input is a complete program. +run_m1pp_suite() { + if [ -z "$NAMES" ]; then + NAMES=$(discover tests/M1pp M1pp) + fi + for name in $NAMES; do + expected=tests/M1pp/$name.expected + m1pp_src=tests/M1pp/$name.M1pp + + if [ ! -e "$m1pp_src" ]; then + echo " SKIP $name (no .M1pp)" + continue + fi + if [ ! -e "$expected" ]; then + echo " SKIP $name (no .expected)" + continue + fi + expected_content=$(cat "$expected") + + label="[$ARCH] $name" + outfile=build/$ARCH/tests/M1pp/$name.hex2pp + mkdir -p "$(dirname "$outfile")" + rm -f "$outfile" + "./build/$ARCH/M1pp/M1pp" "$m1pp_src" "$outfile" >/dev/null 2>&1 || true + if [ -e "$outfile" ]; then + actual=$(cat "$outfile") + else + actual= + fi + + if [ "$actual" != "$expected_content" ]; then + report "$label" FAIL + show_diff "$expected_content" "$actual" + continue + fi + + report "$label" PASS + done +} + +## --- p1 suite ----------------------------------------------------------- + +run_p1_suite() { + if [ -z "$NAMES" ]; then + NAMES=$(discover tests/P1 P1pp) + fi + for name in $NAMES; do + pp_src=tests/P1/$name.P1pp + expected=tests/P1/$name.expected + if [ ! -e "$expected" ]; then echo " SKIP $name (no .expected)"; continue; fi + if [ ! -e "$pp_src" ]; then echo " SKIP $name (no .P1pp)"; continue; fi + expected_content=$(cat "$expected") + + label="[$ARCH] $name" + bin=build/$ARCH/tests/P1/$name + log=build/$ARCH/.work/tests/P1/$name/build.log + mkdir -p "$(dirname "$bin")" "$(dirname "$log")" + if ! sh tests/build-p1pp.sh "$bin" "$pp_src" \ + >"$log" 2>&1; then + fail "$label" "" "$log" + continue + fi + actual=$("./$bin" 2>&1 || true) + if [ "$actual" = "$expected_content" ]; then + report "$label" PASS + else + report "$label" FAIL + show_diff "$expected_content" "$actual" + fi + done +} + +## --- scheme1 suite ------------------------------------------------------ + +run_scheme1_suite() { + if [ -z "$NAMES" ]; then + NAMES=$(discover tests/scheme1 scm) + fi + for name in $NAMES; do + fixture=tests/scheme1/$name.scm + expected_stdout_file=tests/scheme1/$name.expected + expected_exit_file=tests/scheme1/$name.expected-exit + + if [ ! -e "$fixture" ]; then echo " SKIP $name (no .scm)"; continue; fi + if [ -e "$expected_stdout_file" ]; then + expected_stdout=$(cat "$expected_stdout_file") + else + expected_stdout= + fi + if [ -e "$expected_exit_file" ]; then + expected_exit=$(cat "$expected_exit_file") + else + expected_exit=0 + fi + + label="[$ARCH] $name" + bin=build/$ARCH/scheme1/scheme1 + if [ ! -x "$bin" ]; then + report "$label" FAIL + echo " (missing $bin -- run 'make scheme1 ARCH=$ARCH')" >&2 + continue + fi + + tmp_stdout=$(mktemp) + if sh scripts/boot-run-scheme1.sh "$fixture" >"$tmp_stdout" 2>&1; then + actual_exit=0 + else + actual_exit=$? + fi + actual_stdout=$(cat "$tmp_stdout") + rm -f "$tmp_stdout" + + if [ "$actual_stdout" = "$expected_stdout" ] \ + && [ "$actual_exit" = "$expected_exit" ]; then + report "$label" PASS + else + report "$label" FAIL + if [ "$actual_stdout" != "$expected_stdout" ]; then + show_diff "$expected_stdout" "$actual_stdout" + fi + if [ "$actual_exit" != "$expected_exit" ]; then + echo " exit: expected $expected_exit, got $actual_exit" + fi + fi + done +} + +## --- cc-* suites -------------------------------------------------------- + +# _cc_unit_suite <suite-name> <expected-ext> <layer-list> +_cc_unit_suite() { + suite=$1; ext=$2; layers=$3 + [ -n "$NAMES" ] || NAMES=$(discover tests/$suite scm) + for name in $NAMES; do + fixture=tests/$suite/$name.scm + [ -e "$fixture" ] || { echo " SKIP $name (no .scm)"; continue; } + if [ -e "tests/$suite/$name.$ext" ]; then + expout=$(cat "tests/$suite/$name.$ext") + else + expout= + fi + if [ -e "tests/$suite/$name.expected-exit" ]; then + expexit=$(cat "tests/$suite/$name.expected-exit") + else + expexit=0 + fi + tmp=$(mktemp) + # Wrap in `sh -c` so catm failure doesn't abort under set -e and + # scheme1 still runs (matches host runner's prior behavior). + if sh -c " + build/$ARCH/tools/catm /tmp/cc-test.scm $layers $fixture + exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm + " >"$tmp" 2>&1; then + act_exit=0 + else + act_exit=$? + fi + act_out=$(cat "$tmp"); rm -f "$tmp" + compare_runtime "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit" + done +} + +run_cc_util_suite() { + _cc_unit_suite cc-util expected "scheme1/prelude.scm cc/cc.scm" +} + +# _cc_pipeline_suite <suite-name> <expected-ext> <layers> +_cc_pipeline_suite() { + suite=$1; ext=$2; layers=$3 + [ -n "$NAMES" ] || NAMES=$(discover tests/$suite c) + for name in $NAMES; do + fixture=tests/$suite/$name.c + [ -e "$fixture" ] || { echo " SKIP $name (no .c)"; continue; } + if [ -e "tests/$suite/$name.$ext" ]; then + expout=$(grep -v '^;;' "tests/$suite/$name.$ext" || true) + else + expout= + fi + if [ -e "tests/$suite/$name.expected-exit" ]; then + expexit=$(cat "tests/$suite/$name.expected-exit") + else + expexit=0 + fi + tmp=$(mktemp) + if sh -c " + build/$ARCH/tools/catm /tmp/cc-test.scm $layers + exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm $fixture + " >"$tmp" 2>&1; then + act_exit=0 + else + act_exit=$? + fi + if [ "$expexit" != "0" ]; then + act_out= + else + act_out=$(cat "$tmp") + fi + rm -f "$tmp" + compare_runtime "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit" + done +} + +run_cc_lex_suite() { + _cc_pipeline_suite cc-lex expected \ + "scheme1/prelude.scm cc/cc.scm tests/cc-lex/_run-lex.scm" +} + +# Two passes: .c fixtures via the lex+pp pipeline; the lone .scm fixture +# (22-initial-defines) covers the -D mechanism the driver doesn't expose, +# so it stays on the unit-suite path. NAMES is restored between passes +# because _cc_pipeline_suite may have populated it via discovery. +run_cc_pp_suite() { + saved=$NAMES + _cc_pipeline_suite cc-pp expected \ + "scheme1/prelude.scm cc/cc.scm tests/cc-pp/_run-pp.scm" + NAMES=$saved + _cc_unit_suite cc-pp expected \ + "scheme1/prelude.scm cc/cc.scm" +} + +# _cc_runtime_suite <suite-name> <fixture-ext> <layers> [<fixture-as-arg?>] +_cc_runtime_suite() { + suite=$1; fext=$2; layers=$3; arg_pass=${4:-0} + [ -n "$NAMES" ] || NAMES=$(discover tests/$suite "$fext") + for name in $NAMES; do + fixture=tests/$suite/$name.$fext + [ -e "$fixture" ] || { echo " SKIP $name (no .$fext)"; continue; } + + if [ -e tests/$suite/$name.expected ]; then + expout=$(cat tests/$suite/$name.expected) + else + expout= + fi + if [ -e tests/$suite/$name.expected-exit ]; then + expexit=$(cat tests/$suite/$name.expected-exit) + else + expexit=0 + fi + + elf=build/$ARCH/tests/$suite/$name + workdir=build/$ARCH/.work/tests/$suite/$name + p1pp=$workdir/$name.P1pp + mkdir -p "$(dirname "$elf")" "$workdir" + + if [ "$arg_pass" = "1" ]; then + cmd=" + build/$ARCH/tools/catm /tmp/cc-test.scm $layers + exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm $fixture + " + else + cmd=" + build/$ARCH/tools/catm /tmp/cc-test.scm $layers $fixture + exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm + " + fi + label="[$ARCH] $suite/$name" + cg_log=$workdir/cg.log + if ! sh -c "$cmd" >"$p1pp" 2>"$cg_log"; then + fail "$label" "cg emission failed:" "$cg_log" + continue + fi + + p1pp_log=$workdir/p1pp.log + if ! WORK_SUBPATH=tests/$suite/$name \ + sh tests/build-p1pp.sh "$elf" "$p1pp" \ + >"$p1pp_log" 2>&1; then + fail "$label" "P1pp assemble failed:" "$p1pp_log" + continue + fi + + tmp=$(mktemp) + if "./$elf" >"$tmp" 2>&1; then + act_exit=0 + else + act_exit=$? + fi + act_out=$(cat "$tmp"); rm -f "$tmp" + compare_runtime "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit" + done +} + +run_cc_cg_suite() { + _cc_runtime_suite cc-cg scm \ + "scheme1/prelude.scm cc/cc.scm" 0 +} + +run_cc_suite() { + [ -n "$NAMES" ] || NAMES=$(discover tests/cc c) + for name in $NAMES; do + src=tests/cc/$name.c + [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } + if [ -e tests/cc/$name.expected ]; then + expout=$(cat tests/cc/$name.expected) + else + expout= + fi + if [ -e tests/cc/$name.expected-exit ]; then + expexit=$(cat tests/cc/$name.expected-exit) + else + expexit=0 + fi + elf=build/$ARCH/tests/cc/$name + workdir=build/$ARCH/.work/tests/cc/$name + p1pp=$workdir/$name.P1pp + label="[$ARCH] cc/$name" + mkdir -p "$(dirname "$elf")" "$workdir" + + cc_log=$workdir/cc.log + # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split. + if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \ + "$src" "$p1pp" >"$cc_log" 2>&1; then + fail "$label" "cc compile failed:" "$cc_log" + continue + fi + + p1pp_log=$workdir/p1pp.log + if ! WORK_SUBPATH=tests/cc/$name \ + sh tests/build-p1pp.sh "$elf" "$p1pp" \ + >"$p1pp_log" 2>&1; then + fail "$label" "P1pp assemble failed:" "$p1pp_log" + continue + fi + + tmp=$(mktemp) + if "./$elf" >"$tmp" 2>&1; then + act_exit=0 + else + act_exit=$? + fi + act_out=$(cat "$tmp"); rm -f "$tmp" + compare_runtime "$label" "$expout" "$expexit" "$act_out" "$act_exit" + done +} + +## --- cc-libc suite ------------------------------------------------------ +## +## Mirrors run_cc_suite but links the prepended libc.P1pp into every +## fixture. Targeted red-green TDD on the cc.scm + libc combination — +## each .c is small (one feature: printf with %d, malloc round-trip, +## getenv lookup, …) so a failure isolates the bug to one symbol path. +run_cc_libc_suite() { + [ -n "$NAMES" ] || NAMES=$(discover tests/cc-libc c) + for name in $NAMES; do + src=tests/cc-libc/$name.c + [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } + if [ -e tests/cc-libc/$name.expected ]; then + expout=$(cat tests/cc-libc/$name.expected) + else + expout= + fi + if [ -e tests/cc-libc/$name.expected-exit ]; then + expexit=$(cat tests/cc-libc/$name.expected-exit) + else + expexit=0 + fi + elf=build/$ARCH/tests/cc-libc/$name + workdir=build/$ARCH/.work/tests/cc-libc/$name + client_p1pp=$workdir/$name.client.P1pp + label="[$ARCH] cc-libc/$name" + mkdir -p "$(dirname "$elf")" "$workdir" + + # Compile the client TU in lib mode so it doesn't emit its + # own :p1_main / :ELF_end and namespaces its anonymous string + # labels under app__cc__str_N — distinct from libc.P1pp's + # libc__cc__str_N. + cc_log=$workdir/cc.log + # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split. + if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \ + --lib=app__ "$src" "$client_p1pp" \ + >"$cc_log" 2>&1; then + fail "$label" "cc compile failed:" "$cc_log" + continue + fi + + # catm chain: entry-libc supplies :p1_main (calls __libc_init + # then main), libc.P1pp supplies the libc routines, the client + # supplies :main, elf-end supplies the :ELF_end terminator. + p1pp_log=$workdir/p1pp.log + if ! WORK_SUBPATH=tests/cc-libc/$name \ + sh tests/build-p1pp.sh "$elf" \ + P1/entry-libc.P1pp build/$ARCH/vendor/mes-libc/libc.P1pp \ + "$client_p1pp" P1/elf-end.P1pp \ + >"$p1pp_log" 2>&1; then + fail "$label" "P1pp assemble failed:" "$p1pp_log" + continue + fi + + tmp=$(mktemp) + if "./$elf" >"$tmp" 2>&1; then + act_exit=0 + else + act_exit=$? + fi + act_out=$(cat "$tmp"); rm -f "$tmp" + compare_runtime "$label" "$expout" "$expexit" "$act_out" "$act_exit" + done +} + +## --- cc-ext suite ------------------------------------------------------- +## +## External C subset coverage via the vendored c-testsuite single-exec +## fixtures (vendor/c-testsuite/single-exec/NNNNN.c). This complements +## tests/cc, which is hand-curated; the goal here is breadth, to surface +## bugs in cc.scm against programs we did not write ourselves. +## +## Fixture spec: each program returns 0 on success and non-zero on +## failure; the .expected file pins stdout+stderr. We honest-report: +## PASS = runs + matches expected + exit 0, anything else = FAIL. +## Compile/assemble errors count as FAIL too — every regression in the +## supported subset shows up. As cc.scm grows the FAIL count drops. +## +## Pipeline switches on the .tags file: tests with `needs-libc` are +## linked through the same chain as the cc-libc suite (entry-libc + +## mes-libc + client + elf-end, with --lib=app__ to namespace string +## labels); plain tests use the bare cc -> P1pp -> ELF flow. +## +## Selection: with no name args, runs every fixture. Otherwise the args +## are basenames (e.g. 00001) under vendor/c-testsuite/single-exec/. +run_cc_ext_suite() { + dir=vendor/c-testsuite/single-exec + [ -n "$NAMES" ] || NAMES=$(discover "$dir" c) + for name in $NAMES; do + src=$dir/$name.c + tags=$dir/$name.c.tags + expected=$dir/$name.c.expected + label="[$ARCH] cc-ext/$name" + + [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } + + needs_libc=0 + if [ -e "$tags" ] && grep -q '^needs-libc$' "$tags"; then + needs_libc=1 + fi + + expout= + [ -e "$expected" ] && expout=$(cat "$expected") + expexit=0 + + elf=build/$ARCH/tests/cc-ext/$name + workdir=build/$ARCH/.work/tests/cc-ext/$name + client_p1pp=$workdir/$name.P1pp + mkdir -p "$(dirname "$elf")" "$workdir" + + cc_log=$workdir/cc.log + if [ "$needs_libc" = "1" ]; then + # Lib mode: client TU compiled with --lib=app__ so it doesn't + # emit its own :p1_main / :ELF_end and namespaces anonymous + # string labels under app__cc__str_N (libc supplies its own). + # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split. + if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \ + --lib=app__ "$src" "$client_p1pp" \ + >"$cc_log" 2>&1; then + fail "$label" "cc compile failed:" "$cc_log" + continue + fi + else + # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split. + if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \ + "$src" "$client_p1pp" \ + >"$cc_log" 2>&1; then + fail "$label" "cc compile failed:" "$cc_log" + continue + fi + fi + + p1pp_log=$workdir/p1pp.log + if [ "$needs_libc" = "1" ]; then + # catm chain matches run_cc_libc_suite: entry-libc supplies + # :p1_main (calls __libc_init then main), libc.P1pp the libc + # routines, the client :main, elf-end the :ELF_end terminator. + if ! WORK_SUBPATH=tests/cc-ext/$name \ + sh tests/build-p1pp.sh "$elf" \ + P1/entry-libc.P1pp build/$ARCH/vendor/mes-libc/libc.P1pp \ + "$client_p1pp" P1/elf-end.P1pp \ + >"$p1pp_log" 2>&1; then + fail "$label" "P1pp assemble failed:" "$p1pp_log" + continue + fi + else + if ! WORK_SUBPATH=tests/cc-ext/$name \ + sh tests/build-p1pp.sh "$elf" "$client_p1pp" \ + >"$p1pp_log" 2>&1; then + fail "$label" "P1pp assemble failed:" "$p1pp_log" + continue + fi + fi + + tmp=$(mktemp) + if "./$elf" >"$tmp" 2>&1; then + act_exit=0 + else + act_exit=$? + fi + act_out=$(cat "$tmp"); rm -f "$tmp" + compare_runtime "$label" "$expout" "$expexit" "$act_out" "$act_exit" + done +} + +## --- tcc-cc suite ------------------------------------------------------- +## +## Runs the plain tests/cc fixtures through a self-built tcc. STAGE +## selects the compiler — STAGE=2 uses tcc-tcc (twice-compiled, built +## by tcc-boot2 which was itself built by cc.scm), STAGE=3 uses +## tcc-tcc-tcc (thrice-compiled, built by tcc-tcc — the README +## endpoint, the first tcc whose machine code an actual tcc emitted). +## start.o / mem.o come from the tcc-cc tree (cross-asm and +## tcc-boot2-built respectively); they don't change between stages. +run_tcc_cc_suite() { + case "$ARCH" in + aarch64) tcc_target=ARM64; tcc_banner='AArch64' ;; + amd64) tcc_target=X86_64; tcc_banner='x86_64' ;; + riscv64) tcc_target=RISCV64; tcc_banner='riscv64' ;; + *) + echo " FAIL [$ARCH] tcc-cc" + echo " tcc-cc supports ARCH in {aarch64, amd64, riscv64} only" >&2 + return + ;; + esac + + case "${STAGE:-2}" in + 2) tcc=build/$ARCH/tcc-tcc/tcc-tcc; stage_tag=stage2 ;; + 3) tcc=build/$ARCH/tcc-tcc-tcc/tcc-tcc-tcc; stage_tag=stage3 ;; + *) + echo " FAIL [$ARCH] tcc-cc" + echo " unknown STAGE='$STAGE' (expected 2 or 3)" >&2 + return + ;; + esac + start=build/$ARCH/tcc-cc/start.o + mem=build/$ARCH/tcc-cc/mem.o + tcc_include=build/$ARCH/vendor/tcc/tcc-0.9.26-1147-gee75a10c/include + # x86_64 only: __va_start / __va_arg intrinsics for variadic + # functions. Other arches lower va_arg without out-of-line helpers. + if [ "$ARCH" = "amd64" ]; then + va_list=build/$ARCH/tcc-cc/va_list.o + else + va_list= + fi + if [ ! -x "$tcc" ]; then + echo " FAIL [$ARCH] tcc-cc" + echo " missing $tcc -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 + return + fi + if [ ! -e "$start" ]; then + echo " FAIL [$ARCH] tcc-cc" + echo " missing $start -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 + return + fi + if [ ! -e "$mem" ]; then + echo " FAIL [$ARCH] tcc-cc" + echo " missing $mem -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 + return + fi + if [ -n "$va_list" ] && [ ! -e "$va_list" ]; then + echo " FAIL [$ARCH] tcc-cc" + echo " missing $va_list -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 + return + fi + if ! "$tcc" -version 2>/dev/null | grep "$tcc_banner" >/dev/null; then + echo " FAIL [$ARCH] tcc-cc" + echo " $tcc is not a $tcc_banner-targeted tcc; rebuild with TCC_TARGET=$tcc_target" >&2 + return + fi + + [ -n "$NAMES" ] || NAMES=$(discover tests/cc c) + for name in $NAMES; do + src=tests/cc/$name.c + [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } + if [ -e tests/cc/$name.expected ]; then + expout=$(cat tests/cc/$name.expected) + else + expout= + fi + if [ -e tests/cc/$name.expected-exit ]; then + expexit=$(cat tests/cc/$name.expected-exit) + else + expexit=0 + fi + + elf=build/$ARCH/tests/tcc-cc/$stage_tag/$name + workdir=build/$ARCH/.work/tests/tcc-cc/$stage_tag/$name + label="[$ARCH] tcc-cc[$stage_tag]/$name" + mkdir -p "$(dirname "$elf")" "$workdir" + + tcc_log=$workdir/tcc.log + # shellcheck disable=SC2086 # $va_list is intentionally word-split (may be empty). + if ! "$tcc" -nostdlib -I "$tcc_include" \ + "$start" "$mem" $va_list "$src" -o "$elf" \ + >"$tcc_log" 2>&1; then + fail "$label" "tcc compile/link failed:" "$tcc_log" + continue + fi + + tmp=$(mktemp) + if "./$elf" >"$tmp" 2>&1; then + act_exit=0 + else + act_exit=$? + fi + act_out=$(cat "$tmp"); rm -f "$tmp" + compare_runtime "$label" "$expout" "$expexit" "$act_out" "$act_exit" + done +} + +## --- tcc-libc suite ----------------------------------------------------- +## +## End-to-end "tcc as a real compiler" check, run through a self-built +## tcc. STAGE selects the compiler — STAGE=2 uses tcc-tcc (twice- +## compiled), STAGE=3 uses tcc-tcc-tcc (thrice-compiled, README +## endpoint). tcc-boot2 already compiled mes-libc into libc.o; for each +## tests/cc-libc fixture, the selected tcc compiles + links it against +## start.o per-arch entry stub: __libc_init then main then exit +## sys_stubs.o per-arch raw-syscall sys_* implementations +## mem.o mem* compiler-builtin runtime (memcpy/memmove/memset/memcmp) +## libc.o tcc-boot2-built mes-libc +## and runs the resulting ELF natively in the per-arch container. +run_tcc_libc_suite() { + case "$ARCH" in + aarch64) tcc_target=ARM64; tcc_banner='AArch64' ;; + amd64) tcc_target=X86_64; tcc_banner='x86_64' ;; + riscv64) tcc_target=RISCV64; tcc_banner='riscv64' ;; + *) + echo " FAIL [$ARCH] tcc-libc" + echo " tcc-libc supports ARCH in {aarch64, amd64, riscv64} only" >&2 + return + ;; + esac + + case "${STAGE:-2}" in + 2) tcc=build/$ARCH/tcc-tcc/tcc-tcc; stage_tag=stage2 ;; + 3) tcc=build/$ARCH/tcc-tcc-tcc/tcc-tcc-tcc; stage_tag=stage3 ;; + *) + echo " FAIL [$ARCH] tcc-libc" + echo " unknown STAGE='$STAGE' (expected 2 or 3)" >&2 + return + ;; + esac + start=build/$ARCH/tcc-libc/start.o + sys_stubs=build/$ARCH/tcc-libc/sys_stubs.o + mem=build/$ARCH/tcc-libc/mem.o + libc=build/$ARCH/tcc-libc/libc.o + tcc_include=build/$ARCH/vendor/tcc/tcc-0.9.26-1147-gee75a10c/include + # x86_64 only: __va_start / __va_arg intrinsics for variadic + # functions. mes-libc's printf family hits this directly. + if [ "$ARCH" = "amd64" ]; then + va_list=build/$ARCH/tcc-cc/va_list.o + else + va_list= + fi + for f in "$tcc" "$start" "$sys_stubs" "$mem" "$libc"; do + if [ ! -e "$f" ]; then + echo " FAIL [$ARCH] tcc-libc" + echo " missing $f -- run 'make test SUITE=tcc-libc ARCH=$ARCH'" >&2 + return + fi + done + if [ -n "$va_list" ] && [ ! -e "$va_list" ]; then + echo " FAIL [$ARCH] tcc-libc" + echo " missing $va_list -- run 'make test SUITE=tcc-libc ARCH=$ARCH'" >&2 + return + fi + if ! "$tcc" -version 2>/dev/null | grep "$tcc_banner" >/dev/null; then + echo " FAIL [$ARCH] tcc-libc" + echo " $tcc is not a $tcc_banner-targeted tcc; rebuild with TCC_TARGET=$tcc_target" >&2 + return + fi + + [ -n "$NAMES" ] || NAMES=$(discover tests/cc-libc c) + for name in $NAMES; do + src=tests/cc-libc/$name.c + [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; } + if [ -e tests/cc-libc/$name.expected ]; then + expout=$(cat tests/cc-libc/$name.expected) + else + expout= + fi + if [ -e tests/cc-libc/$name.expected-exit ]; then + expexit=$(cat tests/cc-libc/$name.expected-exit) + else + expexit=0 + fi + + elf=build/$ARCH/tests/tcc-libc/$stage_tag/$name + workdir=build/$ARCH/.work/tests/tcc-libc/$stage_tag/$name + label="[$ARCH] tcc-libc[$stage_tag]/$name" + mkdir -p "$(dirname "$elf")" "$workdir" + + tcc_log=$workdir/tcc.log + # shellcheck disable=SC2086 # $va_list is intentionally word-split (may be empty). + if ! "$tcc" -nostdlib -I "$tcc_include" \ + "$start" "$sys_stubs" "$mem" "$libc" $va_list "$src" -o "$elf" \ + >"$tcc_log" 2>&1; then + fail "$label" "tcc compile/link failed:" "$tcc_log" + continue + fi + + tmp=$(mktemp) + if "./$elf" >"$tmp" 2>&1; then + act_exit=0 + else + act_exit=$? + fi + act_out=$(cat "$tmp"); rm -f "$tmp" + compare_runtime "$label" "$expout" "$expexit" "$act_out" "$act_exit" + done +} + +case "$SUITE" in + m1pp) run_m1pp_suite ;; + p1) run_p1_suite ;; + scheme1) run_scheme1_suite ;; + cc-util) run_cc_util_suite ;; + cc-lex) run_cc_lex_suite ;; + cc-pp) run_cc_pp_suite ;; + cc-cg) run_cc_cg_suite ;; + cc) run_cc_suite ;; + cc-libc) run_cc_libc_suite ;; + cc-ext) run_cc_ext_suite ;; + tcc-cc) run_tcc_cc_suite ;; + tcc-libc) run_tcc_libc_suite ;; +esac diff --git a/tests/run.sh b/tests/run.sh @@ -0,0 +1,177 @@ +#!/bin/sh +## tests/run.sh — host-side dispatcher for the unified test runner. +## +## All build/run/diff work happens inside the container via +## tests/run-suite.sh: this script just starts one podman +## process per requested arch and aggregates the per-arch +## PASS/FAIL totals. Prior versions of this runner re-entered the +## container per fixture (and per build/run within a fixture); now +## a whole arch's suite is one podman invocation. +## +## The one bit of work that stays on the host is the lint preflight +## for the m1pp and p1 suites: scripts/lint.sh runs python, which the +## busybox container doesn't carry. Names that fail lint are reported +## here (FAIL + diagnostic) and excluded from the in-container batch. +## +## Suites: +## m1pp tests/M1pp/<name>.M1pp — m1pp expander parity test. +## p1 tests/P1/<name>.P1pp — built via tests/build-p1pp.sh +## and run; stdout diffed. +## tests/P1/<name>.P1 — raw P1, built via +## tests/build-p1.sh (no expander). +## scheme1 tests/scheme1/<name>.scm — run by per-arch scheme1. +## cc-util tests/cc-util/<name>.scm — scheme1 prelude+util byte-diff. +## cc-lex tests/cc-lex/<name>.c — lex pipeline byte-diff. +## cc-pp tests/cc-pp/<name>.c — pp pipeline byte-diff (+ .scm). +## cc-cg tests/cc-cg/<name>.scm — cg emit -> P1pp -> ELF -> run. +## cc tests/cc/<name>.c — cc -> P1pp -> ELF -> run. +## cc-ext vendor/c-testsuite/single-exec/<name>.c — broad subset +## coverage from the upstream +## c-testsuite. needs-libc tests +## link the mes-libc chain (same +## as cc-libc); plain tests use +## the bare cc pipeline. Any +## compile/assemble/runtime error +## counts as FAIL. +## tcc-cc tests/cc/<name>.c — tcc-boot2 -> ELF -> run. +## tcc-libc tests/cc-libc/<name>.c — tcc-boot2 builds mes-libc into +## libc.o, then compiles + links +## each fixture against it -> run. +## +## All three arches by default; --arch restricts to one. +## +## Usage: tests/run.sh --suite <suite> [--arch ARCH] [name ...] + +set -eu + +SUITE= +ARCH= +NAMES= +STAGE= + +while [ "$#" -gt 0 ]; do + case "$1" in + --suite) shift; SUITE=$1 ;; + --suite=*) SUITE=${1#--suite=} ;; + --arch) shift; ARCH=$1 ;; + --arch=*) ARCH=${1#--arch=} ;; + --stage) shift; STAGE=$1 ;; + --stage=*) STAGE=${1#--stage=} ;; + --) shift; while [ "$#" -gt 0 ]; do NAMES="$NAMES $1"; shift; done; break ;; + -*) echo "$0: unknown flag '$1'" >&2; exit 2 ;; + *) NAMES="$NAMES $1" ;; + esac + shift +done + +case "$SUITE" in + m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc|cc-libc|cc-ext|tcc-cc|tcc-libc) ;; + "") echo "$0: --suite required (m1pp | p1 | scheme1 | cc-util | cc-lex | cc-pp | cc-cg | cc | cc-libc | cc-ext | tcc-cc | tcc-libc)" >&2; exit 2 ;; + *) echo "$0: unknown suite '$SUITE'" >&2; exit 2 ;; +esac + +REPO=$(cd "$(dirname "$0")/.." && pwd) +cd "$REPO" + +platform_of() { + case "$1" in + aarch64) echo linux/arm64 ;; + amd64) echo linux/amd64 ;; + riscv64) echo linux/riscv64 ;; + *) echo "$0: unknown arch '$1'" >&2; return 1 ;; + esac +} + +run_in_container() { + arch=$1; shift + podman run --rm --pull=never --platform "$(platform_of "$arch")" \ + --tmpfs /tmp:size=512M \ + -e "ARCH=$arch" \ + -e "CC_TRACE_EMIT=${CC_TRACE_EMIT:-0}" \ + -e "CC_DEBUG=${CC_DEBUG:-0}" \ + -e "STAGE=${STAGE:-}" \ + -v "$REPO":/work -w /work \ + "boot2-busybox:$arch" "$@" +} + +if [ -z "$ARCH" ]; then + ARCHES="aarch64 amd64 riscv64" +else + ARCHES=$ARCH +fi + +PASS=0 +FAIL=0 + +# Lint preflight: lint.sh uses python (host-only). Discover the +# fixture set if --names was empty, lint each raw fixture (.M1pp / .P1), +# emit a host-side FAIL + diagnostic for any miss, write the kept name +# list to $keep_file. FAIL line goes to stdout to interleave with the +# container's PASS/FAIL output; the FAIL counter updates in-scope. +# +# Suite layout: +# m1pp: tests/M1pp/<name>.M1pp (no raw .M1 fixtures any more) +# p1: tests/P1/<name>.P1pp (lint skipped — expander output) +# tests/P1/<name>.P1 (lint runs) +lint_preflight() { + arch=$1; keep_file=$2; dir=$3; raw_ext=$4; pp_ext=$5 + : > "$keep_file" + if [ -z "$NAMES" ]; then + raw=$(ls "$dir" 2>/dev/null \ + | sed -n "s/^\([^_][^.]*\)\.${raw_ext}\$/\1/p") + pp=$(ls "$dir" 2>/dev/null \ + | sed -n "s/^\([^_][^.]*\)\.${pp_ext}\$/\1/p") + all=$(printf '%s\n%s\n' "$raw" "$pp" | sort -u | tr '\n' ' ') + else + all=$NAMES + fi + for name in $all; do + raw_src=$dir/$name.$raw_ext + if [ -e "$raw_src" ] \ + && ! ARCH=$arch sh scripts/lint.sh "$raw_src" >/dev/null 2>&1; then + echo " FAIL [$arch] $name" + ARCH=$arch sh scripts/lint.sh "$raw_src" 2>&1 \ + | sed 's/^/ /' >&2 || true + FAIL=$((FAIL + 1)) + else + printf '%s ' "$name" >> "$keep_file" + fi + done +} + +for arch in $ARCHES; do + case "$SUITE" in + m1pp) preflight_args="tests/M1pp M1 M1pp" ;; + p1) preflight_args="tests/P1 P1 P1pp" ;; + *) preflight_args= ;; + esac + if [ -n "$preflight_args" ]; then + keep_file=$(mktemp) + # shellcheck disable=SC2086 # $preflight_args is intentionally word-split. + lint_preflight "$arch" "$keep_file" $preflight_args + names=$(cat "$keep_file") + rm -f "$keep_file" + # Skip the container call only when the user gave names AND + # all of them failed lint. With no names, an empty kept set + # would mean nothing to run anyway. + names_trimmed=$(echo "$names" | tr -d ' \t\n') + if [ -z "$names_trimmed" ]; then + continue + fi + else + names=$NAMES + fi + + out=$(mktemp) + # shellcheck disable=SC2086 # $names is intentionally word-split. + run_in_container "$arch" sh tests/run-suite.sh \ + --suite="$SUITE" $names | tee "$out" + p=$(grep -c '^ PASS ' "$out" 2>/dev/null || true) + f=$(grep -c '^ FAIL ' "$out" 2>/dev/null || true) + PASS=$((PASS + ${p:-0})) + FAIL=$((FAIL + ${f:-0})) + rm -f "$out" +done + +echo "$PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] diff --git a/tests/seed-accept.sh b/tests/seed-accept.sh @@ -0,0 +1,283 @@ +#!/bin/sh +## tests/seed-accept.sh — seed-driver acceptance tests. +## +## One script, three modes: +## +## kernel (default) Tier-2 acceptance for boot0/1/2 on seed-kernel: +## loads boot2-built scheme1 as /init, runs a +## driver.scm that exercises read/write/openat/ +## close/brk/exit_group + clone/execve/waitid via +## a child catm spawn. Verifies transcript markers +## and that the extracted output file is correct. +## +## boot34 Run boot3 (and optionally boot4 with WITH_BOOT4=1) +## under DRIVER=seed and assert byte-identical +## outputs vs the podman reference. +## +## boot5 Run boot5 under DRIVER=seed and assert byte- +## identical libc.a / crt1.o / crti.o / crtn.o / +## hello vs the podman reference. +## +## All three modes target ARCH=aarch64 (the only seed-driver-complete +## arch today). Prereq for every mode: build/aarch64/podman/boot{0..6}/ +## populated via `./scripts/boot.sh aarch64` (default DRIVER=podman), +## including boot6/Image as the seed kernel. +## +## Usage: +## tests/seed-accept.sh # mode=kernel +## tests/seed-accept.sh kernel +## tests/seed-accept.sh boot34 # also: WITH_BOOT4=1 … +## tests/seed-accept.sh boot5 + +set -eu + +MODE=${1:-kernel} +case "$MODE" in + kernel|boot34|boot5) ;; + *) echo "$0: unknown mode '$MODE' (kernel | boot34 | boot5)" >&2; exit 2 ;; +esac + +ARCH=aarch64 +ROOT=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT" + +PODMAN=build/$ARCH/podman +SEED=build/$ARCH/seed +KERNEL=$PODMAN/boot6/Image + +[ -f "$KERNEL" ] || { + echo "missing $KERNEL — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2 + exit 1 +} + +# ─── Mode: boot34 ───────────────────────────────────────────────────── +if [ "$MODE" = "boot34" ]; then + [ -x $PODMAN/boot3/tcc0 ] || { + echo "$PODMAN/boot3/tcc0 missing — run scripts/boot3.sh aarch64" >&2 + exit 1 + } + + echo "[seed-accept boot34] DRIVER=seed scripts/boot3.sh $ARCH" + DRIVER=seed scripts/boot3.sh $ARCH + + if ! cmp -s $SEED/boot3/tcc0 $PODMAN/boot3/tcc0; then + s_seed=$(wc -c < $SEED/boot3/tcc0) + s_ref=$(wc -c < $PODMAN/boot3/tcc0) + echo "[seed-accept boot34] boot3 FAIL: tcc0 differs (seed=$s_seed podman=$s_ref)" >&2 + exit 3 + fi + echo "[seed-accept boot34] boot3 PASS — tcc0 byte-identical vs podman" + + if [ "${WITH_BOOT4:-0}" != 1 ]; then + exit 0 + fi + + [ -x $PODMAN/boot4/tcc3 ] || { + echo "$PODMAN/boot4/tcc3 missing — run scripts/boot4.sh aarch64 under podman first" >&2 + exit 1 + } + + echo "[seed-accept boot34] DRIVER=seed scripts/boot4.sh $ARCH" + DRIVER=seed scripts/boot4.sh $ARCH + + fail=0 + # All boot4 outputs — including the intermediate crt1.o / libc.a / + # libtcc1.a — must match podman byte-for-byte. The strip-file-prefix + # tcc patch (simple-patches/tcc-0.9.26/) drops the /work/in/[tcc-lib/] + # mount prefix from STT_FILE entries, so seed's flat-basename staging + # and podman's /work/in/ mounts produce identical .o relocations. + for f in tcc3 hello crt1.o libc.a libtcc1.a; do + if ! cmp -s $SEED/boot4/$f $PODMAN/boot4/$f; then + s_seed=$(wc -c < $SEED/boot4/$f) + s_ref=$(wc -c < $PODMAN/boot4/$f) + echo "[seed-accept boot34] boot4 DIFF $f: seed=$s_seed podman=$s_ref" >&2 + fail=1 + fi + done + [ $fail -eq 0 ] || exit 4 + echo "[seed-accept boot34] boot4 PASS — tcc3/hello/crt1.o/libc.a/libtcc1.a byte-identical vs podman" + exit 0 +fi + +# ─── Mode: boot5 ────────────────────────────────────────────────────── +if [ "$MODE" = "boot5" ]; then + [ -d $PODMAN/boot5 ] || { + echo "$PODMAN/boot5 missing — run scripts/boot5.sh aarch64" >&2 + exit 1 + } + for f in libc.a crt1.o crti.o crtn.o hello; do + [ -e $PODMAN/boot5/$f ] || { + echo "$PODMAN/boot5/$f missing — run scripts/boot5.sh aarch64" >&2 + exit 1 + } + done + + echo "[seed-accept boot5] DRIVER=seed scripts/boot5.sh $ARCH" + DRIVER=seed scripts/boot5.sh $ARCH + + fails=0 + for f in libc.a crt1.o crti.o crtn.o hello; do + seed_size=$(wc -c < $SEED/boot5/$f) + ref_size=$(wc -c < $PODMAN/boot5/$f) + if cmp -s $SEED/boot5/$f $PODMAN/boot5/$f; then + echo "[seed-accept boot5] $f: byte-identical ($seed_size bytes)" + else + echo "[seed-accept boot5] $f: DIFF (seed=$seed_size podman=$ref_size)" + fails=$((fails + 1)) + fi + done + + if [ $fails -eq 0 ]; then + echo "[seed-accept boot5] PASS — all 5 outputs byte-identical" + exit 0 + else + echo "[seed-accept boot5] FAIL — $fails outputs differ" >&2 + exit 4 + fi +fi + +# ─── Mode: kernel ───────────────────────────────────────────────────── + +EXTRACT=seed-kernel/scripts/extract-blk.sh +SCHEME1=$PODMAN/boot2/scheme1 +CATM=$PODMAN/boot2/catm +PRELUDE=scheme1/prelude.scm + +[ -x "$SCHEME1" ] || { echo "missing $SCHEME1 — run boot2 first" >&2; exit 1; } +[ -x "$CATM" ] || { echo "missing $CATM — run boot2 first" >&2; exit 1; } + +OUTDIR=$ROOT/build/$ARCH/seed-accept +rm -rf "$OUTDIR"; mkdir -p "$OUTDIR" + +STAGE=$(mktemp -d -t seed-accept.XXXXXX) +trap 'rm -rf "$STAGE"' EXIT + +# ─── driver.scm — the in-VM acceptance program ──────────────────────── +cat > "$STAGE/driver.scm" <<'SCM' +;; driver.scm — Tier-2 acceptance for seed-kernel. +(write-string stdout "scheme1: hello from acceptance driver\n") +(write-string stdout "scheme1: spawning child-prog (catm) C <- A + B\n") + +(let ((r (run "child-prog" "C" "A" "B"))) + (if (car r) + (begin + (write-string stdout "scheme1: child returned\n")) + (begin + (write-string stdout "scheme1: spawn FAILED\n") + (exit 1)))) + +(let ((rp (open-input "C"))) + (if (car rp) + (let* ((p (cdr rp)) + (rb (read-all p))) + (if (car rb) + (begin + (write-string stdout "scheme1: read C: [") + (write-bytes stdout (cdr rb)) + (write-string stdout "]\n")) + (write-string stdout "scheme1: read C FAILED\n")) + (close p)) + (write-string stdout "scheme1: open C FAILED\n"))) + +(write-string stdout "scheme1: ALL-OK\n") +(exit 0) +SCM + +# ─── Combine prelude + driver via host catm — this matches the chain's +# own boot-run-scheme1.sh wrapper, so the .scm shape is identical +# to what scheme1 expects everywhere. ────────────────────────────── +cat "$PRELUDE" "$STAGE/driver.scm" > "$STAGE/combined.scm" + +# ─── Two demo input files. catm reads them from the in-VM tmpfs. ────── +printf 'Hello, ' > "$STAGE/A" +printf 'seed-kernel!\n' > "$STAGE/B" + +# ─── Stage the cpio: /init=scheme1, /child-prog=catm, plus inputs. ──── +mkdir -p "$STAGE/cpio" +cp "$SCHEME1" "$STAGE/cpio/init"; chmod +x "$STAGE/cpio/init" +cp "$CATM" "$STAGE/cpio/child-prog"; chmod +x "$STAGE/cpio/child-prog" +cp "$STAGE/combined.scm" "$STAGE/cpio/combined.scm" +cp "$STAGE/A" "$STAGE/cpio/A" +cp "$STAGE/B" "$STAGE/cpio/B" + +NAMES='init +child-prog +combined.scm +A +B' + +( cd "$STAGE/cpio" && printf '%s\n' "$NAMES" | cpio -o -H newc 2>/dev/null ) > "$STAGE/initramfs.cpio" +sz=$(wc -c < "$STAGE/initramfs.cpio") +pad=$(( (512 - sz % 512) % 512 )) +if [ "$pad" -gt 0 ]; then + head -c "$pad" /dev/zero >> "$STAGE/initramfs.cpio" +fi +mv "$STAGE/initramfs.cpio" "$STAGE/in.img" +truncate -s 256M "$STAGE/out.img" + +TRANSCRIPT=$OUTDIR/transcript.txt +echo "[seed-accept] booting scheme1 + driver.scm on seed-kernel" +qemu-system-aarch64 \ + -machine virt,gic-version=3,accel=hvf -cpu host -m 2048M \ + -nographic -no-reboot \ + -global virtio-mmio.force-legacy=false \ + -kernel "$KERNEL" \ + -drive file="$STAGE/in.img",if=none,format=raw,id=hd0,readonly=on \ + -device virtio-blk-device,drive=hd0 \ + -drive file="$STAGE/out.img",if=none,format=raw,id=hd1 \ + -device virtio-blk-device,drive=hd1 \ + -append "init combined.scm" \ + > "$TRANSCRIPT" 2>&1 & +QPID=$! +( sleep 240; kill -9 $QPID 2>/dev/null ) </dev/null >/dev/null 2>&1 & +WATCHER=$! +wait $QPID 2>/dev/null || true +kill $WATCHER 2>/dev/null || true + +# Extract files (we want C from the tmpfs). +if ! "$EXTRACT" "$OUTDIR" "$STAGE/out.img" >/dev/null 2>&1; then + echo "[seed-accept] FAIL: extract-blk failed (kernel didn't reach exit?)" >&2 + tail -60 "$TRANSCRIPT" >&2 + exit 3 +fi + +# ─── Verify ─────────────────────────────────────────────────────────── +fail=0 +for needle in \ + 'scheme1: hello from acceptance driver' \ + 'scheme1: spawning child-prog' \ + 'scheme1: child returned' \ + 'scheme1: read C: \[Hello, seed-kernel!' \ + 'scheme1: ALL-OK' \ + 'exit_group(0)' +do + if ! grep -q "$needle" "$TRANSCRIPT"; then + echo "[seed-accept] MISSING in transcript: $needle" >&2 + fail=1 + fi +done + +if [ ! -f "$OUTDIR/C" ]; then + echo "[seed-accept] MISSING extracted file: $OUTDIR/C" >&2 + fail=1 +elif ! diff -q "$OUTDIR/C" - <<EOF >/dev/null +Hello, seed-kernel! +EOF +then + echo "[seed-accept] C differs from expected:" >&2 + od -c "$OUTDIR/C" | head -3 >&2 + fail=1 +fi + +if [ $fail -ne 0 ]; then + echo "[seed-accept] FAIL — see $TRANSCRIPT" >&2 + exit 4 +fi + +echo "" +echo "=== driver log (excerpt from transcript) ===" +grep '^scheme1:' "$TRANSCRIPT" || true +echo "===========================================" +echo "" +echo "[seed-accept] PASS — scheme1 + .scm + child-prog cycle complete" +echo "[seed-accept] artifacts in $OUTDIR/"