Skip to content

make test fails with "unbound variable" error on bash 3.2 #21124

@Ixecd

Description

@Ixecd

Bug report criteria

What happened?

When running make test or make test-unit without setting the TESTCASE environment variable, the test script fails with:

PASSES="unit" ./scripts/test.sh 
Running with --race
Starting at: Wed Jan 14 09:47:58 CST 2026

'unit' started at Wed Jan 14 09:47:58 CST 2026
./scripts/test.sh: line 130: RUN_ARG[@]: unbound variable
make: *** [test-unit] Error 1

This prevents developers from running tests locally on macOS (which uses bash 3.2 by default).

What did you expect to happen?

The test script should run successfully without requiring the TESTCASE environment variable to be set. According to the script's documentation, TESTCASE is optional and should only be used when running specific test cases.

How can we reproduce it (as minimally and precisely as possible)?

  1. Clone the etcd repository
  2. Ensure you have bash 3.2 (macOS default) or any bash version that strictly enforces set -o nounset
  3. Run make test-unit or make test without setting TESTCASE
  4. The script will fail with the "unbound variable" error

Minimal reproduction:

# On macOS with default bash 3.2
cd /path/to/etcd
make test-unit

Expected: Tests run successfully
Actual: Script fails with "unbound variable" error

Anything else we need to know?

Environment Information

  • OS: macOS (darwin 26.2.0)
  • Bash version: GNU bash, version 3.2.57(1)-release (arm64-apple-darwin25)
  • etcd version: Current main branch
  • Go version: (not relevant for this issue)

Root Cause Analysis

The issue occurs because:

  1. The script uses set -o nounset (line 55) for strict error checking
  2. When TESTCASE is not set, RUN_ARG is initialized as an empty array: RUN_ARG=()
  3. The script then tries to expand the empty array: "${RUN_ARG[@]}"
  4. In bash 3.2 (and some other versions), expanding an empty array under set -o nounset triggers an "unbound variable" error

The problematic code is in scripts/test.sh:

RUN_ARG=()
if [ -n "${TESTCASE:-}" ]; then
  RUN_ARG=("-run=${TESTCASE}")
fi

# Later in functions like unit_pass, integration_pass, etc.:
"${RUN_ARG[@]}"  # This fails when RUN_ARG is empty

Impact

  • Affected users: Developers on macOS or systems with bash 3.2
  • Severity: Medium - blocks local development and testing
  • Workaround: Set TESTCASE="" (empty string) or upgrade bash, but this shouldn't be necessary

Proposed Solutions

Solution 1: Use string instead of array (Recommended)

Change RUN_ARG from an array to a string. This is the simplest and most elegant solution:

RUN_ARG=""
if [ -n "${TESTCASE:-}" ]; then
  RUN_ARG="-run=${TESTCASE}"
fi

# Later in functions:
$RUN_ARG  # Empty string expands to nothing, no error

This approach:

  • ✅ Works with bash 3.2+ (backward compatible)
  • ✅ Maintains set -o nounset strict checking
  • ✅ Doesn't change existing functionality
  • ✅ Makes TESTCASE truly optional as documented
  • ✅ No external dependencies or version requirements
  • ✅ Minimal code changes (just change array to string)
  • ✅ No conditional checks needed in each function

Cons:

  • Requires changing array syntax to string syntax (but simpler overall)

Solution 2: Upgrade bash version (Alternative)

Require developers to use bash 4.4+ where empty array expansion works correctly under set -o nounset.

brew install bash
echo 'export PATH="/opt/homebrew/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

Pros:

  • ✅ No code changes needed
  • ✅ Uses more modern bash features

Cons:

  • ❌ Breaks compatibility with macOS default bash (3.2)
  • ❌ Requires all contributors to upgrade bash
  • ❌ May exclude developers who can't easily upgrade
  • ❌ Goes against the project's compatibility goals (using #!/usr/bin/env bash suggests compatibility is important)
  • ❌ Not a practical solution for a widely-used open source project

Additional Context

  • This is a development tool issue, not an etcd server bug
  • The script's documentation indicates TESTCASE should be optional
  • CI/CD environments may use newer bash versions where this doesn't fail, but local development environments (especially macOS) are affected
  • Similar compatibility issues have been addressed in other parts of the codebase

Related

This issue affects the following test functions in scripts/test.sh:

  • unit_pass
  • integration_extra
  • integration_pass
  • e2e_pass
  • robustness_pass
  • grpcproxy_integration_pass
  • grpcproxy_e2e_pass

Request for good first issue label:

This issue seems suitable for a good first issue label because:

  • No Barrier to Entry - Only requires basic bash knowledge
  • Solution Explained - Solution clearly described above
  • Identifies Relevant Code - Code location clearly identified
  • Ready to Test - Can be tested with make test-unit
  • Low Risk - Simple syntax change

Etcd version (please run commands below)

not relevant for this issue

Etcd configuration (command line flags or environment variables)

not relevant for this issue

Etcd debug information (please run commands below, feel free to obfuscate the IP address or FQDN in the output)

not relevant for this issue

Relevant log output

PASSES="unit" ./scripts/test.sh 
Running with --race
Starting at: Wed Jan 14 09:47:58 CST 2026

'unit' started at Wed Jan 14 09:47:58 CST 2026
./scripts/test.sh: line 130: RUN_ARG[@]: unbound variable
make: *** [test-unit] Error 1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions