| 1 | #!/bin/sh |
| 2 | # Hermetic tests for scripts/dev-cache.sh. No cargo compile, no cache |
| 3 | # deletion. Exercises portable defaults, overrides, missing-sccache |
| 4 | # fallback, incremental gating, and new-vs-existing worktree policy. |
| 5 | set -eu |
| 6 | |
| 7 | repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) |
| 8 | DEV_CACHE=$repo_root/scripts/dev-cache.sh |
| 9 | DEV_CARGO=$repo_root/scripts/dev-cargo.sh |
| 10 | |
| 11 | fail=0 |
| 12 | pass=0 |
| 13 | |
| 14 | ok() { |
| 15 | pass=$((pass + 1)) |
| 16 | printf 'ok - %s\n' "$1" |
| 17 | } |
| 18 | |
| 19 | bad() { |
| 20 | fail=$((fail + 1)) |
| 21 | printf 'FAIL - %s\n' "$1" |
| 22 | if [ -n "${2:-}" ]; then |
| 23 | printf '%s\n' "$2" | sed 's/^/ /' |
| 24 | fi |
| 25 | } |
| 26 | |
| 27 | work=$(mktemp -d "${TMPDIR:-/tmp}/codewhale-dev-cache.XXXXXX") |
| 28 | cleanup() { rm -rf "$work"; } |
| 29 | trap cleanup EXIT INT HUP TERM |
| 30 | |
| 31 | HOME_DIR=$work/home |
| 32 | mkdir -p "$HOME_DIR" |
| 33 | FAKEBIN=$work/bin |
| 34 | mkdir -p "$FAKEBIN" |
| 35 | NEW_WT=$work/new-wt |
| 36 | OLD_WT=$work/old-wt |
| 37 | mkdir -p "$NEW_WT" "$OLD_WT/target" |
| 38 | |
| 39 | # cargo/rustc/sccache are injected per case. Keep the base PATH hermetic. |
| 40 | BASE_PATH=$FAKEBIN:/usr/bin:/bin |
| 41 | |
| 42 | run_apply() { |
| 43 | env -i \ |
| 44 | PATH="$BASE_PATH" \ |
| 45 | HOME="$HOME_DIR" \ |
| 46 | CODEWHALE_DEV_CACHE_QUIET=1 \ |
| 47 | DEV_CACHE="$DEV_CACHE" \ |
| 48 | "$@" \ |
| 49 | /bin/sh -c ' |
| 50 | set -eu |
| 51 | . "$DEV_CACHE" |
| 52 | codewhale_dev_cache_apply |
| 53 | codewhale_dev_cache_status |
| 54 | ' # shellcheck disable=SC2016 -- $DEV_CACHE expands in the child. |
| 55 | } |
| 56 | |
| 57 | contains() { |
| 58 | printf '%s' "$1" | grep -qF -- "$2" |
| 59 | } |
| 60 | |
| 61 | # 1. Portable default root is $HOME/.cache/codewhale, never a desk path. |
| 62 | out=$(run_apply CODEWHALE_DEV_CACHE=0 CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT") |
| 63 | if contains "$out" "cache_root=$HOME_DIR/.cache/codewhale"; then |
| 64 | ok "default cache root is HOME/.cache/codewhale" |
| 65 | else |
| 66 | bad "default cache root is HOME/.cache/codewhale" "$out" |
| 67 | fi |
| 68 | if printf '%s\n' "$out" | grep -v '^repo_root=' | grep -qF /Volumes/VIXinSSD; then |
| 69 | bad "default paths must not mention /Volumes/VIXinSSD" "$out" |
| 70 | else |
| 71 | ok "default paths do not mention /Volumes/VIXinSSD" |
| 72 | fi |
| 73 | if grep -E '/Users/hunterbown|/Volumes/VIXinSSD' "$DEV_CACHE" "$DEV_CARGO" >/dev/null; then |
| 74 | bad "helper source must not hard-code a desk path" |
| 75 | else |
| 76 | ok "helper source has no desk-local absolute paths" |
| 77 | fi |
| 78 | |
| 79 | # 2. XDG_CACHE_HOME and CODEWHALE_CACHE_ROOT win in that documented order. |
| 80 | out=$(run_apply CODEWHALE_DEV_CACHE=0 XDG_CACHE_HOME="$work/xdg") |
| 81 | if contains "$out" "cache_root=$work/xdg/codewhale"; then |
| 82 | ok "XDG_CACHE_HOME/codewhale is the default when set" |
| 83 | else |
| 84 | bad "XDG_CACHE_HOME/codewhale is the default when set" "$out" |
| 85 | fi |
| 86 | out=$(run_apply CODEWHALE_DEV_CACHE=0 \ |
| 87 | XDG_CACHE_HOME="$work/xdg" \ |
| 88 | CODEWHALE_CACHE_ROOT="$work/explicit-root") |
| 89 | if contains "$out" "cache_root=$work/explicit-root"; then |
| 90 | ok "CODEWHALE_CACHE_ROOT overrides XDG and HOME" |
| 91 | else |
| 92 | bad "CODEWHALE_CACHE_ROOT overrides XDG and HOME" "$out" |
| 93 | fi |
| 94 | |
| 95 | # 3. Disabled mode leaves Cargo/sccache vars alone. |
| 96 | out=$(run_apply CODEWHALE_DEV_CACHE=0) |
| 97 | if contains "$out" "mode=disabled" \ |
| 98 | && contains "$out" "CARGO_BUILD_BUILD_DIR=<unset>" \ |
| 99 | && contains "$out" "RUSTC_WRAPPER=<unset>"; then |
| 100 | ok "CODEWHALE_DEV_CACHE=0 does not set cargo or sccache vars" |
| 101 | else |
| 102 | bad "CODEWHALE_DEV_CACHE=0 does not set cargo or sccache vars" "$out" |
| 103 | fi |
| 104 | |
| 105 | # 4. New worktree (no ./target) gets isolated build-dir with the cargo template. |
| 106 | out=$(run_apply \ |
| 107 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ |
| 108 | CODEWHALE_CACHE_ROOT="$work/cache") |
| 109 | if contains "$out" "mode=isolated-build-dir" \ |
| 110 | && contains "$out" "CARGO_BUILD_BUILD_DIR=$work/cache/build/{workspace-path-hash}"; then |
| 111 | ok "new worktree uses isolated build-dir template" |
| 112 | else |
| 113 | bad "new worktree uses isolated build-dir template" "$out" |
| 114 | fi |
| 115 | if contains "$out" "CARGO_INCREMENTAL=<unset>"; then |
| 116 | ok "isolated default does not turn incremental off" |
| 117 | else |
| 118 | bad "isolated default does not turn incremental off" "$out" |
| 119 | fi |
| 120 | if contains "$out" "sccache=skipped-incremental"; then |
| 121 | ok "sccache stays off while incremental is on" |
| 122 | else |
| 123 | bad "sccache stays off while incremental is on" "$out" |
| 124 | fi |
| 125 | |
| 126 | # 5. A stub or leftover ./target does not abandon isolation (Cargo still |
| 127 | # writes CACHEDIR.TAG under ./target when build-dir is split). |
| 128 | out=$(run_apply \ |
| 129 | CODEWHALE_DEV_CACHE_REPO_ROOT="$OLD_WT" \ |
| 130 | CODEWHALE_CACHE_ROOT="$work/cache") |
| 131 | if contains "$out" "mode=isolated-build-dir" \ |
| 132 | && contains "$out" "CARGO_BUILD_BUILD_DIR=$work/cache/build/{workspace-path-hash}"; then |
| 133 | ok "existing ./target does not abandon isolated build-dir" |
| 134 | else |
| 135 | bad "existing ./target does not abandon isolated build-dir" "$out" |
| 136 | fi |
| 137 | |
| 138 | # 6. CODEWHALE_DEV_CACHE=local keeps ./target. |
| 139 | out=$(run_apply \ |
| 140 | CODEWHALE_DEV_CACHE=local \ |
| 141 | CODEWHALE_DEV_CACHE_REPO_ROOT="$OLD_WT" \ |
| 142 | CODEWHALE_CACHE_ROOT="$work/cache") |
| 143 | if contains "$out" "mode=existing-target" \ |
| 144 | && contains "$out" "CARGO_BUILD_BUILD_DIR=<unset>"; then |
| 145 | ok "CODEWHALE_DEV_CACHE=local keeps ./target" |
| 146 | else |
| 147 | bad "CODEWHALE_DEV_CACHE=local keeps ./target" "$out" |
| 148 | fi |
| 149 | |
| 150 | # 7. Already-set CARGO_TARGET_DIR / CARGO_BUILD_BUILD_DIR are respected. |
| 151 | out=$(run_apply \ |
| 152 | CODEWHALE_DEV_CACHE=1 \ |
| 153 | CARGO_TARGET_DIR="$work/user-target" \ |
| 154 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT") |
| 155 | if contains "$out" "mode=inherited-target-dir" \ |
| 156 | && contains "$out" "CARGO_TARGET_DIR=$work/user-target" \ |
| 157 | && contains "$out" "CARGO_BUILD_BUILD_DIR=<unset>"; then |
| 158 | ok "existing CARGO_TARGET_DIR is not overwritten" |
| 159 | else |
| 160 | bad "existing CARGO_TARGET_DIR is not overwritten" "$out" |
| 161 | fi |
| 162 | out=$(run_apply \ |
| 163 | CARGO_BUILD_BUILD_DIR="$work/user-build" \ |
| 164 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT") |
| 165 | if contains "$out" "mode=inherited-build-dir" \ |
| 166 | && contains "$out" "CARGO_BUILD_BUILD_DIR=$work/user-build"; then |
| 167 | ok "existing CARGO_BUILD_BUILD_DIR is not overwritten" |
| 168 | else |
| 169 | bad "existing CARGO_BUILD_BUILD_DIR is not overwritten" "$out" |
| 170 | fi |
| 171 | |
| 172 | # 8. Missing sccache is a fallback, not a failure, even when requested. |
| 173 | out=$(run_apply \ |
| 174 | CODEWHALE_SCCACHE=1 \ |
| 175 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ |
| 176 | CODEWHALE_CACHE_ROOT="$work/cache") |
| 177 | if contains "$out" "sccache=not-found" \ |
| 178 | && contains "$out" "RUSTC_WRAPPER=<unset>" \ |
| 179 | && contains "$out" "CARGO_INCREMENTAL=0"; then |
| 180 | ok "missing sccache falls back without setting RUSTC_WRAPPER" |
| 181 | else |
| 182 | bad "missing sccache falls back without setting RUSTC_WRAPPER" "$out" |
| 183 | fi |
| 184 | |
| 185 | # 9. sccache wraps only when incremental is off and the binary exists. |
| 186 | printf '%s\n' '#!/bin/sh' 'exit 0' >"$FAKEBIN/sccache" |
| 187 | chmod +x "$FAKEBIN/sccache" |
| 188 | |
| 189 | out=$(run_apply \ |
| 190 | CARGO_INCREMENTAL=0 \ |
| 191 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ |
| 192 | CODEWHALE_CACHE_ROOT="$work/cache" \ |
| 193 | CODEWHALE_RUSTC_COMMIT=abc123deadbeef) |
| 194 | if contains "$out" "sccache=enabled" \ |
| 195 | && contains "$out" "RUSTC_WRAPPER=$FAKEBIN/sccache" \ |
| 196 | && contains "$out" "SCCACHE_DIR=$work/cache/sccache/abc123deadbeef"; then |
| 197 | ok "sccache wraps rustc when incremental is off" |
| 198 | else |
| 199 | bad "sccache wraps rustc when incremental is off" "$out" |
| 200 | fi |
| 201 | |
| 202 | out=$(run_apply \ |
| 203 | CARGO_INCREMENTAL=1 \ |
| 204 | CODEWHALE_SCCACHE=1 \ |
| 205 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ |
| 206 | CODEWHALE_CACHE_ROOT="$work/cache") |
| 207 | if contains "$out" "sccache=skipped-incremental" \ |
| 208 | && contains "$out" "RUSTC_WRAPPER=<unset>" \ |
| 209 | && contains "$out" "CARGO_INCREMENTAL=1"; then |
| 210 | ok "explicit incremental=1 wins over CODEWHALE_SCCACHE=1" |
| 211 | else |
| 212 | bad "explicit incremental=1 wins over CODEWHALE_SCCACHE=1" "$out" |
| 213 | fi |
| 214 | |
| 215 | out=$(run_apply \ |
| 216 | CODEWHALE_SCCACHE=1 \ |
| 217 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ |
| 218 | CODEWHALE_CACHE_ROOT="$work/cache" \ |
| 219 | CODEWHALE_RUSTC_COMMIT=abc123deadbeef) |
| 220 | if contains "$out" "sccache=enabled" \ |
| 221 | && contains "$out" "CARGO_INCREMENTAL=0"; then |
| 222 | ok "CODEWHALE_SCCACHE=1 requests incremental=0 and wraps" |
| 223 | else |
| 224 | bad "CODEWHALE_SCCACHE=1 requests incremental=0 and wraps" "$out" |
| 225 | fi |
| 226 | |
| 227 | out=$(run_apply \ |
| 228 | RUSTC_WRAPPER=/usr/bin/true \ |
| 229 | CARGO_INCREMENTAL=0 \ |
| 230 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT") |
| 231 | if contains "$out" "sccache=external-wrapper" \ |
| 232 | && contains "$out" "RUSTC_WRAPPER=/usr/bin/true"; then |
| 233 | ok "existing RUSTC_WRAPPER is left alone" |
| 234 | else |
| 235 | bad "existing RUSTC_WRAPPER is left alone" "$out" |
| 236 | fi |
| 237 | |
| 238 | # 10. Cargo older than 1.91 falls back to a per-workspace CARGO_TARGET_DIR. |
| 239 | printf '%s\n' '#!/bin/sh' 'echo "cargo 1.88.0 (test)"' >"$FAKEBIN/cargo" |
| 240 | chmod +x "$FAKEBIN/cargo" |
| 241 | out=$(run_apply \ |
| 242 | CODEWHALE_DEV_CACHE=1 \ |
| 243 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ |
| 244 | CODEWHALE_CACHE_ROOT="$work/cache") |
| 245 | if contains "$out" "legacy-target-dir" \ |
| 246 | && contains "$out" "CARGO_BUILD_BUILD_DIR=<unset>" \ |
| 247 | && contains "$out" "CARGO_TARGET_DIR=$work/cache/target/"; then |
| 248 | ok "cargo 1.88 falls back to an isolated CARGO_TARGET_DIR" |
| 249 | else |
| 250 | bad "cargo 1.88 falls back to an isolated CARGO_TARGET_DIR" "$out" |
| 251 | fi |
| 252 | rm -f "$FAKEBIN/cargo" |
| 253 | |
| 254 | # 11. CLI self-check passes for a new worktree with portable HOME. |
| 255 | cli_out=$( |
| 256 | env -i \ |
| 257 | PATH="$BASE_PATH" \ |
| 258 | HOME="$HOME_DIR" \ |
| 259 | PWD="$NEW_WT" \ |
| 260 | CODEWHALE_DEV_CACHE_QUIET=1 \ |
| 261 | CODEWHALE_DEV_CACHE_REPO_ROOT="$NEW_WT" \ |
| 262 | CODEWHALE_CACHE_ROOT="$work/cache" \ |
| 263 | /bin/sh "$DEV_CACHE" --self-check |
| 264 | ) || cli_rc=$? |
| 265 | cli_rc=${cli_rc:-0} |
| 266 | if [ "$cli_rc" -eq 0 ] && contains "$cli_out" "dev-cache: self-check ok"; then |
| 267 | ok "CLI --self-check passes on a new worktree" |
| 268 | else |
| 269 | bad "CLI --self-check passes on a new worktree" "$cli_out" |
| 270 | fi |
| 271 | |
| 272 | # 12. Two worktree paths produce different legacy fingerprints. |
| 273 | # shellcheck source=scripts/dev-cache.sh |
| 274 | . "$DEV_CACHE" |
| 275 | fp_a=$(codewhale_dev_cache_path_fingerprint "$NEW_WT") |
| 276 | fp_b=$(codewhale_dev_cache_path_fingerprint "$OLD_WT") |
| 277 | if [ -n "$fp_a" ] && [ -n "$fp_b" ] && [ "$fp_a" != "$fp_b" ]; then |
| 278 | ok "path fingerprints differ across worktrees" |
| 279 | else |
| 280 | bad "path fingerprints differ across worktrees" "a=$fp_a b=$fp_b" |
| 281 | fi |
| 282 | |
| 283 | # 13. dev-cargo.sh --status is the same helper. |
| 284 | if /bin/sh "$DEV_CARGO" --help | grep -q 'dev-cache'; then |
| 285 | ok "dev-cargo.sh --help delegates to the cache helper" |
| 286 | else |
| 287 | bad "dev-cargo.sh --help delegates to the cache helper" |
| 288 | fi |
| 289 | |
| 290 | if [ "$fail" -eq 0 ]; then |
| 291 | printf 'dev-cache.test.sh: all %s checks passed\n' "$pass" |
| 292 | exit 0 |
| 293 | fi |
| 294 | printf 'dev-cache.test.sh: %s/%s checks failed\n' "$fail" "$((pass + fail))" >&2 |
| 295 | exit 1 |
| 296 |