summaryrefslogtreecommitdiffstats
path: root/test/t
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:03:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:03:18 +0000
commitf4acb49ea148cdd899f7f29f1591c7bc853c2135 (patch)
treeb62d60873864065d6428a84a119dd8a3c90f1397 /test/t
parentAdding upstream version 1:2.11. (diff)
downloadbash-completion-f4acb49ea148cdd899f7f29f1591c7bc853c2135.tar.xz
bash-completion-f4acb49ea148cdd899f7f29f1591c7bc853c2135.zip
Adding upstream version 1:2.12.0.upstream/1%2.12.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/t')
-rw-r--r--test/t/Makefile.am33
-rw-r--r--test/t/conftest.py780
-rw-r--r--test/t/test_2to3.py2
-rw-r--r--test/t/test_7z.py6
-rw-r--r--test/t/test_alias.py8
-rw-r--r--test/t/test_ant.py39
-rw-r--r--test/t/test_apt_mark.py39
-rw-r--r--test/t/test_arp.py2
-rw-r--r--test/t/test_arpspoof.py9
-rw-r--r--test/t/test_avahi_browse.py27
-rw-r--r--test/t/test_b2sum.py11
-rw-r--r--test/t/test_bsdtar.py11
-rw-r--r--test/t/test_cd.py10
-rw-r--r--test/t/test_chmod.py1
-rw-r--r--test/t/test_chown.py2
-rw-r--r--test/t/test_chsh.py13
-rw-r--r--test/t/test_configure.py8
-rw-r--r--test/t/test_convert.py2
-rw-r--r--test/t/test_cpan2dist.py4
-rw-r--r--test/t/test_createdb.py1
-rw-r--r--test/t/test_createuser.py1
-rw-r--r--test/t/test_csplit.py2
-rw-r--r--test/t/test_curl.py17
-rw-r--r--test/t/test_cvs.py20
-rw-r--r--test/t/test_cvsps.py5
-rw-r--r--test/t/test_date.py2
-rw-r--r--test/t/test_dcop.py4
-rw-r--r--test/t/test_dd.py8
-rw-r--r--test/t/test_declare.py16
-rw-r--r--test/t/test_dict.py24
-rw-r--r--test/t/test_dmesg.py7
-rw-r--r--test/t/test_dmypy.py2
-rw-r--r--test/t/test_dot.py8
-rw-r--r--test/t/test_dpkg.py10
-rw-r--r--test/t/test_dpkg_query.py8
-rw-r--r--test/t/test_dropdb.py1
-rw-r--r--test/t/test_evince.py36
-rw-r--r--test/t/test_export.py17
-rw-r--r--test/t/test_finger.py4
-rw-r--r--test/t/test_fio.py40
-rw-r--r--test/t/test_firefox.py7
-rw-r--r--test/t/test_function.py12
-rw-r--r--test/t/test_gdb.py10
-rw-r--r--test/t/test_getconf.py2
-rw-r--r--test/t/test_grep.py12
-rw-r--r--test/t/test_gssdp_device_sniffer.py14
-rw-r--r--test/t/test_hash.py11
-rw-r--r--test/t/test_help.py21
-rw-r--r--test/t/test_info.py8
-rw-r--r--test/t/test_installpkg.py3
-rw-r--r--test/t/test_invoke_rc_d.py9
-rw-r--r--test/t/test_ip.py15
-rw-r--r--test/t/test_iperf.py5
-rw-r--r--test/t/test_iperf3.py5
-rw-r--r--test/t/test_isort.py2
-rw-r--r--test/t/test_java.py45
-rw-r--r--test/t/test_javaws.py8
-rw-r--r--test/t/test_jq.py8
-rw-r--r--test/t/test_jsonschema.py4
-rw-r--r--test/t/test_kdvi.py22
-rw-r--r--test/t/test_kill.py4
-rw-r--r--test/t/test_killall.py1
-rw-r--r--test/t/test_kpdf.py18
-rw-r--r--test/t/test_ld.py2
-rw-r--r--test/t/test_lrzip.py9
-rw-r--r--test/t/test_ls.py2
-rw-r--r--test/t/test_lvm.py8
-rw-r--r--test/t/test_make.py56
-rw-r--r--test/t/test_makepkg.py2
-rw-r--r--test/t/test_man.py77
-rw-r--r--test/t/test_md5sum.py2
-rw-r--r--test/t/test_munindoc.py1
-rw-r--r--test/t/test_mutt.py13
-rw-r--r--test/t/test_mypy.py2
-rw-r--r--test/t/test_neomutt.py7
-rw-r--r--test/t/test_nmap.py2
-rw-r--r--test/t/test_nproc.py8
-rw-r--r--test/t/test_op.py11
-rw-r--r--test/t/test_pdftoppm.py11
-rw-r--r--test/t/test_pgrep.py2
-rw-r--r--test/t/test_pidof.py1
-rw-r--r--test/t/test_pkg_config.py10
-rw-r--r--test/t/test_pkgconf.py22
-rw-r--r--test/t/test_portinstall.py13
-rw-r--r--test/t/test_pr.py2
-rw-r--r--test/t/test_printenv.py2
-rw-r--r--test/t/test_ps.py50
-rw-r--r--test/t/test_psql.py1
-rw-r--r--test/t/test_pushd.py4
-rw-r--r--test/t/test_pydocstyle.py4
-rw-r--r--test/t/test_pylint.py14
-rw-r--r--test/t/test_pyston.py19
-rw-r--r--test/t/test_pytest.py2
-rw-r--r--test/t/test_python.py47
-rw-r--r--test/t/test_python3.py10
-rw-r--r--test/t/test_qemu.py4
-rw-r--r--test/t/test_qemu_system_x86_64.py16
-rw-r--r--test/t/test_reportbug.py4
-rw-r--r--test/t/test_rmdir.py2
-rw-r--r--test/t/test_rpm.py2
-rw-r--r--test/t/test_rsync.py6
-rw-r--r--test/t/test_scp.py20
-rw-r--r--test/t/test_secret_tool.py4
-rw-r--r--test/t/test_service.py6
-rw-r--r--test/t/test_sha224sum.py7
-rw-r--r--test/t/test_sha256sum.py15
-rw-r--r--test/t/test_sha384sum.py7
-rw-r--r--test/t/test_sha512sum.py7
-rw-r--r--test/t/test_ssh.py13
-rw-r--r--test/t/test_ssh_add.py20
-rw-r--r--test/t/test_ssh_copy_id.py2
-rw-r--r--test/t/test_ssh_keygen.py30
-rw-r--r--test/t/test_ssh_keyscan.py19
-rw-r--r--test/t/test_sshfs.py2
-rw-r--r--test/t/test_su.py4
-rw-r--r--test/t/test_sudo.py10
-rw-r--r--test/t/test_sum.py2
-rw-r--r--test/t/test_synclient.py1
-rw-r--r--test/t/test_tar.py6
-rw-r--r--test/t/test_time.py12
-rw-r--r--test/t/test_tox.py4
-rw-r--r--test/t/test_tree.py23
-rw-r--r--test/t/test_truncate.py11
-rw-r--r--test/t/test_tshark.py4
-rw-r--r--test/t/test_ulimit.py14
-rw-r--r--test/t/test_umount.py11
-rw-r--r--test/t/test_upgradepkg.py4
-rw-r--r--test/t/test_useradd.py4
-rw-r--r--test/t/test_usermod.py4
-rw-r--r--test/t/test_valgrind.py1
-rw-r--r--test/t/test_vipw.py7
-rw-r--r--test/t/test_vncviewer.py4
-rw-r--r--test/t/test_who.py4
-rw-r--r--test/t/test_wine.py4
-rw-r--r--test/t/test_xdg_mime.py6
-rw-r--r--test/t/test_xev.py11
-rw-r--r--test/t/test_xfreerdp.py6
-rw-r--r--test/t/test_xhost.py8
-rw-r--r--test/t/test_xmlwf.py4
-rw-r--r--test/t/test_xrandr.py119
-rw-r--r--test/t/test_xz.py5
-rw-r--r--test/t/unit/Makefile.am27
-rw-r--r--test/t/unit/test_unit_abspath.py67
-rw-r--r--test/t/unit/test_unit_command_offset.py144
-rw-r--r--test/t/unit/test_unit_compgen.py173
-rw-r--r--test/t/unit/test_unit_compgen_commands.py47
-rw-r--r--test/t/unit/test_unit_compgen_split.py102
-rw-r--r--test/t/unit/test_unit_count_args.py134
-rw-r--r--test/t/unit/test_unit_delimited.py42
-rw-r--r--test/t/unit/test_unit_deprecate_func.py15
-rw-r--r--test/t/unit/test_unit_dequote.py161
-rw-r--r--test/t/unit/test_unit_expand.py21
-rw-r--r--test/t/unit/test_unit_expand_glob.py83
-rw-r--r--test/t/unit/test_unit_expand_tilde.py (renamed from test/t/unit/test_unit_expand_tilde_by_ref.py)31
-rw-r--r--test/t/unit/test_unit_filedir.py10
-rw-r--r--test/t/unit/test_unit_get_cword.py20
-rw-r--r--test/t/unit/test_unit_get_first_arg.py90
-rw-r--r--test/t/unit/test_unit_get_words.py (renamed from test/t/unit/test_unit_get_comp_words_by_ref.py)30
-rw-r--r--test/t/unit/test_unit_init_completion.py34
-rw-r--r--test/t/unit/test_unit_initialize.py66
-rw-r--r--test/t/unit/test_unit_ip_addresses.py20
-rw-r--r--test/t/unit/test_unit_known_hosts.py (renamed from test/t/unit/test_unit_known_hosts_real.py)109
-rw-r--r--test/t/unit/test_unit_load_completion.py94
-rw-r--r--test/t/unit/test_unit_longopt.py4
-rw-r--r--test/t/unit/test_unit_looks_like_path.py33
-rw-r--r--test/t/unit/test_unit_parse_help.py63
-rw-r--r--test/t/unit/test_unit_parse_usage.py46
-rw-r--r--test/t/unit/test_unit_pgids.py34
-rw-r--r--test/t/unit/test_unit_pids.py34
-rw-r--r--test/t/unit/test_unit_pnames.py29
-rw-r--r--test/t/unit/test_unit_quote.py19
-rw-r--r--test/t/unit/test_unit_quote_compgen.py173
-rw-r--r--test/t/unit/test_unit_quote_readline.py15
-rw-r--r--test/t/unit/test_unit_realcommand.py90
-rw-r--r--test/t/unit/test_unit_split.py90
-rw-r--r--test/t/unit/test_unit_tilde.py9
-rw-r--r--test/t/unit/test_unit_unlocal.py18
-rw-r--r--test/t/unit/test_unit_variables.py6
-rw-r--r--test/t/unit/test_unit_xfunc.py71
-rw-r--r--test/t/unit/test_unit_xinetd_services.py10
180 files changed, 4015 insertions, 630 deletions
diff --git a/test/t/Makefile.am b/test/t/Makefile.am
index 801841f..5a59969 100644
--- a/test/t/Makefile.am
+++ b/test/t/Makefile.am
@@ -21,6 +21,7 @@ EXTRA_DIST = \
test_apt_build.py \
test_apt_cache.py \
test_apt_get.py \
+ test_apt_mark.py \
test_aptitude.py \
test_arch.py \
test_arp.py \
@@ -36,8 +37,10 @@ EXTRA_DIST = \
test_autoscan.py \
test_autossh.py \
test_autoupdate.py \
+ test_avahi_browse.py \
test_avctrl.py \
test_awk.py \
+ test_b2sum.py \
test_badblocks.py \
test_base64.py \
test_bash.py \
@@ -47,6 +50,7 @@ EXTRA_DIST = \
test_bk.py \
test_bmake.py \
test_brctl.py \
+ test_bsdtar.py \
test_btdownloadcurses_py.py \
test_btdownloadgui_py.py \
test_btdownloadheadless_py.py \
@@ -214,13 +218,16 @@ EXTRA_DIST = \
test_growisofs.py \
test_grpck.py \
test_grub.py \
+ test_gssdp_device_sniffer.py \
test_gssdp_discover.py \
test_gzip.py \
+ test_hash.py \
test_hciattach.py \
test_hciconfig.py \
test_hcitool.py \
test_hddtemp.py \
test_head.py \
+ test_help.py \
test_hexdump.py \
test_hid2hci.py \
test_host.py \
@@ -253,6 +260,7 @@ EXTRA_DIST = \
test_ip.py \
test_ipcalc.py \
test_iperf.py \
+ test_iperf3.py \
test_ipmitool.py \
test_ipsec.py \
test_iptables.py \
@@ -395,6 +403,7 @@ EXTRA_DIST = \
test_mysqladmin.py \
test_nc.py \
test_ncftp.py \
+ test_neomutt.py \
test_nethogs.py \
test_netstat.py \
test_newgrp.py \
@@ -413,7 +422,6 @@ EXTRA_DIST = \
test_objdump.py \
test_od.py \
test_oggdec.py \
- test_op.py \
test_openssl.py \
test_opera.py \
test_optipng.py \
@@ -422,6 +430,7 @@ EXTRA_DIST = \
test_passwd.py \
test_paste.py \
test_patch.py \
+ test_pdftoppm.py \
test_pdftotext.py \
test_perl.py \
test_perlcritic.py \
@@ -439,6 +448,7 @@ EXTRA_DIST = \
test_pkg_get.py \
test_pkg_info.py \
test_pkgadd.py \
+ test_pkgconf.py \
test_pkgrm.py \
test_pkgtool.py \
test_pkgutil.py \
@@ -461,6 +471,7 @@ EXTRA_DIST = \
test_prelink.py \
test_printenv.py \
test_protoc.py \
+ test_ps.py \
test_psql.py \
test_ptx.py \
test_puppet.py \
@@ -483,11 +494,13 @@ EXTRA_DIST = \
test_pyflakes.py \
test_pylint.py \
test_pylint_3.py \
+ test_pyston.py \
test_pytest.py \
test_python.py \
test_python3.py \
test_pyvenv.py \
test_qemu.py \
+ test_qemu_system_x86_64.py \
test_qrunner.py \
test_querybts.py \
test_quota.py \
@@ -540,6 +553,10 @@ EXTRA_DIST = \
test_sftp.py \
test_sh.py \
test_sha1sum.py \
+ test_sha224sum.py \
+ test_sha256sum.py \
+ test_sha384sum.py \
+ test_sha512sum.py \
test_shar.py \
test_shellcheck.py \
test_sitecopy.py \
@@ -564,6 +581,7 @@ EXTRA_DIST = \
test_ssh_add.py \
test_ssh_copy_id.py \
test_ssh_keygen.py \
+ test_ssh_keyscan.py \
test_sshfs.py \
test_sshmitm.py \
test_sshow.py \
@@ -600,6 +618,8 @@ EXTRA_DIST = \
test_tox.py \
test_tr.py \
test_tracepath.py \
+ test_tree.py \
+ test_truncate.py \
test_tshark.py \
test_tsig_keygen.py \
test_tune2fs.py \
@@ -663,6 +683,7 @@ EXTRA_DIST = \
test_wvdial.py \
test_xdg_mime.py \
test_xdg_settings.py \
+ test_xev.py \
test_xfreerdp.py \
test_xgamma.py \
test_xhost.py \
@@ -691,8 +712,14 @@ all:
PYTEST = @PYTEST@
-check-local:
- $(PYTEST) $(PYTESTFLAGS) $(srcdir)
+# Some tests require completions/ symlinks to be in place, which would be a
+# chore to achieve in the build dir with VPATH builds (well not the symlinks,
+# but the "main" files they target), e.g. "make distcheck". Therefore we test
+# the installed tree instead, which isn't a bad idea in the first place.
+installcheck-local:
+ ABS_TOP_BUILDDIR="$(abs_top_builddir)" \
+ BASH_COMPLETION_TEST_BASH_COMPLETION="$(DESTDIR)/$(pkgdatadir)/bash_completion" \
+ $(PYTEST) $(PYTESTFLAGS) $(srcdir)
clean-local:
$(RM) -R __pycache__
diff --git a/test/t/conftest.py b/test/t/conftest.py
index 5c1603d..874ef1c 100644
--- a/test/t/conftest.py
+++ b/test/t/conftest.py
@@ -2,15 +2,32 @@ import difflib
import os
import re
import shlex
+import shutil
import subprocess
+import sys
+import tempfile
import time
-from typing import Callable, Iterable, Iterator, List, Optional, Tuple
-
-import pexpect
+from enum import Enum
+from pathlib import Path
+from types import TracebackType
+from typing import (
+ Callable,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ Optional,
+ TextIO,
+ Tuple,
+ Type,
+)
+
+import pexpect # type: ignore[import]
import pytest
PS1 = "/@"
-MAGIC_MARK = "__MaGiC-maRKz!__"
+MAGIC_MARK = "__MaGiC-maRKz-NEtXZVZfKC__"
+MAGIC_MARK2 = "Re8SCgEdfN"
def find_unique_completion_pair(
@@ -115,8 +132,8 @@ def _avahi_hosts(bash: pexpect.spawn) -> List[str]:
def known_hosts(bash: pexpect.spawn) -> List[str]:
output = assert_bash_exec(
bash,
- '_known_hosts_real ""; '
- r'printf "%s\n" "${COMPREPLY[@]}"; unset COMPREPLY',
+ '_comp_compgen_known_hosts ""; '
+ r'printf "%s\n" "${COMPREPLY[@]}"; unset -v COMPREPLY',
want_output=True,
)
return sorted(set(output.split()))
@@ -127,7 +144,9 @@ 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()
+ # We used to echo $HOME here, but we expect that it will be consistent with
+ # ~user as far as bash is concerned which may not hold.
+ home = assert_bash_exec(bash, "echo ~%s" % user, want_output=True).strip()
return (user, home)
@@ -167,107 +186,175 @@ def partialize(
@pytest.fixture(scope="class")
def bash(request) -> pexpect.spawn:
+ logfile: Optional[TextIO] = None
+ histfile = None
+ tmpdir = None
+ bash = None
+
+ if os.environ.get("BASH_COMPLETION_TEST_LOGFILE"):
+ logfile = open(os.environ["BASH_COMPLETION_TEST_LOGFILE"], "w")
+ elif os.environ.get("CI"):
+ logfile = sys.stdout
- logfile = None
- if os.environ.get("BASHCOMP_TEST_LOGFILE"):
- logfile = open(os.environ["BASHCOMP_TEST_LOGFILE"], "w")
testdir = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir)
)
- env = os.environ.copy()
- env.update(
- dict(
- SRCDIR=testdir, # TODO needed at least by bashrc
- SRCDIRABS=testdir, # TODO needed?
- PS1=PS1,
- INPUTRC="%s/config/inputrc" % testdir,
- TERM="dumb",
- LC_COLLATE="C", # to match Python's default locale unaware sort
- )
- )
- fixturesdir = os.path.join(testdir, "fixtures")
- os.chdir(fixturesdir)
-
- # Start bash
- bash = pexpect.spawn(
- "%s --norc" % os.environ.get("BASHCOMP_TEST_BASH", "bash"),
- maxread=os.environ.get("BASHCOMP_TEST_PEXPECT_MAXREAD", 20000),
- logfile=logfile,
- cwd=fixturesdir,
- env=env,
- encoding="utf-8", # TODO? or native or...?
- # FIXME: Tests shouldn't depend on dimensions, but it's difficult to
- # expect robustly enough for Bash to wrap lines anywhere (e.g. inside
- # MAGIC_MARK). Increase window width to reduce wrapping.
- dimensions=(24, 160),
- # TODO? codec_errors="replace",
+ # Create an empty temporary file for HISTFILE.
+ #
+ # To prevent the tested Bash processes from writing to the user's
+ # history file or any other files, we prepare an empty temporary
+ # file for each test.
+ #
+ # - Note that HISTFILE=/dev/null may not work. It results in the
+ # removal of the device /dev/null and the creation of a regular
+ # file at /dev/null when the number of commands reach
+ # HISTFILESIZE due to a bug in bash 4.3. This causes execution of
+ # garbage through BASH_COMPLETION_USER_FILE=/dev/null.
+ # - Note also that "unset -v HISTFILE" in "test/config/bashrc" was not
+ # adopted because "test/config/bashrc" is loaded after the
+ # history is read from the history file.
+ #
+ histfile = tempfile.NamedTemporaryFile(
+ prefix="bash-completion-test_", delete=False
)
- bash.expect_exact(PS1)
- # Load bashrc and bash_completion
- assert_bash_exec(bash, "source '%s/config/bashrc'" % testdir)
- assert_bash_exec(bash, "source '%s/../bash_completion'" % testdir)
-
- # Use command name from marker if set, or grab from test filename
- cmd = None # type: Optional[str]
- cmd_found = False
- marker = request.node.get_closest_marker("bashcomp")
- if marker:
- cmd = marker.kwargs.get("cmd")
- cmd_found = "cmd" in marker.kwargs
- # Run pre-test commands, early so they're usable in skipif
- for pre_cmd in marker.kwargs.get("pre_cmds", []):
- assert_bash_exec(bash, pre_cmd)
- # Process skip and xfail conditions
- skipif = marker.kwargs.get("skipif")
- if skipif:
- try:
- assert_bash_exec(bash, skipif, want_output=None)
- except AssertionError:
- pass
- else:
- bash.close()
- pytest.skip(skipif)
- xfail = marker.kwargs.get("xfail")
- if xfail:
- try:
- assert_bash_exec(bash, xfail, want_output=None)
- except AssertionError:
- pass
- else:
- pytest.xfail(xfail)
- if not cmd_found:
- match = re.search(
- r"^test_(.+)\.py$", os.path.basename(str(request.fspath))
+ try:
+ # release the file handle so that Bash can open the file.
+ histfile.close()
+
+ env = os.environ.copy()
+ env.update(
+ dict(
+ SRCDIR=testdir, # TODO needed at least by bashrc
+ SRCDIRABS=testdir,
+ PS1=PS1,
+ INPUTRC="%s/config/inputrc" % testdir,
+ TERM="dumb",
+ LC_COLLATE="C", # to match Python's default locale unaware sort
+ HISTFILE=histfile.name,
+ )
+ )
+
+ marker = request.node.get_closest_marker("bashcomp")
+
+ # Set up the current working directory
+ cwd = None
+ if marker:
+ if "cwd" in marker.kwargs and marker.kwargs.get("cwd") is not None:
+ cwd = os.path.join(
+ testdir, "fixtures", marker.kwargs.get("cwd")
+ )
+ elif "temp_cwd" in marker.kwargs and marker.kwargs.get("temp_cwd"):
+ tmpdir = tempfile.TemporaryDirectory(
+ prefix="bash-completion-test_"
+ )
+ cwd = tmpdir.name
+ if cwd is None:
+ cwd = os.path.join(testdir, "fixtures")
+ os.chdir(cwd)
+
+ # Start bash
+ bash = pexpect.spawn(
+ "%s --norc" % os.environ.get("BASH_COMPLETION_TEST_BASH", "bash"),
+ maxread=os.environ.get(
+ "BASH_COMPLETION_TEST_PEXPECT_MAXREAD", 20000
+ ),
+ logfile=logfile,
+ cwd=cwd,
+ env=env,
+ encoding="utf-8", # TODO? or native or...?
+ # FIXME: Tests shouldn't depend on dimensions, but it's difficult to
+ # expect robustly enough for Bash to wrap lines anywhere (e.g. inside
+ # MAGIC_MARK). Increase window width to reduce wrapping.
+ dimensions=(24, 240),
+ # TODO? codec_errors="replace",
)
- if match:
- cmd = match.group(1)
-
- request.cls.cmd = cmd
-
- if (cmd_found and cmd is None) or is_testable(bash, cmd):
- before_env = get_env(bash)
- yield bash
- # Not exactly sure why, but some errors leave bash in state where
- # getting the env here would fail and trash our test output. So
- # reset to a good state first (Ctrl+C, expect prompt).
- bash.sendintr()
bash.expect_exact(PS1)
- diff_env(
- before_env,
- get_env(bash),
- marker.kwargs.get("ignore_env") if marker else "",
+
+ # Load bashrc and bash_completion
+ bash_completion = os.environ.get(
+ "BASH_COMPLETION_TEST_BASH_COMPLETION",
+ "%s/../bash_completion" % testdir,
)
+ assert_bash_exec(bash, "source '%s/config/bashrc'" % testdir)
+ assert_bash_exec(bash, "source '%s'" % bash_completion)
+
+ # Use command name from marker if set, or grab from test filename
+ cmd = None # type: Optional[str]
+ cmd_found = False
+ if marker:
+ cmd = marker.kwargs.get("cmd")
+ cmd_found = "cmd" in marker.kwargs
+ # Run pre-test commands, early so they're usable in skipif
+ for pre_cmd in marker.kwargs.get("pre_cmds", []):
+ assert_bash_exec(bash, pre_cmd, want_output=None)
+ # Process skip and xfail conditions
+ skipif = marker.kwargs.get("skipif")
+ if skipif:
+ try:
+ assert_bash_exec(bash, skipif, want_output=None)
+ except AssertionError:
+ pass
+ else:
+ bash.close()
+ bash = None
+ pytest.skip(skipif)
+ xfail = marker.kwargs.get("xfail")
+ if xfail:
+ try:
+ assert_bash_exec(bash, xfail, want_output=None)
+ except AssertionError:
+ pass
+ else:
+ pytest.xfail(xfail)
+ if not cmd_found:
+ match = re.search(
+ r"^test_(.+)\.py$", os.path.basename(str(request.fspath))
+ )
+ if match:
+ cmd = match.group(1)
+ if (
+ marker
+ and marker.kwargs
+ and marker.kwargs.get("require_cmd", False)
+ ):
+ if not is_bash_type(bash, cmd):
+ pytest.skip("Command not found")
+
+ request.cls.cmd = cmd
+
+ if (cmd_found and cmd is None) or is_testable(bash, cmd):
+ before_env = get_env(bash)
+ yield bash
+ # Not exactly sure why, but some errors leave bash in state where
+ # getting the env here would fail and trash our test output. So
+ # reset to a good state first (Ctrl+C, expect prompt).
+ bash.sendintr()
+ bash.expect_exact(PS1)
+ diff_env(
+ before_env,
+ get_env(bash),
+ marker.kwargs.get("ignore_env") if marker else "",
+ )
- if marker:
- for post_cmd in marker.kwargs.get("post_cmds", []):
- assert_bash_exec(bash, post_cmd)
+ if marker:
+ for post_cmd in marker.kwargs.get("post_cmds", []):
+ assert_bash_exec(bash, post_cmd, want_output=None)
- # Clean up
- bash.close()
- if logfile:
- logfile.close()
+ finally:
+ # Clean up
+ if bash:
+ bash.close()
+ if tmpdir:
+ tmpdir.cleanup()
+ if histfile:
+ try:
+ os.remove(histfile.name)
+ except OSError:
+ pass
+ if logfile and logfile != sys.stdout:
+ logfile.close()
def is_testable(bash: pexpect.spawn, cmd: Optional[str]) -> bool:
@@ -292,9 +379,9 @@ def is_bash_type(bash: pexpect.spawn, cmd: Optional[str]) -> bool:
def load_completion_for(bash: pexpect.spawn, cmd: str) -> bool:
try:
- # Allow __load_completion to fail so we can test completions
+ # Allow _comp_load to fail so we can test completions
# that are directly loaded in bash_completion without a separate file.
- assert_bash_exec(bash, "__load_completion %s || :" % cmd)
+ assert_bash_exec(bash, "_comp_load %s || :" % cmd)
assert_bash_exec(bash, "complete -p %s &>/dev/null" % cmd)
except AssertionError:
return False
@@ -341,27 +428,301 @@ def assert_bash_exec(
if output:
assert want_output, (
'Unexpected output from "%s": exit status=%s, output="%s"'
- % (cmd, status, output)
+ % (
+ cmd,
+ status,
+ output,
+ )
)
else:
assert not want_output, (
'Expected output from "%s": exit status=%s, output="%s"'
- % (cmd, status, output)
+ % (
+ cmd,
+ status,
+ output,
+ )
)
return output
+class bash_env_saved:
+ counter: int = 0
+
+ class saved_state(Enum):
+ ChangesDetected = 1
+ ChangesIgnored = 2
+
+ def __init__(self, bash: pexpect.spawn, sendintr: bool = False):
+ bash_env_saved.counter += 1
+ self.prefix: str = "_comp__test_%d" % bash_env_saved.counter
+
+ self.bash = bash
+ self.cwd_changed: bool = False
+ self.saved_set: Dict[str, bash_env_saved.saved_state] = {}
+ self.saved_shopt: Dict[str, bash_env_saved.saved_state] = {}
+ self.saved_variables: Dict[str, bash_env_saved.saved_state] = {}
+ self.sendintr = sendintr
+
+ self.noexcept: bool = False
+ self.captured_error: Optional[Exception] = None
+
+ def __enter__(self):
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_value: Optional[BaseException],
+ exc_traceback: Optional[TracebackType],
+ ) -> None:
+ self._restore_env()
+ return None
+
+ def _safe_sendintr(self):
+ try:
+ self.bash.sendintr()
+ self.bash.expect_exact(PS1)
+ except Exception as e:
+ if self.noexcept:
+ self.captured_error = e
+ else:
+ raise
+
+ def _safe_exec(self, cmd: str):
+ try:
+ self.bash.sendline(cmd)
+ self.bash.expect_exact(cmd)
+ self.bash.expect_exact("\r\n" + PS1)
+ except Exception as e:
+ if self.noexcept:
+ self._safe_sendintr()
+ self.captured_error = e
+ else:
+ raise
+
+ def _safe_assert(self, cmd: str):
+ try:
+ assert_bash_exec(self.bash, cmd, want_output=None)
+ except Exception as e:
+ if self.noexcept:
+ self._safe_sendintr()
+ self.captured_error = e
+ else:
+ raise
+
+ def _copy_variable(self, src_var: str, dst_var: str):
+ self._safe_exec(
+ "if [[ ${%s+set} ]]; then %s=${%s}; else unset -v %s; fi"
+ % (src_var, dst_var, src_var, dst_var),
+ )
+
+ def _unset_variable(self, varname: str):
+ self._safe_exec("unset -v %s" % varname)
+
+ def _save_cwd(self):
+ if not self.cwd_changed:
+ self.cwd_changed = True
+ self._copy_variable("PWD", "%s_OLDPWD" % self.prefix)
+
+ def _check_set(self, name: str):
+ if self.saved_set[name] != bash_env_saved.saved_state.ChangesDetected:
+ return
+ self._safe_assert(
+ '[[ $(shopt -po %s) == "${%s_NEWSHOPT_%s}" ]]'
+ % (name, self.prefix, name),
+ )
+
+ def _unprotect_set(self, name: str):
+ if name not in self.saved_set:
+ self.saved_set[name] = bash_env_saved.saved_state.ChangesDetected
+ self._safe_exec(
+ "%s_OLDSHOPT_%s=$(shopt -po %s || true)"
+ % (self.prefix, name, name),
+ )
+ else:
+ self._check_set(name)
+
+ def _protect_set(self, name: str):
+ self._safe_exec(
+ "%s_NEWSHOPT_%s=$(shopt -po %s || true)"
+ % (self.prefix, name, name),
+ )
+
+ def _check_shopt(self, name: str):
+ if (
+ self.saved_shopt[name]
+ != bash_env_saved.saved_state.ChangesDetected
+ ):
+ return
+ self._safe_assert(
+ '[[ $(shopt -p %s) == "${%s_NEWSHOPT_%s}" ]]'
+ % (name, self.prefix, name),
+ )
+
+ def _unprotect_shopt(self, name: str):
+ if name not in self.saved_shopt:
+ self.saved_shopt[name] = bash_env_saved.saved_state.ChangesDetected
+ self._safe_exec(
+ "%s_OLDSHOPT_%s=$(shopt -p %s || true)"
+ % (self.prefix, name, name),
+ )
+ else:
+ self._check_shopt(name)
+
+ def _protect_shopt(self, name: str):
+ self._safe_exec(
+ "%s_NEWSHOPT_%s=$(shopt -p %s || true)"
+ % (self.prefix, name, name),
+ )
+
+ def _check_variable(self, varname: str):
+ if (
+ self.saved_variables[varname]
+ != bash_env_saved.saved_state.ChangesDetected
+ ):
+ return
+ try:
+ self._safe_assert(
+ '[[ ${%s-%s} == "${%s_NEWVAR_%s-%s}" ]]'
+ % (varname, MAGIC_MARK2, self.prefix, varname, MAGIC_MARK2),
+ )
+ except Exception:
+ self._copy_variable(
+ "%s_NEWVAR_%s" % (self.prefix, varname), varname
+ )
+ raise
+ else:
+ if self.noexcept and self.captured_error:
+ self._copy_variable(
+ "%s_NEWVAR_%s" % (self.prefix, varname), varname
+ )
+
+ def _unprotect_variable(self, varname: str):
+ if varname not in self.saved_variables:
+ self.saved_variables[
+ varname
+ ] = bash_env_saved.saved_state.ChangesDetected
+ self._copy_variable(
+ varname, "%s_OLDVAR_%s" % (self.prefix, varname)
+ )
+ else:
+ self._check_variable(varname)
+
+ def _protect_variable(self, varname: str):
+ self._copy_variable(varname, "%s_NEWVAR_%s" % (self.prefix, varname))
+
+ def _restore_env(self):
+ self.noexcept = True
+
+ if self.sendintr:
+ self._safe_sendintr()
+
+ # We first go back to the original directory before restoring
+ # variables because "cd" affects "OLDPWD".
+ if self.cwd_changed:
+ self._unprotect_variable("OLDPWD")
+ self._safe_exec('command cd -- "$%s_OLDPWD"' % self.prefix)
+ self._protect_variable("OLDPWD")
+ self._unset_variable("%s_OLDPWD" % self.prefix)
+ self.cwd_changed = False
+
+ for varname in self.saved_variables:
+ self._check_variable(varname)
+ self._copy_variable(
+ "%s_OLDVAR_%s" % (self.prefix, varname), varname
+ )
+ self._unset_variable("%s_OLDVAR_%s" % (self.prefix, varname))
+ self._unset_variable("%s_NEWVAR_%s" % (self.prefix, varname))
+ self.saved_variables = {}
+
+ for name in self.saved_shopt:
+ self._check_shopt(name)
+ self._safe_exec('eval "$%s_OLDSHOPT_%s"' % (self.prefix, name))
+ self._unset_variable("%s_OLDSHOPT_%s" % (self.prefix, name))
+ self._unset_variable("%s_NEWSHOPT_%s" % (self.prefix, name))
+ self.saved_shopt = {}
+
+ for name in self.saved_set:
+ self._check_set(name)
+ self._safe_exec('eval "$%s_OLDSHOPT_%s"' % (self.prefix, name))
+ self._unset_variable("%s_OLDSHOPT_%s" % (self.prefix, name))
+ self._unset_variable("%s_NEWSHOPT_%s" % (self.prefix, name))
+ self.saved_set = {}
+
+ self.noexcept = False
+ if self.captured_error:
+ raise self.captured_error
+
+ def chdir(self, path: str):
+ self._save_cwd()
+ self.cwd_changed = True
+ self._unprotect_variable("OLDPWD")
+ self._safe_exec("command cd -- %s" % shlex.quote(path))
+ self._protect_variable("OLDPWD")
+
+ def set(self, name: str, value: bool):
+ self._unprotect_set(name)
+ if value:
+ self._safe_exec("set -u %s" % name)
+ else:
+ self._safe_exec("set +o %s" % name)
+ self._protect_set(name)
+
+ def save_set(self, name: str):
+ self._unprotect_set(name)
+ self.saved_set[name] = bash_env_saved.saved_state.ChangesIgnored
+
+ def shopt(self, name: str, value: bool):
+ self._unprotect_shopt(name)
+ if value:
+ self._safe_exec("shopt -s %s" % name)
+ else:
+ self._safe_exec("shopt -u %s" % name)
+ self._protect_shopt(name)
+
+ def save_shopt(self, name: str):
+ self._unprotect_shopt(name)
+ self.saved_shopt[name] = bash_env_saved.saved_state.ChangesIgnored
+
+ def write_variable(self, varname: str, new_value: str, quote: bool = True):
+ if quote:
+ new_value = shlex.quote(new_value)
+ self._unprotect_variable(varname)
+ self._safe_exec("%s=%s" % (varname, new_value))
+ self._protect_variable(varname)
+
+ def save_variable(self, varname: str):
+ self._unprotect_variable(varname)
+ self.saved_variables[
+ varname
+ ] = bash_env_saved.saved_state.ChangesIgnored
+
+ # TODO: We may restore the "export" attribute as well though it is
+ # not currently tested in "diff_env"
+ def write_env(self, envname: str, new_value: str, quote: bool = True):
+ if quote:
+ new_value = shlex.quote(new_value)
+ self._unprotect_variable(envname)
+ self._safe_exec("export %s=%s" % (envname, new_value))
+ self._protect_variable(envname)
+
+
def get_env(bash: pexpect.spawn) -> List[str]:
- return (
- assert_bash_exec(
+ return [
+ x
+ for x in assert_bash_exec(
bash,
- "{ (set -o posix ; set); declare -F; shopt -p; set -o; }",
+ "_comp__test_get_env",
want_output=True,
)
.strip()
.splitlines()
- )
+ # Sometimes there are empty lines in the output due to unknown
+ # reasons, e.g. in GitHub Actions' macos-latest OS. Filter them out.
+ if x
+ ]
def diff_env(before: List[str], after: List[str], ignore: str):
@@ -371,7 +732,11 @@ def diff_env(before: List[str], after: List[str], ignore: str):
# Remove unified diff markers:
if not re.search(r"^(---|\+\+\+|@@ )", x)
# Ignore variables expected to change:
- and not re.search("^[-+](_|PPID|BASH_REMATCH|OLDPWD)=", x)
+ and not re.search(
+ r"^[-+](_|PPID|BASH_REMATCH|(BASH_)?LINENO)=",
+ x,
+ re.ASCII,
+ )
# Ignore likely completion functions added by us:
and not re.search(r"^\+declare -f _.+", x)
# ...and additional specified things:
@@ -455,73 +820,72 @@ def assert_complete(
pass
else:
pytest.xfail(xfail)
- cwd = kwargs.get("cwd")
- if cwd:
- assert_bash_exec(bash, "cd '%s'" % cwd)
- env_prefix = "_BASHCOMP_TEST_"
- env = kwargs.get("env", {})
- if env:
- # Back up environment and apply new one
- assert_bash_exec(
- bash,
- " ".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()),
- )
- try:
- bash.send(cmd + "\t")
+
+ with bash_env_saved(bash, sendintr=True) as bash_env:
+ cwd = kwargs.get("cwd")
+ if cwd:
+ bash_env.chdir(str(cwd))
+
+ for k, v in kwargs.get("env", {}).items():
+ bash_env.write_env(k, v, quote=False)
+
+ for k, v in kwargs.get("shopt", {}).items():
+ bash_env.shopt(k, v)
+
+ input_cmd = cmd
+ rendered_cmd = kwargs.get("rendered_cmd", cmd)
+ re_MAGIC_MARK = re.escape(MAGIC_MARK)
+
+ trail = kwargs.get("trail")
+ if trail:
+ # \002 = ^B = cursor left
+ input_cmd += trail + "\002" * len(trail)
+ rendered_cmd += trail + "\b" * len(trail)
+
+ # After reading the results, 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.
+ maybe_trail = "(%s%s)?" % (re.escape(trail), "\b" * len(trail))
+ re_MAGIC_MARK = "".join(
+ re.escape(x) + maybe_trail for x in MAGIC_MARK
+ )
+
+ bash.send(input_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.expect_exact(rendered_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),
+ r"\r\n" + re.escape(PS1 + rendered_cmd) + ".*" + re_MAGIC_MARK,
# 1: no completion
- r"^" + re.escape(MAGIC_MARK),
+ r"^" + re_MAGIC_MARK,
# 2: on same line, result in .match
- r"^([^\r]+)%s$" % re.escape(MAGIC_MARK),
+ r"^([^\r]+)%s$" % re_MAGIC_MARK,
+ # 3: error messages
+ r"^([^\r].*)%s$" % re_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)
+ output = re.sub(re_MAGIC_MARK + "$", "", bash.before)
+ return CompletionResult(output)
elif got == 2:
output = bash.match.group(1)
- result = CompletionResult(output)
+ return CompletionResult(output)
+ elif got == 3:
+ output = bash.match.group(1)
+ raise AssertionError("Unexpected output: [%s]" % 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
+ return CompletionResult()
@pytest.fixture
@@ -530,7 +894,7 @@ def completion(request, bash: pexpect.spawn) -> CompletionResult:
if not marker:
return CompletionResult()
for pre_cmd in marker.kwargs.get("pre_cmds", []):
- assert_bash_exec(bash, pre_cmd)
+ assert_bash_exec(bash, pre_cmd, want_output=None)
cmd = getattr(request.cls, "cmd", None)
if marker.kwargs.get("require_longopt"):
# longopt completions require both command presence and that it
@@ -539,66 +903,18 @@ def completion(request, bash: pexpect.spawn) -> CompletionResult:
marker.kwargs["require_cmd"] = True
if "xfail" not in marker.kwargs:
marker.kwargs["xfail"] = (
+ # --help is required to exit with zero in order to not get a
+ # positive for cases where it errors out with a message like
+ # "foo: unrecognized option '--help'"
"! %s --help &>/dev/null || "
"! %s --help 2>&1 | command grep -qF -- --help"
) % ((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(
@@ -624,6 +940,56 @@ def in_container() -> bool:
return False
+def prepare_fixture_dir(
+ request, files: Iterable[str], dirs: Iterable[str]
+) -> Tuple[Path, List[str], List[str]]:
+ """
+ Fixture to prepare a test dir with dummy contents on the fly.
+
+ Tests that contain filenames differing only by case should use this to
+ prepare a dir on the fly rather than including their fixtures in git and
+ the tarball. This is to work better with case insensitive file systems.
+ """
+ tempdir = Path(tempfile.mkdtemp(prefix="bash-completion-fixture-dir"))
+ request.addfinalizer(lambda: shutil.rmtree(str(tempdir)))
+
+ old_cwd = os.getcwd()
+ try:
+ os.chdir(tempdir)
+ new_files, new_dirs = create_dummy_filedirs(files, dirs)
+ finally:
+ os.chdir(old_cwd)
+
+ return tempdir, new_files, new_dirs
+
+
+def create_dummy_filedirs(
+ files: Iterable[str], dirs: Iterable[str]
+) -> Tuple[List[str], List[str]]:
+ """
+ Create dummy files and directories on the fly in the current directory.
+
+ Tests that contain filenames differing only by case should use this to
+ prepare a dir on the fly rather than including their fixtures in git and
+ the tarball. This is to work better with case insensitive file systems.
+ """
+ new_files = []
+ new_dirs = []
+
+ for dir_ in dirs:
+ path = Path(dir_)
+ if not path.exists():
+ path.mkdir()
+ new_dirs.append(dir_)
+ for file_ in files:
+ path = Path(file_)
+ if not path.exists():
+ path.touch()
+ new_files.append(file_)
+
+ return sorted(new_files), sorted(new_dirs)
+
+
class TestUnitBase:
def _test_unit(
self, func, bash, comp_words, comp_cword, comp_line, comp_point, arg=""
diff --git a/test/t/test_2to3.py b/test/t/test_2to3.py
index 4bce44e..1fc14e7 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, require_longopt=True)
+ @pytest.mark.complete("2to3 -", 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 d4308d9..67681a5 100644
--- a/test/t/test_7z.py
+++ b/test/t/test_7z.py
@@ -17,7 +17,7 @@ class Test7z:
@pytest.mark.complete("7z x ", cwd="7z")
def test_4(self, completion):
- assert completion == "a.7z"
+ assert completion == "a.7z hello.7z.001".split()
@pytest.mark.complete("7z d a.7z ", cwd="7z", require_cmd=True)
def test_5(self, completion):
@@ -25,7 +25,9 @@ class Test7z:
@pytest.mark.complete("7z a -air@", cwd="7z")
def test_6(self, completion):
- assert completion == sorted("-air@a.7z -air@f.txt".split())
+ assert completion == sorted(
+ "-air@a.7z -air@hello.7z.001 -air@hello.7z.002 -air@f.txt".split()
+ )
@pytest.mark.complete("7z a -o")
def test_7(self, completion):
diff --git a/test/t/test_alias.py b/test/t/test_alias.py
index cc592a8..0a9eb3b 100644
--- a/test/t/test_alias.py
+++ b/test/t/test_alias.py
@@ -19,3 +19,11 @@ class TestAlias:
@pytest.mark.complete("alias ", trail="foo")
def test_alias_at_point(self, completion):
assert completion == "bar foo".split()
+
+ @pytest.mark.complete("alias -")
+ def test_options(self, completion):
+ assert completion
+
+ @pytest.mark.complete("alias -p ")
+ def test_p(self, completion):
+ assert not completion
diff --git a/test/t/test_ant.py b/test/t/test_ant.py
index 94acea1..de4c414 100644
--- a/test/t/test_ant.py
+++ b/test/t/test_ant.py
@@ -3,28 +3,43 @@ import pytest
from conftest import assert_bash_exec
-@pytest.mark.bashcomp(ignore_env=r"^\+ANT_ARGS=")
+@pytest.mark.bashcomp(
+ ignore_env=r"^\+ANT_ARGS=",
+ temp_cwd=True,
+ pre_cmds=('cp "$SRCDIRABS"/fixtures/ant/*.xml .',),
+)
class TestAnt:
+ @pytest.fixture(scope="class")
+ def has_complete_ant_cmd_pl(self, bash):
+ output = assert_bash_exec(bash, "complete -p ant", want_output=True)
+ return "complete-ant-cmd.pl" in output
+
@pytest.mark.complete("ant -", require_cmd=True)
def test_1(self, completion):
assert completion
- @pytest.mark.complete("ant ", cwd="ant")
+ @pytest.mark.complete("ant ")
def test_2(self, completion):
assert completion == "bashcomp clean init realclean".split()
- @pytest.mark.complete("ant -f build-with-import.xml ", cwd="ant")
- def test_3(self, completion):
- assert completion == "build-with-import imported-build".split()
+ @pytest.mark.complete("ant -f build-with-import.xml ")
+ def test_3(self, completion, has_complete_ant_cmd_pl):
+ if has_complete_ant_cmd_pl:
+ # Some versions of complete-ant-cmd.pl add "import-project-name."
+ # prefix to imported targets, just check that the ones we add
+ # are there.
+ assert all(
+ x in completion
+ for x in "build-with-import imported-build".split()
+ )
+ else:
+ assert completion == "build-with-import imported-build".split()
- @pytest.mark.complete(
- "ant ", cwd="ant", env=dict(ANT_ARGS="'-f named-build.xml'")
- )
- def test_4(self, bash, completion):
- output = assert_bash_exec(bash, "complete -p ant", want_output=True)
- if "complete-ant-cmd.pl" in output:
+ @pytest.mark.complete("ant ", env=dict(ANT_ARGS="'-f named-build.xml'"))
+ def test_4(self, bash, completion, has_complete_ant_cmd_pl):
+ if has_complete_ant_cmd_pl:
# Some versions of complete-ant-cmd.pl don't treat ANT_ARGS right;
- # in those cases we get the correct completion produced by _ant
+ # in those cases we get the correct completion produced by us
# plus whatever complete-ant-cmd.pl was able to get from build.xml
assert "named-build" in completion
else:
diff --git a/test/t/test_apt_mark.py b/test/t/test_apt_mark.py
new file mode 100644
index 0000000..541dbe5
--- /dev/null
+++ b/test/t/test_apt_mark.py
@@ -0,0 +1,39 @@
+import pytest
+
+
+@pytest.mark.bashcomp(cmd="apt-mark")
+class TestAptMark:
+ @pytest.mark.complete("apt-mark ")
+ def test_1(self, completion):
+ assert all(
+ x in completion
+ for x in (
+ "auto manual remove showinstall showremove "
+ "hold minimize-manual showauto showmanual unhold install "
+ "purge showhold showpurge"
+ ).split()
+ )
+
+ @pytest.mark.complete("apt-mark minimize-manual ")
+ def test_2(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("apt-mark --file=", cwd="dpkg")
+ def test_3(self, completion):
+ assert (
+ completion
+ == "bash-completion-test-nonsubject.txt bash-completion-test-subject.deb".split()
+ )
+
+ @pytest.mark.complete("apt-mark --config-file ", cwd="apt-mark")
+ def test_4(self, completion):
+ assert completion == "example.conf"
+
+ @pytest.mark.complete("apt-mark --option ")
+ def test_5(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("apt-mark --dont-fail-in-unset-mode")
+ def test_unknown_option(self, completion):
+ # Just see that it does not error out
+ pass
diff --git a/test/t/test_arp.py b/test/t/test_arp.py
index cd038bd..f8a9543 100644
--- a/test/t/test_arp.py
+++ b/test/t/test_arp.py
@@ -3,7 +3,7 @@ import pytest
class TestArp:
@pytest.mark.complete(
- "arp ", require_cmd=True, skipif='test -z "$(arp 2>/dev/null)"'
+ "arp ", require_cmd=True, skipif='test ! "$(arp 2>/dev/null)"'
)
def test_1(self, completion):
assert completion
diff --git a/test/t/test_arpspoof.py b/test/t/test_arpspoof.py
index 74c09a4..c02f5c4 100644
--- a/test/t/test_arpspoof.py
+++ b/test/t/test_arpspoof.py
@@ -5,8 +5,13 @@ class TestArpspoof:
@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",
+ # May require privileges or network interfaces available even for
+ # outputting the usage message. Unfortunately --help provokes a
+ # non-zero exit status so we cannot test for that.
+ skipif=(
+ "arpspoof 2>&1 | "
+ "command grep -qE 'libnet_(open_link|select_device)'"
+ ),
)
def test_1(self, completion):
assert completion
diff --git a/test/t/test_avahi_browse.py b/test/t/test_avahi_browse.py
new file mode 100644
index 0000000..2c3cf0d
--- /dev/null
+++ b/test/t/test_avahi_browse.py
@@ -0,0 +1,27 @@
+import pytest
+
+
+@pytest.mark.bashcomp(
+ cmd="avahi-browse",
+)
+class TestAvahiBrowse:
+ @pytest.mark.complete("avahi-browse --", require_cmd=True)
+ def test_options(self, completion):
+ assert completion
+
+ @pytest.mark.complete(
+ "avahi-browse _",
+ require_cmd=True,
+ xfail='test ! "$(avahi-browse --dump-db 2>/dev/null)"',
+ )
+ def test_service_types(self, completion):
+ assert completion
+
+ @pytest.mark.complete("avahi-browse -a _")
+ def test_no_service_type_with_a(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("avahi-browse --dont-fail-in-unset-mode")
+ def test_unknown_option(self, completion):
+ # Just see that it does not error out
+ pass
diff --git a/test/t/test_b2sum.py b/test/t/test_b2sum.py
new file mode 100644
index 0000000..b184457
--- /dev/null
+++ b/test/t/test_b2sum.py
@@ -0,0 +1,11 @@
+import pytest
+
+
+class TestB2sum:
+ @pytest.mark.complete("b2sum ")
+ def test_1(self, completion):
+ assert completion
+
+ @pytest.mark.complete("b2sum --", require_longopt=True)
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_bsdtar.py b/test/t/test_bsdtar.py
new file mode 100644
index 0000000..ec3acd1
--- /dev/null
+++ b/test/t/test_bsdtar.py
@@ -0,0 +1,11 @@
+import pytest
+
+
+class TestBsdtar:
+ @pytest.mark.complete("bsdtar xf bsdtar/")
+ def test_readable_archives(self, completion):
+ assert completion == "test.pax test.rar".split()
+
+ @pytest.mark.complete("bsdtar uf bsdtar/")
+ def test_writable_archives(self, completion):
+ assert completion == "test.pax test.shar".split()
diff --git a/test/t/test_cd.py b/test/t/test_cd.py
index 5b7789a..7243b93 100644
--- a/test/t/test_cd.py
+++ b/test/t/test_cd.py
@@ -7,11 +7,11 @@ class TestCd:
def test_1(self, completion):
assert completion == ["bar bar.d/", "foo.d/"]
- @pytest.mark.complete("cd fo", env=dict(CDPATH="shared/default"))
+ @pytest.mark.complete("cd foo", env=dict(CDPATH="shared/default"))
def test_2(self, completion):
- assert completion == "o.d/"
+ assert completion == ".d/"
- @pytest.mark.complete("cd fo")
+ @pytest.mark.complete("cd foo")
def test_3(self, completion):
assert not completion
@@ -24,3 +24,7 @@ class TestCd:
@pytest.mark.complete("cd shared/default/", trail="foo")
def test_dir_at_point(self, completion):
assert completion == ["bar bar.d/", "foo.d/"]
+
+ @pytest.mark.complete("cd -")
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_chmod.py b/test/t/test_chmod.py
index 3838b55..e1a7119 100644
--- a/test/t/test_chmod.py
+++ b/test/t/test_chmod.py
@@ -2,7 +2,6 @@ import pytest
class TestChmod:
-
# No completion here until mode completion is implemented
@pytest.mark.complete("chmod ")
def test_1(self, completion):
diff --git a/test/t/test_chown.py b/test/t/test_chown.py
index 9643f3e..ee81346 100644
--- a/test/t/test_chown.py
+++ b/test/t/test_chown.py
@@ -8,7 +8,7 @@ from conftest import assert_complete
@pytest.mark.bashcomp(
pre_cmds=(
# Fake root command to get all users/groups completed at least for now
- "root_command=sudo",
+ "_comp_root_command=sudo",
)
)
class TestChown:
diff --git a/test/t/test_chsh.py b/test/t/test_chsh.py
index fe1c7f6..c2d8344 100644
--- a/test/t/test_chsh.py
+++ b/test/t/test_chsh.py
@@ -1,3 +1,5 @@
+import os
+
import pytest
@@ -7,9 +9,16 @@ class TestChsh:
assert completion
@pytest.mark.complete("chsh -s ")
- def test_2(self, completion):
- assert completion
+ def test_shells(self, completion):
+ if os.path.exists("/etc/shells"):
+ assert completion
+ else:
+ assert not completion
@pytest.mark.complete("chsh -", require_cmd=True)
def test_3(self, completion):
assert completion
+
+ @pytest.mark.complete("chsh --root shells -s ")
+ def test_chroot_shells(self, completion):
+ assert completion == "/bash/completion/canary"
diff --git a/test/t/test_configure.py b/test/t/test_configure.py
index 0fc6117..c3b3393 100644
--- a/test/t/test_configure.py
+++ b/test/t/test_configure.py
@@ -15,3 +15,11 @@ class TestConfigure:
@pytest.mark.complete("configure --prefix ")
def test_2(self, completion):
assert completion
+
+ @pytest.mark.complete("configure --unknown-option-with-split=")
+ def test_unknown_split_filedir_fallback(self, completion):
+ assert "shared/" in completion
+
+ @pytest.mark.complete("configure --unknown-option ")
+ def test_unknown_filedir_fallback(self, completion):
+ assert "shared/" in completion
diff --git a/test/t/test_convert.py b/test/t/test_convert.py
index c903ea0..b355c7a 100644
--- a/test/t/test_convert.py
+++ b/test/t/test_convert.py
@@ -6,7 +6,7 @@ class TestConvert:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("convert -format ")
+ @pytest.mark.complete("convert -format ", require_cmd=True)
def test_2(self, completion):
assert completion
diff --git a/test/t/test_cpan2dist.py b/test/t/test_cpan2dist.py
index 1ab5de1..ff64dbd 100644
--- a/test/t/test_cpan2dist.py
+++ b/test/t/test_cpan2dist.py
@@ -2,8 +2,6 @@ import pytest
class TestCpan2dist:
- @pytest.mark.complete(
- "cpan2dist -", require_cmd=True, require_longopt=True
- )
+ @pytest.mark.complete("cpan2dist -", require_longopt=True)
def test_1(self, completion):
assert completion
diff --git a/test/t/test_createdb.py b/test/t/test_createdb.py
index 030338a..6ac255a 100644
--- a/test/t/test_createdb.py
+++ b/test/t/test_createdb.py
@@ -2,7 +2,6 @@ import pytest
class TestCreatedb:
-
# --help can fail due to missing package dependencies, e.g. on Ubuntu 14
@pytest.mark.complete(
"createdb -", require_cmd=True, xfail="! createdb --help &>/dev/null"
diff --git a/test/t/test_createuser.py b/test/t/test_createuser.py
index ea8d0e3..f3b1c67 100644
--- a/test/t/test_createuser.py
+++ b/test/t/test_createuser.py
@@ -2,7 +2,6 @@ import pytest
class TestCreateuser:
-
# --help can fail due to missing package dependencies, e.g. on Ubuntu 14
@pytest.mark.complete(
"createuser -", xfail="! createuser --help &>/dev/null"
diff --git a/test/t/test_csplit.py b/test/t/test_csplit.py
index 609c7e5..f1ead89 100644
--- a/test/t/test_csplit.py
+++ b/test/t/test_csplit.py
@@ -6,6 +6,6 @@ class TestCsplit:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("csplit -", require_cmd=True)
+ @pytest.mark.complete("csplit -", require_longopt=True)
def test_options(self, completion):
assert completion
diff --git a/test/t/test_curl.py b/test/t/test_curl.py
index 63e969f..07050e9 100644
--- a/test/t/test_curl.py
+++ b/test/t/test_curl.py
@@ -26,3 +26,20 @@ class TestCurl:
def test_data_atfile_dir(self, completion):
assert completion == "d/"
assert not completion.endswith(" ")
+
+ @pytest.mark.complete("curl --dont-fail-in-unset-mode")
+ def test_unknown_option(self, completion):
+ # Just see that it does not error out
+ pass
+
+ @pytest.mark.complete("curl --data-bina", require_cmd=True)
+ def test_help_all_option(self, completion):
+ """
+ The option used as a canary here is one that should be available
+ in all curl versions. It should be only listed in `--help all` output
+ for curl versions that have their help output split to multiple
+ categories (i.e. ones that support `--help all` to get the complete
+ list), as well as the basic `--help` output for earlier versions that
+ do not have that.
+ """
+ assert completion
diff --git a/test/t/test_cvs.py b/test/t/test_cvs.py
index 97361e9..6d2deb3 100644
--- a/test/t/test_cvs.py
+++ b/test/t/test_cvs.py
@@ -18,3 +18,23 @@ class TestCvs:
@pytest.mark.complete("cvs -", require_cmd=True)
def test_4(self, completion):
assert completion
+
+ @pytest.mark.complete("cvs update -AdP foo/", cwd="cvs")
+ def test_5(self, completion):
+ assert completion == "bar"
+
+ @pytest.mark.complete("cvs log -v foo/", cwd="cvs")
+ def test_6(self, completion):
+ assert completion == "bar"
+
+ @pytest.mark.complete("cvs diff foo/", cwd="cvs")
+ def test_7(self, completion):
+ assert completion == "bar"
+
+ @pytest.mark.complete("cvs status -v foo/", cwd="cvs")
+ def test_8(self, completion):
+ assert completion == "bar"
+
+ @pytest.mark.complete("cvs status foo/", cwd="cvs")
+ def test_9(self, completion):
+ assert completion == "bar"
diff --git a/test/t/test_cvsps.py b/test/t/test_cvsps.py
index 4039893..2095832 100644
--- a/test/t/test_cvsps.py
+++ b/test/t/test_cvsps.py
@@ -1,7 +1,10 @@
import pytest
-@pytest.mark.bashcomp(pre_cmds=("HOME=$PWD/cvs",))
+@pytest.mark.bashcomp(
+ pre_cmds=("HOME=$PWD/cvs",),
+ ignore_env=r"^[+-]COMP_CVS_REMOTE=",
+)
class TestCvsps:
@pytest.mark.complete("cvsps -", require_cmd=True)
def test_1(self, completion):
diff --git a/test/t/test_date.py b/test/t/test_date.py
index 57d61b8..f0f93b6 100644
--- a/test/t/test_date.py
+++ b/test/t/test_date.py
@@ -6,6 +6,6 @@ class TestDate:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("date -", require_cmd=True)
+ @pytest.mark.complete("date -", require_longopt=True)
def test_options(self, completion):
assert completion
diff --git a/test/t/test_dcop.py b/test/t/test_dcop.py
index 5c3c04d..3e6d363 100644
--- a/test/t/test_dcop.py
+++ b/test/t/test_dcop.py
@@ -2,6 +2,8 @@ import pytest
class TestDcop:
- @pytest.mark.complete("dcop ", require_cmd=True)
+ @pytest.mark.complete(
+ "dcop ", require_cmd=True, skipif="! dcop &>/dev/null"
+ )
def test_1(self, completion):
assert completion
diff --git a/test/t/test_dd.py b/test/t/test_dd.py
index e082faa..c0978f3 100644
--- a/test/t/test_dd.py
+++ b/test/t/test_dd.py
@@ -2,13 +2,7 @@ import pytest
class TestDd:
- @pytest.mark.complete(
- "dd --",
- xfail=(
- "! dd --help &>/dev/null || "
- "! dd --help 2>&1 | command grep -qF -- --help"
- ),
- )
+ @pytest.mark.complete("dd --", require_longopt=True)
def test_1(self, completion):
assert completion
diff --git a/test/t/test_declare.py b/test/t/test_declare.py
index a61d926..924eef0 100644
--- a/test/t/test_declare.py
+++ b/test/t/test_declare.py
@@ -15,6 +15,18 @@ class TestDeclare:
# bash 5.0 has BASH_ARGV0 too
assert all(x in completion for x in "BASH_ARGC BASH_ARGV".split())
- @pytest.mark.complete("declare -f _parse_")
+ @pytest.mark.complete("declare -f _comp_comp")
def test_4(self, completion):
- assert "_parse_help" in completion
+ assert "_comp_compgen" in completion
+
+ @pytest.mark.complete("declare -a BASH_VERS")
+ def test_arrayvar(self, completion):
+ assert "INFO" in completion
+
+ @pytest.mark.complete("declare -f BASH_VERS")
+ def test_no_arrayvar_for_f(self, completion):
+ assert "INFO" not in completion
+
+ @pytest.mark.complete("declare -i BASH_VERS")
+ def test_no_arrayvar_for_i(self, completion):
+ assert "INFO" not in completion
diff --git a/test/t/test_dict.py b/test/t/test_dict.py
index 99c4a21..64adb54 100644
--- a/test/t/test_dict.py
+++ b/test/t/test_dict.py
@@ -1,3 +1,5 @@
+import os
+
import pytest
@@ -5,3 +7,25 @@ class TestDict:
@pytest.mark.complete("dict -", require_cmd=True)
def test_1(self, completion):
assert completion
+
+ @pytest.mark.xfail(
+ os.environ.get("NETWORK") == "none",
+ reason="The database list is unavailable without network",
+ )
+ @pytest.mark.complete("dict --database ", require_cmd=True)
+ def test_database(self, completion):
+ # Ensure the directory name "_comp_load/" not generated because
+ # filenames in the current dictory (i.e., test/fixtures) are generated
+ # by "-o default" when "_comp_cmd_dict" fails to generate any
+ # completions.
+ assert completion and "_comp_load/" not in completion
+
+ @pytest.mark.xfail(
+ os.environ.get("NETWORK") == "none",
+ reason="The database list is unavailable without network",
+ )
+ @pytest.mark.complete(
+ "dict -h dict.org --database ", require_cmd=True, env=dict(IFS="")
+ )
+ def test_database_IFS(self, completion):
+ assert completion and "_comp_load/" not in completion
diff --git a/test/t/test_dmesg.py b/test/t/test_dmesg.py
index a081fb6..e1001b0 100644
--- a/test/t/test_dmesg.py
+++ b/test/t/test_dmesg.py
@@ -1,7 +1,12 @@
+import sys
+
import pytest
class TestDmesg:
@pytest.mark.complete("dmesg -", require_cmd=True)
def test_1(self, completion):
- assert completion
+ if sys.platform == "darwin":
+ assert not completion # takes no options
+ else:
+ assert completion
diff --git a/test/t/test_dmypy.py b/test/t/test_dmypy.py
index 4c031dd..6253d91 100644
--- a/test/t/test_dmypy.py
+++ b/test/t/test_dmypy.py
@@ -9,6 +9,6 @@ class TestDmypy:
assert "help" in completion
assert not any("," in x for x in completion)
- @pytest.mark.complete("dmypy -", require_cmd=True, require_longopt=True)
+ @pytest.mark.complete("dmypy -", require_longopt=True)
def test_options(self, completion):
assert "--help" in completion
diff --git a/test/t/test_dot.py b/test/t/test_dot.py
index a4aa674..733555c 100644
--- a/test/t/test_dot.py
+++ b/test/t/test_dot.py
@@ -5,3 +5,11 @@ class TestDot:
@pytest.mark.complete("dot ")
def test_1(self, completion):
assert completion
+
+ @pytest.mark.complete("dot t", cwd="dot")
+ def test_2(self, completion):
+ assert completion == ["test1.gv", "test2.dot"]
+
+ @pytest.mark.complete("dot test1", cwd="dot")
+ def test_3(self, completion):
+ assert completion == ".gv"
diff --git a/test/t/test_dpkg.py b/test/t/test_dpkg.py
index eb1228b..ce6a047 100644
--- a/test/t/test_dpkg.py
+++ b/test/t/test_dpkg.py
@@ -6,10 +6,18 @@ class TestDpkg:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("dpkg -L ", xfail='test -z "$(dpkg -l 2>/dev/null)"')
+ @pytest.mark.complete("dpkg -L ", xfail='test ! "$(dpkg -l 2>/dev/null)"')
def test_2(self, completion):
assert completion
@pytest.mark.complete("dpkg -i ~")
def test_3(self, completion):
assert completion
+
+ @pytest.mark.complete("dpkg -i dpkg/")
+ def test_i_deb(self, completion):
+ assert completion == "bash-completion-test-subject.deb"
+
+ @pytest.mark.complete("dpkg -")
+ def test_no_trailing_dash_options(self, completion):
+ assert not any(x.endswith("-") for x in completion)
diff --git a/test/t/test_dpkg_query.py b/test/t/test_dpkg_query.py
index 37c5621..743d668 100644
--- a/test/t/test_dpkg_query.py
+++ b/test/t/test_dpkg_query.py
@@ -3,7 +3,7 @@ import os.path
import pytest
-@pytest.mark.bashcomp(cmd="dpkg-query",)
+@pytest.mark.bashcomp(cmd="dpkg-query")
class TestDpkgQuery:
@pytest.mark.complete("dpkg-query --", require_cmd=True)
def test_options(self, completion):
@@ -13,6 +13,10 @@ class TestDpkgQuery:
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)
+ @pytest.mark.complete(
+ "dpkg-query -W dpk",
+ require_cmd=True,
+ xfail="! apt-cache show &>/dev/null", # empty cache?
+ )
def test_show(self, completion):
assert "dpkg" in completion
diff --git a/test/t/test_dropdb.py b/test/t/test_dropdb.py
index 2f65857..f75979a 100644
--- a/test/t/test_dropdb.py
+++ b/test/t/test_dropdb.py
@@ -2,7 +2,6 @@ import pytest
class TestDropdb:
-
# --help can fail due to missing package dependencies, e.g. on Ubuntu 14
@pytest.mark.complete(
"dropdb -", require_cmd=True, xfail="! dropdb --help &>/dev/null"
diff --git a/test/t/test_evince.py b/test/t/test_evince.py
index 9e9245d..f89e97c 100644
--- a/test/t/test_evince.py
+++ b/test/t/test_evince.py
@@ -1,22 +1,32 @@
import pytest
+from conftest import assert_complete, create_dummy_filedirs
+
+@pytest.mark.bashcomp(temp_cwd=True)
class TestEvince:
- @pytest.mark.complete("evince ", cwd="evince")
- def test_1(self, completion):
- # .txt should not be here
- assert completion == sorted(
- "foo/ .bmp .BMP .cbr .CBR .cbz .CBZ .djv .DJV .djvu .DJVU .dvi "
- ".DVI .dvi.bz2 .dvi.BZ2 .DVI.bz2 .DVI.BZ2 .dvi.gz .dvi.GZ "
- ".DVI.gz .DVI.GZ .eps .EPS .eps.bz2 .eps.BZ2 .EPS.bz2 .EPS.BZ2 "
- ".eps.gz .eps.GZ .EPS.gz .EPS.GZ .gif .GIF .ico .ICO .jpeg "
- ".JPEG .jpg .JPG .miff .MIFF .pbm .PBM .pcx .PCX .pdf .PDF "
- ".pdf.bz2 .pdf.BZ2 .PDF.bz2 .PDF.BZ2 .pdf.gz .pdf.GZ .PDF.gz "
- ".PDF.GZ .pgm .PGM .png .PNG .pnm .PNM .ppm .PPM .ps .PS "
- ".ps.bz2 .ps.BZ2 .PS.bz2 .PS.BZ2 .ps.gz .ps.GZ .PS.gz .PS.GZ "
- ".tga .TGA .tif .TIF .tiff .TIFF .xpm .XPM .xwd .XWD".split()
+ def test_1(self, bash):
+ files, dirs = create_dummy_filedirs(
+ (
+ ".cb7 .CB7 .cbr .CBR .cbt .CBT .cbz .CBZ .djv .DJV .djvu "
+ ".DJVU .dvi .DVI .dvi.bz2 .dvi.BZ2 .DVI.bz2 .DVI.BZ2 .dvi.gz "
+ ".dvi.GZ .DVI.gz .DVI.GZ .eps .EPS .eps.bz2 .eps.BZ2 .EPS.bz2 "
+ ".EPS.BZ2 .eps.gz .eps.GZ .EPS.gz .EPS.GZ .oxps .OXPS .pdf "
+ ".PDF .pdf.bz2 .pdf.BZ2 .PDF.bz2 .PDF.BZ2 .pdf.gz .pdf.GZ "
+ ".PDF.gz .PDF.GZ .ps .PS .ps.bz2 .ps.BZ2 .PS.bz2 .PS.BZ2 "
+ ".ps.gz .ps.GZ .PS.gz .PS.GZ .tif .TIF .tiff .TIFF .txt .TXT "
+ ".xps .XPS"
+ ).split(),
+ "foo".split(),
)
+ completion = assert_complete(bash, "evince ")
+ assert completion == [
+ x
+ for x in sorted(files + ["%s/" % d for d in dirs])
+ if x.lower() != ".txt"
+ ]
+
@pytest.mark.complete("evince -", require_cmd=True)
def test_2(self, completion):
assert completion
diff --git a/test/t/test_export.py b/test/t/test_export.py
index 8738913..8abb564 100644
--- a/test/t/test_export.py
+++ b/test/t/test_export.py
@@ -1,5 +1,7 @@
import pytest
+from conftest import assert_bash_exec
+
class TestExport:
@pytest.mark.complete("export BASH")
@@ -22,10 +24,9 @@ class TestExport:
def test_5(self, completion):
assert completion == ["foo", "foo.d/"]
- @pytest.mark.complete("export -fn _ex")
+ @pytest.mark.complete("export -fn _comp_cmd_ex")
def test_6(self, completion):
- assert "_expand" in completion
- assert "_export" in completion
+ assert completion == "port"
@pytest.mark.complete(r"export FOO=$BASH")
def test_7(self, completion):
@@ -34,3 +35,13 @@ class TestExport:
@pytest.mark.complete("export -", require_cmd=True)
def test_8(self, completion):
assert completion
+
+ @pytest.fixture(scope="class")
+ def export_f_canary(self, request, bash):
+ assert_bash_exec(bash, "_comp__test_export_f_canary() { return; }")
+
+ @pytest.mark.complete("export -f _comp__test_export_f_canar")
+ def test_no_equals_sign_for_function(self, completion, export_f_canary):
+ assert completion
+ assert "=" not in "".join(completion)
+ assert completion.endswith(" ")
diff --git a/test/t/test_finger.py b/test/t/test_finger.py
index d765fdd..4aca977 100644
--- a/test/t/test_finger.py
+++ b/test/t/test_finger.py
@@ -26,8 +26,8 @@ class TestFinger:
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))
+ completion = assert_complete(bash, f"finger {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]
+ assert completion == [f"{user}@{x}" for x in partial_hosts]
diff --git a/test/t/test_fio.py b/test/t/test_fio.py
index 0f6eba7..dadb5ac 100644
--- a/test/t/test_fio.py
+++ b/test/t/test_fio.py
@@ -10,6 +10,44 @@ class TestFio:
def test_2(self, completion):
assert completion
- @pytest.mark.complete("fio --debug=foo,")
+ @pytest.mark.complete("fio --debug=foo,", require_cmd=True)
def test_3(self, completion):
assert completion
+
+ @pytest.mark.complete("fio --ioengin", require_cmd=True)
+ def test_cmdhelp_all(self, completion):
+ """Test we got a "known present" option from --cmdhelp=all."""
+ assert completion == "e=" or "e" in completion
+
+ @pytest.mark.complete("fio --ioengine=", require_cmd=True)
+ def test_enghelp(self, completion):
+ """Test --enghelp parsing."""
+ assert completion
+
+ @pytest.mark.complete("fio --unlink=", require_cmd=True)
+ def test_cmdhelp_boolean(self, completion):
+ """Test --cmdhelp=COMMAND boolean parsing."""
+ assert completion == "0 1".split()
+
+ @pytest.mark.complete("fio --kb_base=", require_cmd=True)
+ def test_cmdhelp_valid_values(self, completion):
+ """Test --cmdhelp=COMMAND valid values parsing."""
+ # We expect kb_base args to be stable, no additions/removals.
+ assert completion == "1000 1024".split()
+
+ @pytest.mark.complete("fio --non_exist3nt_option=", require_cmd=True)
+ def test_cmdhelp_nonexistent(self, completion):
+ """Test --cmdhelp=COMMAND errors out gracefully."""
+ assert not completion
+
+ @pytest.mark.complete(
+ "fio --crctest=",
+ require_cmd=True,
+ xfail="! fio --help 2>&1 | command grep -q -- --crctest",
+ )
+ def test_crctest(self, completion):
+ assert "sha1" in completion
+
+ @pytest.mark.complete("fio --debug=", require_cmd=True)
+ def test_debug(self, completion):
+ assert "process" in completion
diff --git a/test/t/test_firefox.py b/test/t/test_firefox.py
index 2e05255..619318c 100644
--- a/test/t/test_firefox.py
+++ b/test/t/test_firefox.py
@@ -6,7 +6,12 @@ class TestFirefox:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("firefox -", require_cmd=True)
+ # --help test: running as root in GH actions container croaks:
+ # Running Firefox as root in a regular user's session is not supported.
+ # ($HOME is /github/home which is owned by uid 1001.)
+ @pytest.mark.complete(
+ "firefox -", require_cmd=True, xfail="! firefox --help &>/dev/null"
+ )
def test_2(self, completion):
assert completion
assert not completion.endswith(" ")
diff --git a/test/t/test_function.py b/test/t/test_function.py
index 4401f02..ce74780 100644
--- a/test/t/test_function.py
+++ b/test/t/test_function.py
@@ -1,7 +1,19 @@
import pytest
+from conftest import assert_bash_exec, assert_complete
+
+@pytest.mark.bashcomp(ignore_env=r"^\+declare -f fn$")
class TestFunction:
@pytest.mark.complete("function _parse_")
def test_1(self, completion):
assert completion
+
+ @pytest.mark.complete("function non_existent_function ")
+ def test_2(self, completion):
+ assert completion == "()"
+
+ def test_3(self, bash):
+ assert_bash_exec(bash, "fn() { echo; }")
+ completion = assert_complete(bash, "function fn ")
+ assert completion == "() { ^J echo^J}"
diff --git a/test/t/test_gdb.py b/test/t/test_gdb.py
index 2ad12c4..9f038f8 100644
--- a/test/t/test_gdb.py
+++ b/test/t/test_gdb.py
@@ -12,3 +12,13 @@ class TestGdb:
"core core.12345 "
"core.weston.1000.deadbeef.5308.1555362132000000".split()
)
+
+ @pytest.mark.complete("gdb aw")
+ def test_3(self, completion):
+ """Check that the completion can generate command names"""
+ assert completion == ["k"] or "awk" in completion
+
+ @pytest.mark.complete("gdb built")
+ def test_4(self, completion):
+ """Check that the completion does not generate builtin names"""
+ assert not (completion == ["in"] or "builtin" in completion)
diff --git a/test/t/test_getconf.py b/test/t/test_getconf.py
index c80c803..e9578e7 100644
--- a/test/t/test_getconf.py
+++ b/test/t/test_getconf.py
@@ -2,7 +2,7 @@ import pytest
class TestGetconf:
- @pytest.mark.complete("getconf P")
+ @pytest.mark.complete("getconf P", skipif="! getconf -a &>/dev/null")
def test_1(self, completion):
assert completion
diff --git a/test/t/test_grep.py b/test/t/test_grep.py
index a249122..ded7fb5 100644
--- a/test/t/test_grep.py
+++ b/test/t/test_grep.py
@@ -11,6 +11,16 @@ class TestGrep:
"""
Test --no-*dir isn't restricted to dirs only.
- Not really a grep option, but tests _longopt.
+ Not really a grep option, but tests _comp_complete_longopt.
"""
assert completion == "foo foo.d/".split()
+
+ @pytest.mark.complete("grep TZ ", cwd="shared/default")
+ def test_no_variable_assignment_confusion(self, completion):
+ """
+ Test TZ doesn't trigger known variable value assignment completion.
+
+ Not really a grep specific, but good to test somewhere.
+ Refs https://github.com/scop/bash-completion/issues/457
+ """
+ assert "foo" in completion
diff --git a/test/t/test_gssdp_device_sniffer.py b/test/t/test_gssdp_device_sniffer.py
new file mode 100644
index 0000000..be05d0d
--- /dev/null
+++ b/test/t/test_gssdp_device_sniffer.py
@@ -0,0 +1,14 @@
+import pytest
+
+
+@pytest.mark.bashcomp(
+ cmd="gssdp-device-sniffer",
+)
+class TestGssdpDeviceSniffer:
+ @pytest.mark.complete("gssdp-device-sniffer ")
+ def test_basic(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("gssdp-device-sniffer -", require_cmd=True)
+ def test_options(self, completion):
+ assert "--help" in completion
diff --git a/test/t/test_hash.py b/test/t/test_hash.py
new file mode 100644
index 0000000..2a1bf0c
--- /dev/null
+++ b/test/t/test_hash.py
@@ -0,0 +1,11 @@
+import pytest
+
+
+class TestHash:
+ @pytest.mark.complete("hash ", require_cmd=True)
+ def test_basic(self, completion):
+ assert completion
+
+ @pytest.mark.complete("hash -", require_cmd=True)
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_help.py b/test/t/test_help.py
new file mode 100644
index 0000000..b056c55
--- /dev/null
+++ b/test/t/test_help.py
@@ -0,0 +1,21 @@
+import pytest
+
+
+class TestHelp:
+ @pytest.mark.complete("help ")
+ def test_basic(self, completion):
+ assert "help" in completion
+
+ @pytest.mark.complete("help -")
+ def test_options(self, completion):
+ assert completion
+
+ @pytest.mark.complete(
+ r"help \(",
+ skipif="! compgen -A helptopic | grep -qxF '(( ... ))'", # bash 4.2
+ )
+ def test_parens(self, completion):
+ # Assumption: an item like "(( ... ))" exists in the output
+ assert any(
+ x.startswith(r"\(") and x.endswith(r"\)\)") for x in completion
+ )
diff --git a/test/t/test_info.py b/test/t/test_info.py
index e12d900..2fdbcca 100644
--- a/test/t/test_info.py
+++ b/test/t/test_info.py
@@ -10,3 +10,11 @@ class TestInfo:
@pytest.mark.complete("info -", require_cmd=True)
def test_2(self, completion):
assert completion
+
+ @pytest.mark.complete(
+ "info nonexistent-na",
+ env=dict(INFOPATH="'$(echo malicious code >/dev/tty)'"),
+ )
+ def test_infopath_code_injection(self, completion):
+ # no completion, no space appended
+ assert not completion
diff --git a/test/t/test_installpkg.py b/test/t/test_installpkg.py
index e665f52..0ae4840 100644
--- a/test/t/test_installpkg.py
+++ b/test/t/test_installpkg.py
@@ -12,7 +12,8 @@ class TestInstallpkg:
@pytest.mark.complete("installpkg --")
def test_2(self, completion):
assert (
- completion == "--ask --infobox --md5sum --menu "
+ completion
+ == "--ask --infobox --md5sum --menu "
"--priority --root --tagfile --terse --warn".split()
)
diff --git a/test/t/test_invoke_rc_d.py b/test/t/test_invoke_rc_d.py
index 61e2987..5ef768c 100644
--- a/test/t/test_invoke_rc_d.py
+++ b/test/t/test_invoke_rc_d.py
@@ -1,8 +1,14 @@
+import sys
+
import pytest
@pytest.mark.bashcomp(cmd="invoke-rc.d")
class TestInvokeRcD:
+ @pytest.mark.xfail(
+ sys.platform == "darwin",
+ reason="Service completion not available on macOS",
+ )
@pytest.mark.complete("invoke-rc.d ")
def test_1(self, completion):
assert completion
@@ -10,5 +16,6 @@ class TestInvokeRcD:
@pytest.mark.complete("invoke-rc.d --no-fallback --")
def test_2(self, completion):
"""Test already specified option is not offered."""
- assert completion
+ if sys.platform != "darwin": # no service completion
+ assert completion
assert "--no-fallback" not in completion
diff --git a/test/t/test_ip.py b/test/t/test_ip.py
index 320647f..0be088c 100644
--- a/test/t/test_ip.py
+++ b/test/t/test_ip.py
@@ -10,6 +10,19 @@ class TestIp:
def test_2(self, completion):
assert completion
- @pytest.mark.complete("ip route replace ")
+ @pytest.mark.complete("ip route replace ", require_cmd=True)
def test_r_r(self, completion):
assert completion
+
+ @pytest.mark.complete(
+ "ip monitor ",
+ require_cmd=True,
+ skipif="ip monitor help 2>/dev/null; (( $? != 255 ))",
+ )
+ def test_monitor(self, completion):
+ assert "neigh" in completion
+ assert "all" in completion
+
+ @pytest.mark.complete("ip -", require_cmd=True)
+ def test_options(self, completion):
+ assert "-family" in completion
diff --git a/test/t/test_iperf.py b/test/t/test_iperf.py
index c38e954..0b67001 100644
--- a/test/t/test_iperf.py
+++ b/test/t/test_iperf.py
@@ -22,3 +22,8 @@ class TestIperf:
@pytest.mark.complete("iperf -", require_cmd=True)
def test_5(self, completion):
assert completion
+
+ @pytest.mark.complete("iperf --format ", require_cmd=True)
+ def test_format(self, completion):
+ # 2.0.5 has only up to m/M, later may have g/G, t/T, ...
+ assert all(x in completion for x in "k m K M".split())
diff --git a/test/t/test_iperf3.py b/test/t/test_iperf3.py
index 15f3a03..35070c1 100644
--- a/test/t/test_iperf3.py
+++ b/test/t/test_iperf3.py
@@ -18,3 +18,8 @@ class TestIperf3:
@pytest.mark.complete("iperf3 --server --", require_cmd=True)
def test_4(self, completion):
assert "--daemon" in completion
+
+ @pytest.mark.complete("iperf3 --format ", require_cmd=True)
+ def test_format(self, completion):
+ # 3.0.7 has only up to m/M, later may have g/G, t/T, ...
+ assert all(x in completion for x in "k m K M".split())
diff --git a/test/t/test_isort.py b/test/t/test_isort.py
index b142d1c..1c86b84 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, require_longopt=True)
+ @pytest.mark.complete("isort -", require_longopt=True)
def test_2(self, completion):
assert completion
diff --git a/test/t/test_java.py b/test/t/test_java.py
index ce0f773..03f1520 100644
--- a/test/t/test_java.py
+++ b/test/t/test_java.py
@@ -1,6 +1,6 @@
import pytest
-from conftest import is_bash_type
+from conftest import is_bash_type, assert_bash_exec, bash_env_saved
@pytest.mark.bashcomp(
@@ -47,3 +47,46 @@ class TestJava:
@pytest.mark.complete("java -jar java/")
def test_6(self, completion):
assert completion == "a/ bashcomp.jar bashcomp.war".split()
+
+ @pytest.mark.complete("javadoc -sourcepath java/a:java/a/c ")
+ def test_sourcepath_1(self, completion):
+ """sourcepath should be split by `:`"""
+ assert completion == "c"
+
+ @pytest.mark.complete("javadoc -sourcepath java/?:java/x ")
+ def test_sourcepath_2(self, completion):
+ """pathname expansion should not happen after splitting the argument by
+ `:`"""
+ assert not completion
+
+ @pytest.mark.complete("javadoc -sourcepath java/a ")
+ def test_packages_1(self, completion):
+ assert completion == "c"
+
+ @pytest.mark.complete("javadoc -sourcepath java/a x")
+ def test_packages_2(self, completion):
+ assert not completion
+
+ @pytest.mark.complete(
+ "javadoc -sourcepath java/a x", shopt=dict(failglob=True)
+ )
+ def test_packages_3(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("javadoc -sourcepath java/a ", env=dict(IFS="a"))
+ def test_packages_4(self, completion):
+ assert completion == "c"
+
+ def test_packages_5(self, bash):
+ """_comp_cmd_java__packages should not modify the outerscope `cur`"""
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "a.b.c")
+ assert_bash_exec(
+ bash,
+ "_comp_test_f() { local cword=3 words=(javadoc -sourcepath java/a a.b.c); COMPREPLY+=(); _comp_cmd_java__packages; }; _comp_test_f",
+ )
+
+ @pytest.mark.complete("javadoc -sourcepath java a.")
+ def test_packages_6(self, completion):
+ """A period in package names should not be converted to slash."""
+ assert completion == "c"
diff --git a/test/t/test_javaws.py b/test/t/test_javaws.py
index 596c735..4d033c3 100644
--- a/test/t/test_javaws.py
+++ b/test/t/test_javaws.py
@@ -6,6 +6,12 @@ class TestJavaws:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("javaws -", require_cmd=True)
+ @pytest.mark.complete(
+ "javaws -",
+ require_cmd=True,
+ xfail=(
+ "! (javaws -help 2>&1 || :) | " "command grep -q -- '[[:space:]]-'"
+ ),
+ )
def test_2(self, completion):
assert completion
diff --git a/test/t/test_jq.py b/test/t/test_jq.py
index 4701414..ca4d0e0 100644
--- a/test/t/test_jq.py
+++ b/test/t/test_jq.py
@@ -28,3 +28,11 @@ class TestJq:
@pytest.mark.complete("jq --slurpfile foo ")
def test_6(self, completion):
assert completion
+
+ @pytest.mark.complete("jq --args ")
+ def test_no_completion_after_args(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("jq --jsonargs foo ")
+ def test_no_completion_after_jsonargs(self, completion):
+ assert not completion
diff --git a/test/t/test_jsonschema.py b/test/t/test_jsonschema.py
index 6027f5d..5f43435 100644
--- a/test/t/test_jsonschema.py
+++ b/test/t/test_jsonschema.py
@@ -6,8 +6,6 @@ class TestJsonschema:
def test_1(self, completion):
assert completion
- @pytest.mark.complete(
- "jsonschema -", require_cmd=True, require_longopt=True
- )
+ @pytest.mark.complete("jsonschema -", require_longopt=True)
def test_2(self, completion):
assert completion
diff --git a/test/t/test_kdvi.py b/test/t/test_kdvi.py
index c2ab011..114e024 100644
--- a/test/t/test_kdvi.py
+++ b/test/t/test_kdvi.py
@@ -1,10 +1,22 @@
import pytest
+from conftest import assert_complete, create_dummy_filedirs
+
+@pytest.mark.bashcomp(temp_cwd=True)
class TestKdvi:
- @pytest.mark.complete("kdvi ", cwd="kdvi")
- def test_1(self, completion):
- assert completion == sorted(
- "foo/ .dvi .DVI .dvi.bz2 .DVI.bz2 .dvi.gz "
- ".DVI.gz .dvi.Z .DVI.Z".split()
+ def test_1(self, bash):
+ files, dirs = create_dummy_filedirs(
+ (
+ ".dvi .DVI .dvi.bz2 .DVI.bz2 .dvi.gz .DVI.gz .dvi.Z .DVI.Z "
+ ".txt"
+ ).split(),
+ "foo".split(),
)
+
+ completion = assert_complete(bash, "kdvi ")
+ assert completion == [
+ x
+ for x in sorted(files + ["%s/" % d for d in dirs])
+ if x.lower() != ".txt"
+ ]
diff --git a/test/t/test_kill.py b/test/t/test_kill.py
index 9699435..9233c00 100644
--- a/test/t/test_kill.py
+++ b/test/t/test_kill.py
@@ -13,3 +13,7 @@ class TestKill:
@pytest.mark.complete("kill -")
def test_3(self, completion):
assert all("-%s" % x in completion for x in "l s ABRT USR1".split())
+
+ @pytest.mark.complete("kill %", pre_cmds=("bash -c 'sleep 5' &",))
+ def test_jobs(self, bash, completion):
+ assert "bash" in completion
diff --git a/test/t/test_killall.py b/test/t/test_killall.py
index 4b67d96..96eab8a 100644
--- a/test/t/test_killall.py
+++ b/test/t/test_killall.py
@@ -2,7 +2,6 @@ import pytest
class TestKillall:
-
# "p": Assume our process name completion runs ps and at least it is shown
@pytest.mark.complete("killall p")
def test_1(self, completion):
diff --git a/test/t/test_kpdf.py b/test/t/test_kpdf.py
index 68b36fe..b7e658f 100644
--- a/test/t/test_kpdf.py
+++ b/test/t/test_kpdf.py
@@ -1,7 +1,19 @@
import pytest
+from conftest import assert_complete, create_dummy_filedirs
+
+@pytest.mark.bashcomp(temp_cwd=True)
class TestKpdf:
- @pytest.mark.complete("kpdf ", cwd="kpdf")
- def test_1(self, completion):
- assert completion == sorted("foo/ .eps .ps .EPS .PS .pdf .PDF".split())
+ def test_1(self, bash):
+ files, dirs = create_dummy_filedirs(
+ ".eps .EPS .pdf .PDF .ps .PS .txt".split(),
+ "foo".split(),
+ )
+
+ completion = assert_complete(bash, "kpdf ")
+ assert completion == [
+ x
+ for x in sorted(files + ["%s/" % d for d in dirs])
+ if x.lower() != ".txt"
+ ]
diff --git a/test/t/test_ld.py b/test/t/test_ld.py
index f6a16bb..004692e 100644
--- a/test/t/test_ld.py
+++ b/test/t/test_ld.py
@@ -6,6 +6,6 @@ class TestLd:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("ld -", require_cmd=True)
+ @pytest.mark.complete("ld -", require_longopt=True)
def test_options(self, completion):
assert completion
diff --git a/test/t/test_lrzip.py b/test/t/test_lrzip.py
index d61ee9d..dbc3939 100644
--- a/test/t/test_lrzip.py
+++ b/test/t/test_lrzip.py
@@ -13,3 +13,12 @@ class TestLrzip:
@pytest.mark.complete("lrzip -", require_cmd=True)
def test_3(self, completion):
assert completion
+
+ @pytest.mark.complete(
+ "lrzip --",
+ # require_longopt not applicable, useful --help may give nonzero exit
+ require_cmd=True,
+ xfail=("! { lrzip --help 2>&1 || :; } | command grep -qF -- --help"),
+ )
+ def test_longopt(self, completion):
+ assert completion
diff --git a/test/t/test_ls.py b/test/t/test_ls.py
index 8abcb59..f91ee6b 100644
--- a/test/t/test_ls.py
+++ b/test/t/test_ls.py
@@ -24,7 +24,7 @@ class TestLs:
assert_bash_exec(
bash,
"for u in $(compgen -u); do "
- "eval test -d ~$u || echo $u; unset u; done",
+ "eval test -d ~$u || echo $u; unset -v u; done",
want_output=True,
)
.strip()
diff --git a/test/t/test_lvm.py b/test/t/test_lvm.py
index ea25b97..82f1aa4 100644
--- a/test/t/test_lvm.py
+++ b/test/t/test_lvm.py
@@ -5,3 +5,11 @@ class TestLvm:
@pytest.mark.complete("lvm pv")
def test_1(self, completion):
assert completion
+
+ @pytest.mark.complete(
+ "lvm lvcreate --",
+ require_cmd=True,
+ xfail="! lvm lvcreate --help &>/dev/null",
+ )
+ def test_subcommand_options(self, completion):
+ assert completion
diff --git a/test/t/test_make.py b/test/t/test_make.py
index 19861b0..34fc7e5 100644
--- a/test/t/test_make.py
+++ b/test/t/test_make.py
@@ -2,6 +2,8 @@ import os
import pytest
+from conftest import assert_complete
+
class TestMake:
@pytest.mark.complete("make -f Ma", cwd="make")
@@ -12,12 +14,12 @@ class TestMake:
def test_2(self, bash, completion):
"""Hidden targets."""
assert completion == ".cache/ .test_passes".split()
- os.remove("%s/make/%s" % (bash.cwd, "extra_makefile"))
+ os.remove(f"{bash.cwd}/make/extra_makefile")
@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"))
+ assert completion == ".cache/1 .cache/2".split()
+ os.remove(f"{bash.cwd}/make/extra_makefile")
@pytest.mark.complete("make ", cwd="shared/empty_dir")
def test_4(self, completion):
@@ -30,18 +32,58 @@ class TestMake:
@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"))
+ os.remove(f"{bash.cwd}/make/extra_makefile")
@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"))
+ assert completion == ".cache/.1 .cache/.2".split()
+ os.remove(f"{bash.cwd}/make/extra_makefile")
@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"))
+ os.remove(f"{bash.cwd}/make/extra_makefile")
+
+ @pytest.mark.complete("make -nC make ", require_cmd=True)
+ def test_8n(self, bash, completion):
+ assert completion == "all clean extra_makefile install sample".split()
+ os.remove(f"{bash.cwd}/make/extra_makefile")
@pytest.mark.complete("make -", require_cmd=True)
def test_9(self, completion):
assert completion
+
+
+@pytest.mark.bashcomp(require_cmd=True, cwd="make/test2")
+class TestMake2:
+ def test_github_issue_544_1(self, bash):
+ completion = assert_complete(bash, "make ab")
+ assert completion == "c/xyz"
+
+ def test_github_issue_544_2(self, bash):
+ completion = assert_complete(bash, "make 1")
+ assert completion == "23/"
+
+ def test_github_issue_544_3(self, bash):
+ completion = assert_complete(bash, "make 123/")
+ assert completion == ["123/xaa", "123/xbb"]
+
+ def test_github_issue_544_4(self, bash):
+ completion = assert_complete(bash, "make 123/xa")
+ assert completion == "a"
+
+ def test_subdir_1(self, bash):
+ completion = assert_complete(bash, "make sub1")
+ assert completion == "test/bar/"
+
+ def test_subdir_2(self, bash):
+ completion = assert_complete(bash, "make sub2")
+ assert completion == "test/bar/alpha"
+
+ def test_subdir_3(self, bash):
+ completion = assert_complete(bash, "make sub3")
+ assert completion == "test/"
+
+ def test_subdir_4(self, bash):
+ completion = assert_complete(bash, "make sub4")
+ assert completion == "sub4test/bar/ sub4test2/foo/gamma".split()
diff --git a/test/t/test_makepkg.py b/test/t/test_makepkg.py
index f643a29..ad37762 100644
--- a/test/t/test_makepkg.py
+++ b/test/t/test_makepkg.py
@@ -2,7 +2,7 @@ import pytest
@pytest.mark.bashcomp(
- ignore_env=r"^-declare -f _makepkg_bootstrap$",
+ ignore_env=r"^-declare -f _comp_cmd_makepkg__bootstrap$",
xfail="! makepkg --help 2>&1 | command grep -qiF slackware",
)
class TestMakepkg:
diff --git a/test/t/test_man.py b/test/t/test_man.py
index 1ff9f84..081b8fc 100644
--- a/test/t/test_man.py
+++ b/test/t/test_man.py
@@ -1,13 +1,17 @@
import pytest
-from conftest import assert_bash_exec
+from conftest import (
+ assert_bash_exec,
+ assert_complete,
+ is_bash_type,
+ prepare_fixture_dir,
+)
@pytest.mark.bashcomp(
- ignore_env=r"^[+-]((BASHOPTS|MANPATH)=|shopt -. failglob)"
+ ignore_env=r"^[+-]((BASHOPTS|MANPATH|manpath)=|shopt -. failglob)"
)
class TestMan:
-
manpath = "$PWD/man"
assumed_present = "man"
@@ -20,13 +24,12 @@ class TestMan:
else:
pytest.skip("Cygwin doesn't like paths with colons")
return
- assert_bash_exec(bash, "mkdir -p $TESTDIR/../tmp/man/man3")
- assert_bash_exec(
- bash, "touch $TESTDIR/../tmp/man/man3/Bash::Completion.3pm.gz"
- )
- request.addfinalizer(
- lambda: assert_bash_exec(bash, "rm -r $TESTDIR/../tmp/man")
+ tmpdir, _, _ = prepare_fixture_dir(
+ request,
+ files=["man/man3/Bash::Completion.3pm.gz"],
+ dirs=["man", "man/man3"],
)
+ return tmpdir
@pytest.mark.complete(
"man bash-completion-testcas",
@@ -96,20 +99,60 @@ class TestMan:
"man %s" % assumed_present,
require_cmd=True,
cwd="shared/empty_dir",
- pre_cmds=("shopt -s failglob",),
+ shopt=dict(failglob=True),
)
def test_9(self, bash, completion):
assert self.assumed_present in completion
- assert_bash_exec(bash, "shopt -u failglob")
- @pytest.mark.complete(
- "man Bash::C",
- require_cmd=True,
- env=dict(MANPATH="%s:../tmp/man" % manpath),
- )
- def test_10(self, bash, colonpath, completion):
+ def test_10(self, request, bash, colonpath):
+ if not is_bash_type(bash, "man"):
+ pytest.skip("Command not found")
+ completion = assert_complete(
+ bash,
+ "man Bash::C",
+ env=dict(MANPATH="%s:%s/man" % (TestMan.manpath, colonpath)),
+ )
assert completion == "ompletion"
@pytest.mark.complete("man -", require_cmd=True)
def test_11(self, completion):
assert completion
+
+ @pytest.mark.complete("man -S 1", require_cmd=True)
+ def test_delimited_first(self, completion):
+ # just appends space
+ assert not completion
+ assert completion.endswith(" ")
+
+ @pytest.mark.complete("man -S 1:", require_cmd=True)
+ def test_delimited_after_delimiter(self, completion):
+ assert completion
+ assert "1" not in completion
+
+ @pytest.mark.complete("man -S 1:2", require_cmd=True)
+ def test_delimited_later(self, completion):
+ # just appends space
+ assert not completion
+ assert completion.endswith(" ")
+
+ @pytest.mark.complete("man -S 1:1", require_cmd=True)
+ def test_delimited_deduplication(self, completion):
+ # no completion, no space appended
+ assert not completion
+ assert not completion.endswith(" ")
+
+ @pytest.mark.complete(
+ "man bash-completion-zstd-testcas",
+ env=dict(MANPATH=manpath),
+ require_cmd=True,
+ )
+ def test_zstd_arbitrary_sectsuffix(self, completion):
+ assert completion == "e"
+
+ @pytest.mark.complete(
+ "man bash-completion-testcas",
+ env=dict(MANPATH="'$(echo malicious code >/dev/tty)'"),
+ )
+ def test_manpath_code_injection(self, completion):
+ # no completion, no space appended
+ assert not completion
diff --git a/test/t/test_md5sum.py b/test/t/test_md5sum.py
index 0a3286a..e1568a1 100644
--- a/test/t/test_md5sum.py
+++ b/test/t/test_md5sum.py
@@ -6,6 +6,6 @@ class TestMd5sum:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("md5sum -", require_longopt=True)
+ @pytest.mark.complete("md5sum --", require_longopt=True)
def test_options(self, completion):
assert completion
diff --git a/test/t/test_munindoc.py b/test/t/test_munindoc.py
index eea13ca..38c7d52 100644
--- a/test/t/test_munindoc.py
+++ b/test/t/test_munindoc.py
@@ -2,7 +2,6 @@ import pytest
class TestMunindoc:
-
# Assume at least munin* available
# require_cmd is not strictly correct here, but...
@pytest.mark.complete("munindoc m", require_cmd=True)
diff --git a/test/t/test_mutt.py b/test/t/test_mutt.py
index 0c4074f..e5b6387 100644
--- a/test/t/test_mutt.py
+++ b/test/t/test_mutt.py
@@ -5,6 +5,13 @@ from conftest import assert_bash_exec
@pytest.mark.bashcomp(pre_cmds=("HOME=$PWD/mutt",))
class TestMutt:
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ '_comp_test__muttconffiles() { local REPLY; _comp_cmd_mutt__get_conffiles "$@" && printf "%s\\n" "${REPLY[@]}"; }',
+ )
+
@pytest.mark.complete("mutt -")
def test_1(self, completion):
assert completion
@@ -17,17 +24,17 @@ class TestMutt:
def test_3(self, completion):
assert completion == "a1 a2".split()
- def test_4(self, bash):
+ def test_4(self, bash, functions):
got = (
assert_bash_exec(
bash,
- '_muttconffiles "$HOME/muttrc" "$HOME/muttrc"',
+ '_comp_test__muttconffiles "$HOME/muttrc" "$HOME/muttrc"',
want_output=True,
)
.strip()
.split()
)
assert got == [
- "%s/mutt/%s" % (bash.cwd, x)
+ f"{bash.cwd}/mutt/{x}"
for x in ("muttrc", "bar/muttrc_b", "foo/muttrc_f")
]
diff --git a/test/t/test_mypy.py b/test/t/test_mypy.py
index 11628c1..9d5f16b 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, require_longopt=True)
+ @pytest.mark.complete("mypy --", require_longopt=True)
def test_2(self, completion):
assert completion
diff --git a/test/t/test_neomutt.py b/test/t/test_neomutt.py
new file mode 100644
index 0000000..36657fe
--- /dev/null
+++ b/test/t/test_neomutt.py
@@ -0,0 +1,7 @@
+import pytest
+
+
+class TestNeomutt:
+ @pytest.mark.complete("neomutt -")
+ def test_1(self, completion):
+ assert completion
diff --git a/test/t/test_nmap.py b/test/t/test_nmap.py
index 9aff8b2..76aa989 100644
--- a/test/t/test_nmap.py
+++ b/test/t/test_nmap.py
@@ -7,7 +7,7 @@ class TestNmap:
@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")
+ assert_bash_exec(bash, "complete -F _comp_cmd_nmap _mock_nmap")
@pytest.mark.complete("nmap --v", require_cmd=True)
def test_live_options(self, completion):
diff --git a/test/t/test_nproc.py b/test/t/test_nproc.py
index 66a49ac..29f208b 100644
--- a/test/t/test_nproc.py
+++ b/test/t/test_nproc.py
@@ -6,12 +6,6 @@ class TestNproc:
def test_1(self, completion):
assert not completion
- @pytest.mark.complete(
- "nproc --",
- xfail=(
- "! nproc --help &>/dev/null || "
- "! nproc --help 2>&1 | command grep -qF -- --help"
- ),
- )
+ @pytest.mark.complete("nproc --", require_longopt=True)
def test_2(self, completion):
assert completion
diff --git a/test/t/test_op.py b/test/t/test_op.py
deleted file mode 100644
index 662cde5..0000000
--- a/test/t/test_op.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import pytest
-
-
-class TestOp:
- @pytest.mark.complete("op ", require_cmd=True)
- def test_1(self, completion):
- assert completion
-
- @pytest.mark.complete("op --", require_cmd=True)
- def test_2(self, completion):
- assert completion
diff --git a/test/t/test_pdftoppm.py b/test/t/test_pdftoppm.py
new file mode 100644
index 0000000..2a1a4c6
--- /dev/null
+++ b/test/t/test_pdftoppm.py
@@ -0,0 +1,11 @@
+import pytest
+
+
+class TestPdftoppm:
+ @pytest.mark.complete("pdftoppm ")
+ def test_files(self, completion):
+ assert completion
+
+ @pytest.mark.complete("pdftoppm -", require_cmd=True)
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_pgrep.py b/test/t/test_pgrep.py
index 9a998ed..db3dfdd 100644
--- a/test/t/test_pgrep.py
+++ b/test/t/test_pgrep.py
@@ -2,7 +2,6 @@ import pytest
class TestPgrep:
-
# "p": Assume that our process name completion runs ps
@pytest.mark.complete("pgrep p")
def test_1(self, completion):
@@ -32,4 +31,3 @@ class TestPgrep:
)
def test_nslist_after_comma(self, completion):
assert completion
- assert not any("," in x for x in completion)
diff --git a/test/t/test_pidof.py b/test/t/test_pidof.py
index c33a4d3..de77298 100644
--- a/test/t/test_pidof.py
+++ b/test/t/test_pidof.py
@@ -2,7 +2,6 @@ import pytest
class TestPidof:
-
# "p": Assume that our process name completion runs ps
@pytest.mark.complete("pidof p")
def test_1(self, completion):
diff --git a/test/t/test_pkg_config.py b/test/t/test_pkg_config.py
index 81e02ca..14f9c86 100644
--- a/test/t/test_pkg_config.py
+++ b/test/t/test_pkg_config.py
@@ -1,3 +1,5 @@
+import os
+
import pytest
@@ -10,3 +12,11 @@ class TestPkgConfig:
@pytest.mark.complete("pkg-config -", require_cmd=True)
def test_2(self, completion):
assert completion
+
+ @pytest.mark.complete(
+ "pkg-config %s/bash-completion.pc --variable="
+ % os.getenv("ABS_TOP_BUILDDIR", "../.."),
+ require_cmd=True,
+ )
+ def test_variable(self, completion):
+ assert "completionsdir" in completion
diff --git a/test/t/test_pkgconf.py b/test/t/test_pkgconf.py
new file mode 100644
index 0000000..1466a89
--- /dev/null
+++ b/test/t/test_pkgconf.py
@@ -0,0 +1,22 @@
+import os
+
+import pytest
+
+
+@pytest.mark.bashcomp(cmd="pkgconf")
+class TestPkgconf:
+ @pytest.mark.complete("pkgconf ")
+ def test_1(self, completion):
+ assert completion
+
+ @pytest.mark.complete("pkgconf -", require_cmd=True)
+ def test_2(self, completion):
+ assert completion
+
+ @pytest.mark.complete(
+ "pkgconf %s/bash-completion.pc --variable="
+ % os.getenv("ABS_TOP_BUILDDIR", "../.."),
+ require_cmd=True,
+ )
+ def test_variable(self, completion):
+ assert "completionsdir" in completion
diff --git a/test/t/test_portinstall.py b/test/t/test_portinstall.py
index eb2118e..73a21be 100644
--- a/test/t/test_portinstall.py
+++ b/test/t/test_portinstall.py
@@ -3,22 +3,19 @@ import pytest
from conftest import assert_bash_exec
-@pytest.mark.bashcomp(ignore_env=r"^[+-]PORTSDIR=")
+@pytest.mark.bashcomp(ignore_env=r"^[+-]PORTSDIR=", temp_cwd=True)
class TestPortinstall:
@pytest.fixture(scope="class")
def portsdir(self, request, bash):
- assert_bash_exec(bash, "PORTSDIR=$PWD/../tmp")
+ assert_bash_exec(bash, "PORTSDIR=$PWD")
assert_bash_exec(
bash,
"command sed -e s,PORTSDIR,$PORTSDIR,g "
- "pkgtools/ports/INDEX.dist >$PORTSDIR/INDEX",
- )
- assert_bash_exec(bash, "cp $PORTSDIR/INDEX $PORTSDIR/INDEX-5")
- request.addfinalizer(
- lambda: assert_bash_exec(bash, "rm $PORTSDIR/INDEX{,-5}")
+ '"$SRCDIRABS"/fixtures/pkgtools/ports/INDEX.dist >INDEX',
)
+ assert_bash_exec(bash, "cp INDEX INDEX-5")
- @pytest.mark.complete("portinstall ", env=dict(PORTSDIR="$PWD/../tmp"))
+ @pytest.mark.complete("portinstall ", env=dict(PORTSDIR="$PWD"))
def test_1(self, completion, portsdir):
assert (
completion
diff --git a/test/t/test_pr.py b/test/t/test_pr.py
index c790a86..23fa767 100644
--- a/test/t/test_pr.py
+++ b/test/t/test_pr.py
@@ -6,6 +6,6 @@ class TestPr:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("pr -", require_cmd=True)
+ @pytest.mark.complete("pr -", require_longopt=True)
def test_options(self, completion):
assert completion
diff --git a/test/t/test_printenv.py b/test/t/test_printenv.py
index 540c4f6..8384ec0 100644
--- a/test/t/test_printenv.py
+++ b/test/t/test_printenv.py
@@ -13,7 +13,7 @@ class TestPrintenv:
@pytest.mark.complete(
"printenv -",
require_cmd=True,
- xfail="! printenv --help 2>&1 | command grep -qF -- ' -'",
+ xfail="! printenv --help 2>&1 | command grep -q '^[[:space:]]*-'",
)
def test_options(self, completion):
assert completion
diff --git a/test/t/test_ps.py b/test/t/test_ps.py
new file mode 100644
index 0000000..a6bfec1
--- /dev/null
+++ b/test/t/test_ps.py
@@ -0,0 +1,50 @@
+import pytest
+
+
+def is_int(s):
+ try:
+ int(s)
+ except ValueError:
+ return False
+ else:
+ return True
+
+
+class TestPs:
+ @pytest.mark.complete("ps -", require_cmd=True)
+ def test_1(self, completion):
+ assert completion
+
+ @pytest.mark.complete("ps --help ")
+ def test_2(self, completion):
+ assert completion == [
+ "all",
+ "list",
+ "misc",
+ "output",
+ "simple",
+ "threads",
+ ]
+
+ @pytest.mark.complete("ps --help all ")
+ def test_3(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("ps --version ")
+ def test_4(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("ps --pid ")
+ def test_5(self, completion):
+ assert completion
+ assert all(map(is_int, completion))
+
+ @pytest.mark.complete("ps --format ", require_cmd=True)
+ def test_6(self, completion):
+ assert completion
+ assert all(map(lambda c: not c.startswith(("-", ",")), completion))
+
+ @pytest.mark.complete("ps --format user,", require_cmd=True)
+ def test_7(self, completion):
+ assert completion
+ assert all(map(lambda c: not c.startswith(("-", ",")), completion))
diff --git a/test/t/test_psql.py b/test/t/test_psql.py
index ffd6c05..73abc4b 100644
--- a/test/t/test_psql.py
+++ b/test/t/test_psql.py
@@ -2,7 +2,6 @@ import pytest
class TestPsql:
-
# --help can fail due to missing package dependencies, e.g. on Ubuntu 14
@pytest.mark.complete(
"psql -", require_cmd=True, xfail="! psql --help &>/dev/null"
diff --git a/test/t/test_pushd.py b/test/t/test_pushd.py
index 290e1d1..5b115d3 100644
--- a/test/t/test_pushd.py
+++ b/test/t/test_pushd.py
@@ -5,3 +5,7 @@ class TestPushd:
@pytest.mark.complete("pushd ")
def test_1(self, completion):
assert completion
+
+ @pytest.mark.complete("pushd -")
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_pydocstyle.py b/test/t/test_pydocstyle.py
index 1f44320..028a3c0 100644
--- a/test/t/test_pydocstyle.py
+++ b/test/t/test_pydocstyle.py
@@ -6,8 +6,6 @@ class TestPydocstyle:
def test_1(self, completion):
assert completion
- @pytest.mark.complete(
- "pydocstyle -", require_cmd=True, require_longopt=True
- )
+ @pytest.mark.complete("pydocstyle -", 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 43a4c43..76c7778 100644
--- a/test/t/test_pylint.py
+++ b/test/t/test_pylint.py
@@ -2,10 +2,22 @@ import pytest
class TestPylint:
- @pytest.mark.complete("pylint --v", require_cmd=True, require_longopt=True)
+ @pytest.mark.complete("pylint --v", require_longopt=True)
def test_1(self, completion):
assert completion
@pytest.mark.complete("pylint --confidence=HIGH,")
def test_2(self, completion):
assert completion
+
+ @pytest.mark.complete("pylint --help-msg=", require_longopt=True)
+ def test_all_message_ids(self, completion):
+ assert any("-" in x for x in completion)
+
+ @pytest.mark.complete("pylint --disable=", require_longopt=True)
+ def test_enabled_message_ids(self, completion):
+ assert any("-" in x for x in completion)
+
+ @pytest.mark.complete("pylint --enable=foo,", require_longopt=True)
+ def test_disabled_message_ids(self, completion):
+ assert any("-" in x for x in completion)
diff --git a/test/t/test_pyston.py b/test/t/test_pyston.py
new file mode 100644
index 0000000..3c23f31
--- /dev/null
+++ b/test/t/test_pyston.py
@@ -0,0 +1,19 @@
+import pytest
+
+
+class TestPyston:
+ @pytest.mark.complete("pyston ")
+ def test_basic(self, completion):
+ assert completion
+
+ @pytest.mark.complete("pyston -", require_cmd=True)
+ def test_options(self, completion):
+ assert completion
+
+ @pytest.mark.complete(
+ "pyston -b",
+ require_cmd=True,
+ skipif="! pyston -h | command grep -qwF -- -bb",
+ )
+ def test_bb(self, completion):
+ assert "-bb" in completion
diff --git a/test/t/test_pytest.py b/test/t/test_pytest.py
index e70c7a5..dededc2 100644
--- a/test/t/test_pytest.py
+++ b/test/t/test_pytest.py
@@ -8,7 +8,7 @@ class TestPytest:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("pytest -")
+ @pytest.mark.complete("pytest -", require_cmd=True)
def test_2(self, completion):
assert completion
diff --git a/test/t/test_python.py b/test/t/test_python.py
index 5308dcb..bcc566b 100644
--- a/test/t/test_python.py
+++ b/test/t/test_python.py
@@ -37,3 +37,50 @@ class TestPython:
@pytest.mark.complete("python -m json.", require_cmd=True)
def test_9(self, completion):
assert "json.tool" in completion
+
+ @pytest.mark.complete(
+ "python -b",
+ require_cmd=True,
+ skipif="! python -h | command grep -qwF -- -bb",
+ )
+ def test_bb(self, completion):
+ assert "-bb" in completion
+
+ @pytest.mark.complete("python foo ", cwd="python")
+ def test_script_arg(self, completion):
+ assert "bar.txt" in completion
+
+ @pytest.mark.complete("python -- foo ", cwd="python")
+ def test_script_arg_with_double_hyphen(self, completion):
+ assert "bar.txt" in completion
+
+ @pytest.mark.complete("python -m foo bar -p ", cwd="python")
+ def test_module_arg(self, completion):
+ assert "bar.txt" in completion
+
+ @pytest.mark.complete("python foo bar -p ", cwd="python")
+ def test_script_arg_after_option(self, completion):
+ assert "bar.txt" in completion
+
+ @pytest.mark.complete("python -- foo bar -p ", cwd="python")
+ def test_script_arg_after_option_with_double_hyphen(self, completion):
+ assert "bar.txt" in completion
+
+ @pytest.mark.complete("python -m foo bar -p ", cwd="python")
+ def test_module_arg_after_option(self, completion):
+ assert "bar.txt" in completion
+
+ @pytest.mark.complete("python -mfoo bar -p ", cwd="python")
+ def test_module_arg_after_option_with_connected_m_arg(self, completion):
+ assert "bar.txt" in completion
+
+ @pytest.mark.complete("python -- ", cwd="python")
+ def test_script_name(self, completion):
+ assert "bar.txt" not in completion
+
+ @pytest.mark.complete("python -W -mfoo ", cwd="python")
+ def test_script_name_with_fake_m_arg(self, completion):
+ """In this case, -mfoo looks like an option to specify the module, but
+ it should not be treated as the module name because it is an option
+ argument to -W."""
+ assert "bar.txt" not in completion
diff --git a/test/t/test_python3.py b/test/t/test_python3.py
index a4f6d96..179f90c 100644
--- a/test/t/test_python3.py
+++ b/test/t/test_python3.py
@@ -34,6 +34,10 @@ class TestPython3:
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
+ @pytest.mark.complete(
+ "python3 -b",
+ require_cmd=True,
+ skipif="! python3 -h | command grep -qwF -- -bb",
+ )
+ def test_bb(self, completion):
+ assert "-bb" in completion
diff --git a/test/t/test_qemu.py b/test/t/test_qemu.py
index 129c0b4..0dd0a2a 100644
--- a/test/t/test_qemu.py
+++ b/test/t/test_qemu.py
@@ -9,3 +9,7 @@ class TestQemu:
@pytest.mark.complete("qemu -", require_cmd=True)
def test_2(self, completion):
assert completion
+
+ @pytest.mark.complete("qemu -k ", require_cmd=True)
+ def test_keymaps(self, completion):
+ assert any(x.lower().startswith("en") for x in completion)
diff --git a/test/t/test_qemu_system_x86_64.py b/test/t/test_qemu_system_x86_64.py
new file mode 100644
index 0000000..c9e8052
--- /dev/null
+++ b/test/t/test_qemu_system_x86_64.py
@@ -0,0 +1,16 @@
+import pytest
+
+
+@pytest.mark.bashcomp(cmd="qemu-system-x86_64")
+class TestQemuSystemX8664:
+ @pytest.mark.complete("qemu-system-x86_64 ")
+ def test_basic(self, completion):
+ assert completion
+
+ @pytest.mark.complete("qemu-system-x86_64 -", require_cmd=True)
+ def test_options(self, completion):
+ assert completion
+
+ @pytest.mark.complete("qemu-system-x86_64 -k ", require_cmd=True)
+ def test_keymaps(self, completion):
+ assert any(x.lower().startswith("en") for x in completion)
diff --git a/test/t/test_reportbug.py b/test/t/test_reportbug.py
index 2c57b56..9347adf 100644
--- a/test/t/test_reportbug.py
+++ b/test/t/test_reportbug.py
@@ -5,3 +5,7 @@ class TestReportbug:
@pytest.mark.complete("reportbug --m", require_cmd=True)
def test_1(self, completion):
assert completion
+
+ @pytest.mark.complete("reportbug --bts=", require_cmd=True)
+ def test_bts(self, completion):
+ assert "default" in completion
diff --git a/test/t/test_rmdir.py b/test/t/test_rmdir.py
index b981677..19b5ea4 100644
--- a/test/t/test_rmdir.py
+++ b/test/t/test_rmdir.py
@@ -11,6 +11,6 @@ class TestRmdir:
"""Should complete dirs only, also when invoked using full path."""
assert completion == ["bar bar.d/", "foo.d/"]
- @pytest.mark.complete("rmdir -", require_cmd=True)
+ @pytest.mark.complete("rmdir -", require_longopt=True)
def test_options(self, completion):
assert completion
diff --git a/test/t/test_rpm.py b/test/t/test_rpm.py
index e6f7198..2f1567b 100644
--- a/test/t/test_rpm.py
+++ b/test/t/test_rpm.py
@@ -6,7 +6,7 @@ class TestRpm:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("rpm -q ", skipif='test -z "$(rpm -qa 2>/dev/null)"')
+ @pytest.mark.complete("rpm -q ", skipif='test ! "$(rpm -qa 2>/dev/null)"')
def test_2(self, completion):
assert completion
diff --git a/test/t/test_rsync.py b/test/t/test_rsync.py
index d54ce6f..0f98dcc 100644
--- a/test/t/test_rsync.py
+++ b/test/t/test_rsync.py
@@ -1,7 +1,7 @@
import pytest
-@pytest.mark.bashcomp(ignore_env=r"^[+-]_scp_path_esc=")
+@pytest.mark.bashcomp(ignore_env=r"^[+-]_comp_cmd_scp__path_esc=")
class TestRsync:
@pytest.mark.complete("rsync ")
def test_1(self, completion):
@@ -14,3 +14,7 @@ class TestRsync:
@pytest.mark.complete("rsync --rsh=")
def test_3(self, completion):
assert completion == "rsh ssh".split()
+
+ @pytest.mark.complete("rsync --", require_cmd=True)
+ def test_4(self, completion):
+ assert "--help" in completion
diff --git a/test/t/test_scp.py b/test/t/test_scp.py
index 66b8da2..3bd06ee 100644
--- a/test/t/test_scp.py
+++ b/test/t/test_scp.py
@@ -76,4 +76,22 @@ class TestScp:
Connection to it must open sufficiently quickly for the
ConnectTimeout and sleep_after_tab settings.
"""
- assert completion == "%s:%s/" % (LIVE_HOST, live_pwd)
+ assert completion == f"{LIVE_HOST}:{live_pwd}/"
+
+ @pytest.mark.complete("scp -o Foo=")
+ def test_option_arg(self, completion):
+ assert not completion # and no errors either
+
+ @pytest.mark.complete(
+ "scp hostname-not-expected-to-exist-in-known-hosts:",
+ shopt=dict(nullglob=True),
+ )
+ def test_remote_path_with_nullglob(self, completion):
+ assert not completion
+
+ @pytest.mark.complete(
+ "scp hostname-not-expected-to-exist-in-known-hosts:",
+ shopt=dict(failglob=True),
+ )
+ def test_remote_path_with_failglob(self, completion):
+ assert not completion
diff --git a/test/t/test_secret_tool.py b/test/t/test_secret_tool.py
index cbfc0cb..7791446 100644
--- a/test/t/test_secret_tool.py
+++ b/test/t/test_secret_tool.py
@@ -1,7 +1,7 @@
import pytest
-@pytest.mark.bashcomp(cmd="secret-tool",)
+@pytest.mark.bashcomp(cmd="secret-tool")
class TestSecretTool:
@pytest.mark.complete("secret-tool ", require_cmd=True)
def test_modes(self, completion):
@@ -9,4 +9,4 @@ class TestSecretTool:
@pytest.mark.complete("secret-tool search ")
def test_no_complete(self, completion):
- assert not completion
+ assert completion == "--all --unlock".split()
diff --git a/test/t/test_service.py b/test/t/test_service.py
index 7ce4312..8e7fb05 100644
--- a/test/t/test_service.py
+++ b/test/t/test_service.py
@@ -1,7 +1,13 @@
+import sys
+
import pytest
class TestService:
+ @pytest.mark.xfail(
+ sys.platform == "darwin",
+ reason="Service completion not available on macOS",
+ )
@pytest.mark.complete("service ")
def test_1(self, completion):
assert completion
diff --git a/test/t/test_sha224sum.py b/test/t/test_sha224sum.py
new file mode 100644
index 0000000..c6b6138
--- /dev/null
+++ b/test/t/test_sha224sum.py
@@ -0,0 +1,7 @@
+import pytest
+
+
+class TestSha224sum:
+ @pytest.mark.complete("sha224sum --", require_longopt=True)
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_sha256sum.py b/test/t/test_sha256sum.py
new file mode 100644
index 0000000..c8df94c
--- /dev/null
+++ b/test/t/test_sha256sum.py
@@ -0,0 +1,15 @@
+import pytest
+
+
+class TestSha256sum:
+ @pytest.mark.complete("sha256sum --", require_longopt=True)
+ def test_options(self, completion):
+ assert completion
+
+ @pytest.mark.complete("sha256sum ", cwd="sha256sum")
+ def test_summing(self, completion):
+ assert completion == "foo"
+
+ @pytest.mark.complete("sha256sum -c ", cwd="sha256sum")
+ def test_checking(self, completion):
+ assert completion == "foo.sha256"
diff --git a/test/t/test_sha384sum.py b/test/t/test_sha384sum.py
new file mode 100644
index 0000000..903ee8c
--- /dev/null
+++ b/test/t/test_sha384sum.py
@@ -0,0 +1,7 @@
+import pytest
+
+
+class TestSha384sum:
+ @pytest.mark.complete("sha384sum --", require_longopt=True)
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_sha512sum.py b/test/t/test_sha512sum.py
new file mode 100644
index 0000000..746e64e
--- /dev/null
+++ b/test/t/test_sha512sum.py
@@ -0,0 +1,7 @@
+import pytest
+
+
+class TestSha512sum:
+ @pytest.mark.complete("sha512sum --", require_longopt=True)
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_ssh.py b/test/t/test_ssh.py
index 8e95819..f714d99 100644
--- a/test/t/test_ssh.py
+++ b/test/t/test_ssh.py
@@ -10,8 +10,12 @@ class TestSsh:
@pytest.mark.complete("ssh -F config ls", cwd="ssh")
def test_2(self, completion):
- """Should complete both commands and hostname."""
- assert all(x in completion for x in "ls ls_known_host".split())
+ """
+ Should not complete commands when host is not specified.
+
+ Test sanity assumes there are commands starting with `ls`.
+ """
+ assert completion == "_known_host"
@pytest.mark.complete("ssh bash", cwd="ssh")
def test_3(self, completion):
@@ -58,3 +62,8 @@ class TestSsh:
def test_protocol_option_bundling(self, bash, protocol):
completion = assert_complete(bash, "ssh -%sF ssh/" % protocol)
assert "config" in completion
+
+ @pytest.mark.complete("ssh -F config -o ForwardX11=yes ls", cwd="ssh")
+ def test_options_with_args_and_arg_counting(self, completion):
+ """Options with arguments should not confuse arg counting."""
+ assert completion == "_known_host"
diff --git a/test/t/test_ssh_add.py b/test/t/test_ssh_add.py
index 7e49372..cec12c7 100644
--- a/test/t/test_ssh_add.py
+++ b/test/t/test_ssh_add.py
@@ -15,3 +15,23 @@ class TestSshAdd:
)
def test_2(self, completion):
assert completion
+
+ @pytest.mark.complete(
+ "ssh-add -",
+ require_cmd=True,
+ xfail="ssh-add --help 2>&1 | "
+ "command grep -qiF 'Could not open a connection'",
+ shopt=dict(failglob=True),
+ )
+ def test_2_failglob(self, completion):
+ assert completion
+
+ @pytest.mark.complete(
+ "ssh-add -",
+ require_cmd=True,
+ xfail="ssh-add --help 2>&1 | "
+ "command grep -qiF 'Could not open a connection'",
+ shopt=dict(nullglob=True),
+ )
+ def test_2_nullglob(self, completion):
+ assert completion
diff --git a/test/t/test_ssh_copy_id.py b/test/t/test_ssh_copy_id.py
index e38e901..327ee7c 100644
--- a/test/t/test_ssh_copy_id.py
+++ b/test/t/test_ssh_copy_id.py
@@ -8,7 +8,7 @@ import pytest
# identities are found. Try to make sure there is at least one.
"HOME=$PWD/ssh-copy-id",
),
- ignore_env=r"^[+-]_scp_path_esc=",
+ ignore_env=r"^[+-]_comp_cmd_scp__path_esc=",
)
class TestSshCopyId:
@pytest.mark.complete("ssh-copy-id -", require_cmd=True)
diff --git a/test/t/test_ssh_keygen.py b/test/t/test_ssh_keygen.py
index b773ab4..cc6ff4e 100644
--- a/test/t/test_ssh_keygen.py
+++ b/test/t/test_ssh_keygen.py
@@ -7,6 +7,18 @@ class TestSshKeygen:
def test_1(self, completion):
assert completion
+ @pytest.mark.complete(
+ "ssh-keygen -", require_cmd=True, shopt=dict(failglob=True)
+ )
+ def test_1_failglob(self, completion):
+ assert completion
+
+ @pytest.mark.complete(
+ "ssh-keygen -", require_cmd=True, shopt=dict(nullglob=True)
+ )
+ def test_1_nullglob(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
@@ -15,13 +27,11 @@ class TestSshKeygen:
@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 ")
@@ -57,3 +67,19 @@ class TestSshKeygen:
@pytest.mark.complete("ssh-keygen -O unknown=")
def test_O_unknown(self, completion):
assert not completion
+
+ @pytest.mark.complete("ssh-keygen -O application=")
+ def test_O_application(self, completion):
+ assert completion == "ssh:"
+
+ @pytest.mark.complete("ssh-keygen -O application=s")
+ def test_O_application_s(self, completion):
+ assert completion == "sh:"
+
+ @pytest.mark.complete("ssh-keygen -O application=ssh:")
+ def test_O_application_ssh_colon(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("ssh-keygen -O application=nonexistent")
+ def test_O_application_nonexistent(self, completion):
+ assert not completion
diff --git a/test/t/test_ssh_keyscan.py b/test/t/test_ssh_keyscan.py
new file mode 100644
index 0000000..ee65832
--- /dev/null
+++ b/test/t/test_ssh_keyscan.py
@@ -0,0 +1,19 @@
+import pytest
+
+
+@pytest.mark.bashcomp(
+ cmd="ssh-keyscan",
+)
+class TestSshKeyscan:
+ @pytest.mark.complete("ssh-keyscan ")
+ def test_basic(self, completion):
+ assert completion
+
+ @pytest.mark.complete("ssh-keyscan -", require_cmd=True)
+ def test_options(self, completion):
+ assert completion
+
+ @pytest.mark.complete("ssh-keyscan -t rsa,", require_cmd=True)
+ def test_type_delimited(self, completion):
+ assert completion
+ assert "rsa" not in completion
diff --git a/test/t/test_sshfs.py b/test/t/test_sshfs.py
index 44daed3..cb4189b 100644
--- a/test/t/test_sshfs.py
+++ b/test/t/test_sshfs.py
@@ -1,7 +1,7 @@
import pytest
-@pytest.mark.bashcomp(ignore_env=r"^[+-]_scp_path_esc=")
+@pytest.mark.bashcomp(ignore_env=r"^[+-]_comp_cmd_scp__path_esc=")
class TestSshfs:
@pytest.mark.complete("sshfs ./")
def test_1(self, completion):
diff --git a/test/t/test_su.py b/test/t/test_su.py
index 9aa064d..d71d6e1 100644
--- a/test/t/test_su.py
+++ b/test/t/test_su.py
@@ -6,6 +6,8 @@ class TestSu:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("su -", require_cmd=True)
+ @pytest.mark.complete(
+ "su -", require_cmd=True, skipif="[[ $OSTYPE != *linux* ]]"
+ )
def test_2(self, completion):
assert completion
diff --git a/test/t/test_sudo.py b/test/t/test_sudo.py
index a349466..a7d67de 100644
--- a/test/t/test_sudo.py
+++ b/test/t/test_sudo.py
@@ -1,6 +1,6 @@
import pytest
-from conftest import assert_complete
+from conftest import assert_bash_exec, assert_complete
class TestSudo:
@@ -8,9 +8,9 @@ class TestSudo:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("sudo cd fo", cwd="shared/default")
+ @pytest.mark.complete("sudo cd foo", cwd="shared/default")
def test_2(self, completion):
- assert completion == "o.d/"
+ assert completion == ".d/"
assert not completion.endswith(" ")
@pytest.mark.complete("sudo sh share")
@@ -81,3 +81,7 @@ class TestSudo:
part, _ = part_full_group
completion = assert_complete(bash, "sudo chown foo:bar:%s" % part)
assert not completion
+
+ def test_12(self, bash):
+ assert_complete(bash, 'sudo "/tmp/aaa bbb" ')
+ assert_bash_exec(bash, "! complete -p aaa", want_output=None)
diff --git a/test/t/test_sum.py b/test/t/test_sum.py
index bfb2cf4..2060370 100644
--- a/test/t/test_sum.py
+++ b/test/t/test_sum.py
@@ -6,6 +6,6 @@ class TestSum:
def test_1(self, completion):
assert completion
- @pytest.mark.complete("sum -", require_longopt=True)
+ @pytest.mark.complete("sum --", require_longopt=True)
def test_options(self, completion):
assert completion
diff --git a/test/t/test_synclient.py b/test/t/test_synclient.py
index 8a31a65..8ce66f6 100644
--- a/test/t/test_synclient.py
+++ b/test/t/test_synclient.py
@@ -2,7 +2,6 @@ import pytest
class TestSynclient:
-
# synclient -l may error out with e.g.
# "Couldn't find synaptics properties. No synaptics driver loaded?"
@pytest.mark.complete(
diff --git a/test/t/test_tar.py b/test/t/test_tar.py
index 4518d0b..2616446 100644
--- a/test/t/test_tar.py
+++ b/test/t/test_tar.py
@@ -5,7 +5,7 @@ import pytest
from conftest import assert_bash_exec
-@pytest.mark.bashcomp(ignore_env=r"^-declare -f _tar$")
+@pytest.mark.bashcomp(ignore_env=r"^-declare -f _comp_cmd_tar$")
class TestTar:
@pytest.fixture(scope="class")
def gnu_tar(self, bash):
@@ -13,8 +13,8 @@ class TestTar:
if not re.search(r"\bGNU ", got):
pytest.skip("Not GNU tar")
- @pytest.mark.complete("tar ")
- def test_1(self, completion):
+ @pytest.mark.complete("tar ", shopt=dict(failglob=True))
+ def test_1(self, bash, completion):
assert completion
# Test "f" when mode is not as first option
diff --git a/test/t/test_time.py b/test/t/test_time.py
index 231f14e..f2049d5 100644
--- a/test/t/test_time.py
+++ b/test/t/test_time.py
@@ -4,9 +4,15 @@ import pytest
class TestTime:
- @pytest.mark.complete("time set")
- def test_1(self, completion):
- assert completion
+ @pytest.mark.complete("time _comp_delimite", cwd="shared/empty_dir")
+ def test_command(self, completion):
+ """
+ Test completion of commands.
+
+ We use a function of ours as the test subject, as that's guaranteed
+ to be available, and do not rely on anything in particular in $PATH.
+ """
+ assert completion == "d" or "_comp_delimited" in completion
@pytest.mark.complete("time -p find -typ")
def test_2(self, completion):
diff --git a/test/t/test_tox.py b/test/t/test_tox.py
index f012a03..b101b9b 100644
--- a/test/t/test_tox.py
+++ b/test/t/test_tox.py
@@ -2,7 +2,7 @@ import pytest
class TestTox:
- @pytest.mark.complete("tox -")
+ @pytest.mark.complete("tox -", require_cmd=True)
def test_1(self, completion):
assert completion
@@ -12,7 +12,7 @@ class TestTox:
@pytest.mark.complete("tox -e foo,", cwd="tox")
def test_3(self, completion):
- assert all(x in completion for x in "py37 ALL".split())
+ assert all("foo," + x in completion for x in "py37 ALL".split())
@pytest.mark.complete("tox -e foo -- ", cwd="tox")
def test_default_after_dashdash(self, completion):
diff --git a/test/t/test_tree.py b/test/t/test_tree.py
new file mode 100644
index 0000000..214e415
--- /dev/null
+++ b/test/t/test_tree.py
@@ -0,0 +1,23 @@
+import pytest
+
+
+class TestTree:
+ @pytest.mark.complete("tree ", cwd="shared/default")
+ def test_basic(self, completion):
+ assert completion == ["bar bar.d/", "foo.d/"]
+
+ @pytest.mark.complete("tree --fromfile ", cwd="shared/default")
+ def test_fromfile(self, completion):
+ assert completion == ["bar", "bar bar.d/", "foo", "foo.d/"]
+
+ @pytest.mark.complete(
+ "tree -",
+ require_cmd=True,
+ xfail="! tree --help 2>&1 | command grep -qF -- ' -'",
+ )
+ def test_options(self, completion):
+ assert completion
+
+ @pytest.mark.complete("tree --sort=", require_cmd=True)
+ def test_equals_sign_split(self, completion):
+ assert completion
diff --git a/test/t/test_truncate.py b/test/t/test_truncate.py
new file mode 100644
index 0000000..b50baac
--- /dev/null
+++ b/test/t/test_truncate.py
@@ -0,0 +1,11 @@
+import pytest
+
+
+class TestTruncate:
+ @pytest.mark.complete("truncate ")
+ def test_basic(self, completion):
+ assert completion
+
+ @pytest.mark.complete("truncate -", require_cmd=True)
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_tshark.py b/test/t/test_tshark.py
index f49533e..d44e62d 100644
--- a/test/t/test_tshark.py
+++ b/test/t/test_tshark.py
@@ -1,7 +1,7 @@
import pytest
-@pytest.mark.bashcomp(ignore_env=r"^\+_tshark_pr(ef|otocol)s=")
+@pytest.mark.bashcomp(ignore_env=r"^\+_comp_cmd_tshark__pr(ef|otocol)s=")
class TestTshark:
@pytest.mark.complete("tshark -", require_cmd=True)
def test_1(self, completion):
@@ -14,7 +14,7 @@ class TestTshark:
@pytest.mark.complete("tshark -O foo,htt", require_cmd=True)
def test_3(self, completion):
# p: one completion only; http: e.g. http and http2
- assert completion == "p" or "http" in completion
+ assert completion == "p" or "foo,http" in completion
@pytest.mark.complete("tshark -o tcp", require_cmd=True)
def test_4(self, completion):
diff --git a/test/t/test_ulimit.py b/test/t/test_ulimit.py
index 3ab974c..271b6ce 100644
--- a/test/t/test_ulimit.py
+++ b/test/t/test_ulimit.py
@@ -1,10 +1,12 @@
import pytest
+from conftest import assert_complete
+
class TestUlimit:
@pytest.mark.complete("ulimit ")
def test_1(self, completion):
- assert completion
+ assert not completion
@pytest.mark.complete("ulimit -", require_cmd=True)
def test_2(self, completion):
@@ -33,3 +35,13 @@ class TestUlimit:
def test_7(self, completion):
"""Test modes are NOT completed with -a given somewhere."""
assert not completion
+
+ @pytest.mark.parametrize("flag", ["-S", "-H"])
+ def test_no_special_values_after_soft_or_hard(self, bash, flag):
+ completion = assert_complete(bash, "ulimit %s " % flag)
+ assert not completion
+
+ @pytest.mark.complete("ulimit -c 0 -n ")
+ def test_special_not_just_first(self, completion):
+ """Test we offer limit values not only for the first option."""
+ assert completion
diff --git a/test/t/test_umount.py b/test/t/test_umount.py
index 2baf0da..de18835 100644
--- a/test/t/test_umount.py
+++ b/test/t/test_umount.py
@@ -1,3 +1,5 @@
+import sys
+
import pytest
from conftest import assert_bash_exec
@@ -11,12 +13,14 @@ class TestUmount:
(correctly) uses absolute paths. So we create a custom completion which
reads from a file in our text fixture instead.
"""
+ if sys.platform != "linux":
+ pytest.skip("Linux specific")
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; "
+ "local cur prev;_comp_get_words cur prev; "
+ '_comp_cmd_umount__linux_fstab "$prev" < mount/test-fstab; '
"} && complete -F _mnt_completion _mnt",
)
request.addfinalizer(
@@ -75,9 +79,10 @@ class TestUmount:
def test_mnt_label_quote(self, completion, dummy_mnt):
assert completion == r"ian-it\'s\ awesome"
+ @pytest.mark.skipif(sys.platform != "linux", reason="Linux specific")
def test_linux_fstab_unescape(self, bash):
assert_bash_exec(bash, r"var=one\'two\\040three\\")
- assert_bash_exec(bash, "__linux_fstab_unescape var")
+ assert_bash_exec(bash, "_comp_cmd_umount__linux_fstab_unescape var")
output = assert_bash_exec(
bash, r'printf "%s\n" "$var"', want_output=True
)
diff --git a/test/t/test_upgradepkg.py b/test/t/test_upgradepkg.py
index 87fe8e4..8411333 100644
--- a/test/t/test_upgradepkg.py
+++ b/test/t/test_upgradepkg.py
@@ -12,8 +12,8 @@ class TestUpgradepkg:
@pytest.mark.complete("upgradepkg --")
def test_2(self, completion):
assert (
- completion == "--dry-run --install-new --reinstall "
- "--verbose".split()
+ completion
+ == "--dry-run --install-new --reinstall --verbose".split()
)
@pytest.mark.complete("upgradepkg ", cwd="slackware/home")
diff --git a/test/t/test_useradd.py b/test/t/test_useradd.py
index 5cbf6ce..2eddede 100644
--- a/test/t/test_useradd.py
+++ b/test/t/test_useradd.py
@@ -9,3 +9,7 @@ class TestUseradd:
@pytest.mark.complete("useradd -", require_cmd=True)
def test_2(self, completion):
assert completion
+
+ @pytest.mark.complete("useradd -R shells --shell=")
+ def test_chroot_shells(self, completion):
+ assert completion == "/bash/completion/canary"
diff --git a/test/t/test_usermod.py b/test/t/test_usermod.py
index ef3dd5a..61c99a0 100644
--- a/test/t/test_usermod.py
+++ b/test/t/test_usermod.py
@@ -9,3 +9,7 @@ class TestUsermod:
@pytest.mark.complete("usermod -", require_cmd=True)
def test_2(self, completion):
assert completion
+
+ @pytest.mark.complete("useradd --root shells -s ")
+ def test_chroot_shells(self, completion):
+ assert completion == "/bash/completion/canary"
diff --git a/test/t/test_valgrind.py b/test/t/test_valgrind.py
index 0553b55..dbe7028 100644
--- a/test/t/test_valgrind.py
+++ b/test/t/test_valgrind.py
@@ -4,7 +4,6 @@ import pytest
class TestValgrind:
-
# b: Assume we have at least bash that starts with b in PATH
@pytest.mark.complete("valgrind b")
def test_1(self, completion):
diff --git a/test/t/test_vipw.py b/test/t/test_vipw.py
index 07b454b..b78fcbd 100644
--- a/test/t/test_vipw.py
+++ b/test/t/test_vipw.py
@@ -1,7 +1,12 @@
+import sys
+
import pytest
class TestVipw:
@pytest.mark.complete("vipw -", require_cmd=True)
def test_1(self, completion):
- assert completion
+ if sys.platform == "darwin":
+ assert not completion # takes no options
+ else:
+ assert completion
diff --git a/test/t/test_vncviewer.py b/test/t/test_vncviewer.py
index 9e2f148..643d98d 100644
--- a/test/t/test_vncviewer.py
+++ b/test/t/test_vncviewer.py
@@ -1,7 +1,9 @@
import pytest
-@pytest.mark.bashcomp(ignore_env=r"^-declare -f _vncviewer_bootstrap$")
+@pytest.mark.bashcomp(
+ ignore_env=r"^-declare -f _comp_cmd_vncviewer__bootstrap$"
+)
class TestVncviewer:
@pytest.mark.complete("vncviewer ")
def test_1(self, completion):
diff --git a/test/t/test_who.py b/test/t/test_who.py
index 9131ac7..69b93c9 100644
--- a/test/t/test_who.py
+++ b/test/t/test_who.py
@@ -2,8 +2,6 @@ import pytest
class TestWho:
- @pytest.mark.complete(
- "who --", require_cmd=True, xfail="! who --help &>/dev/null"
- )
+ @pytest.mark.complete("who --", require_longopt=True)
def test_1(self, completion):
assert completion
diff --git a/test/t/test_wine.py b/test/t/test_wine.py
index d0e5698..e18ea1c 100644
--- a/test/t/test_wine.py
+++ b/test/t/test_wine.py
@@ -9,3 +9,7 @@ class TestWine:
@pytest.mark.complete("wine notepad ", cwd="shared/default")
def test_2(self, completion):
assert completion == ["bar", "bar bar.d/", "foo", "foo.d/"]
+
+ @pytest.mark.complete("wine --help ")
+ def test_no_complete_after_help(self, completion):
+ assert not completion
diff --git a/test/t/test_xdg_mime.py b/test/t/test_xdg_mime.py
index 432be06..b91e75a 100644
--- a/test/t/test_xdg_mime.py
+++ b/test/t/test_xdg_mime.py
@@ -19,10 +19,14 @@ class TestXdgMime:
def test_4(self, completion):
assert completion
- @pytest.mark.complete("xdg-mime default foo.desktop ")
+ @pytest.mark.complete("xdg-mime default foo.desktop ", require_cmd=True)
def test_5(self, completion):
assert completion
@pytest.mark.complete("xdg-mime install --mode ")
def test_6(self, completion):
assert completion
+
+ @pytest.mark.complete("xdg-mime query filetype foo ")
+ def test_filetype_one_arg(self, completion):
+ assert not completion
diff --git a/test/t/test_xev.py b/test/t/test_xev.py
new file mode 100644
index 0000000..3ced4e0
--- /dev/null
+++ b/test/t/test_xev.py
@@ -0,0 +1,11 @@
+import pytest
+
+
+class TestXev:
+ @pytest.mark.complete("xev ")
+ def test_basic(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("xev -", require_cmd=True)
+ def test_options(self, completion):
+ assert completion
diff --git a/test/t/test_xfreerdp.py b/test/t/test_xfreerdp.py
index 5616271..3dce4ad 100644
--- a/test/t/test_xfreerdp.py
+++ b/test/t/test_xfreerdp.py
@@ -42,7 +42,7 @@ class TestXfreerdp:
@pytest.mark.complete(
"xfreerdp /kbd:",
require_cmd=True,
- skipif='test -z "$(xfreerdp /kbd-list 2>/dev/null)"',
+ skipif='test ! "$(xfreerdp /kbd-list 2>/dev/null)"',
)
def test_4(self, bash, completion, help_success, slash_syntax):
assert completion
@@ -58,3 +58,7 @@ class TestXfreerdp:
@pytest.mark.complete("xfreerdp --help ", require_cmd=True)
def test_7(self, completion):
assert not completion
+
+ @pytest.mark.complete("xfreerdp ./")
+ def test_rdp_files(self, completion):
+ assert completion # just dirs for now in the fixture, but that'll do
diff --git a/test/t/test_xhost.py b/test/t/test_xhost.py
index bb2df82..b704b9c 100644
--- a/test/t/test_xhost.py
+++ b/test/t/test_xhost.py
@@ -8,15 +8,13 @@ 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]
+ assert completion == [f"{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))
+ completion = assert_complete(bash, f"xhost {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
- )
+ assert completion == sorted(f"{prefix}{x}" for x in partial_hosts)
diff --git a/test/t/test_xmlwf.py b/test/t/test_xmlwf.py
index 901f78a..eab9894 100644
--- a/test/t/test_xmlwf.py
+++ b/test/t/test_xmlwf.py
@@ -9,3 +9,7 @@ class TestXmlwf:
@pytest.mark.complete("xmlwf -", require_cmd=True)
def test_2(self, completion):
assert completion
+
+ @pytest.mark.complete("xmlwf -sa ")
+ def test_no_arg_to_a(self, completion):
+ assert not completion
diff --git a/test/t/test_xrandr.py b/test/t/test_xrandr.py
index e766922..8d8a6bc 100644
--- a/test/t/test_xrandr.py
+++ b/test/t/test_xrandr.py
@@ -1,15 +1,122 @@
import pytest
+ENV = dict(PATH="$PWD/xrandr:$PATH")
+OUTPUTS = sorted("DP-0 DP-1 DP-2 DP-3 eDP-1-1 HDMI-0".split())
+
+@pytest.mark.bashcomp(pre_cmds=("PATH=$PATH:$PWD/xrandr",))
class TestXrandr:
@pytest.mark.complete("xrandr ", require_cmd=True)
- def test_1(self, completion):
+ def test_no_args(self, completion):
assert completion
- @pytest.mark.complete("xrandr --mode ")
- def test_2(self, completion):
- assert not completion
-
@pytest.mark.complete("xrandr -", require_cmd=True)
- def test_3(self, completion):
+ def test_single_dash(self, completion):
assert completion
+
+ @pytest.mark.complete("xrandr --output ", env=ENV)
+ def test_output(self, completion):
+ assert completion == OUTPUTS
+
+ @pytest.mark.complete("xrandr --output HDMI-0 --left-of ", env=ENV)
+ def test_output_left_of(self, completion):
+ assert completion == OUTPUTS
+
+ @pytest.mark.complete("xrandr --output HDMI-0 --reflect ", env=ENV)
+ def test_output_reflect(self, completion):
+ assert completion == sorted("normal x y xy".split())
+
+ @pytest.mark.complete("xrandr --reflect ", require_cmd=True)
+ def test_output_reflect_nooutput(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("xrandr --output HDMI-0 --rotate ", env=ENV)
+ def test_output_rotate(self, completion):
+ assert completion == sorted("normal inverted left right".split())
+
+ @pytest.mark.complete("xrandr --rotate ", require_cmd=True)
+ def test_output_rotate_nooutput(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("xrandr --output HDMI-0 --filter ", env=ENV)
+ def test_output_filter(self, completion):
+ assert completion == sorted("bilinear nearest".split())
+
+ @pytest.mark.complete("xrandr --output HDMI-0 --mode ", env=ENV)
+ def test_output_mode(self, completion):
+ assert completion == sorted(
+ "1024x768 1280x1024 1280x800 1600x900 1920x1080 720x480 "
+ "800x600 1152x864 1280x720 1440x900 1680x1050 640x480 720x576".split()
+ )
+
+ @pytest.mark.complete("xrandr --mode ", require_cmd=True)
+ def test_output_mode_nooutput(self, completion):
+ assert not completion
+
+ @pytest.mark.complete("xrandr --addmode ", env=ENV)
+ def test_addmode_first(self, completion):
+ assert completion == OUTPUTS
+
+ @pytest.mark.complete("xrandr --addmode HDMI-0 ", env=ENV)
+ def test_addmode_second(self, completion):
+ assert completion == sorted(
+ "1024x576 1280x800 1440x900 320x200 432x243 640x350 700x450 800x450 928x696 "
+ "1024x768 1280x960 1600x900 320x240 480x270 640x360 700x525 800x600 960x540 "
+ "1024x768i 1368x768 1680x1050 360x200 512x288 640x400 720x400 832x624 960x600 "
+ "1152x864 1400x1050 1920x1080 360x202 512x384 640x480 720x405 840x525 960x720 "
+ "1280x1024 1400x900 320x175 400x300 512x384i 640x512 720x480 864x486 "
+ "1280x720 1440x810 320x180 416x312 576x432 684x384 720x576 896x672".split()
+ )
+
+ @pytest.mark.complete("xrandr --delmode ", env=ENV)
+ def test_delmode_first(self, completion):
+ assert completion == OUTPUTS
+
+ @pytest.mark.complete("xrandr --delmode HDMI-0 ", env=ENV)
+ def test_delmode_second(self, completion):
+ assert completion == sorted(
+ "1024x768 1280x1024 1280x800 1600x900 1920x1080 720x480 "
+ "800x600 1152x864 1280x720 1440x900 1680x1050 640x480 720x576".split()
+ )
+
+ @pytest.mark.complete("xrandr --dpi ", env=ENV)
+ def test_dpi(self, completion):
+ assert completion == OUTPUTS
+
+ @pytest.mark.complete("xrandr -o ", env=ENV)
+ def test_orientation(self, completion):
+ assert completion == sorted(
+ "normal inverted left right 0 1 2 3".split()
+ )
+
+ @pytest.mark.complete("xrandr --setmonitor testmonitor ", env=ENV)
+ def test_setmonitor_second(self, completion):
+ assert completion == sorted("auto".split())
+
+ @pytest.mark.complete("xrandr --setmonitor testmonitor auto ", env=ENV)
+ def test_setmonitor_third(self, completion):
+ assert completion == OUTPUTS + ["none"]
+
+ @pytest.mark.complete("xrandr --delmonitor ", env=ENV)
+ def test_delmonitor(self, completion):
+ assert completion == sorted("eDP-1-1 HDMI-0".split())
+
+ @pytest.mark.complete("xrandr --setprovideroutputsource ", env=ENV)
+ def test_setprovideroutputsource_first(self, completion):
+ assert completion == sorted("modesetting".split())
+
+ @pytest.mark.complete(
+ "xrandr --setprovideroutputsource modesetting ", env=ENV
+ )
+ def test_setprovideroutputsource_second(self, completion):
+ assert completion == sorted("0x0 modesetting NVIDIA-0".split())
+
+ @pytest.mark.complete("xrandr --setprovideroffloadsink ", env=ENV)
+ def test_setprovideroffloadsink_first(self, completion):
+ assert completion == sorted("modesetting".split())
+
+ @pytest.mark.complete(
+ "xrandr --setprovideroffloadsink modesetting ", env=ENV
+ )
+ def test_setprovideroffloadsink_second(self, completion):
+ assert completion == sorted("0x0 modesetting".split())
diff --git a/test/t/test_xz.py b/test/t/test_xz.py
index f226d02..8ecb1a0 100644
--- a/test/t/test_xz.py
+++ b/test/t/test_xz.py
@@ -9,8 +9,9 @@ class TestXz:
@pytest.mark.complete("xz -d xz/")
def test_2(self, completion):
assert (
- completion == "a/ bashcomp.lzma bashcomp.tar.xz "
- "bashcomp.tlz bashcomp.xz".split()
+ completion
+ == "a/ bashcomp.lzma bashcomp.tar.xz bashcomp.tlz "
+ "bashcomp.xz".split()
)
@pytest.mark.complete("xz xz/")
diff --git a/test/t/unit/Makefile.am b/test/t/unit/Makefile.am
index 3eb652a..54722de 100644
--- a/test/t/unit/Makefile.am
+++ b/test/t/unit/Makefile.am
@@ -1,21 +1,38 @@
EXTRA_DIST = \
+ test_unit_abspath.py \
+ test_unit_command_offset.py \
+ test_unit_compgen.py \
+ test_unit_compgen_commands.py \
test_unit_count_args.py \
+ test_unit_delimited.py \
+ test_unit_deprecate_func.py \
+ test_unit_dequote.py \
test_unit_expand.py \
- test_unit_expand_tilde_by_ref.py \
+ test_unit_expand_glob.py \
+ test_unit_expand_tilde.py \
test_unit_filedir.py \
test_unit_find_unique_completion_pair.py \
- test_unit_get_comp_words_by_ref.py \
+ test_unit_get_first_arg.py \
test_unit_get_cword.py \
- test_unit_init_completion.py \
+ test_unit_get_words.py \
+ test_unit_initialize.py \
test_unit_ip_addresses.py \
- test_unit_known_hosts_real.py \
+ test_unit_known_hosts.py \
test_unit_longopt.py \
+ test_unit_looks_like_path.py \
test_unit_parse_help.py \
test_unit_parse_usage.py \
+ test_unit_pgids.py \
+ test_unit_pids.py \
+ test_unit_pnames.py \
test_unit_quote.py \
- test_unit_quote_readline.py \
+ test_unit_quote_compgen.py \
+ test_unit_realcommand.py \
+ test_unit_split.py \
test_unit_tilde.py \
+ test_unit_unlocal.py \
test_unit_variables.py \
+ test_unit_xfunc.py \
test_unit_xinetd_services.py
all:
diff --git a/test/t/unit/test_unit_abspath.py b/test/t/unit/test_unit_abspath.py
new file mode 100644
index 0000000..97d506c
--- /dev/null
+++ b/test/t/unit/test_unit_abspath.py
@@ -0,0 +1,67 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(
+ cmd=None, cwd="shared", ignore_env=r"^\+declare -f __tester$"
+)
+class TestUnitAbsPath:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ (
+ "__tester() { "
+ "local REPLY; "
+ '_comp_abspath "$1"; '
+ 'printf %s "$REPLY"; '
+ "}"
+ ),
+ )
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local REPLY=; _comp_abspath bar; }; foo; unset -f foo",
+ want_output=None,
+ )
+
+ def test_absolute(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester /foo/bar",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip() == "/foo/bar"
+
+ def test_relative(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester foo/bar",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/foo/bar")
+
+ def test_cwd(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester ./foo/./bar",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/foo/bar")
+
+ def test_parent(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester ../shared/foo/bar",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith(
+ "/shared/foo/bar"
+ ) and not output.strip().endswith("../shared/foo/bar")
diff --git a/test/t/unit/test_unit_command_offset.py b/test/t/unit/test_unit_command_offset.py
new file mode 100644
index 0000000..0e32c1f
--- /dev/null
+++ b/test/t/unit/test_unit_command_offset.py
@@ -0,0 +1,144 @@
+from shlex import quote
+
+import pytest
+
+from conftest import assert_bash_exec, assert_complete, bash_env_saved
+
+
+def join(words):
+ """Return a shell-escaped string from *words*."""
+ return " ".join(quote(word) for word in words)
+
+
+@pytest.mark.bashcomp(
+ cmd=None,
+ cwd="_command_offset",
+ ignore_env=r"^[+-](COMPREPLY|REPLY)=",
+)
+class TestUnitCommandOffset:
+ wordlist = sorted(["foo", "bar"])
+
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "_cmd1() { _comp_command_offset 1; }; complete -F _cmd1 cmd1; "
+ "complete -F _comp_command meta; "
+ "_compfunc() { COMPREPLY=(%s); }" % join(self.wordlist),
+ )
+
+ completions = [
+ 'complete -F _compfunc "${COMP_WORDS[0]}"',
+ 'complete -W %s "${COMP_WORDS[0]}"' % quote(join(self.wordlist)),
+ 'COMPREPLY=(dummy); complete -r "${COMP_WORDS[0]}"',
+ "COMPREPLY+=(${#COMPREPLY[@]})",
+ ]
+ for idx, comp in enumerate(completions, 2):
+ assert_bash_exec(
+ bash,
+ "_cmd%(idx)s() { %(comp)s && return 124; }; "
+ "complete -F _cmd%(idx)s cmd%(idx)s"
+ % {"idx": idx, "comp": comp},
+ )
+
+ assert_bash_exec(
+ bash, "complete -W %s 'cmd!'" % quote(join(self.wordlist))
+ )
+ assert_bash_exec(bash, 'complete -W \'"$word1" "$word2"\' cmd6')
+
+ assert_bash_exec(bash, "complete -C ./completer cmd7")
+
+ def test_1(self, bash, functions):
+ assert_complete(bash, 'cmd1 "/tmp/aaa bbb" ')
+ assert_bash_exec(bash, "! complete -p aaa", want_output=None)
+
+ @pytest.mark.parametrize(
+ "cmd,expected_completion",
+ [
+ ("cmd2", wordlist),
+ ("cmd3", wordlist),
+ ("cmd4", []),
+ ("cmd5", ["0"]),
+ ],
+ )
+ def test_2(self, bash, functions, cmd, expected_completion):
+ """Test meta-completion for completion functions that signal that
+ completion should be retried (i.e. change compspec and return 124).
+
+ cmd2: The case when the completion spec is overwritten by the one that
+ contains "-F func"
+
+ cmd3: The case when the completion spec is overwritten by the one
+ without "-F func".
+
+ cmd4: The case when the completion spec is removed, in which we expect
+ no completions. This mimics the behavior of Bash's progcomp for the
+ exit status 124.
+
+ cmd5: The case when the completion spec is unchanged. The retry should
+ be attempted at most once to avoid infinite loops. COMPREPLY should be
+ cleared before the retry.
+ """
+ assert assert_complete(bash, "meta %s " % cmd) == expected_completion
+
+ @pytest.mark.parametrize(
+ "cmd,expected_completion",
+ [
+ ("cmd7 ", wordlist),
+ ("cmd7 l", ["line\\^Jtwo", "long"]),
+ ("cmd7 lo", ["ng"]),
+ ("cmd7 line", ["\\^Jtwo"]),
+ ("cmd7 cont1", ["cont10", "cont11\\"]),
+ ],
+ )
+ def test_3(self, bash, functions, cmd, expected_completion):
+ got = assert_complete(bash, f"cmd1 {cmd}")
+ assert got == assert_complete(bash, cmd)
+ assert got == expected_completion
+
+ def test_cmd_quoted(self, bash, functions):
+ assert assert_complete(bash, "meta 'cmd2' ") == self.wordlist
+
+ def test_cmd_specialchar(self, bash, functions):
+ assert assert_complete(bash, "meta 'cmd!' ") == self.wordlist
+
+ def test_space(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("word1", "a b c")
+ bash_env.write_variable("word2", "d e f")
+ assert assert_complete(bash, "meta cmd6 ") == ["a b c", "d e f"]
+
+ @pytest.fixture(scope="class")
+ def find_original_word_functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "_comp_test_reassemble() {"
+ " local IFS=$' \\t\\n' REPLY;"
+ ' COMP_LINE=$1; _comp_split COMP_WORDS "$2"; COMP_CWORD=$((${#COMP_WORDS[@]}-1));'
+ " _comp__reassemble_words = words cword;"
+ "}",
+ )
+ assert_bash_exec(
+ bash,
+ "_comp_test_1() {"
+ ' local COMP_WORDS COMP_LINE COMP_CWORD words cword REPLY; _comp_test_reassemble "$1" "$2";'
+ ' _comp__find_original_word "$3";'
+ ' echo "$REPLY";'
+ "}",
+ )
+
+ def test_find_original_word_1(self, bash, find_original_word_functions):
+ result = assert_bash_exec(
+ bash,
+ '_comp_test_1 "sudo su do su do abc" "sudo su do su do abc" 3',
+ want_output=True,
+ ).strip()
+ assert result == "3"
+
+ def test_find_original_word_2(self, bash, find_original_word_functions):
+ result = assert_bash_exec(
+ bash,
+ '_comp_test_1 "sudo --prefix=su su do abc" "sudo --prefix = su su do abc" 2',
+ want_output=True,
+ ).strip()
+ assert result == "4"
diff --git a/test/t/unit/test_unit_compgen.py b/test/t/unit/test_unit_compgen.py
new file mode 100644
index 0000000..cfdec8e
--- /dev/null
+++ b/test/t/unit/test_unit_compgen.py
@@ -0,0 +1,173 @@
+import pytest
+import re
+
+from conftest import assert_bash_exec, bash_env_saved, assert_complete
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUtilCompgen:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "_comp__test_dump() { ((${#arr[@]})) && printf '<%s>' \"${arr[@]}\"; echo; }",
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_compgen() { local -a arr=(00); _comp_compgen -v arr "$@"; _comp__test_dump; }',
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_words() { local -a input=("${@:1:$#-1}"); _comp__test_compgen -c "${@:$#}" -- -W \'${input[@]+"${input[@]}"}\'; }',
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_words_ifs() { local input=$2; _comp__test_compgen -F "$1" -c "${@:$#}" -- -W \'$input\'; }',
+ )
+
+ assert_bash_exec(
+ bash,
+ '_comp_cmd_fc() { _comp_compgen -c "$(_get_cword)" -C _filedir filedir; }; '
+ "complete -F _comp_cmd_fc fc; "
+ "complete -F _comp_cmd_fc -o filenames fc2",
+ )
+ assert_bash_exec(
+ bash,
+ '_comp_cmd_fcd() { _comp_compgen -c "$(_get_cword)" -C _filedir filedir -d; }; '
+ "complete -F _comp_cmd_fcd fcd",
+ )
+
+ # test_8_option_U
+ assert_bash_exec(
+ bash,
+ "_comp_compgen_gen8() { local -a arr=(x y z); _comp_compgen -U arr -- -W '\"${arr[@]}\"'; }",
+ )
+
+ # test_9_inherit_a
+ assert_bash_exec(
+ bash,
+ '_comp_compgen_gen9sub() { local -a gen=(00); _comp_compgen -v gen -- -W 11; _comp_compgen_set "${gen[@]}"; }; '
+ "_comp_compgen_gen9() { _comp_compgen_gen9sub; _comp_compgen -a gen9sub; }",
+ )
+
+ def test_1_basic(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp__test_words 12 34 56 ''", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_2_space(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words $'a b' $'c d\\t' ' e ' $'\\tf\\t' ''",
+ want_output=True,
+ )
+ assert output.strip() == "<a b><c d\t>< e ><\tf\t>"
+
+ def test_2_IFS(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("IFS", "34")
+ output = assert_bash_exec(
+ bash, "_comp__test_words 12 34 56 ''", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_3_glob(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words '*' '[a-z]*' '[a][b][c]' ''",
+ want_output=True,
+ )
+ assert output.strip() == "<*><[a-z]*><[a][b][c]>"
+
+ def test_3_failglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words '*' '[a-z]*' '[a][b][c]' ''",
+ want_output=True,
+ )
+ assert output.strip() == "<*><[a-z]*><[a][b][c]>"
+
+ def test_3_nullglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words '*' '[a-z]*' '[a][b][c]' ''",
+ want_output=True,
+ )
+ assert output.strip() == "<*><[a-z]*><[a][b][c]>"
+
+ def test_4_empty(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp__test_words ''", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_5_option_F(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_words_ifs '25' ' 123 456 555 ' ''",
+ want_output=True,
+ )
+ assert output.strip() == "< 1><3 4><6 >< >"
+
+ def test_6_option_C_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_compgen -c a -C _filedir filedir",
+ want_output=True,
+ )
+ set1 = set(re.findall(r"<[^<>]*>", output.strip()))
+ assert set1 == {"<a b>", "<a$b>", "<a&b>", "<a'b>", "<ab>", "<aé>"}
+
+ def test_6_option_C_2(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_compgen -c b -C _filedir -- -d",
+ want_output=True,
+ )
+ assert output.strip() == "<brackets>"
+
+ @pytest.mark.parametrize("funcname", "fc fc2".split())
+ def test_6_option_C_3(self, bash, functions, funcname):
+ completion = assert_complete(bash, "%s _filedir ab/" % funcname)
+ assert completion == "e"
+
+ @pytest.mark.complete(r"fcd a\ ")
+ def test_6_option_C_4(self, functions, completion):
+ # Note: we are not in the original directory that "b" exists, so Bash
+ # will not suffix a slash to the directory name.
+ assert completion == "b"
+
+ def test_7_icmd(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR", "$PWD/_comp_compgen", quote=False
+ )
+
+ completions = assert_complete(bash, "compgen-cmd1 '")
+ assert completions == ["012", "123", "234", "5abc", "6def", "7ghi"]
+
+ def test_7_xcmd(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR", "$PWD/_comp_compgen", quote=False
+ )
+
+ completions = assert_complete(bash, "compgen-cmd2 '")
+ assert completions == ["012", "123", "234", "5foo", "6bar", "7baz"]
+
+ def test_8_option_U(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp__test_compgen gen8", want_output=True
+ )
+ assert output.strip() == "<x><y><z>"
+
+ def test_9_inherit_a(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp__test_compgen gen9", want_output=True
+ )
+ assert output.strip() == "<11><11>"
diff --git a/test/t/unit/test_unit_compgen_commands.py b/test/t/unit/test_unit_compgen_commands.py
new file mode 100644
index 0000000..d866239
--- /dev/null
+++ b/test/t/unit/test_unit_compgen_commands.py
@@ -0,0 +1,47 @@
+import pytest
+
+from conftest import assert_bash_exec, assert_complete, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
+class TestUtilCompgenCommands:
+ @pytest.fixture(scope="class")
+ def functions(self, request, bash):
+ assert_bash_exec(
+ bash,
+ r"_comp_compgen_commands__test() {"
+ r" local COMPREPLY=() cur=${1-};"
+ r" _comp_compgen_commands;"
+ r' printf "%s\n" "${COMPREPLY[@]-}";'
+ r"}",
+ )
+ assert_bash_exec(
+ bash,
+ "_comp_cmd_ccc() {"
+ " local cur;"
+ " _comp_get_words cur;"
+ " unset -v COMPREPLY;"
+ " _comp_compgen_commands;"
+ "}; complete -F _comp_cmd_ccc ccc",
+ )
+
+ def test_basic(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "_comp_compgen_commands__test sh", want_output=True
+ )
+ assert output.strip()
+
+ @pytest.mark.parametrize(
+ "shopt_no_empty,result_empty", ((True, True), (False, False))
+ )
+ def test_empty(self, bash, functions, shopt_no_empty, result_empty):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("no_empty_cmd_completion", shopt_no_empty)
+ output = assert_bash_exec(
+ bash, "_comp_compgen_commands__test", want_output=True
+ )
+ assert (output.strip() == "") == result_empty
+
+ def test_spaces(self, bash, functions):
+ completion = assert_complete(bash, "ccc shared/default/bar")
+ assert completion == r"\ bar.d/"
diff --git a/test/t/unit/test_unit_compgen_split.py b/test/t/unit/test_unit_compgen_split.py
new file mode 100644
index 0000000..935ea2a
--- /dev/null
+++ b/test/t/unit/test_unit_compgen_split.py
@@ -0,0 +1,102 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUtilCompgenSplit:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "_comp__test_dump() { ((${#arr[@]})) && printf '<%s>' \"${arr[@]}\"; echo; }",
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_compgen() { local -a arr=(00); _comp_compgen -v arr "$@"; _comp__test_dump; }',
+ )
+
+ assert_bash_exec(
+ bash,
+ "_comp__test_cmd1() { echo foo bar; echo baz; }",
+ )
+ assert_bash_exec(
+ bash,
+ '_comp__test_attack() { echo "\\$(echo should_not_run >&2)"; }',
+ )
+
+ def test_1_basic(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<foo><bar><baz>"
+
+ def test_2_attack(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -- "$(_comp__test_attack)"',
+ want_output=True,
+ )
+ assert output.strip() == "<$(echo><should_not_run><>&2)>"
+
+ def test_3_sep1(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -l -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<foo bar><baz>"
+
+ def test_3_sep2(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "_comp__test_compgen split -F $'b\\n' -- \"$(_comp__test_cmd1)\"",
+ want_output=True,
+ )
+ assert output.strip() == "<foo ><ar><az>"
+
+ def test_4_optionX(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -X bar -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<foo><baz>"
+
+ def test_4_optionS(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -S .txt -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<foo.txt><bar.txt><baz.txt>"
+
+ def test_4_optionP(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -P /tmp/ -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "</tmp/foo></tmp/bar></tmp/baz>"
+
+ def test_4_optionPS(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '_comp__test_compgen split -P [ -S ] -- "$(_comp__test_cmd1)"',
+ want_output=True,
+ )
+ assert output.strip() == "<[foo]><[bar]><[baz]>"
+
+ def test_5_empty(self, bash, functions):
+ output = assert_bash_exec(
+ bash, '_comp__test_compgen split -- ""', want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_5_empty2(self, bash, functions):
+ output = assert_bash_exec(
+ bash, '_comp__test_compgen split -- " "', want_output=True
+ )
+ assert output.strip() == ""
diff --git a/test/t/unit/test_unit_count_args.py b/test/t/unit/test_unit_count_args.py
index 56bce2c..7b018e4 100644
--- a/test/t/unit/test_unit_count_args.py
+++ b/test/t/unit/test_unit_count_args.py
@@ -4,63 +4,167 @@ from conftest import TestUnitBase, assert_bash_exec
@pytest.mark.bashcomp(
- cmd=None, ignore_env=r"^[+-](args|COMP_(WORDS|CWORD|LINE|POINT))="
+ cmd=None,
+ ignore_env=r"^[+-](REPLY|cword|words|COMP_(WORDS|CWORD|LINE|POINT))=",
)
class TestUnitCountArgs(TestUnitBase):
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ '_comp__test_unit() { local -a words=(); local cword REPLY=""; _comp__reassemble_words "<>&" words cword; _comp_count_args "$@"; echo "$REPLY"; }',
+ )
+
def _test(self, *args, **kwargs):
- return self._test_unit("_count_args %s; echo $args", *args, **kwargs)
+ return self._test_unit("_comp__test_unit %s", *args, **kwargs)
def test_1(self, bash):
- assert_bash_exec(bash, "COMP_CWORD= _count_args >/dev/null")
+ assert_bash_exec(
+ bash,
+ 'COMP_LINE= COMP_POINT=0 COMP_WORDS=() COMP_CWORD=; _comp_count_args -n ""',
+ )
- def test_2(self, bash):
+ def test_2(self, bash, functions):
"""a b| should set args to 1"""
output = self._test(bash, "(a b)", 1, "a b", 3)
assert output == "1"
- def test_3(self, bash):
+ def test_3(self, bash, functions):
"""a b|c should set args to 1"""
output = self._test(bash, "(a bc)", 1, "a bc", 3)
assert output == "1"
- def test_4(self, bash):
+ def test_4(self, bash, functions):
"""a b c| should set args to 2"""
output = self._test(bash, "(a b c)", 2, "a b c", 4)
assert output == "2"
- def test_5(self, bash):
+ def test_5(self, bash, functions):
"""a b| c should set args to 1"""
output = self._test(bash, "(a b c)", 1, "a b c", 3)
assert output == "1"
- def test_6(self, bash):
+ def test_6(self, bash, functions):
"""a b -c| d should set args to 2"""
output = self._test(bash, "(a b -c d)", 2, "a b -c d", 6)
assert output == "2"
- def test_7(self, bash):
+ def test_7(self, bash, functions):
"""a b -c d e| with -c arg excluded should set args to 2"""
output = self._test(
- bash, "(a b -c d e)", 4, "a b -c d e", 10, arg='"" "@(-c|--foo)"'
+ bash, "(a b -c d e)", 4, "a b -c d e", 10, arg='-a "@(-c|--foo)"'
)
assert output == "2"
- def test_8(self, bash):
+ def test_8(self, bash, functions):
"""a -b -c d e| with -c arg excluded
- and -b included should set args to 1"""
+ and -b included should set args to 1"""
output = self._test(
bash,
"(a -b -c d e)",
4,
"a -b -c d e",
11,
- arg='"" "@(-c|--foo)" "-[b]"',
+ arg='-a "@(-c|--foo)" -i "-[b]"',
)
assert output == "2"
- def test_9(self, bash):
+ def test_9(self, bash, functions):
"""a -b -c d e| with -b included should set args to 3"""
output = self._test(
- bash, "(a -b -c d e)", 4, "a -b -c d e", 11, arg='"" "" "-b"'
+ bash, "(a -b -c d e)", 4, "a -b -c d e", 11, arg='-i "-b"'
+ )
+ assert output == "3"
+
+ def test_10_single_hyphen_1(self, bash):
+ """- should be counted as an argument representing stdout/stdin"""
+ output = self._test(bash, "(a -b - c -d e)", 5, "a -b - c -d e", 12)
+ assert output == "3"
+
+ def test_10_single_hyphen_2(self, bash):
+ """- in an option argument should be skipped"""
+ output = self._test(
+ bash, "(a -b - c - e)", 5, "a -b - c - e", 11, arg='-a "-b"'
+ )
+ assert output == "3"
+
+ def test_11_double_hyphen_1(self, bash):
+ """all the words after -- should be counted"""
+ output = self._test(
+ bash, "(a -b -- -c -d e)", 5, "a -b -- -c -d e", 14
)
assert output == "3"
+
+ def test_11_double_hyphen_2(self, bash):
+ """all the words after -- should be counted"""
+ output = self._test(bash, "(a b -- -c -d e)", 5, "a b -- -c -d e", 13)
+ assert output == "4"
+
+ def test_12_exclude_optarg_1(self, bash):
+ """an option argument should be skipped even if it matches the argument pattern"""
+ output = self._test(
+ bash, "(a -o -x b c)", 4, "a -o -x b c", 10, arg='-a "-o" -i "-x"'
+ )
+ assert output == "2"
+
+ def test_12_exclude_optarg_2(self, bash):
+ """an option argument should be skipped even if it matches the argument pattern"""
+ output = self._test(
+ bash,
+ "(a -o -x -x c)",
+ 4,
+ "a -o -x -x c",
+ 11,
+ arg='-a "-o" -i "-x"',
+ )
+ assert output == "2"
+
+ def test_12_exclude_optarg_3(self, bash):
+ """an option argument should be skipped even if it matches the argument pattern"""
+ output = self._test(
+ bash,
+ "(a -o -x -y c)",
+ 4,
+ "a -o -x -y c",
+ 11,
+ arg='-a "-o" -i "-x"',
+ )
+ assert output == "1"
+
+ def test_13_plus_option_optarg(self, bash):
+ """When +o is specified to be an option taking an option argument, it should not be counted as an argument"""
+ output = self._test(
+ bash, "(a +o b c)", 3, "a +o b c", 7, arg='-a "+o"'
+ )
+ assert output == "1"
+
+ def test_14_no_optarg_chain_1(self, bash):
+ """an option argument should not take another option argument"""
+ output = self._test(
+ bash, "(a -o -o -o -o c)", 5, "a -o -o -o -o c", 14, arg='-a "-o"'
+ )
+ assert output == "1"
+
+ def test_14_no_optarg_chain_2(self, bash):
+ """an option argument should not take another option argument"""
+ output = self._test(
+ bash,
+ "(a -o -o b -o -o c)",
+ 6,
+ "a -o -o b -o -o c",
+ 16,
+ arg='-a "-o"',
+ )
+ assert output == "2"
+
+ def test_15_double_hyphen_optarg(self, bash):
+ """-- should lose its meaning when it is an option argument"""
+ output = self._test(
+ bash, "(a -o -- -b -c d)", 5, "a -o -- -b -c d", 14, arg='-a "-o"'
+ )
+ assert output == "1"
+
+ def test_16_empty_word(self, bash):
+ """An empty word should not take an option argument"""
+ output = self._test(bash, "(a '' x '' y d)", 5, "a x y d", 8)
+ assert output == "5"
diff --git a/test/t/unit/test_unit_delimited.py b/test/t/unit/test_unit_delimited.py
new file mode 100644
index 0000000..e20dcd1
--- /dev/null
+++ b/test/t/unit/test_unit_delimited.py
@@ -0,0 +1,42 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitDelimited:
+ @pytest.fixture(scope="class")
+ def functions(self, request, bash):
+ assert_bash_exec(
+ bash,
+ "_comp_cmd_test_delim() {"
+ " local cur prev words cword comp_args;"
+ " _comp_get_words cur;"
+ " _comp_delimited , -W 'alpha beta bravo';"
+ "};"
+ "complete -F _comp_cmd_test_delim test_delim",
+ )
+
+ @pytest.mark.complete("test_delim --opt=a")
+ def test_1(self, functions, completion):
+ assert completion == ["lpha"]
+
+ @pytest.mark.complete("test_delim --opt=b")
+ def test_2(self, functions, completion):
+ assert completion == ["beta", "bravo"]
+
+ @pytest.mark.complete("test_delim --opt=alpha,b")
+ def test_3(self, functions, completion):
+ assert completion == ["alpha,beta", "alpha,bravo"]
+
+ @pytest.mark.complete("test_delim --opt=alpha,be")
+ def test_4(self, functions, completion):
+ assert completion == ["ta"]
+
+ @pytest.mark.complete("test_delim --opt=beta,a")
+ def test_5(self, functions, completion):
+ assert completion == ["lpha"]
+
+ @pytest.mark.complete("test_delim --opt=c")
+ def test_6(self, functions, completion):
+ assert not completion
diff --git a/test/t/unit/test_unit_deprecate_func.py b/test/t/unit/test_unit_deprecate_func.py
new file mode 100644
index 0000000..c825e8c
--- /dev/null
+++ b/test/t/unit/test_unit_deprecate_func.py
@@ -0,0 +1,15 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+declare -f func[12]$")
+class TestUnitDeprecateFunc:
+ def test_1(self, bash):
+ assert_bash_exec(
+ bash,
+ 'func1() { echo "func1($*)"; }; '
+ "_comp_deprecate_func 2.12 func2 func1",
+ )
+ output = assert_bash_exec(bash, "func2 1 2 3", want_output=True)
+ assert output.strip() == "func1(1 2 3)"
diff --git a/test/t/unit/test_unit_dequote.py b/test/t/unit/test_unit_dequote.py
new file mode 100644
index 0000000..0ac814d
--- /dev/null
+++ b/test/t/unit/test_unit_dequote.py
@@ -0,0 +1,161 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(
+ cmd=None,
+ cwd="_filedir",
+ ignore_env=r"^\+declare -f __tester$",
+)
+class TestDequote:
+ def test_1_char(self, bash):
+ assert_bash_exec(
+ bash,
+ '__tester() { local REPLY=dummy v=var;_comp_dequote "$1";local ext=$?;((${#REPLY[@]}))&&printf \'<%s>\' "${REPLY[@]}";echo;return $ext;}',
+ )
+ output = assert_bash_exec(bash, "__tester a", want_output=True)
+ assert output.strip() == "<a>"
+
+ def test_2_str(self, bash):
+ output = assert_bash_exec(bash, "__tester abc", want_output=True)
+ assert output.strip() == "<abc>"
+
+ def test_3_null(self, bash):
+ output = assert_bash_exec(bash, "__tester ''", want_output=True)
+ assert output.strip() == ""
+
+ def test_4_empty(self, bash):
+ output = assert_bash_exec(bash, "__tester \"''\"", want_output=True)
+ assert output.strip() == "<>"
+
+ def test_5_brace(self, bash):
+ output = assert_bash_exec(bash, "__tester 'a{1..3}'", want_output=True)
+ assert output.strip() == "<a1><a2><a3>"
+
+ def test_6_glob(self, bash):
+ output = assert_bash_exec(bash, "__tester 'a?b'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b>"
+
+ def test_7_quote_1(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester '\"a\"'\\'b\\'\\$\\'c\\'", want_output=True
+ )
+ assert output.strip() == "<abc>"
+
+ def test_7_quote_2(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester '\\\"\\'\\''\\$\\`'", want_output=True
+ )
+ assert output.strip() == "<\"'$`>"
+
+ def test_7_quote_3(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester \\$\\'a\\\\tb\\'", want_output=True
+ )
+ assert output.strip() == "<a\tb>"
+
+ def test_7_quote_4(self, bash):
+ output = assert_bash_exec(
+ bash, '__tester \'"abc\\"def"\'', want_output=True
+ )
+ assert output.strip() == '<abc"def>'
+
+ def test_7_quote_5(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester \\'abc\\'\\\\\\'\\'def\\'", want_output=True
+ )
+ assert output.strip() == "<abc'def>"
+
+ def test_8_param_1(self, bash):
+ output = assert_bash_exec(bash, "__tester '$v'", want_output=True)
+ assert output.strip() == "<var>"
+
+ def test_8_param_2(self, bash):
+ output = assert_bash_exec(bash, "__tester '${v}'", want_output=True)
+ assert output.strip() == "<var>"
+
+ def test_8_param_3(self, bash):
+ output = assert_bash_exec(bash, "__tester '${#v}'", want_output=True)
+ assert output.strip() == "<3>"
+
+ def test_8_param_4(self, bash):
+ output = assert_bash_exec(bash, "__tester '${v[0]}'", want_output=True)
+ assert output.strip() == "<var>"
+
+ def test_9_qparam_1(self, bash):
+ output = assert_bash_exec(bash, "__tester '\"$v\"'", want_output=True)
+ assert output.strip() == "<var>"
+
+ def test_9_qparam_2(self, bash):
+ output = assert_bash_exec(
+ bash, "__tester '\"${v[@]}\"'", want_output=True
+ )
+ assert output.strip() == "<var>"
+
+ def test_10_pparam_1(self, bash):
+ output = assert_bash_exec(bash, "__tester '$?'", want_output=True)
+ assert output.strip() == "<0>"
+
+ def test_10_pparam_2(self, bash):
+ output = assert_bash_exec(bash, "__tester '${#1}'", want_output=True)
+ assert output.strip() == "<5>" # The string `${#1}` is five characters
+
+ def test_unsafe_1(self, bash):
+ output = assert_bash_exec(
+ bash, "! __tester '$(echo hello >&2)'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_unsafe_2(self, bash):
+ output = assert_bash_exec(
+ bash, "! __tester '|echo hello >&2'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_unsafe_3(self, bash):
+ output = assert_bash_exec(
+ bash, "! __tester '>| important_file.txt'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_unsafe_4(self, bash):
+ output = assert_bash_exec(
+ bash, "! __tester '`echo hello >&2`'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_glob_default(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", False)
+ bash_env.shopt("nullglob", False)
+ output = assert_bash_exec(
+ bash, "__tester 'non-existent-*.txt'", want_output=True
+ )
+ assert output.strip() == "<non-existent-*.txt>"
+
+ def test_glob_noglob(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.set("noglob", True)
+ output = assert_bash_exec(
+ bash,
+ "__tester 'non-existent-*.txt'",
+ want_output=True,
+ )
+ assert output.strip() == "<non-existent-*.txt>"
+
+ def test_glob_failglob(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "! __tester 'non-existent-*.txt'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_glob_nullglob(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash, "__tester 'non-existent-*.txt'", want_output=True
+ )
+ assert output.strip() == ""
diff --git a/test/t/unit/test_unit_expand.py b/test/t/unit/test_unit_expand.py
index d2a3ebc..0be6c52 100644
--- a/test/t/unit/test_unit_expand.py
+++ b/test/t/unit/test_unit_expand.py
@@ -1,31 +1,42 @@
import pytest
-from conftest import assert_bash_exec
+from conftest import assert_bash_exec, bash_env_saved
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^[+-](cur|COMPREPLY)=")
class TestUnitExpand:
def test_1(self, bash):
- assert_bash_exec(bash, "_expand >/dev/null")
+ assert_bash_exec(bash, "_comp_expand >/dev/null")
def test_2(self, bash):
"""Test environment non-pollution, detected at teardown."""
- assert_bash_exec(bash, "foo() { _expand; }; foo; unset foo")
+ assert_bash_exec(bash, "foo() { _comp_expand; }; foo; unset -f foo")
def test_user_home_compreply(self, bash, user_home):
user, home = user_home
output = assert_bash_exec(
bash,
- r'cur="~%s"; _expand; printf "%%s\n" "$COMPREPLY"' % user,
+ r'cur="~%s"; _comp_expand; printf "%%s\n" "$COMPREPLY"' % user,
want_output=True,
)
assert output.strip() == home
+ def test_user_home_compreply_failglob(self, bash, user_home):
+ user, home = user_home
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash,
+ r'cur="~%s"; _comp_expand; printf "%%s\n" "$COMPREPLY"' % user,
+ want_output=True,
+ )
+ assert output.strip() == home
+
def test_user_home_cur(self, bash, user_home):
user, home = user_home
output = assert_bash_exec(
bash,
- r'cur="~%s/a"; _expand; printf "%%s\n" "$cur"' % user,
+ r'cur="~%s/a"; _comp_expand; printf "%%s\n" "$cur"' % user,
want_output=True,
)
assert output.strip() == "%s/a" % home
diff --git a/test/t/unit/test_unit_expand_glob.py b/test/t/unit/test_unit_expand_glob.py
new file mode 100644
index 0000000..64d04a7
--- /dev/null
+++ b/test/t/unit/test_unit_expand_glob.py
@@ -0,0 +1,83 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(
+ cmd=None,
+ cwd="_filedir",
+ ignore_env=r"^\+declare -f (dump_array|__tester)$",
+)
+class TestExpandGlob:
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ "dump_array() { ((${#arr[@]})) && printf '<%s>' \"${arr[@]}\"; echo; }",
+ )
+ assert_bash_exec(
+ bash,
+ '__tester() { local LC_ALL= LC_COLLATE=C arr; _comp_expand_glob arr "$@";dump_array; }',
+ )
+
+ def test_match_all(self, bash, functions):
+ output = assert_bash_exec(bash, "__tester '*'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b><ab><aé><brackets><ext>"
+
+ def test_match_pattern(self, bash, functions):
+ output = assert_bash_exec(bash, "__tester 'a*'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b><ab><aé>"
+
+ def test_match_unmatched(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester 'unmatched-*'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_match_multiple_words(self, bash, functions):
+ output = assert_bash_exec(bash, "__tester 'b* e*'", want_output=True)
+ assert output.strip() == "<brackets><ext>"
+
+ def test_match_brace_expansion(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester 'brac{ket,unmatched}*'", want_output=True
+ )
+ assert output.strip() == "<brackets>"
+
+ def test_protect_from_noglob(self, bash, functions):
+ with bash_env_saved(bash, functions) as bash_env:
+ bash_env.set("noglob", True)
+ output = assert_bash_exec(bash, "__tester 'a*'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b><ab><aé>"
+
+ def test_protect_from_failglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "__tester 'unmatched-*'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_protect_from_nullglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", False)
+ output = assert_bash_exec(
+ bash, "__tester 'unmatched-*'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_protect_from_dotglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("dotglob", True)
+ output = assert_bash_exec(
+ bash, "__tester 'ext/foo/*'", want_output=True
+ )
+ assert output.strip() == ""
+
+ def test_protect_from_GLOBIGNORE(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ # Note: dotglob is changed by GLOBIGNORE
+ bash_env.save_shopt("dotglob")
+ bash_env.write_variable("GLOBIGNORE", "*")
+ output = assert_bash_exec(bash, "__tester 'a*'", want_output=True)
+ assert output.strip() == "<a b><a$b><a&b><a'b><ab><aé>"
diff --git a/test/t/unit/test_unit_expand_tilde_by_ref.py b/test/t/unit/test_unit_expand_tilde.py
index 17bdedf..3552fd6 100644
--- a/test/t/unit/test_unit_expand_tilde_by_ref.py
+++ b/test/t/unit/test_unit_expand_tilde.py
@@ -3,16 +3,27 @@ import pytest
from conftest import assert_bash_exec
-@pytest.mark.bashcomp(cmd=None, ignore_env=r"^[+-]var=")
-class TestUnitExpandTildeByRef:
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitExpandTilde:
def test_1(self, bash):
+ """The old interface `__expand_tilde_by_ref` should not fail when it is
+ called without arguments"""
assert_bash_exec(bash, "__expand_tilde_by_ref >/dev/null")
def test_2(self, bash):
"""Test environment non-pollution, detected at teardown."""
assert_bash_exec(
bash,
- '_x() { local aa="~"; __expand_tilde_by_ref aa; }; _x; unset _x',
+ '_x() { local REPLY; _comp_expand_tilde "~"; }; _x; unset -f _x',
+ )
+
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ # $HOME tinkering: protect against $HOME != ~user; our "home" is the
+ # latter but plain_tilde follows $HOME
+ assert_bash_exec(
+ bash,
+ '_comp__test_unit() { local REPLY HOME=$1; _comp_expand_tilde "$2"; printf "%s\\n" "$REPLY"; }',
)
@pytest.mark.parametrize("plain_tilde", (True, False))
@@ -28,19 +39,21 @@ class TestUnitExpandTildeByRef:
("/a;echo hello", True),
),
)
- def test_expand(self, bash, user_home, plain_tilde, suffix_expanded):
+ def test_expand(
+ self, bash, user_home, plain_tilde, suffix_expanded, functions
+ ):
user, home = user_home
suffix, expanded = suffix_expanded
+ home2 = home
if plain_tilde:
user = ""
if not suffix or not expanded:
- home = "~"
+ home2 = "~"
elif not expanded:
- home = "~%s" % user
+ home2 = "~%s" % user
output = assert_bash_exec(
bash,
- r'var="~%s%s"; __expand_tilde_by_ref var; printf "%%s\n" "$var"'
- % (user, suffix),
+ r'_comp__test_unit "%s" "~%s%s"' % (home, user, suffix),
want_output=True,
)
- assert output.strip() == "%s%s" % (home, suffix.replace(r"\$", "$"),)
+ assert output.strip() == "%s%s" % (home2, suffix.replace(r"\$", "$"))
diff --git a/test/t/unit/test_unit_filedir.py b/test/t/unit/test_unit_filedir.py
index b847efc..07ae2f3 100644
--- a/test/t/unit/test_unit_filedir.py
+++ b/test/t/unit/test_unit_filedir.py
@@ -15,18 +15,18 @@ class TestUnitFiledir:
def functions(self, request, bash):
assert_bash_exec(
bash,
- "_f() { local cur=$(_get_cword); unset COMPREPLY; _filedir; }; "
+ "_f() { local cur;_comp_get_words cur; unset -v COMPREPLY; _comp_compgen_filedir; }; "
"complete -F _f f; "
"complete -F _f -o filenames f2",
)
assert_bash_exec(
bash,
- "_g() { local cur=$(_get_cword); unset COMPREPLY; _filedir e1; }; "
+ "_g() { local cur;_comp_get_words cur; unset -v COMPREPLY; _comp_compgen_filedir e1; }; "
"complete -F _g g",
)
assert_bash_exec(
bash,
- "_fd() { local cur=$(_get_cword); unset COMPREPLY; _filedir -d; };"
+ "_fd() { local cur;_comp_get_words cur; unset -v COMPREPLY; _comp_compgen_filedir -d; };"
"complete -F _fd fd",
)
@@ -59,7 +59,7 @@ class TestUnitFiledir:
return lc_ctype
def test_1(self, bash):
- assert_bash_exec(bash, "_filedir >/dev/null")
+ assert_bash_exec(bash, "_comp_compgen_filedir >/dev/null")
@pytest.mark.parametrize("funcname", "f f2".split())
def test_2(self, bash, functions, funcname):
@@ -196,7 +196,7 @@ class TestUnitFiledir:
@pytest.mark.parametrize("funcname", "f f2".split())
def test_22(self, bash, functions, funcname, non_windows_testdir):
completion = assert_complete(
- bash, r"%s '%s/a\b/" % (funcname, non_windows_testdir)
+ bash, rf"{funcname} '{non_windows_testdir}/a\b/"
)
assert completion == "g'"
diff --git a/test/t/unit/test_unit_get_cword.py b/test/t/unit/test_unit_get_cword.py
index 0b56d16..d2bb526 100644
--- a/test/t/unit/test_unit_get_cword.py
+++ b/test/t/unit/test_unit_get_cword.py
@@ -1,11 +1,12 @@
-import pexpect
+import pexpect # type: ignore[import]
import pytest
from conftest import PS1, TestUnitBase, assert_bash_exec
@pytest.mark.bashcomp(
- cmd=None, ignore_env=r"^[+-](COMP_(WORDS|CWORD|LINE|POINT)|_scp_path_esc)="
+ cmd=None,
+ ignore_env=r"^[+-](COMP_(WORDS|CWORD|LINE|POINT)|_comp_cmd_scp__path_esc)=",
)
class TestUnitGetCword(TestUnitBase):
def _test(self, *args, **kwargs):
@@ -49,12 +50,12 @@ class TestUnitGetCword(TestUnitBase):
assert output == r"b\ c"
def test_8(self, bash):
- r"""a b\| c should return b\ """
+ r"""a b\| c should return b\ """ # fmt: skip
output = self._test(bash, r"(a 'b\ c')", 1, r"a b\ c", 4)
assert output == "b\\"
def test_9(self, bash):
- r"""a "b\| should return "b\ """
+ r"""a "b\| should return "b\ """ # fmt: skip
output = self._test(bash, "(a '\"b\\')", 1, r"a \"b\\", 5)
assert output == '"b\\'
@@ -103,7 +104,7 @@ class TestUnitGetCword(TestUnitBase):
a -n| should return -n
This test makes sure `_get_cword' doesn't use `echo' to return its
- value, because -n might be interpreted by `echo' and thus woud not
+ value, because -n might be interpreted by `echo' and thus would not
be returned.
"""
output = self._test(bash, "(a -n)", 1, "a -n", 4)
@@ -152,3 +153,12 @@ class TestUnitGetCword(TestUnitBase):
]
)
assert got == 1
+
+ def test_25(self, bash):
+ """
+ a b c:| with trailing whitespace after the caret (no more words) and
+ with WORDBREAKS -= : should return c:
+ """
+ assert_bash_exec(bash, "add_comp_wordbreak_char :")
+ output = self._test(bash, "(a b c :)", 3, "a b c: ", 6, arg=":")
+ assert output == "c:"
diff --git a/test/t/unit/test_unit_get_first_arg.py b/test/t/unit/test_unit_get_first_arg.py
new file mode 100644
index 0000000..415e217
--- /dev/null
+++ b/test/t/unit/test_unit_get_first_arg.py
@@ -0,0 +1,90 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitGetFirstArg:
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ '_comp__test_unit() { local -a "words=$1"; local cword=$2 REPLY=; shift 2; _comp_get_first_arg "$@" && printf "%s\\n" "$REPLY"; return 0; }',
+ )
+
+ def _test(self, bash, words, cword, args=""):
+ return assert_bash_exec(
+ bash,
+ '_comp__test_unit "%s" %d %s' % (words, cword, args),
+ want_output=None,
+ ).strip()
+
+ def test_1(self, bash, functions):
+ assert_bash_exec(bash, "_comp__test_unit '()' 0")
+
+ def test_2(self, bash, functions):
+ output = self._test(bash, "(a b)", 2)
+ assert output == "b"
+
+ def test_3(self, bash, functions):
+ output = self._test(bash, "(a bc)", 2)
+ assert output == "bc"
+
+ def test_4(self, bash, functions):
+ output = self._test(bash, "(a b c)", 2)
+ assert output == "b"
+
+ def test_5(self, bash, functions):
+ """Neither of the current word and the command name should be picked
+ as the first argument"""
+ output = self._test(bash, "(a b c)", 1)
+ assert output == ""
+
+ def test_6(self, bash, functions):
+ """Options starting with - should not be picked as the first
+ argument"""
+ output = self._test(bash, "(a -b -c d e)", 4)
+ assert output == "d"
+
+ def test_7_single_hyphen(self, bash, functions):
+ """- should be counted as an argument representing stdout/stdin"""
+ output = self._test(bash, "(a -b - c -d e)", 5)
+ assert output == "-"
+
+ def test_8_double_hyphen_1(self, bash, functions):
+ """any word after -- should be picked"""
+ output = self._test(bash, "(a -b -- -c -d e)", 5)
+ assert output == "-c"
+
+ def test_8_double_hyphen_2(self, bash, functions):
+ """any word after -- should be picked only without any preceding argument"""
+ output = self._test(bash, "(a b -- -c -d e)", 5)
+ assert output == "b"
+
+ def test_9_skip_optarg_1(self, bash, functions):
+ output = self._test(bash, "(a -b -c d e f)", 5, '-a "@(-c|--foo)"')
+ assert output == "e"
+
+ def test_9_skip_optarg_2(self, bash, functions):
+ output = self._test(bash, "(a -b --foo d e f)", 5, '-a "@(-c|--foo)"')
+ assert output == "e"
+
+ def test_9_skip_optarg_3(self, bash):
+ output = self._test(bash, "(a -b - c d e)", 5, '-a "-b"')
+ assert output == "c"
+
+ def test_9_skip_optarg_4(self, bash):
+ output = self._test(bash, "(a -b -c d e f)", 5, '-a "-[bc]"')
+ assert output == "d"
+
+ def test_9_skip_optarg_5(self, bash):
+ output = self._test(bash, "(a +o b c d)", 4, '-a "+o"')
+ assert output == "c"
+
+ def test_9_skip_optarg_6(self, bash):
+ output = self._test(bash, "(a -o -o -o -o b c)", 6, '-a "-o"')
+ assert output == "b"
+
+ def test_9_skip_optarg_7(self, bash):
+ output = self._test(bash, "(a -o -- -b -c d e)", 6, '-a "-o"')
+ assert output == "d"
diff --git a/test/t/unit/test_unit_get_comp_words_by_ref.py b/test/t/unit/test_unit_get_words.py
index b6498fa..63c4034 100644
--- a/test/t/unit/test_unit_get_comp_words_by_ref.py
+++ b/test/t/unit/test_unit_get_words.py
@@ -11,10 +11,10 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
def _test(self, bash, *args, **kwargs):
assert_bash_exec(bash, "unset cur prev")
output = self._test_unit(
- "_get_comp_words_by_ref %s cur prev; echo $cur,${prev-}",
+ "_comp_get_words %s cur prev; echo $cur,${prev-}",
bash,
*args,
- **kwargs
+ **kwargs,
)
return output.strip()
@@ -22,7 +22,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
assert_bash_exec(
bash,
"COMP_WORDS=() COMP_CWORD= COMP_POINT= COMP_LINE= "
- "_get_comp_words_by_ref cur >/dev/null",
+ "_comp_get_words cur >/dev/null",
)
def test_2(self, bash):
@@ -41,12 +41,12 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
assert output == ","
def test_5(self, bash):
- """|a """
+ """|a """ # fmt: skip
output = self._test(bash, "(a)", 0, "a ", 0)
assert output == ","
def test_6(self, bash):
- """ | a """
+ """ | a """ # fmt: skip
output = self._test(bash, "(a)", 0, " a ", 1)
assert output.strip() == ","
@@ -134,9 +134,9 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
def test_23(self, bash):
"""a -n|
- This test makes sure `_get_cword' doesn't use `echo' to return its
- value, because -n might be interpreted by `echo' and thus woud not
- be returned.
+ This test makes sure `_comp_get_words' doesn't use `echo' to
+ return its value, because -n might be interpreted by `echo'
+ and thus would not be returned.
"""
output = self._test(bash, "(a -n)", 1, "a -n", 4)
assert output == "-n,a"
@@ -175,7 +175,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b| to all vars"""
assert_bash_exec(bash, "unset words cword cur prev")
output = self._test_unit(
- "_get_comp_words_by_ref words cword cur prev%s; "
+ "_comp_get_words words cword cur prev%s; "
'echo "${words[@]}",$cword,$cur,$prev',
bash,
"(a b)",
@@ -189,7 +189,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b| to alternate vars"""
assert_bash_exec(bash, "unset words2 cword2 cur2 prev2")
output = self._test_unit(
- "_get_comp_words_by_ref -w words2 -i cword2 -c cur2 -p prev2%s; "
+ "_comp_get_words -w words2 -i cword2 -c cur2 -p prev2%s; "
'echo $cur2,$prev2,"${words2[@]}",$cword2',
bash,
"(a b)",
@@ -204,7 +204,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b : c| with wordbreaks -= :"""
assert_bash_exec(bash, "unset words")
output = self._test_unit(
- '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"',
+ '_comp_get_words -n : words%s; echo "${words[@]}"',
bash,
"(a b : c)",
3,
@@ -217,7 +217,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b: c| with wordbreaks -= :"""
assert_bash_exec(bash, "unset words")
output = self._test_unit(
- '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"',
+ '_comp_get_words -n : words%s; echo "${words[@]}"',
bash,
"(a b : c)",
3,
@@ -230,7 +230,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
"""a b :c| with wordbreaks -= :"""
assert_bash_exec(bash, "unset words")
output = self._test_unit(
- '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"',
+ '_comp_get_words -n : words%s; echo "${words[@]}"',
bash,
"(a b : c)",
3,
@@ -243,7 +243,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
r"""a b\ :c| with wordbreaks -= :"""
assert_bash_exec(bash, "unset words")
output = self._test_unit(
- '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"',
+ '_comp_get_words -n : words%s; echo "${words[@]}"',
bash,
"(a 'b ' : c)",
3,
@@ -255,6 +255,6 @@ class TestUnitGetCompWordsByRef(TestUnitBase):
def test_unknown_arg_error(self, bash):
with pytest.raises(AssertionError) as ex:
_ = assert_bash_exec(
- bash, "_get_comp_words_by_ref dummy", want_output=True
+ bash, "_comp_get_words dummy", want_output=True
)
ex.match("dummy.* unknown argument")
diff --git a/test/t/unit/test_unit_init_completion.py b/test/t/unit/test_unit_init_completion.py
deleted file mode 100644
index 64a5a79..0000000
--- a/test/t/unit/test_unit_init_completion.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import pytest
-
-from conftest import TestUnitBase, assert_bash_exec, assert_complete
-
-
-@pytest.mark.bashcomp(
- cmd=None,
- ignore_env=r"^[+-](COMP(_(WORDS|CWORD|LINE|POINT)|REPLY)|"
- r"cur|cword|words)=",
-)
-class TestUnitInitCompletion(TestUnitBase):
- def test_1(self, bash):
- """Test environment non-pollution, detected at teardown."""
- assert_bash_exec(
- bash,
- "foo() { "
- "local cur prev words cword "
- "COMP_WORDS=() COMP_CWORD=0 COMP_LINE= COMP_POINT=0; "
- "_init_completion; }; "
- "foo; unset foo",
- )
-
- def test_2(self, bash):
- output = self._test_unit(
- "_init_completion %s; echo $cur,${prev-}", bash, "(a)", 0, "a", 0
- )
- assert output == ","
-
- @pytest.mark.parametrize("redirect", "> >> 2> < &>".split())
- def test_redirect(self, bash, redirect):
- completion = assert_complete(
- bash, "%s " % redirect, cwd="shared/default"
- )
- assert all(x in completion for x in "foo bar".split())
diff --git a/test/t/unit/test_unit_initialize.py b/test/t/unit/test_unit_initialize.py
new file mode 100644
index 0000000..63fddee
--- /dev/null
+++ b/test/t/unit/test_unit_initialize.py
@@ -0,0 +1,66 @@
+import pytest
+
+from conftest import TestUnitBase, assert_bash_exec, assert_complete
+
+
+@pytest.mark.bashcomp(
+ cmd=None,
+ ignore_env=r"^[+-](COMP(_(WORDS|CWORD|LINE|POINT)|REPLY)|"
+ r"cur|prev|cword|words)=|^\+declare -f _cmd1$",
+)
+class TestUnitInitCompletion(TestUnitBase):
+ def test_1(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { "
+ "local cur prev words cword comp_args "
+ "COMP_WORDS=() COMP_CWORD=0 COMP_LINE= COMP_POINT=0; "
+ "_comp_initialize; }; "
+ "foo; unset -f foo",
+ )
+
+ def test_2(self, bash):
+ output = self._test_unit(
+ "_comp_initialize %s; echo $cur,${prev-}", bash, "(a)", 0, "a", 0
+ )
+ assert output == ","
+
+ @pytest.mark.parametrize("redirect", "> >> 2> < &>".split())
+ def test_redirect(self, bash, redirect):
+ completion = assert_complete(
+ bash, "%s " % redirect, cwd="shared/default"
+ )
+ assert all(x in completion for x in "foo bar".split())
+
+ @pytest.fixture(scope="class")
+ def cmd1_empty_completion_setup(self, bash):
+ assert_bash_exec(
+ bash,
+ '_cmd1() { local cur prev words cword comp_args; _comp_initialize -- "$@"; } && '
+ "complete -F _cmd1 cmd1",
+ )
+
+ @pytest.mark.parametrize("redirect", "> >> 2> {fd1}> < &> &>> >|".split())
+ def test_redirect_2(self, bash, cmd1_empty_completion_setup, redirect):
+ # Note: Bash 4.3 and below cannot properly extract the redirection ">|"
+ if redirect == ">|":
+ skipif = "((BASH_VERSINFO[0] * 100 + BASH_VERSINFO[1] < 404))"
+ try:
+ assert_bash_exec(bash, skipif, want_output=None)
+ except AssertionError:
+ pass
+ else:
+ pytest.skip(skipif)
+
+ completion = assert_complete(
+ bash, "cmd1 %s f" % redirect, cwd="shared/default"
+ )
+ assert "foo" in completion
+
+ @pytest.mark.parametrize("redirect", "> >> 2> < &>".split())
+ def test_redirect_3(self, bash, redirect):
+ completion = assert_complete(
+ bash, "cmd1 %sf" % redirect, cwd="shared/default"
+ )
+ assert "foo" in completion
diff --git a/test/t/unit/test_unit_ip_addresses.py b/test/t/unit/test_unit_ip_addresses.py
index 8120c88..37f3b0e 100644
--- a/test/t/unit/test_unit_ip_addresses.py
+++ b/test/t/unit/test_unit_ip_addresses.py
@@ -9,41 +9,41 @@ class TestUnitIpAddresses:
def functions(self, request, bash):
assert_bash_exec(
bash,
- "_ia() { local cur=$(_get_cword);unset COMPREPLY;"
- "_ip_addresses; }",
+ "_ia() { local cur;_comp_get_words cur;"
+ "unset -v COMPREPLY;_comp_compgen_ip_addresses; }",
)
assert_bash_exec(bash, "complete -F _ia ia")
assert_bash_exec(
bash,
- "_iaa() { local cur=$(_get_cword);unset COMPREPLY;"
- "_ip_addresses -a; }",
+ "_iaa() { local cur;_comp_get_words cur;"
+ "unset -v COMPREPLY;_comp_compgen_ip_addresses -a; }",
)
assert_bash_exec(bash, "complete -F _iaa iaa")
assert_bash_exec(
bash,
- " _ia6() { local cur=$(_get_cword);unset COMPREPLY;"
- "_ip_addresses -6; }",
+ " _ia6() { local cur;_comp_get_words cur;"
+ "unset -v COMPREPLY;_comp_compgen_ip_addresses -6; }",
)
assert_bash_exec(bash, "complete -F _ia6 ia6")
def test_1(self, bash):
- assert_bash_exec(bash, "_ip_addresses")
+ assert_bash_exec(bash, "_comp_compgen_ip_addresses")
@pytest.mark.complete("iaa ")
def test_2(self, functions, completion):
- """_ip_addresses -a should complete ip addresses."""
+ """_comp_compgen_ip_addresses -a should complete ip addresses."""
assert completion
assert all("." in x or ":" in x for x in completion)
@pytest.mark.complete("ia ")
def test_3(self, functions, completion):
- """_ip_addresses should complete ipv4 addresses."""
+ """_comp_compgen_ip_addresses should complete ipv4 addresses."""
assert completion
assert all("." in x for x in completion)
@pytest.mark.xfail(in_container(), reason="Probably fails in a container")
@pytest.mark.complete("ia6 ")
def test_4(self, functions, completion):
- """_ip_addresses -6 should complete ipv6 addresses."""
+ """_comp_compgen_ip_addresses -6 should complete ipv6 addresses."""
assert completion
assert all(":" in x for x in completion)
diff --git a/test/t/unit/test_unit_known_hosts_real.py b/test/t/unit/test_unit_known_hosts.py
index ac5205e..b0f715b 100644
--- a/test/t/unit/test_unit_known_hosts_real.py
+++ b/test/t/unit/test_unit_known_hosts.py
@@ -2,14 +2,14 @@ from itertools import chain
import pytest
-from conftest import assert_bash_exec
+from conftest import assert_bash_exec, bash_env_saved
@pytest.mark.bashcomp(
cmd=None,
- ignore_env="^[+-](COMP(REPLY|_KNOWN_HOSTS_WITH_HOSTFILE)|OLDHOME)=",
+ ignore_env="^[+-](COMPREPLY|BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE)=",
)
-class TestUnitKnownHostsReal:
+class TestUnitCompgenKnownHosts:
@pytest.mark.parametrize(
"prefix,colon_flag,hostfile",
[("", "", True), ("", "", False), ("user@", "c", True)],
@@ -21,9 +21,9 @@ class TestUnitKnownHostsReal:
"%s%s%s" % (prefix, x, ":" if colon_flag else "")
for x in chain(
hosts if hostfile else avahi_hosts,
- # fixtures/_known_hosts_real/config
+ # fixtures/_known_hosts/config
"gee hus jar #not-a-comment".split(),
- # fixtures/_known_hosts_real/known_hosts
+ # fixtures/_known_hosts/known_hosts
(
"doo",
"ike",
@@ -43,14 +43,14 @@ class TestUnitKnownHostsReal:
)
assert_bash_exec(
bash,
- "unset -v COMP_KNOWN_HOSTS_WITH_HOSTFILE"
+ "unset -v BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE"
if hostfile
- else "COMP_KNOWN_HOSTS_WITH_HOSTFILE=",
+ else "BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE=",
)
output = assert_bash_exec(
bash,
- "_known_hosts_real -a%sF _known_hosts_real/config '%s'; "
- r'printf "%%s\n" "${COMPREPLY[@]}"; unset COMPREPLY'
+ "_comp_compgen_known_hosts -a%sF _known_hosts/config '%s'; "
+ r'printf "%%s\n" "${COMPREPLY[@]}"; unset -v COMPREPLY'
% (colon_flag, prefix),
want_output=True,
)
@@ -66,12 +66,13 @@ class TestUnitKnownHostsReal:
)
def test_ip_filtering(self, bash, family, result):
assert_bash_exec(
- bash, "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE"
+ bash,
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE",
)
output = assert_bash_exec(
bash,
- "COMP_KNOWN_HOSTS_WITH_HOSTFILE= "
- "_known_hosts_real -%sF _known_hosts_real/localhost_config ''; "
+ "BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE= "
+ "_comp_compgen_known_hosts -%sF _known_hosts/localhost_config ''; "
r'printf "%%s\n" "${COMPREPLY[@]}"' % family,
want_output=True,
)
@@ -79,17 +80,17 @@ class TestUnitKnownHostsReal:
def test_consecutive_spaces(self, bash, hosts):
expected = hosts.copy()
- # fixtures/_known_hosts_real/spaced conf
+ # fixtures/_known_hosts/spaced conf
expected.extend("gee hus #not-a-comment".split())
- # fixtures/_known_hosts_real/known_hosts2
+ # fixtures/_known_hosts/known_hosts2
expected.extend("two two2 two3 two4".split())
# fixtures/_known_hosts_/spaced known_hosts
expected.extend("doo ike".split())
output = assert_bash_exec(
bash,
- "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
- "_known_hosts_real -aF '_known_hosts_real/spaced conf' ''; "
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE; "
+ "_comp_compgen_known_hosts -aF '_known_hosts/spaced conf' ''; "
r'printf "%s\n" "${COMPREPLY[@]}"',
want_output=True,
)
@@ -97,62 +98,58 @@ class TestUnitKnownHostsReal:
def test_files_starting_with_tilde(self, bash, hosts):
expected = hosts.copy()
- # fixtures/_known_hosts_real/known_hosts2
+ # fixtures/_known_hosts/known_hosts2
expected.extend("two two2 two3 two4".split())
- # fixtures/_known_hosts_real/known_hosts3
+ # fixtures/_known_hosts/known_hosts3
expected.append("three")
- # fixtures/_known_hosts_real/known_hosts4
+ # fixtures/_known_hosts/known_hosts4
expected.append("four")
- assert_bash_exec(bash, 'OLDHOME="$HOME"; HOME="%s"' % bash.cwd)
- output = assert_bash_exec(
- bash,
- "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
- "_known_hosts_real -aF _known_hosts_real/config_tilde ''; "
- r'printf "%s\n" "${COMPREPLY[@]}"',
- want_output=True,
- )
- assert_bash_exec(bash, 'HOME="$OLDHOME"')
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("HOME", bash.cwd)
+ output = assert_bash_exec(
+ bash,
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE;"
+ " _comp_compgen_known_hosts -aF _known_hosts/config_tilde ''; "
+ r'printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ )
+
assert sorted(set(output.strip().split())) == sorted(expected)
def test_included_configs(self, bash, hosts):
expected = hosts.copy()
- # fixtures/_known_hosts_real/config_include_recursion
+ # fixtures/_known_hosts/config_include_recursion
expected.append("recursion")
- # fixtures/_known_hosts_real/.ssh/config_relative_path
+ # fixtures/_known_hosts/.ssh/config_relative_path
expected.append("relative_path")
- # fixtures/_known_hosts_real/.ssh/config_asterisk_*
+ # fixtures/_known_hosts/.ssh/config_asterisk_*
expected.extend("asterisk_1 asterisk_2".split())
- # fixtures/_known_hosts_real/.ssh/config_question_mark
+ # fixtures/_known_hosts/.ssh/config_question_mark
expected.append("question_mark")
- assert_bash_exec(
- bash, 'OLDHOME="$HOME"; HOME="%s/_known_hosts_real"' % bash.cwd
- )
- output = assert_bash_exec(
- bash,
- "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
- "_known_hosts_real -aF _known_hosts_real/config_include ''; "
- r'printf "%s\n" "${COMPREPLY[@]}"',
- want_output=True,
- )
- assert_bash_exec(bash, 'HOME="$OLDHOME"')
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("HOME", "%s/_known_hosts" % bash.cwd)
+ output = assert_bash_exec(
+ bash,
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE;"
+ " _comp_compgen_known_hosts -aF _known_hosts/config_include ''; "
+ r'printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ )
assert sorted(set(output.strip().split())) == sorted(expected)
def test_no_globbing(self, bash):
- assert_bash_exec(
- bash, 'OLDHOME="$HOME"; HOME="%s/_known_hosts_real"' % bash.cwd
- )
- output = assert_bash_exec(
- bash,
- "cd _known_hosts_real; "
- "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
- "_known_hosts_real -aF config ''; "
- r'printf "%s\n" "${COMPREPLY[@]}"; '
- "cd - &>/dev/null",
- want_output=True,
- )
- assert_bash_exec(bash, 'HOME="$OLDHOME"')
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("HOME", "%s/_known_hosts" % bash.cwd)
+ bash_env.chdir("_known_hosts")
+ output = assert_bash_exec(
+ bash,
+ "unset -v COMPREPLY BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE;"
+ " _comp_compgen_known_hosts -aF config ''; "
+ r'printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ )
completion = sorted(set(output.strip().split()))
assert "gee" in completion
assert "gee-filename-canary" not in completion
diff --git a/test/t/unit/test_unit_load_completion.py b/test/t/unit/test_unit_load_completion.py
new file mode 100644
index 0000000..6272e5b
--- /dev/null
+++ b/test/t/unit/test_unit_load_completion.py
@@ -0,0 +1,94 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, cwd="_comp_load")
+class TestLoadCompletion:
+ def test_userdir_1(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR",
+ "$PWD/userdir1:$PWD/userdir2:$BASH_COMPLETION_USER_DIR",
+ quote=False,
+ )
+ bash_env.write_variable(
+ "PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False
+ )
+ output = assert_bash_exec(
+ bash, "_comp_load cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from userdir1"
+ output = assert_bash_exec(
+ bash, "_comp_load cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from userdir2"
+
+ def test_PATH_1(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False
+ )
+ output = assert_bash_exec(
+ bash, "_comp_load cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "_comp_load cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "complete -p cmd2", want_output=True
+ )
+ assert " cmd2" in output
+ output = assert_bash_exec(
+ bash, 'complete -p "$PWD/prefix1/sbin/cmd2"', want_output=True
+ )
+ assert "/prefix1/sbin/cmd2" in output
+
+ def test_cmd_path_1(self, bash):
+ assert_bash_exec(bash, "complete -r cmd1 || :", want_output=None)
+ output = assert_bash_exec(
+ bash, "_comp_load prefix1/bin/cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, 'complete -p "$PWD/prefix1/bin/cmd1"', want_output=True
+ )
+ assert "/prefix1/bin/cmd1" in output
+ assert_bash_exec(bash, "! complete -p cmd1", want_output=None)
+ output = assert_bash_exec(
+ bash, "_comp_load prefix1/sbin/cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "_comp_load bin/cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "_comp_load bin/cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from prefix1"
+
+ def test_cmd_path_2(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("PATH", "$PWD/bin:$PATH", quote=False)
+ output = assert_bash_exec(
+ bash, "_comp_load cmd1", want_output=True
+ )
+ assert output.strip() == "cmd1: sourced from prefix1"
+ output = assert_bash_exec(
+ bash, "_comp_load cmd2", want_output=True
+ )
+ assert output.strip() == "cmd2: sourced from prefix1"
+
+ def test_cmd_intree_precedence(self, bash):
+ """
+ Test in-tree, i.e. completions/$cmd relative to the main script
+ has precedence over location derived from PATH.
+ """
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("PATH", "$PWD/prefix1/bin", quote=False)
+ # The in-tree `sh` completion should be loaded here,
+ # and cause no output, unlike our `$PWD/prefix1/bin/sh` canary.
+ assert_bash_exec(bash, "_comp_load sh", want_output=False)
diff --git a/test/t/unit/test_unit_longopt.py b/test/t/unit/test_unit_longopt.py
index c5488e3..ab823d4 100644
--- a/test/t/unit/test_unit_longopt.py
+++ b/test/t/unit/test_unit_longopt.py
@@ -10,9 +10,9 @@ class TestUnitLongopt:
@pytest.fixture(scope="class")
def functions(self, request, bash):
assert_bash_exec(bash, "_grephelp() { cat _longopt/grep--help.txt; }")
- assert_bash_exec(bash, "complete -F _longopt _grephelp")
+ assert_bash_exec(bash, "complete -F _comp_complete_longopt _grephelp")
assert_bash_exec(bash, "_various() { cat _longopt/various.txt; }")
- assert_bash_exec(bash, "complete -F _longopt _various")
+ assert_bash_exec(bash, "complete -F _comp_complete_longopt _various")
@pytest.mark.complete("_grephelp --")
def test_1(self, functions, completion):
diff --git a/test/t/unit/test_unit_looks_like_path.py b/test/t/unit/test_unit_looks_like_path.py
new file mode 100644
index 0000000..3e86b48
--- /dev/null
+++ b/test/t/unit/test_unit_looks_like_path.py
@@ -0,0 +1,33 @@
+import shlex
+
+import pytest
+
+from conftest import TestUnitBase, assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitQuote(TestUnitBase):
+ @pytest.mark.parametrize(
+ "thing_looks_like",
+ (
+ ("", False),
+ ("foo", False),
+ ("/foo", True),
+ ("foo/", True),
+ ("foo/bar", True),
+ (".", True),
+ ("../", True),
+ ("~", True),
+ ("~foo", True),
+ ),
+ )
+ def test_1(self, bash, thing_looks_like):
+ thing, looks_like = thing_looks_like
+ output = assert_bash_exec(
+ bash,
+ f"_comp_looks_like_path {shlex.quote(thing)}; printf %s $?",
+ want_output=True,
+ want_newline=False,
+ )
+ is_zero = output.strip() == "0"
+ assert (looks_like and is_zero) or (not looks_like and not is_zero)
diff --git a/test/t/unit/test_unit_parse_help.py b/test/t/unit/test_unit_parse_help.py
index 4a02155..1a46f3f 100644
--- a/test/t/unit/test_unit_parse_help.py
+++ b/test/t/unit/test_unit_parse_help.py
@@ -2,29 +2,29 @@
import pytest
-from conftest import assert_bash_exec
+from conftest import assert_bash_exec, bash_env_saved
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+declare -f fn$")
class TestUnitParseHelp:
def test_1(self, bash):
assert_bash_exec(bash, "fn() { echo; }")
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_2(self, bash):
assert_bash_exec(bash, "fn() { echo 'no dashes here'; }")
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_3(self, bash):
assert_bash_exec(bash, "fn() { echo 'internal-dash'; }")
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_4(self, bash):
assert_bash_exec(bash, "fn() { echo 'no -leading-dashes'; }")
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_5(self, bash):
@@ -94,6 +94,20 @@ class TestUnitParseHelp:
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
assert output.split() == "--foo".split()
+ def test_17_failglob(self, bash):
+ assert_bash_exec(bash, "fn() { echo '--foo[=bar]'; }")
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
+ assert output.split() == "--foo".split()
+
+ def test_17_nullglob(self, bash):
+ assert_bash_exec(bash, "fn() { echo '--foo[=bar]'; }")
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
+ assert output.split() == "--foo".split()
+
def test_18(self, bash):
assert_bash_exec(bash, "fn() { echo '--foo=<bar>'; }")
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
@@ -144,6 +158,13 @@ class TestUnitParseHelp:
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
assert output.split() == "--foo".split()
+ def test_27_middle_dot(self, bash):
+ """We do not want to include the period at the end of the sentence but
+ want to include dots connecting names."""
+ assert_bash_exec(bash, "fn() { echo '--foo.bar.'; }")
+ output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
+ assert output.split() == "--foo.bar".split()
+
def test_28(self, bash):
assert_bash_exec(bash, "fn() { echo '-f or --foo'; }")
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
@@ -161,7 +182,7 @@ class TestUnitParseHelp:
assert_bash_exec(
bash, r"fn() { printf '%s\n' $'----\n---foo\n----- bar'; }"
)
- output = assert_bash_exec(bash, "_parse_help fn")
+ output = assert_bash_exec(bash, "_parse_help fn; (($? == 1))")
assert not output
def test_31(self, bash):
@@ -181,3 +202,33 @@ class TestUnitParseHelp:
)
output = assert_bash_exec(bash, "_parse_help fn", want_output=True)
assert output.split() == "--exclude=".split()
+
+ def test_custom_helpopt1(self, bash):
+ assert_bash_exec(bash, "fn() { [[ $1 == -h ]] && echo '-option'; }")
+ output = assert_bash_exec(bash, "_parse_help fn -h", want_output=True)
+ assert output.split() == "-option".split()
+
+ def test_custom_helpopt2(self, bash):
+ assert_bash_exec(bash, "fn() { [[ $1 == '-?' ]] && echo '-option'; }")
+ output = assert_bash_exec(
+ bash, "_parse_help fn '-?'", want_output=True
+ )
+ assert output.split() == "-option".split()
+
+ def test_custom_helpopt2_failglob(self, bash):
+ assert_bash_exec(bash, "fn() { [[ $1 == '-?' ]] && echo '-option'; }")
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "_parse_help fn '-?'", want_output=True
+ )
+ assert output.split() == "-option".split()
+
+ def test_custom_helpopt2_nullglob(self, bash):
+ assert_bash_exec(bash, "fn() { [[ $1 == '-?' ]] && echo '-option'; }")
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash, "_parse_help fn '-?'", want_output=True
+ )
+ assert output.split() == "-option".split()
diff --git a/test/t/unit/test_unit_parse_usage.py b/test/t/unit/test_unit_parse_usage.py
index f0cb711..0106922 100644
--- a/test/t/unit/test_unit_parse_usage.py
+++ b/test/t/unit/test_unit_parse_usage.py
@@ -1,18 +1,18 @@
import pytest
-from conftest import assert_bash_exec
+from conftest import assert_bash_exec, bash_env_saved
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+declare -f fn$")
class TestUnitParseUsage:
def test_1(self, bash):
assert_bash_exec(bash, "fn() { echo; }")
- output = assert_bash_exec(bash, "_parse_usage fn")
+ output = assert_bash_exec(bash, "_parse_usage fn; (($? == 1))")
assert not output
def test_2(self, bash):
assert_bash_exec(bash, "fn() { echo 'no dashes here'; }")
- output = assert_bash_exec(bash, "_parse_usage fn")
+ output = assert_bash_exec(bash, "_parse_usage fn; (($? == 1))")
assert not output
def test_3(self, bash):
@@ -59,7 +59,7 @@ class TestUnitParseUsage:
assert_bash_exec(
bash, "fn() { echo ----; echo ---foo; echo '----- bar'; }"
)
- output = assert_bash_exec(bash, "_parse_usage fn")
+ output = assert_bash_exec(bash, "_parse_usage fn; (($? == 1))")
assert not output
def test_12(self, bash):
@@ -67,3 +67,41 @@ class TestUnitParseUsage:
bash, "echo '[-duh]' | _parse_usage -", want_output=True
)
assert output.split() == "-d -u -h".split()
+
+ def test_custom_helpopt1(self, bash):
+ assert_bash_exec(
+ bash, "fn() { [[ $1 == -h ]] && echo 'fn [-option]'; true; }"
+ )
+ output = assert_bash_exec(bash, "_parse_usage fn -h", want_output=True)
+ assert output.split() == "-o -p -t -i -o -n".split()
+
+ def test_custom_helpopt2(self, bash):
+ assert_bash_exec(
+ bash, "fn() { [[ $1 == '-?' ]] && echo 'fn [-option]'; }"
+ )
+ output = assert_bash_exec(
+ bash, "_parse_usage fn '-?'", want_output=True
+ )
+ assert output.split() == "-o -p -t -i -o -n".split()
+
+ def test_custom_helpopt2_failglob(self, bash):
+ assert_bash_exec(
+ bash, "fn() { [[ $1 == '-?' ]] && echo 'fn [-option]'; }"
+ )
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "_parse_usage fn '-?'", want_output=True
+ )
+ assert output.split() == "-o -p -t -i -o -n".split()
+
+ def test_custom_helpopt2_nullglob(self, bash):
+ assert_bash_exec(
+ bash, "fn() { [[ $1 == '-?' ]] && echo 'fn [-option]'; }"
+ )
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash, "_parse_usage fn '-?'", want_output=True
+ )
+ assert output.split() == "-o -p -t -i -o -n".split()
diff --git a/test/t/unit/test_unit_pgids.py b/test/t/unit/test_unit_pgids.py
new file mode 100644
index 0000000..05019ec
--- /dev/null
+++ b/test/t/unit/test_unit_pgids.py
@@ -0,0 +1,34 @@
+import os
+
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+(COMPREPLY|cur)=")
+class TestUnitPgids:
+ def test_smoke(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ assert_bash_exec(bash, "_comp_compgen_pgids >/dev/null")
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local cur=; _comp_compgen_pgids; }; foo; unset -f foo",
+ )
+
+ def test_ints(self, bash):
+ """Test that we get something sensible, and only int'y strings."""
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ completion = assert_bash_exec(
+ bash,
+ r'_comp_compgen_pgids; printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ ).split()
+ assert completion
+ if hasattr(os, "getpgid"):
+ assert str(os.getpgid(0)) in completion
+ assert all(x.isdigit() for x in completion)
diff --git a/test/t/unit/test_unit_pids.py b/test/t/unit/test_unit_pids.py
new file mode 100644
index 0000000..3681c8c
--- /dev/null
+++ b/test/t/unit/test_unit_pids.py
@@ -0,0 +1,34 @@
+import os
+
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
+class TestUnitPids:
+ def test_smoke(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ assert_bash_exec(bash, "_comp_compgen_pids >/dev/null")
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local cur=; _comp_compgen_pids; }; foo; unset -f foo",
+ )
+
+ def test_ints(self, bash):
+ """Test that we get something sensible, and only int'y strings."""
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ completion = assert_bash_exec(
+ bash,
+ r'_comp_compgen_pids; printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ ).split()
+ assert completion
+ if hasattr(os, "getpid"):
+ assert str(os.getpid()) in completion
+ assert all(x.isdigit() for x in completion)
diff --git a/test/t/unit/test_unit_pnames.py b/test/t/unit/test_unit_pnames.py
new file mode 100644
index 0000000..e0819e5
--- /dev/null
+++ b/test/t/unit/test_unit_pnames.py
@@ -0,0 +1,29 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
+class TestUnitPnames:
+ def test_smoke(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ assert_bash_exec(bash, "_comp_compgen_pnames >/dev/null")
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local cur=; _comp_compgen_pnames; }; foo; unset -f foo",
+ )
+
+ def test_something(self, bash):
+ """Test that we get something."""
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ completion = assert_bash_exec(
+ bash,
+ r'_comp_compgen_pids; printf "%s\n" "${COMPREPLY[@]}"',
+ want_output=True,
+ ).split()
+ assert completion
diff --git a/test/t/unit/test_unit_quote.py b/test/t/unit/test_unit_quote.py
index b280bd6..3508782 100644
--- a/test/t/unit/test_unit_quote.py
+++ b/test/t/unit/test_unit_quote.py
@@ -3,34 +3,41 @@ import pytest
from conftest import TestUnitBase, assert_bash_exec
-@pytest.mark.bashcomp(cmd=None)
+@pytest.mark.bashcomp(
+ cmd=None,
+ ignore_env=r"^\+declare -f __tester$",
+)
class TestUnitQuote(TestUnitBase):
def test_1(self, bash):
+ assert_bash_exec(
+ bash,
+ '__tester() { local REPLY; _comp_quote "$1"; printf %s "$REPLY"; }',
+ )
output = assert_bash_exec(
- bash, 'quote "a b"', want_output=True, want_newline=False
+ bash, '__tester "a b"', want_output=True, want_newline=False
)
assert output.strip() == "'a b'"
def test_2(self, bash):
output = assert_bash_exec(
- bash, 'quote "a b"', want_output=True, want_newline=False
+ bash, '__tester "a b"', want_output=True, want_newline=False
)
assert output.strip() == "'a b'"
def test_3(self, bash):
output = assert_bash_exec(
- bash, 'quote " a "', want_output=True, want_newline=False
+ bash, '__tester " a "', want_output=True, want_newline=False
)
assert output.strip() == "' a '"
def test_4(self, bash):
output = assert_bash_exec(
- bash, "quote \"a'b'c\"", want_output=True, want_newline=False
+ bash, "__tester \"a'b'c\"", want_output=True, want_newline=False
)
assert output.strip() == r"'a'\''b'\''c'"
def test_5(self, bash):
output = assert_bash_exec(
- bash, 'quote "a\'"', want_output=True, want_newline=False
+ bash, '__tester "a\'"', want_output=True, want_newline=False
)
assert output.strip() == r"'a'\'''"
diff --git a/test/t/unit/test_unit_quote_compgen.py b/test/t/unit/test_unit_quote_compgen.py
new file mode 100644
index 0000000..faf23fe
--- /dev/null
+++ b/test/t/unit/test_unit_quote_compgen.py
@@ -0,0 +1,173 @@
+import os
+
+import pytest
+
+from conftest import assert_bash_exec, assert_complete, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None, temp_cwd=True)
+class TestUnitQuoteCompgen:
+ @pytest.fixture(scope="class")
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ '_comp__test_quote_compgen() { local REPLY; _comp_quote_compgen "$1"; printf %s "$REPLY"; }',
+ )
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_exec(self, bash, functions, funcname):
+ assert_bash_exec(bash, "%s '' >/dev/null" % funcname)
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_env_non_pollution(self, bash, functions, funcname):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash, "foo() { %s meh >/dev/null; }; foo; unset -f foo" % funcname
+ )
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_1(self, bash, functions, funcname):
+ output = assert_bash_exec(
+ bash, "%s '';echo" % funcname, want_output=True
+ )
+ assert output.strip() == "''"
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_2(self, bash, functions, funcname):
+ output = assert_bash_exec(
+ bash, "%s foo;echo" % funcname, want_output=True
+ )
+ assert output.strip() == "foo"
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_3(self, bash, functions, funcname):
+ output = assert_bash_exec(
+ bash, '%s foo\\"bar;echo' % funcname, want_output=True
+ )
+ assert output.strip() == 'foo\\"bar'
+
+ @pytest.mark.parametrize(
+ "funcname", "_comp__test_quote_compgen quote_readline".split()
+ )
+ def test_4(self, bash, functions, funcname):
+ output = assert_bash_exec(
+ bash, "%s '$(echo x >&2)';echo" % funcname, want_output=True
+ )
+ assert output.strip() == "\\$\\(echo\\ x\\ \\>\\&2\\)"
+
+ def test_github_issue_189_1(self, bash, functions):
+ """Test error messages on a certain command line
+
+ Reported at https://github.com/scop/bash-completion/issues/189
+
+ Syntax error messages should not be shown by completion on the
+ following line:
+
+ $ ls -- '${[TAB]
+ $ rm -- '${[TAB]
+
+ """
+ assert_bash_exec(bash, "_comp__test_quote_compgen $'\\'${' >/dev/null")
+
+ def test_github_issue_492_1(self, bash, functions):
+ """Test unintended code execution on a certain command line
+
+ Reported at https://github.com/scop/bash-completion/pull/492
+
+ Arbitrary commands could be unintendedly executed by
+ _comp_quote_compgen. In the following example, the command "touch
+ 1.txt" would be unintendedly created before the fix. The file "1.txt"
+ should not be created by completion on the following line:
+
+ $ echo '$(touch file.txt)[TAB]
+
+ """
+ assert_bash_exec(
+ bash, "_comp__test_quote_compgen $'\\'$(touch 1.txt)' >/dev/null"
+ )
+ assert not os.path.exists("./1.txt")
+
+ def test_github_issue_492_2(self, bash, functions):
+ """Test the file clear by unintended redirection on a certain command line
+
+ Reported at https://github.com/scop/bash-completion/pull/492
+
+ The file "1.0" should not be created by completion on the following
+ line:
+
+ $ awk '$1 > 1.0[TAB]
+
+ """
+ assert_bash_exec(
+ bash, "_comp__test_quote_compgen $'\\'$1 > 1.0' >/dev/null"
+ )
+ assert not os.path.exists("./1.0")
+
+ def test_github_issue_492_3(self, bash, functions):
+ """Test code execution through unintended pathname expansions
+
+ When there is a file named "quote=$(COMMAND)" (for
+ _comp_compgen_filedir) or "REPLY=$(COMMAND)" (for _comp_quote_compgen),
+ the completion of the word '$* results in the execution of COMMAND.
+
+ $ echo '$*[TAB]
+
+ """
+ os.mkdir("./REPLY=$(echo injected >&2)")
+ assert_bash_exec(bash, "_comp__test_quote_compgen $'\\'$*' >/dev/null")
+
+ def test_github_issue_492_4(self, bash, functions):
+ """Test error messages through unintended pathname expansions
+
+ When "shopt -s failglob" is set by the user, the completion of the word
+ containing glob character and special characters (e.g. TAB) results in
+ the failure of pathname expansions.
+
+ $ shopt -s failglob
+ $ echo a\\ b*[TAB]
+
+ """
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ assert_bash_exec(
+ bash, "_comp__test_quote_compgen $'a\\\\\\tb*' >/dev/null"
+ )
+
+ def test_github_issue_526_1(self, bash):
+ r"""Regression tests for unprocessed escape sequences after quotes
+
+ Ref [1] https://github.com/scop/bash-completion/pull/492#discussion_r637213822
+ Ref [2] https://github.com/scop/bash-completion/pull/526
+
+ The escape sequences in the local variable of "value" in
+ "_comp_quote_compgen" needs to be unescaped by passing it to printf as
+ the format string. This causes a problem in the following case [where
+ the spaces after "alpha\" is a TAB character inserted in the command
+ string by "C-v TAB"]:
+
+ $ echo alpha\ b[TAB]
+
+ """
+ os.mkdir("./alpha\tbeta")
+ assert (
+ assert_complete(
+ # Remark on "rendered_cmd": Bash aligns the last character 'b'
+ # in the rendered cmd to an "8 x n" boundary using spaces.
+ # Here, the command string is assumed to start from column 2
+ # because the width of PS1 (conftest.PS1 = '/@') is 2,
+ bash,
+ "echo alpha\\\026\tb",
+ rendered_cmd="echo alpha\\ b",
+ )
+ == "eta/"
+ )
diff --git a/test/t/unit/test_unit_quote_readline.py b/test/t/unit/test_unit_quote_readline.py
deleted file mode 100644
index e2b437e..0000000
--- a/test/t/unit/test_unit_quote_readline.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import pytest
-
-from conftest import assert_bash_exec
-
-
-@pytest.mark.bashcomp(cmd=None)
-class TestUnitQuoteReadline:
- def test_exec(self, bash):
- assert_bash_exec(bash, "quote_readline '' >/dev/null")
-
- def test_env_non_pollution(self, bash):
- """Test environment non-pollution, detected at teardown."""
- assert_bash_exec(
- bash, "foo() { quote_readline meh >/dev/null; }; foo; unset foo"
- )
diff --git a/test/t/unit/test_unit_realcommand.py b/test/t/unit/test_unit_realcommand.py
new file mode 100644
index 0000000..4eb7b73
--- /dev/null
+++ b/test/t/unit/test_unit_realcommand.py
@@ -0,0 +1,90 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(
+ cmd=None, cwd="shared", ignore_env=r"^\+declare -f __tester$"
+)
+class TestUnitRealCommand:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash,
+ (
+ "__tester() { "
+ "local REPLY rc; "
+ '_comp_realcommand "$1"; '
+ "rc=$?; "
+ 'printf %s "$REPLY"; '
+ "return $rc; "
+ "}"
+ ),
+ )
+
+ def test_non_pollution(self, bash):
+ """Test environment non-pollution, detected at teardown."""
+ assert_bash_exec(
+ bash,
+ "foo() { local REPLY=; _comp_realcommand bar; }; foo; unset -f foo",
+ want_output=None,
+ )
+
+ def test_basename(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("PATH", "$PWD/bin:$PATH", quote=False)
+ output = assert_bash_exec(
+ bash,
+ "__tester arp",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/bin/arp")
+
+ def test_basename_nonexistent(self, bash, functions):
+ filename = "non-existent-file-for-bash-completion-tests"
+ skipif = "! type -P %s" % filename
+ try:
+ assert_bash_exec(bash, skipif, want_output=None)
+ except AssertionError:
+ pytest.skipif(skipif)
+ output = assert_bash_exec(
+ bash,
+ "! __tester %s" % filename,
+ want_output=False,
+ )
+ assert output.strip() == ""
+
+ def test_relative(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "__tester bin/arp",
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/bin/arp")
+
+ def test_relative_nonexistent(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ "! __tester bin/non-existent",
+ want_output=False,
+ )
+ assert output.strip() == ""
+
+ def test_absolute(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '__tester "$PWD/bin/arp"',
+ want_output=True,
+ want_newline=False,
+ )
+ assert output.strip().endswith("/shared/bin/arp")
+
+ def test_absolute_nonexistent(self, bash, functions):
+ output = assert_bash_exec(
+ bash,
+ '! __tester "$PWD/bin/non-existent"',
+ want_output=False,
+ )
+ assert output.strip() == ""
diff --git a/test/t/unit/test_unit_split.py b/test/t/unit/test_unit_split.py
new file mode 100644
index 0000000..d1f228e
--- /dev/null
+++ b/test/t/unit/test_unit_split.py
@@ -0,0 +1,90 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(
+ cmd=None, ignore_env=r"^\+declare -f (dump_array|__tester)$"
+)
+class TestUtilSplit:
+ @pytest.fixture
+ def functions(self, bash):
+ assert_bash_exec(
+ bash, "dump_array() { printf '<%s>' \"${arr[@]}\"; echo; }"
+ )
+ assert_bash_exec(
+ bash,
+ '__tester() { local -a arr=(00); _comp_split "${@:1:$#-1}" arr "${@:$#}"; dump_array; }',
+ )
+
+ def test_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester '12 34 56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_2(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester $'12\\n34\\n56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_3(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester '12:34:56'", want_output=True
+ )
+ assert output.strip() == "<12:34:56>"
+
+ def test_option_F_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -F : '12:34:56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_option_F_2(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -F : '12 34 56'", want_output=True
+ )
+ assert output.strip() == "<12 34 56>"
+
+ def test_option_l_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -l $'12\\n34\\n56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_option_l_2(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -l '12 34 56'", want_output=True
+ )
+ assert output.strip() == "<12 34 56>"
+
+ def test_option_a_1(self, bash, functions):
+ output = assert_bash_exec(
+ bash, "__tester -aF : '12:34:56'", want_output=True
+ )
+ assert output.strip() == "<00><12><34><56>"
+
+ def test_protect_from_failglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("failglob", True)
+ output = assert_bash_exec(
+ bash, "__tester -F '*' '12*34*56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_protect_from_nullglob(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.shopt("nullglob", True)
+ output = assert_bash_exec(
+ bash, "__tester -F '*' '12*34*56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
+
+ def test_protect_from_IFS(self, bash, functions):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("IFS", "34")
+ output = assert_bash_exec(
+ bash, "__tester '12 34 56'", want_output=True
+ )
+ assert output.strip() == "<12><34><56>"
diff --git a/test/t/unit/test_unit_tilde.py b/test/t/unit/test_unit_tilde.py
index 35a4e4c..fae0dd6 100644
--- a/test/t/unit/test_unit_tilde.py
+++ b/test/t/unit/test_unit_tilde.py
@@ -6,23 +6,24 @@ from conftest import assert_bash_exec
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
class TestUnitTilde:
def test_1(self, bash):
- assert_bash_exec(bash, "_tilde >/dev/null")
+ assert_bash_exec(bash, "! _comp_compgen_tilde >/dev/null")
def test_2(self, bash):
"""Test environment non-pollution, detected at teardown."""
assert_bash_exec(
- bash, 'foo() { local aa="~"; _tilde "$aa"; }; foo; unset foo'
+ bash,
+ 'foo() { local aa="~"; _comp_compgen -c "$aa" tilde; }; foo; unset -f foo',
)
def test_3(self, bash):
"""Test for https://bugs.debian.org/766163"""
- assert_bash_exec(bash, "_tilde ~-o")
+ assert_bash_exec(bash, "! _comp_compgen -c ~-o tilde")
def _test_part_full(self, bash, part, full):
res = (
assert_bash_exec(
bash,
- '_tilde "~%s"; echo "${COMPREPLY[@]}"' % part,
+ '_comp_compgen -c "~%s" tilde; echo "${COMPREPLY[@]}"' % part,
want_output=True,
)
.strip()
diff --git a/test/t/unit/test_unit_unlocal.py b/test/t/unit/test_unit_unlocal.py
new file mode 100644
index 0000000..be5ec56
--- /dev/null
+++ b/test/t/unit/test_unit_unlocal.py
@@ -0,0 +1,18 @@
+import pytest
+
+from conftest import assert_bash_exec
+
+
+@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+(VAR=|declare -f foo)")
+class TestUnlocal:
+ def test_1(self, bash):
+ cmd = (
+ "foo() { "
+ "local VAR=inner; "
+ "_comp_unlocal VAR; "
+ "echo $VAR; "
+ "}; "
+ "VAR=outer; foo; "
+ )
+ res = assert_bash_exec(bash, cmd, want_output=True).strip()
+ assert res == "outer"
diff --git a/test/t/unit/test_unit_variables.py b/test/t/unit/test_unit_variables.py
index d62bc4a..f15d249 100644
--- a/test/t/unit/test_unit_variables.py
+++ b/test/t/unit/test_unit_variables.py
@@ -11,7 +11,7 @@ class TestUnitVariables:
assert_bash_exec(
bash, "unset assoc2 && declare -A assoc2=([idx1]=1 [idx2]=2)"
)
- assert_bash_exec(bash, "unset ${!___v*} && declare ___var=''")
+ assert_bash_exec(bash, 'unset ${!___v*} && declare ___var=""')
request.addfinalizer(
lambda: assert_bash_exec(bash, "unset ___var assoc1 assoc2")
)
@@ -28,6 +28,10 @@ class TestUnitVariables:
def test_multiple_array_indexes(self, functions, completion):
assert completion == "${assoc2[idx1]} ${assoc2[idx2]}".split()
+ @pytest.mark.complete(": ${assoc2[", shopt=dict(failglob=True))
+ def test_multiple_array_indexes_failglob(self, functions, completion):
+ assert completion == "${assoc2[idx1]} ${assoc2[idx2]}".split()
+
@pytest.mark.complete(": ${assoc1[bogus]")
def test_closing_curly_after_square(self, functions, completion):
assert completion == "}"
diff --git a/test/t/unit/test_unit_xfunc.py b/test/t/unit/test_unit_xfunc.py
new file mode 100644
index 0000000..3fb3a72
--- /dev/null
+++ b/test/t/unit/test_unit_xfunc.py
@@ -0,0 +1,71 @@
+import pytest
+
+from conftest import assert_bash_exec, bash_env_saved
+
+
+@pytest.mark.bashcomp(cmd=None)
+class TestUnitXfunc:
+ def test_1(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR", "$PWD/_comp_xfunc", quote=False
+ )
+
+ # test precondition
+ assert_bash_exec(
+ bash,
+ "! declare -F _comp_xfunc_xfunc_test1_utility1 &>/dev/null",
+ )
+
+ # first invocation (completion/xfunc-test1 is sourced)
+ output = assert_bash_exec(
+ bash,
+ "_comp_xfunc xfunc-test1 utility1 'a b' cde fgh",
+ want_output=True,
+ )
+ assert output.strip() == "util1[<a b><cde><fgh>]"
+
+ # test precondition
+ assert_bash_exec(
+ bash, "declare -F _comp_xfunc_xfunc_test1_utility1 &>/dev/null"
+ )
+
+ # second invocation (completion/xfunc-test1 is not sourced)
+ output = assert_bash_exec(
+ bash,
+ "_comp_xfunc xfunc-test1 utility1 'a b' cde fgh",
+ want_output=True,
+ )
+ assert output.strip() == "util1[<a b><cde><fgh>]"
+
+ def test_2(self, bash):
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable(
+ "BASH_COMPLETION_USER_DIR", "$PWD/_comp_xfunc", quote=False
+ )
+
+ # test precondition
+ assert_bash_exec(
+ bash, "! declare -F _comp_xfunc_non_standard_name &>/dev/null"
+ )
+
+ # first invocation (completion/xfunc-test2 is sourced)
+ output = assert_bash_exec(
+ bash,
+ "_comp_xfunc xfunc-test2 _comp_xfunc_non_standard_name 'a b' cde fgh",
+ want_output=True,
+ )
+ assert output.strip() == "util2[<a b><cde><fgh>]"
+
+ # test precondition
+ assert_bash_exec(
+ bash, "declare -F _comp_xfunc_non_standard_name &>/dev/null"
+ )
+
+ # second invocation (completion/xfunc-test2 is not sourced)
+ output = assert_bash_exec(
+ bash,
+ "_comp_xfunc xfunc-test2 _comp_xfunc_non_standard_name 'a b' cde fgh",
+ want_output=True,
+ )
+ assert output.strip() == "util2[<a b><cde><fgh>]"
diff --git a/test/t/unit/test_unit_xinetd_services.py b/test/t/unit/test_unit_xinetd_services.py
index 7a90cb7..a9e33a9 100644
--- a/test/t/unit/test_unit_xinetd_services.py
+++ b/test/t/unit/test_unit_xinetd_services.py
@@ -6,17 +6,19 @@ from conftest import assert_bash_exec
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
class TestUnitXinetdServices:
def test_direct(self, bash):
- assert_bash_exec(bash, "_xinetd_services >/dev/null")
+ assert_bash_exec(bash, "_comp_compgen_xinetd_services >/dev/null")
def test_env_non_pollution(self, bash):
"""Test environment non-pollution, detected at teardown."""
- assert_bash_exec(bash, "foo() { _xinetd_services; }; foo; unset foo")
+ assert_bash_exec(
+ bash, "foo() { _comp_compgen_xinetd_services; }; foo; unset -f foo"
+ )
def test_basic(self, bash):
output = assert_bash_exec(
bash,
- "foo() { local BASHCOMP_XINETDDIR=$PWD/shared/bin;unset COMPREPLY; "
- '_xinetd_services; printf "%s\\n" "${COMPREPLY[@]}"; }; foo; unset foo',
+ "foo() { local _comp__test_xinetd_dir=$PWD/shared/bin; unset -v COMPREPLY; "
+ '_comp_compgen_xinetd_services; printf "%s\\n" "${COMPREPLY[@]}"; }; foo; unset -f foo',
want_output=True,
)
assert sorted(output.split()) == ["arp", "ifconfig"]