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`.
This commit is contained in:
Kevin Ballard 2014-09-07 19:11:34 -07:00
parent efb1467e4e
commit 190712d4b0
11 changed files with 432 additions and 0 deletions

43
tests/interactive.config Normal file
View file

@ -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

0
tests/interactive.err Normal file
View file

228
tests/interactive.expect.rc Normal file
View file

@ -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"
}
}

44
tests/interactive.fish Normal file
View file

@ -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

1
tests/interactive.out Normal file
View file

@ -0,0 +1 @@
File read.expect tested ok

1
tests/interactive.status Normal file
View file

@ -0,0 +1 @@
0

77
tests/read.expect Normal file
View file

@ -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

0
tests/read.expect.err Normal file
View file

9
tests/read.expect.out Normal file
View file

@ -0,0 +1,9 @@
$foo: 'text'
$foo: 'again'
$foo: 'bar'
$foo: '123'
$foo: '456'
$foo: 'hello'
$bar: 'world!'
$foo: 'tete'
$foo: '12te'

1
tests/read.expect.status Normal file
View file

@ -0,0 +1 @@
0

View file

@ -37,6 +37,34 @@ if [ "$argv" != '-n' ]
echo "Profiling failed" echo "Profiling failed"
end 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 if test $res = ok
echo "File test.fish tested ok" echo "File test.fish tested ok"
exit 0 exit 0