diff options
Diffstat (limited to 'test/t/unit')
-rw-r--r-- | test/t/unit/Makefile.am | 24 | ||||
-rw-r--r-- | test/t/unit/test_unit_count_args.py | 66 | ||||
-rw-r--r-- | test/t/unit/test_unit_expand.py | 31 | ||||
-rw-r--r-- | test/t/unit/test_unit_expand_tilde_by_ref.py | 46 | ||||
-rw-r--r-- | test/t/unit/test_unit_filedir.py | 235 | ||||
-rw-r--r-- | test/t/unit/test_unit_find_unique_completion_pair.py | 55 | ||||
-rw-r--r-- | test/t/unit/test_unit_get_comp_words_by_ref.py | 260 | ||||
-rw-r--r-- | test/t/unit/test_unit_get_cword.py | 154 | ||||
-rw-r--r-- | test/t/unit/test_unit_init_completion.py | 34 | ||||
-rw-r--r-- | test/t/unit/test_unit_ip_addresses.py | 49 | ||||
-rw-r--r-- | test/t/unit/test_unit_known_hosts_real.py | 158 | ||||
-rw-r--r-- | test/t/unit/test_unit_longopt.py | 52 | ||||
-rw-r--r-- | test/t/unit/test_unit_parse_help.py | 183 | ||||
-rw-r--r-- | test/t/unit/test_unit_parse_usage.py | 69 | ||||
-rw-r--r-- | test/t/unit/test_unit_quote.py | 36 | ||||
-rw-r--r-- | test/t/unit/test_unit_quote_readline.py | 15 | ||||
-rw-r--r-- | test/t/unit/test_unit_tilde.py | 42 | ||||
-rw-r--r-- | test/t/unit/test_unit_variables.py | 41 | ||||
-rw-r--r-- | test/t/unit/test_unit_xinetd_services.py | 22 |
19 files changed, 1572 insertions, 0 deletions
diff --git a/test/t/unit/Makefile.am b/test/t/unit/Makefile.am new file mode 100644 index 0000000..3eb652a --- /dev/null +++ b/test/t/unit/Makefile.am @@ -0,0 +1,24 @@ +EXTRA_DIST = \ + test_unit_count_args.py \ + test_unit_expand.py \ + test_unit_expand_tilde_by_ref.py \ + test_unit_filedir.py \ + test_unit_find_unique_completion_pair.py \ + test_unit_get_comp_words_by_ref.py \ + test_unit_get_cword.py \ + test_unit_init_completion.py \ + test_unit_ip_addresses.py \ + test_unit_known_hosts_real.py \ + test_unit_longopt.py \ + test_unit_parse_help.py \ + test_unit_parse_usage.py \ + test_unit_quote.py \ + test_unit_quote_readline.py \ + test_unit_tilde.py \ + test_unit_variables.py \ + test_unit_xinetd_services.py + +all: + +clean-local: + $(RM) -R __pycache__ diff --git a/test/t/unit/test_unit_count_args.py b/test/t/unit/test_unit_count_args.py new file mode 100644 index 0000000..56bce2c --- /dev/null +++ b/test/t/unit/test_unit_count_args.py @@ -0,0 +1,66 @@ +import pytest + +from conftest import TestUnitBase, assert_bash_exec + + +@pytest.mark.bashcomp( + cmd=None, ignore_env=r"^[+-](args|COMP_(WORDS|CWORD|LINE|POINT))=" +) +class TestUnitCountArgs(TestUnitBase): + def _test(self, *args, **kwargs): + return self._test_unit("_count_args %s; echo $args", *args, **kwargs) + + def test_1(self, bash): + assert_bash_exec(bash, "COMP_CWORD= _count_args >/dev/null") + + def test_2(self, bash): + """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): + """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): + """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): + """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): + """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): + """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)"' + ) + assert output == "2" + + def test_8(self, bash): + """a -b -c d e| with -c arg excluded + 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]"', + ) + assert output == "2" + + def test_9(self, bash): + """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"' + ) + assert output == "3" diff --git a/test/t/unit/test_unit_expand.py b/test/t/unit/test_unit_expand.py new file mode 100644 index 0000000..d2a3ebc --- /dev/null +++ b/test/t/unit/test_unit_expand.py @@ -0,0 +1,31 @@ +import pytest + +from conftest import assert_bash_exec + + +@pytest.mark.bashcomp(cmd=None, ignore_env=r"^[+-](cur|COMPREPLY)=") +class TestUnitExpand: + def test_1(self, bash): + assert_bash_exec(bash, "_expand >/dev/null") + + def test_2(self, bash): + """Test environment non-pollution, detected at teardown.""" + assert_bash_exec(bash, "foo() { _expand; }; foo; unset 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, + 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, + want_output=True, + ) + assert output.strip() == "%s/a" % home diff --git a/test/t/unit/test_unit_expand_tilde_by_ref.py b/test/t/unit/test_unit_expand_tilde_by_ref.py new file mode 100644 index 0000000..17bdedf --- /dev/null +++ b/test/t/unit/test_unit_expand_tilde_by_ref.py @@ -0,0 +1,46 @@ +import pytest + +from conftest import assert_bash_exec + + +@pytest.mark.bashcomp(cmd=None, ignore_env=r"^[+-]var=") +class TestUnitExpandTildeByRef: + def test_1(self, bash): + 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', + ) + + @pytest.mark.parametrize("plain_tilde", (True, False)) + @pytest.mark.parametrize( + "suffix_expanded", + ( + ("", True), + ("/foo", True), + (r"/\$HOME", True), + ("/a b", True), + ("/*", True), + (";echo hello", False), + ("/a;echo hello", True), + ), + ) + def test_expand(self, bash, user_home, plain_tilde, suffix_expanded): + user, home = user_home + suffix, expanded = suffix_expanded + if plain_tilde: + user = "" + if not suffix or not expanded: + home = "~" + elif not expanded: + home = "~%s" % user + output = assert_bash_exec( + bash, + r'var="~%s%s"; __expand_tilde_by_ref var; printf "%%s\n" "$var"' + % (user, suffix), + want_output=True, + ) + assert output.strip() == "%s%s" % (home, suffix.replace(r"\$", "$"),) diff --git a/test/t/unit/test_unit_filedir.py b/test/t/unit/test_unit_filedir.py new file mode 100644 index 0000000..b847efc --- /dev/null +++ b/test/t/unit/test_unit_filedir.py @@ -0,0 +1,235 @@ +import os +import shutil +import sys +import tempfile +from pathlib import Path + +import pytest + +from conftest import assert_bash_exec, assert_complete + + +@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=") +class TestUnitFiledir: + @pytest.fixture(scope="class") + def functions(self, request, bash): + assert_bash_exec( + bash, + "_f() { local cur=$(_get_cword); unset COMPREPLY; _filedir; }; " + "complete -F _f f; " + "complete -F _f -o filenames f2", + ) + assert_bash_exec( + bash, + "_g() { local cur=$(_get_cword); unset COMPREPLY; _filedir e1; }; " + "complete -F _g g", + ) + assert_bash_exec( + bash, + "_fd() { local cur=$(_get_cword); unset COMPREPLY; _filedir -d; };" + "complete -F _fd fd", + ) + + @pytest.fixture(scope="class") + def non_windows_testdir(self, request, bash): + if sys.platform.startswith("win"): + pytest.skip("Filenames not allowed on Windows") + tempdir = Path(tempfile.mkdtemp(prefix="bash-completion_filedir")) + request.addfinalizer(lambda: shutil.rmtree(str(tempdir))) + subdir = tempdir / 'a"b' + subdir.mkdir() + (subdir / "d").touch() + subdir = tempdir / "a*b" + subdir.mkdir() + (subdir / "j").touch() + subdir = tempdir / r"a\b" + subdir.mkdir() + (subdir / "g").touch() + return tempdir + + @pytest.fixture(scope="class") + def utf8_ctype(self, bash): + # TODO: this likely is not the right thing to do. Instead we should + # grab the setting from the running shell, possibly eval $(locale) + # in a subshell and grab LC_CTYPE from there. That doesn't seem to work + # either everywhere though. + lc_ctype = os.environ.get("LC_CTYPE", "") + if "UTF-8" not in lc_ctype: + pytest.skip("Applicable only in LC_CTYPE=UTF-8 setups") + return lc_ctype + + def test_1(self, bash): + assert_bash_exec(bash, "_filedir >/dev/null") + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_2(self, bash, functions, funcname): + completion = assert_complete(bash, "%s ab/" % funcname, cwd="_filedir") + assert completion == "e" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_3(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s a\ b/" % funcname, cwd="_filedir" + ) + assert completion == "i" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_4(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s a\'b/" % funcname, cwd="_filedir" + ) + assert completion == "c" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_5(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s a\&b/" % funcname, cwd="_filedir" + ) + assert completion == "f" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_6(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s a\$" % funcname, cwd="_filedir" + ) + assert completion == "b/" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_7(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s 'ab/" % funcname, cwd="_filedir" + ) + assert completion == "e'" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_8(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s 'a b/" % funcname, cwd="_filedir" + ) + assert completion == "i'" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_9(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s 'a$b/" % funcname, cwd="_filedir" + ) + assert completion == "h'" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_10(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s 'a&b/" % funcname, cwd="_filedir" + ) + assert completion == "f'" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_11(self, bash, functions, funcname): + completion = assert_complete( + bash, r'%s "ab/' % funcname, cwd="_filedir" + ) + assert completion == 'e"' + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_12(self, bash, functions, funcname): + completion = assert_complete( + bash, r'%s "a b/' % funcname, cwd="_filedir" + ) + assert completion == 'i"' + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_13(self, bash, functions, funcname): + completion = assert_complete( + bash, "%s \"a'b/" % funcname, cwd="_filedir" + ) + assert completion == 'c"' + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_14(self, bash, functions, funcname): + completion = assert_complete( + bash, '%s "a&b/' % funcname, cwd="_filedir" + ) + assert completion == 'f"' + + @pytest.mark.complete(r"fd a\ ", cwd="_filedir") + def test_15(self, functions, completion): + assert completion == "b/" + + @pytest.mark.complete("g ", cwd="_filedir/ext") + def test_16(self, functions, completion): + assert completion == sorted("ee.e1 foo/ gg.e1 ii.E1".split()) + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_17(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s a\$b/" % funcname, cwd="_filedir" + ) + assert completion == "h" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_18(self, bash, functions, funcname): + completion = assert_complete( + bash, r"%s \[x" % funcname, cwd="_filedir/brackets" + ) + assert completion == r"\]" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_19(self, bash, functions, funcname, non_windows_testdir): + completion = assert_complete( + bash, '%s a\\"b/' % funcname, cwd=non_windows_testdir + ) + assert completion == "d" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_20(self, bash, functions, funcname, non_windows_testdir): + completion = assert_complete( + bash, r"%s a\\b/" % funcname, cwd=non_windows_testdir + ) + assert completion == "g" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_21(self, bash, functions, funcname, non_windows_testdir): + completion = assert_complete( + bash, "%s 'a\"b/" % funcname, cwd=non_windows_testdir + ) + assert completion == "d'" + + @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) + ) + assert completion == "g'" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_23(self, bash, functions, funcname, non_windows_testdir): + completion = assert_complete( + bash, r'%s "a\"b/' % funcname, cwd=non_windows_testdir + ) + assert completion == 'd"' + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_24(self, bash, functions, funcname, non_windows_testdir): + completion = assert_complete( + bash, r'%s "a\\b/' % funcname, cwd=non_windows_testdir + ) + assert completion == 'g"' + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_25(self, bash, functions, funcname): + completion = assert_complete( + bash, r'%s "a\b/' % funcname, cwd="_filedir" + ) + assert completion == '\b\b\bb/e"' + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_26(self, bash, functions, funcname): + completion = assert_complete( + bash, r'%s "a\$b/' % funcname, cwd="_filedir" + ) + assert completion == 'h"' + + @pytest.mark.xfail(reason="TODO: non-ASCII issues with test suite?") + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_27(self, bash, functions, funcname, utf8_ctype): + completion = assert_complete(bash, "%s aé/" % funcname, cwd="_filedir") + assert completion == "g" diff --git a/test/t/unit/test_unit_find_unique_completion_pair.py b/test/t/unit/test_unit_find_unique_completion_pair.py new file mode 100644 index 0000000..25cf9d3 --- /dev/null +++ b/test/t/unit/test_unit_find_unique_completion_pair.py @@ -0,0 +1,55 @@ +import pytest + +from conftest import find_unique_completion_pair + + +@pytest.mark.bashcomp(cmd=None) +class TestUnitFindUniqueCompletionPair: + def _test(self, inp: str, exp: str) -> None: + res = find_unique_completion_pair(inp.split()) + if exp: + part, cont = exp.split() + assert res == (part, part + cont) + else: + assert not exp + + def test_1(self): + self._test("a", "") + + def test_2(self): + self._test("ab", "a b") + + def test_3(self): + self._test("ab ab ab", "a b") + + def test_4(self): + self._test("a ab abcd abc", "") + + def test_5(self): + self._test("user1 user2", "") + + def test_6(self): + self._test("root username1 username2", "ro ot") + + def test_7(self): + self._test("root username21 username2", "ro ot") + + def test_8(self): + self._test( + "long_user_name lang_user_name long_usor_name", "lang_us er_name" + ) + + def test_9(self): + self._test( + "lang_user_name1 long_user_name lang_user_name long_usor_name", + "long_use r_name", + ) + + def test_10(self): + self._test("root username", "user name") + + def test_11(self): + self._test("a aladin", "ala din") + + def test_12(self): + self._test("ala aladin", "alad in") diff --git a/test/t/unit/test_unit_get_comp_words_by_ref.py b/test/t/unit/test_unit_get_comp_words_by_ref.py new file mode 100644 index 0000000..b6498fa --- /dev/null +++ b/test/t/unit/test_unit_get_comp_words_by_ref.py @@ -0,0 +1,260 @@ +import pytest + +from conftest import TestUnitBase, assert_bash_exec + + +@pytest.mark.bashcomp( + cmd=None, + ignore_env=r"^(\+(words|cword|cur|prev)|[+-]COMP_(WORDS|CWORD|LINE|POINT))=", +) +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-}", + bash, + *args, + **kwargs + ) + return output.strip() + + def test_1(self, bash): + assert_bash_exec( + bash, + "COMP_WORDS=() COMP_CWORD= COMP_POINT= COMP_LINE= " + "_get_comp_words_by_ref cur >/dev/null", + ) + + def test_2(self, bash): + """a b|""" + output = self._test(bash, "(a b)", 1, "a b", 3) + assert output == "b,a" + + def test_3(self, bash): + """a |""" + output = self._test(bash, "(a)", 1, "a ", 2) + assert output == ",a" + + def test_4(self, bash): + """|a""" + output = self._test(bash, "(a)", 0, "a", 0) + assert output == "," + + def test_5(self, bash): + """|a """ + output = self._test(bash, "(a)", 0, "a ", 0) + assert output == "," + + def test_6(self, bash): + """ | a """ + output = self._test(bash, "(a)", 0, " a ", 1) + assert output.strip() == "," + + def test_7(self, bash): + """a b |""" + output = self._test(bash, "(a b '')", 2, "a b ", 4) + assert output == ",b" + + def test_8(self, bash): + """a b | with WORDBREAKS -= :""" + output = self._test(bash, "(a b '')", 2, "a b ", 4, arg="-n :") + assert output == ",b" + + def test_9(self, bash): + """a b|c""" + output = self._test(bash, "(a bc)", 1, "a bc", 3) + assert output == "b,a" + + def test_10(self, bash): + """a | b""" + output = self._test(bash, "(a b)", 1, "a b", 2) + assert output == ",a" + + def test_11(self, bash): + r"""a b\ c|""" + output = self._test(bash, r"(a 'b\ c')", 1, r"a b\ c", 6) + assert output == r"b\ c,a" + + def test_12(self, bash): + r"""a\ b a\ b|""" + output = self._test(bash, r"('a\ b' 'a\ b')", 1, r"a\ b a\ b", 9) + assert output == r"a\ b,a\ b" + + def test_13(self, bash): + r"""a b\| c""" + output = self._test(bash, r"(a 'b\ c')", 1, r"a b\ c", 4) + assert output == r"b\,a" + + def test_14(self, bash): + r"""a "b\|""" + output = self._test(bash, "(a '\"b')", 1, 'a "b\\', 5) + assert output == r'"b\,a' + + def test_15(self, bash): + """a 'b c|""" + output = self._test(bash, '(a "\'b c")', 1, "a 'b c", 6) + assert output == "'b c,a" + + def test_16(self, bash): + """a "b c|""" + output = self._test(bash, r'(a "\"b c")', 1, 'a "b c', 6) + assert output == '"b c,a' + + def test_17(self, bash): + """a b:c| with WORDBREAKS += :""" + assert_bash_exec(bash, "add_comp_wordbreak_char :") + output = self._test(bash, "(a b : c)", 3, "a b:c", 5) + assert output == "c,:" + + def test_18(self, bash): + """a b:c| with WORDBREAKS -= :""" + output = self._test(bash, "(a b : c)", 3, "a b:c", 5, arg="-n :") + assert output == "b:c,a" + + def test_19(self, bash): + """a b c:| with WORDBREAKS -= :""" + output = self._test(bash, "(a b c :)", 3, "a b c:", 6, arg="-n :") + assert output == "c:,b" + + def test_20(self, bash): + r"""a b:c | with WORDBREAKS -= :""" + output = self._test(bash, "(a b : c '')", 4, "a b:c ", 6, arg="-n :") + assert output == ",b:c" + + def test_21(self, bash): + """a :| with WORDBREAKS -= :""" + output = self._test(bash, "(a :)", 1, "a :", 3, arg="-n :") + assert output == ":,a" + + def test_22(self, bash): + """a b::| with WORDBREAKS -= :""" + output = self._test(bash, "(a b ::)", 2, "a b::", 5, arg="-n :") + assert output == "b::,a" + + 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. + """ + output = self._test(bash, "(a -n)", 1, "a -n", 4) + assert output == "-n,a" + + def test_24(self, bash): + """a b>c|""" + output = self._test(bash, r"(a b \> c)", 3, "a b>c", 5) + assert output.startswith("c,") + + def test_25(self, bash): + """a b=c|""" + output = self._test(bash, "(a b = c)", 3, "a b=c", 5) + assert output.startswith("c,") + + def test_26(self, bash): + """a *|""" + output = self._test(bash, r"(a \*)", 1, "a *", 4) + assert output == "*,a" + + def test_27(self, bash): + """a $(b c|""" + output = self._test(bash, "(a '$(b c')", 1, "a $(b c", 7) + assert output == "$(b c,a" + + def test_28(self, bash): + r"""a $(b c\ d|""" + output = self._test(bash, r"(a '$(b c\ d')", 1, r"a $(b c\ d", 10) + assert output == r"$(b c\ d,a" + + def test_29(self, bash): + """a 'b&c|""" + output = self._test(bash, '(a "\'b&c")', 1, "a 'b&c", 6) + assert output == "'b&c,a" + + def test_30(self, bash): + """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; " + 'echo "${words[@]}",$cword,$cur,$prev', + bash, + "(a b)", + 1, + "a b", + 3, + ) + assert output == "a b,1,b,a" + + def test_31(self, bash): + """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; " + 'echo $cur2,$prev2,"${words2[@]}",$cword2', + bash, + "(a b)", + 1, + "a b", + 3, + ) + assert output == "b,a,a b,1" + assert_bash_exec(bash, "unset words2 cword2 cur2 prev2") + + def test_32(self, bash): + """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[@]}"', + bash, + "(a b : c)", + 3, + "a b : c", + 7, + ) + assert output == "a b : c" + + def test_33(self, bash): + """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[@]}"', + bash, + "(a b : c)", + 3, + "a b: c", + 6, + ) + assert output == "a b: c" + + def test_34(self, bash): + """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[@]}"', + bash, + "(a b : c)", + 3, + "a b :c", + 6, + ) + assert output == "a b :c" + + def test_35(self, bash): + 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[@]}"', + bash, + "(a 'b ' : c)", + 3, + r"a b\ :c", + 7, + ) + assert output == "a b :c" + + 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 + ) + ex.match("dummy.* unknown argument") diff --git a/test/t/unit/test_unit_get_cword.py b/test/t/unit/test_unit_get_cword.py new file mode 100644 index 0000000..0b56d16 --- /dev/null +++ b/test/t/unit/test_unit_get_cword.py @@ -0,0 +1,154 @@ +import pexpect +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)=" +) +class TestUnitGetCword(TestUnitBase): + def _test(self, *args, **kwargs): + return self._test_unit("_get_cword %s; echo", *args, **kwargs) + + def test_1(self, bash): + assert_bash_exec( + bash, + "COMP_WORDS=() COMP_CWORD= COMP_LINE= COMP_POINT= " + "_get_cword >/dev/null", + ) + + def test_2(self, bash): + """a b| should return b""" + output = self._test(bash, "(a b)", 1, "a b", 3) + assert output == "b" + + def test_3(self, bash): + """a | should return nothing""" + output = self._test(bash, "(a)", 1, "a ", 2) + assert not output + + def test_4(self, bash): + """a b | should return nothing""" + output = self._test(bash, "(a b '')", 2, "a b ", 4) + assert not output + + def test_5(self, bash): + """a b | with WORDBREAKS -= : should return nothing""" + output = self._test(bash, "(a b '')", 2, "a b ", 4, arg=":") + assert not output + + def test_6(self, bash): + """a b|c should return b""" + output = self._test(bash, "(a bc)", 1, "a bc", 3) + assert output == "b" + + def test_7(self, bash): + r"""a b\ c| should return b\ c""" + output = self._test(bash, r"(a 'b\ c')", 1, r"a b\ c", 6) + assert output == r"b\ c" + + def test_8(self, bash): + r"""a b\| c should return b\ """ + 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\ """ + output = self._test(bash, "(a '\"b\\')", 1, r"a \"b\\", 5) + assert output == '"b\\' + + def test_10(self, bash): + r"""a 'b c| should return 'b c""" + output = self._test(bash, '(a "\'b c")', 1, "a 'b c", 6) + assert output == "'b c" + + def test_11(self, bash): + r"""a "b c| should return "b c""" + output = self._test(bash, "(a '\"b c')", 1, 'a "b c', 6) + assert output == '"b c' + + def test_12(self, bash): + """a b:c| with WORDBREAKS += : should return c""" + assert_bash_exec(bash, "add_comp_wordbreak_char :") + output = self._test(bash, "(a b : c)", 3, "a b:c", 5) + assert output == "c" + + def test_13(self, bash): + """a b:c| with WORDBREAKS -= : should return b:c""" + assert_bash_exec(bash, "add_comp_wordbreak_char :") + output = self._test(bash, "(a b : c)", 3, "a b:c", 5, arg=":") + assert output == "b:c" + + def test_14(self, bash): + """a b c:| 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:" + + def test_15(self, bash): + """a :| with WORDBREAKS -= : should return :""" + assert_bash_exec(bash, "add_comp_wordbreak_char :") + output = self._test(bash, "(a :)", 1, "a :", 3, arg=":") + assert output == ":" + + def test_16(self, bash): + """a b::| with WORDBREAKS -= : should return b::""" + assert_bash_exec(bash, "add_comp_wordbreak_char :") + output = self._test(bash, "(a b::)", 1, "a b::", 5, arg=":") + assert output == "b::" + + def test_17(self, bash): + """ + 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 + be returned. + """ + output = self._test(bash, "(a -n)", 1, "a -n", 4) + assert output == "-n" + + def test_18(self, bash): + """a b>c| should return c""" + output = self._test(bash, r"(a b \> c)", 3, "a b>c", 5) + assert output == "c" + + def test_19(self, bash): + """a b=c| should return c""" + output = self._test(bash, "(a b = c)", 3, "a b=c", 5) + assert output == "c" + + def test_20(self, bash): + """a *| should return *""" + output = self._test(bash, r"(a \*)", 1, "a *", 4) + assert output == "*" + + def test_21(self, bash): + """a $(b c| should return $(b c""" + output = self._test(bash, r"(a '$(b c')", 1, "a $(b c", 7) + assert output == "$(b c" + + def test_22(self, bash): + r"""a $(b c\ d| should return $(b c\ d""" + output = self._test(bash, r"(a '$(b c\ d')", 1, r"a $(b c\ d", 10) + assert output == r"$(b c\ d" + + def test_23(self, bash): + """a 'b&c| should return 'b&c""" + output = self._test(bash, '(a "\'b&c")', 1, "a 'b&c", 6) + assert output == "'b&c" + + @pytest.mark.xfail(reason="TODO: non-ASCII issues with test suite?") + def test_24(self, bash): + """Index shouldn't drop below 0""" + bash.send("scp ääää§ se\t\r\n") + got = bash.expect_exact( + [ + "index: substring expression < 0", + PS1, + pexpect.EOF, + pexpect.TIMEOUT, + ] + ) + assert got == 1 diff --git a/test/t/unit/test_unit_init_completion.py b/test/t/unit/test_unit_init_completion.py new file mode 100644 index 0000000..64a5a79 --- /dev/null +++ b/test/t/unit/test_unit_init_completion.py @@ -0,0 +1,34 @@ +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_ip_addresses.py b/test/t/unit/test_unit_ip_addresses.py new file mode 100644 index 0000000..8120c88 --- /dev/null +++ b/test/t/unit/test_unit_ip_addresses.py @@ -0,0 +1,49 @@ +import pytest + +from conftest import assert_bash_exec, in_container + + +@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=") +class TestUnitIpAddresses: + @pytest.fixture(scope="class") + def functions(self, request, bash): + assert_bash_exec( + bash, + "_ia() { local cur=$(_get_cword);unset COMPREPLY;" + "_ip_addresses; }", + ) + assert_bash_exec(bash, "complete -F _ia ia") + assert_bash_exec( + bash, + "_iaa() { local cur=$(_get_cword);unset COMPREPLY;" + "_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; }", + ) + assert_bash_exec(bash, "complete -F _ia6 ia6") + + def test_1(self, bash): + assert_bash_exec(bash, "_ip_addresses") + + @pytest.mark.complete("iaa ") + def test_2(self, functions, completion): + """_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.""" + 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.""" + 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_real.py new file mode 100644 index 0000000..ac5205e --- /dev/null +++ b/test/t/unit/test_unit_known_hosts_real.py @@ -0,0 +1,158 @@ +from itertools import chain + +import pytest + +from conftest import assert_bash_exec + + +@pytest.mark.bashcomp( + cmd=None, + ignore_env="^[+-](COMP(REPLY|_KNOWN_HOSTS_WITH_HOSTFILE)|OLDHOME)=", +) +class TestUnitKnownHostsReal: + @pytest.mark.parametrize( + "prefix,colon_flag,hostfile", + [("", "", True), ("", "", False), ("user@", "c", True)], + ) + def test_basic( + self, bash, hosts, avahi_hosts, prefix, colon_flag, hostfile + ): + expected = ( + "%s%s%s" % (prefix, x, ":" if colon_flag else "") + for x in chain( + hosts if hostfile else avahi_hosts, + # fixtures/_known_hosts_real/config + "gee hus jar #not-a-comment".split(), + # fixtures/_known_hosts_real/known_hosts + ( + "doo", + "ike", + "jub", + "10.0.0.1", + "kyl", + "100.0.0.2", + "10.10.0.3", + "blah", + "fd00:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:5555", + "fe80::123:0xff:dead:beef%eth0", + "1111:2222:3333:4444:5555:6666:xxxx:abab", + "11xx:2222:3333:4444:5555:6666:xxxx:abab", + "::42", + ), + ) + ) + assert_bash_exec( + bash, + "unset -v COMP_KNOWN_HOSTS_WITH_HOSTFILE" + if hostfile + else "COMP_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' + % (colon_flag, prefix), + want_output=True, + ) + assert sorted(set(output.split())) == sorted(expected) + + @pytest.mark.parametrize( + "family,result", + ( + ("4", "127.0.0.1 localhost"), + ("6", "::1 localhost"), + ("46", "localhost"), + ), + ) + def test_ip_filtering(self, bash, family, result): + assert_bash_exec( + bash, "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE" + ) + output = assert_bash_exec( + bash, + "COMP_KNOWN_HOSTS_WITH_HOSTFILE= " + "_known_hosts_real -%sF _known_hosts_real/localhost_config ''; " + r'printf "%%s\n" "${COMPREPLY[@]}"' % family, + want_output=True, + ) + assert sorted(set(output.strip().split())) == sorted(result.split()) + + def test_consecutive_spaces(self, bash, hosts): + expected = hosts.copy() + # fixtures/_known_hosts_real/spaced conf + expected.extend("gee hus #not-a-comment".split()) + # fixtures/_known_hosts_real/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' ''; " + r'printf "%s\n" "${COMPREPLY[@]}"', + want_output=True, + ) + assert sorted(set(output.strip().split())) == sorted(expected) + + def test_files_starting_with_tilde(self, bash, hosts): + expected = hosts.copy() + # fixtures/_known_hosts_real/known_hosts2 + expected.extend("two two2 two3 two4".split()) + # fixtures/_known_hosts_real/known_hosts3 + expected.append("three") + # fixtures/_known_hosts_real/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"') + 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 + expected.append("recursion") + # fixtures/_known_hosts_real/.ssh/config_relative_path + expected.append("relative_path") + # fixtures/_known_hosts_real/.ssh/config_asterisk_* + expected.extend("asterisk_1 asterisk_2".split()) + # fixtures/_known_hosts_real/.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"') + 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"') + 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_longopt.py b/test/t/unit/test_unit_longopt.py new file mode 100644 index 0000000..c5488e3 --- /dev/null +++ b/test/t/unit/test_unit_longopt.py @@ -0,0 +1,52 @@ +# Based on work by Stephen Gildea, October 2010. + +import pytest + +from conftest import assert_bash_exec + + +@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=") +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, "_various() { cat _longopt/various.txt; }") + assert_bash_exec(bash, "complete -F _longopt _various") + + @pytest.mark.complete("_grephelp --") + def test_1(self, functions, completion): + """First long option should be included""" + assert completion + assert all( + x in completion for x in "--quiet --recursive --text".split() + ) + + @pytest.mark.complete("_grephelp -") + def test_2(self, functions, completion): + """Only long options should be included""" + assert completion + assert all(x.startswith("--") for x in completion) + + @pytest.mark.complete("_grephelp --") + def test_3(self, functions, completion): + """Should have both ones ending with a = and ones not""" + assert completion + assert any(x.endswith("=") for x in completion) + assert any(not x.endswith("=") for x in completion) + + @pytest.mark.complete("_various --") + def test_no_dashdashdash(self, functions, completion): + assert all(not x.startswith("---") for x in completion) + + @pytest.mark.complete("_various --") + def test_no_trailingdash(self, functions, completion): + assert all(not x.endswith("-") for x in completion) + + @pytest.mark.complete("_various --") + def test_underscore(self, functions, completion): + assert "--foo_bar" in completion + + @pytest.mark.complete("_various --") + def test_equals(self, functions, completion): + assert "--foo=" in completion diff --git a/test/t/unit/test_unit_parse_help.py b/test/t/unit/test_unit_parse_help.py new file mode 100644 index 0000000..4a02155 --- /dev/null +++ b/test/t/unit/test_unit_parse_help.py @@ -0,0 +1,183 @@ +# Based on work by Stephen Gildea, October 2010. + +import pytest + +from conftest import assert_bash_exec + + +@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") + 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") + assert not output + + def test_3(self, bash): + assert_bash_exec(bash, "fn() { echo 'internal-dash'; }") + output = assert_bash_exec(bash, "_parse_help fn") + 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") + assert not output + + def test_5(self, bash): + assert_bash_exec(bash, "fn() { echo '-one dash'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "-one".split() + + def test_6(self, bash): + assert_bash_exec(bash, "fn() { echo ' -space dash'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "-space".split() + + def test_7(self, bash): + assert_bash_exec(bash, "fn() { echo '-one -two dashes'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "-one".split() + + def test_8(self, bash): + assert_bash_exec(bash, "fn() { echo '-one,-t dashes'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "-one".split() + + def test_9(self, bash): + assert_bash_exec(bash, "fn() { echo '-one dash-inside'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "-one".split() + + def test_10(self, bash): + """Test value not included in completion.""" + assert_bash_exec(bash, "fn() { echo '--long-arg=value'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--long-arg=".split() + + def test_11(self, bash): + """Test -value not seen as option.""" + assert_bash_exec(bash, "fn() { echo '--long-arg=-value'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--long-arg=".split() + + def test_12(self, bash): + assert_bash_exec(bash, "fn() { echo '--long-arg=-value,--opt2=val'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--long-arg=".split() + + def test_13(self, bash): + assert_bash_exec(bash, "fn() { echo '-m,--mirror'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--mirror".split() + + def test_14(self, bash): + assert_bash_exec(bash, "fn() { echo '-T/--upload-file'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--upload-file".split() + + def test_15(self, bash): + assert_bash_exec(bash, "fn() { echo '-T|--upload-file'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--upload-file".split() + + def test_16(self, bash): + assert_bash_exec(bash, "fn() { echo '-f, -F, --foo'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--foo".split() + + def test_17(self, bash): + assert_bash_exec(bash, "fn() { echo '--foo[=bar]'; }") + 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) + assert output.split() == "--foo=".split() + + def test_19(self, bash): + assert_bash_exec(bash, "fn() { echo '--foo={bar,quux}'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--foo=".split() + + def test_20(self, bash): + assert_bash_exec(bash, "fn() { echo '--[no]foo'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--foo --nofoo".split() + + def test_21(self, bash): + assert_bash_exec(bash, "fn() { echo '--[no-]bar[=quux]'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--bar --no-bar".split() + + def test_22(self, bash): + assert_bash_exec(bash, "fn() { echo '--[no-]bar=quux'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--bar= --no-bar=".split() + + def test_23(self, bash): + assert_bash_exec(bash, "fn() { echo '--[dont-]foo'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--foo --dont-foo".split() + + def test_24(self, bash): + assert_bash_exec(bash, "fn() { echo '-[dont]x --[dont]yy'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--yy --dontyy".split() + + def test_25(self, bash): + assert_bash_exec(bash, "fn() { echo '-f FOO, --foo=FOO'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--foo=".split() + + def test_26(self, bash): + assert_bash_exec(bash, "fn() { echo '-f [FOO], --foo[=FOO]'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--foo".split() + + def test_27(self, bash): + assert_bash_exec(bash, "fn() { echo '--foo.'; }") + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--foo".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) + assert output.split() == "--foo".split() + + def test_29(self, bash): + """Test parsing from stdin.""" + output = assert_bash_exec( + bash, "echo '-f or --foo' | _parse_help -", want_output=True + ) + assert output.split() == "--foo".split() + + def test_30(self, bash): + """More than two dashes should not be treated as options.""" + assert_bash_exec( + bash, r"fn() { printf '%s\n' $'----\n---foo\n----- bar'; }" + ) + output = assert_bash_exec(bash, "_parse_help fn") + assert not output + + def test_31(self, bash): + assert_bash_exec( + bash, + r"fn() { printf '%s\n' " + r"'-F ERROR_FORMAT, --error-format ERROR_FORMAT'; }", + ) + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--error-format".split() + + def test_32(self, bash): + assert_bash_exec( + bash, + r"fn() { printf '%s\n' " + r"'-e CODE1,CODE2.. --exclude=CODE1,CODE2..'; }", + ) + output = assert_bash_exec(bash, "_parse_help fn", want_output=True) + assert output.split() == "--exclude=".split() diff --git a/test/t/unit/test_unit_parse_usage.py b/test/t/unit/test_unit_parse_usage.py new file mode 100644 index 0000000..f0cb711 --- /dev/null +++ b/test/t/unit/test_unit_parse_usage.py @@ -0,0 +1,69 @@ +import pytest + +from conftest import assert_bash_exec + + +@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") + 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") + assert not output + + def test_3(self, bash): + assert_bash_exec(bash, "fn() { echo 'foo [-f]'; }") + output = assert_bash_exec(bash, "_parse_usage fn", want_output=True) + assert output.split() == "-f".split() + + def test_4(self, bash): + assert_bash_exec(bash, "fn() { echo 'bar [-aBcD] [-e X]'; }") + output = assert_bash_exec(bash, "_parse_usage fn", want_output=True) + assert output.split() == "-a -B -c -D -e".split() + + def test_5(self, bash): + assert_bash_exec(bash, "fn() { echo '[-[XyZ]] [--long=arg]'; }") + output = assert_bash_exec(bash, "_parse_usage fn", want_output=True) + assert output.split() == "-X -y -Z --long=".split() + + def test_6(self, bash): + assert_bash_exec(bash, "fn() { echo '[-s|--long]'; }") + output = assert_bash_exec(bash, "_parse_usage fn", want_output=True) + assert output.split() == "--long".split() + + def test_7(self, bash): + assert_bash_exec(bash, "fn() { echo '[-s, --long=arg]'; }") + output = assert_bash_exec(bash, "_parse_usage fn", want_output=True) + assert output.split() == "--long=".split() + + def test_8(self, bash): + assert_bash_exec(bash, "fn() { echo '[--long/-s] [-S/--longer]'; }") + output = assert_bash_exec(bash, "_parse_usage fn", want_output=True) + assert output.split() == "--long --longer".split() + + def test_9(self, bash): + assert_bash_exec(bash, "fn() { echo '[ -a ] [ -b foo ]'; }") + output = assert_bash_exec(bash, "_parse_usage fn", want_output=True) + assert output.split() == "-a -b".split() + + def test_10(self, bash): + assert_bash_exec(bash, "fn() { echo '[ -a | --aa ]'; }") + output = assert_bash_exec(bash, "_parse_usage fn", want_output=True) + assert output.split() == "--aa".split() + + def test_11(self, bash): + assert_bash_exec( + bash, "fn() { echo ----; echo ---foo; echo '----- bar'; }" + ) + output = assert_bash_exec(bash, "_parse_usage fn") + assert not output + + def test_12(self, bash): + output = assert_bash_exec( + bash, "echo '[-duh]' | _parse_usage -", want_output=True + ) + assert output.split() == "-d -u -h".split() diff --git a/test/t/unit/test_unit_quote.py b/test/t/unit/test_unit_quote.py new file mode 100644 index 0000000..b280bd6 --- /dev/null +++ b/test/t/unit/test_unit_quote.py @@ -0,0 +1,36 @@ +import pytest + +from conftest import TestUnitBase, assert_bash_exec + + +@pytest.mark.bashcomp(cmd=None) +class TestUnitQuote(TestUnitBase): + def test_1(self, bash): + output = assert_bash_exec( + bash, 'quote "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 + ) + assert output.strip() == "'a b'" + + def test_3(self, bash): + output = assert_bash_exec( + bash, 'quote " 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 + ) + 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 + ) + assert output.strip() == r"'a'\'''" diff --git a/test/t/unit/test_unit_quote_readline.py b/test/t/unit/test_unit_quote_readline.py new file mode 100644 index 0000000..e2b437e --- /dev/null +++ b/test/t/unit/test_unit_quote_readline.py @@ -0,0 +1,15 @@ +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_tilde.py b/test/t/unit/test_unit_tilde.py new file mode 100644 index 0000000..35a4e4c --- /dev/null +++ b/test/t/unit/test_unit_tilde.py @@ -0,0 +1,42 @@ +import pytest + +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") + + def test_2(self, bash): + """Test environment non-pollution, detected at teardown.""" + assert_bash_exec( + bash, 'foo() { local aa="~"; _tilde "$aa"; }; foo; unset foo' + ) + + def test_3(self, bash): + """Test for https://bugs.debian.org/766163""" + assert_bash_exec(bash, "_tilde ~-o") + + def _test_part_full(self, bash, part, full): + res = ( + assert_bash_exec( + bash, + '_tilde "~%s"; echo "${COMPREPLY[@]}"' % part, + want_output=True, + ) + .strip() + .split() + ) + assert res + assert res[0] == "~%s" % full + + def test_4(self, bash, part_full_user): + """~full should complete to ~full unmodified.""" + _, full = part_full_user + self._test_part_full(bash, full, full) + + def test_5(self, bash, part_full_user): + """~part should complete to ~full.""" + part, full = part_full_user + self._test_part_full(bash, part, full) diff --git a/test/t/unit/test_unit_variables.py b/test/t/unit/test_unit_variables.py new file mode 100644 index 0000000..d62bc4a --- /dev/null +++ b/test/t/unit/test_unit_variables.py @@ -0,0 +1,41 @@ +import pytest + +from conftest import assert_bash_exec + + +@pytest.mark.bashcomp(cmd=None, ignore_env=r"^[+-](___var|assoc[12])=") +class TestUnitVariables: + @pytest.fixture(scope="class") + def functions(self, request, bash): + assert_bash_exec(bash, "unset assoc1 && declare -A assoc1=([idx]=1)") + assert_bash_exec( + bash, "unset assoc2 && declare -A assoc2=([idx1]=1 [idx2]=2)" + ) + assert_bash_exec(bash, "unset ${!___v*} && declare ___var=''") + request.addfinalizer( + lambda: assert_bash_exec(bash, "unset ___var assoc1 assoc2") + ) + + @pytest.mark.complete(": $___v") + def test_simple_variable_name(self, functions, completion): + assert completion == "ar" + + @pytest.mark.complete(": ${assoc1[") + def test_single_array_index(self, functions, completion): + assert completion == "idx]}" + + @pytest.mark.complete(": ${assoc2[") + def test_multiple_array_indexes(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 == "}" + + @pytest.mark.complete(": ${assoc1[@") + def test_closing_brackets_after_at(self, functions, completion): + assert completion == "]}" + + @pytest.mark.complete(": ${#___v") + def test_hash_prefix(self, functions, completion): + assert completion == "ar}" diff --git a/test/t/unit/test_unit_xinetd_services.py b/test/t/unit/test_unit_xinetd_services.py new file mode 100644 index 0000000..7a90cb7 --- /dev/null +++ b/test/t/unit/test_unit_xinetd_services.py @@ -0,0 +1,22 @@ +import pytest + +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") + + def test_env_non_pollution(self, bash): + """Test environment non-pollution, detected at teardown.""" + assert_bash_exec(bash, "foo() { _xinetd_services; }; foo; unset 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', + want_output=True, + ) + assert sorted(output.split()) == ["arp", "ifconfig"] |