kit

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

cross_test.sh (18310B)


      1 #!/usr/bin/env bash
      2 # scripts/cross_test.sh — cross-compilation correctness orchestrator: the engine
      3 # behind `make test-cross`. For each target in the (expanded) support set it
      4 # cross-compiles and cross-links with the host kit, then optionally runs the
      5 # linked artifact on that target through the shared exec seam, checking the
      6 # result.
      7 #
      8 #   cross_test.sh <selector> [DEPTH]
      9 #
     10 # selector  hosted.sh grammar: all | <os> | linux-<libc> | <token> | a,b,c
     11 # DEPTH     coarse          — smoke cases plus compile+run front/back amalgam
     12 #                             at -O0 and -O1 (execution follows RUN)
     13 #           smoke (default) — exit/hello smoke cases, build/link + optional run,
     14 #                             exit code (+ stdout) when execution is enabled
     15 #           full            — coarse lane + toy/parse/libc corpora
     16 #
     17 # env: KIT (default build/kit), KIT_VM (default 1; 0 drops freebsd/windows),
     18 # KIT_CROSS_RUN (default 1; 0 disables execution), DEPTH (overridden by the
     19 # positional arg).
     20 #
     21 # Provision-or-error: a requested target whose sysroot/image/VM is missing is a
     22 # hard error (all missing reported up front), never a silent skip. Provision with
     23 # `make provision TARGET=…` (see doc/plan/PORT.md).
     24 
     25 set -u
     26 
     27 ROOT="$(cd "$(dirname "$0")/.." && pwd)"
     28 KIT="${KIT:-$ROOT/build/kit}"
     29 HOSTED="$ROOT/scripts/hosted.sh"
     30 CASES="$ROOT/test/cross/cases"
     31 BUILD_DIR="$ROOT/build/test/cross"
     32 SELECTOR="${1:-${TARGET:-all}}"
     33 DEPTH="${2:-${DEPTH:-smoke}}"
     34 export KIT_VM="${KIT_VM:-1}"
     35 KIT_CROSS_RUN="${KIT_CROSS_RUN:-${CROSS_RUN:-${RUN:-1}}}"
     36 case "$KIT_CROSS_RUN" in 0|false|FALSE|no|NO|off|OFF) KIT_CROSS_RUN=0 ;; *) KIT_CROSS_RUN=1 ;; esac
     37 case "$DEPTH" in
     38   coarse|smoke|full) ;;
     39   *) echo "cross_test: bad DEPTH '$DEPTH' (want coarse|smoke|full)" >&2; exit 2 ;;
     40 esac
     41 export KIT_CROSS_RUN
     42 
     43 [ -x "$KIT" ] || { echo "cross_test: kit not found at $KIT (run 'make bin')" >&2; exit 2; }
     44 mkdir -p "$BUILD_DIR"
     45 RUN_LIST_SMOKE="$BUILD_DIR/.cross-run-list-smoke.$$"
     46 RUN_LIST_COARSE="$BUILD_DIR/.cross-run-list-coarse.$$"
     47 LIST_PATH="$RUN_LIST_SMOKE"
     48 
     49 # shellcheck source=../test/lib/kit_sh_report.sh
     50 . "$ROOT/test/lib/kit_sh_report.sh"
     51 
     52 # exec_target caller contract (linux/macos runners read these; VM/bare ignore).
     53 have_podman=0; command -v podman >/dev/null 2>&1 && have_podman=1
     54 QEMU_BIN="${QEMU_BIN:-$(command -v qemu-aarch64 2>/dev/null || true)}"
     55 QEMU_RV64_BIN="${QEMU_RV64_BIN:-$(command -v qemu-riscv64 2>/dev/null || true)}"
     56 have_qemu=0; [ -n "$QEMU_BIN" ] && have_qemu=1
     57 case "$(uname -m 2>/dev/null)" in aarch64|arm64) is_aarch64=1 ;; *) is_aarch64=0 ;; esac
     58 export have_podman QEMU_BIN QEMU_RV64_BIN have_qemu is_aarch64
     59 EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR"; export EXEC_TARGET_MOUNT_ROOT
     60 # shellcheck source=../test/lib/exec_target.sh
     61 . "$ROOT/test/lib/exec_target.sh"
     62 
     63 kit_report_init
     64 trap exec_target_teardown_all EXIT
     65 
     66 # ---- token helpers ---------------------------------------------------------
     67 tok_os()   { printf '%s' "${1%%-*}"; }
     68 tok_arch() { printf '%s' "${1##*-}"; }
     69 tok_libc() { case "$1" in linux-*) local r="${1#linux-}"; printf '%s' "${r%%-*}" ;; *) printf '' ;; esac; }
     70 
     71 # Per-arch -march/-mabi a freestanding corpus object must be compiled with so
     72 # its ELF e_flags match the bare-metal stub + runtime it is linked against
     73 # (test/lib/exec_bare.sh). Both RISC-V widths have a float-ABI axis, so make the
     74 # contract explicit rather than inheriting a compiler default. rv32's harness
     75 # is hard-single (ilp32f); rv64 enables D and uses hard-double (lp64d). Keep in
     76 # sync with _bare_emit_rv32/_bare_emit_rv64 and test/toy/run.sh.
     77 freestanding_abi_flags() {  # token -> echoes extra cc flags ("" for most arches)
     78   case "$(tok_arch "$1")" in
     79     rv32) printf '%s' "-march=rv32imafc_zicsr_zifencei -mabi=ilp32f" ;;
     80     rv64) printf '%s' "-march=rv64imafd_zicsr_zifencei -mabi=lp64d" ;;
     81     *)    printf '' ;;
     82   esac
     83 }
     84 
     85 # Link modes to smoke per target. musl exercises BOTH static (libc.a) and
     86 # dynamic (libc.so) — the two ways the sysroot is consumed. freebsd is static
     87 # (self-contained base). Everything else uses the toolchain default (one mode;
     88 # glibc is dynamic-only — static glibc is discouraged/NSS-fragile).
     89 link_modes() {
     90   case "$1" in
     91     linux-musl-*) echo "static dynamic" ;;
     92     freebsd-*)    echo "static" ;;
     93     *)            echo "default" ;;
     94   esac
     95 }
     96 # static -> libc.a; dynamic -> kit's default link for a libc sysroot is already
     97 # dynamic (.interp + libc.so), and `-dynamic` is not a kit flag, so dynamic and
     98 # the single-mode default both pass no link flag.
     99 mode_flag() { case "$1" in static) echo -static ;; *) echo ;; esac; }
    100 
    101 # ---- provisioning pre-flight ----------------------------------------------
    102 # Echo a one-line missing-reason for a token, or nothing if ready to build+run.
    103 preflight_token() {
    104   local t="$1" os tag sr
    105   os="$(tok_os "$t")"; tag="$("$HOSTED" tag "$t")"; sr="$("$HOSTED" path "$t" 2>/dev/null)"
    106   case "$os" in
    107     linux|freebsd|windows|android)
    108       if [ -z "$sr" ] || [ ! -d "$sr" ]; then
    109         echo "sysroot missing — run 'make provision TARGET=$t'"; return
    110       fi ;;
    111   esac
    112   if [ "$KIT_CROSS_RUN" -eq 1 ] && ! exec_target_supported "$tag"; then
    113     case "$os" in
    114       freestanding) echo "qemu-system missing for $tag — install qemu-system-$(printf '%s' "$($HOSTED triple "$t")" | sed 's/-none-elf//')" ;;
    115       freebsd|windows) echo "VM not provisioned/reachable — run 'make provision TARGET=$t KIT_VM=1'" ;;
    116       macos)        echo "macOS exec needs a Darwin host with matching arch" ;;
    117       linux)        echo "no linux runner — run 'make provision TARGET=$t' (sysroot) + 'make test-images'/'make hosted-glibc-images' (run image)" ;;
    118       android)      echo "no Android runner — use RUN=0 for build/link-only validation" ;;
    119       *)            echo "no runner for $tag" ;;
    120     esac
    121   fi
    122 }
    123 
    124 # ---- smoke build/link + run lanes -----------------------------------------
    125 # Per target: the `exit` case (toolchain + link + crt + exit path; the only case
    126 # freestanding can run) then `hello` (sysroot headers + libc + stdout, hosted
    127 # only) — each built once per link mode (link_modes): musl gets static + dynamic.
    128 smoke_build_token() {
    129   local t="$1" os name m
    130   os="$(tok_os "$t")"
    131   if [ "$os" = android ]; then
    132     smoke_build_android "$t"
    133     return
    134   fi
    135   if [ "$os" = freestanding ]; then
    136     smoke_build_case "$t" exit ""           # bare-metal: no link mode
    137     return
    138   fi
    139   for name in exit hello; do
    140     for m in $(link_modes "$t"); do
    141       smoke_build_case "$t" "$name" "$m"
    142     done
    143   done
    144 }
    145 
    146 smoke_build_android() {
    147   local t="$1" tag cdir src obj so label
    148   tag="$("$HOSTED" tag "$t")"
    149   cdir="$BUILD_DIR/$t"; mkdir -p "$cdir"
    150   src="$CASES/native_activity.c"
    151   obj="$cdir/native_activity.o"
    152   so="$cdir/libkit_native_activity.so"
    153   label="native_activity/$t"
    154   if ! "$HOSTED" cc "$t" -O1 -fPIC -c "$src" -o "$obj" \
    155         >"$cdir/native_activity.cc.out" 2>"$cdir/native_activity.cc.err"; then
    156     kit_fail "$label:cc" "cc failed"; sed 's/^/    | /' "$cdir/native_activity.cc.err" | head; return
    157   fi
    158   kit_pass "$label:cc"
    159   if ! "$HOSTED" cc "$t" -shared "$obj" -landroid -o "$so" \
    160         >"$cdir/native_activity.ld.out" 2>"$cdir/native_activity.ld.err"; then
    161     kit_fail "$label:ld" "ld failed"; sed 's/^/    | /' "$cdir/native_activity.ld.err" | head; return
    162   fi
    163   if [ ! -s "$so" ]; then
    164     kit_fail "$label:ld" "missing shared object"
    165     return
    166   fi
    167   : "$tag"
    168   kit_pass "$label:ld"
    169 }
    170 
    171 coarse_build_token() {
    172   local t="$1" os mode opt sfx cdir tag
    173   os="$(tok_os "$t")"
    174   if [ "$os" = android ]; then
    175     printf 'cross: NOTE %s — Android coarse lane uses the native_activity shared-library smoke only\n' "$t"
    176     return
    177   fi
    178   tag="$($HOSTED tag "$t")"
    179   cdir="$BUILD_DIR/$t"; mkdir -p "$cdir"
    180   for opt in O0 O1; do
    181     if [ "$os" = freestanding ]; then
    182       local fobj="$cdir/c_frontback_amalgam.${opt}.o"
    183       local out="$cdir/c_frontback_amalgam.${opt}.out"
    184       local err="$cdir/c_frontback_amalgam.${opt}.err"
    185       # shellcheck disable=SC2046
    186       if ! "$HOSTED" cc "$t" -$opt $(freestanding_abi_flags "$t") -ffreestanding -c "$CASES/c_frontback_amalgam.c" -o "$fobj" \
    187             >"$cdir/c_frontback_amalgam.$opt.cc.out" 2>"$cdir/c_frontback_amalgam.$opt.cc.err"; then
    188         kit_fail "frontback_amalgam/$t:$opt:cc" "cc failed"; sed 's/^/    | /' "$cdir/c_frontback_amalgam.$opt.cc.err" | head; return
    189       fi
    190       kit_pass "frontback_amalgam/$t:$opt:cc"
    191       run_list_add target "frontback_amalgam/$t:$opt" "$tag" "$fobj" "$out" "$err" 0 -
    192     else
    193       for mode in $(link_modes "$t"); do
    194         case "$mode" in static|dynamic) sfx=".$mode" ;; *) sfx="" ;; esac
    195         local ext=""; [ "$os" = windows ] && ext=.exe
    196         local fobj="$cdir/c_frontback_amalgam_${opt}${sfx}.o"
    197         local fexe="$cdir/c_frontback_amalgam_${opt}${sfx}$ext"
    198         if ! "$HOSTED" cc "$t" -$opt -c "$CASES/c_frontback_amalgam.c" -o "$fobj" \
    199               >"$cdir/c_frontback_amalgam_${opt}${sfx}.cc.out" 2>"$cdir/c_frontback_amalgam_${opt}${sfx}.cc.err"; then
    200           kit_fail "frontback_amalgam/$t:$opt$sfx:cc" "cc failed"; sed 's/^/    | /' "$cdir/c_frontback_amalgam_${opt}${sfx}.cc.err" | head; return
    201         fi
    202         kit_pass "frontback_amalgam/$t:$opt$sfx:cc"
    203         # shellcheck disable=SC2046
    204         if ! "$HOSTED" cc "$t" -$opt $(mode_flag "$mode") "$fobj" -o "$fexe" \
    205               >"$cdir/c_frontback_amalgam_${opt}${sfx}.ld.out" 2>"$cdir/c_frontback_amalgam_${opt}${sfx}.ld.err"; then
    206           kit_fail "frontback_amalgam/$t:$opt$sfx:ld" "ld failed"; sed 's/^/    | /' "$cdir/c_frontback_amalgam_${opt}${sfx}.ld.err" | head; return
    207         fi
    208         kit_pass "frontback_amalgam/$t:$opt$sfx:ld"
    209         run_list_add target "frontback_amalgam/$t:$opt$sfx" "$tag" "$fexe" \
    210           "$cdir/c_frontback_amalgam_${opt}${sfx}.out" "$cdir/c_frontback_amalgam_${opt}${sfx}.err" 0 -
    211       done
    212     fi
    213   done
    214 }
    215 
    216 run_list_add() {  # kind label tag exe out err expected want-file
    217   printf '%s|%s|%s|%s|%s|%s|%s|%s\n' "$@" >> "$LIST_PATH"
    218 }
    219 
    220 # smoke_build_case TOKEN CASENAME MODE — compile and link one case in one link
    221 # mode, then queue the linked artifact for phase-2 execution. MODE is
    222 # static/dynamic (suffixes the lane) or default/"" (unsuffixed).
    223 smoke_build_case() {
    224   local t="$1" name="$2" mode="$3" os tag cdir label sfx src exp want_file
    225   os="$(tok_os "$t")"; tag="$("$HOSTED" tag "$t")"
    226   cdir="$BUILD_DIR/$t"; mkdir -p "$cdir"
    227   src="$CASES/$name.c"
    228   case "$mode" in static|dynamic) sfx=".$mode" ;; *) sfx="" ;; esac
    229   label="$name/$t${sfx}"
    230   exp=0; [ -f "$CASES/$name.expected" ] && exp="$(cat "$CASES/$name.expected")"
    231   want_file="-"; [ -f "$CASES/$name.stdout" ] && want_file="$CASES/$name.stdout"
    232 
    233   if [ "$os" = freestanding ]; then
    234     # Bare-metal: compile to an object, link with the reset stub now, and leave
    235     # only the qemu boot for the execution phase.
    236     local obj="$cdir/$name.o" elf="$cdir/$name.elf" barch reason smopt
    237     barch="$(tok_arch "$t")"
    238     # Every freestanding arch (arm32 included, since Phase 2 landed the -O1
    239     # known-frame path) exercises the optimizer at -O1.
    240     smopt=O1
    241     # shellcheck disable=SC2046
    242     if ! "$HOSTED" cc "$t" -$smopt $(freestanding_abi_flags "$t") -ffreestanding -c "$src" -o "$obj" \
    243           >"$cdir/$name.cc.out" 2>"$cdir/$name.cc.err"; then
    244       kit_fail "$label:cc" "cc failed"; sed 's/^/    | /' "$cdir/$name.cc.err" | head; return
    245     fi
    246     kit_pass "$label:cc"
    247     if ! exec_bare_setup_build "$barch" "$EXEC_BARE_WORK" >"$cdir/$name.bare.out" 2>"$cdir/$name.bare.err"; then
    248       kit_fail "$label:ld" "bare setup failed"; sed 's/^/    | /' "$cdir/$name.bare.err" | head; return
    249     fi
    250     reason="$(exec_bare_link "$barch" "$obj" "$EXEC_BARE_WORK" "$elf" 2>&1)" || {
    251       kit_fail "$label:ld" "${reason:-kit ld failed}"; return
    252     }
    253     kit_pass "$label:ld"
    254     run_list_add bare "$label" "$tag" "$elf" "$cdir/$name.out" "$cdir/$name.err" "$exp" "$want_file"
    255   else
    256     local ext=""; [ "$os" = windows ] && ext=.exe
    257     local obj="$cdir/$name$sfx.o" exe="$cdir/$name$sfx$ext"
    258     if ! "$HOSTED" cc "$t" -O1 -c "$src" -o "$obj" \
    259           >"$cdir/$name$sfx.cc.out" 2>"$cdir/$name$sfx.cc.err"; then
    260       kit_fail "$label:cc" "cc failed"; sed 's/^/    | /' "$cdir/$name$sfx.cc.err" | head; return
    261     fi
    262     kit_pass "$label:cc"
    263     # shellcheck disable=SC2046
    264     if ! "$HOSTED" cc "$t" $(mode_flag "$mode") "$obj" -o "$exe" \
    265           >"$cdir/$name$sfx.ld.out" 2>"$cdir/$name$sfx.ld.err"; then
    266       kit_fail "$label:ld" "ld failed"; sed 's/^/    | /' "$cdir/$name$sfx.ld.err" | head; return
    267     fi
    268     kit_pass "$label:ld"
    269     run_list_add target "$label" "$tag" "$exe" "$cdir/$name$sfx.out" "$cdir/$name$sfx.err" "$exp" "$want_file"
    270   fi
    271 }
    272 
    273 cross_run_all() {
    274   local list="$1"
    275   local kind label tag exe out err exp want_file rc got want barch
    276   [ -s "$list" ] || { echo "cross: no linked artifacts to execute"; return; }
    277   # Read the list on a private fd (9), not stdin: the runners below spawn
    278   # children that read host stdin (qemu-system -nographic muxes the guest UART
    279   # onto stdio; VM readiness ssh probes forward stdin), which would otherwise
    280   # drain the list mid-loop and truncate execution after the first such case.
    281   # Each runner also gets </dev/null so it can never block on or consume stdin.
    282   while IFS='|' read -r kind label tag exe out err exp want_file <&9; do
    283     case "$kind" in
    284       bare)
    285         barch="$(_exec_target_arch "$tag")"
    286         exec_bare_run_image "$barch" "$exe" "$out" "$err" </dev/null ;;
    287       *)
    288         exec_target_run "$tag" "$exe" "$out" "$err" </dev/null ;;
    289     esac
    290     rc="${RUN_RC:-127}"; got="$(cat "$out" 2>/dev/null)"
    291     want=""; [ "$want_file" != "-" ] && want="$(cat "$want_file")"
    292     if [ "$((rc & 255))" -ne "$((exp & 255))" ]; then
    293       kit_fail "$label:run" "expected rc $exp, got $rc"
    294     elif [ "$want_file" != "-" ] && [ "$got" != "$want" ]; then
    295       kit_fail "$label:run" "stdout mismatch (want '$want' got '$got')"
    296     else
    297       kit_pass "$label:run"
    298     fi
    299   done 9< "$list"
    300 }
    301 
    302 # ---- full lanes ------------------------------------------------------------
    303 # Run an existing corpus runner as a sub-process; its exit status is one verdict.
    304 _full_lane() {
    305   local label="$1"; shift
    306   printf '\ncross: full lane %s →\n' "$label"
    307   if "$@"; then kit_pass "$label"; else kit_fail "$label" "lane failed (see output above)"; fi
    308 }
    309 
    310 full_token() {
    311   local t="$1" os arch libc vm_arch
    312   os="$(tok_os "$t")"; arch="$(tok_arch "$t")"; libc="$(tok_libc "$t")"
    313   case "$os" in
    314     linux)
    315       _full_lane "$t:toy"   env KIT="$KIT" KIT_TOY_CROSS_ARCHS="$arch" KIT_TEST_PATHS=X bash "$ROOT/test/toy/run.sh"
    316       _full_lane "$t:parse" env KIT="$KIT" KIT_TEST_ARCH="$arch" KIT_TEST_PATHS=E bash "$ROOT/test/parse/run.sh"
    317       _full_lane "$t:libc"  env KIT="$KIT" KIT_LIBC_ARCHES="$arch" bash "$ROOT/test/libc/$libc/run.sh"
    318       ;;
    319     macos)
    320       local ha; ha="$(uname -m 2>/dev/null)"
    321       case "$arch:$ha" in
    322         aa64:arm64|aa64:aarch64|x64:x86_64|x64:amd64)
    323           # Native arch: run the corpus in-process.
    324           _full_lane "$t:toy"   env KIT="$KIT" bash "$ROOT/test/toy/run.sh"
    325           _full_lane "$t:parse" env KIT="$KIT" bash "$ROOT/test/parse/run.sh" ;;
    326         *)
    327           # Cross arch (e.g. x86_64 on Apple silicon): cross-compile the toy
    328           # corpus and run it via Rosetta 2 through the shared exec_target seam.
    329           _full_lane "$t:toy-rosetta" \
    330             env KIT="$KIT" KIT_TOY_MACOS_ARCHES="$arch" bash "$ROOT/test/toy/vm.sh" macos ;;
    331       esac
    332       ;;
    333     freebsd)
    334       case "$arch" in
    335         x64) vm_arch=amd64 ;;
    336         aa64) vm_arch=aarch64 ;;
    337         rv64) vm_arch=riscv64 ;;
    338       esac
    339       _full_lane "$t:toy-vm" \
    340         env KIT="$KIT" KIT_TOY_FREEBSD_ARCHES="$vm_arch" \
    341         bash "$ROOT/test/toy/vm.sh" freebsd
    342       ;;
    343     windows)
    344       case "$arch" in
    345         x64) vm_arch=x64 ;;
    346         aa64) vm_arch=aarch64 ;;
    347       esac
    348       _full_lane "$t:toy-vm" \
    349         env KIT="$KIT" KIT_TOY_WINDOWS_ARCHES="$vm_arch" \
    350         bash "$ROOT/test/toy/vm.sh" windows
    351       ;;
    352     freestanding)
    353       case "$arch" in
    354         rv32)
    355           _full_lane "$t:toy"   env KIT="$KIT" KIT_TOY_CROSS_ARCHS=rv32 KIT_TEST_PATHS=X bash "$ROOT/test/toy/run.sh"
    356           _full_lane "$t:parse" env KIT="$KIT" KIT_TEST_ARCH=rv32 KIT_TEST_PATHS=E bash "$ROOT/test/parse/run.sh" ;;
    357         arm32)
    358           _full_lane "$t:toy"   env KIT="$KIT" KIT_TOY_CROSS_ARCHS=arm32 KIT_TEST_PATHS=X bash "$ROOT/test/toy/run.sh"
    359           _full_lane "$t:parse" env KIT="$KIT" KIT_TEST_ARCH=arm32 KIT_TEST_PATHS=E bash "$ROOT/test/parse/run.sh" ;;
    360         *)
    361           printf 'cross: NOTE %s — full-corpus bare-metal not yet wired for %s (smoke only); see doc/plan/PORT.md\n' "$t" "$arch" ;;
    362       esac
    363       ;;
    364   esac
    365 }
    366 
    367 # ---- drive -----------------------------------------------------------------
    368 TOKENS="$("$HOSTED" expand "$SELECTOR" --mode=cross)" || exit 1
    369 [ -n "$TOKENS" ] || { echo "cross_test: selector '$SELECTOR' expanded to nothing" >&2; exit 2; }
    370 
    371 printf 'cross: DEPTH=%s KIT_VM=%s KIT_CROSS_RUN=%s\ncross: targets=%s\n' \
    372   "$DEPTH" "$KIT_VM" "$KIT_CROSS_RUN" "$(echo $TOKENS | tr '\n' ' ')"
    373 
    374 missing=0
    375 for t in $TOKENS; do
    376   reason="$(preflight_token "$t")"
    377   if [ -n "$reason" ]; then
    378     printf 'cross: NOT PROVISIONED  %-22s %s\n' "$t" "$reason"; missing=1
    379   fi
    380 done
    381 if [ "$missing" -eq 1 ]; then
    382   echo "cross: aborting — provision the targets above, or narrow TARGET / set KIT_VM=0." >&2
    383   exit 1
    384 fi
    385 
    386 printf '\ncross: phase 1 — compile/link smoke cases\n'
    387 : > "$RUN_LIST_SMOKE"
    388 : > "$RUN_LIST_COARSE"
    389 for t in $TOKENS; do
    390   LIST_PATH="$RUN_LIST_SMOKE"
    391   smoke_build_token "$t"
    392   if [ "$DEPTH" != smoke ]; then
    393     LIST_PATH="$RUN_LIST_COARSE"
    394     coarse_build_token "$t"
    395   fi
    396 done
    397 
    398 if [ "$KIT_CROSS_RUN" -eq 1 ]; then
    399   printf '\ncross: phase 2 — execute smoke cases\n'
    400   cross_run_all "$RUN_LIST_SMOKE"
    401   if [ "$DEPTH" != smoke ]; then
    402     printf '\ncross: phase 3 — execute front/back amalgam lane\n'
    403     cross_run_all "$RUN_LIST_COARSE"
    404   fi
    405   if [ "$DEPTH" = full ]; then
    406     printf '\ncross: phase 4 — full execution lanes\n'
    407     for t in $TOKENS; do
    408       full_token "$t"
    409     done
    410   fi
    411 else
    412   printf '\ncross: phase 2 — execution disabled (KIT_CROSS_RUN=0)\n'
    413 fi
    414 
    415 KIT_SKIP_IS_FAILURE=0
    416 kit_summary test-cross
    417 kit_exit