mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-25 12:23:09 +00:00
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:
parent
efb1467e4e
commit
190712d4b0
11 changed files with 432 additions and 0 deletions
43
tests/interactive.config
Normal file
43
tests/interactive.config
Normal 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
0
tests/interactive.err
Normal file
228
tests/interactive.expect.rc
Normal file
228
tests/interactive.expect.rc
Normal 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
44
tests/interactive.fish
Normal 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
1
tests/interactive.out
Normal file
|
@ -0,0 +1 @@
|
|||
File read.expect tested ok
|
1
tests/interactive.status
Normal file
1
tests/interactive.status
Normal file
|
@ -0,0 +1 @@
|
|||
0
|
77
tests/read.expect
Normal file
77
tests/read.expect
Normal 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
0
tests/read.expect.err
Normal file
9
tests/read.expect.out
Normal file
9
tests/read.expect.out
Normal 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
1
tests/read.expect.status
Normal file
|
@ -0,0 +1 @@
|
|||
0
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue