bash-unit-tests
Run every *.ci-test.sh file under a directory and fail the job when one or more scripts fail.
Each script runs with its current working directory set to the directory that contains the script. The job prints colored run, success, failed, and summary lines. The job also publishes a JUnit report for the GitLab Test summary widget.
Usage
include:
- component: $CI_SERVER_FQDN/xrow-public/ci-tools/bash-unit-tests@$CI_COMMIT_SHA
inputs:
path: tests
Place one or more executable or non-executable Bash test files below the configured path. The file name must end with .ci-test.sh.
tests/
container/
builds-image.ci-test.sh
helm/
renders-default-values.ci-test.sh
Each test is executed with Bash from the repository root while the working directory is changed to the directory that contains the test file. For example, tests/helm/renders-default-values.ci-test.sh runs from tests/helm, so relative fixture paths can stay close to the test.
Example Tests
A small test should set strict Bash options, arrange its own temporary data, run one behavior, and assert the output that matters.
#!/usr/bin/env bash
set -euo pipefail
tmpdir="$(mktemp -d)"
trap 'rm -rf "${tmpdir}"' EXIT
cat > "${tmpdir}/values.yaml" <<'YAML'
replicaCount: 2
YAML
helm template example ../../chart --values "${tmpdir}/values.yaml" > "${tmpdir}/rendered.yaml"
grep -q 'replicas: 2' "${tmpdir}/rendered.yaml"
For command-line tools, keep the assertion close to the command and print useful context before failing.
#!/usr/bin/env bash
set -euo pipefail
output="$(../../scripts/my-tool --format json)"
if ! jq -e '.status == "ok"' <<< "${output}" >/dev/null; then
printf 'Unexpected my-tool output:\n%s\n' "${output}" >&2
exit 1
fi
To verify an expected failure, temporarily disable errexit around the command and assert the exit code.
#!/usr/bin/env bash
set -euo pipefail
set +e
output="$(../../scripts/my-tool --invalid 2>&1)"
status=$?
set -e
test "${status}" -ne 0
grep -q 'unknown option' <<< "${output}"
Good Test Guidelines
- Test one behavior per file. Prefer several focused
*.ci-test.shfiles over one large script with many unrelated assertions. - Use
set -euo pipefailso failed commands, unset variables, and broken pipelines fail the test. - Create temporary files with
mktemp -dand clean them with atrap. - Keep fixtures close to the test directory and use relative paths from the test's working directory.
- Assert observable behavior, such as rendered YAML, JSON fields, generated files, or exit codes.
- Print the captured output before exiting when a failure would otherwise be hard to diagnose from the job log.
- Avoid tests that depend on local machine state, timing, external services, or secrets unless those dependencies are explicit CI inputs.
- Do not hide failures with
|| true. If a command is expected to fail, capture and assert its status. - Make tests deterministic. Sort generated lists before comparing them and avoid relying on random names unless they are isolated in a temporary directory.
Local Run
Run the same test files locally before pushing:
find tests -type f -name '*.ci-test.sh' -print0 |
sort -z |
while IFS= read -r -d '' test_file; do
(cd "$(dirname "${test_file}")" && bash "./$(basename "${test_file}")")
done
Inputs
| Name | Default | Description |
|---|---|---|
stage | test | The stage for the test job. |
needs | [] | Jobs that the test job depends on. |
allow-failure | false | Whether the pipeline may continue after failed tests. |
path | . | Directory to search for *.ci-test.sh files. |
junit-report | bash-unit-tests-junit.xml | Path to the JUnit report for the GitLab Test summary widget. |
rules | exists: **/*.ci-test.sh | Rules for the test job. |
Inputs
| Name | Description | Default | Type |
| --- | --- | --- | --- |
| allow-failure | Should the pipeline continue if one or more tests fail? | false | boolean |
| junit-report | Path to the JUnit report for the GitLab Test summary widget. | bash-unit-tests-junit.xml | string |
| needs | The jobs that this job depends on. | [] | array |
| path | The directory to search for *.ci-test.sh files. | . | string |
| rules | The rules for the test job. | [{"exists":["**/*.ci-test.sh"],"when":"on_success"},{"when":"never"}] | array |
| stage | The stage for the test job. | test | string |