diff --git a/helpers/harness.tcl b/helpers/harness.tcl index 2624131..6bbe470 100755 --- a/helpers/harness.tcl +++ b/helpers/harness.tcl @@ -19,9 +19,11 @@ # ☠ ⇒ shell itself exits, with code # → ⇒ user input # ← ⇒ response on stdout +# ↵ ⇒ response on stdout delimited by a CR or LF # ≠ ⇒ anything but this on stdout (at least one line) # ✓ ⇒ expect zero exit status of previous command (not implemented) # ✗ ⇒ expect nonzero exit status of previous command (not implemented) +# ⌛ ⇒ wait a little extra (0.5 seconds) proc expand {s} { string map { @@ -68,7 +70,11 @@ proc setup_execution_environment {} { } proc wait_for_exit {} { - expect eof; # seems to be needed for buffering issues on BSD + # seems to be needed for buffering issues on BSD + expect { + eof {} + timeout {} + } foreach {pid spawn_id is_os_error code} [wait] break if {0 == $is_os_error} { return $code } not_ok @@ -88,21 +94,20 @@ fconfigure $test_file -encoding utf-8 set n_tests 0 while {![eof $test_file]} { switch -re [string index [gets $test_file] 0] { - ←|≠|✓|✗|☠ {incr n_tests} + ←|↵|≠|✓|✗|☠ {incr n_tests} default {} } } seek $test_file 0 start log_user 0 -set send_slow { 1 .01 } setup_execution_environment if [catch {spawn $shell} err] {error $err} -# waiting for first prompt can help +# waiting a little extra for first prompt can help. if your shell +# emits no prompt, that's not very friendly. +set timeout 3 +expect -re .+ set timeout 2 -expect -re . -expect * -set timeout 1 set line_num 0 set test_num 1 @@ -124,6 +129,21 @@ proc not_ok {} { } proc is {x} { uplevel 1 [list if $x {ok} {not_ok}]} +proc re_quote {s} { + string map { + \\ \\\\ + ^ \\^ + \$ \\\$ + \[ \\\[ + \( \\\( + . \\. + + \\+ + * \\* + ? \\? + \{ \\\{ + } $s +} + proc expect_line {line} { # NB: this is fairly unreliable, and we should have some lint # where we reject tests whose expected output would match the tail @@ -131,15 +151,22 @@ proc expect_line {line} { # with the output and timing differences between shells, short of # going full ptrace as mentioned previously. set rv [expect { - "$line\r\n" {return 1} + -re $line {return 1} default {return 0} }] expect * return $rv } +# The timing of sending a line can be tricky. Shells are usually +# sending their prompts to stderr, and so output to stdout can race +# with that. We used to use send -s (send_slow) here, but in practice +# it seems unnecessary; what does seem necessary, is giving the shell +# a little bit of time before we look at the output or send the next +# line. proc careful_send {m} { - if {[catch {send -s $m} err]} { error $err } + if {[catch {send -- $m} err]} { error $err } + sleep 0.01 } if {1 == $emit_tap} {puts "1..$n_tests"} @@ -149,11 +176,13 @@ while {![eof $test_file]} { if {0 == [string length [string trim $command]]} { continue } set line [expand [rest-of $command]] switch [string index $command 0] { - → {expect *; careful_send $line; sleep 0.1} - ← {is {1 == [expect_line $line]}} - ≠ {is {0 == [expect_line $line]}} + → {expect *; careful_send $line} + ↵ {is {1 == [expect_line "\[\r\n][re_quote $line]"]}} + ← {is {1 == [expect_line [re_quote $line]]}} + ≠ {is {0 == [expect_line "\[\r\n][re_quote $line]"]}} ✓ {error "sorry we decided not to do this $line_num"} ✗ {error "sorry we decided not to do this $line_num"} + ⌛ {sleep 0.2} ☠ { is {$line eq [wait_for_exit]} if {$test_num <= $n_tests} { @@ -168,5 +197,5 @@ while {![eof $test_file]} { } # Calling close causes zsh to exit unhappily, so we send ^D instead. -send -s "\x04" +sleep 0.1; send "\x04" if {0 != [wait_for_exit]} {error "shell didn't exit cleanly"} diff --git a/stage_1/01-fork-exec.t b/stage_1/01-fork-exec.t index 0acce79..f1e6d8b 100644 --- a/stage_1/01-fork-exec.t +++ b/stage_1/01-fork-exec.t @@ -1,8 +1,10 @@ → /bin/echo foo bar⏎ -← foo bar -→ tr a-z n-za-m⏎foo bar baz⏎^D -← sbb one onm +↵ foo bar +→ tr a-z n-za-m⏎ +→ foo bar baz⏎ +↵ sbb one onm +→ ^D → ./helpers/echo-rot13 foo bar⏎ -← sbb one +↵ sbb one → ./helpers/echo-exit ./helpers/successful-exit-status⏎ -← 0 +↵ 0 diff --git a/stage_1/02-commands-invoked-from-path.t b/stage_1/02-commands-invoked-from-path.t index 2608074..3754467 100644 --- a/stage_1/02-commands-invoked-from-path.t +++ b/stage_1/02-commands-invoked-from-path.t @@ -1,3 +1,3 @@ # harness puts helpers/ in our path → echo-exit ./helpers/successful-exit-status⏎ -← 0 +↵ 0 diff --git a/stage_1/03-cd-changes-directory.t b/stage_1/03-cd-changes-directory.t index 295695f..f7fd267 100644 --- a/stage_1/03-cd-changes-directory.t +++ b/stage_1/03-cd-changes-directory.t @@ -1,6 +1,6 @@ → /bin/pwd⏎ -≠ /tmp +≠ /tmp\n → cd /tmp⏎ → cd ..⏎ → /bin/pwd⏎ -← / +↵ /\n diff --git a/stage_1/04-semicolon-separates-commands.t b/stage_1/04-semicolon-separates-commands.t index f70dd74..11e0281 100644 --- a/stage_1/04-semicolon-separates-commands.t +++ b/stage_1/04-semicolon-separates-commands.t @@ -1,7 +1,7 @@ # POSIX calls these sequential lists → echo -n foo; echo -n bar; echo baz⏎ -← foobarbaz +↵ foobarbaz → cd /tmp; pwd⏎ -← /tmp +↵ /tmp → false; echo-rot13 foo⏎ -← sbb +↵ sbb diff --git a/stage_1/05-and-or-chaining.t b/stage_1/05-and-or-chaining.t index 9342429..edb07e6 100644 --- a/stage_1/05-and-or-chaining.t +++ b/stage_1/05-and-or-chaining.t @@ -1,15 +1,19 @@ # POSIX calls these AND Lists and OR Lists → true && echo-rot13 foo⏎ -← sbb +↵ sbb → false && echo-rot13 foo⏎ ≠ sbb → true && false && echo-rot13 foo⏎ ≠ sbb → false || echo-rot13 foo⏎ -← sbb +↵ sbb → true || false || echo-rot13 foo⏎ ≠ sbb → false || true && echo-rot13 foo⏎ -← sbb +↵ sbb +# we wait a bit here because some shells have an expensive +# command_not_found_handler hook; e.g. Fedora checks a package +# database. → nonexistent-command || echo-rot13 zim⏎ -← mvz +⌛ +↵ mvz diff --git a/stage_1/07-subshell.t b/stage_1/07-subshell.t index 838650d..70f52c2 100644 --- a/stage_1/07-subshell.t +++ b/stage_1/07-subshell.t @@ -1,10 +1,10 @@ → pwd⏎ -≠ /tmp +≠ /tmp\n → (cd /tmp; pwd)⏎ -← /tmp +↵ /tmp\n → pwd⏎ -≠ /tmp +≠ /tmp\n → (exit 1) && echo-rot13 foo⏎ -≠ sbb +≠ sbb\n → (exit 0 && exit 1) && echo-rot13 foo⏎ -← sbb +↵ sbb\n diff --git a/stage_1/08-bang-negates-exit-code.t b/stage_1/08-bang-negates-exit-code.t index 4018a52..63d7bd7 100644 --- a/stage_1/08-bang-negates-exit-code.t +++ b/stage_1/08-bang-negates-exit-code.t @@ -1,6 +1,6 @@ → ! false && echo-rot13 foo⏎ -← sbb +↵ sbb → ! true && echo-rot13 foo⏎ ≠ sbb → ! true || echo-rot13 foo⏎ -← sbb +↵ sbb diff --git a/stage_1/09-backslash-continues-line.t b/stage_1/09-backslash-continues-line.t index 8794a59..ae3a756 100644 --- a/stage_1/09-backslash-continues-line.t +++ b/stage_1/09-backslash-continues-line.t @@ -1,2 +1,2 @@ → echo foo\⏎bar\⏎baz⏎ -← foobarbaz +← foobarbaz\n diff --git a/stage_1/10-multiline-list.t b/stage_1/10-multiline-list.t index 3f67029..b97ac2f 100644 --- a/stage_1/10-multiline-list.t +++ b/stage_1/10-multiline-list.t @@ -1,2 +1,5 @@ -→ true &&⏎false ||⏎echo-rot13 foo ||⏎echo-rot13 bar⏎ -← sbb +→ true &&⏎ +→ false ||⏎ +→ echo-rot13 foo ||⏎ +→ echo-rot13 bar⏎ +↵ sbb diff --git a/stage_2/01-pipeline-execution.t b/stage_2/01-pipeline-execution.t index d616f97..f8b0b75 100644 --- a/stage_2/01-pipeline-execution.t +++ b/stage_2/01-pipeline-execution.t @@ -1,2 +1,2 @@ → echo foo | grep -c foo⏎ -← 1 +↵ 1 diff --git a/stage_2/02-redirections.t b/stage_2/02-redirections.t index 04b76a0..8460054 100644 --- a/stage_2/02-redirections.t +++ b/stage_2/02-redirections.t @@ -1,22 +1,22 @@ # we're started in a temp directory → echo zim > foo && cat foo | tr n-za-m a-z⏎ -← mvz +↵ mvz → cat foo⏎ -← zim +↵ zim → echo a > foo ; echo b > foo ; cat foo⏎ -← b +↵ b # NB echo -n isn't portable... → echo -n a > foo ; echo b >> foo ; cat foo⏎ -← ab +↵ ab → cat < /non-existent-file || echo-rot13 foo⏎ -← sbb +↵ sbb → cat /non-existent-file 2>foo || grep -c . foo && cat < foo⏎ -← quux +↵ quux → echo quux >foo && echo -n blah <>foo && cat foo && cat <>foo⏎ -← bar +↵ bar → echo mitten >foo && echo -n k 1<>foo && cat foo⏎ -← kitten +↵ kitten diff --git a/stage_2/03-redirection-permissions.t b/stage_2/03-redirection-permissions.t index cc27007..9626c2f 100644 --- a/stage_2/03-redirection-permissions.t +++ b/stage_2/03-redirection-permissions.t @@ -1,15 +1,15 @@ # NB stdin/out/err when connected to a terminal is usually rw → fd-perms 0 /dev/null⏎ -← rw +↵ rw # it's tricky to test fd 1 without losing the output. we could have # an exit status instead, with tools called is-writable and # is-readable, but meh. → fd-perms 3 3>/dev/null⏎ -← w +↵ w # An Arrow in Heart → fd-perms 3 3/dev/null⏎ -← rw +↵ rw diff --git a/stage_2/04-ensure-pipes-are-closed.t b/stage_2/04-ensure-pipes-are-closed.t index 4004254..e7b5631 100644 --- a/stage_2/04-ensure-pipes-are-closed.t +++ b/stage_2/04-ensure-pipes-are-closed.t @@ -1,4 +1,4 @@ # This will hang in the case of a very common bug: you forget to close # the other ends of the pipe. → cat /dev/null | echo-rot13 foo⏎ -← sbb +↵ sbb diff --git a/stage_2/05-no-extraneous-fds.t b/stage_2/05-no-extraneous-fds.t index 58c025c..902ecc6 100644 --- a/stage_2/05-no-extraneous-fds.t +++ b/stage_2/05-no-extraneous-fds.t @@ -1,8 +1,8 @@ → list-fds⏎ -← 0\n1\n2 +↵ 0\n1\n2 # probably want to check a few other cases of this → list-fds 0<&- 2<&-⏎ -← 1 +↵ 1 # is this order specified by POSIX? seems like you could get 0,1 here → list-fds 0<&- 2<&- 3 bar && cat $(echo bar)⏎ -← foo +→ echo-rot13 foo > bar && cat $(echo bar)⏎ +↵ sbb diff --git a/stage_2/07-bang-only-at-start-of-pipelines.t b/stage_2/07-bang-only-at-start-of-pipelines.t index 7906a50..9a44de0 100644 --- a/stage_2/07-bang-only-at-start-of-pipelines.t +++ b/stage_2/07-bang-only-at-start-of-pipelines.t @@ -1,5 +1,5 @@ -# Would prefer to test this but mksh doesn't agree -#→ echo zim | ! tr z b | grep -qc bim || echo foo⏎ -#≠ foo +# mksh does not agree with this interpretation +→ echo zim | ! tr z b | grep -qc bim || echo foo⏎ +≠ foo → ! echo zim | tr z b | grep -qc bim || echo-rot13 foo⏎ -← sbb +↵ sbb diff --git a/stage_2/08-builtin-in-pipeline.t b/stage_2/08-builtin-in-pipeline.t index 4deef2d..bc7703e 100644 --- a/stage_2/08-builtin-in-pipeline.t +++ b/stage_2/08-builtin-in-pipeline.t @@ -1,4 +1,4 @@ → echo foo | cd /tmp | pwd⏎ -≠ /tmp +≠ /tmp\n → echo foo | cd /tmp | echo-rot13 bar⏎ -← one +↵ one diff --git a/stage_2/process-redirection.bonus b/stage_2/process-redirection.bonus index 5ba7f6c..7f44b90 100644 --- a/stage_2/process-redirection.bonus +++ b/stage_2/process-redirection.bonus @@ -1,3 +1,3 @@ # not part of posix shell → cat <(echo foo) -← foo +⏎ foo diff --git a/stage_3/01-executed-command-receives-signals.t b/stage_3/01-executed-command-receives-signals.t index 61db2a6..50c317e 100644 --- a/stage_3/01-executed-command-receives-signals.t +++ b/stage_3/01-executed-command-receives-signals.t @@ -1,9 +1,11 @@ # this would be nicer if we already had $@... → echo-signal INT⏎ -← ready -→ ^C⏎ +↵ ready +→ ^C +⌛ ← INT → echo-signal TSTP⏎ -← ready -→ ^Z⏎ +↵ ready +→ ^Z +⌛ ← TSTP diff --git a/stage_3/02-job-control-with-fg-bg.t b/stage_3/02-job-control-with-fg-bg.t index 4923564..12b586c 100644 --- a/stage_3/02-job-control-with-fg-bg.t +++ b/stage_3/02-job-control-with-fg-bg.t @@ -1,14 +1,16 @@ → echo-signal CONT⏎ -← ready +↵ ready → ^Z +⌛ → echo-rot13 foo⏎ -← sbb +↵ sbb → fg⏎ ← CONT → echo-signal INT &⏎ ← ready → echo-rot13 foo⏎ -← sbb +↵ sbb → fg⏎ -→ ^C⏎ +⌛ +→ ^C ← INT diff --git a/stage_3/03-sequencing-with-suspend.t b/stage_3/03-sequencing-with-suspend.t index 3f7a2de..2e61242 100644 --- a/stage_3/03-sequencing-with-suspend.t +++ b/stage_3/03-sequencing-with-suspend.t @@ -1,6 +1,8 @@ -→ echo-signal INT & sleep 1; echo-rot13 foo⏎ +→ echo-signal INT & sleep 0.5; echo-rot13 foo⏎ ← ready +⌛ ← sbb → fg⏎ -→ ^C⏎ +⌛ +→ ^C ← INT diff --git a/stage_3/04-ttin-and-ttou.t b/stage_3/04-ttin-and-ttou.t index 1d142a2..1888420 100644 --- a/stage_3/04-ttin-and-ttou.t +++ b/stage_3/04-ttin-and-ttou.t @@ -1,10 +1,10 @@ → tr a-z n-za-m