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