Implement Unit Testing Script
This skill produces a single executable script that runs the unit tests for a generated build folder, following a consistent, language-agnostic pattern.
The reference implementation is assets/run_unittests_java.sh. Read it first — every script you produce must be a faithful translation of that pattern into the target language's tooling and the user's shell environment. There are also Windows PowerShell equivalents of these scripts in assets/run_unittests_*.ps1.
Pick the Shell First
Before writing anything, decide which shell flavor the script must target — it depends on the user's environment, not on the language:
- Bash () — macOS, Linux, WSL, CI runners on Linux. Default unless the user is on native Windows.
- PowerShell () — native Windows / PowerShell-only environments.
If you can't tell from the project (no obvious OS hints, no existing scripts), ask the user.
The same seven-step pattern applies to both. Only the syntax changes.
The Pattern
Every testing script must implement these steps in this order:
- Toolchain check. Verify that the required language runtime / build tool (and the required version, if any) is installed. If not, print an error and exit with code .
- Argument validation. Require exactly one positional argument: the source build folder name. If missing, print usage and exit with code .
- Working directory setup. Define a working folder at . If it exists, wipe its contents; otherwise create it. This folder — and only this folder — is where every subsequent write must land.
- Copy the build. Recursively copy everything from the source folder into the working folder. After this step the source folder () is treated as read-only for the rest of the script.
- Enter the working directory. / into . If that fails, exit with code . All remaining steps run from inside the working folder; they must never write back to the source build folder.
- Install dependencies into an isolated environment inside . Set up a per-working-folder dependency location (a Python venv at , a local , a project-scoped Maven repo at , etc.) and install/resolve all dependencies into it. Never install into the source build folder, the user's global cache (, system-wide , , , ...), or anywhere outside . If the install command fails, propagate its exit code immediately and do not proceed to step 7. See Dependency isolation for per-language specifics.
- Run the tests. Invoke the language's standard test command (e.g. , , , , ), pointed at the same isolated environment from step 6. The script's final exit code is whatever the test command returns.
The build folder is read-only — hard rule
The source build folder passed in as
is
input only. The script must never:
- install dependencies into it (no inside , no inside , no writing into , no Cargo build artifacts ending up under ),
- write a virtualenv / / / / directory inside it,
- run the test command from inside it (every test command runs from inside after the in step 5),
- create logs, caches, build outputs, or temp files inside it.
The build folder is shared with the renderer (
by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. Every write must go into
— the whole point of staging via
is so the source build folder stays a clean, reproducible artifact of the render.
If you find yourself about to issue any command whose
is the source folder, or whose target path starts with
,
stop. Either move the operation into
, or you're doing something the script must not do.
Conventions
Shared across both shell flavors:
- Exit codes:
- — bad usage (missing argument).
- — filesystem problem (couldn't enter the working folder).
- — required toolchain / runtime is not installed.
- Any other non-zero code — propagated from the underlying test command.
- Working folder naming: where is a short identifier for the language (, , , , , ...). All dependency installs, build outputs, caches, and the test run itself live inside this folder. Nothing the script does should touch the source build folder after step 4.
- Logging: print short progress lines (,
"Installing dependencies into ..."
, "Running <lang> unittests in ..."
) so failures are easy to triage.
Dependency isolation
The dependency environment must live
inside so the test run can't be polluted by — or pollute — the user's global caches. Pick the most idiomatic isolation mechanism for the language:
| Language | Isolation mechanism | Install command (run inside ) | Test command |
|---|
| Python | at | python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt
(or / / ) | (or ./.venv/bin/python -m pytest
) |
| Node.js | local (default) | (preferred) or | |
| Java | project-scoped Maven repo at | mvn -Dmaven.repo.local=./.m2 dependency:resolve
(optional pre-warm) | mvn -Dmaven.repo.local=./.m2 test
|
| Go | module cache at | GOMODCACHE="$PWD/.gocache" go mod download
(optional pre-warm) | GOMODCACHE="$PWD/.gocache" go test ./...
|
| Rust | cargo home at | CARGO_HOME="$PWD/.cargo" cargo fetch
(optional pre-warm) | CARGO_HOME="$PWD/.cargo" cargo test
|
Notes:
- Every path in the install command and test command is relative to . That's why the script s into the working folder in step 5 — from that point on, , , , etc. all resolve under , never under the source build folder.
- Always pass the isolation flag/env var to both the install command and the test command — they must agree on where deps live, otherwise the test command will silently fall back to the global cache or (worse) the source build folder.
- Python is the only ecosystem where the venv is mandatory to satisfy "into a virtual environment" literally. The others use language-native equivalents that achieve the same isolation.
- Pre-warming is optional for Java/Go/Rust — their test commands will fetch deps on demand. Doing it as a separate step makes failures easier to diagnose and gives a clean "install failed vs test failed" signal.
- Don't activate the venv in Bash via
source .venv/bin/activate
— call directly. It's more portable and avoids subshell weirdness. In PowerShell, use & .\.venv\Scripts\<tool>.exe
similarly.
- Propagate the install exit code immediately. In Bash: . In PowerShell: check and if non-zero.
Bash specifics
- Shebang: .
- File naming: , placed in .
- Argument: .
- Make it executable:
chmod +x assets/run_unittests_<lang>.sh
.
PowerShell specifics
- No shebang. Use a
param([Parameter(Mandatory=$true)][string]$Subfolder)
block at the top instead.
- File naming: , placed in .
- Exit codes: use etc. (PowerShell honors them just like Bash).
- Toolchain check: prefer
Get-Command <tool> -ErrorAction SilentlyContinue
and, where a specific version is needed, parse the tool's output.
- Filesystem: use ,
Remove-Item -Recurse -Force
, New-Item -ItemType Directory
, , . Quote paths to handle spaces.
- No step needed. If execution policy is likely to block the script, mention
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
to the user — don't bake it into the script.
Workflow
- Confirm the target language, shell flavor (Bash or PowerShell), and dependency manifest (, / , , , , ...). Ask if any is unclear.
- Read assets/run_unittests_java.sh to refresh the exact structure.
- Translate each of the seven steps above into the equivalent commands for the target language and shell. The toolchain check, dependency install, and test invocation are the language-specific parts; the rest is mechanical translation between Bash and PowerShell syntax.
- Pick the dependency-isolation mechanism from the Dependency isolation table and use it consistently in both step 6 and step 7.
- Save the new script to
assets/run_unittests_<lang>.sh
or assets/run_unittests_<lang>.ps1
. For Bash, it.
Anti-Patterns
- (Hard mistake) Don't install into, build into, or otherwise write to the source build folder. The build folder passed as is read-only input. Every install, cache, build artifact, log, and temp file must land in . This includes never running , , , or with the source folder as their or target, never letting a venv / / / / directory appear inside the source folder, and never running the test command from inside it. The whole point of staging the build into is so the source folder remains a clean, reproducible artifact of the render — writing to it corrupts the renderer's view and breaks subsequent renders.
- Don't skip the toolchain check, even when "everyone has it installed" — exit code is what the calling system relies on to detect a missing runtime.
- Don't reuse the source folder in place. Always copy into first; the renderer relies on this isolation.
- Don't change the exit-code contract. Other parts of the system branch on , , and specifically — and these codes must be identical between the Bash and PowerShell variants.
- Don't write a cross-shell hybrid (e.g. a that detects PowerShell, or vice versa). Ship one script per shell, named with the appropriate extension.
- Don't install dependencies into the user's global location (, system-wide , , etc.). Always isolate inside so concurrent runs and other projects can't interfere.
- Don't run the test command without first verifying the install step succeeded. A failed install followed by a "test" run produces misleading errors that look like test failures.