Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,54 @@
#───────────────────────────────────────────────────────────────────────────────
# Test Discovery
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEFAULT_PATH= # Default: tests
BASHUNIT_BOOTSTRAP= # Default: tests/bootstrap.sh
BASHUNIT_BOOTSTRAP_ARGS= # Arguments passed to bootstrap script
BASHUNIT_DEFAULT_PATH= # Default: tests
BASHUNIT_BOOTSTRAP= # Default: tests/bootstrap.sh
BASHUNIT_BOOTSTRAP_ARGS= # Arguments passed to bootstrap script

#───────────────────────────────────────────────────────────────────────────────
# Output Display
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_SHOW_HEADER= # Default: true
BASHUNIT_HEADER_ASCII_ART= # Default: false
BASHUNIT_SIMPLE_OUTPUT= # Default: false (use dots instead of test names)
BASHUNIT_VERBOSE= # Default: false (show environment variables)
BASHUNIT_NO_OUTPUT= # Default: false (suppress all output)
BASHUNIT_SHOW_EXECUTION_TIME= # Default: true
BASHUNIT_SHOW_SKIPPED= # Default: false (show skipped test details)
BASHUNIT_SHOW_INCOMPLETE= # Default: false (show incomplete test details)
BASHUNIT_FAILURES_ONLY= # Default: false (only show failures)
BASHUNIT_NO_COLOR= # Default: false (disable colors)
BASHUNIT_SHOW_HEADER= # Default: true
BASHUNIT_HEADER_ASCII_ART= # Default: false
BASHUNIT_SIMPLE_OUTPUT= # Default: false (use dots instead of test names)
BASHUNIT_VERBOSE= # Default: false (show environment variables)
BASHUNIT_NO_OUTPUT= # Default: false (suppress all output)
BASHUNIT_SHOW_EXECUTION_TIME= # Default: true
BASHUNIT_SHOW_SKIPPED= # Default: false (show skipped test details)
BASHUNIT_SHOW_INCOMPLETE= # Default: false (show incomplete test details)
BASHUNIT_FAILURES_ONLY= # Default: false (only show failures)
BASHUNIT_NO_COLOR= # Default: false (disable colors)

#───────────────────────────────────────────────────────────────────────────────
# Test Execution
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_PARALLEL_RUN= # Default: false
BASHUNIT_STOP_ON_FAILURE= # Default: false (stop suite on first failure)
BASHUNIT_PARALLEL_RUN= # Default: false
BASHUNIT_STOP_ON_FAILURE= # Default: false (stop suite on first failure)
BASHUNIT_STOP_ON_ASSERTION_FAILURE= # Default: true (stop test on first assertion fail)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)

#───────────────────────────────────────────────────────────────────────────────
# Reports
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)

#───────────────────────────────────────────────────────────────────────────────
# Code Coverage
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_COVERAGE= # Default: false
BASHUNIT_COVERAGE_PATHS= # Default: src/ (comma-separated paths to track)
BASHUNIT_COVERAGE_EXCLUDE= # Default: tests/*,vendor/*,*_test.sh,*Test.sh
BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH=# Default: 80 (green above this)
BASHUNIT_COVERAGE= # Default: false
BASHUNIT_COVERAGE_PATHS= # Default: src/ (comma-separated paths to track)
BASHUNIT_COVERAGE_EXCLUDE= # Default: tests/*,vendor/*,*_test.sh,*Test.sh
BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green above this)

#───────────────────────────────────────────────────────────────────────────────
# Advanced / Debug
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
3 changes: 3 additions & 0 deletions .github/workflows/tests-bash-3.0.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ jobs:
with:
fetch-depth: 1

- name: Setup Config
run: cp .env.example .env

- name: Download Docker image artifact
uses: actions/download-artifact@v4
with:
Expand Down
67 changes: 67 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,54 @@ jobs:
with:
fetch-depth: 1

- name: Setup Config
run: cp .env.example .env

- name: Run Tests
run: make test

localized-ubuntu:
name: "Ubuntu - ${{ matrix.name }} Locale"
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
include:
- name: "Spanish"
lang: "es_ES.UTF-8"
tz: "Europe/Madrid"
language_pack: "language-pack-es-base"
- name: "Brazilian"
lang: "pt_BR.UTF-8"
tz: "America/Sao_Paulo"
language_pack: "language-pack-pt-base"
- name: "Japanese"
lang: "ja_JP.UTF-8"
tz: "Asia/Tokyo"
language_pack: "language-pack-ja-base"
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Install locales
run: |
sudo apt-get update
sudo apt-get install -y locales "${{ matrix.language_pack }}"
sudo locale-gen "${{ matrix.lang }}"

- name: Setup Config
run: cp .env.example .env

- name: Run Tests
env:
LANG: ${{ matrix.lang }}
LC_ALL: ${{ matrix.lang }}
TZ: ${{ matrix.tz }}
run: make test

macos:
name: "macOS - latest"
runs-on: macos-latest
Expand All @@ -30,6 +75,9 @@ jobs:
with:
fetch-depth: 1

- name: Setup Config
run: cp .env.example .env

- name: Run Tests
run: make test

Expand Down Expand Up @@ -63,6 +111,10 @@ jobs:
with:
fetch-depth: 1

- name: Setup Config
shell: bash
run: cp .env.example .env

- name: Run tests
shell: bash
run: |
Expand All @@ -78,6 +130,9 @@ jobs:
with:
fetch-depth: 1

- name: Setup Config
run: cp .env.example .env

- name: Run Tests
run: |
docker run --rm -v "$(pwd)":/project alpine:latest /bin/sh -c " \
Expand All @@ -97,6 +152,9 @@ jobs:
with:
fetch-depth: 1

- name: Setup Config
run: cp .env.example .env

- name: Run Tests
run: |
./bashunit --simple tests/
Expand All @@ -111,6 +169,9 @@ jobs:
with:
fetch-depth: 1

- name: Setup Config
run: cp .env.example .env

- name: Run Tests
run: |
./bashunit --parallel --simple tests/
Expand All @@ -125,6 +186,9 @@ jobs:
with:
fetch-depth: 1

- name: Setup Config
run: cp .env.example .env

- name: Run Tests
run: |
./bashunit --parallel tests/
Expand All @@ -139,6 +203,9 @@ jobs:
with:
fetch-depth: 1

- name: Setup Config
run: cp .env.example .env

- name: Run Tests with strict mode
run: |
./bashunit --parallel --simple --strict tests/
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Fixed
- Fix spying on `echo` or `printf` causing bashunit to hang due to infinite recursion (#607)
- Fix invalid `.env.example` coverage threshold entry and copy `.env.example` to `.env` in CI test workflows so configuration parse errors are caught during automated test runs
- Fix `clock::now` shell-time parsing when `EPOCHREALTIME` uses a comma decimal separator

## [0.34.1](https://github.com/TypedDevs/bashunit/compare/0.34.0...0.34.1) - 2026-03-20

Expand Down
37 changes: 35 additions & 2 deletions src/assert_dates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,25 @@ function bashunit::date::to_epoch() {
;;
esac

# Normalize ISO 8601: replace T with space, strip Z suffix, strip tz offset
# Handle Z (UTC) suffix explicitly: BusyBox needs TZ=UTC, BSD needs +0000
case "$input" in
*Z)
local utc_input="${input%Z}"
local utc_norm="${utc_input/T/ }"
local epoch
# GNU/BusyBox: parse in explicit UTC
epoch=$(TZ=UTC date -d "$utc_input" +%s 2>/dev/null) && { echo "$epoch"; return 0; }
epoch=$(TZ=UTC date -d "$utc_norm" +%s 2>/dev/null) && { echo "$epoch"; return 0; }
# BSD: use +0000 offset which %z understands
epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S%z" "${utc_input}+0000" +%s 2>/dev/null) && { echo "$epoch"; return 0; }
echo "$input"
return 1
;;
esac

# Normalize ISO 8601: replace T with space, strip tz offset
local normalized="$input"
normalized="${normalized/T/ }"
normalized="${normalized%Z}"
# Strip timezone offset (+HHMM or -HHMM) at end for initial parsing
case "$normalized" in
*[+-][0-9][0-9][0-9][0-9])
Expand All @@ -30,6 +45,24 @@ function bashunit::date::to_epoch() {
echo "$epoch"
return 0
}
# If input has timezone offset, parse in UTC and adjust manually (BusyBox)
case "$input" in
*[+-][0-9][0-9][0-9][0-9])
epoch=$(TZ=UTC date -d "$normalized" +%s 2>/dev/null) && {
local ilen=${#input}
local ostart=$((ilen - 5))
local osign="${input:$ostart:1}"
local ohh="${input:$((ostart + 1)):2}"
local omm="${input:$((ostart + 3)):2}"
local osecs=$(( (10#$ohh * 3600) + (10#$omm * 60) ))
if [ "$osign" = "+" ]; then
osecs=$(( -osecs ))
fi
echo $(( epoch + osecs ))
return 0
}
;;
esac
# Try GNU date with normalized (space-separated) input
if [ "$normalized" != "$input" ]; then
epoch=$(date -d "$normalized" +%s 2>/dev/null) && {
Expand Down
7 changes: 5 additions & 2 deletions src/clock.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,11 @@ EOF
shell)
# shellcheck disable=SC2155
local shell_time="$(bashunit::clock::shell_time)"
local seconds="${shell_time%%.*}"
local microseconds="${shell_time#*.}"
local seconds="${shell_time%%[.,]*}"
local microseconds="${shell_time#*[.,]}"
if [ "$seconds" = "$shell_time" ]; then
microseconds=""
fi
# Pad to 6 digits and strip leading zeros for arithmetic
microseconds="${microseconds}000000"
microseconds="${microseconds:0:6}"
Expand Down
2 changes: 1 addition & 1 deletion tests/acceptance/bashunit_bootstrap_args_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function test_bootstrap_args_via_env_variable() {
# Use --env flag to set the bootstrap file (avoiding .env override),
# but use BASHUNIT_BOOTSTRAP_ARGS from environment
output=$(BASHUNIT_BOOTSTRAP_ARGS="hello world" \
./bashunit --no-parallel --simple \
./bashunit --no-parallel --simple --skip-env-file \
--env "tests/acceptance/fixtures/bootstrap_with_args.sh" \
tests/acceptance/fixtures/test_bootstrap_args.sh 2>&1) || true

Expand Down
2 changes: 1 addition & 1 deletion tests/acceptance/bashunit_execution_error_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ function test_bashunit_when_a_execution_error() {
printf "%sRunning ./tests/acceptance/fixtures/test_bashunit_when_a_execution_error.sh%s\n" \
"${color_bold}" "${color_default}"
printf "%s✗ Error%s: Error\n" "${color_red}" "${color_default}"
printf " %sline 4: invalid_function_name: command not found%s\n" "${color_dim}" "${color_default}"
)
local fixture_end=$(
format_summary_title "Tests: "
Expand All @@ -39,6 +38,7 @@ function test_bashunit_when_a_execution_error() {

local actual="$(./bashunit --no-parallel --detailed --env "$TEST_ENV_FILE" "$test_file")"
assert_contains "$fixture_start" "$actual"
assert_contains "invalid_function_name" "$actual"
assert_contains "$fixture_end" "$actual"
assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file")"
}
2 changes: 1 addition & 1 deletion tests/acceptance/bashunit_no_color_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function set_up_before_script() {
function test_bashunit_no_color_flag_disables_colors() {
local test_file=./tests/acceptance/fixtures/test_bashunit_when_a_test_passes.sh
local output
output=$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file" --no-color --simple)
output=$(./bashunit --no-parallel --skip-env-file --env "$TEST_ENV_FILE" "$test_file" --no-color --simple)

# ANSI escape codes start with \x1b[ (ESC[) - should not be present
assert_not_contains $'\e[' "$output"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function test_bashunit_when_set_up_before_script_errors() {

local header_line="Running $fixture"
local error_line="✗ Error: Set up before script"
local message_line=" $fixture: line 4: invalid_function_name: command not found"
local message_line="invalid_function_name"
local tests_summary="Tests: 1 failed, 1 total"
local assertions_summary="Assertions: 0 failed, 0 total"

Expand Down
2 changes: 1 addition & 1 deletion tests/acceptance/bashunit_setup_error_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function test_bashunit_when_set_up_errors() {

local header_line="Running $fixture"
local error_line="✗ Error: Set up"
local message_line=" $fixture: line 4: invalid_function_name: command not found"
local message_line="invalid_function_name"
local tests_summary="Tests: 1 failed, 1 total"
local assertions_summary="Assertions: 0 failed, 0 total"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function test_bashunit_when_tear_down_after_script_errors() {

local header_line="Running $fixture"
local error_line="✗ Error: Tear down after script"
local message_line=" $fixture: line 4: missing_cleanup_command: command not found"
local message_line="missing_cleanup_command"
local tests_summary="Tests: 1 passed, 1 failed, 2 total"
local assertions_summary="Assertions: 1 passed, 0 failed, 1 total"

Expand Down
2 changes: 1 addition & 1 deletion tests/acceptance/bashunit_teardown_error_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function test_bashunit_when_tear_down_errors() {

local header_line="Running $fixture"
local error_line="✗ Error: Tear down"
local message_line=" $fixture: line 4: invalid_function_name: command not found"
local message_line="invalid_function_name"
local tests_summary="Tests: 1 failed, 1 total"
local assertions_summary="Assertions: 0 failed, 0 total"

Expand Down
4 changes: 2 additions & 2 deletions tests/unit/assert_dates_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,10 @@ function test_successful_assert_date_equals_epoch_vs_space_separated() {
assert_empty "$(assert_date_equals "$epoch" "2023-11-14 12:00:00")"
}

# UTC Z suffix test (documents existing behavior)
# UTC Z suffix test

function test_successful_assert_date_equals_with_utc_z_suffix() {
assert_empty "$(assert_date_equals "2023-11-14T12:00:00" "2023-11-14T12:00:00Z")"
assert_empty "$(assert_date_equals "2023-11-14T14:00:00+0200" "2023-11-14T12:00:00Z")"
}

# Timezone offset tests
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/clock_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ function test_now_prefers_shell_time_over_perl() {
assert_same "1234567890000" "$(bashunit::clock::now)"
}

function test_now_handles_shell_time_with_comma_decimal_separator() {
bashunit::mock bashunit::clock::shell_time <<<"1234,567890"

assert_same "1234567890000" "$(bashunit::clock::now)"
}

function test_now_prefers_python_over_node() {
bashunit::mock bashunit::clock::shell_time mock_non_existing_fn
bashunit::mock date mock_non_existing_fn
Expand Down
Loading