first basic implementation of portable tomb

this new "flavor" of tomb uses veracrypt for mounted volumes and
POSIX sh only for its scripting, is a work in progress and still
lacks full functionality, but provides a proof-of-concept to be
developed further if needs arise.
This commit is contained in:
Jaromil 2022-11-13 22:00:41 +01:00
parent 12684e6740
commit be911b1e16
47 changed files with 7531 additions and 0 deletions

47
portable/GNUmakefile Normal file
View File

@ -0,0 +1,47 @@
.PHONY: test
system := $(shell uname -s)
# if [ ${system} == "FreeBSD" ]; then sudo kldload fusefs; fi
TOMB ?= test.tomb
#########
## BUILD
#########
all:
${CC} -O2 -Wall -Wextra --std=c99 --pedantic -o tomb-str2size strtosize.c
########
## TEST
########
create-open-close:
TOMB=${TOMB} ./test/bats/bin/bats ./test/create_open_close.bats
chmod a+r ${TOMB}
# arg: TOMB='path to tomb containing random.data'
# assumes $TOMB.hash is the pre-calculated hash on creation
check-random-data:
TOMB=${TOMB} ./test/bats/bin/bats test/check-random-data.bats
########
## LINT
########
shellcheck:
shellcheck -s sh tomb -e SC2006,SC2059,SC2034
########
## DEPS
########
download-deps-ubuntu22:
curl https://files.dyne.org/tomb3/third-party/veracrypt-ubuntu22-amd64 -o /usr/local/bin/veracrypt && chmod +x /usr/local/bin/veracrypt
curl https://files.dyne.org/zenroom/nightly/zenroom-linux-amd64 -o /usr/local/bin/zenroom && chmod +x /usr/local/bin/zenroom
download-deps-ubuntu20:
curl https://files.dyne.org/tomb3/third-party/veracrypt-ubuntu20-amd64 -o /usr/local/bin/veracrypt && chmod +x /usr/local/bin/veracrypt
curl https://files.dyne.org/zenroom/nightly/zenroom-linux-amd64 -o /usr/local/bin/zenroom && chmod +x /usr/local/bin/zenroom
download-deps-freebsd13:
curl https://files.dyne.org/tomb3/third-party/veracrypt-freebsd13-amd64 -o /usr/local/bin/veracrypt && chmod +x /usr/local/bin/veracrypt
curl https://files.dyne.org/tomb3/third-party/zenroom-freebsd13-amd64 -o /usr/local/bin/zenroom && chmod +x /usr/local/bin/zenroom
#curl https://files.dyne.org/zenroom/nightly/zenroom-freebsd13-amd64 -o /usr/local/bin/zenroom && chmod +x /usr/local/bin/zenroom

64
portable/README.md Normal file
View File

@ -0,0 +1,64 @@
![Tomb's skull icon](docs/monmort.jpg)
# Portable Tomb :: the crypto undertaker runs everywhere
<!-- [![continuous integration tests badge](https://github.com/dyne/tomb3/actions/workflows/ci.yml/badge.svg)](https://github.com/dyne/Tomb3/actions) test coverage status for Ubuntu-20, FreeBSD-13 and Mac/OSX. -->
## ⚠️ WORK IN PROGRESS 🛠️
This is the portable version of [Tomb](https://github.com/dyne/tomb) using [Veracrypt](https://www.veracrypt.fr) in place of cryptsetup.
[![software by Dyne.org](https://files.dyne.org/software_by_dyne.png)](http://www.dyne.org)
# Purpose
Portable tomb achieves direct **interoperable access to tomb volumes** between:
- GNU/Linux
- MS/Windows using WSL2
- Apple/OSX using [MacFUSE](https://osxfuse.github.io/)
- FreeBSD
as well reduce dependencies and in particular use only the **POSIX sh interpreter**.
## References:
- [Milestone on v2 repo](https://github.com/dyne/Tomb/milestone/8)
- [Feature poll](https://github.com/dyne/Tomb/issues/409)
## Features
The following v2 features will be ported:
[ ] mount bind
[ ] ps / slam
[ ] resize
[ ] index & search
[ ] bury / exhume
## Dependencies
- FreeBSD: `fusefs-libs3 fusefs-lkl e2fsprogs util-linux`
- Linux: `fuse3 util-linux`
- crossplatform [Veracrypt binaries](https://files.dyne.org/tomb3/third-party) console-only
### Note on Veracrypt
The way upstream developers distribute Veracrypt is far from meeting our minimalist needs, but the console-only binary once installed has a few library dependencies and is all what we need. We will likely setup our own build (and eventually need to [fork veracrypt's commandline-client interface](https://github.com/dyne/Tomb3/issues/2)) meanwhile binary builds of Veracrypt v1.25.9 are provided for download on our own file repository for testing purposes: **use at your own risk**.
# Disclaimer
Tomb is Copyright (C) 2007-2022 by the Dyne.org Foundation and
developed by [Jaromil](https://github.com/jaromil).
This source code is free software; you can redistribute it and/or
modify it under the terms of the GNU Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please refer
to the GNU Public License for more details.
You should have received a copy of the GNU Public License along with
this source code; if not, write to: Free Software Foundation, Inc.,
675 Mass Ave, Cambridge, MA 02139, USA.

214
portable/strtosize.c Normal file
View File

@ -0,0 +1,214 @@
/* strtosize from util-linux */
/* No copyright is claimed. This code is in the public domain. */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <inttypes.h>
static int do_scale_by_power (uintmax_t *x, int base, int power)
{
while (power--) {
if (UINTMAX_MAX / base < *x)
return -ERANGE;
*x *= base;
}
return 0;
}
/*
* strtosize() - convert string to size (uintmax_t).
*
* Supported suffixes:
*
* XiB or X for 2^N
* where X = {K,M,G,T,P,E,Z,Y}
* or X = {k,m,g,t,p,e} (undocumented for backward compatibility only)
* for example:
* 10KiB = 10240
* 10K = 10240
*
* XB for 10^N
* where X = {K,M,G,T,P,E,Z,Y}
* for example:
* 10KB = 10000
*
* The optional 'power' variable returns number associated with used suffix
* {K,M,G,T,P,E,Z,Y} = {1,2,3,4,5,6,7,8}.
*
* The function also supports decimal point, for example:
* 0.5MB = 500000
* 0.5MiB = 512000
*
* Note that the function does not accept numbers with '-' (negative sign)
* prefix.
*/
int strtosize(const char *str, uintmax_t *res, int *power)
{
const char *p;
char *end;
uintmax_t x, frac = 0;
int base = 1024, rc = 0, pwr = 0, frac_zeros = 0;
static const char *suf = "KMGTPEZY";
static const char *suf2 = "kmgtpezy";
const char *sp;
*res = 0;
if (!str || !*str) {
rc = -EINVAL;
goto err;
}
/* Only positive numbers are acceptable
*
* Note that this check is not perfect, it would be better to
* use lconv->negative_sign. But coreutils use the same solution,
* so it's probably good enough...
*/
p = str;
while (isspace((unsigned char) *p))
p++;
if (*p == '-') {
rc = -EINVAL;
goto err;
}
errno = 0, end = NULL;
x = strtoumax(str, &end, 0);
if (end == str ||
(errno != 0 && (x == UINTMAX_MAX || x == 0))) {
rc = errno ? -errno : -EINVAL;
goto err;
}
if (!end || !*end)
goto done; /* without suffix */
p = end;
/*
* Check size suffixes
*/
check_suffix:
if (*(p + 1) == 'i' && (*(p + 2) == 'B' || *(p + 2) == 'b') && !*(p + 3))
base = 1024; /* XiB, 2^N */
else if ((*(p + 1) == 'B' || *(p + 1) == 'b') && !*(p + 2))
base = 1000; /* XB, 10^N */
else if (*(p + 1)) {
struct lconv const *l = localeconv();
const char *dp = l ? l->decimal_point : NULL;
size_t dpsz = dp ? strlen(dp) : 0;
if (frac == 0 && *p && dp && strncmp(dp, p, dpsz) == 0) {
const char *fstr = p + dpsz;
for (p = fstr; *p == '0'; p++)
frac_zeros++;
fstr = p;
if (isdigit(*fstr)) {
errno = 0, end = NULL;
frac = strtoumax(fstr, &end, 0);
if (end == fstr ||
(errno != 0 && (frac == UINTMAX_MAX || frac == 0))) {
rc = errno ? -errno : -EINVAL;
goto err;
}
} else
end = (char *) p;
if (frac && (!end || !*end)) {
rc = -EINVAL;
goto err; /* without suffix, but with frac */
}
p = end;
goto check_suffix;
}
rc = -EINVAL;
goto err; /* unexpected suffix */
}
sp = strchr(suf, *p);
if (sp)
pwr = (sp - suf) + 1;
else {
sp = strchr(suf2, *p);
if (sp)
pwr = (sp - suf2) + 1;
else {
rc = -EINVAL;
goto err;
}
}
rc = do_scale_by_power(&x, base, pwr);
if (power)
*power = pwr;
if (frac && pwr) {
int i;
uintmax_t frac_div = 10, frac_poz = 1, frac_base = 1;
/* mega, giga, ... */
do_scale_by_power(&frac_base, base, pwr);
/* maximal divisor for last digit (e.g. for 0.05 is
* frac_div=100, for 0.054 is frac_div=1000, etc.)
*
* Reduce frac if too large.
*/
while (frac_div < frac) {
if (frac_div <= UINTMAX_MAX/10)
frac_div *= 10;
else
frac /= 10;
}
/* 'frac' is without zeros (5 means 0.5 as well as 0.05) */
for (i = 0; i < frac_zeros; i++) {
if (frac_div <= UINTMAX_MAX/10)
frac_div *= 10;
else
frac /= 10;
}
/*
* Go backwardly from last digit and add to result what the
* digit represents in the frac_base. For example 0.25G
*
* 5 means 1GiB / (100/5)
* 2 means 1GiB / (10/2)
*/
do {
unsigned int seg = frac % 10; /* last digit of the frac */
uintmax_t seg_div = frac_div / frac_poz; /* what represents the segment 1000, 100, .. */
frac /= 10; /* remove last digit from frac */
frac_poz *= 10;
if (seg && seg_div / seg)
x += frac_base / (seg_div / seg);
} while (frac);
}
done:
*res = x;
err:
if (rc < 0)
errno = -rc;
return rc;
}
int main(int argc, char **argv) {
// TODO: secure check args
uint64_t size;
if(argc!=2) return(1);
int res = strtosize(argv[1], &size, NULL);
if(res==0) {
fprintf(stdout,"%lu\n",size);
} else {
fprintf(stderr,"%s\n",strerror(errno));
}
return(res);
}

59
portable/test/bats/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" "$@"

View File

@ -0,0 +1,98 @@
#!/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
}

View File

@ -0,0 +1,116 @@
#!/usr/bin/env bash
# reads (extended) bats tap streams from stdin and calls callback functions for each line
# bats_tap_stream_plan <number of tests> -> when the test plan is encountered
# bats_tap_stream_begin <test index> <test name> -> when a new test is begun WARNING: extended only
# bats_tap_stream_ok [--duration <milliseconds] <test index> <test name> -> when a test was successful
# bats_tap_stream_not_ok [--duration <milliseconds>] <test index> <test name> -> when a test has failed
# bats_tap_stream_skipped <test index> <test name> <skip reason> -> when a test was skipped
# 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_suite <file name> -> when a new file is begun WARNING: extended only
# 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( (.*))?$"
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
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]}"
bats_tap_stream_skipped "$ok_index" "$test_name" "$skip_reason"
else
scope=ok
if [[ "$line" =~ $timing_expr ]]; then
bats_tap_stream_ok --duration "${BASH_REMATCH[1]}" "$ok_index" "$test_name"
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" =~ $timing_expr ]]; then
bats_tap_stream_not_ok --duration "${BASH_REMATCH[1]}" "$not_ok_index" "$test_name"
else
bats_tap_stream_not_ok "$not_ok_index" "$test_name"
fi
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"
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,107 @@
#!/usr/bin/env bash
# setup the semaphore environment for the loading file
bats_semaphore_setup() {
export -f bats_semaphore_get_free_slot_count
export -f bats_semaphore_acquire_while_locked
export BATS_SEMAPHORE_DIR="$BATS_RUN_TMPDIR/semaphores"
if command -v flock >/dev/null; then
bats_run_under_lock() {
flock "$BATS_SEMAPHORE_DIR" "$@"
}
elif command -v shlock >/dev/null; then
bats_run_under_lock() {
local lockfile="$BATS_SEMAPHORE_DIR/shlock.lock"
while ! shlock -p $$ -f "$lockfile"; do
sleep 1
done
# we got the lock now, execute the command
"$@"
local status=$?
# free the lock
rm -f "$lockfile"
return $status
}
else
printf "ERROR: flock/shlock is required for parallelization within files!\n" >&2
exit 1
fi
}
# $1 - output directory for stdout/stderr
# $@ - command to run
# run the given command in a semaphore
# 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_lock 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,357 @@
#!/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_require_lib_path() {
if [[ -z "${BATS_LIB_PATH:-}" ]]; then
printf "%s: requires BATS_LIB_PATH to be set!\n" "${FUNCNAME[1]}" >&2
exit 1
fi
}
bats_load_library_safe() { # <slug>
local slug="${1:?}" library_path
bats_require_lib_path
# Check for library load paths in BATS_TEST_DIRNAME and BATS_LIB_PATH
if [[ ${slug:0:1} != / ]]; then
find_in_bats_lib_path library_path "$slug"
if [[ -z "$library_path" ]]; 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>
bats_require_lib_path
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
echo "${FUNCNAME[0]} $LINENO" >&3
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
local origIFS="$IFS"
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"
bats_separate_lines stderr_lines stderr
fi
# shellcheck disable=SC2034
BATS_RUN_COMMAND="${*}"
IFS="$origIFS"
set "-$origFlags"
if [[ ${BATS_VERBOSE_RUN:-} ]]; then
printf "%s\n" "$output"
fi
if [[ -n "$expected_rc" ]]; then
if [[ "$expected_rc" = "-1" ]]; then
if [[ "$status" -eq 0 ]]; then
BATS_ERROR_SUFFIX=", expected nonzero exit code!"
return 1
fi
elif [ "$status" -ne "$expected_rc" ]; then
# shellcheck disable=SC2034
BATS_ERROR_SUFFIX=", expected exit code $expected_rc, got $status"
return 1
fi
elif [[ "$status" -eq 127 ]]; then # "command not found"
bats_generate_warning 1 "$BATS_RUN_COMMAND"
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 ))
}

View File

@ -0,0 +1,386 @@
#!/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
if [[ $index -eq $count ]]; then
printf 'in test file %s, line %d)\n' "$filename" "$lineno"
else
printf 'in file %s, line %d,\n' "$filename" "$lineno"
fi
((++index))
done
}
bats_print_failed_command() {
local stack_trace=("${@}")
if [[ ${#stack_trace[@]} -eq 0 ]]; then
return
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_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
printf '%s [%s:%d]\n' "${padding::${#BASH_LINENO[@]}-4}" "${file##*/}" "$line" >&4
fi
printf '%s %s\n' "${padding::${#BASH_LINENO[@]}-4}" "$BASH_COMMAND" >&4
BATS_LAST_BASH_COMMAND="$BASH_COMMAND"
BATS_LAST_BASH_LINENO="$line"
BATS_LAST_BASH_SOURCE="${BASH_SOURCE[2]}"
BATS_LAST_STACK_DEPTH="${#BASH_LINENO[@]}"
fi
fi
}
# bats_debug_trap 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 ]]; 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
exit $BATS_ERROR_STATUS
}
# 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 $BATS_ERROR_STATUS
}

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,35 @@
#!/usr/bin/env bash
# shellcheck source=lib/bats-core/tracing.bash
source "$BATS_ROOT/lib/bats-core/tracing.bash"
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."
)
# generate a warning report for the parent call's call site
bats_generate_warning() { # <warning number> [<printf args for warning string>...]
local warning_number="$1" padding="00"
shift
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}" "$@"
bats_capture_stack_trace
BATS_STACK_TRACE_PREFIX=' ' bats_print_stack_trace "${BATS_DEBUG_LAST_STACK_TRACE[@]}"
} >> "$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
}

View File

@ -0,0 +1,467 @@
#!/usr/bin/env bash
set -e
export BATS_VERSION='1.7.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
-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
-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%/*}"
local result="$2"
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
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='tap'
report_formatter=''
recursive=
setup_suite_file=''
export BATS_TEMPDIR_CLEANUP=1
output=
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 =~ ^(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
output="$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")
;;
-*)
abort "Bad command line option '$1'"
;;
*)
arguments+=("$1")
;;
esac
shift
done
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
# default to the current directory for output
if [[ -z "$output" ]]; then
output=.
fi
# only set BATS_REPORT_FILENAME if none was given
if [[ -z "${BATS_REPORT_FILENAME:-}" ]]; then
case "$report_formatter" in
tap|tap13)
BATS_REPORT_FILE_NAME="report.tap"
;;
junit)
BATS_REPORT_FILE_NAME="report.xml"
;;
*)
BATS_REPORT_FILE_NAME="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 "$output" ]]; then
if [[ ! -w "${output}" ]]; then
abort "Output path ${output} is not writeable"
fi
export BATS_REPORT_OUTPUT_PATH="$output"
fi
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
if [[ -n "$report_formatter" ]]; then
exec bats-exec-suite "${flags[@]}" "${filenames[@]}" | \
tee >("$interpolated_report_formatter" "${report_formatter_flags[@]}" >"${BATS_REPORT_OUTPUT_PATH}/${BATS_REPORT_FILE_NAME}") | \
bats_test_count_validator | \
"$interpolated_formatter" "${formatter_flags[@]}"
else
exec bats-exec-suite "${flags[@]}" "${filenames[@]}" | \
bats_test_count_validator | \
"$interpolated_formatter" "${formatter_flags[@]}"
fi

View File

@ -0,0 +1,338 @@
#!/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_OUT" 2>&1
BATS_SOURCE_FILE_COMPLETED=1
setup_file >>"$BATS_OUT" 2>&1
BATS_SETUP_FILE_COMPLETED=1
}
bats_run_teardown_file() {
# 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_COMPLETED=1
}
bats_file_teardown_trap() {
bats_run_teardown_file
bats_file_exit_trap
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
bats_file_exit_trap() {
trap - ERR EXIT
local failure_reason
local -i failure_test_index=$(( BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE + 1 ))
if [[ -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
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
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 [[ "$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 [[ $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

View File

@ -0,0 +1,359 @@
#!/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=''
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}"
abort() {
printf 'Error: %s\n' "$1" >&2
exit 1
}
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"
;;
--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)
;;
--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 && [[ -z "$bats_no_parallelize_across_files" ]]; then
abort "Cannot execute \"${num_jobs}\" jobs without GNU parallel"
exit 1
fi
# shellcheck source=lib/bats-core/semaphore.bash
source "${BATS_ROOT}/lib/bats-core/semaphore.bash"
bats_semaphore_setup
fi
# create a file that contains all (filtered) tests to run from all files
TESTS_LIST_FILE="${BATS_RUN_TMPDIR}/test_list_file.txt"
bats_gather_tests() {
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#* }"
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"
fi
bats_gather_tests "$@"
if [[ -n "$filter_status" ]]; then
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
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"
exit 1
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"
exit 1
fi
# only abort on the lowest levels
trap 'BATS_INTERRUPTED=true' INT
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
bats_suite_exit_trap() {
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[@]}"
bats_exec_suite_status=1
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
rm "$BATS_RUNLOG_FILE"
fi
exit "$bats_exec_suite_status"
}
bats_run_teardown_suite() {
# 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
set -eET
BATS_TEARDOWN_SUITE_COMPLETED=
teardown_suite 2>&1
BATS_TEARDOWN_SUITE_COMPLETED=1
set +ET
}
bats_teardown_suite_trap() {
bats_run_teardown_suite
bats_suite_exit_trap
}
setup_suite() {
:
}
teardown_suite() {
:
}
trap bats_teardown_suite_trap EXIT
if [[ -n "$setup_suite_file" ]]; then
setup_suite() {
printf "%s does not define \`setup_suite()\`\n" "$setup_suite_file" >&2
exit 1
}
# shellcheck disable=SC1090
source "$setup_suite_file"
fi
set -eET
BATS_SETUP_SUITE_COMPLETED=
setup_suite 2>&1
BATS_SETUP_SUITE_COMPLETED=1
set +ET
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
exit "$bats_exec_suite_status"

View File

@ -0,0 +1,231 @@
#!/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
# 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
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
bats_exit_trap() {
local status
local skipped=''
trap - ERR EXIT
if [[ -n "$BATS_TEST_SKIPPED" ]]; then
skipped=' # skip'
if [[ "$BATS_TEST_SKIPPED" != '1' ]]; then
skipped+=" $BATS_TEST_SKIPPED"
fi
fi
BATS_TEST_TIME=''
if [[ -z "${skipped}" && -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\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}${BATS_TEST_DESCRIPTION}${BATS_TEST_TIME}" >&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 && -n "${output:-}" ]]; then
printf "Last output:\n%s\n" "$output" >> "$BATS_OUT"
fi
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}" \
"$skipped" >&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"
}
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
BATS_TEST_COMPLETED=
BATS_TEST_SKIPPED=
BATS_TEARDOWN_COMPLETED=
BATS_ERROR_STATUS=
bats_setup_tracing
# mark this call as trap call
trap 'bats_teardown_trap as-exit-trap' EXIT
BATS_TEST_START_TIME=$(get_mills_since_epoch)
"$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1 4>&1
BATS_TEST_COMPLETED=1
trap 'bats_exit_trap' EXIT
bats_teardown_trap "" # 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,251 @@
#!/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
_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() { # [--duration <milliseconds] <test index> <test name>
if [[ "$1" == "--duration" ]]; then
test_exec_time="${BASH_REMATCH[1]}"
else
test_exec_time=0
fi
((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>
((file_count += 1))
((file_skipped += 1))
test_result_state='skipped'
test_exec_time=0
test_skip_message="$3"
}
bats_tap_stream_not_ok() { # [--duration <milliseconds>] <test index> <test name>
((file_count += 1))
((file_failures += 1))
if [[ "$1" == "--duration" ]]; then
test_exec_time="${BASH_REMATCH[1]}"
else
test_exec_time=0
fi
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,328 @@
#!/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 "$BATS_ENABLE_TIMING" ]]; then
set_color 2
buffer ' [%s]' "$TIMING"
fi
advance
move_down $(( line_backoff_count - 1 ))
}
pass() {
TIMING="${1:-}"
finish_test ' ✓ %s' "$name"
test_result=pass
}
skip() {
local reason="$1"
if [[ -n "$reason" ]]; then
reason=": $reason"
fi
BATS_ENABLE_TIMING='' finish_test ' - %s (skipped%s)' "$name" "$reason"
test_result=skip
}
fail() {
set_color 1 bold
TIMING="${1:-}"
finish_test ' ✗ %s' "$name"
test_result=fail
}
log() {
case ${test_result} in
pass)
clear_color
;;
fail)
set_color 1
;;
esac
buffer ' %s\n' "$1"
clear_color
}
summary() {
if [ "$failures" -eq 0 ] ; then
set_color 2 bold
else
set_color 1 bold
fi
buffer '\n%d test' "$count"
if [[ "$count" -ne 1 ]]; then
buffer 's'
fi
buffer ', %d failure' "$failures"
if [[ "$failures" -ne 1 ]]; then
buffer 's'
fi
if [[ "$skipped" -gt 0 ]]; then
buffer ', %d skipped' "$skipped"
fi
not_run=$((count - passed - failures - skipped))
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
name=
update_count_column_width
}
bats_tap_stream_begin() {
index="$1"
name="$2"
begin
flush
}
bats_tap_stream_ok() {
local duration=
if [[ "$1" == "--duration" ]]; then
duration="$2"
shift 2
fi
index="$1"
name="$2"
((++passed))
pass "$duration"
}
bats_tap_stream_skipped() {
index="$1"
name="$2"
((++skipped))
skip "$3"
}
bats_tap_stream_not_ok() {
local duration=
if [[ "$1" == "--duration" ]]; then
duration="$2"
shift 2
fi
index="$1"
name="$2"
((++failures))
fail "$duration"
}
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,52 @@
#!/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() { # [--duration <milliseconds] <test index> <test name>
if [[ "$1" == "--duration" ]]; then
printf "ok %d %s # in %d ms\n" "$3" "$4" "$2"
else
printf "ok %d %s\n" "$1" "$2"
fi
}
bats_tap_stream_not_ok() { # [--duration <milliseconds>] <test index> <test name>
if [[ "$1" == "--duration" ]]; then
printf "not ok %d %s # in %d ms\n" "$3" "$4" "$2"
else
printf "not ok %d %s\n" "$1" "$2"
fi
}
bats_tap_stream_skipped() { # <test index> <test name> <reason>
if [[ -n "$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,91 @@
#!/usr/bin/env bash
set -e
while [[ "$#" -ne 0 ]]; do
case "$1" in
-T)
BATS_ENABLE_TIMING="-T"
;;
esac
shift
done
header_pattern='[0-9]+\.\.[0-9]+'
IFS= read -r header
if [[ "$header" =~ $header_pattern ]]; then
printf "TAP version 13\n"
printf "%s\n" "$header"
else
# If the first line isn't a TAP plan, print it and pass the rest through
printf '%s\n' "$header"
exec cat
fi
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
while IFS= read -r line; do
case "$line" in
'begin '*) ;;
'ok '*)
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
if [[ -n "${BATS_ENABLE_TIMING-}" ]]; then
timing_expr="(ok [0-9]+ .+) in ([0-9]+)ms$"
if [[ "$line" =~ $timing_expr ]]; then
printf "%s\n" "${BASH_REMATCH[1]}"
add_yaml_entry "duration_ms" "${BASH_REMATCH[2]}"
else
echo "Could not match output line to timing regex: $line" >&2
exit 1
fi
else
printf "%s\n" "${line}"
fi
;;
'not ok '*)
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
timing_expr="(not ok [0-9]+ .+) in ([0-9])+ms$"
if [[ -n "${BATS_ENABLE_TIMING-}" ]]; then
if [[ "$line" =~ $timing_expr ]]; then
printf "%s\n" "${BASH_REMATCH[1]}"
add_yaml_entry "duration_ms" "${BASH_REMATCH[2]}"
else
echo "Could not match failure line to timing regex: $line" >&2
exit 1
fi
else
printf "%s\n" "${line}"
fi
;;
'# '*)
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" "${line:2}"
;;
'suite '*) ;;
esac
done
# close the final block if there was one
close_previous_yaml_block

View File

@ -0,0 +1,60 @@
#!/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:]]*\$"
test_file="$1"
tests=()
{
while IFS= read -r line; do
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" || :
if [[ -z "$BATS_TEST_FILTER" || "$name" =~ $BATS_TEST_FILTER ]]; then
tests+=("$encoded_name")
fi
else
printf '%s\n' "$line"
fi
done
} <<<"$(<"$test_file")"$'\n'
for test_name in "${tests[@]}"; do
printf 'bats_test_function %s\n' "$test_name"
done

197
portable/test/bats_setup Normal file
View File

@ -0,0 +1,197 @@
# setup paths for BATS test units
setup() {
[ ! -f ${BATS_PARENT_TMPNAME}.skip ] || skip "skip remaining tests"
bats_require_minimum_version 1.5.0
R="$BATS_TEST_DIRNAME"
# R=`cd "$T"/.. && pwd`
TMP="$BATS_FILE_TMPDIR"
load test_helper/bats-support/load
load test_helper/bats-assert/load
load test_helper/bats-file/load
ZTMP="$BATS_FILE_TMPDIR"
if [ "$ZENROOM_EXECUTABLE" == "" ]; then
ZENROOM_EXECUTABLE="/usr/local/bin/zenroom"
fi
PATH="$PATH:/usr/local/bin:/usr/local/sbin"
MNT="/media/`basename $BATS_TEST_FILENAME`"
PIM=42
# max password stdin size to veracrypt is 128 bytes
PW="91cd69b2caab05cb64f8e1c8ae22a7c5a25564e7fc5116d0303dce1ffd26861ea0483c25bdb85adb216718c19815eb59ac14ec9783bfbbb57786ca7d9038c845"
# `openssl rand -hex 64`"
FS='ext4'
SIZE='20M'
[ "$TOMB" = "" ] && TOMB=test.tomb
# default on freebsd is 1001
user_uid=1000
user_gid=1000
system="`uname -s`"
f_create=""
f_format=""
f_map=""
f_mount=""
f_close=""
case "$system" in
FreeBSD)
f_create=portable_create
f_format=freebsd_format
f_map=portable_map
f_mount=freebsd_mount
f_close=freebsd_close
user_uid=1001
user_gid=1001
;;
Linux)
f_create=portable_create
f_format=linux_format
f_map=portable_map
f_mount=linux_mount
f_close=portable_close
user_uid=1000
user_gid=1000
;;
*)
>&2 echo "Unsupported system: $system"
exit 1
esac
# cd $ZTMP
}
teardown() {
[ -n "$BATS_TEST_COMPLETED" ] || touch ${BATS_PARENT_TMPNAME}.skip
>&3 echo
}
# usage: echo PASSWORD | portable_create file size pim
portable_create() {
local file="$1" # must not exist
local size="$2" # veracrypt format (accepts M or G)
local pim="$3" # any number
# >&2 echo "portable_create $file $size $pim"
veracrypt --non-interactive --text --stdin \
-m nokernelcrypto \
-c ${file} --volume-type normal \
--hash sha512 --encryption serpent-aes \
--filesystem none --size ${size} --pim ${pim} \
--random-source /dev/urandom -k ''
return $?
}
# usage: echo PASSWORD | portable_map file pim
portable_map() {
local file="$1"
local pim="$2"
# >&2 echo "portable_map $file $pim"
veracrypt --non-interactive --text --stdin \
--protect-hidden no -m nokernelcrypto \
-k '' --pim ${pim} --filesystem none \
${file}
local loop=`veracrypt -l "$file" | awk '{print $3}'`
return $?
}
portable_close() {
local file="$1"
veracrypt -d ${file}
return $?
}
linux_format() {
local file="$1"
# loop="`losetup -j ${vera}/volume | cut -d: -f1`"
local loop=`veracrypt -l "$FILE" | awk '{print $3}'`
# losetup -l
>&2 echo "veramap format: ${loop}"
sync
sudo mkfs.ext4 -E root_owner="${user_uid}:${user_gid}" "$loop"
return $?
}
# usage: _mount /tmp/.veracrypt_ mountpoint
linux_mount() {
local file="$1"
local mnt="$2"
# local loop="`losetup -j ${vera}/volume | cut -d: -f1`"
local loop=`veracrypt -l "$file" | awk '{print $3}'`
>&3 echo "fsck $loop"
fsck.ext4 -p -C0 "$loop"
sudo mount ${loop} "$mnt"
return $?
}
freebsd_format() {
file="$1"
local loop=`veracrypt -l "$FILE" | awk '{print $3}'`
mkfs.ext4 -E root_owner="${user_uid}:${user_gid}" "${loop}"
return $?
}
freebsd_mount() {
local file="$1"
local mnt="$2"
>&2 echo `veracrypt -l "$file"`
local loop=`veracrypt -l "$file" | awk '{print $3}'`
>&3 echo "fsck $loop"
fsck.ext4 -p -C0 "$loop"
lklfuse -o type=ext4 "${loop}" "$mnt"
return $?
}
freebsd_close() {
local file="$1"
local md=`veracrypt -l "$file" | awk '{print $3}'`
# umount "$mnt"
>&2 echo "md: $md"
local mnt=`pgrep -lf "lklfuse.*$md" | awk '{print $6}'`
>&2 echo "mnt: $mnt"
lkl=`pgrep -f "lklfuse.*$md" | awk '{print $1}'`
>&3 echo "kill $lkl (lklfuse on $md)"
# trying to deail with lklfuse bug
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=239831
renice -20 $lkl
kill $lkl
sync
sleep 1
kill -9 $lkl
# lkl should have really exited now
>&3 echo "veracrypt -d $file"
veracrypt -d "$file"
return $?
}
Z() {
tmptmp=0
if ! [ "$TMP" ]; then
TMP=`mktemp -d`; tmptmp=1
fi
script=$1
if [ "$script" == "-" ]; then
cat > $TMP/script_stdin
script=$TMP/script_stdin
else
if ! [ -r "$script" ]; then
script=$T/$script
fi
fi
if ! [ -r $script ]; then
>&2 echo "Error - script not found: $script"
return 1
fi
shift 1
if [ "${1##*.}" == "zen" ]; then
$ZENROOM_EXECUTABLE $@ -z $script
else
$ZENROOM_EXECUTABLE $@ $script
fi
if [ $tmptmp = 1 ]; then
rm -f $TMP/*
rmdir $TMP
fi
}

View File

@ -0,0 +1,26 @@
load bats_setup
@test "Find the tomb" {
assert_file_exists "$TOMB"
>&3 echo "Found: $TOMB"
}
@test "Open tomb with key" {
mkdir -p "$TOMB".mnt
./tomb open -k "$TOMB".key "$TOMB" "$TOMB".mnt
}
@test "Check integrity of random data" {
sha512sum "$TOMB".mnt/random.data | awk '{print $1}'
cat "$TOMB".hash
>&2 echo $newhash
>&2 echo $oldhash
assert_equal "$newhash" "$oldhash"
if [ -r "$TOMB".uname ]; then
>&3 cat "$TOMB".uname
fi
}
@test "Close tomb" {
./tomb close "$TOMB"
}

View File

@ -0,0 +1,79 @@
load bats_setup
# f_create=""
# f_format=""
# f_map=""
# f_mount=""
# f_close=""
@test "Create a new tomb" {
# >&3 echo $PW
>&3 echo $TOMB
echo -n "$PW" | $f_create "$TOMB" 20M ${PIM}
}
@test "Map the tomb" {
echo -n "$PW" | $f_map "$TOMB" ${PIM}
}
@test "Format the tomb" {
$f_format "/tmp/.veracrypt_aux_mnt1" || {
$f_close "$TOMB"
return false
}
}
@test "Mount the tomb" {
mkdir -p "$MNT"
$f_mount "$TOMB" "$MNT" || {
$f_close "$TOMB"
return false
}
}
@test "Create random.data inside the tomb" {
dd if=/dev/urandom of="$MNT"/random.data bs=1024 count=10000 || {
# sudo umount testmnt
$f_close "$TOMB"
return false
}
>&3 ls -l "$MNT"/random.data
sha512sum "$MNT"/random.data | awk '{print $1}' | >&3 tee "$TOMB".hash
uname -a > "$TOMB".uname
}
@test "Close the test.tomb" {
>&3 veracrypt -l
# sudo umount "$TMP"/testmnt
$f_close "$TOMB"
}
@test "Re-Map the test.tomb" {
echo -n "$PW" | $f_map "$TOMB" ${PIM}
}
@test "Re-Mount the test.tomb" {
mkdir -p "$MNT"
$f_mount "$TOMB" "$MNT" || {
$f_close "$TOMB"
return false
}
}
@test "Re-Check integrity of random data" {
newhash=`sha512sum "$MNT"/random.data | awk '{print $1}'`
oldhash=`cat "$TOMB".hash`
>&2 echo $newhash
>&2 echo $oldhash
assert_equal "$newhash" "$oldhash"
if [ -r "$TOMB".uname ]; then
>&3 cat "$TOMB".uname
fi
}
@test "Re-Close the test.tomb" {
>&3 veracrypt -l
$f_close "$TOMB"
}

View File

@ -0,0 +1,51 @@
load bats_setup
@test "Dig tomb" {
rm -f "$TOMB"
>&3 echo "$TOMB"
./tomb dig -s 20MiB "$TOMB"
}
@test "Forge key" {
rm -f "$TOMB".key
./tomb forge "$TOMB".key
}
@test "Lock tomb with key" {
./tomb lock -k "$TOMB".key "$TOMB"
}
@test "Open tomb with key" {
mkdir -p "$TOMB".mnt
./tomb open -k "$TOMB".key "$TOMB" "$TOMB".mnt
}
@test "Create random.data inside the tomb" {
dd if=/dev/urandom of="$TOMB".mnt/random.data bs=1024 count=10000
>&3 ls -l "$TOMB".mnt/random.data
sha512sum "$TOMB".mnt/random.data | awk '{print $1}' | >&3 tee "$TOMB".hash
uname -a > "$TOMB".uname
}
@test "Close tomb" {
./tomb close "$TOMB"
}
@test "Re-open tomb with key" {
./tomb open -k "$TOMB".key "$TOMB" "$TOMB".mnt
}
@test "Check integrity of random data" {
newhash=`sha512sum "$TOMB".mnt/random.data | awk '{print $1}'`
oldhash=`cat "$TOMB".hash`
>&2 echo $newhash
>&2 echo $oldhash
assert_equal "$newhash" "$oldhash"
if [ -r "$TOMB".uname ]; then
>&3 cat "$TOMB".uname
fi
}
@test "Close the tomb again" {
./tomb close "$TOMB"
}

View File

@ -0,0 +1,33 @@
# bats-assert - Common assertions for Bats
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
# Assertions are functions that perform a test and output relevant
# information on failure to help debugging. They return 1 on failure
# and 0 otherwise.
#
# All output is formatted for readability using the functions of
# `output.bash' and sent to the standard error.
# shellcheck disable=1090
source "$(dirname "${BASH_SOURCE[0]}")/src/assert.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/refute.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_equal.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_not_equal.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_success.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_failure.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_output.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/refute_output.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_line.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/refute_line.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_regex.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/refute_regex.bash"

View File

@ -0,0 +1,42 @@
# assert
# ======
#
# Summary: Fail if the given expression evaluates to false.
#
# Usage: assert <expression>
# Options:
# <expression> The expression to evaluate for truthiness.
# *__Note:__ The expression must be a simple command.
# [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands),
# such as `[[`, can be used only when executed with `bash -c`.*
#
# IO:
# STDERR - the failed expression, on failure
# Globals:
# none
# Returns:
# 0 - if expression evaluates to true
# 1 - otherwise
#
# ```bash
# @test 'assert()' {
# touch '/var/log/test.log'
# assert [ -e '/var/log/test.log' ]
# }
# ```
#
# On failure, the failed expression is displayed.
#
# ```
# -- assertion failed --
# expression : [ -e /var/log/test.log ]
# --
# ```
assert() {
if ! "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion failed' \
| fail
fi
}

View File

@ -0,0 +1,42 @@
# assert_equal
# ============
#
# Summary: Fail if the actual and expected values are not equal.
#
# Usage: assert_equal <actual> <expected>
#
# Options:
# <actual> The value being compared.
# <expected> The value to compare against.
#
# ```bash
# @test 'assert_equal()' {
# assert_equal 'have' 'want'
# }
# ```
#
# IO:
# STDERR - expected and actual values, on failure
# Globals:
# none
# Returns:
# 0 - if values equal
# 1 - otherwise
#
# On failure, the expected and actual values are displayed.
#
# ```
# -- values do not equal --
# expected : want
# actual : have
# --
# ```
assert_equal() {
if [[ $1 != "$2" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$2" \
'actual' "$1" \
| batslib_decorate 'values do not equal' \
| fail
fi
}

View File

@ -0,0 +1,78 @@
# assert_failure
# ==============
#
# Summary: Fail if `$status` is 0; or is not equal to the optionally provided status.
#
# Usage: assert_failure [<expected_status>]
#
# Options:
# <expected_status> The specific status code to check against.
# If not provided, simply asserts status is != 0.
#
# IO:
# STDERR - `$output`, on failure;
# - also, `$status` and `expected_status`, if provided
# Globals:
# status
# output
# Returns:
# 0 - if `$status' is 0,
# or if expected_status is provided but does not equal `$status'
# 1 - otherwise
#
# ```bash
# @test 'assert_failure() status only' {
# run echo 'Success!'
# assert_failure
# }
# ```
#
# On failure, `$output` is displayed.
#
# ```
# -- command succeeded, but it was expected to fail --
# output : Success!
# --
# ```
#
# ## Expected status
#
# When `expected_status` is provided, fail if `$status` does not equal the `expected_status`.
#
# ```bash
# @test 'assert_failure() with expected status' {
# run bash -c "echo 'Error!'; exit 1"
# assert_failure 2
# }
# ```
#
# On failure, both the expected and actual statuses, and `$output` are displayed.
#
# ```
# -- command failed as expected, but status differs --
# expected : 2
# actual : 1
# output : Error!
# --
# ```
assert_failure() {
: "${output?}"
: "${status?}"
(( $# > 0 )) && local -r expected="$1"
if (( status == 0 )); then
batslib_print_kv_single_or_multi 6 'output' "$output" \
| batslib_decorate 'command succeeded, but it was expected to fail' \
| fail
elif (( $# > 0 )) && (( status != expected )); then
{ local -ir width=8
batslib_print_kv_single "$width" \
'expected' "$expected" \
'actual' "$status"
batslib_print_kv_single_or_multi "$width" \
'output' "$output"
} \
| batslib_decorate 'command failed as expected, but status differs' \
| fail
fi
}

View File

@ -0,0 +1,248 @@
# assert_line
# ===========
#
# Summary: Fail if the expected line is not found in the output (default) or at a specific line number.
#
# Usage: assert_line [-n index] [-p | -e] [--] <expected>
#
# Options:
# -n, --index <idx> Match the <idx>th line
# -p, --partial Match if `expected` is a substring of `$output` or line <idx>
# -e, --regexp Treat `expected` as an extended regular expression
# <expected> The expected line string, substring, or regular expression
#
# IO:
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# lines
# Returns:
# 0 - if matching line found
# 1 - otherwise
#
# Similarly to `assert_output`, this function verifies that a command or function produces the expected output.
# (It is the logical complement of `refute_line`.)
# It checks that the expected line appears in the output (default) or at a specific line number.
# Matching can be literal (default), partial or regular expression.
#
# *__Warning:__
# Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`,
# causing line indices to change and preventing testing for empty lines.*
#
# [bats-93]: https://github.com/sstephenson/bats/pull/93
#
# ## Looking for a line in the output
#
# By default, the entire output is searched for the expected line.
# The assertion fails if the expected line is not found in `${lines[@]}`.
#
# ```bash
# @test 'assert_line() looking for line' {
# run echo $'have-0\nhave-1\nhave-2'
# assert_line 'want'
# }
# ```
#
# On failure, the expected line and the output are displayed.
#
# ```
# -- output does not contain line --
# line : want
# output (3 lines):
# have-0
# have-1
# have-2
# --
# ```
#
# ## Matching a specific line
#
# When the `--index <idx>` option is used (`-n <idx>` for short), the expected line is matched only against the line identified by the given index.
# The assertion fails if the expected line does not equal `${lines[<idx>]}`.
#
# ```bash
# @test 'assert_line() specific line' {
# run echo $'have-0\nhave-1\nhave-2'
# assert_line --index 1 'want-1'
# }
# ```
#
# On failure, the index and the compared lines are displayed.
#
# ```
# -- line differs --
# index : 1
# expected : want-1
# actual : have-1
# --
# ```
#
# ## Partial matching
#
# Partial matching can be enabled with the `--partial` option (`-p` for short).
# When used, a match fails if the expected *substring* is not found in the matched line.
#
# ```bash
# @test 'assert_line() partial matching' {
# run echo $'have 1\nhave 2\nhave 3'
# assert_line --partial 'want'
# }
# ```
#
# On failure, the same details are displayed as for literal matching, except that the substring replaces the expected line.
#
# ```
# -- no output line contains substring --
# substring : want
# output (3 lines):
# have 1
# have 2
# have 3
# --
# ```
#
# ## Regular expression matching
#
# Regular expression matching can be enabled with the `--regexp` option (`-e` for short).
# When used, a match fails if the *extended regular expression* does not match the line being tested.
#
# *__Note__:
# As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.*
#
# ```bash
# @test 'assert_line() regular expression matching' {
# run echo $'have-0\nhave-1\nhave-2'
# assert_line --index 1 --regexp '^want-[0-9]$'
# }
# ```
#
# On failure, the same details are displayed as for literal matching, except that the regular expression replaces the expected line.
#
# ```
# -- regular expression does not match line --
# index : 1
# regexp : ^want-[0-9]$
# line : have-1
# --
# ```
# FIXME(ztombol): Display `${lines[@]}' instead of `$output'!
assert_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^-?([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Arguments.
local -r expected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if ! [[ ${lines[$idx]} =~ $expected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression does not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} != *"$expected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line does not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} != "$expected" ]]; then
batslib_print_kv_single 8 \
'index' "$idx" \
'expected' "$expected" \
'actual' "${lines[$idx]}" \
| batslib_decorate 'line differs' \
| fail
fi
fi
else
# Contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} =~ $expected ]] && return 0
done
{ local -ar single=( 'regexp' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line matches regular expression' \
| fail
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
done
{ local -ar single=( 'substring' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line contains substring' \
| fail
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == "$expected" ]] && return 0
done
{ local -ar single=( 'line' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'output does not contain line' \
| fail
fi
fi
}

View File

@ -0,0 +1,42 @@
# assert_not_equal
# ============
#
# Summary: Fail if the actual and unexpected values are equal.
#
# Usage: assert_not_equal <actual> <unexpected>
#
# Options:
# <actual> The value being compared.
# <unexpected> The value to compare against.
#
# ```bash
# @test 'assert_not_equal()' {
# assert_not_equal 'foo' 'foo'
# }
# ```
#
# IO:
# STDERR - expected and actual values, on failure
# Globals:
# none
# Returns:
# 0 - if actual does not equal unexpected
# 1 - otherwise
#
# On failure, the unexpected and actual values are displayed.
#
# ```
# -- values should not be equal --
# unexpected : foo
# actual : foo
# --
# ```
assert_not_equal() {
if [[ "$1" == "$2" ]]; then
batslib_print_kv_single_or_multi 10 \
'unexpected' "$2" \
'actual' "$1" \
| batslib_decorate 'values should not be equal' \
| fail
fi
}

View File

@ -0,0 +1,197 @@
# assert_output
# =============
#
# Summary: Fail if `$output' does not match the expected output.
#
# Usage: assert_output [-p | -e] [- | [--] <expected>]
#
# Options:
# -p, --partial Match if `expected` is a substring of `$output`
# -e, --regexp Treat `expected` as an extended regular expression
# -, --stdin Read `expected` value from STDIN
# <expected> The expected value, substring or regular expression
#
# IO:
# STDIN - [=$1] expected output
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# Returns:
# 0 - if output matches the expected value/partial/regexp
# 1 - otherwise
#
# This function verifies that a command or function produces the expected output.
# (It is the logical complement of `refute_output`.)
# Output matching can be literal (the default), partial or by regular expression.
# The expected output can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag.
#
# ## Literal matching
#
# By default, literal matching is performed.
# The assertion fails if `$output` does not equal the expected output.
#
# ```bash
# @test 'assert_output()' {
# run echo 'have'
# assert_output 'want'
# }
#
# @test 'assert_output() with pipe' {
# run echo 'hello'
# echo 'hello' | assert_output -
# }
#
# @test 'assert_output() with herestring' {
# run echo 'hello'
# assert_output - <<< hello
# }
# ```
#
# On failure, the expected and actual output are displayed.
#
# ```
# -- output differs --
# expected : want
# actual : have
# --
# ```
#
# ## Existence
#
# To assert that any output exists at all, omit the `expected` argument.
#
# ```bash
# @test 'assert_output()' {
# run echo 'have'
# assert_output
# }
# ```
#
# On failure, an error message is displayed.
#
# ```
# -- no output --
# expected non-empty output, but output was empty
# --
# ```
#
# ## Partial matching
#
# 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`.
#
# ```bash
# @test 'assert_output() partial matching' {
# run echo 'ERROR: no such file or directory'
# assert_output --partial 'SUCCESS'
# }
# ```
#
# On failure, the substring and the output are displayed.
#
# ```
# -- output does not contain substring --
# substring : SUCCESS
# output : ERROR: no such file or directory
# --
# ```
#
# ## Regular expression matching
#
# Regular expression matching can be enabled with the `--regexp` option (`-e` for short).
# When used, the assertion fails if the *extended regular expression* does not match `$output`.
#
# *__Note__:
# The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output;
# not individual lines.*
#
# ```bash
# @test 'assert_output() regular expression matching' {
# run echo 'Foobar 0.1.0'
# assert_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$'
# }
# ```
#
# On failure, the regular expression and the output are displayed.
#
# ```
# -- regular expression does not match output --
# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$
# output : Foobar 0.1.0
# --
# ```
assert_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_nonempty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_nonempty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_output' \
| fail
return $?
fi
# Arguments.
local expected
if (( use_stdin )); then
expected="$(cat -)"
else
expected="${1-}"
fi
# Matching.
if (( is_mode_nonempty )); then
if [ -z "$output" ]; then
echo 'expected non-empty output, but output was empty' \
| batslib_decorate 'no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_output' \
| fail
elif ! [[ $output =~ $expected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$expected" \
'output' "$output" \
| batslib_decorate 'regular expression does not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output != *"$expected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$expected" \
'output' "$output" \
| batslib_decorate 'output does not contain substring' \
| fail
fi
else
if [[ $output != "$expected" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$expected" \
'actual' "$output" \
| batslib_decorate 'output differs' \
| fail
fi
fi
}

View File

@ -0,0 +1,56 @@
# `assert_regex`
#
# This function is similar to `assert_equal` but uses pattern matching instead
# of equality, by wrapping `[[ value =~ pattern ]]`.
#
# Fail if the value (first parameter) does not match the pattern (second
# parameter).
#
# ```bash
# @test 'assert_regex()' {
# assert_regex 'what' 'x$'
# }
# ```
#
# On failure, the value and the pattern are displayed.
#
# ```
# -- values does not match regular expression --
# value : what
# pattern : x$
# --
# ```
#
# If the value is longer than one line then it is displayed in *multi-line*
# format.
#
# An error is displayed if the specified extended regular expression is invalid.
#
# For description of the matching behavior, refer to the documentation of the
# `=~` operator in the
# [Bash manual]: https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html.
# Note that the `BASH_REMATCH` array is available immediately after the
# assertion succeeds but is fragile, i.e. prone to being overwritten as a side
# effect of other actions.
assert_regex() {
local -r value="${1}"
local -r pattern="${2}"
if [[ '' =~ ${pattern} ]] || (( ${?} == 2 )); then
echo "Invalid extended regular expression: \`${pattern}'" \
| batslib_decorate 'ERROR: assert_regex' \
| fail
elif ! [[ "${value}" =~ ${pattern} ]]; then
if shopt -p nocasematch &>/dev/null; then
local case_sensitive=insensitive
else
local case_sensitive=sensitive
fi
batslib_print_kv_single_or_multi 8 \
'value' "${value}" \
'pattern' "${pattern}" \
'case' "${case_sensitive}" \
| batslib_decorate 'value does not match regular expression' \
| fail
fi
}

View File

@ -0,0 +1,44 @@
# assert_success
# ==============
#
# Summary: Fail if `$status` is not 0.
#
# Usage: assert_success
#
# IO:
# STDERR - `$status` and `$output`, on failure
# Globals:
# status
# output
# Returns:
# 0 - if `$status' is 0
# 1 - otherwise
#
# ```bash
# @test 'assert_success() status only' {
# run bash -c "echo 'Error!'; exit 1"
# assert_success
# }
# ```
#
# On failure, `$status` and `$output` are displayed.
#
# ```
# -- command failed --
# status : 1
# output : Error!
# --
# ```
assert_success() {
: "${output?}"
: "${status?}"
if (( status != 0 )); then
{ local -ir width=6
batslib_print_kv_single "$width" 'status' "$status"
batslib_print_kv_single_or_multi "$width" 'output' "$output"
} \
| batslib_decorate 'command failed' \
| fail
fi
}

View File

@ -0,0 +1,42 @@
# refute
# ======
#
# Summary: Fail if the given expression evaluates to true.
#
# Usage: refute <expression>
#
# Options:
# <expression> The expression to evaluate for falsiness.
# *__Note:__ The expression must be a simple command.
# [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands),
# such as `[[`, can be used only when executed with `bash -c`.*
#
# IO:
# STDERR - the successful expression, on failure
# Globals:
# none
# Returns:
# 0 - if expression evaluates to false
# 1 - otherwise
#
# ```bash
# @test 'refute()' {
# rm -f '/var/log/test.log'
# refute [ -e '/var/log/test.log' ]
# }
# ```
#
# On failure, the successful expression is displayed.
#
# ```
# -- assertion succeeded, but it was expected to fail --
# expression : [ -e /var/log/test.log ]
# --
# ```
refute() {
if "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
| fail
fi
}

View File

@ -0,0 +1,271 @@
# refute_line
# ===========
#
# Summary: Fail if the unexpected line is found in the output (default) or at a specific line number.
#
# Usage: refute_line [-n index] [-p | -e] [--] <unexpected>
#
# Options:
# -n, --index <idx> Match the <idx>th line
# -p, --partial Match if `unexpected` is a substring of `$output` or line <idx>
# -e, --regexp Treat `unexpected` as an extended regular expression
# <unexpected> The unexpected line string, substring, or regular expression.
#
# IO:
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# lines
# Returns:
# 0 - if match not found
# 1 - otherwise
#
# Similarly to `refute_output`, this function verifies that a command or function does not produce the unexpected output.
# (It is the logical complement of `assert_line`.)
# It checks that the unexpected line does not appear in the output (default) or at a specific line number.
# Matching can be literal (default), partial or regular expression.
#
# *__Warning:__
# Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`,
# causing line indices to change and preventing testing for empty lines.*
#
# [bats-93]: https://github.com/sstephenson/bats/pull/93
#
# ## Looking for a line in the output
#
# By default, the entire output is searched for the unexpected line.
# The assertion fails if the unexpected line is found in `${lines[@]}`.
#
# ```bash
# @test 'refute_line() looking for line' {
# run echo $'have-0\nwant\nhave-2'
# refute_line 'want'
# }
# ```
#
# On failure, the unexpected line, the index of its first match and the output with the matching line highlighted are displayed.
#
# ```
# -- line should not be in output --
# line : want
# index : 1
# output (3 lines):
# have-0
# > want
# have-2
# --
# ```
#
# ## Matching a specific line
#
# When the `--index <idx>` option is used (`-n <idx>` for short), the unexpected line is matched only against the line identified by the given index.
# The assertion fails if the unexpected line equals `${lines[<idx>]}`.
#
# ```bash
# @test 'refute_line() specific line' {
# run echo $'have-0\nwant-1\nhave-2'
# refute_line --index 1 'want-1'
# }
# ```
#
# On failure, the index and the unexpected line are displayed.
#
# ```
# -- line should differ --
# index : 1
# line : want-1
# --
# ```
#
# ## Partial matching
#
# Partial matching can be enabled with the `--partial` option (`-p` for short).
# When used, a match fails if the unexpected *substring* is found in the matched line.
#
# ```bash
# @test 'refute_line() partial matching' {
# run echo $'have 1\nwant 2\nhave 3'
# refute_line --partial 'want'
# }
# ```
#
# On failure, in addition to the details of literal matching, the substring is also displayed.
# When used with `--index <idx>` the substring replaces the unexpected line.
#
# ```
# -- no line should contain substring --
# substring : want
# index : 1
# output (3 lines):
# have 1
# > want 2
# have 3
# --
# ```
#
# ## Regular expression matching
#
# Regular expression matching can be enabled with the `--regexp` option (`-e` for short).
# When used, a match fails if the *extended regular expression* matches the line being tested.
#
# *__Note__:
# As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.*
#
# ```bash
# @test 'refute_line() regular expression matching' {
# run echo $'Foobar v0.1.0\nRelease date: 2015-11-29'
# refute_line --index 0 --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$'
# }
# ```
#
# On failure, in addition to the details of literal matching, the regular expression is also displayed.
# When used with `--index <idx>` the regular expression replaces the unexpected line.
#
# ```
# -- regular expression should not match line --
# index : 0
# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$
# line : Foobar v0.1.0
# --
# ```
# FIXME(ztombol): Display `${lines[@]}' instead of `$output'!
refute_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^-?([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Arguments.
local -r unexpected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if [[ ${lines[$idx]} =~ $unexpected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression should not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} == "$unexpected" ]]; then
batslib_print_kv_single 5 \
'index' "$idx" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should differ' \
| fail
fi
fi
else
# Line contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} =~ $unexpected ]]; then
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should match the regular expression' \
| fail
return $?
fi
done
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should contain substring' \
| fail
return $?
fi
done
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == "$unexpected" ]]; then
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'line should not be in output' \
| fail
return $?
fi
done
fi
fi
}

View File

@ -0,0 +1,199 @@
# refute_output
# =============
#
# Summary: Fail if `$output' matches the unexpected output.
#
# Usage: refute_output [-p | -e] [- | [--] <unexpected>]
#
# Options:
# -p, --partial Match if `unexpected` is a substring of `$output`
# -e, --regexp Treat `unexpected` as an extended regular expression
# -, --stdin Read `unexpected` value from STDIN
# <unexpected> The unexpected value, substring, or regular expression
#
# IO:
# STDIN - [=$1] unexpected output
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# Returns:
# 0 - if output matches the unexpected value/partial/regexp
# 1 - otherwise
#
# This function verifies that a command or function does not produce the unexpected output.
# (It is the logical complement of `assert_output`.)
# Output matching can be literal (the default), partial or by regular expression.
# The unexpected output can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag.
#
# ## Literal matching
#
# By default, literal matching is performed.
# The assertion fails if `$output` equals the unexpected output.
#
# ```bash
# @test 'refute_output()' {
# run echo 'want'
# refute_output 'want'
# }
#
# @test 'refute_output() with pipe' {
# run echo 'hello'
# echo 'world' | refute_output -
# }
#
# @test 'refute_output() with herestring' {
# run echo 'hello'
# refute_output - <<< world
# }
# ```
#
# On failure, the output is displayed.
#
# ```
# -- output equals, but it was expected to differ --
# output : want
# --
# ```
#
# ## Existence
#
# To assert that there is no output at all, omit the matching argument.
#
# ```bash
# @test 'refute_output()' {
# run foo --silent
# refute_output
# }
# ```
#
# On failure, an error message is displayed.
#
# ```
# -- unexpected output --
# expected no output, but output was non-empty
# --
# ```
#
# ## Partial matching
#
# Partial matching can be enabled with the `--partial` option (`-p` for short).
# When used, the assertion fails if the unexpected _substring_ is found in `$output`.
#
# ```bash
# @test 'refute_output() partial matching' {
# run echo 'ERROR: no such file or directory'
# refute_output --partial 'ERROR'
# }
# ```
#
# On failure, the substring and the output are displayed.
#
# ```
# -- output should not contain substring --
# substring : ERROR
# output : ERROR: no such file or directory
# --
# ```
#
# ## Regular expression matching
#
# Regular expression matching can be enabled with the `--regexp` option (`-e` for short).
# When used, the assertion fails if the *extended regular expression* matches `$output`.
#
# *__Note__:
# The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output;
# not individual lines.*
#
# ```bash
# @test 'refute_output() regular expression matching' {
# run echo 'Foobar v0.1.0'
# refute_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$'
# }
# ```
#
# On failure, the regular expression and the output are displayed.
#
# ```
# -- regular expression should not match output --
# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$
# output : Foobar v0.1.0
# --
# ```
refute_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_empty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_empty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Arguments.
local unexpected
if (( use_stdin )); then
unexpected="$(cat -)"
else
unexpected="${1-}"
fi
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Matching.
if (( is_mode_empty )); then
if [ -n "$output" ]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output non-empty, but expected no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ $output =~ $unexpected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$unexpected" \
'output' "$output" \
| batslib_decorate 'regular expression should not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output == *"$unexpected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$unexpected" \
'output' "$output" \
| batslib_decorate 'output should not contain substring' \
| fail
fi
else
if [[ $output == "$unexpected" ]]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output equals, but it was expected to differ' \
| fail
fi
fi
}

View File

@ -0,0 +1,66 @@
# `refute_regex`
#
# This function is similar to `refute_equal` but uses pattern matching instead
# of equality, by wrapping `! [[ value =~ pattern ]]`.
#
# Fail if the value (first parameter) matches the pattern (second parameter).
#
# ```bash
# @test 'refute_regex()' {
# refute_regex 'WhatsApp' 'Threema'
# }
# ```
#
# On failure, the value, the pattern and the match are displayed.
#
# ```
# @test 'refute_regex()' {
# refute_regex 'WhatsApp' 'What.'
# }
#
# -- value matches regular expression --
# value : WhatsApp
# pattern : What.
# match : Whats
# case : sensitive
# --
# ```
#
# If the value or pattern is longer than one line then it is displayed in
# *multi-line* format.
#
# An error is displayed if the specified extended regular expression is invalid.
#
# For description of the matching behavior, refer to the documentation of the
# `=~` operator in the
# [Bash manual]: https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html.
#
# Note that the `BASH_REMATCH` array is available immediately after the
# assertion fails but is fragile, i.e. prone to being overwritten as a side
# effect of other actions like calling `run`. Thus, it's good practice to avoid
# using `BASH_REMATCH` in conjunction with `refute_regex()`. The valuable
# information the array contains is the matching part of the value which is
# printed in the failing test log, as mentioned above.
refute_regex() {
local -r value="${1}"
local -r pattern="${2}"
if [[ '' =~ ${pattern} ]] || (( ${?} == 2 )); then
echo "Invalid extended regular expression: \`${pattern}'" \
| batslib_decorate 'ERROR: refute_regex' \
| fail
elif [[ "${value}" =~ ${pattern} ]]; then
if shopt -p nocasematch &>/dev/null; then
local case_sensitive=insensitive
else
local case_sensitive=sensitive
fi
batslib_print_kv_single_or_multi 8 \
'value' "${value}" \
'pattern' "${pattern}" \
'match' "${BASH_REMATCH[0]}" \
'case' "${case_sensitive}" \
| batslib_decorate 'value matches regular expression' \
| fail
fi
}

View File

@ -0,0 +1,2 @@
source "$(dirname "${BASH_SOURCE[0]}")/src/file.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/temp.bash"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,182 @@
#
# bats-file - Common filesystem assertions and helpers for Bats
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
#
# temp.bash
# ---------
#
# Functions for handling temporary directories.
#
# Create a temporary directory for the current test in `BATS_TMPDIR`,
# and display its path on the standard output.
#
# The directory name is derived from the test's filename and number, and
# a random string for uniqueness.
#
# <test-filename>-<test-number>-<random-string>
#
# When `--prefix <prefix>' is specified, `<prefix>' is prepended to the
# directory name.
#
# <prefix><test-filename>-<test-number>-<random-string>
#
# Must be called from `setup', `@test' or `teardown'.
#
# Example:
#
# setup() {
# TEST_TEMP_DIR="$(temp_make --prefix 'myapp-')"
# }
#
# teardown() {
# temp_del "$TEST_TEMP_DIR"
# }
#
# Globals:
# BATS_TEST_NAME
# BATS_TEST_FILENAME
# BATS_TEST_NUMBER
# BATS_TMPDIR
# Arguments:
# none
# Options:
# -p, --prefix <prefix> - prefix the directory name with `<prefix>'
# Returns:
# 0 - on success
# 1 - otherwise
# Outputs:
# STDOUT - path of temporary directory
# STDERR - error messages
temp_make() {
# Check caller.
if ! ( batslib_is_caller --indirect 'setup' \
|| batslib_is_caller --indirect 'setup_file' \
|| batslib_is_caller --indirect "$BATS_TEST_NAME" \
|| batslib_is_caller --indirect 'teardown' \
|| batslib_is_caller --indirect 'teardown_file' )
then
echo "Must be called from \`setup', \`@test' or \`teardown'" \
| batslib_decorate 'ERROR: temp_make' \
| fail
return $?
fi
# Handle options.
local prefix=''
while (( $# > 0 )); do
case "$1" in
-p|--prefix)
if (( $# < 2 )); then
echo "\`--prefix' requires an argument" \
| batslib_decorate 'ERROR: temp_make' \
| fail
return $?
fi
prefix="$2"
shift 2
;;
--) shift; break ;;
*) break ;;
esac
done
# Create directory.
local template="$prefix"
template+="${BATS_TEST_FILENAME##*/}"
template+="-${BATS_TEST_NUMBER}"
template+='-XXXXXXXXXX'
local path
path="$(mktemp -d -- "${BATS_TMPDIR}/${template}" 2>&1)"
if (( $? )); then
echo "$path" \
| batslib_decorate 'ERROR: temp_make' \
| fail
return $?
fi
echo "$path"
}
# Delete a temporary directory, typically created with `temp_make', and
# its contents.
#
# Note: Actually, this function can be used to delete any file or
# directory. However, it is most useful in deleting temporary
# directories created with `temp_make', hence the naming.
#
# For development and debugging, deletion can be prevented using
# environment variables.
#
# When `BATSLIB_TEMP_PRESERVE' is set to 1, the function succeeds but
# the directory is not deleted.
#
# When `BATSLIB_TEMP_PRESERVE_ON_FAILURE' is set to 1 and `temp_del' is
# called, directly or indirectly, from `teardown', the function succeeds
# but the directory is not deleted if the test has failed.
#
# Example:
#
# setup() {
# TEST_TEMP_DIR="$(temp_make --prefix 'myapp-')"
# }
#
# teardown() {
# temp_del "$TEST_TEMP_DIR"
# }
#
# Globals:
# BATSLIB_TEMP_PRESERVE
# BATSLIB_TEMP_PRESERVE_ON_FAILURE
# BATS_TEST_COMPLETED
# Arguments:
# $1 - path of directory
# Returns:
# 0 - on success
# 1 - otherwise
# Outputs:
# STDERR - error messages
temp_del() {
local -r path="$1"
# Environment variables.
if [[ ${BATSLIB_TEMP_PRESERVE-} == '1' ]]; then
return 0
elif [[ ${BATSLIB_TEMP_PRESERVE_ON_FAILURE-} == '1' ]]; then
# Check caller.
if ! ( batslib_is_caller --indirect 'teardown' \
|| batslib_is_caller --indirect 'teardown_file' )
then
echo "Must be called from \`teardown' or \`teardown_file' when using \`BATSLIB_TEMP_PRESERVE_ON_FAILURE'" \
| batslib_decorate 'ERROR: temp_del' \
| fail
return $?
fi
(( ${BATS_TEST_COMPLETED:-0} != 1 )) && return 0
fi
# Delete directory.
local result
result="$(rm -r -- "$path" 2>&1 </dev/null)"
if (( $? )); then
echo "$result" \
| batslib_decorate 'ERROR: temp_del' \
| fail
return $?
fi
}

View File

@ -0,0 +1,3 @@
source "$(dirname "${BASH_SOURCE[0]}")/src/output.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/error.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/lang.bash"

View File

@ -0,0 +1,41 @@
#
# bats-support - Supporting library for Bats test helpers
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
#
# error.bash
# ----------
#
# Functions implementing error reporting. Used by public helper
# functions or test suits directly.
#
# Fail and display a message. When no parameters are specified, the
# message is read from the standard input. Other functions use this to
# report failure.
#
# Globals:
# none
# Arguments:
# $@ - [=STDIN] message
# Returns:
# 1 - always
# Inputs:
# STDIN - [=$@] message
# Outputs:
# STDERR - message
fail() {
(( $# == 0 )) && batslib_err || batslib_err "$@"
return 1
}

View File

@ -0,0 +1,73 @@
#
# bats-util - Various auxiliary functions for Bats
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
#
# lang.bash
# ---------
#
# Bash language and execution related functions. Used by public helper
# functions.
#
# Check whether the calling function was called from a given function.
#
# By default, direct invocation is checked. The function succeeds if the
# calling function was called directly from the given function. In other
# words, if the given function is the next element on the call stack.
#
# When `--indirect' is specified, indirect invocation is checked. The
# function succeeds if the calling function was called from the given
# function with any number of intermediate calls. In other words, if the
# given function can be found somewhere on the call stack.
#
# Direct invocation is a form of indirect invocation with zero
# intermediate calls.
#
# Globals:
# FUNCNAME
# Options:
# -i, --indirect - check indirect invocation
# Arguments:
# $1 - calling function's name
# Returns:
# 0 - current function was called from the given function
# 1 - otherwise
batslib_is_caller() {
local -i is_mode_direct=1
# Handle options.
while (( $# > 0 )); do
case "$1" in
-i|--indirect) is_mode_direct=0; shift ;;
--) shift; break ;;
*) break ;;
esac
done
# Arguments.
local -r func="$1"
# Check call stack.
if (( is_mode_direct )); then
[[ $func == "${FUNCNAME[2]}" ]] && return 0
else
local -i depth
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
done
fi
return 1
}

View File

@ -0,0 +1,279 @@
#
# bats-support - Supporting library for Bats test helpers
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
#
# output.bash
# -----------
#
# Private functions implementing output formatting. Used by public
# helper functions.
#
# Print a message to the standard error. When no parameters are
# specified, the message is read from the standard input.
#
# Globals:
# none
# Arguments:
# $@ - [=STDIN] message
# Returns:
# none
# Inputs:
# STDIN - [=$@] message
# Outputs:
# STDERR - message
batslib_err() {
{ if (( $# > 0 )); then
echo "$@"
else
cat -
fi
} >&2
}
# Count the number of lines in the given string.
#
# TODO(ztombol): Fix tests and remove this note after #93 is resolved!
# NOTE: Due to a bug in Bats, `batslib_count_lines "$output"' does not
# give the same result as `${#lines[@]}' when the output contains
# empty lines.
# See PR #93 (https://github.com/sstephenson/bats/pull/93).
#
# Globals:
# none
# Arguments:
# $1 - string
# Returns:
# none
# Outputs:
# STDOUT - number of lines
batslib_count_lines() {
local -i n_lines=0
local line
while IFS='' read -r line || [[ -n $line ]]; do
(( ++n_lines ))
done < <(printf '%s' "$1")
echo "$n_lines"
}
# Determine whether all strings are single-line.
#
# Globals:
# none
# Arguments:
# $@ - strings
# Returns:
# 0 - all strings are single-line
# 1 - otherwise
batslib_is_single_line() {
for string in "$@"; do
(( $(batslib_count_lines "$string") > 1 )) && return 1
done
return 0
}
# Determine the length of the longest key that has a single-line value.
#
# This function is useful in determining the correct width of the key
# column in two-column format when some keys may have multi-line values
# and thus should be excluded.
#
# Globals:
# none
# Arguments:
# $odd - key
# $even - value of the previous key
# Returns:
# none
# Outputs:
# STDOUT - length of longest key
batslib_get_max_single_line_key_width() {
local -i max_len=-1
while (( $# != 0 )); do
local -i key_len="${#1}"
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
shift 2
done
echo "$max_len"
}
# Print key-value pairs in two-column format.
#
# Keys are displayed in the first column, and their corresponding values
# in the second. To evenly line up values, the key column is fixed-width
# and its width is specified with the first parameter (possibly computed
# using `batslib_get_max_single_line_key_width').
#
# Globals:
# none
# Arguments:
# $1 - width of key column
# $even - key
# $odd - value of the previous key
# Returns:
# none
# Outputs:
# STDOUT - formatted key-value pairs
batslib_print_kv_single() {
local -ir col_width="$1"; shift
while (( $# != 0 )); do
printf '%-*s : %s\n' "$col_width" "$1" "$2"
shift 2
done
}
# Print key-value pairs in multi-line format.
#
# The key is displayed first with the number of lines of its
# corresponding value in parenthesis. Next, starting on the next line,
# the value is displayed. For better readability, it is recommended to
# indent values using `batslib_prefix'.
#
# Globals:
# none
# Arguments:
# $odd - key
# $even - value of the previous key
# Returns:
# none
# Outputs:
# STDOUT - formatted key-value pairs
batslib_print_kv_multi() {
while (( $# != 0 )); do
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
printf '%s\n' "$2"
shift 2
done
}
# Print all key-value pairs in either two-column or multi-line format
# depending on whether all values are single-line.
#
# If all values are single-line, print all pairs in two-column format
# with the specified key column width (identical to using
# `batslib_print_kv_single').
#
# Otherwise, print all pairs in multi-line format after indenting values
# with two spaces for readability (identical to using `batslib_prefix'
# and `batslib_print_kv_multi')
#
# Globals:
# none
# Arguments:
# $1 - width of key column (for two-column format)
# $even - key
# $odd - value of the previous key
# Returns:
# none
# Outputs:
# STDOUT - formatted key-value pairs
batslib_print_kv_single_or_multi() {
local -ir width="$1"; shift
local -a pairs=( "$@" )
local -a values=()
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
values+=( "${pairs[$i]}" )
done
if batslib_is_single_line "${values[@]}"; then
batslib_print_kv_single "$width" "${pairs[@]}"
else
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
done
batslib_print_kv_multi "${pairs[@]}"
fi
}
# Prefix each line read from the standard input with the given string.
#
# Globals:
# none
# Arguments:
# $1 - [= ] prefix string
# Returns:
# none
# Inputs:
# STDIN - lines
# Outputs:
# STDOUT - prefixed lines
batslib_prefix() {
local -r prefix="${1:- }"
local line
while IFS='' read -r line || [[ -n $line ]]; do
printf '%s%s\n' "$prefix" "$line"
done
}
# Mark select lines of the text read from the standard input by
# overwriting their beginning with the given string.
#
# Usually the input is indented by a few spaces using `batslib_prefix'
# first.
#
# Globals:
# none
# Arguments:
# $1 - marking string
# $@ - indices (zero-based) of lines to mark
# Returns:
# none
# Inputs:
# STDIN - lines
# Outputs:
# STDOUT - lines after marking
batslib_mark() {
local -r symbol="$1"; shift
# Sort line numbers.
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
local line
local -i idx=0
while IFS='' read -r line || [[ -n $line ]]; do
if (( ${1:--1} == idx )); then
printf '%s\n' "${symbol}${line:${#symbol}}"
shift
else
printf '%s\n' "$line"
fi
(( ++idx ))
done
}
# Enclose the input text in header and footer lines.
#
# The header contains the given string as title. The output is preceded
# and followed by an additional newline to make it stand out more.
#
# Globals:
# none
# Arguments:
# $1 - title
# Returns:
# none
# Inputs:
# STDIN - text
# Outputs:
# STDOUT - decorated text
batslib_decorate() {
echo
echo "-- $1 --"
cat -
echo '--'
echo
}

402
portable/tomb Executable file
View File

@ -0,0 +1,402 @@
#!/bin/sh
#
# Tomb v3, the Crypto Undertaker portable version 3
#
# A commandline tool to easily operate encryption of secret data
#
# {{{ License
# Copyright (C) 2007-2022 Dyne.org Foundation
#
# Tomb is designed, written and maintained by Denis Roio <jaromil@dyne.org>
#
# Please refer to the AUTHORS file for more information.
#
# This source code is free software; you can redistribute it and/or
# modify it under the terms of the GNU Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This source code is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please refer
# to the GNU Public License for more details.
#
# You should have received a copy of the GNU Public License along with
# this source code; if not, , see <https://www.gnu.org/licenses/>.
# }}} - License
# {{{ Global variables
VERSION="3.1.0"
DATE="Nov/2022"
TOMBEXEC="$0"
TMPDIR="${TMP:-/tmp}"
TOMBTMPFILES=""
PATH="${PATH}:.:/usr/local/bin"
exitcode=0
# }}}
# {{{ Logging functions
_success() {
echo "[*] " "$1" "$2" "$3" "$4" 1>&2
}
_message() {
echo " . " "$1" "$2" "$3" "$4" 1>&2
}
_warning() {
echo "[W] " "$1" "$2" "$3" "$4" 1>&2
}
_verbose() {
echo "[D] " "$1" "$2" "$3" "$4" 1>&2
}
_error() {
echo "[!] " "$1" "$2" "$3" "$4" 1>&2
exitcode=1
}
_failure() {
echo "[!!] " "$1" "$2" "$3" "$4" 1>&2
exitcode=1
exit 1
}
# }}}
_success "Starting Tomb v$VERSION"
# {{{ Internal functions
_random_string() {
len=${1:-32}
[ "$1" != "" ] && {
echo "print(O.random($len):base58())" | zenroom 2>/dev/null
}
return $?
}
_tmp_create() {
[ -d "$TMPDIR" ] || {
# we create the tempdir with the sticky bit on
mkdir -m 1777 "$TMPDIR"
[ $? = 0 ] || {
_failure "Fatal error creating the temporary directory: %s" "$TMPDIR"
}
}
tfile="${TMPDIR}/`_random_string 64`" # Temporary file
umask 066
[ $? = 0 ] || {
_failure "Fatal error setting the permission umask for temporary files"
}
[ -r "$tfile" ] && {
_failure "Someone is messing up with us trying to hijack temporary files."
}
touch "$tfile"
[ $? = 0 ] || {
_failure "Fatal error creating a temporary file: %s" "$tfile"
}
_verbose "Created tempfile: ::1 temp file::" "$tfile"
TOMBTMP="$tfile"
TOMBTMPFILES="$TOMBTMPFILES:$tfile"
return 0
}
# }}}
# {{{ FreeBSD
freebsd_mount() {
file="$1"
mnt="$2"
_verbose `veracrypt -l "$file"`
loop=`veracrypt -l "$file" | awk '{print $3}'`
_verbose "fsck $loop"
fsck.ext4 -p -C0 "$loop"
lklfuse -o type=ext4 "${loop}" "$mnt"
return $?
}
freebsd_close() {
file="$1"
md=`veracrypt -l "$file" | awk '{print $3}'`
# umount "$mnt"
_verbose "md: $md"
mnt=`pgrep -lf "lklfuse.*$md" | awk '{print $6}'`
_verbose "mnt: $mnt"
lkl=`pgrep -f "lklfuse.*$md" | awk '{print $1}'`
_verbose "kill $lkl (lklfuse on $md)"
# trying to deail with lklfuse bug
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=239831
renice -20 $lkl
kill $lkl
sync
sleep 1
kill -9 $lkl
# lkl should have really exited now
_verbose "veracrypt -d $file"
veracrypt --text --non-interactive -d "$file"
return $?
}
# }}}
# {{{ Linux
# usage: _mount /tmp/.veracrypt_ mountpoint
linux_mount() {
file="$1"
mnt="$2"
veralist=`veracrypt -l "$file" | awk '{print($2,":",$3,":",$4)}'`
[ "$veralist" = "" ] && {
_error "Cannot mount tomb not yet mapped " "$file"
return 1
}
loop=`echo $veralist | cut -d: -f2 | xargs`
[ "`echo $veralist | cut -d: -f3 | xargs`" != "-" ] && {
_error "Tomb already mounted " "$file on $loop"
return 1
}
_verbose "fsck $loop"
fsck.ext4 -p -C0 "$loop" 1>&2
_verbose "linux_mount $mnt"
mount "$loop" "$mnt"
return $?
}
# }}}
# {{{ POSIX portable
# usage: echo PASSWORD | posix_create file size pim
posix_create() {
file="$1" # must not exist
size="$2" # veracrypt format (accepts M or G)
pim="$3" # any number
_verbose "posix_create $file $size $pim"
veracrypt --text --non-interactive --stdin \
-m nokernelcrypto \
-c "$file" --volume-type normal \
--hash sha512 --encryption serpent-aes \
--filesystem none --size "$size" --pim "$pim" \
--random-source /dev/urandom -k ''
return $?
}
posix_format() {
file="$1"
loop=`veracrypt -l "$file" | awk '{print $3}'`
_verbose "posix_format: ${loop}"
mkfs.ext4 -L "`basename $file`" "$loop" # -E root_owner="${user_uid}:${user_gid}" "$loop"
return $?
}
# usage: echo PASSWORD | posix_map file pim
posix_map() {
file="$1"
pim="$2"
_verbose "posix_map $file $pim"
veracrypt --text --non-interactive --stdin \
--protect-hidden no -m nokernelcrypto \
-k '' --pim "$pim" --filesystem none \
"$file"
return $?
}
posix_close() {
file="$1"
_verbose "posix_close $file"
veracrypt --text --non-interactive -d "$file"
return $?
}
# }}}
# {{{ Initialization
system="unknown"
create=""
format=""
map=""
mount=""
close=""
tomb_init() {
system="`uname -s`"
case "$system" in
FreeBSD)
cat <<EOF
create=posix_create
format=posix_format
map=posix_map
mount=freebsd_mount
close=freebsd_close
EOF
;;
Linux)
cat <<EOF
create=posix_create
format=posix_format
map=posix_map
mount=linux_mount
close=posix_close
EOF
;;
*)
_failure "Unsupported system: %s" "$system"
esac
echo "system=$system"
# _verbose "system detected: %s" $system
}
# }}}
# {{{ Main
[ "$1" = "source" ] && return 0
eval "`tomb_init`"
tombfile=""
tombsize=""
PIM=69
cmd="$1"
shift 1
case "$cmd" in
dig)
args=2
while [ $args -gt 0 ]; do
[ "$1" = '-s' ] && { tombsize="$2"; shift 2; }
[ "$1" != "" ] && { tombfile="$1"; shift 1; }
args=$(( $args - 1 ))
done
[ "$tombfile" = "" ] && _failure "Missing path to tomb"
[ "$tombsize" = "" ] && _failure "Size argument missing, use -s"
# TODO: check if size is integer
# [ "$tombsize" -ge 10 ] || _failure "Tombs can't be smaller than 10 mebibytes"
[ -e "$tombfile" ] && {
_error "A tomb exists already. I'm not digging here:"
ls -l "$tombfile"; exit 1
}
_message "Commanded to dig tomb " "$tombfile" " sized $tombsize"
touch "$tombfile" || _failure "Error creating the tomb " "$tombfile"
# dd if=/dev/urandom bs=1048576 count=$tombsize of="$tombfile" || {
# _failure "Error creating the tomb " "$tombfile"
# exit 1
# }
if [ "$system" = "Linux" ]; then
fallocate -x -l "$tombsize" "$tombfile" ||
_failure "Error creating the tomb " "$tombfile"
else
bytesize="`tomb-str2size $tombsize`"
[ "$bytesize" = "" ] && _failure "Error reading tomb size " "$tombsize"
dd if=/dev/zero of="$tombfile" count="$(( bytesize / 1048576))" bs=1048576 ||
_failure "Error creating the tomb " "$tombfile of $tombsize"
fi
;;
forge)
args=1
[ "$1" = "" ] && _failure "Missing argument in command " "forge"
while [ $args -gt 0 ]; do
case "$1" in
'-k') tombkey="$2"; shift 2 ;;
*) tombkey="$1"; shift 1 ;;
esac
args=$(( $args - 1 ))
done
[ "$tombkey" = "" ] && _failure "Missing path to tomb key"
[ -e "$tombkey" ] && {
_error "A tomb key exists already. I'm not forging here:"
ls -l "$tombkey"; exit 1
}
_message "Commanded to forge tomb key " "$tombkey"
touch "$tombkey" || _failure "Error creating the tomb key " "$tombkey"
cat <<EOF | zenroom -z | cut -d\" -f4 > "$tombkey"
rule check version 2.0.0
Given nothing
When I create the random object of '64' bytes
Then print the 'random object' as 'hex'
EOF
;;
lock)
args=2
while [ $args -gt 0 ]; do
case "$1" in
'-k') tombkey="$2"; shift 2 ;;
*) tombfile="$1"; shift 1 ;;
esac
args=$(( $args - 1 ))
done
case "$system" in
Linux)
tombsize=`stat "$tombfile" | awk '/Size:/ {print $2; exit}'`
;;
*)
tombsize=`stat -f '%z' "$tombfile"`
;;
esac
_message "Commanded to lock tomb " "$tombfile of $tombsize with key $tombkey"
[ "$tombfile" = "" ] && _failure "Missing path to tomb"
[ -r "$tombfile" ] || _failure "Tomb file not readable"
[ "$tombkey" = "" ] && _failure "Missing path to key"
[ -r "$tombkey" ] || _failure "Key file not readable"
"$create" "$tombfile" "$tombsize" "$PIM" < "$tombkey" || {
_failure "Error creating the tomb " "$tombfile"
exit 1
}
"$map" "$tombfile" "$PIM" < "$tombkey" ||
_failure "Error mapping the tomb " "$tombfile with key $tombkey"
"$format" "$tombfile" ||
_error "Error formatting tomb " "$tombfile"
"$close" "$tombfile"
[ $exitcode = 0 ] &&
_success "Success locking tomb " "$tombfile with key $tombkey"
;;
open)
args=3
while [ $args -gt 0 ]; do
case "$1" in
'-k') tombkey="$2"; shift 2 ;;
*) if [ "$tombfile" = "" ]; then
tombfile="$1"
else
tombmount="$1"
fi
shift 1 ;;
esac
args=$(( $args - 1 ))
done
_message "Commanded to open tomb " "$tombfile with key $tombkey on $tombmount"
[ "$tombfile" = "" ] && _failure "Missing path to tomb"
[ -r "$tombfile" ] || _failure "Tomb file not readable"
[ "$tombkey" = "" ] && _failure "Missing path to key"
[ -r "$tombkey" ] || _failure "Key file not readable"
[ "$tombmount" = "" ] && tombmount="`basename $tombfile`"
"$map" "$tombfile" "$PIM" < "$tombkey"
[ $? != 0 ] &&
_failure "Error mapping the tomb " "$tombfile with key $tombkey"
"$mount" "$tombfile" "$tombmount" || {
"$close" "$tombfile"
_failure "Error mounting the tomb " "$tombfile on $tombmount"
}
;;
close)
# args=1
if [ "$1" = "all" ]; then
"$close"
elif [ -e "$1" ]; then
"$close" "`realpath $1`" ||
_failure "Error closing " "$1"
elif [ "$1" = "" ]; then
_failure "Missing argument: tomb file or mountpoint"
else
_failure "Wrong argument: $1"
fi
;;
list)
veracrypt -l
;;
esac
# }}}
exit 0