diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ae8ea932e..7e9bb01cad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,17 +20,11 @@ jobs: - name: Install greadlink if: startsWith(runner.os, 'macOS') run: brew install coreutils - - name: Install parallel - if: startsWith(runner.os, 'macOS') - run: | - brew install parallel - which parallel # Verify installation - name: Test code run: test/run env: - # Ubuntu runners ship GNU parallel, causing --tap + --jobs count mismatches. - # Force single-threaded on Linux; let macOS use parallel (installed above). - TEST_JOBS: ${{ runner.os == 'Linux' && '1' || 'detect' }} + # --tap + --jobs causes TAP plan count mismatches on both Linux and macOS. + TEST_JOBS: 1 build-docs: runs-on: ubuntu-latest diff --git a/completion/available/herdr.completion.bash b/completion/available/herdr.completion.bash new file mode 100644 index 0000000000..09d467d0a0 --- /dev/null +++ b/completion/available/herdr.completion.bash @@ -0,0 +1,13 @@ +# shellcheck shell=bash +about-completion "Herdr Terminal Multiplexer tab completion" + +if ! _binary_exists herdr; then + _log_error "herdr not found in PATH — completion not loaded. (${BASH_SOURCE[0]})" + return 1 +else + if ! _bash-it-completion-helper-sufficient herdr; then + _log_warning "The completion is set for 'herdr' externally. Activation will be skipped. (${BASH_SOURCE[0]})" + else + eval "$(herdr completion bash)" + fi +fi diff --git a/completion/available/op.completion.bash b/completion/available/op.completion.bash new file mode 100644 index 0000000000..6e500024c0 --- /dev/null +++ b/completion/available/op.completion.bash @@ -0,0 +1,13 @@ +# shellcheck shell=bash +about-completion "1Password CLI tab completion" + +if ! _binary_exists op; then + _log_error "op not found in PATH — completion not loaded. (${BASH_SOURCE[0]})" + return 1 +else + if ! _bash-it-completion-helper-sufficient op; then + _log_warning "The completion is set for 'op' externally. Activation will be skipped. (${BASH_SOURCE[0]})" + else + eval "$(op completion bash)" + fi +fi diff --git a/docs/test.rst b/docs/test.rst index 2923eb7af7..f5ba0cd411 100644 --- a/docs/test.rst +++ b/docs/test.rst @@ -26,17 +26,46 @@ To execute the unit tests, please run the ``run`` script: The ``run`` script will automatically install if it is not already present, and will then run all tests found under the ``test`` directory, including subdirectories. -To run only a subset of the tests, you can provide the name of the test subdirectory that you want to run, e.g. like this for the tests in the ``test/themes`` directory: +To run only a subset of the tests, you can provide a directory or a specific test file: .. code-block:: bash - # If you are in the root `.bash_it` directory: + # Run all tests in a directory: test/run test/themes -By default, the tests run in single-threaded mode. -If you want to speed up the test execution, you can install the `GNU ``parallel`` tool `_\ , which is supported by Bats. -When using ``parallel``\ , the ``test/run`` script will use a number of threads in parallel, depending on the available CPU cores of your system. -This can speed up test execution significantly. + # Run a single test file: + test/run test/completion/op.completion.bats + + # Run multiple specific files: + test/run test/completion/op.completion.bats test/completion/herdr.completion.bats + +The tests always run in single-threaded mode (``TEST_JOBS=1``). Parallel execution +via GNU ``parallel`` was previously supported but caused TAP plan count mismatches +when combined with the ``--tap`` flag, producing false failures. Single-threaded +mode is reliable and is what CI enforces. + +Local Runs and Isolation +~~~~~~~~~~~~~~~~~~~~~~~~ + +Running ``test/run`` directly on your machine works, but it inherits your shell's +``PATH``. Any tool installed locally (e.g. ``op``, ``herdr``, ``docker``) will be +visible to the tests, which can cause tests to behave differently than they do on CI, +where the runner is a clean environment with only a known set of packages installed. + +To reproduce CI conditions exactly, use ``test/run-local``, which builds a minimal +Docker image and runs the tests inside it: + +.. code-block:: bash + + # Run all tests in a clean container: + test/run-local + + # Run a subset: + test/run-local test/completion/op.completion.bats + +The image is built on the first run and cached afterwards, so subsequent runs are +fast. The trade-off compared to running ``test/run`` directly is that the first run +takes longer and Docker must be installed. Writing Tests ------------- diff --git a/test/completion/herdr.completion.bats b/test/completion/herdr.completion.bats new file mode 100644 index 0000000000..9a32a1daf6 --- /dev/null +++ b/test/completion/herdr.completion.bats @@ -0,0 +1,47 @@ +# shellcheck shell=bats + +load "${MAIN_BASH_IT_DIR?}/test/test_helper.bash" + +function local_setup_file() { + setup_libs "helpers" + load "${BASH_IT?}/lib/completion.bash" +} + +function local_setup() { + MOCK_BIN="${BATS_TEST_TMPDIR}/mock-bin" + mkdir -p "${MOCK_BIN}" + _SAVED_PATH="${PATH}" +} + +function local_teardown() { + PATH="${_SAVED_PATH}" + complete -r herdr 2> /dev/null || true +} + +function _mock_herdr() { + printf '%s\n' \ + '#!/usr/bin/env bash' \ + '[[ "$*" == "completion bash" ]] && echo "complete -o nospace herdr"' \ + > "${MOCK_BIN}/herdr" + chmod +x "${MOCK_BIN}/herdr" + PATH="${MOCK_BIN}:${PATH}" +} + +@test "completion herdr: fails to load when herdr is not in PATH" { + PATH="${MOCK_BIN}" run source "${BASH_IT?}/completion/available/herdr.completion.bash" + assert_failure +} + +@test "completion herdr: loads successfully when herdr is available" { + _mock_herdr + run source "${BASH_IT?}/completion/available/herdr.completion.bash" + assert_success +} + +@test "completion herdr: skips eval when completion is already managed externally" { + _mock_herdr + complete -F : herdr + source "${BASH_IT?}/completion/available/herdr.completion.bash" + run complete -p herdr + assert_output "complete -F : herdr" +} diff --git a/test/completion/op.completion.bats b/test/completion/op.completion.bats new file mode 100644 index 0000000000..c30ab0b2ca --- /dev/null +++ b/test/completion/op.completion.bats @@ -0,0 +1,47 @@ +# shellcheck shell=bats + +load "${MAIN_BASH_IT_DIR?}/test/test_helper.bash" + +function local_setup_file() { + setup_libs "helpers" + load "${BASH_IT?}/lib/completion.bash" +} + +function local_setup() { + MOCK_BIN="${BATS_TEST_TMPDIR}/mock-bin" + mkdir -p "${MOCK_BIN}" + _SAVED_PATH="${PATH}" +} + +function local_teardown() { + PATH="${_SAVED_PATH}" + complete -r op 2> /dev/null || true +} + +function _mock_op() { + printf '%s\n' \ + '#!/usr/bin/env bash' \ + '[[ "$*" == "completion bash" ]] && echo "complete -o nospace op"' \ + > "${MOCK_BIN}/op" + chmod +x "${MOCK_BIN}/op" + PATH="${MOCK_BIN}:${PATH}" +} + +@test "completion op: fails to load when op is not in PATH" { + PATH="${MOCK_BIN}" run source "${BASH_IT?}/completion/available/op.completion.bash" + assert_failure +} + +@test "completion op: loads successfully when op is available" { + _mock_op + run source "${BASH_IT?}/completion/available/op.completion.bash" + assert_success +} + +@test "completion op: skips eval when completion is already managed externally" { + _mock_op + complete -F : op + source "${BASH_IT?}/completion/available/op.completion.bash" + run complete -p op + assert_output "complete -F : op" +} diff --git a/test/run-local b/test/run-local new file mode 100755 index 0000000000..450d993d41 --- /dev/null +++ b/test/run-local @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +IMAGE="bash-it-local-test-runner" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# Build the image — idempotent, Docker cache makes subsequent runs instant +docker build --tag "${IMAGE}" - <<'DOCKERFILE' +FROM ubuntu:24.04 +RUN apt-get update -q \ + && apt-get install -qy git locales curl iproute2 \ + && locale-gen en_US.UTF-8 \ + && rm -rf /var/lib/apt/lists/* +ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 +DOCKERFILE + +# Mirror CI exactly: TEST_JOBS=1 avoids --tap + --jobs TAP count mismatches. +# Pass any arguments through to test/run (e.g. test/completion/op.completion.bats) +exec docker run --rm \ + --volume "${REPO_ROOT}:/workspace" \ + --workdir /workspace \ + --env TEST_JOBS=1 \ + "${IMAGE}" \ + bash test/run "$@"