Import Upstream version 1.8.2

This commit is contained in:
denghao 2023-02-28 13:36:06 +08:00
commit d3b7385143
320 changed files with 15761 additions and 0 deletions

15
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,15 @@
ARG bashver=latest
FROM bash:${bashver}
# Install parallel and accept the citation notice (we aren't using this in a
# context where it make sense to cite GNU Parallel).
RUN echo "@edgecomm http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \
apk update && \
apk add --no-cache parallel ncurses shellcheck@edgecomm && \
mkdir -p ~/.parallel && touch ~/.parallel/will-cite
RUN ln -s /opt/bats/bin/bats /usr/sbin/bats
COPY . /opt/bats/
ENTRYPOINT ["bash", "/usr/sbin/bats"]

View File

@ -0,0 +1,5 @@
{
"name": "Bats core development environment",
"dockerFile": "Dockerfile",
"build": {"args": {"bashver": "4.3"}}
}

35
.editorconfig Normal file
View File

@ -0,0 +1,35 @@
root = true
[*]
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true
# The JSON files contain newlines inconsistently
[*.json]
indent_size = 2
insert_final_newline = ignore
# YAML
[*.{yml,yaml}]
indent_style = space
indent_size = 2
# Makefiles always use tabs for recipe indentation
[{Makefile,*.mak}]
indent_style = tab
# Markdown
[*.{md,rmd,mkd,mkdn,mdwn,mdown,markdown,litcoffee}]
max_line_length = 80
# tabs behave as if they were replaced by spaces with a tab stop of 4 characters
tab_width = 4
# trailing spaces indicates word wrap
trim_trailing_spaces = false
trim_trailing_whitespace = false
[test/fixtures/bats/*_no_shellcheck.bats]
ignore = true

3
.gitattributes vendored Executable file
View File

@ -0,0 +1,3 @@
* text=auto
*.sh eol=lf
libexec/* eol=lf

29
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,29 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'Priority: NeedsTriage, Type: Bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Environment (please complete the following information):**
- Bats Version [e.g. 1.4.0 or commit hash]
- OS: [e.g. Linux, FreeBSD, MacOS]
- Bash version: [e.g. 5.1]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'Priority: NeedsTriage, Type: Enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context about the feature request here.

10
.github/workflows/check_pr_label.sh vendored Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/bash
get_pr_json() {
curl -s -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/bats-core/bats-core/pulls/$1"
}
PR_NUMBER="$1"
LABEL="$2"
get_pr_json "$PR_NUMBER" | jq .labels[].name | grep "$LABEL"

30
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Release
on:
release: { types: [published] }
workflow_dispatch:
jobs:
npmjs:
runs-on: ubuntu-latest
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
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 GitHub Packages
run: npm init -y --scope ${{ github.repository_owner }}
- run: npm publish --ignore-scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

47
.github/workflows/release_dockerhub.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Release to docker hub
on:
release: { types: [published] }
workflow_dispatch:
inputs:
version:
description: 'Version to simulate for deploy'
required: true
jobs:
dockerhub:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- id: version
run: |
EXPECTED_VERSION=${{ github.event.inputs.version }}
TAG_VERSION=${GITHUB_REF#refs/tags/v} # refs/tags/v1.2.3 -> 1.2.3
echo ::set-output name=version::${EXPECTED_VERSION:-$TAG_VERSION}
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- uses: docker/build-push-action@v2
with:
platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6
tags: ${{ secrets.DOCKER_USERNAME }}/bats:${{ steps.version.outputs.version }},${{ secrets.DOCKER_USERNAME }}/bats:latest
push: true
- uses: docker/build-push-action@v2
with:
platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6
tags: ${{ secrets.DOCKER_USERNAME }}/bats:${{ steps.version.outputs.version }}-no-faccessat2,${{ secrets.DOCKER_USERNAME }}/bats:latest-no-faccessat2
push: true
build-args: bashver=5.1.4

1
.github/workflows/set_nounset.bash vendored Normal file
View File

@ -0,0 +1 @@
set -u

265
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,265 @@
name: Tests
# Controls when the action will run.
on: [push, pull_request, workflow_dispatch]
jobs:
changelog:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Check that PR is mentioned in Changelog
run: |
if ! ./.github/workflows/check_pr_label.sh "${{github.event.pull_request.number}}" "no changelog"; then
grep "#${{github.event.pull_request.number}}" docs/CHANGELOG.md
fi
if: ${{github.event.pull_request}}
shfmt:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- run: |
curl https://github.com/mvdan/sh/releases/download/v3.5.1/shfmt_v3.5.1_linux_amd64 -o shfmt
chmod a+x shfmt
- run: ./shfmt --diff .
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-22.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 --print-output-on-failure --formatter tap test"
unset_variables:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check unset variables
shell: 'script -q -e -c "bash {0}"' # work around tty issues
env:
TERM: linux # fix tput for tty issue work around
BASH_ENV: ${GITHUB_WORKSPACE}/.github/workflows/set_nounset.bash
run: bin/bats test --print-output-on-failure
npm_on_linux:
strategy:
matrix:
os: ['ubuntu-20.04', 'ubuntu-22.04']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@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: |
npm pack ./
sudo npm install -g ./bats-*.tgz
bats test --print-output-on-failure
windows:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- run: |
bash --version
bash -c "time bin/bats --print-output-on-failure --formatter tap test"
npm_on_windows:
strategy:
matrix:
os: ['windows-2019']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- run: npm pack ./
- run: npm install -g (get-item .\bats-*.tgz).FullName
- run: bats -T --print-output-on-failure test
macos:
strategy:
matrix:
os: ['macos-11', 'macos-12']
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 --print-output-on-failure --formatter tap test"
npm_on_macos:
strategy:
matrix:
os: ['macos-11', 'macos-12']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@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: |
npm pack ./
# somehow there is already an installed bats version around
npm install --force -g ./bats-*.tgz
bats --print-output-on-failure test
bash-version:
strategy:
matrix:
version: ['3.2', '4.0', '4.1', '4.2', '4.3', '4.4', '4', '5.0', '5.1', '5', 'rc']
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 }}" --print-output-on-failure --tap /opt/bats/test
alpine:
runs-on: ubuntu-latest
container: alpine:latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: apk add bash ncurses util-linux
- name: Run test on bash version ${{ matrix.version }}
shell: 'script -q -e -c "bash {0}"' # work around tty issues
env:
TERM: linux # fix tput for tty issue work around
run:
time ./bin/bats --print-output-on-failure test/
freebsd:
runs-on: macos-12
strategy:
matrix:
packages:
- flock
- ""
steps:
- uses: actions/checkout@v2
- uses: vmactions/freebsd-vm@v0
with:
prepare: pkg install -y bash parallel ${{ matrix.packages }}
run: |
time ./bin/bats --print-output-on-failure test/
find_broken_symlinks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# list symlinks that are broken and force non-zero exit if there are any
- run: "! find . -xtype l | grep ."
rpm:
runs-on: ubuntu-latest
container: almalinux:8
steps:
- uses: actions/checkout@v2
- run: dnf install -y rpm-build rpmdevtools
- name: Build and install RPM and dependencies
run: |
rpmdev-setuptree
version=$(rpmspec -q --qf '%{version}' contrib/rpm/bats.spec)
tar --transform "s,^,bats-core-${version}/," -cf /github/home/rpmbuild/SOURCES/v${version}.tar.gz ./
rpmbuild -v -bb ./contrib/rpm/bats.spec
ls -al /github/home/rpmbuild/RPMS/noarch/
dnf install -y /github/home/rpmbuild/RPMS/noarch/bats-*.rpm
dnf -y install procps-ng # avoid timeout failure
- name: Run tests
shell: 'script -q -e -c "bash {0}"' # work around tty issues
env:
TERM: linux # fix tput for tty issue work around
run: bats --print-output-on-failure --filter-tags !dep:install_sh test/
dockerfile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- uses: docker/build-push-action@v2
with:
platforms: linux/amd64
tags: bats:test
load: true
- run: docker run -itv "$PWD":/code bats:test --tap --print-output-on-failure test/
shell: 'script -q -e -c "bash {0}"' # work around tty issues
env:
TERM: linux # fix tput for tty issue work around
- uses: actions/checkout@v2
with:
repository: bats-core/bats-assert
path: bats-assert
- uses: actions/checkout@v2
with:
repository: bats-core/bats-support
path: bats-support
- uses: actions/checkout@v2
with:
repository: bats-core/bats-file
path: bats-file
- run: |
<<EOF cat >test.sh
apk add sudo python3 # install bats-file's dependencies
ln -sf python3 /usr/bin/python # bats-file uses python without version
bats --tap --print-output-on-failure bats-*/test/
EOF
docker run -itv "$PWD":/code --entrypoint bash bats:test test.sh
shell: 'script -q -e -c "bash {0}"' # work around tty issues
env:
TERM: linux # fix tput for tty issue work around

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
/docker-compose.override.yml
/docs/build
# npm
/bats-*.tgz
# we don't have any deps; un-ignore if that changes
/package-lock.json
test/.bats/run-logs/
# scratch file that should never be committed
/test.bats

9
.readthedocs.yml Normal file
View File

@ -0,0 +1,9 @@
version: 2
sphinx:
configuration: docs/source/conf.py
python:
version: 3.7
install:
- requirements: docs/source/requirements.txt

4
AUTHORS Normal file
View File

@ -0,0 +1,4 @@
Andrew Martin (https://control-plane.io/)
Bianca Tamayo <hi@biancatamayo.me> (https://biancatamayo.me/)
Jason Karns <jason.karns@gmail.com> (http://jasonkarns.com/)
Mike Bland <mbland@acm.org> (https://mike-bland.com/)

43
Dockerfile Normal file
View File

@ -0,0 +1,43 @@
ARG bashver=latest
FROM bash:${bashver}
ARG TINI_VERSION=v0.19.0
ARG TARGETPLATFORM
ARG LIBS_VER_SUPPORT=0.3.0
ARG LIBS_VER_FILE=0.3.0
ARG LIBS_VER_ASSERT=2.1.0
ARG LIBS_VER_DETIK=1.1.0
ARG UID=1001
ARG GID=115
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL maintainer="Bats-core Team"
LABEL org.opencontainers.image.authors="Bats-core Team"
LABEL org.opencontainers.image.title="Bats"
LABEL org.opencontainers.image.description="Bash Automated Testing System"
LABEL org.opencontainers.image.url="https://hub.docker.com/r/bats/bats"
LABEL org.opencontainers.image.source="https://github.com/bats-core/bats-core"
LABEL org.opencontainers.image.base.name="docker.io/bash"
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 bats libs
RUN /tmp/docker/install_libs.sh support ${LIBS_VER_SUPPORT}
RUN /tmp/docker/install_libs.sh file ${LIBS_VER_FILE}
RUN /tmp/docker/install_libs.sh assert ${LIBS_VER_ASSERT}
RUN /tmp/docker/install_libs.sh detik ${LIBS_VER_DETIK}
# Install parallel and accept the citation notice (we aren't using this in a
# context where it make sense to cite GNU Parallel).
RUN apk add --no-cache parallel ncurses && \
mkdir -p ~/.parallel && touch ~/.parallel/will-cite \
&& mkdir /code
RUN ln -s /opt/bats/bin/bats /usr/local/bin/bats
COPY . /opt/bats/
WORKDIR /code/
ENTRYPOINT ["/tini", "--", "bash", "bats"]

53
LICENSE.md Normal file
View File

@ -0,0 +1,53 @@
Copyright (c) 2017 bats-core contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
* [bats-core] is a continuation of [bats]. Copyright for portions of the
bats-core project are held by Sam Stephenson, 2014 as part of the project
[bats], licensed under MIT:
Copyright (c) 2014 Sam Stephenson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For details, please see the [version control history][commits].
[bats-core]: https://github.com/bats-core/bats-core
[bats]:https://github.com/sstephenson/bats
[commits]:https://github.com/bats-core/bats-core/commits/master

127
README.md Normal file
View File

@ -0,0 +1,127 @@
# Bats-core: Bash Automated Testing System
[![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: 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]
Bats is a [TAP](https://testanything.org/)-compliant testing framework for Bash. It provides a simple
way to verify that the UNIX programs you write behave as expected.
A Bats test file is a Bash script with special syntax for defining test cases.
Under the hood, each test case is just a function with a description.
```bash
#!/usr/bin/env bats
@test "addition using bc" {
result="$(echo 2+2 | bc)"
[ "$result" -eq 4 ]
}
@test "addition using dc" {
result="$(echo 2 2+p | dc)"
[ "$result" -eq 4 ]
}
```
Bats is most useful when testing software written in Bash, but you can use it to
test any UNIX program.
Test cases consist of standard shell commands. Bats makes use of Bash's
`errexit` (`set -e`) option when running test cases. If every command in the
test case exits with a `0` status code (success), the test passes. In this way,
each line is an assertion of truth.
## Table of contents
**NOTE** The documentation has moved to <https://bats-core.readthedocs.io>
<!-- toc -->
- [Testing](#testing)
- [Support](#support)
- [Contributing](#contributing)
- [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)
- [Copyright](#copyright)
<!-- tocstop -->
## Testing
```sh
bin/bats --tap test
```
See also the [CI](./.github/workflows/tests.yml) settings for the current test environment and
scripts.
## Support
The Bats source code repository is [hosted on
GitHub](https://github.com/bats-core/bats-core). There you can file bugs on the
issue tracker or submit tested pull requests for review.
For real-world examples from open-source projects using Bats, see [Projects
Using Bats](https://github.com/bats-core/bats-core/wiki/Projects-Using-Bats) on
the wiki.
To learn how to set up your editor for Bats syntax highlighting, see [Syntax
Highlighting](https://github.com/bats-core/bats-core/wiki/Syntax-Highlighting)
on the wiki.
## Contributing
For now see the [`docs`](docs) folder for project guides, work with us on the wiki
or look at the other communication channels.
## Contact
- You can find and chat with us on our [Gitter].
## Version history
See `docs/CHANGELOG.md`.
## Background
<!-- markdownlint-disable MD026 -->
### What's the plan and why?
<!-- markdownlint-enable MD026 -->
**Tuesday, September 19, 2017:** This was forked from [Bats][bats-orig] at
commit [0360811][]. It was created via `git clone --bare` and `git push
--mirror`.
[bats-orig]: https://github.com/sstephenson/bats
[0360811]: https://github.com/sstephenson/bats/commit/03608115df2071fff4eaaff1605768c275e5f81f
This [bats-core repo](https://github.com/bats-core/bats-core) is the community-maintained Bats project.
<!-- markdownlint-disable MD026 -->
### Why was this fork created?
<!-- markdownlint-enable MD026 -->
There was an initial [call for maintainers][call-maintain] for the original Bats repository, but write access to it could not be obtained. With development activity stalled, this fork allowed ongoing maintenance and forward progress for Bats.
[call-maintain]: https://github.com/sstephenson/bats/issues/150
## Copyright
© 2017-2022 bats-core organization
© 2011-2016 Sam Stephenson
Bats is released under an MIT-style license; see `LICENSE.md` for details.
See the [parent project](https://github.com/bats-core) at GitHub or the
[AUTHORS](AUTHORS) file for the current project maintainer team.
[gitter]: https://gitter.im/bats-core/bats-core

59
bin/bats Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
if command -v greadlink >/dev/null; then
bats_readlinkf() {
greadlink -f "$1"
}
else
bats_readlinkf() {
readlink -f "$1"
}
fi
fallback_to_readlinkf_posix() {
bats_readlinkf() {
[ "${1:-}" ] || return 1
max_symlinks=40
CDPATH='' # to avoid changing to an unexpected directory
target=$1
[ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes
[ -d "${target:-/}" ] && target="$target/"
cd -P . 2>/dev/null || return 1
while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do
if [ ! "$target" = "${target%/*}" ]; then
case $target in
/*) cd -P "${target%/*}/" 2>/dev/null || break ;;
*) cd -P "./${target%/*}" 2>/dev/null || break ;;
esac
target=${target##*/}
fi
if [ ! -L "$target" ]; then
target="${PWD%/}${target:+/}${target}"
printf '%s\n' "${target:-/}"
return 0
fi
# `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n",
# <file mode>, <number of links>, <owner name>, <group name>,
# <size>, <date and time>, <pathname of link>, <contents of link>
# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
link=$(ls -dl -- "$target" 2>/dev/null) || break
target=${link#*" $target -> "}
done
return 1
}
}
if ! BATS_PATH=$(bats_readlinkf "${BASH_SOURCE[0]}" 2>/dev/null); then
fallback_to_readlinkf_posix
BATS_PATH=$(bats_readlinkf "${BASH_SOURCE[0]}")
fi
export BATS_ROOT=${BATS_PATH%/*/*}
export -f bats_readlinkf
exec env BATS_ROOT="$BATS_ROOT" "$BATS_ROOT/libexec/bats-core/bats" "$@"

178
contrib/release.sh Executable file
View File

@ -0,0 +1,178 @@
#!/usr/bin/env bash
#
# bats-core git releaser
#
## Usage: %SCRIPT_NAME% [options]
##
## Options:
## --major Major version bump
## --minor Minor version bump
## --patch Patch version bump
##
## -v, --version Print version
## --debug Enable debug mode
## -h, --help Display this message
##
set -Eeuo pipefail
DIR=$(cd "$(dirname "${0}")" && pwd)
THIS_SCRIPT="${DIR}/$(basename "${0}")"
BATS_VERSION=$(
# shellcheck disable=SC1090
source <(grep '^export BATS_VERSION=' libexec/bats-core/bats)
echo "${BATS_VERSION}"
)
declare -r DIR
declare -r THIS_SCRIPT
declare -r BATS_VERSION
BUMP_INTERVAL=""
NEW_BATS_VERSION=""
main() {
handle_arguments "${@}"
if [[ "${BUMP_INTERVAL:-}" == "" ]]; then
echo "${BATS_VERSION}"
exit 0
fi
local NEW_BATS_VERSION
NEW_BATS_VERSION=$(semver bump "${BUMP_INTERVAL}" "${BATS_VERSION}")
declare -r NEW_BATS_VERSION
local BATS_RELEASE_NOTES="/tmp/bats-release-${NEW_BATS_VERSION}"
echo "Releasing: ${BATS_VERSION} to ${NEW_BATS_VERSION}"
echo
echo "Ensure docs/CHANGELOG.md is correctly updated"
replace_in_files
write_changelog
git diff --staged
cat <<EOF
1. Version numbers have been updated. Commit the changes:
git commit -m "feat: release Bats v${NEW_BATS_VERSION}"
2. Verify this autogenerated changelog (from docs/CHANGELOG.md):
# changelog start
EOF
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 \
-e 's,^## \[([0-9\.]+)] - (.*),Bats \1\n\nReleased: \2,' \
-e 's,^### (.*),\1:,g' |
tee "${BATS_RELEASE_NOTES}"
cat <<EOF
# changelog end
3. Tag the release using the autogenerated changelog:
git tag -a -s "v${NEW_BATS_VERSION}" --message "${BATS_RELEASE_NOTES}"
4. Push the changes:
git push --follow-tags
5. Use GitHub hub to make a draft release:
hub release create "v${NEW_BATS_VERSION}" --draft --file "${BATS_RELEASE_NOTES}"
6. Navigate to the provided URL, verify changes, and release Bats ${NEW_BATS_VERSION}.
EOF
exit 0
}
replace_in_files() {
declare -a FILE_REPLACEMENTS=(
"contrib/rpm/bats.spec,^Version:"
"libexec/bats-core/bats,^export BATS_VERSION="
"package.json,^ \"version\":"
)
for FILE_REPLACEMENT in "${FILE_REPLACEMENTS[@]}"; do
FILE="${FILE_REPLACEMENT/,*/}"
MATCH="${FILE_REPLACEMENT/*,/}"
sed -E -i.bak "/${MATCH}/ { s,${BATS_VERSION},${NEW_BATS_VERSION},g; }" "${FILE}"
rm "${FILE}.bak" || true
git add -f "${FILE}"
done
}
write_changelog() {
local FILE="docs/CHANGELOG.md"
sed -E -i.bak "/## \[Unreleased\]/ a \\\n## [${NEW_BATS_VERSION}] - $(date +%Y-%m-%d)" "${FILE}"
rm "${FILE}.bak" || true
cp "${FILE}" "${FILE}.new"
sed -E -i.bak '/## \[Unreleased\]/,+1d' "${FILE}"
git add -f "${FILE}"
mv "${FILE}.new" "${FILE}"
}
handle_arguments() {
parse_arguments "${@:-}"
}
parse_arguments() {
local CURRENT_ARG
if [[ "${#}" == 1 && "${1:-}" == "" ]]; then
return 0
fi
while [[ "${#}" -gt 0 ]]; do
CURRENT_ARG="${1}"
case ${CURRENT_ARG} in
--major)
BUMP_INTERVAL="major"
;;
# ---
--minor)
BUMP_INTERVAL="minor"
;;
--patch)
BUMP_INTERVAL="patch"
;;
-h | --help) usage ;;
-v | --version)
get_version
exit 0
;;
--debug)
set -xe
;;
-*) usage "${CURRENT_ARG}: unknown option" ;;
esac
shift
done
}
semver() {
"${DIR}/semver" "${@:-}"
}
usage() {
sed -n '/^##/,/^$/s/^## \{0,1\}//p' "${THIS_SCRIPT}" | sed "s/%SCRIPT_NAME%/$(basename "${THIS_SCRIPT}")/g"
exit 2
} 2>/dev/null
get_version() {
echo "${THIS_SCRIPT_VERSION:-0.1}"
}
main "${@}"

66
contrib/rpm/bats.spec Normal file
View File

@ -0,0 +1,66 @@
%global provider github.com
%global project bats-core
%global repo bats-core
Name: bats
Version: 1.9.0
Release: 1%{?dist}
Summary: Bash Automated Testing System
Group: Development/Libraries
License: MIT
URL: https://%{provider}/%{project}/%{repo}
Source0: https://%{provider}/%{project}/%{repo}/archive/v%{version}.tar.gz
BuildArch: noarch
Requires: bash
%description
Bats is a TAP-compliant testing framework for Bash.
It provides a simple way to verify that the UNIX programs you write behave as expected.
Bats is most useful when testing software written in Bash, but you can use it to test any UNIX program.
%prep
%setup -q -n %{repo}-%{version}
%install
mkdir -p ${RPM_BUILD_ROOT}%{_prefix} ${RPM_BUILD_ROOT}%{_libexecdir} ${RPM_BUILD_ROOT}%{_mandir}
./install.sh ${RPM_BUILD_ROOT}%{_prefix}
%clean
rm -rf $RPM_BUILD_ROOT
%check
%files
%doc README.md LICENSE.md
%{_bindir}/%{name}
%{_libexecdir}/%{repo}
%{_mandir}/man1/%{name}.1.gz
%{_mandir}/man7/%{name}.7.gz
/usr/lib/%{repo}/common.bash
/usr/lib/%{repo}/formatter.bash
/usr/lib/%{repo}/preprocessing.bash
/usr/lib/%{repo}/semaphore.bash
/usr/lib/%{repo}/test_functions.bash
/usr/lib/%{repo}/tracing.bash
/usr/lib/%{repo}/validator.bash
/usr/lib/%{repo}/warnings.bash
%changelog
* Wed Sep 07 2022 Marcel Hecko <marcel@blava.net> - 1.2.0-1
- Fix and test RPM build on Rocky Linux release 8.6
* Sun Jul 08 2018 mbland <mbland@acm.org> - 1.1.0-1
- Increase version to match upstream release
* Mon Jun 18 2018 pixdrift <support@pixeldrift.net> - 1.0.2-1
- Increase version to match upstream release
- Relocate libraries to bats-core subdirectory
* Sat Jun 09 2018 pixdrift <support@pixeldrift.net> - 1.0.1-1
- Increase version to match upstream release
* Fri Jun 08 2018 pixdrift <support@pixeldrift.net> - 1.0.0-1
- Initial package build of forked (bats-core) github project

358
contrib/semver Executable file
View File

@ -0,0 +1,358 @@
#!/usr/bin/env bash
# v3.0.0
# https://github.com/fsaintjacques/semver-tool
set -o errexit -o nounset -o pipefail
NAT='0|[1-9][0-9]*'
ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*'
IDENT="$NAT|$ALPHANUM"
FIELD='[0-9A-Za-z-]+'
SEMVER_REGEX="\
^[vV]?\
($NAT)\\.($NAT)\\.($NAT)\
(\\-(${IDENT})(\\.(${IDENT}))*)?\
(\\+${FIELD}(\\.${FIELD})*)?$"
PROG=semver
PROG_VERSION="3.0.0"
USAGE="\
Usage:
$PROG bump (major|minor|patch|release|prerel <prerel>|build <build>) <version>
$PROG compare <version> <other_version>
$PROG get (major|minor|patch|release|prerel|build) <version>
$PROG --help
$PROG --version
Arguments:
<version> A version must match the following regular expression:
\"${SEMVER_REGEX}\"
In English:
-- The version must match X.Y.Z[-PRERELEASE][+BUILD]
where X, Y and Z are non-negative integers.
-- PRERELEASE is a dot separated sequence of non-negative integers and/or
identifiers composed of alphanumeric characters and hyphens (with
at least one non-digit). Numeric identifiers must not have leading
zeros. A hyphen (\"-\") introduces this optional part.
-- BUILD is a dot separated sequence of identifiers composed of alphanumeric
characters and hyphens. A plus (\"+\") introduces this optional part.
<other_version> See <version> definition.
<prerel> A string as defined by PRERELEASE above.
<build> A string as defined by BUILD above.
Options:
-v, --version Print the version of this tool.
-h, --help Print this help message.
Commands:
bump Bump by one of major, minor, patch; zeroing or removing
subsequent parts. \"bump prerel\" sets the PRERELEASE part and
removes any BUILD part. \"bump build\" sets the BUILD part.
\"bump release\" removes any PRERELEASE or BUILD parts.
The bumped version is written to stdout.
compare Compare <version> with <other_version>, output to stdout the
following values: -1 if <other_version> is newer, 0 if equal, 1 if
older. The BUILD part is not used in comparisons.
get Extract given part of <version>, where part is one of major, minor,
patch, prerel, build, or release.
See also:
https://semver.org -- Semantic Versioning 2.0.0"
function error {
echo -e "$1" >&2
exit 1
}
function usage-help {
error "$USAGE"
}
function usage-version {
echo -e "${PROG}: $PROG_VERSION"
exit 0
}
function validate-version {
local version=$1
if [[ "$version" =~ $SEMVER_REGEX ]]; then
# if a second argument is passed, store the result in var named by $2
if [ "$#" -eq "2" ]; then
local major=${BASH_REMATCH[1]}
local minor=${BASH_REMATCH[2]}
local patch=${BASH_REMATCH[3]}
local prere=${BASH_REMATCH[4]}
local build=${BASH_REMATCH[8]}
eval "$2=(\"$major\" \"$minor\" \"$patch\" \"$prere\" \"$build\")"
else
echo "$version"
fi
else
error "version $version does not match the semver scheme 'X.Y.Z(-PRERELEASE)(+BUILD)'. See help for more information."
fi
}
function is-nat {
[[ "$1" =~ ^($NAT)$ ]]
}
function is-null {
[ -z "$1" ]
}
function order-nat {
[ "$1" -lt "$2" ] && {
echo -1
return
}
[ "$1" -gt "$2" ] && {
echo 1
return
}
echo 0
}
function order-string {
[[ $1 < $2 ]] && {
echo -1
return
}
[[ $1 > $2 ]] && {
echo 1
return
}
echo 0
}
# given two (named) arrays containing NAT and/or ALPHANUM fields, compare them
# one by one according to semver 2.0.0 spec. Return -1, 0, 1 if left array ($1)
# is less-than, equal, or greater-than the right array ($2). The longer array
# is considered greater-than the shorter if the shorter is a prefix of the longer.
#
function compare-fields {
local l="$1[@]"
local r="$2[@]"
local leftfield=("${!l}")
local rightfield=("${!r}")
local left
local right
local i=$((-1))
local order=$((0))
while true; do
[ $order -ne 0 ] && {
echo $order
return
}
: $((i++))
left="${leftfield[$i]}"
right="${rightfield[$i]}"
is-null "$left" && is-null "$right" && {
echo 0
return
}
is-null "$left" && {
echo -1
return
}
is-null "$right" && {
echo 1
return
}
is-nat "$left" && is-nat "$right" && {
order=$(order-nat "$left" "$right")
continue
}
is-nat "$left" && {
echo -1
return
}
is-nat "$right" && {
echo 1
return
}
{
order=$(order-string "$left" "$right")
continue
}
done
}
# shellcheck disable=SC2206 # checked by "validate"; ok to expand prerel id's into array
function compare-version {
local order
validate-version "$1" V
validate-version "$2" V_
# compare major, minor, patch
local left=("${V[0]}" "${V[1]}" "${V[2]}")
local right=("${V_[0]}" "${V_[1]}" "${V_[2]}")
order=$(compare-fields left right)
[ "$order" -ne 0 ] && {
echo "$order"
return
}
# compare pre-release ids when M.m.p are equal
local prerel="${V[3]:1}"
local prerel_="${V_[3]:1}"
local left=(${prerel//./ })
local right=(${prerel_//./ })
# if left and right have no pre-release part, then left equals right
# if only one of left/right has pre-release part, that one is less than simple M.m.p
[ -z "$prerel" ] && [ -z "$prerel_" ] && {
echo 0
return
}
[ -z "$prerel" ] && {
echo 1
return
}
[ -z "$prerel_" ] && {
echo -1
return
}
# otherwise, compare the pre-release id's
compare-fields left right
}
function command-bump {
local new
local version
local sub_version
local command
case $# in
2) case $1 in
major | minor | patch | release)
command=$1
version=$2
;;
*) usage-help ;;
esac ;;
3) case $1 in
prerel | build)
command=$1
sub_version=$2 version=$3
;;
*) usage-help ;;
esac ;;
*) usage-help ;;
esac
validate-version "$version" parts
# shellcheck disable=SC2154
local major="${parts[0]}"
local minor="${parts[1]}"
local patch="${parts[2]}"
local prere="${parts[3]}"
local build="${parts[4]}"
case "$command" in
major) new="$((major + 1)).0.0" ;;
minor) new="${major}.$((minor + 1)).0" ;;
patch) new="${major}.${minor}.$((patch + 1))" ;;
release) new="${major}.${minor}.${patch}" ;;
prerel) new=$(validate-version "${major}.${minor}.${patch}-${sub_version}") ;;
build) new=$(validate-version "${major}.${minor}.${patch}${prere}+${sub_version}") ;;
*) usage-help ;;
esac
echo "$new"
exit 0
}
function command-compare {
local v
local v_
case $# in
2)
v=$(validate-version "$1")
v_=$(validate-version "$2")
;;
*) usage-help ;;
esac
set +u # need unset array element to evaluate to null
compare-version "$v" "$v_"
exit 0
}
# shellcheck disable=SC2034
function command-get {
local part version
if [[ "$#" -ne "2" ]] || [[ -z "$1" ]] || [[ -z "$2" ]]; then
usage-help
exit 0
fi
part="$1"
version="$2"
validate-version "$version" parts
local major="${parts[0]}"
local minor="${parts[1]}"
local patch="${parts[2]}"
local prerel="${parts[3]:1}"
local build="${parts[4]:1}"
local release="${major}.${minor}.${patch}"
case "$part" in
major | minor | patch | release | prerel | build) echo "${!part}" ;;
*) usage-help ;;
esac
exit 0
}
case $# in
0)
echo "Unknown command: $*"
usage-help
;;
esac
case $1 in
--help | -h)
echo -e "$USAGE"
exit 0
;;
--version | -v) usage-version ;;
bump)
shift
command-bump "$@"
;;
get)
shift
command-get "$@"
;;
compare)
shift
command-compare "$@"
;;
*)
echo "Unknown arguments: $*"
usage-help
;;
esac

View File

@ -0,0 +1,8 @@
# Copy this file to docker-compose.override.yml
version: '3.6'
services:
bats:
entrypoint:
- "bash"
networks:
default:

13
docker-compose.yml Normal file
View File

@ -0,0 +1,13 @@
version: '3.6'
services:
bats:
build:
context: "."
dockerfile: "Dockerfile"
networks:
- "default"
user: "root"
volumes:
- "./:/opt/bats"
networks:
default:

51
docker/install_libs.sh Executable file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
LIBNAME="${1:-support}"
LIVERSION="${2:-0.3.0}"
BASEURL='https://github.com/bats-core'
DESTDIR="${BATS_LIBS_DEST_DIR:-/usr/lib/bats}"
TMPDIR=$(mktemp -d -t bats-libs-XXXXXX)
USAGE="Please provide the bats libe name and version \nFor example: install_libs.sh support 2.0.0\n"
trap 'test -d "${TMPDIR}" && rm -fr "${TMPDIR}"' EXIT ERR SIGINT SIGTERM
[[ $# -ne 2 ]] && { _log FATAL "$USAGE"; exit 1; }
_log() {
printf "$(date "+%Y-%m-%d %H:%M:%S") - %s - %s\n" "${1}" "${2}"
}
create_temp_dirs() {
mkdir -p "${TMPDIR}/${1}"
if [[ ${LIBNAME} != "detik" ]]; then
mkdir -p "${DESTDIR}/bats-${1}/src"
else
_log INFO "Skipping src 'cause Detik does not need it"
fi
}
download_extract_source() {
wget -qO- ${BASEURL}/bats-"${1}"/archive/refs/tags/v"${2}".tar.gz | tar xz -C "${TMPDIR}/${1}" --strip-components 1
}
install_files() {
if [[ ${LIBNAME} != "detik" ]]; then
install -Dm755 "${TMPDIR}/${1}/load.bash" "${DESTDIR}/bats-${1}/load.bash"
for fn in "${TMPDIR}/${1}/src/"*.bash; do install -Dm755 "$fn" "${DESTDIR}/bats-${1}/src/$(basename "$fn")"; done
else
for fn in "${TMPDIR}/${1}/lib/"*.bash; do install -Dm755 "$fn" "${DESTDIR}/bats-${1}/$(basename "$fn")"; done
fi
}
_log INFO "Starting to install ${LIBNAME} ver ${LIVERSION}"
_log INFO "Creating directories"
create_temp_dirs "${LIBNAME}"
_log INFO "Downloading"
download_extract_source "${LIBNAME}" "${LIVERSION}"
_log INFO "Installation"
install_files "${LIBNAME}"
_log INFO "Done, cleaning.."

30
docker/install_tini.sh Executable file
View File

@ -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

107
docker/tini.pubkey.gpg Normal file
View File

@ -0,0 +1,107 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFANDtsBEACpb69Ul0Ko7D4XxRIvPGnDMuGdocb8PxR+EGbnHe0uS2tCbsfj
TOoWWUrjufrWYxGlKNqOxbEhzFA2wSQ6VD6xROPQT5dAdKaGnSCiaUg7XTzcb9u3
a5Qbx99EDZWaYDNMnLZnIElDX+YmkkEyrrmjiML63m+1P88Bz7ag18hLkqpCiIVM
TMRfQluBJVvndX7Stzm35utugN+xeTQryjLx74CO6TUWyC7hAjvQhR5IdAk4H0oT
RsOKZ9OQmpO0CJ1XXpKkDdDc60WVrLp1jwq2M7fx/Nz+z13nTHa3fDw8j10+1k0+
c2HafM+GLR5CHlXVMqveWJrimII1ZILxRj/86fFCEC8ZhVW1ym4j+mqEENrzP4I7
L3OnyKLxNKIY9CFDhfzLhNAuNeuIp6KgynzuyxWnJO4q7m/B0zcRIBcjXPrpblIx
QlT3qQ/vFdcylDDSdbgtjD+9URG6bFR9PVlRTllBDPGQEK8vjV44pxLCenm/TzdB
Y4RlEePf+3y7wVrkjg+l4rIDH57Vl188RODuWVGeLZ3IYWqvRUnYxHmta27UH6zY
7FNN5p7H2VqP6v9GFhiHOCTKdUbQhOoPLmUTyBas0WsC8sXdwpTy3mJthzfUwgVN
2SIXPnndz7RcHwZtW1x9ZtVMDr6ll99kT63+sdZJHmUdlnDr+EGEd/L61QARAQAB
tCBUaG9tYXMgT3JvemNvIDx0aG9tYXNAb3JvemNvLmZyPokCOAQTAQIAIgUCUA0O
2wIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQmoQVnXABpOViYA//dDQt
4f7NGbZNnQ0DgOGgBQjAabeaup6HX1UBVMBmU3OEKkUQoA62Aix8kOz19HJcuOl0
Y3koZ7pOrf/39s+tlZKvVuX7F6RFiJpx1+8f+f4IfQEPJRiurwkEp2zwTQfEOlo4
Jzv57pLUtmyJVnxMDi2vFeUue+j9BePp20Z1ZEaMBJDjmMZao3iVCVvSHZnS28vk
D5+y8VUbyhH4dyaDtu8sb06vCu2uD7ZsMfDTtdkDWRqOLs8FdamZLf8P8AD6AX2c
PhhASMWF7Ty2kRvhzHriuplzDUBqod38iLxoC2l5xoDjNMZsY0q9dOXBS5RsKws2
qxrJLa/F672lpxlMA9Xnm0LMCNMcnXreldUhl18zVNofIDGex5yP20djOxl7WsQ6
0VBAzbbiGLaKyfko5CuSmulzTJNOUNxHTiXjSi6Qz5+5lh0wYnHFulhiYE16EX25
/rhvDzN5BgsT3Kdyk/XT/klIB5k4eLXK8PgMsRzp+FDVUL7HKM1HpHMY65gdSR8y
ZuXzKXVC43MFPYJMrjdxoEJXJjnzhf+KO6jkyrna0OQoM1YTH6/5X9XoXzQz8TDe
7pJt4S27PZCv1NhuqNv7nPhUANr7nFMRzrbx2DNHyxUrxPvuyNmYx/KkoAzw3yaT
frbFXU6ccwsXWW6zdcyLEYI2ZqGDfLS7zkjmRsa5AQ0EUzNVzQEIAKxWZf55R676
M3IURgx8Ovt8+F3M4Tj+ifePcpY5JHSj9sGkJRugW9Nye5WehuJjFYOEhKrljCTP
/mjnE7iQqVTyYC1Ar+cTtNQpadWvQR2MW/UzimdZjBtZxdtGFCWR203jtnG+LGRs
R7HyR6A8OBKLl0heTSHx92f+dJCGESZJeVgY49xNOlG4ILl8NseYhaD36QQZReLn
Cazy2aOep2H/jz7vnzye38QMkdHcjaw/WfQHWDKzM5Wlf+5TLF/2VTPlKcmtn0QF
Rw/vw6kjwsv9eCQ3ThIn/FV/ycdUkhOfz9Su2aIbwYC4C6Xi82RxJKbnqUGOExeC
PG2luuWnyQcAEQEAAYkDRAQYAQIADwUCUzNVzQIbAgUJA8JnAAEpCRCahBWdcAGk
5cBdIAQZAQIABgUCUzNVzQAKCRAqiuDav/LlpshzCACnQxndwXLZYWwiXOiYvmUI
U9mPKynUW2vfTwaIpFBIYnqLAjVsPspwfx7zYCoMtQ+UK9d+Jhyts8vCaPwJHPqC
CrBbP/y9K2zRdcDA1DYW/cuubwcstypmO6Eh6iC6xRxa5IHZogK6U+Trqf/EGfI/
Y/sc3KBVYSdLkngASdVr5TQltVH9LsxDUo7Ba382Ci0g/VmKkUht/aFRkEfvMrrj
ONEGbyCwk2hZuwK1aE6Yweq3tbzrByOF4wqIIe6e1pwPz7lchoGYm7XJ7uckpbGi
lZojvu9ruxbGl8tirY/Bj0UZYjASDNCcXXRMvri2FZhDyWMMoeRLj7Mw8xpeWjr7
NhoP/2AA16HHwRjfPQo5fDXe7eGXIebwWhbr1nAYQtL2PHFeHG/imEVozcftENNt
tVwq/JcfjyiIcKA0niGZmroQCO0HRzRs7T85ITWfWcn/Hqaok6gmLh6QJ6daC7E9
Mse07zYM52S2/cS4osfzZj4JEC2n82VuqK9fqzR58GCkPn9rshwljEkMgEfYfTvA
k/tm8jACpSdH3bCpSP2rL6FBtm+RuL8zbAucg8BCaVPoOhtp3326MWCnxH7QdiHl
1dT4Dgmq4UluXOvyMZY7HI1cq4RXW5eXG5mG0ATQh1JIhIJUaEPzdaEtP5LOVqKE
4220MVCWWak/27O9mAFm+lBA5d8O8MWW2ERqpwyYMWg/DVvQP/pB/D6wO4Dp7CMD
MLl1LszHab9oRo8vW2cWNVS6mQ/56jgS10nRe8o/VQROfd1De6sk5k9ypk2y2iC7
pp7sime3MsnzjR0fKcxjCDAX1Sopi9QzkypM66WHO46Uamn0GeL7OhcwjcoURzJc
y5ue1jeeKmmR7ATexlgE87rA4nuMZak1uVz0s6Fsb1ekYIKFEFqwEpqNnyKgBrvt
GA8yn9Zp4wF+6luaKMqwptx0q1PmMZacN9E62XdOyGBaVXPq599F3R6049zo04oW
WMW7s7V10Vn/pqRkB4bhhUzatiUHWVuamhpR8SligZ4kWPHZuQINBFANDtsBEADJ
MvBhyWEBoLgi9nO9hgbbLxiLjKnotKzRpu5m79rhpmtqEN2k0APmoRdUUrE2Y8sd
Z551jT0TE2O1j70qLV9c5puK6qyV3BZb9OgQ8wqyCFFjmIdPQAEgnD3K92SOh8Mu
lqJW++EsxSqDBt2iVbLyzQuklbIJOg4nVK8ZgswIyewiHVeh3xgcGxJtVb3QjKSz
xhj0GYM6X7mo5rjzxEFVyiJXd8ZH79y4FHUr+tJQEzJbvWAGJClPx9czh9Drw2r+
Tgq+2v/EkAoyXdv3z7uChh/9s33oQtssJ5TVfAbC06QsPp55RJCkGbTrBQgRB8Xa
idPJhG9sIbhVgrRejn5kmK5L/8ACngbSU4zO1NTaquLZGZMzI7Jbx/QeG2DbVqjV
6BLA3X+WieM9IUa1vi/CXiKedzlOMv7+CX9N76y4Wls4zsTwXTIzmPCDoysG+75D
AoFZW1XJlMeEOGPhBDlR6X47qdLGzOI14NJZELni14+Iih6VvxhvUWmJfTnT+zE+
PWb03C0EHsF8uTYqBPag9LYw2cDqvP31bUSiV+Z5YR+4sDcfiVT40rXECd1752Ia
yANeL7uG/gBvl+6LHzb3VhI5ILkknVummVY0ry8YpAA0r1GfBE/mFLE3jwTqYkMx
nr/SDa3HIql7qBh9k9AhTMId8tXX6p3lO6jd6TeewQARAQABiQIfBBgBAgAJBQJQ
DQ7bAhsMAAoJEJqEFZ1wAaTlTeIP/R6/0EAqKyISxnx/6+VCy2j5mj4v8b+KauTT
deXJhP37i2EMsosGqUD1LMo8Wv9Az7XOSA/2lW4v8UHQolGwssLFm9L4DDTZBH8M
gOFzB++wHNxgIoD2u9vC2eehhMfVlCcH+YWtzTSs94+5gI9xcy8rejkO+AHhXYDR
0nr5MM076CWEjWtORgZdHbXWilmr/SdKnIdvkXDXvNcd7tC6izIfnDxN7/4beWOF
nv/1s0JLzIFIutFtqqYDC8Y/4JxoMcALhmop+FyYk+RUHT5uGeDauJunjwdBS3VY
5NrMcw4LxapV0OQExxu8RAMcYXx6FZmoBMlFI+J6R5ZMS6y1TKLKIpq46CsOSz2V
X1SAactdOpXuNOXLe2cv0mYswWGAURebcmcmN49n9JEn1IN8hhawFgFuYdUWjpYx
K2K6NZ0vFgRHkRnei9xrf2mW7ob1vKwzeBYGvZj/xIEu/Fv3kizS6t1IeMJKUKlp
semAObW+sEO0jLOelL+ZfUO+fImL+0fFxQyzkfNKk5dpxztPlNmv3DY6KTddc1L1
uOGCznAmsg8Jp0v0OmCB2Xl15WPwwnYi3CkLGbEcK9stFmu2pZuEeR2DuVGlz3Nr
Bu1W/34cXeyudUTxveehuvfkjYBMXVfEM35BHEUqgCtA49b5ZM8SpYgqU3omVsNz
7RjMAIe3uQINBFaoGQEBEACypQbnC5fMhpCft6augXnnVzmEh0Se2wBxUum3DMFl
U48DJYNlEsKQYsgzEvaayTI0gA1ZyeDg3E4Fnk6ysQmzW9BJ/3Q2pa0GKIkvXOgL
nwvSXSnTTqK3zCDuJI0Nj4u9gI8bX7d4PHqQyyFzPWjiIbg9tWHbhT8wwCaay1iG
qCZsTa8Iwyve0WaV+7YtRJQeXEfY9Z6oEGqjis7QJNef1MKy1gS1Kq+4sqvdwgo3
f3AFSNR14gagYv2myA30ehf4EzzIi8dh9DATc4T86CBuK3TszILLSUwFnrUVT00j
3sr2WiqWrxIq/paky8zNEV3Q0vaf2kheLSZRSZsoVH1AfCsVHj4OoWH3BOAzdr5w
sTPsV2HVR0O9K98NoA0f45eqfMFBiGVsFuJamBJatPsTVVXZ6fR9aDDKySSB540W
egHoABwai+s4FhUtjjZarLPJo47/gFmxF7HHUyNIYYKj5j99h00zcM7XOtKeyHzH
vfZvmu150u2DccY9XX5xdzDsWuBeQJQ7VdyVVTPoZGIBZ1Mw3EIfFyAzYvglTCKo
MMHH6912PnsMLcbcmF/7pJ4rrehp8bkfS7R3BBNMglLWtoZIYBSWoK8Nl0OyBzQ5
lsPvRdUHuMe6rxwA11gRnx4yLd91bWRw/xIBwxz6Jq/lk0ySgdum/hIcOmrNdg73
swARAQABiQQ+BBgBAgAJBQJWqBkBAhsCAikJEJqEFZ1wAaTlwV0gBBkBAgAGBQJW
qBkBAAoJEAtYjf8FJ6m3uPQP/jonQ5QrOEN+B7ddYmotAwj6wL9whs7rcgb+TJfI
rJskyBQG+kHymWEBiODo/AcqkKglGzTNcaDG153l38/IQ/Lo9mxZmkNHtLD6Srkb
GjNrBIksRSR+hWbr8UjA/WuqAmDQvFWebmyF4p6deEAf3Rsv2Ml/a0lvC6TyWRds
dMyWlDPtlKYn9q2qHjCVX4e2m4uB38Vq3fo8+Ypags5KRC9KyGgZFEGy3F37p+4U
/rDT+t/6oKuo5/8RWAixeWNWIli7DQ9DbA4w9qdwL4KGwcnOCcCClT9KqKA3MOds
r+dcAF+LkNrG3yvnp+eZ7LHF6PVJAkXnSLOzuEZzXp90pA+1Sw7QWGWpyV/v6CSP
sxZInLPjBPJmdVhjMrma2s9SGKs2k4eivDeK4Gr6oF3GRK3VQzsRPDYk9vr8wSbA
BR4Qh6Fy/WiME1SyEdcs2UWc+eGZ0ya7QEkQqz7Cvjp6VezUtXWFgy28pVkgEORE
sqogypjbSnybeis6Rzvxf0g681fWjWSTcMBGXKTvgW7XnLqG9XMWdx0n6pa6s5yL
nkoM5zYcjL+V6wDVQwnF9uoallcO11PEjsEMeK3gz0Y81EmLuHN6411IK1cWfxAI
HunwOoPe7nzGOH2EX1+IkCv4PPJqMloEcIIPlwP0ILxTaPuy17wed0pH3dvRZglv
J7qQU/AP/3Nhh5AjUtMRI+mDgJpXpYuMy7tNdgKAvgGIL1bP+1LnLtP2+7uy1Pok
3938/eAJwAT9sOhpG3qRPs15zj4tc20/Jsypbk4032EhRFWGL5TBtYf9LVl9doFY
hGHUgAzREWcw8l7h0/p5VNPm9K0kElCw2fN3z7xwreSPa1WGVsqMdM3nc/B44ocZ
HFqrd9kg6N4al6IxkbL7DYAUjfhjkhOQXZXU+MLPVA9lflF4694lbrr7F4GwnzfF
8/SOw9LHVh9K3agno6jHB64P/Vqtka4a5+9AKcEkojWEHDoLU8BMUXDZZu9OkFSJ
tzqByAzti0dsLE+8GGfas47b3/rjf6qyKvh2+iPw+I6zHH4k7LKpIiK+MkQ2+ZfM
zB3jQ9NPNAc63AL/YbnF5XDSFxGBRkcFZo50rZJV5WHA3p+UMISsjoNizE7agLqI
2JPnKBzB3aQFFpI3QiivWhKfsO02Vtzl+1TjqReWu4qS1uwJbSsLectAy+4K2WFn
RNZ1m/r9/enzEhyKo8zOmUBUzVLhRR/GG7S1MmxB8FSOZh7aHg1xbTYVvq62AW6a
0PKvEpn1g0wvPEoKQ+41/dc/ieWgnkZiKJJFxDshKGmxfDznWnZqnOESLz/3hbGy
U41nk0UPzHlkUZwsLo9UBdr9fUVYvlYUylBp2dV0jEc5qL62gypA
=4tCW
-----END PGP PUBLIC KEY BLOCK-----

3
docs/.markdownlint.json Normal file
View File

@ -0,0 +1,3 @@
{
"MD024": { "siblings_only": true }
}

505
docs/CHANGELOG.md Normal file
View File

@ -0,0 +1,505 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog][kac] and this project adheres to
[Semantic Versioning][semver].
[kac]: https://keepachangelog.com/en/1.0.0/
[semver]: https://semver.org/
## [1.9.0] - 2023-02-12
### Added
* add installation instructions for Debian, Fedora, Gentoo, and OpenSUSE (#659)
* add `--line-reference-format` to switch file/line references in stack traces (#665)
* `comma_line` (default): `file.bats, line 1`
* `colon`: `file.bats:1`
* `uri`: `file:///path/to/file.bats:1`
* `custom`: define your own formatter in `bats_format_file_line_reference_custom`
* add `bats:focus` tag to run only focused tests (#679)
* add bats-support, bats-assert, bats-file and bats-detik to Dockerfile (#674)
### Documentation
* add `--help` text and `man` page content for `--filter-tags` (#679)
### Fixed
* explicitly check for GNU parallel (#691)
* wait for report-formatter to finish before ending `bats`' execution,
to fix empty files with `--report-fomatter junit` under Docker (#692)
#### Documentation
* improved clarity of section about output in free code (#671)
* fixed typos (#673)
* clarify use cases of `run` (#366)
## [1.8.2] - 2022-10-19
### Fixed
* fix non zero return code on successful retried tests (#670)
* fix `skip` in `setup_file` failing test suite (#687)
## [1.8.1] - 2022-10-19
### Fixed
* `shfmt` all files and enforce via CI (#651)
* avoid kernel warning flood/hang with CTRL+C on Bash 5.2 RC (#656)
* Fix infinite wait with (invalid) `-j<n>` (without space) (#657)
## [1.8.0] - 2022-09-15
### Added
* using external formatters via `--formatter <absolute path>` (also works for
`--report-formatter`) (#602)
* running only tests that failed in the last run via `--filter-status failed` (#483)
* variable `BATS_TEST_RETRIES` that specifies how often a test should be
reattempted before it is considered failed (#618)
* Docker tags `latest-no-faccessat2` and `<bats-version\>-no-faccessat2` for
avoiding `bash: bats: No such file or directory` on `docker<20.10` (or
`runc<v1.0.0-rc93`) (#622)
* `BATS_TEST_TIMEOUT` variable to force a timeout on test (including `setup()`) (#491)
* also print (nonempty) `$stderr` (from `run --separate-stderr`) with
`--print-output-on-failure` (#631)
* `# bats test_tags=<tag list>`/`# bats file_tags=<tag list>` and
`--filter-tags <tag list>` for tagging tests for execution filters (#642)
* warning BW03: inform about `setup_suite` in wrong file (`.bats` instead of `setup_suite.bash`) (#652)
#### Documentation
* update gotcha about negated statements: Recommend using `run !` on Bats
versions >=1.5.0 (#593)
* add documentation for `bats_require_minimum_version` (#595)
* improve documentation about `setup_suite` (#652)
### Fixed
* added missing shebang (#597)
* remaining instances of `run -<N>` being incorrectly documented as `run =<N>` (#599)
* allow `--gather-test-outputs-in <directory>` to work with existing, empty
directories (#603)
* also add `--clean-and-gather-test-outputs-in <directory>` for improved UX
* double slashes in paths derived from TMPDIR on MacOS (#607)
* fix `load` in `teardown` marking failed tests as not run (#612)
* fix unset variable errors (with set -u) and add regression test (#621)
* `teardown_file` errors don't swallow `setup_file` errors anymore, the behavior
is more like `teardown`'s now (only `return`/last command can trigger `teardown`
errors) (#623)
* upgraded from deprecated CI envs for MacOS (10 -> 11,12) and Ubuntu
(18.04 -> 22.04) (#630)
* add `/usr/lib/bats` as default value for `BATS_LIB_PATH` (#628)
* fix unset variable in `bats-formatter-junit` when `setup_file` fails (#632)
* unify error behavior of `teardown`/`teardown_file`/`teardown_suite` functions:
only fail via return code, not via ERREXIT (#633)
* fix unbound variable errors with `set -u` on `setup_suite` failures (#643)
* fix `load` not being available in `setup_suite` (#644)
* fix RPM spec, add regression test (#648)
* fix handling of `IFS` by `run` (#650)
* only print `setup_suite`'s stderr on errors (#649)
#### Documentation
* fix typos, spelling and links (#596, #604, #619, #627)
* fix redirection order of an example in the tutorial (#617)
## [1.7.0] - 2022-05-14
### Added
* Pretty formatter print filename when entering file (#561)
* BATS_TEST_NAME_PREFIX allows prefixing test names on stdout and in reports (#561)
* setup_suite and teardown_suite (#571, #585)
* out-of-band warning infrastructure, with following warnings:
* BW01: run command not found (exit code 127) (#586)
* BW02: run uses flags without proper `bats_require_minimum_version` guard (#587)
* `bats_require_minimum_version` to guard code that would not run on older
versions (#587)
#### Documentation
* document `$BATS_VERSION` (#557)
* document new warning infrastructure (#589, #587, #586)
### Fixed
* unbound variable errors in formatters when using `SHELLOPTS=nounset` (`-u`) (#558)
* don't require `flock` *and* `shlock` for parallel mode test (#554)
* print name of failing test when using TAP13 with timing information (#559, #555)
* removed broken symlink, added regression test (#560)
* don't show empty lines as `#` with pretty formatter (#561)
* prevent `teardown`, `teardown_file`, and `teardown_suite` from overriding bats'
exit code by setting `$status` (e.g. via calling `run`) (#581, #575)
* **CRITICAL**: this can return exit code 0 despite failed tests, thus preventing
your CI from reporting test failures! The regression happened in version 1.6.0.
* `run --keep-empty-lines` now reports 0 lines on empty `$output` (#583)
#### Documentation
* remove 2018 in title, update copyright dates in README.md (#567)
* fix broken links (#568)
* corrected invalid documentation of `run -N` (had `=N` instead) (#579)
* **CRITICAL**: using the incorrect form can lead to silent errors. See
[issue #578](https://github.com/bats-core/bats-core/issues/578) for more
details and how to find out if your tests are affected.
## [1.6.1] - 2022-05-14
### Fixed
* prevent `teardown`, `teardown_file`, and `teardown_suite` from overriding bats'
exit code by setting `$status` (e.g. via calling `run`) (#581, #575)
* **CRITICAL**: this can return exit code 0 despite failed tests, thus preventing
your CI from reporting test failures! The regression happened in version 1.6.0.
#### Documentation
* corrected invalid documentation of `run -N` (had `=N` instead) (#579)
* **CRITICAL**: using the incorrect form can lead to silent errors. See
[issue #578](https://github.com/bats-core/bats-core/issues/578) for more
details and how to find out if your tests are affected.
## [1.6.0] - 2022-02-24
### Added
* new flag `--code-quote-style` (and `$BATS_CODE_QUOTE_STYLE`) to customize
quotes around code blocks in error output (#506)
* an example/regression test for running background tasks without blocking the
test run (#525, #535)
* `bats_load_library` for loading libraries from the search path
`$BATS_LIB_PATH` (#548)
### Fixed
* improved error trace for some broken cases (#279)
* removed leftover debug file `/tmp/latch` in selftest suite
(single use latch) (#516)
* fix recurring errors on CTRL+C tests with NPM on Windows in selftest suite (#516)
* fixed leaking of local variables from debug trap (#520)
* don't mark FD3 output from `teardown_file` as `<failure>` in junit output (#532)
* fix unbound variable error with Bash pre 4.4 (#550)
#### Documentation
* remove links to defunct freenode IRC channel (#515)
* improved grammar (#534)
* fixed link to TAP spec (#537)
## [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
### Added
* custom test-file extension via `BATS_FILE_EXTENSION` when searching for test
files in a directory (#376)
* TAP13 formatter, including millisecond timing (#337)
* automatic release to NPM via GitHub Actions (#406)
#### Documentation
* added documentation about overusing `run` (#343)
* improved documentation of `load` (#332)
### Changed
* recursive suite mode will follow symlinks now (#370)
* split options for (file-) `--report-formatter` and (stdout) `--formatter` (#345)
* **WARNING**: This changes the meaning of `--formatter junit`.
stdout will now show unified xml instead of TAP. From now on, please use
`--report-formatter junit` to obtain the `.xml` report file!
* removed `--parallel-preserve-environment` flag, as this is the default
behavior (#324)
* moved CI from Travis/AppVeyor to GitHub Actions (#405)
* preprocessed files are no longer removed if `--no-tempdir-cleanup` is
specified (#395)
#### Documentation
* moved documentation to [readthedocs](https://bats-core.readthedocs.io/en/latest/)
### Fixed
#### Correctness
* fix internal failures due to unbound variables when test files use `set -u` (#392)
* fix internal failures due to changes to `$PATH` in test files (#387)
* fix test duration always being 0 on busybox installs (#363)
* fix hangs on CTRL+C (#354)
* make `BATS_TEST_NUMBER` count per file again (#326)
* include `lib/` in npm package (#352)
#### Performance
* don't fork bomb in parallel mode (#339)
* preprocess each file only once (#335)
* avoid running duplicate files n^2 times (#338)
#### Documentation
* fix documentation for `--formatter junit` (#334)
* fix documentation for `setup_file` variables (#333)
* fix link to examples page (#331)
* fix link to "File Descriptor 3" section (#301)
## [1.2.1] - 2020-07-06
### Added
* JUnit output and extensible formatter rewrite (#246)
* `load` function now reads from absolute and relative paths, and $PATH (#282)
* Beginner-friendly examples in /docs/examples (#243)
* @peshay's `bats-file` fork contributed to `bats-core/bats-file` (#276)
### Changed
* Duplicate test names now error (previous behaviour was to issue a warning) (#286)
* Changed default formatter in Docker to pretty by adding `ncurses` to
Dockerfile, override with `--tap` (#239)
* Replace "readlink -f" dependency with Bash solution (#217)
## [1.2.0] - 2020-04-25
Support parallel suite execution and filtering by test name.
### Added
* docs/CHANGELOG.md and docs/releasing.md (#122)
* The `-f, --filter` flag to run only the tests matching a regular expression (#126)
* Optimize stack trace capture (#138)
* `--jobs n` flag to support parallel execution of tests with GNU parallel (#172)
### Changed
* AppVeyor builds are now semver-compliant (#123)
* Add Bash 5 as test target (#181)
* Always use upper case signal names to avoid locale dependent err… (#215)
* Fix for tests reading from stdin (#227)
* Fix wrong line numbers of errors in bash < 4.4 (#229)
* Remove preprocessed source after test run (#232)
## [1.1.0] - 2018-07-08
This is the first release with new features relative to the original Bats 0.4.0.
### Added
* The `-r, --recursive` flag to scan directory arguments recursively for
`*.bats` files (#109)
* The `contrib/rpm/bats.spec` file to build RPMs (#111)
### Changed
* Travis exercises latest versions of Bash from 3.2 through 4.4 (#116, #117)
* Error output highlights invalid command line options (#45, #46, #118)
* Replaced `echo` with `printf` (#120)
### Fixed
* Fixed `BATS_ERROR_STATUS` getting lost when `bats_error_trap` fired multiple
times under Bash 4.2.x (#110)
* Updated `bin/bats` symlink resolution, handling the case on CentOS where
`/bin` is a symlink to `/usr/bin` (#113, #115)
## [1.0.2] - 2018-06-18
* Fixed sstephenson/bats#240, whereby `skip` messages containing parentheses
were truncated (#48)
* Doc improvements:
* Docker usage (#94)
* Better README badges (#101)
* Better installation instructions (#102, #104)
* Packaging/installation improvements:
* package.json update (#100)
* Moved `libexec/` files to `libexec/bats-core/`, improved `install.sh` (#105)
## [1.0.1] - 2018-06-09
* Fixed a `BATS_CWD` bug introduced in #91 whereby it was set to the parent of
`PWD`, when it should've been set to `PWD` itself (#98). This caused file
names in stack traces to contain the basename of `PWD` as a prefix, when the
names should've been purely relative to `PWD`.
* Ensure the last line of test output prints when it doesn't end with a newline
(#99). This was a quasi-bug introduced by replacing `sed` with `while` in #88.
## [1.0.0] - 2018-06-08
`1.0.0` generally preserves compatibility with `0.4.0`, but with some Bash
compatibility improvements and a massive performance boost. In other words:
* all existing tests should remain compatible
* tests that might've failed or exhibited unexpected behavior on earlier
versions of Bash should now also pass or behave as expected
Changes:
* Added support for Docker.
* Added support for test scripts that have the [unofficial strict
mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/) enabled.
* Improved stability on Windows and macOS platforms.
* Massive performance improvements, especially on Windows (#8)
* Workarounds for inconsistent behavior between Bash versions (#82)
* Workaround for preserving stack info after calling an exported function under
Bash < 4.4 (#87)
* Fixed TAP compliance for skipped tests
* Added support for tabs in test names.
* `bin/bats` and `install.sh` now work reliably on Windows (#91)
## [0.4.0] - 2014-08-13
* Improved the display of failing test cases. Bats now shows the source code of
failing test lines, along with full stack traces including function names,
filenames, and line numbers.
* Improved the display of the pretty-printed test summary line to include the
number of skipped tests, if any.
* Improved the speed of the preprocessor, dramatically shortening test and suite
startup times.
* Added support for absolute pathnames to the `load` helper.
* Added support for single-line `@test` definitions.
* Added bats(1) and bats(7) manual pages.
* Modified the `bats` command to default to TAP output when the `$CI` variable
is set, to better support environments such as Travis CI.
## [0.3.1] - 2013-10-28
* Fixed an incompatibility with the pretty formatter in certain environments
such as tmux.
* Fixed a bug where the pretty formatter would crash if the first line of a test
file's output was invalid TAP.
## [0.3.0] - 2013-10-21
* Improved formatting for tests run from a terminal. Failing tests are now
colored in red, and the total number of failing tests is displayed at the end
of the test run. When Bats is not connected to a terminal (e.g. in CI runs),
or when invoked with the `--tap` flag, output is displayed in standard TAP
format.
* Added the ability to skip tests using the `skip` command.
* Added a message to failing test case output indicating the file and line
number of the statement that caused the test to fail.
* Added "ad-hoc" test suite support. You can now invoke `bats` with multiple
filename or directory arguments to run all the specified tests in aggregate.
* Added support for test files with Windows line endings.
* Fixed regular expression warnings from certain versions of Bash.
* Fixed a bug running tests containing lines that begin with `-e`.
## [0.2.0] - 2012-11-16
* Added test suite support. The `bats` command accepts a directory name
containing multiple test files to be run in aggregate.
* Added the ability to count the number of test cases in a file or suite by
passing the `-c` flag to `bats`.
* Preprocessed sources are cached between test case runs in the same file for
better performance.
## [0.1.0] - 2011-12-30
* Initial public release.
[Unreleased]: https://github.com/bats-core/bats-core/compare/v1.7.0...HEAD
[1.7.0]: https://github.com/bats-core/bats-core/compare/v1.6.1...v1.7.0
[1.6.1]: https://github.com/bats-core/bats-core/compare/v1.6.0...v1.6.1
[1.6.0]: https://github.com/bats-core/bats-core/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/bats-core/bats-core/compare/v1.4.1...v1.5.0
[1.4.1]: https://github.com/bats-core/bats-core/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/bats-core/bats-core/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/bats-core/bats-core/compare/v1.2.1...v1.3.0
[1.2.1]: https://github.com/bats-core/bats-core/compare/v1.2.0...v1.2.1
[1.2.0]: https://github.com/bats-core/bats-core/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/bats-core/bats-core/compare/v1.0.2...v1.1.0
[1.0.2]: https://github.com/bats-core/bats-core/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/bats-core/bats-core/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/bats-core/bats-core/compare/v0.4.0...v1.0.0
[0.4.0]: https://github.com/bats-core/bats-core/compare/v0.3.1...v0.4.0
[0.3.1]: https://github.com/bats-core/bats-core/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/bats-core/bats-core/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/bats-core/bats-core/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/bats-core/bats-core/commits/v0.1.0

4
docs/CODEOWNERS Normal file
View File

@ -0,0 +1,4 @@
# This enables automatic code review requests per:
# - https://help.github.com/articles/about-codeowners/
# - https://help.github.com/articles/enabling-required-reviews-for-pull-requests/
* @bats-core/bats-core

92
docs/CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,92 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting one of the current [project maintainers](#project-maintainers) listed below. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Project Maintainers
### Current Maintainers
* [Bianca Tamayo][bt-gh]
* [Mike Bland][mb-gh]
* [Jason Karns][jk-gh]
* [Andrew Martin][am-gh]
### Past Maintainers
* Sam Stephenson <<sstephenson@gmail.com>> (Original author)
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[bt-gh]: https://github.com/btamayo
[mb-gh]: https://github.com/mbland
[jk-gh]: https://github.com/jasonkarns
[am-gh]: https://github.com/sublimino
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/

392
docs/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,392 @@
# Contributing Guidelines
## Welcome!
Thank you for considering contributing to the development of this project's
development and/or documentation. Just a reminder: if you're new to this project
or to OSS and want to find issues to work on, please check the following labels
on issues:
- [help wanted][helpwantedlabel]
- [docs][docslabel]
- [good first issue][goodfirstissuelabel]
[docslabel]: https://github.com/bats-core/bats-core/labels/docs
[helpwantedlabel]: https://github.com/bats-core/bats-core/labels/help%20wanted
[goodfirstissuelabel]: https://github.com/bats-core/bats-core/labels/good%20first%20issue
To see all labels and their meanings, [check this wiki page][labelswiki].
This guide borrows **heavily** from [@mbland's go-script-bash][gsb] (with some
sections directly quoted), which in turn was
drafted with tips from [Wrangling Web Contributions: How to Build
a CONTRIBUTING.md][moz] and with some inspiration from [the Atom project's
CONTRIBUTING.md file][atom].
[gsb]: https://github.com/mbland/go-script-bash/blob/master/CONTRIBUTING.md
[moz]: https://mozillascience.github.io/working-open-workshop/contributing/
[atom]: https://github.com/atom/atom/blob/master/CONTRIBUTING.md
[labelswiki]: https://github.com/bats-core/bats-core/wiki/GitHub-Issue-Labels
## Table of contents
* [Contributing Guidelines](#contributing-guidelines)
* [Welcome!](#welcome)
* [Table of contents](#table-of-contents)
* [Quick links](#quick-links)
* [Contributor License Agreement](#contributor-license-agreement)
* [Code of conduct](#code-of-conduct)
* [Asking questions and reporting issues](#asking-questions-and-reporting-issues)
* [Updating documentation](#updating-documentation)
* [Environment setup](#environment-setup)
* [Workflow](#workflow)
* [Testing](#testing)
* [Coding conventions](#coding-conventions)
* [Formatting](#formatting)
* [Naming](#naming)
* [Function declarations](#function-declarations)
* [Variable and parameter declarations](#variable-and-parameter-declarations)
* [Command substitution](#command-substitution)
* [Process substitution](#process-substitution)
* [Conditionals and loops](#conditionals-and-loops)
* [Generating output](#generating-output)
* [Gotchas](#gotchas)
* [Open Source License](#open-source-license)
* [Credits](#credits)
## Quick links
- [Gitter channel →][gitterurl]: Feel free to come chat with us on Gitter
- [README →][README]
- [Code of conduct →][CODE_OF_CONDUCT]
- [License information →][LICENSE]
- [Original repository →][repohome]
- [Issues →][repoissues]
- [Pull requests →][repoprs]
- [Milestones →][repomilestones]
- [Projects →][repoprojects]
[README]: https://github.com/bats-core/bats-core/blob/master/README.md
[CODE_OF_CONDUCT]: https://github.com/bats-core/bats-core/blob/master/docs/CODE_OF_CONDUCT.md
[LICENSE]: https://github.com/bats-core/bats-core/blob/master/LICENSE.md
## Contributor License Agreement
Per the [GitHub Terms of Service][gh-tos], be aware that by making a
contribution to this project, you agree:
* to license your contribution under the same terms as [this project's
license][osmit], and
* that you have the right to license your contribution under those terms.
See also: ["Does my project need an additional contributor agreement? Probably
not."][cla-needed]
[gh-tos]: https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license
[osmit]: #open-source-license
[cla-needed]: https://opensource.guide/legal/#does-my-project-need-an-additional-contributor-agreement
## Code of conduct
Harassment or rudeness of any kind will not be tolerated, period. For
specifics, see the [CODE_OF_CONDUCT][] file.
## Asking questions and reporting issues
### Asking questions
Please check the [README][] or existing [issues][repoissues] first.
If you cannot find an answer to your question, please feel free to hop on our
[Gitter][gitterurl]. [![Gitter](https://badges.gitter.im/bats-core/bats-core.svg)](https://gitter.im/bats-core/bats-core)
### Reporting issues
Before reporting an issue, please use the search feature on the [issues
page][repoissues] to see if an issue matching the one you've observed has already
been filed.
### Updating or filing a new issue
#### Information to include
Try to be as specific as possible about your environment and the problem you're
observing. At a minimum, include:
#### Installation issues
1. State the version of Bash you're using `bash --version`
1. State your operating system and its version
1. If you're installing through homebrew, run `brew doctor`, and attach the
output of `brew info bats-core`
#### Bugs/usage issues
1. State the version of Bash you're using `bash --version`
1. State your operating system and its version
1. Command line steps or code snippets that reproduce the issue
1. Any apparently relevant information from the [Bash changelog][bash-changes]
[bash-changes]: https://tiswww.case.edu/php/chet/bash/CHANGES
Also consider using:
- Bash's `time` builtin to collect running times
- a regression test to add to the suite
- memory usage as reported by a tool such as
[memusg](https://gist.github.com/netj/526585)
### On existing issues
1. DO NOT add a +1 comment: Use the reactions provided instead
1. DO add information if you're facing a similar issue to someone else, but
within a different context (e.g. different steps needed to reproduce the issue
than previous stated, different version of Bash or BATS, different OS, etc.)
You can read on how to do that here: [Information to include](#information-to-include)
1. DO remember that you can use the *Subscribe* button on the right side of the
page to receive notifications of further conversations or a resolution.
## Updating documentation
We love documentation and people who love documentation!
If you love writing clear, accessible docs, please don't be shy about pull
requests. Remember: docs are just as important as code.
Also: _no typo is too small to fix!_ Really. Of course, batches of fixes are
preferred, but even one nit is one nit too many.
## Environment setup
Make sure you have Bash installed per the [Environment setup in the
README][env-setup].
[env-setup]: https://github.com/bats-core/bats-core/blob/master/README.md#environment-setup
## Workflow
The basic workflow for submitting changes resembles that of the [GitHub Git
Flow][github-flow] (a.k.a. GitHub Flow), except that you will be working with
your own fork of the repository and issuing pull requests to the original.
[github-flow]: https://guides.github.com/introduction/flow/
1. Fork the repo on GitHub (look for the "Fork" button)
1. Clone your forked repo to your local machine
1. Create your feature branch (`git checkout -b my-new-feature`)
1. Develop _and [test](#testing)_ your changes as necessary.
1. Commit your changes (`git commit -am 'Add some feature'`)
1. Push to the branch (`git push origin my-new-feature`)
1. Create a new [GitHub pull request][gh-pr] for your feature branch based
against the original repository's `master` branch
1. If your request is accepted, you can [delete your feature branch][rm-branch]
and pull the updated `master` branch from the original repository into your
fork. You may even [delete your fork][rm-fork] if you don't anticipate making
further changes.
[gh-pr]: https://help.github.com/articles/using-pull-requests/
[rm-branch]: https://help.github.com/articles/deleting-unused-branches/
[rm-fork]: https://help.github.com/articles/deleting-a-repository/
## Testing
- Continuous integration status: [![Tests](https://github.com/bats-core/bats-core/workflows/Tests/badge.svg)](https://github.com/bats-core/bats-core/actions?query=workflow%3ATests)
## Coding conventions
- [Formatting](#formatting)
- [Naming](#naming)
- [Variable and parameter declarations](#variable-and-parameter-declarations)
- [Command substitution](#command-substitution)
- [Conditions and loops](#conditionals-and-loops)
- [Gotchas](#gotchas)
### Formatting
- Keep all files 80 characters wide.
- Indent using two spaces.
- Enclose all variables in double quotes when used to avoid having them
interpreted as glob patterns (unless the variable contains a glob pattern)
and to avoid word splitting when the value contains spaces. Both scenarios
can introduce errors that often prove difficult to diagnose.
- **This is especially important when the variable is used to generate a
glob pattern**, since spaces may appear in a path value.
- If the variable itself contains a glob pattern, make sure to set
`IFS=$'\n'` before using it so that the pattern itself and any matching
file names containing spaces are not split apart.
- Exceptions: Quotes are not required within math contexts, i.e. `(( ))` or
`$(( ))`, and must not be used for variables on the right side of the `=~`
operator.
- Enclose all string literals in single quotes.
- Exception: If the string contains an apostrophe, use double quotes.
- Use quotes around variables and literals even inside of `[[ ]]` conditions.
- This is because strings that contain '[' or ']' characters may fail to
compare equally when they should.
- Exception: Do not quote variables that contain regular expression patterns
appearing on the right side of the `=~` operator.
- _Only_ quote arguments to the right of `=~` if the expression is a literal
match without any metacharacters.
The following are intended to prevent too-compact code:
- Declare only one item per `declare`, `local`, `export`, or `readonly` call.
- _Note:_ This also helps avoid subtle bugs, as trying to initialize one
variable using the value of another declared in the same statement will
not do what you may expect. The initialization of the first variable will
not yet be complete when the second variable is declared, so the first
variable will have an empty value.
- Do not use one-line `if`, `for`, `while`, `until`, `case`, or `select`
statements.
- Do not use `&&` or `||` to avoid writing `if` statements.
- Do not write functions entirely on one line.
- For `case` statements: put each pattern on a line by itself; put each command
on a line by itself; put the `;;` terminator on a line by itself.
### Naming
- Use `snake_case` for all identifiers.
### Function declarations
- Declare functions without the `function` keyword.
- Strive to always use `return`, never `exit`, unless an error condition is
severe enough to warrant it.
- Calling `exit` makes it difficult for the caller to recover from an error,
or to compose new commands from existing ones.
### Variable and parameter declarations
- _Gotcha:_ Never initialize an array on the same line as an `export` or
`declare -g` statement. See [the Gotchas section](#gotchas) below for more
details.
- Declare all variables inside functions using `local`.
- Declare temporary file-level variables using `declare`. Use `unset` to remove
them when finished.
- Don't use `local -r`, as a readonly local variable in one scope can cause a
conflict when it calls a function that declares a `local` variable of the same
name.
- Don't use type flags with `declare` or `local`. Assignments to integer
variables in particular may behave differently, and it has no effect on array
variables.
- For most functions, the first lines should use `local` declarations to
assign the original positional parameters to more meaningful names, e.g.:
```bash
format_summary() {
local cmd_name="$1"
local summary="$2"
local longest_name_len="$3"
```
For very short functions, this _may not_ be necessary, e.g.:
```bash
has_spaces() {
[[ "$1" != "${1//[[:space:]]/}" ]]
}
```
### Command substitution
- If possible, don't. While this capability is one of Bash's core strengths,
every new process created by Bats makes the framework slower, and speed is
critical to encouraging the practice of automated testing. (This is especially
true on Windows, [where process creation is one or two orders of magnitude
slower][win-slow]. See [bats-core/bats-core#8][pr-8] for an illustration of
the difference avoiding subshells makes.) Bash is quite powerful; see if you
can do what you need in pure Bash first.
- If you need to capture the output from a function, store the output using
`printf -v` instead if possible. `-v` specifies the name of the variable into
which to write the result; the caller can supply this name as a parameter.
- If you must use command substitution, use `$()` instead of backticks, as it's
more robust, more searchable, and can be nested.
[win-slow]: https://rufflewind.com/2014-08-23/windows-bash-slow
[pr-8]: https://github.com/bats-core/bats-core/pull/8
### Process substitution
- If possible, don't use it. See the advice on avoiding subprocesses and using
`printf -v` in the **Command substitution** section above.
- Use wherever necessary and possible, such as when piping input into a `while`
loop (which avoids having the loop body execute in a subshell) or running a
command taking multiple filename arguments based on output from a function or
pipeline (e.g. `diff`).
- *Warning*: It is impossible to directly determine the exit status of a process
substitution; emitting an exit status as the last line of output is a possible
workaround.
### Conditionals and loops
- Always use `[[` and `]]` for evaluating variables. Per the guideline under
**Formatting**, quote variables and strings within the brackets, but not
regular expressions (or variables containing regular expressions) appearing
on the right side of the `=~` operator.
### Generating output
- Use `printf` instead of `echo`. Both are Bash builtins, and there's no
perceptible performance difference when running Bats under the `time` builtin.
However, `printf` provides a more consistent experience in general, as `echo`
has limitations to the arguments it accepts, and even the same version of Bash
may produce different results for `echo` based on how the binary was compiled.
See [Stack Overflow: Why is printf better than echo?][printf-vs-echo] for
excruciating details.
[printf-vs-echo]: https://unix.stackexchange.com/a/65819
### Signal names
Always use upper case signal names (e.g. `trap - INT EXIT`) to avoid locale
dependent errors. In some locales (for example Turkish, see
[Turkish dotless i](https://en.wikipedia.org/wiki/Dotted_and_dotless_I)) lower
case signal names cause Bash to error. An example of the problem:
```bash
$ echo "tr_TR.UTF-8 UTF-8" >> /etc/locale.gen && locale-gen tr_TR.UTF-8 # Ubuntu derivatives
$ LC_CTYPE=tr_TR.UTF-8 LC_MESSAGES=C bash -c 'trap - int && echo success'
bash: line 0: trap: int: invalid signal specification
$ LC_CTYPE=tr_TR.UTF-8 LC_MESSAGES=C bash -c 'trap - INT && echo success'
success
```
### Gotchas
- If you wish to use command substitution to initialize a `local` variable, and
then check the exit status of the command substitution, you _must_ declare the
variable on one line and perform the substitution on another. If you don't,
the exit status will always indicate success, as it is the status of the
`local` declaration, not the command substitution.
- To work around a bug in some versions of Bash whereby arrays declared with
`declare -g` or `export` and initialized in the same statement eventually go
out of scope, always `export` the array name on one line and initialize it the
next line. See:
- https://lists.gnu.org/archive/html/bug-bash/2012-06/msg00068.html
- ftp://ftp.gnu.org/gnu/bash/bash-4.2-patches/bash42-025
- http://lists.gnu.org/archive/html/help-bash/2012-03/msg00078.html
- [ShellCheck](https://www.shellcheck.net/) can help to identify many of these issues
## Open Source License
This software is made available under the [MIT License][osmit].
For the text of the license, see the [LICENSE][] file.
## Credits
- This guide was heavily written by BATS-core member [@mbland](https://github.com/mbland)
for [go-script-bash](https://github.com/mbland/go-script-bash), tweaked for [BATS-core][repohome]
- Table of Contents created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
- The [official bash logo](https://github.com/odb/official-bash-logo) is copyrighted
by the [Free Software Foundation](https://www.fsf.org/), 2016 under the [Free Art License](http://artlibre.org/licence/lal/en/)
[repoprojects]: https://github.com/bats-core/bats-core/projects
[repomilestones]: https://github.com/bats-core/bats-core/milestones
[repoprs]: https://github.com/bats-core/bats-core/pulls
[repoissues]: https://github.com/bats-core/bats-core/issues
[repohome]: https://github.com/bats-core/bats-core
[osmit]: https://opensource.org/licenses/MIT
[gitterurl]: https://gitter.im/bats-core/bats-core

20
docs/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -0,0 +1,5 @@
- [ ] I have reviewed the [Contributor Guidelines][contributor].
- [ ] I have reviewed the [Code of Conduct][coc] and agree to abide by it
[contributor]: https://github.com/bats-core/bats-core/blob/master/docs/CONTRIBUTING.md
[coc]: https://github.com/bats-core/bats-core/blob/master/docs/CODE_OF_CONDUCT.md

6
docs/examples/README.md Normal file
View File

@ -0,0 +1,6 @@
# Examples
This directory contains example .bats files.
See the [bats-core wiki][examples] for more details.
[examples]: https://github.com/bats-core/bats-core/wiki/Examples

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
# "unofficial" bash strict mode
# See: http://redsymbol.net/articles/unofficial-bash-strict-mode
set -o errexit # Exit when simple command fails 'set -e'
set -o errtrace # Exit on error inside any functions or subshells.
set -o nounset # Trigger error when expanding unset variables 'set -u'
set -o pipefail # Do not hide errors within pipes 'set -o pipefail'
set -o xtrace # Display expanded command and arguments 'set -x'
IFS=$'\n\t' # Split words on \n\t rather than spaces
main() {
tar -czf "$dst_tarball" -C "$src_dir" .
}
main "$@"

View File

@ -0,0 +1,51 @@
#!/usr/bin/env bats
setup() {
export dst_tarball="${BATS_TMPDIR}/dst.tar.gz"
export src_dir="${BATS_TMPDIR}/src_dir"
rm -rf "${dst_tarball}" "${src_dir}"
mkdir "${src_dir}"
touch "${src_dir}"/{a,b,c}
}
main() {
bash "${BATS_TEST_DIRNAME}"/package-tarball
}
@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" {
# shellcheck disable=SC2030
src_dir='not-a-dir'
run main
[ "${status}" -ne 0 ]
}
# shellcheck disable=SC2016
@test "pass when \$src_dir directory is empty" {
# shellcheck disable=SC2031,SC2030
rm -rf "${src_dir:?}/*"
run main
echo "$output"
[ "${status}" -eq 0 ]
}
# shellcheck disable=SC2016
@test "files in \$src_dir are added to tar archive" {
run main
[ "${status}" -eq 0 ]
run tar tf "$dst_tarball"
[ "${status}" -eq 0 ]
[[ "${output}" =~ a ]]
[[ "${output}" =~ b ]]
[[ "${output}" =~ c ]]
}

35
docs/make.bat Normal file
View File

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

127
docs/releasing.md Normal file
View File

@ -0,0 +1,127 @@
# Releasing a new Bats version
These notes reflect the current process. There's a lot more we could do, in
terms of automation and expanding the number of platforms to which we formally
release (see #103).
## Update docs/CHANGELOG.md
Create a new entry at the top of `docs/CHANGELOG.md` that enumerates the
significant updates to the new version.
## Bumping the version number
Bump the version numbers in the following files:
- contrib/rpm/bats.spec
- libexec/bats-core/bats
- package.json
Commit these changes (including the `docs/CHANGELOG.md` changes) in a commit
with the message `Bats <VERSION>`, where `<VERSION>` is the new version number.
Create a new signed, annotated tag with:
```bash
$ git tag -a -s <VERSION>
```
Include the `docs/CHANGELOG.md` notes corresponding to the new version as the
tag annotation, except the first line should be: `Bats <VERSION> - YYYY-MM-DD`
and any Markdown headings should become plain text, e.g.:
```md
### Added
```
should become:
```md
Added:
```
## Create a GitHub release
Push the new version commit and tag to GitHub via the following:
```bash
$ git push --follow-tags
```
Then visit https://github.com/bats-core/bats-core/releases, and:
* Click **Draft a new release**.
* Select the new version tag.
* Name the release: `Bats <VERSION>`.
* Paste the same notes from the version tag annotation as the description,
except change the first line to read: `Released: YYYY-MM-DD`.
* Click **Publish release**.
For more on `git push --follow-tags`, see:
* [git push --follow-tags in the online manual][ft-man]
* [Stack Overflow: How to push a tag to a remote repository using Git?][ft-so]
[ft-man]: https://git-scm.com/docs/git-push#git-push---follow-tags
[ft-so]: https://stackoverflow.com/a/26438076
## NPM
`npm publish`. Pretty easy!
For the paranoid, use `npm pack` and install the resulting tarball locally with
`npm install` before publishing.
## Homebrew
The basic instructions are in the [Submit a new version of an existing
formula][brew] section of the Homebrew docs.
[brew]: https://github.com/Homebrew/brew/blob/master/docs/How-To-Open-a-Homebrew-Pull-Request.md#submit-a-new-version-of-an-existing-formula
An example using v1.1.0 (notice that this uses the sha256 sum of the tarball):
```bash
$ curl -LOv https://github.com/bats-core/bats-core/archive/v1.1.0.tar.gz
$ openssl sha256 v1.1.0.tar.gz
SHA256(v1.1.0.tar.gz)=855d8b8bed466bc505e61123d12885500ef6fcdb317ace1b668087364717ea82
# Add the --dry-run flag to see the individual steps without executing.
$ brew bump-formula-pr \
--url=https://github.com/bats-core/bats-core/archive/v1.1.0.tar.gz \
--sha256=855d8b8bed466bc505e61123d12885500ef6fcdb317ace1b668087364717ea82
```
This resulted in https://github.com/Homebrew/homebrew-core/pull/29864, which was
automatically merged once the build passed.
## Alpine Linux
An example using v1.1.0 (notice that this uses the sha512 sum of the Zip file):
```bash
$ curl -LOv https://github.com/bats-core/bats-core/archive/v1.1.0.zip
$ openssl sha512 v1.1.0.zip
SHA512(v1.1.0.zip)=accd83cfec0025a2be40982b3f9a314c2bbf72f5c85daffa9e9419611904a8d34e376919a5d53e378382e0f3794d2bd781046d810225e2a77812474e427bed9e
```
After cloning alpinelinux/aports, I used the above information to create:
https://github.com/alpinelinux/aports/pull/4696
**Note:** Currently users must enable the `edge` branch of the `community` repo
by adding/uncommenting the corresponding entry in `/etc/apk/repositories`.
## Announce
It's worth making a brief announcement like [the v1.1.0 announcement via
Gitter][gitter]:
[gitter]: https://gitter.im/bats-core/bats-core?at=5b42c9a57b811a6d63daacb5
```
v1.1.0 is now available via Homebrew and npm:
https://github.com/bats-core/bats-core/releases/tag/v1.1.0
It'll eventually be available in Alpine via the edge branch of the community
repo once alpinelinux/aports#4696 gets merged. (Check /etc/apk/repositories to
ensure this repo is enabled.)
```

View File

View File

72
docs/source/conf.py Normal file
View File

@ -0,0 +1,72 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'bats-core'
copyright = '2022, bats-core organization'
author = 'bats-core organization'
# The full version, including alpha/beta/rc tags
release = '1'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'recommonmark',
'sphinxcontrib.programoutput'
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
html_sidebars = { '**': [
'about.html',
'navigation.html',
'relations.html',
'searchbox.html',
'donate.html'] }
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
#html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
#man_pages = [ ('man.1', 'bats', 'bats documentation', ['bats-core Contributors'], 1)]
def setup(app):
app.add_config_value('recommonmark_config', {'enable_eval_rst': True}, True)
import recommonmark
from recommonmark.transform import AutoStructify
app.add_transform(AutoStructify)

View File

@ -0,0 +1,58 @@
# Docker Usage Guide
- [Docker Usage Guide](#docker-usage-guide)
* [Basic Usage](#basic-usage)
* [Docker Gotchas](#docker-gotchas)
* [Extending from the base image](#extending-from-the-base-image)
## Basic Usage
To build and run `bats`' own tests:
```bash
$ git clone https://github.com/bats-core/bats-core.git
Cloning into 'bats-core'...
remote: Counting objects: 1222, done.
remote: Compressing objects: 100% (53/53), done.
remote: Total 1222 (delta 34), reused 55 (delta 21), pack-reused 1146
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/bats:latest .
...
$ 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/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:/code" bats/bats:latest /code/test
```
## Docker Gotchas
Relying on functionality provided by your environment (ssh keys or agent, installed binaries, fixtures outside the mounted test directory) will fail when running inside Docker.
`--interactive`/`-i` attaches an interactive terminal and is useful to kill hanging processes (otherwise has to be done via docker stop command). `--tty`/`-t` simulates a tty (often not used, but most similar to test runs from a Bash prompt). Interactivity is important to a user, but not a build, and TTYs are probably more important to a headless build. Everything's least-surprising to a new Docker use if both are used.
## Extending from the base image
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/bats
RUN \
apk \
--no-cache \
--update \
add \
openssh
```

170
docs/source/faq.rst Normal file
View File

@ -0,0 +1,170 @@
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 might 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?
----------------------------------
Set the variable `$BATS_TEST_TIMEOUT` before `setup()` starts. This means you can set it either on the command line,
in free code in the test file or in `setup_file()`.
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>`_.

132
docs/source/gotchas.rst Normal file
View File

@ -0,0 +1,132 @@
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).
Use `run !` on Bats 1.5.0 and above. For older bats versions, use one of `! x || false` or `run` with `[ $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 one of the correct forms mentioned above 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.
Background tasks prevent the test run from terminating when finished
--------------------------------------------------------------------
When running a task in background, it will inherit the opened FDs of the process it was forked from.
This means that the background task forked from a Bats test will hold the FD for the pipe to the formatter that prints to the terminal,
thus keeping it open until the background task finished.
Due to implementation internals of Bats and bash, this pipe might be held in multiple FDs which all have to be closed by the background task.
You can use `close_non_std_fds from `test/fixtures/bats/issue-205.bats` in the background job to close all FDs except stdin, stdout and stderr, thus solving the problem.
More details about the issue can be found in [#205](https://github.com/bats-core/bats-core/issues/205#issuecomment-973572596).

18
docs/source/index.rst Normal file
View File

@ -0,0 +1,18 @@
Welcome to bats-core's documentation!
=====================================
Versions before v1.2.1 are documented over `there <https://github.com/bats-core/bats-core/blob/master/docs/versions.md>`_.
.. toctree::
:maxdepth: 2
:caption: Contents:
tutorial
installation
usage
docker-usage
writing-tests
gotchas
faq
warnings/index
support-matrix

View File

@ -0,0 +1,138 @@
Installation
============
Linux: Distribition Package Manager
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Following Linux distributions provide Bats via their package manager:
* Arch Linux: `community/bash-bats <https://archlinux.org/packages/community/any/bash-bats/>`__
* Alpine Linux: `bats <https://pkgs.alpinelinux.org/package/edge/main/x86/bats>`__
* Debian Linux: `shells/bats <https://packages.debian.org/search?keywords=bats>`__
* Fedora Linux: `rpms/bats <https://src.fedoraproject.org/rpms/bats>`__
* Gentoo Linux `dev-util/bats <https://packages.gentoo.org/packages/dev-util/bats>`__
* OpenSUSE Linux: `bats <https://software.opensuse.org/package/bats>`__
* Ubuntu Linux `shells/bats <https://packages.ubuntu.com/search?keywords=bats>`__
**Note**: Bats versions pre 1.0 are from sstephenson's original project.
Consider using one of the other installation methods below to get the latest Bats release.
The test matrix above only applies to the latest Bats version.
If your favorite distribution is not listed above,
you can try one of the following package managers or install from source.
MacOS: Homebrew
^^^^^^^^^^^^^^^
On macOS, you can install `Homebrew <https://brew.sh/>`__ if you haven't already,
then run:
.. code-block:: bash
$ brew install bats-core
Any OS: npm
^^^^^^^^^^^
You can install the `Bats npm package <https://www.npmjs.com/package/bats>`__ via:
.. code-block::
# To install globally:
$ npm install -g bats
# To install into your project and save it as one of the "devDependencies" in
# your package.json:
$ npm install --save-dev bats
Any OS: Installing Bats from source
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Check out a copy of the Bats repository. Then, either add the Bats ``bin``
directory to your ``$PATH``\ , or run the provided ``install.sh`` command with the
location to the prefix in which you want to install Bats. For example, to
install Bats into ``/usr/local``\ ,
.. code-block::
$ git clone https://github.com/bats-core/bats-core.git
$ cd bats-core
$ ./install.sh /usr/local
**Note:** You may need to run ``install.sh`` with ``sudo`` if you do not have
permission to write to the installation prefix.
Windows: Installing Bats from source via Git Bash
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Check out a copy of the Bats repository and install it to ``$HOME``. This
will place the ``bats`` executable in ``$HOME/bin``\ , which should already be
in ``$PATH``.
.. code-block::
$ git clone https://github.com/bats-core/bats-core.git
$ cd bats-core
$ ./install.sh $HOME
Running Bats in Docker
^^^^^^^^^^^^^^^^^^^^^^
There is an official image on the Docker Hub:
.. code-block::
$ docker run -it bats/bats:latest --version
Building a Docker image
~~~~~~~~~~~~~~~~~~~~~~~
Check out a copy of the Bats repository, then build a container image:
.. code-block::
$ git clone https://github.com/bats-core/bats-core.git
$ cd bats-core
$ docker build --tag bats/bats:latest .
This creates a local Docker image called ``bats/bats:latest`` based on `Alpine
Linux <https://github.com/gliderlabs/docker-alpine/blob/master/docs/usage.md>`__
(to push to private registries, tag it with another organisation, e.g.
``my-org/bats:latest``\ ).
To run Bats' internal test suite (which is in the container image at
``/opt/bats/test``\ ):
.. code-block::
$ docker run -it bats/bats:latest /opt/bats/test
To run a test suite from a directory called ``test`` in the current directory of
your local machine, mount in a volume and direct Bats to its path inside the
container:
.. code-block::
$ docker run -it -v "${PWD}:/code" bats/bats:latest test
..
``/code`` is the working directory of the Docker image. "${PWD}/test" is the
location of the test directory on the local machine.
This is a minimal Docker image. If more tools are required this can be used as a
base image in a Dockerfile using ``FROM <Docker image>``. In the future there may
be images based on Debian, and/or with more tools installed (\ ``curl`` and ``openssl``\ ,
for example). If you require a specific configuration please search and +1 an
issue or `raise a new issue <https://github.com/bats-core/bats-core/issues>`__.
Further usage examples are in
`the wiki <https://github.com/bats-core/bats-core/wiki/Docker-Usage-Examples>`__.

View File

@ -0,0 +1,2 @@
sphinxcontrib-programoutput
recommonmark

View File

@ -0,0 +1,26 @@
Support Matrix
==============
Supported Bash versions
^^^^^^^^^^^^^^^^^^^^^^^
The following is a list of Bash versions that are currently supported by Bats and verified through automated tests:
* 3.2.57(1) (macOS's highest bundled version)
* 4.0, 4.1, 4.2, 4.3, 4.4
* 5.0, 5.1, 5.2
Supported Operating systems
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following Operating Systems are supported and tested automatically (CI) or manually during development:
* Linux: Alpine (CI), Alma 8 (CI), Arch Linux (manual), Ubuntu 20.04/22.04 (CI)
* FreeBSD: 11 (CI)
* macOS: 11 (CI), 12 (CI)
* Windows: Server 2019 (CI), 10 (manual)
* Git for Windows Bash (MSYS2 based)
* Windows Subsystem for Linux
* MSYS2
* Cygwin

661
docs/source/tutorial.rst Normal file
View File

@ -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 (nonexistent) 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 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 not 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 2>&1 >/dev/null)
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.

114
docs/source/usage.md Normal file
View File

@ -0,0 +1,114 @@
# Usage
Bats comes with two manual pages. After installation you can view them with `man
1 bats` (usage manual) and `man 7 bats` (writing test files manual). Also, you
can view the available command line options that Bats supports by calling Bats
with the `-h` or `--help` options. These are the options that Bats currently
supports:
``` eval_rst
.. program-output:: ../../bin/bats --help
```
To run your tests, invoke the `bats` interpreter with one or more paths to test
files ending with the `.bats` extension, or paths to directories containing test
files. (`bats` will only execute `.bats` files at the top level of each
directory; it will not recurse unless you specify the `-r` flag.)
Test cases from each file are run sequentially and in isolation. If all the test
cases pass, `bats` exits with a `0` status code. If there are any failures,
`bats` exits with a `1` status code.
When you run Bats from a terminal, you'll see output as each test is performed,
with a check-mark next to the test's name if it passes or an "X" if it fails.
```text
$ bats addition.bats
✓ addition using bc
✓ addition using dc
2 tests, 0 failures
```
If Bats is not connected to a terminal—in other words, if you run it from a
continuous integration system, or redirect its output to a file—the results are
displayed in human-readable, machine-parsable [TAP format][tap-format].
You can force TAP output from a terminal by invoking Bats with the `--formatter tap`
option.
```text
$ bats --formatter tap addition.bats
1..2
ok 1 addition using bc
ok 2 addition using dc
```
With `--formatter junit`, it is possible
to output junit-compatible report files.
```text
$ bats --formatter junit addition.bats
1..2
ok 1 addition using bc
ok 2 addition using dc
```
If you have your own formatter, you can use an absolute path to the executable
to use it:
```bash
$ bats --formatter /absolute/path/to/my-formatter addition.bats
addition using bc WORKED
addition using dc FAILED
```
You can also generate test report files via `--report-formatter` which accepts
the same options as `--formatter`. By default, the file is stored in the current
workdir. However, it may be placed elsewhere by specifying the `--output` flag.
```text
$ bats --report-formatter junit addition.bats --output /tmp
1..2
ok 1 addition using bc
ok 2 addition using dc
$ cat /tmp/report.xml
<?xml version="1.0" encoding="UTF-8"?>
<testsuites time="0.073">
<testsuite name="addition.bats" tests="2" failures="0" errors="0" skipped="0">
<testcase classname="addition.bats" name="addition using bc" time="0.034" />
<testcase classname="addition.bats" name="addition using dc" time="0.039" />
</testsuite>
</testsuites>
```
## Parallel Execution
``` eval_rst
.. versionadded:: 1.0.0
```
By default, Bats will execute your tests serially. However, Bats supports
parallel execution of tests (provided you have [GNU parallel][gnu-parallel] or
a compatible replacement installed) using the `--jobs` parameter. This can
result in your tests completing faster (depending on your tests and the testing
hardware).
Ordering of parallelised tests is not guaranteed, so this mode may break suites
with dependencies between tests (or tests that write to shared locations). When
enabling `--jobs` for the first time be sure to re-run bats multiple times to
identify any inter-test dependencies or non-deterministic test behaviour.
When parallelizing, the results of a file only become visible after it has been finished.
You can use `--no-parallelize-across-files` to get immediate output at the cost of reduced
overall parallelity, as parallelization will only happen within files and files will be run
sequentially.
If you have files where tests within the file would interfere with each other, you can use
`--no-parallelize-within-files` to disable parallelization within all files.
If you want more fine-grained control, you can `export BATS_NO_PARALLELIZE_WITHIN_FILE=true` in `setup_file()`
or outside any function to disable parallelization only within the containing file.
[tap-format]: https://testanything.org
[gnu-parallel]: https://www.gnu.org/software/parallel/

View File

@ -0,0 +1,17 @@
BW01: `run`'s command `<command>` exited with code 127, indicating 'Command not found'. Use run's return code checks, e.g. `run -127`, to fix this message.
===========================================================================================================================================================
Due to `run`'s default behavior of always succeeding, errors in the command string can remain hidden from the user, e.g.[here](https://github.com/bats-core/bats-core/issues/578).
As a proxy for this problem, the return code is checked for value 127 ("Command not found").
How to fix
----------
If your command should actually return code 127, then you can simply use `run -127 <your command>` to state your intent and the message will go away.
If your command should not return 127, you should fix the problem with the command.
Take a careful look at the command string in the warning message, to see if it contains code that you did not intend to run.
If your command should sometimes return 127, but never 0, you can use `run ! <your command>`.
If your command can sometimes return 127 and sometimes 0, the please submit an issue.

View File

@ -0,0 +1,46 @@
BW02: <feature> requires at least BATS_VERSION=<version>. Use `bats_require_minimum_version <version>` to fix this message.
===========================================================================================================================
Using a feature that is only available starting with a certain version can be a problem when your tests also run on older versions of Bats.
In most cases, running this code in older versions will generate an error due to a missing command.
However, in cases like `run`'s where old version simply take all parameters as command to execute, the failure can be silent.
How to fix
----------
When you encounter this warning, you can simply guard your code with `bats_require_minimum_version <version>` as the message says.
For example, consider the following code:
.. code-block:: bash
@test test {
bats_require_minimum_version 1.5.0
# pre 1.5.0 the flag --separate-stderr would be interpreted as command to run
run --separate-stderr some-command
[ $output = "blablabla" ]
}
The call to `bats_require_minimum_version` can be put anywhere before the warning generating command, even in `setup`, `setup_file`, or even outside any function.
This can be used to give fine control over the version dependencies:
.. code-block:: bash
@test test {
bats_require_minimum_version 1.5.0
# pre 1.5.0 the flag --separate-stderr would be interpreted as command to run
run --separate-stderr some-command
[ $output = "blablabla" ]
}
@test test2 {
run some-other-command # no problem executing on earlier version
}
If the above code is executed on a system with a `BATS_VERSION` pre 1.5.0, the first test will fail on `bats_require_minimum_version 1.5.0`.
Instances:
----------
- run's non command parameters like `--keep-empty-lines` are only available since 1.5.0

View File

@ -0,0 +1,15 @@
BW03: `setup_suite` is visible to test file '<path>', but was not executed. It belongs into 'setup_suite.bash' to be picked up automatically.
=============================================================================================================================================
In contrast to the other setup functions, `setup_suite` must not be defined in `*.bats` files but in `setup_suite.bash`.
When a file is executed and sees `setup_suite` defined but not run before the tests, this warning will be printed.
How to fix
----------
The fix depends on your actual intention. There are basically two cases:
1. You want a setup before all tests and accidentally put `setup_suite` into a test file instead of `setup_suite.bash`.
Simply move `setup_suite` (and `teardown_suite`!) into `setup_suite.bash`.
2. You did not mean to run a setup before any test but need to defined a function named `setup_suite` in your test file.
In this case, you can silence this warning by assigning `BATS_SETUP_SUITE_COMPLETED='suppress BW03'`.

View File

@ -0,0 +1,28 @@
Warnings
========
Starting with version 1.7.0 Bats shows warnings about issues it found during the test run.
They are printed on stderr after all other output:
.. code-block:: bash
BW01.bats
✓ Trigger BW01
1 test, 0 failures
The following warnings were encountered during tests:
BW01: `run`'s command `=0 actually-intended-command with some args` exited with code 127, indicating 'Command not found'. Use run's return code checks, e.g. `run -127`, to fix this message.
(from function `run' in file lib/bats-core/test_functions.bash, line 299,
in test file test/fixtures/warnings/BW01.bats, line 3)
A warning will not make a successful run fail but should be investigated and taken seriously, since it hints at a possible error.
Currently, Bats emits the following warnings:
.. toctree::
BW01
BW02
BW03

View File

@ -0,0 +1,613 @@
# Writing tests
Each Bats test file is evaluated _n+1_ times, where _n_ 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.
For more details about how Bats evaluates test files, see [Bats Evaluation
Process][bats-eval] on the wiki.
For sample test files, see [examples](https://github.com/bats-core/bats-core/tree/master/docs/examples).
[bats-eval]: https://github.com/bats-core/bats-core/wiki/Bats-Evaluation-Process
## Tagging tests
Starting with version 1.8.0, Bats comes with a tagging system that allows users
to categorize their tests and filter according to those categories.
Each test has a list of tags attached to it. Without specification, this list is empty.
Tags can be defined in two ways. The first being `# bats test_tags=`:
```bash
# bats test_tags=tag:1, tag:2, tag:3
@test "second test" {
# ...
}
@test "second test" {
# ...
}
```
These tags (`tag:1`, `tag:2`, `tag:3`) will be attached to the test `first test`.
The second test will have no tags attached. Values defined in the `# bats test_tags=`
directive will be assigned to the next `@test` that is being encountered in the
file and forgotten after that. Only the value of the last `# bats test_tags=` directive
before a given test will be used.
Sometimes, we want to give all tests in a file a set of the same tags. This can
be achieved via `# bats file_tags=`. They will be added to all tests in the file
after that directive. An additional `# bats file_tags=` directive will override
the previously defined values:
```bash
@test "Zeroth test" {
# will have no tags
}
# bats file_tags=a:b
# bats test_tags=c:d
@test "First test" {
# will be tagged a:b, c:d
}
# bats file_tags=
@test "Second test" {
# will have no tags
}
```
Tags are case sensitive and must only consist of alphanumeric characters and `_`,
`-`, or `:`. They must not contain whitespaces!
The colon is intended as a separator for (recursive) namespacing.
Tag lists must be separated by commas and are allowed to contain whitespace.
They must not contain empty tags like `test_tags=,b` (first tag is empty),
`test_tags=a,,c`, `test_tags=a, ,c` (second tag is only whitespace/empty),
`test_tags=a,b,` (third tag is empty).
Every tag starting with `bats:` (case insensitive!) is reserved for Bats'
internal use.
### Special tags
#### Focusing on tests with `bats:focus` tag
If a test with the tag `bats:focus` is encountered in a test suite,
all other tests will be filtered out and only those tagged with this tag will be executed.
In focus mode, the exit code of successful runs will be overriden to 1 to prevent CI from silently running on a subset of tests due to an accidentally commited `bats:focus` tag.
Should you require the true exit code, e.g. for a `git bisect` operation, you can disable this behavior by setting
`BATS_NO_FAIL_FOCUS_RUN=1` when running `bats`, but make sure not to commit this to CI!
### Filtering execution
Tags can be used for more finegrained filtering of which tests to run via `--filter-tags`.
This accepts a comma separated list of tags. Only tests that match all of these
tags will be executed. For example, `bats --filter-tags a,b,c` will pick up tests
with tags `a,b,c`, but not tests that miss one or more of those tags.
Additionally, you can specify negative tags via `bats --filter-tags a,!b,c`,
which now won't match tests with tags `a,b,c`, due to the `b`, but will select `a,c`.
To put it more formally, `--filter-tags` is a boolean conjunction.
To allow for more complex queries, you can specify multiple `--filter-tags`.
A test will be executed, if it matches at least one of them.
This means multiple `--filter-tags` form a boolean disjunction.
A query of `--filter-tags a,!b --filter-tags b,c` can be translated to:
Execute only tests that (have tag a, but not tag b) or (have tag b and c).
An empty tag list matches tests without tags.
## 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`
alternative:
```bash
function invoking_foo_without_arguments_prints_usage { #@test
run foo
[ "$status" -eq 1 ]
[ "${lines[0]}" = "usage: foo <filename>" ]
}
```
When using this syntax, the function name will be the title in the result output
and the value checked when using `--filter`.
## `run`: Test other commands
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.
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.
```bash
@test "invoking foo with a nonexistent file prints an error" {
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, the
`$output` variable contains the combined contents of the command's standard
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`
without any arguments prints usage information on the first line:
```bash
@test "invoking foo without arguments prints usage" {
run -1 foo
[ "${lines[0]}" = "usage: foo <filename>" ]
}
```
__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.
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.
### When not to use `run`
In case you only need to check the command succeeded, it is better to not use `run`, since the following code
```bash
run -0 command args ...
```
is equivalent to
```bash
command args ...
```
(because bats sets `set -e` for all tests).
__Note__: In contrast to the above, testing that a command failed is best done via
```bash
run ! command args ...
```
because
```bash
! command args ...
```
will only fail the test if it is the last command and thereby determines the test function's exit code.
This is due to Bash's decision to (counterintuitively?) not trigger `set -e` on `!` commands.
(See also [the associated gotcha](https://bats-core.readthedocs.io/en/stable/gotchas.html#my-negated-statement-e-g-true-does-not-fail-the-test-even-when-it-should))
### `run` and pipes
Don't fool yourself with pipes when using `run`. Bash parses the pipe outside of `run`, not internal to its command. Take this example:
```bash
run command args ... | jq -e '.limit == 42'
```
Here, `jq` receives no input (which is captured by `run`),
executes no filters, and always succeeds, so the test does not work as
expected.
Instead use a Bash subshell:
```bash
run bash -c "command args ... | jq -e '.limit == 42'"
```
This subshell is a fresh Bash environment, and will only inherit variables
and functions that are exported into it.
```bash
limit() { jq -e '.limit == 42'; }
export -f limit
run bash -c "command args ... | limit"
```
## `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 files
relative to the current test file and from absolute paths.
For example, if you have a Bats test in `test/foo.bats`, the command
```bash
load test_helper.bash
```
will source the script `test/test_helper.bash` in your test file (limitations
apply, see below). This can be useful for sharing functions to set up your
environment or load fixtures. `load` delegates to Bash's `source` command after
resolving paths.
If `load` encounters errors - e.g. because the targeted source file
errored - it will print a message with the failing library and Bats
exits.
To allow to use `load` in conditions `bats_load_safe` has been added.
`bats_load_safe` prints a message and returns `1` if a source file cannot be
loaded instead of exiting Bats.
Aside from that `bats_load_safe` acts exactly like `load`.
As pointed out by @iatrou in https://www.tldp.org/LDP/abs/html/declareref.html,
using the `declare` builtin restricts scope of a variable. Thus, since actual
`source`-ing is performed in context of the `load` function, `declare`d symbols
will _not_ be made available to callers of `load`.
### `load` argument resolution
`load` supports the following arguments:
- absolute paths
- relative paths (to the current test file)
> For backwards compatibility `load` first searches for a file ending in
> `.bash` (e.g. `load test_helper` searches for `test_helper.bash` before
> it looks for `test_helper`). This behaviour is deprecated and subject to
> change, please use exact filenames instead.
If `argument` is an absolute path `load` tries to determine the load
path directly.
If `argument` is a relative path or a name `load` looks for a matching
path in the directory of the current test.
## `bats_load_library`: Load system wide libraries
Some libraries are installed on the system, e.g. by `npm` or `brew`.
These should not be `load`ed, as their path depends on the installation method.
Instead, one should use `bats_load_library` together with setting
`BATS_LIB_PATH`, a `PATH`-like colon-delimited variable.
`bats_load_library` has two modes of resolving requests:
1. by relative path from the `BATS_LIB_PATH` to a file in the library
2. by library name, expecting libraries to have a `load.bash` entrypoint
For example if your `BATS_LIB_PATH` is set to
`~/.bats/libs:/usr/lib/bats`, then `bats_load_library test_helper`
would look for existing files with the following paths:
- `~/.bats/libs/test_helper`
- `~/.bats/libs/test_helper/load.bash`
- `/usr/lib/bats/test_helper`
- `/usr/lib/bats/test_helper/load.bash`
The first existing file in this list will be sourced.
If you want to load only part of a library or the entry point is not named `load.bash`,
you have to include it in the argument:
`bats_load_library library_name/file_to_load` will try
- `~/.bats/libs/library_name/file_to_load`
- `~/.bats/libs/library_name/file_to_load/load.bash`
- `/usr/lib/bats/library_name/file_to_load`
- `/usr/lib/bats/library_name/file_to_load/load.bash`
Apart from the changed lookup rules, `bats_load_library` behaves like `load`.
__Note:__ As seen above `load.bash` is the entry point for libraries and
meant to load more files from its directory or other libraries.
__Note:__ Obviously, the actual `BATS_LIB_PATH` is highly dependent on the environment.
To maintain a uniform location across systems, (distribution) package maintainers
are encouraged to use `/usr/lib/bats/` as the install path for libraries where possible.
However, if the package manager has another preferred location, like `npm` or `brew`,
you should use this instead.
## `skip`: Easily skip tests
Tests can be skipped by using the `skip` command at the point in a test you wish
to skip.
```bash
@test "A test I don't want to execute for now" {
skip
run foo
[ "$status" -eq 0 ]
}
```
Optionally, you may include a reason for skipping:
```bash
@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 ]
}
```
Or you can skip conditionally:
```bash
@test "A test which should run" {
if [ foo != bar ]; then
skip "foo isn't bar"
fi
run foo
[ "$status" -eq 0 ]
}
```
__Note:__ `setup` and `teardown` hooks still run for skipped tests.
## `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
environment, and clean up when you're done.
You can also define `setup_file` and `teardown_file`, which will run once before
the first test's `setup` and after the last test's `teardown` for the containing
file. Variables that are exported in `setup_file` will be visible to all following
functions (`setup`, the test itself, `teardown`, `teardown_file`).
Similarly, there is `setup_suite` (and `teardown_suite`) which run once before (and
after) all tests of the test run.
__Note:__ As `setup_suite` and `teardown_suite` are intended for all files in a suite,
they must be defined in a separate `setup_suite.bash` file. Automatic discovery works
by searching for `setup_suite.bash` in the folder of the first `*.bats` file of the suite.
If this automatism does not work for your usecase, you can work around by specifying
`--setup-suite-file` on the `bats` command. If you have a `setup_suite.bash`, it must define
`setup_suite`! However, defining `teardown_suite` is optional.
<!-- markdownlint-disable MD033 -->
<details>
<summary>Example of setup/{,_file,_suite} (and teardown{,_file,_suite}) call order</summary>
For example the following call order would result from two files (file 1 with
tests 1 and 2, and file 2 with test3) with a corresponding `setup_suite.bash` file being tested:
```text
setup_suite # from setup_suite.bash
setup_file # from file 1, on entering file 1
setup
test1
teardown
setup
test2
teardown
teardown_file # from file 1, on leaving file 1
setup_file # from file 2, on enter file 2
setup
test3
teardown
teardown_file # from file 2, on leaving file 2
teardown_suite # from setup_suite.bash
```
</details>
<!-- markdownlint-enable MD033 -->
Note that the `teardown*` functions can fail a test, if their return code is nonzero.
This means, using `return 1` or having the last command in teardown fail, will
fail the teardown. Unlike `@test`, failing commands within `teardown` won't
trigger failure as ERREXIT is disabled.
<!-- markdownlint-disable MD033 -->
<details>
<summary>Example of different teardown failure modes</summary>
```bash
teardown() {
false # this will fail the test, as it determines the return code
}
teardown() {
false # this won't fail the test ...
echo some more code # ... and this will be executed too!
}
teardown() {
return 1 # this will fail the test, but the rest won't be executed
echo some more code
}
teardown() {
if true; then
false # this will also fail the test, as it is the last command in this function
else
true
fi
}
```
</details>
<!-- markdownlint-enable MD033 -->
## `bats_require_minimum_version <Bats version number>`
Added in [v1.7.0](https://github.com/bats-core/bats-core/releases/tag/v1.7.0)
Code for newer versions of Bats can be incompatible with older versions.
In the best case this will lead to an error message and a failed test suite.
In the worst case, the tests will pass erroneously, potentially masking a failure.
Use `bats_require_minimum_version <Bats version number>` to avoid this.
It communicates in a concise manner, that you intend the following code to be run
under the given Bats version or higher.
Additionally, this function will communicate the current Bats version floor to
subsequent code, allowing e.g. Bats' internal warning to give more informed warnings.
__Note__: By default, calling `bats_require_minimum_version` with versions before
Bats 1.7.0 will fail regardless of the required version as the function is not
available. However, you can use the
[bats-backports plugin](https://github.com/bats-core/bats-backports) to make
your code usable with older versions, e.g. during migration while your CI system
is not yet upgraded.
## Code outside of test cases
In general you should avoid code outside tests, because each test file will be evaluated many times.
However, there are situations in which this might be useful, e.g. when you want to check for dependencies
and fail immediately if they're not present.
In general, you should avoid printing outside of `@test`, `setup*` or `teardown*` functions.
Have a look at section [printing to the terminal](#printing-to-the-terminal) for more details.
## 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
order to produce TAP-compliant output. In the [Printing to the
terminal](#printing-to-the-terminal) section, there are details on how to use
file descriptor 3 to print custom text properly.
A side effect of using file descriptor 3 is that, under some circumstances, it
can cause Bats to block and execution to seem dead without reason. This can
happen if a child process is spawned in the background from a test. In this
case, the child process will inherit file descriptor 3. Bats, as the parent
process, will wait for the file descriptor to be closed by the child process
before continuing execution. If the child process takes a lot of time to
complete (eg if the child process is a `sleep 100` command or a background
service that will run indefinitely), Bats will be similarly blocked for the same
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
Bats produces output compliant with [version 12 of the TAP protocol](https://testanything.org/tap-specification.html). The
produced TAP stream is by default piped to a pretty formatter for human
consumption, but if Bats is called with the `-t` flag, then the TAP stream is
directly printed to the console.
This has implications if you try to print custom text to the terminal. As
mentioned in [File descriptor 3](#file-descriptor-3-read-this-if-bats-hangs),
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**:
- 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.
- Printing **from within the `setup*` or `teardown*` functions**: The same hold
true as for printing with test functions.
- Printing **outside test or `setup*`/`teardown*` functions**:
- You should avoid printing in free code: Due to the multiple executions
contexts (`setup_file`, multiple `@test`s) of test files, output
will be printed more than once.
- Regardless of where text is redirected to (stdout, stderr or file descriptor 3)
text is immediately visible in the terminal, as it is not piped into the formatter.
- Text printed to stdout may interfere with formatters as it can
make output non-compliant with the TAP spec. The reason for this is that
such output will be produced before the [_plan line_][tap-plan] is printed,
contrary to the spec that requires the _plan line_ to be either the first or
the last line of the output.
- Due to internal pipes/redirects, output to stderr is always printed first.
[tap-plan]: https://testanything.org/tap-specification.html#the-plan
## 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.
- `$BATS_TEST_NAME` is the name of the function containing the current test case.
- `BATS_TEST_NAME_PREFIX` will be prepended to the description of each test on
stdout and in reports.
- `$BATS_TEST_DESCRIPTION` is the description of the current test case.
- `BATS_TEST_RETRIES` is the maximum number of additional attempts that will be
made on a failed test before it is finally considered failed.
The default of 0 means the test must pass on the first attempt.
- `BATS_TEST_TIMEOUT` is the number of seconds after which a test (including setup)
will be aborted and marked as failed. Updates to this value in `setup()` or `@test`
cannot change the running timeout countdown, so the latest useful update location
is `setup_file()`.
- `$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 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.
- `$BATS_VERSION` is the version of Bats running the test.
## Libraries and Add-ons
Bats supports loading external assertion libraries and helpers. Those under `bats-core` are officially supported libraries (integration tests welcome!):
- <https://github.com/bats-core/bats-assert> - common assertions for Bats
- <https://github.com/bats-core/bats-support> - supporting library for Bats test helpers
- <https://github.com/bats-core/bats-file> - common filesystem assertions for Bats
- <https://github.com/bats-core/bats-detik> - e2e tests of applications in K8s environments
and some external libraries, supported on a "best-effort" basis:
- <https://github.com/ztombol/bats-docs> (still relevant? Requires review)
- <https://github.com/grayhemp/bats-mock> (as per #147)
- <https://github.com/jasonkarns/bats-mock> (how is this different from grayhemp/bats-mock?)

9
docs/versions.md Normal file
View File

@ -0,0 +1,9 @@
Here are the docs of following versions:
* [v1.2.0](../../v1.2.0/README.md)
* [v1.1.0](../../v1.1.0/README.md)
* [v1.0.2](../../v1.0.2/README.md)
* [v0.4.0](../../v0.4.0/README.md)
* [v0.3.1](../../v0.3.1/README.md)
* [v0.2.0](../../v0.2.0/README.md)
* [v0.1.0](../../v0.1.0/README.md)

23
install.sh Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -e
BATS_ROOT="${0%/*}"
PREFIX="$1"
LIBDIR="${2:-lib}"
if [[ -z "$PREFIX" ]]; then
printf '%s\n' \
"usage: $0 <prefix>" \
" e.g. $0 /usr/local" >&2
exit 1
fi
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/${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"
echo "Installed Bats to $PREFIX/bin/bats"

249
lib/bats-core/common.bash Normal file
View File

@ -0,0 +1,249 @@
#!/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
}
function bats_replace_filename() {
local line
while read -r line; do
printf "%s\n" "${line//$BATS_TEST_SOURCE/$BATS_TEST_FILENAME}"
done
if [[ -n "$line" ]]; then
printf "%s\n" "${line//$BATS_TEST_SOURCE/$BATS_TEST_FILENAME}"
fi
}
bats_quote_code() { # <var> <code>
printf -v "$1" -- "%s%s%s" "$BATS_BEGIN_CODE_QUOTE" "$2" "$BATS_END_CODE_QUOTE"
}
bats_check_valid_version() {
if [[ ! $1 =~ [0-9]+.[0-9]+.[0-9]+ ]]; then
printf "ERROR: version '%s' must be of format <major>.<minor>.<patch>!\n" "$1" >&2
exit 1
fi
}
# compares two versions. Return 0 when version1 < version2
bats_version_lt() { # <version1> <version2>
bats_check_valid_version "$1"
bats_check_valid_version "$2"
local -a version1_parts version2_parts
IFS=. read -ra version1_parts <<<"$1"
IFS=. read -ra version2_parts <<<"$2"
for i in {0..2}; do
if ((version1_parts[i] < version2_parts[i])); then
return 0
elif ((version1_parts[i] > version2_parts[i])); then
return 1
fi
done
# if we made it this far, they are equal -> also not less then
return 2 # use other failing return code to distinguish equal from gt
}
# ensure a minimum version of bats is running or exit with failure
bats_require_minimum_version() { # <required version>
local required_minimum_version=$1
if bats_version_lt "$BATS_VERSION" "$required_minimum_version"; then
printf "BATS_VERSION=%s does not meet required minimum %s\n" "$BATS_VERSION" "$required_minimum_version"
exit 1
fi
if bats_version_lt "$BATS_GUARANTEED_MINIMUM_VERSION" "$required_minimum_version"; then
BATS_GUARANTEED_MINIMUM_VERSION="$required_minimum_version"
fi
}
bats_binary_search() { # <search-value> <array-name>
if [[ $# -ne 2 ]]; then
printf "ERROR: bats_binary_search requires exactly 2 arguments: <search value> <array name>\n" >&2
return 2
fi
local -r search_value=$1 array_name=$2
# we'd like to test if array is set but we cannot distinguish unset from empty arrays, so we need to skip that
local start=0 mid end mid_value
# start is inclusive, end is exclusive ...
eval "end=\${#${array_name}[@]}"
# so start == end means empty search space
while ((start < end)); do
mid=$(((start + end) / 2))
eval "mid_value=\${${array_name}[$mid]}"
if [[ "$mid_value" == "$search_value" ]]; then
return 0
elif [[ "$mid_value" < "$search_value" ]]; then
# This branch excludes equality -> +1 to skip the mid element.
# This +1 also avoids endless recursion on odd sized search ranges.
start=$((mid + 1))
else
end=$mid
fi
done
# did not find it -> its not there
return 1
}
# store the values in ascending (string!) order in result array
# Intended for short lists! (uses insertion sort)
bats_sort() { # <result-array-name> <values to sort...>
local -r result_name=$1
shift
if (($# == 0)); then
eval "$result_name=()"
return 0
fi
local -a sorted_array=()
local -i i
while (( $# > 0 )); do # loop over input values
local current_value="$1"
shift
for ((i = ${#sorted_array[@]}; i >= 0; --i)); do # loop over output array from end
if (( i == 0 )) || [[ ${sorted_array[i - 1]} < $current_value ]]; then
# shift bigger elements one position to the end
sorted_array[i]=$current_value
break
else
# insert new element at (freed) desired location
sorted_array[i]=${sorted_array[i - 1]}
fi
done
done
eval "$result_name=(\"\${sorted_array[@]}\")"
}
# check if all search values (must be sorted!) are in the (sorted!) array
# Intended for short lists/arrays!
bats_all_in() { # <sorted-array> <sorted search values...>
local -r haystack_array=$1
shift
local -i haystack_length # just to appease shellcheck
eval "local -r haystack_length=\${#${haystack_array}[@]}"
local -i haystack_index=0 # initialize only here to continue from last search position
local search_value haystack_value # just to appease shellcheck
for ((i = 1; i <= $#; ++i)); do
eval "local search_value=${!i}"
for (( ; haystack_index < haystack_length; ++haystack_index)); do
eval "local haystack_value=\${${haystack_array}[$haystack_index]}"
if [[ $haystack_value > "$search_value" ]]; then
# we passed the location this value would have been at -> not found
return 1
elif [[ $haystack_value == "$search_value" ]]; then
continue 2 # search value found -> try the next one
fi
done
return 1 # we ran of the end of the haystack without finding the value!
done
# did not return from loop above -> all search values were found
return 0
}
# check if any search value (must be sorted!) is in the (sorted!) array
# intended for short lists/arrays
bats_any_in() { # <sorted-array> <sorted search values>
local -r haystack_array=$1
shift
local -i haystack_length # just to appease shellcheck
eval "local -r haystack_length=\${#${haystack_array}[@]}"
local -i haystack_index=0 # initialize only here to continue from last search position
local search_value haystack_value # just to appease shellcheck
for ((i = 1; i <= $#; ++i)); do
eval "local search_value=${!i}"
for (( ; haystack_index < haystack_length; ++haystack_index)); do
eval "local haystack_value=\${${haystack_array}[$haystack_index]}"
if [[ $haystack_value > "$search_value" ]]; then
continue 2 # search value not in array! -> try next
elif [[ $haystack_value == "$search_value" ]]; then
return 0 # search value found
fi
done
done
# did not return from loop above -> no search value was found
return 1
}
bats_trim() { # <output-variable> <string>
local -r bats_trim_ltrimmed=${2#"${2%%[![:space:]]*}"} # cut off leading whitespace
# shellcheck disable=SC2034 # used in eval!
local -r bats_trim_trimmed=${bats_trim_ltrimmed%"${bats_trim_ltrimmed##*[![:space:]]}"} # cut off trailing whitespace
eval "$1=\$bats_trim_trimmed"
}
# a helper function to work around unbound variable errors with ${arr[@]} on Bash 3
bats_append_arrays_as_args() { # <array...> -- <command ...>
local -a trailing_args=()
while (($# > 0)) && [[ $1 != -- ]]; do
local array=$1
shift
if eval "(( \${#${array}[@]} > 0 ))"; then
eval "trailing_args+=(\"\${${array}[@]}\")"
fi
done
shift # remove -- separator
if (($# == 0)); then
printf "Error: append_arrays_as_args is missing a command or -- separator\n" >&2
return 1
fi
if ((${#trailing_args[@]} > 0)); then
"$@" "${trailing_args[@]}"
else
"$@"
fi
}
bats_format_file_line_reference() { # <output> <file> <line>
# shellcheck disable=SC2034 # will be used in subimplementation
local output="${1?}"
shift
"bats_format_file_line_reference_${BATS_LINE_REFERENCE_FORMAT?}" "$@"
}
bats_format_file_line_reference_comma_line() {
printf -v "$output" "%s, line %d" "$@"
}
bats_format_file_line_reference_colon() {
printf -v "$output" "%s:%d" "$@"
}
# approximate realpath without subshell
bats_approx_realpath() { # <output-variable> <path>
local output=$1 path=$2
if [[ $path != /* ]]; then
path="$PWD/$path"
fi
# x/./y -> x/y
path=${path//\/.\//\/}
printf -v "$output" "%s" "$path"
}
bats_format_file_line_reference_uri() {
local filename=${1?} line=${2?}
bats_approx_realpath filename "$filename"
printf -v "$output" "file://%s:%d" "$filename" "$line"
}

View File

@ -0,0 +1,143 @@
#!/usr/bin/env bash
# reads (extended) bats tap streams from stdin and calls callback functions for each line
#
# Segmenting functions
# ====================
# bats_tap_stream_plan <number of tests> -> when the test plan is encountered
# bats_tap_stream_suite <file name> -> when a new file is begun WARNING: extended only
# bats_tap_stream_begin <test index> <test name> -> when a new test is begun WARNING: extended only
#
# Test result functions
# =====================
# If timing was enabled, BATS_FORMATTER_TEST_DURATION will be set to their duration in milliseconds
# bats_tap_stream_ok <test index> <test name> -> when a test was successful
# bats_tap_stream_not_ok <test index> <test name> -> when a test has failed. If the failure was due to a timeout,
# BATS_FORMATTER_TEST_TIMEOUT is set to the timeout duration in seconds
# bats_tap_stream_skipped <test index> <test name> <skip reason> -> when a test was skipped
#
# Context functions
# =================
# bats_tap_stream_comment <comment text without leading '# '> <scope> -> when a comment line was encountered,
# scope tells the last encountered of plan, begin, ok, not_ok, skipped, suite
# bats_tap_stream_unknown <full line> <scope> -> when a line is encountered that does not match the previous entries,
# scope @see bats_tap_stream_comment
# forwards all input as is, when there is no TAP test plan header
function bats_parse_internal_extended_tap() {
local header_pattern='[0-9]+\.\.[0-9]+'
IFS= read -r header
if [[ "$header" =~ $header_pattern ]]; then
bats_tap_stream_plan "${header:3}"
else
# If the first line isn't a TAP plan, print it and pass the rest through
printf '%s\n' "$header"
exec cat
fi
ok_line_regexpr="ok ([0-9]+) (.*)"
skip_line_regexpr="ok ([0-9]+) (.*) # skip( (.*))?$"
timeout_line_regexpr="not ok ([0-9]+) (.*) # timeout after ([0-9]+)s$"
not_ok_line_regexpr="not ok ([0-9]+) (.*)"
timing_expr="in ([0-9]+)ms$"
local test_name begin_index ok_index not_ok_index index scope
begin_index=0
index=0
scope=plan
while IFS= read -r line; do
unset BATS_FORMATTER_TEST_DURATION BATS_FORMATTER_TEST_TIMEOUT
case "$line" in
'begin '*) # this might only be called in extended tap output
((++begin_index))
scope=begin
test_name="${line#* "$begin_index" }"
bats_tap_stream_begin "$begin_index" "$test_name"
;;
'ok '*)
((++index))
if [[ "$line" =~ $ok_line_regexpr ]]; then
ok_index="${BASH_REMATCH[1]}"
test_name="${BASH_REMATCH[2]}"
if [[ "$line" =~ $skip_line_regexpr ]]; then
scope=skipped
test_name="${BASH_REMATCH[2]}" # cut off name before "# skip"
local skip_reason="${BASH_REMATCH[4]}"
if [[ "$test_name" =~ $timing_expr ]]; then
local BATS_FORMATTER_TEST_DURATION="${BASH_REMATCH[1]}"
test_name="${test_name% in "${BATS_FORMATTER_TEST_DURATION}"ms}"
bats_tap_stream_skipped "$ok_index" "$test_name" "$skip_reason"
else
bats_tap_stream_skipped "$ok_index" "$test_name" "$skip_reason"
fi
else
scope=ok
if [[ "$line" =~ $timing_expr ]]; then
local BATS_FORMATTER_TEST_DURATION="${BASH_REMATCH[1]}"
bats_tap_stream_ok "$ok_index" "${test_name% in "${BASH_REMATCH[1]}"ms}"
else
bats_tap_stream_ok "$ok_index" "$test_name"
fi
fi
else
printf "ERROR: could not match ok line: %s" "$line" >&2
exit 1
fi
;;
'not ok '*)
((++index))
scope=not_ok
if [[ "$line" =~ $not_ok_line_regexpr ]]; then
not_ok_index="${BASH_REMATCH[1]}"
test_name="${BASH_REMATCH[2]}"
if [[ "$line" =~ $timeout_line_regexpr ]]; then
not_ok_index="${BASH_REMATCH[1]}"
test_name="${BASH_REMATCH[2]}"
# shellcheck disable=SC2034 # used in bats_tap_stream_ok
local BATS_FORMATTER_TEST_TIMEOUT="${BASH_REMATCH[3]}"
fi
if [[ "$test_name" =~ $timing_expr ]]; then
# shellcheck disable=SC2034 # used in bats_tap_stream_ok
local BATS_FORMATTER_TEST_DURATION="${BASH_REMATCH[1]}"
test_name="${test_name% in "${BASH_REMATCH[1]}"ms}"
fi
bats_tap_stream_not_ok "$not_ok_index" "$test_name"
else
printf "ERROR: could not match not ok line: %s" "$line" >&2
exit 1
fi
;;
'# '*)
bats_tap_stream_comment "${line:2}" "$scope"
;;
'#')
bats_tap_stream_comment "" "$scope"
;;
'suite '*)
scope=suite
# pass on the
bats_tap_stream_suite "${line:6}"
;;
*)
bats_tap_stream_unknown "$line" "$scope"
;;
esac
done
}
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"
}

View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
BATS_TMPNAME="$BATS_RUN_TMPDIR/bats.$$"
BATS_PARENT_TMPNAME="$BATS_RUN_TMPDIR/bats.$PPID"
# shellcheck disable=SC2034
BATS_OUT="${BATS_TMPNAME}.out" # used in bats-exec-file
bats_preprocess_source() {
# export to make it visible to bats_evaluate_preprocessed_source
# since the latter runs in bats-exec-test's bash while this runs in bats-exec-file's
export BATS_TEST_SOURCE="${BATS_TMPNAME}.src"
CHECK_BATS_COMMENT_COMMANDS=1 bats-preprocess "$BATS_TEST_FILENAME" >"$BATS_TEST_SOURCE"
}
bats_evaluate_preprocessed_source() {
if [[ -z "${BATS_TEST_SOURCE:-}" ]]; then
BATS_TEST_SOURCE="${BATS_PARENT_TMPNAME}.src"
fi
# Dynamically loaded user files provided outside of Bats.
# shellcheck disable=SC1090
source "$BATS_TEST_SOURCE"
}

View File

@ -0,0 +1,113 @@
#!/usr/bin/env bash
bats_run_under_flock() {
flock "$BATS_SEMAPHORE_DIR" "$@"
}
bats_run_under_shlock() {
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
}
# 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_LOCKING_IMPLEMENTATION=flock
elif command -v shlock >/dev/null; then
BATS_LOCKING_IMPLEMENTATION=shlock
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
# block when there is no free slot for the semaphore
# when there is a free slot, run the command in background
# gather the output of the command in files in the given directory
bats_semaphore_run() {
local output_dir=$1
shift
local semaphore_slot
semaphore_slot=$(bats_semaphore_acquire_slot)
bats_semaphore_release_wrapper "$output_dir" "$semaphore_slot" "$@" &
printf "%d\n" "$!"
}
# $1 - output directory for stdout/stderr
# $@ - command to run
# this wraps the actual function call to install some traps on exiting
bats_semaphore_release_wrapper() {
local output_dir="$1"
local semaphore_name="$2"
shift 2 # all other parameters will be use for the command to execute
# shellcheck disable=SC2064 # we want to expand the semaphore_name right now!
trap "status=$?; bats_semaphore_release_slot '$semaphore_name'; exit $status" EXIT
mkdir -p "$output_dir"
"$@" 2>"$output_dir/stderr" >"$output_dir/stdout"
local status=$?
# bash bug: the exit trap is not called for the background process
bats_semaphore_release_slot "$semaphore_name"
trap - EXIT # avoid calling release twice
return $status
}
bats_semaphore_acquire_while_locked() {
if [[ $(bats_semaphore_get_free_slot_count) -gt 0 ]]; then
local slot=0
while [[ -e "$BATS_SEMAPHORE_DIR/slot-$slot" ]]; do
((++slot))
done
if [[ $slot -lt $BATS_SEMAPHORE_NUMBER_OF_SLOTS ]]; then
touch "$BATS_SEMAPHORE_DIR/slot-$slot" && printf "%d\n" "$slot" && return 0
fi
fi
return 1
}
# block until a semaphore slot becomes free
# prints the number of the slot that it received
bats_semaphore_acquire_slot() {
mkdir -p "$BATS_SEMAPHORE_DIR"
# wait for a slot to become free
# TODO: avoid busy waiting by using signals -> this opens op prioritizing possibilities as well
while true; do
# don't lock for reading, we are fine with spuriously getting no free slot
if [[ $(bats_semaphore_get_free_slot_count) -gt 0 ]]; then
bats_run_under_"$BATS_LOCKING_IMPLEMENTATION" \
bash -c bats_semaphore_acquire_while_locked \
&& break
fi
sleep 1
done
}
bats_semaphore_release_slot() {
# we don't need to lock this, since only our process owns this file
# and freeing a semaphore cannot lead to conflicts with others
rm "$BATS_SEMAPHORE_DIR/slot-$1" # this will fail if we had not acquired a semaphore!
}
bats_semaphore_get_free_slot_count() {
# find might error out without returning something useful when a file is deleted,
# while the directory is traversed -> only continue when there was no error
until used_slots=$(find "$BATS_SEMAPHORE_DIR" -name 'slot-*' 2>/dev/null | wc -l); do :; done
echo $((BATS_SEMAPHORE_NUMBER_OF_SLOTS - used_slots))
}

View File

@ -0,0 +1,359 @@
#!/usr/bin/env bash
BATS_TEST_DIRNAME="${BATS_TEST_FILENAME%/*}"
BATS_TEST_NAMES=()
# shellcheck source=lib/bats-core/warnings.bash
source "$BATS_ROOT/lib/bats-core/warnings.bash"
# find_in_bats_lib_path echoes the first recognized load path to
# a library in BATS_LIB_PATH or relative to BATS_TEST_DIRNAME.
#
# Libraries relative to BATS_TEST_DIRNAME take precedence over
# BATS_LIB_PATH.
#
# Library load paths are recognized using find_library_load_path.
#
# If no library is found find_in_bats_lib_path returns 1.
find_in_bats_lib_path() { # <return-var> <library-name>
local return_var="${1:?}"
local library_name="${2:?}"
local -a bats_lib_paths
IFS=: read -ra bats_lib_paths <<<"$BATS_LIB_PATH"
for path in "${bats_lib_paths[@]}"; do
if [[ -f "$path/$library_name" ]]; then
printf -v "$return_var" "%s" "$path/$library_name"
# A library load path was found, return
return 0
elif [[ -f "$path/$library_name/load.bash" ]]; then
printf -v "$return_var" "%s" "$path/$library_name/load.bash"
# A library load path was found, return
return 0
fi
done
return 1
}
# bats_internal_load expects an absolute path that is a library load path.
#
# If the library load path points to a file (a library loader) it is
# sourced.
#
# If it points to a directory all files ending in .bash inside of the
# directory are sourced.
#
# If the sourcing of the library loader or of a file in a library
# directory fails bats_internal_load prints an error message and returns 1.
#
# If the passed library load path is not absolute or is not a valid file
# or directory bats_internal_load prints an error message and returns 1.
bats_internal_load() {
local library_load_path="${1:?}"
if [[ "${library_load_path:0:1}" != / ]]; then
printf "Passed library load path is not an absolute path: %s\n" "$library_load_path" >&2
return 1
fi
# library_load_path is a library loader
if [[ -f "$library_load_path" ]]; then
# shellcheck disable=SC1090
if ! source "$library_load_path"; then
printf "Error while sourcing library loader at '%s'\n" "$library_load_path" >&2
return 1
fi
return 0
fi
printf "Passed library load path is neither a library loader nor library directory: %s\n" "$library_load_path" >&2
return 1
}
# bats_load_safe accepts an argument called 'slug' and attempts to find and
# source a library based on the slug.
#
# A slug can be an absolute path, a library name or a relative path.
#
# If the slug is an absolute path bats_load_safe attempts to find the library
# load path using find_library_load_path.
# What is considered a library load path is documented in the
# documentation for find_library_load_path.
#
# If the slug is not an absolute path it is considered a library name or
# relative path. bats_load_safe attempts to find the library load path using
# find_in_bats_lib_path.
#
# If bats_load_safe can find a library load path it is passed to bats_internal_load.
# If bats_internal_load fails bats_load_safe returns 1.
#
# If no library load path can be found bats_load_safe prints an error message
# and returns 1.
bats_load_safe() {
local slug="${1:?}"
if [[ ${slug:0:1} != / ]]; then # relative paths are relative to BATS_TEST_DIRNAME
slug="$BATS_TEST_DIRNAME/$slug"
fi
if [[ -f "$slug.bash" ]]; then
bats_internal_load "$slug.bash"
return $?
elif [[ -f "$slug" ]]; then
bats_internal_load "$slug"
return $?
fi
# loading from PATH (retained for backwards compatibility)
if [[ ! -f "$1" ]] && type -P "$1" >/dev/null; then
# shellcheck disable=SC1090
source "$1"
return $?
fi
# No library load path can be found
printf "bats_load_safe: Could not find '%s'[.bash]\n" "$slug" >&2
return 1
}
bats_load_library_safe() { # <slug>
local slug="${1:?}" library_path
# Check for library load paths in BATS_TEST_DIRNAME and BATS_LIB_PATH
if [[ ${slug:0:1} != / ]]; then
if ! find_in_bats_lib_path library_path "$slug"; then
printf "Could not find library '%s' relative to test file or in BATS_LIB_PATH\n" "$slug" >&2
return 1
fi
else
# absolute paths are taken as is
library_path="$slug"
if [[ ! -f "$library_path" ]]; then
printf "Could not find library on absolute path '%s'\n" "$library_path" >&2
return 1
fi
fi
bats_internal_load "$library_path"
return $?
}
# immediately exit on error, use bats_load_library_safe to catch and handle errors
bats_load_library() { # <slug>
if ! bats_load_library_safe "$@"; then
exit 1
fi
}
# load acts like bats_load_safe but exits the shell instead of returning 1.
load() {
if ! bats_load_safe "$@"; then
exit 1
fi
}
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>
local output_array_name="$1"
local input_var_name="$2"
if [[ $keep_empty_lines ]]; then
local bats_separate_lines_lines=()
if [[ -n "${!input_var_name}" ]]; then # avoid getting an empty line for empty input
while IFS= read -r line; do
bats_separate_lines_lines+=("$line")
done <<<"${!input_var_name}"
fi
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}" || true # don't fail due to EOF
fi
}
run() { # [!|-N] [--keep-empty-lines] [--separate-stderr] [--] <command to run...>
# This has to be restored on exit from this function to avoid leaking our trap INT into surrounding code.
# Non zero exits won't restore under the assumption that they will fail the test before it can be aborted,
# which allows us to avoid duplicating the restore code on every exit path
trap bats_interrupt_trap_in_run INT
local expected_rc=
local keep_empty_lines=
local output_case=merged
local has_flags=
# parse options starting with -
while [[ $# -gt 0 ]] && [[ $1 == -* || $1 == '!' ]]; do
has_flags=1
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
;;
*)
printf "Usage error: unknown flag '%s'" "$1" >&2
return 1
;;
esac
shift
done
if [[ -n $has_flags ]]; then
bats_warn_minimum_guaranteed_version "Using flags on \`run\`" 1.5.0
fi
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
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=0 || status=$?
output="${output%.}"
else
# 'output', 'status', 'lines' are global variables available to tests.
# shellcheck disable=SC2034
output="$("$pre_command" "$@")" && status=0 || status=$?
fi
bats_separate_lines lines output
if [[ "$output_case" == separate ]]; then
# shellcheck disable=SC2034
read -d '' -r stderr <"$bats_run_separate_stderr_file" || true
bats_separate_lines stderr_lines stderr
fi
# shellcheck disable=SC2034
BATS_RUN_COMMAND="${*}"
set "-$origFlags"
bats_run_print_output() {
if [[ -n "$output" ]]; then
printf "%s\n" "$output"
fi
if [[ "$output_case" == separate && -n "$stderr" ]]; then
printf "stderr:\n%s\n" "$stderr"
fi
}
if [[ -n "$expected_rc" ]]; then
if [[ "$expected_rc" = "-1" ]]; then
if [[ "$status" -eq 0 ]]; then
BATS_ERROR_SUFFIX=", expected nonzero exit code!"
bats_run_print_output
return 1
fi
elif [ "$status" -ne "$expected_rc" ]; then
# shellcheck disable=SC2034
BATS_ERROR_SUFFIX=", expected exit code $expected_rc, got $status"
bats_run_print_output
return 1
fi
elif [[ "$status" -eq 127 ]]; then # "command not found"
bats_generate_warning 1 "$BATS_RUN_COMMAND"
fi
if [[ ${BATS_VERBOSE_RUN:-} ]]; then
bats_run_print_output
fi
# don't leak our trap into surrounding code
trap bats_interrupt_trap INT
}
setup() {
return 0
}
teardown() {
return 0
}
skip() {
# if this is a skip in teardown ...
if [[ -n "${BATS_TEARDOWN_STARTED-}" ]]; then
# ... we want to skip the rest of teardown.
# communicate to bats_exit_trap that the teardown was completed without error
# shellcheck disable=SC2034
BATS_TEARDOWN_COMPLETED=1
# if we are already in the exit trap (e.g. due to previous skip) ...
if [[ "$BATS_TEARDOWN_STARTED" == as-exit-trap ]]; then
# ... we need to do the rest of the tear_down_trap that would otherwise be skipped after the next call to exit
bats_exit_trap
# and then do the exit (at the end of this function)
fi
# if we aren't in exit trap, the normal exit handling should suffice
else
# ... this is either skip in test or skip in setup.
# Following variables are used in bats-exec-test which sources this file
# shellcheck disable=SC2034
BATS_TEST_SKIPPED="${1:-1}"
# shellcheck disable=SC2034
BATS_TEST_COMPLETED=1
fi
exit 0
}
bats_test_begin() {
BATS_TEST_DESCRIPTION="$1"
if [[ -n "$BATS_EXTENDED_SYNTAX" ]]; then
printf 'begin %d %s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}$BATS_TEST_DESCRIPTION" >&3
fi
setup
}
bats_test_function() {
local test_name="$1"
BATS_TEST_NAMES+=("$test_name")
}
# decides whether a failed test should be run again
bats_should_retry_test() {
# test try number starts at 1
# 0 retries means run only first try
((BATS_TEST_TRY_NUMBER <= BATS_TEST_RETRIES))
}

399
lib/bats-core/tracing.bash Normal file
View File

@ -0,0 +1,399 @@
#!/usr/bin/env bash
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
bats_capture_stack_trace() {
local test_file
local funcname
local i
BATS_DEBUG_LAST_STACK_TRACE=()
for ((i = 2; i != ${#FUNCNAME[@]}; ++i)); do
# Use BATS_TEST_SOURCE if necessary to work around Bash < 4.4 bug whereby
# calling an exported function erases the test file's BASH_SOURCE entry.
test_file="${BASH_SOURCE[$i]:-$BATS_TEST_SOURCE}"
funcname="${FUNCNAME[$i]}"
BATS_DEBUG_LAST_STACK_TRACE+=("${BASH_LINENO[$((i - 1))]} $funcname $test_file")
case "$funcname" in
"${BATS_TEST_NAME-}" | setup | teardown | setup_file | teardown_file | setup_suite | teardown_suite)
break
;;
esac
if [[ "${BASH_SOURCE[$i + 1]:-}" == *"bats-exec-file" ]] && [[ "$funcname" == 'source' ]]; then
break
fi
done
}
bats_get_failure_stack_trace() {
local stack_trace_var
# See bats_debug_trap for details.
if [[ -n "${BATS_DEBUG_LAST_STACK_TRACE_IS_VALID:-}" ]]; then
stack_trace_var=BATS_DEBUG_LAST_STACK_TRACE
else
stack_trace_var=BATS_DEBUG_LASTLAST_STACK_TRACE
fi
# shellcheck disable=SC2016
eval "$(printf \
'%s=(${%s[@]+"${%s[@]}"})' \
"${1}" \
"${stack_trace_var}" \
"${stack_trace_var}")"
}
bats_print_stack_trace() {
local frame
local index=1
local count="${#@}"
local filename
local lineno
for frame in "$@"; do
bats_frame_filename "$frame" 'filename'
bats_trim_filename "$filename" 'filename'
bats_frame_lineno "$frame" 'lineno'
printf '%s' "${BATS_STACK_TRACE_PREFIX-# }"
if [[ $index -eq 1 ]]; then
printf '('
else
printf ' '
fi
local fn
bats_frame_function "$frame" 'fn'
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
local quoted_fn
bats_quote_code quoted_fn "$fn"
printf "from function %s " "$quoted_fn"
fi
local reference
bats_format_file_line_reference reference "$filename" "$lineno"
if [[ $index -eq $count ]]; then
printf 'in test file %s)\n' "$reference"
else
printf 'in file %s,\n' "$reference"
fi
((++index))
done
}
bats_print_failed_command() {
local stack_trace=("${@}")
if [[ ${#stack_trace[@]} -eq 0 ]]; then
return 0
fi
local frame="${stack_trace[${#stack_trace[@]} - 1]}"
local filename
local lineno
local failed_line
local failed_command
bats_frame_filename "$frame" 'filename'
bats_frame_lineno "$frame" 'lineno'
bats_extract_line "$filename" "$lineno" 'failed_line'
bats_strip_string "$failed_line" 'failed_command'
local quoted_failed_command
bats_quote_code quoted_failed_command "$failed_command"
printf '# %s ' "${quoted_failed_command}"
if [[ "${BATS_TIMED_OUT-NOTSET}" != NOTSET ]]; then
# the other values can be safely overwritten here,
# as the timeout is the primary reason for failure
BATS_ERROR_SUFFIX=" due to timeout"
fi
if [[ "$BATS_ERROR_STATUS" -eq 1 ]]; then
printf 'failed%s\n' "$BATS_ERROR_SUFFIX"
else
printf 'failed with status %d%s\n' "$BATS_ERROR_STATUS" "$BATS_ERROR_SUFFIX"
fi
}
bats_frame_lineno() {
printf -v "$2" '%s' "${1%% *}"
}
bats_frame_function() {
local __bff_function="${1#* }"
printf -v "$2" '%s' "${__bff_function%% *}"
}
bats_frame_filename() {
local __bff_filename="${1#* }"
__bff_filename="${__bff_filename#* }"
if [[ "$__bff_filename" == "${BATS_TEST_SOURCE-}" ]]; then
__bff_filename="$BATS_TEST_FILENAME"
fi
printf -v "$2" '%s' "$__bff_filename"
}
bats_extract_line() {
local __bats_extract_line_line
local __bats_extract_line_index=0
while IFS= read -r __bats_extract_line_line; do
if [[ "$((++__bats_extract_line_index))" -eq "$2" ]]; then
printf -v "$3" '%s' "${__bats_extract_line_line%$'\r'}"
break
fi
done <"$1"
}
bats_strip_string() {
[[ "$1" =~ ^[[:space:]]*(.*)[[:space:]]*$ ]]
printf -v "$2" '%s' "${BASH_REMATCH[1]}"
}
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" path="$2"
if [[ "$output_var" != NORMALIZED_INPUT ]]; then
local NORMALIZED_INPUT
fi
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 definition 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
local reference
bats_format_file_line_reference reference "${file##*/}" "$line"
printf '%s [%s]\n' "${padding::${#BASH_LINENO[@]}-4}" "$reference" >&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 tracks the last line of code executed within a test. This is
# necessary because $BASH_LINENO is often incorrect inside of ERR and EXIT
# trap handlers.
#
# Below are tables describing different command failure scenarios and the
# reliability of $BASH_LINENO within different the executed DEBUG, ERR, and EXIT
# trap handlers. Naturally, the behaviors change between versions of Bash.
#
# Table rows should be read left to right. For example, on bash version
# 4.0.44(2)-release, if a test executes `false` (or any other failing external
# command), bash will do the following in order:
# 1. Call the DEBUG trap handler (bats_debug_trap) with $BASH_LINENO referring
# to the source line containing the `false` command, then
# 2. Call the DEBUG trap handler again, but with an incorrect $BASH_LINENO, then
# 3. Call the ERR trap handler, but with a (possibly-different) incorrect
# $BASH_LINENO, then
# 4. Call the DEBUG trap handler again, but with $BASH_LINENO set to 1, then
# 5. Call the EXIT trap handler, with $BASH_LINENO set to 1.
#
# bash version 4.4.20(1)-release
# command | first DEBUG | second DEBUG | ERR | third DEBUG | EXIT
# -------------+-------------+--------------+---------+-------------+--------
# false | OK | OK | OK | BAD[1] | BAD[1]
# [[ 1 = 2 ]] | OK | BAD[2] | BAD[2] | BAD[1] | BAD[1]
# (( 1 = 2 )) | OK | BAD[2] | BAD[2] | BAD[1] | BAD[1]
# ! true | OK | --- | BAD[4] | --- | BAD[1]
# $var_dne | OK | --- | --- | BAD[1] | BAD[1]
# source /dne | OK | --- | --- | BAD[1] | BAD[1]
#
# bash version 4.0.44(2)-release
# command | first DEBUG | second DEBUG | ERR | third DEBUG | EXIT
# -------------+-------------+--------------+---------+-------------+--------
# false | OK | BAD[3] | BAD[3] | BAD[1] | BAD[1]
# [[ 1 = 2 ]] | OK | --- | BAD[3] | --- | BAD[1]
# (( 1 = 2 )) | OK | --- | BAD[3] | --- | BAD[1]
# ! true | OK | --- | BAD[3] | --- | BAD[1]
# $var_dne | OK | --- | --- | BAD[1] | BAD[1]
# source /dne | OK | --- | --- | BAD[1] | BAD[1]
#
# [1] The reported line number is always 1.
# [2] The reported source location is that of the beginning of the function
# calling the command.
# [3] The reported line is that of the last command executed in the DEBUG trap
# handler.
# [4] The reported source location is that of the call to the function calling
# the command.
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
# also don't record new stack traces while handling interruptions, to avoid overriding the interrupted command
if [[ -z "$file_excluded" &&
"${BATS_INTERRUPTED-NOTSET}" == NOTSET &&
"${BATS_TIMED_OUT-NOTSET}" == NOTSET ]]; then
BATS_DEBUG_LASTLAST_STACK_TRACE=(
${BATS_DEBUG_LAST_STACK_TRACE[@]+"${BATS_DEBUG_LAST_STACK_TRACE[@]}"}
)
BATS_DEBUG_LAST_LINENO=(${BASH_LINENO[@]+"${BASH_LINENO[@]}"})
BATS_DEBUG_LAST_SOURCE=(${BASH_SOURCE[@]+"${BASH_SOURCE[@]}"})
bats_capture_stack_trace
bats_emit_trace
fi
}
# For some versions of Bash, the `ERR` trap may not always fire for every
# command failure, but the `EXIT` trap will. Also, some command failures may not
# set `$?` properly. See #72 and #81 for details.
#
# For this reason, we call `bats_check_status_from_trap` at the very beginning
# of `bats_teardown_trap` and check the value of `$BATS_TEST_COMPLETED` before
# taking other actions. We also adjust the exit status value if needed.
#
# See `bats_exit_trap` for an additional EXIT error handling case when `$?`
# isn't set properly during `teardown()` errors.
bats_check_status_from_trap() {
local status="$?"
if [[ -z "${BATS_TEST_COMPLETED:-}" ]]; then
BATS_ERROR_STATUS="${BATS_ERROR_STATUS:-$status}"
if [[ "$BATS_ERROR_STATUS" -eq 0 ]]; then
BATS_ERROR_STATUS=1
fi
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
local normalized_dir
bats_normalize_windows_dir_path normalized_dir "$1"
BATS_DEBUG_EXCLUDE_PATHS+=("$normalized_dir")
else
BATS_DEBUG_EXCLUDE_PATHS+=("$1")
fi
}
bats_setup_tracing() {
# Variables for capturing accurate stack traces. See bats_debug_trap for
# details.
#
# BATS_DEBUG_LAST_LINENO, BATS_DEBUG_LAST_SOURCE, and
# BATS_DEBUG_LAST_STACK_TRACE hold data from the most recent call to
# bats_debug_trap.
#
# BATS_DEBUG_LASTLAST_STACK_TRACE holds data from two bats_debug_trap calls
# ago.
#
# BATS_DEBUG_LAST_STACK_TRACE_IS_VALID indicates that
# BATS_DEBUG_LAST_STACK_TRACE contains the stack trace of the test's error. If
# unset, BATS_DEBUG_LAST_STACK_TRACE is unreliable and
# BATS_DEBUG_LASTLAST_STACK_TRACE should be used instead.
BATS_DEBUG_LASTLAST_STACK_TRACE=()
BATS_DEBUG_LAST_LINENO=()
BATS_DEBUG_LAST_SOURCE=()
BATS_DEBUG_LAST_STACK_TRACE=()
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=
BATS_ERROR_SUFFIX=
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
local exclude_paths path
# 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 excludes to avoid tracing the exclude setup
trap 'bats_debug_trap "$BASH_SOURCE"' DEBUG
trap 'bats_error_trap' ERR
}
bats_error_trap() {
bats_check_status_from_trap
# If necessary, undo the most recent stack trace captured by bats_debug_trap.
# See bats_debug_trap for details.
if [[ "${BASH_LINENO[*]}" = "${BATS_DEBUG_LAST_LINENO[*]:-}" &&
"${BASH_SOURCE[*]}" = "${BATS_DEBUG_LAST_SOURCE[*]:-}" &&
-z "$BATS_DEBUG_LAST_STACK_TRACE_IS_VALID" ]]; then
BATS_DEBUG_LAST_STACK_TRACE=(
${BATS_DEBUG_LASTLAST_STACK_TRACE[@]+"${BATS_DEBUG_LASTLAST_STACK_TRACE[@]}"}
)
fi
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1
}
bats_interrupt_trap() {
# mark the interruption, to handle during exit
BATS_INTERRUPTED=true
BATS_ERROR_STATUS=130
# debug trap fires before interrupt trap but gets wrong linenumber (line 1)
# -> use last stack trace instead of BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=true
}
# this is used inside run()
bats_interrupt_trap_in_run() {
# mark the interruption, to handle during exit
BATS_INTERRUPTED=true
BATS_ERROR_STATUS=130
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=true
exit 130
}

View File

@ -0,0 +1,37 @@
#!/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
printf "%s\n" "$header"
# if we detect a TAP plan
if [[ "$header" =~ $header_pattern ]]; then
# extract the number of tests ...
local expected_number_of_tests="${header:3}"
# ... count the actual number of [not ] oks...
local actual_number_of_tests=0
while IFS= read -r line; do
# forward line
printf "%s\n" "$line"
case "$line" in
'ok '*)
((++actual_number_of_tests))
;;
'not ok'*)
((++actual_number_of_tests))
;;
esac
done
# ... and error if they are not the same
if [[ "${actual_number_of_tests}" != "${expected_number_of_tests}" ]]; then
printf '# bats warning: Executed %s instead of expected %s tests\n' "$actual_number_of_tests" "$expected_number_of_tests"
return 1
fi
else
# forward output unchanged
cat
fi
}

View File

@ -0,0 +1,44 @@
#!/usr/bin/env bash
# shellcheck source=lib/bats-core/tracing.bash
source "$BATS_ROOT/lib/bats-core/tracing.bash"
# generate a warning report for the parent call's call site
bats_generate_warning() { # <warning number> [--no-stacktrace] [<printf args for warning string>...]
local warning_number="${1-}" padding="00"
shift
local no_stacktrace=
if [[ ${1-} == --no-stacktrace ]]; then
no_stacktrace=1
shift
fi
if [[ $warning_number =~ [0-9]+ ]] && ((warning_number < ${#BATS_WARNING_SHORT_DESCS[@]})); then
{
printf "BW%s: ${BATS_WARNING_SHORT_DESCS[$warning_number]}\n" "${padding:${#warning_number}}${warning_number}" "$@"
if [[ -z "$no_stacktrace" ]]; then
bats_capture_stack_trace
BATS_STACK_TRACE_PREFIX=' ' bats_print_stack_trace "${BATS_DEBUG_LAST_STACK_TRACE[@]}"
fi
} >>"$BATS_WARNING_FILE" 2>&3
else
printf "Invalid Bats warning number '%s'. It must be an integer between 1 and %d." "$warning_number" "$((${#BATS_WARNING_SHORT_DESCS[@]} - 1))" >&2
exit 1
fi
}
# generate a warning if the BATS_GUARANTEED_MINIMUM_VERSION is not high enough
bats_warn_minimum_guaranteed_version() { # <feature> <minimum required version>
if bats_version_lt "$BATS_GUARANTEED_MINIMUM_VERSION" "$2"; then
bats_generate_warning 2 "$1" "$2" "$2"
fi
}
# put after functions to avoid line changes in tests when new ones get added
BATS_WARNING_SHORT_DESCS=(
# to start with 1
'PADDING'
# see issue #578 for context
"\`run\`'s command \`%s\` exited with code 127, indicating 'Command not found'. Use run's return code checks, e.g. \`run -127\`, to fix this message."
"%s requires at least BATS_VERSION=%s. Use \`bats_require_minimum_version %s\` to fix this message."
"\`setup_suite\` is visible to test file '%s', but was not executed. It belongs into 'setup_suite.bash' to be picked up automatically."
)

504
libexec/bats-core/bats Executable file
View File

@ -0,0 +1,504 @@
#!/usr/bin/env bash
set -e
export BATS_VERSION='1.9.0'
VALID_FORMATTERS="pretty, junit, tap, tap13"
version() {
printf 'Bats %s\n' "$BATS_VERSION"
}
abort() {
local print_usage=1
if [[ ${1:-} == --no-print-usage ]]; then
print_usage=
shift
fi
printf 'Error: %s\n' "$1" >&2
if [[ -n $print_usage ]]; then
usage >&2
fi
exit 1
}
usage() {
local cmd="${0##*/}"
local line
cat <<HELP_TEXT_HEADER
Usage: ${cmd} [OPTIONS] <tests>
${cmd} [-h | -v]
HELP_TEXT_HEADER
cat <<'HELP_TEXT_BODY'
<tests> is the path to a Bats test file, or the path to a directory
containing Bats test files (ending with ".bats")
-c, --count Count test cases without running any tests
--code-quote-style <style>
A two character string of code quote delimiters
or 'custom' which requires setting $BATS_BEGIN_CODE_QUOTE and
$BATS_END_CODE_QUOTE. Can also be set via $BATS_CODE_QUOTE_STYLE
--line-reference-format Controls how file/line references e.g. in stack traces are printed:
- comma_line (default): a.bats, line 1
- colon: a.bats:1
- uri: file:///tests/a.bats:1
- custom: provide your own via defining bats_format_file_line_reference_custom
with parameters <filename> <line>, store via `printf -v "$output"`
-f, --filter <regex> Only run tests that match the regular expression
--filter-status <status> Only run tests with the given status in the last completed (no CTRL+C/SIGINT) run.
Valid <status> values are:
failed - runs tests that failed or were not present in the last run
missed - runs tests that were not present in the last run
--filter-tags <comma-separated-tag-list>
Only run tests that match all the tags in the list (&&).
You can negate a tag via prepending '!'.
Specifying this flag multiple times allows for logical or (||):
`--filter-tags A,B --filter-tags A,!C` matches tags (A && B) || (A && !C)
-F, --formatter <type> Switch between formatters: pretty (default),
tap (default w/o term), tap13, junit, /<absolute path to formatter>
--gather-test-outputs-in <directory>
Gather the output of failing *and* passing tests
as files in directory (if existing, must be empty)
-h, --help Display this help message
-j, --jobs <jobs> Number of parallel jobs (requires GNU parallel)
--no-tempdir-cleanup Preserve test output temporary directory
--no-parallelize-across-files
Serialize test file execution instead of running
them in parallel (requires --jobs >1)
--no-parallelize-within-files
Serialize test execution within files instead of
running them in parallel (requires --jobs >1)
--report-formatter <type> Switch between reporters (same options as --formatter)
-o, --output <dir> Directory to write report files (must exist)
-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
HELP_TEXT_BODY
}
expand_path() {
local path="${1%/}"
local dirname="${path%/*}"
if [[ -z "$dirname" ]]; then
dirname=/
fi
local result="$2"
local OLDPWD="$PWD"
if [[ "$dirname" == "$path" ]]; then
dirname="$PWD"
else
cd "$dirname"
dirname="$PWD"
cd "$OLDPWD"
fi
printf -v "$result" '%s/%s' "$dirname" "${path##*/}"
}
BATS_LIBEXEC="$(
cd "$(dirname "$(bats_readlinkf "${BASH_SOURCE[0]}")")"
pwd
)"
export BATS_LIBEXEC
export BATS_CWD="$PWD"
export BATS_TEST_FILTER=
export PATH="$BATS_LIBEXEC:$PATH"
export BATS_ROOT_PID=$$
export BATS_TMPDIR="${TMPDIR:-/tmp}"
BATS_TMPDIR=${BATS_TMPDIR%/} # chop off trailing / to avoid duplication
export BATS_RUN_TMPDIR=
export BATS_GUARANTEED_MINIMUM_VERSION=0.0.0
export BATS_LIB_PATH=${BATS_LIB_PATH-/usr/lib/bats}
BATS_REPORT_OUTPUT_DIR=${BATS_REPORT_OUTPUT_DIR-.}
export BATS_LINE_REFERENCE_FORMAT=${BATS_LINE_REFERENCE_FORMAT-comma_line}
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
arguments=()
# Unpack single-character options bundled together, e.g. -cr, -pr.
for arg in "$@"; do
if [[ "$arg" =~ ^-[^-]. ]]; then
index=1
while option="${arg:$((index++)):1}"; do
if [[ -z "$option" ]]; then
break
fi
arguments+=("-$option")
done
else
arguments+=("$arg")
fi
shift
done
set -- "${arguments[@]}"
arguments=()
unset flags recursive formatter_flags
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=${BATS_FORMATTER:-'tap'}
report_formatter=''
recursive=
setup_suite_file=''
export BATS_TEMPDIR_CLEANUP=1
if [[ -z "${CI:-}" && -t 0 && -t 1 ]] && command -v tput >/dev/null; then
formatter='pretty'
fi
while [[ "$#" -ne 0 ]]; do
case "$1" in
-h | --help)
version
usage
exit 0
;;
-v | --version)
version
exit 0
;;
-c | --count)
flags+=('-c')
;;
-f | --filter)
shift
flags+=('-f' "$1")
;;
-F | --formatter)
shift
# allow cat formatter to see extended output but don't advertise to users
if [[ $1 =~ ^(pretty|junit|tap|tap13|cat|/.*)$ ]]; then
formatter="$1"
else
printf "Unknown formatter '%s', valid options are %s\n" "$1" "${VALID_FORMATTERS}"
exit 1
fi
;;
--report-formatter)
shift
if [[ $1 =~ ^(cat|pretty|junit|tap|tap13|/.*)$ ]]; then
report_formatter="$1"
else
printf "Unknown report formatter '%s', valid options are %s\n" "$1" "${VALID_FORMATTERS}"
exit 1
fi
;;
-o | --output)
shift
BATS_REPORT_OUTPUT_DIR="$1"
;;
-p | --pretty)
formatter='pretty'
;;
-j | --jobs)
shift
flags+=('-j' "$1")
;;
-r | --recursive)
recursive=1
;;
-t | --tap)
formatter='tap'
;;
-T | --timing)
flags+=('-T')
formatter_flags+=('-T')
;;
# this flag is now a no-op, as it is the parallel default
--parallel-preserve-environment) ;;
--no-parallelize-across-files)
flags+=("--no-parallelize-across-files")
;;
--no-parallelize-within-files)
flags+=("--no-parallelize-within-files")
;;
--no-tempdir-cleanup)
BATS_TEMPDIR_CLEANUP=''
;;
--tempdir) # for internal test consumption only!
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 [ -d "$output_dir" ]; then
if ! find "$output_dir" -mindepth 1 -exec false {} + 2>/dev/null; then
abort --no-print-usage "Directory '$output_dir' must be empty for --gather-test-outputs-in"
fi
elif ! mkdir "$output_dir" 2>/dev/null; then
abort --no-print-usage "Could not create '$output_dir' for --gather-test-outputs-in"
fi
flags+=(--gather-test-outputs-in "$output_dir")
;;
--setup-suite-file)
shift
setup_suite_file="$1"
;;
--code-quote-style)
shift
BATS_CODE_QUOTE_STYLE="$1"
;;
--filter-status)
shift
flags+=('--filter-status' "$1")
;;
--filter-tags)
shift
flags+=('--filter-tags' "$1")
;;
--line-reference-format)
shift
BATS_LINE_REFERENCE_FORMAT=$1
;;
-*)
abort "Bad command line option '$1'"
;;
*)
arguments+=("$1")
;;
esac
shift
done
if [[ ! $BATS_LINE_REFERENCE_FORMAT =~ (custom|comma_line|colon|uri) ]]; then
abort "Invalid BATS_LINE_REFERENCE_FORMAT '$BATS_LINE_REFERENCE_FORMAT' (e.g. via --line-reference-format)"
fi
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
export BATS_WARNING_FILE="${BATS_RUN_TMPDIR}/warnings.log"
bats_exit_trap() {
if [[ -s "$BATS_WARNING_FILE" ]]; then
local pre_cat='' post_cat=''
if [[ $formatter == pretty ]]; then
pre_cat=$'\x1B[31m'
post_cat=$'\x1B[0m'
fi
printf "\nThe following warnings were encountered during tests:\n%s" "$pre_cat"
cat "$BATS_WARNING_FILE"
printf "%s" "$post_cat"
fi >&2
if [[ -n "$BATS_TEMPDIR_CLEANUP" ]]; then
rm -rf "$BATS_RUN_TMPDIR"
else
printf "BATS_RUN_TMPDIR: %s\n" "$BATS_RUN_TMPDIR" >&2
fi
}
trap bats_exit_trap EXIT
if [[ "$formatter" != "tap" ]]; then
flags+=('-x')
fi
if [[ -n "$report_formatter" && "$report_formatter" != "tap" ]]; then
flags+=('-x')
fi
if [[ "$formatter" == "junit" ]]; then
flags+=('-T')
formatter_flags+=('--base-path' "${arguments[0]}")
fi
if [[ "$report_formatter" == "junit" ]]; then
flags+=('-T')
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"
fi
bats_check_formatter() { # <formatter-path>
local -r formatter="$1"
if [[ ! -f "$formatter" ]]; then
printf "ERROR: Formatter '%s' is not readable!\n" "$formatter"
exit 1
elif [[ ! -x "$formatter" ]]; then
printf "ERROR: Formatter '%s' is not executable!\n" "$formatter"
exit 1
fi
}
if [[ $formatter == /* ]]; then # absolute paths are direct references to formatters
bats_check_formatter "$formatter"
interpolated_formatter="$formatter"
else
interpolated_formatter="bats-format-${formatter}"
fi
if [[ "${#arguments[@]}" -eq 0 ]]; then
abort 'Must specify at least one <test>'
fi
if [[ -n "$report_formatter" ]]; then
if [[ ! -w "${BATS_REPORT_OUTPUT_DIR}" ]]; then
abort "Output path ${BATS_REPORT_OUTPUT_DIR} is not writeable"
fi
# only set BATS_REPORT_FILENAME if none was given
if [[ -z "${BATS_REPORT_FILENAME:-}" ]]; then
case "$report_formatter" in
tap | tap13)
BATS_REPORT_FILENAME="report.tap"
;;
junit)
BATS_REPORT_FILENAME="report.xml"
;;
*)
BATS_REPORT_FILENAME="report.log"
;;
esac
fi
fi
if [[ $report_formatter == /* ]]; then # absolute paths are direct references to formatters
bats_check_formatter "$report_formatter"
interpolated_report_formatter="${report_formatter}"
else
interpolated_report_formatter="bats-format-${report_formatter}"
fi
if [[ "${BATS_CODE_QUOTE_STYLE-BATS_CODE_QUOTE_STYLE_UNSET}" == BATS_CODE_QUOTE_STYLE_UNSET ]]; then
BATS_CODE_QUOTE_STYLE="\`'"
fi
case "${BATS_CODE_QUOTE_STYLE}" in
??)
BATS_BEGIN_CODE_QUOTE="${BATS_CODE_QUOTE_STYLE::1}"
BATS_END_CODE_QUOTE="${BATS_CODE_QUOTE_STYLE:1:1}"
export BATS_BEGIN_CODE_QUOTE BATS_END_CODE_QUOTE
;;
custom)
if [[ ${BATS_BEGIN_CODE_QUOTE-BATS_BEGIN_CODE_QUOTE_UNSET} == BATS_BEGIN_CODE_QUOTE_UNSET ||
${BATS_END_CODE_QUOTE-BATS_BEGIN_CODE_QUOTE_UNSET} == BATS_BEGIN_CODE_QUOTE_UNSET ]]; then
printf "ERROR: BATS_CODE_QUOTE_STYLE=custom requires BATS_BEGIN_CODE_QUOTE and BATS_END_CODE_QUOTE to be set\n" >&2
exit 1
fi
;;
*)
printf "ERROR: Unknown BATS_CODE_QUOTE_STYLE: %s\n" "$BATS_CODE_QUOTE_STYLE" >&2
exit 1
;;
esac
if [[ -n "$setup_suite_file" && ! -f "$setup_suite_file" ]]; then
abort "--setup-suite-file $setup_suite_file does not exist!"
fi
filenames=()
for filename in "${arguments[@]}"; do
expand_path "$filename" 'filename'
if [[ -z "$setup_suite_file" ]]; then
if [[ -d "$filename" ]]; then
dirname="$filename"
else
dirname="${filename%/*}"
fi
potential_setup_suite_file="$dirname/setup_suite.bash"
if [[ -e "$potential_setup_suite_file" ]]; then
setup_suite_file="$potential_setup_suite_file"
fi
fi
if [[ -d "$filename" ]]; then
shopt -s nullglob
if [[ "$recursive" -eq 1 ]]; then
while IFS= read -r -d $'\0' file; do
filenames+=("$file")
done < <(find -L "$filename" -type f -name "*.${BATS_FILE_EXTENSION:-bats}" -print0 | sort -z)
else
for suite_filename in "$filename"/*."${BATS_FILE_EXTENSION:-bats}"; do
filenames+=("$suite_filename")
done
fi
shopt -u nullglob
else
filenames+=("$filename")
fi
done
if [[ -n "$setup_suite_file" ]]; then
flags+=("--setup-suite-file" "$setup_suite_file")
fi
# 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
# pipe stdin into command and to stdout
# pipe command stdout to file
bats_tee() { # <output-file> <command...>
local output_file=$1 status=0
shift
exec 3<&1 # use FD3 to get around pipe
tee >(cat >&3) | "$@" >"$output_file" || status=$?
if (( status != 0 )); then
printf "ERROR: command \`%s\` failed with status %d\n" "$*" "$status" >&2
fi
return $status
}
if [[ -n "$report_formatter" ]]; then
exec bats-exec-suite "${flags[@]}" "${filenames[@]}" |
bats_tee "${BATS_REPORT_OUTPUT_DIR}/${BATS_REPORT_FILENAME}" "$interpolated_report_formatter" "${report_formatter_flags[@]}" |
bats_test_count_validator |
"$interpolated_formatter" "${formatter_flags[@]}"
else
exec bats-exec-suite "${flags[@]}" "${filenames[@]}" |
bats_test_count_validator |
"$interpolated_formatter" "${formatter_flags[@]}"
fi

383
libexec/bats-core/bats-exec-file Executable file
View File

@ -0,0 +1,383 @@
#!/usr/bin/env bash
set -eET
flags=('--dummy-flag')
num_jobs=${BATS_NUMBER_OF_PARALLEL_JOBS:-1}
extended_syntax=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
declare -r BATS_RETRY_RETURN_CODE=126
export BATS_TEST_RETRIES=0 # no retries by default
while [[ "$#" -ne 0 ]]; do
case "$1" in
-j)
shift
num_jobs="$1"
;;
-T)
flags+=('-T')
;;
-x)
flags+=('-x')
extended_syntax=1
;;
--no-parallelize-within-files)
# use singular to allow for users to override in file
BATS_NO_PARALLELIZE_WITHIN_FILE=1
;;
--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
;;
esac
shift
done
filename="$1"
TESTS_FILE="$2"
if [[ ! -f "$filename" ]]; then
printf 'Testfile "%s" not found\n' "$filename" >&2
exit 1
fi
export BATS_TEST_FILENAME="$filename"
# shellcheck source=lib/bats-core/preprocessing.bash
# shellcheck disable=SC2153
source "$BATS_ROOT/lib/bats-core/preprocessing.bash"
bats_run_setup_file() {
# shellcheck source=lib/bats-core/tracing.bash
# shellcheck disable=SC2153
source "$BATS_ROOT/lib/bats-core/tracing.bash"
# shellcheck source=lib/bats-core/test_functions.bash
# shellcheck disable=SC2153
source "$BATS_ROOT/lib/bats-core/test_functions.bash"
exec 3<&1
# these are defined only to avoid errors when referencing undefined variables down the line
# shellcheck disable=2034
BATS_TEST_NAME= # used in tracing.bash
# 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
touch "$BATS_OUT"
bats_setup_tracing
trap 'bats_file_teardown_trap' EXIT
local status=0
# get the setup_file/teardown_file functions for this file (if it has them)
# shellcheck disable=SC1090
source "$BATS_TEST_SOURCE"
BATS_SOURCE_FILE_COMPLETED=1
setup_file >>"$BATS_OUT" 2>&1
BATS_SETUP_FILE_COMPLETED=1
}
bats_run_teardown_file() {
local bats_teardown_file_status=0
# avoid running the therdown trap due to errors in teardown_file
trap 'bats_file_exit_trap' EXIT
# rely on bats_error_trap to catch failures
teardown_file >>"$BATS_OUT" 2>&1 || bats_teardown_file_status=$?
if ((bats_teardown_file_status == 0)); then
BATS_TEARDOWN_FILE_COMPLETED=1
elif [[ -n "${BATS_SETUP_FILE_COMPLETED:-}" ]]; then
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1
BATS_ERROR_STATUS=$bats_teardown_file_status
return $BATS_ERROR_STATUS
fi
}
# shellcheck disable=SC2317
bats_file_teardown_trap() {
bats_run_teardown_file
bats_file_exit_trap in-teardown_trap
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
# shellcheck disable=SC2317
bats_file_exit_trap() {
local -r last_return_code=$?
if [[ ${1:-} != in-teardown_trap ]]; then
BATS_ERROR_STATUS=$last_return_code
fi
trap - ERR EXIT
local failure_reason
local -i failure_test_index=$((BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE + 1))
if [[ -n "${BATS_TEST_SKIPPED-}" ]]; then
export BATS_TEST_SKIPPED # indicate to exec-test that it should skip
# print skip message for each test (use this to avoid reimplementing filtering)
bats_run_tests 1<&3 # restore original stdout (this is running in setup_file's redirection to BATS_OUT)
bats_exec_file_status=0 # this should not lead to errors
elif [[ -z "$BATS_SETUP_FILE_COMPLETED" || -z "$BATS_TEARDOWN_FILE_COMPLETED" ]]; then
if [[ -z "$BATS_SETUP_FILE_COMPLETED" ]]; then
failure_reason='setup_file'
elif [[ -z "$BATS_TEARDOWN_FILE_COMPLETED" ]]; then
failure_reason='teardown_file'
failure_test_index=$((BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE + ${#tests_to_run[@]} + 1))
elif [[ -z "$BATS_SOURCE_FILE_COMPLETED" ]]; then
failure_reason='source'
else
failure_reason='unknown internal'
fi
printf "not ok %d %s\n" "$failure_test_index" "$failure_reason failed" >&3
local stack_trace
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}" >&3
bats_print_failed_command "${stack_trace[@]}" >&3
bats_prefix_lines_for_tap_output <"$BATS_OUT" | bats_replace_filename >&3
rm -rf "$BATS_OUT"
bats_exec_file_status=1
fi
# setup_file not executed but defined in this test file? -> might be defined in the wrong file
if [[ -z "${BATS_SETUP_SUITE_COMPLETED-}" ]] && declare -F setup_suite >/dev/null; then
bats_generate_warning 3 --no-stacktrace "$BATS_TEST_FILENAME"
fi
exit "$bats_exec_file_status"
}
function setup_file() {
return 0
}
function teardown_file() {
return 0
}
bats_forward_output_of_parallel_test() {
local test_number_in_suite=$1
local status=0
wait "$(cat "$output_folder/$test_number_in_suite/pid")" || status=1
cat "$output_folder/$test_number_in_suite/stdout"
cat "$output_folder/$test_number_in_suite/stderr" >&2
return $status
}
bats_is_next_parallel_test_finished() {
local PID
# get the pid of the next potentially finished test
PID=$(cat "$output_folder/$((test_number_in_suite_of_last_finished_test + 1))/pid")
# try to send a signal to this process
# if it fails, the process exited,
# if it succeeds, the process is still running
if kill -0 "$PID" 2>/dev/null; then
return 1
fi
}
# prints output from all tests in the order they were started
# $1 == "blocking": wait for a test to finish before printing
# != "blocking": abort printing, when a test has not finished
bats_forward_output_for_parallel_tests() {
local status=0
# was the next test already started?
while ((test_number_in_suite_of_last_finished_test + 1 <= test_number_in_suite)); do
# if we are okay with waiting or if the test has already been finished
if [[ "$1" == "blocking" ]] || bats_is_next_parallel_test_finished; then
((++test_number_in_suite_of_last_finished_test))
bats_forward_output_of_parallel_test "$test_number_in_suite_of_last_finished_test" || status=$?
else
# non-blocking and the process has not finished -> abort the printing
break
fi
done
return $status
}
bats_run_test_with_retries() { # <args>
local status=0
local should_try_again=1 try_number
for ((try_number = 1; should_try_again; ++try_number)); do
if "$BATS_LIBEXEC/bats-exec-test" "$@" "$try_number"; then
should_try_again=0
else
status=$?
if ((status == BATS_RETRY_RETURN_CODE)); then
should_try_again=1
status=0 # this is not the last try -> reset status
else
should_try_again=0
bats_exec_file_status=$status
fi
fi
done
return $status
}
bats_run_tests_in_parallel() {
local output_folder="$BATS_RUN_TMPDIR/parallel_output"
local status=0
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="$BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE" # stores which test was printed last
local test_number_in_file=0 test_number_in_suite=$BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE
for test_name in "${tests_to_run[@]}"; do
# Only handle non-empty lines
if [[ $test_name ]]; then
((++test_number_in_suite))
((++test_number_in_file))
mkdir -p "$output_folder/$test_number_in_suite"
bats_semaphore_run "$output_folder/$test_number_in_suite" \
bats_run_test_with_retries "${flags[@]}" "$filename" "$test_name" "$test_number_in_suite" "$test_number_in_file" \
>"$output_folder/$test_number_in_suite/pid"
fi
# print results early to get interactive feedback
bats_forward_output_for_parallel_tests non-blocking || status=1 # ignore if we did not finish yet
done
bats_forward_output_for_parallel_tests blocking || status=1
return $status
}
bats_read_tests_list_file() {
local line_number=0
tests_to_run=()
# the global test number must be visible to traps -> not local
local test_number_in_suite=''
while read -r test_line; do
# check if the line begins with filename
# filename might contain some hard to parse characters,
# use simple string operations to work around that issue
if [[ "$filename" == "${test_line::${#filename}}" ]]; then
# get the rest of the line without the separator \t
test_name=${test_line:$((1 + ${#filename}))}
tests_to_run+=("$test_name")
# save the first test's number for later iteration
# this assumes that tests for a file are stored consecutive in the file!
if [[ -z "$test_number_in_suite" ]]; then
test_number_in_suite=$line_number
fi
fi
((++line_number))
done <"$TESTS_FILE"
BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE="$test_number_in_suite"
declare -ri BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE # mark readonly (cannot merge assignment, because value would be lost)
}
bats_run_tests() {
bats_exec_file_status=0
if [[ "${BATS_RUN_TESTS_SKIPPED-}" ]]; then
# shellcheck disable=SC2317
bats_test_begin() {
printf "ok %d %s # skip %s\n" "$test_number_in_suite" "$1" "$BATS_RUN_TESTS_SKIPPED_REASON" >&3
return 1
}
local test_number_in_suite=0
for test_name in "${tests_to_run[@]}"; do
((++test_number_in_suite))
eval "$test_name" || true
done
exit 0
fi
if [[ "$num_jobs" -lt 1 ]]; then
printf 'Invalid number of jobs: %s\n' "$num_jobs" >&2
exit 1
fi
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" || bats_exec_file_status=1
else
local test_number_in_suite=$BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE \
test_number_in_file=0
for test_name in "${tests_to_run[@]}"; do
if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
bats_exec_file_status=130 # bash's code for SIGINT exits
break
fi
# Only handle non-empty lines
if [[ -n ${test_name-} ]]; then
((++test_number_in_suite))
((++test_number_in_file))
bats_run_test_with_retries "${flags[@]}" "$filename" "$test_name" \
"$test_number_in_suite" "$test_number_in_file" || bats_exec_file_status=$?
fi
done
fi
}
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/${BATS_FILE_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_FILE_FIRST_TEST_NUMBER_IN_SUITE=0 # predeclare as Bash 3.2 does not support declare -g
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"
trap bats_interrupt_trap INT
bats_run_setup_file
# during tests, we don't want to get backtraces from this level
# just wait for the test to be interrupted and display their trace
trap 'BATS_INTERRUPTED=true' INT
bats_run_tests
trap bats_interrupt_trap INT
bats_run_teardown_file
exit $bats_exec_file_status

467
libexec/bats-core/bats-exec-suite Executable file
View File

@ -0,0 +1,467 @@
#!/usr/bin/env bash
set -e
count_only_flag=''
filter=''
num_jobs=${BATS_NUMBER_OF_PARALLEL_JOBS:-1}
bats_no_parallelize_across_files=${BATS_NO_PARALLELIZE_ACROSS_FILES-}
bats_no_parallelize_within_files=
filter_status=''
filter_tags_list=()
flags=('--dummy-flag') # add a dummy flag to prevent unset variable errors on empty array expansion in old bash versions
setup_suite_file=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS=
abort() {
printf 'Error: %s\n' "$1" >&2
exit 1
}
# shellcheck source=lib/bats-core/common.bash disable=SC2153
source "$BATS_ROOT/lib/bats-core/common.bash"
while [[ "$#" -ne 0 ]]; do
case "$1" in
-c)
count_only_flag=1
;;
-f)
shift
filter="$1"
;;
-j)
shift
num_jobs="$1"
flags+=('-j' "$num_jobs")
;;
-T)
flags+=('-T')
;;
-x)
flags+=('-x')
;;
--no-parallelize-across-files)
bats_no_parallelize_across_files=1
;;
--no-parallelize-within-files)
bats_no_parallelize_within_files=1
flags+=("--no-parallelize-within-files")
;;
--filter-status)
shift
filter_status="$1"
;;
--filter-tags)
shift
IFS=, read -ra tags <<<"$1" || true
if ((${#tags[@]} > 0)); then
for ((i = 0; i < ${#tags[@]}; ++i)); do
bats_trim "tags[$i]" "${tags[$i]}"
done
bats_sort sorted_tags "${tags[@]}"
IFS=, filter_tags_list+=("${sorted_tags[*]}")
else
filter_tags_list+=("")
fi
;;
--dummy-flag) ;;
--trace)
flags+=('--trace')
((++BATS_TRACE_LEVEL)) # avoid returning 0
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS=1
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
flags+=(--gather-test-outputs-in "$1")
;;
--setup-suite-file)
shift
setup_suite_file="$1"
;;
*)
break
;;
esac
shift
done
if [[ "$num_jobs" != 1 ]]; then
if ! type -p parallel >/dev/null && parallel --version &>/dev/null && [[ -z "$bats_no_parallelize_across_files" ]]; then
abort "Cannot execute \"${num_jobs}\" jobs without GNU parallel"
fi
# shellcheck source=lib/bats-core/semaphore.bash
source "${BATS_ROOT}/lib/bats-core/semaphore.bash"
bats_semaphore_setup
fi
# create a file that contains all (filtered) tests to run from all files
TESTS_LIST_FILE="${BATS_RUN_TMPDIR}/test_list_file.txt"
focus_mode=
bats_gather_tests() {
local line test_line tags
all_tests=()
for filename in "$@"; do
if [[ ! -f "$filename" ]]; then
abort "Test file \"${filename}\" does not exist"
fi
test_names=()
test_dupes=()
while read -r line; do
if [[ ! "$line" =~ ^bats_test_function\ ]]; then
continue
fi
line="${line%$'\r'}"
line="${line#* }"
TAG_REGEX="--tags '(.*)' (.*)"
if [[ "$line" =~ $TAG_REGEX ]]; then
IFS=, read -ra tags <<<"${BASH_REMATCH[1]}" || true
line="${BASH_REMATCH[2]}"
else
tags=()
fi
# is this test focused?
if bats_all_in tags 'bats:focus'; then
if [[ $focus_mode == 1 ]]; then
# focused tests in focus mode should just be registered
:
else
# the current test enables focus mode ...
focus_mode=1
# ... -> remove previously found, unfocused tests
all_tests=()
: > "$TESTS_LIST_FILE"
fi
elif [[ $focus_mode == 1 ]]; then
# the current test is not focused but focus mode is enabled -> filter out
continue
# no else -> unfocused tests outside focus mode should just be registered
fi
if [[ ${#filter_tags_list[@]} -gt 0 ]]; then
local match=
for filter_tags in "${filter_tags_list[@]}"; do
# empty search tags only match empty test tags!
if [[ -z "$filter_tags" ]]; then
if [[ ${#tags[@]} -eq 0 ]]; then
match=1
break
fi
continue
fi
local -a positive_filter_tags=() negative_filter_tags=()
IFS=, read -ra filter_tags <<<"$filter_tags" || true
for filter_tag in "${filter_tags[@]}"; do
if [[ $filter_tag == !* ]]; then
bats_trim filter_tag "${filter_tag#!}"
negative_filter_tags+=("${filter_tag}")
else
positive_filter_tags+=("${filter_tag}")
fi
done
if bats_append_arrays_as_args positive_filter_tags -- bats_all_in tags &&
! bats_append_arrays_as_args negative_filter_tags -- bats_any_in tags; then
match=1
fi
done
if [[ -z "$match" ]]; then
continue
fi
fi
test_line=$(printf "%s\t%s" "$filename" "$line")
all_tests+=("$test_line")
printf "%s\n" "$test_line" >>"$TESTS_LIST_FILE"
# avoid unbound variable errors on empty array expansion with old bash versions
if [[ ${#test_names[@]} -gt 0 && " ${test_names[*]} " == *" $line "* ]]; then
test_dupes+=("$line")
continue
fi
test_names+=("$line")
done < <(BATS_TEST_FILTER="$filter" bats-preprocess "$filename")
if [[ "${#test_dupes[@]}" -ne 0 ]]; then
abort "Duplicate test name(s) in file \"${filename}\": ${test_dupes[*]}"
fi
done
test_count="${#all_tests[@]}"
}
TEST_ROOT=${1-}
TEST_ROOT=${TEST_ROOT%/*}
BATS_RUN_LOGS_DIRECTORY="$TEST_ROOT/.bats/run-logs"
if [[ ! -d "$BATS_RUN_LOGS_DIRECTORY" ]]; then
if [[ -n "$filter_status" ]]; then
printf "Error: --filter-status needs '%s/' to save failed tests. Please create this folder, add it to .gitignore and try again.\n" "$BATS_RUN_LOGS_DIRECTORY"
exit 1
else
BATS_RUN_LOGS_DIRECTORY=
fi
# discard via sink instead of having a conditional later
export BATS_RUNLOG_FILE='/dev/null'
else
# use UTC (-u) to avoid problems with TZ changes
BATS_RUNLOG_DATE=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
export BATS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/${BATS_RUNLOG_DATE}.log"
if [[ ! -w "$BATS_RUN_LOGS_DIRECTORY" ]]; then
printf "WARNING: Cannot write in %s. This run will not write a log!\n" "$BATS_RUN_LOGS_DIRECTORY" >&2
BATS_RUNLOG_FILE='/dev/null' # disable runlog file
fi
fi
bats_gather_tests "$@"
if [[ -n "$filter_status" ]]; then
case "$filter_status" in
failed)
bats_filter_test_by_status() { # <line>
! bats_binary_search "$1" "passed_tests"
}
;;
passed)
bats_filter_test_by_status() {
! bats_binary_search "$1" "failed_tests"
}
;;
missed)
bats_filter_test_by_status() {
! bats_binary_search "$1" "failed_tests" && ! bats_binary_search "$1" "passed_tests"
}
;;
*)
printf "Error: Unknown value '%s' for --filter-status. Valid values are 'failed' and 'missed'.\n" "$filter_status" >&2
exit 1
;;
esac
if IFS='' read -d $'\n' -r BATS_PREVIOUS_RUNLOG_FILE < <(ls -1r "$BATS_RUN_LOGS_DIRECTORY"); then
BATS_PREVIOUS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/$BATS_PREVIOUS_RUNLOG_FILE"
if [[ $BATS_PREVIOUS_RUNLOG_FILE == "$BATS_RUNLOG_FILE" ]]; then
count=$(find "$BATS_RUN_LOGS_DIRECTORY" -name "$BATS_RUNLOG_DATE*" | wc -l)
BATS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/${BATS_RUNLOG_DATE}-$count.log"
fi
failed_tests=()
passed_tests=()
# store tests that were already filtered out in the last run for the same filter reason
last_filtered_tests=()
i=0
while read -rd $'\n' line; do
((++i))
case "$line" in
"passed "*)
passed_tests+=("${line#passed }")
;;
"failed "*)
failed_tests+=("${line#failed }")
;;
"status-filtered $filter_status"*) # pick up tests that were filtered in the last round for the same status
last_filtered_tests+=("${line#status-filtered "$filter_status" }")
;;
"status-filtered "*) # ignore other status-filtered lines
;;
"#"*) # allow for comments
;;
*)
printf "Error: %s:%d: Invalid format: %s\n" "$BATS_PREVIOUS_RUNLOG_FILE" "$i" "$line" >&2
exit 1
;;
esac
done < <(sort "$BATS_PREVIOUS_RUNLOG_FILE")
filtered_tests=()
for line in "${all_tests[@]}"; do
if bats_filter_test_by_status "$line" && ! bats_binary_search "$line" last_filtered_tests; then
printf "%s\n" "$line"
filtered_tests+=("$line")
else
printf "status-filtered %s %s\n" "$filter_status" "$line" >>"$BATS_RUNLOG_FILE"
fi
done >"$TESTS_LIST_FILE"
# save filtered tests to exclude them again in next round
for test_line in "${last_filtered_tests[@]}"; do
printf "status-filtered %s %s\n" "$filter_status" "$test_line"
done >>"$BATS_RUNLOG_FILE"
test_count="${#filtered_tests[@]}"
if [[ ${#failed_tests[@]} -eq 0 && ${#filtered_tests[@]} -eq 0 ]]; then
printf "There where no failed tests in the last recorded run.\n" >&2
fi
else
printf "No recording of previous runs found. Running all tests!\n" >&2
fi
fi
if [[ -n "$count_only_flag" ]]; then
printf '%d\n' "${test_count}"
exit
fi
if [[ -n "$bats_no_parallelize_across_files" ]] && [[ ! "$num_jobs" -gt 1 ]]; then
abort "The flag --no-parallelize-across-files requires at least --jobs 2"
fi
if [[ -n "$bats_no_parallelize_within_files" ]] && [[ ! "$num_jobs" -gt 1 ]]; then
abort "The flag --no-parallelize-across-files requires at least --jobs 2"
fi
# only abort on the lowest levels
trap 'BATS_INTERRUPTED=true' INT
if [[ -n "$focus_mode" ]]; then
printf "WARNING: This test run only contains tests tagged \`bats:focus\`!\n"
fi
bats_exec_suite_status=0
printf '1..%d\n' "${test_count}"
# No point on continuing if there's no tests.
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.
# Thus, it suffices to bats-exec-file it once to run all repeated tests on it.
IFS=$'\n' read -d '' -r -a BATS_UNIQUE_TEST_FILENAMES < <(printf "%s\n" "$@" | nl | sort -k 2 | uniq -f 1 | sort -n | cut -f 2-) || true
# shellcheck source=lib/bats-core/tracing.bash
source "$BATS_ROOT/lib/bats-core/tracing.bash"
bats_setup_tracing
trap bats_suite_exit_trap EXIT
exec 3<&1
# shellcheck disable=SC2317
bats_suite_exit_trap() {
local print_bats_out="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS}"
if [[ -z "${BATS_SETUP_SUITE_COMPLETED}" || -z "${BATS_TEARDOWN_SUITE_COMPLETED}" ]]; then
if [[ -z "${BATS_SETUP_SUITE_COMPLETED}" ]]; then
printf "not ok 1 setup_suite\n"
elif [[ -z "${BATS_TEARDOWN_SUITE_COMPLETED}" ]]; then
printf "not ok %d teardown_suite\n" $((test_count + 1))
fi
local stack_trace
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}"
bats_print_failed_command "${stack_trace[@]}"
print_bats_out=1
bats_exec_suite_status=1
fi
if [[ -n "$print_bats_out" ]]; then
bats_prefix_lines_for_tap_output <"$BATS_OUT"
fi
if [[ ${BATS_INTERRUPTED-NOTSET} != NOTSET ]]; then
printf "\n# Received SIGINT, aborting ...\n\n"
fi
if [[ -d "$BATS_RUN_LOGS_DIRECTORY" && -n "${BATS_INTERRUPTED:-}" ]]; then
# aborting a test run with CTRL+C does not save the runlog file
[[ "$BATS_RUNLOG_FILE" != /dev/null ]] && rm "$BATS_RUNLOG_FILE"
fi
exit "$bats_exec_suite_status"
} >&3
bats_run_teardown_suite() {
local bats_teardown_suite_status=0
# avoid being called twice, in case this is not called through bats_teardown_suite_trap
# but from the end of file
trap bats_suite_exit_trap EXIT
BATS_TEARDOWN_SUITE_COMPLETED=
teardown_suite >>"$BATS_OUT" 2>&1 || bats_teardown_suite_status=$?
if ((bats_teardown_suite_status == 0)); then
BATS_TEARDOWN_SUITE_COMPLETED=1
elif [[ -n "${BATS_SETUP_SUITE_COMPLETED:-}" ]]; then
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1
BATS_ERROR_STATUS=$bats_teardown_suite_status
return $BATS_ERROR_STATUS
fi
}
# shellcheck disable=SC2317
bats_teardown_suite_trap() {
bats_run_teardown_suite
bats_suite_exit_trap
}
teardown_suite() {
:
}
trap bats_teardown_suite_trap EXIT
BATS_OUT="$BATS_RUN_TMPDIR/suite.out"
if [[ -n "$setup_suite_file" ]]; then
setup_suite() {
printf "%s does not define \`setup_suite()\`\n" "$setup_suite_file" >&2
return 1
}
# shellcheck disable=SC2034 # will be used in the sourced file below
BATS_TEST_FILENAME="$setup_suite_file"
# shellcheck source=lib/bats-core/test_functions.bash
source "$BATS_ROOT/lib/bats-core/test_functions.bash"
# shellcheck disable=SC1090
source "$setup_suite_file"
set -eET
export BATS_SETUP_SUITE_COMPLETED=
setup_suite >>"$BATS_OUT" 2>&1
BATS_SETUP_SUITE_COMPLETED=1
set +ET
else
# prevent exit trap from printing an error because of incomplete setup_suite,
# when there was none to execute
BATS_SETUP_SUITE_COMPLETED=1
fi
if [[ "$num_jobs" -gt 1 ]] && [[ -z "$bats_no_parallelize_across_files" ]]; then
# run files in parallel to get the maximum pool of parallel tasks
# 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 || bats_exec_suite_status=1
else
for filename in "${BATS_UNIQUE_TEST_FILENAMES[@]}"; do
if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
bats_exec_suite_status=130 # bash's code for SIGINT exits
break
fi
bats-exec-file "${flags[@]}" "$filename" "${TESTS_LIST_FILE}" || bats_exec_suite_status=1
done
fi
set -eET
bats_run_teardown_suite
if [[ "$focus_mode" == 1 && $bats_exec_suite_status -eq 0 ]]; then
if [[ ${BATS_NO_FAIL_FOCUS_RUN-} == 1 ]]; then
printf "WARNING: This test run only contains tests tagged \`bats:focus\`!\n"
else
printf "Marking test run as failed due to \`bats:focus\` tag. (Set \`BATS_NO_FAIL_FOCUS_RUN=1\` to disable.)\n" >&2
bats_exec_suite_status=1
fi
fi
exit "$bats_exec_suite_status" # the actual exit code will be set by the exit trap using bats_exec_suite_status

345
libexec/bats-core/bats-exec-test Executable file
View File

@ -0,0 +1,345 @@
#!/usr/bin/env bash
set -eET
# Variables used in other scripts.
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:-}"
BATS_TEST_NAME_PREFIX="${BATS_TEST_NAME_PREFIX:-}"
while [[ "$#" -ne 0 ]]; do
case "$1" in
-T)
BATS_ENABLE_TIMING='-T'
;;
-x)
# shellcheck disable=SC2034
BATS_EXTENDED_SYNTAX='-x'
;;
--dummy-flag) ;;
--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
;;
esac
shift
done
export BATS_TEST_FILENAME="$1"
export BATS_TEST_NAME="$2"
export BATS_SUITE_TEST_NUMBER="$3"
export BATS_TEST_NUMBER="$4"
BATS_TEST_TRY_NUMBER="$5"
if [[ -z "$BATS_TEST_FILENAME" ]]; then
printf 'usage: bats-exec-test <filename>\n' >&2
exit 1
elif [[ ! -f "$BATS_TEST_FILENAME" ]]; then
printf 'bats: %s does not exist\n' "$BATS_TEST_FILENAME" >&2
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%d: %s\n' "$BATS_TEST_TRY_NUMBER" "$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"
# shellcheck source=lib/bats-core/tracing.bash disable=SC2153
source "$BATS_ROOT/lib/bats-core/tracing.bash"
bats_teardown_trap() {
bats_check_status_from_trap
local bats_teardown_trap_status=0
# `bats_teardown_trap` is always called with one parameter (BATS_TEARDOWN_STARTED)
# The second parameter is optional and corresponds for to the killer pid
# that will be forwarded to the exit trap to kill it if necesary
local killer_pid=${2:-}
# mark the start of this function to distinguish where skip is called
# parameter 1 will signify the reason why this function was called
# this is used to identify when this is called as exit trap function
BATS_TEARDOWN_STARTED=${1:-1}
teardown >>"$BATS_OUT" 2>&1 || bats_teardown_trap_status="$?"
if [[ $bats_teardown_trap_status -eq 0 ]]; then
BATS_TEARDOWN_COMPLETED=1
elif [[ -n "$BATS_TEST_COMPLETED" ]]; then
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1
BATS_ERROR_STATUS="$bats_teardown_trap_status"
fi
bats_exit_trap "$killer_pid"
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
bats_exit_trap() {
local status
local exit_metadata=''
local killer_pid=${1:-}
trap - ERR EXIT
if [[ -n "${BATS_TEST_TIMEOUT:-}" ]]; then
# Kill the watchdog in the case of of kernel finished before the timeout
bats_abort_timeout_countdown "$killer_pid" || status=1
fi
if [[ -n "$BATS_TEST_SKIPPED" ]]; then
exit_metadata=' # skip'
if [[ "$BATS_TEST_SKIPPED" != '1' ]]; then
exit_metadata+=" $BATS_TEST_SKIPPED"
fi
elif [[ "${BATS_TIMED_OUT-NOTSET}" != NOTSET ]]; then
exit_metadata=" # timeout after ${BATS_TEST_TIMEOUT}s"
fi
BATS_TEST_TIME=''
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
BATS_TEST_TIME=" in "$(($(get_mills_since_epoch) - BATS_TEST_START_TIME))"ms"
fi
local print_bats_out="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS}"
local should_retry=''
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).
# Thanks to the `BATS_TEARDOWN_COMPLETED` signal, this will pinpoint such
# errors if they happen during `teardown()` when `bats_perform_test` calls
# `bats_teardown_trap` directly after the test itself passes.
#
# If instead the test fails, and the `teardown()` error happens while
# `bats_teardown_trap` runs as the EXIT trap, the test will fail with no
# output, since there's no way to reach the `bats_exit_trap` call.
BATS_ERROR_STATUS=1
fi
if bats_should_retry_test; then
should_retry=1
status=126 # signify retry
rm -r "$BATS_TEST_TMPDIR" # clean up for retry
else
printf 'not ok %d %s%s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}${BATS_TEST_DESCRIPTION}${BATS_TEST_TIME}" "$exit_metadata" >&3
local stack_trace
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}" >&3
bats_print_failed_command "${stack_trace[@]}" >&3
if [[ $BATS_PRINT_OUTPUT_ON_FAILURE ]]; then
if [[ -n "${output:-}" ]]; then
printf "Last output:\n%s\n" "$output"
fi
if [[ -n "${stderr:-}" ]]; then
printf "Last stderr: \n%s\n" "$stderr"
fi
fi >>"$BATS_OUT"
print_bats_out=1
status=1
local state=failed
fi
else
printf 'ok %d %s%s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}${BATS_TEST_DESCRIPTION}${BATS_TEST_TIME}" \
"$exit_metadata" >&3
status=0
local state=passed
fi
if [[ -z "$should_retry" ]]; then
printf "%s %s\t%s\n" "$state" "$BATS_TEST_FILENAME" "$BATS_TEST_NAME" >>"$BATS_RUNLOG_FILE"
if [[ $print_bats_out ]]; then
bats_prefix_lines_for_tap_output <"$BATS_OUT" | bats_replace_filename >&3
fi
fi
if [[ $BATS_GATHER_TEST_OUTPUTS_IN ]]; then
local try_suffix=
if [[ -n "$should_retry" ]]; then
try_suffix="-try$BATS_TEST_TRY_NUMBER"
fi
cp "$BATS_OUT" "$BATS_GATHER_TEST_OUTPUTS_IN/$BATS_SUITE_TEST_NUMBER$try_suffix-$BATS_TEST_DESCRIPTION.log"
fi
rm -f "$BATS_OUT"
exit "$status"
}
# Marks the test as failed due to timeout.
# The actual termination of subprocesses is done via pkill in the background
# process in bats_start_timeout_countdown
# shellcheck disable=SC2317
bats_timeout_trap() {
BATS_TIMED_OUT=1
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=
exit 1
}
bats_get_child_processes_of() { # <parent-pid>
local -ri parent_pid=${1?}
{
read -ra header
local pid_col ppid_col
for ((i = 0; i < ${#header[@]}; ++i)); do
if [[ ${header[$i]} == "PID" ]]; then
pid_col=$i
fi
if [[ ${header[$i]} == "PPID" ]]; then
ppid_col=$i
fi
done
while read -ra row; do
if ((${row[$ppid_col]} == parent_pid)); then
printf "%d\n" "${row[$pid_col]}"
fi
done
} < <(ps -ef "$parent_pid")
}
bats_kill_childprocesses_of() { # <parent-pid>
local -ir parent_pid="${1?}"
if command -v pkill; then
pkill -P "$parent_pid"
else
# kill in reverse order (latest first)
while read -r pid; do
kill "$pid"
done < <(bats_get_child_processes_of "$parent_pid" | sort -r)
fi >/dev/null
}
# sets a timeout for this process
#
# using SIGABRT for interprocess communication.
# Ruled out:
# USR1/2 - not available on Windows
# SIGALRM - interferes with sleep:
# "sleep(3) may be implemented using SIGALRM; mixing calls to alarm()
# and sleep(3) is a bad idea." ~ https://linux.die.net/man/2/alarm
bats_start_timeout_countdown() { # <timeout>
local -ri timeout=$1
local -ri target_pid=$$
# shellcheck disable=SC2064
trap "bats_timeout_trap $target_pid" ABRT
if ! (command -v ps || command -v pkill) >/dev/null; then
printf "Error: Cannot execute timeout because neither pkill nor ps are available on this system!\n" >&2
exit 1
fi
# Start another process to kill the children of this process
(
sleep "$timeout" &
# sleep won't recieve signals, so we use wait below
# and kill sleep explicitly when signalled to do so
# shellcheck disable=SC2064
trap "kill $!; exit 0" ABRT
wait
if kill -ABRT "$target_pid"; then
# get rid of signal blocking child processes (like sleep)
bats_kill_childprocesses_of "$target_pid"
fi &>/dev/null
) &
}
bats_abort_timeout_countdown() {
# kill the countdown process, don't care if its still there
kill -ABRT "$1" &>/dev/null || true
}
get_mills_since_epoch() {
local ms_since_epoch
ms_since_epoch=$(date +%s%N)
if [[ "$ms_since_epoch" == *N || "${#ms_since_epoch}" -lt 19 ]]; then
ms_since_epoch=$(($(date +%s) * 1000))
else
ms_since_epoch=$((ms_since_epoch / 1000000))
fi
printf "%d\n" "$ms_since_epoch"
}
bats_perform_test() {
if ! declare -F "$BATS_TEST_NAME" &>/dev/null; then
local quoted_test_name
bats_quote_code quoted_test_name "$BATS_TEST_NAME"
printf "bats: unknown test name %s\n" "$quoted_test_name" >&2
exit 1
fi
# is this skipped from outside ?
if [[ -n "${BATS_TEST_SKIPPED-}" ]]; then
# forward skip (with message) by overriding setup
# shellcheck disable=SC2317
setup() {
skip "$BATS_TEST_SKIPPED"
}
fi
local BATS_killer_pid=''
if [[ -n "${BATS_TEST_TIMEOUT:-}" ]]; then
bats_start_timeout_countdown "$BATS_TEST_TIMEOUT"
BATS_killer_pid=$!
fi
BATS_TEST_COMPLETED=
BATS_TEST_SKIPPED=${BATS_TEST_SKIPPED-}
BATS_TEARDOWN_COMPLETED=
BATS_ERROR_STATUS=
bats_setup_tracing
# use parameter to mark this call as trap call
# shellcheck disable=SC2064
trap "bats_teardown_trap as-exit-trap $BATS_killer_pid" EXIT
BATS_TEST_START_TIME=$(get_mills_since_epoch)
"$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1 4>&1
BATS_TEST_COMPLETED=1
# shellcheck disable=SC2064
trap "bats_exit_trap $BATS_killer_pid" EXIT
bats_teardown_trap "" "$BATS_killer_pid" # pass empty parameter to signify call outside trap
}
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

View File

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

View File

@ -0,0 +1,250 @@
#!/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
normalize_base_path BASE_PATH "$1"
;;
esac
shift
done
init_suite() {
suite_test_exec_time=0
# since we have to print the suite header before its contents but we don't know the contents before the header,
# we have to buffer the contents
_suite_buffer=""
test_result_state="" # declare for the first flush, when no test has been encountered
}
_buffer_log=
init_file() {
file_count=0
file_failures=0
file_skipped=0
file_exec_time=0
test_exec_time=0
name=""
_buffer=""
_buffer_log=""
_system_out_log=""
test_result_state="" # mark that no test has run in this file so far
}
host() {
local hostname="${HOST:-}"
[[ -z "$hostname" ]] && hostname="${HOSTNAME:-}"
[[ -z "$hostname" ]] && hostname="$(uname -n)"
[[ -z "$hostname" ]] && hostname="$(hostname -f)"
echo "$hostname"
}
# convert $1 (time in milliseconds) to seconds
milliseconds_to_seconds() {
# we cannot rely on having bc for this calculation
full_seconds=$(($1 / 1000))
remaining_milliseconds=$(($1 % 1000))
if [[ $remaining_milliseconds -eq 0 ]]; then
printf "%d" "$full_seconds"
else
printf "%d.%03d" "$full_seconds" "$remaining_milliseconds"
fi
}
suite_header() {
printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<testsuites time=\"%s\">\n" "$(milliseconds_to_seconds "${suite_test_exec_time}")"
}
file_header() {
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S")
printf "<testsuite name=\"%s\" tests=\"%s\" failures=\"%s\" errors=\"0\" skipped=\"%s\" time=\"%s\" timestamp=\"%s\" hostname=\"%s\">\n" \
"$(xml_escape "${class}")" "${file_count}" "${file_failures}" "${file_skipped}" "$(milliseconds_to_seconds "${file_exec_time}")" "${timestamp}" "$(host)"
}
file_footer() {
printf "</testsuite>\n"
}
suite_footer() {
printf "</testsuites>\n"
}
print_test_case() {
if [[ "$test_result_state" == ok && -z "$_system_out_log" && -z "$_buffer_log" ]]; then
# pass and no output can be shortened
printf " <testcase classname=\"%s\" name=\"%s\" time=\"%s\" />\n" "$(xml_escape "${class}")" "$(xml_escape "${name}")" "$(milliseconds_to_seconds "${test_exec_time}")"
else
printf " <testcase classname=\"%s\" name=\"%s\" time=\"%s\">\n" "$(xml_escape "${class}")" "$(xml_escape "${name}")" "$(milliseconds_to_seconds "${test_exec_time}")"
if [[ -n "$_system_out_log" ]]; then
printf " <system-out>%s</system-out>\n" "$(xml_escape "${_system_out_log}")"
fi
if [[ -n "$_buffer_log" || "$test_result_state" == not_ok ]]; then
printf " <failure type=\"failure\">%s</failure>\n" "$(xml_escape "${_buffer_log}")"
fi
if [[ "$test_result_state" == skipped ]]; then
printf " <skipped>%s</skipped>\n" "$(xml_escape "$test_skip_message")"
fi
printf " </testcase>\n"
fi
}
xml_escape() {
output=${1//&/\&amp;}
output=${output//</\&lt;}
output=${output//>/\&gt;}
output=${output//'"'/\&quot;}
output=${output//\'/\&#39;}
local CONTROL_CHAR=$'\033'
output=${output//$CONTROL_CHAR/\&#27;}
printf "%s" "$output"
}
suite_buffer() {
local output
output="$(
"$@"
printf "x"
)" # use x marker to avoid losing trailing newlines
_suite_buffer="${_suite_buffer}${output%x}"
}
suite_flush() {
echo -n "${_suite_buffer}"
_suite_buffer=""
}
buffer() {
local output
output="$(
"$@"
printf "x"
)" # use x marker to avoid losing trailing newlines
_buffer="${_buffer}${output%x}"
}
flush() {
echo -n "${_buffer}"
_buffer=""
}
log() {
if [[ -n "$_buffer_log" ]]; then
_buffer_log="${_buffer_log}
$1"
else
_buffer_log="$1"
fi
}
flush_log() {
if [[ -n "$test_result_state" ]]; then
buffer print_test_case
fi
_buffer_log=""
_system_out_log=""
}
log_system_out() {
if [[ -n "$_system_out_log" ]]; then
_system_out_log="${_system_out_log}
$1"
else
_system_out_log="$1"
fi
}
finish_file() {
if [[ "${class-JUNIT_FORMATTER_NO_FILE_ENCOUNTERED}" != JUNIT_FORMATTER_NO_FILE_ENCOUNTERED ]]; then
file_header
printf "%s\n" "${_buffer}"
file_footer
fi
}
finish_suite() {
flush_log
suite_header
suite_flush
finish_file # must come after suite flush to not print the last file before the others
suite_footer
}
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
# set after flushing to avoid overriding name of test
name="$2"
}
bats_tap_stream_ok() { # <test index> <test name>
test_exec_time=${BATS_FORMATTER_TEST_DURATION:-0}
((file_count += 1))
test_result_state='ok'
file_exec_time="$((file_exec_time + test_exec_time))"
suite_test_exec_time=$((suite_test_exec_time + test_exec_time))
}
bats_tap_stream_skipped() { # <test index> <test name> <skip reason>
test_exec_time=${BATS_FORMATTER_TEST_DURATION:-0}
((file_count += 1))
((file_skipped += 1))
test_result_state='skipped'
test_exec_time=0
test_skip_message="$3"
}
bats_tap_stream_not_ok() { # <test index> <test name>
test_exec_time=${BATS_FORMATTER_TEST_DURATION:-0}
((file_count += 1))
((file_failures += 1))
test_result_state=not_ok
file_exec_time="$((file_exec_time + test_exec_time))"
suite_test_exec_time=$((suite_test_exec_time + test_exec_time))
}
bats_tap_stream_comment() { # <comment text without leading '# '> <scope>
local comment="$1" scope="$2"
case "$scope" in
begin)
# everything that happens between begin and [not] ok is FD3 output from the test
log_system_out "$comment"
;;
ok)
# non failed tests can produce FD3 output
log_system_out "$comment"
;;
*)
# everything else is considered error output
log "$1"
;;
esac
}
bats_tap_stream_suite() { # <file name>
flush_log
suite_buffer finish_file
init_file
class="${1/$BASE_PATH/}"
}
bats_tap_stream_unknown() { # <full line>
:
}
bats_parse_internal_extended_tap

View File

@ -0,0 +1,348 @@
#!/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
update_count_column_width() {
count_column_width=$((${#count} * 2 + 2))
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
# additional space for ' in %s sec'
count_column_width=$((count_column_width + ${#SECONDS} + 8))
fi
# also update dependent value
update_count_column_left
}
update_screen_width() {
screen_width="$(tput cols)"
# also update dependent value
update_count_column_left
}
update_count_column_left() {
count_column_left=$((screen_width - count_column_width))
}
# avoid unset variables
count=0
screen_width=80
update_count_column_width
update_screen_width
test_result=
trap update_screen_width WINCH
begin() {
test_result= # reset to avoid carrying over result state from previous test
line_backoff_count=0
go_to_column 0
update_count_column_width
buffer_with_truncation $((count_column_left - 1)) ' %s' "$name"
clear_to_end_of_line
go_to_column $count_column_left
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
buffer "%${#count}s/${count} in %s sec" "$index" "$SECONDS"
else
buffer "%${#count}s/${count}" "$index"
fi
go_to_column 1
}
finish_test() {
move_up $line_backoff_count
go_to_column 0
buffer "$@"
if [[ -n "${TIMEOUT-}" ]]; then
set_color 2
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
buffer ' [%s (timeout: %s)]' "$TIMING" "$TIMEOUT"
else
buffer ' [timeout: %s]' "$TIMEOUT"
fi
else
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
set_color 2
buffer ' [%s]' "$TIMING"
fi
fi
advance
move_down $((line_backoff_count - 1))
}
pass() {
local TIMING="${1:-}"
finish_test ' ✓ %s' "$name"
test_result=pass
}
skip() {
local reason="$1" TIMING="${2:-}"
if [[ -n "$reason" ]]; then
reason=": $reason"
fi
finish_test ' - %s (skipped%s)' "$name" "$reason"
test_result=skip
}
fail() {
local TIMING="${1:-}"
set_color 1 bold
finish_test ' ✗ %s' "$name"
test_result=fail
}
timeout() {
local TIMING="${1:-}"
set_color 3 bold
TIMEOUT="${2:-}" finish_test ' ✗ %s' "$name"
test_result=timeout
}
log() {
case ${test_result} in
pass)
clear_color
;;
fail)
set_color 1
;;
timeout)
set_color 3
;;
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'
fi
buffer ', %d failure' "$failures"
if [[ "$failures" -ne 1 ]]; then
buffer 's'
fi
if [[ "$skipped" -gt 0 ]]; then
buffer ', %d skipped' "$skipped"
fi
if ((timed_out > 0)); then
buffer ', %d timed out' "$timed_out"
fi
not_run=$((count - passed - failures - skipped - timed_out))
if [[ "$not_run" -gt 0 ]]; then
buffer ', %d not run' "$not_run"
fi
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
buffer " in $SECONDS seconds"
fi
buffer '\n'
clear_color
}
buffer_with_truncation() {
local width="$1"
shift
local string
# shellcheck disable=SC2059
printf -v 'string' -- "$@"
if [[ "${#string}" -gt "$width" ]]; then
buffer '%s...' "${string:0:$((width - 4))}"
else
buffer '%s' "$string"
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))
}
clear_to_end_of_line() {
buffer '\x1B[K'
}
advance() {
clear_to_end_of_line
buffer '\n'
clear_color
}
set_color() {
local color="$1"
local weight=22
if [[ "${2:-}" == 'bold' ]]; then
weight=1
fi
buffer '\x1B[%d;%dm' "$((30 + color))" "$weight"
}
clear_color() {
buffer '\x1B[0m'
}
_buffer=
buffer() {
local content
# shellcheck disable=SC2059
printf -v content -- "$@"
_buffer+="$content"
}
prefix_buffer_with() {
local old_buffer="$_buffer"
_buffer=''
"$@"
_buffer="$_buffer$old_buffer"
}
flush() {
printf '%s' "$_buffer"
_buffer=
}
finish() {
flush
printf '\n'
}
trap finish EXIT
trap '' INT
bats_tap_stream_plan() {
count="$1"
index=0
passed=0
failures=0
skipped=0
timed_out=0
name=
update_count_column_width
}
bats_tap_stream_begin() {
index="$1"
name="$2"
begin
flush
}
bats_tap_stream_ok() {
index="$1"
name="$2"
((++passed))
pass "${BATS_FORMATTER_TEST_DURATION:-}"
}
bats_tap_stream_skipped() {
index="$1"
name="$2"
((++skipped))
skip "$3" "${BATS_FORMATTER_TEST_DURATION:-}"
}
bats_tap_stream_not_ok() {
index="$1"
name="$2"
if [[ ${BATS_FORMATTER_TEST_TIMEOUT-x} != x ]]; then
timeout "${BATS_FORMATTER_TEST_DURATION:-}" "${BATS_FORMATTER_TEST_TIMEOUT}s"
((++timed_out))
else
fail "${BATS_FORMATTER_TEST_DURATION:-}"
((++failures))
fi
}
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"
line_backoff_count=0
index=
# indicate filename for failures
local file_name="${1#"$BASE_PATH"}"
name="File $file_name"
set_color 4 bold
buffer "%s\n" "$file_name"
clear_color
}
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
summary

View File

@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -e
trap '' INT
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"
bats_tap_stream_plan() {
printf "1..%d\n" "$1"
}
bats_tap_stream_begin() { #<test index> <test name>
:
}
bats_tap_stream_ok() { # [<test index> <test name>
printf "ok %d %s" "$1" "$2"
if [[ "${BATS_FORMATTER_TEST_DURATION-x}" != x ]]; then
printf " # in %d ms" "$BATS_FORMATTER_TEST_DURATION"
fi
printf "\n"
}
bats_tap_stream_not_ok() { # <test index> <test name>
printf "not ok %d %s" "$1" "$2"
if [[ "${BATS_FORMATTER_TEST_DURATION-x}" != x ]]; then
printf " # in %d ms" "$BATS_FORMATTER_TEST_DURATION"
fi
if [[ "${BATS_FORMATTER_TEST_TIMEOUT-x}" != x ]]; then
printf " # timeout after %d s" "${BATS_FORMATTER_TEST_TIMEOUT}"
fi
printf "\n"
}
bats_tap_stream_skipped() { # <test index> <test name> <reason>
if [[ $# -eq 3 ]]; then
printf "ok %d %s # skip %s\n" "$1" "$2" "$3"
else
printf "ok %d %s # skip\n" "$1" "$2"
fi
}
bats_tap_stream_comment() { # <comment text without leading '# '>
printf "# %s\n" "$1"
}
bats_tap_stream_suite() { # <file name>
:
}
bats_tap_stream_unknown() { # <full line>
printf "%s\n" "$1"
}
bats_parse_internal_extended_tap

View File

@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -e
yaml_block_open=''
add_yaml_entry() {
if [[ -z "$yaml_block_open" ]]; then
printf " ---\n"
fi
printf " %s: %s\n" "$1" "$2"
yaml_block_open=1
}
close_previous_yaml_block() {
if [[ -n "$yaml_block_open" ]]; then
printf " ...\n"
yaml_block_open=''
fi
}
trap '' INT
number_of_printed_log_lines_for_this_test_so_far=0
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"
bats_tap_stream_plan() {
printf "TAP version 13\n"
printf "1..%d\n" "$1"
}
bats_tap_stream_begin() { #<test index> <test name>
:
}
bats_tap_stream_ok() { # <test index> <test name>
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
printf "ok %d %s\n" "$1" "$2"
if [[ "${BATS_FORMATTER_TEST_DURATION-x}" != x ]]; then
add_yaml_entry "duration_ms" "${BATS_FORMATTER_TEST_DURATION}"
fi
}
pass_on_optional_data() {
if [[ "${BATS_FORMATTER_TEST_DURATION-x}" != x ]]; then
add_yaml_entry "duration_ms" "${BATS_FORMATTER_TEST_DURATION}"
fi
if [[ "${BATS_FORMATTER_TEST_TIMEOUT-x}" != x ]]; then
add_yaml_entry "timeout_sec" "${BATS_FORMATTER_TEST_TIMEOUT}"
fi
}
bats_tap_stream_not_ok() { # <test index> <test name>
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
printf "not ok %d %s\n" "$1" "$2"
pass_on_optional_data
}
bats_tap_stream_skipped() { # <test index> <test name> <reason>
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
printf "not ok %d %s # SKIP %s\n" "$1" "$2" "$3"
pass_on_optional_data
}
bats_tap_stream_comment() { # <comment text without leading '# '>
if [[ $number_of_printed_log_lines_for_this_test_so_far -eq 0 ]]; then
add_yaml_entry "message" "|" # use a multiline string for this entry
fi
((++number_of_printed_log_lines_for_this_test_so_far))
printf " %s\n" "$1"
}
bats_tap_stream_suite() { # <file name>
:
}
bats_tap_stream_unknown() { # <full line>
:
}
bats_parse_internal_extended_tap
# close the final block if there was one
close_previous_yaml_block

119
libexec/bats-core/bats-preprocess Executable file
View File

@ -0,0 +1,119 @@
#!/usr/bin/env bash
set -e
bats_encode_test_name() {
local name="$1"
local result='test_'
local hex_code
if [[ ! "$name" =~ [^[:alnum:]\ _-] ]]; then
name="${name//_/-5f}"
name="${name//-/-2d}"
name="${name// /_}"
result+="$name"
else
local length="${#name}"
local char i
for ((i = 0; i < length; i++)); do
char="${name:$i:1}"
if [[ "$char" == ' ' ]]; then
result+='_'
elif [[ "$char" =~ [[:alnum:]] ]]; then
result+="$char"
else
printf -v 'hex_code' -- '-%02x' \'"$char"
result+="$hex_code"
fi
done
fi
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:]]*\$"
BATS_COMMENT_COMMAND_PATTERN="^[[:blank:]]*#[[:blank:]]*bats[[:blank:]]+(.*)$"
BATS_VALID_TAG_PATTERN="[-_:[:alnum:]]+"
BATS_VALID_TAGS_PATTERN="^ *($BATS_VALID_TAG_PATTERN)?( *, *$BATS_VALID_TAG_PATTERN)* *$"
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
extract_tags() { # <tag_type/return_var> <tags-string>
local -r tag_type=$1 tags_string=$2
local -a tags=()
if [[ $tags_string =~ $BATS_VALID_TAGS_PATTERN ]]; then
IFS=, read -ra tags <<<"$tags_string"
local -ri length=${#tags[@]}
for ((i = 0; i < length; ++i)); do
local element="tags[$i]"
bats_trim "$element" "${!element}" 2>/dev/null # printf on bash 3 will complain but work anyways
if [[ -z "${!element}" && -n "${CHECK_BATS_COMMENT_COMMANDS:-}" ]]; then
printf "%s:%d: Error: Invalid %s: '%s'. " "$test_file" "$line_number" "$tag_type" "$tags_string"
printf "Tags must not be empty. Please remove redundant commas!\n"
exit_code=1
fi
done
elif [[ -n "${CHECK_BATS_COMMENT_COMMANDS:-}" ]]; then
printf "%s:%d: Error: Invalid %s: '%s'. " "$test_file" "$line_number" "$tag_type" "$tags_string"
printf "Valid tags must match %s and be separated with comma (and optional spaces)\n" "$BATS_VALID_TAG_PATTERN"
exit_code=1
fi >&2
if ((${#tags[@]} > 0)); then
eval "$tag_type=(\"\${tags[@]}\")"
else
eval "$tag_type=()"
fi
}
test_file="$1"
tests=()
test_tags=()
# shellcheck disable=SC2034 # used in `bats_sort tags`/`extract_tags``
file_tags=()
line_number=0
exit_code=0
{
while IFS= read -r line; do
((++line_number))
line="${line//$'\r'/}"
if [[ "$line" =~ $BATS_TEST_PATTERN ]] || [[ "$line" =~ $BATS_TEST_PATTERN_COMMENT ]]; then
name="${BASH_REMATCH[1]#[\'\"]}"
name="${name%[\'\"]}"
body="${BASH_REMATCH[2]:-}"
bats_encode_test_name "$name" 'encoded_name'
printf '%s() { bats_test_begin "%s"; %s\n' "${encoded_name:?}" "$name" "$body" || :
bats_append_arrays_as_args \
test_tags file_tags \
-- bats_sort tags
if [[ -z "$BATS_TEST_FILTER" || "$name" =~ $BATS_TEST_FILTER ]]; then
IFS=,
tests+=("--tags '${tags[*]-}' $encoded_name")
fi
# shellcheck disable=SC2034 # used in `bats_sort tags`/`extract_tags`
test_tags=() # reset test tags for next test
else
if [[ "$line" =~ $BATS_COMMENT_COMMAND_PATTERN ]]; then
command=${BASH_REMATCH[1]}
case $command in
'test_tags='*)
extract_tags test_tags "${command#test_tags=}"
;;
'file_tags='*)
extract_tags file_tags "${command#file_tags=}"
;;
esac
fi
printf '%s\n' "$line"
fi
done
} <<<"$(<"$test_file")"$'\n'
for test_name in "${tests[@]}"; do
printf 'bats_test_function %s\n' "$test_name"
done
exit $exit_code

23
man/Makefile Normal file
View File

@ -0,0 +1,23 @@
# Makefile
#
# bats-core manpages
#
RONN := ronn -W
PAGES := bats.1 bats.7
ORG := bats-core
MANUAL := 'Bash Automated Testing System'
ISOFMT := $(shell date -I)
RM := rm -f
.PHONY: all clean
all: $(PAGES)
bats.1: bats.1.ronn
$(RONN) --date=$(ISOFMT) --manual=$(MANUAL) --organization=$(ORG) --roff $<
bats.7: bats.7.ronn
$(RONN) --date=$(ISOFMT) --manual=$(MANUAL) --organization=$(ORG) --roff $<
clean:
$(RM) $(PAGES)

5
man/README.md Normal file
View File

@ -0,0 +1,5 @@
Bats man pages are generated with [Ronn](http://rtomayko.github.io/ronn/).
After making changes to `bats.1.ronn` or `bats.7.ronn`, run `make` in
this directory to generate `bats.1` and `bats.7`. **Do not edit the
`bats.1` or `bats.7` files directly.**

143
man/bats.1 Normal file
View File

@ -0,0 +1,143 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "BATS" "1" "November 2022" "bats-core" "Bash Automated Testing System"
.SH "NAME"
\fBbats\fR \- Bash Automated Testing System
.SH "SYNOPSIS"
Usage: bats [OPTIONS] \fItests\fR bats [\-h | \-v]
.P
\fItests\fR is the path to a Bats test file, or the path to a directory containing Bats test files (ending with "\.bats")
.SH "DESCRIPTION"
Bats is a TAP\-compliant testing framework for Bash\. It provides a simple way to verify that the UNIX programs you write behave as expected\.
.P
A Bats test file is a Bash script with special syntax for defining test cases\. Under the hood, each test case is just a function with a description\.
.P
Test cases consist of standard shell commands\. Bats makes use of Bash\'s \fBerrexit\fR (\fBset \-e\fR) option when running test cases\. If every command in the test case exits with a \fB0\fR status code (success), the test passes\. In this way, each line is an assertion of truth\.
.P
See \fBbats\fR(7) for more information on writing Bats tests\.
.SH "RUNNING TESTS"
To run your tests, invoke the \fBbats\fR interpreter with a path to a test file\. The file\'s test cases are run sequentially and in isolation\. If all the test cases pass, \fBbats\fR exits with a \fB0\fR status code\. If there are any failures, \fBbats\fR exits with a \fB1\fR status code\.
.P
You can invoke the \fBbats\fR interpreter with multiple test file arguments, or with a path to a directory containing multiple \fB\.bats\fR files\. Bats will run each test file individually and aggregate the results\. If any test case fails, \fBbats\fR exits with a \fB1\fR status code\.
.SH "FILTERING TESTS"
There are multiple mechanisms to filter which tests to execute:
.IP "\[ci]" 4
\fB\-\-filter <regex>\fR to filter by test name
.IP "\[ci]" 4
\fB\-\-filter\-status <status>\fR to filter by the test\'s status in the last run
.IP "\[ci]" 4
\fB\-\-filter\-tags <tag\-list>\fR to filter by the tags of a test
.IP "" 0
.SH "\-\-FILTER\-TAGS <var>TAG\-LIST</var>"
Tags can be used for finegrained filtering of which tests to run via \fB\-\-filter\-tags\fR\. This accepts a comma separated list of tags\. Only tests that match all of these tags will be executed\. For example, \fBbats \-\-filter\-tags a,b,c\fR will pick up tests with tags \fBa,b,c\fR, but not tests that miss one or more of those tags\.
.P
Additionally, you can specify negative tags via \fBbats \-\-filter\-tags a,!b,c\fR, which now won\'t match tests with tags \fBa,b,c\fR, due to the \fBb\fR, but will select \fBa,c\fR\. To put it more formally, \fB\-\-filter\-tags\fR is a boolean conjunction\.
.P
To allow for more complex queries, you can specify multiple \fB\-\-filter\-tags\fR\. A test will be executed, if it matches at least one of them\. This means multiple \fB\-\-filter\-tags\fR form a boolean disjunction\.
.P
A query of \fB\-\-filter\-tags a,!b \-\-filter\-tags b,c\fR can be translated to: Execute only tests that (have tag a, but not tag b) or (have tag b and c)\.
.P
An empty tag list matches tests without tags\.
.SH "OPTIONS"
.TP
\fB\-c\fR, \fB\-\-count\fR
Count the number of test cases without running any tests
.TP
\fB\-\-code\-quote\-style <style>\fR
A two character string of code quote delimiters or \fBcustom\fR which requires setting \fB$BATS_BEGIN_CODE_QUOTE\fR and \fB$BATS_END_CODE_QUOTE\fR\. Can also be set via \fB$BATS_CODE_QUOTE_STYLE\fR\.
.TP
\fB\-f\fR, \fB\-\-filter <regex>\fR
Filter test cases by names matching the regular expression
.TP
\fB\-F\fR, \fB\-\-formatter <type>\fR
Switch between formatters: pretty (default), tap (default w/o term), tap13, junit, \fB/<absolute path to formatter>\fR
.TP
\fB\-\-filter\-status <status>\fR
Only run tests with the given status in the last completed (no CTRL+C/SIGINT) run\. Valid \fIstatus\fR values are: failed \- runs tests that failed or were not present in the last run missed \- runs tests that were not present in the last run
.TP
\fB\-\-filter\-tags <comma\-separated\-tag\-list>\fR
Only run tests that match all the tags in the list (\fB&&\fR)\. You can negate a tag via prepending \fB!\fR\. Specifying this flag multiple times allows for logical or (\fB||\fR): \fB\-\-filter\-tags A,B \-\-filter\-tags A,!C\fR matches tags \fB(A && B) || (A && !C)\fR
.TP
\fB\-\-gather\-test\-outputs\-in <directory>\fR
Gather the output of failing \fIand\fR passing tests as files in directory
.TP
\fB\-h\fR, \fB\-\-help\fR
Display this help message
.TP
\fB\-j\fR, \fB\-\-jobs <jobs>\fR
Number of parallel jobs (requires GNU parallel)
.TP
\fB\-\-no\-tempdir\-cleanup\fR
Preserve test output temporary directory
.TP
\fB\-\-no\-parallelize\-across\-files\fR
Serialize test file execution instead of running them in parallel (requires \-\-jobs >1)
.TP
\fB\-\-no\-parallelize\-within\-files\fR
Serialize test execution within files instead of running them in parallel (requires \-\-jobs >1)
.TP
\fB\-\-report\-formatter <type>\fR
Switch between reporters (same options as \-\-formatter)
.TP
\fB\-o\fR, \fB\-\-output <dir>\fR
Directory to write report files
.TP
\fB\-p\fR, \fB\-\-pretty\fR
Shorthand for "\-\-formatter pretty"
.TP
\fB\-\-print\-output\-on\-failure\fR
Automatically print the value of \fB$output\fR on failed tests
.TP
\fB\-r\fR, \fB\-\-recursive\fR
Include tests in subdirectories
.TP
\fB\-\-show\-output\-of\-passing\-tests\fR
Print output of passing tests
.TP
\fB\-t\fR, \fB\-\-tap\fR
Shorthand for "\-\-formatter tap"
.TP
\fB\-T\fR, \fB\-\-timing\fR
Add timing information to tests
.TP
\fB\-x\fR, \fB\-\-trace\fR
Print test commands as they are executed (like \fBset \-x\fR)
.TP
\fB\-\-verbose\-run\fR
Make \fBrun\fR print \fB$output\fR by default
.TP
\fB\-v\fR, \fB\-\-version\fR
Display the version number
.SH "OUTPUT"
When you run Bats from a terminal, you\'ll see output as each test is performed, with a check\-mark next to the test\'s name if it passes or an "X" if it fails\.
.IP "" 4
.nf
$ bats addition\.bats
✓ addition using bc
✓ addition using dc
2 tests, 0 failures
.fi
.IP "" 0
.P
If Bats is not connected to a terminal\-\-in other words, if you run it from a continuous integration system or redirect its output to a file\-\-the results are displayed in human\-readable, machine\-parsable TAP format\. You can force TAP output from a terminal by invoking Bats with the \fB\-\-tap\fR option\.
.IP "" 4
.nf
$ bats \-\-tap addition\.bats
1\.\.2
ok 1 addition using bc
ok 2 addition using dc
.fi
.IP "" 0
.SH "EXIT STATUS"
The \fBbats\fR interpreter exits with a value of \fB0\fR if all test cases pass, or \fB1\fR if one or more test cases fail\.
.SH "SEE ALSO"
Bats wiki: \fIhttps://github\.com/bats\-core/bats\-core/wiki/\fR
.P
\fBbash\fR(1), \fBbats\fR(7)
.SH "COPYRIGHT"
(c) 2017\-2022 bats\-core organization
.br
(c) 2011\-2016 Sam Stephenson
.P
Bats is released under the terms of an MIT\-style license\.

192
man/bats.1.ronn Normal file
View File

@ -0,0 +1,192 @@
bats(1) -- Bash Automated Testing System
========================================
SYNOPSIS
--------
Usage: bats [OPTIONS] <tests>
bats [-h | -v]
<tests> is the path to a Bats test file, or the path to a directory
containing Bats test files (ending with ".bats")
DESCRIPTION
-----------
Bats is a TAP-compliant testing framework for Bash. It provides a simple
way to verify that the UNIX programs you write behave as expected.
A Bats test file is a Bash script with special syntax for defining
test cases. Under the hood, each test case is just a function with a
description.
Test cases consist of standard shell commands. Bats makes use of
Bash's `errexit` (`set -e`) option when running test cases. If every
command in the test case exits with a `0` status code (success), the
test passes. In this way, each line is an assertion of truth.
See `bats`(7) for more information on writing Bats tests.
RUNNING TESTS
-------------
To run your tests, invoke the `bats` interpreter with a path to a test
file. The file's test cases are run sequentially and in isolation. If
all the test cases pass, `bats` exits with a `0` status code. If there
are any failures, `bats` exits with a `1` status code.
You can invoke the `bats` interpreter with multiple test file arguments,
or with a path to a directory containing multiple `.bats` files. Bats
will run each test file individually and aggregate the results. If any
test case fails, `bats` exits with a `1` status code.
FILTERING TESTS
---------------
There are multiple mechanisms to filter which tests to execute:
* `--filter <regex>` to filter by test name
* `--filter-status <status>` to filter by the test's status in the last run
* `--filter-tags <tag-list>` to filter by the tags of a test
--FILTER-TAGS <TAG-LIST>
------------------------
Tags can be used for finegrained filtering of which tests to run via `--filter-tags`.
This accepts a comma separated list of tags. Only tests that match all of these
tags will be executed. For example, `bats --filter-tags a,b,c` will pick up tests
with tags `a,b,c`, but not tests that miss one or more of those tags.
Additionally, you can specify negative tags via `bats --filter-tags a,!b,c`,
which now won't match tests with tags `a,b,c`, due to the `b`, but will select `a,c`.
To put it more formally, `--filter-tags` is a boolean conjunction.
To allow for more complex queries, you can specify multiple `--filter-tags`.
A test will be executed, if it matches at least one of them.
This means multiple `--filter-tags` form a boolean disjunction.
A query of `--filter-tags a,!b --filter-tags b,c` can be translated to:
Execute only tests that (have tag a, but not tag b) or (have tag b and c).
An empty tag list matches tests without tags.
OPTIONS
-------
* `-c`, `--count`:
Count the number of test cases without running any tests
* `--code-quote-style <style>`:
A two character string of code quote delimiters or `custom`
which requires setting `$BATS_BEGIN_CODE_QUOTE` and
`$BATS_END_CODE_QUOTE`.
Can also be set via `$BATS_CODE_QUOTE_STYLE`.
* `--line-reference-format`
Controls how file/line references e.g. in stack traces are printed:
- comma_line (default): a.bats, line 1
- colon: a.bats:1
- uri: file:///tests/a.bats:1
- custom: provide your own via defining bats_format_file_line_reference_custom
with parameters <filename> <line>, store via `printf -v "$output"`
* `-f`, `--filter <regex>`:
Filter test cases by names matching the regular expression
* `-F`, `--formatter <type>`:
Switch between formatters: pretty (default), tap (default w/o term), tap13, junit,
`/<absolute path to formatter>`
* `--filter-status <status>`:
Only run tests with the given status in the last completed (no CTRL+C/SIGINT) run.
Valid <status> values are:
failed - runs tests that failed or were not present in the last run
missed - runs tests that were not present in the last run
* `--filter-tags <comma-separated-tag-list>`:
Only run tests that match all the tags in the list (`&&`). You can negate a
tag via prepending `!`.
Specifying this flag multiple times allows for logical or (`||`):
`--filter-tags A,B --filter-tags A,!C` matches tags `(A && B) || (A && !C)`
* `--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
* `--no-parallelize-across-files`:
Serialize test file execution instead of running them in parallel (requires --jobs >1)
* `--no-parallelize-within-files`:
Serialize test execution within files instead of running them in parallel (requires --jobs >1)
* `--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
OUTPUT
------
When you run Bats from a terminal, you'll see output as each test is
performed, with a check-mark next to the test's name if it passes or
an "X" if it fails.
$ bats addition.bats
✓ addition using bc
✓ addition using dc
2 tests, 0 failures
If Bats is not connected to a terminal--in other words, if you run it
from a continuous integration system or redirect its output to a
file--the results are displayed in human-readable, machine-parsable
TAP format. You can force TAP output from a terminal by invoking Bats
with the `--tap` option.
$ bats --tap addition.bats
1..2
ok 1 addition using bc
ok 2 addition using dc
EXIT STATUS
-----------
The `bats` interpreter exits with a value of `0` if all test cases pass,
or `1` if one or more test cases fail.
SEE ALSO
--------
Bats wiki: _https://github.com/bats\-core/bats\-core/wiki/_
`bash`(1), `bats`(7)
COPYRIGHT
---------
(c) 2017-2022 bats-core organization<br/>
(c) 2011-2016 Sam Stephenson
Bats is released under the terms of an MIT-style license.

239
man/bats.7 Normal file
View File

@ -0,0 +1,239 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "BATS" "7" "November 2022" "bats-core" "Bash Automated Testing System"
.SH "NAME"
\fBbats\fR \- Bats test file format
.SH "DESCRIPTION"
A Bats test file is a Bash script with special syntax for defining test cases\. Under the hood, each test case is just a function with a description\.
.IP "" 4
.nf
#!/usr/bin/env bats
@test "addition using bc" {
result="$(echo 2+2 | bc)"
[ "$result" \-eq 4 ]
}
@test "addition using dc" {
result="$(echo 2 2+p | dc)"
[ "$result" \-eq 4 ]
}
.fi
.IP "" 0
.P
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 "Tagging tests"
Each test has a list of tags attached to it\. Without specification, this list is empty\. Tags can be defined in two ways\. The first being \fB# bats test_tags=\fR:
.P
# bats test_tags=tag:1, tag:2, tag:3 @test "second test" { # \|\.\|\.\|\. }
.P
@test "second test" { # \|\.\|\.\|\. }
.P
These tags (\fBtag:1\fR, \fBtag:2\fR, \fBtag:3\fR) will be attached to the test \fBfirst test\fR\. The second test will have no tags attached\. Values defined in the \fB# bats test_tags=\fR directive will be assigned to the next \fB@test\fR that is being encountered in the file and forgotten after that\. Only the value of the last \fB# bats test_tags=\fR directive before a given test will be used\.
.P
Sometimes, we want to give all tests in a file a set of the same tags\. This can be achieved via \fB# bats file_tags=\fR\. They will be added to all tests in the file after that directive\. An additional \fB# bats file_tags=\fR directive will override the previously defined values:
.IP "" 4
.nf
@test "Zeroth test" {
# will have no tags
}
# bats file_tags=a:b
# bats test_tags=c:d
@test "First test" {
# will be tagged a:b, c:d
}
# bats file_tags=
@test "Second test" {
# will have no tags
}
.fi
.IP "" 0
.P
Tags are case sensitive and must only consist of alphanumeric characters and \fB_\fR, \fB\-\fR, or \fB:\fR\. They must not contain whitespaces! The colon is intended as a separator for (recursive) namespacing\.
.P
Tag lists must be separated by commas and are allowed to contain whitespace\. They must not contain empty tags like \fBtest_tags=,b\fR (first tag is empty), \fBtest_tags=a,,c\fR, \fBtest_tags=a, ,c\fR (second tag is only whitespace/empty), \fBtest_tags=a,b,\fR (third tag is empty)\.
.P
Every tag starting with \fBbats:\fR (case insensitive!) is reserved for Bats\' internal use:
.TP
\fBbats:focus\fR
If any test with the tag \fBbats:focus\fR is encountered in a test suite, only those tagged with this tag will be executed\. To prevent the CI from silently running on a subset of tests due to an accidentally commited \fBbats:focus\fR tag, the exit code of successful runs will be overriden to 1\.
.IP
Should you require the true exit code, e\.g\. for a \fBgit bisect\fR operation, you can disable this behavior by setting \fBBATS_NO_FAIL_FOCUS_RUN=1\fR when running \fBbats\fR, but make sure to not commit this to CI!
.SH "THE RUN HELPER"
Usage: run [OPTIONS] [\-\-]
.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\.
.IP "" 4
.nf
@test "invoking foo with a nonexistent file prints an error" {
run \-1 foo nonexistent_filename
[ "$output" = "foo: no such file \'nonexistent_filename\'" ]
}
.fi
.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
A third special variable, the \fB$lines\fR array, is available for easily accessing individual lines of output\. For example, if you want to test that invoking \fBfoo\fR without any arguments prints usage information on the first line:
.IP "" 4
.nf
@test "invoking foo without arguments prints usage" {
run \-1 foo
[ "${lines[0]}" = "usage: foo <filename>" ]
}
.fi
.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 \fB\-\-separate\-stderr\fR to split stdout and stderr into \fB$output\fR/\fB$stderr\fR and \fB${lines[@]}\fR/\fB${stderr_lines[@]}\fR\.
.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
.IP "" 4
.nf
load test_helper
.fi
.IP "" 0
.P
will source the script \fBtest/test_helper\.bash\fR in your test file\. This can be useful for sharing functions to set up your environment or load fixtures\.
.SH "THE BATS_LOAD_LIBRARY COMMAND"
Some libraries are installed on the system, e\.g\. by \fBnpm\fR or \fBbrew\fR\. These should not be \fBload\fRed, as their path depends on the installation method\. Instead, one should use \fBbats_load_library\fR together with setting \fBBATS_LIB_PATH\fR, a \fBPATH\fR\-like colon\-delimited variable\.
.P
\fBbats_load_library\fR has two modes of resolving requests:
.IP "1." 4
by relative path from the \fBBATS_LIB_PATH\fR to a file in the library
.IP "2." 4
by library name, expecting libraries to have a \fBload\.bash\fR entrypoint
.IP "" 0
.P
For example if your \fBBATS_LIB_PATH\fR is set to \fB~/\.bats/libs:/usr/lib/bats\fR, then \fBbats_load_library test_helper\fR would look for existing files with the following paths:
.IP "\[ci]" 4
\fB~/\.bats/libs/test_helper\fR
.IP "\[ci]" 4
\fB~/\.bats/libs/test_helper/load\.bash\fR
.IP "\[ci]" 4
\fB/usr/lib/bats/test_helper\fR
.IP "\[ci]" 4
\fB/usr/lib/bats/test_helper/load\.bash\fR
.IP "" 0
.P
The first existing file in this list will be sourced\.
.P
If you want to load only part of a library or the entry point is not named \fBload\.bash\fR, you have to include it in the argument: \fBbats_load_library library_name/file_to_load\fR will try
.IP "\[ci]" 4
\fB~/\.bats/libs/library_name/file_to_load\fR
.IP "\[ci]" 4
\fB~/\.bats/libs/library_name/file_to_load/load\.bash\fR
.IP "\[ci]" 4
\fB/usr/lib/bats/library_name/file_to_load\fR
.IP "\[ci]" 4
\fB/usr/lib/bats/library_name/file_to_load/load\.bash\fR
.IP "" 0
.P
Apart from the changed lookup rules, \fBbats_load_library\fR behaves like \fBload\fR\.
.P
\fBNote\fR: As seen above \fBload\.bash\fR is the entry point for libraries and meant to load more files from its directory or other libraries\.
.P
\fBNote\fR: Obviously, the actual \fBBATS_LIB_PATH\fR is highly dependent on the environment\. To maintain a uniform location across systems, (distribution) package maintainers are encouraged to use \fB/usr/lib/bats/\fR as the install path for libraries where possible\. However, if the package manager has another preferred location, like \fBnpm\fR or \fBbrew\fR, you should use this instead\.
.SH "THE SKIP COMMAND"
Tests can be skipped by using the \fBskip\fR command at the point in a test you wish to skip\.
.IP "" 4
.nf
@test "A test I don\'t want to execute for now" {
skip
run \-0 foo
}
.fi
.IP "" 0
.P
Optionally, you may include a reason for skipping:
.IP "" 4
.nf
@test "A test I don\'t want to execute for now" {
skip "This command will return zero soon, but not now"
run \-0 foo
}
.fi
.IP "" 0
.P
Or you can skip conditionally:
.IP "" 4
.nf
@test "A test which should run" {
if [ foo != bar ]; then
skip "foo isn\'t bar"
fi
run \-0 foo
}
.fi
.IP "" 0
.SH "THE BATS_REQUIRE_MINIMUM_VERSION COMMAND"
Code for newer versions of Bats can be incompatible with older versions\. In the best case this will lead to an error message and a failed test suite\. In the worst case, the tests will pass erroneously, potentially masking a failure\.
.P
Use \fBbats_require_minimum_version <Bats version number>\fR to avoid this\. It communicates in a concise manner, that you intend the following code to be run under the given Bats version or higher\.
.P
Additionally, this function will communicate the current Bats version floor to subsequent code, allowing e\.g\. Bats\' internal warning to give more informed warnings\.
.P
\fBNote\fR: By default, calling \fBbats_require_minimum_version\fR with versions before Bats 1\.7\.0 will fail regardless of the required version as the function is not available\. However, you can use the bats\-backports plugin (https://github\.com/bats\-core/bats\-backports) to make your code usable with older versions, e\.g\. during migration while your CI system is not yet upgraded\.
.SH "SETUP AND TEARDOWN FUNCTIONS"
You can define special \fBsetup\fR and \fBteardown\fR functions which run before and after each test case, respectively\. Use these to load fixtures, set up your environment, and clean up when you\'re done\.
.SH "CODE OUTSIDE OF TEST CASES"
You can include code in your test file outside of \fB@test\fR functions\. For example, this may be useful if you want to check for dependencies and fail immediately if they\'re not present\. However, any output that you print in code outside of \fB@test\fR, \fBsetup\fR or \fBteardown\fR functions must be redirected to \fBstderr\fR (\fB>&2\fR)\. Otherwise, the output may cause Bats to fail by polluting the TAP stream on \fBstdout\fR\.
.SH "SPECIAL VARIABLES"
There are several global variables you can use to introspect on Bats tests:
.IP "\[ci]" 4
\fB$BATS_TEST_FILENAME\fR is the fully expanded path to the Bats test file\.
.IP "\[ci]" 4
\fB$BATS_TEST_DIRNAME\fR is the directory in which the Bats test file is located\.
.IP "\[ci]" 4
\fB$BATS_TEST_NAMES\fR is an array of function names for each test case\.
.IP "\[ci]" 4
\fB$BATS_TEST_NAME\fR is the name of the function containing the current test case\.
.IP "\[ci]" 4
\fBBATS_TEST_NAME_PREFIX\fR will be prepended to the description of each test on stdout and in reports\.
.IP "\[ci]" 4
\fB$BATS_TEST_DESCRIPTION\fR is the description of the current test case\.
.IP "\[ci]" 4
\fBBATS_TEST_RETRIES\fR is the maximum number of additional attempts that will be made on a failed test before it is finally considered failed\. The default of 0 means the test must pass on the first attempt\.
.IP "\[ci]" 4
\fBBATS_TEST_TIMEOUT\fR is the number of seconds after which a test (including setup) will be aborted and marked as failed\. Updates to this value in \fBsetup()\fR or \fB@test\fR cannot change the running timeout countdown, so the latest useful update location is \fBsetup_file()\fR\.
.IP "\[ci]" 4
\fB$BATS_TEST_NUMBER\fR is the (1\-based) index of the current test case in the test file\.
.IP "\[ci]" 4
\fB$BATS_SUITE_TEST_NUMBER\fR is the (1\-based) index of the current test case in the test suite (over all files)\.
.IP "\[ci]" 4
\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 "\[ci]" 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 "\[ci]" 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 "\[ci]" 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 "\[ci]" 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 "\[ci]" 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 "\[ci]" 4
\fB$BATS_VERSION\fR is the version of Bats running the test\.
.IP "" 0
.SH "SEE ALSO"
\fBbash\fR(1), \fBbats\fR(1)

332
man/bats.7.ronn Normal file
View File

@ -0,0 +1,332 @@
bats(7) -- Bats test file format
================================
DESCRIPTION
-----------
A Bats test file is a Bash script with special syntax for defining
test cases. Under the hood, each test case is just a function with a
description.
#!/usr/bin/env bats
@test "addition using bc" {
result="$(echo 2+2 | bc)"
[ "$result" -eq 4 ]
}
@test "addition using dc" {
result="$(echo 2 2+p | dc)"
[ "$result" -eq 4 ]
}
Each Bats test file is evaluated n+1 times, where _n_ 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.
Tagging tests
-------------
Each test has a list of tags attached to it. Without specification, this list is empty.
Tags can be defined in two ways. The first being `# bats test_tags=`:
# bats test_tags=tag:1, tag:2, tag:3
@test "second test" {
# ...
}
@test "second test" {
# ...
}
These tags (`tag:1`, `tag:2`, `tag:3`) will be attached to the test `first test`.
The second test will have no tags attached. Values defined in the `# bats test_tags=`
directive will be assigned to the next `@test` that is being encountered in the
file and forgotten after that. Only the value of the last `# bats test_tags=` directive
before a given test will be used.
Sometimes, we want to give all tests in a file a set of the same tags. This can
be achieved via `# bats file_tags=`. They will be added to all tests in the file
after that directive. An additional `# bats file_tags=` directive will override
the previously defined values:
@test "Zeroth test" {
# will have no tags
}
# bats file_tags=a:b
# bats test_tags=c:d
@test "First test" {
# will be tagged a:b, c:d
}
# bats file_tags=
@test "Second test" {
# will have no tags
}
Tags are case sensitive and must only consist of alphanumeric characters and `_`,
`-`, or `:`. They must not contain whitespaces!
The colon is intended as a separator for (recursive) namespacing.
Tag lists must be separated by commas and are allowed to contain whitespace.
They must not contain empty tags like `test_tags=,b` (first tag is empty),
`test_tags=a,,c`, `test_tags=a, ,c` (second tag is only whitespace/empty),
`test_tags=a,b,` (third tag is empty).
Every tag starting with `bats:` (case insensitive!) is reserved for Bats'
internal use:
* `bats:focus`:
If any test with the tag `bats:focus` is encountered in a test suite, only those tagged with this tag will be executed.
In focus mode, the exit code of successful runs will be overriden to 1 to prevent CI from silently running on a subset
of tests due to an accidentally commited `bats:focus` tag.
Should you require the true exit code, e.g. for a `git bisect` operation, you can disable this behavior by setting
`BATS_NO_FAIL_FOCUS_RUN=1` when running `bats`, but make sure not to commit this to CI!
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 empty 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 (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 -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.
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` without any arguments prints usage information on
the first line:
@test "invoking foo without arguments prints usage" {
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
----------------
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 location of the current test file. For example, if you
have a Bats test in `test/foo.bats`, the command
load test_helper
will source the script `test/test_helper.bash` in your test file. This
can be useful for sharing functions to set up your environment or load
fixtures.
THE BATS_LOAD_LIBRARY COMMAND
-----------------------------
Some libraries are installed on the system, e.g. by `npm` or `brew`.
These should not be `load`ed, as their path depends on the installation method.
Instead, one should use `bats_load_library` together with setting
`BATS_LIB_PATH`, a `PATH`-like colon-delimited variable.
`bats_load_library` has two modes of resolving requests:
1. by relative path from the `BATS_LIB_PATH` to a file in the library
2. by library name, expecting libraries to have a `load.bash` entrypoint
For example if your `BATS_LIB_PATH` is set to
`~/.bats/libs:/usr/lib/bats`, then `bats_load_library test_helper`
would look for existing files with the following paths:
- `~/.bats/libs/test_helper`
- `~/.bats/libs/test_helper/load.bash`
- `/usr/lib/bats/test_helper`
- `/usr/lib/bats/test_helper/load.bash`
The first existing file in this list will be sourced.
If you want to load only part of a library or the entry point is not named `load.bash`,
you have to include it in the argument:
`bats_load_library library_name/file_to_load` will try
- `~/.bats/libs/library_name/file_to_load`
- `~/.bats/libs/library_name/file_to_load/load.bash`
- `/usr/lib/bats/library_name/file_to_load`
- `/usr/lib/bats/library_name/file_to_load/load.bash`
Apart from the changed lookup rules, `bats_load_library` behaves like `load`.
**Note**: As seen above `load.bash` is the entry point for libraries and
meant to load more files from its directory or other libraries.
**Note**: Obviously, the actual `BATS_LIB_PATH` is highly dependent on the environment.
To maintain a uniform location across systems, (distribution) package maintainers
are encouraged to use `/usr/lib/bats/` as the install path for libraries where possible.
However, if the package manager has another preferred location, like `npm` or `brew`,
you should use this instead.
THE SKIP COMMAND
----------------
Tests can be skipped by using the `skip` command at the point in a
test you wish to skip.
@test "A test I don't want to execute for now" {
skip
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 -0 foo
}
Or you can skip conditionally:
@test "A test which should run" {
if [ foo != bar ]; then
skip "foo isn't bar"
fi
run -0 foo
}
THE BATS_REQUIRE_MINIMUM_VERSION COMMAND
----------------------------------------
Code for newer versions of Bats can be incompatible with older versions.
In the best case this will lead to an error message and a failed test suite.
In the worst case, the tests will pass erroneously, potentially masking a failure.
Use `bats_require_minimum_version <Bats version number>` to avoid this.
It communicates in a concise manner, that you intend the following code to be run
under the given Bats version or higher.
Additionally, this function will communicate the current Bats version floor to
subsequent code, allowing e.g. Bats' internal warning to give more informed warnings.
**Note**: By default, calling `bats_require_minimum_version` with versions before
Bats 1.7.0 will fail regardless of the required version as the function is not
available. However, you can use the
bats-backports plugin (https://github.com/bats-core/bats-backports) to make
your code usable with older versions, e.g. during migration while your CI system
is not yet upgraded.
SETUP AND TEARDOWN FUNCTIONS
----------------------------
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 environment, and clean up when you're done.
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 immediately if they're not present. However, any output that
you print in code 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`.
SPECIAL VARIABLES
-----------------
There are several global variables you can use to introspect on Bats
tests:
* `$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.
* `$BATS_TEST_NAME` is the name of the function containing the current
test case.
* `BATS_TEST_NAME_PREFIX` will be prepended to the description of each test
on stdout and in reports.
* `$BATS_TEST_DESCRIPTION` is the description of the current test
case.
* `BATS_TEST_RETRIES` is the maximum number of additional attempts that will be
made on a failed test before it is finally considered failed.
The default of 0 means the test must pass on the first attempt.
* `BATS_TEST_TIMEOUT` is the number of seconds after which a test (including setup)
will be aborted and marked as failed. Updates to this value in `setup()` or `@test`
cannot change the running timeout countdown, so the latest useful update location is `setup_file()`.
* `$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 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.
* `$BATS_VERSION` is the version of Bats running the test.
SEE ALSO
--------
`bash`(1), `bats`(1)

34
package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "bats",
"version": "1.9.0",
"description": "Bash Automated Testing System",
"homepage": "https://github.com/bats-core/bats-core#readme",
"license": "MIT",
"author": "Sam Stephenson <sstephenson@gmail.com> (http://sstephenson.us/)",
"repository": "github:bats-core/bats-core",
"bugs": "https://github.com/bats-core/bats-core/issues",
"files": [
"bin",
"libexec",
"lib",
"man",
"install.sh",
"uninstall.sh"
],
"directories": {
"bin": "bin",
"doc": "docs",
"man": "man",
"test": "test"
},
"scripts": {
"test": "bin/bats test"
},
"keywords": [
"bats",
"bash",
"shell",
"test",
"unit"
]
}

22
shellcheck.sh Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -e
targets=()
while IFS= read -r -d $'\0'; do
targets+=("$REPLY")
done < <(
find . -type f \( -name \*.bash -o -name \*.sh \) -print0
find . -type f -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[@]}"
exit $?

View File

1564
test/bats.bats Executable file

File diff suppressed because it is too large Load Diff

204
test/common.bats Normal file
View File

@ -0,0 +1,204 @@
@test bats_version_lt {
bats_require_minimum_version 1.5.0
run ! bats_version_lt 1.0.0 1.0
[ "$output" = "ERROR: version '1.0' must be of format <major>.<minor>.<patch>!" ]
run ! bats_version_lt 1.0 1.0.0
[ "$output" = "ERROR: version '1.0' must be of format <major>.<minor>.<patch>!" ]
run -0 bats_version_lt 1.0.0 2.0.0
run -0 bats_version_lt 1.2.0 2.0.0
run -0 bats_version_lt 1.2.3 2.0.0
run -0 bats_version_lt 1.0.0 1.1.0
run -0 bats_version_lt 1.0.2 1.1.0
run -0 bats_version_lt 1.0.0 1.0.1
run -1 bats_version_lt 2.0.0 1.0.0
run -1 bats_version_lt 2.0.0 1.2.0
run -1 bats_version_lt 2.0.0 1.2.3
run -1 bats_version_lt 1.1.0 1.0.0
run -1 bats_version_lt 1.1.0 1.0.2
run -1 bats_version_lt 1.0.1 1.0.0
run -2 bats_version_lt 1.0.0 1.0.0
}
@test bats_require_minimum_version {
[ "$BATS_GUARANTEED_MINIMUM_VERSION" = 0.0.0 ] # check default
bats_require_minimum_version 0.1.2 # (a version that should be safe not to fail)
[ "${BATS_GUARANTEED_MINIMUM_VERSION}" = 0.1.2 ]
# a higher version should upgrade
bats_require_minimum_version 0.2.3
[ "${BATS_GUARANTEED_MINIMUM_VERSION}" = 0.2.3 ]
# a lower version should not change
bats_require_minimum_version 0.1.2
[ "${BATS_GUARANTEED_MINIMUM_VERSION}" = 0.2.3 ]
}
@test bats_binary_search {
bats_require_minimum_version 1.5.0
run -2 bats_binary_search "search-value"
[ "$output" = "ERROR: bats_binary_search requires exactly 2 arguments: <search value> <array name>" ]
# unset array = empty array: a bit unfortunate but we can't tell the difference (on older Bash?)
unset no_array
run -1 bats_binary_search "search-value" "no_array"
# shellcheck disable=SC2034
empty_array=()
run -1 bats_binary_search "search-value" "empty_array"
# shellcheck disable=SC2034
odd_length_array=(1 2 3)
run -1 bats_binary_search a odd_length_array
run -0 bats_binary_search 1 odd_length_array
run -0 bats_binary_search 2 odd_length_array
run -0 bats_binary_search 3 odd_length_array
# shellcheck disable=SC2034
even_length_array=(1 2 3 4)
run -1 bats_binary_search a even_length_array
run -0 bats_binary_search 1 even_length_array
run -0 bats_binary_search 2 even_length_array
run -0 bats_binary_search 3 even_length_array
run -0 bats_binary_search 4 even_length_array
}
@test bats_sort {
local -a empty one_element two_sorted two_elements_reversed three_elements_scrambled
bats_sort empty
echo "empty(${#empty[@]}): ${empty[*]}"
[ ${#empty[@]} -eq 0 ]
bats_sort one_element 1
echo "one_element(${#one_element[@]}): ${one_element[*]}"
[ ${#one_element[@]} -eq 1 ]
[ "${one_element[0]}" = 1 ]
bats_sort two_sorted 1 2
echo "two_sorted(${#two_sorted[@]}): ${two_sorted[*]}"
[ ${#two_sorted[@]} -eq 2 ]
[ "${two_sorted[0]}" = 1 ]
[ "${two_sorted[1]}" = 2 ]
bats_sort two_elements_reversed 2 1
echo "two_elements_reversed(${#two_elements_reversed[@]}): ${two_elements_reversed[*]}"
[ ${#two_elements_reversed[@]} -eq 2 ]
[ "${two_elements_reversed[0]}" = 1 ]
[ "${two_elements_reversed[1]}" = 2 ]
bats_sort three_elements_scrambled 2 1 3
echo "three_elements_scrambled(${#three_elements_scrambled[@]}): ${three_elements_scrambled[*]}"
[ ${#three_elements_scrambled[@]} -eq 3 ]
[ "${three_elements_scrambled[0]}" = 1 ]
[ "${three_elements_scrambled[1]}" = 2 ]
[ "${three_elements_scrambled[2]}" = 3 ]
}
@test bats_all_in {
bats_require_minimum_version 1.5.0
local -ra empty=() one=(1) onetwo=(1 2)
# find nothing in any array
run -0 bats_all_in empty
run -0 bats_all_in one
run -0 bats_all_in onetwo
# find single search value in single element array
run -0 bats_all_in one 1
# find single search values in multi element array
run -0 bats_all_in onetwo 1
# find multiple search values in multi element array
run -0 bats_all_in onetwo 1 2
# don't find in empty array
run -1 bats_all_in empty 1
# don't find in non empty
run -1 bats_all_in one 2
# don't find smaller values
run -1 bats_all_in onetwo 0 1 2
# don't find greater values
run -1 bats_all_in onetwo 1 2 3
}
@test bats_any_in {
bats_require_minimum_version 1.5.0
# shellcheck disable=SC2030,SC2034
local -ra empty=() one=(1) onetwo=(1 2)
# empty search set is always false
run -1 bats_any_in empty
run -1 bats_any_in one
run -1 bats_any_in onetwo
# find single search value in single element array
run -0 bats_any_in one 1
# find single search values in multi element array
run -0 bats_any_in onetwo 2
# find multiple search values in multi element array
run -0 bats_any_in onetwo 1 2
# don't find in empty array
run -1 bats_any_in empty 1
# don't find in non empty
run -1 bats_any_in one 2
# don't find smaller values
run -1 bats_any_in onetwo 0
# don't find greater values
run -1 bats_any_in onetwo 3
}
@test bats_trim {
local empty already_trimmed trimmed whitespaces_within
bats_trim empty ""
# shellcheck disable=SC2031
[ "${empty-NOTSET}" = "" ]
bats_trim already_trimmed "abc"
[ "$already_trimmed" = abc ]
bats_trim trimmed " abc "
[ "$trimmed" = abc ]
bats_trim whitespaces_within " a b "
[ "$whitespaces_within" = "a b" ]
}
@test bats_append_arrays_as_args {
bats_require_minimum_version 1.5.0
count_and_print_args() {
echo "$# $*"
}
run -1 bats_append_arrays_as_args
[ "${lines[0]}" == "Error: append_arrays_as_args is missing a command or -- separator" ]
run -1 bats_append_arrays_as_args --
[ "${lines[0]}" == "Error: append_arrays_as_args is missing a command or -- separator" ]
# shellcheck disable=SC2034
empty=()
run -0 bats_append_arrays_as_args empty -- count_and_print_args
[ "${lines[0]}" == '0 ' ]
run -0 bats_append_arrays_as_args -- count_and_print_args
[ "${lines[0]}" == '0 ' ]
# shellcheck disable=SC2034
arr=(a)
run -0 bats_append_arrays_as_args arr -- count_and_print_args
[ "${lines[0]}" == '1 a' ]
# shellcheck disable=SC2034
arr2=(b)
run -0 bats_append_arrays_as_args arr arr2 -- count_and_print_args
[ "${lines[0]}" == '2 a b' ]
run -0 bats_append_arrays_as_args arr empty arr2 -- count_and_print_args
[ "${lines[0]}" == '2 a b' ]
}

View File

@ -0,0 +1,65 @@
# 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"
}
single-use-latch::_filename() { # <latch-name>
printf "%s\n" "${SINGLE_USE_LATCH_DIR?}/latch-${1//\//_}"
}

View File

@ -0,0 +1,198 @@
bats_require_minimum_version 1.5.0
load 'test_helper'
fixtures file_setup_teardown
setup_file() {
export SETUP_FILE_EXPORT_TEST=true
}
@test "setup_file is run once per file" {
# 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" {
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/teardown_file_once.log"
reentrant_run bats "$FIXTURE_ROOT/teardown_file.bats"
[[ $status -eq 0 ]]
# output the log for faster debugging
cat "$LOG"
# expect to find an entry for the tested file
grep 'teardown_file.bats' "$LOG"
# it should be the only entry
run wc -l <"$LOG"
[[ $output -eq 1 ]]
}
@test "setup_file is called correctly in multi file suite" {
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/setup_file_multi_file_suite.log"
reentrant_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"
# each setup_file[2].bats is in the log exactly once!
[[ $output -eq 2 ]]
grep setup_file.bats "$LOG"
grep setup_file2.bats "$LOG"
}
@test "teardown_file is called correctly in multi file suite" {
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/teardown_file_multi_file_suite.log"
reentrant_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"
# each teardown_file[2].bats is in the log exactly once!
[[ $output -eq 2 ]]
grep teardown_file.bats "$LOG"
grep teardown_file2.bats "$LOG"
}
@test "setup_file failure aborts tests for this file" {
# this might need to mark them as skipped as the test count is already determined at this point
reentrant_run bats "$FIXTURE_ROOT/setup_file_failed.bats"
echo "$output"
[[ "${lines[0]}" == "1..2" ]]
[[ "${lines[1]}" == "not ok 1 setup_file failed" ]]
[[ "${lines[2]}" == "# (from function \`setup_file' in test file $RELATIVE_FIXTURE_ROOT/setup_file_failed.bats, line 2)" ]]
[[ "${lines[3]}" == "# \`false' failed" ]]
[[ "${lines[4]}" == "# bats warning: Executed 1 instead of expected 2 tests" ]] # this warning is expected
# to appease the count validator, we would have to reduce the expected number of tests (retroactively?) or
# output even those tests that should be skipped due to a failed setup_file.
# Since we are already in a failure mode, the additional error does not hurt and is less verbose than
# printing all the failed/skipped tests due to the setup failure.
}
@test "teardown_file failure fails at least one test from the file" {
reentrant_run bats "$FIXTURE_ROOT/teardown_file_failed.bats"
[[ $status -ne 0 ]]
echo "$output"
[[ "${lines[0]}" == "1..1" ]]
[[ "${lines[1]}" == "ok 1 test" ]]
[[ "${lines[2]}" == "not ok 2 teardown_file failed" ]]
[[ "${lines[3]}" == "# (from function \`teardown_file' in test file $RELATIVE_FIXTURE_ROOT/teardown_file_failed.bats, line 2)" ]]
[[ "${lines[4]}" == "# \`false' failed" ]]
[[ "${lines[5]}" == "# bats warning: Executed 2 instead of expected 1 tests" ]] # for now this warning is expected
# for a failed teardown_file not to change the number of tests being reported, we would have to alter at least one previous test result report
# this would require arbitrary amounts of buffering so we simply add our own line with a fake test number
# tripping the count validator won't change the overall result, as we already are in a failure mode
}
@test "teardown_file runs even if any test in the file failed" {
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/teardown_file_failed.log"
reentrant_run bats "$FIXTURE_ROOT/teardown_file_after_failing_test.bats"
[[ $status -ne 0 ]]
grep teardown_file_after_failing_test.bats "$LOG"
echo "$output"
[[ $output == "1..1
not ok 1 failing test
# (in test file $RELATIVE_FIXTURE_ROOT/teardown_file_after_failing_test.bats, line 6)
# \`false' failed" ]]
}
@test "teardown_file should run even after user abort via CTRL-C" {
if [[ "${BATS_NUMBER_OF_PARALLEL_JOBS:-1}" -gt 1 ]]; then
skip "Aborts don't work in parallel mode"
fi
# 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
# run testsubprocess in background to not avoid blocking this test
bats "$FIXTURE_ROOT/teardown_file_after_long_test.bats" &
SUBPROCESS_PID=$!
# wait until we enter the test
sleep 2
# fake sending SIGINT (CTRL-C) to the process group of the background subprocess
kill -SIGINT -- -$SUBPROCESS_PID
wait # for the test to finish either way (SIGINT or normal execution)
echo "Waited: $SECONDS seconds"
[[ $SECONDS -lt 10 ]] # make sure we really cut it short with SIGINT
# check that teardown_file ran and created the log file
[[ -f "$LOG" ]]
grep teardown_file_after_long_test.bats "$LOG"
# but the test must not have run to the end!
run ! grep "test finished successfully" "$LOG"
}
@test "setup_file runs even if all tests in the file are skipped" {
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/setup_file_skipped.log"
reentrant_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" {
# shellcheck disable=SC2031,SC2030
export LOG="$BATS_TEST_TMPDIR/teardown_file_skipped.log"
reentrant_run bats "$FIXTURE_ROOT/teardown_file_even_if_all_tests_are_skipped.bats"
[[ $status -eq 0 ]]
[[ -f "$LOG" ]]
grep teardown_file_even_if_all_tests_are_skipped.bats "$LOG"
}
@test "setup_file must not leak context between tests in the same suite" {
# 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!"
reentrant_run bats "$FIXTURE_ROOT/setup_file_does_not_leak_env.bats" "$FIXTURE_ROOT/setup_file_does_not_leak_env2.bats"
echo "$output"
[[ $status -eq 0 ]]
}
@test "teardown_file must not leak context between tests in the same suite" {
# example: BATS_ROOT was unset in one test but used in others, therefore, the suite failed
reentrant_run bats "$FIXTURE_ROOT/teardown_file_does_not_leak.bats" "$FIXTURE_ROOT/teardown_file_does_not_leak2.bats"
echo "$output"
[[ $status -eq 0 ]]
[[ $output == "1..2
ok 1 test
ok 2 must not see variable from first run" ]]
}
@test "halfway setup_file errors are caught and reported" {
reentrant_run bats "$FIXTURE_ROOT/setup_file_halfway_error.bats"
[ $status -ne 0 ]
echo "$output"
[ "${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 ignored" {
reentrant_run -0 bats "$FIXTURE_ROOT/teardown_file_halfway_error.bats"
[ "${lines[0]}" == "1..1" ]
[ "${lines[1]}" == "ok 1 empty" ]
[ "${#lines[@]}" -eq 2 ]
}
@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
reentrant_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
}
@test "Failure in setup_file and teardown_file still prints error message" {
reentrant_run ! bats "$FIXTURE_ROOT/error_in_setup_and_teardown_file.bats"
[ "${lines[0]}" == '1..1' ]
[ "${lines[1]}" == 'not ok 1 setup_file failed' ]
[ "${lines[2]}" == "# (from function \`setup_file' in test file $RELATIVE_FIXTURE_ROOT/error_in_setup_and_teardown_file.bats, line 2)" ]
[ "${lines[3]}" == "# \`false' failed" ]
[ "${#lines[@]}" -eq 4 ]
}

8
test/fixtures/bats/BATS_TMPDIR.bats vendored Executable file
View File

@ -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} ]]
}

View File

@ -0,0 +1,9 @@
@test "BATS_* variables don't contain double slashes" {
for var_name in ${!BATS_@}; do
local var_value="${!var_name}"
if [[ "$var_value" == *'//'* ]]; then
echo "$var_name contains // ${#var_value}: ${var_value}" && false
fi
done
}

19
test/fixtures/bats/cmd_using_stdin.bash vendored Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Fractional timeout supported in bash 4+
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
timeout=1
else
timeout=0.01
fi
# Just reading from stdin
while read -r -t $timeout foo; do
if [ "$foo" == "EXIT" ]; then
echo "Found"
exit 0
fi
done
echo "Not found"
exit 1

34
test/fixtures/bats/comment_style.bats vendored Normal file
View File

@ -0,0 +1,34 @@
function should_be_found { # @test
true
}
function should_be_found_with_trailing_whitespace { # @test
true
}
should_be_found_with_parens() { #@test
true
}
should_be_found_with_parens_and_whitespace() { #@test
true
}
function should_be_found_with_function_and_parens() { #@test
true
}
function should_be_found_with_function_parens_and_whitespace() { #@test
true
}
should_not_be_found() {
# shellcheck disable=SC2317
false
#@test
}
should_not_be_found() {
# shellcheck disable=SC2317
false
} #@test

View File

@ -0,0 +1,3 @@
@test "foo" {
echo "foo"
}

View File

@ -0,0 +1,13 @@
# This does not fail as expected
@test "gizmo test" {
false
}
@test "gizmo test" "this does fail, as expected" {
false
}
# This overrides any previous test from the suite with the same description
@test "gizmo test" {
true
}

1
test/fixtures/bats/empty.bats vendored Normal file
View File

@ -0,0 +1 @@

10
test/fixtures/bats/environment.bats vendored Normal file
View File

@ -0,0 +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:-}" ]
}

View File

@ -0,0 +1,5 @@
echo "file1" >>"$TEMPFILE"
@test "test1" {
:
}

View File

@ -0,0 +1,9 @@
echo "file2" >>"$TEMPFILE"
@test "test 1" {
:
}
@test "test 2" {
:
}

2
test/fixtures/bats/exit_11.bash vendored Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
exit 11

View File

@ -0,0 +1,3 @@
@test "$SUITE: test with variable in name" {
true
}

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