Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for customizing code quotes #506

5 changes: 5 additions & 0 deletions docs/CHANGELOG.md
Expand Up @@ -10,6 +10,11 @@ The format is based on [Keep a Changelog][kac] and this project adheres to

## [Unreleased]

### Added

* new flag `--code-quote-style` (and `$BATS_CODE_QUOTE_STYLE`) to customize
quotes around code blocks in error output (#506)

### Fixed

* improved error trace for some broken cases (#279)
Expand Down
6 changes: 5 additions & 1 deletion lib/bats-core/common.bash
Expand Up @@ -7,4 +7,8 @@ bats_prefix_lines_for_tap_output() {
if [[ -n "$line" ]]; then
printf '# %s\n' "$line"
fi
}
}

bats_quote_code() { # <var> <code>
printf -v "$1" -- "%s%s%s" "$BATS_BEGIN_CODE_QUOTE" "$2" "$BATS_END_CODE_QUOTE"
}
4 changes: 4 additions & 0 deletions lib/bats-core/test_functions.bash
Expand Up @@ -87,6 +87,10 @@ run() { # [!|-N] [--keep-empty-lines] [--separate-stderr] [--] <command to run..
shift # eat the -- before breaking away
break
;;
*)
printf "Usage error: unknown flag '%s'" "$1" >&2
return 1
;;
esac
shift
done
Expand Down
8 changes: 6 additions & 2 deletions lib/bats-core/tracing.bash
Expand Up @@ -64,7 +64,9 @@ bats_print_stack_trace() {
# 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
printf "from function \`%s' " "$fn"
local quoted_fn
bats_quote_code quoted_fn "$fn"
printf "from function %s " "$quoted_fn"
fi

if [[ $index -eq $count ]]; then
Expand Down Expand Up @@ -92,7 +94,9 @@ bats_print_failed_command() {
bats_frame_lineno "$frame" 'lineno'
bats_extract_line "$filename" "$lineno" 'failed_line'
bats_strip_string "$failed_line" 'failed_command'
printf '%s' "# \`${failed_command}' "
local quoted_failed_command
bats_quote_code quoted_failed_command "$failed_command"
printf '# %s ' "${quoted_failed_command}"

if [[ "$BATS_ERROR_STATUS" -eq 1 ]]; then
printf 'failed%s\n' "$BATS_ERROR_SUFFIX"
Expand Down
31 changes: 31 additions & 0 deletions libexec/bats-core/bats
Expand Up @@ -29,6 +29,10 @@ HELP_TEXT_HEADER
containing Bats test files (ending with ".bats")

-c, --count Count test cases without running any tests
--code-quote-style <style>
A two character string of code quote delimiters
or 'custom' which requires setting $BATS_BEGIN_CODE_QUOTE and
$BATS_END_CODE_QUOTE. Can also be set via $BATS_CODE_QUOTE_STYLE
-f, --filter <regex> Only run tests that match the regular expression
-F, --formatter <type> Switch between formatters: pretty (default),
tap (default w/o term), tap13, junit
Expand Down Expand Up @@ -220,6 +224,10 @@ while [[ "$#" -ne 0 ]]; do
fi
flags+=(--gather-test-outputs-in "$output_dir")
;;
--code-quote-style)
shift
BATS_CODE_QUOTE_STYLE="$1"
;;
-*)
abort "Bad command line option '$1'"
;;
Expand Down Expand Up @@ -298,6 +306,29 @@ if [[ -n "$report_formatter" ]]; then
esac
fi

if [[ "${BATS_CODE_QUOTE_STYLE-BATS_CODE_QUOTE_STYLE_UNSET}" == BATS_CODE_QUOTE_STYLE_UNSET ]]; then
BATS_CODE_QUOTE_STYLE="\`'"
fi

case "${BATS_CODE_QUOTE_STYLE}" in
??)
BATS_BEGIN_CODE_QUOTE="${BATS_CODE_QUOTE_STYLE::1}"
BATS_END_CODE_QUOTE="${BATS_CODE_QUOTE_STYLE:1:1}"
export BATS_BEGIN_CODE_QUOTE BATS_END_CODE_QUOTE
;;
custom)
if [[ ${BATS_BEGIN_CODE_QUOTE-BATS_BEGIN_CODE_QUOTE_UNSET} == BATS_BEGIN_CODE_QUOTE_UNSET
|| ${BATS_END_CODE_QUOTE-BATS_BEGIN_CODE_QUOTE_UNSET} == BATS_BEGIN_CODE_QUOTE_UNSET ]]; then
printf "ERROR: BATS_CODE_QUOTE_STYLE=custom requires BATS_BEGIN_CODE_QUOTE and BATS_END_CODE_QUOTE to be set\n" >&2
exit 1
fi
;;
*)
printf "ERROR: Unknown BATS_CODE_QUOTE_STYLE: %s\n" "$BATS_CODE_QUOTE_STYLE" >&2
exit 1
;;
esac

if [[ -n "$output" ]]; then
if [[ ! -w "${output}" ]]; then
abort "Output path ${output} is not writeable"
Expand Down
4 changes: 3 additions & 1 deletion libexec/bats-core/bats-exec-test
Expand Up @@ -188,7 +188,9 @@ get_mills_since_epoch() {

bats_perform_test() {
if ! declare -F "$BATS_TEST_NAME" &>/dev/null; then
printf "bats: unknown test name \`%s'\n" "$BATS_TEST_NAME" >&2
local quoted_test_name
bats_quote_code quoted_test_name "$BATS_TEST_NAME"
printf "bats: unknown test name %s\n" "$quoted_test_name" >&2
exit 1
fi

Expand Down
5 changes: 4 additions & 1 deletion man/bats.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BATS" "1" "August 2021" "bats-core" "Bash Automated Testing System"
.TH "BATS" "1" "November 2021" "bats-core" "Bash Automated Testing System"
.
.SH "NAME"
\fBbats\fR \- Bash Automated Testing System
Expand Down Expand Up @@ -36,6 +36,9 @@ You can invoke the \fBbats\fR interpreter with multiple test file arguments, or
\fB\-c\fR, \fB\-\-count\fR: Count the number of test cases without running any tests
.
.IP "\(bu" 4
\fB\-\-code\-quote\-style <style>\fR: A two character string of code quote delimiters or \fBcustom\fR which requires setting \fB$BATS_BEGIN_CODE_QUOTE\fR and \fB$BATS_END_CODE_QUOTE\fR\. Can also be set via \fB$BATS_CODE_QUOTE_STYLE\fR\.
.
.IP "\(bu" 4
\fB\-f\fR, \fB\-\-filter <regex>\fR: Filter test cases by names matching the regular expression
.
.IP "\(bu" 4
Expand Down
6 changes: 5 additions & 1 deletion man/bats.1.ronn
Expand Up @@ -49,6 +49,11 @@ OPTIONS

* `-c`, `--count`:
Count the number of test cases without running any tests
* `--code-quote-style <style>`:
A two character string of code quote delimiters or `custom`
which requires setting `$BATS_BEGIN_CODE_QUOTE` and
`$BATS_END_CODE_QUOTE`.
Can also be set via `$BATS_CODE_QUOTE_STYLE`.
* `-f`, `--filter <regex>`:
Filter test cases by names matching the regular expression
* `-F`, `--formatter <type>`:
Expand Down Expand Up @@ -88,7 +93,6 @@ OPTIONS
* `-v`, `--version`:
Display the version number


OUTPUT
------

Expand Down
34 changes: 10 additions & 24 deletions man/bats.7
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BATS" "7" "August 2021" "bats-core" "Bash Automated Testing System"
.TH "BATS" "7" "November 2021" "bats-core" "Bash Automated Testing System"
.
.SH "NAME"
\fBbats\fR \- Bats test file format
Expand Down Expand Up @@ -33,7 +33,7 @@ A Bats test file is a Bash script with special syntax for defining test cases\.
Each Bats test file is evaluated n+1 times, where \fIn\fR is the number of test cases in the file\. The first run counts the number of test cases, then iterates over the test cases and executes each one in its own process\.
.
.SH "THE RUN HELPER"
Usage: run [OPTIONS] [\-\-] <command\.\.\.> Options: ! check for non zero exit code =\fIN\fR check that exit code is \fIN\fR \-\-output {merged,separate,stdout,stderr} control which output is recorded \-\-keep\-empty\-lines retain emtpy lines in \fB${lines[@]}\fR
Usage: run [OPTIONS] [\-\-] <command\.\.\.> Options: ! check for non zero exit code \-\fIN\fR check that exit code is \fIN\fR \-\-separate\-stderr split stderr and stdout \-\-keep\-empty\-lines retain emtpy lines in \fB${lines[@]}\fR/\fB${stderr_lines[@]}\fR
.
.P
Many Bats tests need to run a command and then make assertions about its exit status and output\. Bats includes a \fBrun\fR helper that invokes its arguments as a command, saves the exit status and output into special global variables, and (optionally) checks exit status against a given expected value\. If successful, \fBrun\fR returns with a \fB0\fR status code so you can continue to make assertions in your test case\.
Expand All @@ -46,7 +46,7 @@ For example, let\'s say you\'re testing that the \fBfoo\fR command, when passed
.nf

@test "invoking foo with a nonexistent file prints an error" {
run -1 foo nonexistent_filename
run \-1 foo nonexistent_filename
[ "$output" = "foo: no such file \'nonexistent_filename\'" ]
}
.
Expand All @@ -55,14 +55,14 @@ For example, let\'s say you\'re testing that the \fBfoo\fR command, when passed
.IP "" 0
.
.P
The \fB=1\fR as first argument tells \fBrun\fR to expect 1 as an exit status, and to fail if the command exits with any other value\. On failure, both actual and expected values will be displayed, along with the invoked command and its output:
The \fB\-1\fR as first argument tells \fBrun\fR to expect 1 as an exit status, and to fail if the command exits with any other value\. On failure, both actual and expected values will be displayed, along with the invoked command and its output:
.
.IP "" 4
.
.nf

(in test file test\.bats, line 2)
`run -1 foo nonexistent_filename\' failed, expected exit code 1, got 127
`run \-1 foo nonexistent_filename\' failed, expected exit code 1, got 127
.
.fi
.
Expand All @@ -82,7 +82,7 @@ A third special variable, the \fB$lines\fR array, is available for easily access
.nf

@test "invoking foo without arguments prints usage" {
run -1 foo
run \-1 foo
[ "${lines[0]}" = "usage: foo <filename>" ]
}
.
Expand All @@ -94,21 +94,7 @@ A third special variable, the \fB$lines\fR array, is available for easily access
By default \fBrun\fR leaves out empty lines in \fB${lines[@]}\fR\. Use \fBrun \-\-keep\-empty\-lines\fR to retain them\.
.
.P
Additionally, you can use \fBrun \-\-output <mode>\fR to control what goes into \fB$output\fR and \fB$lines\fR\. The available values for \fB<mode>\fR are:
.
.IP "\(bu" 4
\fBmerged\fR: the default when \fB\-\-output\fR is not specified, interleaves stdout and stderr
.
.IP "\(bu" 4
\fBseparate\fR: splits stderr off to \fB$stderr\fR and \fB${stderr_lines[@]}\fR, stdout is still available as \fB$output\fR and \fB${lines[@]}\fR
.
.IP "\(bu" 4
\fBstderr\fR: discards stdout and fills \'$stderr\fBand\fR${stderr_lines[@]}`
.
.IP "\(bu" 4
\fBstdout\fR: discards stdout and fills \fB$output\fR and \fB${lines[@]}\fR
.
.IP "" 0
Additionally, you can use \fB\-\-separate\-stderr\fR to split stdout and stderr into \fB$output\fR/\fB$stderr\fR and \fB${lines[@]}\fR/\fB${stderr_lines[@]}\fR\.
.
.P
All additional parameters to run should come before the command\. If you want to run a command that starts with \fB\-\fR, prefix it with \fB\-\-\fR to prevent \fBrun\fR from parsing it as an option\.
Expand Down Expand Up @@ -138,7 +124,7 @@ Tests can be skipped by using the \fBskip\fR command at the point in a test you

@test "A test I don\'t want to execute for now" {
skip
run -0 foo
run \-0 foo
}
.
.fi
Expand All @@ -154,7 +140,7 @@ Optionally, you may include a reason for skipping:

@test "A test I don\'t want to execute for now" {
skip "This command will return zero soon, but not now"
run -0 foo
run \-0 foo
}
.
.fi
Expand All @@ -173,7 +159,7 @@ Or you can skip conditionally:
skip "foo isn\'t bar"
fi

run -0 foo
run \-0 foo
}
.
.fi
Expand Down
45 changes: 44 additions & 1 deletion test/bats.bats
Expand Up @@ -1213,4 +1213,47 @@ EOF

@test "Test with a name that is waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay too long" {
skip "This test should only check if the long name chokes bats' internals during execution"
}
}

@test "BATS_CODE_QUOTE_STYLE works with any two characters (even unicode)" {
BATS_CODE_QUOTE_STYLE='``' run -1 bats --tap "${FIXTURE_ROOT}/failing.bats"
# shellcheck disable=SC2016
[ "${lines[3]}" == '# `eval "( exit ${STATUS:-1} )"` failed' ]


export BATS_CODE_QUOTE_STYLE='😁😂'
if [[ ${#BATS_CODE_QUOTE_STYLE} -ne 2 ]]; then
# for example, this happens on windows!
skip 'Unicode chars are not counted as one char in this system'
fi
run -1 bats --tap "${FIXTURE_ROOT}/failing.bats"
# shellcheck disable=SC2016
[ "${lines[3]}" == '# 😁eval "( exit ${STATUS:-1} )"😂 failed' ]
}

@test "BATS_CODE_QUOTE_STYLE=custom requires BATS_CODE_QUOTE_BEGIN/END" {
# unset because they are set in the surrounding scope
unset BATS_BEGIN_CODE_QUOTE BATS_END_CODE_QUOTE

BATS_CODE_QUOTE_STYLE=custom run -1 bats --tap "${FIXTURE_ROOT}/passing.bats"
[ "${lines[0]}" == 'ERROR: BATS_CODE_QUOTE_STYLE=custom requires BATS_BEGIN_CODE_QUOTE and BATS_END_CODE_QUOTE to be set' ]

# shellcheck disable=SC2016
BATS_CODE_QUOTE_STYLE=custom \
BATS_BEGIN_CODE_QUOTE='$(' \
BATS_END_CODE_QUOTE=')' \
run -1 bats --tap "${FIXTURE_ROOT}/failing.bats"
# shellcheck disable=SC2016
[ "${lines[3]}" == '# $(eval "( exit ${STATUS:-1} )") failed' ]
}

@test "Warn about invalid BATS_CODE_QUOTE_STYLE" {
BATS_CODE_QUOTE_STYLE='' run -1 bats --tap "${FIXTURE_ROOT}/passing.bats"
[ "${lines[0]}" == 'ERROR: Unknown BATS_CODE_QUOTE_STYLE: ' ]

BATS_CODE_QUOTE_STYLE='1' run -1 bats --tap "${FIXTURE_ROOT}/passing.bats"
[ "${lines[0]}" == 'ERROR: Unknown BATS_CODE_QUOTE_STYLE: 1' ]

BATS_CODE_QUOTE_STYLE='three' run -1 bats --tap "${FIXTURE_ROOT}/passing.bats"
[ "${lines[0]}" == 'ERROR: Unknown BATS_CODE_QUOTE_STYLE: three' ]
}