summaryrefslogtreecommitdiffstats
path: root/test/t/unit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:03:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:03:19 +0000
commit6c09f2a45c5541e9c207d14fc7aa21a4a0066bde (patch)
tree0221189d367bf661f6f9493c4f17a03f0dd4b7d2 /test/t/unit
parentReleasing progress-linux version 1:2.11-8~progress7.99u1. (diff)
downloadbash-completion-6c09f2a45c5541e9c207d14fc7aa21a4a0066bde.tar.xz
bash-completion-6c09f2a45c5541e9c207d14fc7aa21a4a0066bde.zip
Merging upstream version 1:2.12.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/t/unit')
-rw-r--r--test/t/unit/Makefile.am27
-rw-r--r--test/t/unit/test_unit_abspath.py67
-rw-r--r--test/t/unit/test_unit_command_offset.py144
-rw-r--r--test/t/unit/test_unit_compgen.py173
-rw-r--r--test/t/unit/test_unit_compgen_commands.py47
-rw-r--r--test/t/unit/test_unit_compgen_split.py102
-rw-r--r--test/t/unit/test_unit_count_args.py134
-rw-r--r--test/t/unit/test_unit_delimited.py42
-rw-r--r--test/t/unit/test_unit_deprecate_func.py15
-rw-r--r--test/t/unit/test_unit_dequote.py161
-rw-r--r--test/t/unit/test_unit_expand.py21
-rw-r--r--test/t/unit/test_unit_expand_glob.py83
-rw-r--r--test/t/unit/test_unit_expand_tilde.py (renamed from test/t/unit/test_unit_expand_tilde_by_ref.py)31
-rw-r--r--test/t/unit/test_unit_filedir.py10
-rw-r--r--test/t/unit/test_unit_get_cword.py20
-rw-r--r--test/t/unit/test_unit_get_first_arg.py90
-rw-r--r--test/t/unit/test_unit_get_words.py (renamed from test/t/unit/test_unit_get_comp_words_by_ref.py)30
-rw-r--r--test/t/unit/test_unit_init_completion.py34
-rw-r--r--test/t/unit/test_unit_initialize.py66
-rw-r--r--test/t/unit/test_unit_ip_addresses.py20
-rw-r--r--test/t/unit/test_unit_known_hosts.py (renamed from test/t/unit/test_unit_known_hosts_real.py)109
-rw-r--r--test/t/unit/test_unit_load_completion.py94
-rw-r--r--test/t/unit/test_unit_longopt.py4
-rw-r--r--test/t/unit/test_unit_looks_like_path.py33
-rw-r--r--test/t/unit/test_unit_parse_help.py63
-rw-r--r--test/t/unit/test_unit_parse_usage.py46
-rw-r--r--test/t/unit/test_unit_pgids.py34
-rw-r--r--test/t/unit/test_unit_pids.py34
-rw-r--r--test/t/unit/test_unit_pnames.py29
-rw-r--r--test/t/unit/test_unit_quote.py19
-rw-r--r--test/t/unit/test_unit_quote_compgen.py173
-rw-r--r--test/t/unit/test_unit_quote_readline.py15
-rw-r--r--test/t/unit/test_unit_realcommand.py90
-rw-r--r--test/t/unit/test_unit_split.py90
-rw-r--r--test/t/unit/test_unit_tilde.py9
-rw-r--r--test/t/unit/test_unit_unlocal.py18
-rw-r--r--test/t/unit/test_unit_variables.py6
-rw-r--r--test/t/unit/test_unit_xfunc.py71
-rw-r--r--test/t/unit/test_unit_xinetd_services.py10
39 files changed, 2063 insertions, 201 deletions
diff --git a/test/t/unit/Makefile.am b/test/t/unit/Makefile.am
index 3eb652a..54722de 100644
--- a/test/t/unit/Makefile.am
+++ b/test/t/unit/Makefile.am
@@ -1,21 +1,38 @@
EXTRA_DIST = \
+ test_unit_abspath.py \
+ test_unit_command_offset.py \
+ test_unit_compgen.py \
+ test_unit_compgen_commands.py \
test_unit_count_args.py \
+ test_unit_delimited.py \
+ test_unit_deprecate_func.py \
+ test_unit_dequote.py \
test_unit_expand.py \
- test_unit_expand_tilde_by_ref.py \
+ test_unit_expand_glob.py \
+ test_unit_expand_tilde.py \
test_unit_filedir.py \
test_unit_find_unique_completion_pair.py \
- test_unit_get_comp_words_by_ref.py \
+ test_unit_get_first_arg.py \
test_unit_get_cword.py \
- test_unit_init_completion.py \
+ test_unit_get_words.py \
+ test_unit_initialize.py \
test_unit_ip_addresses.py \
- test_unit_known_hosts_real.py \
+ test_unit_known_hosts.py \
test_unit_longopt.py \
+ test_unit_looks_like_path.py \
test_unit_parse_help.py \
test_unit_parse_usage.py \
+ test_unit_pgids.py \
+ test_unit_pids.py \
+ test_unit_pnames.py \
test_unit_quote.py \
- test_unit_quote_readline.py \
+ test_unit_quote_compgen.py \
+ test_unit_realcommand.py \
+ test_unit_split.py \
test_unit_tilde.py \
+ test_unit_unlocal.py \
test_unit_variables.py \
+ test_unit_xfunc.py \
test_unit_xinetd_services.py
all:
diff --git a/test/t/unit/test_unit_abspath.py b/test/t/unit/test_unit_abspath.py
new file mode 100644
index 0000000..97d506c
--- /dev/null
+++ b/test/t/unit/test_unit_abspath.py
@@ -0,0 +1,67 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(
+ cmd=None, cwd="shared", ignore_env=r"^\+declare -f __tester$"
+)
+class TestUnitAbsPath:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ (
+ "__tester() { "
+ "local REPLY; "
+ '_comp_abspath "$1"; '
+ 'printf %s "$REPLY"; '
+ "}"
+ ),
+ )
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local REPLY=; _comp_abspath bar; }; foo; unset -f foo",
+ want_output=None,
+ )
+
+ def test_absolute(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester /foo/bar",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip() == "/foo/bar"
+
+ def test_relative(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester foo/bar",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/foo/bar")
+
+ def test_cwd(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester ./foo/./bar",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/foo/bar")
+
+ def test_parent(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester ../shared/foo/bar",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith(
+ "/shared/foo/bar"
+ ) and not output.strip().endswith("../shared/foo/bar")
diff --git a/test/t/unit/test_unit_command_offset.py b/test/t/unit/test_unit_command_offset.py
new file mode 100644
index 0000000..0e32c1f
--- /dev/null
+++ b/test/t/unit/test_unit_command_offset.py
@@ -0,0 +1,144 @@
+from shlex import quote
+
+import pytest
+
+from conftest import assert_bash_exec, assert_complete, bash_env_saved
+
+
+def join(words):
+ """Return a shell-escaped string from *words*."""
+ return " ".join(quote(word) for word in words)
+
+
+@pytest.mark.bashcomp(
+ cmd=None,
+ cwd="_command_offset",
+ ignore_env=r"^[+-](COMPREPLY|REPLY)=",
+)
+class TestUnitCommandOffset:
+ wordlist = sorted(["foo", "bar"])
+
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "_cmd1() { _comp_command_offset 1; }; complete -F _cmd1 cmd1; "
+ "complete -F _comp_command meta; "
+ "_compfunc() { COMPREPLY=(%s); }" % join(self.wordlist),
+ )
+
+ completions = [
+ 'complete -F _compfunc "${COMP_WORDS[0]}"',
+ 'complete -W %s "${COMP_WORDS[0]}"' % quote(join(self.wordlist)),
+ 'COMPREPLY=(dummy); complete -r "${COMP_WORDS[0]}"',
+ "COMPREPLY+=(${#COMPREPLY[@]})",
+ ]
+ for idx, comp in enumerate(completions, 2):
+ assert_bash_exec(
+ bash,
+ "_cmd%(idx)s() { %(comp)s && return 124; }; "
+ "complete -F _cmd%(idx)s cmd%(idx)s"
+ % {"idx": idx, "comp": comp},
+ )
+
+ assert_bash_exec(
+ bash, "complete -W %s 'cmd!'" % quote(join(self.wordlist))
+ )
+ assert_bash_exec(bash, 'complete -W \'"$word1" "$word2"\' cmd6')
+
+ assert_bash_exec(bash, "complete -C ./completer cmd7")
+
+ def test_1(self, bash, functions):
+ assert_complete(bash, 'cmd1 "/tmp/aaa bbb" ')
+ assert_bash_exec(bash, "! complete -p aaa", want_output=None)
+
+ @pytest.mark.parametrize(
+ "cmd,expected_completion",
+ [
+ ("cmd2", wordlist),
+ ("cmd3", wordlist),
+ ("cmd4", []),
+ ("cmd5", ["0"]),
+ ],
+ )
+ def test_2(self, bash, functions, cmd, expected_completion):
+ """Test meta-completion for completion functions that signal that
+ completion should be retried (i.e. change compspec and return 124).
+
+ cmd2: The case when the completion spec is overwritten by the one that
+ contains "-F func"
+
+ cmd3: The case when the completion spec is overwritten by the one
+ without "-F func".
+
+ cmd4: The case when the completion spec is removed, in which we expect
+ no completions. This mimics the behavior of Bash's progcomp for the
+ exit status 124.
+
+ cmd5: The case when the completion spec is unchanged. The retry should
+ be attempted at most once to avoid infinite loops. COMPREPLY should be
+ cleared before the retry.
+ """
+ assert assert_complete(bash, "meta %s " % cmd) == expected_completion
+
+ @pytest.mark.parametrize(
+ "cmd,expected_completion",
+ [
+ ("cmd7 ", wordlist),
+ ("cmd7 l", ["line\\^Jtwo", "long"]),
+ ("cmd7 lo", ["ng"]),
+ ("cmd7 line", ["\\^Jtwo"]),
+ ("cmd7 cont1", ["cont10", "cont11\\"]),
+ ],
+ )
+ def test_3(self, bash, functions, cmd, expected_completion):
+ got = assert_complete(bash, f"cmd1 {cmd}")
+ assert got == assert_complete(bash, cmd)
+ assert got == expected_completion
+
+ def test_cmd_quoted(self, bash, functions):
+ assert assert_complete(bash, "meta 'cmd2' ") == self.wordlist
+
+ def test_cmd_specialchar(self, bash, functions):
+ assert assert_complete(bash, "meta 'cmd!' ") == self.wordlist
+
+ def test_space(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("word1", "a b c")
+ bash_env.write_variable("word2", "d e f")
+ assert assert_complete(bash, "meta cmd6 ") == ["a b c", "d e f"]
+
+ @pytest.fixture(scope="class")
+ def find_original_word_functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "_comp_test_reassemble() {"
+ " local IFS=$' \\t\\n' REPLY;"
+ ' COMP_LINE=$1; _comp_split COMP_WORDS "$2"; COMP_CWORD=$((${#COMP_WORDS[@]}-1));'
+ " _comp__reassemble_words = words cword;"
+ "}",
+ )
+ assert_bash_exec(
+ bash,
+ "_comp_test_1() {"
+ ' local COMP_WORDS COMP_LINE COMP_CWORD words cword REPLY; _comp_test_reassemble "$1" "$2";'
+ ' _comp__find_original_word "$3";'
+ ' echo "$REPLY";'
+ "}",
+ )
+
+ def test_find_original_word_1(self, bash, find_original_word_functions):
+ result = assert_bash_exec(
+ bash,
+ '_comp_test_1 "sudo su do su do abc" "sudo su do su do abc" 3',
+ want_output=True,
+ ).strip()
+ assert result == "3"
+
+ def test_find_original_word_2(self, bash, find_original_word_functions):
+ result = assert_bash_exec(
+ bash,
+ '_comp_test_1 "sudo --prefix=su su do abc" "sudo --prefix = su su do abc" 2',
+ want_output=True,
+ ).strip()
+ assert result == "4"
diff --git a/test/t/unit/test_unit_compgen.py b/test/t/unit/test_unit_compgen.py
new file mode 100644
index 0000000..cfdec8e
--- /dev/null
+++ b/test/t/unit/test_unit_compgen.py
@@ -0,0 +1,173 @@
+import pytest
+import re
+
+from conftest import assert_bash_exec, bash_env_saved, assert_complete
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUtilCompgen:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "_comp__test_dump() { ((${#arr[@]})) && printf '<%s>' \"${arr[@]}\"; echo; }",
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_compgen() { local -a arr=(00); _comp_compgen -v arr "$@"; _comp__test_dump; }',
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_words() { local -a input=("${@:1:$#-1}"); _comp__test_compgen -c "${@:$#}" -- -W \'${input[@]+"${input[@]}"}\'; }',
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_words_ifs() { local input=$2; _comp__test_compgen -F "$1" -c "${@:$#}" -- -W \'$input\'; }',
+ )
+
+ assert_bash_exec(
+ bash,
+ '_comp_cmd_fc() { _comp_compgen -c "$(_get_cword)" -C _filedir filedir; }; '
+ "complete -F _comp_cmd_fc fc; "
+ "complete -F _comp_cmd_fc -o filenames fc2",
+ )
+ assert_bash_exec(
+ bash,
+ '_comp_cmd_fcd() { _comp_compgen -c "$(_get_cword)" -C _filedir filedir -d; }; '
+ "complete -F _comp_cmd_fcd fcd",
+ )
+
+ # test_8_option_U
+ assert_bash_exec(
+ bash,
+ "_comp_compgen_gen8() { local -a arr=(x y z); _comp_compgen -U arr -- -W '\"${arr[@]}\"'; }",
+ )
+
+ # test_9_inherit_a
+ assert_bash_exec(
+ bash,
+ '_comp_compgen_gen9sub() { local -a gen=(00); _comp_compgen -v gen -- -W 11; _comp_compgen_set "${gen[@]}"; }; '
+ "_comp_compgen_gen9() { _comp_compgen_gen9sub; _comp_compgen -a gen9sub; }",
+ )
+
+ def test_1_basic(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp__test_words 12 34 56 ''", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_2_space(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words $'a b' $'c d\\t' ' e ' $'\\tf\\t' ''",
+ want_output=True,
+ )
+ assert output.strip() == "<a b><c d\t>< e ><\tf\t>"
+
+ def test_2_IFS(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("IFS", "34")
+ output = assert_bash_exec(
+ bash, "_comp__test_words 12 34 56 ''", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_3_glob(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words '*' '[a-z]*' '[a][b][c]' ''",
+ want_output=True,
+ )
+ assert output.strip() == "<*><[a-z]*><[a][b][c]>"
+
+ def test_3_failglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words '*' '[a-z]*' '[a][b][c]' ''",
+ want_output=True,
+ )
+ assert output.strip() == "<*><[a-z]*><[a][b][c]>"
+
+ def test_3_nullglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words '*' '[a-z]*' '[a][b][c]' ''",
+ want_output=True,
+ )
+ assert output.strip() == "<*><[a-z]*><[a][b][c]>"
+
+ def test_4_empty(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp__test_words ''", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_5_option_F(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words_ifs '25' ' 123 456 555 ' ''",
+ want_output=True,
+ )
+ assert output.strip() == "< 1><3 4><6 >< >"
+
+ def test_6_option_C_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_compgen -c a -C _filedir filedir",
+ want_output=True,
+ )
+ set1 = set(re.findall(r"<[^<>]*>", output.strip()))
+ assert set1 == {"<a b>", "<a$b>", "<a&b>", "<a'b>", "<ab>", "<aƩ>"}
+
+ def test_6_option_C_2(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_compgen -c b -C _filedir -- -d",
+ want_output=True,
+ )
+ assert output.strip() == "<brackets>"
+
+ @pytest.mark.parametrize("funcname", "fc fc2".split())
+ def test_6_option_C_3(self, bash, functions, funcname):
+ completion = assert_complete(bash, "%s _filedir ab/" % funcname)
+ assert completion == "e"
+
+ @pytest.mark.complete(r"fcd a\ ")
+ def test_6_option_C_4(self, functions, completion):
+ # Note: we are not in the original directory that "b" exists, so Bash
+ # will not suffix a slash to the directory name.
+ assert completion == "b"
+
+ def test_7_icmd(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR", "$PWD/_comp_compgen", quote=False
+ )
+
+ completions = assert_complete(bash, "compgen-cmd1 '")
+ assert completions == ["012", "123", "234", "5abc", "6def", "7ghi"]
+
+ def test_7_xcmd(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR", "$PWD/_comp_compgen", quote=False
+ )
+
+ completions = assert_complete(bash, "compgen-cmd2 '")
+ assert completions == ["012", "123", "234", "5foo", "6bar", "7baz"]
+
+ def test_8_option_U(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp__test_compgen gen8", want_output=True
+ )
+ assert output.strip() == "<x><y><z>"
+
+ def test_9_inherit_a(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp__test_compgen gen9", want_output=True
+ )
+ assert output.strip() == "<11><11>"
diff --git a/test/t/unit/test_unit_compgen_commands.py b/test/t/unit/test_unit_compgen_commands.py
new file mode 100644
index 0000000..d866239
--- /dev/null
+++ b/test/t/unit/test_unit_compgen_commands.py
@@ -0,0 +1,47 @@
+import pytest
+
+from conftest import assert_bash_exec, assert_complete, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
+class TestUtilCompgenCommands:
+ @pytest.fixture(scope="class")
+ def functions(self, request, bash):
+ assert_bash_exec(
+ bash,
+ r"_comp_compgen_commands__test() {"
+ r" local COMPREPLY=() cur=${1-};"
+ r" _comp_compgen_commands;"
+ r' printf "%s\n" "${COMPREPLY[@]-}";'
+ r"}",
+ )
+ assert_bash_exec(
+ bash,
+ "_comp_cmd_ccc() {"
+ " local cur;"
+ " _comp_get_words cur;"
+ " unset -v COMPREPLY;"
+ " _comp_compgen_commands;"
+ "}; complete -F _comp_cmd_ccc ccc",
+ )
+
+ def test_basic(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp_compgen_commands__test sh", want_output=True
+ )
+ assert output.strip()
+
+ @pytest.mark.parametrize(
+ "shopt_no_empty,result_empty", ((True, True), (False, False))
+ )
+ def test_empty(self, bash, functions, shopt_no_empty, result_empty):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("no_empty_cmd_completion", shopt_no_empty)
+ output = assert_bash_exec(
+ bash, "_comp_compgen_commands__test", want_output=True
+ )
+ assert (output.strip() == "") == result_empty
+
+ def test_spaces(self, bash, functions):
+ completion = assert_complete(bash, "ccc shared/default/bar")
+ assert completion == r"\ bar.d/"
diff --git a/test/t/unit/test_unit_compgen_split.py b/test/t/unit/test_unit_compgen_split.py
new file mode 100644
index 0000000..935ea2a
--- /dev/null
+++ b/test/t/unit/test_unit_compgen_split.py
@@ -0,0 +1,102 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUtilCompgenSplit:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "_comp__test_dump() { ((${#arr[@]})) && printf '<%s>' \"${arr[@]}\"; echo; }",
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_compgen() { local -a arr=(00); _comp_compgen -v arr "$@"; _comp__test_dump; }',
+ )
+
+ assert_bash_exec(
+ bash,
+ "_comp__test_cmd1() { echo foo bar; echo baz; }",
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_attack() { echo "\\$(echo should_not_run >&2)"; }',
+ )
+
+ def test_1_basic(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<foo><bar><baz>"
+
+ def test_2_attack(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -- "$(_comp__test_attack)"',
+ want_output=True,
+ )
+ assert output.strip() == "<$(echo><should_not_run><>&2)>"
+
+ def test_3_sep1(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -l -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<foo bar><baz>"
+
+ def test_3_sep2(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_compgen split -F $'b\\n' -- \"$(_comp__test_cmd1)\"",
+ want_output=True,
+ )
+ assert output.strip() == "<foo ><ar><az>"
+
+ def test_4_optionX(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -X bar -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<foo><baz>"
+
+ def test_4_optionS(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -S .txt -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<foo.txt><bar.txt><baz.txt>"
+
+ def test_4_optionP(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -P /tmp/ -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "</tmp/foo></tmp/bar></tmp/baz>"
+
+ def test_4_optionPS(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -P [ -S ] -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<[foo]><[bar]><[baz]>"
+
+ def test_5_empty(self, bash, functions):
+ output = assert_bash_exec(
+ bash, '_comp__test_compgen split -- ""', want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_5_empty2(self, bash, functions):
+ output = assert_bash_exec(
+ bash, '_comp__test_compgen split -- " "', want_output=True
+ )
+ assert output.strip() == ""
diff --git a/test/t/unit/test_unit_count_args.py b/test/t/unit/test_unit_count_args.py
index 56bce2c..7b018e4 100644
--- a/test/t/unit/test_unit_count_args.py
+++ b/test/t/unit/test_unit_count_args.py
@@ -4,63 +4,167 @@ from conftest import TestUnitBase, assert_bash_exec
@pytest.mark.bashcomp(
- cmd=None, ignore_env=r"^[+-](args|COMP_(WORDS|CWORD|LINE|POINT))="
+ cmd=None,
+ ignore_env=r"^[+-](REPLY|cword|words|COMP_(WORDS|CWORD|LINE|POINT))=",
)
class TestUnitCountArgs(TestUnitBase):
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ '_comp__test_unit() { local -a words=(); local cword REPLY=""; _comp__reassemble_words "<>&" words cword; _comp_count_args "$@"; echo "$REPLY"; }',
+ )
+
def _test(self, *args, **kwargs):
- return self._test_unit("_count_args %s; echo $args", *args, **kwargs)
+ return self._test_unit("_comp__test_unit %s", *args, **kwargs)
def test_1(self, bash):
- assert_bash_exec(bash, "COMP_CWORD= _count_args >/dev/null")
+ assert_bash_exec(
+ bash,
+ 'COMP_LINE= COMP_POINT=0 COMP_WORDS=() COMP_CWORD=; _comp_count_args -n ""',
+ )
- def test_2(self, bash):
+ def test_2(self, bash, functions):
"""a b| should set args to 1"""
output = self._test(bash, "(a b)", 1, "a b", 3)
assert output == "1"
- def test_3(self, bash):
+ def test_3(self, bash, functions):
"""a b|c should set args to 1"""
output = self._test(bash, "(a bc)", 1, "a bc", 3)
assert output == "1"
- def test_4(self, bash):
+ def test_4(self, bash, functions):
"""a b c| should set args to 2"""
output = self._test(bash, "(a b c)", 2, "a b c", 4)
assert output == "2"
- def test_5(self, bash):
+ def test_5(self, bash, functions):
"""a b| c should set args to 1"""
output = self._test(bash, "(a b c)", 1, "a b c", 3)
assert output == "1"
- def test_6(self, bash):
+ def test_6(self, bash, functions):
"""a b -c| d should set args to 2"""
output = self._test(bash, "(a b -c d)", 2, "a b -c d", 6)
assert output == "2"
- def test_7(self, bash):
+ def test_7(self, bash, functions):
"""a b -c d e| with -c arg excluded should set args to 2"""
output = self._test(
- bash, "(a b -c d e)", 4, "a b -c d e", 10, arg='"" "@(-c|--foo)"'
+ bash, "(a b -c d e)", 4, "a b -c d e", 10, arg='-a "@(-c|--foo)"'
)
assert output == "2"
- def test_8(self, bash):
+ def test_8(self, bash, functions):
"""a -b -c d e| with -c arg excluded
- and -b included should set args to 1"""
+ and -b included should set args to 1"""
output = self._test(
bash,
"(a -b -c d e)",
4,
"a -b -c d e",
11,
- arg='"" "@(-c|--foo)" "-[b]"',
+ arg='-a "@(-c|--foo)" -i "-[b]"',
)
assert output == "2"
- def test_9(self, bash):
+ def test_9(self, bash, functions):
"""a -b -c d e| with -b included should set args to 3"""
output = self._test(
- bash, "(a -b -c d e)", 4, "a -b -c d e", 11, arg='"" "" "-b"'
+ bash, "(a -b -c d e)", 4, "a -b -c d e", 11, arg='-i "-b"'
+ )
+ assert output == "3"
+
+ def test_10_single_hyphen_1(self, bash):
+ """- should be counted as an argument representing stdout/stdin"""
+ output = self._test(bash, "(a -b - c -d e)", 5, "a -b - c -d e", 12)
+ assert output == "3"
+
+ def test_10_single_hyphen_2(self, bash):
+ """- in an option argument should be skipped"""
+ output = self._test(
+ bash, "(a -b - c - e)", 5, "a -b - c - e", 11, arg='-a "-b"'
+ )
+ assert output == "3"
+
+ def test_11_double_hyphen_1(self, bash):
+ """all the words after -- should be counted"""
+ output = self._test(
+ bash, "(a -b -- -c -d e)", 5, "a -b -- -c -d e", 14
)
assert output == "3"
+
+ def test_11_double_hyphen_2(self, bash):
+ """all the words after -- should be counted"""
+ output = self._test(bash, "(a b -- -c -d e)", 5, "a b -- -c -d e", 13)
+ assert output == "4"
+
+ def test_12_exclude_optarg_1(self, bash):
+ """an option argument should be skipped even if it matches the argument pattern"""
+ output = self._test(
+ bash, "(a -o -x b c)", 4, "a -o -x b c", 10, arg='-a "-o" -i "-x"'
+ )
+ assert output == "2"
+
+ def test_12_exclude_optarg_2(self, bash):
+ """an option argument should be skipped even if it matches the argument pattern"""
+ output = self._test(
+ bash,
+ "(a -o -x -x c)",
+ 4,
+ "a -o -x -x c",
+ 11,
+ arg='-a "-o" -i "-x"',
+ )
+ assert output == "2"
+
+ def test_12_exclude_optarg_3(self, bash):
+ """an option argument should be skipped even if it matches the argument pattern"""
+ output = self._test(
+ bash,
+ "(a -o -x -y c)",
+ 4,
+ "a -o -x -y c",
+ 11,
+ arg='-a "-o" -i "-x"',
+ )
+ assert output == "1"
+
+ def test_13_plus_option_optarg(self, bash):
+ """When +o is specified to be an option taking an option argument, it should not be counted as an argument"""
+ output = self._test(
+ bash, "(a +o b c)", 3, "a +o b c", 7, arg='-a "+o"'
+ )
+ assert output == "1"
+
+ def test_14_no_optarg_chain_1(self, bash):
+ """an option argument should not take another option argument"""
+ output = self._test(
+ bash, "(a -o -o -o -o c)", 5, "a -o -o -o -o c", 14, arg='-a "-o"'
+ )
+ assert output == "1"
+
+ def test_14_no_optarg_chain_2(self, bash):
+ """an option argument should not take another option argument"""
+ output = self._test(
+ bash,
+ "(a -o -o b -o -o c)",
+ 6,
+ "a -o -o b -o -o c",
+ 16,
+ arg='-a "-o"',
+ )
+ assert output == "2"
+
+ def test_15_double_hyphen_optarg(self, bash):
+ """-- should lose its meaning when it is an option argument"""
+ output = self._test(
+ bash, "(a -o -- -b -c d)", 5, "a -o -- -b -c d", 14, arg='-a "-o"'
+ )
+ assert output == "1"
+
+ def test_16_empty_word(self, bash):
+ """An empty word should not take an option argument"""
+ output = self._test(bash, "(a '' x '' y d)", 5, "a x y d", 8)
+ assert output == "5"
diff --git a/test/t/unit/test_unit_delimited.py b/test/t/unit/test_unit_delimited.py
new file mode 100644
index 0000000..e20dcd1
--- /dev/null
+++ b/test/t/unit/test_unit_delimited.py
@@ -0,0 +1,42 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitDelimited:
+ @pytest.fixture(scope="class")
+ def functions(self, request, bash):
+ assert_bash_exec(
+ bash,
+ "_comp_cmd_test_delim() {"
+ " local cur prev words cword comp_args;"
+ " _comp_get_words cur;"
+ " _comp_delimited , -W 'alpha beta bravo';"
+ "};"
+ "complete -F _comp_cmd_test_delim test_delim",
+ )
+
+ @pytest.mark.complete("test_delim --opt=a")
+ def test_1(self, functions, completion):
+ assert completion == ["lpha"]
+
+ @pytest.mark.complete("test_delim --opt=b")
+ def test_2(self, functions, completion):
+ assert completion == ["beta", "bravo"]
+
+ @pytest.mark.complete("test_delim --opt=alpha,b")
+ def test_3(self, functions, completion):
+ assert completion == ["alpha,beta", "alpha,bravo"]
+
+ @pytest.mark.complete("test_delim --opt=alpha,be")
+ def test_4(self, functions, completion):
+ assert completion == ["ta"]
+
+ @pytest.mark.complete("test_delim --opt=beta,a")
+ def test_5(self, functions, completion):
+ assert completion == ["lpha"]
+
+ @pytest.mark.complete("test_delim --opt=c")
+ def test_6(self, functions, completion):
+ assert not completion
diff --git a/test/t/unit/test_unit_deprecate_func.py b/test/t/unit/test_unit_deprecate_func.py
new file mode 100644
index 0000000..c825e8c
--- /dev/null
+++ b/test/t/unit/test_unit_deprecate_func.py
@@ -0,0 +1,15 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+declare -f func[12]$")
+class TestUnitDeprecateFunc:
+ def test_1(self, bash):
+ assert_bash_exec(
+ bash,
+ 'func1() { echo "func1($*)"; }; '
+ "_comp_deprecate_func 2.12 func2 func1",
+ )
+ output = assert_bash_exec(bash, "func2 1 2 3", want_output=True)
+ assert output.strip() == "func1(1 2 3)"
diff --git a/test/t/unit/test_unit_dequote.py b/test/t/unit/test_unit_dequote.py
new file mode 100644
index 0000000..0ac814d
--- /dev/null
+++ b/test/t/unit/test_unit_dequote.py
@@ -0,0 +1,161 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(
+ cmd=None,
+ cwd="_filedir",
+ ignore_env=r"^\+declare -f __tester$",
+)
+class TestDequote:
+ def test_1_char(self, bash):
+ assert_bash_exec(
+ bash,
+ '__tester() { local REPLY=dummy v=var;_comp_dequote "$1";local ext=$?;((${#REPLY[@]}))&&printf \'<%s>\' "${REPLY[@]}";echo;return $ext;}',
+ )
+ output = assert_bash_exec(bash, "__tester a", want_output=True)
+ assert output.strip() == "<a>"
+
+ def test_2_str(self, bash):
+ output = assert_bash_exec(bash, "__tester abc", want_output=True)
+ assert output.strip() == "<abc>"
+
+ def test_3_null(self, bash):
+ output = assert_bash_exec(bash, "__tester ''", want_output=True)
+ assert output.strip() == ""
+
+ def test_4_empty(self, bash):
+ output = assert_bash_exec(bash, "__tester \"''\"", want_output=True)
+ assert output.strip() == "<>"
+
+ def test_5_brace(self, bash):
+ output = assert_bash_exec(bash, "__tester 'a{1..3}'", want_output=True)
+ assert output.strip() == "<a1><a2><a3>"
+
+ def test_6_glob(self, bash):
+ output = assert_bash_exec(bash, "__tester 'a?b'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b>"
+
+ def test_7_quote_1(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester '\"a\"'\\'b\\'\\$\\'c\\'", want_output=True
+ )
+ assert output.strip() == "<abc>"
+
+ def test_7_quote_2(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester '\\\"\\'\\''\\$\\`'", want_output=True
+ )
+ assert output.strip() == "<\"'$`>"
+
+ def test_7_quote_3(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester \\$\\'a\\\\tb\\'", want_output=True
+ )
+ assert output.strip() == "<a\tb>"
+
+ def test_7_quote_4(self, bash):
+ output = assert_bash_exec(
+ bash, '__tester \'"abc\\"def"\'', want_output=True
+ )
+ assert output.strip() == '<abc"def>'
+
+ def test_7_quote_5(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester \\'abc\\'\\\\\\'\\'def\\'", want_output=True
+ )
+ assert output.strip() == "<abc'def>"
+
+ def test_8_param_1(self, bash):
+ output = assert_bash_exec(bash, "__tester '$v'", want_output=True)
+ assert output.strip() == "<var>"
+
+ def test_8_param_2(self, bash):
+ output = assert_bash_exec(bash, "__tester '${v}'", want_output=True)
+ assert output.strip() == "<var>"
+
+ def test_8_param_3(self, bash):
+ output = assert_bash_exec(bash, "__tester '${#v}'", want_output=True)
+ assert output.strip() == "<3>"
+
+ def test_8_param_4(self, bash):
+ output = assert_bash_exec(bash, "__tester '${v[0]}'", want_output=True)
+ assert output.strip() == "<var>"
+
+ def test_9_qparam_1(self, bash):
+ output = assert_bash_exec(bash, "__tester '\"$v\"'", want_output=True)
+ assert output.strip() == "<var>"
+
+ def test_9_qparam_2(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester '\"${v[@]}\"'", want_output=True
+ )
+ assert output.strip() == "<var>"
+
+ def test_10_pparam_1(self, bash):
+ output = assert_bash_exec(bash, "__tester '$?'", want_output=True)
+ assert output.strip() == "<0>"
+
+ def test_10_pparam_2(self, bash):
+ output = assert_bash_exec(bash, "__tester '${#1}'", want_output=True)
+ assert output.strip() == "<5>" # The string `${#1}` is five characters
+
+ def test_unsafe_1(self, bash):
+ output = assert_bash_exec(
+ bash, "! __tester '$(echo hello >&2)'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_unsafe_2(self, bash):
+ output = assert_bash_exec(
+ bash, "! __tester '|echo hello >&2'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_unsafe_3(self, bash):
+ output = assert_bash_exec(
+ bash, "! __tester '>| important_file.txt'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_unsafe_4(self, bash):
+ output = assert_bash_exec(
+ bash, "! __tester '`echo hello >&2`'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_glob_default(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", False)
+ bash_env.shopt("nullglob", False)
+ output = assert_bash_exec(
+ bash, "__tester 'non-existent-*.txt'", want_output=True
+ )
+ assert output.strip() == "<non-existent-*.txt>"
+
+ def test_glob_noglob(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.set("noglob", True)
+ output = assert_bash_exec(
+ bash,
+ "__tester 'non-existent-*.txt'",
+ want_output=True,
+ )
+ assert output.strip() == "<non-existent-*.txt>"
+
+ def test_glob_failglob(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "! __tester 'non-existent-*.txt'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_glob_nullglob(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash, "__tester 'non-existent-*.txt'", want_output=True
+ )
+ assert output.strip() == ""
diff --git a/test/t/unit/test_unit_expand.py b/test/t/unit/test_unit_expand.py
index d2a3ebc..0be6c52 100644
--- a/test/t/unit/test_unit_expand.py
+++ b/test/t/unit/test_unit_expand.py
@@ -1,31 +1,42 @@
import pytest
-from conftest import assert_bash_exec
+from conftest import assert_bash_exec, bash_env_saved
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^[+-](cur|COMPREPLY)=")
class TestUnitExpand:
def test_1(self, bash):
- assert_bash_exec(bash, "_expand >/dev/null")
+ assert_bash_exec(bash, "_comp_expand >/dev/null")
def test_2(self, bash):
"""Test environment non-pollution, detected at teardown."""
- assert_bash_exec(bash, "foo() { _expand; }; foo; unset foo")
+ assert_bash_exec(bash, "foo() { _comp_expand; }; foo; unset -f foo")
def test_user_home_compreply(self, bash, user_home):
user, home = user_home
output = assert_bash_exec(
bash,
- r'cur="~%s"; _expand; printf "%%s\n" "$COMPREPLY"' % user,
+ r'cur="~%s"; _comp_expand; printf "%%s\n" "$COMPREPLY"' % user,
want_output=True,
)
assert output.strip() == home
+ def test_user_home_compreply_failglob(self, bash, user_home):
+ user, home = user_home
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash,
+ r'cur="~%s"; _comp_expand; printf "%%s\n" "$COMPREPLY"' % user,
+ want_output=True,
+ )
+ assert output.strip() == home
+
def test_user_home_cur(self, bash, user_home):
user, home = user_home
output = assert_bash_exec(
bash,
- r'cur="~%s/a"; _expand; printf "%%s\n" "$cur"' % user,
+ r'cur="~%s/a"; _comp_expand; printf "%%s\n" "$cur"' % user,
want_output=True,
)
assert output.strip() == "%s/a" % home
diff --git a/test/t/unit/test_unit_expand_glob.py b/test/t/unit/test_unit_expand_glob.py
new file mode 100644
index 0000000..64d04a7
--- /dev/null
+++ b/test/t/unit/test_unit_expand_glob.py
@@ -0,0 +1,83 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(
+ cmd=None,
+ cwd="_filedir",
+ ignore_env=r"^\+declare -f (dump_array|__tester)$",
+)
+class TestExpandGlob:
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "dump_array() { ((${#arr[@]})) && printf '<%s>' \"${arr[@]}\"; echo; }",
+ )
+ assert_bash_exec(
+ bash,
+ '__tester() { local LC_ALL= LC_COLLATE=C arr; _comp_expand_glob arr "$@";dump_array; }',
+ )
+
+ def test_match_all(self, bash, functions):
+ output = assert_bash_exec(bash, "__tester '*'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b><ab><aƩ><brackets><ext>"
+
+ def test_match_pattern(self, bash, functions):
+ output = assert_bash_exec(bash, "__tester 'a*'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b><ab><aƩ>"
+
+ def test_match_unmatched(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester 'unmatched-*'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_match_multiple_words(self, bash, functions):
+ output = assert_bash_exec(bash, "__tester 'b* e*'", want_output=True)
+ assert output.strip() == "<brackets><ext>"
+
+ def test_match_brace_expansion(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester 'brac{ket,unmatched}*'", want_output=True
+ )
+ assert output.strip() == "<brackets>"
+
+ def test_protect_from_noglob(self, bash, functions):
+ with bash_env_saved(bash, functions) as bash_env:
+ bash_env.set("noglob", True)
+ output = assert_bash_exec(bash, "__tester 'a*'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b><ab><aƩ>"
+
+ def test_protect_from_failglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "__tester 'unmatched-*'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_protect_from_nullglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", False)
+ output = assert_bash_exec(
+ bash, "__tester 'unmatched-*'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_protect_from_dotglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("dotglob", True)
+ output = assert_bash_exec(
+ bash, "__tester 'ext/foo/*'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_protect_from_GLOBIGNORE(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ # Note: dotglob is changed by GLOBIGNORE
+ bash_env.save_shopt("dotglob")
+ bash_env.write_variable("GLOBIGNORE", "*")
+ output = assert_bash_exec(bash, "__tester 'a*'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b><ab><aƩ>"
diff --git a/test/t/unit/test_unit_expand_tilde_by_ref.py b/test/t/unit/test_unit_expand_tilde.py
index 17bdedf..3552fd6 100644
--- a/test/t/unit/test_unit_expand_tilde_by_ref.py
+++ b/test/t/unit/test_unit_expand_tilde.py
@@ -3,16 +3,27 @@ import pytest
from conftest import assert_bash_exec
-@pytest.mark.bashcomp(cmd=None, ignore_env=r"^[+-]var=")
-class TestUnitExpandTildeByRef:
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitExpandTilde:
def test_1(self, bash):
+ """The old interface `__expand_tilde_by_ref` should not fail when it is
+ called without arguments"""
assert_bash_exec(bash, "__expand_tilde_by_ref >/dev/null")
def test_2(self, bash):
"""Test environment non-pollution, detected at teardown."""
assert_bash_exec(
bash,
- '_x() { local aa="~"; __expand_tilde_by_ref aa; }; _x; unset _x',
+ '_x() { local REPLY; _comp_expand_tilde "~"; }; _x; unset -f _x',
+ )
+
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ # $HOME tinkering: protect against $HOME != ~user; our "home" is the
+ # latter but plain_tilde follows $HOME
+ assert_bash_exec(
+ bash,
+ '_comp__test_unit() { local REPLY HOME=$1; _comp_expand_tilde "$2"; printf "%s\\n" "$REPLY"; }',
)
@pytest.mark.parametrize("plain_tilde", (True, False))
@@ -28,19 +39,21 @@ class TestUnitExpandTildeByRef:
("/a;echo hello", True),
),
)
- def test_expand(self, bash, user_home, plain_tilde, suffix_expanded):
+ def test_expand(
+ self, bash, user_home, plain_tilde, suffix_expanded, functions
+ ):
user, home = user_home
suffix, expanded = suffix_expanded
+ home2 = home
if plain_tilde:
user = ""
if not suffix or not expanded:
- home = "~"
+ home2 = "~"
elif not expanded:
- home = "~%s" % user
+ home2 = "~%s" % user
output = assert_bash_exec(
bash,
- r'var="~%s%s"; __expand_tilde_by_ref var; printf "%%s\n" "$var"'
- % (user, suffix),
+ r'_comp__test_unit "%s" "~%s%s"' % (home, user, suffix),
want_output=True,
)
- assert output.strip() == "%s%s" % (home, suffix.replace(r"\$", "$"),)
+ assert output.strip() == "%s%s" % (home2, suffix.replace(r"\$", "$"))
diff --git a/test/t/unit/test_unit_filedir.py b/test/t/unit/test_unit_filedir.py
index b847efc..07ae2f3 100644
--- a/test/t/unit/test_unit_filedir.py
+++ b/test/t/unit/test_unit_filedir.py
@@ -15,18 +15,18 @@ class TestUnitFiledir:
def functions(self, request, bash):
assert_bash_exec(
bash,
- "_f() { local cur=$(_get_cword); unset COMPREPLY; _filedir; }; "
+ "_f() { local cur;_comp_get_words cur; unset -v COMPREPLY; _comp_compgen_filedir; }; "
"complete -F _f f; "
"complete -F _f -o filenames f2",
)
assert_bash_exec(
bash,
- "_g() { local cur=$(_get_cword); unset COMPREPLY; _filedir e1; }; "
+ "_g() { local cur;_comp_get_words cur; unset -v COMPREPLY; _comp_compgen_filedir e1; }; "
"complete -F _g g",
)
assert_bash_exec(
bash,
- "_fd() { local cur=$(_get_cword); unset COMPREPLY; _filedir -d; };"
+ "_fd() { local cur;_comp_get_words cur; unset -v COMPREPLY; _comp_compgen_filedir -d; };"
"complete -F _fd fd",
)
@@ -59,7 +59,7 @@ class TestUnitFiledir:
return lc_ctype
def test_1(self, bash):
- assert_bash_exec(bash, "_filedir >/dev/null")
+ assert_bash_exec(bash, "_comp_compgen_filedir >/dev/null")
@pytest.mark.parametrize("funcname", "f f2".split())
def test_2(self, bash, functions, funcname):
@@ -196,7 +196,7 @@ class TestUnitFiledir:
@pytest.mark.parametrize("funcname", "f f2".split())
def test_22(self, bash, functions, funcname, non_windows_testdir):
completion = assert_complete(
- bash, r"%s '%s/a\b/" % (funcname, non_windows_testdir)
+ bash, rf"{funcname} '{non_windows_testdir}/a\b/"
)
assert completion == "g'"
diff --git a/test/t/unit/test_unit_get_cword.py b/test/t/unit/test_unit_get_cword.py
index 0b56d16..d2bb526 100644
--- a/test/t/unit/test_unit_get_cword.py
+++ b/test/t/unit/test_unit_get_cword.py
@@ -1,11 +1,12 @@
-import pexpect
+import pexpect # type: ignore[import]
import pytest
from conftest import PS1, TestUnitBase, assert_bash_exec
@pytest.mark.bashcomp(
- cmd=None, ignore_env=r"^[+-](COMP_(WORDS|CWORD|LINE|POINT)|_scp_path_esc)="
+ cmd=None,
+ ignore_env=r"^[+-](COMP_(WORDS|CWORD|LINE|POINT)|_comp_cmd_scp__path_esc)=",
)
class TestUnitGetCword(TestUnitBase):
def _test(self, *args, **kwargs):
@@ -49,12 +50,12 @@ class TestUnitGetCword(TestUnitBase):
assert output == r"b\ c"
def test_8(self, bash):
- r"""a b\| c should return b\ """
+ r"""a b\| c should return b\ """ # fmt: skip
output = self._test(bash, r"(a 'b\ c')", 1, r"a b\ c", 4)
assert output == "b\\"
def test_9(self, bash):
- r"""a "b\| should return "b\ """
+ r"""a "b\| should return "b\ """ # fmt: skip
output = self._test(bash, "(a '\"b\\')", 1, r"a \"b\\", 5)
assert output == '"b\\'
@@ -103,7 +104,7 @@ class TestUnitGetCword(TestUnitBase):
a -n| should return -n
This test makes sure `_get_cword' doesn't use `echo' to return its
- value, because -n might be interpreted by `echo' and thus woud not
+ value, because -n might be interpreted by `echo' and thus would not
be returned.
"""
output = self._test(bash, "(a -n)", 1, "a -n", 4)
@@ -152,3 +153,12 @@ class TestUnitGetCword(TestUnitBase):
]
)
assert got == 1
+
+ def test_25(self, bash):
+ """
+ a b c:| with trailing whitespace after the caret (no more words) and
+ with WORDBREAKS -= : should return c:
+ """
+ assert_bash_exec(bash, "add_comp_wordbreak_char :")
+ output = self._test(bash, "(a b c :)", 3, "a b c: ", 6, arg=":")
+ assert output == "c:"
diff --git a/test/t/unit/test_unit_get_first_arg.py b/test/t/unit/test_unit_get_first_arg.py
new file mode 100644
index 0000000..415e217
--- /dev/null
+++ b/test/t/unit/test_unit_get_first_arg.py
@@ -0,0 +1,90 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitGetFirstArg:
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ '_comp__test_unit() { local -a "words=$1"; local cword=$2 REPLY=; shift 2; _comp_get_first_arg "$@" && printf "%s\\n" "$REPLY"; return 0; }',
+ )
+
+ def _test(self, bash, words, cword, args=""):
+ return assert_bash_exec(
+ bash,
+ '_comp__test_unit "%s" %d %s' % (words, cword, args),
+ want_output=None,
+ ).strip()
+
+ def test_1(self, bash, functions):
+ assert_bash_exec(bash, "_comp__test_unit '()' 0")
+
+ def test_2(self, bash, functions):
+ output = self._test(bash, "(a b)", 2)
+ assert output == "b"
+
+ def test_3(self, bash, functions):
+ output = self._test(bash, "(a bc)", 2)
+ assert output == "bc"
+
+ def test_4(self, bash, functions):
+ output = self._test(bash, "(a b c)", 2)
+ assert output == "b"
+
+ def test_5(self, bash, functions):
+ """Neither of the current word and the command name should be picked
+ as the first argument"""
+ output = self._test(bash, "(a b c)", 1)
+ assert output == ""
+
+ def test_6(self, bash, functions):
+ """Options starting with - should not be picked as the first
+ argument"""
+ output = self._test(bash, "(a -b -c d e)", 4)
+ assert output == "d"
+
+ def test_7_single_hyphen(self, bash, functions):
+ """- should be counted as an argument representing stdout/stdin"""
+ output = self._test(bash, "(a -b - c -d e)", 5)
+ assert output == "-"
+
+ def test_8_double_hyphen_1(self, bash, functions):
+ """any word after -- should be picked"""
+ output = self._test(bash, "(a -b -- -c -d e)", 5)
+ assert output == "-c"
+
+ def test_8_double_hyphen_2(self, bash, functions):
+ """any word after -- should be picked only without any preceding argument"""
+ output = self._test(bash, "(a b -- -c -d e)", 5)
+ assert output == "b"
+
+ def test_9_skip_optarg_1(self, bash, functions):
+ output = self._test(bash, "(a -b -c d e f)", 5, '-a "@(-c|--foo)"')
+ assert output == "e"
+
+ def test_9_skip_optarg_2(self, bash, functions):
+ output = self._test(bash, "(a -b --foo d e f)", 5, '-a "@(-c|--foo)"')
+ assert output == "e"
+
+ def test_9_skip_optarg_3(self, bash):
+ output = self._test(bash, "(a -b - c d e)", 5, '-a "-b"')
+ assert output == "c"
+
+ def test_9_skip_optarg_4(self, bash):
+ output = self._test(bash, "(a -b -c d e f)", 5, '-a "-[bc]"')
+ assert output == "d"
+
+ def test_9_skip_optarg_5(self, bash):
+ output = self._test(bash, "(a +o b c d)", 4, '-a "+o"')
+ assert output == "c"
+
+ def test_9_skip_optarg_6(self, bash):
+ output = self._test(bash, "(a -o -o -o -o b c)", 6, '-a "-o"')
+ assert output == "b"
+
+ def test_9_skip_optarg_7(self, bash):
+ output = self._test(bash, "(a -o -- -b -c d e)", 6, '-a "-o"')
+ assert output == "d"
diff --git a/test/t/unit/test_unit_get_comp_words_by_ref.py b/test/t/unit/test_unit_get_words.py
index b6498fa..63c4034 100644
--- a/test/t/unit/test_unit_get_comp_words_by_ref.py
+++ b/test/t/unit/test_unit_get_words.py
@@ -11,10 +11,10 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
def _test(self, bash, *args, **kwargs):
assert_bash_exec(bash, "unset cur prev")
output = self._test_unit(
- "_get_comp_words_by_ref %s cur prev; echo $cur,${prev-}",
+ "_comp_get_words %s cur prev; echo $cur,${prev-}",
bash,
*args,
- **kwargs
+ **kwargs,
)
return output.strip()
@@ -22,7 +22,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
assert_bash_exec(
bash,
"COMP_WORDS=() COMP_CWORD= COMP_POINT= COMP_LINE= "
- "_get_comp_words_by_ref cur >/dev/null",
+ "_comp_get_words cur >/dev/null",
)
def test_2(self, bash):
@@ -41,12 +41,12 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
assert output == ","
def test_5(self, bash):
- """|a """
+ """|a """ # fmt: skip
output = self._test(bash, "(a)", 0, "a ", 0)
assert output == ","
def test_6(self, bash):
- """ | a """
+ """ | a """ # fmt: skip
output = self._test(bash, "(a)", 0, " a ", 1)
assert output.strip() == ","
@@ -134,9 +134,9 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
def test_23(self, bash):
"""a -n|
- This test makes sure `_get_cword' doesn't use `echo' to return its
- value, because -n might be interpreted by `echo' and thus woud not
- be returned.
+ This test makes sure `_comp_get_words' doesn't use `echo' to
+ return its value, because -n might be interpreted by `echo'
+ and thus would not be returned.
"""
output = self._test(bash, "(a -n)", 1, "a -n", 4)
assert output == "-n,a"
@@ -175,7 +175,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b| to all vars"""
assert_bash_exec(bash, "unset words cword cur prev")
output = self._test_unit(
- "_get_comp_words_by_ref words cword cur prev%s; "
+ "_comp_get_words words cword cur prev%s; "
'echo "${words[@]}",$cword,$cur,$prev',
bash,
"(a b)",
@@ -189,7 +189,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b| to alternate vars"""
assert_bash_exec(bash, "unset words2 cword2 cur2 prev2")
output = self._test_unit(
- "_get_comp_words_by_ref -w words2 -i cword2 -c cur2 -p prev2%s; "
+ "_comp_get_words -w words2 -i cword2 -c cur2 -p prev2%s; "
'echo $cur2,$prev2,"${words2[@]}",$cword2',
bash,
"(a b)",
@@ -204,7 +204,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b : c| with wordbreaks -= :"""
assert_bash_exec(bash, "unset words")
output = self._test_unit(
- '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"',
+ '_comp_get_words -n : words%s; echo "${words[@]}"',
bash,
"(a b : c)",
3,
@@ -217,7 +217,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b: c| with wordbreaks -= :"""
assert_bash_exec(bash, "unset words")
output = self._test_unit(
- '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"',
+ '_comp_get_words -n : words%s; echo "${words[@]}"',
bash,
"(a b : c)",
3,
@@ -230,7 +230,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b :c| with wordbreaks -= :"""
assert_bash_exec(bash, "unset words")
output = self._test_unit(
- '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"',
+ '_comp_get_words -n : words%s; echo "${words[@]}"',
bash,
"(a b : c)",
3,
@@ -243,7 +243,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
r"""a b\ :c| with wordbreaks -= :"""
assert_bash_exec(bash, "unset words")
output = self._test_unit(
- '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"',
+ '_comp_get_words -n : words%s; echo "${words[@]}"',
bash,
"(a 'b ' : c)",
3,
@@ -255,6 +255,6 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
def test_unknown_arg_error(self, bash):
with pytest.raises(AssertionError) as ex:
_ = assert_bash_exec(
- bash, "_get_comp_words_by_ref dummy", want_output=True
+ bash, "_comp_get_words dummy", want_output=True
)
ex.match("dummy.* unknown argument")
diff --git a/test/t/unit/test_unit_init_completion.py b/test/t/unit/test_unit_init_completion.py
deleted file mode 100644
index 64a5a79..0000000
--- a/test/t/unit/test_unit_init_completion.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import pytest
-
-from conftest import TestUnitBase, assert_bash_exec, assert_complete
-
-
-@pytest.mark.bashcomp(
- cmd=None,
- ignore_env=r"^[+-](COMP(_(WORDS|CWORD|LINE|POINT)|REPLY)|"
- r"cur|cword|words)=",
-)
-class TestUnitInitCompletion(TestUnitBase):
- def test_1(self, bash):
- """Test environment non-pollution, detected at teardown."""
- assert_bash_exec(
- bash,
- "foo() { "
- "local cur prev words cword "
- "COMP_WORDS=() COMP_CWORD=0 COMP_LINE= COMP_POINT=0; "
- "_init_completion; }; "
- "foo; unset foo",
- )
-
- def test_2(self, bash):
- output = self._test_unit(
- "_init_completion %s; echo $cur,${prev-}", bash, "(a)", 0, "a", 0
- )
- assert output == ","
-
- @pytest.mark.parametrize("redirect", "> >> 2> < &>".split())
- def test_redirect(self, bash, redirect):
- completion = assert_complete(
- bash, "%s " % redirect, cwd="shared/default"
- )
- assert all(x in completion for x in "foo bar".split())
diff --git a/test/t/unit/test_unit_initialize.py b/test/t/unit/test_unit_initialize.py
new file mode 100644
index 0000000..63fddee
--- /dev/null
+++ b/test/t/unit/test_unit_initialize.py
@@ -0,0 +1,66 @@
+import pytest
+
+from conftest import TestUnitBase, assert_bash_exec, assert_complete
+
+
+@pytest.mark.bashcomp(
+ cmd=None,
+ ignore_env=r"^[+-](COMP(_(WORDS|CWORD|LINE|POINT)|REPLY)|"
+ r"cur|prev|cword|words)=|^\+declare -f _cmd1$",
+)
+class TestUnitInitCompletion(TestUnitBase):
+ def test_1(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { "
+ "local cur prev words cword comp_args "
+ "COMP_WORDS=() COMP_CWORD=0 COMP_LINE= COMP_POINT=0; "
+ "_comp_initialize; }; "
+ "foo; unset -f foo",
+ )
+
+ def test_2(self, bash):
+ output = self._test_unit(
+ "_comp_initialize %s; echo $cur,${prev-}", bash, "(a)", 0, "a", 0
+ )
+ assert output == ","
+
+ @pytest.mark.parametrize("redirect", "> >> 2> < &>".split())
+ def test_redirect(self, bash, redirect):
+ completion = assert_complete(
+ bash, "%s " % redirect, cwd="shared/default"
+ )
+ assert all(x in completion for x in "foo bar".split())
+
+ @pytest.fixture(scope="class")
+ def cmd1_empty_completion_setup(self, bash):
+ assert_bash_exec(
+ bash,
+ '_cmd1() { local cur prev words cword comp_args; _comp_initialize -- "$@"; } && '
+ "complete -F _cmd1 cmd1",
+ )
+
+ @pytest.mark.parametrize("redirect", "> >> 2> {fd1}> < &> &>> >|".split())
+ def test_redirect_2(self, bash, cmd1_empty_completion_setup, redirect):
+ # Note: Bash 4.3 and below cannot properly extract the redirection ">|"
+ if redirect == ">|":
+ skipif = "((BASH_VERSINFO[0] * 100 + BASH_VERSINFO[1] < 404))"
+ try:
+ assert_bash_exec(bash, skipif, want_output=None)
+ except AssertionError:
+ pass
+ else:
+ pytest.skip(skipif)
+
+ completion = assert_complete(
+ bash, "cmd1 %s f" % redirect, cwd="shared/default"
+ )
+ assert "foo" in completion
+
+ @pytest.mark.parametrize("redirect", "> >> 2> < &>".split())
+ def test_redirect_3(self, bash, redirect):
+ completion = assert_complete(
+ bash, "cmd1 %sf" % redirect, cwd="shared/default"
+ )
+ assert "foo" in completion
diff --git a/test/t/unit/test_unit_ip_addresses.py b/test/t/unit/test_unit_ip_addresses.py
index 8120c88..37f3b0e 100644
--- a/test/t/unit/test_unit_ip_addresses.py
+++ b/test/t/unit/test_unit_ip_addresses.py
@@ -9,41 +9,41 @@ class TestUnitIpAddresses:
def functions(self, request, bash):
assert_bash_exec(
bash,
- "_ia() { local cur=$(_get_cword);unset COMPREPLY;"
- "_ip_addresses; }",
+ "_ia() { local cur;_comp_get_words cur;"
+ "unset -v COMPREPLY;_comp_compgen_ip_addresses; }",
)
assert_bash_exec(bash, "complete -F _ia ia")
assert_bash_exec(
bash,
- "_iaa() { local cur=$(_get_cword);unset COMPREPLY;"
- "_ip_addresses -a; }",
+ "_iaa() { local cur;_comp_get_words cur;"
+ "unset -v COMPREPLY;_comp_compgen_ip_addresses -a; }",
)
assert_bash_exec(bash, "complete -F _iaa iaa")
assert_bash_exec(
bash,
- " _ia6() { local cur=$(_get_cword);unset COMPREPLY;"
- "_ip_addresses -6; }",
+ " _ia6() { local cur;_comp_get_words cur;"
+ "unset -v COMPREPLY;_comp_compgen_ip_addresses -6; }",
)
assert_bash_exec(bash, "complete -F _ia6 ia6")
def test_1(self, bash):
- assert_bash_exec(bash, "_ip_addresses")
+ assert_bash_exec(bash, "_comp_compgen_ip_addresses")
@pytest.mark.complete("iaa ")
def test_2(self, functions, completion):
- """_ip_addresses -a should complete ip addresses."""
+ """_comp_compgen_ip_addresses -a should complete ip addresses."""
assert completion
assert all("." in x or ":" in x for x in completion)
@pytest.mark.complete("ia ")
def test_3(self, functions, completion):
- """_ip_addresses should complete ipv4 addresses."""
+ """_comp_compgen_ip_addresses should complete ipv4 addresses."""
assert completion
assert all("." in x for x in completion)
@pytest.mark.xfail(in_container(), reason="Probably fails in a container")
@pytest.mark.complete("ia6 ")
def test_4(self, functions, completion):
- """_ip_addresses -6 should complete ipv6 addresses."""
+ """_comp_compgen_ip_addresses -6 should complete ipv6 addresses."""
assert completion
assert all(":" in x for x in completion)
diff --git a/test/t/unit/test_unit_known_hosts_real.py b/test/t/unit/test_unit_known_hosts.py
index ac5205e..b0f715b 100644
--- a/test/t/unit/test_unit_known_hosts_real.py
+++ b/test/t/unit/test_unit_known_hosts.py
@@ -2,14 +2,14 @@ from itertools import chain
import pytest
-from conftest import assert_bash_exec
+from conftest import assert_bash_exec, bash_env_saved
@pytest.mark.bashcomp(
cmd=None,
- ignore_env="^[+-](COMP(REPLY|_KNOWN_HOSTS_WITH_HOSTFILE)|OLDHOME)=",
+ ignore_env="^[+-](COMPREPLY|BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE)=",
)
-class TestUnitKnownHostsReal:
+class TestUnitCompgenKnownHosts:
@pytest.mark.parametrize(
"prefix,colon_flag,hostfile",
[("", "", True), ("", "", False), ("user@", "c", True)],
@@ -21,9 +21,9 @@ class TestUnitKnownHostsReal:
"%s%s%s" % (prefix, x, ":" if colon_flag else "")
for x in chain(
hosts if hostfile else avahi_hosts,
- # fixtures/_known_hosts_real/config
+ # fixtures/_known_hosts/config
"gee hus jar #not-a-comment".split(),
- # fixtures/_known_hosts_real/known_hosts
+ # fixtures/_known_hosts/known_hosts
(
"doo",
"ike",
@@ -43,14 +43,14 @@ class TestUnitKnownHostsReal:
)
assert_bash_exec(
bash,
- "unset -v COMP_KNOWN_HOSTS_WITH_HOSTFILE"
+ "unset -v BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE"
if hostfile
- else "COMP_KNOWN_HOSTS_WITH_HOSTFILE=",
+ else "BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE=",
)
output = assert_bash_exec(
bash,
- "_known_hosts_real -a%sF _known_hosts_real/config '%s'; "
- r'printf "%%s\n" "${COMPREPLY[@]}"; unset COMPREPLY'
+ "_comp_compgen_known_hosts -a%sF _known_hosts/config '%s'; "
+ r'printf "%%s\n" "${COMPREPLY[@]}"; unset -v COMPREPLY'
% (colon_flag, prefix),
want_output=True,
)
@@ -66,12 +66,13 @@ class TestUnitKnownHostsReal:
)
def test_ip_filtering(self, bash, family, result):
assert_bash_exec(
- bash, "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE"
+ bash,
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE",
)
output = assert_bash_exec(
bash,
- "COMP_KNOWN_HOSTS_WITH_HOSTFILE= "
- "_known_hosts_real -%sF _known_hosts_real/localhost_config ''; "
+ "BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE= "
+ "_comp_compgen_known_hosts -%sF _known_hosts/localhost_config ''; "
r'printf "%%s\n" "${COMPREPLY[@]}"' % family,
want_output=True,
)
@@ -79,17 +80,17 @@ class TestUnitKnownHostsReal:
def test_consecutive_spaces(self, bash, hosts):
expected = hosts.copy()
- # fixtures/_known_hosts_real/spaced conf
+ # fixtures/_known_hosts/spaced conf
expected.extend("gee hus #not-a-comment".split())
- # fixtures/_known_hosts_real/known_hosts2
+ # fixtures/_known_hosts/known_hosts2
expected.extend("two two2 two3 two4".split())
# fixtures/_known_hosts_/spaced known_hosts
expected.extend("doo ike".split())
output = assert_bash_exec(
bash,
- "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
- "_known_hosts_real -aF '_known_hosts_real/spaced conf' ''; "
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE; "
+ "_comp_compgen_known_hosts -aF '_known_hosts/spaced conf' ''; "
r'printf "%s\n" "${COMPREPLY[@]}"',
want_output=True,
)
@@ -97,62 +98,58 @@ class TestUnitKnownHostsReal:
def test_files_starting_with_tilde(self, bash, hosts):
expected = hosts.copy()
- # fixtures/_known_hosts_real/known_hosts2
+ # fixtures/_known_hosts/known_hosts2
expected.extend("two two2 two3 two4".split())
- # fixtures/_known_hosts_real/known_hosts3
+ # fixtures/_known_hosts/known_hosts3
expected.append("three")
- # fixtures/_known_hosts_real/known_hosts4
+ # fixtures/_known_hosts/known_hosts4
expected.append("four")
- assert_bash_exec(bash, 'OLDHOME="$HOME"; HOME="%s"' % bash.cwd)
- output = assert_bash_exec(
- bash,
- "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
- "_known_hosts_real -aF _known_hosts_real/config_tilde ''; "
- r'printf "%s\n" "${COMPREPLY[@]}"',
- want_output=True,
- )
- assert_bash_exec(bash, 'HOME="$OLDHOME"')
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("HOME", bash.cwd)
+ output = assert_bash_exec(
+ bash,
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE;"
+ " _comp_compgen_known_hosts -aF _known_hosts/config_tilde ''; "
+ r'printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ )
+
assert sorted(set(output.strip().split())) == sorted(expected)
def test_included_configs(self, bash, hosts):
expected = hosts.copy()
- # fixtures/_known_hosts_real/config_include_recursion
+ # fixtures/_known_hosts/config_include_recursion
expected.append("recursion")
- # fixtures/_known_hosts_real/.ssh/config_relative_path
+ # fixtures/_known_hosts/.ssh/config_relative_path
expected.append("relative_path")
- # fixtures/_known_hosts_real/.ssh/config_asterisk_*
+ # fixtures/_known_hosts/.ssh/config_asterisk_*
expected.extend("asterisk_1 asterisk_2".split())
- # fixtures/_known_hosts_real/.ssh/config_question_mark
+ # fixtures/_known_hosts/.ssh/config_question_mark
expected.append("question_mark")
- assert_bash_exec(
- bash, 'OLDHOME="$HOME"; HOME="%s/_known_hosts_real"' % bash.cwd
- )
- output = assert_bash_exec(
- bash,
- "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
- "_known_hosts_real -aF _known_hosts_real/config_include ''; "
- r'printf "%s\n" "${COMPREPLY[@]}"',
- want_output=True,
- )
- assert_bash_exec(bash, 'HOME="$OLDHOME"')
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("HOME", "%s/_known_hosts" % bash.cwd)
+ output = assert_bash_exec(
+ bash,
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE;"
+ " _comp_compgen_known_hosts -aF _known_hosts/config_include ''; "
+ r'printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ )
assert sorted(set(output.strip().split())) == sorted(expected)
def test_no_globbing(self, bash):
- assert_bash_exec(
- bash, 'OLDHOME="$HOME"; HOME="%s/_known_hosts_real"' % bash.cwd
- )
- output = assert_bash_exec(
- bash,
- "cd _known_hosts_real; "
- "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
- "_known_hosts_real -aF config ''; "
- r'printf "%s\n" "${COMPREPLY[@]}"; '
- "cd - &>/dev/null",
- want_output=True,
- )
- assert_bash_exec(bash, 'HOME="$OLDHOME"')
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("HOME", "%s/_known_hosts" % bash.cwd)
+ bash_env.chdir("_known_hosts")
+ output = assert_bash_exec(
+ bash,
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE;"
+ " _comp_compgen_known_hosts -aF config ''; "
+ r'printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ )
completion = sorted(set(output.strip().split()))
assert "gee" in completion
assert "gee-filename-canary" not in completion
diff --git a/test/t/unit/test_unit_load_completion.py b/test/t/unit/test_unit_load_completion.py
new file mode 100644
index 0000000..6272e5b
--- /dev/null
+++ b/test/t/unit/test_unit_load_completion.py
@@ -0,0 +1,94 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, cwd="_comp_load")
+class TestLoadCompletion:
+ def test_userdir_1(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR",
+ "$PWD/userdir1:$PWD/userdir2:$BASH_COMPLETION_USER_DIR",
+ quote=False,
+ )
+ bash_env.write_variable(
+ "PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False
+ )
+ output = assert_bash_exec(
+ bash, "_comp_load cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from userdir1"
+ output = assert_bash_exec(
+ bash, "_comp_load cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from userdir2"
+
+ def test_PATH_1(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False
+ )
+ output = assert_bash_exec(
+ bash, "_comp_load cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "_comp_load cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "complete -p cmd2", want_output=True
+ )
+ assert " cmd2" in output
+ output = assert_bash_exec(
+ bash, 'complete -p "$PWD/prefix1/sbin/cmd2"', want_output=True
+ )
+ assert "/prefix1/sbin/cmd2" in output
+
+ def test_cmd_path_1(self, bash):
+ assert_bash_exec(bash, "complete -r cmd1 || :", want_output=None)
+ output = assert_bash_exec(
+ bash, "_comp_load prefix1/bin/cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, 'complete -p "$PWD/prefix1/bin/cmd1"', want_output=True
+ )
+ assert "/prefix1/bin/cmd1" in output
+ assert_bash_exec(bash, "! complete -p cmd1", want_output=None)
+ output = assert_bash_exec(
+ bash, "_comp_load prefix1/sbin/cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "_comp_load bin/cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "_comp_load bin/cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from prefix1"
+
+ def test_cmd_path_2(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("PATH", "$PWD/bin:$PATH", quote=False)
+ output = assert_bash_exec(
+ bash, "_comp_load cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "_comp_load cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from prefix1"
+
+ def test_cmd_intree_precedence(self, bash):
+ """
+ Test in-tree, i.e. completions/$cmd relative to the main script
+ has precedence over location derived from PATH.
+ """
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("PATH", "$PWD/prefix1/bin", quote=False)
+ # The in-tree `sh` completion should be loaded here,
+ # and cause no output, unlike our `$PWD/prefix1/bin/sh` canary.
+ assert_bash_exec(bash, "_comp_load sh", want_output=False)
diff --git a/test/t/unit/test_unit_longopt.py b/test/t/unit/test_unit_longopt.py
index c5488e3..ab823d4 100644
--- a/test/t/unit/test_unit_longopt.py
+++ b/test/t/unit/test_unit_longopt.py
@@ -10,9 +10,9 @@ class TestUnitLongopt:
@pytest.fixture(scope="class")
def functions(self, request, bash):
assert_bash_exec(bash, "_grephelp() { cat _longopt/grep--help.txt; }")
- assert_bash_exec(bash, "complete -F _longopt _grephelp")
+ assert_bash_exec(bash, "complete -F _comp_complete_longopt _grephelp")
assert_bash_exec(bash, "_various() { cat _longopt/various.txt; }")
- assert_bash_exec(bash, "complete -F _longopt _various")
+ assert_bash_exec(bash, "complete -F _comp_complete_longopt _various")
@pytest.mark.complete("_grephelp --")
def test_1(self, functions, completion):
diff --git a/test/t/unit/test_unit_looks_like_path.py b/test/t/unit/test_unit_looks_like_path.py
new file mode 100644
index 0000000..3e86b48
--- /dev/null
+++ b/test/t/unit/test_unit_looks_like_path.py
@@ -0,0 +1,33 @@
+import shlex
+
+import pytest
+
+from conftest import TestUnitBase, assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitQuote(TestUnitBase):
+ @pytest.mark.parametrize(
+ "thing_looks_like",
+ (
+ ("", False),
+ ("foo", False),
+ ("/foo", True),
+ ("foo/", True),
+ ("foo/bar", True),
+ (".", True),
+ ("../", True),
+ ("~", True),
+ ("~foo", True),
+ ),
+ )
+ def test_1(self, bash, thing_looks_like):
+ thing, looks_like = thing_looks_like
+ output = assert_bash_exec(
+ bash,
+ f"_comp_looks_like_path {shlex.quote(thing)}; printf %s $?",
+ want_output=True,
+ want_newline=False,
+ )
+ is_zero = output.strip() == "0"
+ assert (looks_like and is_zero) or (not looks_like and not is_zero)
diff --git a/test/t/unit/test_unit_parse_help.py b/test/t/unit/test_unit_parse_help.py
index 4a02155..1a46f3f 100644
--- a/test/t/unit/test_unit_parse_help.py
+++ b/test/t/unit/test_unit_parse_help.py
@@ -2,29 +2,29 @@
import pytest
-from conftest import assert_bash_exec
+from conftest import assert_bash_exec, bash_env_saved
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+declare -f fn$")
class TestUnitParseHelp:
def test_1(self, bash):
assert_bash_exec(bash, "fn() { echo; }")
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_2(self, bash):
assert_bash_exec(bash, "fn() { echo 'no dashes here'; }")
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_3(self, bash):
assert_bash_exec(bash, "fn() { echo 'internal-dash'; }")
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_4(self, bash):
assert_bash_exec(bash, "fn() { echo 'no -leading-dashes'; }")
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_5(self, bash):
@@ -94,6 +94,20 @@ class TestUnitParseHelp:
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
assert output.split() == "--foo".split()
+ def test_17_failglob(self, bash):
+ assert_bash_exec(bash, "fn() { echo '--foo[=bar]'; }")
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
+ assert output.split() == "--foo".split()
+
+ def test_17_nullglob(self, bash):
+ assert_bash_exec(bash, "fn() { echo '--foo[=bar]'; }")
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
+ assert output.split() == "--foo".split()
+
def test_18(self, bash):
assert_bash_exec(bash, "fn() { echo '--foo=<bar>'; }")
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
@@ -144,6 +158,13 @@ class TestUnitParseHelp:
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
assert output.split() == "--foo".split()
+ def test_27_middle_dot(self, bash):
+ """We do not want to include the period at the end of the sentence but
+ want to include dots connecting names."""
+ assert_bash_exec(bash, "fn() { echo '--foo.bar.'; }")
+ output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
+ assert output.split() == "--foo.bar".split()
+
def test_28(self, bash):
assert_bash_exec(bash, "fn() { echo '-f or --foo'; }")
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
@@ -161,7 +182,7 @@ class TestUnitParseHelp:
assert_bash_exec(
bash, r"fn() { printf '%s\n' $'----\n---foo\n----- bar'; }"
)
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_31(self, bash):
@@ -181,3 +202,33 @@ class TestUnitParseHelp:
)
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
assert output.split() == "--exclude=".split()
+
+ def test_custom_helpopt1(self, bash):
+ assert_bash_exec(bash, "fn() { [[ $1 == -h ]] && echo '-option'; }")
+ output = assert_bash_exec(bash, "_parse_help fn -h", want_output=True)
+ assert output.split() == "-option".split()
+
+ def test_custom_helpopt2(self, bash):
+ assert_bash_exec(bash, "fn() { [[ $1 == '-?' ]] && echo '-option'; }")
+ output = assert_bash_exec(
+ bash, "_parse_help fn '-?'", want_output=True
+ )
+ assert output.split() == "-option".split()
+
+ def test_custom_helpopt2_failglob(self, bash):
+ assert_bash_exec(bash, "fn() { [[ $1 == '-?' ]] && echo '-option'; }")
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "_parse_help fn '-?'", want_output=True
+ )
+ assert output.split() == "-option".split()
+
+ def test_custom_helpopt2_nullglob(self, bash):
+ assert_bash_exec(bash, "fn() { [[ $1 == '-?' ]] && echo '-option'; }")
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash, "_parse_help fn '-?'", want_output=True
+ )
+ assert output.split() == "-option".split()
diff --git a/test/t/unit/test_unit_parse_usage.py b/test/t/unit/test_unit_parse_usage.py
index f0cb711..0106922 100644
--- a/test/t/unit/test_unit_parse_usage.py
+++ b/test/t/unit/test_unit_parse_usage.py
@@ -1,18 +1,18 @@
import pytest
-from conftest import assert_bash_exec
+from conftest import assert_bash_exec, bash_env_saved
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+declare -f fn$")
class TestUnitParseUsage:
def test_1(self, bash):
assert_bash_exec(bash, "fn() { echo; }")
- output = assert_bash_exec(bash, "_parse_usage fn")
+ output = assert_bash_exec(bash, "_parse_usage fn; (($? == 1))")
assert not output
def test_2(self, bash):
assert_bash_exec(bash, "fn() { echo 'no dashes here'; }")
- output = assert_bash_exec(bash, "_parse_usage fn")
+ output = assert_bash_exec(bash, "_parse_usage fn; (($? == 1))")
assert not output
def test_3(self, bash):
@@ -59,7 +59,7 @@ class TestUnitParseUsage:
assert_bash_exec(
bash, "fn() { echo ----; echo ---foo; echo '----- bar'; }"
)
- output = assert_bash_exec(bash, "_parse_usage fn")
+ output = assert_bash_exec(bash, "_parse_usage fn; (($? == 1))")
assert not output
def test_12(self, bash):
@@ -67,3 +67,41 @@ class TestUnitParseUsage:
bash, "echo '[-duh]' | _parse_usage -", want_output=True
)
assert output.split() == "-d -u -h".split()
+
+ def test_custom_helpopt1(self, bash):
+ assert_bash_exec(
+ bash, "fn() { [[ $1 == -h ]] && echo 'fn [-option]'; true; }"
+ )
+ output = assert_bash_exec(bash, "_parse_usage fn -h", want_output=True)
+ assert output.split() == "-o -p -t -i -o -n".split()
+
+ def test_custom_helpopt2(self, bash):
+ assert_bash_exec(
+ bash, "fn() { [[ $1 == '-?' ]] && echo 'fn [-option]'; }"
+ )
+ output = assert_bash_exec(
+ bash, "_parse_usage fn '-?'", want_output=True
+ )
+ assert output.split() == "-o -p -t -i -o -n".split()
+
+ def test_custom_helpopt2_failglob(self, bash):
+ assert_bash_exec(
+ bash, "fn() { [[ $1 == '-?' ]] && echo 'fn [-option]'; }"
+ )
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "_parse_usage fn '-?'", want_output=True
+ )
+ assert output.split() == "-o -p -t -i -o -n".split()
+
+ def test_custom_helpopt2_nullglob(self, bash):
+ assert_bash_exec(
+ bash, "fn() { [[ $1 == '-?' ]] && echo 'fn [-option]'; }"
+ )
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash, "_parse_usage fn '-?'", want_output=True
+ )
+ assert output.split() == "-o -p -t -i -o -n".split()
diff --git a/test/t/unit/test_unit_pgids.py b/test/t/unit/test_unit_pgids.py
new file mode 100644
index 0000000..05019ec
--- /dev/null
+++ b/test/t/unit/test_unit_pgids.py
@@ -0,0 +1,34 @@
+import os
+
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+(COMPREPLY|cur)=")
+class TestUnitPgids:
+ def test_smoke(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ assert_bash_exec(bash, "_comp_compgen_pgids >/dev/null")
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local cur=; _comp_compgen_pgids; }; foo; unset -f foo",
+ )
+
+ def test_ints(self, bash):
+ """Test that we get something sensible, and only int'y strings."""
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ completion = assert_bash_exec(
+ bash,
+ r'_comp_compgen_pgids; printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ ).split()
+ assert completion
+ if hasattr(os, "getpgid"):
+ assert str(os.getpgid(0)) in completion
+ assert all(x.isdigit() for x in completion)
diff --git a/test/t/unit/test_unit_pids.py b/test/t/unit/test_unit_pids.py
new file mode 100644
index 0000000..3681c8c
--- /dev/null
+++ b/test/t/unit/test_unit_pids.py
@@ -0,0 +1,34 @@
+import os
+
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
+class TestUnitPids:
+ def test_smoke(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ assert_bash_exec(bash, "_comp_compgen_pids >/dev/null")
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local cur=; _comp_compgen_pids; }; foo; unset -f foo",
+ )
+
+ def test_ints(self, bash):
+ """Test that we get something sensible, and only int'y strings."""
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ completion = assert_bash_exec(
+ bash,
+ r'_comp_compgen_pids; printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ ).split()
+ assert completion
+ if hasattr(os, "getpid"):
+ assert str(os.getpid()) in completion
+ assert all(x.isdigit() for x in completion)
diff --git a/test/t/unit/test_unit_pnames.py b/test/t/unit/test_unit_pnames.py
new file mode 100644
index 0000000..e0819e5
--- /dev/null
+++ b/test/t/unit/test_unit_pnames.py
@@ -0,0 +1,29 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
+class TestUnitPnames:
+ def test_smoke(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ assert_bash_exec(bash, "_comp_compgen_pnames >/dev/null")
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local cur=; _comp_compgen_pnames; }; foo; unset -f foo",
+ )
+
+ def test_something(self, bash):
+ """Test that we get something."""
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ completion = assert_bash_exec(
+ bash,
+ r'_comp_compgen_pids; printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ ).split()
+ assert completion
diff --git a/test/t/unit/test_unit_quote.py b/test/t/unit/test_unit_quote.py
index b280bd6..3508782 100644
--- a/test/t/unit/test_unit_quote.py
+++ b/test/t/unit/test_unit_quote.py
@@ -3,34 +3,41 @@ import pytest
from conftest import TestUnitBase, assert_bash_exec
-@pytest.mark.bashcomp(cmd=None)
+@pytest.mark.bashcomp(
+ cmd=None,
+ ignore_env=r"^\+declare -f __tester$",
+)
class TestUnitQuote(TestUnitBase):
def test_1(self, bash):
+ assert_bash_exec(
+ bash,
+ '__tester() { local REPLY; _comp_quote "$1"; printf %s "$REPLY"; }',
+ )
output = assert_bash_exec(
- bash, 'quote "a b"', want_output=True, want_newline=False
+ bash, '__tester "a b"', want_output=True, want_newline=False
)
assert output.strip() == "'a b'"
def test_2(self, bash):
output = assert_bash_exec(
- bash, 'quote "a b"', want_output=True, want_newline=False
+ bash, '__tester "a b"', want_output=True, want_newline=False
)
assert output.strip() == "'a b'"
def test_3(self, bash):
output = assert_bash_exec(
- bash, 'quote " a "', want_output=True, want_newline=False
+ bash, '__tester " a "', want_output=True, want_newline=False
)
assert output.strip() == "' a '"
def test_4(self, bash):
output = assert_bash_exec(
- bash, "quote \"a'b'c\"", want_output=True, want_newline=False
+ bash, "__tester \"a'b'c\"", want_output=True, want_newline=False
)
assert output.strip() == r"'a'\''b'\''c'"
def test_5(self, bash):
output = assert_bash_exec(
- bash, 'quote "a\'"', want_output=True, want_newline=False
+ bash, '__tester "a\'"', want_output=True, want_newline=False
)
assert output.strip() == r"'a'\'''"
diff --git a/test/t/unit/test_unit_quote_compgen.py b/test/t/unit/test_unit_quote_compgen.py
new file mode 100644
index 0000000..faf23fe
--- /dev/null
+++ b/test/t/unit/test_unit_quote_compgen.py
@@ -0,0 +1,173 @@
+import os
+
+import pytest
+
+from conftest import assert_bash_exec, assert_complete, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, temp_cwd=True)
+class TestUnitQuoteCompgen:
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ '_comp__test_quote_compgen() { local REPLY; _comp_quote_compgen "$1"; printf %s "$REPLY"; }',
+ )
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_exec(self, bash, functions, funcname):
+ assert_bash_exec(bash, "%s '' >/dev/null" % funcname)
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_env_non_pollution(self, bash, functions, funcname):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash, "foo() { %s meh >/dev/null; }; foo; unset -f foo" % funcname
+ )
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_1(self, bash, functions, funcname):
+ output = assert_bash_exec(
+ bash, "%s '';echo" % funcname, want_output=True
+ )
+ assert output.strip() == "''"
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_2(self, bash, functions, funcname):
+ output = assert_bash_exec(
+ bash, "%s foo;echo" % funcname, want_output=True
+ )
+ assert output.strip() == "foo"
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_3(self, bash, functions, funcname):
+ output = assert_bash_exec(
+ bash, '%s foo\\"bar;echo' % funcname, want_output=True
+ )
+ assert output.strip() == 'foo\\"bar'
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_4(self, bash, functions, funcname):
+ output = assert_bash_exec(
+ bash, "%s '$(echo x >&2)';echo" % funcname, want_output=True
+ )
+ assert output.strip() == "\\$\\(echo\\ x\\ \\>\\&2\\)"
+
+ def test_github_issue_189_1(self, bash, functions):
+ """Test error messages on a certain command line
+
+ Reported at https://github.com/scop/bash-completion/issues/189
+
+ Syntax error messages should not be shown by completion on the
+ following line:
+
+ $ ls -- '${[TAB]
+ $ rm -- '${[TAB]
+
+ """
+ assert_bash_exec(bash, "_comp__test_quote_compgen $'\\'${' >/dev/null")
+
+ def test_github_issue_492_1(self, bash, functions):
+ """Test unintended code execution on a certain command line
+
+ Reported at https://github.com/scop/bash-completion/pull/492
+
+ Arbitrary commands could be unintendedly executed by
+ _comp_quote_compgen. In the following example, the command "touch
+ 1.txt" would be unintendedly created before the fix. The file "1.txt"
+ should not be created by completion on the following line:
+
+ $ echo '$(touch file.txt)[TAB]
+
+ """
+ assert_bash_exec(
+ bash, "_comp__test_quote_compgen $'\\'$(touch 1.txt)' >/dev/null"
+ )
+ assert not os.path.exists("./1.txt")
+
+ def test_github_issue_492_2(self, bash, functions):
+ """Test the file clear by unintended redirection on a certain command line
+
+ Reported at https://github.com/scop/bash-completion/pull/492
+
+ The file "1.0" should not be created by completion on the following
+ line:
+
+ $ awk '$1 > 1.0[TAB]
+
+ """
+ assert_bash_exec(
+ bash, "_comp__test_quote_compgen $'\\'$1 > 1.0' >/dev/null"
+ )
+ assert not os.path.exists("./1.0")
+
+ def test_github_issue_492_3(self, bash, functions):
+ """Test code execution through unintended pathname expansions
+
+ When there is a file named "quote=$(COMMAND)" (for
+ _comp_compgen_filedir) or "REPLY=$(COMMAND)" (for _comp_quote_compgen),
+ the completion of the word '$* results in the execution of COMMAND.
+
+ $ echo '$*[TAB]
+
+ """
+ os.mkdir("./REPLY=$(echo injected >&2)")
+ assert_bash_exec(bash, "_comp__test_quote_compgen $'\\'$*' >/dev/null")
+
+ def test_github_issue_492_4(self, bash, functions):
+ """Test error messages through unintended pathname expansions
+
+ When "shopt -s failglob" is set by the user, the completion of the word
+ containing glob character and special characters (e.g. TAB) results in
+ the failure of pathname expansions.
+
+ $ shopt -s failglob
+ $ echo a\\ b*[TAB]
+
+ """
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ assert_bash_exec(
+ bash, "_comp__test_quote_compgen $'a\\\\\\tb*' >/dev/null"
+ )
+
+ def test_github_issue_526_1(self, bash):
+ r"""Regression tests for unprocessed escape sequences after quotes
+
+ Ref [1] https://github.com/scop/bash-completion/pull/492#discussion_r637213822
+ Ref [2] https://github.com/scop/bash-completion/pull/526
+
+ The escape sequences in the local variable of "value" in
+ "_comp_quote_compgen" needs to be unescaped by passing it to printf as
+ the format string. This causes a problem in the following case [where
+ the spaces after "alpha\" is a TAB character inserted in the command
+ string by "C-v TAB"]:
+
+ $ echo alpha\ b[TAB]
+
+ """
+ os.mkdir("./alpha\tbeta")
+ assert (
+ assert_complete(
+ # Remark on "rendered_cmd": Bash aligns the last character 'b'
+ # in the rendered cmd to an "8 x n" boundary using spaces.
+ # Here, the command string is assumed to start from column 2
+ # because the width of PS1 (conftest.PS1 = '/@') is 2,
+ bash,
+ "echo alpha\\\026\tb",
+ rendered_cmd="echo alpha\\ b",
+ )
+ == "eta/"
+ )
diff --git a/test/t/unit/test_unit_quote_readline.py b/test/t/unit/test_unit_quote_readline.py
deleted file mode 100644
index e2b437e..0000000
--- a/test/t/unit/test_unit_quote_readline.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import pytest
-
-from conftest import assert_bash_exec
-
-
-@pytest.mark.bashcomp(cmd=None)
-class TestUnitQuoteReadline:
- def test_exec(self, bash):
- assert_bash_exec(bash, "quote_readline '' >/dev/null")
-
- def test_env_non_pollution(self, bash):
- """Test environment non-pollution, detected at teardown."""
- assert_bash_exec(
- bash, "foo() { quote_readline meh >/dev/null; }; foo; unset foo"
- )
diff --git a/test/t/unit/test_unit_realcommand.py b/test/t/unit/test_unit_realcommand.py
new file mode 100644
index 0000000..4eb7b73
--- /dev/null
+++ b/test/t/unit/test_unit_realcommand.py
@@ -0,0 +1,90 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(
+ cmd=None, cwd="shared", ignore_env=r"^\+declare -f __tester$"
+)
+class TestUnitRealCommand:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ (
+ "__tester() { "
+ "local REPLY rc; "
+ '_comp_realcommand "$1"; '
+ "rc=$?; "
+ 'printf %s "$REPLY"; '
+ "return $rc; "
+ "}"
+ ),
+ )
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local REPLY=; _comp_realcommand bar; }; foo; unset -f foo",
+ want_output=None,
+ )
+
+ def test_basename(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("PATH", "$PWD/bin:$PATH", quote=False)
+ output = assert_bash_exec(
+ bash,
+ "__tester arp",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/bin/arp")
+
+ def test_basename_nonexistent(self, bash, functions):
+ filename = "non-existent-file-for-bash-completion-tests"
+ skipif = "! type -P %s" % filename
+ try:
+ assert_bash_exec(bash, skipif, want_output=None)
+ except AssertionError:
+ pytest.skipif(skipif)
+ output = assert_bash_exec(
+ bash,
+ "! __tester %s" % filename,
+ want_output=False,
+ )
+ assert output.strip() == ""
+
+ def test_relative(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester bin/arp",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/bin/arp")
+
+ def test_relative_nonexistent(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "! __tester bin/non-existent",
+ want_output=False,
+ )
+ assert output.strip() == ""
+
+ def test_absolute(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '__tester "$PWD/bin/arp"',
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/bin/arp")
+
+ def test_absolute_nonexistent(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '! __tester "$PWD/bin/non-existent"',
+ want_output=False,
+ )
+ assert output.strip() == ""
diff --git a/test/t/unit/test_unit_split.py b/test/t/unit/test_unit_split.py
new file mode 100644
index 0000000..d1f228e
--- /dev/null
+++ b/test/t/unit/test_unit_split.py
@@ -0,0 +1,90 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(
+ cmd=None, ignore_env=r"^\+declare -f (dump_array|__tester)$"
+)
+class TestUtilSplit:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash, "dump_array() { printf '<%s>' \"${arr[@]}\"; echo; }"
+ )
+ assert_bash_exec(
+ bash,
+ '__tester() { local -a arr=(00); _comp_split "${@:1:$#-1}" arr "${@:$#}"; dump_array; }',
+ )
+
+ def test_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester '12 34 56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_2(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester $'12\\n34\\n56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_3(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester '12:34:56'", want_output=True
+ )
+ assert output.strip() == "<12:34:56>"
+
+ def test_option_F_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -F : '12:34:56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_option_F_2(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -F : '12 34 56'", want_output=True
+ )
+ assert output.strip() == "<12 34 56>"
+
+ def test_option_l_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -l $'12\\n34\\n56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_option_l_2(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -l '12 34 56'", want_output=True
+ )
+ assert output.strip() == "<12 34 56>"
+
+ def test_option_a_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -aF : '12:34:56'", want_output=True
+ )
+ assert output.strip() == "<00><12><34><56>"
+
+ def test_protect_from_failglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "__tester -F '*' '12*34*56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_protect_from_nullglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash, "__tester -F '*' '12*34*56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_protect_from_IFS(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("IFS", "34")
+ output = assert_bash_exec(
+ bash, "__tester '12 34 56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
diff --git a/test/t/unit/test_unit_tilde.py b/test/t/unit/test_unit_tilde.py
index 35a4e4c..fae0dd6 100644
--- a/test/t/unit/test_unit_tilde.py
+++ b/test/t/unit/test_unit_tilde.py
@@ -6,23 +6,24 @@ from conftest import assert_bash_exec
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
class TestUnitTilde:
def test_1(self, bash):
- assert_bash_exec(bash, "_tilde >/dev/null")
+ assert_bash_exec(bash, "! _comp_compgen_tilde >/dev/null")
def test_2(self, bash):
"""Test environment non-pollution, detected at teardown."""
assert_bash_exec(
- bash, 'foo() { local aa="~"; _tilde "$aa"; }; foo; unset foo'
+ bash,
+ 'foo() { local aa="~"; _comp_compgen -c "$aa" tilde; }; foo; unset -f foo',
)
def test_3(self, bash):
"""Test for https://bugs.debian.org/766163"""
- assert_bash_exec(bash, "_tilde ~-o")
+ assert_bash_exec(bash, "! _comp_compgen -c ~-o tilde")
def _test_part_full(self, bash, part, full):
res = (
assert_bash_exec(
bash,
- '_tilde "~%s"; echo "${COMPREPLY[@]}"' % part,
+ '_comp_compgen -c "~%s" tilde; echo "${COMPREPLY[@]}"' % part,
want_output=True,
)
.strip()
diff --git a/test/t/unit/test_unit_unlocal.py b/test/t/unit/test_unit_unlocal.py
new file mode 100644
index 0000000..be5ec56
--- /dev/null
+++ b/test/t/unit/test_unit_unlocal.py
@@ -0,0 +1,18 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+(VAR=|declare -f foo)")
+class TestUnlocal:
+ def test_1(self, bash):
+ cmd = (
+ "foo() { "
+ "local VAR=inner; "
+ "_comp_unlocal VAR; "
+ "echo $VAR; "
+ "}; "
+ "VAR=outer; foo; "
+ )
+ res = assert_bash_exec(bash, cmd, want_output=True).strip()
+ assert res == "outer"
diff --git a/test/t/unit/test_unit_variables.py b/test/t/unit/test_unit_variables.py
index d62bc4a..f15d249 100644
--- a/test/t/unit/test_unit_variables.py
+++ b/test/t/unit/test_unit_variables.py
@@ -11,7 +11,7 @@ class TestUnitVariables:
assert_bash_exec(
bash, "unset assoc2 && declare -A assoc2=([idx1]=1 [idx2]=2)"
)
- assert_bash_exec(bash, "unset ${!___v*} && declare ___var=''")
+ assert_bash_exec(bash, 'unset ${!___v*} && declare ___var=""')
request.addfinalizer(
lambda: assert_bash_exec(bash, "unset ___var assoc1 assoc2")
)
@@ -28,6 +28,10 @@ class TestUnitVariables:
def test_multiple_array_indexes(self, functions, completion):
assert completion == "${assoc2[idx1]} ${assoc2[idx2]}".split()
+ @pytest.mark.complete(": ${assoc2[", shopt=dict(failglob=True))
+ def test_multiple_array_indexes_failglob(self, functions, completion):
+ assert completion == "${assoc2[idx1]} ${assoc2[idx2]}".split()
+
@pytest.mark.complete(": ${assoc1[bogus]")
def test_closing_curly_after_square(self, functions, completion):
assert completion == "}"
diff --git a/test/t/unit/test_unit_xfunc.py b/test/t/unit/test_unit_xfunc.py
new file mode 100644
index 0000000..3fb3a72
--- /dev/null
+++ b/test/t/unit/test_unit_xfunc.py
@@ -0,0 +1,71 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitXfunc:
+ def test_1(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR", "$PWD/_comp_xfunc", quote=False
+ )
+
+ # test precondition
+ assert_bash_exec(
+ bash,
+ "! declare -F _comp_xfunc_xfunc_test1_utility1 &>/dev/null",
+ )
+
+ # first invocation (completion/xfunc-test1 is sourced)
+ output = assert_bash_exec(
+ bash,
+ "_comp_xfunc xfunc-test1 utility1 'a b' cde fgh",
+ want_output=True,
+ )
+ assert output.strip() == "util1[<a b><cde><fgh>]"
+
+ # test precondition
+ assert_bash_exec(
+ bash, "declare -F _comp_xfunc_xfunc_test1_utility1 &>/dev/null"
+ )
+
+ # second invocation (completion/xfunc-test1 is not sourced)
+ output = assert_bash_exec(
+ bash,
+ "_comp_xfunc xfunc-test1 utility1 'a b' cde fgh",
+ want_output=True,
+ )
+ assert output.strip() == "util1[<a b><cde><fgh>]"
+
+ def test_2(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR", "$PWD/_comp_xfunc", quote=False
+ )
+
+ # test precondition
+ assert_bash_exec(
+ bash, "! declare -F _comp_xfunc_non_standard_name &>/dev/null"
+ )
+
+ # first invocation (completion/xfunc-test2 is sourced)
+ output = assert_bash_exec(
+ bash,
+ "_comp_xfunc xfunc-test2 _comp_xfunc_non_standard_name 'a b' cde fgh",
+ want_output=True,
+ )
+ assert output.strip() == "util2[<a b><cde><fgh>]"
+
+ # test precondition
+ assert_bash_exec(
+ bash, "declare -F _comp_xfunc_non_standard_name &>/dev/null"
+ )
+
+ # second invocation (completion/xfunc-test2 is not sourced)
+ output = assert_bash_exec(
+ bash,
+ "_comp_xfunc xfunc-test2 _comp_xfunc_non_standard_name 'a b' cde fgh",
+ want_output=True,
+ )
+ assert output.strip() == "util2[<a b><cde><fgh>]"
diff --git a/test/t/unit/test_unit_xinetd_services.py b/test/t/unit/test_unit_xinetd_services.py
index 7a90cb7..a9e33a9 100644
--- a/test/t/unit/test_unit_xinetd_services.py
+++ b/test/t/unit/test_unit_xinetd_services.py
@@ -6,17 +6,19 @@ from conftest import assert_bash_exec
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
class TestUnitXinetdServices:
def test_direct(self, bash):
- assert_bash_exec(bash, "_xinetd_services >/dev/null")
+ assert_bash_exec(bash, "_comp_compgen_xinetd_services >/dev/null")
def test_env_non_pollution(self, bash):
"""Test environment non-pollution, detected at teardown."""
- assert_bash_exec(bash, "foo() { _xinetd_services; }; foo; unset foo")
+ assert_bash_exec(
+ bash, "foo() { _comp_compgen_xinetd_services; }; foo; unset -f foo"
+ )
def test_basic(self, bash):
output = assert_bash_exec(
bash,
- "foo() { local BASHCOMP_XINETDDIR=$PWD/shared/bin;unset COMPREPLY; "
- '_xinetd_services; printf "%s\\n" "${COMPREPLY[@]}"; }; foo; unset foo',
+ "foo() { local _comp__test_xinetd_dir=$PWD/shared/bin; unset -v COMPREPLY; "
+ '_comp_compgen_xinetd_services; printf "%s\\n" "${COMPREPLY[@]}"; }; foo; unset -f foo',
want_output=True,
)
assert sorted(output.split()) == ["arp", "ifconfig"]