21 Commits

Author SHA1 Message Date
Thomas Levine
5e3c918675 add + indent symbol so directories line up 2016-01-28 15:12:34 +00:00
Thomas Levine
f5f98c8c2f fix fixture to not expect stderr 2016-01-28 15:11:27 +00:00
Thomas Levine
ce175cc3df test stdout stderr for TAP 2016-01-28 14:55:11 +00:00
Thomas Levine
cabaaa7ba4 TAP test 2016-01-28 14:51:43 +00:00
Thomas Levine
a663085069 remove awk dependency in test suite
I'm on NixOS, so awk isn't in /usr/bin. I can search for it with
/usr/bin/env, but then I can't use -f in the shebang line.
2016-01-27 10:51:04 +00:00
Thomas Levine
6bb606a3cc test plan 2016-01-27 10:39:07 +00:00
Thomas Levine
1c93e9a5c2 logfile 2016-01-27 10:31:51 +00:00
Thomas Levine
508e695dc3 error on fail 2016-01-27 10:25:21 +00:00
Thomas Levine
a51d96631f print skip count at end 2016-01-27 10:24:28 +00:00
Thomas Levine
1cd9991587 print stdout for tap and not-tap 2016-01-27 10:17:33 +00:00
Thomas Levine
0f1c2848b4 tap indentation comments for directories 2016-01-27 10:14:22 +00:00
Thomas Levine
9d10e12633 assorted tap stuff 2016-01-27 09:48:30 +00:00
Thomas Levine
40979f6e18 more tap 2016-01-27 01:13:55 +00:00
Thomas Levine
33e158e8f7 more tap 2016-01-27 01:08:04 +00:00
Thomas Levine
7ecacad132 oops 2016-01-27 01:05:43 +00:00
Thomas Levine
c8df46014d test test successes better 2016-01-27 01:05:08 +00:00
Thomas Levine
de2da89169 convert indents to comments 2016-01-27 00:50:55 +00:00
Thomas Levine
a6d6730e74 start writing non-tap cases 2016-01-27 00:32:51 +00:00
Thomas Levine
d01e993041 call urchin -s in cross-shell tests 2016-01-25 14:00:47 +00:00
Thomas Levine
b5c6464eab remove "urchin -x" test 2016-01-25 13:57:14 +00:00
Thomas Levine
329fc27929 remove "urchin -x"
unnecessary now that shall exists
2016-01-25 13:35:54 +00:00
11 changed files with 167 additions and 89 deletions

View File

@@ -6,7 +6,7 @@ for shell in dash bash ksh zsh; do
if which $shell > /dev/null 2> /dev/null; then if which $shell > /dev/null 2> /dev/null; then
echo echo
echo Running urchin tests in $shell echo Running urchin tests in $shell
$shell urchin tests | tail -n 3 $shell urchin -s $shell tests | tail -n 3
else else
echo echo
echo Skipping $shell because it is not in the PATH echo Skipping $shell because it is not in the PATH

View File

@@ -4,9 +4,9 @@
/ /_/ / / / /__/ / / / / / / / / /_/ / / / /__/ / / / / / / /
\__,_/_/ \___/_/ /_/_/_/ /_/ \__,_/_/ \___/_/ /_/_/_/ /_/
Urchin is a file-based test harness, normally used for testing shell programs. Urchin is a test framework for shell. It is implemented in
It is written in portable shell and should thus work on GNU/Linux, BSD portable /bin/sh and should work on GNU/Linux, Mac OS X, and
(including Mac OS X), and other Unix-like platforms. other Unix platforms.
## Try it out ## Try it out
Urchin's tests are written in Urchin, so you can run them to see what Urchin Urchin's tests are written in Urchin, so you can run them to see what Urchin
@@ -27,15 +27,14 @@ run this:
cd urchin cd urchin
./cross-shell-tests ./cross-shell-tests
## Install ## Globally
Urchin is contained in a single file, so you can install it by copying it to a Download Urchin like so (as root) (or use npm, below):
directory in your `PATH`. For example, you can run the following as root.
cd /usr/local/bin cd /usr/local/bin
wget https://raw.github.com/tlevine/urchin/master/urchin wget https://raw.github.com/tlevine/urchin/master/urchin
chmod +x urchin chmod +x urchin
Urchin can be installed with npm too. Can be installed with npm too:
npm install -g urchin npm install -g urchin
@@ -87,14 +86,15 @@ Files are only run if they are executable, and files beginning with `.` are
ignored. Thus, fixtures and libraries can be included sloppily within the test ignored. Thus, fixtures and libraries can be included sloppily within the test
directory tree. The test passes if the file exits 0; otherwise, it fails. directory tree. The test passes if the file exits 0; otherwise, it fails.
Tests files and subdirectories are run in ASCIIbetical order within each In case you care about the order in which your tests execute, consider that
directory; that is,
urchin looks for files within a directory in the following manner. urchin looks for files within a directory in the following manner.
for file in *; do for file in *; do
do_something_with_test_file $file do_something_with_test_file $file
done done
Tests within a directory are executed in whatever order `*` returns.
### Writing cross-shell compatibility tests for testing shell code ### Writing cross-shell compatibility tests for testing shell code
While you could write your test scripts to explicitly invoke the functionality While you could write your test scripts to explicitly invoke the functionality
@@ -106,20 +106,24 @@ The specific approach depends on your test scenario:
* (b) Your scripts _source_ scripts containing portable shell code. * (b) Your scripts _source_ scripts containing portable shell code.
#### (a) Cross-shell tests with test scripts that _invoke_ shell scripts #### (a) Cross-shell tests with test scripts that _invoke_ shell scripts
Urchin sets the `TEST_SHELL` environment variable so that you may change the
shell with which your tests call other shell programs. To run your test
scripts in multiple shells you must call `$TEST_SHELL` in your tests and then
run urchin with the appropriate option.
First, consider using [shall](https://github.com/mklement0/shall).
#!/usr/bin/env shall
echo This is a test file.
Alternatively, you can use urchin's built-in recognition of the
`TEST_SHELL` environment variable.
In your test scripts, invoke the shell scripts to test via the shell In your test scripts, invoke the shell scripts to test via the shell
specified in environment variable `TEST_SHELL` rather than directly; specified in environment variable `TEST_SHELL` rather than directly;
e.g.: `$TEST_SHELL ../foo bar` (rather than just `../foo bar`). e.g.: `$TEST_SHELL ../foo bar` (rather than just `../foo bar`).
Note that if you alsow want your test scripts to work when run directly,
outside of Urchin, be sure to target scripts that happen to be in the
current directory with prefix `./`; e.g., `$TEST_SHELL ./baz`
(rather than `$TEST_SHELL baz`).
On invocation of Urchin, prepend a definition of environment variable Then, on invocation of Urchin, prepend a definition of environment variable
`TEST_SHELL` specifying the shell to test with, e.g., `TEST_SHELL` specifying the shell to test with, e.g.: `TEST_SHELL=zsh urchin ./tests`.
TEST_SHELL=zsh urchin ./tests
To test with multiple shells in sequence, use something like: To test with multiple shells in sequence, use something like:
for shell in sh bash ksh zsh; do for shell in sh bash ksh zsh; do
@@ -127,20 +131,14 @@ To test with multiple shells in sequence, use something like:
done done
If `TEST_SHELL` has no value, Urchin defines it as `/bin/sh`, so the test If `TEST_SHELL` has no value, Urchin defines it as `/bin/sh`, so the test
scripts can rely on `$TEST_SHELL` always containing a value when Urchin runs scripts can rely on `$TEST_SHELL` always containing a value.
them.
That said, we still recommand that you account for the possibility that
`$TEST_SHELL` does not contain a value so that you may run your test scripts
without Urchin. Supporting this case is very simple; when you invoke scripts
that happen to be in the current directory, be sure to use the prefix `./`,
e.g., `$TEST_SHELL ./baz` rather than `$TEST_SHELL baz`.
#### (b) Cross-shell tests with test scripts that _source_ shell scripts #### (b) Cross-shell tests with test scripts that _source_ shell scripts
If you _source_ shell code in your test scripts, it is the test scripts If you _source_ shell code in your test scripts, it is the test scripts
themselves that must be run with the shell specified. themselves that must be run with the shell specified.
Urchin supports the `-s <shell>` option, which instructs To that end, Urchin supports the `-s <shell>` option, which instructs
Urchin to invoke the test scripts with the specified shell; e.g., `-s bash`. Urchin to invoke the test scripts with the specified shell; e.g., `-s bash`.
(In addition, Urchin sets environment variable `TEST_SHELL` to the specified (In addition, Urchin sets environment variable `TEST_SHELL` to the specified
shell.) shell.)
@@ -156,12 +154,21 @@ To test with multiple shells in sequence, use something like:
urchin -s $shell ./tests urchin -s $shell ./tests
done done
Also consider using [shall](https://github.com/mklement0/shall). <!--
It does something similar, but the interface may be more intuitive. #### (c) Cross shell tests with `urchin -x` (experimental)
If you run urchin with the `-x` flag, it will be as if you ran
`$TEST_SHELL`. Unless `$TEST_SHELL` isn't set, in which case it'll
be as if you ran `/bin/sh`. Putting this in she shebang line might
eventually work out to be a cleaner way of doing cross-shell testing.
#!/usr/bin/env shall #!/usr/bin/env urchin -x
echo This is a test file. test a = a
It might make sense if you do this.
export TEST_SHELL=zsh && urchin -x
export TEST_SHELL=bash && urchin -x
-->
## Alternatives to Urchin ## Alternatives to Urchin
Alternatives to Urchin are discussed in Alternatives to Urchin are discussed in
[this blog post](https://blog.scraperwiki.com/2012/12/how-to-test-shell-scripts/). [this blog post](https://blog.scraperwiki.com/2012/12/how-to-test-shell-scripts/).

View File

@@ -1,5 +1,3 @@
#!/usr/bin/awk -f #!/usr/bin/env true
true will processed the contents of this script, but that
# This script will only succeed if it is indeed processed by awk. means that nothing will happen and the script will exit 0
BEGIN { print "ok" }

View File

@@ -3,6 +3,5 @@
# Tests the `-s <shell> option, which invokes shebang-less and sh-shebang-line test scripts with the specified shell (for testing *sourced* shell code). # Tests the `-s <shell> option, which invokes shebang-less and sh-shebang-line test scripts with the specified shell (for testing *sourced* shell code).
which bash >/dev/null || { echo "Cannot test -s option: bash cannot be located." >&2; exit 2; } which bash >/dev/null || { echo "Cannot test -s option: bash cannot be located." >&2; exit 2; }
which /usr/bin/awk >/dev/null || { echo "Cannot test -s option: /usr/bin/awk not found." >&2; exit 2; }
../../urchin -s bash ./.test-run-by-specified-shell ../../urchin -s bash ./.test-run-by-specified-shell

View File

@@ -0,0 +1,10 @@
# Begin - .testsuite/
not ok 1 - a
# ------------ Begin output ------------
# This is stdout from a.
# ------------ End output ------------
ok 2 - b
ok 3 - # SKIP c
# End - .testsuite/
# Took 0 seconds.
1..3

4
tests/TAP/.testsuite/a Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
echo This is stderr from a. > /dev/stderr
echo This is stdout from a. > /dev/stdout
false

4
tests/TAP/.testsuite/b Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
echo This is stderr from b. > /dev/stderr
echo This is stdout from b. > /dev/stdout
true

1
tests/TAP/.testsuite/c Normal file
View File

@@ -0,0 +1 @@
This should not be run.

View File

@@ -0,0 +1,4 @@
tmp=$(mktemp)
../../urchin -t .testsuite/ | sed 1d > $tmp
diff $tmp .expected-output

View File

@@ -1,3 +0,0 @@
#!/bin/sh
test c = $(../urchin -x .print-arg-3 a 'b b b b' c d e)

102
urchin
View File

@@ -30,9 +30,16 @@ recurse() {
if [ -d "$potential_test" ] if [ -d "$potential_test" ]
then then
(
if $tap_format; then
indent $indent_level | sed 's/ /#/g'
echo "# Begin - ${potential_test}"
else
indent $indent_level indent $indent_level
echo " ${potential_test}" echo "+ ${potential_test}"
fi
(
cd -- "$potential_test" cd -- "$potential_test"
[ -f setup_dir ] && [ -x setup_dir ] && ./setup_dir >> "$stdout_file" [ -f setup_dir ] && [ -x setup_dir ] && ./setup_dir >> "$stdout_file"
@@ -51,9 +58,15 @@ recurse() {
[ -f teardown ] && [ -x teardown ] && ./teardown >> "$stdout_file" [ -f teardown ] && [ -x teardown ] && ./teardown >> "$stdout_file"
done done
[ -f teardown_dir ] && [ -x teardown_dir ] && ./teardown_dir >> "$stdout_file" [ -f teardown_dir ] && [ -x teardown_dir ] && ./teardown_dir >> "$stdout_file"
echo
) )
elif [ -x "$potential_test" ] if $tap_format; then
indent $indent_level | sed 's/ /#/g'
echo "# End - ${potential_test}"
else
echo
fi
else
if [ -x "$potential_test" ]
then then
[ -f setup ] && [ -x setup ] && ./setup >> "$stdout_file" [ -f setup ] && [ -x setup ] && ./setup >> "$stdout_file"
@@ -69,22 +82,55 @@ recurse() {
[ -f teardown ] && [ -x teardown ] && ./teardown >> "$stdout_file" [ -f teardown ] && [ -x teardown ] && ./teardown >> "$stdout_file"
if [ $exit_code -eq 0 ]; then
result=success
else
result=fail
fi
else
result=skip
fi
echo "${result}" >> "$logfile"
if $tap_format; then
n=$(grep -ce '^\(success\|fail\|skip\)' "$logfile")
if [ "$result" == fail ]; then
not='not '
else
not=''
fi
if [ "$result" == skip ]; then
skip='# SKIP '
else
skip=''
fi
echo "${not}ok $n - ${skip}${potential_test}"
if [ "$result" == fail ]; then
echo '# ------------ Begin output ------------'
sed 's/^/# /' "$stdout_file"
echo '# ------------ End output ------------'
fi
else
indent $indent_level indent $indent_level
if [ $exit_code -eq 0 ] case "$result" in
then success)
# On success, print a green '✓' # On success, print a green '✓'
printf '\033[32m✓ \033[0m' printf '\033[32m✓ \033[0m'
printf '%s\n' "${potential_test}" printf '%s\n' "${potential_test}"
printf '%s\n' "${potential_test} passed" >> "$logfile" ;;
else fail)
# On fail, print a red '✗' # On fail, print a red '✗'
printf '\033[31m✗ \033[0m' printf '\033[31m✗ \033[0m'
printf '%s\n' "${potential_test}" printf '%s\n' "${potential_test}"
printf '%s\n' "${potential_test} failed" >> "$logfile"
printf '\033[31m' # Print output captured from failed test in red. printf '\033[31m' # Print output captured from failed test in red.
cat "$stdout_file" cat "$stdout_file"
printf '\033[0m' printf '\033[0m'
;;
skip)
printf ' %s\n' "${potential_test}"
;;
esac
fi fi
fi fi
[ $indent_level -eq 0 ] && rm "$stdout_file" [ $indent_level -eq 0 ] && rm "$stdout_file"
@@ -107,15 +153,12 @@ $USAGE
have shebang line "#!/bin/sh" with the specified shell. have shebang line "#!/bin/sh" with the specified shell.
-f Force running even if the test directory's name does not -f Force running even if the test directory's name does not
contain the word "test". contain the word "test".
-t Format output in Test Anything Protocol (TAP)
-h This help. -h This help.
Go to https://github.com/tlevine/urchin for documentation on writing tests. Go to https://github.com/tlevine/urchin for documentation on writing tests.
EOF EOF
# [Experimental -x option left undocumented for now.]
# -x [Experimental; not meant for direct invocation, but for use in
# the shebang line of test scripts]
# Run with "\$TEST_SHELL", falling back on /bin/sh.
} }
plural () { plural () {
@@ -131,7 +174,11 @@ plural () {
} }
urchin_go() { urchin_go() {
echo Running tests at $(date +%Y-%m-%dT%H:%M:%S) | tee "$logfile" rm -f "$logfile"
if "$tap_format"; then
printf \#\
fi
echo Running tests at $(date +%Y-%m-%dT%H:%M:%S)
start=$(date +%s) start=$(date +%s)
# Determine the environment variable to define for test scripts # Determine the environment variable to define for test scripts
@@ -151,13 +198,23 @@ urchin_go() {
finish=$(date +%s) finish=$(date +%s)
elapsed=$(($finish - $start)) elapsed=$(($finish - $start))
passed=$(grep -c '^success' "$logfile")
failed=$(grep -c '^fail' "$logfile")
skipped=$(grep -c '^skip' "$logfile")
if $tap_format; then
echo "# Took $elapsed $(plural second $elapsed)."
echo 1..$(($passed + $failed + $skipped))
else
echo "Done, took $elapsed $(plural second $elapsed)." echo "Done, took $elapsed $(plural second $elapsed)."
set -- $(grep -e 'passed$' "$logfile"|wc -l) $(grep -e 'failed$' "$logfile"|wc -l) printf '%s\n' "$passed $(plural test "$passed") passed."
printf '%s\n' "$1 $(plural test "$1") passed." printf '%s\n' "$skipped $(plural test "$skipped") skipped."
[ $2 -gt 0 ] && printf '\033[31m' || printf '\033[32m' # If tests failed, print the message in red, otherwise in green. [ $failed -gt 0 ] && printf '\033[31m' || printf '\033[32m' # If tests failed, print the message in red, otherwise in green.
printf '%s\n' "$2 $(plural test "$2") failed." printf '%s\n' "$failed $(plural test "$failed") failed."
printf '\033[m' printf '\033[m'
return "$2" fi
rm -f "$logfile"
test -z "$failed" || test "$failed" -eq '0'
} }
urchin_molly_guard() { urchin_molly_guard() {
@@ -174,6 +231,7 @@ urchin_molly_guard() {
shell_for_sh_tests= shell_for_sh_tests=
force=false force=false
tap_format=false
while [ $# -gt 0 ] while [ $# -gt 0 ]
do do
case "$1" in case "$1" in
@@ -183,11 +241,7 @@ do
shell_for_sh_tests=$1 shell_for_sh_tests=$1
which "$shell_for_sh_tests" >/dev/null || { echo "Cannot find specified shell: '$shell_for_sh_tests'" >&2; urchin_help >&2; exit 2; } which "$shell_for_sh_tests" >/dev/null || { echo "Cannot find specified shell: '$shell_for_sh_tests'" >&2; urchin_help >&2; exit 2; }
;; ;;
-x) # [EXPERIMENTAL; UNDOCUMENTED FOR NOW] `urchin -x <test-script>` in a test script's shebang line is equivalent to invoking that script with `"$TEST_SHELL" <test-script>` -t) tap_format=true;;
shift
urchinsh=${TEST_SHELL:-/bin/sh}
"$urchinsh" "$@"
exit $?;;
-h|--help) urchin_help -h|--help) urchin_help
exit 0;; exit 0;;
-*) urchin_help >&2 -*) urchin_help >&2