diff --git a/.jules/bolt.md b/.jules/bolt.md index 1a8e67d..86b56f6 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -4,6 +4,9 @@ ## 2026-01-16 - [Shell Dependency Check Overhead] **Learning:** `lib/core/common.sh` functions like `safe_json_get_key` re-check dependencies (`command -v jq`) on every invocation. In tight loops, this adds significant overhead (~13% in micro-benchmark). **Action:** Cache dependency checks in global variables (e.g., `_JSON_PROCESSOR_CACHE`) when the script is sourced, rather than checking inside hot functions. +## 2025-05-22 - [Awk vs Grep Loop] +**Learning:** Attempted to replace a loop of `grep` calls (checking multiple strings in a file) with a single `awk` script using `tolower($0) ~ pattern`. Benchmarking showed `awk` was significantly slower (~3x) because `grep`'s internal text search optimization (C implementation) far outperforms `awk`'s interpreted regex matching loop, even when avoiding multiple process spawns. +**Action:** Stick to multiple `grep` calls for simple string searching unless the pattern complexity or number of patterns is very high. ## 2025-02-18 - [Bash Subshell Caching] **Learning:** Caching detection results in a shell function (e.g. `get_json_processor`) is ineffective if the function is commonly called inside command substitution `$(...)`, as variables set in the subshell are lost. **Action:** Detect and export the cached value at the library source time (parent shell) so subshells inherit it. diff --git a/lib/task_manager/simple.sh b/lib/task_manager/simple.sh index 493fa46..d124bca 100755 --- a/lib/task_manager/simple.sh +++ b/lib/task_manager/simple.sh @@ -97,22 +97,47 @@ create_task() { local depends_on="${4:-}" local tags="${5:-}" + if [ -z "$description" ]; then + if command -v log_error >/dev/null; then + log_error "Task description required" + else + echo "Error: Task description required" >&2 + fi + return 1 + fi + init_tasks - local id=$(generate_hierarchical_id "$parent") local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u +"%Y-%m-%d %H:%M:%S") + local id="" if [ "$JQ_CMD" = "jq" ]; then - # Optimized single-pass jq execution for array creation and appending - jq --arg id "$id" \ - --arg desc "$description" \ + # Generate random suffix for potential use (if root task) + local random_suffix=$(generate_id) + + # Optimized single-pass jq execution: count children (if needed), generate ID, and append + # Outputs the new ID to stderr for capture to avoid second read + jq --arg desc "$description" \ --arg priority "$priority" \ --arg parent "${parent:-null}" \ --arg tags "$tags" \ --arg depends "$depends_on" \ --arg timestamp "$timestamp" \ - '.tasks += [{ - id: $id, + --arg random_suffix "$random_suffix" \ + ' + (if $parent == "null" or $parent == "" then + "task-" + $random_suffix + else + # Count children for hierarchical ID + ([.tasks[] | select(.parent == $parent)] | length) as $count | + $parent + "." + ($count + 1 | tostring) + end) as $new_id | + + # Side effect: print ID to stderr + ($new_id | stderr) as $ignored | + + .tasks += [{ + id: $new_id, description: $desc, status: "todo", priority: $priority, @@ -122,8 +147,22 @@ create_task() { comments: [], created_at: $timestamp, updated_at: $timestamp - }] | .next_id += 1' "$TASKS_FILE" > "${TASKS_FILE}.tmp" && mv "${TASKS_FILE}.tmp" "$TASKS_FILE" + }] | .next_id += 1' "$TASKS_FILE" > "${TASKS_FILE}.tmp" 2> "${TASKS_FILE}.id" + + if [ $? -eq 0 ]; then + mv "${TASKS_FILE}.tmp" "$TASKS_FILE" + if [ -f "${TASKS_FILE}.id" ]; then + id=$(cat "${TASKS_FILE}.id") + rm "${TASKS_FILE}.id" + fi + else + rm -f "${TASKS_FILE}.tmp" "${TASKS_FILE}.id" + echo "Error: Failed to create task using jq" >&2 + return 1 + fi + elif [ "$JQ_CMD" = "python3" ]; then + id=$(generate_hierarchical_id "$parent") python3 < + + + + + + +# Bats-core: Bash Automated Testing System + +Bats is a [TAP](https://testanything.org/)-compliant testing framework for Bash +3.2 or above. It provides a simple way to verify that the UNIX programs you +write behave as expected. + +A Bats test file is a Bash script with special syntax for defining test cases. +Under the hood, each test case is just a function with a description. + +```bash +#!/usr/bin/env bats + +@test "addition using bc" { + result="$(echo 2+2 | bc)" + [ "$result" -eq 4 ] +} + +@test "addition using dc" { + result="$(echo 2 2+p | dc)" + [ "$result" -eq 4 ] +} +``` + +Bats is most useful when testing software written in Bash, but you can use it to +test any UNIX program. + +Test cases consist of standard shell commands. Bats makes use of Bash's +`errexit` (`set -e`) option when running test cases. If every command in the +test case exits with a `0` status code (success), the test passes. In this way, +each line is an assertion of truth. + +## Table of contents + +**NOTE** The documentation has moved to + + + +- [Testing](#testing) +- [Support](#support) +- [Contributing](#contributing) +- [Contact](#contact) +- [Version history](#version-history) +- [Background](#background) + * [What's the plan and why?](#whats-the-plan-and-why) + * [Why was this fork created?](#why-was-this-fork-created) +- [Copyright](#copyright) + + + +## Testing + +```sh +bin/bats --tap test +``` + +See also the [CI](./.github/workflows/tests.yml) settings for the current test environment and +scripts. + +## Support + +The Bats source code repository is [hosted on +GitHub](https://github.com/bats-core/bats-core). There you can file bugs on the +issue tracker or submit tested pull requests for review. + +For real-world examples from open-source projects using Bats, see [Projects +Using Bats](https://github.com/bats-core/bats-core/wiki/Projects-Using-Bats) on +the wiki. + +To learn how to set up your editor for Bats syntax highlighting, see [Syntax +Highlighting](https://github.com/bats-core/bats-core/wiki/Syntax-Highlighting) +on the wiki. + +## Contributing + +For now see the [`docs`](docs) folder for project guides, work with us on the wiki +or look at the other communication channels. + +## Contact + +- You can find and chat with us on our [Gitter]. + +## Version history + +See `docs/CHANGELOG.md`. + +## Background + + +### Why was this fork created? + + +There was an initial [call for maintainers][call-maintain] for the original Bats repository, but write access to it could not be obtained. With development activity stalled, this fork allowed ongoing maintenance and forward progress for Bats. + +**Tuesday, September 19, 2017:** This was forked from [Bats][bats-orig] at +commit [0360811][]. It was created via `git clone --bare` and `git push +--mirror`. + +As of **Thursday, April 29, 2021:** the original [Bats][bats-orig] has been +archived by the owner and is now read-only. + +This [bats-core](https://github.com/bats-core/bats-core) repo is now the community-maintained Bats project. + +[call-maintain]: https://github.com/sstephenson/bats/issues/150 +[bats-orig]: https://github.com/sstephenson/bats +[0360811]: https://github.com/sstephenson/bats/commit/03608115df2071fff4eaaff1605768c275e5f81f + +## Copyright + +The Bats Logo was created by [Vukory](https://www.artstation.com/vukory) ([Github](https://github.com/vukory)) and sponsored by [SethFalco](https://github.com/SethFalco). If you want to use our logo, have a look at our [guidelines](./docs/source/assets/README.md#Usage-Guide-for-Third-Parties). + +© 2017-2024 bats-core organization + +© 2011-2016 Sam Stephenson + +Bats is released under an MIT-style license; see `LICENSE.md` for details. + +See the [parent project](https://github.com/bats-core) at GitHub or the +[AUTHORS](AUTHORS) file for the current project maintainer team. + +[gitter]: https://gitter.im/bats-core/bats-core diff --git a/node_modules/bats/bin/bats b/node_modules/bats/bin/bats new file mode 100755 index 0000000..25ad051 --- /dev/null +++ b/node_modules/bats/bin/bats @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Note: We first need to use POSIX's `[ ... ]' instead of Bash's `[[ ... ]]' +# because this is the check for Bash, where the shell may not be Bash. Once we +# confirm that we are in Bash, we can use [[ ... ]] and (( ... )). Note that +# these [[ ... ]] and (( ... )) do not cause syntax errors in POSIX shells, +# though they can be parsed differently. +if [ -z "${BASH_VERSION-}" ] || + [[ -z "${BASH_VERSINFO-}" ]] || + ((BASH_VERSINFO[0] < 3 || (BASH_VERSINFO[0] == 3 && BASH_VERSINFO[1] < 2))) +then + printf 'bats: this program needs to be run by Bash >= 3.2\n' >&2 + exit 1 +fi + +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", + # , , , , + # , , , + # 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_SAVED_PATH=$PATH +BATS_BASE_LIBDIR=lib # this will be patched with the true value in install.sh + +export BATS_ROOT=${BATS_PATH%/*/*} +export -f bats_readlinkf +exec env BATS_ROOT="$BATS_ROOT" BATS_LIBDIR="${BATS_BASE_LIBDIR:-lib}" "$BATS_ROOT/libexec/bats-core/bats" "$@" diff --git a/node_modules/bats/install.sh b/node_modules/bats/install.sh new file mode 100755 index 0000000..edc26af --- /dev/null +++ b/node_modules/bats/install.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -e + +BATS_ROOT="${0%/*}" +PREFIX="${1%/}" +LIBDIR="${2:-lib}" + +if [[ -z "$PREFIX" ]]; then + printf '%s\n' \ + "usage: $0 [base_libdir]" \ + " e.g. $0 /usr/local" \ + " $0 /usr/local lib64" >&2 + exit 1 +fi + +install -d -m 755 "$PREFIX"/{bin,libexec/bats-core,"${LIBDIR}"/bats-core,share/man/man{1,7}} + +install -m 755 "$BATS_ROOT/bin"/* "$PREFIX/bin" +install -m 755 "$BATS_ROOT/libexec/bats-core"/* "$PREFIX/libexec/bats-core" +install -m 755 "$BATS_ROOT/lib/bats-core"/* "$PREFIX/${LIBDIR}/bats-core" +install -m 644 "$BATS_ROOT/man/bats.1" "$PREFIX/share/man/man1" +install -m 644 "$BATS_ROOT/man/bats.7" "$PREFIX/share/man/man7" + +read -rd '' BATS_EXE_CONTENTS <"$PREFIX/bin/bats" || true +BATS_EXE_CONTENTS=${BATS_EXE_CONTENTS/"BATS_BASE_LIBDIR=lib"/"BATS_BASE_LIBDIR=${LIBDIR}"} +printf "%s" "$BATS_EXE_CONTENTS" >| "$PREFIX/bin/bats" + +echo "Installed Bats to $PREFIX/bin/bats" diff --git a/node_modules/bats/lib/bats-core/common.bash b/node_modules/bats/lib/bats-core/common.bash new file mode 100644 index 0000000..2bd1a5b --- /dev/null +++ b/node_modules/bats/lib/bats-core/common.bash @@ -0,0 +1,278 @@ +#!/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() { # + 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 ..!\n" "$1" >&2 + exit 1 + fi +} + +# compares two versions. Return 0 when version1 < version2 +bats_version_lt() { # + 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" + + local -i i + 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() { # + 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 +} + +# returns 0 when search-value is found in array +bats_linear_reverse_search() { # + local -r search_value=$1 array_name=$2 + eval "local -ri array_length=\${#${array_name}[@]}" + # shellcheck disable=SC2154 + for ((i=array_length - 1; i >=0; --i)); do + eval "local value=\"\${${array_name}[i]}\"" + # shellcheck disable=SC2154 + if [[ $value == "$search_value" ]]; then + return 0 + fi + done + return 1 +} + +# returns 0 when search-value is found in array +bats_binary_search() { # + if [[ $# -ne 2 ]]; then + printf "ERROR: bats_binary_search requires exactly 2 arguments: \n" >&2 + return 2 + fi + + local -r search_value=$1 array_name=$2 + + # we'd like to test if array is set but we cannot distinguish unset from empty arrays, so we need to skip that + + local start=0 mid end mid_value + # start is inclusive, end is exclusive ... + eval "end=\${#${array_name}[@]}" + + # so start == end means empty search space + while ((start < end)); do + mid=$(((start + end) / 2)) + eval "mid_value=\${${array_name}[$mid]}" + if [[ "$mid_value" == "$search_value" ]]; then + return 0 + elif [[ "$mid_value" < "$search_value" ]]; then + # This branch excludes equality -> +1 to skip the mid element. + # This +1 also avoids endless recursion on odd sized search ranges. + start=$((mid + 1)) + else + end=$mid + fi + done + + # did not find it -> its not there + return 1 +} + +# store the values in ascending (string!) order in result array +# Intended for short lists! (uses insertion sort) +bats_sort() { # + local -r result_name=$1 + shift + + if (($# == 1)) && [[ $1 == "" ]]; then + shift # remove leading "" to deal with bash 3 expanding "${empty_array[@]}" to one "" + fi + + if (($# == 0)); then + eval "$result_name=()" + return 0 + fi + + local -a sorted_array=() + local -i i + while (( $# > 0 )); do # loop over input values + local current_value="$1" + shift + for ((i = ${#sorted_array[@]}; i >= 0; --i)); do # loop over output array from end + if (( i == 0 )) || [[ ${sorted_array[i - 1]} < $current_value ]]; then + # insert new element at (freed) desired location + sorted_array[i]=$current_value + break + else + # shift bigger elements one position to the end + sorted_array[i]=${sorted_array[i - 1]} + fi + done + done + + eval "$result_name=(\"\${sorted_array[@]}\")" +} + +# check if all search values (must be sorted!) are in the (sorted!) array +# Intended for short lists/arrays! +bats_all_in() { # + local -r haystack_array=$1 + shift + + local -i haystack_length # just to appease shellcheck + eval "local -r haystack_length=\${#${haystack_array}[@]}" + + local -i haystack_index=0 # initialize only here to continue from last search position + local search_value haystack_value # just to appease shellcheck + local -i i + for ((i = 1; i <= $#; ++i)); do + eval "local search_value=${!i}" + for (( ; haystack_index < haystack_length; ++haystack_index)); do + eval "local haystack_value=\${${haystack_array}[$haystack_index]}" + if [[ $haystack_value > "$search_value" ]]; then + # we passed the location this value would have been at -> not found + return 1 + elif [[ $haystack_value == "$search_value" ]]; then + continue 2 # search value found -> try the next one + fi + done + return 1 # we ran of the end of the haystack without finding the value! + done + + # did not return from loop above -> all search values were found + return 0 +} + +# check if any search value (must be sorted!) is in the (sorted!) array +# intended for short lists/arrays +bats_any_in() { # + local -r haystack_array=$1 + shift + + local -i haystack_length # just to appease shellcheck + eval "local -r haystack_length=\${#${haystack_array}[@]}" + + local -i haystack_index=0 # initialize only here to continue from last search position + local search_value haystack_value # just to appease shellcheck + local -i i + for ((i = 1; i <= $#; ++i)); do + eval "local search_value=${!i}" + for (( ; haystack_index < haystack_length; ++haystack_index)); do + eval "local haystack_value=\${${haystack_array}[$haystack_index]}" + if [[ $haystack_value > "$search_value" ]]; then + continue 2 # search value not in array! -> try next + elif [[ $haystack_value == "$search_value" ]]; then + return 0 # search value found + fi + done + done + + # did not return from loop above -> no search value was found + return 1 +} + +bats_trim() { # + local -r bats_trim_ltrimmed=${2#"${2%%[![:space:]]*}"} # cut off leading whitespace + # shellcheck disable=SC2034 # used in eval! + local -r bats_trim_trimmed=${bats_trim_ltrimmed%"${bats_trim_ltrimmed##*[![:space:]]}"} # cut off trailing whitespace + eval "$1=\$bats_trim_trimmed" +} + +# a helper function to work around unbound variable errors with ${arr[@]} on Bash 3 +bats_append_arrays_as_args() { # -- + local -a trailing_args=() + while (($# > 0)) && [[ $1 != -- ]]; do + local array=$1 + shift + + if eval "(( \${#${array}[@]} > 0 ))"; then + eval "trailing_args+=(\"\${${array}[@]}\")" + fi + done + shift # remove -- separator + + if (($# == 0)); then + printf "Error: append_arrays_as_args is missing a command or -- separator\n" >&2 + return 1 + fi + + if ((${#trailing_args[@]} > 0)); then + "$@" "${trailing_args[@]}" + else + "$@" + fi +} + +bats_format_file_line_reference() { # + # shellcheck disable=SC2034 # will be used in subimplementation + local output="${1?}" + shift + "bats_format_file_line_reference_${BATS_LINE_REFERENCE_FORMAT?}" "$@" +} + +bats_format_file_line_reference_comma_line() { + printf -v "$output" "%s, line %d" "$@" +} + +bats_format_file_line_reference_colon() { + printf -v "$output" "%s:%d" "$@" +} + +# approximate realpath without subshell +bats_approx_realpath() { # + local output=$1 path=$2 + if [[ $path != /* ]]; then + path="$PWD/$path" + fi + # x/./y -> x/y + path=${path//\/.\//\/} + printf -v "$output" "%s" "$path" +} + +bats_format_file_line_reference_uri() { + local filename=${1?} line=${2?} + bats_approx_realpath filename "$filename" + printf -v "$output" "file://%s:%d" "$filename" "$line" +} + +# execute command with backed up path +# to prevent path mocks from interfering with our internals +bats_execute() { # + PATH="${BATS_SAVED_PATH?}" "$@" +} diff --git a/node_modules/bats/lib/bats-core/formatter.bash b/node_modules/bats/lib/bats-core/formatter.bash new file mode 100644 index 0000000..aaf8096 --- /dev/null +++ b/node_modules/bats/lib/bats-core/formatter.bash @@ -0,0 +1,151 @@ +#!/usr/bin/env bash + +# reads (extended) bats tap streams from stdin and calls callback functions for each line +# +# Segmenting functions +# ==================== +# bats_tap_stream_plan -> when the test plan is encountered +# bats_tap_stream_suite -> when a new file is begun WARNING: extended only +# bats_tap_stream_begin -> when a new test is begun WARNING: extended only +# +# Test result functions +# ===================== +# If timing was enabled, BATS_FORMATTER_TEST_DURATION will be set to their duration in milliseconds +# bats_tap_stream_ok -> when a test was successful +# bats_tap_stream_not_ok -> when a test has failed. If the failure was due to a timeout, +# BATS_FORMATTER_TEST_TIMEOUT is set to the timeout duration in seconds +# bats_tap_stream_skipped -> when a test was skipped +# +# Context functions +# ================= +# bats_tap_stream_comment -> when a comment line was encountered, +# scope tells the last encountered of plan, begin, ok, not_ok, skipped, suite +# bats_tap_stream_unknown -> when a line is encountered that does not match the previous entries, +# scope @see bats_tap_stream_comment +# forwards all input as is, when there is no TAP test plan header +function bats_parse_internal_extended_tap() { + local header_pattern='[0-9]+\.\.[0-9]+' + IFS= read -r header + + if [[ "$header" =~ $header_pattern ]]; then + bats_tap_stream_plan "${header:3}" + else + # If the first line isn't a TAP plan, print it and pass the rest through + printf '%s\n' "$header" + exec cat + fi + + ok_line_regexpr="ok ([0-9]+) (.*)" + skip_line_regexpr="ok ([0-9]+) (.*) # skip( (.*))?$" + timeout_line_regexpr="not ok ([0-9]+) (.*) # timeout after ([0-9]+)s$" + not_ok_line_regexpr="not ok ([0-9]+) (.*)" + + timing_expr="in ([0-9]+)ms$" + local test_name begin_index last_begin_index try_index ok_index not_ok_index index scope + begin_index=0 + last_begin_index=-1 + try_index=0 + index=0 + scope=plan + while IFS= read -r line; do + unset BATS_FORMATTER_TEST_DURATION BATS_FORMATTER_TEST_TIMEOUT + case "$line" in + 'begin '*) # this might only be called in extended tap output + scope=begin + begin_index=${line#begin } + begin_index=${begin_index%% *} + if [[ $begin_index == "$last_begin_index" ]]; then + (( ++try_index )) + else + try_index=0 + fi + test_name="${line#begin "$begin_index" }" + bats_tap_stream_begin "$begin_index" "$test_name" + ;; + 'ok '*) + ((++index)) + if [[ "$line" =~ $ok_line_regexpr ]]; then + ok_index="${BASH_REMATCH[1]}" + test_name="${BASH_REMATCH[2]}" + if [[ "$line" =~ $skip_line_regexpr ]]; then + scope=skipped + test_name="${BASH_REMATCH[2]}" # cut off name before "# skip" + local skip_reason="${BASH_REMATCH[4]}" + if [[ "$test_name" =~ $timing_expr ]]; then + local BATS_FORMATTER_TEST_DURATION="${BASH_REMATCH[1]}" + test_name="${test_name% in "${BATS_FORMATTER_TEST_DURATION}"ms}" + bats_tap_stream_skipped "$ok_index" "$test_name" "$skip_reason" + else + bats_tap_stream_skipped "$ok_index" "$test_name" "$skip_reason" + fi + else + scope=ok + if [[ "$line" =~ $timing_expr ]]; then + local BATS_FORMATTER_TEST_DURATION="${BASH_REMATCH[1]}" + bats_tap_stream_ok "$ok_index" "${test_name% in "${BASH_REMATCH[1]}"ms}" + else + bats_tap_stream_ok "$ok_index" "$test_name" + fi + fi + else + printf "ERROR: could not match ok line: %s" "$line" >&2 + exit 1 + fi + ;; + 'not ok '*) + ((++index)) + scope=not_ok + if [[ "$line" =~ $not_ok_line_regexpr ]]; then + not_ok_index="${BASH_REMATCH[1]}" + test_name="${BASH_REMATCH[2]}" + if [[ "$line" =~ $timeout_line_regexpr ]]; then + not_ok_index="${BASH_REMATCH[1]}" + test_name="${BASH_REMATCH[2]}" + # shellcheck disable=SC2034 # used in bats_tap_stream_ok + local BATS_FORMATTER_TEST_TIMEOUT="${BASH_REMATCH[3]}" + fi + if [[ "$test_name" =~ $timing_expr ]]; then + # shellcheck disable=SC2034 # used in bats_tap_stream_ok + local BATS_FORMATTER_TEST_DURATION="${BASH_REMATCH[1]}" + test_name="${test_name% in "${BASH_REMATCH[1]}"ms}" + fi + bats_tap_stream_not_ok "$not_ok_index" "$test_name" + else + printf "ERROR: could not match not ok line: %s" "$line" >&2 + exit 1 + fi + ;; + '# '*) + bats_tap_stream_comment "${line:2}" "$scope" + ;; + '#') + bats_tap_stream_comment "" "$scope" + ;; + 'suite '*) + scope=suite + # pass on the + bats_tap_stream_suite "${line:6}" + ;; + *) + bats_tap_stream_unknown "$line" "$scope" + ;; + esac + done +} + +normalize_base_path() { # + # 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" +} diff --git a/node_modules/bats/lib/bats-core/preprocessing.bash b/node_modules/bats/lib/bats-core/preprocessing.bash new file mode 100644 index 0000000..069cfd4 --- /dev/null +++ b/node_modules/bats/lib/bats-core/preprocessing.bash @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +bats_export_preprocess_source_BATS_TEST_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_RUN_TMPDIR/${BATS_TEST_FILE_NUMBER?}-${BATS_TEST_FILENAME##*/}.src" +} + +bats_preprocess_source() { # index + bats_export_preprocess_source_BATS_TEST_SOURCE + # shellcheck disable=SC2153 + CHECK_BATS_COMMENT_COMMANDS=1 "$BATS_ROOT/libexec/bats-core/bats-preprocess" "$BATS_TEST_FILENAME" >"$BATS_TEST_SOURCE" +} + +bats_evaluate_preprocessed_source() { + # Dynamically loaded user files provided outside of Bats. + # shellcheck disable=SC1090 + source "${BATS_TEST_SOURCE?}" +} diff --git a/node_modules/bats/lib/bats-core/semaphore.bash b/node_modules/bats/lib/bats-core/semaphore.bash new file mode 100644 index 0000000..a196ac8 --- /dev/null +++ b/node_modules/bats/lib/bats-core/semaphore.bash @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +bats_run_under_flock() { + flock "$BATS_SEMAPHORE_DIR" "$@" +} + +bats_run_under_shlock() { + local lockfile="$BATS_SEMAPHORE_DIR/shlock.lock" + while ! shlock -p $$ -f "$lockfile"; do + sleep 1 + done + # we got the lock now, execute the command + "$@" + local status=$? + # free the lock + rm -f "$lockfile" + return $status + } + +# setup the semaphore environment for the loading file +bats_semaphore_setup() { + export -f bats_semaphore_get_free_slot_count + export -f bats_semaphore_acquire_while_locked + export BATS_SEMAPHORE_DIR="$BATS_RUN_TMPDIR/semaphores" + + if command -v flock >/dev/null; then + BATS_LOCKING_IMPLEMENTATION=flock + elif command -v shlock >/dev/null; then + BATS_LOCKING_IMPLEMENTATION=shlock + else + printf "ERROR: flock/shlock is required for parallelization within files!\n" >&2 + exit 1 + fi +} + +# $1 - output directory for stdout/stderr +# $@ - command to run +# run the given command in a semaphore +# block when there is no free slot for the semaphore +# when there is a free slot, run the command in background +# gather the output of the command in files in the given directory +bats_semaphore_run() { + local output_dir=$1 + shift + local semaphore_slot + semaphore_slot=$(bats_semaphore_acquire_slot) + bats_semaphore_release_wrapper "$output_dir" "$semaphore_slot" "$@" & + printf "%d\n" "$!" +} + +# $1 - output directory for stdout/stderr +# $@ - command to run +# this wraps the actual function call to install some traps on exiting +bats_semaphore_release_wrapper() { + local output_dir="$1" + local semaphore_name="$2" + shift 2 # all other parameters will be use for the command to execute + + # shellcheck disable=SC2064 # we want to expand the semaphore_name right now! + trap "status=$?; bats_semaphore_release_slot '$semaphore_name'; exit $status" EXIT + + mkdir -p "$output_dir" + "$@" 2>"$output_dir/stderr" >"$output_dir/stdout" + local status=$? + + # bash bug: the exit trap is not called for the background process + bats_semaphore_release_slot "$semaphore_name" + trap - EXIT # avoid calling release twice + return $status +} + +bats_semaphore_acquire_while_locked() { + if [[ $(bats_semaphore_get_free_slot_count) -gt 0 ]]; then + local slot=0 + while [[ -e "$BATS_SEMAPHORE_DIR/slot-$slot" ]]; do + ((++slot)) + done + if [[ $slot -lt $BATS_SEMAPHORE_NUMBER_OF_SLOTS ]]; then + touch "$BATS_SEMAPHORE_DIR/slot-$slot" && printf "%d\n" "$slot" && return 0 + fi + fi + return 1 +} + +# block until a semaphore slot becomes free +# prints the number of the slot that it received +bats_semaphore_acquire_slot() { + mkdir -p "$BATS_SEMAPHORE_DIR" + # wait for a slot to become free + # TODO: avoid busy waiting by using signals -> this opens op prioritizing possibilities as well + while true; do + # don't lock for reading, we are fine with spuriously getting no free slot + if [[ $(bats_semaphore_get_free_slot_count) -gt 0 ]]; then + bats_run_under_"$BATS_LOCKING_IMPLEMENTATION" \ + bash -c bats_semaphore_acquire_while_locked \ + && break + fi + sleep 1 + done +} + +bats_semaphore_release_slot() { + # we don't need to lock this, since only our process owns this file + # and freeing a semaphore cannot lead to conflicts with others + rm "$BATS_SEMAPHORE_DIR/slot-$1" # this will fail if we had not acquired a semaphore! +} + +bats_semaphore_get_free_slot_count() { + # find might error out without returning something useful when a file is deleted, + # while the directory is traversed -> only continue when there was no error + until used_slots=$(find "$BATS_SEMAPHORE_DIR" -name 'slot-*' 2>/dev/null | wc -l); do :; done + echo $((BATS_SEMAPHORE_NUMBER_OF_SLOTS - used_slots)) +} diff --git a/node_modules/bats/lib/bats-core/test_functions.bash b/node_modules/bats/lib/bats-core/test_functions.bash new file mode 100644 index 0000000..dbae1f3 --- /dev/null +++ b/node_modules/bats/lib/bats-core/test_functions.bash @@ -0,0 +1,517 @@ +#!/usr/bin/env bash + +# this must be called for each test file! +_bats_test_functions_setup() { # + BATS_TEST_DIRNAME="${BATS_TEST_FILENAME%/*}" + BATS_TEST_NAMES=() + # shellcheck disable=SC2034 + BATS_TEST_NUMBER=${1?} +} + +# shellcheck source=lib/bats-core/warnings.bash +source "$BATS_ROOT/$BATS_LIBDIR/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. +# +# If no library is found find_in_bats_lib_path returns 1. +find_in_bats_lib_path() { # + 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 or a relative path. +# +# If the slug is not an absolute path it is resolved relative to +# BATS_TEST_DIRNAME +# +# The resolved slug is passed to bats_internal_load. +# If bats_internal_load fails bats_load_safe returns 1. +# +# If no library load path can be found bats_load_safe prints an error message +# and returns 1. +bats_load_safe() { + local slug="${1:?}" + if [[ ${slug:0:1} != / ]]; then # relative paths are relative to BATS_TEST_DIRNAME + slug="$BATS_TEST_DIRNAME/$slug" + fi + + if [[ -f "$slug.bash" ]]; then + bats_internal_load "$slug.bash" + return $? + elif [[ -f "$slug" ]]; then + bats_internal_load "$slug" + return $? + fi + + # loading from PATH (retained for backwards compatibility) + if [[ ! -f "$1" ]] && type -P "$1" >/dev/null; then + # shellcheck disable=SC1090 + source "$1" + return $? + fi + + # No library load path can be found + printf "bats_load_safe: Could not find '%s'[.bash]\n" "$slug" >&2 + return 1 +} + +bats_load_library_safe() { # + local slug="${1:?}" library_path + + # Check for library load paths in BATS_TEST_DIRNAME and BATS_LIB_PATH + if [[ ${slug:0:1} != / ]]; then + if ! find_in_bats_lib_path library_path "$slug"; then + printf "Could not find library '%s' relative to test file or in BATS_LIB_PATH\n" "$slug" >&2 + return 1 + fi + else + # absolute paths are taken as is + library_path="$slug" + if [[ ! -f "$library_path" ]]; then + printf "Could not find library on absolute path '%s'\n" "$library_path" >&2 + return 1 + fi + fi + + bats_internal_load "$library_path" + return $? +} + +# immediately exit on error, use bats_load_library_safe to catch and handle errors +bats_load_library() { # + if ! bats_load_library_safe "$@"; then + exit 1 + fi +} + +# load acts like bats_load_safe but exits the shell instead of returning 1. +load() { + if ! bats_load_safe "$@"; then + exit 1 + fi +} + +bats_redirect_stderr_into_file() { + "$@" 2>>"$bats_run_separate_stderr_file" # use >> to see collisions' content +} + +bats_merge_stdout_and_stderr() { + "$@" 2>&1 +} + +# write separate lines from into +bats_separate_lines() { # + local -r output_array_name="$1" + local -r input_var_name="$2" + local input="${!input_var_name}" + if [[ $keep_empty_lines ]]; then + local bats_separate_lines_lines=() + if [[ -n "$input" ]]; then # avoid getting an empty line for empty input + # remove one trailing \n if it exists to compensate its addition by <<< + input=${input%$'\n'} + while IFS= read -r line; do + bats_separate_lines_lines+=("$line") + done <<<"${input}" + 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 +} + +bats_pipe() { # [-N] [--] command0 [ \| command1 [ \| command2 [...]]] + # This will run each command given, piping them appropriately. + # Meant to be used in combination with `run` helper to allow piped commands + # to be used. + # Note that `\|` must be used, not `|`. + # By default, the exit code of this command will be the last failure in the + # chain of piped commands (similar to `set -o pipefail`). + # Supplying -N (e.g. -0) will instead always use the exit code of the command + # at that position in the chain. + # --returned-status=N could be used as an alternative to -N. This also allows + # for negative values (which count from the end in reverse order). + + local pipestatus_position= + + # parse options starting with - + while [[ $# -gt 0 ]] && [[ $1 == -* ]]; do + case "$1" in + -[0-9]*) + pipestatus_position="${1#-}" + ;; + --returned-status*) + if [ "$1" = "--returned-status" ]; then + pipestatus_position="$2" + shift + elif [[ "$1" =~ ^--returned-status= ]]; then + pipestatus_position="${1#--returned-status=}" + else + printf "Usage error: unknown flag '%s'" "$1" >&2 + return 1 + fi + ;; + --) + shift # eat the -- before breaking away + break + ;; + *) + printf "Usage error: unknown flag '%s'" "$1" >&2 + return 1 + ;; + esac + shift + done + + # parse and validate arguments, escape as necessary + local -a commands_and_args=("$@") + local -a escaped_args=() + local -i pipe_count=0 + local -i previous_pipe_index=-1 + local -i index=0 + for (( index = 0; index < $#; index++ )); do + local current_command_or_arg="${commands_and_args[$index]}" + local escaped_arg="$current_command_or_arg" + if [[ "$current_command_or_arg" != '|' ]]; then + # escape args to protect them when eval'd (e.g. if they contain whitespace). + printf -v escaped_arg "%q" "$current_command_or_arg" + elif [ "$current_command_or_arg" = "|" ]; then + if [ "$index" -eq 0 ]; then + printf "Usage error: Cannot have leading \`\\|\`.\n" >&2 + return 1 + fi + if (( (previous_pipe_index + 1) >= index )); then + printf "Usage error: Cannot have consecutive \`\\|\`. Found at argument position '%s'.\n" "$index" >&2 + return 1 + fi + (( ++pipe_count )) + previous_pipe_index="$index" + fi + escaped_args+=("$escaped_arg") + done + + if (( (previous_pipe_index > 0) && (previous_pipe_index == ($# - 1)) )); then + printf "Usage error: Cannot have trailing \`\\|\`.\n" >&2 + return 1 + fi + + if (( pipe_count == 0 )); then + # Don't allow for no pipes. This might be a typo in the test, + # e.g. `run bats_pipe command0 | command1` + # instead of `run bats_pipe command0 \| command1` + # Unfortunately, we can't catch `run bats_pipe command0 \| command1 | command2`. + # But this check is better than just allowing no pipes. + printf "Usage error: No \`\\|\`s found. Is this an error?\n" >&2 + return 1 + fi + + # there will be pipe_count + 1 entries in PIPE_STATUS (pipe_count number of \|'s between each entry). + # valid indices are [-(pipe_count + 1), pipe_count] + if [ -n "$pipestatus_position" ] && (( (pipestatus_position > pipe_count) || (-pipestatus_position > (pipe_count + 1)) )); then + printf "Usage error: Too large of -N argument (or --returned-status) given. Argument value: '%s'.\n" "$pipestatus_position" >&2 + return 1 + fi + + # run commands and return appropriate pipe status + local -a __bats_pipe_eval_pipe_status=() + eval "${escaped_args[*]}" '; __bats_pipe_eval_pipe_status=(${PIPESTATUS[@]})' + + local result_status= + if [ -z "$pipestatus_position" ]; then + # if we are performing default "last failure" behavior, + # iterate backwards through pipe_status to find the last error. + result_status=0 + for index in "${!__bats_pipe_eval_pipe_status[@]}"; do + # OSX bash doesn't support negative indexing. + local backward_iter_index="$((${#__bats_pipe_eval_pipe_status[@]} - index - 1))" + local status_at_backward_iter_index="${__bats_pipe_eval_pipe_status[$backward_iter_index]}" + if (( status_at_backward_iter_index != 0 )); then + result_status="$status_at_backward_iter_index" + break; + fi + done + elif (( pipestatus_position >= 0 )); then + result_status="${__bats_pipe_eval_pipe_status[$pipestatus_position]}" + else + # Must use positive values for some bash's (like OSX). + local backward_iter_index="$((${#__bats_pipe_eval_pipe_status[@]} + pipestatus_position))" + result_status="${__bats_pipe_eval_pipe_status[$backward_iter_index]}" + fi + + return "$result_status" +} + +run() { # [!|-N] [--keep-empty-lines] [--separate-stderr] [--] + # This has to be restored on exit from this function to avoid leaking our trap INT into surrounding code. + # Non zero exits won't restore under the assumption that they will fail the test before it can be aborted, + # which allows us to avoid duplicating the restore code on every exit path + trap bats_interrupt_trap_in_run INT + local expected_rc= + local keep_empty_lines= + local output_case=merged + local has_flags= + # parse options starting with - + while [[ $# -gt 0 ]] && [[ $1 == -* || $1 == '!' ]]; do + has_flags=1 + case "$1" in + '!') + expected_rc=-1 + ;; + -[0-9]*) + expected_rc=${1#-} + if [[ $expected_rc =~ [^0-9] ]]; then + printf "Usage error: run: '-NNN' requires numeric NNN (got: %s)\n" "$expected_rc" >&2 + return 1 + elif [[ $expected_rc -gt 255 ]]; then + printf "Usage error: run: '-NNN': NNN must be <= 255 (got: %d)\n" "$expected_rc" >&2 + return 1 + fi + ;; + --keep-empty-lines) + keep_empty_lines=1 + ;; + --separate-stderr) + output_case="separate" + ;; + --) + shift # eat the -- before breaking away + break + ;; + *) + printf "Usage error: unknown flag '%s'" "$1" >&2 + return 1 + ;; + esac + shift + done + + if [[ -n $has_flags ]]; then + bats_warn_minimum_guaranteed_version "Using flags on \`run\`" 1.5.0 + fi + + local pre_command= + + case "$output_case" in + merged) # redirects stderr into stdout and fills only $output/$lines + pre_command=bats_merge_stdout_and_stderr + ;; + separate) # splits stderr into own file and fills $stderr/$stderr_lines too + local bats_run_separate_stderr_file + bats_run_separate_stderr_file="$(mktemp "${BATS_TEST_TMPDIR}/separate-stderr-XXXXXX")" + pre_command=bats_redirect_stderr_into_file + ;; + esac + + local origFlags="$-" + set +eET + if [[ $keep_empty_lines ]]; then + # 'output', 'status', 'lines' are global variables available to tests. + # preserve trailing newlines by appending . and removing it later + # shellcheck disable=SC2034 + output="$( + "$pre_command" "$@" + status=$? + printf . + exit $status + )" && status=0 || status=$? + output="${output%.}" + else + # 'output', 'status', 'lines' are global variables available to tests. + # shellcheck disable=SC2034 + output="$("$pre_command" "$@")" && status=0 || status=$? + fi + + bats_separate_lines lines output + + if [[ "$output_case" == separate ]]; then + # shellcheck disable=SC2034 + read -d '' -r stderr <"$bats_run_separate_stderr_file" || true + bats_separate_lines stderr_lines stderr + else + unset stderr stderr_lines + fi + + # shellcheck disable=SC2034 + BATS_RUN_COMMAND="${*}" + set "-$origFlags" + + bats_run_print_output() { + if [[ -n "$output" ]]; then + printf "%s\n" "$output" + fi + if [[ "$output_case" == separate && -n "$stderr" ]]; then + printf "stderr:\n%s\n" "$stderr" + fi + } + + if [[ -n "$expected_rc" ]]; then + if [[ "$expected_rc" = "-1" ]]; then + if [[ "$status" -eq 0 ]]; then + BATS_ERROR_SUFFIX=", expected nonzero exit code!" + bats_run_print_output + return 1 + fi + elif [ "$status" -ne "$expected_rc" ]; then + # shellcheck disable=SC2034 + BATS_ERROR_SUFFIX=", expected exit code $expected_rc, got $status" + bats_run_print_output + return 1 + fi + elif [[ "$status" -eq 127 ]]; then # "command not found" + bats_generate_warning 1 "$BATS_RUN_COMMAND" + fi + + if [[ ${BATS_VERBOSE_RUN:-} ]]; then + bats_run_print_output + fi + + # don't leak our trap into surrounding code + trap bats_interrupt_trap INT +} + +setup() { + return 0 +} + +teardown() { + return 0 +} + +skip() { + # if this is a skip in teardown ... + if [[ -n "${BATS_TEARDOWN_STARTED-}" ]]; then + # ... we want to skip the rest of teardown. + # communicate to bats_exit_trap that the teardown was completed without error + # shellcheck disable=SC2034 + BATS_TEARDOWN_COMPLETED=1 + # if we are already in the exit trap (e.g. due to previous skip) ... + if [[ "$BATS_TEARDOWN_STARTED" == as-exit-trap ]]; then + # ... we need to do the rest of the tear_down_trap that would otherwise be skipped after the next call to exit + bats_exit_trap + # and then do the exit (at the end of this function) + fi + # if we aren't in exit trap, the normal exit handling should suffice + else + # ... this is either skip in test or skip in setup. + # Following variables are used in bats-exec-test which sources this file + # shellcheck disable=SC2034 + BATS_TEST_SKIPPED="${1:-1}" + # shellcheck disable=SC2034 + BATS_TEST_COMPLETED=1 + fi + exit 0 +} + +bats_test_function() { + local tags=() current_tags=() + while (( $# > 0 )); do + case "$1" in + --description) + local test_description= + # use eval to resolve variable references in test names + eval "printf -v test_description '%s' \"$2\"" + shift 2 + ;; + --tags) + if [[ "$2" != "" ]]; then # avoid unbound variable with set -u Bash 3 + IFS=',' read -ra current_tags <<<"$2" + tags+=("${current_tags[@]}") + fi + shift 2 + ;; + --) + shift + break + ;; + *) + printf "ERROR: unknown option %s for bats_test_function" "$1" >&2 + exit 1 + ;; + esac + done + + if (( ${#tags[@]} > 1 )); then # avoid unbound variable with set -u Bash 3 + bats_sort tags "${tags[@]}" + fi + + BATS_TEST_NAMES+=("$*") + local quoted_parameters + printf -v quoted_parameters " %q" "$@" + quoted_parameters=${quoted_parameters:1} # cut off leading space + + # if this is the currently selected test, set tags and name + # this should only be entered from bats-exec-test + if [[ ${BATS_TEST_NAME-} == "$quoted_parameters" ]]; then + # shellcheck disable=SC2034 + BATS_TEST_TAGS=("${tags[@]+${tags[@]}}") + export BATS_TEST_DESCRIPTION="${test_description-$*}" + # shellcheck disable=SC2034 + BATS_TEST_COMMAND=("$@") + fi +} + +# 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)) +} diff --git a/node_modules/bats/lib/bats-core/tracing.bash b/node_modules/bats/lib/bats-core/tracing.bash new file mode 100644 index 0000000..0f32e84 --- /dev/null +++ b/node_modules/bats/lib/bats-core/tracing.bash @@ -0,0 +1,425 @@ +#!/usr/bin/env bash + +# shellcheck source=lib/bats-core/common.bash +source "$BATS_ROOT/$BATS_LIBDIR/bats-core/common.bash" + +# set limit such that traces are only captured for calls at the same depth as this function in the calltree +bats_set_stacktrace_limit() { + BATS_STACK_TRACE_LIMIT=$(( ${#FUNCNAME[@]} - 1 )) # adjust by -1 to account for call to this functions +} + +bats_capture_stack_trace() { + local test_file + local funcname + local i + + BATS_DEBUG_LAST_STACK_TRACE=() + local limit=$(( ${#FUNCNAME[@]} - ${BATS_STACK_TRACE_LIMIT-0} )) + # TODO: why is the line number off by one in @test "--trace recurses into functions but not into run" + for ((i = 2; i < limit ; ++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") + done +} + +bats_get_failure_stack_trace() { + local stack_trace_var + # See bats_debug_trap for details. + if [[ -n "${BATS_DEBUG_LAST_STACK_TRACE_IS_VALID:-}" ]]; then + stack_trace_var=BATS_DEBUG_LAST_STACK_TRACE + else + stack_trace_var=BATS_DEBUG_LASTLAST_STACK_TRACE + fi + # shellcheck disable=SC2016 + eval "$(printf \ + '%s=(${%s[@]+"${%s[@]}"})' \ + "${1}" \ + "${stack_trace_var}" \ + "${stack_trace_var}")" +} + +bats_print_stack_trace() { + local frame + local index=1 + local count="${#@}" + local filename + local lineno + + for frame in "$@"; do + bats_frame_filename "$frame" 'filename' + bats_trim_filename "$filename" 'filename' + bats_frame_lineno "$frame" 'lineno' + + printf '%s' "${BATS_STACK_TRACE_PREFIX-# }" + if [[ $index -eq 1 ]]; then + printf '(' + else + printf ' ' + fi + + local fn + bats_frame_function "$frame" 'fn' + if [[ "$fn" != "${BATS_TEST_NAME-}" ]] && + # don't print "from function `source'"", + # when failing in free code during `source $test_file` from bats-exec-file + ! [[ "$fn" == 'source' && $index -eq $count ]]; then + local quoted_fn + bats_quote_code quoted_fn "$fn" + printf "from function %s " "$quoted_fn" + fi + + local reference + bats_format_file_line_reference reference "$filename" "$lineno" + if [[ $index -eq $count ]]; then + printf 'in test file %s)\n' "$reference" + else + printf 'in file %s,\n' "$reference" + fi + + ((++index)) + done +} + +bats_print_failed_command() { + local stack_trace=("${@}") + if [[ ${#stack_trace[@]} -eq 0 ]]; then + return 0 + fi + local frame="${stack_trace[${#stack_trace[@]} - 1]}" + local filename + local lineno + local failed_line + local failed_command + + bats_frame_filename "$frame" 'filename' + bats_frame_lineno "$frame" 'lineno' + bats_extract_line "$filename" "$lineno" 'failed_line' + bats_strip_string "$failed_line" 'failed_command' + local quoted_failed_command + bats_quote_code quoted_failed_command "$failed_command" + printf '# %s ' "${quoted_failed_command}" + + if [[ "${BATS_TIMED_OUT-NOTSET}" != NOTSET ]]; then + # the other values can be safely overwritten here, + # as the timeout is the primary reason for failure + BATS_ERROR_SUFFIX=" due to timeout" + fi + + if [[ "$BATS_ERROR_STATUS" -eq 1 ]]; then + printf 'failed%s\n' "$BATS_ERROR_SUFFIX" + else + printf 'failed with status %d%s\n' "$BATS_ERROR_STATUS" "$BATS_ERROR_SUFFIX" + fi +} + +bats_frame_lineno() { + printf -v "$2" '%s' "${1%% *}" +} + +bats_frame_function() { + local __bff_function="${1#* }" + printf -v "$2" '%s' "${__bff_function%% *}" +} + +bats_frame_filename() { + local __bff_filename="${1#* }" + __bff_filename="${__bff_filename#* }" + + if [[ "$__bff_filename" == "${BATS_TEST_SOURCE-}" ]]; then + __bff_filename="$BATS_TEST_FILENAME" + fi + printf -v "$2" '%s' "$__bff_filename" +} + +bats_extract_line() { + local __bats_extract_line_line + local __bats_extract_line_index=0 + + while IFS= read -r __bats_extract_line_line; do + if [[ "$((++__bats_extract_line_index))" -eq "$2" ]]; then + printf -v "$3" '%s' "${__bats_extract_line_line%$'\r'}" + break + fi + done <"$1" +} + +bats_strip_string() { + [[ "$1" =~ ^[[:space:]]*(.*)[[:space:]]*$ ]] + printf -v "$2" '%s' "${BASH_REMATCH[1]}" +} + +bats_trim_filename() { + printf -v "$2" '%s' "${1#"$BATS_CWD"/}" +} + +# normalize a windows path from e.g. C:/directory to /c/directory +# The path must point to an existing/accessible directory, not a file! +bats_normalize_windows_dir_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_context() { + local padding='$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' + local reference + bats_format_file_line_reference reference "${file##*/}" "$line" + printf '%s [%s]\n' "${padding::${#BASH_LINENO[@]}-limit-3}" "$reference" >&4 +} + +bats_emit_trace_command() { + local padding='$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' + printf '%s %s\n' "${padding::${#BASH_LINENO[@]}-limit-3}" "$BASH_COMMAND" >&4 + + # keep track of printed commands + BATS_LAST_BASH_COMMAND="$BASH_COMMAND" + BATS_LAST_BASH_LINENO="$line" +} + +BATS_EMIT_TRACE_LAST_STACK_DIFF=0 + +bats_emit_trace() { + if [[ ${BATS_TRACE_LEVEL:-0} -gt 0 ]]; then + local line=${BASH_LINENO[1]} limit=${BATS_STACK_TRACE_LIMIT-0} + # shellcheck disable=SC2016 + if (( ${#FUNCNAME[@]} > limit + 2 )) # only emit below BATS_STRACK_TRACE_LIMIT (adjust by 2 for trap+this function call) + # avoid printing the same line twice on errexit + [[ $BASH_COMMAND != "$BATS_LAST_BASH_COMMAND" || $line != "$BATS_LAST_BASH_LINENO" ]]; 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 + # stack size difference since last call of this function + # <0: means new function call + # >0: means return + # =0: in same function as before (assuming we did not skip return/call) + local stack_diff=$(( BATS_LAST_STACK_DEPTH - ${#BASH_LINENO[@]} )) + # show context immediately when returning or on second command in new function + # as the first "command" is the function itself + if (( stack_diff > 0 )) || (( BATS_EMIT_TRACE_LAST_STACK_DIFF < 0 )); then + bats_emit_trace_context + fi + # only print command when moving up or staying in same function + # again, avoids printing the first command (the function itself) in new function + if (( stack_diff >= 0 )); then + bats_emit_trace_command + fi + + BATS_EMIT_TRACE_LAST_STACK_DIFF=$stack_diff + fi + # always update to detect stack depth changes regardless of printing + BATS_LAST_STACK_DEPTH="${#BASH_LINENO[@]}" + 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 path + for path in "${BATS_DEBUG_EXCLUDE_PATHS[@]}"; do + if [[ "$NORMALIZED_INPUT" == "$path"* ]]; then + return # skip this call + 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 [[ "${BATS_INTERRUPTED-NOTSET}" == NOTSET && + "${BATS_TIMED_OUT-NOTSET}" == NOTSET ]]; then + BATS_DEBUG_LASTLAST_STACK_TRACE=( + ${BATS_DEBUG_LAST_STACK_TRACE[@]+"${BATS_DEBUG_LAST_STACK_TRACE[@]}"} + ) + + BATS_DEBUG_LAST_LINENO=(${BASH_LINENO[@]+"${BASH_LINENO[@]}"}) + BATS_DEBUG_LAST_SOURCE=(${BASH_SOURCE[@]+"${BASH_SOURCE[@]}"}) + bats_capture_stack_trace + bats_emit_trace + fi +} + +# For some versions of Bash, the `ERR` trap may not always fire for every +# command failure, but the `EXIT` trap will. Also, some command failures may not +# set `$?` properly. See #72 and #81 for details. +# +# For this reason, we call `bats_check_status_from_trap` at the very beginning +# of `bats_teardown_trap` and check the value of `$BATS_TEST_COMPLETED` before +# taking other actions. We also adjust the exit status value if needed. +# +# See `bats_exit_trap` for an additional EXIT error handling case when `$?` +# isn't set properly during `teardown()` errors. +bats_check_status_from_trap() { + local status="$?" + if [[ -z "${BATS_TEST_COMPLETED:-}" ]]; then + BATS_ERROR_STATUS="${BATS_ERROR_STATUS:-$status}" + if [[ "$BATS_ERROR_STATUS" -eq 0 ]]; then + BATS_ERROR_STATUS=1 + fi + trap - DEBUG + fi +} + +bats_add_debug_exclude_path() { # + 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/$BATS_LIBDIR/" + 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_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 +} + +# predefine to avoid problems when the user does not declare one +bats::on_failure() { + : +} + +bats_error_trap() { + bats_check_status_from_trap + bats::on_failure "$BATS_ERROR_STATUS" + + # If necessary, undo the most recent stack trace captured by bats_debug_trap. + # See bats_debug_trap for details. + if [[ "${BASH_LINENO[*]}" = "${BATS_DEBUG_LAST_LINENO[*]:-}" && + "${BASH_SOURCE[*]}" = "${BATS_DEBUG_LAST_SOURCE[*]:-}" && + -z "$BATS_DEBUG_LAST_STACK_TRACE_IS_VALID" ]]; then + BATS_DEBUG_LAST_STACK_TRACE=( + ${BATS_DEBUG_LASTLAST_STACK_TRACE[@]+"${BATS_DEBUG_LASTLAST_STACK_TRACE[@]}"} + ) + fi + BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1 +} + +bats_interrupt_trap() { + # mark the interruption, to handle during exit + BATS_INTERRUPTED=true + BATS_ERROR_STATUS=130 + # debug trap fires before interrupt trap but gets wrong linenumber (line 1) + # -> use last stack trace instead of BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=true +} + +# this is used inside run() +bats_interrupt_trap_in_run() { + # mark the interruption, to handle during exit + BATS_INTERRUPTED=true + BATS_ERROR_STATUS=130 + BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=true + exit 130 +} diff --git a/node_modules/bats/lib/bats-core/validator.bash b/node_modules/bats/lib/bats-core/validator.bash new file mode 100644 index 0000000..59fc2c1 --- /dev/null +++ b/node_modules/bats/lib/bats-core/validator.bash @@ -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 +} diff --git a/node_modules/bats/lib/bats-core/warnings.bash b/node_modules/bats/lib/bats-core/warnings.bash new file mode 100644 index 0000000..5762d74 --- /dev/null +++ b/node_modules/bats/lib/bats-core/warnings.bash @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# shellcheck source=lib/bats-core/tracing.bash +source "$BATS_ROOT/$BATS_LIBDIR/bats-core/tracing.bash" + +# generate a warning report for the parent call's call site +bats_generate_warning() { # [--no-stacktrace] [...] + local warning_number="${1-}" padding="00" + shift + local no_stacktrace= + if [[ ${1-} == --no-stacktrace ]]; then + no_stacktrace=1 + shift + fi + if [[ $warning_number =~ [0-9]+ ]] && ((warning_number < ${#BATS_WARNING_SHORT_DESCS[@]})); then + { + printf "BW%s: ${BATS_WARNING_SHORT_DESCS[$warning_number]}\n" "${padding:${#warning_number}}${warning_number}" "$@" + if [[ -z "$no_stacktrace" ]]; then + bats_capture_stack_trace + BATS_STACK_TRACE_PREFIX=' ' bats_print_stack_trace "${BATS_DEBUG_LAST_STACK_TRACE[@]}" + fi + } >>"$BATS_WARNING_FILE" 2>&3 + else + printf "Invalid Bats warning number '%s'. It must be an integer between 1 and %d." "$warning_number" "$((${#BATS_WARNING_SHORT_DESCS[@]} - 1))" >&2 + exit 1 + fi +} + +# generate a warning if the BATS_GUARANTEED_MINIMUM_VERSION is not high enough +bats_warn_minimum_guaranteed_version() { # + if bats_version_lt "$BATS_GUARANTEED_MINIMUM_VERSION" "$2"; then + bats_generate_warning 2 "$1" "$2" "$2" + fi +} + +# put after functions to avoid line changes in tests when new ones get added +BATS_WARNING_SHORT_DESCS=( + # to start with 1 + 'PADDING' + # see issue #578 for context + "\`run\`'s command \`%s\` exited with code 127, indicating 'Command not found'. Use run's return code checks, e.g. \`run -127\`, to fix this message." + "%s requires at least BATS_VERSION=%s. Use \`bats_require_minimum_version %s\` to fix this message." + "\`setup_suite\` is visible to test file '%s', but was not executed. It belongs into 'setup_suite.bash' to be picked up automatically." +) diff --git a/node_modules/bats/libexec/bats-core/bats b/node_modules/bats/libexec/bats-core/bats new file mode 100755 index 0000000..d30f799 --- /dev/null +++ b/node_modules/bats/libexec/bats-core/bats @@ -0,0 +1,525 @@ +#!/usr/bin/env bash +set -e + +export BATS_VERSION='1.13.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 < + ${cmd} [-h | -v] + +HELP_TEXT_HEADER + + cat <<'HELP_TEXT_BODY' + is the path to a Bats test file, or the path to a directory + containing Bats test files (ending with ".bats") + + --abort Stop execution of suite on first failed test + -c, --count Count test cases without running any tests + --code-quote-style