Codebase list clitest / 08fecc3
New upstream version 0.4.0 Antonio Terceiro 3 years ago
14 changed file(s) with 473 addition(s) and 339 deletion(s). Raw diff Collapse all Expand all
0 root = true
1
2 [*]
3 indent_style = space
4 indent_size = 4
5 end_of_line = lf
6 charset = utf-8
7 trim_trailing_whitespace = true
8 insert_final_newline = true
9
10 [*.md]
11 trim_trailing_whitespace = false
12
13 [*.yml]
14 indent_size = 2
0 /local/
0 .vscode
00 language: bash
11
2 os:
3 - linux
4 - osx
5
6 # Prepare the environment
7 addons:
8 apt:
9 packages:
10 # Linux: only bash (and sh) are installed by default
11 - ksh
12 - zsh
13 # Linux: install the checkbashisms script
14 - devscripts
15 before_install:
16 # OS X: install the checkbashisms script (||true to avoid Linux fail)
17 - test "$TRAVIS_OS_NAME" = osx && brew update || true
18 - test "$TRAVIS_OS_NAME" = osx && brew install checkbashisms || true
19 - test "$TRAVIS_OS_NAME" = osx && brew install dash || true
20
212 script:
22 # Run some code checkings on the script itself
23 - checkbashisms --posix clitest
24 # Run the full test suite in all the supported POSIX shells
25 - bash clitest test.md
26 - dash clitest test.md
27 - ksh clitest test.md
28 - zsh clitest test.md
29 - sh clitest test.md
3 - make docker-build
4 - make versions
5 - make lint
6 - make test
307
318 notifications:
329 email: false
0 # clitest changelog
1
2 The changelog is here:
3 https://github.com/aureliojargas/clitest/releases
0 # clitest-dev
1 # Local Docker image used for clitest development.
2 #
3 # It has all the required tools for linting and testing clitest code.
4 # See Makefile for commands to build and run this image.
5 #
6 # If you're searching for the official clitest Docker image (for users):
7 # https://hub.docker.com/r/aureliojargas/clitest
8
9 FROM alpine:3.12
10
11 # Perl is required by clitest's --regex matching mode
12 RUN apk --no-cache add \
13 bash dash mksh zsh \
14 perl \
15 make \
16 checkbashisms shellcheck shfmt
17
18 WORKDIR /mnt
0 # Developer workflow: run locally the same commands Travis will run in
1 # the CI. See the .travis.yml file for the list of commands.
2 #
3 # By default, the linting and testing targets are run inside the
4 # clitest-dev Docker container. To run them directly on the host,
5 # avoiding the container, unset the `docker_run` variable. Examples:
6 #
7 # make test-bash # test using container's bash
8 # make test-bash docker_run= # test using host's bash
9
10 docker_image = clitest-dev
11 docker_run = docker run --rm -it -v $$PWD:/mnt $(docker_image)
12 test_cmd = ./clitest --first --progress none test.md
13
14 default:
15 @echo "Read the comments in the Makefile for help"
16
17 fmt:
18 $(docker_run) shfmt -w -i 4 -ci -kp -sr clitest
19
20 lint:
21 $(docker_run) shfmt -d -i 4 -ci -kp -sr clitest
22 $(docker_run) checkbashisms --posix clitest
23 $(docker_run) shellcheck clitest
24
25 test: test-bash test-dash test-mksh test-sh test-zsh
26 test-%:
27 $(docker_run) $* $(test_cmd)
28
29 versions:
30 @$(docker_run) sh -c 'apk list 2>/dev/null | cut -d " " -f 1 | sort'
31
32 docker-build:
33 docker build -t $(docker_image) -f Dockerfile.dev .
34
35 docker-run:
36 $(docker_run) $(cmd)
3232
3333 Now check if everything is fine:
3434
35 ```console
36 $ clitest -V
37 clitest HEAD
38 https://github.com/aureliojargas/clitest/tree/HEAD
39 $
40 ```
35 ```
36 clitest --help
37 ```
38
39
40 ## Docker image
41
42 You can also run clitest in a Docker container ([more info in Docker Hub](https://hub.docker.com/r/aureliojargas/clitest)).
43
44 ```
45 docker run --rm -t aureliojargas/clitest --help
46 ```
47
4148
4249 ## Quick Intro
4350
110117 $
111118 ```
112119
113 There are more examples and instructions in the [examples folder][10]. For a real-life collection of hundreds of test files, see [funcoeszz test files][24].
120 There are more examples and instructions in the [examples folder][10].
121 For a real-life collection of hundreds of test files, see
122 [funcoeszz test files][24].
114123
115124
116125 ## Testable Documentation
188197
189198 Examples of testable documentation handled by clitest:
190199
200 * https://github.com/aureliojargas/txt2regex/blob/master/tests/features.md
201 * https://github.com/aureliojargas/txt2regex/blob/master/tests/cmdline.md
202 * https://github.com/aureliojargas/sedsed/blob/master/test/command_line.md
203 * https://github.com/aureliojargas/replace/blob/master/README.md
204 * https://github.com/aureliojargas/clitest/blob/master/test.md
191205 * https://github.com/caarlos0/jvm/blob/master/tests/test.clitest.md
192206 * https://github.com/caarlos0/git-add-remote/blob/master/tests/suite.clitest.md
193 * https://github.com/aureliojargas/clitest/blob/master/examples/install-software.md
194 * https://github.com/aureliojargas/clitest/blob/master/test.md
195207
196208
197209 ## Alternative Syntax: Inline Output
316328
317329 ## Quiet operation
318330
319 When automating the tests execution, use `--quiet` to show no output
320 and just check the exit code to make sure all tests have passed. Using `--first` to fail fast is also a good idea in this case.
331 When automating the tests execution, use `--quiet` to show no output and
332 just check the exit code to make sure all tests have passed. Using
333 `--first` to fail fast is also a good idea in this case.
321334
322335 ```bash
323336 if clitest --quiet --first tests.txt
379392 ## Nerdiness
380393
381394 * Use any text file format for the tests, it doesn't matter. The command
382 lines just need to be grepable and have a fixed prefix (or none).
395 lines just need to be grepable and have a fixed prefix (or even none).
383396 Even Windows text files (CR+LF) will work fine.
384397
385 * The cmdline power is available in your test files: use variables,
398 * The command line power is available in your test files: use variables,
386399 pipes, redirection, create files, folders, move around…
387400
388 * All the commands are tested in the same shell. Defined variables,
389 aliases and functions will persist between tests.
390
391 * Both STDIN and STDOUT are catch, you can also test error messages.
401 * All the commands are tested using a single shell session. This means
402 that variables, aliases and functions defined in one test will persist
403 in the following tests.
404
405 * Both STDOUT and STDERR are captured, so you can also test error
406 messages.
392407
393408 * To test STDOUT/STDERR and the exit code at the same time, add a
394409 `;echo $?` after the command.
420435
421436 ## Choose the execution shell
422437
423 The clitest shebang is `#!/bin/sh`. That's the default shell that will be used to run your test command lines. Depending on the system, that path points to a different shell, such as ash, dash, or bash ([running in POSIX mode][23]).
424
425 To force your test commands to always run on a specific shell, just call the desired shell before:
438 The clitest shebang is `#!/bin/sh`. That's the default shell that will
439 be used to run your test command lines. Depending on the system, that
440 path points to a different shell, such as ash, dash, or bash
441 ([running in POSIX mode][23]).
442
443 To force your test commands to always run on a specific shell, just call
444 the desired shell before:
426445
427446 ```bash
428447 clitest tests.txt # Uses /bin/sh
433452 ## Portability
434453
435454 This script was carefully coded to be portable between [POSIX][13]
436 shells.
437
438 It was tested in:
439
440 * Bash 3.2
441 * dash 0.5.5.1
442 * ksh 93u 2011-02-08
443 * zsh 5.0.5
455 shells. It's code is validated by [checkbashisms][25] and
456 [shellcheck][26].
457
458 To make sure it keeps working as expected, after every change clitest is
459 automatically tested in the CI, using the following shells:
460
461 - bash
462 - dash
463 - ksh
464 - sh (busybox)
465 - zsh
444466
445467 Portability issues are considered serious bugs, please
446468 [report them][14]!
492514 [22]: https://github.com/aureliojargas/clitest/blob/master/LICENSE.txt
493515 [23]: https://www.gnu.org/software/bash/manual/html_node/Bash-POSIX-Mode.html
494516 [24]: https://github.com/funcoeszz/funcoeszz/tree/master/testador
517 [25]: https://linux.die.net/man/1/checkbashisms
518 [26]: https://www.shellcheck.net/
+257
-236
clitest less more
66 # GitHub: https://github.com/aureliojargas/clitest
77 #
88 # POSIX shell script:
9 # This script was coded to be compatible with POSIX shells.
10 # Tested in Bash 3.2, dash 0.5.5.1, ksh 93u 2011-02-08.
11 # Note: Can't set -o posix nor POSIXLY_CORRECT: test env must be intact.
9 # This script is carefully coded to be compatible with POSIX shells.
10 # It is currently tested in bash, dash, ksh, zsh and busybox's sh.
1211 #
1312 # Exit codes:
1413 # 0 All tests passed, or normal operation (--help, --list, ...)
1817 # Test environment:
1918 # By default, the tests will run in the current working directory ($PWD).
2019 # You can change to another dir normally using 'cd' inside the test file.
21 # All the tests are executed in the same shell, using eval. Test data
22 # such as variables and working directory will persist between tests.
20 # All the tests are executed in the same shell session, using eval. Test
21 # data such as variables and working directory will persist between tests.
2322 #
2423 # Namespace:
2524 # All variables and functions in this script are prefixed by 'tt_' to
2625 # avoid clashing with test's variables, functions, aliases and commands.
2726
2827 tt_my_name="$(basename "$0")"
29 tt_my_version='HEAD'
30 tt_my_version_url="https://github.com/aureliojargas/clitest/tree/$tt_my_version"
28 tt_my_url='https://github.com/aureliojargas/clitest'
29 tt_my_version='0.4.0'
3130
3231 # Customization (if needed, edit here or use the command line options)
3332 tt_prefix=''
5958 --diff-options OPTIONS Set diff command options (default: '$tt_diff_options')
6059 --inline-prefix PREFIX Set inline output prefix (default: '$tt_inline_prefix')
6160 --prefix PREFIX Set command line prefix (default: '$tt_prefix')
62 --prompt STRING Set prompt string (default: '$tt_prompt')"
61 --prompt STRING Set prompt string (default: '$tt_prompt')
62
63 See also: $tt_my_url"
6364
6465 # Flags (0=off, 1=on), most can be altered by command line options
6566 tt_debug=0
108109 tt_nl='
109110 '
110111
111
112112 ### Utilities
113113
114 tt_clean_up ()
115 {
114 tt_clean_up() {
116115 rm -rf "$tt_temp_dir"
117116 }
118 tt_message ()
119 {
117 tt_message() {
120118 test "$tt_output_mode" = 'quiet' && return 0
121119 test $tt_missing_nl -eq 1 && echo
122120 printf '%s\n' "$*"
123121 tt_separator_line_shown=0
124122 tt_missing_nl=0
125123 }
126 tt_message_part () # no line break
127 {
124 tt_message_part() { # no line break
128125 test "$tt_output_mode" = 'quiet' && return 0
129126 printf '%s' "$*"
130127 tt_separator_line_shown=0
131128 tt_missing_nl=1
132129 }
133 tt_error ()
134 {
130 tt_error() {
135131 test $tt_missing_nl -eq 1 && echo
136132 printf '%s\n' "$tt_my_name: Error: $1" >&2
137133 tt_clean_up
138134 exit 2
139135 }
140 tt_debug () # $1=id, $2=contents
141 {
136 tt_debug() { # $1=id, $2=contents
142137 test $tt_debug -ne 1 && return 0
143 if test INPUT_LINE = "$1"
144 then
138 if test INPUT_LINE = "$1"; then
145139 # Original input line is all blue
146140 printf "${tt_color_blue}[%10s: %s]${tt_color_off}\n" "$1" "$2"
147141 else
151145 sed "s/$tt_tab/${tt_color_green}<tab>${tt_color_off}/g"
152146 fi
153147 }
154 tt_separator_line ()
155 {
148 tt_separator_line() {
156149 printf "%${COLUMNS}s" ' ' | tr ' ' -
157150 }
158 tt_list_test () # $1=normal|list|ok|fail
159 {
151 tt_list_test() { # $1=normal|list|ok|fail
160152 # Show the test command in normal mode, --list and --list-run
161153 case "$1" in
162154 normal | list)
163155 # Normal line, no color, no stamp (--list)
164156 tt_message "#${tt_test_number}${tt_tab}${tt_test_command}"
165 ;;
157 ;;
166158 ok)
167159 # Green line or OK stamp (--list-run)
168 if test $tt_use_colors -eq 1
169 then
160 if test $tt_use_colors -eq 1; then
170161 tt_message "${tt_color_green}#${tt_test_number}${tt_tab}${tt_test_command}${tt_color_off}"
171162 else
172163 tt_message "#${tt_test_number}${tt_tab}OK${tt_tab}${tt_test_command}"
173164 fi
174 ;;
165 ;;
175166 fail)
176167 # Red line or FAIL stamp (--list-run)
177 if test $tt_use_colors -eq 1
178 then
168 if test $tt_use_colors -eq 1; then
179169 tt_message "${tt_color_red}#${tt_test_number}${tt_tab}${tt_test_command}${tt_color_off}"
180170 else
181171 tt_message "#${tt_test_number}${tt_tab}FAIL${tt_tab}${tt_test_command}"
182172 fi
183 ;;
173 ;;
184174 esac
185175 }
186 tt_parse_range () # $1=range
187 {
176 tt_parse_range() { # $1=range
188177 # Parse numeric ranges and output them in an expanded format
189178 #
190179 # Supported formats Expanded
198187 # Later we will just grep for :number: in each test.
199188
200189 case "$1" in
201 # No range, nothing to do
202190 0 | '')
191 # No range, nothing to do
203192 return 0
204 ;;
205 # Error: strange chars, not 0123456789,-
193 ;;
206194 *[!0-9,-]*)
195 # Error: strange chars, not 0123456789,-
207196 return 1
208 ;;
197 ;;
209198 esac
210199
211200 # OK, all valid chars in range, let's parse them
213202 tt_part=
214203 tt_n1=
215204 tt_n2=
216 tt_operation=
205 tt_swap=
217206 tt_range_data=':' # :1:2:4:7:
218207
219208 # Loop each component: a number or a range
220 for tt_part in $(echo "$1" | tr , ' ')
221 do
209 for tt_part in $(echo "$1" | tr , ' '); do
222210 # If there's an hyphen, it's a range
223211 case "$tt_part" in
224212 *-*)
228216 tt_n1=${tt_part%-*}
229217 tt_n2=${tt_part#*-}
230218
231 tt_operation='+'
232 test $tt_n1 -gt $tt_n2 && tt_operation='-'
219 # Negative range, let's just reverse it (5-1 => 1-5)
220 if test "$tt_n1" -gt "$tt_n2"; then
221 tt_swap=$tt_n1
222 tt_n1=$tt_n2
223 tt_n2=$tt_swap
224 fi
233225
234226 # Expand the range (1-4 => 1:2:3:4)
235227 tt_part=$tt_n1:
236 while test $tt_n1 -ne $tt_n2
237 do
238 tt_n1=$(($tt_n1 $tt_operation 1))
228 while test "$tt_n1" -ne "$tt_n2"; do
229 tt_n1=$((tt_n1 + 1))
239230 tt_part=$tt_part$tt_n1:
240231 done
241232 tt_part=${tt_part%:}
242 ;;
233 ;;
243234 esac
244235
245236 # Append the number or expanded range to the holder
246 test $tt_part != 0 && tt_range_data=$tt_range_data$tt_part:
237 test "$tt_part" != 0 && tt_range_data=$tt_range_data$tt_part:
247238 done
248239
249 test $tt_range_data != ':' && echo $tt_range_data
240 test "$tt_range_data" != ':' && echo "$tt_range_data"
250241 return 0
251242 }
252 tt_reset_test_data ()
253 {
243 tt_reset_test_data() {
254244 tt_test_command=
255245 tt_test_inline=
256246 tt_test_mode=
259249 tt_test_diff=
260250 tt_test_ok_text=
261251 }
262 tt_run_test ()
263 {
264 tt_test_number=$(($tt_test_number + 1))
265 tt_nr_total_tests=$(($tt_nr_total_tests + 1))
266 tt_nr_file_tests=$(($tt_nr_file_tests + 1))
252 tt_run_test() {
253 tt_test_number=$((tt_test_number + 1))
254 tt_nr_total_tests=$((tt_nr_total_tests + 1))
255 tt_nr_file_tests=$((tt_nr_file_tests + 1))
267256
268257 # Run range on: skip this test if it's not listed in $tt_run_range_data
269 if test -n "$tt_run_range_data" && test "$tt_run_range_data" = "${tt_run_range_data#*:$tt_test_number:}"
270 then
271 tt_nr_total_skips=$(($tt_nr_total_skips + 1))
272 tt_nr_file_skips=$(($tt_nr_file_skips + 1))
258 if test -n "$tt_run_range_data" && test "$tt_run_range_data" = "${tt_run_range_data#*:$tt_test_number:}"; then
259 tt_nr_total_skips=$((tt_nr_total_skips + 1))
260 tt_nr_file_skips=$((tt_nr_file_skips + 1))
273261 tt_reset_test_data
274262 return 0
275263 fi
276264
277265 # Skip range on: skip this test if it's listed in $tt_skip_range_data
278266 # Note: --skip always wins over --test, regardless of order
279 if test -n "$tt_skip_range_data" && test "$tt_skip_range_data" != "${tt_skip_range_data#*:$tt_test_number:}"
280 then
281 tt_nr_total_skips=$(($tt_nr_total_skips + 1))
282 tt_nr_file_skips=$(($tt_nr_file_skips + 1))
267 if test -n "$tt_skip_range_data" && test "$tt_skip_range_data" != "${tt_skip_range_data#*:$tt_test_number:}"; then
268 tt_nr_total_skips=$((tt_nr_total_skips + 1))
269 tt_nr_file_skips=$((tt_nr_file_skips + 1))
283270 tt_reset_test_data
284271 return 0
285272 fi
290277 case "$tt_progress" in
291278 test)
292279 tt_list_test normal
293 ;;
280 ;;
294281 number)
295282 tt_message_part "$tt_test_number "
296 ;;
283 ;;
297284 none)
298285 :
299 ;;
286 ;;
300287 *)
301288 tt_message_part "$tt_progress"
302 ;;
289 ;;
303290 esac
304 ;;
291 ;;
305292 list)
306293 # List mode: just show the command and return (no execution)
307294 tt_list_test list
308295 tt_reset_test_data
309296 return 0
310 ;;
297 ;;
311298 esac
312299
313300 #tt_debug EVAL "$tt_test_command"
324311 printf %s "$tt_test_ok_text" > "$tt_test_ok_file"
325312 tt_test_diff=$(diff $tt_diff_options "$tt_test_ok_file" "$tt_test_output_file")
326313 tt_test_status=$?
327 ;;
314 ;;
328315 text)
329316 # Inline OK text represents a full line, with \n
330317 printf '%s\n' "$tt_test_inline" > "$tt_test_ok_file"
331318 tt_test_diff=$(diff $tt_diff_options "$tt_test_ok_file" "$tt_test_output_file")
332319 tt_test_status=$?
333 ;;
320 ;;
334321 eval)
335322 eval "$tt_test_inline" > "$tt_test_ok_file"
336323 tt_test_diff=$(diff $tt_diff_options "$tt_test_ok_file" "$tt_test_output_file")
337324 tt_test_status=$?
338 ;;
325 ;;
339326 lines)
340327 tt_test_output=$(sed -n '$=' "$tt_test_output_file")
341328 test -z "$tt_test_output" && tt_test_output=0
342329 test "$tt_test_output" -eq "$tt_test_inline"
343330 tt_test_status=$?
344331 tt_test_diff="Expected $tt_test_inline lines, got $tt_test_output."
345 ;;
332 ;;
346333 file)
347334 # If path is relative, make it relative to the test file path, not $PWD
348 if test $tt_test_inline = ${tt_test_inline#/}
349 then
335 if test "$tt_test_inline" = "${tt_test_inline#/}"; then
350336 tt_test_inline="$(dirname "$tt_test_file")/$tt_test_inline"
351337 fi
352338 # Abort when ok file not found/readable
353 if test ! -f "$tt_test_inline" || test ! -r "$tt_test_inline"
354 then
339 if test ! -f "$tt_test_inline" || test ! -r "$tt_test_inline"; then
355340 tt_error "cannot read inline output file '$tt_test_inline', from line $tt_line_number of $tt_test_file"
356341 fi
357342
358343 tt_test_diff=$(diff $tt_diff_options "$tt_test_inline" "$tt_test_output_file")
359344 tt_test_status=$?
360 ;;
345 ;;
361346 egrep)
362 egrep "$tt_test_inline" "$tt_test_output_file" > /dev/null
347 grep -E "$tt_test_inline" "$tt_test_output_file" > /dev/null
363348 tt_test_status=$?
364349
365350 # Test failed: the regex not matched
366 if test $tt_test_status -eq 1
367 then
351 if test $tt_test_status -eq 1; then
368352 tt_test_diff="egrep '$tt_test_inline' failed in:$tt_nl$(cat "$tt_test_output_file")"
369353
370354 # Regex errors are common and user must take action to fix them
371 elif test $tt_test_status -gt 1
372 then
355 elif test $tt_test_status -gt 1; then
373356 tt_error "check your inline egrep regex at line $tt_line_number of $tt_test_file"
374357 fi
375 ;;
358 ;;
376359 perl | regex)
377360 # Escape regex delimiter (if any) inside the regex: ' => \'
378 if test "$tt_test_inline" != "${tt_test_inline#*\'}"
379 then
361 if test "$tt_test_inline" != "${tt_test_inline#*\'}"; then
380362 tt_test_inline=$(printf %s "$tt_test_inline" | sed "s/'/\\\\'/g")
381363 fi
382364
387369 case $tt_test_status in
388370 0) # Test matched, nothing to do
389371 :
390 ;;
372 ;;
391373 1) # Test failed: the regex not matched
392374 tt_test_diff="Perl regex '$tt_test_inline' not matched in:$tt_nl$(cat "$tt_test_output_file")"
393 ;;
375 ;;
394376 127) # Perl not found :(
395377 tt_error "Perl not found. It's needed by --$tt_test_mode at line $tt_line_number of $tt_test_file"
396 ;;
378 ;;
397379 255) # Regex syntax errors are common and user must take action to fix them
398380 tt_error "check your inline Perl regex at line $tt_line_number of $tt_test_file"
399 ;;
381 ;;
400382 *)
401383 tt_error "unknown error when running Perl for --$tt_test_mode at line $tt_line_number of $tt_test_file"
402 ;;
384 ;;
403385 esac
404 ;;
386 ;;
405387 exit)
406388 test "$tt_test_exit_code" -eq "$tt_test_inline"
407389 tt_test_status=$?
408390 tt_test_diff="Expected exit code $tt_test_inline, got $tt_test_exit_code"
409 ;;
391 ;;
410392 *)
411393 tt_error "unknown test mode '$tt_test_mode'"
412 ;;
394 ;;
413395 esac
414396
415397 # Test failed :(
416 if test $tt_test_status -ne 0
417 then
418 tt_nr_file_fails=$(($tt_nr_file_fails + 1))
419 tt_nr_total_fails=$(($tt_nr_total_fails + 1))
398 if test $tt_test_status -ne 0; then
399 tt_nr_file_fails=$((tt_nr_file_fails + 1))
400 tt_nr_total_fails=$((tt_nr_total_fails + 1))
420401 tt_failed_range="$tt_failed_range$tt_test_number,"
421402
422403 # Decide the message format
423 if test "$tt_output_mode" = 'list-run'
424 then
404 if test "$tt_output_mode" = 'list-run'; then
425405 # List mode
426406 tt_list_test fail
427407 else
428408 # Normal mode: show FAILED message and the diff
429 if test $tt_separator_line_shown -eq 0 # avoid dups
430 then
409 if test $tt_separator_line_shown -eq 0; then # avoid dups
431410 tt_message "${tt_color_red}$(tt_separator_line)${tt_color_off}"
432411 fi
433412 tt_message "${tt_color_red}[FAILED #$tt_test_number, line $tt_test_line_number] $tt_test_command${tt_color_off}"
437416 fi
438417
439418 # Should I abort now?
440 if test $tt_stop_on_first_fail -eq 1
441 then
419 if test $tt_stop_on_first_fail -eq 1; then
442420 tt_clean_up
443421 exit 1
444422 fi
450428
451429 tt_reset_test_data
452430 }
453 tt_process_test_file ()
454 {
431 tt_process_test_file() {
455432 # Reset counters
456433 tt_nr_file_tests=0
457434 tt_nr_file_fails=0
462439 # Loop for each line of input file
463440 # Note: changing IFS to avoid right-trimming of spaces/tabs
464441 # Note: read -r to preserve the backslashes
465 while IFS='' read -r tt_input_line || test -n "$tt_input_line"
466 do
467 tt_line_number=$(($tt_line_number + 1))
442 while IFS='' read -r tt_input_line || test -n "$tt_input_line"; do
443 tt_line_number=$((tt_line_number + 1))
468444 #tt_debug INPUT_LINE "$tt_input_line"
469445
470446 case "$tt_input_line" in
471447
472 # Prompt alone: closes previous command line (if any)
473448 "$tt_prefix$tt_prompt" | "$tt_prefix${tt_prompt% }" | "$tt_prefix$tt_prompt ")
449 # Prompt alone: closes previous command line (if any)
450
474451 #tt_debug 'LINE_$' "$tt_input_line"
475452
476453 # Run pending tests
477454 test -n "$tt_test_command" && tt_run_test
478 ;;
479
480 # This line is a command line to be tested
455 ;;
456
481457 "$tt_prefix$tt_prompt"*)
458 # This line is a command line to be tested
459
482460 #tt_debug LINE_CMD "$tt_input_line"
483461
484462 # Run pending tests
491469 tt_test_line_number=$tt_line_number
492470
493471 # This is a special test with inline output?
494 if printf '%s\n' "$tt_test_command" | grep "$tt_inline_prefix" > /dev/null
495 then
472 if printf '%s\n' "$tt_test_command" | grep "$tt_inline_prefix" > /dev/null; then
496473 # Separate command from inline output
497474 tt_test_command="${tt_test_command%"$tt_inline_prefix"*}"
498475 tt_test_inline="${tt_input_line##*"$tt_inline_prefix"}"
505482 '--egrep '*)
506483 tt_test_inline=${tt_test_inline#--egrep }
507484 tt_test_mode='egrep'
508 ;;
485 ;;
509486 '--regex '*) # alias to --perl
510487 tt_test_inline=${tt_test_inline#--regex }
511488 tt_test_mode='regex'
512 ;;
489 ;;
513490 '--perl '*)
514491 tt_test_inline=${tt_test_inline#--perl }
515492 tt_test_mode='perl'
516 ;;
493 ;;
517494 '--file '*)
518495 tt_test_inline=${tt_test_inline#--file }
519496 tt_test_mode='file'
520 ;;
497 ;;
521498 '--lines '*)
522499 tt_test_inline=${tt_test_inline#--lines }
523500 tt_test_mode='lines'
524 ;;
501 ;;
525502 '--exit '*)
526503 tt_test_inline=${tt_test_inline#--exit }
527504 tt_test_mode='exit'
528 ;;
505 ;;
529506 '--eval '*)
530507 tt_test_inline=${tt_test_inline#--eval }
531508 tt_test_mode='eval'
532 ;;
509 ;;
533510 '--text '*)
534511 tt_test_inline=${tt_test_inline#--text }
535512 tt_test_mode='text'
536 ;;
513 ;;
537514 *)
538515 tt_test_mode='text'
539 ;;
516 ;;
540517 esac
541518
542519 #tt_debug OK_TEXT "$tt_test_inline"
543520
544521 # There must be a number in --lines and --exit
545 if test "$tt_test_mode" = 'lines' || test "$tt_test_mode" = 'exit'
546 then
522 if test "$tt_test_mode" = 'lines' || test "$tt_test_mode" = 'exit'; then
547523 case "$tt_test_inline" in
548524 '' | *[!0-9]*)
549525 tt_error "--$tt_test_mode requires a number. See line $tt_line_number of $tt_test_file"
550 ;;
526 ;;
551527 esac
552528 fi
553529
554530 # An empty inline parameter is an error user must see
555 if test -z "$tt_test_inline" && test "$tt_test_mode" != 'text'
556 then
531 if test -z "$tt_test_inline" && test "$tt_test_mode" != 'text'; then
557532 tt_error "empty --$tt_test_mode at line $tt_line_number of $tt_test_file"
558533 fi
559534
565540
566541 #tt_debug NEW_CMD "$tt_test_command"
567542 fi
568 ;;
569
570 # Test output, blank line or comment
543 ;;
544
571545 *)
546 # Test output, blank line or comment
547
572548 #tt_debug 'LINE_*' "$tt_input_line"
573549
574550 # Ignore this line if there's no pending test
575551 test -n "$tt_test_command" || continue
576552
577553 # Required prefix is missing: we just left a command block
578 if test -n "$tt_prefix" && test "${tt_input_line#"$tt_prefix"}" = "$tt_input_line"
579 then
554 if test -n "$tt_prefix" && test "${tt_input_line#"$tt_prefix"}" = "$tt_input_line"; then
580555 #tt_debug BLOCK_OUT "$tt_input_line"
581556
582557 # Run the pending test and we're done in this line
588563 tt_test_ok_text="$tt_test_ok_text${tt_input_line#"$tt_prefix"}$tt_nl"
589564
590565 #tt_debug OK_TEXT "${tt_input_line#"$tt_prefix"}"
591 ;;
566 ;;
592567 esac
593568 done < "$tt_temp_file"
594569
597572 # Run pending tests
598573 test -n "$tt_test_command" && tt_run_test
599574 }
600 tt_make_temp_dir ()
601 {
575 tt_make_temp_dir() {
602576 # Create private temporary dir and sets global $tt_temp_dir.
603577 # http://mywiki.wooledge.org/BashFAQ/062
604578
606580 tt_temp_dir=$(mktemp -d "${TMPDIR:-/tmp}/clitest.XXXXXX" 2> /dev/null) && return 0
607581
608582 # No mktemp, let's create the dir manually
583 # shellcheck disable=SC2015
609584 tt_temp_dir="${TMPDIR:-/tmp}/clitest.$(awk 'BEGIN { srand(); print rand() }').$$" &&
610 mkdir -m 700 "$tt_temp_dir" || tt_error "cannot create temporary dir: $tt_temp_dir"
611 }
612
585 mkdir -m 700 "$tt_temp_dir" ||
586 tt_error "cannot create temporary dir: $tt_temp_dir"
587 }
613588
614589 ### Init process
615590
617592 tt_temp_dir=
618593 tt_make_temp_dir # sets global $tt_temp_dir
619594 tt_temp_file="$tt_temp_dir/temp.txt"
595 tt_stdin_file="$tt_temp_dir/stdin.txt"
620596 tt_test_ok_file="$tt_temp_dir/ok.txt"
621597 tt_test_output_file="$tt_temp_dir/output.txt"
622598
623599 # Handle command line options
624 while test "${1#-}" != "$1"
625 do
600 while test "${1#-}" != "$1"; do
626601 case "$1" in
627 -1|--first ) shift; tt_stop_on_first_fail=1 ;;
628 -l|--list ) shift; tt_output_mode='list' ;;
629 -L|--list-run ) shift; tt_output_mode='list-run' ;;
630 -q|--quiet ) shift; tt_output_mode='quiet' ;;
631 -t|--test ) shift; tt_run_range="$1"; shift ;;
632 -s|--skip ) shift; tt_skip_range="$1"; shift ;;
633 --pre-flight ) shift; tt_pre_command="$1"; shift ;;
634 --post-flight ) shift; tt_post_command="$1"; shift ;;
635 --debug ) shift; tt_debug=1 ;;
636 -P|--progress ) shift; tt_progress="$1"; tt_output_mode='normal'; shift ;;
637 --color|--colour) shift; tt_color_mode="$1"; shift ;;
638 --diff-options ) shift; tt_diff_options="$1"; shift ;;
639 --inline-prefix ) shift; tt_inline_prefix="$1"; shift ;;
640 --prefix ) shift; tt_prefix="$1"; shift ;;
641 --prompt ) shift; tt_prompt="$1"; shift ;;
642 -h|--help)
602 -1 | --first)
603 shift
604 tt_stop_on_first_fail=1
605 ;;
606 -l | --list)
607 shift
608 tt_output_mode='list'
609 ;;
610 -L | --list-run)
611 shift
612 tt_output_mode='list-run'
613 ;;
614 -q | --quiet)
615 shift
616 tt_output_mode='quiet'
617 ;;
618 -t | --test)
619 shift
620 tt_run_range="$1"
621 shift
622 ;;
623 -s | --skip)
624 shift
625 tt_skip_range="$1"
626 shift
627 ;;
628 --pre-flight)
629 shift
630 tt_pre_command="$1"
631 shift
632 ;;
633 --post-flight)
634 shift
635 tt_post_command="$1"
636 shift
637 ;;
638 --debug)
639 shift
640 tt_debug=1
641 ;;
642 -P | --progress)
643 shift
644 tt_progress="$1"
645 tt_output_mode='normal'
646 shift
647 ;;
648 --color | --colour)
649 shift
650 tt_color_mode="$1"
651 shift
652 ;;
653 --diff-options)
654 shift
655 tt_diff_options="$1"
656 shift
657 ;;
658 --inline-prefix)
659 shift
660 tt_inline_prefix="$1"
661 shift
662 ;;
663 --prefix)
664 shift
665 tt_prefix="$1"
666 shift
667 ;;
668 --prompt)
669 shift
670 tt_prompt="$1"
671 shift
672 ;;
673 -h | --help)
643674 printf '%s\n' "$tt_my_help"
644675 exit 0
645 ;;
646 -V|--version)
647 printf '%s %s\n%s\n' $tt_my_name $tt_my_version $tt_my_version_url
676 ;;
677 -V | --version)
678 printf '%s %s\n' "$tt_my_name" "$tt_my_version"
648679 exit 0
649 ;;
680 ;;
650681 --)
651682 # No more options to process
652683 shift
653684 break
654 ;;
685 ;;
655686 -)
656 # Argument - means "read from STDIN" (not supported)
687 # Argument - means "read test file from STDIN"
657688 break
658 ;;
689 ;;
659690 *)
660691 tt_error "invalid option $1"
661 ;;
692 ;;
662693 esac
663694 done
664695
666697 tt_nr_files=$#
667698
668699 # No files?
669 if test $tt_nr_files -eq 0
670 then
700 if test $tt_nr_files -eq 0; then
671701 tt_error 'no test file informed (try --help)'
672702 fi
673703
675705 case "$tt_prefix" in
676706 tab)
677707 tt_prefix="$tt_tab"
678 ;;
708 ;;
679709 0)
680710 tt_prefix=''
681 ;;
711 ;;
682712 [1-9] | [1-9][0-9]) # 1-99
683713 # convert number to spaces: 2 => ' '
684714 tt_prefix=$(printf "%${tt_prefix}s" ' ')
685 ;;
715 ;;
686716 *\\*)
687717 tt_prefix="$(printf %b "$tt_prefix")" # expand \t and others
688 ;;
718 ;;
689719 esac
690720
691721 # Validate and normalize progress value
692 if test "$tt_output_mode" = 'normal'
693 then
722 if test "$tt_output_mode" = 'normal'; then
694723 case "$tt_progress" in
695724 test)
696725 :
697 ;;
726 ;;
698727 number | n | [0-9])
699728 tt_progress='number'
700 ;;
729 ;;
701730 dot | .)
702731 tt_progress='.'
703 ;;
732 ;;
704733 none | no)
705734 tt_progress='none'
706 ;;
735 ;;
707736 ?) # Single char, use it as the progress
708737 :
709 ;;
738 ;;
710739 *)
711740 tt_error "invalid value '$tt_progress' for --progress. Use: test, number, dot or none."
712 ;;
741 ;;
713742 esac
714743 fi
715744
717746 case "$tt_color_mode" in
718747 always | yes | y)
719748 tt_use_colors=1
720 ;;
749 ;;
721750 never | no | n)
722751 tt_use_colors=0
723 ;;
752 ;;
724753 auto | a)
725754 # The auto mode will use colors if the output is a terminal
726755 # Note: test -t is in POSIX
727 if test -t 1
728 then
756 if test -t 1; then
729757 tt_use_colors=1
730758 else
731759 tt_use_colors=0
732760 fi
733 ;;
761 ;;
734762 *)
735763 tt_error "invalid value '$tt_color_mode' for --color. Use: auto, always or never."
736 ;;
764 ;;
737765 esac
738766
739767 # Set colors
740768 # Remember: colors must be readable in dark and light backgrounds
741769 # Customization: tweak the numbers after [ to adjust the colors
742 if test $tt_use_colors -eq 1
743 then
770 if test $tt_use_colors -eq 1; then
744771 tt_color_red=$( printf '\033[31m') # fail
745772 tt_color_green=$(printf '\033[32m') # ok
746773 tt_color_blue=$( printf '\033[34m') # debug
753780 # In other shells, try to use 'tput cols' (not POSIX).
754781 # If not, defaults to 50 columns, a conservative amount.
755782 : ${COLUMNS:=$(tput cols 2> /dev/null)}
756 : ${COLUMNS:=50}
783 : "${COLUMNS:=50}"
757784
758785 # Parse and validate --test option value, if informed
759786 tt_run_range_data=$(tt_parse_range "$tt_run_range")
760 if test $? -ne 0
761 then
787 if test $? -ne 0; then
762788 tt_error "invalid argument for -t or --test: $tt_run_range"
763789 fi
764790
765791 # Parse and validate --skip option value, if informed
766792 tt_skip_range_data=$(tt_parse_range "$tt_skip_range")
767 if test $? -ne 0
768 then
793 if test $? -ne 0; then
769794 tt_error "invalid argument for -s or --skip: $tt_skip_range"
770795 fi
771796
772
773797 ### Real execution begins here
774798
775799 # Some preparing command to run before all the tests?
776 if test -n "$tt_pre_command"
777 then
800 if test -n "$tt_pre_command"; then
778801 eval "$tt_pre_command" ||
779802 tt_error "pre-flight command failed with status=$?: $tt_pre_command"
780803 fi
781804
782805 # For each input file in $@
783 for tt_test_file
784 do
806 for tt_test_file; do
785807 # Some tests may 'cd' to another dir, we need to get back
786808 # to preserve the relative paths of the input files
787 cd "$tt_original_dir"
809 cd "$tt_original_dir" ||
810 tt_error "cannot enter starting directory $tt_original_dir"
811
812 # Support using '-' to read the test file from STDIN
813 if test "$tt_test_file" = '-'; then
814 tt_test_file="$tt_stdin_file"
815 cat > "$tt_test_file"
816 fi
817
818 # Abort when test file is a directory
819 if test -d "$tt_test_file"; then
820 tt_error "input file is a directory: $tt_test_file"
821 fi
788822
789823 # Abort when test file not found/readable
790 if test ! -f "$tt_test_file" || test ! -r "$tt_test_file"
791 then
824 if test ! -r "$tt_test_file"; then
792825 tt_error "cannot read input file: $tt_test_file"
793826 fi
794827
795828 # In multifile mode, identify the current file
796 if test $tt_nr_files -gt 1
797 then
829 if test $tt_nr_files -gt 1; then
798830 case "$tt_output_mode" in
799831 normal)
800832 # Normal mode, show message with filename
801833 case "$tt_progress" in
802834 test | none)
803835 tt_message "Testing file $tt_test_file"
804 ;;
836 ;;
805837 *)
806838 test $tt_missing_nl -eq 1 && echo
807839 tt_message_part "Testing file $tt_test_file "
808 ;;
840 ;;
809841 esac
810 ;;
842 ;;
811843 list | list-run)
812844 # List mode, show ------ and the filename
813845 tt_message "$(tt_separator_line | cut -c 1-40)" "$tt_test_file"
814 ;;
846 ;;
815847 esac
816848 fi
817849
824856 tt_process_test_file
825857
826858 # Abort when no test found (and no active range with --test or --skip)
827 if test $tt_nr_file_tests -eq 0 && test -z "$tt_run_range_data" && test -z "$tt_skip_range_data"
828 then
859 if test $tt_nr_file_tests -eq 0 && test -z "$tt_run_range_data" && test -z "$tt_skip_range_data"; then
829860 tt_error "no test found in input file: $tt_test_file"
830861 fi
831862
832863 # Save file stats
833 tt_nr_file_ok=$(($tt_nr_file_tests - $tt_nr_file_fails - $tt_nr_file_skips))
864 tt_nr_file_ok=$((tt_nr_file_tests - tt_nr_file_fails - tt_nr_file_skips))
834865 tt_files_stats="$tt_files_stats$tt_nr_file_ok $tt_nr_file_fails $tt_nr_file_skips$tt_nl"
835866
836867 # Dots mode: any missing new line?
841872 tt_clean_up
842873
843874 # Some clean up command to run after all the tests?
844 if test -n "$tt_post_command"
845 then
846 eval "$tt_post_command"
875 if test -n "$tt_post_command"; then
876 eval "$tt_post_command" ||
877 tt_error "post-flight command failed with status=$?: $tt_post_command"
847878 fi
848879
849880 #-----------------------------------------------------------------------
851882 #-----------------------------------------------------------------------
852883
853884 # Range active, but no test matched :(
854 if test $tt_nr_total_tests -eq $tt_nr_total_skips
855 then
856 if test -n "$tt_run_range_data" && test -n "$tt_skip_range_data"
857 then
885 if test $tt_nr_total_tests -eq $tt_nr_total_skips; then
886 if test -n "$tt_run_range_data" && test -n "$tt_skip_range_data"; then
858887 tt_error "no test found. The combination of -t and -s resulted in no tests."
859 elif test -n "$tt_run_range_data"
860 then
888 elif test -n "$tt_run_range_data"; then
861889 tt_error "no test found for the specified number or range '$tt_run_range'"
862 elif test -n "$tt_skip_range_data"
863 then
890 elif test -n "$tt_skip_range_data"; then
864891 tt_error "no test found. Maybe '--skip $tt_skip_range' was too much?"
865892 fi
866893 fi
867894
868895 # List mode has no stats
869 if test "$tt_output_mode" = 'list' || test "$tt_output_mode" = 'list-run'
870 then
871 if test $tt_nr_total_fails -eq 0
872 then
896 if test "$tt_output_mode" = 'list' || test "$tt_output_mode" = 'list-run'; then
897 if test $tt_nr_total_fails -eq 0; then
873898 exit 0
874899 else
875900 exit 1
884909 # ok fail skip
885910 # 100 0 23 foo.sh
886911 # 12 34 0 bar.sh
887 if test $tt_nr_files -gt 1 && test "$tt_output_mode" != 'quiet'
888 then
912 if test $tt_nr_files -gt 1 && test "$tt_output_mode" != 'quiet'; then
889913 echo
890914 printf ' %5s %5s %5s\n' ok fail skip
891 printf %s "$tt_files_stats" | while read ok fail skip
892 do
893 printf ' %5s %5s %5s %s\n' $ok $fail $skip "$1"
915 printf %s "$tt_files_stats" | while read -r ok fail skip; do
916 printf ' %5s %5s %5s %s\n' "$ok" "$fail" "$skip" "$1"
894917 shift
895918 done | sed 's/ 0/ -/g' # hide zeros
896919 echo
902925 # FAIL: 123 of 123 tests failed
903926 # FAIL: 100 of 123 tests failed (23 skipped)
904927 skips=
905 if test $tt_nr_total_skips -gt 0
906 then
928 if test $tt_nr_total_skips -gt 0; then
907929 skips=" ($tt_nr_total_skips skipped)"
908930 fi
909 if test $tt_nr_total_fails -eq 0
910 then
931 if test $tt_nr_total_fails -eq 0; then
911932 stamp="${tt_color_green}OK:${tt_color_off}"
912 stats="$(($tt_nr_total_tests - $tt_nr_total_skips)) of $tt_nr_total_tests tests passed"
933 stats="$((tt_nr_total_tests - tt_nr_total_skips)) of $tt_nr_total_tests tests passed"
913934 test $tt_nr_total_tests -eq 1 && stats=$(echo "$stats" | sed 's/tests /test /')
914935 tt_message "$stamp $stats$skips"
915936 exit 0
1616
1717 Download the .tgz file for the version 2.6, directly from Google Code.
1818
19 $ url="http://txt2tags.googlecode.com/files/txt2tags-2.6.tgz"
19 $ url="https://fossies.org/linux/privat/txt2tags-2.6.tgz"
2020 $ curl -O -s -S "$url"
2121
2222
00 $ cd /tmp
1 $ url="http://txt2tags.googlecode.com/files/txt2tags-2.6.tgz"
1 $ url="https://fossies.org/linux/privat/txt2tags-2.6.tgz"
22 $ curl -O -s -S "$url" # download
33 $ du -h txt2tags-2.6.tgz # verify size
44 532K txt2tags-2.6.tgz
0 $ echo "error: file not found" #=> --file XXnotfoundXX
0 $ echo "error: file not found" #=> --file notfound
2525
2626 # Showing STDERR
2727
28 $ ./clitest foo
29 clitest: Error: cannot read input file: foo
30 $ ./clitest foo > /dev/null
31 clitest: Error: cannot read input file: foo
28 $ ./clitest notfound
29 clitest: Error: cannot read input file: notfound
30 $ ./clitest notfound > /dev/null
31 clitest: Error: cannot read input file: notfound
3232 $
3333
3434 # Redirecting STDERR to STDOUT
3535
36 $ ./clitest foo 2>&1
37 clitest: Error: cannot read input file: foo
36 $ ./clitest notfound 2>&1
37 clitest: Error: cannot read input file: notfound
3838 $
3939
4040 # Closing STDERR
4141
42 $ ./clitest foo 2> /dev/null
43 $ ./clitest foo > /dev/null 2>&1
42 $ ./clitest notfound 2> /dev/null
43 $ ./clitest notfound > /dev/null 2>&1
4444 $
2121 Set a default terminal width of 80 columns. It's used by separator lines.
2222
2323 ```
24 $ shopt -u checkwinsize 2> /dev/null # bash: disable automatic check
25 $ unset COLUMNS # mksh: first unset, then one can manually set it
2426 $ COLUMNS=80
2527 $ export COLUMNS
2628 $
4446
4547 ```
4648 $ TMPDIR___SAVE="$TMPDIR"
47 $ TMPDIR=/XXnotfoundXX
49 $ TMPDIR=/notfound
4850 $ export TMPDIR
4951 $ ./clitest test/ok-1.sh 2>&1 | grep ^clitest | sed 's/clitest\..*$/clitest.XXXXXX/'
50 clitest: Error: cannot create temporary dir: /XXnotfoundXX/clitest.XXXXXX
52 clitest: Error: cannot create temporary dir: /notfound/clitest.XXXXXX
5153 $ TMPDIR="$TMPDIR___SAVE"
5254 $
5355 ```
7072 File not found
7173
7274 ```
73 $ ./clitest XXnotfoundXX.sh; echo $?
74 clitest: Error: cannot read input file: XXnotfoundXX.sh
75 2
75 $ ./clitest notfound; echo $?
76 clitest: Error: cannot read input file: notfound
77 2
78 $
79 ```
80
81 File is a directory
82
83 ```
7684 $ ./clitest .
77 clitest: Error: cannot read input file: .
85 clitest: Error: input file is a directory: .
7886 $ ./clitest ./
79 clitest: Error: cannot read input file: ./
87 clitest: Error: input file is a directory: ./
8088 $ ./clitest /etc
81 clitest: Error: cannot read input file: /etc
89 clitest: Error: input file is a directory: /etc
8290 $
8391 ```
8492
111119
112120 ```
113121 $ ./clitest --version
114 clitest HEAD
115 https://github.com/aureliojargas/clitest/tree/HEAD
122 clitest 0.4.0
116123 $ ./clitest -V
117 clitest HEAD
118 https://github.com/aureliojargas/clitest/tree/HEAD
124 clitest 0.4.0
119125 $
120126 ```
121127
145151 --inline-prefix PREFIX Set inline output prefix (default: '#=> ')
146152 --prefix PREFIX Set command line prefix (default: '')
147153 --prompt STRING Set prompt string (default: '$ ')
154
155 See also: https://github.com/aureliojargas/clitest
148156 0
149157 $
150158 ```
154162 ```
155163 $ ./clitest -h | sed -n '1p; $p'
156164 Usage: clitest [options] <file ...>
157 --prompt STRING Set prompt string (default: '$ ')
165 See also: https://github.com/aureliojargas/clitest
158166 $
159167 ```
160168
180188 ## Option --quiet has no effect in error messages
181189
182190 ```
183 $ ./clitest --quiet /etc
184 clitest: Error: cannot read input file: /etc
191 $ ./clitest --quiet notfound
192 clitest: Error: cannot read input file: notfound
185193 $
186194 ```
187195
724732 #5 OK echo 5
725733 #6 OK echo 6
726734 #7 OK echo 7
735 $
736 ```
737
738 Reverse ranges and repeated numbers are supported
739
740 ```
741 $ ./clitest --list -t 3,7-5,3,6,5 test/ok-10.sh
742 #3 echo 3
743 #5 echo 5
744 #6 echo 6
745 #7 echo 7
727746 $
728747 ```
729748
13311350 2
13321351 $ ./clitest test/inline-match-file-error-2.sh; echo $?
13331352 #1 echo "error: file not found"
1334 clitest: Error: cannot read inline output file 'test/XXnotfoundXX', from line 1 of test/inline-match-file-error-2.sh
1353 clitest: Error: cannot read inline output file 'test/notfound', from line 1 of test/inline-match-file-error-2.sh
13351354 2
13361355 $ ./clitest test/inline-match-file-error-3.sh; echo $?
13371356 #1 echo "error: directory"
20732092 $ ./clitest --pre-flight 'false' test/ok-1.sh; echo $?
20742093 clitest: Error: pre-flight command failed with status=1: false
20752094 2
2095 $ ./clitest --post-flight 'false' test/ok-1.sh; echo $?
2096 #1 echo ok
2097 clitest: Error: post-flight command failed with status=1: false
2098 2
20762099 $
20772100 ```
20782101
21012124 $
21022125 ```
21032126
2104 ## File - meaning STDIN (not supported)
2127 ## File - meaning STDIN
21052128
21062129 ```
21072130 $ cat test/ok-1.sh | ./clitest -
2108 clitest: Error: cannot read input file: -
2131 #1 echo ok
2132 OK: 1 of 1 test passed
21092133 $ cat test/ok-1.sh | ./clitest -- -; echo $?
2110 clitest: Error: cannot read input file: -
2111 2
2112 $
2113 ```
2114
2115 ## Read test file from STDIN (not supported)
2116
2117 ```
2118 $ cat test/ok-1.sh | ./clitest /dev/stdin; echo $?
2119 clitest: Error: cannot read input file: /dev/stdin
2120 2
2134 #1 echo ok
2135 OK: 1 of 1 test passed
2136 0
2137 $
2138 ```
2139
2140 ## Read test file from /dev/stdin
2141
2142 ```
2143 $ cat test/ok-1.sh | ./clitest /dev/stdin
2144 #1 echo ok
2145 OK: 1 of 1 test passed
2146 $
2147 ```
2148
2149 ## Test file is a symlink
2150
2151 ```
2152 $ ln -s test/ok-1.sh testsymlink
2153 $ ./clitest testsymlink
2154 #1 echo ok
2155 OK: 1 of 1 test passed
2156 $ rm testsymlink
21212157 $
21222158 ```
21232159
21262162 Test exit code and STDOUT/STDERR at the same time
21272163
21282164 ```
2129 $ ./clitest foo; echo $?
2130 clitest: Error: cannot read input file: foo
2165 $ ./clitest notfound; echo $?
2166 clitest: Error: cannot read input file: notfound
21312167 2
21322168 $ ./clitest test/exit-code-and-stdout.sh
21332169 #1 echo "zero"; echo $?
21452181 #3 echo "stderr" 1>&2
21462182 #4 echo "stdout" > /dev/null
21472183 #5 echo "stdout" 2> /dev/null 1>&2
2148 #6 ./clitest foo
2149 #7 ./clitest foo > /dev/null
2150 #8 ./clitest foo 2>&1
2151 #9 ./clitest foo 2> /dev/null
2152 #10 ./clitest foo > /dev/null 2>&1
2184 #6 ./clitest notfound
2185 #7 ./clitest notfound > /dev/null
2186 #8 ./clitest notfound 2>&1
2187 #9 ./clitest notfound 2> /dev/null
2188 #10 ./clitest notfound > /dev/null 2>&1
21532189 OK: 10 of 10 tests passed
21542190 $
21552191 ```