upgrade bats-core to v1.5.0, for #755

pull/700/head
joshr 2 years ago
parent 626cfac075
commit bc03f224a2

Binary file not shown.

12
DIFF

@ -0,0 +1,12 @@
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c83ea5..f8b676c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
### Bugfixes
- Fix adding newlines to `.gitignore` entries (#643)
+- Fix `cat` and `reveal` on named files while in repo subdir (#710)
### Misc

@ -1,65 +0,0 @@
name: Release
on:
release: { types: [published] }
workflow_dispatch:
inputs:
version:
description: 'Version to simulate for deploy'
required: true
jobs:
version-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
EXPECTED_VERSION=${{ github.event.inputs.version }}
EXPECTED_VERSION=${EXPECTED_VERSION:-${GITHUB_REF/refs\/tags\//}}
echo "EXPECTED_VERSION=$EXPECTED_VERSION" >> $GITHUB_ENV
- name: Check tag version matches artifact versions
run: |
echo "Expected version: $EXPECTED_VERSION"
# use double negation to see the result unless we get a match
(./bin/bats --version | grep -F "$EXPECTED_VERSION") || (echo "Bats version check failed: "; ./bin/bats --version; exit -1)
(npm view . version | grep -F "$EXPECTED_VERSION") || (echo "npm version check failed: "; npm view . version; exit -1)
(grep '^Version:' 'contrib/rpm/bats.spec' | grep -F "$EXPECTED_VERSION") || (echo "debian package version check failed: "; grep '^Version:' 'contrib/rpm/bats.spec'; exit -1)
npmjs:
runs-on: ubuntu-latest
needs: version-check
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
registry-url: "https://registry.npmjs.org"
- run: npm publish --ignore-scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
github-npm:
runs-on: ubuntu-latest
needs: version-check
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
registry-url: "https://npm.pkg.github.com"
- name: scope package name as required by GHPR
run: npm init -y --scope ${{ github.repository_owner }}
- run: npm publish --ignore-scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
dockerhub:
runs-on: ubuntu-latest
needs: version-check
steps:
- uses: actions/checkout@v2
- uses: docker/build-push-action@v1
with:
file: ./Dockerfile
platforms: linux/amd64
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
tags: bats/bats:${GITHUB_REF/refs\/tags\//}

@ -1,91 +0,0 @@
name: Tests
# Controls when the action will run.
on: [push, pull_request, workflow_dispatch]
jobs:
shellcheck:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Run shellcheck
run: |
sudo apt-get update -y
sudo apt-get install shellcheck
./shellcheck.sh
linux:
strategy:
matrix:
os: ['ubuntu-20.04', 'ubuntu-18.04', 'ubuntu-16.04']
env_vars:
- ''
# allow for some parallelity without GNU parallel, since it is not installed by default
- 'BATS_NO_PARALLELIZE_ACROSS_FILES=1 BATS_NUMBER_OF_PARALLEL_JOBS=2'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Run test on OS ${{ matrix.os }}
shell: 'script -q -e -c "bash {0}"' # work around tty issues
env:
TERM: linux # fix tput for tty issue work around
run: |
bash --version
bash -c "time ${{ matrix.env_vars }} bin/bats --formatter tap test"
windows:
strategy:
matrix:
os: ['windows-2019']
env_vars:
- ''
# allow for some parallelity without GNU parallel, since it is not installed by default
- 'BATS_NO_PARALLELIZE_ACROSS_FILES=1 BATS_NUMBER_OF_PARALLEL_JOBS=2'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Run test on OS ${{ matrix.os }}
run: |
bash --version
bash -c "time ${{ matrix.env_vars }} bin/bats --formatter tap test"
macos:
strategy:
matrix:
os: ['macos-10.15']
env_vars:
- ''
# allow for some parallelity without GNU parallel, since it is not installed by default
- 'BATS_NO_PARALLELIZE_ACROSS_FILES=1 BATS_NUMBER_OF_PARALLEL_JOBS=2'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Install unbuffer via expect
run: brew install expect
- name: Run test on OS ${{ matrix.os }}
shell: 'unbuffer bash {0}' # work around tty issues
env:
TERM: linux # fix tput for tty issue work around
run: |
bash --version
bash -c "time ${{ matrix.env_vars }} bin/bats --formatter tap test"
bash-version:
strategy:
matrix:
version: ['3.2', '4.0', '4.1', '4.2', '4.3', '4.4', '4', '5.0', '5.1', '5', 'latest']
env_vars:
- ''
# also test running (recursively!) in parallel
- '-e BATS_NUMBER_OF_PARALLEL_JOBS=2'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run test on bash version ${{ matrix.version }}
shell: 'script -q -e -c "bash {0}"' # work around tty issues
run: |
set -e
docker build --build-arg bashver="${{ matrix.version }}" --tag "bats/bats:bash-${{ matrix.version }}" .
docker run -it "bash:${{ matrix.version }}" --version
time docker run -it ${{ matrix.env_vars }} "bats/bats:bash-${{ matrix.version }}" --tap /opt/bats/test

@ -1,6 +1,13 @@
ARG bashver=latest
FROM bash:${bashver}
ARG TINI_VERSION=v0.19.0
ARG TARGETPLATFORM
COPY ./docker /tmp/docker
# default to amd64 when not running in buildx environment that provides target platform
RUN /tmp/docker/install_tini.sh "${TARGETPLATFORM-linux/amd64}"
# Install parallel and accept the citation notice (we aren't using this in a
# context where it make sense to cite GNU Parallel).
@ -10,6 +17,7 @@ RUN apk add --no-cache parallel ncurses && \
RUN ln -s /opt/bats/bin/bats /usr/local/bin/bats
COPY . /opt/bats/
RUN mkdir -p /code
WORKDIR /code/
ENTRYPOINT ["bash", "bats"]
ENTRYPOINT ["/tini", "--", "bash", "bats"]

@ -2,9 +2,9 @@
[![Latest release](https://img.shields.io/github/release/bats-core/bats-core.svg)](https://github.com/bats-core/bats-core/releases/latest)
[![npm package](https://img.shields.io/npm/v/bats.svg)](https://www.npmjs.com/package/bats)
[![License](https://img.shields.io/github/license/bats-core/bats-core.svg)](https://github.com/bats-core/bats-core/blob/master/LICENSE.md)
[[![Continuous integration status](https://github.com/bats-core/bats-core/workflows/Tests/badge.svg)](https://github.com/bats-core/bats-core/actions?query=workflow%3ATests)](https://github.com/bats-core/bats-core/actions?query=workflow%3ATests)
[![Read the docs status](https://readthedocs.org/projects/bats-core/badge/?version=latest&style=plastic)](https://bats-core.readthedocs.io)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/bats-core/bats-core/blob/master/LICENSE.md)
[![Continuous integration status](https://github.com/bats-core/bats-core/workflows/Tests/badge.svg)](https://github.com/bats-core/bats-core/actions?query=workflow%3ATests)
[![Read the docs status](https://readthedocs.org/projects/bats-core/badge/)](https://bats-core.readthedocs.io)
[![Join the chat in bats-core/bats-core on gitter](https://badges.gitter.im/bats-core/bats-core.svg)][gitter]
@ -50,8 +50,8 @@ each line is an assertion of truth.
- [Contact](#contact)
- [Version history](#version-history)
- [Background](#background)
- [What's the plan and why?](#whats-the-plan-and-why)
- [Why was this fork created?](#why-was-this-fork-created)
* [What's the plan and why?](#whats-the-plan-and-why)
* [Why was this fork created?](#why-was-this-fork-created)
- [Copyright](#copyright)
<!-- tocstop -->
@ -118,7 +118,7 @@ There was an initial [call for maintainers][call-maintain] for the original Bats
## Copyright
© 2017-2020 bats-core organization
© 2017-2021 bats-core organization
© 2011-2016 Sam Stephenson

@ -65,7 +65,8 @@ git commit -m "feat: release Bats v${NEW_BATS_VERSION}"
# changelog start
EOF
local DELIM=$(echo -en "\001");
local DELIM
DELIM=$(echo -en "\001");
sed -E -n "\\${DELIM}^## \[${NEW_BATS_VERSION}\]${DELIM},\\${DELIM}^## ${DELIM}p" docs/CHANGELOG.md \
| head -n -1 \
| sed -E \

@ -3,7 +3,7 @@
%global repo bats-core
Name: bats
Version: 1.3.0
Version: 1.5.0
Release: 1%{?dist}
Summary: Bash Automated Testing System

@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -e
case ${1#linux/} in
386)
TINI_PLATFORM=i386
;;
arm/v7)
TINI_PLATFORM=armhf
;;
arm/v6)
TINI_PLATFORM=armel
;;
*)
TINI_PLATFORM=${1#linux/}
;;
esac
echo "Installing tini for $TINI_PLATFORM"
wget "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-${TINI_PLATFORM}" -O /tini
wget "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-${TINI_PLATFORM}.asc" -O /tini.asc
chmod +x /tini
apk add gnupg
gpg --import < /tmp/docker/tini.pubkey.gpg
gpg --batch --verify /tini.asc /tini
apk del gnupg

@ -0,0 +1,107 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=4tCW
-----END PGP PUBLIC KEY BLOCK-----

@ -8,7 +8,89 @@ The format is based on [Keep a Changelog][kac] and this project adheres to
[kac]: https://keepachangelog.com/en/1.0.0/
[semver]: https://semver.org/
## [Unreleased]
## [1.5.0] - 2021-10-22
### Added
* new command line flags (#488)
* `--verbose-run`: Make `run` print `$output` by default
* `-x`, `--trace`: Print test commands as they are executed (like `set -x`)`
* `--show-output-of-passing-tests`: Print output of passing tests
* `--print-output-on-failure`: Automatically print the value of `$output` on
failed tests
* `--gather-test-outputs-in <directory>`: Gather the output of failing **and**
passing tests as files in directory
* Experimental: add return code checks to `run` via `!`/`-<N>` (#367, #507)
* `install.sh` and `uninstall.sh` take an optional second parameter for the lib
folder name to allow for multilib install, e.g. into lib64 (#452)
* add `run` flag `--keep-empty-lines` to retain empty lines in `${lines[@]}` (#224,
a894fbfa)
* add `run` flag `--separate-stderr` which also fills `$stderr` and
`$stderr_lines` (#47, 5c9b173d, #507)
### Fixed
* don't glob `run`'s `$output` when splitting into `${lines[@]}`
(#151, #152, #158, #156, #281, #289)
* remove empty line after test with pretty formatter on some terminals (#481)
* don't run setup_file/teardown_file on files without tests, e.g. due to
filtering (#484)
* print final line without newline on Bash 3.2 for midtest (ERREXIT) failures
too (#495, #145)
* abort with error on missing flock/shlock when running in parallel mode (#496)
* improved `set -u` test and fixed some unset variable accesses (#498, #501)
* shorten suite/file/test temporary folder paths to leave enough space even on
restricted systems (#503)
#### Documentation
* minor edits (#478)
## [1.4.1] - 2021-07-24
### Added
* Docker image architectures amd64, 386, arm64, arm/v7, arm/v6, ppc64le, s390x (#438)
### Fixed
* automatic push to Dockerhub (#438)
## [1.4.0] - 2021-07-23
### Added
* added BATS_TEST_TMPDIR, BATS_FILE_TMPDIR, BATS_SUITE_TMPDIR (#413)
* added checks and improved documentation for `$BATS_TMPDIR` (#410)
* the docker container now uses [tini](https://github.com/krallin/tini) as the
container entrypoint to improve signal forwarding (#407)
* script to uninstall bats from a given prefix (#400)
* replace preprocessed file path (e.g. `/tmp/bats-run-22908-NP0f9h/bats.23102.src`)
with original filename in stdout/err (but not FD3!) (#429)
* print aborted command on SIGINT/CTRL+C (#368)
* print error message when BATS_RUN_TMPDIR could not be created (#422)
#### Documentation
* added tutorial for new users (#397)
* fixed example invocation of docker container (#440)
* minor edits (#431, #439, #445, #463, #464, #465)
### Fixed
* fix `bats_tap_stream_unknown: command not found` with pretty formatter, when
writing non compliant extended output (#412)
* avoid collisions on `$BATS_RUN_TMPDIR` with `--no-tempdir-cleanup` and docker
by using `mktemp` additionally to PID (#409)
* pretty printer now puts text that is printed to FD 3 below the test name (#426)
* `rm semaphores/slot-: No such file or directory` in parallel mode on MacOS
(#434, #433)
* fix YAML blocks in TAP13 formatter using `...` instead of `---` to start
a block (#442)
* fixed some typos in comments (#441, #447)
* ensure `/code` exists in docker container, to make examples work again (#440)
* also display error messages from free code (#429)
* npm installed version on Windows: fix broken internal LIBEXEC paths (#459)
## [1.3.0] - 2021-03-08

@ -13,21 +13,24 @@ main() {
bash "${BATS_TEST_DIRNAME}"/package-tarball
}
@test 'fail when \$src_dir and \$dst_tarball are unbound' {
@test "fail when \$src_dir and \$dst_tarball are unbound" {
unset src_dir dst_tarball
run main
[ "${status}" -ne 0 ]
}
@test 'fail when \$src_dir is a non-existent directory' {
@test "fail when \$src_dir is a non-existent directory" {
# shellcheck disable=SC2030
src_dir='not-a-dir'
run main
[ "${status}" -ne 0 ]
}
@test 'pass when \$src_dir directory is empty' {
# shellcheck disable=SC2016
@test "pass when \$src_dir directory is empty" {
# shellcheck disable=SC2031,SC2030
rm -rf "${src_dir:?}/*"
run main
@ -35,7 +38,8 @@ main() {
[ "${status}" -eq 0 ]
}
@test 'files in \$src_dir are added to tar archive' {
# shellcheck disable=SC2016
@test "files in \$src_dir are added to tar archive" {
run main
[ "${status}" -eq 0 ]

@ -18,7 +18,7 @@
# -- Project information -----------------------------------------------------
project = 'bats-core'
copyright = '2020, bats-core origanization'
copyright = '2021, bats-core origanization'
author = 'bats-core origanization'
# The full version, including alpha/beta/rc tags

@ -18,21 +18,21 @@ Receiving objects: 100% (1222/1222), 327.28 KiB | 1.70 MiB/s, done.
Resolving deltas: 100% (661/661), done.
$ cd bats-core/
$ docker build --tag bats:latest .
$ docker build --tag bats/bats:latest .
...
$ docker run -it bats:latest --formatter tap /opt/bats/test
$ docker run -it bats/bats:latest --formatter tap /opt/bats/test
```
To mount your tests into the container, first build the image as above. Then, for example with `bats`:
```bash
$ docker run -it -v "$PWD:/opt/bats" bats:latest /opt/bats/test
$ docker run -it -v "$PWD:/opt/bats" bats/bats:latest /opt/bats/test
```
This runs the `test/` directory from the bats-core repository inside the bats Docker container.
For test suites that are intended to run in isolation from the project (i.e. the tests do not depend on project files outside of the test directory), you can mount the test directory by itself and execute the tests like so:
```bash
$ docker run -it -v "$PWD/test:/test" bats:latest /test
$ docker run -it -v "$PWD:/code" bats/bats:latest /code/test
```
## Docker Gotchas
@ -46,7 +46,7 @@ Relying on functionality provided by your environment (ssh keys or agent, instal
Docker operates on a principle of isolation, and bundles all dependencies required into the Docker image. These can be mounted in at runtime (for test files, configuration, etc). For binary dependencies it may be better to extend the base Docker image with further tools and files.
```dockerfile
FROM bats
FROM bats/bats
RUN \
apk \

@ -0,0 +1,169 @@
FAQ
===
How do I set the working directory?
-----------------------------------
The working directory is simply the directory where you started when executing bats.
If you want to enforce a specific directory, you can use `cd` in the `setup_file`/`setup` functions.
However, be aware that code outside any function will run before any of these setup functions and my interfere with bats' internals.
How do I see the output of the command under `run` when a test fails?
---------------------------------------------------------------------
`run` captures stdout and stderr of its command and stores it in the `$output` and `${lines[@]}` variables.
If you want to see this output, you need to print it yourself, or use functions like `assert_output` that will reproduce it on failure.
Can I use `--filter` to exclude files/tests?
--------------------------------------------
No, not directly. `--filter` uses a regex to match against test names. So you could try to invert the regex.
The filename won't be part of the strings that are tested, so you cannot filter against files.
How can I exclude a single test from a test run?
------------------------------------------------
If you want to exclude only few tests from a run, you can either `skip` them:
.. code-block:: bash
@test "Testname" {
# yadayada
}
becomes
.. code-block:: bash
@test "Testname" {
skip 'Optional skip message'
# yadayada
}
or comment them out, e.g.:
.. code-block:: bash
@test "Testname" {
becomes
.. code-block:: bash
disabled() { # @test "Testname" {
For multiple tests or all tests of a file, this becomes tedious, so read on.
How can I exclude all tests of a file from a test run?
--------------------------------------------------------
If you run your test suite by naming individual files like:
.. code-block:: bash
$ bats test/a.bats test/b.bats ...
you can simply omit your file. When running a folder like
.. code-block:: bash
$ bats test/
you can prevent test files from being picked up by changing their extension to something other than `.bats`.
It is also possible to `skip` in `setup_file`/`setup` which will skip all tests in the file.
How can I include my own `.sh` files for testing?
-------------------------------------------------
You can simply `source <your>.sh` files. However, be aware that `source`ing files with errors outside of any function (or inside `setup_file`) will trip up bats
and lead to hard to diagnose errors.
Therefore, it is safest to only `source` inside `setup` or the test functions themselves.
How can I debug a failing test?
-------------------------------
Short of using a bash debugger you should make sure to use appropriate asserts for your task instead of raw bash comparisons, e.g.:
.. code-block:: bash
@test test {
run echo test failed
assert_output "test"
# instead of
[ "$output" = "test" ]
}
Because the former will print the output when the test fails while the latter won't.
Similarly, you should use `assert_success`/`assert_failure` instead of `[ "$status" -eq 0 ]` for return code checks.
Is there a mechanism to add file/test specific functionality to a common setup function?
----------------------------------------------------------------------------------------
Often the setup consists of parts that are common between different files of a test suite and parts that are specific to each file.
There is no suite wide setup functionality yet, so you should extract these common setup steps into their own file (e.g. `common-test-setup.sh`) and function (e.g. `commonSetup() {}`),
which can be `source`d or `load`ed and call it in `setup_file` or `setup`.
How can I use helper libraries like bats-assert?
------------------------------------------------
This is a short reproduction of https://github.com/ztombol/bats-docs.
At first, you should make sure the library is installed. This is usually done in the `test_helper/` folders alongside the `.bats` files, giving you a filesystem layout like this:
.. code-block::
test/
test.bats
test_helper/
bats-support/
bats-assert/
Next, you should load those helper libraries:
.. code-block:: bash
setup() {
load 'test_helper/bats-support/load' # this is required by bats-assert!
load 'test_helper/bats-assert/load'
}
Now, you should be able to use the functions from these helpers inside your tests, e.g.:
.. code-block:: bash
@test "test" {
run echo test
assert_output "test"
}
Note that you obviously need to load the library before using it.
If you need the library inside `setup_file` or `teardown_file` you need to load it in `setup_file`.
How to set a test timeout in bats?
----------------------------------
Unfortunately, this is not possible yet. Please contribute to issue `#396 <https://github.com/bats-core/bats-core/issues/396>`_ for further progress.
How can I lint/shell-format my bats tests?
------------------------------------------
Due to their custom syntax (`@test`), `.bats` files are not standard bash. This prevents most tools from working with bats.
However, there is an alternative syntax `function_name { # @test` to declare tests in a bash compliant manner.
- shellcheck support since version 0.7
- shfmt support since version 3.2.0 (using `-ln bats`)
How can I check if a test failed/succeeded during teardown?
-----------------------------------------------------------
You can check `BATS_TEST_COMPLETED` which will be set to 1 if the test was successful or empty if it was not.
There is also `BATS_TEST_SKIPPED` which will be non-empty (contains the skip message or -1) when `skip` was called.
How can I setup/cleanup before/after all tests?
-----------------------------------------------
Currently, this is not supported. Please contribute your usecase to issue `#39 <https://github.com/bats-core/bats-core/issues/39>`_.

@ -0,0 +1,120 @@
Gotchas
=======
My test fails although I return true?
-------------------------------------
Using `return 1` to signify `true` for a success as is done often in other languages does not mesh well with Bash's
convention of using return code 0 to signify success and everything non-zero to indicate a failure.
Please adhere to this idiom while using bats, or you will constantly work against your environment.
My negated statement (e.g. ! true) does not fail the test, even when it should.
-------------------------------------------------------------------------------
Bash deliberately excludes negated return values from causing a pipeline to exit (see bash's `-e` option). You'll need to use the form `! x || false` or (recommended) use `run` and check for `[ $status != 0 ]`.
If the negated command is the final statement in a test, that final statement's (negated) exit status will propagate through to the test's return code as usual.
Negated statements of the form `! x || false` will explicitly fail the test when the pipeline returns true, regardless of where they occur in the test.
I cannot register a test multiple times via for loop.
-----------------------------------------------------
The usual bats tests (`@test`) are preprocessed into functions.
Wrapping them into a for loop only redeclares this function.
If you are interested in registering multiple calls to the same function, contribute your wishes to issue `#306 <https://github.com/bats-core/bats-core/issues/306>`_.
I cannot pass parameters to test or .bats files.
------------------------------------------------
Especially while using bats via shebang:
.. code-block:: bash
#!/usr/bin/env bats
@test "test" {
# ...
}
You could be tempted to pass parameters to the test invocation like `./test.bats param1 param2`.
However, bats does not support passing parameters to files or tests.
If you need such a feature, please let us know about your usecase.
As a workaround you can use environment variables to pass parameters.
Why can't my function return results via a variable when using `run`?
---------------------------------------------------------------------
The `run` function executes its command in a subshell which means the changes to variables won't be available in the calling shell.
If you want to test these functions, you should call them without `run`.
`run` doesn't fail, although the same command without `run` does.
-----------------------------------------------------------------
`run` is a wrapper that always succeeds. The wrapped command's exit code is stored in `$status` and the stdout/stderr in `$output`.
If you want to fail the test, you should explicitly check `$status` or omit `run`. See also `when not to use run <writing-tests.html#when-not-to-use-run>`_.
`load` won't load my `.sh` files.
---------------------------------
`load` is intended as an internal helper function that always loads `.bash` files (by appending this suffix).
If you want to load an `.sh` file, you can simple `source` it.
I can't lint/shell-format my bats tests.
----------------------------------------
Bats uses a custom syntax for annotating tests (`@test`) that is not bash compliant.
Therefore, standard bash tooling won't be able to interact directly with `.bats` files.
Shellcheck supports bats' native syntax as of version 0.7.
Additionally, there is bash compatible syntax for tests:
.. code-block:: bash
function bash_compliant_function_name_as_test_name { # @test
# your code
}
The output (stdout/err) from commands under `run` is not visible in failed tests.
---------------------------------------------------------------------------------
By default, `run` only stores stdout/stderr in `$output` (and `${lines[@]}`).
If you want to see this output, you either should use bat-assert's assertions or have to print `$output` before the check that fails.
My piped command does not work under run.
-----------------------------------------
Be careful with using pipes and with `run`. While your mind model of `run` might wrap the whole command behind it, bash's parser won't
.. code-block:: bash
run echo foo | grep bar
Won't `run (echo foo | grep bar)` but will `(run echo foo) | grep bar`. If you need to incorporate pipes, you either should do
.. code-block:: bash
run bash -c 'echo foo | grep bar'
or use a function to wrap the pipe in:
.. code-block:: bash
fun_with_pipes() {
echo foo | grep bar
}
run fun_with_pipes
`[[ ]]` (or `(( ))` did not fail my test
----------------------------------------
The `set -e` handling of `[[ ]]` and `(( ))` changed in Bash 4.1. Older versions, like 3.2 on MacOS,
don't abort the test when they fail, unless they are the last command before the (test) function returns,
making their exit code the return code.
`[ ]` does not suffer from this, but is no replacement for all `[[ ]]` usecases. Appending ` || false` will work in all cases.

@ -7,8 +7,11 @@ Versions before v1.2.1 are documented over `there <https://github.com/bats-core/
:maxdepth: 2
:caption: Contents:
tutorial
installation
usage
docker-usage
writing-tests
gotchas
faq

@ -0,0 +1,661 @@
Tutorial
========
This tutorial is intended for beginners with bats and possibly bash.
Make sure to also read the list of gotchas and the faq.
For this tutorial we are assuming you already have a project in a git repository and want to add tests.
Ultimately they should run in the CI environment but will also be started locally during development.
..
TODO: link to example repository?
Quick installation
------------------
Since we already have an existing git repository, it is very easy to include bats and its libraries as submodules.
We are aiming for following filesystem structure:
.. code-block::
src/
project.sh
...
test/
bats/ <- submodule
test_helper/
bats-support/ <- submodule
bats-assert/ <- submodule
test.bats
...
So we start from the project root:
.. code-block:: console
git submodule add https://github.com/bats-core/bats-core.git test/bats
git submodule add https://github.com/bats-core/bats-support.git test/test_helper/bats-support
git submodule add https://github.com/bats-core/bats-assert.git test/test_helper/bats-assert
Your first test
---------------
Now we want to add our first test.
In the tutorial repository, we want to build up our project in a TDD fashion.
Thus, we start with an empty project and our first test is to just run our (non existing) shell script.
We start by creating a new test file `test/test.bats`
.. code-block:: bash
@test "can run our script" {
./project.sh
}
and run it by
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
✗ can run our script
(in test file test/test.bats, line 2)
`./project.sh' failed with status 127
/tmp/bats-run-19605/bats.19627.src: line 2: ./project.sh: No such file or directory
1 test, 1 failure
Okay, our test is red. Obviously, the project.sh doesn't exist, so we create the file `src/project.sh`:
.. code-block:: console
mkdir src/
echo '#!/usr/bin/env bash' > src/project.sh
chmod a+x src/project.sh
A new test run gives us
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
✗ can run our script
(in test file test/test.bats, line 2)
`./project.sh' failed with status 127
/tmp/bats-run-19605/bats.19627.src: line 2: ./project.sh: No such file or directory
1 test, 1 failure
Oh, we still used the wrong path. No problem, we just need to use the correct path to `project.sh`.
Since we're still in the same directory as when we started `bats`, we can simply do:
.. code-block:: bash
@test "can run our script" {
./src/project.sh
}
and get:
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
✓ can run our script
1 test, 0 failures
Yesss! But that victory feels shallow: What if somebody less competent than us starts bats from another directory?
Let's do some setup
-------------------
The obvious solution to becoming independent of `$PWD` is using some fixed anchor point in the filesystem.
We can use the path to the test file itself as an anchor and rely on the internal project structure.
Since we are lazy people and want to treat our project's files as first class citizens in the executable world, we will also put them on the `$PATH`.
Our new `test/test.bats` now looks like this:
.. code-block:: bash
setup() {
# get the containing directory of this file
# use $BATS_TEST_FILENAME instead of ${BASH_SOURCE[0]} or $0,
# as those will point to the bats executable's location or the preprocessed file respectively
DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )"
# make executables in src/ visible to PATH
PATH="$DIR/../src:$PATH"
}
@test "can run our script" {
# notice the missing ./
# As we added src/ to $PATH, we can omit the relative path to `src/project.sh`.
project.sh
}
still giving us:
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
✓ can run our script
1 test, 0 failures
It still works as expected. This is because the newly added `setup` function put the absolute path to `src/` onto `$PATH`.
This setup function is automatically called before each test.
Therefore, our test could execute `project.sh` directly, without using a (relative) path.
.. important::
The `setup` function will be called before each individual test in the file.
Each file can only define one setup function for all tests in the file.
However, the setup functions can differ between different files.
Dealing with output
-------------------
Okay, we have a green test but our executable does not do anything useful.
To keep things simple, let us start with an error message. Our new `src/project.sh` now reads:
.. code-block:: bash
#!/usr/bin/env bash
echo "Welcome to our project!"
echo "NOT IMPLEMENTED!" >&2
exit 1
And gives is this test output:
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
✗ can run our script
(in test file test/test.bats, line 11)
`project.sh' failed
Welcome to our project!
NOT IMPLEMENTED!
1 test, 1 failure
Okay, our test failed, because we now exit with 1 instead of 0.
Additionally, we see the stdout and stderr of the failing program.
Our goal now is to retarget our test and check that we get the welcome message.
bats-assert gives us some help with this, so we should now load it (and its dependency bats-support),
so we change `test/test.bats` to
.. code-block:: bash
setup() {
load 'test_helper/bats-support/load'
load 'test_helper/bats-assert/load'
# ... the remaining setup is unchanged
# get the containing directory of this file
# use $BATS_TEST_FILENAME instead of ${BASH_SOURCE[0]} or $0,
# as those will point to the bats executable's location or the preprocessed file respectively
DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )"
# make executables in src/ visible to PATH
PATH="$DIR/../src:$PATH"
}
@test "can run our script" {
run project.sh # notice `run`!
assert_output 'Welcome to our project!'
}
which gives us the following test output:
.. code-block:: console
$ LANG=C ./test/bats/bin/bats test/test.bats
✗ can run our script
(from function `assert_output' in file test/test_helper/bats-assert/src/assert_output.bash, line 194,
in test file test/test.bats, line 14)
`assert_output 'Welcome to our project!'' failed
-- output differs --
expected (1 lines):
Welcome to our project!
actual (2 lines):
Welcome to our project!
NOT IMPLEMENTED!
--
1 test, 1 failure
The first change in this output is the failure description. We now fail on assert_output instead of the call itself.
We prefixed our call to `project.sh` with `run`, which is a function provided by bats that executes the command it gets passed as parameters.
Then, `run` sucks up the stdout and stderr of the command it ran and stores it in `$output`, stores the exit code in `$status` and returns 0.
This means `run` never fails the test and won't generate any context/output in the log of a failed test on its own.
Marking the test as failed and printing context information is up to the consumers of `$status` and `$output`.
`assert_output` is such a consumer, it compares `$output` to the the parameter it got and tells us quite succinctly that it did not match in this case.
For our current test we don't care about any other output or the error message, so we want it gone.
`grep` is always at our fingertips, so we tape together this ramshackle construct
.. code-block:: bash
run project.sh 2>&1 | grep Welcome
which gives us the following test result:
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
✗ can run our script
(in test file test/test.bats, line 13)
`run project.sh | grep Welcome' failed
1 test, 1 failure
Huh, what is going on? Why does it fail the `run` line again?
This is a common mistake that can happen when our mind parses the file differently than the bash parser.
`run` is just a function, so the pipe won't actually be forwarded into the function. Bash reads this as `(run project.sh) | grep Welcome`,
instead of our intended `run (project.sh | grep Welcome)`.
Unfortunately, the latter is no valid bash syntax, so we have to work around it, e.g. by using a function:
.. code-block:: bash
get_projectsh_welcome_message() {
project.sh 2>&1 | grep Welcome
}
@test "Check welcome message" {
run get_projectsh_welcome_message
assert_output 'Welcome to our project!'
}
Now our test passes again but having to write a function each time we want only a partial match does not accommodate our laziness.
Isn't there an app for that? Maybe we should look at the documentation?
Partial matching can be enabled with the --partial option (-p for short). When used, the assertion fails if the expected substring is not found in $output.
-- the documentation for `assert_output <https://github.com/bats-core/bats-assert#partial-matching>`_
Okay, so maybe we should try that:
.. code-block:: bash
@test "Check welcome message" {
run project.sh
assert_output --partial 'Welcome to our project!'
}
Aaannnd ... the test stays green. Yay!
There are many other asserts and options but this is not the place for all of them.
Skimming the documentation of `bats-assert <https://github.com/bats-core/bats-assert>`_ will give you a good idea what you can do.
You should also have a look at the other helper libraries `here <https://github.com/bats-core>`_ like `bats-file <https://github.com/bats-core/bats-file>`_,
to avoid reinventing the wheel.
Cleaning up your mess
---------------------
Often our setup or tests leave behind some artifacts that clutter our test environment.
You can define a `teardown` function which will be called after each test, regardless whether it failed or not.
For example, we now want our project.sh to only show the welcome message on the first invocation.
So we change our test to this:
.. code-block:: bash
@test "Show welcome message on first invocation" {
run project.sh
assert_output --partial 'Welcome to our project!'
run project.sh
refute_output --partial 'Welcome to our project!'
}
This test fails as expected:
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
✗ Show welcome message on first invocation
(from function `refute_output' in file test/test_helper/bats-assert/src/refute_output.bash, line 189,
in test file test/test.bats, line 17)
`refute_output --partial 'Welcome to our project!'' failed
-- output should not contain substring --
substring (1 lines):
Welcome to our project!
output (2 lines):
Welcome to our project!
NOT IMPLEMENTED!
--
1 test, 1 failure
Now, to get the test green again, we want to store the information that we already ran in the file `/tmp/bats-tutorial-project-ran`,
so our `src/project.sh` becomes:
.. code-block:: bash
#!/usr/bin/env bash
FIRST_RUN_FILE=/tmp/bats-tutorial-project-ran
if [[ ! -e "$FIRST_RUN_FILE" ]]; then
echo "Welcome to our project!"
touch "$FIRST_RUN_FILE"
fi
echo "NOT IMPLEMENTED!" >&2
exit 1
And our test says:
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
✓ Show welcome message on first invocation
1 test, 0 failures
Nice, we're done, or are we? Running the test again now gives:
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
✗ Show welcome message on first invocation
(from function `assert_output' in file test/test_helper/bats-assert/src/assert_output.bash, line 186,
in test file test/test.bats, line 14)
`assert_output --partial 'Welcome to our project!'' failed
-- output does not contain substring --
substring : Welcome to our project!
output : NOT IMPLEMENTED!
--
1 test, 1 failure
Now the first assert failed, because of the leftover `$FIRST_RUN_FILE` from the last test run.
Luckily, bats offers the `teardown` function, which can take care of that, we add the following code to `test/test.bats`:
.. code-block:: bash
teardown() {
rm -f /tmp/bats-tutorial-project-ran
}
Now running the test again first give us the same error, as the teardown has not run yet.
On the second try we get a clean `/tmp` folder again and our test passes consistently now.
It is worth noting that we could do this `rm` in the test code itself but it would get skipped on failures.
.. important::
A test ends at its first failure. None of the subsequent commands in this test will be executed.
The `teardown` function runs after each individual test in a file, regardless of test success or failure.
Similarly to `setup`, each `.bats` file can have its own `teardown` function which will be the same for all tests in the file.
Test what you can
-----------------
Sometimes tests rely on the environment to provide infrastructure that is needed for the test.
If not all test environments provide this infrastructure but we still want to test on them,
it would be unhelpful to get errors on parts that are not testable.
Bats provides you with the `skip` command which can be used in `setup` and `test`.
.. tip::
You should `skip` as early as you know it does not make sense to continue.
In our example project we rewrite the welcome message test to `skip` instead of doing cleanup:
.. code-block:: bash
teardown() {
: # Look Ma! No cleanup!
}
@test "Show welcome message on first invocation" {
if [[ -e /tmp/bats-tutorial-project-ran ]]; then
skip 'The FIRST_RUN_FILE already exists'
fi
run project.sh
assert_output --partial 'Welcome to our project!'
run project.sh
refute_output --partial 'Welcome to our project!'
}
The first test run still works due to the cleanup from the last round. However, our second run gives us:
.. code-block:: console
$ ./test/bats/bin/bats test/test.bats
- Show welcome message on first invocation (skipped: The FIRST_RUN_FILE already exists)
1 test, 0 failures, 1 skipped
.. important::
Skipped tests won't fail a test suite and are counted separately.
No test command after `skip` will be executed. If an error occurs before `skip`, the test will fail.
An optional reason can be passed to `skip` and will be printed in the test output.
Setting up a multifile test suite
---------------------------------
With a growing project, putting all tests into one file becomes unwieldy.
For our example project, we will extract functionality into the additional file `src/helper.sh`:
.. code-block:: bash
#!/usr/bin/env bash
_is_first_run() {
local FIRST_RUN_FILE=/tmp/bats-tutorial-project-ran
if [[ ! -e "$FIRST_RUN_FILE" ]]; then
touch "$FIRST_RUN_FILE"
return 0
fi
return 1
}
This allows for testing it separately in a new file `test/helper.bats`:
.. code-block:: bash
setup() {
load 'test_helper/common-setup'
_common_setup
source "$PROJECT_ROOT/src/helper.sh"
}
teardown() {
rm -f "$NON_EXISTANT_FIRST_RUN_FILE"
rm -f "$EXISTING_FIRST_RUN_FILE"
}
@test "Check first run" {
NON_EXISTANT_FIRST_RUN_FILE=$(mktemp -u) # only create the name, not the file itself
assert _is_first_run
refute _is_first_run
refute _is_first_run
EXISTING_FIRST_RUN_FILE=$(mktemp)
refute _is_first_run
refute _is_first_run
}
Since the setup function would have duplicated much of the other files', we split that out into the file `test/test_helper/common-setup.bash`:
.. code-block:: bash
#!/usr/bin/env bash
_common_setup() {
load 'test_helper/bats-support/load'
load 'test_helper/bats-assert/load'
# get the containing directory of this file
# use $BATS_TEST_FILENAME instead of ${BASH_SOURCE[0]} or $0,
# as those will point to the bats executable's location or the preprocessed file respectively
PROJECT_ROOT="$( cd "$( dirname "$BATS_TEST_FILENAME" )/.." >/dev/null 2>&1 && pwd )"
# make executables in src/ visible to PATH
PATH="$PROJECT_ROOT/src:$PATH"
}
with the following `setup` in `test/test.bats`:
.. code-block:: bash
setup() {
load 'test_helper/common-setup'
_common_setup
}
Please note, that we gave our helper the extension `.bash`, which is automatically appended by `load`.
.. important::
`load` automatically tries to append `.bash` to its argument.
In our new `test/helper.bats` we can see, that loading `.sh` is simply done via `source`.
.. tip::
Avoid using `load` and `source` outside of any functions.
If there is an error in the test file's "free code", the diagnostics are much worse than for code in `setup` or `@test`.
With the new changes in place, we can run our tests again. However, our previous run command does not include the new file.
You could add the new file to the parameter list, e.g. by running `./test/bats/bin/bats test/*.bats`.
However, bats also can handle directories:
.. code-block:: console
$ ./test/bats/bin/bats test/
✓ Check first run
- Show welcome message on first invocation (skipped: The FIRST_RUN_FILE already exists)
2 tests, 0 failures, 1 skipped
In this mode, bats will pick up all `.bats` files in the directory it was given. There is an additional `-r` switch that will recursively search for more `.bats` files.
However, in our project layout this would pick up the test files of bats itself from `test/bats/test`. We don't have test subfolders anyways, so we can do without `-r`.
Avoiding costly repeated setups
-------------------------------
We already have seen the `setup` function in use, which is called before each test.
Sometimes our setup is very costly, such as booting up a service just for testing.
If we can reuse the same setup across multiple tests, we might want to do only one setup before all these tests.
This usecase is exactly what the `setup_file` function was created for.
It can be defined per file and will run before all tests of the respective file.
Similarly, we have `teardown_file`, which will run after all tests of the file, even when you abort a test run or a test failed.
As an example, we want to add an echo server capability to our project. First, we add the following `server.bats` to our suite:
.. code-block:: bash
setup_file() {
load 'test_helper/common-setup'
_common_setup
PORT=$(project.sh start-echo-server >/dev/null 2>&1)
export PORT
}
@test "server is reachable" {
nc -z localhost "$PORT"
}
Which will obviously fail:
Note that `export PORT` to make it visible to the test!
Running this gives us:
..
TODO: Update this example with fixed test name reporting from setup_file? (instead of "✗ ")
.. code-block:: console
$ ./test/bats/bin/bats test/server.bats
(from function `setup_file' in test file test/server.bats, line 4)
`PORT=$(project.sh start-echo-server >/dev/null 2>&1)' failed
1 test, 1 failure
Now that we got our red test, we need to get it green again.
Our new `project.sh` now ends with:
.. code-block:: bash
case $1 in
start-echo-server)
echo "Starting echo server"
PORT=2000
ncat -l $PORT -k -c 'xargs -n1 echo' 2>/dev/null & # don't keep open this script's stderr
echo $! > /tmp/project-echo-server.pid
echo "$PORT" >&2
;;
*)
echo "NOT IMPLEMENTED!" >&2
exit 1
;;
esac
and the tests now say
.. code-block:: console
$ LANG=C ./test/bats/bin/bats test/server.bats
✓ server is reachable
1 test, 0 failures
However, running this a second time gives:
.. code-block:: console
$ ./test/bats/bin/bats test/server.bats
✗ server is reachable
(in test file test/server.bats, line 14)
`nc -z -w 2 localhost "$PORT"' failed
2000
Ncat: bind to :::2000: Address already in use. QUITTING.
nc: port number invalid: 2000
Ncat: bind to :::2000: Address already in use. QUITTING.
1 test, 1 failure
Obviously, we did not turn off our server after testing.
This is a task for `teardown_file` in `server.bats`:
.. code-block:: bash
teardown_file() {
project.sh stop-echo-server
}
Our `project.sh` should also get the new command:
.. code-block:: bash
stop-echo-server)
kill "$(< "/tmp/project-echo-server.pid")"
rm /tmp/project-echo-server.pid
;;
Now starting our tests again will overwrite the .pid file with the new instance's, so we have to do manual cleanup once.
From now on, our test should clean up after itself.
.. note::
`teardown_file` will run regardless of tests failing or succeeding.

@ -28,12 +28,32 @@ nonexistent filename, exits with a `1` status code and prints an error message.
run foo nonexistent_filename
[ "$status" -eq 1 ]
[ "$output" = "foo: no such file 'nonexistent_filename'" ]
[ "$BATS_RUN_COMMAND" = "foo nonexistent_filename" ]
}
```
The `$status` variable contains the status code of the command, and the
The `$status` variable contains the status code of the command, the
`$output` variable contains the combined contents of the command's standard
output and standard error streams.
output and standard error streams, and the `$BATS_RUN_COMMAND` string contains the
command and command arguments passed to `run` for execution.
If invoked with one of the following as the first argument, `run`
will perform an implicit check on the exit status of the invoked command:
```pre
=N expect exit status N (0-255), fail if otherwise
! expect nonzero exit status (1-255), fail if command succeeds
```
We can then write the above more elegantly as:
```bash
@test "invoking foo with a nonexistent file prints an error" {
run -1 foo nonexistent_filename
[ "$output" = "foo: no such file 'nonexistent_filename'" ]
}
```
A third special variable, the `$lines` array, is available for easily accessing
individual lines of output. For example, if you want to test that invoking `foo`
@ -41,8 +61,7 @@ without any arguments prints usage information on the first line:
```bash
@test "invoking foo without arguments prints usage" {
run foo
[ "$status" -eq 1 ]
run -1 foo
[ "${lines[0]}" = "usage: foo <filename>" ]
}
```
@ -51,63 +70,21 @@ __Note:__ The `run` helper executes its argument(s) in a subshell, so if
writing tests against environmental side-effects like a variable's value
being changed, these changes will not persist after `run` completes.
### When not to use `run`
In some cases, using `run` is redundant and results in a longer and less readable code.
Here are a few examples.
#### 1. In case you only need to check the command succeeded, it is better to not use run, since
```bash
run command args ...
echo "$output"
[ "$status" -eq 0 ]
```
is equivalent to
```bash
command args ...
```
since bats sets `set -e` for all tests.
#### 2. In case you want to hide the command output (which `run` does), use output redirection instead
By default `run` leaves out empty lines in `${lines[@]}`. Use
`run --keep-empty-lines` to retain them.
This
Additionally, you can use `--separate-stderr` to split stdout and stderr
into `$output`/`$stderr` and `${lines[@]}`/`${stderr_lines[@]}`.
```bash
run command ...
[ "$status" -eq 0 ]
```
All additional parameters to run should come before the command.
If you want to run a command that starts with `-`, prefix it with `--` to
prevent `run` from parsing it as an option.
is equivalent to
```bash
command ... >/dev/null
```
Note that the output is only shown if the test case fails.
#### 3. In case you need to assign command output to a variable (and maybe check the command exit status), it is better to not use run, since
```bash
run command args ...
[ "$status" -eq 0 ]
var="$output"
```
is equivalent to
```bash
var=$(command args ...)
```
#### Comment syntax
## Comment syntax
External tools (like `shellcheck`, `shfmt`, and various IDE's) may not support
the standard `.bats` syntax. Because of this, we provide a valid `bash`
alterntative:
alternative:
```bash
function invoking_foo_without_arguments_prints_usage { #@test
@ -120,7 +97,7 @@ function invoking_foo_without_arguments_prints_usage { #@test
When using this syntax, the function name will be the title in the result output
and the value checked when using `--filter`.
### `load`: Share common code
## `load`: Share common code
You may want to share common code across multiple test files. Bats includes a
convenient `load` command for sourcing a Bash source file relative to the
@ -146,7 +123,7 @@ will _not_ be made available to callers of `load`.
> it looks for `test_helper`). This behaviour is deprecated and subject to
> change, please use exact filenames instead.
### `skip`: Easily skip tests
## `skip`: Easily skip tests
Tests can be skipped by using the `skip` command at the point in a test you wish
to skip.
@ -184,7 +161,7 @@ Or you can skip conditionally:
__Note:__ `setup` and `teardown` hooks still run for skipped tests.
### `setup` and `teardown`: Pre- and post-test hooks
## `setup` and `teardown`: Pre- and post-test hooks
You can define special `setup` and `teardown` functions, which run before and
after each test case, respectively. Use these to load fixtures, set up your
@ -216,7 +193,7 @@ teardown_file # from file 2, on leaving file 2
</details>
<!-- markdownlint-enable MD033 -->
### Code outside of test cases
## Code outside of test cases
You can include code in your test file outside of `@test` functions. For
example, this may be useful if you want to check for dependencies and fail
@ -225,7 +202,7 @@ outside of `@test`, `setup` or `teardown` functions must be redirected to
`stderr` (`>&2`). Otherwise, the output may cause Bats to fail by polluting the
TAP stream on `stdout`.
### File descriptor 3 (read this if Bats hangs)
## File descriptor 3 (read this if Bats hangs)
Bats makes a separation between output from the code under test and output that
forms the TAP stream (which is produced by Bats internals). This is done in
@ -246,7 +223,7 @@ amount of time.
**To prevent this from happening, close FD 3 explicitly when running any command
that may launch long-running child processes**, e.g. `command_name 3>&-` .
### Printing to the terminal
## Printing to the terminal
Bats produces output compliant with [version 12 of the TAP protocol][TAP]. The
produced TAP stream is by default piped to a pretty formatter for human
@ -259,21 +236,18 @@ bats provides a special file descriptor, `&3`, that you should use to print
your custom text. Here are some detailed guidelines to refer to:
- Printing **from within a test function**:
- To have text printed from within a test function you need to redirect the
output to file descriptor 3, eg `echo 'text' >&3`. This output will become
part of the TAP stream. You are encouraged to prepend text printed this way
with a hash (eg `echo '# text' >&3`) in order to produce 100% TAP compliant
- First you should consider if you want the text to be always visible or only
when the test fails. Text that is output directly to stdout or stderr (file
descriptor 1 or 2), ie `echo 'text'` is considered part of the test function
output and is printed only on test failures for diagnostic purposes,
regardless of the formatter used (TAP or pretty).
- To have text printed unconditionally from within a test function you need to
redirect the output to file descriptor 3, eg `echo 'text' >&3`. This output
will become part of the TAP stream. You are encouraged to prepend text printed
this way with a hash (eg `echo '# text' >&3`) in order to produce 100% TAP compliant
output. Otherwise, depending on the 3rd-party tools you use to analyze the
TAP stream, you can encounter unexpected behavior or errors.
- The pretty formatter that Bats uses by default to process the TAP stream
will filter out and not print text output to file descriptor 3.
- Text that is output directly to stdout or stderr (file descriptor 1 or 2),
ie `echo 'text'` is considered part of the test function output and is
printed only on test failures for diagnostic purposes, regardless of the
formatter used (TAP or pretty).
- Printing **from within the `setup` or `teardown` functions**: The same hold
true as for printing with test functions.
@ -293,10 +267,11 @@ your custom text. Here are some detailed guidelines to refer to:
[tap-plan]: https://testanything.org/tap-specification.html#the-plan
### Special variables
## Special variables
There are several global variables you can use to introspect on Bats tests:
- `$BATS_RUN_COMMAND` is the run command used in your test case.
- `$BATS_TEST_FILENAME` is the fully expanded path to the Bats test file.
- `$BATS_TEST_DIRNAME` is the directory in which the Bats test file is located.
- `$BATS_TEST_NAMES` is an array of function names for each test case.
@ -304,10 +279,21 @@ There are several global variables you can use to introspect on Bats tests:
- `$BATS_TEST_DESCRIPTION` is the description of the current test case.
- `$BATS_TEST_NUMBER` is the (1-based) index of the current test case in the test file.
- `$BATS_SUITE_TEST_NUMBER` is the (1-based) index of the current test case in the test suite (over all files).
- `$BATS_TMPDIR` is the location to a directory that may be used to store temporary files.
- `$BATS_TMPDIR` is the base temporary directory used by bats to create its
temporary files / directories.
(default: `$TMPDIR`. If `$TMPDIR` is not set, `/tmp` is used.)
- `$BATS_RUN_TMPDIR` is the location to the temporary directory used by bats to
store all its internal temporary files during the tests.
(default: `$BATS_TMPDIR/bats-run-$BATS_ROOT_PID-XXXXXX`)
- `$BATS_FILE_EXTENSION` (default: `bats`) specifies the extension of test files that should be found when running a suite (via `bats [-r] suite_folder/`)
### Libraries and Add-ons
- `$BATS_SUITE_TMPDIR` is a temporary directory common to all tests of a suite.
Could be used to create files required by multiple tests.
- `$BATS_FILE_TMPDIR` is a temporary directory common to all tests of a test file.
Could be used to create files required by multiple tests in the same test file.
- `$BATS_TEST_TMPDIR` is a temporary directory unique for each test.
Could be used to create files required only for specific tests.
## Libraries and Add-ons
Bats supports loading external assertion libraries and helpers. Those under `bats-core` are officially supported libraries (integration tests welcome!):

@ -4,6 +4,7 @@ set -e
BATS_ROOT="${0%/*}"
PREFIX="$1"
LIBDIR="${2:-lib}"
if [[ -z "$PREFIX" ]]; then
printf '%s\n' \
@ -12,10 +13,10 @@ if [[ -z "$PREFIX" ]]; then
exit 1
fi
install -d -m 755 "$PREFIX"/{bin,libexec/bats-core,lib/bats-core,share/man/man{1,7}}
install -d -m 755 "$PREFIX"/{bin,libexec/bats-core,"${LIBDIR}"/bats-core,share/man/man{1,7}}
install -m 755 "$BATS_ROOT/bin"/* "$PREFIX/bin"
install -m 755 "$BATS_ROOT/libexec/bats-core"/* "$PREFIX/libexec/bats-core"
install -m 755 "$BATS_ROOT/lib/bats-core"/* "$PREFIX/lib/bats-core"
install -m 755 "$BATS_ROOT/lib/bats-core"/* "$PREFIX/${LIBDIR}/bats-core"
install -m 644 "$BATS_ROOT/man/bats.1" "$PREFIX/share/man/man1"
install -m 644 "$BATS_ROOT/man/bats.7" "$PREFIX/share/man/man7"

@ -0,0 +1,10 @@
#!/usr/bin/env bash
bats_prefix_lines_for_tap_output() {
while IFS= read -r line; do
printf '# %s\n' "$line" || break # avoid feedback loop when errors are redirected into BATS_OUT (see #353)
done
if [[ -n "$line" ]]; then
printf '# %s\n' "$line"
fi
}

@ -109,3 +109,20 @@ remove_prefix() {
printf "%s" "$path"
fi
}
normalize_base_path() { # <target variable> <base path>
# the relative path root to use for reporting filenames
# this is mainly intended for suite mode, where this will be the suite root folder
local base_path="$2"
# use the containing directory when --base-path is a file
if [[ ! -d "$base_path" ]]; then
base_path="$(dirname "$base_path")"
fi
# get the absolute path
base_path="$(cd "$base_path" && pwd)"
# ensure the path ends with / to strip that later on
if [[ "${base_path}" != *"/" ]]; then
base_path="$base_path/"
fi
printf -v "$1" "%s" "$base_path"
}

@ -1,11 +1,5 @@
#!/usr/bin/env bash
if [[ -z "${TMPDIR:-}" ]]; then
export BATS_TMPDIR='/tmp'
else
export BATS_TMPDIR="${TMPDIR%/}"
fi
BATS_TMPNAME="$BATS_RUN_TMPDIR/bats.$$"
BATS_PARENT_TMPNAME="$BATS_RUN_TMPDIR/bats.$PPID"
# shellcheck disable=SC2034

@ -1,5 +1,34 @@
#!/usr/bin/env bash
# setup the semaphore environment for the loading file
bats_semaphore_setup() {
export -f bats_semaphore_get_free_slot_count
export -f bats_semaphore_acquire_while_locked
export BATS_SEMAPHORE_DIR="$BATS_RUN_TMPDIR/semaphores"
if command -v flock >/dev/null; then
bats_run_under_lock() {
flock "$BATS_SEMAPHORE_DIR" "$@"
}
elif command -v shlock >/dev/null; then
bats_run_under_lock() {
local lockfile="$BATS_SEMAPHORE_DIR/shlock.lock"
while ! shlock -p $$ -f "$lockfile"; do
sleep 1
done
# we got the lock now, execute the command
"$@"
local status=$?
# free the lock
rm -f "$lockfile"
return $status
}
else
printf "ERROR: flock/shlock is required for parallelization within files!\n" >&2
exit 1
fi
}
# $1 - output directory for stdout/stderr
# $@ - command to run
# run the given command in a semaphore
@ -15,8 +44,6 @@ bats_semaphore_run() {
printf "%d\n" "$!"
}
export BATS_SEMAPHORE_DIR="$BATS_RUN_TMPDIR/semaphores"
# $1 - output directory for stdout/stderr
# $@ - command to run
# this wraps the actual function call to install some traps on exiting
@ -51,25 +78,6 @@ bats_semaphore_acquire_while_locked() {
return 1
}
export -f bats_semaphore_acquire_while_locked
if command -v flock >/dev/null; then
bats_run_under_lock() {
flock "$BATS_SEMAPHORE_DIR" "$@"
}
elif command -v shlock >/dev/null; then
bats_run_under_lock() {
local lockfile="$BATS_SEMAPHORE_DIR/shlock.lock"
while ! shlock -p $$ -f "$lockfile"; do
sleep 1
done
# we got the lock now, execute the command
"$@"
# free the lock
rm -f "$lockfile"
}
fi
# block until a semaphore slot becomes free
# prints the number of the slot that it received
bats_semaphore_acquire_slot() {
@ -97,5 +105,3 @@ bats_semaphore_get_free_slot_count() {
until used_slots=$(find "$BATS_SEMAPHORE_DIR" -name 'slot-*' 2>/dev/null | wc -l); do :; done
echo $(( BATS_SEMAPHORE_NUMBER_OF_SLOTS - used_slots ))
}
export -f bats_semaphore_get_free_slot_count

@ -29,19 +29,125 @@ load() {
source "${file}"
}
run() {
bats_redirect_stderr_into_file() {
"$@" 2>>"$bats_run_separate_stderr_file" # use >> to see collisions' content
}
bats_merge_stdout_and_stderr() {
"$@" 2>&1
}
# write separate lines from <input-var> into <output-array>
bats_separate_lines() { # <output-array> <input-var>
output_array_name="$1"
input_var_name="$2"
if [[ $keep_empty_lines ]]; then
local bats_separate_lines_lines=()
while IFS= read -r line; do
bats_separate_lines_lines+=("$line")
done <<<"${!input_var_name}"
eval "${output_array_name}=(\"\${bats_separate_lines_lines[@]}\")"
else
# shellcheck disable=SC2034,SC2206
IFS=$'\n' read -d '' -r -a "$output_array_name" <<<"${!input_var_name}"
fi
}
run() { # [!|-N] [--keep-empty-lines] [--separate-stderr] [--] <command to run...>
trap bats_interrupt_trap_in_run INT
local expected_rc=
local keep_empty_lines=
local output_case=merged
# parse options starting with -
while [[ $# -gt 0 ]] && [[ $1 == -* || $1 == '!' ]]; do
case "$1" in
'!')
expected_rc=-1
;;
-[0-9]*)
expected_rc=${1#-}
if [[ $expected_rc =~ [^0-9] ]]; then
printf "Usage error: run: '=NNN' requires numeric NNN (got: %s)\n" "$expected_rc" >&2
return 1
elif [[ $expected_rc -gt 255 ]]; then
printf "Usage error: run: '=NNN': NNN must be <= 255 (got: %d)\n" "$expected_rc" >&2
return 1
fi
;;
--keep-empty-lines)
keep_empty_lines=1
;;
--separate-stderr)
output_case="separate"
;;
--)
shift # eat the -- before breaking away
break
;;
esac
shift
done
local pre_command=
case "$output_case" in
merged) # redirects stderr into stdout and fills only $output/$lines
pre_command=bats_merge_stdout_and_stderr
;;
separate) # splits stderr into own file and fills $stderr/$stderr_lines too
local bats_run_separate_stderr_file
bats_run_separate_stderr_file="$(mktemp "${BATS_TEST_TMPDIR}/separate-stderr-XXXXXX")"
pre_command=bats_redirect_stderr_into_file
;;
esac
local origFlags="$-"
set +eET
local origIFS="$IFS"
# 'output', 'status', 'lines' are global variables available to tests.
# shellcheck disable=SC2034
output="$("$@" 2>&1)"
status=0
if [[ $keep_empty_lines ]]; then
# 'output', 'status', 'lines' are global variables available to tests.
# preserve trailing newlines by appending . and removing it later
# shellcheck disable=SC2034
output="$($pre_command "$@"; status=$?; printf .; exit $status)" || status="$?"
output="${output%.}"
else
# 'output', 'status', 'lines' are global variables available to tests.
# shellcheck disable=SC2034
output="$($pre_command "$@")" || status="$?"
fi
bats_separate_lines lines output
if [[ "$output_case" == separate ]]; then
# shellcheck disable=SC2034
read -d '' -r stderr < "$bats_run_separate_stderr_file"
bats_separate_lines stderr_lines stderr
fi
# shellcheck disable=SC2034
status="$?"
# shellcheck disable=SC2034,SC2206
IFS=$'\n' lines=($output)
BATS_RUN_COMMAND="${*}"
IFS="$origIFS"
set "-$origFlags"
if [[ ${BATS_VERBOSE_RUN:-} ]]; then
printf "%s\n" "$output"
fi
if [[ -n "$expected_rc" ]]; then
if [[ "$expected_rc" = "-1" ]]; then
if [[ "$status" -eq 0 ]]; then
bats_capture_stack_trace # fix backtrace
BATS_ERROR_SUFFIX=", expected nonzero exit code!"
return 1
fi
elif [ "$status" -ne "$expected_rc" ]; then
bats_capture_stack_trace # fix backtrace
# shellcheck disable=SC2034
BATS_ERROR_SUFFIX=", expected exit code $expected_rc, got $status"
return 1
fi
fi
}
setup() {

@ -5,6 +5,13 @@ bats_capture_stack_trace() {
local funcname
local i
# The last entry in the stack trace is not useful when en error occured:
# It is either duplicated (kinda correct) or has wrong line number (Bash < 4.4)
# Therefore we capture the stacktrace but use it only after the next debug
# trap fired.
# Expansion is required for empty arrays which otherwise error
BATS_CURRENT_STACK_TRACE=("${BATS_STACK_TRACE[@]+"${BATS_STACK_TRACE[@]}"}")
BATS_STACK_TRACE=()
for ((i = 2; i != ${#FUNCNAME[@]}; ++i)); do
@ -13,12 +20,13 @@ bats_capture_stack_trace() {
test_file="${BASH_SOURCE[$i]:-$BATS_TEST_SOURCE}"
funcname="${FUNCNAME[$i]}"
BATS_STACK_TRACE+=("${BASH_LINENO[$((i - 1))]} $funcname $test_file")
if [[ "$test_file" == "$BATS_TEST_SOURCE" ]]; then
case "$funcname" in
"$BATS_TEST_NAME" | setup | teardown | setup_file | teardown_file)
break
;;
esac
case "$funcname" in
"$BATS_TEST_NAME" | setup | teardown | setup_file | teardown_file)
break
;;
esac
if [[ "${BASH_SOURCE[$i + 1]:-}" == *"bats-exec-file" ]] && [[ "$funcname" == 'source' ]]; then
break
fi
done
}
@ -43,7 +51,10 @@ bats_print_stack_trace() {
local fn
bats_frame_function "$frame" 'fn'
if [[ "$fn" != "$BATS_TEST_NAME" ]]; then
if [[ "$fn" != "$BATS_TEST_NAME" ]] &&
# don't print "from function `source'"",
# when failing in free code during `source $test_file` from bats-exec-file
! [[ "$fn" == 'source' && $index -eq $count ]]; then
printf "from function \`%s' " "$fn"
fi
@ -58,6 +69,9 @@ bats_print_stack_trace() {
}
bats_print_failed_command() {
if [[ ${#BATS_STACK_TRACE[@]} -eq 0 ]]; then
return
fi
local frame="${BATS_STACK_TRACE[${#BATS_STACK_TRACE[@]} - 1]}"
local filename
local lineno
@ -71,9 +85,9 @@ bats_print_failed_command() {
printf '%s' "# \`${failed_command}' "
if [[ "$BATS_ERROR_STATUS" -eq 1 ]]; then
printf 'failed\n'
printf 'failed%s\n' "$BATS_ERROR_SUFFIX"
else
printf 'failed with status %d\n' "$BATS_ERROR_STATUS"
printf 'failed with status %d%s\n' "$BATS_ERROR_STATUS" "$BATS_ERROR_SUFFIX"
fi
}
@ -117,16 +131,63 @@ bats_trim_filename() {
printf -v "$2" '%s' "${1#$BATS_CWD/}"
}
# normalize a windows path from e.g. C:/directory to /c/directory
# The path must point to an existing/accessable directory, not a file!
bats_normalize_windows_dir_path() { # <output-var> <path>
local output_var="$1"
local path="$2"
if [[ $path == ?:* ]]; then
NORMALIZED_INPUT="$(cd "$path" || exit 1; pwd)"
else
NORMALIZED_INPUT="$path"
fi
printf -v "$output_var" "%s" "$NORMALIZED_INPUT"
}
bats_emit_trace() {
if [[ $BATS_TRACE_LEVEL -gt 0 ]]; then
local line=${BASH_LINENO[1]}
# shellcheck disable=SC2016
if [[ $BASH_COMMAND != '"$BATS_TEST_NAME" >> "$BATS_OUT" 2>&1 4>&1' && $BASH_COMMAND != "bats_test_begin "* ]] && # don't emit these internal calls
[[ $BASH_COMMAND != "$BATS_LAST_BASH_COMMAND" || $line != "$BATS_LAST_BASH_LINENO" ]] &&
# avoid printing a function twice (at call site and at definiton site)
[[ $BASH_COMMAND != "$BATS_LAST_BASH_COMMAND" || ${BASH_LINENO[2]} != "$BATS_LAST_BASH_LINENO" || ${BASH_SOURCE[3]} != "$BATS_LAST_BASH_SOURCE" ]]; then
local file="${BASH_SOURCE[2]}" # index 2: skip over bats_emit_trace and bats_debug_trap
if [[ $file == "${BATS_TEST_SOURCE}" ]]; then
file="$BATS_TEST_FILENAME"
fi
local padding='$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$'
if (( BATS_LAST_STACK_DEPTH != ${#BASH_LINENO[@]} )); then
printf '%s [%s:%d]\n' "${padding::${#BASH_LINENO[@]}-4}" "${file##*/}" "$line" >&4
fi
printf '%s %s\n' "${padding::${#BASH_LINENO[@]}-4}" "$BASH_COMMAND" >&4
BATS_LAST_BASH_COMMAND="$BASH_COMMAND"
BATS_LAST_BASH_LINENO="$line"
BATS_LAST_BASH_SOURCE="${BASH_SOURCE[2]}"
BATS_LAST_STACK_DEPTH="${#BASH_LINENO[@]}"
fi
fi
}
bats_debug_trap() {
# on windows we sometimes get a mix of paths (when install via nmp install -g)
# which have C:/... or /c/... comparing them is going to be problematic.
# We need to normalize them to a common format!
local NORMALIZED_INPUT
bats_normalize_windows_dir_path NORMALIZED_INPUT "${1%/*}"
local file_excluded='' path
for path in "${_BATS_DEBUG_EXCLUDE_PATHS[@]}"; do
if [[ "$NORMALIZED_INPUT" == "$path"* ]]; then
file_excluded=1
break
fi
done
# don't update the trace within library functions or we get backtraces from inside traps
if [[ "$1" != $BATS_ROOT/lib/* && "$1" != $BATS_ROOT/libexec/* ]]; then
# The last entry in the stack trace is not useful when en error occured:
# It is either duplicated (kinda correct) or has wrong line number (Bash < 4.4)
# Therefore we capture the stacktrace but use it only after the next debug
# trap fired.
# Expansion is required for empty arrays which otherwise error
BATS_CURRENT_STACK_TRACE=("${BATS_STACK_TRACE[@]+"${BATS_STACK_TRACE[@]}"}")
# also don't record new stack traces while handling interruptions, to avoid overriding the interrupted command
if [[ -z "$file_excluded" && "${BATS_INTERRUPTED-NOTSET}" == NOTSET ]]; then
bats_capture_stack_trace
bats_emit_trace
fi
}
@ -152,3 +213,49 @@ bats_error_trap() {
trap - DEBUG
fi
}
bats_add_debug_exclude_path() { # <path>
if [[ -z "$1" ]]; then # don't exclude everything
printf "bats_add_debug_exclude_path: Exclude path must not be empty!\n" >&2
return 1
fi
if [[ "$OSTYPE" == cygwin || "$OSTYPE" == msys ]]; then
bats_normalize_windows_dir_path normalized_dir "$1"
_BATS_DEBUG_EXCLUDE_PATHS+=("$normalized_dir")
else
_BATS_DEBUG_EXCLUDE_PATHS+=("$1")
fi
}
bats_setup_tracing() {
_BATS_DEBUG_EXCLUDE_PATHS=()
# exclude some paths by default
bats_add_debug_exclude_path "$BATS_ROOT/lib/"
bats_add_debug_exclude_path "$BATS_ROOT/libexec/"
exec 4<&1 # used for tracing
if [[ "${BATS_TRACE_LEVEL:-0}" -gt 0 ]]; then
# avoid undefined variable errors
BATS_LAST_BASH_COMMAND=
BATS_LAST_BASH_LINENO=
BATS_LAST_BASH_SOURCE=
BATS_LAST_STACK_DEPTH=
# try to exclude helper libraries if found, this is only relevant for tracing
while read -r path; do
bats_add_debug_exclude_path "$path"
done < <(find "$PWD" -type d -name bats-assert -o -name bats-support)
fi
# exclude user defined libraries
IFS=':' read -r exclude_paths <<< "${BATS_DEBUG_EXCLUDE_PATHS:-}"
for path in "${exclude_paths[@]}"; do
if [[ -n "$path" ]]; then
bats_add_debug_exclude_path "$path"
fi
done
# turn on traps after setting excludedes to avoid tracing the exclude setup
trap 'bats_debug_trap "$BASH_SOURCE"' DEBUG
trap 'bats_error_trap' ERR
}

@ -1,6 +1,7 @@
#!/usr/bin/env bash
bats_test_count_validator() {
trap '' INT # continue forwarding
header_pattern='[0-9]+\.\.[0-9]+'
IFS= read -r header
# repeat the header

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
export BATS_VERSION='1.3.0'
export BATS_VERSION='1.5.0'
VALID_FORMATTERS="pretty, junit, tap, tap13"
version() {
@ -32,6 +32,9 @@ HELP_TEXT_HEADER
-f, --filter <regex> Only run tests that match the regular expression
-F, --formatter <type> Switch between formatters: pretty (default),
tap (default w/o term), tap13, junit
--gather-test-outputs-in <directory>
Gather the output of failing *and* passing tests
as files in directory
-h, --help Display this help message
-j, --jobs <jobs> Number of parallel jobs (requires GNU parallel)
--no-tempdir-cleanup Preserve test output temporary directory
@ -44,9 +47,14 @@ HELP_TEXT_HEADER
--report-formatter <type> Switch between reporters (same options as --formatter)
-o, --output <dir> Directory to write report files
-p, --pretty Shorthand for "--formatter pretty"
--print-output-on-failure Automatically print the value of `$output` on failed tests
-r, --recursive Include tests in subdirectories
--show-output-of-passing-tests
Print output of passing tests
-t, --tap Shorthand for "--formatter tap"
-T, --timing Add timing information to tests
-x, --trace Print test commands as they are executed (like `set -x`)
--verbose-run Make `run` print `$output` by default
-v, --version Display the version number
For more information, see https://github.com/bats-core/bats-core
@ -68,20 +76,22 @@ expand_path() {
printf -v "$result" '%s/%s' "$dirname" "${path##*/}"
}
BATS_LIBEXEC="$(dirname "$(bats_readlinkf "${BASH_SOURCE[0]}")")"
BATS_LIBEXEC="$(cd "$(dirname "$(bats_readlinkf "${BASH_SOURCE[0]}")")"; pwd)"
export BATS_LIBEXEC
export BATS_CWD="$PWD"
export BATS_TEST_PATTERN="^[[:blank:]]*@test[[:blank:]]+(.*[^[:blank:]])[[:blank:]]+\{(.*)\$"
export BATS_TEST_PATTERN_COMMENT="[[:blank:]]*([^[:blank:]()]+)[[:blank:]]*\(?\)?[[:blank:]]+\{[[:blank:]]+#[[:blank:]]*@test[[:blank:]]*\$"
export BATS_TEST_FILTER=
export PATH="$BATS_LIBEXEC:$PATH"
export BATS_ROOT_PID=$$
if [[ -z "${TMPDIR-}" ]]; then
export BATS_TMPDIR="/tmp"
else
export BATS_TMPDIR="${TMPDIR%/}"
export BATS_TMPDIR="${TMPDIR:-/tmp}"
export BATS_RUN_TMPDIR=
if [[ ! -d "${BATS_TMPDIR}" ]];then
printf "Error: BATS_TMPDIR (%s) does not exist or is not a directory" "${BATS_TMPDIR}" >&2
exit 1
elif [[ ! -w "${BATS_TMPDIR}" ]];then
printf "Error: BATS_TMPDIR (%s) is not writable" "${BATS_TMPDIR}" >&2
exit 1
fi
export BATS_RUN_TMPDIR="$BATS_TMPDIR/bats-run-$BATS_ROOT_PID"
arguments=()
@ -105,8 +115,8 @@ set -- "${arguments[@]}"
arguments=()
unset flags recursive formatter_flags
flags=('--dummy-flag') # add a dummy flag to prevent unset varialeb errors on empty array expansion in old bash versions
formatter_flags=('--dummy-flag') # add a dummy flag to prevent unset varialeb errors on empty array expansion in old bash versions
flags=('--dummy-flag') # add a dummy flag to prevent unset variable errors on empty array expansion in old bash versions
formatter_flags=('--dummy-flag') # add a dummy flag to prevent unset variable errors on empty array expansion in old bash versions
formatter='tap'
report_formatter=''
recursive=
@ -190,6 +200,26 @@ while [[ "$#" -ne 0 ]]; do
BATS_RUN_TMPDIR="$2"
shift
;;
-x | --trace)
flags+=(--trace)
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
output_dir="$1"
if ! mkdir "$output_dir"; then
abort "Could not create $output_dir for --gather-test-outputs-in"
fi
flags+=(--gather-test-outputs-in "$output_dir")
;;
-*)
abort "Bad command line option '$1'"
;;
@ -200,14 +230,24 @@ while [[ "$#" -ne 0 ]]; do
shift
done
if [[ -d "$BATS_RUN_TMPDIR" ]]; then
printf "Error: BATS_RUN_TMPDIR (%s) already exists\n" "$BATS_RUN_TMPDIR" >&2
printf "Reusing old run directories can lead to unexpected results ... aborting!\n" >&2
if [[ -n "${BATS_RUN_TMPDIR:-}" ]];then
if [[ -d "$BATS_RUN_TMPDIR" ]]; then
printf "Error: BATS_RUN_TMPDIR (%s) already exists\n" "$BATS_RUN_TMPDIR" >&2
printf "Reusing old run directories can lead to unexpected results ... aborting!\n" >&2
exit 1
elif ! mkdir -p "$BATS_RUN_TMPDIR" ;then
printf "Error: Failed to create BATS_RUN_TMPDIR (%s)\n" "$BATS_RUN_TMPDIR" >&2
exit 1
fi
elif ! BATS_RUN_TMPDIR=$(mktemp -d "${BATS_TMPDIR}/bats-run-XXXXXX");then
printf "Error: Failed to create BATS_RUN_TMPDIR (%s) with mktemp\n" "${BATS_TMPDIR}/bats-run-XXXXXX" >&2
exit 1
fi
mkdir -p "$BATS_RUN_TMPDIR"
if [[ -n "$BATS_TEMPDIR_CLEANUP" ]]; then
trap 'rm -rf "$BATS_RUN_TMPDIR"' ERR EXIT
else
trap 'printf "BATS_RUN_TMPDIR: $BATS_RUN_TMPDIR\n">&2' EXIT
fi
if [[ "$formatter" != "tap" ]]; then
@ -227,6 +267,10 @@ if [[ "$report_formatter" == "junit" ]]; then
report_formatter_flags+=('--base-path' "${arguments[0]}")
fi
if [[ "$formatter" == "pretty" ]]; then
formatter_flags+=('--base-path' "${arguments[0]}")
fi
# if we don't need to filter extended syntax, use the faster formatter
if [[ "$formatter" == tap && -z "$report_formatter" ]]; then
formatter="cat"
@ -285,6 +329,8 @@ done
# shellcheck source=lib/bats-core/validator.bash
source "$BATS_ROOT/lib/bats-core/validator.bash"
trap 'BATS_INTERRUPTED=true' INT # let the lower levels handle the interruption
set -o pipefail execfail
if [[ -n "$report_formatter" ]]; then

@ -5,6 +5,7 @@ export flags=()
num_jobs=1
filter=''
extended_syntax=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
while [[ "$#" -ne 0 ]]; do
case "$1" in
@ -32,6 +33,22 @@ while [[ "$#" -ne 0 ]]; do
;;
--dummy-flag)
;;
--trace)
flags+=('--trace')
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
flags+=(--gather-test-outputs-in "$1")
;;
*)
break
;;
@ -47,7 +64,7 @@ if [[ ! -f "$filename" ]]; then
exit 1
fi
BATS_TEST_FILENAME="$filename"
export BATS_TEST_FILENAME="$filename"
# shellcheck source=lib/bats-core/preprocessing.bash
# shellcheck disable=SC2153
@ -73,23 +90,37 @@ bats_run_setup_file() {
# shellcheck disable=2034
BATS_TEST_COMPLETED= # used in tracing.bash
BATS_SOURCE_FILE_COMPLETED=
BATS_SETUP_FILE_COMPLETED=
BATS_TEARDOWN_FILE_COMPLETED=
# shellcheck disable=2034
BATS_ERROR_STATUS= # used in tracing.bash
trap 'bats_debug_trap "$BASH_SOURCE"' DEBUG
trap 'bats_error_trap' ERR
touch "$BATS_OUT"
bats_setup_tracing
trap 'bats_file_teardown_trap' EXIT
touch "$BATS_OUT"
local status=0
# get the setup_file/teardown_file functions for this file (if it has them)
# shellcheck disable=SC1090
source "$BATS_TEST_SOURCE"
setup_file >>"$BATS_OUT" 2>&1
source "$BATS_TEST_SOURCE" > >(bats_replace_filename) 2>&1
BATS_SOURCE_FILE_COMPLETED=1
setup_file > >(bats_replace_filename >>"$BATS_OUT") 2>&1
BATS_SETUP_FILE_COMPLETED=1
}
function bats_replace_filename() {
local line
while read -r line; do
printf "%s\n" "${line//$BATS_TEST_SOURCE/$filename}"
done
if [[ -n "$line" ]]; then
printf "%s\n" "${line//$BATS_TEST_SOURCE/$filename}"
fi
}
bats_run_teardown_file() {
# avoid running the therdown trap due to errors in teardown_file
trap 'bats_file_exit_trap' EXIT
@ -108,23 +139,25 @@ bats_file_teardown_trap() {
bats_file_exit_trap
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
bats_file_exit_trap() {
trap - ERR EXIT
if [[ -z "$BATS_SETUP_FILE_COMPLETED" || -z "$BATS_TEARDOWN_FILE_COMPLETED" ]]; then
if [[ -z "$BATS_SETUP_FILE_COMPLETED" ]]; then
FAILURE_REASON='setup_file'
else
elif [[ -z "$BATS_TEARDOWN_FILE_COMPLETED" ]]; then
FAILURE_REASON='teardown_file'
elif [[ -z "$BATS_SOURCE_FILE_COMPLETED" ]]; then
FAILURE_REASON='source'
else
FAILURE_REASON='unknown internal'
fi
printf "not ok %d %s\n" "$((test_number_in_suite + 1))" "$FAILURE_REASON failed" >&3
bats_print_stack_trace "${BATS_STACK_TRACE[@]}" >&3
bats_print_failed_command >&3
while IFS= read -r line; do
printf "# %s\n" "$line"
done <"$BATS_OUT" >&3
if [[ -n "$line" ]]; then
printf '# %s\n' "$line"
fi
bats_prefix_lines_for_tap_output < "$BATS_OUT" >&3
rm -rf "$BATS_OUT"
status=1
fi
@ -185,6 +218,7 @@ bats_run_tests_in_parallel() {
mkdir -p "$output_folder"
# shellcheck source=lib/bats-core/semaphore.bash
source "$BATS_ROOT/lib/bats-core/semaphore.bash"
bats_semaphore_setup
# the test_number_in_file is not yet incremented -> one before the next test to run
local test_number_in_suite_of_last_finished_test="$test_number_in_suite" # stores which test was printed last
for test_name in "${tests_to_run[@]}"; do
@ -204,10 +238,9 @@ bats_run_tests_in_parallel() {
return $status
}
bats_run_tests() {
status=0
tests_to_run=()
bats_read_tests_list_file() {
local line_number=0
tests_to_run=()
# the global test number must be visible to traps -> not local
first_test_number_in_suite=''
while read -r test_line; do
@ -229,11 +262,20 @@ bats_run_tests() {
test_number_in_suite="$first_test_number_in_suite"
test_number_in_file=0
}
bats_run_tests() {
status=0
if [[ "$num_jobs" != 1 && "${BATS_NO_PARALLELIZE_WITHIN_FILE-False}" == False ]]; then
export BATS_SEMAPHORE_NUMBER_OF_SLOTS="$num_jobs"
bats_run_tests_in_parallel "$BATS_RUN_TMPDIR/parallel_output" || status=1
else
for test_name in "${tests_to_run[@]}"; do
if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
status=130 # bash's code for SIGINT exits
break
fi
# Only handle non-empty lines
if [[ $test_name ]]; then
((++test_number_in_suite))
@ -250,10 +292,38 @@ bats_run_tests() {
export status
}
bats_create_file_tempdirs() {
local bats_files_tmpdir="${BATS_RUN_TMPDIR}/file"
if ! mkdir -p "$bats_files_tmpdir"; then
printf 'Failed to create %s\n' "$bats_files_tmpdir" >&2
exit 1
fi
BATS_FILE_TMPDIR="$bats_files_tmpdir/$first_test_number_in_suite"
if ! mkdir "$BATS_FILE_TMPDIR"; then
printf 'Failed to create BATS_FILE_TMPDIR=%s\n' "$BATS_FILE_TMPDIR" >&2
exit 1
fi
ln -s "$BATS_TEST_FILENAME" "$BATS_FILE_TMPDIR-$(basename "$BATS_TEST_FILENAME").source_file"
export BATS_FILE_TMPDIR
}
trap 'BATS_INTERRUPTED=true' INT
if [[ -n "$extended_syntax" ]]; then
printf "suite %s\n" "$filename"
fi
bats_read_tests_list_file
# don't run potentially expensive setup/teardown_file
# when there are no tests to run
if [[ ${#tests_to_run[@]} -eq 0 ]]; then
exit 0
fi
# requires the test list to be read but not empty
bats_create_file_tempdirs
bats_preprocess_source "$filename"
bats_run_setup_file
bats_run_tests

@ -4,8 +4,6 @@ set -e
count_only_flag=''
filter=''
num_jobs=${BATS_NUMBER_OF_PARALLEL_JOBS-1}
have_gnu_parallel=
bats_parallel_args=()
bats_no_parallelize_across_files=${BATS_NO_PARALLELIZE_ACROSS_FILES-}
bats_no_parallelize_within_files=
flags=('--dummy-flag') # add a dummy flag to prevent unset varialeb errors on empty array expansion in old bash versions
@ -45,6 +43,22 @@ while [[ "$#" -ne 0 ]]; do
;;
--dummy-flag)
;;
--trace)
flags+=('--trace')
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
flags+=(--gather-test-outputs-in "$1")
;;
*)
break
;;
@ -52,16 +66,16 @@ while [[ "$#" -ne 0 ]]; do
shift
done
if (type -p parallel &>/dev/null); then
# shellcheck disable=SC2034
have_gnu_parallel=1
elif [[ "$num_jobs" != 1 && -z "$bats_no_parallelize_across_files" ]]; then
abort "Cannot execute \"${num_jobs}\" jobs without GNU parallel"
exit 1
if [[ "$num_jobs" != 1 ]]; then
if ! type -p parallel >/dev/null && [[ -z "$bats_no_parallelize_across_files" ]]; then
abort "Cannot execute \"${num_jobs}\" jobs without GNU parallel"
exit 1
fi
# shellcheck source=lib/bats-core/semaphore.bash
source "${BATS_ROOT}/lib/bats-core/semaphore.bash"
bats_semaphore_setup
fi
trap 'kill 0; exit 1' INT
# create a file that contains all (filtered) tests to run from all files
TESTS_LIST_FILE="${BATS_RUN_TMPDIR}/test_list_file.txt"
@ -112,6 +126,18 @@ if [[ -n "$bats_no_parallelize_within_files" ]] && [[ ! "$num_jobs" -gt 1 ]]; th
exit 1
fi
# only abort on the lowest levels
trap 'BATS_INTERRUPTED=true' INT
trap bats_exit_trap EXIT
bats_exit_trap() {
if [[ ${BATS_INTERRUPTED-NOTSET} != NOTSET ]]; then
printf "\n# Received SIGINT, aborting ...\n\n"
fi
exit "$status"
}
status=0
printf '1..%d\n' "${test_count}"
@ -120,6 +146,12 @@ if [[ "${test_count}" == 0 ]]; then
exit
fi
export BATS_SUITE_TMPDIR="${BATS_RUN_TMPDIR}/suite"
if ! mkdir "$BATS_SUITE_TMPDIR"; then
printf '%s\n' "Failed to create BATS_SUITE_TMPDIR" >&2
exit 1
fi
# Deduplicate filenames (without reordering) to avoid running duplicate tests n by n times.
# (see https://github.com/bats-core/bats-core/issues/329)
# If a file was specified multiple times, we already got it repeatedly in our TESTS_LIST_FILE.
@ -128,17 +160,16 @@ IFS=$'\n' read -d '' -r -a BATS_UNIQUE_TEST_FILENAMES < <(printf "%s\n" "$@"| n
if [[ "$num_jobs" -gt 1 ]] && [[ -z "$bats_no_parallelize_across_files" ]]; then
# run files in parallel to get the maximum pool of parallel tasks
if [[ ${#flags[@]} -eq 0 ]]; then
# if there are no flags, our quoting below would keep an empty arg, which is wrong
parallel "${bats_parallel_args[@]}" --keep-order --jobs "$num_jobs" bats-exec-file "{}" "$TESTS_LIST_FILE" ::: "${BATS_UNIQUE_TEST_FILENAMES[@]}" 2>&1 || status=1
else
# shellcheck disable=SC2086,SC2068
# we need to handle the quoting of ${flags[@]} ourselves,
# because parallel can only quote it as one
parallel --keep-order --jobs "$num_jobs" bats-exec-file "$(printf "%q " "${flags[@]}")" "{}" "$TESTS_LIST_FILE" ::: "${BATS_UNIQUE_TEST_FILENAMES[@]}" 2>&1 || status=1
fi
# shellcheck disable=SC2086,SC2068
# we need to handle the quoting of ${flags[@]} ourselves,
# because parallel can only quote it as one
parallel --keep-order --jobs "$num_jobs" bats-exec-file "$(printf "%q " "${flags[@]}")" "{}" "$TESTS_LIST_FILE" ::: "${BATS_UNIQUE_TEST_FILENAMES[@]}" 2>&1 || status=1
else
for filename in "${BATS_UNIQUE_TEST_FILENAMES[@]}"; do
if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
status=130 # bash's code for SIGINT exits
break
fi
bats-exec-file "${flags[@]}" "$filename" "${TESTS_LIST_FILE}" || status=1
done
fi

@ -6,6 +6,11 @@ BATS_COUNT_ONLY=''
BATS_TEST_FILTER=''
BATS_ENABLE_TIMING=''
BATS_EXTENDED_SYNTAX=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
BATS_PRINT_OUTPUT_ON_FAILURE="${BATS_PRINT_OUTPUT_ON_FAILURE:-}"
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS:-}"
BATS_VERBOSE_RUN="${BATS_VERBOSE_RUN:-}"
BATS_GATHER_TEST_OUTPUTS_IN="${BATS_GATHER_TEST_OUTPUTS_IN:-}"
while [[ "$#" -ne 0 ]]; do
case "$1" in
@ -20,12 +25,27 @@ while [[ "$#" -ne 0 ]]; do
;;
-T)
BATS_ENABLE_TIMING='-T'
BATS_PERFORM_TEST_CMD+=('-T')
;;
-x)
# shellcheck disable=SC2034
BATS_EXTENDED_SYNTAX='-x'
;;
--trace)
(( ++BATS_TRACE_LEVEL )) # avoid returning 0
;;
--print-output-on-failure)
BATS_PRINT_OUTPUT_ON_FAILURE=1
;;
--show-output-of-passing-tests)
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS=1
;;
--verbose-run)
BATS_VERBOSE_RUN=1
;;
--gather-test-outputs-in)
shift
BATS_GATHER_TEST_OUTPUTS_IN="$1"
;;
*)
break
;;
@ -33,8 +53,11 @@ while [[ "$#" -ne 0 ]]; do
shift
done
BATS_TEST_FILENAME="$1"
shift
export BATS_TEST_FILENAME="$1"
export BATS_TEST_NAME="$2"
export BATS_SUITE_TEST_NUMBER="$3"
export BATS_TEST_NUMBER="$4"
if [[ -z "$BATS_TEST_FILENAME" ]]; then
printf 'usage: bats-exec-test <filename>\n' >&2
exit 1
@ -43,6 +66,24 @@ elif [[ ! -f "$BATS_TEST_FILENAME" ]]; then
exit 1
fi
bats_create_test_tmpdirs() {
local tests_tmpdir="${BATS_RUN_TMPDIR}/test/"
if ! mkdir -p "$tests_tmpdir"; then
printf 'Failed to create: %s\n' "$tests_tmpdir" >&2
exit 1
fi
BATS_TEST_TMPDIR="$tests_tmpdir/$BATS_SUITE_TEST_NUMBER"
if ! mkdir "$BATS_TEST_TMPDIR"; then
printf 'Failed to create BATS_TEST_TMPDIR: %s\n' "$BATS_TEST_TMPDIR" >&2
exit 1
fi
printf "%s\n" "$BATS_TEST_NAME" > "$BATS_TEST_TMPDIR.name"
export BATS_TEST_TMPDIR
}
# load the test helper functions like `load` or `run` that are needed to run a (preprocessed) .bats file without bash errors
# shellcheck source=lib/bats-core/test_functions.bash disable=SC2153
source "$BATS_ROOT/lib/bats-core/test_functions.bash"
@ -67,6 +108,9 @@ bats_teardown_trap() {
bats_exit_trap
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
bats_exit_trap() {
local line
local status
@ -85,7 +129,9 @@ bats_exit_trap() {
BATS_TEST_TIME=" in "$(( $(get_mills_since_epoch) - BATS_TEST_START_TIME ))"ms"
fi
if [[ -z "$BATS_TEST_COMPLETED" || -z "$BATS_TEARDOWN_COMPLETED" ]]; then
local print_bats_out="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS}"
if [[ -z "$BATS_TEST_COMPLETED" || -z "$BATS_TEARDOWN_COMPLETED" || "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
if [[ "$BATS_ERROR_STATUS" -eq 0 ]]; then
# For some versions of bash, `$?` may not be set properly for some error
# conditions before triggering the EXIT trap directly (see #72 and #81).
@ -103,12 +149,11 @@ bats_exit_trap() {
bats_print_stack_trace "${BATS_STACK_TRACE[@]}" >&3
bats_print_failed_command >&3
while IFS= read -r line; do
printf '# %s\n' "$line" || break # avoid feedback loop when errors are redirected into BATS_OUT (see #353)
done <"$BATS_OUT" >&3
if [[ -n "$line" ]]; then
printf '# %s\n' "$line"
fi
if [[ $BATS_PRINT_OUTPUT_ON_FAILURE && -n "${output:-}" ]]; then
printf "Last output:\n%s\n" "$output" >> "$BATS_OUT"
fi
print_bats_out=1
status=1
else
printf 'ok %d %s%s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_DESCRIPTION}${BATS_TEST_TIME}" \
@ -116,6 +161,14 @@ bats_exit_trap() {
status=0
fi
if [[ $print_bats_out ]]; then
bats_prefix_lines_for_tap_output < "$BATS_OUT" >&3
fi
if [[ $BATS_GATHER_TEST_OUTPUTS_IN ]]; then
cp "$BATS_OUT" "$BATS_GATHER_TEST_OUTPUTS_IN/$BATS_SUITE_TEST_NUMBER-$BATS_TEST_DESCRIPTION.log"
fi
rm -f "$BATS_OUT"
exit "$status"
}
@ -133,10 +186,6 @@ get_mills_since_epoch() {
}
bats_perform_test() {
export BATS_TEST_NAME="$1"
export BATS_SUITE_TEST_NUMBER="$2"
export BATS_TEST_NUMBER="$3"
if ! declare -F "$BATS_TEST_NAME" &>/dev/null; then
printf "bats: unknown test name \`%s'\n" "$BATS_TEST_NAME" >&2
exit 1
@ -156,24 +205,43 @@ bats_perform_test() {
BATS_TEST_SKIPPED=
BATS_TEARDOWN_COMPLETED=
BATS_ERROR_STATUS=
trap 'bats_debug_trap "$BASH_SOURCE"' DEBUG
trap 'bats_error_trap' ERR
BATS_ERROR_SUFFIX=
bats_setup_tracing
# mark this call as trap call
trap 'bats_teardown_trap as-exit-trap' EXIT
BATS_TEST_START_TIME=$(get_mills_since_epoch)
"$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1
"$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1 4>&1
BATS_TEST_COMPLETED=1
trap 'bats_exit_trap' EXIT
bats_teardown_trap "" # pass empty parameter to signify call outside trap
}
bats_interrupt_trap() {
# mark the interruption, to handle during exit
BATS_INTERRUPTED=true
exit 130
}
# keep in sync with bats_interrupt_trap
# this is used inside run()
bats_interrupt_trap_in_run() {
BATS_INTERRUPTED=true
# we don't get a second stack trace when interrupted in run,
# so we have to use the first!
BATS_CURRENT_STACK_TRACE=("${BATS_STACK_TRACE[@]+"${BATS_STACK_TRACE[@]}"}")
exit 130
}
trap bats_interrupt_trap INT
# shellcheck source=lib/bats-core/preprocessing.bash
source "$BATS_ROOT/lib/bats-core/preprocessing.bash"
exec 3<&1
bats_create_test_tmpdirs
# Run the given test.
bats_evaluate_preprocessed_source
bats_perform_test "$@"
bats_perform_test

@ -1,4 +1,6 @@
#!/usr/bin/env bash
set -e
trap '' INT
cat

@ -1,25 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"
BASE_PATH=.
while [[ "$#" -ne 0 ]]; do
case "$1" in
--base-path)
shift
# the relative path root to use for reporting filenames
# this is mainly intended for suite mode, where this will be the suite root folder
BASE_PATH="$1"
# use the containing directory when --base-path is a file
if [[ ! -d "$BASE_PATH" ]]; then
BASE_PATH="$(dirname "$BASE_PATH")"
fi
# get the absolute path
BASE_PATH="$(cd "$BASE_PATH"; pwd)"
# ensure the path ends with / to strip that later on
if [[ "${BASE_PATH}" != *"/" ]]; then
BASE_PATH="$BASE_PATH/"
fi
normalize_base_path BASE_PATH "$1"
;;
esac
shift
@ -180,15 +172,13 @@ finish_suite() {
suite_footer
}
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"
bats_tap_stream_plan() { # <number of tests>
:
}
init_suite
trap finish_suite EXIT
trap '' INT
bats_tap_stream_begin() { # <test index> <test name>
flush_log

@ -1,11 +1,21 @@
#!/usr/bin/env bash
set -e
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"
BASE_PATH=.
BATS_ENABLE_TIMING=
while [[ "$#" -ne 0 ]]; do
case "$1" in
-T)
BATS_ENABLE_TIMING="-T"
;;
--base-path)
shift
normalize_base_path BASE_PATH "$1"
;;
esac
shift
done
@ -30,10 +40,16 @@ update_count_column_left() {
count_column_left=$((screen_width - count_column_width))
}
trap update_screen_width WINCH
# avoid unset variables
count=0
screen_width=80
update_count_column_width
update_screen_width
trap update_screen_width WINCH
begin() {
line_backoff_count=0
go_to_column 0
update_count_column_width
buffer_with_truncation $((count_column_left - 1)) ' %s' "$name"
@ -47,14 +63,22 @@ begin() {
go_to_column 1
}
pass() {
finish_test() {
move_up $line_backoff_count
go_to_column 0
buffer ' ✓ %s' "$name"
buffer "$@"
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
set_color 2
buffer ' [%s]' "$1"
buffer ' [%s]' "$TIMING"
fi
advance
move_down $(( line_backoff_count - 1 ))
}
pass() {
TIMING="${1:-}"
finish_test ' ✓ %s' "$name"
test_result=pass
}
skip() {
@ -62,29 +86,37 @@ skip() {
if [[ -n "$reason" ]]; then
reason=": $reason"
fi
go_to_column 0
buffer ' - %s (skipped%s)' "$name" "$reason"
advance
BATS_ENABLE_TIMING='' finish_test ' - %s (skipped%s)' "$name" "$reason"
test_result=skip
}
fail() {
go_to_column 0
set_color 1 bold
buffer ' ✗ %s' "$name"
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
set_color 2
buffer ' [%s]' "$1"
fi
advance
TIMING="${1:-}"
finish_test ' ✗ %s' "$name"
test_result=fail
}
log() {
set_color 1
case $test_result in
pass)
clear_color
;;
fail)
set_color 1
;;
esac
buffer ' %s\n' "$1"
clear_color
}
summary() {
if [ "$failures" -eq 0 ] ; then
set_color 2 bold
else
set_color 1 bold
fi
buffer '\n%d test' "$count"
if [[ "$count" -ne 1 ]]; then
buffer 's'
@ -109,6 +141,7 @@ summary() {
fi
buffer '\n'
clear_color
}
buffer_with_truncation() {
@ -126,6 +159,18 @@ buffer_with_truncation() {
fi
}
move_up() {
if [[ $1 -gt 0 ]]; then # avoid moving if we got 0
buffer '\x1B[%dA' "$1"
fi
}
move_down() {
if [[ $1 -gt 0 ]]; then # avoid moving if we got 0
buffer '\x1B[%dB' "$1"
fi
}
go_to_column() {
local column="$1"
buffer '\x1B[%dG' $((column + 1))
@ -145,7 +190,7 @@ set_color() {
local color="$1"
local weight=22
if [[ "$2" == 'bold' ]]; then
if [[ "${2:-}" == 'bold' ]]; then
weight=1
fi
buffer '\x1B[%d;%dm' "$((30 + color))" "$weight"
@ -164,6 +209,13 @@ buffer() {
_buffer+="$content"
}
prefix_buffer_with() {
local old_buffer="$_buffer"
_buffer=''
"$@"
_buffer="$_buffer$old_buffer"
}
flush() {
printf '%s' "$_buffer"
_buffer=
@ -175,9 +227,7 @@ finish() {
}
trap finish EXIT
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"
trap '' INT
bats_tap_stream_plan() {
count="$1"
@ -195,7 +245,7 @@ bats_tap_stream_begin() {
begin
flush
}
bats_tap_stream_ok() {
index="$1"
((++passed))
@ -222,12 +272,42 @@ bats_tap_stream_not_ok() {
fi
}
bats_tap_stream_comment() {
bats_tap_stream_comment() { # <comment> <scope>
local scope=$2
# count the lines we printed after the begin text,
if [[ $line_backoff_count -eq 0 && $scope == begin ]]; then
# if this is the first line after begin, go down one line
buffer "\n"
(( ++line_backoff_count )) # prefix-increment to avoid "error" due to returning 0
fi
(( ++line_backoff_count ))
(( line_backoff_count += ${#1} / screen_width)) # account for linebreaks due to length
log "$1"
}
bats_tap_stream_suite() {
: #test_file="$1"
#test_file="$1"
line_backoff_count=0
index=
# indicate filename for failures
name="File $(remove_prefix "$BASE_PATH" "$1")"
}
line_backoff_count=0
bats_tap_stream_unknown() { # <full line> <scope>
local scope=$2
# count the lines we printed after the begin text, (or after suite, in case of syntax errors)
if [[ $line_backoff_count -eq 0 && ( $scope == begin || $scope == suite )]]; then
# if this is the first line after begin, go down one line
buffer "\n"
(( ++line_backoff_count )) # prefix-increment to avoid "error" due to returning 0
fi
(( ++line_backoff_count ))
(( line_backoff_count += ${#1} / screen_width)) # account for linebreaks due to length
buffer "%s\n" "$1"
flush
}
bats_parse_internal_extended_tap

@ -1,5 +1,6 @@
#!/usr/bin/env bash
set -e
trap '' INT
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"

@ -25,7 +25,7 @@ fi
yaml_block_open=''
add_yaml_entry() {
if [[ -z "$yaml_block_open" ]]; then
printf " ...\n"
printf " ---\n"
fi
printf " %s: %s\n" "$1" "$2"
yaml_block_open=1
@ -38,6 +38,8 @@ close_previous_yaml_block() {
fi
}
trap '' INT
while IFS= read -r line; do
case "$line" in
'begin '*) ;;

@ -31,6 +31,9 @@ bats_encode_test_name() {
printf -v "$2" '%s' "$result"
}
BATS_TEST_PATTERN="^[[:blank:]]*@test[[:blank:]]+(.*[^[:blank:]])[[:blank:]]+\{(.*)\$"
BATS_TEST_PATTERN_COMMENT="[[:blank:]]*([^[:blank:]()]+)[[:blank:]]*\(?\)?[[:blank:]]+\{[[:blank:]]+#[[:blank:]]*@test[[:blank:]]*\$"
test_file="$1"
tests=()
{

19
vendor/bats-core/man/bats.1 generated vendored

@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BATS" "1" "November 2020" "bats-core" "Bash Automated Testing System"
.TH "BATS" "1" "August 2021" "bats-core" "Bash Automated Testing System"
.
.SH "NAME"
\fBbats\fR \- Bash Automated Testing System
@ -42,6 +42,9 @@ You can invoke the \fBbats\fR interpreter with multiple test file arguments, or
\fB\-F\fR, \fB\-\-formatter <type>\fR: Switch between formatters: pretty (default), tap (default w/o term), tap13, junit
.
.IP "\(bu" 4
\fB\-\-gather\-test\-outputs\-in <directory>\fR: Gather the output of failing \fIand\fR passing tests as files in directory
.
.IP "\(bu" 4
\fB\-h\fR, \fB\-\-help\fR: Display this help message
.
.IP "\(bu" 4
@ -66,15 +69,27 @@ You can invoke the \fBbats\fR interpreter with multiple test file arguments, or
\fB\-p\fR, \fB\-\-pretty\fR: Shorthand for "\-\-formatter pretty"
.
.IP "\(bu" 4
\fB\-\-print\-output\-on\-failure\fR: Automatically print the value of \fB$output\fR on failed tests
.
.IP "\(bu" 4
\fB\-r\fR, \fB\-\-recursive\fR: Include tests in subdirectories
.
.IP "\(bu" 4
\fB\-\-show\-output\-of\-passing\-tests\fR Print output of passing tests
.
.IP "\(bu" 4
\fB\-t\fR, \fB\-\-tap\fR: Shorthand for "\-\-formatter tap"
.
.IP "\(bu" 4
\fB\-T\fR, \fB\-\-timing\fR: Add timing information to tests
.
.IP "\(bu" 4
\fB\-x\fR, \fB\-\-trace\fR: Print test commands as they are executed (like \fBset \-x\fR)
.
.IP "\(bu" 4
\fB\-\-verbose\-run\fR: Make \fBrun\fR print \fB$output\fR by default
.
.IP "\(bu" 4
\fB\-v\fR, \fB\-\-version\fR: Display the version number
.
.IP "" 0
@ -122,7 +137,7 @@ Bats wiki: \fIhttps://github\.com/bats\-core/bats\-core/wiki/\fR
\fBbash\fR(1), \fBbats\fR(7)
.
.SH "COPYRIGHT"
(c) 2017\-2018 bats\-core organization
(c) 2017\-2021 bats\-core organization
.
.br
(c) 2011\-2016 Sam Stephenson

@ -53,6 +53,8 @@ OPTIONS
Filter test cases by names matching the regular expression
* `-F`, `--formatter <type>`:
Switch between formatters: pretty (default), tap (default w/o term), tap13, junit
* `--gather-test-outputs-in <directory>`:
Gather the output of failing *and* passing tests as files in directory
* `-h`, `--help`:
Display this help message
* `-j`, `--jobs <jobs>`:
@ -69,12 +71,20 @@ OPTIONS
Directory to write report files
* `-p`, `--pretty`:
Shorthand for "--formatter pretty"
* `--print-output-on-failure`:
Automatically print the value of `$output` on failed tests
* `-r`, `--recursive`:
Include tests in subdirectories
* `--show-output-of-passing-tests`
Print output of passing tests
* `-t`, `--tap`:
Shorthand for "--formatter tap"
* `-T`, `--timing`:
Add timing information to tests
* `-x`, `--trace`:
Print test commands as they are executed (like `set -x`)
* `--verbose-run`:
Make `run` print `$output` by default
* `-v`, `--version`:
Display the version number
@ -122,7 +132,7 @@ Bats wiki: _https://github.com/bats\-core/bats\-core/wiki/_
COPYRIGHT
---------
(c) 2017-2018 bats-core organization<br/>
(c) 2017-2021 bats-core organization<br/>
(c) 2011-2016 Sam Stephenson
Bats is released under the terms of an MIT-style license.

76
vendor/bats-core/man/bats.7 generated vendored

@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BATS" "7" "November 2020" "bats-core" "Bash Automated Testing System"
.TH "BATS" "7" "August 2021" "bats-core" "Bash Automated Testing System"
.
.SH "NAME"
\fBbats\fR \- Bats test file format
@ -33,7 +33,10 @@ A Bats test file is a Bash script with special syntax for defining test cases\.
Each Bats test file is evaluated n+1 times, where \fIn\fR is the number of test cases in the file\. The first run counts the number of test cases, then iterates over the test cases and executes each one in its own process\.
.
.SH "THE RUN HELPER"
Many Bats tests need to run a command and then make assertions about its exit status and output\. Bats includes a \fBrun\fR helper that invokes its arguments as a command, saves the exit status and output into special global variables, and then returns with a \fB0\fR status code so you can continue to make assertions in your test case\.
Usage: run [OPTIONS] [\-\-] <command\.\.\.> Options: ! check for non zero exit code =\fIN\fR check that exit code is \fIN\fR \-\-output {merged,separate,stdout,stderr} control which output is recorded \-\-keep\-empty\-lines retain emtpy lines in \fB${lines[@]}\fR
.
.P
Many Bats tests need to run a command and then make assertions about its exit status and output\. Bats includes a \fBrun\fR helper that invokes its arguments as a command, saves the exit status and output into special global variables, and (optionally) checks exit status against a given expected value\. If successful, \fBrun\fR returns with a \fB0\fR status code so you can continue to make assertions in your test case\.
.
.P
For example, let\'s say you\'re testing that the \fBfoo\fR command, when passed a nonexistent filename, exits with a \fB1\fR status code and prints an error message\.
@ -43,8 +46,7 @@ For example, let\'s say you\'re testing that the \fBfoo\fR command, when passed
.nf
@test "invoking foo with a nonexistent file prints an error" {
run foo nonexistent_filename
[ "$status" \-eq 1 ]
run -1 foo nonexistent_filename
[ "$output" = "foo: no such file \'nonexistent_filename\'" ]
}
.
@ -53,6 +55,23 @@ For example, let\'s say you\'re testing that the \fBfoo\fR command, when passed
.IP "" 0
.
.P
The \fB=1\fR as first argument tells \fBrun\fR to expect 1 as an exit status, and to fail if the command exits with any other value\. On failure, both actual and expected values will be displayed, along with the invoked command and its output:
.
.IP "" 4
.
.nf
(in test file test\.bats, line 2)
`run -1 foo nonexistent_filename\' failed, expected exit code 1, got 127
.
.fi
.
.IP "" 0
.
.P
This error indicates a possible problem with the installation or configuration of \fBfoo\fR; note that a simple \fB[ $status != 0 ]\fR test would not have caught this kind of failure\.
.
.P
The \fB$status\fR variable contains the status code of the command, and the \fB$output\fR variable contains the combined contents of the command\'s standard output and standard error streams\.
.
.P
@ -63,8 +82,7 @@ A third special variable, the \fB$lines\fR array, is available for easily access
.nf
@test "invoking foo without arguments prints usage" {
run foo
[ "$status" \-eq 1 ]
run -1 foo
[ "${lines[0]}" = "usage: foo <filename>" ]
}
.
@ -72,6 +90,29 @@ A third special variable, the \fB$lines\fR array, is available for easily access
.
.IP "" 0
.
.P
By default \fBrun\fR leaves out empty lines in \fB${lines[@]}\fR\. Use \fBrun \-\-keep\-empty\-lines\fR to retain them\.
.
.P
Additionally, you can use \fBrun \-\-output <mode>\fR to control what goes into \fB$output\fR and \fB$lines\fR\. The available values for \fB<mode>\fR are:
.
.IP "\(bu" 4
\fBmerged\fR: the default when \fB\-\-output\fR is not specified, interleaves stdout and stderr
.
.IP "\(bu" 4
\fBseparate\fR: splits stderr off to \fB$stderr\fR and \fB${stderr_lines[@]}\fR, stdout is still available as \fB$output\fR and \fB${lines[@]}\fR
.
.IP "\(bu" 4
\fBstderr\fR: discards stdout and fills \'$stderr\fBand\fR${stderr_lines[@]}`
.
.IP "\(bu" 4
\fBstdout\fR: discards stdout and fills \fB$output\fR and \fB${lines[@]}\fR
.
.IP "" 0
.
.P
All additional parameters to run should come before the command\. If you want to run a command that starts with \fB\-\fR, prefix it with \fB\-\-\fR to prevent \fBrun\fR from parsing it as an option\.
.
.SH "THE LOAD COMMAND"
You may want to share common code across multiple test files\. Bats includes a convenient \fBload\fR command for sourcing a Bash source file relative to the location of the current test file\. For example, if you have a Bats test in \fBtest/foo\.bats\fR, the command
.
@ -97,8 +138,7 @@ Tests can be skipped by using the \fBskip\fR command at the point in a test you
@test "A test I don\'t want to execute for now" {
skip
run foo
[ "$status" \-eq 0 ]
run -0 foo
}
.
.fi
@ -114,8 +154,7 @@ Optionally, you may include a reason for skipping:
@test "A test I don\'t want to execute for now" {
skip "This command will return zero soon, but not now"
run foo
[ "$status" \-eq 0 ]
run -0 foo
}
.
.fi
@ -134,8 +173,7 @@ Or you can skip conditionally:
skip "foo isn\'t bar"
fi
run foo
[ "$status" \-eq 0 ]
run -0 foo
}
.
.fi
@ -173,11 +211,23 @@ There are several global variables you can use to introspect on Bats tests:
\fB$BATS_SUITE_TEST_NUMBER\fR is the (1\-based) index of the current test case in the test suite (over all files)\.
.
.IP "\(bu" 4
\fB$BATS_TMPDIR\fR is the location to a directory that may be used to store temporary files\.
\fB$BATS_TMPDIR\fR is the base temporary directory used by bats to create its temporary files / directories\. (default: \fB$TMPDIR\fR\. If \fB$TMPDIR\fR is not set, \fB/tmp\fR is used\.)
.
.IP "\(bu" 4
\fB$BATS_RUN_TMPDIR\fR is the location to the temporary directory used by bats to store all its internal temporary files during the tests\. (default: \fB$BATS_TMPDIR/bats\-run\-$BATS_ROOT_PID\-XXXXXX\fR)
.
.IP "\(bu" 4
\fB$BATS_FILE_EXTENSION\fR (default: \fBbats\fR) specifies the extension of test files that should be found when running a suite (via \fBbats [\-r] suite_folder/\fR)
.
.IP "\(bu" 4
\fB$BATS_SUITE_TMPDIR\fR is a temporary directory common to all tests of a suite\. Could be used to create files required by multiple tests\.
.
.IP "\(bu" 4
\fB$BATS_FILE_TMPDIR\fR is a temporary directory common to all tests of a test file\. Could be used to create files required by multiple tests in the same test file\.
.
.IP "\(bu" 4
\fB$BATS_TEST_TMPDIR\fR is a temporary directory unique for each test\. Could be used to create files required only for specific tests\.
.
.IP "" 0
.
.SH "SEE ALSO"

@ -31,22 +31,43 @@ process.
THE RUN HELPER
--------------
Usage: run [OPTIONS] [--] <command...>
Options:
! check for non zero exit code
-<N> check that exit code is <N>
--separate-stderr
split stderr and stdout
--keep-empty-lines
retain emtpy lines in `${lines[@]}`/`${stderr_lines[@]}`
Many Bats tests need to run a command and then make assertions about
its exit status and output. Bats includes a `run` helper that invokes
its arguments as a command, saves the exit status and output into
special global variables, and then returns with a `0` status code so
you can continue to make assertions in your test case.
special global variables, and (optionally) checks exit status against
a given expected value. If successful, `run` returns with a `0` status
code so you can continue to make assertions in your test case.
For example, let's say you're testing that the `foo` command, when
passed a nonexistent filename, exits with a `1` status code and prints
an error message.
@test "invoking foo with a nonexistent file prints an error" {
run foo nonexistent_filename
[ "$status" -eq 1 ]
run -1 foo nonexistent_filename
[ "$output" = "foo: no such file 'nonexistent_filename'" ]
}
The `-1` as first argument tells `run` to expect 1 as an exit
status, and to fail if the command exits with any other value.
On failure, both actual and expected values will be displayed,
along with the invoked command and its output:
(in test file test.bats, line 2)
`run -1 foo nonexistent_filename' failed, expected exit code 1, got 127
This error indicates a possible problem with the installation or
configuration of `foo`; note that a simple `[ $status != 0 ]`
test would not have caught this kind of failure.
The `$status` variable contains the status code of the command, and
the `$output` variable contains the combined contents of the command's
standard output and standard error streams.
@ -57,11 +78,18 @@ that invoking `foo` without any arguments prints usage information on
the first line:
@test "invoking foo without arguments prints usage" {
run foo
[ "$status" -eq 1 ]
run -1 foo
[ "${lines[0]}" = "usage: foo <filename>" ]
}
By default `run` leaves out empty lines in `${lines[@]}`. Use `run --keep-empty-lines` to retain them.
Additionally, you can use `--separate-stderr` to split stdout and stderr
into `$output`/`$stderr` and `${lines[@]}`/`${stderr_lines[@]}`.
All additional parameters to run should come before the command.
If you want to run a command that starts with `-`, prefix it with `--` to
prevent `run` from parsing it as an option.
THE LOAD COMMAND
----------------
@ -86,16 +114,14 @@ test you wish to skip.
@test "A test I don't want to execute for now" {
skip
run foo
[ "$status" -eq 0 ]
run -0 foo
}
Optionally, you may include a reason for skipping:
@test "A test I don't want to execute for now" {
skip "This command will return zero soon, but not now"
run foo
[ "$status" -eq 0 ]
run -0 foo
}
Or you can skip conditionally:
@ -105,8 +131,7 @@ Or you can skip conditionally:
skip "foo isn't bar"
fi
run foo
[ "$status" -eq 0 ]
run -0 foo
}
@ -148,11 +173,21 @@ case.
in the test file.
* `$BATS_SUITE_TEST_NUMBER` is the (1-based) index of the current test
case in the test suite (over all files).
* `$BATS_TMPDIR` is the location to a directory that may be used to
store temporary files.
* `$BATS_TMPDIR` is the base temporary directory used by bats to create its
temporary files / directories.
(default: `$TMPDIR`. If `$TMPDIR` is not set, `/tmp` is used.)
* `$BATS_RUN_TMPDIR` is the location to the temporary directory used by
bats to store all its internal temporary files during the tests.
(default: `$BATS_TMPDIR/bats-run-$BATS_ROOT_PID-XXXXXX`)
* `$BATS_FILE_EXTENSION` (default: `bats`) specifies the extension of
test files that should be found when running a suite (via
`bats [-r] suite_folder/`)
* `$BATS_SUITE_TMPDIR` is a temporary directory common to all tests of a suite.
Could be used to create files required by multiple tests.
* `$BATS_FILE_TMPDIR` is a temporary directory common to all tests of a test file.
Could be used to create files required by multiple tests in the same test file.
* `$BATS_TEST_TMPDIR` is a temporary directory unique for each test.
Could be used to create files required only for specific tests.
SEE ALSO

@ -1,6 +1,6 @@
{
"name": "bats",
"version": "1.3.0",
"version": "1.5.0",
"description": "Bash Automated Testing System",
"homepage": "https://github.com/bats-core/bats-core#readme",
"license": "MIT",
@ -11,7 +11,9 @@
"bin",
"libexec",
"lib",
"man"
"man",
"install.sh",
"uninstall.sh"
],
"directories": {
"bin": "bin",

@ -6,14 +6,15 @@ targets=()
while IFS= read -r -d $'\0'; do
targets+=("$REPLY")
done < <(
find \
bin/bats \
libexec/bats-core \
lib/bats-core \
shellcheck.sh \
-type f \
-print0
)
find . -type f \( -name \*.bash -o -name \*.sh \) -print0; \
find . -name '*.bats' -not -name '*_no_shellcheck*' -print0; \
find libexec -type f -print0;
find bin -type f -print0)
if [[ $1 == --list ]]; then
printf "%s\n" "${targets[@]}"
exit 0
fi
LC_ALL=C.UTF-8 shellcheck "${targets[@]}"

@ -1,11 +1,8 @@
#!/usr/bin/env bats
load test_helper
fixtures bats
teardown() {
# cleanup the test local tmpdir to avoid cleaning up all tests' at once
test_helper::cleanup_tmpdir "$BATS_TEST_NAME"
setup() {
load test_helper
fixtures bats
}
@test "no arguments prints message and usage instructions" {
@ -25,7 +22,7 @@ teardown() {
@test "-v and --version print version number" {
run bats -v
[ $status -eq 0 ]
[ $(expr "$output" : "Bats [0-9][0-9.]*") -ne 0 ]
[ "$(expr "$output" : "Bats [0-9][0-9.]*")" -ne 0 ]
}
@test "-h and --help print help" {
@ -37,7 +34,7 @@ teardown() {
@test "invalid filename prints an error" {
run bats nonexistent
[ $status -eq 1 ]
[ $(expr "$output" : ".*does not exist") -ne 0 ]
[ "$(expr "$output" : ".*does not exist")" -ne 0 ]
}
@test "empty test file runs zero tests" {
@ -143,7 +140,8 @@ teardown() {
}
@test "setup is run once before each test" {
make_bats_test_suite_tmpdir "$BATS_TEST_NAME"
# shellcheck disable=SC2031,SC2030
export BATS_TEST_SUITE_TMPDIR="${BATS_TEST_TMPDIR}"
run bats "$FIXTURE_ROOT/setup.bats"
[ $status -eq 0 ]
run cat "$BATS_TEST_SUITE_TMPDIR/setup.log"
@ -151,7 +149,8 @@ teardown() {
}
@test "teardown is run once after each test, even if it fails" {
make_bats_test_suite_tmpdir "$BATS_TEST_NAME"
# shellcheck disable=SC2031,SC2030
export BATS_TEST_SUITE_TMPDIR="${BATS_TEST_TMPDIR}"
run bats "$FIXTURE_ROOT/teardown.bats"
[ $status -eq 1 ]
run cat "$BATS_TEST_SUITE_TMPDIR/teardown.log"
@ -190,8 +189,7 @@ teardown() {
}
@test "failing test file outside of BATS_CWD" {
make_bats_test_suite_tmpdir "$BATS_TEST_NAME"
cd "$BATS_TEST_SUITE_TMPDIR"
cd "${BATS_TEST_TMPDIR}"
run bats "$FIXTURE_ROOT/failing.bats"
[ $status -eq 1 ]
[ "${lines[2]}" = "# (in test file $FIXTURE_ROOT/failing.bats, line 4)" ]
@ -236,22 +234,23 @@ teardown() {
}
@test "load supports plain symbols" {
local -r helper="${BATS_TMPDIR}/load_helper_plain"
local -r helper="${BATS_TEST_TMPDIR}/load_helper_plain"
{
echo "plain_variable='value of plain variable'"
echo "plain_array=(test me hard)"
} > "${helper}"
load "${helper}"
# shellcheck disable=SC2154
[ "${plain_variable}" = 'value of plain variable' ]
# shellcheck disable=SC2154
[ "${plain_array[2]}" = 'hard' ]
rm "${helper}"
}
@test "load doesn't support _declare_d symbols" {
local -r helper="${BATS_TMPDIR}/load_helper_declared"
local -r helper="${BATS_TEST_TMPDIR}/load_helper_declared"
{
echo "declare declared_variable='value of declared variable'"
echo "declare -r a_constant='constant value'"
@ -262,11 +261,11 @@ teardown() {
load "${helper}"
! [ "${declared_variable:-}" = 'value of declared variable' ]
! [ "${a_constant:-}" = 'constant value' ]
! (( "${an_integer:-2019}" == 2020 ))
! [ "${an_array[2]:-}" = 'hard' ]
! [ "${exported_variable:-}" = 'value of exported variable' ]
[ "${declared_variable:-}" != 'value of declared variable' ]
[ "${a_constant:-}" != 'constant value' ]
(( "${an_integer:-2019}" != 2020 ))
[ "${an_array[2]:-}" != 'hard' ]
[ "${exported_variable:-}" != 'value of exported variable' ]
rm "${helper}"
}
@ -296,7 +295,7 @@ teardown() {
}
@test "dos line endings are stripped before testing" {
run bats "$FIXTURE_ROOT/dos_line.bats"
run bats "$FIXTURE_ROOT/dos_line_no_shellcheck.bats"
[ $status -eq 0 ]
}
@ -320,7 +319,7 @@ teardown() {
# Some systems (Alpine, for example) seem to emit an extra whitespace into
# entries in the 'lines' array when a carriage return is present from the
# pretty formatter. This is why a '+' is used after the 'skipped' note.
[[ "${lines[@]}" =~ "- a skipped test with parentheses in the reason (skipped: "+"a reason (with parentheses))" ]]
[[ "${lines[*]}" =~ "- a skipped test with parentheses in the reason (skipped: "+"a reason (with parentheses))" ]]
}
@test "extended syntax" {
@ -387,13 +386,13 @@ teardown() {
}
@test "single-line tests" {
run bats "$FIXTURE_ROOT/single_line.bats"
run bats "$FIXTURE_ROOT/single_line_no_shellcheck.bats"
[ $status -eq 1 ]
[ "${lines[1]}" = 'ok 1 empty' ]
[ "${lines[2]}" = 'ok 2 passing' ]
[ "${lines[3]}" = 'ok 3 input redirection' ]
[ "${lines[4]}" = 'not ok 4 failing' ]
[ "${lines[5]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/single_line.bats, line 9)" ]
[ "${lines[5]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/single_line_no_shellcheck.bats, line 9)" ]
[ "${lines[6]}" = $'# `@test "failing" { false; }\' failed' ]
}
@ -410,7 +409,7 @@ teardown() {
}
@test "handle quoted and unquoted test names" {
run bats "$FIXTURE_ROOT/quoted_and_unquoted_test_names.bats"
run bats "$FIXTURE_ROOT/quoted_and_unquoted_test_names_no_shellcheck.bats"
[ $status -eq 0 ]
[ "${lines[1]}" = "ok 1 single-quoted name" ]
[ "${lines[2]}" = "ok 2 double-quoted name" ]
@ -420,10 +419,10 @@ teardown() {
@test 'ensure compatibility with unofficial Bash strict mode' {
local expected='ok 1 unofficial Bash strict mode conditions met'
# Run Bats under `set -u` to catch as many unset variable accesses as
# possible.
run bash -u "${BATS_TEST_DIRNAME%/*}/bin/bats" \
"$FIXTURE_ROOT/unofficial_bash_strict_mode.bats"
# Run Bats under SHELLOPTS=nounset (recursive `set -u`) to catch
# as many unset variable accesses as possible.
run run_under_clean_bats_env env SHELLOPTS=nounset \
"${BATS_ROOT}/bin/bats" "$FIXTURE_ROOT/unofficial_bash_strict_mode.bats"
if [[ "$status" -ne 0 || "${lines[1]}" != "$expected" ]]; then
cat <<END_OF_ERR_MSG
@ -441,7 +440,7 @@ See:
If there is no error output from the test fixture, run the following to
debug the problem:
$ bash -u bats $RELATIVE_FIXTURE_ROOT/unofficial_bash_strict_mode.bats
$ SHELLOPTS=nounset bats $RELATIVE_FIXTURE_ROOT/unofficial_bash_strict_mode.bats
If there's no error output even with this command, make sure you're using the
latest version of Bash, as versions before 4.1-alpha may not produce any error
@ -459,7 +458,7 @@ END_OF_ERR_MSG
}
@test "parse @test lines with various whitespace combinations" {
run bats "$FIXTURE_ROOT/whitespace.bats"
run bats "$FIXTURE_ROOT/whitespace_no_shellcheck.bats"
[ $status -eq 0 ]
[ "${lines[1]}" = 'ok 1 no extra whitespace' ]
[ "${lines[2]}" = 'ok 2 tab at beginning of line' ]
@ -475,11 +474,11 @@ END_OF_ERR_MSG
}
@test "duplicate tests error and generate a warning on stderr" {
run bats --tap "$FIXTURE_ROOT/duplicate-tests.bats"
run bats --tap "$FIXTURE_ROOT/duplicate-tests_no_shellcheck.bats"
[ $status -eq 1 ]
local expected='Error: Duplicate test name(s) in file '
expected+="\"${FIXTURE_ROOT}/duplicate-tests.bats\": test_gizmo_test"
expected+="\"${FIXTURE_ROOT}/duplicate-tests_no_shellcheck.bats\": test_gizmo_test"
printf 'expected: "%s"\n' "$expected" >&2
printf 'actual: "%s"\n' "${lines[0]}" >&2
@ -493,7 +492,7 @@ END_OF_ERR_MSG
run bats "$FIXTURE_ROOT/source_nonexistent_file_in_setup.bats"
[ $status -eq 1 ]
[ "${lines[1]}" = 'not ok 1 sourcing nonexistent file fails in setup' ]
[ "${lines[2]}" = "# (from function \`setup' in test file $RELATIVE_FIXTURE_ROOT/source_nonexistent_file_in_setup.bats, line 2)" ]
[ "${lines[2]}" = "# (from function \`setup' in test file $RELATIVE_FIXTURE_ROOT/source_nonexistent_file_in_setup.bats, line 3)" ]
[ "${lines[3]}" = "# \`source \"nonexistent file\"' failed" ]
}
@ -501,7 +500,7 @@ END_OF_ERR_MSG
run bats "$FIXTURE_ROOT/reference_unset_parameter_in_setup.bats"
[ $status -eq 1 ]
[ "${lines[1]}" = 'not ok 1 referencing unset parameter fails in setup' ]
[ "${lines[2]}" = "# (from function \`setup' in test file $RELATIVE_FIXTURE_ROOT/reference_unset_parameter_in_setup.bats, line 3)" ]
[ "${lines[2]}" = "# (from function \`setup' in test file $RELATIVE_FIXTURE_ROOT/reference_unset_parameter_in_setup.bats, line 4)" ]
[ "${lines[3]}" = "# \`echo \"\$unset_parameter\"' failed" ]
}
@ -509,7 +508,7 @@ END_OF_ERR_MSG
run bats "$FIXTURE_ROOT/source_nonexistent_file.bats"
[ $status -eq 1 ]
[ "${lines[1]}" = 'not ok 1 sourcing nonexistent file fails' ]
[ "${lines[2]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/source_nonexistent_file.bats, line 2)" ]
[ "${lines[2]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/source_nonexistent_file.bats, line 3)" ]
[ "${lines[3]}" = "# \`source \"nonexistent file\"' failed" ]
}
@ -517,7 +516,7 @@ END_OF_ERR_MSG
run bats "$FIXTURE_ROOT/reference_unset_parameter.bats"
[ $status -eq 1 ]
[ "${lines[1]}" = 'not ok 1 referencing unset parameter fails' ]
[ "${lines[2]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/reference_unset_parameter.bats, line 3)" ]
[ "${lines[2]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/reference_unset_parameter.bats, line 4)" ]
[ "${lines[3]}" = "# \`echo \"\$unset_parameter\"' failed" ]
}
@ -525,7 +524,7 @@ END_OF_ERR_MSG
run bats "$FIXTURE_ROOT/source_nonexistent_file_in_teardown.bats"
[ $status -eq 1 ]
[ "${lines[1]}" = 'not ok 1 sourcing nonexistent file fails in teardown' ]
[ "${lines[2]}" = "# (from function \`teardown' in test file $RELATIVE_FIXTURE_ROOT/source_nonexistent_file_in_teardown.bats, line 2)" ]
[ "${lines[2]}" = "# (from function \`teardown' in test file $RELATIVE_FIXTURE_ROOT/source_nonexistent_file_in_teardown.bats, line 3)" ]
[ "${lines[3]}" = "# \`source \"nonexistent file\"' failed" ]
}
@ -533,7 +532,7 @@ END_OF_ERR_MSG
run bats "$FIXTURE_ROOT/reference_unset_parameter_in_teardown.bats"
[ $status -eq 1 ]
[ "${lines[1]}" = 'not ok 1 referencing unset parameter fails in teardown' ]
[ "${lines[2]}" = "# (from function \`teardown' in test file $RELATIVE_FIXTURE_ROOT/reference_unset_parameter_in_teardown.bats, line 3)" ]
[ "${lines[2]}" = "# (from function \`teardown' in test file $RELATIVE_FIXTURE_ROOT/reference_unset_parameter_in_teardown.bats, line 4)" ]
[ "${lines[3]}" = "# \`echo \"\$unset_parameter\"' failed" ]
}
@ -554,13 +553,17 @@ END_OF_ERR_MSG
printf 'num lines: %d\n' "${#lines[@]}" >&2
printf 'LINE: %s\n' "${lines[@]}" >&2
[ "$status" -eq 1 ]
[ "${#lines[@]}" -eq 7 ]
[ "${lines[1]}" = 'not ok 1 no final newline' ]
[ "${lines[2]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/no-final-newline.bats, line 2)" ]
[ "${lines[3]}" = "# \`printf 'foo\nbar\nbaz' >&2 && return 1' failed" ]
[ "${#lines[@]}" -eq 11 ]
[ "${lines[1]}" = 'not ok 1 error in test' ]
[ "${lines[2]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/no-final-newline.bats, line 3)" ]
[ "${lines[3]}" = "# \`false' failed" ]
[ "${lines[4]}" = '# foo' ]
[ "${lines[5]}" = '# bar' ]
[ "${lines[6]}" = '# baz' ]
[ "${lines[6]}" = 'not ok 2 test function returns nonzero' ]
[ "${lines[7]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/no-final-newline.bats, line 7)" ]
[ "${lines[8]}" = "# \`printf 'foo\nbar'' failed" ]
[ "${lines[9]}" = '# foo' ]
[ "${lines[10]}" = '# bar' ]
}
@test "run tests which consume stdin (see #197)" {
@ -577,13 +580,15 @@ END_OF_ERR_MSG
[ "$status" -eq 1 ]
[ "${#lines[@]}" -eq 9 ]
[ "${lines[1]}" = 'not ok 1 access unbound variable' ]
[ "${lines[2]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/unbound_variable.bats, line 8)" ]
[ "${lines[2]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/unbound_variable.bats, line 9)" ]
[ "${lines[3]}" = "# \`foo=\$unset_variable' failed" ]
[[ "${lines[4]}" =~ ".src: line 8:" ]]
# shellcheck disable=SC2076
[[ "${lines[4]}" =~ ".src: line 9:" ]]
[ "${lines[5]}" = 'not ok 2 access second unbound variable' ]
[ "${lines[6]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/unbound_variable.bats, line 13)" ]
[ "${lines[6]}" = "# (in test file $RELATIVE_FIXTURE_ROOT/unbound_variable.bats, line 15)" ]
[ "${lines[7]}" = "# \`foo=\$second_unset_variable' failed" ]
[[ "${lines[8]}" =~ ".src: line 13:" ]]
# shellcheck disable=SC2076
[[ "${lines[8]}" =~ ".src: line 15:" ]]
}
@test "report correct line on external function calls" {
@ -599,9 +604,9 @@ END_OF_ERR_MSG
linesPerTest=5
for t in $(seq $expectedNumberOfTests); do
[[ "${lines[$outputOffset]}" =~ "not ok $t " ]]
[[ "${lines[$outputOffset]}" == "not ok $t "* ]]
# Skip backtrace into external function if set
if [[ "${lines[$((outputOffset + 1))]}" =~ "# (from function " ]]; then
if [[ "${lines[$((outputOffset + 1))]}" == "# (from function "* ]]; then
outputOffset=$((outputOffset + 1))
parenChar=" "
else
@ -616,6 +621,7 @@ END_OF_ERR_MSG
}
@test "test count validator catches mismatch and returns non zero" {
# shellcheck source=lib/bats-core/validator.bash
source "$BATS_ROOT/lib/bats-core/validator.bash"
export -f bats_test_count_validator
run bash -c "echo $'1..1\n' | bats_test_count_validator"
@ -651,13 +657,13 @@ END_OF_ERR_MSG
@test "filenames with tab can be used" {
[[ "$OSTYPE" == "linux"* ]] || skip "FS cannot deal with tabs in filenames"
cp "$FIXTURE_ROOT/tab in filename.bats" "$FIXTURE_ROOT/tab"$'\t'"in filename.bats"
bats "$FIXTURE_ROOT/tab"$'\t'"in filename.bats"
cp "${FIXTURE_ROOT}/tab in filename.bats" "${BATS_TEST_TMPDIR}/tab"$'\t'"in filename.bats"
bats "${BATS_TEST_TMPDIR}/tab"$'\t'"in filename.bats"
}
@test "each file is evaluated n+1 times" {
make_bats_test_suite_tmpdir
export TEMPFILE="$BATS_TEST_SUITE_TMPDIR/$BATS_TEST_NAME.log"
# shellcheck disable=SC2031,SC2030
export TEMPFILE="$BATS_TEST_TMPDIR/$BATS_TEST_NAME.log"
run bats "$FIXTURE_ROOT/evaluation_count/"
cat "$TEMPFILE"
@ -670,13 +676,16 @@ END_OF_ERR_MSG
}
@test "Don't hang on CTRL-C (issue #353)" {
load 'concurrent-coordination'
# shellcheck disable=SC2031,SC2030
export SINGLE_USE_LATCH_DIR="${BATS_TEST_TMPDIR}"
# guarantee that background processes get their own process group -> pid=pgid
set -m
run bats "$FIXTURE_ROOT/run_long_command.bats" & # don't block execution, or we cannot send signals
echo "$output"
bats "$FIXTURE_ROOT/hang_in_test.bats" & # don't block execution, or we cannot send signals
SUBPROCESS_PID=$!
sleep 1 # wait for the background process to start on slow systems
single-use-latch::wait hang_in_test 1
# emulate CTRL-C by sending SIGINT to the whole process group
kill -SIGINT -- -$SUBPROCESS_PID
@ -720,18 +729,421 @@ END_OF_ERR_MSG
}
@test "run tmpdir is cleaned up by default" {
TEST_TMPDIR="${BATS_RUN_TMPDIR}/$BATS_TEST_NAME"
TEST_TMPDIR="${BATS_TEST_TMPDIR}/$BATS_TEST_NAME"
bats --tempdir "$TEST_TMPDIR" "$FIXTURE_ROOT/passing.bats"
[ ! -d "$TEST_TMPDIR" ]
}
@test "run tmpdir is not cleanup up with --no-cleanup-tempdir" {
TEST_TMPDIR="${BATS_RUN_TMPDIR}/$BATS_TEST_NAME"
TEST_TMPDIR="${BATS_TEST_TMPDIR}/$BATS_TEST_NAME"
bats --tempdir "$TEST_TMPDIR" --no-tempdir-cleanup "$FIXTURE_ROOT/passing.bats"
[ -d "$TEST_TMPDIR" ]
# should also find preprocessed files!
[ $(find "$TEST_TMPDIR" -name '*.src' | wc -l) -eq 1 ]
[ "$(find "$TEST_TMPDIR" -name '*.src' | wc -l)" -eq 1 ]
}
@test "All formatters (except cat) implement the callback interface" {
cd "$BATS_ROOT/libexec/bats-core/"
for formatter in bats-format-*; do
# the cat formatter is not expected to implement this interface
if [[ "$formatter" == *"bats-format-cat" ]]; then
continue
fi
tested_at_least_one_formatter=1
echo "Formatter: ${formatter}"
# the replay should be possible without errors
"$formatter" >/dev/null <<EOF
1..1
suite "$BATS_FIXTURE_ROOT/failing.bats"
begin 1 test_a_failing_test
not ok 1 a failing test
# (in test file test/fixtures/bats/failing.bats, line 4)
# \`eval "( exit ${STATUS:-1} )"' failed
begin 2 test_a_successful_test
ok 2 a succesful test
unknown line
EOF
done
[[ -n "$tested_at_least_one_formatter" ]]
}
@test "run should exit if tmpdir exist" {
local dir
dir=$(mktemp -d "${BATS_RUN_TMPDIR}/BATS_RUN_TMPDIR_TEST.XXXXXX")
run bats --tempdir "${dir}" "$FIXTURE_ROOT/passing.bats"
[ "$status" -eq 1 ]
[ "${lines[0]}" == "Error: BATS_RUN_TMPDIR (${dir}) already exists" ]
[ "${lines[1]}" == "Reusing old run directories can lead to unexpected results ... aborting!" ]
}
@test "run should exit if TMPDIR can't be created" {
local dir
dir=$(mktemp "${BATS_RUN_TMPDIR}/BATS_RUN_TMPDIR_TEST.XXXXXX")
run bats --tempdir "${dir}" "$FIXTURE_ROOT/passing.bats"
[ "$status" -eq 1 ]
[ "${lines[1]}" == "Error: Failed to create BATS_RUN_TMPDIR (${dir})" ]
}
@test "Fail if BATS_TMPDIR does not exist or is not writable" {
# shellcheck disable=SC2031,SC2030
export TMPDIR
TMPDIR=$(mktemp -u "${BATS_RUN_TMPDIR}/donotexist.XXXXXX")
run bats "$FIXTURE_ROOT/BATS_TMPDIR.bats"
echo "$output"
[ "$status" -eq 1 ]
[ "${lines[0]}" = "Error: BATS_TMPDIR (${TMPDIR}) does not exist or is not a directory" ]
}
@test "Setting BATS_TMPDIR is ignored" {
unset TMPDIR # ensure we don't have a predefined value
expected="/tmp" run bats "$FIXTURE_ROOT/BATS_TMPDIR.bats"
echo "$output"
[ "$status" -eq 0 ]
BATS_TMPDIR="${BATS_RUN_TMPDIR}" expected="/tmp" run bats "$FIXTURE_ROOT/BATS_TMPDIR.bats"
[ "$status" -eq 0 ]
}
@test "Parallel mode works on MacOS with over subscription (issue #433)" {
type -p parallel &>/dev/null || skip "--jobs requires GNU parallel"
(type -p flock &>/dev/null || type -p shlock &>/dev/null) || skip "--jobs requires flock/shlock"
run bats -j 2 "$FIXTURE_ROOT/issue-433"
[ "$status" -eq 0 ]
[[ "$output" != *"No such file or directory"* ]] || exit 1 # ensure failures are detected with old bash
}
@test "Failure in free code (see #399)" {
run bats --tap "$FIXTURE_ROOT/failure_in_free_code.bats"
echo "$output"
[ "$status" -ne 0 ]
[ "${lines[0]}" == 1..1 ]
[ "${lines[1]}" == 'not ok 1 setup_file failed' ]
[ "${lines[2]}" == "# (from function \`helper' in file $RELATIVE_FIXTURE_ROOT/failure_in_free_code.bats, line 4," ]
[ "${lines[3]}" == "# in test file $RELATIVE_FIXTURE_ROOT/failure_in_free_code.bats, line 7)" ]
[ "${lines[4]}" == "# \`helper' failed" ]
}
@test "CTRL-C aborts and fails the current test" {
if [[ "$BATS_NUMBER_OF_PARALLEL_JOBS" -gt 1 ]]; then
skip "Aborts don't work in parallel mode"
fi
# shellcheck disable=SC2031,SC2030
export TEMPFILE="$BATS_TEST_TMPDIR/$BATS_TEST_NAME.log"
# guarantee that background processes get their own process group -> pid=pgid
set -m
load 'concurrent-coordination'
# shellcheck disable=SC2031,SC2030
export SINGLE_USE_LATCH_DIR="${BATS_SUITE_TMPDIR}"
# we cannot use run for a background task, so we have to store the output for later
bats "$FIXTURE_ROOT/hang_in_test.bats" --tap >"$TEMPFILE" 2>&1 & # don't block execution, or we cannot send signals
SUBPROCESS_PID=$!
single-use-latch::wait hang_in_test 1 10 || (cat "$TEMPFILE"; false) # still forward output on timeout
# emulate CTRL-C by sending SIGINT to the whole process group
kill -SIGINT -- -$SUBPROCESS_PID
# the test suite must be marked as failed!
wait $SUBPROCESS_PID && return 1
run cat "$TEMPFILE"
echo "$output"
[[ "${lines[1]}" == "not ok 1 test" ]]
[[ "${lines[2]}" == "# (in test file ${RELATIVE_FIXTURE_ROOT}/hang_in_test.bats, line 7)" ]]
[[ "${lines[3]}" == "# \`sleep 10' failed with status 130" ]]
[[ "${lines[4]}" == "# Received SIGINT, aborting ..." ]]
}
@test "CTRL-C aborts and fails the current run" {
if [[ "$BATS_NUMBER_OF_PARALLEL_JOBS" -gt 1 ]]; then
skip "Aborts don't work in parallel mode"
fi
# shellcheck disable=SC2031,2030
export TEMPFILE="$BATS_TEST_TMPDIR/$BATS_TEST_NAME.log"
# guarantee that background processes get their own process group -> pid=pgid
set -m
load 'concurrent-coordination'
# shellcheck disable=SC2031,SC2030
export SINGLE_USE_LATCH_DIR="${BATS_SUITE_TMPDIR}"
# we cannot use run for a background task, so we have to store the output for later
bats "$FIXTURE_ROOT/hang_in_run.bats" --tap >"$TEMPFILE" 2>&1 & # don't block execution, or we cannot send signals
SUBPROCESS_PID=$!
single-use-latch::wait hang_in_run 1 10
# emulate CTRL-C by sending SIGINT to the whole process group
kill -SIGINT -- -$SUBPROCESS_PID
# the test suite must be marked as failed!
wait $SUBPROCESS_PID && return 1
run cat "$TEMPFILE"
[ "${lines[1]}" == "not ok 1 test" ]
[ "${lines[2]}" == "# (in test file ${RELATIVE_FIXTURE_ROOT}/hang_in_run.bats, line 7)" ]
[ "${lines[3]}" == "# \`run sleep 10' failed with status 130" ]
[ "${lines[4]}" == "# Received SIGINT, aborting ..." ]
}
@test "CTRL-C aborts and fails the current teardown" {
if [[ "$BATS_NUMBER_OF_PARALLEL_JOBS" -gt 1 ]]; then
skip "Aborts don't work in parallel mode"
fi
# shellcheck disable=SC2031,SC2030
export TEMPFILE="$BATS_TEST_TMPDIR/$BATS_TEST_NAME.log"
# guarantee that background processes get their own process group -> pid=pgid
set -m
load 'concurrent-coordination'
# shellcheck disable=SC2031,SC2030
export SINGLE_USE_LATCH_DIR="${BATS_SUITE_TMPDIR}"
# we cannot use run for a background task, so we have to store the output for later
bats "$FIXTURE_ROOT/hang_in_teardown.bats" --tap >"$TEMPFILE" 2>&1 & # don't block execution, or we cannot send signals
SUBPROCESS_PID=$!
single-use-latch::wait hang_in_teardown 1 10
# emulate CTRL-C by sending SIGINT to the whole process group
kill -SIGINT -- -$SUBPROCESS_PID
# the test suite must be marked as failed!
wait $SUBPROCESS_PID && return 1
run cat "$TEMPFILE"
echo "$output"
[[ "${lines[1]}" == "not ok 1 empty" ]]
[[ "${lines[2]}" == "# (from function \`teardown' in test file ${RELATIVE_FIXTURE_ROOT}/hang_in_teardown.bats, line 4)" ]]
[[ "${lines[3]}" == "# \`sleep 10' failed" ]]
[[ "${lines[4]}" == "# Received SIGINT, aborting ..." ]]
}
@test "CTRL-C aborts and fails the current setup_file" {
if [[ "$BATS_NUMBER_OF_PARALLEL_JOBS" -gt 1 ]]; then
skip "Aborts don't work in parallel mode"
fi
# shellcheck disable=SC2031,SC2030
export TEMPFILE="$BATS_TEST_TMPDIR/$BATS_TEST_NAME.log"
# guarantee that background processes get their own process group -> pid=pgid
set -m
load 'concurrent-coordination'
# shellcheck disable=SC2031,SC2030
export SINGLE_USE_LATCH_DIR="${BATS_SUITE_TMPDIR}"
# we cannot use run for a background task, so we have to store the output for later
bats "$FIXTURE_ROOT/hang_in_setup_file.bats" --tap >"$TEMPFILE" 2>&1 & # don't block execution, or we cannot send signals
SUBPROCESS_PID=$!
single-use-latch::wait hang_in_setup_file 1 10
# emulate CTRL-C by sending SIGINT to the whole process group
kill -SIGINT -- -$SUBPROCESS_PID
# the test suite must be marked as failed!
wait $SUBPROCESS_PID && return 1
run cat "$TEMPFILE"
echo "$output"
[[ "${lines[1]}" == "not ok 1 setup_file failed" ]]
[[ "${lines[2]}" == "# (from function \`setup_file' in test file ${RELATIVE_FIXTURE_ROOT}/hang_in_setup_file.bats, line 4)" ]]
[[ "${lines[3]}" == "# \`sleep 10' failed with status 130" ]]
[[ "${lines[4]}" == "# Received SIGINT, aborting ..." ]]
}
@test "CTRL-C aborts and fails the current teardown_file" {
if [[ "$BATS_NUMBER_OF_PARALLEL_JOBS" -gt 1 ]]; then
skip "Aborts don't work in parallel mode"
fi
# shellcheck disable=SC2031
export TEMPFILE="${BATS_TEST_TMPDIR}/$BATS_TEST_NAME.log"
# guarantee that background processes get their own process group -> pid=pgid
set -m
load 'concurrent-coordination'
# shellcheck disable=SC2031
export SINGLE_USE_LATCH_DIR="${BATS_SUITE_TMPDIR}"
# we cannot use run for a background task, so we have to store the output for later
bats "$FIXTURE_ROOT/hang_in_teardown_file.bats" --tap >"$TEMPFILE" 2>&1 & # don't block execution, or we cannot send signals
SUBPROCESS_PID=$!
single-use-latch::wait hang_in_teardown_file 1 10
# emulate CTRL-C by sending SIGINT to the whole process group
kill -SIGINT -- -$SUBPROCESS_PID
# the test suite must be marked as failed!
wait $SUBPROCESS_PID && return 1
run cat "$TEMPFILE"
echo "$output"
[[ "${lines[0]}" == "1..1" ]]
[[ "${lines[1]}" == "ok 1 empty" ]]
[[ "${lines[2]}" == "not ok 2 teardown_file failed" ]]
[[ "${lines[3]}" == "# (from function \`teardown_file' in test file ${RELATIVE_FIXTURE_ROOT}/hang_in_teardown_file.bats, line 4)" ]]
[[ "${lines[4]}" == "# \`sleep 10' failed with status 130" ]]
[[ "${lines[5]}" == "# Received SIGINT, aborting ..." ]]
[[ "${lines[6]}" == "# bats warning: Executed 2 instead of expected 1 tests" ]]
}
@test "single star in output is not treated as a glob" {
star(){ echo '*'; }
run star
[ "${lines[0]}" = '*' ]
}
@test "multiple stars in output are not treated as a glob" {
stars(){ echo '**'; }
run stars
[ "${lines[0]}" = '**' ]
}
@test "ensure all folders are shellchecked" {
if [[ ! -f "$BATS_ROOT/shellcheck.sh" ]]; then
skip "\$BATS_ROOT/shellcheck.sh is required for this test"
fi
cd "$BATS_ROOT"
run "./shellcheck.sh" --list
echo "$output"
grep bin/bats <<< "$output"
grep contrib/ <<< "$output"
grep docker/ <<< "$output"
grep lib/bats-core/ <<< "$output"
grep libexec/bats-core/ <<< "$output"
grep test/fixtures <<< "$output"
grep install.sh <<< "$output"
}
@test "BATS_RUN_COMMAND: test content of variable" {
run bats -v
[[ "${BATS_RUN_COMMAND}" == "bats -v" ]]
run bats "${BATS_TEST_DESCRIPTION}"
echo "$BATS_RUN_COMMAND"
[[ "$BATS_RUN_COMMAND" == "bats BATS_RUN_COMMAND: test content of variable" ]]
}
@test "pretty formatter summary is colorized red on failure" {
run -1 bats --pretty "$FIXTURE_ROOT/failing.bats"
[ "${lines[3]}" == $'\033[0m\033[31;1m' ] # TODO: avoid checking for the leading reset too
[ "${lines[4]}" == '1 test, 1 failure' ]
[ "${lines[5]}" == $'\033[0m' ]
}
@test "pretty formatter summary is colorized green on success" {
run -0 bats --pretty "$FIXTURE_ROOT/passing.bats"
[ "${lines[1]}" == $'\033[0m\033[32;1m' ] # TODO: avoid checking for the leading reset too
[ "${lines[2]}" == '1 test, 0 failures' ]
[ "${lines[3]}" == $'\033[0m' ]
}
@test "--print-output-on-failure works as expected" {
run bats --print-output-on-failure --show-output-of-passing-tests "$FIXTURE_ROOT/print_output_on_failure.bats"
[ "${lines[0]}" == '1..3' ]
[ "${lines[1]}" == 'ok 1 no failure prints no output' ]
# ^ no output despite --show-output-of-passing-tests, because there is no failure
[ "${lines[2]}" == 'not ok 2 failure prints output' ]
[ "${lines[3]}" == "# (in test file $RELATIVE_FIXTURE_ROOT/print_output_on_failure.bats, line 6)" ]
[ "${lines[4]}" == "# \`run -1 echo \"fail hard\"' failed, expected exit code 1, got 0" ]
[ "${lines[5]}" == '# Last output:' ]
[ "${lines[6]}" == '# fail hard' ]
[ "${lines[7]}" == 'not ok 3 empty output on failure' ]
[ "${lines[8]}" == "# (in test file $RELATIVE_FIXTURE_ROOT/print_output_on_failure.bats, line 10)" ]
[ "${lines[9]}" == "# \`false' failed" ]
[ ${#lines[@]} -eq 10 ]
}
@test "--show-output-of-passing-tests works as expected" {
run -0 bats --show-output-of-passing-tests "$FIXTURE_ROOT/show-output-of-passing-tests.bats"
[ "${lines[0]}" == '1..1' ]
[ "${lines[1]}" == 'ok 1 test' ]
[ "${lines[2]}" == '# output' ]
[ ${#lines[@]} -eq 3 ]
}
@test "--verbose-run prints output" {
run -1 bats --verbose-run "$FIXTURE_ROOT/verbose-run.bats"
[ "${lines[0]}" == '1..1' ]
[ "${lines[1]}" == 'not ok 1 test' ]
[ "${lines[2]}" == "# (in test file $RELATIVE_FIXTURE_ROOT/verbose-run.bats, line 2)" ]
[ "${lines[3]}" == "# \`run ! echo test' failed, expected nonzero exit code!" ]
[ "${lines[4]}" == '# test' ]
[ ${#lines[@]} -eq 5 ]
}
@test "BATS_VERBOSE_RUN=1 also prints output" {
run -1 env BATS_VERBOSE_RUN=1 bats "$FIXTURE_ROOT/verbose-run.bats"
[ "${lines[0]}" == '1..1' ]
[ "${lines[1]}" == 'not ok 1 test' ]
[ "${lines[2]}" == "# (in test file $RELATIVE_FIXTURE_ROOT/verbose-run.bats, line 2)" ]
[ "${lines[3]}" == "# \`run ! echo test' failed, expected nonzero exit code!" ]
[ "${lines[4]}" == '# test' ]
[ ${#lines[@]} -eq 5 ]
}
@test "--gather-test-outputs-in gathers outputs of all tests (even succeeding!)" {
local OUTPUT_DIR="$BATS_TEST_TMPDIR/logs"
run bats --verbose-run --gather-test-outputs-in "$OUTPUT_DIR" "$FIXTURE_ROOT/print_output_on_failure.bats"
[ -d "$OUTPUT_DIR" ] # will be generated!
# even outputs of successful tests are generated
OUTPUT=$(<"$OUTPUT_DIR/1-no failure prints no output.log") # own line to trigger failure if file does not exist
[ "$OUTPUT" == "success" ]
OUTPUT=$(<"$OUTPUT_DIR/2-failure prints output.log")
[ "$OUTPUT" == "fail hard" ]
# even empty outputs are generated
OUTPUT=$(<"$OUTPUT_DIR/3-empty output on failure.log")
[ "$OUTPUT" == "" ]
[ "$(find "$OUTPUT_DIR" -type f | wc -l)" -eq 3 ]
}
@test "Tell about missing flock and shlock" {
if ! command -v parallel; then
skip "this test requires GNU parallel to be installed"
fi
if command -v flock; then
skip "this test requires flock not to be installed"
fi
if command -v shlock; then
skip "this test requires flock not to be installed"
fi
run ! bats --jobs 2 "$FIXTURE_ROOT/parallel.bats"
[ "${lines[0]}" == "ERROR: flock/shlock is required for parallelization within files!" ]
[ "${#lines[@]}" -eq 1 ]
}
@test "Test with a name that is waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay too long" {
skip "This test should only check if the long name chokes bats' internals during execution"
}

@ -0,0 +1,66 @@
# block until at least <barrier-size> processes of this barrier group entered the barrier
# once this happened, all latecomers will go through immediately!
# WARNING: a barrier group consists of all processes with the same barrier name *and* size!
single-use-barrier() { # <barrier-name> <barrier-size> [<timeout-in-seconds> [<sleep-cycle-time>]]
local barrier_name="$1"
local barrier_size="$2"
local timeout_in_seconds="${3:-0}"
local sleep_cycle_time="${4:-1}"
# use name and size to distinguish between invocations
# this will block inconsistent sizes on the same name!
local BARRIER_SUFFIX=${barrier_name//\//_}-$barrier_size
local BARRIER_FILE="$BATS_SUITE_TMPDIR/barrier-$BARRIER_SUFFIX"
# mark our entry for all others
# concurrent writes may interleave but should not lose their newlines
echo "in-$$" >> "$BARRIER_FILE"
local start="$SECONDS"
# wait for others to enter
while [[ $(wc -l <"$BARRIER_FILE" ) -lt $barrier_size ]]; do
if [[ $timeout_in_seconds -ne 0 && $(( SECONDS - start )) -gt $timeout_in_seconds ]]; then
mv "$BARRIER_FILE" "$BARRIER_FILE-timeout"
printf "ERROR: single-use-barrier %s timed out\n" "$BARRIER_SUFFIX" >&2
return 1
fi
sleep "$sleep_cycle_time"
done
# mark our exit
echo "out-$$" >> "$BARRIER_FILE"
}
# block until at least <latch-size> signalling threads have passed the latch
# SINGLE_USE_LATCH_DIR must be exported!
single-use-latch::wait() { # <latch-name> <latch-size> [<timeout-in-seconds> [<sleep-cycle-time>]]
local latch_name="$1"
local latch_size="$2"
local timeout_in_seconds="${3:-0}"
local sleep_cycle_time="${4:-1}"
local LATCH_FILE
LATCH_FILE="$(single-use-latch::_filename "$latch_name")"
local start="$SECONDS"
while [[ (! -e "$LATCH_FILE") || $(wc -l <"$LATCH_FILE" ) -lt $latch_size ]]; do
if [[ $timeout_in_seconds -ne 0 && $(( SECONDS - start )) -gt $timeout_in_seconds ]]; then
printf "ERROR: single-use-latch %s timed out\n" "$latch_name" >&2
mv "$LATCH_FILE" "$LATCH_FILE-timeout"
return 1
fi
sleep "$sleep_cycle_time"
done
}
# signal the waiting process that the latch was passed
# this does not block
# SINGLE_USE_LATCH_DIR must be exported!
single-use-latch::signal() { # <latch-name>
local latch_name="$1"
local LATCH_FILE
LATCH_FILE="$(single-use-latch::_filename "$latch_name")"
# mark our passing
# concurrent process might interleave but will still post their newline
echo "passed-$$" >> "$LATCH_FILE"
echo "passed-$$ >> $LATCH_FILE" >> /tmp/latch
}
single-use-latch::_filename() { # <latch-name>
printf "%s\n" "${SINGLE_USE_LATCH_DIR?}/latch-${1//\//_}"
}

@ -5,22 +5,15 @@ setup_file() {
export SETUP_FILE_EXPORT_TEST=true
}
setup() {
# give each test their own tmpdir to allow for parallelization without interference
make_bats_test_suite_tmpdir "$BATS_TEST_NAME"
}
teardown() {
test_helper::cleanup_tmpdir "$BATS_TEST_NAME"
}
@test "setup_file is run once per file" {
export LOG="$BATS_TEST_SUITE_TMPDIR/setup_file_once.log"
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/setup_file_once.log"
bats "$FIXTURE_ROOT/setup_file.bats"
}
@test "teardown_file is run once per file" {
export LOG="$BATS_TEST_SUITE_TMPDIR/teardown_file_once.log"
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/teardown_file_once.log"
run bats "$FIXTURE_ROOT/teardown_file.bats"
[[ $status -eq 0 ]]
# output the log for faster debugging
@ -33,7 +26,8 @@ teardown() {
}
@test "setup_file is called correctly in multi file suite" {
export LOG="$BATS_TEST_SUITE_TMPDIR/setup_file_multi_file_suite.log"
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/setup_file_multi_file_suite.log"
run bats "$FIXTURE_ROOT/setup_file.bats" "$FIXTURE_ROOT/no_setup_file.bats" "$FIXTURE_ROOT/setup_file2.bats"
[[ $status -eq 0 ]]
run wc -l < "$LOG"
@ -44,7 +38,8 @@ teardown() {
}
@test "teardown_file is called correctly in multi file suite" {
export LOG="$BATS_TEST_SUITE_TMPDIR/teardown_file_multi_file_suite.log"
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/teardown_file_multi_file_suite.log"
run bats "$FIXTURE_ROOT/teardown_file.bats" "$FIXTURE_ROOT/no_teardown_file.bats" "$FIXTURE_ROOT/teardown_file2.bats"
[[ $status -eq 0 ]]
run wc -l < "$LOG"
@ -86,7 +81,8 @@ teardown() {
}
@test "teardown_file runs even if any test in the file failed" {
export LOG="$BATS_TEST_SUITE_TMPDIR/teardown_file_failed.log"
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/teardown_file_failed.log"
run bats "$FIXTURE_ROOT/teardown_file_after_failing_test.bats"
[[ $status -ne 0 ]]
grep teardown_file_after_failing_test.bats "$LOG"
@ -98,8 +94,8 @@ not ok 1 failing test
}
@test "teardown_file should run even after user abort via CTRL-C" {
export LOG="$BATS_TEST_SUITE_TMPDIR/teardown_file_abort.log"
STARTTIME=$SECONDS
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/teardown_file_abort.log"
# guarantee that background processes get their own process group -> pid=pgid
set -m
SECONDS=0
@ -117,18 +113,20 @@ not ok 1 failing test
[[ -f "$LOG" ]]
grep teardown_file_after_long_test.bats "$LOG"
# but the test must not have run to the end!
! grep "test finished successfully" "$LOG"
run ! grep "test finished successfully" "$LOG"
}
@test "setup_file runs even if all tests in the file are skipped" {
export LOG="$BATS_TEST_SUITE_TMPDIR/setup_file_skipped.log"
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/setup_file_skipped.log"
run bats "$FIXTURE_ROOT/setup_file_even_if_all_tests_are_skipped.bats"
[[ -f "$LOG" ]]
grep setup_file_even_if_all_tests_are_skipped.bats "$LOG"
}
@test "teardown_file runs even if all tests in the file are skipped" {
export LOG="$BATS_TEST_SUITE_TMPDIR/teardown_file_skipped.log"
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/teardown_file_skipped.log"
run bats "$FIXTURE_ROOT/teardown_file_even_if_all_tests_are_skipped.bats"
[[ $status -eq 0 ]]
[[ -f "$LOG" ]]
@ -139,7 +137,8 @@ not ok 1 failing test
# example: BATS_ROOT was unset in one test but used in others, therefore, the suite failed
# Simulate leaking env var from first to second test by: export SETUP_FILE_VAR="LEAK!"
run bats "$FIXTURE_ROOT/setup_file_does_not_leak_env.bats" "$FIXTURE_ROOT/setup_file_does_not_leak_env2.bats"
[[ $status -eq 0 ]] || (echo $output; return 1)
echo "$output"
[[ $status -eq 0 ]]
}
@test "teardown_file must not leak context between tests in the same suite" {
@ -154,12 +153,12 @@ ok 2 must not see variable from first run" ]]
@test "halfway setup_file errors are caught and reported" {
run bats "$FIXTURE_ROOT/setup_file_halfway_error.bats"
[[ $status -ne 0 ]]
[ $status -ne 0 ]
echo "$output"
[[ "$output" == "1..1
not ok 1 setup_file failed
# (from function \`setup_file' in test file $RELATIVE_FIXTURE_ROOT/setup_file_halfway_error.bats, line 3)
# \`false' failed" ]]
[ "${lines[0]}" == "1..1" ]
[ "${lines[1]}" == "not ok 1 setup_file failed" ]
[ "${lines[2]}" == "# (from function \`setup_file' in test file $RELATIVE_FIXTURE_ROOT/setup_file_halfway_error.bats, line 3)" ]
[ "${lines[3]}" == "# \`false' failed" ]
}
@test "halfway teardown_file errors are caught and reported" {
@ -176,4 +175,14 @@ not ok 1 setup_file failed
@test "variables exported in setup_file are visible in tests" {
[[ $SETUP_FILE_EXPORT_TEST == "true" ]]
}
}
@test "Don't run setup_file for files without tests" {
# shellcheck disable=SC2031
export LOG="$BATS_TEST_TMPDIR/setup_file.log"
# only select the test from no_setup_file
run bats -f test "$FIXTURE_ROOT/setup_file.bats" "$FIXTURE_ROOT/no_setup_file.bats"
[ ! -f "$LOG" ] # setup_file must not have been executed!
[ "${lines[0]}" == '1..1' ] # but at least one test should have been run
}

@ -0,0 +1,8 @@
@test "BATS_TMPDIR is set" {
[ "${BATS_TMPDIR}" == "${expected:-}" ]
}
@test "BATS_RUN_TMPDIR has BATS_TMPDIR as a prefix" {
local regex="^${BATS_TMPDIR}/.+"
[[ ${BATS_RUN_TMPDIR} =~ ${regex} ]]
}

@ -1,8 +1,10 @@
@test "setting a variable" {
# shellcheck disable=SC2030
variable=1
[ $variable -eq 1 ]
}
@test "variables do not persist across tests" {
# shellcheck disable=SC2031
[ -z "$variable" ]
}

@ -0,0 +1,15 @@
setup() {
true
}
teardown() {
true
}
setup_file() {
true
}
teardown_file() {
true
}

@ -0,0 +1,5 @@
load external_functions
@test test {
true
}

@ -0,0 +1,11 @@
helper() {
false
}
helper
@test "everything is ok" {
true
}

@ -0,0 +1,8 @@
setup() {
load '../../concurrent-coordination'
}
@test "test" {
single-use-latch::signal hang_in_run
run sleep 10
}

@ -0,0 +1,9 @@
setup_file() {
load '../../concurrent-coordination'
single-use-latch::signal hang_in_setup_file
sleep 10
}
@test "empty" {
:
}

@ -0,0 +1,9 @@
teardown() {
load '../../concurrent-coordination'
single-use-latch::signal hang_in_teardown
sleep 10
}
@test "empty" {
:
}

@ -0,0 +1,9 @@
teardown_file() {
load '../../concurrent-coordination'
single-use-latch::signal hang_in_teardown_file
sleep 10
}
@test "empty" {
:
}

@ -0,0 +1,8 @@
setup() {
load '../../concurrent-coordination'
}
@test "test" {
single-use-latch::signal hang_in_test
sleep 10
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save