aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Kevin Ballard <kevin@sb.org>2014-09-07 19:11:34 -0700
committerGravatar Kevin Ballard <kevin@sb.org>2014-09-07 23:56:59 -0700
commit190712d4b0fe97f8437b2f3c9b642dd5c46cf60f (patch)
treec023bb8866a40e5d278b6ab51256b1429cedf456
parentefb1467e4ec70016204baaa9b88af658debbf973 (diff)
Add a test harness for interactive behavior
Add a test harness that uses `expect` to drive Fish to test interactive behavior. Include some tests for `read`.
-rw-r--r--tests/interactive.config43
-rw-r--r--tests/interactive.err0
-rw-r--r--tests/interactive.expect.rc228
-rw-r--r--tests/interactive.fish44
-rw-r--r--tests/interactive.out1
-rw-r--r--tests/interactive.status1
-rw-r--r--tests/read.expect77
-rw-r--r--tests/read.expect.err0
-rw-r--r--tests/read.expect.out9
-rw-r--r--tests/read.expect.status1
-rwxr-xr-xtests/test.fish28
11 files changed, 432 insertions, 0 deletions
diff --git a/tests/interactive.config b/tests/interactive.config
new file mode 100644
index 00000000..861cab53
--- /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 00000000..e69de29b
--- /dev/null
+++ b/tests/interactive.err
diff --git a/tests/interactive.expect.rc b/tests/interactive.expect.rc
new file mode 100644
index 00000000..a1a855de
--- /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 00000000..987ad80b
--- /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 00000000..53577b14
--- /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 00000000..573541ac
--- /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 00000000..3c153f73
--- /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 00000000..e69de29b
--- /dev/null
+++ b/tests/read.expect.err
diff --git a/tests/read.expect.out b/tests/read.expect.out
new file mode 100644
index 00000000..2d02931f
--- /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 00000000..573541ac
--- /dev/null
+++ b/tests/read.expect.status
@@ -0,0 +1 @@
+0
diff --git a/tests/test.fish b/tests/test.fish
index a74165b9..9a91bb27 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