diff options
author | Gabriel F. T. Gomes <gabriel@inconstante.net.br> | 2020-08-03 18:43:13 -0300 |
---|---|---|
committer | Gabriel F. T. Gomes <gabriel@inconstante.net.br> | 2020-08-03 18:43:13 -0300 |
commit | 95623d39d6029ba78ec96ad5ea08e9ac12629b91 (patch) | |
tree | ea0fe36eb5e6f40e0a1f765d44c4b0c0b2bfb089 /test/t | |
parent | 019f3cc463db63abc6460f97deb488deec43840b (diff) | |
download | bash-completion-95623d39d6029ba78ec96ad5ea08e9ac12629b91.tar.gz |
New upstream version 2.11upstream/2.11upstream
Diffstat (limited to 'test/t')
101 files changed, 1675 insertions, 359 deletions
diff --git a/test/t/Makefile.am b/test/t/Makefile.am index 0ce46b12..801841fb 100644 --- a/test/t/Makefile.am +++ b/test/t/Makefile.am @@ -45,6 +45,7 @@ EXTRA_DIST = \ test_bind.py \ test_bison.py \ test_bk.py \ + test_bmake.py \ test_brctl.py \ test_btdownloadcurses_py.py \ test_btdownloadgui_py.py \ @@ -132,6 +133,7 @@ EXTRA_DIST = \ test_dot.py \ test_dpkg.py \ test_dpkg_deb.py \ + test_dpkg_query.py \ test_dpkg_reconfigure.py \ test_dpkg_source.py \ test_dropdb.py \ @@ -249,6 +251,7 @@ EXTRA_DIST = \ test_invoke_rc_d.py \ test_ionice.py \ test_ip.py \ + test_ipcalc.py \ test_iperf.py \ test_ipmitool.py \ test_ipsec.py \ @@ -456,6 +459,7 @@ EXTRA_DIST = \ test_povray.py \ test_pr.py \ test_prelink.py \ + test_printenv.py \ test_protoc.py \ test_psql.py \ test_ptx.py \ @@ -523,9 +527,11 @@ EXTRA_DIST = \ test_sbcl.py \ test_sbcl_mt.py \ test_sbopkg.py \ + test_scp.py \ test_screen.py \ test_scrub.py \ test_sdptool.py \ + test_secret_tool.py \ test_sed.py \ test_seq.py \ test_service.py \ @@ -589,11 +595,13 @@ EXTRA_DIST = \ test_time.py \ test_timeout.py \ test_tipc.py \ + test_totem.py \ test_touch.py \ test_tox.py \ test_tr.py \ test_tracepath.py \ test_tshark.py \ + test_tsig_keygen.py \ test_tune2fs.py \ test_udevadm.py \ test_ulimit.py \ @@ -657,6 +665,7 @@ EXTRA_DIST = \ test_xdg_settings.py \ test_xfreerdp.py \ test_xgamma.py \ + test_xhost.py \ test_xm.py \ test_xmllint.py \ test_xmlwf.py \ diff --git a/test/t/conftest.py b/test/t/conftest.py index 20942e87..5c1603d5 100644 --- a/test/t/conftest.py +++ b/test/t/conftest.py @@ -3,18 +3,18 @@ import os import re import shlex import subprocess -from typing import Iterable, List, Optional, Tuple, Union +import time +from typing import Callable, Iterable, Iterator, List, Optional, Tuple import pexpect import pytest - PS1 = "/@" MAGIC_MARK = "__MaGiC-maRKz!__" def find_unique_completion_pair( - items: Iterable[str] + items: Iterable[str], ) -> Optional[Tuple[str, str]]: result = None bestscore = 0 @@ -56,10 +56,22 @@ def find_unique_completion_pair( @pytest.fixture(scope="class") -def part_full_user(bash: pexpect.spawn) -> Optional[Tuple[str, str]]: - res = ( - assert_bash_exec(bash, "compgen -u", want_output=True).strip().split() - ) +def output_sort_uniq(bash: pexpect.spawn) -> Callable[[str], List[str]]: + def _output_sort_uniq(command: str) -> List[str]: + return sorted( + set( # weed out possible duplicates + assert_bash_exec(bash, command, want_output=True).split() + ) + ) + + return _output_sort_uniq + + +@pytest.fixture(scope="class") +def part_full_user( + bash: pexpect.spawn, output_sort_uniq: Callable[[str], List[str]] +) -> Optional[Tuple[str, str]]: + res = output_sort_uniq("compgen -u") pair = find_unique_completion_pair(res) if not pair: pytest.skip("No suitable test user found") @@ -67,10 +79,10 @@ def part_full_user(bash: pexpect.spawn) -> Optional[Tuple[str, str]]: @pytest.fixture(scope="class") -def part_full_group(bash: pexpect.spawn) -> Optional[Tuple[str, str]]: - res = ( - assert_bash_exec(bash, "compgen -g", want_output=True).strip().split() - ) +def part_full_group( + bash: pexpect.spawn, output_sort_uniq: Callable[[str], List[str]] +) -> Optional[Tuple[str, str]]: + res = output_sort_uniq("compgen -g") pair = find_unique_completion_pair(res) if not pair: pytest.skip("No suitable test user found") @@ -78,6 +90,82 @@ def part_full_group(bash: pexpect.spawn) -> Optional[Tuple[str, str]]: @pytest.fixture(scope="class") +def hosts(bash: pexpect.spawn) -> List[str]: + output = assert_bash_exec(bash, "compgen -A hostname", want_output=True) + return sorted(set(output.split() + _avahi_hosts(bash))) + + +@pytest.fixture(scope="class") +def avahi_hosts(bash: pexpect.spawn) -> List[str]: + return _avahi_hosts(bash) + + +def _avahi_hosts(bash: pexpect.spawn) -> List[str]: + output = assert_bash_exec( + bash, + "! type avahi-browse &>/dev/null || " + "avahi-browse -cpr _workstation._tcp 2>/dev/null " + "| command grep ^= | cut -d';' -f7", + want_output=None, + ) + return sorted(set(output.split())) + + +@pytest.fixture(scope="class") +def known_hosts(bash: pexpect.spawn) -> List[str]: + output = assert_bash_exec( + bash, + '_known_hosts_real ""; ' + r'printf "%s\n" "${COMPREPLY[@]}"; unset COMPREPLY', + want_output=True, + ) + return sorted(set(output.split())) + + +@pytest.fixture(scope="class") +def user_home(bash: pexpect.spawn) -> Tuple[str, str]: + user = assert_bash_exec( + bash, 'id -un 2>/dev/null || echo "$USER"', want_output=True + ).strip() + home = assert_bash_exec(bash, 'echo "$HOME"', want_output=True).strip() + return (user, home) + + +def partialize( + bash: pexpect.spawn, items: Iterable[str] +) -> Tuple[str, List[str]]: + """ + Get list of items starting with the first char of first of items. + + Disregard items starting with a COMP_WORDBREAKS character + (e.g. a colon ~ IPv6 address), they are special cases requiring + special tests. + """ + first_char = None + comp_wordbreaks = assert_bash_exec( + bash, + 'printf "%s" "$COMP_WORDBREAKS"', + want_output=True, + want_newline=False, + ) + partial_items = [] + for item in sorted(items): + if first_char is None: + if item[0] not in comp_wordbreaks: + first_char = item[0] + partial_items.append(item) + elif item.startswith(first_char): + partial_items.append(item) + else: + break + if first_char is None: + pytest.skip("Could not generate partial items list from %s" % items) + # superfluous/dead code to assist mypy; pytest.skip always raises + assert first_char is not None + return first_char, partial_items + + +@pytest.fixture(scope="class") def bash(request) -> pexpect.spawn: logfile = None @@ -135,7 +223,7 @@ def bash(request) -> pexpect.spawn: skipif = marker.kwargs.get("skipif") if skipif: try: - assert_bash_exec(bash, skipif) + assert_bash_exec(bash, skipif, want_output=None) except AssertionError: pass else: @@ -144,7 +232,7 @@ def bash(request) -> pexpect.spawn: xfail = marker.kwargs.get("xfail") if xfail: try: - assert_bash_exec(bash, xfail) + assert_bash_exec(bash, xfail, want_output=None) except AssertionError: pass else: @@ -182,7 +270,7 @@ def bash(request) -> pexpect.spawn: logfile.close() -def is_testable(bash: pexpect.spawn, cmd: str) -> bool: +def is_testable(bash: pexpect.spawn, cmd: Optional[str]) -> bool: if not cmd: pytest.fail("Could not resolve name of command to test") return False @@ -214,8 +302,14 @@ def load_completion_for(bash: pexpect.spawn, cmd: str) -> bool: def assert_bash_exec( - bash: pexpect.spawn, cmd: str, want_output: bool = False, want_newline=True + bash: pexpect.spawn, + cmd: str, + want_output: Optional[bool] = False, + want_newline=True, ) -> str: + """ + :param want_output: if None, don't care if got output or not + """ # Send command bash.sendline(cmd) @@ -243,16 +337,17 @@ def assert_bash_exec( status, output, ) - if output: - assert want_output, ( - 'Unexpected output from "%s": exit status=%s, output="%s"' - % (cmd, status, output) - ) - else: - assert not want_output, ( - 'Expected output from "%s": exit status=%s, output="%s"' - % (cmd, status, output) - ) + if want_output is not None: + if output: + assert want_output, ( + 'Unexpected output from "%s": exit status=%s, output="%s"' + % (cmd, status, output) + ) + else: + assert not want_output, ( + 'Expected output from "%s": exit status=%s, output="%s"' + % (cmd, status, output) + ) return output @@ -293,76 +388,52 @@ def diff_env(before: List[str], after: List[str], ignore: str): assert not diff, "Environment should not be modified" -class CompletionResult: +class CompletionResult(Iterable[str]): """ Class to hold completion results. """ - def __init__(self, output: str, items: Optional[Iterable[str]] = None): + def __init__(self, output: Optional[str] = None): """ - When items are specified, they are used as the base for comparisons - provided by this class. When not, regular expressions are used instead. - This is because it is not always possible to unambiguously split a - completion output string into individual items, for example when the - items contain whitespace. - :param output: All completion output as-is. - :param items: Completions as individual items. Should be specified - only in cases where the completions are robustly known to be - exactly the specified ones. """ - self.output = output - self._items = None if items is None else sorted(items) + self.output = output or "" def endswith(self, suffix: str) -> bool: return self.output.endswith(suffix) - def __eq__(self, expected: Union[str, Iterable[str]]) -> bool: + def startswith(self, prefix: str) -> bool: + return self.output.startswith(prefix) + + def _items(self) -> List[str]: + return [x.strip() for x in self.output.strip().splitlines()] + + def __eq__(self, expected: object) -> bool: """ Returns True if completion contains expected items, and no others. Defining __eq__ this way is quite ugly, but facilitates concise testing code. """ - expiter = [expected] if isinstance(expected, str) else expected - if self._items is not None: - return self._items == expiter - return bool( - re.match( - r"^\s*" + r"\s+".join(re.escape(x) for x in expiter) + r"\s*$", - self.output, - ) - ) + if isinstance(expected, str): + expiter = [expected] # type: Iterable + elif not isinstance(expected, Iterable): + return False + else: + expiter = expected + return self._items() == expiter def __contains__(self, item: str) -> bool: - if self._items is not None: - return item in self._items - return bool( - re.search(r"(^|\s)%s(\s|$)" % re.escape(item), self.output) - ) + return item in self._items() - def __iter__(self) -> Iterable[str]: - """ - Note that iteration over items may not be accurate when items were not - specified to the constructor, if individual items in the output contain - whitespace. In those cases, it errs on the side of possibly returning - more items than there actually are, and intends to never return fewer. - """ - return iter( - self._items - if self._items is not None - else re.split(r" {2,}|\r\n", self.output.strip()) - ) + def __iter__(self) -> Iterator[str]: + return iter(self._items()) def __len__(self) -> int: - """ - Uses __iter__, see caveat in it. While possibly inaccurate, this is - good enough for truthiness checks. - """ - return len(list(iter(self))) + return len(self._items()) def __repr__(self) -> str: - return "<CompletionResult %s>" % list(self) + return "<CompletionResult %s>" % self._items() def assert_complete( @@ -371,7 +442,7 @@ def assert_complete( skipif = kwargs.get("skipif") if skipif: try: - assert_bash_exec(bash, skipif) + assert_bash_exec(bash, skipif, want_output=None) except AssertionError: pass else: @@ -379,7 +450,7 @@ def assert_complete( xfail = kwargs.get("xfail") if xfail: try: - assert_bash_exec(bash, xfail) + assert_bash_exec(bash, xfail, want_output=None) except AssertionError: pass else: @@ -393,57 +464,63 @@ def assert_complete( # Back up environment and apply new one assert_bash_exec( bash, - " ".join('%s%s="$%s"' % (env_prefix, k, k) for k in env.keys()), + " ".join('%s%s="${%s-}"' % (env_prefix, k, k) for k in env.keys()), ) assert_bash_exec( bash, "export %s" % " ".join("%s=%s" % (k, v) for k, v in env.items()), ) - bash.send(cmd + "\t") - bash.expect_exact(cmd) - bash.send(MAGIC_MARK) - got = bash.expect( - [ - # 0: multiple lines, result in .before - r"\r\n" + re.escape(PS1 + cmd) + ".*" + MAGIC_MARK, - # 1: no completion - r"^" + MAGIC_MARK, - # 2: on same line, result in .match - r"^([^\r]+)%s$" % MAGIC_MARK, - pexpect.EOF, - pexpect.TIMEOUT, - ] - ) - if got == 0: - output = bash.before - if output.endswith(MAGIC_MARK): - output = bash.before[: -len(MAGIC_MARK)] - result = CompletionResult(output) - elif got == 2: - output = bash.match.group(1) - result = CompletionResult(output, [shlex.split(cmd + output)[-1]]) - else: - # TODO: warn about EOF/TIMEOUT? - result = CompletionResult("", []) - bash.sendintr() - bash.expect_exact(PS1) - if env: - # Restore environment, and clean up backup - # TODO: Test with declare -p if a var was set, backup only if yes, and - # similarly restore only backed up vars. Should remove some need - # for ignore_env. - assert_bash_exec( - bash, - "export %s" - % " ".join('%s="$%s%s"' % (k, env_prefix, k) for k in env.keys()), - ) - assert_bash_exec( - bash, - "unset -v %s" - % " ".join("%s%s" % (env_prefix, k) for k in env.keys()), + try: + bash.send(cmd + "\t") + # Sleep a bit if requested, to avoid `.*` matching too early + time.sleep(kwargs.get("sleep_after_tab", 0)) + bash.expect_exact(cmd) + bash.send(MAGIC_MARK) + got = bash.expect( + [ + # 0: multiple lines, result in .before + r"\r\n" + re.escape(PS1 + cmd) + ".*" + re.escape(MAGIC_MARK), + # 1: no completion + r"^" + re.escape(MAGIC_MARK), + # 2: on same line, result in .match + r"^([^\r]+)%s$" % re.escape(MAGIC_MARK), + pexpect.EOF, + pexpect.TIMEOUT, + ] ) - if cwd: - assert_bash_exec(bash, "cd - >/dev/null") + if got == 0: + output = bash.before + if output.endswith(MAGIC_MARK): + output = bash.before[: -len(MAGIC_MARK)] + result = CompletionResult(output) + elif got == 2: + output = bash.match.group(1) + result = CompletionResult(output) + else: + # TODO: warn about EOF/TIMEOUT? + result = CompletionResult() + finally: + bash.sendintr() + bash.expect_exact(PS1) + if env: + # Restore environment, and clean up backup + # TODO: Test with declare -p if a var was set, backup only if yes, and + # similarly restore only backed up vars. Should remove some need + # for ignore_env. + assert_bash_exec( + bash, + "export %s" + % " ".join( + '%s="$%s%s"' % (k, env_prefix, k) for k in env.keys() + ), + ) + assert_bash_exec( + bash, + "unset -v %s" + % " ".join("%s%s" % (env_prefix, k) for k in env.keys()), + ) + if cwd: + assert_bash_exec(bash, "cd - >/dev/null") return result @@ -451,7 +528,7 @@ def assert_complete( def completion(request, bash: pexpect.spawn) -> CompletionResult: marker = request.node.get_closest_marker("complete") if not marker: - return CompletionResult("", []) + return CompletionResult() for pre_cmd in marker.kwargs.get("pre_cmds", []): assert_bash_exec(bash, pre_cmd) cmd = getattr(request.cls, "cmd", None) @@ -467,9 +544,61 @@ def completion(request, bash: pexpect.spawn) -> CompletionResult: ) % ((cmd,) * 2) if marker.kwargs.get("require_cmd") and not is_bash_type(bash, cmd): pytest.skip("Command not found") + + if "trail" in marker.kwargs: + return assert_complete_at_point( + bash, cmd=marker.args[0], trail=marker.kwargs["trail"] + ) + return assert_complete(bash, marker.args[0], **marker.kwargs) +def assert_complete_at_point( + bash: pexpect.spawn, cmd: str, trail: str +) -> CompletionResult: + # TODO: merge to assert_complete + fullcmd = "%s%s%s" % ( + cmd, + trail, + "\002" * len(trail), + ) # \002 = ^B = cursor left + bash.send(fullcmd + "\t") + bash.send(MAGIC_MARK) + bash.expect_exact(fullcmd.replace("\002", "\b")) + + got = bash.expect_exact( + [ + # 0: multiple lines, result in .before + PS1 + fullcmd.replace("\002", "\b"), + # 1: no completion + MAGIC_MARK, + pexpect.EOF, + pexpect.TIMEOUT, + ] + ) + if got == 0: + output = bash.before + result = CompletionResult(output) + + # At this point, something weird happens. For most test setups, as + # expected (pun intended!), MAGIC_MARK follows as is. But for some + # others (e.g. CentOS 6, Ubuntu 14 test containers), we get MAGIC_MARK + # one character a time, followed each time by trail and the corresponding + # number of \b's. Don't know why, but accept it until/if someone finds out. + # Or just be fine with it indefinitely, the visible and practical end + # result on a terminal is the same anyway. + repeat = "(%s%s)?" % (re.escape(trail), "\b" * len(trail)) + fullexpected = "".join( + "%s%s" % (re.escape(x), repeat) for x in MAGIC_MARK + ) + bash.expect(fullexpected) + else: + # TODO: warn about EOF/TIMEOUT? + result = CompletionResult() + + return result + + def in_container() -> bool: try: container = subprocess.check_output( diff --git a/test/t/test_2to3.py b/test/t/test_2to3.py index 030fb261..4bce44e6 100644 --- a/test/t/test_2to3.py +++ b/test/t/test_2to3.py @@ -6,6 +6,6 @@ class Test2to3: def test_1(self, completion): assert completion - @pytest.mark.complete("2to3 -", require_cmd=True) + @pytest.mark.complete("2to3 -", require_cmd=True, require_longopt=True) def test_2(self, completion): assert completion diff --git a/test/t/test_7z.py b/test/t/test_7z.py index c6e73890..d4308d95 100644 --- a/test/t/test_7z.py +++ b/test/t/test_7z.py @@ -8,12 +8,11 @@ class Test7z: @pytest.mark.complete("7z a ar -tzi") def test_2(self, completion): - assert completion == "-tzip" + assert completion == "p" - @pytest.mark.xfail # TODO: whitespace split issue @pytest.mark.complete(r"7z x -wa\ ", cwd="_filedir") def test_3(self, completion): - assert completion == r"-wa\ b/" + assert completion == "b/" assert not completion.endswith(" ") @pytest.mark.complete("7z x ", cwd="7z") diff --git a/test/t/test_alias.py b/test/t/test_alias.py index da9ecc33..cc592a8c 100644 --- a/test/t/test_alias.py +++ b/test/t/test_alias.py @@ -15,3 +15,7 @@ class TestAlias: def test_2(self, completion): assert completion == "foo='bar'" assert not completion.endswith(" ") + + @pytest.mark.complete("alias ", trail="foo") + def test_alias_at_point(self, completion): + assert completion == "bar foo".split() diff --git a/test/t/test_ant.py b/test/t/test_ant.py index b14beb94..94acea11 100644 --- a/test/t/test_ant.py +++ b/test/t/test_ant.py @@ -1,5 +1,7 @@ import pytest +from conftest import assert_bash_exec + @pytest.mark.bashcomp(ignore_env=r"^\+ANT_ARGS=") class TestAnt: @@ -18,8 +20,15 @@ class TestAnt: @pytest.mark.complete( "ant ", cwd="ant", env=dict(ANT_ARGS="'-f named-build.xml'") ) - def test_4(self, completion): - assert completion == "named-build" + def test_4(self, bash, completion): + output = assert_bash_exec(bash, "complete -p ant", want_output=True) + if "complete-ant-cmd.pl" in output: + # Some versions of complete-ant-cmd.pl don't treat ANT_ARGS right; + # in those cases we get the correct completion produced by _ant + # plus whatever complete-ant-cmd.pl was able to get from build.xml + assert "named-build" in completion + else: + assert completion == "named-build" @pytest.mark.complete("ant -l ") def test_5(self, completion): diff --git a/test/t/test_apt_cache.py b/test/t/test_apt_cache.py index a1c29cda..f9329f22 100644 --- a/test/t/test_apt_cache.py +++ b/test/t/test_apt_cache.py @@ -5,9 +5,13 @@ import pytest class TestAptCache: @pytest.mark.complete("apt-cache ") def test_1(self, completion): - assert completion + assert "search" in completion @pytest.mark.complete("apt-cache showsrc [", require_cmd=True) def test_2(self, completion): # Doesn't actually fail on grep errors, but takes a long time. assert not completion + + @pytest.mark.complete("apt-cache ", trail=" add foo") + def test_special_at_point(self, completion): + assert not completion diff --git a/test/t/test_apt_get.py b/test/t/test_apt_get.py index ccdff6cd..dc8299a9 100644 --- a/test/t/test_apt_get.py +++ b/test/t/test_apt_get.py @@ -9,4 +9,8 @@ class TestAptGet: @pytest.mark.complete("apt-get install ./", cwd="dpkg") def test_2(self, completion): - assert completion == "./bash-completion-test-subject.deb" + assert completion == "bash-completion-test-subject.deb" + + @pytest.mark.complete("apt-get build-dep ") + def test_build_dep_dirs(self, completion): + assert "dpkg/" in completion diff --git a/test/t/test_aptitude.py b/test/t/test_aptitude.py index c59c3580..29569f15 100644 --- a/test/t/test_aptitude.py +++ b/test/t/test_aptitude.py @@ -5,3 +5,19 @@ class TestAptitude: @pytest.mark.complete("aptitude ") def test_1(self, completion): assert completion + + @pytest.mark.complete("aptitude -", require_cmd=True) + def test_options(self, completion): + assert completion + + @pytest.mark.complete("aptitude --", require_cmd=True) + def test_long_options(self, completion): + assert completion + + @pytest.mark.complete("aptitude -u -") + def test_no_i_with_u(self, completion): + assert "-i" not in completion + + @pytest.mark.complete("aptitude -i -") + def test_no_u_with_i(self, completion): + assert "-u" not in completion diff --git a/test/t/test_arpspoof.py b/test/t/test_arpspoof.py index c8955f8d..74c09a43 100644 --- a/test/t/test_arpspoof.py +++ b/test/t/test_arpspoof.py @@ -2,6 +2,11 @@ import pytest class TestArpspoof: - @pytest.mark.complete("arpspoof -", require_cmd=True) + @pytest.mark.complete( + "arpspoof -", + require_cmd=True, + # May require privileges even for outputting the usage message + skipif="arpspoof 2>&1 | command grep -qF libnet_open_link", + ) def test_1(self, completion): assert completion diff --git a/test/t/test_bmake.py b/test/t/test_bmake.py new file mode 100644 index 00000000..bc885d31 --- /dev/null +++ b/test/t/test_bmake.py @@ -0,0 +1,7 @@ +import pytest + + +class TestBmake: + @pytest.mark.complete("bmake -", require_cmd=True) + def test_options(self, completion): + assert completion diff --git a/test/t/test_ccache.py b/test/t/test_ccache.py index 64620ef4..ef55d0d8 100644 --- a/test/t/test_ccache.py +++ b/test/t/test_ccache.py @@ -12,15 +12,15 @@ class TestCcache: @pytest.mark.complete("ccache stt") def test_3(self, completion): - assert "stty" in completion + assert completion == "y" or "stty" in completion @pytest.mark.complete("ccache --zero-stats stt") def test_4(self, completion): - assert "stty" in completion + assert completion == "y" or "stty" in completion @pytest.mark.complete("ccache --hel", require_cmd=True) def test_5(self, completion): - assert "--help" in completion + assert completion == "p" or "--help" in completion @pytest.mark.complete("ccache --zero-stats sh +") def test_6(self, completion): diff --git a/test/t/test_cd.py b/test/t/test_cd.py index fd532312..5b7789ae 100644 --- a/test/t/test_cd.py +++ b/test/t/test_cd.py @@ -9,7 +9,7 @@ class TestCd: @pytest.mark.complete("cd fo", env=dict(CDPATH="shared/default")) def test_2(self, completion): - assert completion == "foo.d/" + assert completion == "o.d/" @pytest.mark.complete("cd fo") def test_3(self, completion): @@ -20,3 +20,7 @@ class TestCd: ) def test_4(self, completion): assert not completion # No subdirs nor CDPATH + + @pytest.mark.complete("cd shared/default/", trail="foo") + def test_dir_at_point(self, completion): + assert completion == ["bar bar.d/", "foo.d/"] diff --git a/test/t/test_chown.py b/test/t/test_chown.py index 37221cfa..9643f3eb 100644 --- a/test/t/test_chown.py +++ b/test/t/test_chown.py @@ -2,7 +2,7 @@ import getpass import pytest -from conftest import assert_bash_exec, assert_complete +from conftest import assert_complete @pytest.mark.bashcomp( @@ -16,10 +16,8 @@ class TestChown: getpass.getuser() != "root", reason="Only root can chown to all users" ) @pytest.mark.complete("chown ") - def test_1(self, bash, completion): - users = sorted( - assert_bash_exec(bash, "compgen -A user", want_output=True).split() - ) + def test_1(self, bash, completion, output_sort_uniq): + users = output_sort_uniq("compgen -u") assert completion == users @pytest.mark.complete("chown foo: shared/default/") @@ -33,37 +31,39 @@ class TestChown: def test_4(self, bash, part_full_user): part, full = part_full_user completion = assert_complete(bash, "chown %s" % part) - assert completion == full + assert completion == full[len(part) :] assert completion.endswith(" ") def test_5(self, bash, part_full_user, part_full_group): _, user = part_full_user partgroup, fullgroup = part_full_group completion = assert_complete(bash, "chown %s:%s" % (user, partgroup)) - assert completion == "%s:%s" % (user, fullgroup) + assert completion == fullgroup[len(partgroup) :] assert completion.output.endswith(" ") def test_6(self, bash, part_full_group): part, full = part_full_group completion = assert_complete(bash, "chown dot.user:%s" % part) - assert completion == "dot.user:%s" % full + assert completion == full[len(part) :] assert completion.output.endswith(" ") - @pytest.mark.xfail # TODO check escaping, whitespace - def test_7(self, bash, part_full_group): - """Test preserving special chars in $prefix$partgroup<TAB>.""" - part, full = part_full_group - for prefix in ( + @pytest.mark.parametrize( + "prefix", + [ r"funky\ user:", "funky.user:", r"funky\.user:", r"fu\ nky.user:", r"f\ o\ o\.\bar:", r"foo\_b\ a\.r\ :", - ): - completion = assert_complete(bash, "chown %s%s" % (prefix, part)) - assert completion == "%s%s" % (prefix, full) - assert completion.output.endswith(" ") + ], + ) + def test_7(self, bash, part_full_group, prefix): + """Test preserving special chars in $prefix$partgroup<TAB>.""" + part, full = part_full_group + completion = assert_complete(bash, "chown %s%s" % (prefix, part)) + assert completion == full[len(part) :] + assert completion.output.endswith(" ") def test_8(self, bash, part_full_user, part_full_group): """Test giving up on degenerate cases instead of spewing junk.""" diff --git a/test/t/test_complete.py b/test/t/test_complete.py index 036f954e..7ff56b41 100644 --- a/test/t/test_complete.py +++ b/test/t/test_complete.py @@ -5,3 +5,7 @@ class TestComplete: @pytest.mark.complete("complete -") def test_1(self, completion): assert completion + + @pytest.mark.complete(r"\complete -") + def test_2(self, completion): + assert completion diff --git a/test/t/test_cpan2dist.py b/test/t/test_cpan2dist.py index f456c0ce..1ab5de13 100644 --- a/test/t/test_cpan2dist.py +++ b/test/t/test_cpan2dist.py @@ -2,6 +2,8 @@ import pytest class TestCpan2dist: - @pytest.mark.complete("cpan2dist -", require_cmd=True) + @pytest.mark.complete( + "cpan2dist -", require_cmd=True, require_longopt=True + ) def test_1(self, completion): assert completion diff --git a/test/t/test_cpio.py b/test/t/test_cpio.py index 1b9e37df..0b739663 100644 --- a/test/t/test_cpio.py +++ b/test/t/test_cpio.py @@ -1,7 +1,5 @@ import pytest -from conftest import assert_bash_exec - class TestCpio: @pytest.mark.complete("cpio --") @@ -9,10 +7,6 @@ class TestCpio: assert completion @pytest.mark.complete("cpio -R ") - def test_2(self, bash, completion): - users = sorted( - assert_bash_exec(bash, "compgen -A user", want_output=True) - .strip() - .splitlines() - ) - assert list(completion) == users + def test_2(self, bash, completion, output_sort_uniq): + users = output_sort_uniq("compgen -u") + assert completion == users diff --git a/test/t/test_cppcheck.py b/test/t/test_cppcheck.py index da770786..73e64f5e 100644 --- a/test/t/test_cppcheck.py +++ b/test/t/test_cppcheck.py @@ -20,12 +20,12 @@ class TestCppcheck: @pytest.mark.complete("cppcheck --enable=al") def test_5(self, completion): - assert completion == "--enable=all" + assert completion == "l" @pytest.mark.complete("cppcheck --enable=xx,styl") def test_6(self, completion): - assert completion == "--enable=xx,style" + assert completion == "e" @pytest.mark.complete("cppcheck --enable=xx,yy,styl") def test_7(self, completion): - assert completion == "--enable=xx,yy,style" + assert completion == "e" diff --git a/test/t/test_crontab.py b/test/t/test_crontab.py index 098fd9e0..a476694c 100644 --- a/test/t/test_crontab.py +++ b/test/t/test_crontab.py @@ -5,3 +5,12 @@ class TestCrontab: @pytest.mark.complete("crontab ") def test_1(self, completion): assert completion + + @pytest.mark.complete("crontab -l -") + def test_only_u_with_l(self, completion): + assert completion == "u" + + @pytest.mark.complete("crontab -r -") + def test_no_l_with_r(self, completion): + assert completion + assert "-l" not in completion diff --git a/test/t/test_curl.py b/test/t/test_curl.py index ebccca95..63e969f9 100644 --- a/test/t/test_curl.py +++ b/test/t/test_curl.py @@ -8,11 +8,11 @@ class TestCurl: @pytest.mark.complete("curl -o f", cwd="shared/default/foo.d") def test_2(self, completion): - assert completion == "foo" + assert completion == "oo" @pytest.mark.complete("curl -LRo f", cwd="shared/default/foo.d") def test_3(self, completion): - assert completion == "foo" + assert completion == "oo" @pytest.mark.complete("curl --o f") def test_4(self, completion): @@ -20,9 +20,9 @@ class TestCurl: @pytest.mark.complete("curl --data @", cwd="shared/default/foo.d") def test_data_atfile(self, completion): - assert completion == "@foo" + assert completion == "foo" @pytest.mark.complete("curl --data @foo.", cwd="shared/default") def test_data_atfile_dir(self, completion): - assert completion == "@foo.d/" + assert completion == "d/" assert not completion.endswith(" ") diff --git a/test/t/test_cvs.py b/test/t/test_cvs.py index ab7fead8..97361e9e 100644 --- a/test/t/test_cvs.py +++ b/test/t/test_cvs.py @@ -13,7 +13,7 @@ class TestCvs: @pytest.mark.complete("cvs diff foo/", cwd="cvs") def test_3(self, completion): - assert completion == "foo/bar" + assert completion == "bar" @pytest.mark.complete("cvs -", require_cmd=True) def test_4(self, completion): diff --git a/test/t/test_dd.py b/test/t/test_dd.py index be1829d3..e082faa9 100644 --- a/test/t/test_dd.py +++ b/test/t/test_dd.py @@ -14,4 +14,4 @@ class TestDd: @pytest.mark.complete("dd bs") def test_2(self, completion): - assert completion == "bs=" + assert completion == "=" diff --git a/test/t/test_dmypy.py b/test/t/test_dmypy.py index efaef7ca..4c031ddd 100644 --- a/test/t/test_dmypy.py +++ b/test/t/test_dmypy.py @@ -2,11 +2,13 @@ import pytest class TestDmypy: - @pytest.mark.complete("dmypy ", require_cmd=True) + @pytest.mark.complete( + "dmypy ", require_cmd=True, xfail="! dmypy --help &>/dev/null" + ) def test_commands(self, completion): assert "help" in completion assert not any("," in x for x in completion) - @pytest.mark.complete("dmypy -", require_cmd=True) + @pytest.mark.complete("dmypy -", require_cmd=True, require_longopt=True) def test_options(self, completion): assert "--help" in completion diff --git a/test/t/test_dnssec_keygen.py b/test/t/test_dnssec_keygen.py index d52e3af0..f8bd6fb1 100644 --- a/test/t/test_dnssec_keygen.py +++ b/test/t/test_dnssec_keygen.py @@ -13,7 +13,7 @@ class TestDnssecKeygen: @pytest.mark.complete("dnssec-keygen -a ") def test_2(self, completion): assert completion - assert "HMAC-MD5" in completion + assert any(x in completion for x in ("HMAC-MD5", "RSASHA1", "ED25519")) assert "|" not in completion assert not any(x.startswith("-") for x in completion) diff --git a/test/t/test_dpkg_deb.py b/test/t/test_dpkg_deb.py index c1ad8191..9be85eb6 100644 --- a/test/t/test_dpkg_deb.py +++ b/test/t/test_dpkg_deb.py @@ -6,3 +6,7 @@ class TestDpkgDeb: @pytest.mark.complete("dpkg-deb --c", require_cmd=True) def test_1(self, completion): assert completion + + @pytest.mark.complete("dpkg-deb --show b", cwd="dpkg") + def test_show(self, completion): + assert completion == "ash-completion-test-subject.deb" diff --git a/test/t/test_dpkg_query.py b/test/t/test_dpkg_query.py new file mode 100644 index 00000000..37c56211 --- /dev/null +++ b/test/t/test_dpkg_query.py @@ -0,0 +1,18 @@ +import os.path + +import pytest + + +@pytest.mark.bashcomp(cmd="dpkg-query",) +class TestDpkgQuery: + @pytest.mark.complete("dpkg-query --", require_cmd=True) + def test_options(self, completion): + assert completion + + @pytest.mark.xfail( + not os.path.exists("/etc/debian_version"), + reason="Likely fails on systems not based on Debian", + ) + @pytest.mark.complete("dpkg-query -W dpk", require_cmd=True) + def test_show(self, completion): + assert "dpkg" in completion diff --git a/test/t/test_feh.py b/test/t/test_feh.py index 51bd77b6..f2d5317b 100644 --- a/test/t/test_feh.py +++ b/test/t/test_feh.py @@ -16,11 +16,11 @@ class TestFeh: @pytest.mark.complete("feh -S pix") def test_3(self, completion): - assert completion == "pixels" + assert completion == "els" @pytest.mark.complete("feh --zoom ma") def test_4(self, completion): - assert completion == "max" + assert completion == "x" @pytest.mark.complete("feh -g 640") def test_5(self, completion): diff --git a/test/t/test_find.py b/test/t/test_find.py index a94e0e0d..9968ade7 100644 --- a/test/t/test_find.py +++ b/test/t/test_find.py @@ -14,10 +14,9 @@ class TestFind: def test_3(self, completion): assert completion - @pytest.mark.xfail # TODO: whitespace split issue @pytest.mark.complete("find -wholename ", cwd="shared/default") def test_4(self, completion): - assert completion == ["bar", "bar bar.d/", "foo", "foo foo.d/"] + assert completion == ["bar", "bar bar.d/", "foo", "foo.d/"] @pytest.mark.complete("find -uid ") def test_5(self, completion): @@ -26,3 +25,13 @@ class TestFind: @pytest.mark.complete("find -gid ") def test_6(self, completion): assert not [x for x in completion if not x.isdigit()] + + @pytest.mark.complete("find -exec shared/bin/ar") + def test_exec(self, completion): + assert completion == "p" + + # sh +: something that produces completions also when command is not + # available, and the chosen completion is not one of find's + @pytest.mark.complete("find /some/where -exec sh +") + def test_exec_args(self, completion): + assert "+o" in completion diff --git a/test/t/test_finger.py b/test/t/test_finger.py index 92c983fa..d765fdd7 100644 --- a/test/t/test_finger.py +++ b/test/t/test_finger.py @@ -1,16 +1,12 @@ import pytest -from conftest import assert_bash_exec +from conftest import assert_complete, partialize class TestFinger: @pytest.fixture(scope="class") - def users_at(self, bash): - return sorted( - assert_bash_exec( - bash, "compgen -A user -S @", want_output=True - ).split() - ) + def users_at(self, bash, output_sort_uniq): + return output_sort_uniq("compgen -u -S @") @pytest.mark.complete("finger ") def test_1(self, bash, completion, users_at): @@ -21,5 +17,17 @@ class TestFinger: if not any(x.startswith("r") for x in users_at): pytest.skip("No users starting with r") assert completion - assert all(x.startswith("r") for x in completion) + idx = 1 if len(completion) == 1 else 0 + assert completion == sorted( + x[idx:] for x in users_at if x.startswith("r") + ) assert not completion.endswith(" ") + + def test_partial_hostname(self, bash, known_hosts): + first_char, partial_hosts = partialize(bash, known_hosts) + user = "test" + completion = assert_complete(bash, "finger %s@%s" % (user, first_char)) + if len(completion) == 1: + assert completion == partial_hosts[0][1:] + else: + assert completion == ["%s@%s" % (user, x) for x in partial_hosts] diff --git a/test/t/test_gcc.py b/test/t/test_gcc.py index 87f25797..50906db5 100644 --- a/test/t/test_gcc.py +++ b/test/t/test_gcc.py @@ -24,7 +24,7 @@ class TestGcc: @pytest.mark.complete("gcc -fsanitize=add") def test_enum_value(self, completion, gcc_with_completion): - assert completion == "-fsanitize=address" + assert completion == "ress" @pytest.mark.complete("gcc -fsanitize=") def test_enum_value_with_eq(self, completion, gcc_with_completion): @@ -48,15 +48,12 @@ class TestGcc: @pytest.mark.complete("gcc --param=lto-max-p") def test_param_with_eq(self, completion, gcc_with_completion): - # starting with GCC 10.1 param end with = - assert ( - completion == "--param=lto-max-partition" - or completion == "--param=lto-max-partition=" - ) + # starting with GCC 10.1 param ends with = + assert completion in ("artition", "artition=") @pytest.mark.complete("gcc -march=amd") def test_march(self, completion, gcc_with_completion, gcc_x86): - assert completion == "-march=amdfam10" + assert completion == "fam10" @pytest.mark.complete("gcc -march=") def test_march_native(self, completion, gcc_with_completion): diff --git a/test/t/test_ip.py b/test/t/test_ip.py index 20752505..320647f4 100644 --- a/test/t/test_ip.py +++ b/test/t/test_ip.py @@ -9,3 +9,7 @@ class TestIp: @pytest.mark.complete("ip a ") def test_2(self, completion): assert completion + + @pytest.mark.complete("ip route replace ") + def test_r_r(self, completion): + assert completion diff --git a/test/t/test_ipcalc.py b/test/t/test_ipcalc.py new file mode 100644 index 00000000..5611674c --- /dev/null +++ b/test/t/test_ipcalc.py @@ -0,0 +1,23 @@ +import pytest + + +class TestIpcalc: + @pytest.mark.complete("ipcalc -", require_cmd=True) + def test_options(self, completion): + assert any(x in completion for x in "--help -h".split()) + + @pytest.mark.complete("ipcalc --split -") + def test_split_3args_1(self, completion): + assert not completion + + @pytest.mark.complete("ipcalc --split 1 -") + def test_split_3args_2(self, completion): + assert not completion + + @pytest.mark.complete("ipcalc --split 1 2 -") + def test_split_3args_3(self, completion): + assert not completion + + @pytest.mark.complete("ipcalc --split 1 2 3 -", require_cmd=True) + def test_split_3args_4(self, completion): + assert any(x in completion for x in "--help -h".split()) diff --git a/test/t/test_irb.py b/test/t/test_irb.py index 03a83c66..801d3739 100644 --- a/test/t/test_irb.py +++ b/test/t/test_irb.py @@ -6,6 +6,6 @@ class TestIrb: def test_1(self, completion): assert completion - @pytest.mark.complete("irb -", require_cmd=True) + @pytest.mark.complete("irb -", require_longopt=True) def test_options(self, completion): assert completion diff --git a/test/t/test_iscsiadm.py b/test/t/test_iscsiadm.py index 932ffeb5..885ca0ab 100644 --- a/test/t/test_iscsiadm.py +++ b/test/t/test_iscsiadm.py @@ -2,6 +2,6 @@ import pytest class TestIscsiadm: - @pytest.mark.complete("iscsiadm --mode") + @pytest.mark.complete("iscsiadm --mod") def test_1(self, completion): - assert completion + assert completion == "e" or "--mode" in completion diff --git a/test/t/test_isort.py b/test/t/test_isort.py index 9f7a6524..b142d1c4 100644 --- a/test/t/test_isort.py +++ b/test/t/test_isort.py @@ -6,6 +6,6 @@ class TestIsort: def test_1(self, completion): assert completion - @pytest.mark.complete("isort -", require_cmd=True) + @pytest.mark.complete("isort -", require_cmd=True, require_longopt=True) def test_2(self, completion): assert completion diff --git a/test/t/test_jsonschema.py b/test/t/test_jsonschema.py index 9e3929e6..6027f5d6 100644 --- a/test/t/test_jsonschema.py +++ b/test/t/test_jsonschema.py @@ -6,6 +6,8 @@ class TestJsonschema: def test_1(self, completion): assert completion - @pytest.mark.complete("jsonschema -", require_cmd=True) + @pytest.mark.complete( + "jsonschema -", require_cmd=True, require_longopt=True + ) def test_2(self, completion): assert completion diff --git a/test/t/test_kcov.py b/test/t/test_kcov.py index 3c7d3dfa..3e377ebe 100644 --- a/test/t/test_kcov.py +++ b/test/t/test_kcov.py @@ -8,7 +8,7 @@ class TestKcov: @pytest.mark.complete("kcov --exclude-patter", require_cmd=True) def test_2(self, completion): - assert completion == "--exclude-pattern=" + assert completion == "n=" assert completion.endswith("=") @pytest.mark.complete("kcov -l 42,") diff --git a/test/t/test_ldd.py b/test/t/test_ldd.py index 70e295a5..7f7201bd 100644 --- a/test/t/test_ldd.py +++ b/test/t/test_ldd.py @@ -6,6 +6,8 @@ class TestLdd: def test_1(self, completion): assert completion - @pytest.mark.complete("ldd -", require_cmd=True) + @pytest.mark.complete( + "ldd -", require_cmd=True, xfail="! ldd --help &>/dev/null" + ) def test_options(self, completion): assert completion diff --git a/test/t/test_less.py b/test/t/test_less.py index 0b14e21e..70833c34 100644 --- a/test/t/test_less.py +++ b/test/t/test_less.py @@ -5,3 +5,7 @@ class TestLess: @pytest.mark.complete("less --", require_longopt=True) def test_1(self, completion): assert completion + + @pytest.mark.complete("less --", require_longopt=True) + def test_no_dashdashdash(self, completion): + assert all(not x.startswith("---") for x in completion) diff --git a/test/t/test_lftp.py b/test/t/test_lftp.py index 765e51e1..f775a4c6 100644 --- a/test/t/test_lftp.py +++ b/test/t/test_lftp.py @@ -1,17 +1,15 @@ import pytest -from conftest import assert_bash_exec - @pytest.mark.bashcomp(pre_cmds=("HOME=$PWD/lftp",)) class TestLftp: - @pytest.mark.complete("lftp ") - def test_1(self, bash, completion): - hosts = assert_bash_exec( - bash, "compgen -A hostname", want_output=True - ).split() + @pytest.mark.complete("lftp ", require_cmd=True) + def test_1(self, bash, completion, output_sort_uniq): + hosts = output_sort_uniq("compgen -A hostname") assert all(x in completion for x in hosts) - assert "lftptest" in completion # defined in lftp/.lftp/bookmarks + # defined in lftp/.lftp/bookmarks + assert all(x in completion for x in "lftptest spacetest".split()) + assert "badbookmark" not in completion @pytest.mark.complete("lftp -", require_cmd=True) def test_2(self, completion): diff --git a/test/t/test_lilo.py b/test/t/test_lilo.py index 9783f506..2c698212 100644 --- a/test/t/test_lilo.py +++ b/test/t/test_lilo.py @@ -5,3 +5,12 @@ class TestLilo: @pytest.mark.complete("lilo -") def test_1(self, completion): assert completion + + @pytest.mark.complete("lilo -C lilo/lilo.conf -D ") + def test_labels(self, completion): + # Note that 2.4.33 should not be here, it's commented out + assert completion == sorted("try tamu PCDOS WinXP oldDOS".split()) + + @pytest.mark.complete("lilo -C -D ") + def test_labels_incorrect_command(self, completion): + assert not completion diff --git a/test/t/test_ls.py b/test/t/test_ls.py index 7e2d1f35..8abcb59d 100644 --- a/test/t/test_ls.py +++ b/test/t/test_ls.py @@ -36,5 +36,5 @@ class TestLs: return part, full = part_full completion = assert_complete(bash, "ls ~%s" % part) - assert completion == "~%s" % full + assert completion == full[len(part) :] assert completion.endswith(" ") diff --git a/test/t/test_lspci.py b/test/t/test_lspci.py index ac18da3f..aba7b5a4 100644 --- a/test/t/test_lspci.py +++ b/test/t/test_lspci.py @@ -6,6 +6,8 @@ class TestLspci: def test_1(self, completion): assert completion - @pytest.mark.complete("lspci -A ", require_cmd=True) + @pytest.mark.complete( + "lspci -A ", require_cmd=True, skipif="! lspci -A help &>/dev/null" + ) def test_2(self, completion): assert completion diff --git a/test/t/test_make.py b/test/t/test_make.py index e6e043cd..19861b00 100644 --- a/test/t/test_make.py +++ b/test/t/test_make.py @@ -2,21 +2,19 @@ import os import pytest -from conftest import in_container - class TestMake: @pytest.mark.complete("make -f Ma", cwd="make") def test_1(self, completion): - assert completion == "Makefile" + assert completion == "kefile" - @pytest.mark.complete("make .", cwd="make") + @pytest.mark.complete("make .", cwd="make", require_cmd=True) def test_2(self, bash, completion): """Hidden targets.""" assert completion == ".cache/ .test_passes".split() os.remove("%s/make/%s" % (bash.cwd, "extra_makefile")) - @pytest.mark.complete("make .cache/", cwd="make") + @pytest.mark.complete("make .cache/", cwd="make", require_cmd=True) def test_3(self, bash, completion): assert completion == "1 2".split() os.remove("%s/make/%s" % (bash.cwd, "extra_makefile")) @@ -29,22 +27,17 @@ class TestMake: def test_5(self, completion): assert completion - @pytest.mark.complete("make ", cwd="make") + @pytest.mark.complete("make ", cwd="make", require_cmd=True) def test_6(self, bash, completion): assert completion == "all clean extra_makefile install sample".split() os.remove("%s/make/%s" % (bash.cwd, "extra_makefile")) - @pytest.mark.xfail( - in_container() and os.environ.get("DIST") == "centos6", - reason="Fails for some unknown reason on CentOS 6, " - "even though the behavior appears to be correct", - ) - @pytest.mark.complete("make .cache/.", cwd="make") + @pytest.mark.complete("make .cache/.", cwd="make", require_cmd=True) def test_7(self, bash, completion): assert completion == ".1 .2".split() os.remove("%s/make/%s" % (bash.cwd, "extra_makefile")) - @pytest.mark.complete("make -C make ") + @pytest.mark.complete("make -C make ", require_cmd=True) def test_8(self, bash, completion): assert completion == "all clean extra_makefile install sample".split() os.remove("%s/make/%s" % (bash.cwd, "extra_makefile")) diff --git a/test/t/test_man.py b/test/t/test_man.py index ad36d96e..1ff9f84b 100644 --- a/test/t/test_man.py +++ b/test/t/test_man.py @@ -1,8 +1,6 @@ -import os - import pytest -from conftest import assert_bash_exec, in_container +from conftest import assert_bash_exec @pytest.mark.bashcomp( @@ -36,24 +34,16 @@ class TestMan: require_cmd=True, ) def test_1(self, completion): - assert completion == "bash-completion-testcase" + assert completion == "e" @pytest.mark.complete("man man1/f", cwd="man", env=dict(MANPATH=manpath)) def test_2(self, completion): - assert completion == "man1/foo.1" + assert completion == "oo.1" @pytest.mark.complete("man man/", cwd="man", env=dict(MANPATH=manpath)) def test_3(self, completion): - assert completion == "man/quux.8" - - @pytest.mark.xfail( - in_container() and os.environ.get("DIST") == "centos6", - reason="TODO: Fails in CentOS for some reason, unknown " - "how to trigger same behavior as tests show (is " - "different and correct when tried manually, but here " - "at least in CI completes things it should not with " - "this MANPATH setting)", - ) + assert completion == "quux.8" + @pytest.mark.complete( "man %s" % assumed_present, cwd="shared/empty_dir", @@ -82,7 +72,7 @@ class TestMan: env=dict(MANPATH="%s:" % manpath), ) def test_6(self, completion): - assert completion == "bash-completion-testcase" + assert completion == "e" @pytest.mark.complete( "man %s" % assumed_present, @@ -100,7 +90,7 @@ class TestMan: env=dict(MANPATH=":%s" % manpath), ) def test_8(self, completion): - assert completion == "bash-completion-testcase" + assert completion == "e" @pytest.mark.complete( "man %s" % assumed_present, @@ -118,7 +108,7 @@ class TestMan: env=dict(MANPATH="%s:../tmp/man" % manpath), ) def test_10(self, bash, colonpath, completion): - assert completion == "Bash::Completion" + assert completion == "ompletion" @pytest.mark.complete("man -", require_cmd=True) def test_11(self, completion): diff --git a/test/t/test_mkdir.py b/test/t/test_mkdir.py index 1b9cb9dc..afc3fd04 100644 --- a/test/t/test_mkdir.py +++ b/test/t/test_mkdir.py @@ -6,10 +6,9 @@ class TestMkdir: def test_1(self, completion): assert completion - @pytest.mark.xfail # TODO: whitespace split issue @pytest.mark.complete("mkdir ", cwd="shared/default") def test_2(self, completion): - assert completion == ["bar bar.d/", "foo", "foo.d/"] + assert completion == ["bar", "bar bar.d/", "foo", "foo.d/"] @pytest.mark.xfail # TODO: why path in completion, basename in .output? @pytest.mark.complete("mkdir shared/default/foo.d/") diff --git a/test/t/test_modprobe.py b/test/t/test_modprobe.py index 38d290ae..9201119d 100644 --- a/test/t/test_modprobe.py +++ b/test/t/test_modprobe.py @@ -6,7 +6,7 @@ import pytest class TestModprobe: @pytest.mark.complete("modprobe --al") def test_1(self, completion): - assert completion == "--all" + assert completion == "l" # "in": intel*, ... @pytest.mark.complete( diff --git a/test/t/test_mount.py b/test/t/test_mount.py index fbd6dcae..8254fd40 100644 --- a/test/t/test_mount.py +++ b/test/t/test_mount.py @@ -12,7 +12,7 @@ class TestMount: @pytest.mark.complete("mount /dev/sda1 def", cwd="shared") def test_3(self, completion): - assert completion == "default/" + assert completion == "ault/" assert not completion.endswith(" ") @pytest.mark.complete( diff --git a/test/t/test_mr.py b/test/t/test_mr.py index 768e1b35..bfad643f 100644 --- a/test/t/test_mr.py +++ b/test/t/test_mr.py @@ -19,7 +19,7 @@ class TestMr: "mr -c shared/default/foo.d/", xfail="! man -h &>/dev/null" ) def test_3(self, completion): - assert completion == "shared/default/foo.d/foo" + assert completion == "foo" @pytest.mark.complete( "mr bootstrap shared/default/", @@ -29,18 +29,21 @@ class TestMr: def test_4(self, completion): assert completion == ["bar", "bar bar.d/", "foo", "foo.d/"] - @pytest.mark.xfail # "clean" doesn't exist before mr 1.20141023 @pytest.mark.complete( - "mr clean -", require_cmd=True, xfail="! man -h &>/dev/null" + "mr clean -", + require_cmd=True, + xfail="! man -h &>/dev/null", + # "clean" does not exist before mr 1.20141023 + skipif="! mr help 2>&1 | command grep -qwF clean", ) def test_5(self, completion): - assert completion == "-f" + assert completion == "f" @pytest.mark.complete( "mr commit -", require_cmd=True, xfail="! man -h &>/dev/null" ) def test_6(self, completion): - assert completion == "-m" + assert completion == "m" @pytest.mark.complete( "mr status ", require_cmd=True, xfail="! man -h &>/dev/null" diff --git a/test/t/test_mypy.py b/test/t/test_mypy.py index 63fc916c..11628c1d 100644 --- a/test/t/test_mypy.py +++ b/test/t/test_mypy.py @@ -6,7 +6,7 @@ class TestMypy: def test_1(self, completion): assert completion - @pytest.mark.complete("mypy --", require_cmd=True) + @pytest.mark.complete("mypy --", require_cmd=True, require_longopt=True) def test_2(self, completion): assert completion diff --git a/test/t/test_nmap.py b/test/t/test_nmap.py index a4d8a899..9aff8b29 100644 --- a/test/t/test_nmap.py +++ b/test/t/test_nmap.py @@ -1,7 +1,47 @@ import pytest +from conftest import assert_bash_exec + class TestNmap: - @pytest.mark.complete("nmap --v") - def test_1(self, completion): + @pytest.fixture(scope="class") + def functions(self, request, bash): + assert_bash_exec(bash, "_mock_nmap() { cat nmap/nmap-h.txt; }") + assert_bash_exec(bash, "complete -F _nmap _mock_nmap") + + @pytest.mark.complete("nmap --v", require_cmd=True) + def test_live_options(self, completion): + assert completion + + @pytest.mark.complete("nmap ") + def test_hosts(self, completion): assert completion + + @pytest.mark.complete("_mock_nmap -") + def test_mock_options(self, completion, functions): + assert completion == sorted( + "-iL -iR --exclude --excludefile -sL -sn -Pn -PS -PA -PU -PY -PE " + "-PP -PM -PO -n -R --dns-servers --system-dns --traceroute -sS " + "-sT -sA -sW -sM -sU -sN -sF -sX --scanflags -sI -sY -sZ -sO -b " + "-p --exclude-ports -F -r --top-ports --port-ratio -sV " + "--version-intensity --version-light --version-all " + "--version-trace -sC --script= --script-args= --script-args-file= " + "--script-trace --script-updatedb --script-help= -O " + "--osscan-limit --osscan-guess " + # TODO: -T known mishandled; should expand -T<0-5> to -T0 ... -T5 + "-T --min-hostgroup --max-hostgroup --min-parallelism " + "--max-parallelism --min-rtt-timeout --max-rtt-timeout " + "--initial-rtt-timeout --max-retries --host-timeout --scan-delay " + "--max-scan-delay --min-rate --max-rate -f --mtu -D -S -e -g " + "--source-port --proxies --data --data-string --data-length " + "--ip-options --ttl --spoof-mac --badsum -oN -oX -oS -oG -oA -v " + "-d --reason --open --packet-trace --iflist --append-output " + "--resume --stylesheet --webxml --no-stylesheet -6 -A --datadir " + "--send-eth --send-ip --privileged --unprivileged -V -h" + "".strip().split() + ) + + @pytest.mark.complete("_mock_nmap --script-args-f") + def test_mock_nospace(self, completion, functions): + assert completion == "ile=" + assert completion.endswith("=") # no space appended diff --git a/test/t/test_openssl.py b/test/t/test_openssl.py index e3af3530..3eaf6d47 100644 --- a/test/t/test_openssl.py +++ b/test/t/test_openssl.py @@ -6,10 +6,11 @@ class TestOpenssl: def test_1(self, completion): assert completion - @pytest.mark.complete("openssl pkey -cipher ") + @pytest.mark.complete("openssl pkey -cipher ", require_cmd=True) def test_2(self, completion): assert completion - @pytest.mark.complete("openssl dgst -s") + @pytest.mark.complete("openssl dgst -s", require_cmd=True) def test_3(self, completion): assert completion + assert any(x.startswith("-sha") for x in completion) diff --git a/test/t/test_perl.py b/test/t/test_perl.py index c8baa2f3..049c91ea 100644 --- a/test/t/test_perl.py +++ b/test/t/test_perl.py @@ -63,7 +63,7 @@ class TestPerl: @pytest.mark.complete("perl -xshared/default/b") def test_14(self, completion): """-x without space should complete dirs.""" - assert completion == ["-xshared/default/bar bar.d/"] + assert completion == r"ar\ bar.d/" @pytest.mark.complete("perl -x shared/default/b") def test_15(self, completion): diff --git a/test/t/test_pgrep.py b/test/t/test_pgrep.py index 9c233311..9a998edb 100644 --- a/test/t/test_pgrep.py +++ b/test/t/test_pgrep.py @@ -11,3 +11,25 @@ class TestPgrep: @pytest.mark.complete("pgrep -", require_cmd=True) def test_2(self, completion): assert completion + + @pytest.mark.complete( + "pgrep --nslist ", + require_cmd=True, + skipif=( + "! pgrep --help 2>&1 | command grep -qF 'Available namespaces'" + ), + ) + def test_nslist(self, completion): + assert completion + assert not any("," in x for x in completion) + + @pytest.mark.complete( + "pgrep --nslist foo,", + require_cmd=True, + skipif=( + "! pgrep --help 2>&1 | command grep -qF 'Available namespaces'" + ), + ) + def test_nslist_after_comma(self, completion): + assert completion + assert not any("," in x for x in completion) diff --git a/test/t/test_postfix.py b/test/t/test_postfix.py index 67a898d1..10020b0b 100644 --- a/test/t/test_postfix.py +++ b/test/t/test_postfix.py @@ -1,3 +1,5 @@ +import getpass + import pytest @@ -6,7 +8,15 @@ class TestPostfix: def test_1(self, completion): assert completion - @pytest.mark.xfail # see TODO in completion - @pytest.mark.complete("postfix -", require_cmd=True) - def test_2(self, completion): + @pytest.mark.xfail( + getpass.getuser() != "root", + reason="Likely outputs usage only for root", + ) + @pytest.mark.complete( + "postfix -", + require_cmd=True, + xfail="! type unbuffer &>/dev/null", + sleep_after_tab=2, # postfix is slow to output usage + ) + def test_options(self, completion): assert completion diff --git a/test/t/test_printenv.py b/test/t/test_printenv.py new file mode 100644 index 00000000..540c4f64 --- /dev/null +++ b/test/t/test_printenv.py @@ -0,0 +1,19 @@ +import pytest + + +class TestPrintenv: + @pytest.mark.complete("printenv ") + def test_empty(self, completion): + assert completion + + @pytest.mark.complete("printenv PAT") + def test_path(self, completion): + assert completion == "H" or "PATH" in completion + + @pytest.mark.complete( + "printenv -", + require_cmd=True, + xfail="! printenv --help 2>&1 | command grep -qF -- ' -'", + ) + def test_options(self, completion): + assert completion diff --git a/test/t/test_protoc.py b/test/t/test_protoc.py index e890c56a..744b99f4 100644 --- a/test/t/test_protoc.py +++ b/test/t/test_protoc.py @@ -9,3 +9,12 @@ class TestProtoc: @pytest.mark.complete("protoc -", require_cmd=True) def test_2(self, completion): assert completion + assert any( + x.endswith("_out") or x.endswith("_out=") for x in completion + ) + + @pytest.mark.complete( + "protoc --non_existent_plugin_out ", cwd="shared/default" + ) + def test_all_out(self, completion): + assert completion == ["bar bar.d/", "foo.d/"] diff --git a/test/t/test_pydocstyle.py b/test/t/test_pydocstyle.py index caa87902..1f443208 100644 --- a/test/t/test_pydocstyle.py +++ b/test/t/test_pydocstyle.py @@ -6,6 +6,8 @@ class TestPydocstyle: def test_1(self, completion): assert completion - @pytest.mark.complete("pydocstyle -", require_cmd=True) + @pytest.mark.complete( + "pydocstyle -", require_cmd=True, require_longopt=True + ) def test_2(self, completion): assert completion diff --git a/test/t/test_pylint.py b/test/t/test_pylint.py index 4b799532..43a4c43f 100644 --- a/test/t/test_pylint.py +++ b/test/t/test_pylint.py @@ -2,7 +2,7 @@ import pytest class TestPylint: - @pytest.mark.complete("pylint --v", require_cmd=True) + @pytest.mark.complete("pylint --v", require_cmd=True, require_longopt=True) def test_1(self, completion): assert completion diff --git a/test/t/test_pytest.py b/test/t/test_pytest.py index 69d01820..e70c7a5d 100644 --- a/test/t/test_pytest.py +++ b/test/t/test_pytest.py @@ -1,3 +1,5 @@ +import inspect + import pytest @@ -9,3 +11,40 @@ class TestPytest: @pytest.mark.complete("pytest -") def test_2(self, completion): assert completion + + @pytest.mark.complete("pytest ../t/test_pytest.py:") + def test_classes_and_functions(self, completion): + assert completion == ":TestPytest :test_function_canary".split() + + @pytest.mark.complete("pytest ../t/test_pytest.py::TestPytest::") + def test_class_methods(self, completion): + methods = [ + x[0] + for x in inspect.getmembers(self, predicate=inspect.ismethod) + if x[0].startswith("test_") + ] + assert completion == methods + + @pytest.mark.complete("pytest pytest/test_async.py:") + def test_classes_and_async_functions(self, completion): + assert completion == ":Testing :test_positive".split() + + @pytest.mark.complete("pytest pytest/test_async.py::Testing::") + def test_async_class_methods(self, completion): + assert completion == "test_positive" + + def non_test_cananary_method(self): + pass + + +def test_function_canary(): + pass + + +def non_test_canary(): + pass + + +class NonTestCanaryClass: + def test_is_this_function_not(self): + pass diff --git a/test/t/test_python.py b/test/t/test_python.py index 57802721..5308dcb1 100644 --- a/test/t/test_python.py +++ b/test/t/test_python.py @@ -33,3 +33,7 @@ class TestPython: @pytest.mark.complete("python -m sy", require_cmd=True) def test_8(self, completion): assert completion + + @pytest.mark.complete("python -m json.", require_cmd=True) + def test_9(self, completion): + assert "json.tool" in completion diff --git a/test/t/test_python3.py b/test/t/test_python3.py index b968a34e..a4f6d966 100644 --- a/test/t/test_python3.py +++ b/test/t/test_python3.py @@ -33,3 +33,7 @@ class TestPython3: @pytest.mark.complete("python3 -m sy", require_cmd=True) def test_8(self, completion): assert completion + + @pytest.mark.complete("python3 -m json.", require_cmd=True) + def test_9(self, completion): + assert "json.tool" in completion diff --git a/test/t/test_ri.py b/test/t/test_ri.py index 9430b667..420b6cbb 100644 --- a/test/t/test_ri.py +++ b/test/t/test_ri.py @@ -13,4 +13,4 @@ class TestRi: @pytest.mark.complete("ri BashCompletio", require_cmd=True) def test_3(self, completion): - assert completion == "BashCompletion" + assert completion == "n" diff --git a/test/t/test_sbcl.py b/test/t/test_sbcl.py index cce4cba3..f05741a7 100644 --- a/test/t/test_sbcl.py +++ b/test/t/test_sbcl.py @@ -2,7 +2,6 @@ import pytest class TestSbcl: - @pytest.mark.xfail # TODO: whitespace split issue @pytest.mark.complete("sbcl shared/default/") def test_1(self, completion): - assert completion == ["bar", "bar bar.d/", "foo", "foo foo.d/"] + assert completion == ["bar", "bar bar.d/", "foo", "foo.d/"] diff --git a/test/t/test_sbcl_mt.py b/test/t/test_sbcl_mt.py index d8049f3f..c3965393 100644 --- a/test/t/test_sbcl_mt.py +++ b/test/t/test_sbcl_mt.py @@ -3,7 +3,6 @@ import pytest @pytest.mark.bashcomp(cmd="sbcl-mt") class TestSbclMt: - @pytest.mark.xfail # TODO: whitespace split issue @pytest.mark.complete("sbcl-mt shared/default/") def test_1(self, completion): - assert completion == ["bar", "bar bar.d/", "foo", "foo foo.d/"] + assert completion == ["bar", "bar bar.d/", "foo", "foo.d/"] diff --git a/test/t/test_scp.py b/test/t/test_scp.py new file mode 100644 index 00000000..66b8da22 --- /dev/null +++ b/test/t/test_scp.py @@ -0,0 +1,79 @@ +from itertools import chain + +import pytest + +from conftest import assert_bash_exec + +LIVE_HOST = "bash_completion" + + +class TestScp: + @pytest.mark.complete("scp -F config ", cwd="scp") + def test_basic(self, hosts, completion): + expected = sorted( + chain( + ( + "%s:" % x + for x in chain( + hosts, + # From fixtures/scp/config + "gee hut".split(), + # From fixtures/scp/known_hosts + "blah doo ike".split(), + ) + ), + # Local filenames + ["config", "known_hosts", r"spaced\ \ conf"], + ) + ) + assert completion == expected + + @pytest.mark.complete("scp -F 'spaced conf' ", cwd="scp") + def test_basic_spaced_conf(self, hosts, completion): + expected = sorted( + chain( + ( + "%s:" % x + for x in chain( + hosts, + # From "fixtures/scp/spaced conf" + "gee jar".split(), + # From fixtures/scp/known_hosts + "blah doo ike".split(), + ) + ), + # Local filenames + ["config", "known_hosts", r"spaced\ \ conf"], + ) + ) + assert completion == expected + + @pytest.mark.complete("scp -F") + def test_capital_f_without_space(self, completion): + assert completion + assert not any( + "option requires an argument -- F" in x for x in completion + ) + + @pytest.fixture(scope="class") + def live_pwd(self, bash): + try: + return assert_bash_exec( + bash, + "ssh -o 'Batchmode yes' -o 'ConnectTimeout 1' " + "%s pwd 2>/dev/null" % LIVE_HOST, + want_output=True, + ).strip() + except AssertionError: + pytest.skip("Live host %s not available" % LIVE_HOST) + + @pytest.mark.complete("scp %s:" % LIVE_HOST, sleep_after_tab=2) + def test_live(self, live_pwd, completion): + """ + To support this test, configure a HostName entry for LIVE_HOST + in ssh's configs, e.g. ~/.ssh/config or /etc/ssh/ssh_config. + + Connection to it must open sufficiently quickly for the + ConnectTimeout and sleep_after_tab settings. + """ + assert completion == "%s:%s/" % (LIVE_HOST, live_pwd) diff --git a/test/t/test_screen.py b/test/t/test_screen.py index d9254bda..3e98837f 100644 --- a/test/t/test_screen.py +++ b/test/t/test_screen.py @@ -19,17 +19,17 @@ class TestScreen: def test_4(self, completion): assert completion - @pytest.mark.complete("screen -T foo cat") + @pytest.mark.complete("screen -T foo ca") def test_5(self, completion): - assert completion + assert completion == "t" or "cat" in completion @pytest.mark.complete("screen //") def test_telnet(self, completion): - assert completion == "//telnet" + assert completion == "telnet" @pytest.mark.complete("screen cat //") def test_not_telnet(self, completion): - assert completion != "//telnet" + assert completion != "telnet" @pytest.mark.complete("screen //telnet ", env=dict(HOME="$PWD/shared")) def test_telnet_first_arg(self, completion): diff --git a/test/t/test_secret_tool.py b/test/t/test_secret_tool.py new file mode 100644 index 00000000..cbfc0cbc --- /dev/null +++ b/test/t/test_secret_tool.py @@ -0,0 +1,12 @@ +import pytest + + +@pytest.mark.bashcomp(cmd="secret-tool",) +class TestSecretTool: + @pytest.mark.complete("secret-tool ", require_cmd=True) + def test_modes(self, completion): + assert "store" in completion + + @pytest.mark.complete("secret-tool search ") + def test_no_complete(self, completion): + assert not completion diff --git a/test/t/test_sftp.py b/test/t/test_sftp.py index 0c039399..a421a449 100644 --- a/test/t/test_sftp.py +++ b/test/t/test_sftp.py @@ -1,11 +1,46 @@ +from itertools import chain + import pytest class TestSftp: @pytest.mark.complete("sftp -Fsp", cwd="sftp") def test_1(self, completion): - assert completion == "-Fspaced conf" + assert completion == r"aced\ \ conf" @pytest.mark.complete("sftp -", require_cmd=True) def test_2(self, completion): assert completion + + @pytest.mark.complete("sftp -F config ", cwd="sftp") + def test_hosts(self, hosts, completion): + expected = sorted( + chain( + hosts, + # From fixtures/sftp/config + "gee hut".split(), + # From fixtures/sftp/known_hosts + "10.10.10.10 doo ike".split(), + ) + ) + assert completion == expected + + @pytest.mark.complete(r"sftp -F spaced\ \ conf ", cwd="sftp") + def test_hosts_spaced_conf(self, hosts, completion): + expected = sorted( + chain( + hosts, + # From "fixtures/sftp/spaced conf" + "gee jar".split(), + # From fixtures/sftp/known_hosts + "10.10.10.10 doo ike".split(), + ) + ) + assert completion == expected + + @pytest.mark.complete("sftp -F") + def test_capital_f_without_space(self, completion): + assert completion + assert not any( + "option requires an argument -- F" in x for x in completion + ) diff --git a/test/t/test_slapt_get.py b/test/t/test_slapt_get.py index 626dde9e..92449711 100644 --- a/test/t/test_slapt_get.py +++ b/test/t/test_slapt_get.py @@ -1,8 +1,26 @@ +import os.path +from tempfile import mkstemp + import pytest +from conftest import assert_complete, is_bash_type + @pytest.mark.bashcomp(cmd="slapt-get") class TestSlaptGet: + @pytest.fixture(scope="class") + def slapt_getrc(self, request, bash): + fd, fname = mkstemp(prefix="slapt-getrc.", text=True) + request.addfinalizer(lambda: os.remove(fname)) + with os.fdopen(fd, "w") as f: + print( + "WORKINGDIR=%s/" + % os.path.join(bash.cwd, *"slackware var slapt-get".split()), + file=f, + ) + print("SOURCE=file:///home/", file=f) + return fname + @pytest.mark.complete("slapt-get -", require_cmd=True) def test_1(self, completion): assert completion @@ -14,3 +32,13 @@ class TestSlaptGet: @pytest.mark.complete("slapt-get -c non-existent-file --install ") def test_3(self, completion): assert not completion + + def test_install(self, bash, slapt_getrc): + if not is_bash_type(bash, "slapt-get"): + pytest.skip("slapt-get not found") + completion = assert_complete( + bash, "slapt-get -c %s --install " % slapt_getrc + ) + assert completion == sorted( + "abc-4-i686-1 ran-1.2-noarch-1 qwe-2.1-i486-1".split() + ) diff --git a/test/t/test_slapt_src.py b/test/t/test_slapt_src.py index dd443b04..b55b722d 100644 --- a/test/t/test_slapt_src.py +++ b/test/t/test_slapt_src.py @@ -1,16 +1,43 @@ +import os +from tempfile import mkstemp + import pytest +from conftest import assert_complete, is_bash_type + @pytest.mark.bashcomp(cmd="slapt-src") class TestSlaptSrc: + @pytest.fixture(scope="class") + def slapt_srcrc(self, request, bash): + fd, fname = mkstemp(prefix="slapt-srcrc.", text=True) + request.addfinalizer(lambda: os.remove(fname)) + with os.fdopen(fd, "w") as f: + print( + "BUILDDIR=%s/" + % os.path.join( + bash.cwd, *"slackware usr src slapt-src".split() + ), + file=f, + ) + return fname + @pytest.mark.complete("slapt-src -", require_cmd=True) def test_1(self, completion): assert completion @pytest.mark.complete("slapt-src --bu", require_cmd=True) def test_2(self, completion): - assert completion == "--build" + assert completion == "ild" or "--build" in completion @pytest.mark.complete("slapt-src --ins", require_cmd=True) def test_3(self, completion): - assert completion == "--install" + assert completion == "tall" or "--install" in completion + + def test_install(self, bash, slapt_srcrc): + if not is_bash_type(bash, "slapt-src"): + pytest.skip("slapt-src not found") + completion = assert_complete( + bash, "slapt-src --config %s --install " % slapt_srcrc + ) + assert completion == "abc:4 qwe:2.1".split() diff --git a/test/t/test_ssh.py b/test/t/test_ssh.py index 204b7c7c..8e958195 100644 --- a/test/t/test_ssh.py +++ b/test/t/test_ssh.py @@ -1,10 +1,12 @@ import pytest +from conftest import assert_complete, partialize + class TestSsh: @pytest.mark.complete("ssh -Fsp", cwd="ssh") def test_1(self, completion): - assert completion == "-Fspaced conf" + assert completion == r"aced\ \ conf" @pytest.mark.complete("ssh -F config ls", cwd="ssh") def test_2(self, completion): @@ -32,3 +34,27 @@ class TestSsh: @pytest.mark.complete("ssh -", require_cmd=True) def test_6(self, completion): assert completion + + @pytest.mark.complete("ssh -F") + def test_capital_f_without_space(self, completion): + assert completion + assert not any( + "option requires an argument -- F" in x for x in completion + ) + + @pytest.mark.complete("ssh -F nonexistent ") + def test_capital_f_nonexistent(self, completion): + assert completion + + def test_partial_hostname(self, bash, known_hosts): + first_char, partial_hosts = partialize(bash, known_hosts) + completion = assert_complete(bash, "ssh %s" % first_char) + if len(completion) == 1: + assert completion == partial_hosts[0][1:] + else: + assert completion == sorted(x for x in partial_hosts) + + @pytest.mark.parametrize("protocol", "4 6 9".split()) + def test_protocol_option_bundling(self, bash, protocol): + completion = assert_complete(bash, "ssh -%sF ssh/" % protocol) + assert "config" in completion diff --git a/test/t/test_ssh_keygen.py b/test/t/test_ssh_keygen.py index 2d53f5f8..b773ab4f 100644 --- a/test/t/test_ssh_keygen.py +++ b/test/t/test_ssh_keygen.py @@ -6,3 +6,54 @@ class TestSshKeygen: @pytest.mark.complete("ssh-keygen -", require_cmd=True) def test_1(self, completion): assert completion + + @pytest.mark.complete("ssh-keygen -s foo_key ssh-copy-id/.ssh/") + def test_filedir_pub_at_end_of_s(self, completion): + assert completion + assert all(x.endswith(".pub") for x in completion) + + @pytest.mark.complete("ssh-keygen -s foo_key -n foo,") + def test_usernames_for_n(self, completion): + assert completion + assert not any("," in x for x in completion) + # TODO check that these are usernames + + @pytest.mark.complete("ssh-keygen -s foo_key -h -n foo,") + def test_host_for_h_n(self, completion): + assert completion + assert not any("," in x for x in completion) + # TODO check that these are hostnames + + @pytest.mark.complete("ssh-keygen -Y foo -n ") + def test_n_with_Y(self, completion): + assert not completion + + @pytest.mark.complete("ssh-keygen -r ") + def test_r_without_Y(self, completion): + assert not completion + + @pytest.mark.complete("ssh-keygen -Y foo -r ") + def test_r_with_Y(self, completion): + assert "ssh/" in completion + + @pytest.mark.complete("ssh-keygen -t ecdsa -b ") + def test_ecdsa_b(self, completion): + assert completion + + @pytest.mark.complete("ssh-keygen -t ecdsa-sk -b ") + def test_ecdsa_sk_b(self, completion): + assert not completion + + @pytest.mark.complete("ssh-keygen -O ") + def test_O(self, completion): + assert completion + assert any(x.endswith("=") for x in completion) + + @pytest.mark.complete("ssh-keygen -O force-command=bas") + def test_O_force_command(self, completion): + assert completion + assert not completion.startswith("force-command=") + + @pytest.mark.complete("ssh-keygen -O unknown=") + def test_O_unknown(self, completion): + assert not completion diff --git a/test/t/test_sudo.py b/test/t/test_sudo.py index ced6662e..a3494664 100644 --- a/test/t/test_sudo.py +++ b/test/t/test_sudo.py @@ -10,27 +10,27 @@ class TestSudo: @pytest.mark.complete("sudo cd fo", cwd="shared/default") def test_2(self, completion): - assert completion == "foo.d/" + assert completion == "o.d/" assert not completion.endswith(" ") @pytest.mark.complete("sudo sh share") def test_3(self, completion): - assert completion == "shared/" + assert completion == "d/" assert not completion.endswith(" ") @pytest.mark.complete("sudo mount /dev/sda1 def", cwd="shared") def test_4(self, completion): - assert completion == "default/" + assert completion == "ault/" assert not completion.endswith(" ") @pytest.mark.complete("sudo -e -u root bar foo", cwd="shared/default") def test_5(self, completion): - assert completion == ["foo", "foo.d/"] + assert completion == "foo foo.d/".split() def test_6(self, bash, part_full_user): part, full = part_full_user completion = assert_complete(bash, "sudo chown %s" % part) - assert completion == full + assert completion == full[len(part) :] assert completion.endswith(" ") def test_7(self, bash, part_full_user, part_full_group): @@ -39,32 +39,32 @@ class TestSudo: completion = assert_complete( bash, "sudo chown %s:%s" % (user, partgroup) ) - assert completion == "%s:%s" % (user, fullgroup) + assert completion == fullgroup[len(partgroup) :] assert completion.endswith(" ") def test_8(self, bash, part_full_group): part, full = part_full_group completion = assert_complete(bash, "sudo chown dot.user:%s" % part) - assert completion == "dot.user:%s" % full + assert completion == full[len(part) :] assert completion.endswith(" ") - @pytest.mark.xfail # TODO check escaping, whitespace - def test_9(self, bash, part_full_group): - """Test preserving special chars in $prefix$partgroup<TAB>.""" - part, full = part_full_group - for prefix in ( + @pytest.mark.parametrize( + "prefix", + [ r"funky\ user:", "funky.user:", r"funky\.user:", r"fu\ nky.user:", r"f\ o\ o\.\bar:", r"foo\_b\ a\.r\ :", - ): - completion = assert_complete( - bash, "sudo chown %s%s" % (prefix, part) - ) - assert completion == "%s%s" % (prefix, full) - assert completion.endswith(" ") + ], + ) + def test_9(self, bash, part_full_group, prefix): + """Test preserving special chars in $prefix$partgroup<TAB>.""" + part, full = part_full_group + completion = assert_complete(bash, "sudo chown %s%s" % (prefix, part)) + assert completion == full[len(part) :] + assert completion.endswith(" ") def test_10(self, bash, part_full_user, part_full_group): """Test giving up on degenerate cases instead of spewing junk.""" diff --git a/test/t/test_tar.py b/test/t/test_tar.py index 309bcc76..4518d0bd 100644 --- a/test/t/test_tar.py +++ b/test/t/test_tar.py @@ -77,28 +77,22 @@ class TestTar: @pytest.mark.complete("tar --add-fil") def test_15(self, completion, gnu_tar): - assert completion == "--add-file=" + assert completion == "e=" assert not completion.endswith(" ") @pytest.mark.complete("tar -cf /dev/null --posi") def test_16(self, completion, gnu_tar): - assert completion == "--posix" + assert completion == "x" assert completion.endswith(" ") @pytest.mark.complete("tar --owner=") - def test_17(self, bash, completion, gnu_tar): - users = sorted( - assert_bash_exec(bash, "compgen -A user", want_output=True).split() - ) + def test_17(self, bash, completion, gnu_tar, output_sort_uniq): + users = output_sort_uniq("compgen -u") assert completion == users @pytest.mark.complete("tar --group=") - def test_18(self, bash, completion, gnu_tar): - groups = sorted( - assert_bash_exec( - bash, "compgen -A group", want_output=True - ).split() - ) + def test_18(self, bash, completion, gnu_tar, output_sort_uniq): + groups = output_sort_uniq("compgen -g") assert completion == groups # Use -b for this as -b is still not handled by tar's completion @@ -121,6 +115,6 @@ class TestTar: @pytest.mark.complete(r"tar tf escape.tar a/b\'", cwd="tar") def test_22(self, bash, completion): """Test listing escaped chars in old option.""" - assert completion == "a/b'c/" + assert completion == "c/" # TODO: "tar tf escape.tar a/b" diff --git a/test/t/test_totem.py b/test/t/test_totem.py new file mode 100644 index 00000000..f6fb26fe --- /dev/null +++ b/test/t/test_totem.py @@ -0,0 +1,7 @@ +import pytest + + +class TestTotem: + @pytest.mark.complete("totem ") + def test_basic(self, completion): + assert completion diff --git a/test/t/test_tshark.py b/test/t/test_tshark.py index 8ed881ee..f49533e0 100644 --- a/test/t/test_tshark.py +++ b/test/t/test_tshark.py @@ -13,9 +13,8 @@ class TestTshark: @pytest.mark.complete("tshark -O foo,htt", require_cmd=True) def test_3(self, completion): - # When there's only one completion, it's be the one with "foo," prefix; - # when multiple (e.g. http and http2), it's the completion alone. - assert completion == "foo,http" or "http" in completion + # p: one completion only; http: e.g. http and http2 + assert completion == "p" or "http" in completion @pytest.mark.complete("tshark -o tcp", require_cmd=True) def test_4(self, completion): @@ -29,3 +28,7 @@ class TestTshark: def test_6(self, completion): """Test there are no URLs in completions.""" assert not any("://" in x for x in completion) + + @pytest.mark.complete("tshark -r ") + def test_input_files(self, completion): + assert completion diff --git a/test/t/test_tsig_keygen.py b/test/t/test_tsig_keygen.py new file mode 100644 index 00000000..8c8a64ac --- /dev/null +++ b/test/t/test_tsig_keygen.py @@ -0,0 +1,12 @@ +import pytest + + +@pytest.mark.bashcomp(cmd="tsig-keygen") +class TestTsigKeygen: + @pytest.mark.complete("tsig-keygen ") + def test_basic(self, completion): + assert not completion + + @pytest.mark.complete("tsig-keygen -", require_cmd=True) + def test_options(self, completion): + assert completion diff --git a/test/t/test_umount.py b/test/t/test_umount.py index dd4ae0b5..2baf0dac 100644 --- a/test/t/test_umount.py +++ b/test/t/test_umount.py @@ -1,7 +1,85 @@ import pytest +from conftest import assert_bash_exec + class TestUmount: + @pytest.fixture(scope="class") + def dummy_mnt(self, request, bash): + """ + umount completion from fstab can't be tested directly because it + (correctly) uses absolute paths. So we create a custom completion which + reads from a file in our text fixture instead. + """ + assert_bash_exec(bash, "unset COMPREPLY cur; unset -f _mnt_completion") + assert_bash_exec( + bash, + "_mnt_completion() { " + "local cur=$(_get_cword); " + "_linux_fstab $(_get_pword) < mount/test-fstab; " + "} && complete -F _mnt_completion _mnt", + ) + request.addfinalizer( + lambda: assert_bash_exec( + bash, "complete -r _mnt; unset -f _mnt_completion" + ) + ) + @pytest.mark.complete("umount ") def test_1(self, completion): assert completion + + @pytest.mark.complete("_mnt /mnt/nice-test-p") + def test_mnt_basic(self, completion, dummy_mnt): + assert completion == "ath" + + # Note in tests below that return only one result, that the result + # is shell unescaped due to how assert_complete handles the + # "one result on same line case". + + @pytest.mark.complete(r"_mnt /mnt/nice\ test-p") + def test_mnt_space(self, completion, dummy_mnt): + assert completion == r"ath" + + @pytest.mark.complete(r"_mnt /mnt/nice\$test-p") + def test_mnt_dollar(self, completion, dummy_mnt): + assert completion == "ath" + + @pytest.mark.complete(r"_mnt /mnt/nice\ test\\p") + def test_mnt_backslash(self, completion, dummy_mnt): + assert completion == "ath" + + @pytest.mark.complete(r"_mnt /mnt/nice\ ") + def test_mnt_after_space(self, completion, dummy_mnt): + assert completion == sorted( + (r"/mnt/nice\ test\\path", r"/mnt/nice\ test-path") + ) + + @pytest.mark.complete(r"_mnt /mnt/nice\$") + def test_mnt_at_dollar(self, completion, dummy_mnt): + assert completion == "test-path" + + @pytest.mark.complete(r"_mnt /mnt/nice\'") + def test_mnt_at_quote(self, completion, dummy_mnt): + assert completion == "test-path" + + @pytest.mark.complete("_mnt /mnt/other") + def test_mnt_other(self, completion, dummy_mnt): + assert completion == r"\'test\ path" + + @pytest.mark.complete("_mnt -L Ubu") + def test_mnt_label_space(self, completion, dummy_mnt): + assert completion == r"ntu\ Karmic" + + @pytest.mark.complete("_mnt -L Deb") + def test_mnt_label_quote(self, completion, dummy_mnt): + assert completion == r"ian-it\'s\ awesome" + + def test_linux_fstab_unescape(self, bash): + assert_bash_exec(bash, r"var=one\'two\\040three\\") + assert_bash_exec(bash, "__linux_fstab_unescape var") + output = assert_bash_exec( + bash, r'printf "%s\n" "$var"', want_output=True + ) + assert output.strip() == "one'two three\\" + assert_bash_exec(bash, "unset var") diff --git a/test/t/test_upgradepkg.py b/test/t/test_upgradepkg.py index 4c72a158..87fe8e4c 100644 --- a/test/t/test_upgradepkg.py +++ b/test/t/test_upgradepkg.py @@ -32,3 +32,20 @@ class TestUpgradepkg: ] ) assert completion == expected + + @pytest.mark.complete("upgradepkg foo%", cwd="slackware/home") + def test_after_percent(self, completion): + expected = sorted( + [ + "%s/" % x + for x in os.listdir("slackware/home") + if os.path.isdir("./slackware/home/%s" % x) + ] + + [ + x + for x in os.listdir("slackware/home") + if os.path.isfile("./slackware/home/%s" % x) + and fnmatch.fnmatch(x, "*.t[bglx]z") + ] + ) + assert completion == ["foo%%%s" % x for x in expected] diff --git a/test/t/test_userdel.py b/test/t/test_userdel.py index 718c6629..3405e127 100644 --- a/test/t/test_userdel.py +++ b/test/t/test_userdel.py @@ -6,6 +6,6 @@ class TestUserdel: def test_1(self, completion): assert completion - @pytest.mark.complete("userdel root") + @pytest.mark.complete("userdel roo") def test_2(self, completion): - assert "root" in completion + assert completion == "t" or "root" in completion diff --git a/test/t/test_valgrind.py b/test/t/test_valgrind.py index c7c979dd..0553b556 100644 --- a/test/t/test_valgrind.py +++ b/test/t/test_valgrind.py @@ -16,13 +16,13 @@ class TestValgrind: @pytest.mark.complete("valgrind --tool=memche", require_cmd=True) def test_3(self, completion): - assert "--tool=memcheck" in completion + assert completion == "ck" or "--tool=memcheck" in completion @pytest.mark.complete( "valgrind --tool=helgrind --history-l", require_cmd=True ) def test_4(self, completion): - assert "--history-level=" in completion + assert completion == "evel=" or "--history-level=" in completion assert not completion.endswith(" ") @pytest.mark.complete(r"valgrind --log-file=v\ 0.log ./bin/", cwd="shared") diff --git a/test/t/test_wol.py b/test/t/test_wol.py index b7a622ee..bf04f76e 100644 --- a/test/t/test_wol.py +++ b/test/t/test_wol.py @@ -5,14 +5,15 @@ import pytest class TestWol: @pytest.mark.complete("wol ") def test_1(self, completion): - assert ( - completion == "00:00:00:00:00:00 11:11:11:11:11:11 " + assert all( + x in completion + for x in "00:00:00:00:00:00 11:11:11:11:11:11 " "22:22:22:22:22:22 33:33:33:33:33:33".split() ) @pytest.mark.complete("wol 00:") def test_2(self, completion): - assert completion == "00:00:00:00:00:00" + assert any(x.endswith("00:00:00:00:00") for x in completion) @pytest.mark.complete("wol -", require_cmd=True) def test_3(self, completion): diff --git a/test/t/test_write.py b/test/t/test_write.py index 8f0886e4..fc4bfa00 100644 --- a/test/t/test_write.py +++ b/test/t/test_write.py @@ -2,6 +2,6 @@ import pytest class TestWrite: - @pytest.mark.complete("write root") + @pytest.mark.complete("write roo") def test_1(self, completion): - assert "root" in completion + assert completion == "t" or "root" in completion diff --git a/test/t/test_xfreerdp.py b/test/t/test_xfreerdp.py index a8435d6c..56162714 100644 --- a/test/t/test_xfreerdp.py +++ b/test/t/test_xfreerdp.py @@ -5,10 +5,20 @@ from conftest import assert_bash_exec class TestXfreerdp: def _help(self, bash): - return assert_bash_exec(bash, "xfreerdp --help || :", want_output=True) + return assert_bash_exec( + bash, "xfreerdp --help 2>&1 || :", want_output=True + ) @pytest.fixture(scope="class") - def slash_syntax(self, bash): + def help_success(self, bash): + output = self._help(bash) + # Example from our CentOS 7 container + # [04:51:31:663] [238:238] [ERROR][com.freerdp.client.x11] - Failed to get pixmap info + if not output or "ERROR" in output.strip().splitlines()[0]: + pytest.skip("--help errored") + + @pytest.fixture(scope="class") + def slash_syntax(self, bash, help_success): if "/help" not in self._help(bash): pytest.skip("Not slash syntax") @@ -18,27 +28,31 @@ class TestXfreerdp: pytest.skip("Not dash syntax") @pytest.mark.complete("xfreerdp /", require_cmd=True) - def test_1(self, bash, completion, slash_syntax): + def test_1(self, bash, completion, help_success, slash_syntax): assert completion @pytest.mark.complete("xfreerdp -", require_cmd=True) - def test_2(self, completion): + def test_2(self, completion, help_success): assert completion @pytest.mark.complete("xfreerdp +", require_cmd=True) - def test_3(self, bash, completion, slash_syntax): + def test_3(self, bash, completion, help_success, slash_syntax): assert completion - @pytest.mark.complete("xfreerdp /kbd:", require_cmd=True) - def test_4(self, bash, completion, slash_syntax): + @pytest.mark.complete( + "xfreerdp /kbd:", + require_cmd=True, + skipif='test -z "$(xfreerdp /kbd-list 2>/dev/null)"', + ) + def test_4(self, bash, completion, help_success, slash_syntax): assert completion @pytest.mark.complete("xfreerdp /help ", require_cmd=True) - def test_5(self, completion): + def test_5(self, completion, help_success): assert not completion @pytest.mark.complete("xfreerdp -k ", require_cmd=True) - def test_6(self, bash, completion, dash_syntax): + def test_6(self, bash, completion, help_success, dash_syntax): assert completion @pytest.mark.complete("xfreerdp --help ", require_cmd=True) diff --git a/test/t/test_xgamma.py b/test/t/test_xgamma.py index beb684f8..151e2d36 100644 --- a/test/t/test_xgamma.py +++ b/test/t/test_xgamma.py @@ -8,5 +8,5 @@ class TestXgamma: @pytest.mark.complete("xgamma -gam", require_cmd=True) def test_2(self, completion): - assert completion == "-gamma" + assert completion == "ma" assert completion.endswith(" ") diff --git a/test/t/test_xhost.py b/test/t/test_xhost.py new file mode 100644 index 00000000..bb2df82a --- /dev/null +++ b/test/t/test_xhost.py @@ -0,0 +1,22 @@ +import pytest + +from conftest import assert_complete, partialize + + +@pytest.mark.bashcomp(pre_cmds=("HOME=$PWD",)) +class TestXhost: + @pytest.mark.parametrize("prefix", ["+", "-", ""]) + def test_hosts(self, bash, hosts, prefix): + completion = assert_complete(bash, "xhost %s" % prefix) + assert completion == ["%s%s" % (prefix, x) for x in hosts] + + @pytest.mark.parametrize("prefix", ["+", "-", ""]) + def test_partial_hosts(self, bash, hosts, prefix): + first_char, partial_hosts = partialize(bash, hosts) + completion = assert_complete(bash, "xhost %s%s" % (prefix, first_char)) + if len(completion) == 1: + assert completion == partial_hosts[0][1:] + else: + assert completion == sorted( + "%s%s" % (prefix, x) for x in partial_hosts + ) diff --git a/test/t/unit/Makefile.am b/test/t/unit/Makefile.am index b96b326c..3eb652af 100644 --- a/test/t/unit/Makefile.am +++ b/test/t/unit/Makefile.am @@ -8,12 +8,15 @@ EXTRA_DIST = \ 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_variables.py \ + test_unit_xinetd_services.py all: diff --git a/test/t/unit/test_unit_count_args.py b/test/t/unit/test_unit_count_args.py index c0afe736..56bce2cb 100644 --- a/test/t/unit/test_unit_count_args.py +++ b/test/t/unit/test_unit_count_args.py @@ -1,6 +1,6 @@ import pytest -from conftest import assert_bash_exec, TestUnitBase +from conftest import TestUnitBase, assert_bash_exec @pytest.mark.bashcomp( @@ -11,7 +11,7 @@ class TestUnitCountArgs(TestUnitBase): return self._test_unit("_count_args %s; echo $args", *args, **kwargs) def test_1(self, bash): - assert_bash_exec(bash, "_count_args >/dev/null") + assert_bash_exec(bash, "COMP_CWORD= _count_args >/dev/null") def test_2(self, bash): """a b| should set args to 1""" diff --git a/test/t/unit/test_unit_expand.py b/test/t/unit/test_unit_expand.py index 7c0a9836..d2a3ebc4 100644 --- a/test/t/unit/test_unit_expand.py +++ b/test/t/unit/test_unit_expand.py @@ -3,7 +3,7 @@ import pytest from conftest import assert_bash_exec -@pytest.mark.bashcomp(cmd=None) +@pytest.mark.bashcomp(cmd=None, ignore_env=r"^[+-](cur|COMPREPLY)=") class TestUnitExpand: def test_1(self, bash): assert_bash_exec(bash, "_expand >/dev/null") @@ -11,3 +11,21 @@ class TestUnitExpand: 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 index fbc172df..17bdedfe 100644 --- a/test/t/unit/test_unit_expand_tilde_by_ref.py +++ b/test/t/unit/test_unit_expand_tilde_by_ref.py @@ -3,7 +3,7 @@ import pytest from conftest import assert_bash_exec -@pytest.mark.bashcomp(cmd=None) +@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") @@ -14,3 +14,33 @@ class TestUnitExpandTildeByRef: 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 index 7f14f294..b847efc2 100644 --- a/test/t/unit/test_unit_filedir.py +++ b/test/t/unit/test_unit_filedir.py @@ -1,3 +1,9 @@ +import os +import shutil +import sys +import tempfile +from pathlib import Path + import pytest from conftest import assert_bash_exec, assert_complete @@ -24,102 +30,206 @@ class TestUnitFiledir: "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 == "ab/e" + 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 == "a b/i" + 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 == "a'b/c" + 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 == "a&b/f" + 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 == "a$b/" + 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 == "ab/e" + 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 == "a b/i" + 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 == "a$b/h" + 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 == "a&b/f" + 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 == "ab/e" + 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 == "a b/i" + 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 == "a'b/c" + 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 == "a&b/f" + assert completion == 'f"' @pytest.mark.complete(r"fd a\ ", cwd="_filedir") def test_15(self, functions, completion): - assert completion == "a b/" + 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_get_comp_words_by_ref.py b/test/t/unit/test_unit_get_comp_words_by_ref.py index 1603bad6..b6498fa7 100644 --- a/test/t/unit/test_unit_get_comp_words_by_ref.py +++ b/test/t/unit/test_unit_get_comp_words_by_ref.py @@ -1,16 +1,17 @@ import pytest -from conftest import assert_bash_exec, TestUnitBase +from conftest import TestUnitBase, assert_bash_exec @pytest.mark.bashcomp( - cmd=None, ignore_env=r"^(\+(cur|prev)|[+-]COMP_(WORDS|CWORD|LINE|POINT))=" + 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", + "_get_comp_words_by_ref %s cur prev; echo $cur,${prev-}", bash, *args, **kwargs @@ -18,7 +19,11 @@ class TestUnitGetCompWordsByRef(TestUnitBase): return output.strip() def test_1(self, bash): - assert_bash_exec(bash, "_get_comp_words_by_ref cur >/dev/null") + 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|""" @@ -165,3 +170,91 @@ class TestUnitGetCompWordsByRef(TestUnitBase): """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 index 3042dd29..0b56d163 100644 --- a/test/t/unit/test_unit_get_cword.py +++ b/test/t/unit/test_unit_get_cword.py @@ -1,17 +1,22 @@ +import pexpect import pytest -from conftest import assert_bash_exec, TestUnitBase +from conftest import PS1, TestUnitBase, assert_bash_exec @pytest.mark.bashcomp( - cmd=None, ignore_env=r"^[+-]COMP_(WORDS|CWORD|LINE|POINT)=" + 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, "_get_cword >/dev/null") + 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""" @@ -133,3 +138,17 @@ class TestUnitGetCword(TestUnitBase): """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 index 64f3b511..64a5a790 100644 --- a/test/t/unit/test_unit_init_completion.py +++ b/test/t/unit/test_unit_init_completion.py @@ -1,6 +1,6 @@ import pytest -from conftest import assert_bash_exec, TestUnitBase +from conftest import TestUnitBase, assert_bash_exec, assert_complete @pytest.mark.bashcomp( @@ -13,12 +13,22 @@ class TestUnitInitCompletion(TestUnitBase): """Test environment non-pollution, detected at teardown.""" assert_bash_exec( bash, - "foo() { local cur prev words cword; _init_completion; }; " + "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 + "_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_known_hosts_real.py b/test/t/unit/test_unit_known_hosts_real.py new file mode 100644 index 00000000..ac5205e1 --- /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 index ac0ac836..c5488e34 100644 --- a/test/t/unit/test_unit_longopt.py +++ b/test/t/unit/test_unit_longopt.py @@ -11,6 +11,8 @@ class TestUnitLongopt: 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): @@ -32,3 +34,19 @@ class TestUnitLongopt: 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_quote.py b/test/t/unit/test_unit_quote.py index e9f81c2d..b280bd68 100644 --- a/test/t/unit/test_unit_quote.py +++ b/test/t/unit/test_unit_quote.py @@ -1,6 +1,6 @@ import pytest -from conftest import assert_bash_exec, TestUnitBase +from conftest import TestUnitBase, assert_bash_exec @pytest.mark.bashcomp(cmd=None) 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 00000000..e2b437e3 --- /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_variables.py b/test/t/unit/test_unit_variables.py index dd7a4219..d62bc4a4 100644 --- a/test/t/unit/test_unit_variables.py +++ b/test/t/unit/test_unit_variables.py @@ -18,11 +18,11 @@ class TestUnitVariables: @pytest.mark.complete(": $___v") def test_simple_variable_name(self, functions, completion): - assert completion == "$___var".split() + assert completion == "ar" @pytest.mark.complete(": ${assoc1[") def test_single_array_index(self, functions, completion): - assert completion == "${assoc1[idx]}".split() + assert completion == "idx]}" @pytest.mark.complete(": ${assoc2[") def test_multiple_array_indexes(self, functions, completion): @@ -30,12 +30,12 @@ class TestUnitVariables: @pytest.mark.complete(": ${assoc1[bogus]") def test_closing_curly_after_square(self, functions, completion): - assert completion == "${assoc1[bogus]}".split() + assert completion == "}" @pytest.mark.complete(": ${assoc1[@") def test_closing_brackets_after_at(self, functions, completion): - assert completion == "${assoc1[@]}".split() + assert completion == "]}" @pytest.mark.complete(": ${#___v") def test_hash_prefix(self, functions, completion): - assert completion == "${#___var}".split() + 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 00000000..7a90cb7f --- /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"] |