返回 CodeWhale
dev-test.sh
根目录 / scripts / dev-test.sh
1 #!/bin/sh
2 # Map a workspace area or source path to the fastest cargo/nextest
3 # invocation for that area, and apply the portable cache topology so a
4 # new worktree actually gets isolated build-dir (+ sccache only when
5 # incremental is already off). Tests run under the shared temporary HOME
6 # boundary; compiler caches and toolchain homes remain persistent.
7 # A libtest run with an explicit filter refuses green when the filter
8 # matches zero tests (nextest already fails loud on empty selections).
9 #
10 # Usage:
11 # scripts/dev-test.sh <area|path> [filter...]
12 # scripts/dev-test.sh --list
13 # scripts/dev-test.sh --status
14 #
15 # Examples:
16 # scripts/dev-test.sh config
17 # scripts/dev-test.sh tui elapsed::
18 # scripts/dev-test.sh crates/runtime/src/elapsed.rs
19 #
20 # Environment:
21 # CODEWHALE_DEV_NEXTEST auto|1|0 (default auto: use cargo-nextest when
22 # it is on PATH; 0 forces cargo test)
23 # Cache knobs are documented in scripts/dev-cache.sh.
24 set -eu
25
26 repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
27 cd "$repo_root"
28
29 # shellcheck source=scripts/dev-cache.sh
30 . "$repo_root/scripts/dev-cache.sh"
31
32 usage() {
33 printf '%s\n' "usage: scripts/dev-test.sh <area|path> [filter...]" >&2
34 printf '%s\n' " scripts/dev-test.sh --list|--status|--self-check" >&2
35 exit 2
36 }
37
38 list_areas() {
39 cat <<'EOF'
40 area command
41 ---- -------
42 agent cargo test -p codewhale-agent --lib --locked
43 app-server cargo test -p codewhale-app-server --lib --locked
44 build-support cargo test -p codewhale-build-support --lib --locked
45 cli cargo test -p codewhale-cli --lib --locked
46 command-contract cargo test -p codewhale-command-contract --lib --locked
47 config cargo test -p codewhale-config --lib --locked
48 core cargo test -p codewhale-core --lib --locked
49 execpolicy cargo test -p codewhale-execpolicy --lib --locked
50 hooks cargo test -p codewhale-hooks --lib --locked
51 lane cargo test -p codewhale-lane --lib --locked
52 mcp cargo test -p codewhale-mcp --lib --locked
53 paths cargo test -p codewhale-paths --lib --locked
54 protocol cargo test -p codewhale-protocol --lib --locked
55 release cargo test -p codewhale-release --lib --locked
56 runtime cargo test -p codewhale-runtime --lib --locked
57 sanitize cargo test -p codewhale-sanitize --lib --locked
58 secrets cargo test -p codewhale-secrets --lib --locked
59 state cargo test -p codewhale-state --lib --locked
60 telemetry cargo test -p codewhale-telemetry --lib --locked
61 tools cargo test -p codewhale-tools --lib --locked
62 tui cargo test -p codewhale-tui --lib --locked
63 tui-integration cargo test -p codewhale-tui --test integration --locked
64 tui-cucumber cargo test -p codewhale-tui --test cucumber --locked
65 workflow cargo test -p codewhale-workflow --lib --locked
66 workflow-js cargo test -p codewhale-workflow-js --lib --locked
67
68 path prefix area / extra filter
69 ----------- -------------------
70 crates/<crate>/ <crate>
71 crates/tui/src/tui/ tui tui::
72 crates/tui/src/tools/ tui tools::
73 crates/tui/src/core/ tui core::
74 crates/tui/src/commands/ tui commands::
75 crates/tui/src/<file>.rs tui <file>::
76 crates/runtime/src/<file>.rs runtime <file>::
77 crates/tui/tests/integration/ tui-integration <stem>
78 crates/tui/tests/cucumber/ tui-cucumber <stem>
79 crates/tui/tests/ tui-integration
80
81 When cargo-nextest is on PATH, the run stage is `cargo nextest run`
82 instead of `cargo test` (same binaries; process per test). Set
83 CODEWHALE_DEV_NEXTEST=0 to force libtest. New worktrees get an isolated
84 Cargo build-dir via scripts/dev-cache.sh; sccache wraps rustc only when
85 incremental is already off. Test HOME and config paths are isolated by
86 scripts/with-hermetic-test-home.sh. Do not use cargo test --workspace for a
87 single-area edit. --lib and --tests are disjoint; a green --lib run does
88 not cover crates/tui/tests/.
89 EOF
90 }
91
92 [ $# -ge 1 ] || usage
93
94 if [ "$1" = "--list" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
95 list_areas
96 exit 0
97 fi
98
99 if [ "$1" = "--status" ] || [ "$1" = "--self-check" ]; then
100 exec "$repo_root/scripts/dev-cache.sh" "$1"
101 fi
102
103 area=$1
104 shift
105
106 # Path form: map a source path onto an area, and invent a filter only when
107 # the caller did not already pass one.
108 if [ -e "$area" ] || printf '%s' "$area" | grep -q /; then
109 rel=${area#./}
110 extra=
111 case $rel in
112 crates/tui/tests/integration/*)
113 area=tui-integration
114 extra=$(basename "$rel" .rs)
115 ;;
116 crates/tui/tests/cucumber/*)
117 area=tui-cucumber
118 extra=$(basename "$rel" .rs)
119 ;;
120 crates/tui/tests/*)
121 area=tui-integration
122 extra=$(basename "$rel" .rs)
123 ;;
124 crates/tui/src/tui/*)
125 area=tui
126 extra=tui::
127 ;;
128 crates/tui/src/tools/*)
129 area=tui
130 extra=tools::
131 ;;
132 crates/tui/src/core/*)
133 area=tui
134 extra=core::
135 ;;
136 crates/tui/src/commands/*)
137 area=tui
138 extra=commands::
139 ;;
140 crates/tui/src/*)
141 area=tui
142 extra=$(basename "$rel" .rs)::
143 ;;
144 crates/tui/*|crates/tui)
145 area=tui
146 ;;
147 crates/runtime/src/*)
148 area=runtime
149 extra=$(printf '%s' "${rel#crates/runtime/src/}" | awk -F/ '{print $1}')
150 extra=${extra%.rs}::
151 ;;
152 crates/*)
153 crate=$(printf '%s' "$rel" | awk -F/ '{print $2}')
154 area=$crate
155 ;;
156 *)
157 printf '%s\n' "dev-test: no area mapping for path: $area" >&2
158 exit 2
159 ;;
160 esac
161 case $extra in
162 ''|main|main::|lib::|mod|mod::) extra= ;;
163 esac
164 if [ $# -eq 0 ] && [ -n "${extra:-}" ]; then
165 set -- "$extra"
166 fi
167 fi
168
169 pkg=
170 target=--lib
171 case $area in
172 agent) pkg=codewhale-agent ;;
173 app-server) pkg=codewhale-app-server ;;
174 build-support) pkg=codewhale-build-support ;;
175 cli) pkg=codewhale-cli ;;
176 command-contract) pkg=codewhale-command-contract ;;
177 config) pkg=codewhale-config ;;
178 core) pkg=codewhale-core ;;
179 execpolicy) pkg=codewhale-execpolicy ;;
180 hooks) pkg=codewhale-hooks ;;
181 lane) pkg=codewhale-lane ;;
182 mcp) pkg=codewhale-mcp ;;
183 paths) pkg=codewhale-paths ;;
184 protocol) pkg=codewhale-protocol ;;
185 release) pkg=codewhale-release ;;
186 runtime) pkg=codewhale-runtime ;;
187 sanitize) pkg=codewhale-sanitize ;;
188 secrets) pkg=codewhale-secrets ;;
189 state) pkg=codewhale-state ;;
190 telemetry) pkg=codewhale-telemetry ;;
191 tools) pkg=codewhale-tools ;;
192 tui) pkg=codewhale-tui ;;
193 workflow) pkg=codewhale-workflow ;;
194 workflow-js) pkg=codewhale-workflow-js ;;
195 tui-integration)
196 pkg=codewhale-tui
197 target=--test
198 harness=integration
199 ;;
200 tui-cucumber)
201 pkg=codewhale-tui
202 target=--test
203 harness=cucumber
204 ;;
205 *)
206 printf '%s\n' "dev-test: unknown area: $area (try --list)" >&2
207 exit 2
208 ;;
209 esac
210
211 use_nextest=0
212 _cw_nextest=${CODEWHALE_DEV_NEXTEST:-auto}
213 if codewhale_dev_cache_falsey "$_cw_nextest"; then
214 use_nextest=0
215 elif command -v cargo-nextest >/dev/null 2>&1; then
216 use_nextest=1
217 elif codewhale_dev_cache_truthy "$_cw_nextest"; then
218 printf '%s\n' "dev-test: CODEWHALE_DEV_NEXTEST=${_cw_nextest} but cargo-nextest is not on PATH" >&2
219 exit 2
220 fi
221
222 if [ "$target" = "--test" ]; then
223 if [ "$use_nextest" -eq 1 ]; then
224 set -- nextest run -p "$pkg" --test "$harness" --locked "$@"
225 else
226 set -- test -p "$pkg" --test "$harness" --locked "$@"
227 fi
228 else
229 if [ "$use_nextest" -eq 1 ]; then
230 set -- nextest run -p "$pkg" --lib --locked "$@"
231 else
232 set -- test -p "$pkg" --lib --locked "$@"
233 fi
234 fi
235
236 printf '+ cargo %s\n' "$*"
237 # Resolve the persistent cache before replacing HOME; dev-cargo applies the
238 # topology once, retaining Cargo's build-dir template and any caller overrides.
239 CODEWHALE_CACHE_ROOT=$(codewhale_dev_cache_root)
240 export CODEWHALE_CACHE_ROOT
241 # Engine acceptance launches the canonical CLI, which is not a binary target
242 # of the Engine library package. Build it with the same cache/lock topology.
243 if [ "$target" = "--test" ]; then
244 "$repo_root/scripts/with-hermetic-test-home.sh" "$repo_root/scripts/dev-cargo.sh" \
245 build -p codewhale-cli --bin codewhale --locked
246 fi
247 # libtest exits 0 when a filter matches nothing, which has been mistaken for
248 # a pass. With an explicit filter, refuse that green. (nextest already fails
249 # loud on an empty selection, so the guard only wraps libtest.)
250 #
251 # pipefail is not POSIX and probing it outside a subshell is fatal where it
252 # is unsupported: `set` is a special builtin, so an illegal option exits the
253 # shell outright with status 2 instead of returning a status a `&&` list can
254 # absorb. That killed this script on every filtered libtest run under dash
255 # (Ubuntu's /bin/sh, which is what CI and Debian users get) while passing on
256 # macOS. Probe in a subshell, and on shells without pipefail capture the run
257 # and replay it so the refusal below still applies; those shells lose live
258 # streaming for the duration of the filtered run, not the guard.
259 base_args=5
260 if [ "$target" = "--test" ]; then
261 base_args=6
262 fi
263 if [ "$use_nextest" -eq 0 ] && [ "$#" -gt "$base_args" ]; then
264 tmp_log=$(mktemp -t dev-test-log.XXXXXX)
265 trap 'rm -f "$tmp_log"' EXIT INT TERM
266 set +e
267 if (set -o pipefail) 2>/dev/null; then
268 set -o pipefail
269 "$repo_root/scripts/with-hermetic-test-home.sh" "$repo_root/scripts/dev-cargo.sh" "$@" 2>&1 | tee "$tmp_log"
270 test_status=$?
271 else
272 "$repo_root/scripts/with-hermetic-test-home.sh" "$repo_root/scripts/dev-cargo.sh" "$@" > "$tmp_log" 2>&1
273 test_status=$?
274 cat "$tmp_log"
275 fi
276 set -e
277 if [ "$test_status" -eq 0 ]; then
278 if grep -q 'test result:' "$tmp_log"; then
279 if grep 'test result:' "$tmp_log" | grep -Eqv '(^|[^0-9])0 passed;'; then
280 : # at least one binary ran tests
281 else
282 printf '%s\n' "dev-test: filter matched zero tests (every binary reports 0 passed); refusing green." >&2
283 test_status=1
284 fi
285 else
286 printf '%s\n' "dev-test: no 'test result:' lines in output; refusing green." >&2
287 test_status=1
288 fi
289 fi
290 rm -f "$tmp_log"
291 trap - EXIT INT TERM
292 exit "$test_status"
293 fi
294 exec "$repo_root/scripts/with-hermetic-test-home.sh" "$repo_root/scripts/dev-cargo.sh" "$@"
295
295 lines BASH