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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

### Added
- `--report-tap <file>` writes a TAP version 13 report to a file (complements the streaming `--output tap`) (#740)
- `assert_within_delta <expected> <actual> <delta>` asserts a number is within a tolerance of another; supports floats (#744)
- `assert_array_length <n> <array>` asserts an array has exactly `n` elements (#743)
- `assert_file_permissions <mode> <file>` asserts a file's octal permission mode (`644`, `0755`); portable across GNU/BSD `stat` (#742)
Expand Down
1 change: 1 addition & 0 deletions docs/command-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ bashunit test tests/ --parallel --simple
| `-p, --parallel` | Run tests in parallel |
| `--no-parallel` | Run tests sequentially |
| `-r, --report-html <file>` | Write HTML report |
| `--report-tap <file>` | Write TAP version 13 report to a file |
| `-R, --run-all` | Run all assertions (don't stop on first failure) |
| `-s, --simple` | Simple output (dots) |
| `--detailed` | Detailed output (default) |
Expand Down
1 change: 1 addition & 0 deletions src/console_header.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Options:
-p, --parallel Run tests in parallel (unlimited concurrency)
--no-parallel Run tests sequentially
-r, --report-html <file> Write HTML report
--report-tap <file> Write TAP version 13 report
-s, --simple Simple output (dots)
--detailed Detailed output (default)
--output <format> Output format: tap (TAP version 13)
Expand Down
3 changes: 3 additions & 0 deletions src/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ _BASHUNIT_DEFAULT_DEV_LOG=""
_BASHUNIT_DEFAULT_LOG_JUNIT=""
_BASHUNIT_DEFAULT_LOG_GHA=""
_BASHUNIT_DEFAULT_REPORT_HTML=""
_BASHUNIT_DEFAULT_REPORT_TAP=""

# Coverage defaults (following kcov, bashcov, SimpleCov conventions)
_BASHUNIT_DEFAULT_COVERAGE="false"
Expand All @@ -79,6 +80,7 @@ _BASHUNIT_DEFAULT_COVERAGE_THRESHOLD_HIGH="80"
: "${BASHUNIT_LOG_JUNIT:=${LOG_JUNIT:=$_BASHUNIT_DEFAULT_LOG_JUNIT}}"
: "${BASHUNIT_LOG_GHA:=${LOG_GHA:=$_BASHUNIT_DEFAULT_LOG_GHA}}"
: "${BASHUNIT_REPORT_HTML:=${REPORT_HTML:=$_BASHUNIT_DEFAULT_REPORT_HTML}}"
: "${BASHUNIT_REPORT_TAP:=${REPORT_TAP:=$_BASHUNIT_DEFAULT_REPORT_TAP}}"

# Coverage
: "${BASHUNIT_COVERAGE:=${COVERAGE:=$_BASHUNIT_DEFAULT_COVERAGE}}"
Expand Down Expand Up @@ -340,6 +342,7 @@ function bashunit::env::print_verbose() {
"BASHUNIT_LOG_JUNIT"
"BASHUNIT_LOG_GHA"
"BASHUNIT_REPORT_HTML"
"BASHUNIT_REPORT_TAP"
"BASHUNIT_PARALLEL_RUN"
"BASHUNIT_SHOW_HEADER"
"BASHUNIT_HEADER_ASCII_ART"
Expand Down
8 changes: 8 additions & 0 deletions src/main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ function bashunit::main::cmd_test() {
export BASHUNIT_REPORT_HTML="$2"
shift
;;
--report-tap)
export BASHUNIT_REPORT_TAP="$2"
shift
;;
--no-output)
export BASHUNIT_NO_OUTPUT=true
;;
Expand Down Expand Up @@ -737,6 +741,10 @@ function bashunit::main::exec_tests() {
bashunit::reports::generate_report_html "$BASHUNIT_REPORT_HTML"
fi

if [ -n "$BASHUNIT_REPORT_TAP" ]; then
bashunit::reports::generate_report_tap "$BASHUNIT_REPORT_TAP"
fi

# Generate coverage report if enabled
if bashunit::env::is_coverage_enabled; then
# Aggregate per-process coverage data from parallel runs
Expand Down
60 changes: 59 additions & 1 deletion src/reports.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ function bashunit::reports::add_test() {
{
[ -n "${BASHUNIT_LOG_JUNIT:-}" ] ||
[ -n "${BASHUNIT_REPORT_HTML:-}" ] ||
[ -n "${BASHUNIT_LOG_GHA:-}" ]
[ -n "${BASHUNIT_LOG_GHA:-}" ] ||
[ -n "${BASHUNIT_REPORT_TAP:-}" ]
} || return 0

local file="$1"
Expand Down Expand Up @@ -128,6 +129,63 @@ function bashunit::reports::generate_junit_xml() {
} >"$output_file"
}

##
# Prepares a failure message for a TAP YAML diagnostic block: strips ANSI escape
# sequences, collapses newlines to spaces and doubles single quotes so the value
# is safe inside a YAML single-quoted scalar. Bash 3.0+ compatible.
##
function bashunit::reports::__tap_message() {
echo "$1" \
| sed -e 's/\x1b\[[0-9;]*[a-zA-Z]//g' \
| tr '\n' ' ' \
| sed -e "s/'/''/g"
}

##
# Writes results in TAP version 13 format (https://testanything.org).
# Passing/snapshot -> "ok", failed -> "not ok" with a YAML diagnostic,
# skipped/risky -> "# SKIP", incomplete -> "# TODO".
# Arguments: $1 - output file
##
function bashunit::reports::generate_report_tap() {
local output_file="$1"
local total="${#_BASHUNIT_REPORTS_TEST_NAMES[@]}"

{
echo "TAP version 13"
echo "1..$total"

local i seq=0
for i in "${!_BASHUNIT_REPORTS_TEST_NAMES[@]}"; do
seq=$((seq + 1))
local name="${_BASHUNIT_REPORTS_TEST_NAMES[$i]:-}"
local status="${_BASHUNIT_REPORTS_TEST_STATUSES[$i]:-}"
local failure_message="${_BASHUNIT_REPORTS_TEST_FAILURES[$i]:-}"

case "$status" in
failed)
echo "not ok $seq - $name"
echo " ---"
echo " message: '$(bashunit::reports::__tap_message "$failure_message")'"
echo " ..."
;;
skipped)
echo "ok $seq - $name # SKIP"
;;
risky)
echo "ok $seq - $name # SKIP risky (no assertions)"
;;
incomplete)
echo "ok $seq - $name # TODO"
;;
*)
echo "ok $seq - $name"
;;
esac
done
} >"$output_file"
}

function bashunit::reports::__gha_encode() {
local text="$1"
# Strip ANSI escape sequences first (one sed call)
Expand Down
22 changes: 22 additions & 0 deletions tests/acceptance/bashunit_report_tap_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

function set_up_before_script() {
TEST_ENV_FILE="tests/acceptance/fixtures/.env.default"
}

function test_bashunit_report_tap_writes_a_valid_tap_file() {
local test_file=./tests/acceptance/fixtures/test_bashunit_when_log_junit.sh
local report=report.tap

# The fixture contains failing tests by design, so the run exits non-zero;
# swallow it (we only care that --report-tap produced a valid TAP file).
./bashunit --no-parallel --env "$TEST_ENV_FILE" --report-tap "$report" "$test_file" >/dev/null 2>&1 || true

assert_file_exists "$report"
assert_file_contains "$report" "TAP version 13"
assert_file_contains "$report" "1.."
assert_file_contains "$report" "ok "
assert_file_contains "$report" "not ok "

rm "$report"
}
68 changes: 68 additions & 0 deletions tests/unit/reports_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function set_up() {
unset BASHUNIT_LOG_JUNIT
unset BASHUNIT_REPORT_HTML
unset BASHUNIT_LOG_GHA
unset BASHUNIT_REPORT_TAP

# Create temp file for output tests
_TEMP_OUTPUT_FILE=$(mktemp)
Expand Down Expand Up @@ -452,6 +453,73 @@ function test_generate_gha_log_strips_ansi_color_codes() {
assert_not_contains $'\033[' "$content"
}

function test_add_test_tracks_when_tap_report_enabled() {
BASHUNIT_REPORT_TAP="report.tap"

bashunit::reports::add_test "file.sh" "test_name" "100" "3" "passed"

assert_same "1" "${#_BASHUNIT_REPORTS_TEST_NAMES[@]}"
}

function test_generate_report_tap_creates_valid_header_and_plan() {
_mock_state_functions
BASHUNIT_REPORT_TAP="report.tap"

bashunit::reports::add_test "test.sh" "test_one" "100" "2" "passed"
bashunit::reports::add_test "test.sh" "test_two" "100" "2" "passed"
bashunit::reports::generate_report_tap "$_TEMP_OUTPUT_FILE"

local content
content=$(cat "$_TEMP_OUTPUT_FILE")

assert_contains "TAP version 13" "$content"
assert_contains "1..2" "$content"
}

function test_generate_report_tap_ok_for_passed_test() {
_mock_state_functions
BASHUNIT_REPORT_TAP="report.tap"

bashunit::reports::add_test "test.sh" "test_one" "100" "2" "passed"
bashunit::reports::generate_report_tap "$_TEMP_OUTPUT_FILE"

assert_contains "ok 1 - test_one" "$(cat "$_TEMP_OUTPUT_FILE")"
}

function test_generate_report_tap_not_ok_for_failed_test() {
_mock_state_functions
BASHUNIT_REPORT_TAP="report.tap"

bashunit::reports::add_test "test.sh" "test_bad" "100" "2" "failed" "expected 1 got 2"
bashunit::reports::generate_report_tap "$_TEMP_OUTPUT_FILE"

local content
content=$(cat "$_TEMP_OUTPUT_FILE")

assert_contains "not ok 1 - test_bad" "$content"
assert_contains "expected 1 got 2" "$content"
}

function test_generate_report_tap_skip_directive_for_skipped_test() {
_mock_state_functions
BASHUNIT_REPORT_TAP="report.tap"

bashunit::reports::add_test "test.sh" "test_skip" "0" "0" "skipped"
bashunit::reports::generate_report_tap "$_TEMP_OUTPUT_FILE"

assert_contains "ok 1 - test_skip # SKIP" "$(cat "$_TEMP_OUTPUT_FILE")"
}

function test_generate_report_tap_todo_directive_for_incomplete_test() {
_mock_state_functions
BASHUNIT_REPORT_TAP="report.tap"

bashunit::reports::add_test "test.sh" "test_todo" "0" "0" "incomplete"
bashunit::reports::generate_report_tap "$_TEMP_OUTPUT_FILE"

assert_contains "ok 1 - test_todo # TODO" "$(cat "$_TEMP_OUTPUT_FILE")"
}

function test_generate_report_html_applies_status_css_classes() {
_mock_state_functions
BASHUNIT_REPORT_HTML="report.html"
Expand Down
Loading