Skip to main content

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.sh files over one large script with many unrelated assertions.
  • Use set -euo pipefail so failed commands, unset variables, and broken pipelines fail the test.
  • Create temporary files with mktemp -d and clean them with a trap.
  • 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

NameDefaultDescription
stagetestThe stage for the test job.
needs[]Jobs that the test job depends on.
allow-failurefalseWhether the pipeline may continue after failed tests.
path.Directory to search for *.ci-test.sh files.
junit-reportbash-unit-tests-junit.xmlPath to the JUnit report for the GitLab Test summary widget.
rulesexists: **/*.ci-test.shRules for the test job.

Inputs

NameDescriptionDefaultType
------------
allow-failureShould the pipeline continue if one or more tests fail?falseboolean
junit-reportPath to the JUnit report for the GitLab Test summary widget.bash-unit-tests-junit.xmlstring
needsThe jobs that this job depends on.[]array
pathThe directory to search for *.ci-test.sh files..string
rulesThe rules for the test job.[{"exists":["**/*.ci-test.sh"],"when":"on_success"},{"when":"never"}]array
stageThe stage for the test job.teststring