mirror of
https://github.com/jlengrand/picocli.git
synced 2026-03-10 08:41:17 +00:00
[#906] initial tests to verify bash TAB completion with DejaGNU and Expect
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -13,3 +13,11 @@ picocli.iml
|
||||
/picocli-shell-jline2/out/
|
||||
/picocli-shell-jline3/out/
|
||||
/picocli-spring-boot-starter/out/
|
||||
/src/test/dejagnu.tests/xtrace.log
|
||||
/src/test/dejagnu.tests/log/completion.sum
|
||||
/src/test/dejagnu.tests/log/completion.log
|
||||
/src/test/dejagnu.tests/completion.log
|
||||
/src/test/dejagnu.tests/completion.sum
|
||||
/src/test/dejagnu.tests/testrun.log
|
||||
/src/test/dejagnu.tests/testrun.sum
|
||||
/src/test/dejagnu.tests/tmp/
|
||||
|
||||
47
src/test/dejagnu.tests/README.adoc
Normal file
47
src/test/dejagnu.tests/README.adoc
Normal file
@@ -0,0 +1,47 @@
|
||||
= Testing Completion with Expect and DejaGNU
|
||||
|
||||
The files in this directory allow testing bash TAB completion.
|
||||
The tests use the https://www.gnu.org/software/dejagnu/[DejaGNU] framework,
|
||||
which is written in https://www.nist.gov/services-resources/software/expect[Expect].
|
||||
Expect in turn uses http://tcl.sourceforge.net/[Tcl] -- Tool command language.
|
||||
|
||||
The tests work by starting a bash session with a predictable configuration,
|
||||
then sourcing some of the completion scripts in `src/test/resources`,
|
||||
and sending strings to the shell that trigger the shell to print completion candidates.
|
||||
The testing framework then asserts that these completion candidates are the expected ones.
|
||||
|
||||
== Setup
|
||||
First, install DejaGNU:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
sudo apt-get install dejagnu
|
||||
----
|
||||
|
||||
That will install the following packages:
|
||||
dejagnu expect libtcl8.6 tcl-expect tcl8.6.
|
||||
|
||||
To get the `cmdline` and `textutil` packages used in `dejagnu.tests/lib/library.exp`, get `tcllib`:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
sudo apt-get install tcllib
|
||||
----
|
||||
|
||||
== Running the Tests
|
||||
|
||||
I have not yet investigated how to automate this.
|
||||
For now, just `cd` into the `dejagnu.tests` directory and run the `./runCompletions` script:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
cd src/tests/dejagnu.tests
|
||||
./runCompletion -v # -v gives verbose output into the log directory
|
||||
----
|
||||
|
||||
== TODO
|
||||
|
||||
* Ensure these tests are run automatically as part of the Gradle build
|
||||
* Log files should be created in the `build` directory, not in `src/tests/dejagnu.tests/log`
|
||||
* Figure out how the tests can be started from any directory
|
||||
|
||||
27
src/test/dejagnu.tests/completion/basic.exp
Normal file
27
src/test/dejagnu.tests/completion/basic.exp
Normal file
@@ -0,0 +1,27 @@
|
||||
# By calling the ./runCompletion script (or runtest --tool=completion),
|
||||
# we ensure the tests are driven by the dejagnu framework:
|
||||
#
|
||||
# Before this script is invoked:
|
||||
# * proc completion_init in dejagnu.tests/lib/completion.exp is called;
|
||||
# * the first time completion_init is called, completion_start is called;
|
||||
# * this in turn starts a bash session and initializes it to give predictable behaviour:
|
||||
# (see dejagnu.tests/config/bashrc and dejagnu.tests/config/inputrc for details)
|
||||
#
|
||||
# Next, this script is called:
|
||||
# 1. source the completion script
|
||||
# 2. call proc assert_source_completions in dejagnu.tests/lib/library.exp (line 420)
|
||||
# This verifies that _some_ completion for the specified command is defined, and then
|
||||
# 3. executes the dejagnu.tests/lib/completions/$cmd.exp script.
|
||||
# This lib/completions/$cmd.exp script is where the completions are verified.
|
||||
set test "basic TAB completion"
|
||||
set script "$srcdir/../resources/basic.bash"
|
||||
#set script "/mnt/c/Users/remko/IdeaProjects/picocli3/src/test/resources/basic.bash"
|
||||
|
||||
verbose "sending 'source $script\r' to the shell..."
|
||||
send "source $script\r"
|
||||
|
||||
set cmd "basicExample"
|
||||
assert_source_completions "$cmd"
|
||||
|
||||
|
||||
|
||||
25
src/test/dejagnu.tests/completion/picocompletion.exp
Normal file
25
src/test/dejagnu.tests/completion/picocompletion.exp
Normal file
@@ -0,0 +1,25 @@
|
||||
# By calling the ./runCompletion script (or runtest --tool=completion),
|
||||
# we ensure the tests are driven by the dejagnu framework:
|
||||
#
|
||||
# Before this script is invoked:
|
||||
# * proc completion_init in dejagnu.tests/lib/completion.exp is called;
|
||||
# * the first time completion_init is called, completion_start is called;
|
||||
# * this in turn starts a bash session and initializes it to give predictable behaviour:
|
||||
# (see dejagnu.tests/config/bashrc and dejagnu.tests/config/inputrc for details)
|
||||
#
|
||||
# Next, this script is called:
|
||||
# 1. source the completion script
|
||||
# 2. call proc assert_source_completions in dejagnu.tests/lib/library.exp (line 420)
|
||||
# This verifies that _some_ completion for the specified command is defined, and then
|
||||
# 3. executes the dejagnu.tests/lib/completions/$cmd.exp script.
|
||||
# This lib/completions/$cmd.exp script is where the completions are verified.
|
||||
|
||||
set test "TAB completion"
|
||||
set script "$srcdir/../resources/picocompletion-demo_completion.bash"
|
||||
#set script "/mnt/c/Users/remko/IdeaProjects/picocli3/src/test/resources/picocompletion-demo_completion.bash"
|
||||
|
||||
verbose "sending 'source $script\r' to the shell..."
|
||||
send "source $script\r"
|
||||
|
||||
set cmd "picocompletion-demo"
|
||||
assert_source_completions "$cmd"
|
||||
54
src/test/dejagnu.tests/config/bashrc
Normal file
54
src/test/dejagnu.tests/config/bashrc
Normal file
@@ -0,0 +1,54 @@
|
||||
# bashrc file for bash-completion test suite
|
||||
|
||||
# Note that we do some initialization that would be too late to do here in
|
||||
# library.exp's start_bash() and conftest.py.
|
||||
|
||||
# Use emacs key bindings
|
||||
set -o emacs
|
||||
|
||||
# Use bash strict mode
|
||||
#set -o posix
|
||||
|
||||
# Unset `command_not_found_handle' as defined on Debian/Ubuntu, because this
|
||||
# troubles and slows down testing
|
||||
unset -f command_not_found_handle
|
||||
|
||||
export TESTDIR=$(pwd)
|
||||
mkdir -p $TESTDIR/tmp
|
||||
|
||||
export PS2='> '
|
||||
|
||||
# Also test completions of system administrator commands, which are
|
||||
# installed via the same PATH expansion in `bash_completion.have()'
|
||||
export PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin
|
||||
|
||||
# ...as well as games on some systems not in PATH by default:
|
||||
export PATH=$PATH:/usr/games:/usr/local/games
|
||||
|
||||
# For clean test state, avoid sourcing user's ~/.bash_completion
|
||||
export BASH_COMPLETION_USER_FILE=/dev/null
|
||||
|
||||
# ...and avoid stuff in BASH_COMPLETION_USER_DIR and system install locations
|
||||
# overriding in-tree completions. Setting the user dir would otherwise suffice,
|
||||
# but simple xspec completions are only installed if a separate one is not
|
||||
# found in any completion dirs. Therefore we also point the "system" dirs to
|
||||
# locations that should not yield valid completions and helpers paths either.
|
||||
export BASH_COMPLETION_USER_DIR=$(cd "$SRCDIR/.."; pwd)
|
||||
# /var/empty isn't necessarily actually always empty :P
|
||||
export BASH_COMPLETION_COMPAT_DIR=/var/empty/bash_completion.d
|
||||
export XDG_DATA_DIRS=/var/empty
|
||||
|
||||
# Make sure default settings are in effect
|
||||
unset -v \
|
||||
COMP_CONFIGURE_HINTS \
|
||||
COMP_CVS_REMOTE \
|
||||
COMP_KNOWN_HOSTS_WITH_HOSTFILE \
|
||||
COMP_TAR_INTERNAL_PATHS
|
||||
|
||||
# Load bash testsuite helper functions
|
||||
. $SRCDIR/lib/library.sh
|
||||
|
||||
# Local variables:
|
||||
# mode: shell-script
|
||||
# End:
|
||||
# ex: filetype=sh
|
||||
24
src/test/dejagnu.tests/config/default.exp
Normal file
24
src/test/dejagnu.tests/config/default.exp
Normal file
@@ -0,0 +1,24 @@
|
||||
# Set default expect fallback routines
|
||||
expect_after {
|
||||
eof {
|
||||
if {[info exists test]} {
|
||||
fail "$test at eof"
|
||||
} elseif {[info level] > 0} {
|
||||
fail "[info level 1] at eof"
|
||||
} else {
|
||||
fail "eof"
|
||||
}
|
||||
}
|
||||
timeout {
|
||||
if {[info exists test]} {
|
||||
fail "$test at timeout"
|
||||
} elseif {[info level] > 0} {
|
||||
fail "[info level 1] at timeout"
|
||||
} else {
|
||||
fail "timeout"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verbose "config/default.exp just got loaded..."
|
||||
|
||||
18
src/test/dejagnu.tests/config/inputrc
Normal file
18
src/test/dejagnu.tests/config/inputrc
Normal file
@@ -0,0 +1,18 @@
|
||||
# Readline init file for DejaGnu testsuite
|
||||
# See: info readline
|
||||
|
||||
# Press TAB once (instead of twice) to auto-complete
|
||||
set show-all-if-ambiguous on
|
||||
# No bell. No ^G in output
|
||||
set bell-style none
|
||||
# Don't query user about viewing the number of possible completions
|
||||
set completion-query-items -1
|
||||
# Display completions sorted horizontally, not vertically
|
||||
set print-completions-horizontally on
|
||||
# Don't use pager when showing completions
|
||||
set page-completions off
|
||||
|
||||
# Local variables:
|
||||
# mode: shell-script
|
||||
# End:
|
||||
# ex: filetype=sh
|
||||
7
src/test/dejagnu.tests/config/unix.exp
Normal file
7
src/test/dejagnu.tests/config/unix.exp
Normal file
@@ -0,0 +1,7 @@
|
||||
# Unsure why this file is necessary; without it we get this warning:
|
||||
# WARNING: Couldn't find tool config file for unix, using default.
|
||||
# Loading /usr/share/dejagnu/config/default.exp
|
||||
proc completion_exit {} {}
|
||||
proc completion_version {} {}
|
||||
|
||||
verbose "config/unix.exp just got loaded..."
|
||||
26
src/test/dejagnu.tests/lib/completion.exp
Normal file
26
src/test/dejagnu.tests/lib/completion.exp
Normal file
@@ -0,0 +1,26 @@
|
||||
source $::srcdir/lib/library.exp
|
||||
|
||||
|
||||
proc completion_exit {} {
|
||||
send "\rexit\r"
|
||||
}
|
||||
|
||||
|
||||
proc completion_init {test_file_name} {
|
||||
verbose "Entered completion_init..."
|
||||
# Call completion_start() only once
|
||||
if {! [info exists ::BASH_VERSINFO]} {
|
||||
verbose "Calling completion_start..."
|
||||
completion_start
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
proc completion_start {} {
|
||||
start_interactive_test
|
||||
}
|
||||
|
||||
|
||||
proc completion_version {} {
|
||||
puts "$::TESTDIR, bash-$::BASH_VERSION"
|
||||
}
|
||||
38
src/test/dejagnu.tests/lib/completions/basicExample.exp
Normal file
38
src/test/dejagnu.tests/lib/completions/basicExample.exp
Normal file
@@ -0,0 +1,38 @@
|
||||
proc setup {} {
|
||||
save_env
|
||||
}
|
||||
|
||||
proc teardown {} {
|
||||
assert_env_unmodified {/OLDPWD=/d}
|
||||
}
|
||||
|
||||
setup
|
||||
|
||||
|
||||
# Try completion with one hyphen
|
||||
set cmd "basicExample -"
|
||||
set test "Tab should complete '$cmd'"
|
||||
verbose "Sending $cmd\t..."
|
||||
send "$cmd\t"
|
||||
#exp_internal 1 # tell Expect to print diagnostics on its internal operations
|
||||
expect {
|
||||
-re "--timeUnit\\s*--timeout\\s*-t\\s*-u\r\n/@$cmd" { pass "'$cmd' COMPLETED OK"}
|
||||
-re /@ { unresolved "$test at prompt" }
|
||||
default { unresolved "$test" }
|
||||
}
|
||||
sync_after_int
|
||||
|
||||
# Try completion with two hyphens
|
||||
set cmd "basicExample --"
|
||||
set test "Tab should complete '$cmd'"
|
||||
verbose "Sending ${cmd}\t..."
|
||||
send "$cmd\t"
|
||||
expect {
|
||||
-re "--timeUnit\\s*--timeout\r\n/@${cmd}time" { pass "'$cmd' COMPLETED OK"}
|
||||
-re /@ { unresolved "$test at prompt" }
|
||||
default { unresolved "$test" }
|
||||
}
|
||||
sync_after_int
|
||||
|
||||
|
||||
teardown
|
||||
@@ -0,0 +1,55 @@
|
||||
proc setup {} {
|
||||
save_env
|
||||
}
|
||||
|
||||
proc teardown {} {
|
||||
assert_env_unmodified {/OLDPWD=/d}
|
||||
}
|
||||
|
||||
setup
|
||||
|
||||
|
||||
# Try completion without args
|
||||
set cmd "picocompletion-demo "
|
||||
set test "Tab should complete '${cmd}'"
|
||||
verbose "Sending ${cmd}\t..."
|
||||
send "${cmd}\t"
|
||||
|
||||
expect "${cmd}\r\n"
|
||||
expect {
|
||||
-re "sub1\\s*sub2\r\n/@${cmd}sub" { pass "$test: we got sub1 and sub2" }
|
||||
-re /@ { unresolved "$test at prompt" }
|
||||
default { unresolved "$test" }
|
||||
}
|
||||
sync_after_int
|
||||
|
||||
# Try completion for sub1 without options
|
||||
set cmd " picocompletion-demo sub1 "
|
||||
set test "Tab should not show completions for '${cmd}'"
|
||||
verbose "Sending ${cmd}\t..."
|
||||
send "${cmd}\t"
|
||||
|
||||
set timeout 1
|
||||
expect "${cmd}\r\n"
|
||||
expect {
|
||||
-re "${cmd}\t\[^\\s]*" { fail "$test: we got $expect_out(0,string)" }
|
||||
default { pass "$test" }
|
||||
}
|
||||
sync_after_int
|
||||
|
||||
# Try completion for sub1
|
||||
set cmd " picocompletion-demo sub1 -"
|
||||
set test "Tab should complete '${cmd}'"
|
||||
verbose "Sending ${cmd}\t..."
|
||||
send "${cmd}\t"
|
||||
|
||||
set timeout 10
|
||||
expect "${cmd}\r\n"
|
||||
expect {
|
||||
-re "--candidates\\s*--num\\s*--str\r\n/@${cmd}-" { pass "$test: we got options for sub1" }
|
||||
-re /@ { unresolved "$test at prompt" }
|
||||
default { unresolved "$test" }
|
||||
}
|
||||
sync_after_int
|
||||
|
||||
teardown
|
||||
873
src/test/dejagnu.tests/lib/library.exp
Normal file
873
src/test/dejagnu.tests/lib/library.exp
Normal file
@@ -0,0 +1,873 @@
|
||||
# Source `init.tcl' again to restore the `unknown' procedure
|
||||
# NOTE: DejaGnu has an old `unknown' procedure which unfortunately disables
|
||||
# tcl auto-loading.
|
||||
source [file join [info library] init.tcl]
|
||||
package require cmdline
|
||||
package require textutil::string
|
||||
|
||||
|
||||
|
||||
# Execute a bash command and make sure the exit status is successful.
|
||||
# If not, output the error message.
|
||||
# @param string $cmd Bash command line to execute. If empty string (""), the
|
||||
# exit status of the previously executed bash command will be
|
||||
# checked; specify `title' to adorn the error message.
|
||||
# @param string $title (optional) Command title. If empty, `cmd' is used.
|
||||
# @param string $prompt (optional) Bash prompt. Default is "/@"
|
||||
# @param mixed $out (optional) Reference to (tcl) variable to hold output.
|
||||
# If variable equals -1 (default) the bash command is expected
|
||||
# to return no output. If variable equals 0, any output
|
||||
# from the bash command is disregarded.
|
||||
proc assert_bash_exec {{aCmd ""} {title ""} {prompt /@} {out -1}} {
|
||||
if {$out != 0 && $out != -1} {upvar $out results}
|
||||
if {[string length $aCmd] != 0} {
|
||||
send "$aCmd\r"
|
||||
expect -ex "$aCmd\r\n"
|
||||
}
|
||||
if {[string length $title] == 0} {set title $aCmd}
|
||||
expect -ex $prompt
|
||||
set results $expect_out(buffer); # Catch output
|
||||
# Remove $prompt suffix from output
|
||||
set results [
|
||||
string range $results 0 [
|
||||
expr [string length $results] - [string length $prompt] - 1
|
||||
]
|
||||
]
|
||||
if {$out == -1 && [string length $results] > 0} {
|
||||
fail "ERROR Unexpected output from bash command \"$title\""
|
||||
}
|
||||
|
||||
set cmd "echo $?"
|
||||
send "$cmd\r"
|
||||
expect {
|
||||
-ex "$cmd\r\n0\r\n$prompt" {}
|
||||
$prompt {fail "ERROR executing bash command \"$title\""}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Test `type ...' in bash
|
||||
# Indicate "unsupported" if `type' exits with error status.
|
||||
# @param string $command Command to locate
|
||||
proc assert_bash_type {command} {
|
||||
set test "$command should be available in bash"
|
||||
set cmd "type $command &>/dev/null && echo -n 0 || echo -n 1"
|
||||
send "$cmd\r"
|
||||
expect "$cmd\r\n"
|
||||
expect {
|
||||
-ex 0 { set result true }
|
||||
-ex 1 { set result false; unsupported "$test" }
|
||||
}
|
||||
expect "/@"
|
||||
return $result
|
||||
}
|
||||
|
||||
|
||||
# Make sure the expected list matches the real list, as returned by executing
|
||||
# the specified bash command.
|
||||
# Specify `-sort' if the real list is sorted.
|
||||
# @param list $expected Expected list items
|
||||
# @param string $cmd Bash command to execute in order to generate real list
|
||||
# items
|
||||
# @param string $test Test title. Becomes "$cmd should show expected output"
|
||||
# if empty string.
|
||||
# @param list $args Options:
|
||||
# -sort Compare list sorted. Default is unsorted
|
||||
# -prompt Bash prompt. Default is `/@'
|
||||
# -chunk-size N Compare list N items at a time. Default
|
||||
# is 20.
|
||||
proc assert_bash_list {expected cmd test {args {}}} {
|
||||
array set arg [::cmdline::getoptions args {
|
||||
{sort "compare list sorted"}
|
||||
{prompt.arg /@ "bash prompt"}
|
||||
{chunk-size.arg 20 "compare N list items at a time"}
|
||||
}]
|
||||
set prompt $arg(prompt)
|
||||
if {$test == ""} {set test "$cmd should show expected output"}
|
||||
if {[llength $expected] == 0} {
|
||||
assert_no_output $cmd $test $prompt
|
||||
} else {
|
||||
send "$cmd\r"
|
||||
expect -ex "$cmd\r\n"
|
||||
if {$arg(sort)} {set bash_sort "-bash-sort"} {set bash_sort ""}
|
||||
if {[
|
||||
eval match_items \$expected $bash_sort -chunk-size \
|
||||
\$arg(chunk-size) -end-newline -end-prompt \
|
||||
-prompt \$prompt
|
||||
]} {
|
||||
pass "$test"
|
||||
} else {
|
||||
fail "$test"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Make sure the expected items are returned by TAB-completing the specified
|
||||
# command. If the number of expected items is one, expected is:
|
||||
#
|
||||
# $cmd<TAB>$expected[<SPACE>]
|
||||
#
|
||||
# SPACE is not expected if -nospace is specified.
|
||||
#
|
||||
# If the number of expected items is greater than one, expected is:
|
||||
#
|
||||
# $cmd<TAB>\n
|
||||
# $expected\n
|
||||
# $prompt + ($cmd - AUTO) + longest-common-prefix-of-$expected
|
||||
#
|
||||
# AUTO is calculated like this: If $cmd ends with non-whitespace, and
|
||||
# the last argument of $cmd equals the longest-common-prefix of
|
||||
# $expected, $cmd minus this argument will be expected.
|
||||
#
|
||||
# If the algorithm above fails, you can manually specify the CWORD to be
|
||||
# subtracted from $cmd specifying `-expect-cmd-minus CWORD'. Known cases where
|
||||
# this is useful are when:
|
||||
# - the last whitespace is escaped, e.g. "finger foo\ " or "finger
|
||||
# 'foo "
|
||||
#
|
||||
# @param list $expected Expected completions.
|
||||
# @param string $cmd Command given to generate items
|
||||
# @param string $test Test title
|
||||
# @param list $args Options:
|
||||
# -prompt PROMPT Bash prompt. Default is `/@'
|
||||
# -chunk-size CHUNK-SIZE Compare list CHUNK-SIZE items at
|
||||
# a time. Default is 20.
|
||||
# -nospace Don't expect space character to be output after completion match.
|
||||
# Valid only if a single completion is expected.
|
||||
# -expect-cmd-minus DWORD Expect $cmd minus DWORD to be echoed.
|
||||
# Expected is:
|
||||
#
|
||||
# $cmd<TAB>\n
|
||||
# $expected\n
|
||||
# $prompt + ($cmd - DWORD) + longest-common-prefix-of-$expected
|
||||
#
|
||||
proc assert_complete {expected cmd {test ""} {args {}}} {
|
||||
set args_orig $args
|
||||
array set arg [::cmdline::getoptions args {
|
||||
{prompt.arg "/@" "bash prompt"}
|
||||
{chunk-size.arg 20 "compare N list items at a time"}
|
||||
{nospace "don't expect space after completion"}
|
||||
{expect-cmd-minus.arg "" "Expect cmd minus DWORD after prompt"}
|
||||
}]
|
||||
if {[llength $expected] == 0} {
|
||||
assert_no_complete $cmd $test
|
||||
} elseif {[llength $expected] == 1} {
|
||||
eval assert_complete_one \$expected \$cmd \$test $args_orig
|
||||
} else {
|
||||
eval assert_complete_many \$expected \$cmd \$test $args_orig
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Make sure the expected multiple items are returned by TAB-completing the
|
||||
# specified command.
|
||||
# @see assert_complete()
|
||||
proc assert_complete_many {expected cmd {test ""} {args {}}} {
|
||||
array set arg [::cmdline::getoptions args {
|
||||
{prompt.arg "/@" "bash prompt"}
|
||||
{chunk-size.arg 20 "compare N list items at a time"}
|
||||
{nospace "don't expect space after completion"}
|
||||
{expect-cmd-minus.arg "" "Expect cmd minus CWORD after prompt"}
|
||||
}]
|
||||
if {$test == ""} {set test "$cmd should show completions"}
|
||||
set prompt $arg(prompt)
|
||||
set dword ""
|
||||
if {$arg(expect-cmd-minus) != ""} {set dword $arg(expect-cmd-minus)}
|
||||
|
||||
send "$cmd\t"
|
||||
expect -ex "$cmd\r\n"
|
||||
|
||||
# Make sure expected items are unique
|
||||
set expected [lsort -unique $expected]
|
||||
|
||||
# Determine common prefix of completions
|
||||
set common [::textutil::string::longestCommonPrefixList $expected]
|
||||
|
||||
set cmd2 [_remove_cword_from_cmd $cmd $dword $common]
|
||||
|
||||
set prompt "$prompt$cmd2$common"
|
||||
if {$arg(nospace)} {set endspace ""} else {set endspace "-end-space"}
|
||||
set endprompt "-end-prompt"
|
||||
if {[
|
||||
eval match_items \$expected -bash-sort -chunk-size \
|
||||
\$arg(chunk-size) $endprompt $endspace -prompt \$prompt
|
||||
]} {
|
||||
pass "$test"
|
||||
} else {
|
||||
fail "$test"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Make sure the expected single item is returned by TAB-completing the
|
||||
# specified command.
|
||||
# @see assert_complete()
|
||||
proc assert_complete_one {expected cmd {test ""} {args {}}} {
|
||||
array set arg [::cmdline::getoptions args {
|
||||
{prompt.arg "/@" "bash prompt"}
|
||||
{chunk-size.arg 20 "compare N list items at a time"}
|
||||
{nospace "don't expect space after completion"}
|
||||
{expect-cmd-minus.arg "" "Expect cmd minus CWORD after prompt"}
|
||||
}]
|
||||
set prompt $arg(prompt)
|
||||
|
||||
if {$test == ""} {set test "$cmd should show completion"}
|
||||
send "$cmd\t"
|
||||
expect -ex "$cmd"
|
||||
set cur ""; # Default to empty word to complete on
|
||||
set words [split_words_bash $cmd]
|
||||
if {[llength $words] > 1} {
|
||||
# Assume last word of `$cmd' is word to complete on.
|
||||
set index [expr [llength $words] - 1]
|
||||
set cur [lindex $words $index]
|
||||
}
|
||||
# Remove second word from beginning of $expected
|
||||
if {[string first $cur $expected] == 0} {
|
||||
set expected [list [string range $expected [string length $cur] end]]
|
||||
}
|
||||
|
||||
if {$arg(nospace)} {set endspace ""} else {set endspace "-end-space"}
|
||||
if {[
|
||||
eval match_items \$expected -bash-sort -chunk-size \
|
||||
\$arg(chunk-size) $endspace -prompt \$prompt
|
||||
]} {
|
||||
pass "$test"
|
||||
} else {
|
||||
fail "$test"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# @param string $cmd Command to remove current-word-to-complete from.
|
||||
# @param string $dword (optional) Manually specify current-word-to-complete,
|
||||
# i.e. word to remove from $cmd. If empty string (default),
|
||||
# `_remove_cword_from_cmd' autodetects if the last argument is the
|
||||
# current-word-to-complete by checking if $cmd doesn't end with whitespace.
|
||||
# Specifying `dword' is only necessary if this autodetection fails, e.g.
|
||||
# when the last whitespace is escaped or quoted, e.g. "finger foo\ " or
|
||||
# "finger 'foo "
|
||||
# @param string $common (optional) Common prefix of expected completions.
|
||||
# @return string Command with current-word-to-complete removed
|
||||
proc _remove_cword_from_cmd {cmd {dword ""} {common ""}} {
|
||||
set cmd2 $cmd
|
||||
# Is $dword specified?
|
||||
if {[string length $dword] > 0} {
|
||||
# Remove $dword from end of $cmd
|
||||
if {[string last $dword $cmd] == [string length $cmd] - [string length $dword]} {
|
||||
set cmd2 [string range $cmd 0 [expr [string last $dword $cmd] - 1]]
|
||||
}
|
||||
} else {
|
||||
# No, $dword not specified;
|
||||
# Check if last argument is really a word-to-complete, i.e.
|
||||
# doesn't end with whitespace.
|
||||
# NOTE: This check fails if trailing whitespace is escaped or quoted,
|
||||
# e.g. "finger foo\ " or "finger 'foo ". Specify parameter
|
||||
# $dword in those cases.
|
||||
# Is last char whitespace?
|
||||
if {! [string is space [string range $cmd end end]]} {
|
||||
# No, last char isn't whitespace;
|
||||
set cmds [split $cmd]
|
||||
# Does word-to-complete start with $common?
|
||||
if {[string first $common [lrange $cmds end end]] == 0} {
|
||||
# Remove word-to-complete from end of $cmd
|
||||
set cmd2 [lrange $cmds 0 end-1]
|
||||
append cmd2 " "
|
||||
}
|
||||
}
|
||||
}
|
||||
return $cmd2
|
||||
}
|
||||
|
||||
|
||||
# Escape regexp special characters
|
||||
proc _escape_regexp_chars {var} {
|
||||
upvar $var str
|
||||
regsub -all {([\^$+*?.|(){}[\]\\])} $str {\\\1} str
|
||||
}
|
||||
|
||||
|
||||
# Make sure the expected files are returned by TAB-completing the specified
|
||||
# command in the specified subdirectory. Be prepared to filter out OLDPWD
|
||||
# changes when calling assert_env_unmodified() after using this procedure.
|
||||
# @param list $expected
|
||||
# @param string $cmd Command given to generate items
|
||||
# @param string $dir Subdirectory to attempt completion in. The directory must be relative from the $TESTDIR and without a trailing slash. E.g. `fixtures/evince'
|
||||
# @param string $test Test title
|
||||
# @param list $args See: assert_complete()
|
||||
# @result boolean True if successful, False if not
|
||||
proc assert_complete_dir {expected cmd dir {test ""} {args {}}} {
|
||||
set prompt "/@"
|
||||
assert_bash_exec "cd $dir" "" $prompt
|
||||
eval assert_complete \$expected \$cmd \$test $args
|
||||
sync_after_int $prompt
|
||||
assert_bash_exec {cd "$TESTDIR"}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Make sure the bash environment hasn't changed between now and the last call
|
||||
# to `save_env()'.
|
||||
# @param string $sed Sed commands to preprocess diff output.
|
||||
# Example calls:
|
||||
#
|
||||
# # Replace `COMP_PATH=.*' with `COMP_PATH=PATH'
|
||||
# assert_env_unmodified {s/COMP_PATH=.*/COMP_PATH=PATH/}
|
||||
#
|
||||
# # Remove lines containing `OLDPWD='
|
||||
# assert_env_unmodified {/OLDPWD=/d}
|
||||
#
|
||||
# @param string $file Filename to generate environment save file from. See
|
||||
# `gen_env_filename()'.
|
||||
# @param string $diff Expected diff output (after being processed by $sed)
|
||||
# @see save_env()
|
||||
proc assert_env_unmodified {{sed ""} {file ""} {diff ""}} {
|
||||
set test "Environment should not be modified"
|
||||
_save_env [gen_env_filename $file 2]
|
||||
|
||||
# Prepare sed script
|
||||
|
||||
# Escape special bash characters ("\)
|
||||
regsub -all {([\"\\])} $sed {\\\1} sed; #"# (fix Vim syntax highlighting)
|
||||
# Escape newlines
|
||||
regsub -all {\n} [string trim $sed] "\r\n" sed
|
||||
|
||||
# Prepare diff script
|
||||
|
||||
# If diff is filled, escape newlines and make sure it ends with a newline
|
||||
if {[string length [string trim $diff]]} {
|
||||
regsub -all {\n} [string trim $diff] "\r\n" diff
|
||||
append diff "\r\n"
|
||||
} else {
|
||||
set diff ""
|
||||
}
|
||||
|
||||
# Execute diff
|
||||
|
||||
# NOTE: The dummy argument 'LAST-ARG' sets bash variable $_ (last argument) to
|
||||
# 'LAST-ARG' so that $_ doesn't mess up the diff (as it would if $_
|
||||
# was the (possibly multi-lined) sed script).
|
||||
set cmd "diff_env \"[gen_env_filename $file 1]\" \"[gen_env_filename $file 2]\" \"$sed\" LAST-ARG"
|
||||
send "$cmd\r"
|
||||
expect "LAST-ARG\r\n"
|
||||
|
||||
expect {
|
||||
-re "^$diff[wd]@$" { pass "$test" }
|
||||
-re [wd]@ {
|
||||
fail "$test"
|
||||
|
||||
# Show diff to user
|
||||
|
||||
set diff $expect_out(buffer)
|
||||
# Remove possible `\r\n[wd]@' from end of diff
|
||||
if {[string last "\r\n[wd]@" $diff] == [string length $diff] - [string length "\r\n[wd]@"]} {
|
||||
set diff [string range $diff 0 [expr [string last "\r\n[wd]@" $diff] - 1]]
|
||||
}
|
||||
send_user $diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Check that no completion is attempted on a certain command.
|
||||
# Params:
|
||||
# @cmd The command to attempt to complete.
|
||||
# @test Optional parameter with test name.
|
||||
proc assert_no_complete {{cmd} {test ""}} {
|
||||
if {[string length $test] == 0} {
|
||||
set test "$cmd shouldn't complete"
|
||||
}
|
||||
|
||||
send "$cmd\t"
|
||||
expect -ex "$cmd"
|
||||
|
||||
# We can't anchor on $, simulate typing a magical string instead.
|
||||
set endguard "Magic End Guard"
|
||||
send "$endguard"
|
||||
expect {
|
||||
-re "^$endguard$" { pass "$test" }
|
||||
default { fail "$test" }
|
||||
timeout { fail "$test" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Check that no output is generated on a certain command.
|
||||
# @param string $cmd The command to attempt to complete.
|
||||
# @param string $test Optional parameter with test name.
|
||||
# @param string $prompt (optional) Bash prompt. Default is "/@"
|
||||
proc assert_no_output {{cmd} {test ""} {prompt /@}} {
|
||||
if {[string length $test] == 0} {
|
||||
set test "$cmd shouldn't generate output"
|
||||
}
|
||||
|
||||
send "$cmd\r"
|
||||
expect -ex "$cmd"
|
||||
|
||||
expect {
|
||||
-re "^\r\n$prompt$" { pass "$test" }
|
||||
default { fail "$test" }
|
||||
timeout { fail "$test" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Source/run file with additional tests if completion for the specified command
|
||||
# is installed in bash, and the command is available.
|
||||
# @param string $command Command to check completion availability for.
|
||||
# @param string $file (optional) File to source/run. Default is
|
||||
# "lib/completions/$cmd.exp".
|
||||
proc assert_source_completions {command {file ""}} {
|
||||
# if {[assert_bash_type $command] # we will test custom commands
|
||||
# && [assert_install_completion_for $command]} { ... }
|
||||
if {[assert_completions_installed_for $command]} {
|
||||
if {[string length $file] == 0} {
|
||||
set file "$::srcdir/lib/completions/$command.exp"
|
||||
}
|
||||
source $file
|
||||
} else {
|
||||
untested $command
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Sort list.
|
||||
# `exec sort' is used instead of `lsort' to achieve exactly the
|
||||
# same sort order as in bash.
|
||||
# @param list $items
|
||||
# @return list Sort list
|
||||
proc bash_sort {items} {
|
||||
return [split [exec sort << [join $items "\n"]] "\n"]
|
||||
}
|
||||
|
||||
|
||||
# Get hostnames
|
||||
# @param list $args Options:
|
||||
# -unsorted Do not sort unique. Default is sort unique.
|
||||
# @return list Hostnames
|
||||
proc get_hosts {{args {}}} {
|
||||
array set arg [::cmdline::getoptions args {
|
||||
{unsorted "do not sort unique"}
|
||||
}]
|
||||
set sort "| sort -u"
|
||||
if {$arg(unsorted)} {set sort ""}
|
||||
set hosts [exec bash -c "compgen -A hostname $sort"]
|
||||
# NOTE: Circumventing var `avahi_hosts' and appending directly to `hosts'
|
||||
# causes an empty element to be inserted in `hosts'.
|
||||
# -- FVu, Fri Jul 17 23:11:46 CEST 2009
|
||||
set avahi_hosts [get_hosts_avahi]
|
||||
if {[llength $avahi_hosts] > 0} {
|
||||
lappend hosts $avahi_hosts
|
||||
}
|
||||
return $hosts
|
||||
}
|
||||
|
||||
|
||||
# Get hostnames according to avahi
|
||||
# @return list Hostnames
|
||||
proc get_hosts_avahi {} {
|
||||
# Retrieving hosts is successful?
|
||||
if { [catch {exec bash -c {
|
||||
type avahi-browse >&/dev/null \
|
||||
&& avahi-browse -cpr _workstation._tcp 2>/dev/null | command grep ^= | cut -d\; -f7 | sort -u
|
||||
}} hosts] } {
|
||||
# No, retrieving hosts yields error;
|
||||
# Reset hosts
|
||||
set hosts {}
|
||||
}
|
||||
return $hosts
|
||||
}
|
||||
|
||||
|
||||
# Initialize tcl globals with bash variables
|
||||
proc init_tcl_bash_globals {} {
|
||||
global BASH_VERSINFO BASH_VERSION COMP_WORDBREAKS LC_CTYPE
|
||||
assert_bash_exec {printf "%s" "$COMP_WORDBREAKS"} {} /@ COMP_WORDBREAKS
|
||||
assert_bash_exec {printf "%s " "${BASH_VERSINFO[@]}"} "" /@ BASH_VERSINFO
|
||||
set BASH_VERSINFO [eval list $BASH_VERSINFO]
|
||||
assert_bash_exec {printf "%s" "$BASH_VERSION"} "" /@ BASH_VERSION
|
||||
assert_bash_exec {printf "%s" "$TESTDIR"} "" /@ TESTDIR
|
||||
assert_bash_exec {eval $(locale); printf "%s" "$LC_CTYPE"} "" /@ LC_CTYPE
|
||||
}
|
||||
|
||||
|
||||
# DEPRECATED
|
||||
# TODO REMOVE THIS PROCEDURE
|
||||
# Try installing completion for the specified command.
|
||||
# @param string $command Command to install completion for.
|
||||
# @return boolean True (1) if completion is installed, False (0) if not.
|
||||
proc _install_completion_for {command} {
|
||||
set test "$command should have completion installed in bash"
|
||||
set cmd "__load_completion $command && echo -n 0 || echo -n 1"
|
||||
send "$cmd\r"
|
||||
expect "$cmd\r\n"
|
||||
expect {
|
||||
-ex 0 { set result true }
|
||||
-ex 1 { set result false }
|
||||
}
|
||||
expect "/@"
|
||||
return $result
|
||||
}
|
||||
|
||||
# Tests whether completions are installed for the specified command.
|
||||
# @param string $command Command to verify that completions are installed.
|
||||
# @return boolean True (1) if completion is installed, False (0) if not.
|
||||
proc assert_completions_installed_for {command} {
|
||||
set test "$command should have completion installed in bash"
|
||||
set cmd "complete -p $command &>/dev/null && echo -n 0 || echo -n 1"
|
||||
send "$cmd\r"
|
||||
expect "$cmd\r\n"
|
||||
expect {
|
||||
-ex 0 { set result true }
|
||||
-ex 1 { set result false }
|
||||
}
|
||||
expect "/@"
|
||||
return $result
|
||||
}
|
||||
|
||||
|
||||
# Detect if test suite is running under Cygwin/Windows
|
||||
proc is_cygwin {} {
|
||||
expr {[string first [string tolower [exec uname -s]] cygwin] >= 0}
|
||||
}
|
||||
|
||||
|
||||
# Expect items, a limited number (20) at a time.
|
||||
# Break items into chunks because `expect' seems to have a limited buffer size
|
||||
# @param list $items Expected list items
|
||||
# @param list $args Options:
|
||||
# -bash-sort Compare list bash-sorted. Default is
|
||||
# unsorted
|
||||
# -prompt PROMPT Bash prompt. Default is `/@'
|
||||
# -chunk-size CHUNK-SIZE Compare list CHUNK-SIZE items at
|
||||
# a time. Default is 20.
|
||||
# -end-newline Expect newline after last item.
|
||||
# Default is not.
|
||||
# -end-prompt Expect prompt after last item.
|
||||
# Default is not.
|
||||
# -end-space Expect single space after last item.
|
||||
# Default is not. Valid only if
|
||||
# `end-newline' not set.
|
||||
# @result boolean True if successful, False if not
|
||||
proc match_items {items {args {}}} {
|
||||
array set arg [::cmdline::getoptions args {
|
||||
{bash-sort "compare list sorted"}
|
||||
{prompt.arg "/@" "bash prompt"}
|
||||
{chunk-size.arg 20 "compare N list items at a time"}
|
||||
{end-newline "expect newline after last item"}
|
||||
{end-prompt "expect prompt after last item"}
|
||||
{end-space "expect space ater last item"}
|
||||
}]
|
||||
set prompt $arg(prompt)
|
||||
set size $arg(chunk-size)
|
||||
if {$arg(bash-sort)} {set items [bash_sort $items]}
|
||||
set result false
|
||||
for {set i 0} {$i < [llength $items]} {set i [expr {$i + $size}]} {
|
||||
# For chunks > 1, allow leading whitespace
|
||||
if {$i > $size} { set expected "\\s*" } else { set expected "" }
|
||||
for {set j 0} {$j < $size && $i + $j < [llength $items]} {incr j} {
|
||||
set item "[lindex $items [expr {$i + $j}]]"
|
||||
_escape_regexp_chars item
|
||||
append expected $item
|
||||
if {[llength $items] > 1} {append expected {\s+}}
|
||||
}
|
||||
if {[llength $items] == 1} {
|
||||
if {$arg(end-prompt)} {set end $prompt} {set end ""}
|
||||
# Both trailing space and newline are specified?
|
||||
if {$arg(end-newline) && $arg(end-space)} {
|
||||
# Indicate both trailing space or newline are ok
|
||||
set expected2 "|^$expected $end$"; # Include space
|
||||
append expected "\r\n$end"; # Include newline
|
||||
} else {
|
||||
if {$arg(end-newline)} {append expected "\r\n$end"}
|
||||
if {$arg(end-space)} {append expected " $end"}
|
||||
set expected2 ""
|
||||
}
|
||||
expect {
|
||||
-re "^$expected$$expected2" { set result true }
|
||||
-re "^$prompt$" {set result false; break }
|
||||
default { set result false; break }
|
||||
timeout { set result false; break }
|
||||
}
|
||||
} else {
|
||||
set end ""
|
||||
if {$arg(end-prompt) && $i + $j == [llength $items]} {
|
||||
set end "$prompt"
|
||||
_escape_regexp_chars end
|
||||
# \$ matches real end of expect_out buffer
|
||||
set end "$end\$"
|
||||
}
|
||||
expect {
|
||||
-re "^$expected$end" { set result true }
|
||||
default { set result false; break }
|
||||
timeout { set result false; break }
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
|
||||
# Generate filename to save environment to.
|
||||
# @param string $file File-basename to save environment to. If the file has a
|
||||
# `.exp' suffix, it is removed. E.g.:
|
||||
# - "file.exp" becomes "file.env1~"
|
||||
# - "" becomes "env.env1~"
|
||||
# - "filename" becomes "filename.env1~"
|
||||
# The file will be stored in the $TESTDIR/tmp directory.
|
||||
# @param integer $seq Sequence number. Must be either 1 or 2.
|
||||
proc gen_env_filename {{file ""} {seq 1}} {
|
||||
if {[string length $file] == 0} {
|
||||
set file "env"
|
||||
} else {
|
||||
# Remove possible directories
|
||||
set file [file tail $file]
|
||||
# Remove possible '.exp' suffix from filename
|
||||
if {[string last ".exp" $file] == [string length $file] - [string length ".exp"]} {
|
||||
set file [string range $file 0 [expr [string last ".exp" $file] - 1]]
|
||||
}
|
||||
}
|
||||
return "\$TESTDIR/tmp/$file.env$seq~"
|
||||
}
|
||||
|
||||
|
||||
# Save the environment for later comparison
|
||||
# @param string $file Filename to generate environment save file from. See
|
||||
# `gen_env_filename()'.
|
||||
proc save_env {{file ""}} {
|
||||
_save_env [gen_env_filename $file 1]
|
||||
}
|
||||
|
||||
|
||||
# Save the environment for later comparison
|
||||
# @param string File to save the environment to. Default is "$TESTDIR/tmp/env1~".
|
||||
# @see assert_env_unmodified()
|
||||
proc _save_env {{file ""}} {
|
||||
assert_bash_exec "{ (set -o posix ; set); declare -F; shopt -p; set -o; } > \"$file\""
|
||||
}
|
||||
|
||||
|
||||
# Source bash_completion package
|
||||
# TODO REMOVE THIS PROCEDURE
|
||||
proc source_bash_completion {} {
|
||||
assert_bash_exec {source $(cd "$SRCDIR/.."; pwd)/bash_completion}
|
||||
}
|
||||
|
||||
|
||||
# Split line into words, disregarding backslash escapes (e.g. \b (backspace),
|
||||
# \g (bell)), but taking backslashed spaces into account.
|
||||
# Aimed for simulating bash word splitting.
|
||||
# Example usage:
|
||||
#
|
||||
# % set a {f cd\ \be}
|
||||
# % split_words $a
|
||||
# f {cd\ \be}
|
||||
#
|
||||
# @param string Line to split
|
||||
# @return list Words
|
||||
proc split_words_bash {line} {
|
||||
set words {}
|
||||
set glue false
|
||||
foreach part [split $line] {
|
||||
set glue_next false
|
||||
# Does `part' end with a backslash (\)?
|
||||
if {[string last "\\" $part] == [string length $part] - [string length "\\"]} {
|
||||
# Remove end backslash
|
||||
set part [string range $part 0 [expr [string length $part] - [string length "\\"] - 1]]
|
||||
# Indicate glue on next run
|
||||
set glue_next true
|
||||
}
|
||||
# Must `part' be appended to latest word (= glue)?
|
||||
if {[llength $words] > 0 && [string is true $glue]} {
|
||||
# Yes, join `part' to latest word;
|
||||
set zz [lindex $words [expr [llength $words] - 1]]
|
||||
# Separate glue with backslash-space (\ );
|
||||
lset words [expr [llength $words] - 1] "$zz\\ $part"
|
||||
} else {
|
||||
# No, don't append word to latest word;
|
||||
# Append `part' as separate word
|
||||
lappend words $part
|
||||
}
|
||||
set glue $glue_next
|
||||
}
|
||||
return $words
|
||||
}
|
||||
|
||||
|
||||
# Given a list of items this proc finds a (part, full) pair so that when
|
||||
# completing from $part $full will be the only option.
|
||||
#
|
||||
# Arguments:
|
||||
# list The list of full completions.
|
||||
# partName Output parameter for the partial string.
|
||||
# fullName Output parameter for the full string, member of item.
|
||||
#
|
||||
# Results:
|
||||
# 1, or 0 if no suitable result was found.
|
||||
proc find_unique_completion_pair {{list} {partName} {fullName}} {
|
||||
upvar $partName part
|
||||
upvar $fullName full
|
||||
set bestscore 0
|
||||
# Uniquify the list, that's what completion does too.
|
||||
set list [lsort -unique $list]
|
||||
set n [llength $list]
|
||||
for {set i 0} {$i < $n} {incr i} {
|
||||
set cur [lindex $list $i]
|
||||
set curlen [string length $cur]
|
||||
|
||||
set prev [lindex $list [expr {$i - 1}]]
|
||||
set next [lindex $list [expr {$i + 1}]]
|
||||
set diffprev [expr {$prev == ""}]
|
||||
set diffnext [expr {$next == ""}]
|
||||
|
||||
# Analyse each item of the list and look for the minimum length of the
|
||||
# partial prefix which is distinct from both $next and $prev. The list
|
||||
# is sorted so the prefix will be unique in the entire list.
|
||||
#
|
||||
# In the worst case we analyse every character in the list 3 times.
|
||||
# That's actually very fast, sorting could take more.
|
||||
for {set j 0} {$j < $curlen} {incr j} {
|
||||
set curchar [string index $cur $j]
|
||||
if {!$diffprev && [string index $prev $j] != $curchar} {
|
||||
set diffprev 1
|
||||
}
|
||||
if {!$diffnext && [string index $next $j] != $curchar} {
|
||||
set diffnext 1
|
||||
}
|
||||
if {$diffnext && $diffprev} {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# At the end of the loop $j is the index of last character of
|
||||
# the unique partial prefix. The length is one plus that.
|
||||
set parlen [expr {$j + 1}]
|
||||
if {$parlen >= $curlen} {
|
||||
continue
|
||||
}
|
||||
|
||||
# Try to find the most "readable pair"; look for a long pair where
|
||||
# $part is about half of $full.
|
||||
if {$parlen < $curlen / 2} {
|
||||
set parlen [expr {$curlen / 2}]
|
||||
}
|
||||
set score [expr {$curlen - $parlen}]
|
||||
if {$score > $bestscore} {
|
||||
set bestscore $score
|
||||
set part [string range $cur 0 [expr {$parlen - 1}]]
|
||||
set full $cur
|
||||
}
|
||||
}
|
||||
return [expr {$bestscore != 0}]
|
||||
}
|
||||
|
||||
|
||||
# Start bash running as test environment.
|
||||
proc start_bash {} {
|
||||
global TESTDIR TOOL_EXECUTABLE spawn_id env srcdirabs
|
||||
set TESTDIR [pwd]
|
||||
set srcdirabs [file normalize $::srcdir]; # Absolute srcdir
|
||||
# If `--tool_exec' option not specified, use "bash"
|
||||
if {! [info exists TOOL_EXECUTABLE]} {set TOOL_EXECUTABLE bash}
|
||||
set env(SRCDIR) $::srcdir
|
||||
set env(SRCDIRABS) $::srcdirabs
|
||||
|
||||
# PS1, INPUTRC, TERM and stty columns must be initialized
|
||||
# *before* starting bash to take proper effect.
|
||||
|
||||
# Set fixed prompt `/@'
|
||||
set env(PS1) "/@"
|
||||
# Configure readline
|
||||
set env(INPUTRC) "$::srcdir/config/inputrc"
|
||||
# Avoid escape junk at beginning of line from readline,
|
||||
# see e.g. http://bugs.gentoo.org/246091
|
||||
set env(TERM) "dumb"
|
||||
# Ensure enough columns so expect doesn't have to care about line breaks
|
||||
set stty_init "columns 150"
|
||||
|
||||
exp_spawn $TOOL_EXECUTABLE --norc
|
||||
assert_bash_exec {} "$TOOL_EXECUTABLE --norc"
|
||||
assert_bash_exec "source $::srcdir/config/bashrc"
|
||||
}
|
||||
|
||||
|
||||
# Redirect xtrace output to a file.
|
||||
#
|
||||
# 'set -x' can be very useful for debugging but by default it writes to
|
||||
# stderr.
|
||||
#
|
||||
# This function uses file descriptor 6. This will break if any completion
|
||||
# tries to use the same descriptor.
|
||||
proc init_bash_xtrace {{fname xtrace.log}} {
|
||||
verbose "Enabling bash xtrace output to '$fname'"
|
||||
assert_bash_exec "exec 6>'$fname'"
|
||||
assert_bash_exec "BASH_XTRACEFD=6"
|
||||
assert_bash_exec "set -o xtrace"
|
||||
}
|
||||
|
||||
|
||||
# Setup test environment
|
||||
#
|
||||
# Common initialization for unit and completion tests.
|
||||
proc start_interactive_test {} {
|
||||
start_bash
|
||||
#source_bash_completion
|
||||
init_tcl_bash_globals
|
||||
|
||||
global OPT_BASH_XTRACE
|
||||
if {[info exists OPT_BASH_XTRACE]} {
|
||||
init_bash_xtrace
|
||||
}
|
||||
global OPT_BUFFER_SIZE
|
||||
if {![info exists OPT_BUFFER_SIZE]} {
|
||||
set OPT_BUFFER_SIZE 20000
|
||||
}
|
||||
verbose "Changing default expect match buffer size to $OPT_BUFFER_SIZE"
|
||||
match_max $OPT_BUFFER_SIZE
|
||||
global OPT_TIMEOUT
|
||||
if {[info exists OPT_TIMEOUT]} {
|
||||
global timeout
|
||||
verbose "Changing default expect timeout from $timeout to $OPT_TIMEOUT"
|
||||
set timeout $OPT_TIMEOUT
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Interrupt completion and sync with prompt.
|
||||
# Send signals QUIT & INT.
|
||||
# @param string $prompt (optional) Bash prompt. Default is "/@"
|
||||
proc sync_after_int {{prompt /@}} {
|
||||
# verbose "Entered sync_after_int..."
|
||||
set test "Sync after INT"
|
||||
sleep .1
|
||||
# send \031\003; # QUIT/INT # OLD: this seemed to cause the issue mentioned in the below link where the first char of the next command is stripped off...
|
||||
send \x03; # ^C (SIGINT)
|
||||
# Wait to allow bash to become ready
|
||||
# See also: http://lists.alioth.debian.org/pipermail/bash-completion-devel/
|
||||
# 2010-February/002566.html
|
||||
sleep .1
|
||||
# NOTE: Regexp `.*' causes `expect' to discard previous unknown output.
|
||||
# This is necessary if a completion doesn't match expectations.
|
||||
# For instance with `filetype_xspec' completion (e.g. `kdvi') if
|
||||
# one expects `.txt' as a completion (wrong, because it isn't
|
||||
# there), the unmatched completions need to be cleaned up.
|
||||
expect -re ".*\\^C.*$prompt$"
|
||||
# verbose "Returning from sync_after_int"
|
||||
}
|
||||
|
||||
|
||||
proc sync_after_tab {} {
|
||||
# NOTE: Wait in case completion returns nothing - because `units' isn't
|
||||
# installed, so that "^$cdm.*$" doesn't match too early - before
|
||||
# comp_install has finished
|
||||
sleep .4
|
||||
}
|
||||
|
||||
|
||||
# Return current working directory with `TESTDIR' stripped
|
||||
# @return string Working directory. E.g. /, or /fixtures/
|
||||
proc wd {} {
|
||||
global TESTDIR
|
||||
# Remove `$TESTDIR' prefix from current working directory
|
||||
set wd [string replace [pwd] 0 [expr [string length $TESTDIR] - 1]]/
|
||||
}
|
||||
38
src/test/dejagnu.tests/lib/library.sh
Normal file
38
src/test/dejagnu.tests/lib/library.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
# Bash library for bash-completion DejaGnu testsuite
|
||||
|
||||
|
||||
# @param $1 Char to add to $COMP_WORDBREAKS
|
||||
add_comp_wordbreak_char() {
|
||||
[[ "${COMP_WORDBREAKS//[^$1]}" ]] || COMP_WORDBREAKS+=$1
|
||||
} # add_comp_wordbreak_char()
|
||||
|
||||
|
||||
# Diff environment files to detect if environment is unmodified
|
||||
# @param $1 File 1
|
||||
# @param $2 File 2
|
||||
# @param $3 Additional sed script
|
||||
diff_env() {
|
||||
diff "$1" "$2" | sed -e "
|
||||
# Remove diff line indicators
|
||||
/^[0-9,]\{1,\}[acd]/d
|
||||
# Remove diff block separators
|
||||
/---/d
|
||||
# Remove underscore variable
|
||||
/[<>] _=/d
|
||||
# Remove PPID bash variable
|
||||
/[<>] PPID=/d
|
||||
# Remove BASH_REMATCH bash variable
|
||||
/[<>] BASH_REMATCH=/d
|
||||
# Remove functions starting with underscore
|
||||
/[<>] declare -f _/d
|
||||
$3"
|
||||
} # diff_env()
|
||||
|
||||
|
||||
# Output array elements, sorted and separated by newline
|
||||
# Unset variable after outputting.
|
||||
# @param $1 Name of array variable to process
|
||||
echo_array() {
|
||||
local name=$1[@]
|
||||
printf "%s\n" "${!name}" | sort
|
||||
} # echo_array()
|
||||
25
src/test/dejagnu.tests/lib/unit.exp
Normal file
25
src/test/dejagnu.tests/lib/unit.exp
Normal file
@@ -0,0 +1,25 @@
|
||||
source $::srcdir/lib/library.exp
|
||||
|
||||
|
||||
proc unit_exit {} {
|
||||
# Exit bash
|
||||
send "\rexit\r"
|
||||
}
|
||||
|
||||
|
||||
proc unit_init {test_file_name} {
|
||||
# Call unit_start() only once
|
||||
if {! [info exists ::BASH_VERSINFO]} {
|
||||
unit_start
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
proc unit_start {} {
|
||||
start_interactive_test
|
||||
}
|
||||
|
||||
|
||||
proc unit_version {} {
|
||||
puts "$::TESTDIR, bash-$::BASH_VERSION"
|
||||
}
|
||||
67
src/test/dejagnu.tests/run
Normal file
67
src/test/dejagnu.tests/run
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
# Print some helpful messages.
|
||||
usage() {
|
||||
echo "Run bash-completion tests"
|
||||
echo
|
||||
echo "The 'tool' is determined automatically from filenames."
|
||||
echo "Unrecognized options are passed through to dejagnu by default."
|
||||
echo
|
||||
echo "Interesting options:"
|
||||
echo " --tool_exec= Test against a different bash executable."
|
||||
echo " --buffer-size Change expect match buffer size from our default of 20000 bytes."
|
||||
echo " --debug Create a dbg.log in the test directory with detailed expect match information."
|
||||
echo " --timeout Change expect timeout from the default of 10 seconds."
|
||||
echo " --debug-xtrace Create an xtrace.log in the test directory with set -x output."
|
||||
echo
|
||||
echo "Example run: ./run unit/_get_cword.exp unit/compgen.exp"
|
||||
}
|
||||
|
||||
|
||||
# Try to set the tool variable; or fail if trying to set different values.
|
||||
set_tool() {
|
||||
if [[ $tool ]]; then
|
||||
if [[ $tool != $1 ]]; then
|
||||
echo "Tool spec mismatch ('$tool' and '$1'). See --usage."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
tool=$1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
|
||||
# Loop over the arguments.
|
||||
args=()
|
||||
while [[ $# > 0 ]]; do
|
||||
case "$1" in
|
||||
--help|--usage) usage; exit 1;;
|
||||
--buffer-size) shift; buffer_size=$1;;
|
||||
--buffer-size=*) buffer_size=${1/--buffer-size=};;
|
||||
--debug-xtrace) args+=(OPT_BASH_XTRACE=1);;
|
||||
--timeout) shift; timeout=$1;;
|
||||
--timeout=*) timeout=${1/--timeout=};;
|
||||
--tool=*) set_tool "${1#/--tool=}";;
|
||||
--tool) shift; set_tool "$1";;
|
||||
completion/*.exp|*/completion/*.exp|unit/*.exp|*/unit/*.exp)
|
||||
arg=${1%/*}
|
||||
set_tool "${arg##*/}"
|
||||
args+=("${1##*/}")
|
||||
;;
|
||||
*) args+=("$1")
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[[ -n $buffer_size ]] && args+=("OPT_BUFFER_SIZE=$buffer_size")
|
||||
[[ -n $timeout ]] && args+=("OPT_TIMEOUT=$timeout")
|
||||
[[ -z $tool ]] && { echo "Must specify tool somehow"; exit 1; }
|
||||
|
||||
runtest --outdir log --tool $tool "${args[@]}"
|
||||
rc=$?
|
||||
[[ $rc -ne 0 && -n "$CI" ]] && cat log/$tool.log
|
||||
exit $rc
|
||||
8
src/test/dejagnu.tests/runCompletion
Normal file
8
src/test/dejagnu.tests/runCompletion
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NOTE: I tried setting up bash_completion_lib within ./lib files, but DejaGnu
|
||||
# isn't initialized at that point (i.e. output of `expect' is shown on
|
||||
# stdout - `open_logs' hasn't run yet?). And running code from a library
|
||||
# file isn't probably a good idea either.
|
||||
exec "${bashcomp_bash:-$BASH}" \
|
||||
"$(dirname "${BASH_SOURCE[0]}")/run" --tool completion $*
|
||||
Reference in New Issue
Block a user