From 178a2f5a01ca594cea9b7bfedefa8b7edeca8d85 Mon Sep 17 00:00:00 2001 From: CodeWhale Bot Date: Sat, 15 Aug 2026 20:57:11 -0700 Subject: [PATCH] chore(dev): isolate worktree builds and wire nextest into dev-test scripts/dev-test.sh only mapped an area onto cargo test -p; a new worktree still compiled into a private cold ./target. Add scripts/dev-cache.sh and scripts/dev-cargo.sh so the measured isolated build-dir topology is actually applied. New worktrees get CARGO_BUILD_BUILD_DIR under ${CODEWHALE_CACHE_ROOT:-${XDG_CACHE_HOME:-$HOME/.cache}/codewhale} with Cargo's {workspace-path-hash} (passed as --config so the template expands). sccache wraps rustc only when incremental is already off; a missing binary is a printed fallback, not an error. Cargo older than 1.91 falls back to a per-workspace CARGO_TARGET_DIR. Already-set CARGO_TARGET_DIR / CARGO_BUILD_BUILD_DIR / RUSTC_WRAPPER are left alone. Defaults contain no machine-local path. dev-test.sh now routes every workspace crate, prefers cargo-nextest when installed (CODEWHALE_DEV_NEXTEST=0 forces libtest), and exports RUST_MIN_STACK=16MiB when unset. nextest retries stay 0. Hermetic checks: scripts/dev-cache.test.sh (22) and scripts/dev-test.test.sh (27), passing under both /bin/sh and dash. --- .config/nextest.toml | 2 + scripts/dev-cache.sh | 437 ++++++++++++++++++++++++++++++++++++++ scripts/dev-cache.test.sh | 294 +++++++++++++++++++++++++ scripts/dev-cargo.sh | 31 +++ scripts/dev-test.sh | 123 ++++++++--- scripts/dev-test.test.sh | 216 +++++++++++++++++++ 6 files changed, 1070 insertions(+), 33 deletions(-) create mode 100755 scripts/dev-cache.sh create mode 100755 scripts/dev-cache.test.sh create mode 100755 scripts/dev-cargo.sh create mode 100755 scripts/dev-test.test.sh diff --git a/.config/nextest.toml b/.config/nextest.toml index c8217ad4a..33f58fd3b 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -19,6 +19,8 @@ slow-timeout = { period = "30s" } retries = 0 fail-fast = false final-status-level = "slow" +# RUST_MIN_STACK is not a nextest.toml key. CI and scripts/dev-test.sh +# export 16 MiB so local nextest matches the product thread stack. [test-groups] pty = { max-threads = 1 } diff --git a/scripts/dev-cache.sh b/scripts/dev-cache.sh new file mode 100755 index 000000000..1abb95ed4 --- /dev/null +++ b/scripts/dev-cache.sh @@ -0,0 +1,437 @@ +#!/bin/sh +# Portable opt-in Cargo cache topology for Codewhale worktrees. +# +# Isolated per-workspace intermediates (Cargo build-dir + {workspace-path-hash}) +# plus optional shared sccache. Never hard-codes a machine path. Does not +# change product behavior. This file is safe to source from other /bin/sh +# helpers; the CLI path is the only one that calls `set -eu` and `exit`. +# +# Usage: +# . scripts/dev-cache.sh && codewhale_dev_cache_apply +# scripts/dev-cache.sh --status +# scripts/dev-cache.sh --self-check +# scripts/dev-cache.sh --print-exports +# scripts/dev-cache.sh --help +# +# Environment: +# CODEWHALE_CACHE_ROOT cache root (default: +# ${XDG_CACHE_HOME:-$HOME/.cache}/codewhale) +# CODEWHALE_DEV_CACHE auto|1|force|0 (default auto) +# auto = isolated build-dir only when ./target +# is absent (new worktree) +# 1/force = always isolated (still respects an +# already-set CARGO_TARGET_DIR / +# CARGO_BUILD_BUILD_DIR) +# 0 = do nothing +# CODEWHALE_SCCACHE auto|1|0 (default auto) +# auto = wrap rustc only when incremental is +# already off and sccache is on PATH +# 1 = request CARGO_INCREMENTAL=0 (unless +# the caller already set it to on) and +# wrap if sccache exists +# 0 = never wrap +# CODEWHALE_DEV_CACHE_QUIET 1 to suppress the one-line status +# CODEWHALE_DEV_CACHE_REPO_ROOT +# workspace root used for ./target detection +# (default: $PWD) +# CODEWHALE_RUSTC_COMMIT override the rustc commit used to namespace +# SCCACHE_DIR (tests / offline hosts) +# +# sccache cannot cache incremental units. This helper never enables the +# wrapper while CARGO_INCREMENTAL is on, and it never turns incremental off +# unless CODEWHALE_SCCACHE=1. Everyday edits stay on cargo's incremental +# default. New-worktree / CI-like rebuilds that already set +# CARGO_INCREMENTAL=0 get the measured shared-compiler-output path. + +codewhale_dev_cache_truthy() { + case ${1:-} in + 1|true|yes|on|force) return 0 ;; + *) return 1 ;; + esac +} + +codewhale_dev_cache_falsey() { + case ${1:-} in + 0|false|no|off|never|disabled) return 0 ;; + *) return 1 ;; + esac +} + +codewhale_dev_cache_root() { + if [ -n "${CODEWHALE_CACHE_ROOT:-}" ]; then + printf '%s\n' "$CODEWHALE_CACHE_ROOT" + return + fi + if [ -n "${XDG_CACHE_HOME:-}" ]; then + printf '%s\n' "$XDG_CACHE_HOME/codewhale" + return + fi + if [ -n "${HOME:-}" ]; then + printf '%s\n' "$HOME/.cache/codewhale" + return + fi + printf '%s\n' "${TMPDIR:-/tmp}/codewhale-cache" +} + +codewhale_dev_cache_repo_root() { + if [ -n "${CODEWHALE_DEV_CACHE_REPO_ROOT:-}" ]; then + printf '%s\n' "$CODEWHALE_DEV_CACHE_REPO_ROOT" + return + fi + pwd +} + +codewhale_dev_cache_rustc_commit() { + if [ -n "${CODEWHALE_RUSTC_COMMIT:-}" ]; then + printf '%s\n' "$CODEWHALE_RUSTC_COMMIT" + return + fi + # rustc -vV is cheap and does not start a compile. + _cw_out=$(rustc -vV 2>/dev/null || true) + _cw_commit=$(printf '%s\n' "$_cw_out" | awk '/^commit-hash:/{print $2; exit}') + if [ -n "$_cw_commit" ]; then + printf '%s\n' "$_cw_commit" + else + printf '%s\n' "unknown" + fi +} + +codewhale_dev_cache_cargo_version_mm() { + # cargo 1.97.0 -> 1097. Empty if cargo is missing or unparsable. + cargo --version 2>/dev/null | awk '{ + split($2, a, ".") + if (a[1] ~ /^[0-9]+$/ && a[2] ~ /^[0-9]+$/) + printf "%d\n", a[1]*1000+a[2] + }' +} + +# Stable 16-hex-char fingerprint of a path. Used only for the pre-1.91 +# CARGO_TARGET_DIR fallback; Cargo 1.91+ expands {workspace-path-hash}. +codewhale_dev_cache_path_fingerprint() { + if command -v sha256sum >/dev/null 2>&1; then + printf '%s' "$1" | sha256sum | awk '{print substr($1,1,16)}' + elif command -v shasum >/dev/null 2>&1; then + printf '%s' "$1" | shasum -a 256 | awk '{print substr($1,1,16)}' + elif command -v openssl >/dev/null 2>&1; then + printf '%s' "$1" | openssl dgst -sha256 | awk '{print substr($NF,1,16)}' + else + printf '%s' "$1" | cksum | awk '{printf "%s%08s\n", $1, $2}' | tr ' ' '0' + fi +} + +codewhale_dev_cache_has_local_target() { + _cw_repo=$(codewhale_dev_cache_repo_root) + [ -d "$_cw_repo/target" ] +} + +codewhale_dev_cache_incremental_off() { + case ${CARGO_INCREMENTAL:-} in + 0|false|no|off) return 0 ;; + *) return 1 ;; + esac +} + +codewhale_dev_cache_incremental_on() { + case ${CARGO_INCREMENTAL:-} in + 1|true|yes|on) return 0 ;; + *) return 1 ;; + esac +} + +codewhale_dev_cache_sccache_bin() { + command -v sccache 2>/dev/null || true +} + +codewhale_dev_cache_log() { + if [ "${CODEWHALE_DEV_CACHE_QUIET:-}" = 1 ]; then + return 0 + fi + printf 'dev-cache: %s\n' "$*" >&2 +} + +# Apply the topology to the current shell. Idempotent. Never overrides an +# already-set CARGO_TARGET_DIR, CARGO_BUILD_BUILD_DIR, RUSTC_WRAPPER, or +# SCCACHE_DIR. +codewhale_dev_cache_apply() { + CODEWHALE_DEV_CACHE_MODE=${CODEWHALE_DEV_CACHE_MODE:-} + # Re-entry after a previous apply in this shell: keep the first decision + # unless the caller unset the mode (tests do that between cases). + if [ -n "${CODEWHALE_DEV_CACHE_APPLIED:-}" ] && [ -n "${CODEWHALE_DEV_CACHE_MODE:-}" ]; then + return 0 + fi + + _cw_want=${CODEWHALE_DEV_CACHE:-auto} + _cw_root=$(codewhale_dev_cache_root) + _cw_sccache_want=${CODEWHALE_SCCACHE:-auto} + _cw_commit=$(codewhale_dev_cache_rustc_commit) + CODEWHALE_DEV_CACHE_SCCACHE_DIR=${SCCACHE_DIR:-${_cw_root}/sccache/${_cw_commit}} + CODEWHALE_DEV_CACHE_BUILD_DIR=${_cw_root}/build/'{workspace-path-hash}' + CODEWHALE_DEV_CACHE_LEGACY_TARGET_DIR=${_cw_root}/target/$(codewhale_dev_cache_path_fingerprint "$(codewhale_dev_cache_repo_root)") + + if codewhale_dev_cache_falsey "$_cw_want"; then + CODEWHALE_DEV_CACHE_MODE=disabled + CODEWHALE_DEV_CACHE_SCCACHE=disabled + CODEWHALE_DEV_CACHE_APPLIED=1 + export CODEWHALE_DEV_CACHE_MODE CODEWHALE_DEV_CACHE_SCCACHE + codewhale_dev_cache_log "disabled (CODEWHALE_DEV_CACHE=${_cw_want})" + return 0 + fi + + if [ -n "${CARGO_TARGET_DIR:-}" ]; then + CODEWHALE_DEV_CACHE_MODE=inherited-target-dir + elif [ -n "${CARGO_BUILD_BUILD_DIR:-}" ]; then + CODEWHALE_DEV_CACHE_MODE=inherited-build-dir + elif codewhale_dev_cache_truthy "$_cw_want"; then + CODEWHALE_DEV_CACHE_MODE=force-isolated + elif codewhale_dev_cache_has_local_target; then + CODEWHALE_DEV_CACHE_MODE=existing-target + else + CODEWHALE_DEV_CACHE_MODE=isolated-build-dir + fi + + case $CODEWHALE_DEV_CACHE_MODE in + force-isolated|isolated-build-dir) + _cw_mm=$(codewhale_dev_cache_cargo_version_mm) + if [ -n "$_cw_mm" ] && [ "$_cw_mm" -lt 1091 ]; then + # Cargo 1.91 introduced build.build-dir. Older cargo only isolates + # via CARGO_TARGET_DIR; keep that fallback per-workspace. + if [ -z "${CARGO_TARGET_DIR:-}" ]; then + CARGO_TARGET_DIR=$CODEWHALE_DEV_CACHE_LEGACY_TARGET_DIR + export CARGO_TARGET_DIR + fi + CODEWHALE_DEV_CACHE_MODE=${CODEWHALE_DEV_CACHE_MODE}-legacy-target-dir + else + CARGO_BUILD_BUILD_DIR=$CODEWHALE_DEV_CACHE_BUILD_DIR + export CARGO_BUILD_BUILD_DIR + mkdir -p "$_cw_root/build" 2>/dev/null || true + fi + ;; + esac + + # sccache: never wrap incremental rustc. Missing binary is a fallback, + # not an error. + if codewhale_dev_cache_falsey "$_cw_sccache_want"; then + CODEWHALE_DEV_CACHE_SCCACHE=disabled + elif [ -n "${RUSTC_WRAPPER:-}" ]; then + case $RUSTC_WRAPPER in + *sccache*) CODEWHALE_DEV_CACHE_SCCACHE=external-sccache ;; + *) CODEWHALE_DEV_CACHE_SCCACHE=external-wrapper ;; + esac + else + if codewhale_dev_cache_truthy "$_cw_sccache_want" && ! codewhale_dev_cache_incremental_on; then + # Request the measured new-worktree / CI-like topology. + if [ -z "${CARGO_INCREMENTAL:-}" ]; then + CARGO_INCREMENTAL=0 + export CARGO_INCREMENTAL + fi + fi + if ! codewhale_dev_cache_incremental_off; then + CODEWHALE_DEV_CACHE_SCCACHE=skipped-incremental + else + _cw_bin=$(codewhale_dev_cache_sccache_bin) + if [ -z "$_cw_bin" ]; then + CODEWHALE_DEV_CACHE_SCCACHE=not-found + else + RUSTC_WRAPPER=$_cw_bin + export RUSTC_WRAPPER + if [ -z "${SCCACHE_DIR:-}" ]; then + SCCACHE_DIR=$CODEWHALE_DEV_CACHE_SCCACHE_DIR + export SCCACHE_DIR + fi + mkdir -p "$SCCACHE_DIR" 2>/dev/null || true + CODEWHALE_DEV_CACHE_SCCACHE=enabled + fi + fi + fi + + CODEWHALE_DEV_CACHE_APPLIED=1 + export CODEWHALE_DEV_CACHE_MODE CODEWHALE_DEV_CACHE_SCCACHE + export CODEWHALE_DEV_CACHE_SCCACHE_DIR + + _cw_line="mode=${CODEWHALE_DEV_CACHE_MODE} sccache=${CODEWHALE_DEV_CACHE_SCCACHE}" + case $CODEWHALE_DEV_CACHE_MODE in + isolated-build-dir*|force-isolated*) + _cw_line="${_cw_line} build-dir=${CARGO_BUILD_BUILD_DIR:-${CARGO_TARGET_DIR:-unset}}" + ;; + inherited-target-dir) + _cw_line="${_cw_line} CARGO_TARGET_DIR=${CARGO_TARGET_DIR}" + ;; + inherited-build-dir) + _cw_line="${_cw_line} CARGO_BUILD_BUILD_DIR=${CARGO_BUILD_BUILD_DIR}" + ;; + existing-target) + _cw_line="${_cw_line} using ./target (set CODEWHALE_DEV_CACHE=1 to isolate)" + ;; + esac + if [ "${CODEWHALE_DEV_CACHE_SCCACHE}" = enabled ]; then + _cw_line="${_cw_line} SCCACHE_DIR=${SCCACHE_DIR} rustc=${_cw_commit}" + fi + codewhale_dev_cache_log "$_cw_line" +} + +codewhale_dev_cache_status() { + _cw_tmpl=${CODEWHALE_DEV_CACHE_BUILD_DIR:-} + if [ -z "$_cw_tmpl" ]; then + _cw_tmpl=$(codewhale_dev_cache_root)/build/'{workspace-path-hash}' + fi + printf '%s\n' \ + "cache_root=$(codewhale_dev_cache_root)" \ + "repo_root=$(codewhale_dev_cache_repo_root)" \ + "mode=${CODEWHALE_DEV_CACHE_MODE:-unset}" \ + "sccache=${CODEWHALE_DEV_CACHE_SCCACHE:-unset}" \ + "build_dir_template=${_cw_tmpl}" \ + "CARGO_BUILD_BUILD_DIR=${CARGO_BUILD_BUILD_DIR:-}" \ + "CARGO_TARGET_DIR=${CARGO_TARGET_DIR:-}" \ + "CARGO_INCREMENTAL=${CARGO_INCREMENTAL:-}" \ + "RUSTC_WRAPPER=${RUSTC_WRAPPER:-}" \ + "SCCACHE_DIR=${SCCACHE_DIR:-}" \ + "rustc_commit=$(codewhale_dev_cache_rustc_commit)" \ + "sccache_bin=$(codewhale_dev_cache_sccache_bin)" +} + +# Extra cargo CLI flags that force template expansion of build-dir. +# CARGO_BUILD_BUILD_DIR is still exported for nested tools; --config is +# what Cargo documents as supporting {workspace-path-hash}. +codewhale_dev_cache_exec_cargo() { + case ${CODEWHALE_DEV_CACHE_MODE:-} in + isolated-build-dir|force-isolated) + if [ -n "${CARGO_BUILD_BUILD_DIR:-}" ]; then + exec cargo --config "build.build-dir = \"${CARGO_BUILD_BUILD_DIR}\"" "$@" + fi + ;; + esac + exec cargo "$@" +} + +codewhale_dev_cache_print_exports() { + [ -n "${CARGO_BUILD_BUILD_DIR:-}" ] && printf 'export CARGO_BUILD_BUILD_DIR=%s\n' "$CARGO_BUILD_BUILD_DIR" + [ -n "${CARGO_TARGET_DIR:-}" ] && printf 'export CARGO_TARGET_DIR=%s\n' "$CARGO_TARGET_DIR" + [ -n "${CARGO_INCREMENTAL:-}" ] && printf 'export CARGO_INCREMENTAL=%s\n' "$CARGO_INCREMENTAL" + [ -n "${RUSTC_WRAPPER:-}" ] && printf 'export RUSTC_WRAPPER=%s\n' "$RUSTC_WRAPPER" + [ -n "${SCCACHE_DIR:-}" ] && printf 'export SCCACHE_DIR=%s\n' "$SCCACHE_DIR" + [ -n "${CODEWHALE_DEV_CACHE_MODE:-}" ] && printf 'export CODEWHALE_DEV_CACHE_MODE=%s\n' "$CODEWHALE_DEV_CACHE_MODE" + [ -n "${CODEWHALE_DEV_CACHE_SCCACHE:-}" ] && printf 'export CODEWHALE_DEV_CACHE_SCCACHE=%s\n' "$CODEWHALE_DEV_CACHE_SCCACHE" +} + +codewhale_dev_cache_self_check() { + _cw_fail=0 + _cw_root=$(codewhale_dev_cache_root) + _cw_home=${HOME:-} + _cw_xdg=${XDG_CACHE_HOME:-} + + if [ -z "${CODEWHALE_CACHE_ROOT:-}" ]; then + if [ -n "$_cw_xdg" ]; then + case $_cw_root in + "$_cw_xdg"/codewhale) ;; + *) + printf 'self-check: expected XDG default %s/codewhale, got %s\n' "$_cw_xdg" "$_cw_root" >&2 + _cw_fail=1 + ;; + esac + elif [ -n "$_cw_home" ]; then + case $_cw_root in + "$_cw_home"/.cache/codewhale) ;; + *) + printf 'self-check: expected HOME default %s/.cache/codewhale, got %s\n' "$_cw_home" "$_cw_root" >&2 + _cw_fail=1 + ;; + esac + fi + fi + + case ${CARGO_BUILD_BUILD_DIR:-} in + *'{workspace-path-hash}'*) + ;; + '') + case ${CODEWHALE_DEV_CACHE_MODE:-} in + isolated-build-dir|force-isolated) + printf 'self-check: isolated mode did not set CARGO_BUILD_BUILD_DIR\n' >&2 + _cw_fail=1 + ;; + esac + ;; + *) + case ${CODEWHALE_DEV_CACHE_MODE:-} in + isolated-build-dir|force-isolated) + printf 'self-check: isolated build-dir lacks {workspace-path-hash}: %s\n' "$CARGO_BUILD_BUILD_DIR" >&2 + _cw_fail=1 + ;; + esac + ;; + esac + + if [ "${CODEWHALE_DEV_CACHE_SCCACHE:-}" = enabled ]; then + if ! codewhale_dev_cache_incremental_off; then + printf 'self-check: sccache enabled while incremental is not off\n' >&2 + _cw_fail=1 + fi + case ${RUSTC_WRAPPER:-} in + *sccache*) ;; + *) + printf 'self-check: sccache enabled but RUSTC_WRAPPER=%s\n' "${RUSTC_WRAPPER:-}" >&2 + _cw_fail=1 + ;; + esac + fi + + if [ "${CODEWHALE_DEV_CACHE_SCCACHE:-}" = not-found ]; then + if [ -n "${RUSTC_WRAPPER:-}" ]; then + printf 'self-check: missing sccache must not set RUSTC_WRAPPER (%s)\n' "$RUSTC_WRAPPER" >&2 + _cw_fail=1 + fi + fi + + if [ "$_cw_fail" -eq 0 ]; then + printf 'dev-cache: self-check ok\n' + return 0 + fi + printf 'dev-cache: self-check FAILED\n' >&2 + return 1 +} + +codewhale_dev_cache_usage() { + cat <<'EOF' +usage: scripts/dev-cache.sh --status|--self-check|--print-exports|--help + +Portable opt-in Cargo cache topology. Source this file and call +codewhale_dev_cache_apply from scripts/dev-test.sh / scripts/dev-cargo.sh. + + --status apply (if needed) and print key=value status + --self-check apply and check portable defaults / sccache fallback + --print-exports apply and print export statements for eval + --help this message + +Environment overrides are documented at the top of this file. Defaults +never contain a machine-specific absolute path. +EOF +} + +# CLI only when this file is the executed program, not when sourced. +case ${0##*/} in + dev-cache.sh) + set -eu + _cw_cmd=${1:---status} + case $_cw_cmd in + --status|-s) + codewhale_dev_cache_apply + codewhale_dev_cache_status + ;; + --self-check) + codewhale_dev_cache_apply + codewhale_dev_cache_status + codewhale_dev_cache_self_check + ;; + --print-exports) + codewhale_dev_cache_apply + codewhale_dev_cache_print_exports + ;; + --help|-h) + codewhale_dev_cache_usage + ;; + *) + codewhale_dev_cache_usage >&2 + exit 2 + ;; + esac + ;; +esac diff --git a/scripts/dev-cache.test.sh b/scripts/dev-cache.test.sh new file mode 100755 index 000000000..4c22d7277 --- /dev/null +++ b/scripts/dev-cache.test.sh @@ -0,0 +1,294 @@ +#!/bin/sh +# Hermetic tests for scripts/dev-cache.sh. No cargo compile, no cache +# deletion. Exercises portable defaults, overrides, missing-sccache +# fallback, incremental gating, and new-vs-existing worktree policy. +set -eu + +repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +DEV_CACHE=$repo_root/scripts/dev-cache.sh +DEV_CARGO=$repo_root/scripts/dev-cargo.sh + +fail=0 +pass=0 + +ok() { + pass=$((pass + 1)) + printf 'ok - %s\n' "$1" +} + +bad() { + fail=$((fail + 1)) + printf 'FAIL - %s\n' "$1" + if [ -n "${2:-}" ]; then + printf '%s\n' "$2" | sed 's/^/ /' + fi +} + +work=$(mktemp -d "${TMPDIR:-/tmp}/codewhale-dev-cache.XXXXXX") +cleanup() { rm -rf "$work"; } +trap cleanup EXIT INT HUP TERM + +HOME_DIR=$work/home +mkdir -p "$HOME_DIR" +FAKEBIN=$work/bin +mkdir -p "$FAKEBIN" +NEW_WT=$work/new-wt +OLD_WT=$work/old-wt +mkdir -p "$NEW_WT" "$OLD_WT/target" + +# cargo/rustc/sccache are injected per case. Keep the base PATH hermetic. +BASE_PATH=$FAKEBIN:/usr/bin:/bin + +run_apply() { + env -i \ + PATH="$BASE_PATH" \ + HOME="$HOME_DIR" \ + CODEWHALE_DEV_CACHE_QUIET=1 \ + DEV_CACHE="$DEV_CACHE" \ + "$@" \ + /bin/sh -c ' + set -eu + . "$DEV_CACHE" + codewhale_dev_cache_apply + codewhale_dev_cache_status + ' # shellcheck disable=SC2016 -- $DEV_CACHE expands in the child. +} + +contains() { + printf '%s' "$1" | grep -qF -- "$2" +} + +# 1. Portable default root is $HOME/.cache/codewhale, never a desk path. +out=$(run_apply CODEWHALE_DEV_CACHE=0 CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT") +if contains "$out" "cache_root=$HOME_DIR/.cache/codewhale"; then + ok "default cache root is HOME/.cache/codewhale" +else + bad "default cache root is HOME/.cache/codewhale" "$out" +fi +if printf '%s\n' "$out" | grep -v '^repo_root=' | grep -qF /Volumes/VIXinSSD; then + bad "default paths must not mention /Volumes/VIXinSSD" "$out" +else + ok "default paths do not mention /Volumes/VIXinSSD" +fi +if grep -E '/Users/hunterbown|/Volumes/VIXinSSD' "$DEV_CACHE" "$DEV_CARGO" >/dev/null; then + bad "helper source must not hard-code a desk path" +else + ok "helper source has no desk-local absolute paths" +fi + +# 2. XDG_CACHE_HOME and CODEWHALE_CACHE_ROOT win in that documented order. +out=$(run_apply CODEWHALE_DEV_CACHE=0 XDG_CACHE_HOME="$work/xdg") +if contains "$out" "cache_root=$work/xdg/codewhale"; then + ok "XDG_CACHE_HOME/codewhale is the default when set" +else + bad "XDG_CACHE_HOME/codewhale is the default when set" "$out" +fi +out=$(run_apply CODEWHALE_DEV_CACHE=0 \ + XDG_CACHE_HOME="$work/xdg" \ + CODEWHALE_CACHE_ROOT="$work/explicit-root") +if contains "$out" "cache_root=$work/explicit-root"; then + ok "CODEWHALE_CACHE_ROOT overrides XDG and HOME" +else + bad "CODEWHALE_CACHE_ROOT overrides XDG and HOME" "$out" +fi + +# 3. Disabled mode leaves Cargo/sccache vars alone. +out=$(run_apply CODEWHALE_DEV_CACHE=0) +if contains "$out" "mode=disabled" \ + && contains "$out" "CARGO_BUILD_BUILD_DIR=" \ + && contains "$out" "RUSTC_WRAPPER="; then + ok "CODEWHALE_DEV_CACHE=0 does not set cargo or sccache vars" +else + bad "CODEWHALE_DEV_CACHE=0 does not set cargo or sccache vars" "$out" +fi + +# 4. New worktree (no ./target) gets isolated build-dir with the cargo template. +out=$(run_apply \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache") +if contains "$out" "mode=isolated-build-dir" \ + && contains "$out" "CARGO_BUILD_BUILD_DIR=$work/cache/build/{workspace-path-hash}"; then + ok "new worktree uses isolated build-dir template" +else + bad "new worktree uses isolated build-dir template" "$out" +fi +if contains "$out" "CARGO_INCREMENTAL="; then + ok "isolated default does not turn incremental off" +else + bad "isolated default does not turn incremental off" "$out" +fi +if contains "$out" "sccache=skipped-incremental"; then + ok "sccache stays off while incremental is on" +else + bad "sccache stays off while incremental is on" "$out" +fi + +# 5. Existing ./target is left alone under auto. +out=$(run_apply \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$OLD_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache") +if contains "$out" "mode=existing-target" \ + && contains "$out" "CARGO_BUILD_BUILD_DIR="; then + ok "existing ./target is not moved under auto" +else + bad "existing ./target is not moved under auto" "$out" +fi + +# 6. CODEWHALE_DEV_CACHE=1 isolates even when ./target exists. +out=$(run_apply \ + CODEWHALE_DEV_CACHE=1 \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$OLD_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache") +if contains "$out" "mode=force-isolated" \ + && contains "$out" "CARGO_BUILD_BUILD_DIR=$work/cache/build/{workspace-path-hash}"; then + ok "CODEWHALE_DEV_CACHE=1 isolates an existing worktree" +else + bad "CODEWHALE_DEV_CACHE=1 isolates an existing worktree" "$out" +fi + +# 7. Already-set CARGO_TARGET_DIR / CARGO_BUILD_BUILD_DIR are respected. +out=$(run_apply \ + CODEWHALE_DEV_CACHE=1 \ + CARGO_TARGET_DIR="$work/user-target" \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT") +if contains "$out" "mode=inherited-target-dir" \ + && contains "$out" "CARGO_TARGET_DIR=$work/user-target" \ + && contains "$out" "CARGO_BUILD_BUILD_DIR="; then + ok "existing CARGO_TARGET_DIR is not overwritten" +else + bad "existing CARGO_TARGET_DIR is not overwritten" "$out" +fi +out=$(run_apply \ + CARGO_BUILD_BUILD_DIR="$work/user-build" \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT") +if contains "$out" "mode=inherited-build-dir" \ + && contains "$out" "CARGO_BUILD_BUILD_DIR=$work/user-build"; then + ok "existing CARGO_BUILD_BUILD_DIR is not overwritten" +else + bad "existing CARGO_BUILD_BUILD_DIR is not overwritten" "$out" +fi + +# 8. Missing sccache is a fallback, not a failure, even when requested. +out=$(run_apply \ + CODEWHALE_SCCACHE=1 \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache") +if contains "$out" "sccache=not-found" \ + && contains "$out" "RUSTC_WRAPPER=" \ + && contains "$out" "CARGO_INCREMENTAL=0"; then + ok "missing sccache falls back without setting RUSTC_WRAPPER" +else + bad "missing sccache falls back without setting RUSTC_WRAPPER" "$out" +fi + +# 9. sccache wraps only when incremental is off and the binary exists. +printf '%s\n' '#!/bin/sh' 'exit 0' >"$FAKEBIN/sccache" +chmod +x "$FAKEBIN/sccache" + +out=$(run_apply \ + CARGO_INCREMENTAL=0 \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache" \ + CODEWHALE_RUSTC_COMMIT=abc123deadbeef) +if contains "$out" "sccache=enabled" \ + && contains "$out" "RUSTC_WRAPPER=$FAKEBIN/sccache" \ + && contains "$out" "SCCACHE_DIR=$work/cache/sccache/abc123deadbeef"; then + ok "sccache wraps rustc when incremental is off" +else + bad "sccache wraps rustc when incremental is off" "$out" +fi + +out=$(run_apply \ + CARGO_INCREMENTAL=1 \ + CODEWHALE_SCCACHE=1 \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache") +if contains "$out" "sccache=skipped-incremental" \ + && contains "$out" "RUSTC_WRAPPER=" \ + && contains "$out" "CARGO_INCREMENTAL=1"; then + ok "explicit incremental=1 wins over CODEWHALE_SCCACHE=1" +else + bad "explicit incremental=1 wins over CODEWHALE_SCCACHE=1" "$out" +fi + +out=$(run_apply \ + CODEWHALE_SCCACHE=1 \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache" \ + CODEWHALE_RUSTC_COMMIT=abc123deadbeef) +if contains "$out" "sccache=enabled" \ + && contains "$out" "CARGO_INCREMENTAL=0"; then + ok "CODEWHALE_SCCACHE=1 requests incremental=0 and wraps" +else + bad "CODEWHALE_SCCACHE=1 requests incremental=0 and wraps" "$out" +fi + +out=$(run_apply \ + RUSTC_WRAPPER=/usr/bin/true \ + CARGO_INCREMENTAL=0 \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT") +if contains "$out" "sccache=external-wrapper" \ + && contains "$out" "RUSTC_WRAPPER=/usr/bin/true"; then + ok "existing RUSTC_WRAPPER is left alone" +else + bad "existing RUSTC_WRAPPER is left alone" "$out" +fi + +# 10. Cargo older than 1.91 falls back to a per-workspace CARGO_TARGET_DIR. +printf '%s\n' '#!/bin/sh' 'echo "cargo 1.88.0 (test)"' >"$FAKEBIN/cargo" +chmod +x "$FAKEBIN/cargo" +out=$(run_apply \ + CODEWHALE_DEV_CACHE=1 \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache") +if contains "$out" "legacy-target-dir" \ + && contains "$out" "CARGO_BUILD_BUILD_DIR=" \ + && contains "$out" "CARGO_TARGET_DIR=$work/cache/target/"; then + ok "cargo 1.88 falls back to an isolated CARGO_TARGET_DIR" +else + bad "cargo 1.88 falls back to an isolated CARGO_TARGET_DIR" "$out" +fi +rm -f "$FAKEBIN/cargo" + +# 11. CLI self-check passes for a new worktree with portable HOME. +cli_out=$( + env -i \ + PATH="$BASE_PATH" \ + HOME="$HOME_DIR" \ + PWD="$NEW_WT" \ + CODEWHALE_DEV_CACHE_QUIET=1 \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache" \ + /bin/sh "$DEV_CACHE" --self-check +) || cli_rc=$? +cli_rc=${cli_rc:-0} +if [ "$cli_rc" -eq 0 ] && contains "$cli_out" "dev-cache: self-check ok"; then + ok "CLI --self-check passes on a new worktree" +else + bad "CLI --self-check passes on a new worktree" "$cli_out" +fi + +# 12. Two worktree paths produce different legacy fingerprints. +# shellcheck source=scripts/dev-cache.sh +. "$DEV_CACHE" +fp_a=$(codewhale_dev_cache_path_fingerprint "$NEW_WT") +fp_b=$(codewhale_dev_cache_path_fingerprint "$OLD_WT") +if [ -n "$fp_a" ] && [ -n "$fp_b" ] && [ "$fp_a" != "$fp_b" ]; then + ok "path fingerprints differ across worktrees" +else + bad "path fingerprints differ across worktrees" "a=$fp_a b=$fp_b" +fi + +# 13. dev-cargo.sh --status is the same helper. +if /bin/sh "$DEV_CARGO" --help | grep -q 'dev-cache'; then + ok "dev-cargo.sh --help delegates to the cache helper" +else + bad "dev-cargo.sh --help delegates to the cache helper" +fi + +if [ "$fail" -eq 0 ]; then + printf 'dev-cache.test.sh: all %s checks passed\n' "$pass" + exit 0 +fi +printf 'dev-cache.test.sh: %s/%s checks failed\n' "$fail" "$((pass + fail))" >&2 +exit 1 diff --git a/scripts/dev-cargo.sh b/scripts/dev-cargo.sh new file mode 100755 index 000000000..6cf1baf4e --- /dev/null +++ b/scripts/dev-cargo.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# Run cargo with the portable Codewhale cache topology applied. +# +# scripts/dev-cargo.sh test -p codewhale-config --lib --locked +# scripts/dev-cargo.sh --status +# scripts/dev-cargo.sh --self-check +# +# This is the everyday compile entry point. It does not change product +# behavior. See scripts/dev-cache.sh and docs/BUILD_PERFORMANCE.md. +set -eu + +repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +cd "$repo_root" + +# shellcheck source=scripts/dev-cache.sh +. "$repo_root/scripts/dev-cache.sh" + +case ${1:-} in + --status|--self-check|--print-exports|--help|-h) + exec "$repo_root/scripts/dev-cache.sh" "$1" + ;; +esac + +codewhale_dev_cache_apply +# Match CI / the product's 16 MiB owner-thread stack so local cargo test +# and nextest do not abort on the default ~2 MiB (Windows ~1 MiB) stack. +if [ -z "${RUST_MIN_STACK:-}" ]; then + RUST_MIN_STACK=16777216 + export RUST_MIN_STACK +fi +codewhale_dev_cache_exec_cargo "$@" diff --git a/scripts/dev-test.sh b/scripts/dev-test.sh index 6eb356f07..63af2a2f4 100755 --- a/scripts/dev-test.sh +++ b/scripts/dev-test.sh @@ -1,24 +1,36 @@ #!/bin/sh -# Map a workspace area or source path to the fastest cargo test for that -# area. Developer iteration aid only; no product behavior. +# Map a workspace area or source path to the fastest cargo/nextest +# invocation for that area, and apply the portable cache topology so a +# new worktree actually gets isolated build-dir (+ sccache only when +# incremental is already off). Developer iteration aid only; no product +# behavior. # # Usage: # scripts/dev-test.sh [filter...] # scripts/dev-test.sh --list +# scripts/dev-test.sh --status # # Examples: # scripts/dev-test.sh config # scripts/dev-test.sh tui elapsed:: # scripts/dev-test.sh crates/tui/src/elapsed.rs # scripts/dev-test.sh tui-pty qa_pty +# +# Environment: +# CODEWHALE_DEV_NEXTEST auto|1|0 (default auto: use cargo-nextest when +# it is on PATH; 0 forces cargo test) +# Cache knobs are documented in scripts/dev-cache.sh. set -eu -repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) cd "$repo_root" +# shellcheck source=scripts/dev-cache.sh +. "$repo_root/scripts/dev-cache.sh" + usage() { printf '%s\n' "usage: scripts/dev-test.sh [filter...]" >&2 - printf '%s\n' " scripts/dev-test.sh --list" >&2 + printf '%s\n' " scripts/dev-test.sh --list|--status|--self-check" >&2 exit 2 } @@ -26,29 +38,34 @@ list_areas() { cat <<'EOF' area command ---- ------- -config cargo test -p codewhale-config --lib --locked -protocol cargo test -p codewhale-protocol --lib --locked -execpolicy cargo test -p codewhale-execpolicy --lib --locked -paths cargo test -p codewhale-paths --lib --locked -secrets cargo test -p codewhale-secrets --lib --locked +agent cargo test -p codewhale-agent --lib --locked +app-server cargo test -p codewhale-app-server --lib --locked +build-support cargo test -p codewhale-build-support --lib --locked cli cargo test -p codewhale-cli --lib --locked +command-contract cargo test -p codewhale-command-contract --lib --locked +config cargo test -p codewhale-config --lib --locked core cargo test -p codewhale-core --lib --locked +execpolicy cargo test -p codewhale-execpolicy --lib --locked +hooks cargo test -p codewhale-hooks --lib --locked +lane cargo test -p codewhale-lane --lib --locked +mcp cargo test -p codewhale-mcp --lib --locked +paths cargo test -p codewhale-paths --lib --locked +protocol cargo test -p codewhale-protocol --lib --locked +release cargo test -p codewhale-release --lib --locked +secrets cargo test -p codewhale-secrets --lib --locked +state cargo test -p codewhale-state --lib --locked +telemetry cargo test -p codewhale-telemetry --lib --locked tools cargo test -p codewhale-tools --lib --locked tui cargo test -p codewhale-tui --lib --locked tui-integration cargo test -p codewhale-tui --test integration --locked tui-pty cargo test -p codewhale-tui --test pty --locked tui-cucumber cargo test -p codewhale-tui --test cucumber --locked +workflow cargo test -p codewhale-workflow --lib --locked +workflow-js cargo test -p codewhale-workflow-js --lib --locked path prefix area / extra filter ----------- ------------------- -crates/config/ config -crates/protocol/ protocol -crates/execpolicy/ execpolicy -crates/paths/ paths -crates/secrets/ secrets -crates/cli/ cli -crates/core/ core -crates/tools/ tools +crates// crates/tui/src/tui/ tui tui:: crates/tui/src/tools/ tui tools:: crates/tui/src/core/ tui core:: @@ -59,8 +76,13 @@ crates/tui/tests/pty/ tui-pty crates/tui/tests/cucumber/ tui-cucumber crates/tui/tests/ tui-integration -Do not use cargo test --workspace for a single-area edit. --lib and ---tests are disjoint; a green --lib run does not cover crates/tui/tests/. +When cargo-nextest is on PATH, the run stage is `cargo nextest run` +instead of `cargo test` (same binaries; process per test). Set +CODEWHALE_DEV_NEXTEST=0 to force libtest. New worktrees get an isolated +Cargo build-dir via scripts/dev-cache.sh; sccache wraps rustc only when +incremental is already off. Do not use cargo test --workspace for a +single-area edit. --lib and --tests are disjoint; a green --lib run does +not cover crates/tui/tests/. EOF } @@ -71,6 +93,10 @@ if [ "$1" = "--list" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then exit 0 fi +if [ "$1" = "--status" ] || [ "$1" = "--self-check" ]; then + exec "$repo_root/scripts/dev-cache.sh" "$1" +fi + area=$1 shift @@ -80,14 +106,6 @@ if [ -e "$area" ] || printf '%s' "$area" | grep -q /; then rel=${area#./} extra= case $rel in - crates/config/*|crates/config) area=config ;; - crates/protocol/*|crates/protocol) area=protocol ;; - crates/execpolicy/*|crates/execpolicy) area=execpolicy ;; - crates/paths/*|crates/paths) area=paths ;; - crates/secrets/*|crates/secrets) area=secrets ;; - crates/cli/*|crates/cli) area=cli ;; - crates/core/*|crates/core) area=core ;; - crates/tools/*|crates/tools) area=tools ;; crates/tui/tests/integration/*) area=tui-integration extra=$(basename "$rel" .rs) @@ -147,15 +165,27 @@ fi pkg= target=--lib case $area in - config) pkg=codewhale-config ;; - protocol) pkg=codewhale-protocol ;; - execpolicy) pkg=codewhale-execpolicy ;; - paths) pkg=codewhale-paths ;; - secrets) pkg=codewhale-secrets ;; + agent) pkg=codewhale-agent ;; + app-server) pkg=codewhale-app-server ;; + build-support) pkg=codewhale-build-support ;; cli) pkg=codewhale-cli ;; + command-contract) pkg=codewhale-command-contract ;; + config) pkg=codewhale-config ;; core) pkg=codewhale-core ;; + execpolicy) pkg=codewhale-execpolicy ;; + hooks) pkg=codewhale-hooks ;; + lane) pkg=codewhale-lane ;; + mcp) pkg=codewhale-mcp ;; + paths) pkg=codewhale-paths ;; + protocol) pkg=codewhale-protocol ;; + release) pkg=codewhale-release ;; + secrets) pkg=codewhale-secrets ;; + state) pkg=codewhale-state ;; + telemetry) pkg=codewhale-telemetry ;; tools) pkg=codewhale-tools ;; tui) pkg=codewhale-tui ;; + workflow) pkg=codewhale-workflow ;; + workflow-js) pkg=codewhale-workflow-js ;; tui-integration) pkg=codewhale-tui target=--test @@ -177,11 +207,38 @@ case $area in ;; esac +codewhale_dev_cache_apply +if [ -z "${RUST_MIN_STACK:-}" ]; then + RUST_MIN_STACK=16777216 + export RUST_MIN_STACK +fi + +use_nextest=0 +_cw_nextest=${CODEWHALE_DEV_NEXTEST:-auto} +if codewhale_dev_cache_falsey "$_cw_nextest"; then + use_nextest=0 +elif command -v cargo-nextest >/dev/null 2>&1; then + use_nextest=1 +elif codewhale_dev_cache_truthy "$_cw_nextest"; then + printf '%s\n' "dev-test: CODEWHALE_DEV_NEXTEST=${_cw_nextest} but cargo-nextest is not on PATH" >&2 + exit 2 +fi + if [ "$target" = "--test" ]; then + if [ "$use_nextest" -eq 1 ]; then + set -- nextest run -p "$pkg" --test "$harness" --locked "$@" + printf '+ cargo %s\n' "$*" + codewhale_dev_cache_exec_cargo "$@" + fi set -- test -p "$pkg" --test "$harness" --locked "$@" else + if [ "$use_nextest" -eq 1 ]; then + set -- nextest run -p "$pkg" --lib --locked "$@" + printf '+ cargo %s\n' "$*" + codewhale_dev_cache_exec_cargo "$@" + fi set -- test -p "$pkg" --lib --locked "$@" fi printf '+ cargo %s\n' "$*" -exec cargo "$@" +codewhale_dev_cache_exec_cargo "$@" diff --git a/scripts/dev-test.test.sh b/scripts/dev-test.test.sh new file mode 100755 index 000000000..264c12a7c --- /dev/null +++ b/scripts/dev-test.test.sh @@ -0,0 +1,216 @@ +#!/bin/sh +# Hermetic tests for scripts/dev-test.sh: area routing, nextest vs +# libtest selection, and that the cache helper is actually applied. +# Uses a fake cargo; does not compile anything. +set -eu + +repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +DEV_TEST=$repo_root/scripts/dev-test.sh + +fail=0 +pass=0 + +ok() { + pass=$((pass + 1)) + printf 'ok - %s\n' "$1" +} + +bad() { + fail=$((fail + 1)) + printf 'FAIL - %s\n' "$1" + if [ -n "${2:-}" ]; then + printf '%s\n' "$2" | sed 's/^/ /' + fi +} + +work=$(mktemp -d "${TMPDIR:-/tmp}/codewhale-dev-test.XXXXXX") +cleanup() { rm -rf "$work"; } +trap cleanup EXIT INT HUP TERM + +FAKEBIN=$work/bin +mkdir -p "$FAKEBIN" +NEW_WT=$work/new-wt +mkdir -p "$NEW_WT" + +cat >"$FAKEBIN/cargo" <<'EOF' +#!/bin/sh +printf 'CARGO:%s\n' "$*" +printf 'CARGO_BUILD_BUILD_DIR=%s\n' "${CARGO_BUILD_BUILD_DIR-}" +printf 'RUSTC_WRAPPER=%s\n' "${RUSTC_WRAPPER-}" +printf 'CARGO_INCREMENTAL=%s\n' "${CARGO_INCREMENTAL-}" +printf 'RUST_MIN_STACK=%s\n' "${RUST_MIN_STACK-}" +exit 0 +EOF +chmod +x "$FAKEBIN/cargo" + +BASE_PATH=$FAKEBIN:/usr/bin:/bin + +run_dev_test() { + # Leading KEY=VAL pairs are extra env; the rest are script arguments. + _extra=$work/extra-env + : >"$_extra" + while [ $# -gt 0 ]; do + case $1 in + *=*) + printf '%s\n' "$1" >>"$_extra" + shift + ;; + *) + break + ;; + esac + done + # env -i + env-file keeps values with spaces intact and never treats + # --list as an env(1) option. + set -- /bin/sh "$DEV_TEST" "$@" + while IFS= read -r _line; do + set -- "$_line" "$@" + done <"$_extra" + env -i \ + PATH="$BASE_PATH" \ + HOME="$work/home" \ + CODEWHALE_DEV_CACHE_QUIET=1 \ + CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ + CODEWHALE_CACHE_ROOT="$work/cache" \ + "$@" +} + +contains() { + printf '%s' "$1" | grep -qF -- "$2" +} + +# --list names every workspace crate family, including the ones the old +# mapper dropped on the floor. +list_out=$(run_dev_test --list) +for needle in \ + "codewhale-agent" \ + "codewhale-app-server" \ + "codewhale-build-support" \ + "codewhale-command-contract" \ + "codewhale-hooks" \ + "codewhale-lane" \ + "codewhale-mcp" \ + "codewhale-release" \ + "codewhale-state" \ + "codewhale-telemetry" \ + "codewhale-workflow" \ + "codewhale-workflow-js" \ + "codewhale-tui --lib" \ + "codewhale-tui --test pty" +do + if contains "$list_out" "$needle"; then + ok "--list mentions $needle" + else + bad "--list mentions $needle" "$list_out" + fi +done + +# Narrow routing: libtest forced so we assert cargo test argv. +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=0 config) +if contains "$out" "+ cargo test -p codewhale-config --lib --locked" \ + && contains "$out" "test -p codewhale-config --lib --locked"; then + ok "area config maps to codewhale-config --lib" +else + bad "area config maps to codewhale-config --lib" "$out" +fi + +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=0 crates/tui/src/elapsed.rs) +if contains "$out" "test -p codewhale-tui --lib --locked elapsed::"; then + ok "path crates/tui/src/elapsed.rs invents elapsed::" +else + bad "path crates/tui/src/elapsed.rs invents elapsed::" "$out" +fi + +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=0 crates/workflow-js/src/eval.rs) +if contains "$out" "test -p codewhale-workflow-js --lib --locked"; then + ok "path crates/workflow-js maps to codewhale-workflow-js" +else + bad "path crates/workflow-js maps to codewhale-workflow-js" "$out" +fi + +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=0 crates/app-server/src/lib.rs) +if contains "$out" "test -p codewhale-app-server --lib --locked"; then + ok "path crates/app-server maps to codewhale-app-server" +else + bad "path crates/app-server maps to codewhale-app-server" "$out" +fi + +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=0 tui-pty qa_pty) +if contains "$out" "test -p codewhale-tui --test pty --locked qa_pty"; then + ok "tui-pty maps to the pty harness" +else + bad "tui-pty maps to the pty harness" "$out" +fi + +# Unknown area fails closed. +rc=0 +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=0 not-a-crate 2>&1) || rc=$? +if [ "$rc" -ne 0 ] && contains "$out" "unknown area"; then + ok "unknown area fails closed" +else + bad "unknown area fails closed" "rc=$rc $out" +fi + +# New worktree applies the isolated build-dir (compile-time topology). +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=0 config) +if contains "$out" "CARGO_BUILD_BUILD_DIR=$work/cache/build/{workspace-path-hash}" \ + && contains "$out" '--config build.build-dir = "'; then + ok "dev-test applies isolated build-dir on a new worktree" +else + bad "dev-test applies isolated build-dir on a new worktree" "$out" +fi +if contains "$out" "RUST_MIN_STACK=16777216"; then + ok "dev-test sets RUST_MIN_STACK when unset" +else + bad "dev-test sets RUST_MIN_STACK when unset" "$out" +fi +if contains "$out" "RUSTC_WRAPPER="; then + ok "dev-test does not wrap rustc on the incremental default" +else + bad "dev-test does not wrap rustc on the incremental default" "$out" +fi + +# CODEWHALE_DEV_CACHE=0 really disables the topology. +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=0 CODEWHALE_DEV_CACHE=0 config) +if contains "$out" "CARGO_BUILD_BUILD_DIR=" \ + && ! contains "$out" '--config build.build-dir'; then + ok "CODEWHALE_DEV_CACHE=0 skips the cache topology" +else + bad "CODEWHALE_DEV_CACHE=0 skips the cache topology" "$out" +fi + +# nextest is used when cargo-nextest is on PATH (test-runtime). +printf '%s\n' '#!/bin/sh' 'exit 0' >"$FAKEBIN/cargo-nextest" +chmod +x "$FAKEBIN/cargo-nextest" +out=$(run_dev_test tui elapsed::) +if contains "$out" "+ cargo nextest run -p codewhale-tui --lib --locked elapsed::" \ + && contains "$out" "nextest run -p codewhale-tui --lib --locked elapsed::"; then + ok "nextest is used when cargo-nextest is installed" +else + bad "nextest is used when cargo-nextest is installed" "$out" +fi + +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=0 tui elapsed::) +if contains "$out" "+ cargo test -p codewhale-tui --lib --locked elapsed::" \ + && contains "$out" "test -p codewhale-tui --lib --locked elapsed::"; then + ok "CODEWHALE_DEV_NEXTEST=0 forces cargo test" +else + bad "CODEWHALE_DEV_NEXTEST=0 forces cargo test" "$out" +fi + +# Forced nextest without the binary fails closed instead of silent cargo. +rm -f "$FAKEBIN/cargo-nextest" +rc=0 +out=$(run_dev_test CODEWHALE_DEV_NEXTEST=1 config 2>&1) || rc=$? +if [ "$rc" -ne 0 ] && contains "$out" "cargo-nextest is not on PATH"; then + ok "CODEWHALE_DEV_NEXTEST=1 without nextest fails closed" +else + bad "CODEWHALE_DEV_NEXTEST=1 without nextest fails closed" "rc=$rc $out" +fi + +if [ "$fail" -eq 0 ]; then + printf 'dev-test.test.sh: all %s checks passed\n' "$pass" + exit 0 +fi +printf 'dev-test.test.sh: %s/%s checks failed\n' "$fail" "$((pass + fail))" >&2 +exit 1