diff --git a/tests/interactive.config b/tests/interactive.config new file mode 100644 index 000000000..861cab534 --- /dev/null +++ b/tests/interactive.config @@ -0,0 +1,43 @@ +# vim: set filetype=fish sw=4 ts=4 et: + +set -g prompt_counter 1 +set -g prompt_counter_incr 0 +function fish_prompt + echo "prompt $prompt_counter>" + if test $prompt_counter_incr -eq 1 + set -g prompt_counter (expr $prompt_counter + 1) + set -g prompt_counter_incr 0 + end +end +function fish_prompt_event --on-event fish_prompt + set -g prompt_counter_incr 1 +end + +set -g fish_greeting '' + +function _quote + echo \'(echo $argv[1] | sed -e 's/\'/\\\\\'/g')\' +end + +function _echo_var --no-scope-shadowing -d '_echo_var varname [guardval]' + if set -q argv[2]; echo "@GUARD:$argv[2]@"; end + set -l var $argv[1] + switch (count $$var) + case 0 + echo "\$$var has no value" + case 1 + set -l IFS '' + echo "\$$var:" (_quote $$var) + case \* + echo "\$$var:" + for i in (seq (count $$var)) + set -l IFS '' + echo "$i:" (_quote $$var[1][$i]) + end + end + if set -q argv[2]; echo "@/GUARD:$argv[2]@"; end +end + +function _marker -d '_marker string - prints @MARKER:$string@' + echo "@MARKER:$argv[1]@" +end diff --git a/tests/interactive.err b/tests/interactive.err new file mode 100644 index 000000000..e69de29bb diff --git a/tests/interactive.expect.rc b/tests/interactive.expect.rc new file mode 100644 index 000000000..a1a855de0 --- /dev/null +++ b/tests/interactive.expect.rc @@ -0,0 +1,228 @@ +# vim: set filetype=expect sw=4 ts=4 et: + +log_user 0 +log_file -noappend interactive.tmp.log + +set fish ../fish + +set timeout 2 + +set send_human {.05 .1 5 .02 .2} + +proc abort {{msg "aborting"}} { + error $msg + exit 1 +} + +# # Debug logging + +set loglevel debug ;# none, info, debug + +proc log_info string { + global loglevel + switch $loglevel { + info - + debug { + send_log "\[INFO] $string\n" + } + } +} + +proc log_debug string { + global loglevel + switch $loglevel { + debug { + send_log "\[DEBUG] $string\n" + } + } +} + +# Utilities + +set prompt_counter 1 +# expect_prompt takes an argument list like `expect` does. +# It supports a special pattern "unmatched" that is run if no +# other provided patterns match. +proc expect_prompt {args} { + global prompt_counter + upvar expect_out expect_out + set prompt_pat [list -re "(?:\\r\\n?|^)prompt $prompt_counter>(?:$|\r)"] + if {[llength $args] == 1 && [string match "\n*" $args]} { + set args [join $args] + } + set prompt_action "" + set expargs {} + upvar expect_out up_expect_out + set state "firstarg" + foreach arg $args { + switch $state { + "pat" { + lappend expargs $arg + set state "action" + } + "action" { + lappend expargs [subst -nocommands { + log_debug "matched extra pattern to expect_prompt: [quote \$expect_out(0,string)]" + if {\$matched} { + exp_continue + } + set matched yes + uplevel 1 {$arg} + exp_continue + }] + set state "firstarg" + } + "firstarg" - + "arg" { + if {$arg eq "unmatched" && $state eq "firstarg"} { + set state "unmatched" + continue + } + lappend expargs $arg + switch $arg { + -gl - + -re - + -ex { + set state "pat" + } + -i - + -timeout { + set state "flagarg" + } + } + } + "flagarg" { + lappend expargs $arg + set state "arg" + } + "unmatched" { + if {$prompt_action ne ""} continue + set prompt_action [subst -nocommands { + if {!\$matched} { + uplevel 1 {$arg} + } + }] + } + default { + error "BUG: non-exhaustive switch in expect_prompt" + } + } + } + if {[llength $expargs] > 0} { + log_info "expecting prompt $prompt_counter + patterns" + } else { + log_info "expecting prompt $prompt_counter" + } + set expargs [concat $prompt_pat [list $prompt_action] $expargs] + set matched no + expect {*}$expargs + incr prompt_counter +} + +trace add execution expect {enter leave} trace_expect +proc trace_expect {cmd args} { + if {[lindex $cmd 1] eq "*" && [llength $cmd] == 3} { + # it's an `expect "*" {..}` command, don't log it + return + } + switch [lindex $args end] { + enter { + log_debug "entering expect" + uplevel {set expect_out(buffer) {}} + } + leave { + set code [lindex $args 0] + if {$code == 0} { + log_debug "expect finished: [quote [uplevel set expect_out(buffer)]]" + } else { + log_debug "expect returned code $code" + } + } + } +} + +trace add execution exp_continue enter trace_exp_continue +proc trace_exp_continue {cmd op} { + log_debug "exp_continue after consuming: [quote [uplevel set expect_out(buffer)]]" +} + + +trace add execution send enter trace_send +proc trace_send {cmd op} { + log_info "[quote $cmd]" +} + +trace add execution spawn {enter leave} trace_spawn +proc trace_spawn {cmd args} { + switch [lindex $args end] { + enter { + log_info "[quote $cmd]" + } + leave { + log_debug "[quote $cmd]: code [lindex $args 0], result [lindex $args 1]" + expect_before { + timeout { + expect "*" { + log_debug "timeout; buffer=[quote $expect_out(buffer)]" + } + abort "timeout" + } + eof { + log_debug "eof; buffer=[quote $expect_out(buffer)]" + abort "eof" + } + } + } + } +} + +proc quote string { + set map { + \\ \\\\ + \r \\r + \n \\n + \t \\t + \a \\a + \v \\v + \x1b \\e + \x7f \\x7f + } + for {set x 0} {$x<32} {incr x} { + lappend map [format %c $x] [format \\x%02x $x] + } + string map $map $string +} + +proc send_line args { + if {[llength $args] > 0} { + lset args end [lindex $args end]\r + } + send {*}$args +} + +proc rand_int {low hi} { + expr {entier(rand() * ($hi-$low))+$low} +} + +# prints the output of `_echo_var $name` (defined in interactive.config) +proc print_var_contents name { + # generate a random "guard" so we know where to stop matching + # the randomness is to defend against the variable value containing the guard + set guard [rand_int 1000000000 9999999999] + + # print the variable + log_info "get_var_contents: $$name" + send_line "_echo_var $name $guard" + + # match on the results + set pat {\r\n@GUARD:$guard@\r\n(.*)\r\n@/GUARD:$guard@\r\n} + set matched false + expect_prompt -re [subst -nocommands -nobackslashes $pat] { + log_info "get_var_contents: result: [quote $expect_out(1,string)]" + puts $expect_out(1,string) + exp_continue + } unmatched { + log_debug "unmatched: [quote $expect_out(buffer)]" + abort "Didn't match output for variable $$name" + } +} diff --git a/tests/interactive.fish b/tests/interactive.fish new file mode 100644 index 000000000..987ad80b0 --- /dev/null +++ b/tests/interactive.fish @@ -0,0 +1,44 @@ +#!/usr/local/bin/fish +# +# Interactive tests using `expect` + +function die + echo $argv[1] >&2 + exit 1 +end + +for i in *.expect + rm -Rf tmp.interactive.config; or die "Couldn't remove tmp.interactive.config" + mkdir -p tmp.interactive.config/fish; or die "Couldn't create tmp.interactive.config/fish" + cp interactive.config tmp.interactive.config/fish/config.fish; or die "Couldn't create tmp.interactive.config/fish/config.fish" + + begin + set -lx XDG_CONFIG_HOME $PWD/tmp.interactive.config + set -lx TERM dumb + expect -n -c 'source interactive.expect.rc' -f $i >tmp.out ^tmp.err + end + set -l tmp_status $status + set res ok + if not diff tmp.out $i.out >/dev/null + set res fail + echo "Output differs for file $i. Diff follows:" + diff -u tmp.out $i.out + end + + if not diff tmp.err $i.err >/dev/null + set res fail + echo "Error output differs for file $i. Diff follows:" + diff -u tmp.err $i.err + end + + if test $tmp_status != (cat $i.status) + set res fail + echo "Exit status differs for file $i." + end + + if test $res = ok + echo "File $i tested ok" + else + echo "File $i failed tests" + end +end diff --git a/tests/interactive.out b/tests/interactive.out new file mode 100644 index 000000000..53577b140 --- /dev/null +++ b/tests/interactive.out @@ -0,0 +1 @@ +File read.expect tested ok diff --git a/tests/interactive.status b/tests/interactive.status new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/tests/interactive.status @@ -0,0 +1 @@ +0 diff --git a/tests/read.expect b/tests/read.expect new file mode 100644 index 000000000..3c153f733 --- /dev/null +++ b/tests/read.expect @@ -0,0 +1,77 @@ +# vim: set filetype=expect: + +proc expect_read_prompt {} { + expect -re "\\r\\n?read> $" +} + +proc expect_marker {text} { + expect_prompt -re "\\r\\n@MARKER:$text@\\r\\n" {} unmatched { + abort "Couldn't find marker line '$text'" + } +} + +spawn $fish + +expect_prompt + +# read + +send_line "read foo" +expect_read_prompt +send_line "text" +expect_prompt +print_var_contents foo + +send_line "read foo" +expect_read_prompt +send_line "again\r_marker 1" +expect_prompt +expect_marker 1 +print_var_contents foo + +send_line "read foo" +expect_read_prompt +send_line -h "bar\r_marker 2" +expect_prompt +expect_marker 2 +print_var_contents foo + +# read -n + +send_line "read -n 3 foo" +expect_read_prompt +send_line -h "123_marker 3" +expect_prompt +expect_marker 3 +print_var_contents foo + +send_line "read -n 3 foo" +expect_read_prompt +send_line "456_marker 4" +expect_prompt +expect_marker 4 +print_var_contents foo + +send_line "read -n 12 foo bar" +expect_read_prompt +send_line "hello world!_marker 5" +expect_prompt +expect_marker 5 +print_var_contents foo +print_var_contents bar + +send_line "bind ` 'commandline -i test'`" +expect_prompt +send_line "read -n 4 foo" +expect_read_prompt +send_line "te`_marker 6" +expect_prompt +expect_marker 6 +print_var_contents foo + +send_line "read -n 4 foo" +expect_read_prompt +send_line -h "12`_marker 7" +expect_prompt +expect_marker 7 +print_var_contents foo diff --git a/tests/read.expect.err b/tests/read.expect.err new file mode 100644 index 000000000..e69de29bb diff --git a/tests/read.expect.out b/tests/read.expect.out new file mode 100644 index 000000000..2d02931f6 --- /dev/null +++ b/tests/read.expect.out @@ -0,0 +1,9 @@ +$foo: 'text' +$foo: 'again' +$foo: 'bar' +$foo: '123' +$foo: '456' +$foo: 'hello' +$bar: 'world!' +$foo: 'tete' +$foo: '12te' diff --git a/tests/read.expect.status b/tests/read.expect.status new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/tests/read.expect.status @@ -0,0 +1 @@ +0 diff --git a/tests/test.fish b/tests/test.fish index a74165b9d..9a91bb278 100755 --- a/tests/test.fish +++ b/tests/test.fish @@ -37,6 +37,34 @@ if [ "$argv" != '-n' ] echo "Profiling failed" end + echo "Testing interactive functionality" + # bug: `fish -n` throws errors on fishscript functions that don't shadow real commands, + # so we can't use `type -q expect` here. + if command -s expect >/dev/null + # we have expect, we can run the interactive tests + begin + ../fish -n ./interactive.fish ^interactive.tmp.err + ../fish ./interactive.fish ^^interactive.tmp.err + end | tee interactive.tmp.out + set -l tmp_status $status + if not diff interactive.tmp.out interactive.out >/dev/null + set res fail + echo "Output differs for file interactive.fish" + end + + if not diff interactive.tmp.err interactive.err >/dev/null + set res fail + echo "Error output differs for file interactive.fish" + end + + if test $tmp_status -ne (cat interactive.status) + set res fail + echo "Exit status differs for file interactive.fish" + end + else + echo "Tests disabled: `expect` not found" + end + if test $res = ok echo "File test.fish tested ok" exit 0