summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/.dockerignore1
-rw-r--r--test/.gitignore2
-rw-r--r--test/Makefile.am8
-rw-r--r--test/config/bashrc40
-rw-r--r--test/docker/Dockerfile6
-rw-r--r--test/docker/alpine/Dockerfile23
-rwxr-xr-xtest/docker/alpine/install-packages.sh14
-rw-r--r--test/docker/centos7/Dockerfile27
-rwxr-xr-xtest/docker/centos7/install-packages.sh15
-rw-r--r--test/docker/debian10/Dockerfile20
-rwxr-xr-xtest/docker/debian10/install-packages.sh113
-rwxr-xr-xtest/docker/docker-script.sh16
-rwxr-xr-xtest/docker/entrypoint.sh22
-rw-r--r--test/docker/fedoradev/Dockerfile22
-rwxr-xr-xtest/docker/fedoradev/install-packages.sh25
-rw-r--r--test/docker/ubuntu14/Dockerfile24
-rwxr-xr-xtest/docker/ubuntu14/install-packages.sh69
-rw-r--r--test/fallback/Makefile.am7
-rw-r--r--test/fallback/completions/Makefile.am53
-rw-r--r--test/fallback/completions/README.md8
l---------test/fallback/completions/adb1
l---------test/fallback/completions/cal1
l---------test/fallback/completions/cargo1
l---------test/fallback/completions/chfn1
l---------test/fallback/completions/chsh1
l---------test/fallback/completions/dmesg1
l---------test/fallback/completions/eject1
l---------test/fallback/completions/flamegraph1
l---------test/fallback/completions/gaiacli1
l---------test/fallback/completions/gh1
l---------test/fallback/completions/golangci-lint1
l---------test/fallback/completions/gsctl1
l---------test/fallback/completions/hexdump1
l---------test/fallback/completions/hwclock1
l---------test/fallback/completions/ionice1
l---------test/fallback/completions/jungle1
l---------test/fallback/completions/keyring1
l---------test/fallback/completions/kontena1
l---------test/fallback/completions/look1
l---------test/fallback/completions/mdbook1
l---------test/fallback/completions/mock1
l---------test/fallback/completions/modules1
l---------test/fallback/completions/mount1
l---------test/fallback/completions/mount.linux1
l---------test/fallback/completions/newgrp1
l---------test/fallback/completions/nmcli1
l---------test/fallback/completions/nox1
l---------test/fallback/completions/nvm1
l---------test/fallback/completions/pip1
l---------test/fallback/completions/pipenv1
l---------test/fallback/completions/renice1
l---------test/fallback/completions/repomanage1
l---------test/fallback/completions/reptyr1
l---------test/fallback/completions/rfkill1
l---------test/fallback/completions/rtcwake1
l---------test/fallback/completions/ruff1
l---------test/fallback/completions/runuser1
l---------test/fallback/completions/rustup1
l---------test/fallback/completions/slackpkg1
l---------test/fallback/completions/su1
l---------test/fallback/completions/svn1
l---------test/fallback/completions/svnadmin1
l---------test/fallback/completions/svnlook1
l---------test/fallback/completions/tokio-console1
l---------test/fallback/completions/udevadm1
l---------test/fallback/completions/umount1
l---------test/fallback/completions/umount.linux1
l---------test/fallback/completions/vault1
l---------test/fallback/completions/write1
l---------test/fallback/completions/xm1
l---------test/fallback/completions/yq1
l---------test/fallback/completions/yum1
-rwxr-xr-xtest/fallback/update-fallback-links20
-rw-r--r--test/fixtures/7z/hello.7z.001bin0 -> 100 bytes
-rw-r--r--test/fixtures/7z/hello.7z.002bin0 -> 24 bytes
-rwxr-xr-xtest/fixtures/_command_offset/completer30
-rw-r--r--test/fixtures/_comp_compgen/completions/compgen-cmd119
-rw-r--r--test/fixtures/_comp_compgen/completions/compgen-cmd211
l---------test/fixtures/_comp_load/bin/cmd11
l---------test/fixtures/_comp_load/bin/cmd21
-rwxr-xr-xtest/fixtures/_comp_load/prefix1/bin/cmd11
-rwxr-xr-xtest/fixtures/_comp_load/prefix1/bin/sh1
-rwxr-xr-xtest/fixtures/_comp_load/prefix1/sbin/cmd21
-rw-r--r--test/fixtures/_comp_load/prefix1/share/bash-completion/completions/cmd12
-rw-r--r--test/fixtures/_comp_load/prefix1/share/bash-completion/completions/cmd22
-rw-r--r--test/fixtures/_comp_load/prefix1/share/bash-completion/completions/sh1
-rw-r--r--test/fixtures/_comp_load/userdir1/completions/cmd12
-rw-r--r--test/fixtures/_comp_load/userdir2/completions/cmd22
-rw-r--r--test/fixtures/_comp_xfunc/completions/xfunc-test112
-rw-r--r--test/fixtures/_comp_xfunc/completions/xfunc-test212
-rw-r--r--test/fixtures/_known_hosts/.ssh/config_asterisk_1 (renamed from test/fixtures/_known_hosts_real/.ssh/config_asterisk_1)0
-rw-r--r--test/fixtures/_known_hosts/.ssh/config_asterisk_2 (renamed from test/fixtures/_known_hosts_real/.ssh/config_asterisk_2)0
-rw-r--r--test/fixtures/_known_hosts/.ssh/config_question_mark (renamed from test/fixtures/_known_hosts_real/.ssh/config_question_mark)0
-rw-r--r--test/fixtures/_known_hosts/.ssh/config_relative_path (renamed from test/fixtures/_known_hosts_real/.ssh/config_relative_path)0
-rw-r--r--test/fixtures/_known_hosts/config9
-rw-r--r--test/fixtures/_known_hosts/config_full_path (renamed from test/fixtures/_known_hosts_real/config_full_path)0
-rw-r--r--test/fixtures/_known_hosts/config_include (renamed from test/fixtures/_known_hosts_real/config_include)4
-rw-r--r--test/fixtures/_known_hosts/config_include_recursion (renamed from test/fixtures/_known_hosts_real/config_include_recursion)0
-rw-r--r--test/fixtures/_known_hosts/config_tilde4
-rw-r--r--test/fixtures/_known_hosts/gee-filename-canary (renamed from test/fixtures/_known_hosts_real/gee-filename-canary)0
-rw-r--r--test/fixtures/_known_hosts/known_hosts (renamed from test/fixtures/_known_hosts_real/known_hosts)0
-rw-r--r--test/fixtures/_known_hosts/known_hosts2 (renamed from test/fixtures/_known_hosts_real/known_hosts2)0
-rw-r--r--test/fixtures/_known_hosts/known_hosts3 (renamed from test/fixtures/_known_hosts_real/known_hosts3)0
-rw-r--r--test/fixtures/_known_hosts/known_hosts4 (renamed from test/fixtures/_known_hosts_real/known_hosts4)0
-rw-r--r--test/fixtures/_known_hosts/localhost_config1
-rw-r--r--test/fixtures/_known_hosts/localhost_hosts (renamed from test/fixtures/_known_hosts_real/localhost_hosts)0
-rw-r--r--test/fixtures/_known_hosts/spaced conf8
-rw-r--r--test/fixtures/_known_hosts/spaced known_hosts (renamed from test/fixtures/_known_hosts_real/spaced known_hosts)0
-rw-r--r--test/fixtures/_known_hosts_real/config7
-rw-r--r--test/fixtures/_known_hosts_real/config_tilde4
-rw-r--r--test/fixtures/_known_hosts_real/localhost_config1
-rw-r--r--test/fixtures/_known_hosts_real/spaced conf8
-rw-r--r--test/fixtures/ant/.gitignore2
-rw-r--r--test/fixtures/ant/imported-build.xml2
-rw-r--r--test/fixtures/apt-mark/example.conf (renamed from test/fixtures/evince/.BMP)0
-rw-r--r--test/fixtures/bsdtar/test.pax (renamed from test/fixtures/evince/.CBR)0
-rw-r--r--test/fixtures/bsdtar/test.rar (renamed from test/fixtures/evince/.CBZ)0
-rw-r--r--test/fixtures/bsdtar/test.shar (renamed from test/fixtures/evince/.DJV)0
-rw-r--r--test/fixtures/dot/test1.gv (renamed from test/fixtures/evince/.DJVU)0
-rw-r--r--test/fixtures/dot/test2.dot (renamed from test/fixtures/evince/.DVI)0
-rw-r--r--test/fixtures/evince/.DVI.gz0
-rw-r--r--test/fixtures/evince/.EPS0
-rw-r--r--test/fixtures/evince/.EPS.BZ20
-rw-r--r--test/fixtures/evince/.EPS.GZ0
-rw-r--r--test/fixtures/evince/.EPS.bz20
-rw-r--r--test/fixtures/evince/.EPS.gz0
-rw-r--r--test/fixtures/evince/.GIF0
-rw-r--r--test/fixtures/evince/.ICO0
-rw-r--r--test/fixtures/evince/.JPEG0
-rw-r--r--test/fixtures/evince/.JPG0
-rw-r--r--test/fixtures/evince/.MIFF0
-rw-r--r--test/fixtures/evince/.PBM0
-rw-r--r--test/fixtures/evince/.PCX0
-rw-r--r--test/fixtures/evince/.PDF0
-rw-r--r--test/fixtures/evince/.PDF.BZ20
-rw-r--r--test/fixtures/evince/.PDF.GZ0
-rw-r--r--test/fixtures/evince/.PDF.bz20
-rw-r--r--test/fixtures/evince/.PDF.gz0
-rw-r--r--test/fixtures/evince/.PGM0
-rw-r--r--test/fixtures/evince/.PNG0
-rw-r--r--test/fixtures/evince/.PNM0
-rw-r--r--test/fixtures/evince/.PPM0
-rw-r--r--test/fixtures/evince/.PS0
-rw-r--r--test/fixtures/evince/.PS.BZ20
-rw-r--r--test/fixtures/evince/.PS.GZ0
-rw-r--r--test/fixtures/evince/.PS.bz20
-rw-r--r--test/fixtures/evince/.PS.gz0
-rw-r--r--test/fixtures/evince/.TGA0
-rw-r--r--test/fixtures/evince/.TIF0
-rw-r--r--test/fixtures/evince/.TIFF0
-rw-r--r--test/fixtures/evince/.XPM0
-rw-r--r--test/fixtures/evince/.XWD0
-rw-r--r--test/fixtures/evince/.bmp0
-rw-r--r--test/fixtures/evince/.cbr0
-rw-r--r--test/fixtures/evince/.cbz0
-rw-r--r--test/fixtures/evince/.djv0
-rw-r--r--test/fixtures/evince/.djvu0
-rw-r--r--test/fixtures/evince/.dvi0
-rw-r--r--test/fixtures/evince/.dvi.BZ20
-rw-r--r--test/fixtures/evince/.dvi.GZ0
-rw-r--r--test/fixtures/evince/.dvi.bz20
-rw-r--r--test/fixtures/evince/.dvi.gz0
-rw-r--r--test/fixtures/evince/.eps0
-rw-r--r--test/fixtures/evince/.eps.BZ20
-rw-r--r--test/fixtures/evince/.eps.GZ0
-rw-r--r--test/fixtures/evince/.eps.bz20
-rw-r--r--test/fixtures/evince/.eps.gz0
-rw-r--r--test/fixtures/evince/.gif0
-rw-r--r--test/fixtures/evince/.ico0
-rw-r--r--test/fixtures/evince/.jpeg0
-rw-r--r--test/fixtures/evince/.jpg0
-rw-r--r--test/fixtures/evince/.miff0
-rw-r--r--test/fixtures/evince/.pbm0
-rw-r--r--test/fixtures/evince/.pcx0
-rw-r--r--test/fixtures/evince/.pdf0
-rw-r--r--test/fixtures/evince/.pdf.BZ20
-rw-r--r--test/fixtures/evince/.pdf.GZ0
-rw-r--r--test/fixtures/evince/.pdf.bz20
-rw-r--r--test/fixtures/evince/.pdf.gz0
-rw-r--r--test/fixtures/evince/.pgm0
-rw-r--r--test/fixtures/evince/.png0
-rw-r--r--test/fixtures/evince/.pnm0
-rw-r--r--test/fixtures/evince/.ppm0
-rw-r--r--test/fixtures/evince/.ps0
-rw-r--r--test/fixtures/evince/.ps.BZ20
-rw-r--r--test/fixtures/evince/.ps.GZ0
-rw-r--r--test/fixtures/evince/.ps.bz20
-rw-r--r--test/fixtures/evince/.ps.gz0
-rw-r--r--test/fixtures/evince/.tga0
-rw-r--r--test/fixtures/evince/.tif0
-rw-r--r--test/fixtures/evince/.tiff0
-rw-r--r--test/fixtures/evince/.txt0
-rw-r--r--test/fixtures/evince/.xpm0
-rw-r--r--test/fixtures/evince/.xwd0
-rw-r--r--test/fixtures/evince/foo/.gitignore0
-rw-r--r--test/fixtures/kdvi/.DVI0
-rw-r--r--test/fixtures/kdvi/.DVI.Z0
-rw-r--r--test/fixtures/kdvi/.DVI.bz20
-rw-r--r--test/fixtures/kdvi/.DVI.gz0
-rw-r--r--test/fixtures/kdvi/.dvi0
-rw-r--r--test/fixtures/kdvi/.dvi.Z0
-rw-r--r--test/fixtures/kdvi/.dvi.bz20
-rw-r--r--test/fixtures/kdvi/.dvi.gz0
-rw-r--r--test/fixtures/kdvi/.txt0
-rw-r--r--test/fixtures/kdvi/foo/.gitignore0
-rw-r--r--test/fixtures/kpdf/.EPS0
-rw-r--r--test/fixtures/kpdf/.PDF0
-rw-r--r--test/fixtures/kpdf/.PS0
-rw-r--r--test/fixtures/kpdf/.eps0
-rw-r--r--test/fixtures/kpdf/.pdf0
-rw-r--r--test/fixtures/kpdf/.ps0
-rw-r--r--test/fixtures/kpdf/.txt0
-rw-r--r--test/fixtures/kpdf/foo/.gitignore0
-rw-r--r--test/fixtures/make/.gitignore2
-rw-r--r--test/fixtures/make/test2/Makefile23
-rw-r--r--test/fixtures/man/man3/bash-completion-zstd-testcase.3head.zstbin0 -> 13 bytes
-rw-r--r--test/fixtures/pkgtools/ports/.gitignore4
-rw-r--r--test/fixtures/python/bar.txt (renamed from test/fixtures/evince/.DVI.BZ2)0
-rw-r--r--test/fixtures/python/foo.py (renamed from test/fixtures/evince/.DVI.GZ)0
-rw-r--r--test/fixtures/sha256sum/foo (renamed from test/fixtures/evince/.DVI.bz2)0
-rw-r--r--test/fixtures/sha256sum/foo.sha2561
-rw-r--r--test/fixtures/shells/etc/shells2
-rwxr-xr-xtest/fixtures/xrandr/xrandr207
-rwxr-xr-xtest/generate2
-rw-r--r--test/requirements-dev.txt8
-rw-r--r--test/requirements.txt6
-rwxr-xr-xtest/runLint51
-rw-r--r--test/setup.cfg15
-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
-rw-r--r--test/test-cmd-list.txt267
-rwxr-xr-xtest/update-test-cmd-list9
410 files changed, 4997 insertions, 980 deletions
diff --git a/test/.dockerignore b/test/.dockerignore
new file mode 100644
index 0000000..61f2dc9
--- /dev/null
+++ b/test/.dockerignore
@@ -0,0 +1 @@
+**/__pycache__/
diff --git a/test/.gitignore b/test/.gitignore
deleted file mode 100644
index c428b94..0000000
--- a/test/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-tmp/
-pytestdebug.log
diff --git a/test/Makefile.am b/test/Makefile.am
index 591c8f7..aaf32e0 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,14 +1,8 @@
-SUBDIRS = t
+SUBDIRS = fallback t
EXTRA_DIST = config \
fixtures \
setup.cfg
-all:
- $(MKDIR_P) tmp
-
CLEANFILES = \
fixtures/make/extra_makefile
-
-clean-local:
- $(RM) -rf tmp
diff --git a/test/config/bashrc b/test/config/bashrc
index 141dddc..dcb8aac 100644
--- a/test/config/bashrc
+++ b/test/config/bashrc
@@ -33,20 +33,26 @@ export BASH_COMPLETION_USER_FILE=/dev/null
# ...and avoid stuff in BASH_COMPLETION_USER_DIR and system install locations
# overriding in-tree completions. Setting the user dir would otherwise suffice,
# but simple xspec completions are only installed if a separate one is not
-# found in any completion dirs. Therefore we also point the "system" dirs to
-# locations that should not yield valid completions and helpers paths either.
-export BASH_COMPLETION_USER_DIR=$(
- cd "$SRCDIR/.." || exit 1
- pwd
-)
-# /var/empty isn't necessarily actually always empty :P
-export BASH_COMPLETION_COMPAT_DIR=/var/empty/bash_completion.d
+# found in *any* completion dirs, and we want to use our "shadow" completion
+# dir with which we cause loading of our in-tree fallback completions
+# instead of possibly (system-)installed upstream ones.
+export BASH_COMPLETION_USER_DIR="$SRCDIRABS/fallback"
+export BASH_COMPLETION_COMPAT_DIR="$SRCDIRABS/../bash_completion.d"
export XDG_DATA_DIRS=/var/empty
# Make sure default settings are in effect
unset -v \
+ BASH_COMPLETION_CMD_CONFIGURE_HINTS \
+ BASH_COMPLETION_CMD_CVS_REMOTE \
+ BASH_COMPLETION_CMD_IWCONFIG_SCAN \
+ BASH_COMPLETION_CMD_TAR_INTERNAL_PATHS \
+ BASH_COMPLETION_COMPAT_IGNORE \
+ BASH_COMPLETION_FILEDIR_FALLBACK \
+ BASH_COMPLETION_KNOWN_HOSTS_WITH_HOSTFILE \
COMP_CONFIGURE_HINTS \
COMP_CVS_REMOTE \
+ COMP_FILEDIR_FALLBACK \
+ COMP_IWLIST_SCAN \
COMP_KNOWN_HOSTS_WITH_HOSTFILE \
COMP_TAR_INTERNAL_PATHS
@@ -56,6 +62,24 @@ add_comp_wordbreak_char()
[[ "${COMP_WORDBREAKS//[^$1]/}" ]] || COMP_WORDBREAKS+=$1
}
+_comp__test_get_env()
+{
+ (
+ # Do not output the state of test variables "_comp__test_+([0-9])_*"
+ # and internal mutable variables "_comp_*_mut_*".
+ local IFS=$' \t\n'
+ # shellcheck disable=SC2046
+ unset -v $(compgen -W '"${!_comp_@}"' -X '!_comp_@(_test_+([0-9])_*|*_mut_*)')
+ _comp_unlocal IFS
+
+ set -o posix
+ set
+ )
+ declare -F
+ shopt -p
+ set -o
+}
+
# Local variables:
# mode: shell-script
# End:
diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile
deleted file mode 100644
index 200f918..0000000
--- a/test/docker/Dockerfile
+++ /dev/null
@@ -1,6 +0,0 @@
-ARG DIST
-FROM vskytta/bash-completion:$DIST
-
-WORKDIR /work
-COPY . .
-CMD ["test/docker/docker-script.sh"]
diff --git a/test/docker/alpine/Dockerfile b/test/docker/alpine/Dockerfile
new file mode 100644
index 0000000..e4d9f3b
--- /dev/null
+++ b/test/docker/alpine/Dockerfile
@@ -0,0 +1,23 @@
+FROM alpine
+
+RUN apk add --no-cache \
+ autoconf \
+ automake \
+ bash \
+ gcc \
+ make \
+ musl-dev \
+ py3-pexpect \
+ py3-pytest-xdist \
+ tar \
+ xvfb \
+ xvfb-run \
+ xz
+
+# test-cmd-list.txt is just a cache buster here
+ADD test-cmd-list.txt \
+ docker/alpine/install-packages.sh \
+ /tmp/
+
+RUN /tmp/install-packages.sh \
+ && rm -r /tmp/*
diff --git a/test/docker/alpine/install-packages.sh b/test/docker/alpine/install-packages.sh
new file mode 100755
index 0000000..7865703
--- /dev/null
+++ b/test/docker/alpine/install-packages.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -xeuo pipefail
+
+cd "${TMPDIR:-/tmp}"
+
+apk upgrade
+
+# Nothing much here, at least yet. Useful this way already in order to
+# test some very base executables, as well as ones that come from
+# busybox. Don't lose that if adding stuff here!
+
+# An arbitrary package containing an init script or the like for
+# testing service completion.
+apk add nginx-openrc
diff --git a/test/docker/centos7/Dockerfile b/test/docker/centos7/Dockerfile
new file mode 100644
index 0000000..c0611e7
--- /dev/null
+++ b/test/docker/centos7/Dockerfile
@@ -0,0 +1,27 @@
+FROM centos:7
+
+RUN set -x \
+ && sed -i -e /tsflags=nodocs/d /etc/yum.conf \
+ && yum -y install \
+ https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
+ && yum -y upgrade \
+ && yum -y install \
+ /usr/bin/autoconf \
+ /usr/bin/automake \
+ /usr/bin/make \
+ # /usr/bin/which: https://bugzilla.redhat.com/show_bug.cgi?id=1443357 \
+ /usr/bin/which \
+ /usr/bin/xvfb-run \
+ python36-pexpect
+
+ADD test-cmd-list.txt \
+ requirements.txt \
+ docker/centos7/install-packages.sh \
+ /tmp/
+
+RUN set -x \
+ && pip3 install --prefix /usr/local -Ir /tmp/requirements.txt
+
+RUN /tmp/install-packages.sh </tmp/test-cmd-list.txt \
+ && yum -Cy clean all \
+ && rm -r /tmp/* /root/.cache/pip /var/lib/yum/history/* /var/lib/yum/yumdb/*
diff --git a/test/docker/centos7/install-packages.sh b/test/docker/centos7/install-packages.sh
new file mode 100755
index 0000000..7373c00
--- /dev/null
+++ b/test/docker/centos7/install-packages.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+set -xeuo pipefail
+
+shopt -s extglob
+
+cd "${TMPDIR:-/tmp}"
+
+while read -r file; do
+ case $file in
+ mock | */mock) printf "%s\n" mock ;;
+ /*) printf "%s\n" "$file" ;;
+ *) printf "%s\n" {/usr,}/{,s}bin/"$file" ;;
+ esac
+done |
+ xargs yum -y install
diff --git a/test/docker/debian10/Dockerfile b/test/docker/debian10/Dockerfile
new file mode 100644
index 0000000..1a19fa3
--- /dev/null
+++ b/test/docker/debian10/Dockerfile
@@ -0,0 +1,20 @@
+FROM debian:10
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+ && apt-get update \
+ && apt-get -y upgrade \
+ && apt-get -y --no-install-recommends install \
+ autoconf \
+ automake \
+ make \
+ python3-pexpect \
+ python3-pytest-xdist \
+ xvfb xauth \
+ && ln -s $(bash -c "type -P pytest-3") /usr/local/bin/pytest
+
+ADD test-cmd-list.txt \
+ docker/debian10/install-packages.sh \
+ /tmp/
+
+RUN /tmp/install-packages.sh </tmp/test-cmd-list.txt \
+ && rm -r /tmp/* /var/lib/apt/lists/*
diff --git a/test/docker/debian10/install-packages.sh b/test/docker/debian10/install-packages.sh
new file mode 100755
index 0000000..20d6556
--- /dev/null
+++ b/test/docker/debian10/install-packages.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+set -xeuo pipefail
+
+cd "${TMPDIR:-/tmp}"
+
+shopt -s extglob
+export DEBIAN_FRONTEND=noninteractive
+
+dpkg --add-architecture i386 # for wine
+
+apt-get update
+apt-get -y upgrade
+
+apt-get -y --no-install-recommends install \
+ apt-file \
+ software-properties-common
+
+apt-add-repository contrib
+apt-add-repository non-free
+
+apt-get -y --no-install-recommends install \
+ npm
+
+npm install -g jshint
+npm cache clean --force
+
+apt-file update
+
+excluded=$(
+ cat <<\EOF
+arping
+bcron
+bison++
+fuse
+gdb-minimal
+gnat-7
+ifupdown
+inetutils-ping
+lpr
+lprng
+make-guile
+netscript-2.4
+ntpsec-ntpdate
+openresolv
+pkg-config
+strongswan-starter
+sudo-ldap
+systemd-cron
+EOF
+)
+
+# https://github.com/moby/moby/issues/1297
+echo "resolvconf resolvconf/linkify-resolvconf boolean false" |
+ debconf-set-selections
+
+# Work around https://bugs.debian.org/1040925
+apt-get -y --no-install-recommends install \
+ ca-certificates-java
+
+while read -r file; do
+ case $file in
+ /*) printf "%s\n" "$file" ;;
+ *) printf "%s\n" {/usr,}/{,s}bin/"$file" ;;
+ esac
+done |
+ apt-file -lFf search - |
+ grep -vF "$excluded" |
+ xargs apt-get -y --no-install-recommends install
+
+# Required but not pulled in by dependencies:
+apt-get -y --no-install-recommends install \
+ postgresql-client
+
+# Build some *BSD tools for testing
+
+apt-get -y --no-install-recommends install \
+ build-essential
+
+install -dm 755 /usr/local/lib/bsd-bin
+apt-get -y --no-install-recommends install bison libbsd-dev subversion
+
+svn co https://svn.freebsd.org/base/release/11.1.0/usr.bin/sed bsd-sed
+cd bsd-sed
+sed -i -e 's,^__FBSDID.*,#include <bsd/bsd.h>,' ./*.c
+cc -O2 -g -Wall -Wno-unused-const-variable -D_GNU_SOURCE ./*.c \
+ -lbsd -o /usr/local/lib/bsd-bin/sed
+cd ..
+rm -r bsd-sed
+
+svn co https://svn.freebsd.org/base/release/11.1.0/contrib/one-true-awk
+cd one-true-awk
+sed -i -e /^__FBSDID/d ./*.c
+make YACC="bison -d -y"
+install a.out /usr/local/lib/bsd-bin/awk
+cd ..
+rm -r one-true-awk
+
+# Install slapt-get and slapt-src
+
+cd /
+curl --fail https://software.jaos.org/slackpacks/slackware64-14.2/slapt-get/slapt-get-0.11.3-x86_64-1.txz |
+ tar xvJ
+bash -x install/doinst.sh
+mkdir -p var/lib/pkgtools/packages # 0.11.3 --available empty without this dir
+rm -r install
+curl --fail https://software.jaos.org/slackpacks/slackware64-14.2/slapt-src/slapt-src-0.3.6-x86_64-1.txz |
+ tar xvJ
+bash -x install/doinst.sh
+rm -r install
+cp -a usr/lib64/* usr/lib/
+ln -s libcrypto.so.1.1 usr/lib/x86_64-linux-gnu/libcrypto.so.1
+rm -r usr/lib64
+cd -
diff --git a/test/docker/docker-script.sh b/test/docker/docker-script.sh
deleted file mode 100755
index b3f351f..0000000
--- a/test/docker/docker-script.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh -ex
-
-if [ "$BSD" ]; then
- PATH=/usr/local/lib/bsd-bin:$PATH
- export PATH
-fi
-
-export bashcomp_bash=bash
-env
-
-autoreconf -i
-./configure
-make -j
-
-xvfb-run make distcheck \
- PYTESTFLAGS="--verbose --numprocesses=auto --dist=loadfile"
diff --git a/test/docker/entrypoint.sh b/test/docker/entrypoint.sh
new file mode 100755
index 0000000..c647782
--- /dev/null
+++ b/test/docker/entrypoint.sh
@@ -0,0 +1,22 @@
+#!/bin/sh -eux
+# shellcheck shell=sh
+
+if [ "${BSD-}" ]; then
+ PATH=/usr/local/lib/bsd-bin:$PATH
+ export PATH
+fi
+
+export bashcomp_bash=bash
+env
+
+oldpwd=$(pwd)
+cp -a . /work
+cd /work
+
+autoreconf -i
+./configure
+make -j
+
+xvfb-run make distcheck \
+ PYTESTFLAGS="${PYTESTFLAGS---verbose -p no:cacheprovider --numprocesses=auto --dist=loadfile}"
+cp -p bash-completion-*.tar.* "$oldpwd/"
diff --git a/test/docker/fedoradev/Dockerfile b/test/docker/fedoradev/Dockerfile
new file mode 100644
index 0000000..7919ac2
--- /dev/null
+++ b/test/docker/fedoradev/Dockerfile
@@ -0,0 +1,22 @@
+FROM fedora:rawhide
+
+RUN echo install_weak_deps=False >> /etc/dnf/dnf.conf \
+ && sed -i -e /tsflags=nodocs/d /etc/dnf/dnf.conf \
+ && dnf -y --refresh upgrade \
+ && dnf -y install \
+ /usr/bin/autoconf \
+ /usr/bin/automake \
+ /usr/bin/make \
+ /usr/bin/xvfb-run \
+ /usr/bin/pytest-3 \
+ python3-pexpect \
+ python3-pytest-xdist \
+ && ln -s $(type -P pytest-3) /usr/local/bin/pytest
+
+ADD test-cmd-list.txt \
+ docker/fedoradev/install-packages.sh \
+ /tmp/
+
+RUN /tmp/install-packages.sh </tmp/test-cmd-list.txt \
+ && dnf -Cy clean all \
+ && rm -r /tmp/* /var/lib/dnf/history.sqlite* /var/lib/dnf/repos/*
diff --git a/test/docker/fedoradev/install-packages.sh b/test/docker/fedoradev/install-packages.sh
new file mode 100755
index 0000000..70d143c
--- /dev/null
+++ b/test/docker/fedoradev/install-packages.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -xeuo pipefail
+
+shopt -s extglob
+
+cd "${TMPDIR:-/tmp}"
+
+# upgrade: base image contains vim-minimal, newer vim-* which
+# implicitly conflicts with it (typically vim.1.gz) may be in
+# repository and pulled in further down, causing install to fail as
+# -minimal won't be updated otherwise.
+dnf --refresh -y upgrade
+
+dnf -y install /usr/bin/xargs
+
+while read -r file; do
+ case $file in
+ /*) printf "%s\n" "$file" ;;
+ *) printf "%s\n" {/usr,}/{,s}bin/"$file" ;;
+ esac
+done |
+ xargs dnf --skip-broken -y install
+# --skip-broken: avoid failing on not found packages. Also prevents actually
+# broken packages from failing the install which is not what we want, but
+# there doesn't seem to be way to cleanly just skip the not found ones.
diff --git a/test/docker/ubuntu14/Dockerfile b/test/docker/ubuntu14/Dockerfile
new file mode 100644
index 0000000..6a8a1d5
--- /dev/null
+++ b/test/docker/ubuntu14/Dockerfile
@@ -0,0 +1,24 @@
+FROM ubuntu:14.04
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+ && apt-get update \
+ && apt-get -y upgrade \
+ && apt-get -y --no-install-recommends install \
+ autoconf \
+ automake \
+ make \
+ software-properties-common \
+ xvfb \
+ && python3.4 -c "import urllib.request; urllib.request.urlretrieve('https://github.com/pyston/pyston/releases/download/pyston_2.3.1/pyston_2.3.1_portable_v2.tar.gz', '/tmp/pyston.tar.gz')" \
+ && tar xCf /usr/local /tmp/pyston.tar.gz --strip-components=1
+
+ADD test-cmd-list.txt \
+ requirements.txt \
+ docker/ubuntu14/install-packages.sh \
+ /tmp/
+
+RUN set -x \
+ && pyston3 -m pip install -Ir /tmp/requirements.txt
+
+RUN /tmp/install-packages.sh </tmp/test-cmd-list.txt \
+ && rm -r /tmp/* /root/.cache/pip /var/lib/apt/lists/*
diff --git a/test/docker/ubuntu14/install-packages.sh b/test/docker/ubuntu14/install-packages.sh
new file mode 100755
index 0000000..c0e3c9c
--- /dev/null
+++ b/test/docker/ubuntu14/install-packages.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+set -xeuo pipefail
+
+cd "${TMPDIR:-/tmp}"
+
+shopt -s extglob
+export DEBIAN_FRONTEND=noninteractive
+
+dpkg --add-architecture i386 # for wine
+
+apt-get update
+apt-get -y upgrade
+
+apt-get -y --no-install-recommends install \
+ apt-file \
+ software-properties-common
+
+apt-add-repository multiverse
+
+apt-file update
+
+excluded=$(
+ cat <<\EOF
+arping
+bcron-run
+bison++
+evince-gtk
+gdb-minimal
+gnat-4.6
+gnuspool
+heimdal
+inetutils-ping
+knot-dnsutils
+knot-host
+lpr
+lprng
+mariadb-client-5.5
+mariadb-client-core-5.5
+mplayer2
+mysql-client-5.5
+mysql-client-core-5.5
+netscript-2.4
+openresolv
+percona-xtradb-cluster-client-5.5
+postgres-xc-client
+python3.5-venv
+strongswan-starter
+sudo-ldap
+xserver-xorg-input-synaptics-lts-utopic
+xserver-xorg-input-synaptics-lts-vivid
+xserver-xorg-input-synaptics-lts-wily
+xserver-xorg-input-synaptics-lts-xenial
+EOF
+)
+
+while read -r file; do
+ case $file in
+ /*) printf "%s\n" "$file" ;;
+ *) printf "%s\n" {/usr,}/{,s}bin/"$file" ;;
+ esac
+done |
+ apt-file -lFf search - |
+ grep -vF "$excluded" |
+ xargs apt-get -y --no-install-recommends install
+
+# Required but not pulled in by dependencies:
+apt-get -y --no-install-recommends install \
+ libwww-perl \
+ postgresql-client
diff --git a/test/fallback/Makefile.am b/test/fallback/Makefile.am
new file mode 100644
index 0000000..096a196
--- /dev/null
+++ b/test/fallback/Makefile.am
@@ -0,0 +1,7 @@
+SUBDIRS = completions
+
+EXTRA_DIST = \
+ update-fallback-links
+
+update:
+ ./update-fallback-links
diff --git a/test/fallback/completions/Makefile.am b/test/fallback/completions/Makefile.am
new file mode 100644
index 0000000..8efc9cc
--- /dev/null
+++ b/test/fallback/completions/Makefile.am
@@ -0,0 +1,53 @@
+EXTRA_DIST = \
+ adb \
+ cal \
+ cargo \
+ chfn \
+ chsh \
+ dmesg \
+ eject \
+ flamegraph \
+ gaiacli \
+ gh \
+ golangci-lint \
+ gsctl \
+ hexdump \
+ hwclock \
+ ionice \
+ jungle \
+ keyring \
+ kontena \
+ look \
+ mdbook \
+ mock \
+ modules \
+ mount \
+ mount.linux \
+ newgrp \
+ nmcli \
+ nox \
+ nvm \
+ pip \
+ pipenv \
+ renice \
+ repomanage \
+ reptyr \
+ rfkill \
+ rtcwake \
+ ruff \
+ runuser \
+ rustup \
+ slackpkg \
+ su \
+ svn \
+ svnadmin \
+ svnlook \
+ tokio-console \
+ udevadm \
+ umount \
+ umount.linux \
+ vault \
+ write \
+ xm \
+ yq \
+ yum
diff --git a/test/fallback/completions/README.md b/test/fallback/completions/README.md
new file mode 100644
index 0000000..020e4b2
--- /dev/null
+++ b/test/fallback/completions/README.md
@@ -0,0 +1,8 @@
+# test/fallback/completions
+
+This directory should contain a non-underscore prefixed symlink to
+corresponding underscore prefixed, fallback completions we have in the tree.
+
+The test suite sets up loading of completions so that this dir is preferred
+over system install locations, in order to test our fallback in-tree
+completions over possibly installed non-fallback out-of-tree ones.
diff --git a/test/fallback/completions/adb b/test/fallback/completions/adb
new file mode 120000
index 0000000..66ed7eb
--- /dev/null
+++ b/test/fallback/completions/adb
@@ -0,0 +1 @@
+../../../completions/_adb \ No newline at end of file
diff --git a/test/fallback/completions/cal b/test/fallback/completions/cal
new file mode 120000
index 0000000..733c95d
--- /dev/null
+++ b/test/fallback/completions/cal
@@ -0,0 +1 @@
+../../../completions/_cal \ No newline at end of file
diff --git a/test/fallback/completions/cargo b/test/fallback/completions/cargo
new file mode 120000
index 0000000..cdc7c30
--- /dev/null
+++ b/test/fallback/completions/cargo
@@ -0,0 +1 @@
+../../../completions/_cargo \ No newline at end of file
diff --git a/test/fallback/completions/chfn b/test/fallback/completions/chfn
new file mode 120000
index 0000000..dc7726d
--- /dev/null
+++ b/test/fallback/completions/chfn
@@ -0,0 +1 @@
+../../../completions/_chfn \ No newline at end of file
diff --git a/test/fallback/completions/chsh b/test/fallback/completions/chsh
new file mode 120000
index 0000000..e99eb81
--- /dev/null
+++ b/test/fallback/completions/chsh
@@ -0,0 +1 @@
+../../../completions/_chsh \ No newline at end of file
diff --git a/test/fallback/completions/dmesg b/test/fallback/completions/dmesg
new file mode 120000
index 0000000..b41aa72
--- /dev/null
+++ b/test/fallback/completions/dmesg
@@ -0,0 +1 @@
+../../../completions/_dmesg \ No newline at end of file
diff --git a/test/fallback/completions/eject b/test/fallback/completions/eject
new file mode 120000
index 0000000..1992645
--- /dev/null
+++ b/test/fallback/completions/eject
@@ -0,0 +1 @@
+../../../completions/_eject \ No newline at end of file
diff --git a/test/fallback/completions/flamegraph b/test/fallback/completions/flamegraph
new file mode 120000
index 0000000..3b06327
--- /dev/null
+++ b/test/fallback/completions/flamegraph
@@ -0,0 +1 @@
+../../../completions/_flamegraph \ No newline at end of file
diff --git a/test/fallback/completions/gaiacli b/test/fallback/completions/gaiacli
new file mode 120000
index 0000000..4b13535
--- /dev/null
+++ b/test/fallback/completions/gaiacli
@@ -0,0 +1 @@
+../../../completions/_gaiacli \ No newline at end of file
diff --git a/test/fallback/completions/gh b/test/fallback/completions/gh
new file mode 120000
index 0000000..823628f
--- /dev/null
+++ b/test/fallback/completions/gh
@@ -0,0 +1 @@
+../../../completions/_gh \ No newline at end of file
diff --git a/test/fallback/completions/golangci-lint b/test/fallback/completions/golangci-lint
new file mode 120000
index 0000000..b0fa4ef
--- /dev/null
+++ b/test/fallback/completions/golangci-lint
@@ -0,0 +1 @@
+../../../completions/_golangci-lint \ No newline at end of file
diff --git a/test/fallback/completions/gsctl b/test/fallback/completions/gsctl
new file mode 120000
index 0000000..e351e86
--- /dev/null
+++ b/test/fallback/completions/gsctl
@@ -0,0 +1 @@
+../../../completions/_gsctl \ No newline at end of file
diff --git a/test/fallback/completions/hexdump b/test/fallback/completions/hexdump
new file mode 120000
index 0000000..90708a0
--- /dev/null
+++ b/test/fallback/completions/hexdump
@@ -0,0 +1 @@
+../../../completions/_hexdump \ No newline at end of file
diff --git a/test/fallback/completions/hwclock b/test/fallback/completions/hwclock
new file mode 120000
index 0000000..7cb778e
--- /dev/null
+++ b/test/fallback/completions/hwclock
@@ -0,0 +1 @@
+../../../completions/_hwclock \ No newline at end of file
diff --git a/test/fallback/completions/ionice b/test/fallback/completions/ionice
new file mode 120000
index 0000000..4861309
--- /dev/null
+++ b/test/fallback/completions/ionice
@@ -0,0 +1 @@
+../../../completions/_ionice \ No newline at end of file
diff --git a/test/fallback/completions/jungle b/test/fallback/completions/jungle
new file mode 120000
index 0000000..e9069c2
--- /dev/null
+++ b/test/fallback/completions/jungle
@@ -0,0 +1 @@
+../../../completions/_jungle \ No newline at end of file
diff --git a/test/fallback/completions/keyring b/test/fallback/completions/keyring
new file mode 120000
index 0000000..354980b
--- /dev/null
+++ b/test/fallback/completions/keyring
@@ -0,0 +1 @@
+../../../completions/_keyring \ No newline at end of file
diff --git a/test/fallback/completions/kontena b/test/fallback/completions/kontena
new file mode 120000
index 0000000..ceeb17d
--- /dev/null
+++ b/test/fallback/completions/kontena
@@ -0,0 +1 @@
+../../../completions/_kontena \ No newline at end of file
diff --git a/test/fallback/completions/look b/test/fallback/completions/look
new file mode 120000
index 0000000..f59ed26
--- /dev/null
+++ b/test/fallback/completions/look
@@ -0,0 +1 @@
+../../../completions/_look \ No newline at end of file
diff --git a/test/fallback/completions/mdbook b/test/fallback/completions/mdbook
new file mode 120000
index 0000000..70a0f1d
--- /dev/null
+++ b/test/fallback/completions/mdbook
@@ -0,0 +1 @@
+../../../completions/_mdbook \ No newline at end of file
diff --git a/test/fallback/completions/mock b/test/fallback/completions/mock
new file mode 120000
index 0000000..9a7a69a
--- /dev/null
+++ b/test/fallback/completions/mock
@@ -0,0 +1 @@
+../../../completions/_mock \ No newline at end of file
diff --git a/test/fallback/completions/modules b/test/fallback/completions/modules
new file mode 120000
index 0000000..c3fa5e2
--- /dev/null
+++ b/test/fallback/completions/modules
@@ -0,0 +1 @@
+../../../completions/_modules \ No newline at end of file
diff --git a/test/fallback/completions/mount b/test/fallback/completions/mount
new file mode 120000
index 0000000..84a65f1
--- /dev/null
+++ b/test/fallback/completions/mount
@@ -0,0 +1 @@
+../../../completions/_mount \ No newline at end of file
diff --git a/test/fallback/completions/mount.linux b/test/fallback/completions/mount.linux
new file mode 120000
index 0000000..216ae8a
--- /dev/null
+++ b/test/fallback/completions/mount.linux
@@ -0,0 +1 @@
+../../../completions/_mount.linux \ No newline at end of file
diff --git a/test/fallback/completions/newgrp b/test/fallback/completions/newgrp
new file mode 120000
index 0000000..cab15d0
--- /dev/null
+++ b/test/fallback/completions/newgrp
@@ -0,0 +1 @@
+../../../completions/_newgrp \ No newline at end of file
diff --git a/test/fallback/completions/nmcli b/test/fallback/completions/nmcli
new file mode 120000
index 0000000..0400cca
--- /dev/null
+++ b/test/fallback/completions/nmcli
@@ -0,0 +1 @@
+../../../completions/_nmcli \ No newline at end of file
diff --git a/test/fallback/completions/nox b/test/fallback/completions/nox
new file mode 120000
index 0000000..1ff4f70
--- /dev/null
+++ b/test/fallback/completions/nox
@@ -0,0 +1 @@
+../../../completions/_nox \ No newline at end of file
diff --git a/test/fallback/completions/nvm b/test/fallback/completions/nvm
new file mode 120000
index 0000000..bcaccbb
--- /dev/null
+++ b/test/fallback/completions/nvm
@@ -0,0 +1 @@
+../../../completions/_nvm \ No newline at end of file
diff --git a/test/fallback/completions/pip b/test/fallback/completions/pip
new file mode 120000
index 0000000..5fb8a9c
--- /dev/null
+++ b/test/fallback/completions/pip
@@ -0,0 +1 @@
+../../../completions/_pip \ No newline at end of file
diff --git a/test/fallback/completions/pipenv b/test/fallback/completions/pipenv
new file mode 120000
index 0000000..faa7ea3
--- /dev/null
+++ b/test/fallback/completions/pipenv
@@ -0,0 +1 @@
+../../../completions/_pipenv \ No newline at end of file
diff --git a/test/fallback/completions/renice b/test/fallback/completions/renice
new file mode 120000
index 0000000..4a62179
--- /dev/null
+++ b/test/fallback/completions/renice
@@ -0,0 +1 @@
+../../../completions/_renice \ No newline at end of file
diff --git a/test/fallback/completions/repomanage b/test/fallback/completions/repomanage
new file mode 120000
index 0000000..b6feb30
--- /dev/null
+++ b/test/fallback/completions/repomanage
@@ -0,0 +1 @@
+../../../completions/_repomanage \ No newline at end of file
diff --git a/test/fallback/completions/reptyr b/test/fallback/completions/reptyr
new file mode 120000
index 0000000..d67c4b5
--- /dev/null
+++ b/test/fallback/completions/reptyr
@@ -0,0 +1 @@
+../../../completions/_reptyr \ No newline at end of file
diff --git a/test/fallback/completions/rfkill b/test/fallback/completions/rfkill
new file mode 120000
index 0000000..6c3c8f6
--- /dev/null
+++ b/test/fallback/completions/rfkill
@@ -0,0 +1 @@
+../../../completions/_rfkill \ No newline at end of file
diff --git a/test/fallback/completions/rtcwake b/test/fallback/completions/rtcwake
new file mode 120000
index 0000000..244c7f1
--- /dev/null
+++ b/test/fallback/completions/rtcwake
@@ -0,0 +1 @@
+../../../completions/_rtcwake \ No newline at end of file
diff --git a/test/fallback/completions/ruff b/test/fallback/completions/ruff
new file mode 120000
index 0000000..2d6104b
--- /dev/null
+++ b/test/fallback/completions/ruff
@@ -0,0 +1 @@
+../../../completions/_ruff \ No newline at end of file
diff --git a/test/fallback/completions/runuser b/test/fallback/completions/runuser
new file mode 120000
index 0000000..c3e3422
--- /dev/null
+++ b/test/fallback/completions/runuser
@@ -0,0 +1 @@
+../../../completions/_runuser \ No newline at end of file
diff --git a/test/fallback/completions/rustup b/test/fallback/completions/rustup
new file mode 120000
index 0000000..18b256a
--- /dev/null
+++ b/test/fallback/completions/rustup
@@ -0,0 +1 @@
+../../../completions/_rustup \ No newline at end of file
diff --git a/test/fallback/completions/slackpkg b/test/fallback/completions/slackpkg
new file mode 120000
index 0000000..990071a
--- /dev/null
+++ b/test/fallback/completions/slackpkg
@@ -0,0 +1 @@
+../../../completions/_slackpkg \ No newline at end of file
diff --git a/test/fallback/completions/su b/test/fallback/completions/su
new file mode 120000
index 0000000..9e972cd
--- /dev/null
+++ b/test/fallback/completions/su
@@ -0,0 +1 @@
+../../../completions/_su \ No newline at end of file
diff --git a/test/fallback/completions/svn b/test/fallback/completions/svn
new file mode 120000
index 0000000..0bf5b7f
--- /dev/null
+++ b/test/fallback/completions/svn
@@ -0,0 +1 @@
+../../../completions/_svn \ No newline at end of file
diff --git a/test/fallback/completions/svnadmin b/test/fallback/completions/svnadmin
new file mode 120000
index 0000000..90ce5ef
--- /dev/null
+++ b/test/fallback/completions/svnadmin
@@ -0,0 +1 @@
+../../../completions/_svnadmin \ No newline at end of file
diff --git a/test/fallback/completions/svnlook b/test/fallback/completions/svnlook
new file mode 120000
index 0000000..efad3dc
--- /dev/null
+++ b/test/fallback/completions/svnlook
@@ -0,0 +1 @@
+../../../completions/_svnlook \ No newline at end of file
diff --git a/test/fallback/completions/tokio-console b/test/fallback/completions/tokio-console
new file mode 120000
index 0000000..1fb8249
--- /dev/null
+++ b/test/fallback/completions/tokio-console
@@ -0,0 +1 @@
+../../../completions/_tokio-console \ No newline at end of file
diff --git a/test/fallback/completions/udevadm b/test/fallback/completions/udevadm
new file mode 120000
index 0000000..2498b90
--- /dev/null
+++ b/test/fallback/completions/udevadm
@@ -0,0 +1 @@
+../../../completions/_udevadm \ No newline at end of file
diff --git a/test/fallback/completions/umount b/test/fallback/completions/umount
new file mode 120000
index 0000000..77f661e
--- /dev/null
+++ b/test/fallback/completions/umount
@@ -0,0 +1 @@
+../../../completions/_umount \ No newline at end of file
diff --git a/test/fallback/completions/umount.linux b/test/fallback/completions/umount.linux
new file mode 120000
index 0000000..6066bd8
--- /dev/null
+++ b/test/fallback/completions/umount.linux
@@ -0,0 +1 @@
+../../../completions/_umount.linux \ No newline at end of file
diff --git a/test/fallback/completions/vault b/test/fallback/completions/vault
new file mode 120000
index 0000000..abd0ad2
--- /dev/null
+++ b/test/fallback/completions/vault
@@ -0,0 +1 @@
+../../../completions/_vault \ No newline at end of file
diff --git a/test/fallback/completions/write b/test/fallback/completions/write
new file mode 120000
index 0000000..e4d2f2c
--- /dev/null
+++ b/test/fallback/completions/write
@@ -0,0 +1 @@
+../../../completions/_write \ No newline at end of file
diff --git a/test/fallback/completions/xm b/test/fallback/completions/xm
new file mode 120000
index 0000000..1fb8840
--- /dev/null
+++ b/test/fallback/completions/xm
@@ -0,0 +1 @@
+../../../completions/_xm \ No newline at end of file
diff --git a/test/fallback/completions/yq b/test/fallback/completions/yq
new file mode 120000
index 0000000..9ea0a28
--- /dev/null
+++ b/test/fallback/completions/yq
@@ -0,0 +1 @@
+../../../completions/_yq \ No newline at end of file
diff --git a/test/fallback/completions/yum b/test/fallback/completions/yum
new file mode 120000
index 0000000..cd8a7ed
--- /dev/null
+++ b/test/fallback/completions/yum
@@ -0,0 +1 @@
+../../../completions/_yum \ No newline at end of file
diff --git a/test/fallback/update-fallback-links b/test/fallback/update-fallback-links
new file mode 100755
index 0000000..5da1bcb
--- /dev/null
+++ b/test/fallback/update-fallback-links
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+is_tracked_by_git()
+{
+ git ls-files --error-unmatch "$1" &>/dev/null
+}
+
+cd "$(dirname "$0")/completions"
+for f in *; do
+ if [[ -L $f ]] && is_tracked_by_git "$f"; then
+ git rm -f "$f"
+ fi
+done
+for f in ../../../completions/_*; do
+ if is_tracked_by_git "$f"; then
+ ln -sf "$f" "${f##*/_}"
+ git add --verbose "${f##*/_}"
+ fi
+done
diff --git a/test/fixtures/7z/hello.7z.001 b/test/fixtures/7z/hello.7z.001
new file mode 100644
index 0000000..029e36f
--- /dev/null
+++ b/test/fixtures/7z/hello.7z.001
Binary files differ
diff --git a/test/fixtures/7z/hello.7z.002 b/test/fixtures/7z/hello.7z.002
new file mode 100644
index 0000000..9d8d081
--- /dev/null
+++ b/test/fixtures/7z/hello.7z.002
Binary files differ
diff --git a/test/fixtures/_command_offset/completer b/test/fixtures/_command_offset/completer
new file mode 100755
index 0000000..8253e6d
--- /dev/null
+++ b/test/fixtures/_command_offset/completer
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+case "${2-}" in
+ b|ba|bar)
+ echo bar
+ ;;
+ cont1*)
+ echo cont10
+ echo cont11\\
+ ;;
+ f|fo|foo)
+ echo foo
+ ;;
+ l)
+ echo line\\
+ echo two
+ echo long
+ ;;
+ li*)
+ echo line\\
+ echo two
+ ;;
+ lo*)
+ echo long
+ ;;
+ *)
+ echo bar
+ echo foo
+ ;;
+esac
diff --git a/test/fixtures/_comp_compgen/completions/compgen-cmd1 b/test/fixtures/_comp_compgen/completions/compgen-cmd1
new file mode 100644
index 0000000..70199ba
--- /dev/null
+++ b/test/fixtures/_comp_compgen/completions/compgen-cmd1
@@ -0,0 +1,19 @@
+# Dummy completion file for _comp_compgen tests -*- shell-script -*-
+
+_comp_xfunc_compgen_cmd1_compgen_generator1() {
+ _comp_compgen -- -W '5foo 6bar 7baz'
+}
+
+_comp_cmd_compgen_cmd1__compgen_generator2() {
+ _comp_compgen -- -W '5abc 6def 7ghi'
+}
+
+_comp_cmd_compgen_cmd1() {
+ local cur prev words cword comp_args
+ _comp_initialize -- "$@" || return
+ _comp_compgen -- -W '012 123 234'
+ _comp_compgen -ai compgen-cmd1 generator2
+} &&
+ complete -F _comp_cmd_compgen_cmd1 compgen-cmd1
+
+# ex: filetype=sh
diff --git a/test/fixtures/_comp_compgen/completions/compgen-cmd2 b/test/fixtures/_comp_compgen/completions/compgen-cmd2
new file mode 100644
index 0000000..6b6255f
--- /dev/null
+++ b/test/fixtures/_comp_compgen/completions/compgen-cmd2
@@ -0,0 +1,11 @@
+# Dummy completion file for _comp_compgen tests -*- shell-script -*-
+
+_comp_cmd_compgen_cmd2() {
+ local cur prev words cword comp_args
+ _comp_initialize -- "$@" || return
+ _comp_compgen -- -W '012 123 234'
+ _comp_compgen -ax compgen-cmd1 generator1
+} &&
+ complete -F _comp_cmd_compgen_cmd2 compgen-cmd2
+
+# ex: filetype=sh
diff --git a/test/fixtures/_comp_load/bin/cmd1 b/test/fixtures/_comp_load/bin/cmd1
new file mode 120000
index 0000000..5e6359b
--- /dev/null
+++ b/test/fixtures/_comp_load/bin/cmd1
@@ -0,0 +1 @@
+../prefix1/bin/cmd1 \ No newline at end of file
diff --git a/test/fixtures/_comp_load/bin/cmd2 b/test/fixtures/_comp_load/bin/cmd2
new file mode 120000
index 0000000..933265d
--- /dev/null
+++ b/test/fixtures/_comp_load/bin/cmd2
@@ -0,0 +1 @@
+../prefix1/sbin/cmd2 \ No newline at end of file
diff --git a/test/fixtures/_comp_load/prefix1/bin/cmd1 b/test/fixtures/_comp_load/prefix1/bin/cmd1
new file mode 100755
index 0000000..8125fcf
--- /dev/null
+++ b/test/fixtures/_comp_load/prefix1/bin/cmd1
@@ -0,0 +1 @@
+echo cmd1
diff --git a/test/fixtures/_comp_load/prefix1/bin/sh b/test/fixtures/_comp_load/prefix1/bin/sh
new file mode 100755
index 0000000..5bc7d46
--- /dev/null
+++ b/test/fixtures/_comp_load/prefix1/bin/sh
@@ -0,0 +1 @@
+echo sh
diff --git a/test/fixtures/_comp_load/prefix1/sbin/cmd2 b/test/fixtures/_comp_load/prefix1/sbin/cmd2
new file mode 100755
index 0000000..8fed03c
--- /dev/null
+++ b/test/fixtures/_comp_load/prefix1/sbin/cmd2
@@ -0,0 +1 @@
+echo cmd2
diff --git a/test/fixtures/_comp_load/prefix1/share/bash-completion/completions/cmd1 b/test/fixtures/_comp_load/prefix1/share/bash-completion/completions/cmd1
new file mode 100644
index 0000000..378a6e3
--- /dev/null
+++ b/test/fixtures/_comp_load/prefix1/share/bash-completion/completions/cmd1
@@ -0,0 +1,2 @@
+echo 'cmd1: sourced from prefix1'
+complete -C true "$1"
diff --git a/test/fixtures/_comp_load/prefix1/share/bash-completion/completions/cmd2 b/test/fixtures/_comp_load/prefix1/share/bash-completion/completions/cmd2
new file mode 100644
index 0000000..167ad62
--- /dev/null
+++ b/test/fixtures/_comp_load/prefix1/share/bash-completion/completions/cmd2
@@ -0,0 +1,2 @@
+echo 'cmd2: sourced from prefix1'
+complete -C true "$1"
diff --git a/test/fixtures/_comp_load/prefix1/share/bash-completion/completions/sh b/test/fixtures/_comp_load/prefix1/share/bash-completion/completions/sh
new file mode 100644
index 0000000..18fe79e
--- /dev/null
+++ b/test/fixtures/_comp_load/prefix1/share/bash-completion/completions/sh
@@ -0,0 +1 @@
+echo 'sh: sourced from prefix1'
diff --git a/test/fixtures/_comp_load/userdir1/completions/cmd1 b/test/fixtures/_comp_load/userdir1/completions/cmd1
new file mode 100644
index 0000000..b26bf1f
--- /dev/null
+++ b/test/fixtures/_comp_load/userdir1/completions/cmd1
@@ -0,0 +1,2 @@
+echo 'cmd1: sourced from userdir1'
+complete -C true "$1"
diff --git a/test/fixtures/_comp_load/userdir2/completions/cmd2 b/test/fixtures/_comp_load/userdir2/completions/cmd2
new file mode 100644
index 0000000..667989b
--- /dev/null
+++ b/test/fixtures/_comp_load/userdir2/completions/cmd2
@@ -0,0 +1,2 @@
+echo 'cmd2: sourced from userdir2'
+complete -C true "$1"
diff --git a/test/fixtures/_comp_xfunc/completions/xfunc-test1 b/test/fixtures/_comp_xfunc/completions/xfunc-test1
new file mode 100644
index 0000000..50a35c0
--- /dev/null
+++ b/test/fixtures/_comp_xfunc/completions/xfunc-test1
@@ -0,0 +1,12 @@
+# Dummy completion file for _comp_xfunc tests -*- shell-script -*-
+
+if declare -F _comp_xfunc_xfunc_test1_utility1 &>/dev/null; then
+ echo "_comp_xfunc_xfunc_test1_utility1 is already defined"
+ return 1
+fi
+
+_comp_xfunc_xfunc_test1_utility1() {
+ printf 'util1['
+ printf '<%s>' "$@"
+ printf ']\n'
+}
diff --git a/test/fixtures/_comp_xfunc/completions/xfunc-test2 b/test/fixtures/_comp_xfunc/completions/xfunc-test2
new file mode 100644
index 0000000..ff847b3
--- /dev/null
+++ b/test/fixtures/_comp_xfunc/completions/xfunc-test2
@@ -0,0 +1,12 @@
+# Dummy completion file for _comp_xfunc tests -*- shell-script -*-
+
+if declare -F _comp_xfunc_non_standard_name &>/dev/null; then
+ echo "_comp_xfunc_non_standard_name is already defined"
+ return 1
+fi
+
+_comp_xfunc_non_standard_name() {
+ printf 'util2['
+ printf '<%s>' "$@"
+ printf ']\n'
+}
diff --git a/test/fixtures/_known_hosts_real/.ssh/config_asterisk_1 b/test/fixtures/_known_hosts/.ssh/config_asterisk_1
index fc09eb0..fc09eb0 100644
--- a/test/fixtures/_known_hosts_real/.ssh/config_asterisk_1
+++ b/test/fixtures/_known_hosts/.ssh/config_asterisk_1
diff --git a/test/fixtures/_known_hosts_real/.ssh/config_asterisk_2 b/test/fixtures/_known_hosts/.ssh/config_asterisk_2
index 42243ad..42243ad 100644
--- a/test/fixtures/_known_hosts_real/.ssh/config_asterisk_2
+++ b/test/fixtures/_known_hosts/.ssh/config_asterisk_2
diff --git a/test/fixtures/_known_hosts_real/.ssh/config_question_mark b/test/fixtures/_known_hosts/.ssh/config_question_mark
index 08e1201..08e1201 100644
--- a/test/fixtures/_known_hosts_real/.ssh/config_question_mark
+++ b/test/fixtures/_known_hosts/.ssh/config_question_mark
diff --git a/test/fixtures/_known_hosts_real/.ssh/config_relative_path b/test/fixtures/_known_hosts/.ssh/config_relative_path
index a7ad4d1..a7ad4d1 100644
--- a/test/fixtures/_known_hosts_real/.ssh/config_relative_path
+++ b/test/fixtures/_known_hosts/.ssh/config_relative_path
diff --git a/test/fixtures/_known_hosts/config b/test/fixtures/_known_hosts/config
new file mode 100644
index 0000000..fad532b
--- /dev/null
+++ b/test/fixtures/_known_hosts/config
@@ -0,0 +1,9 @@
+ UserKnownHostsFile _known_hosts/known_hosts
+
+ # Unindented
+Host *
+ IPQoS none
+Host gee* jar?this-part-we-do-not-complete-at-least-yet
+ HostName %h.example.com
+ # Indented, with = separator
+ Host = hus%%eth0 !negated #not-a-comment
diff --git a/test/fixtures/_known_hosts_real/config_full_path b/test/fixtures/_known_hosts/config_full_path
index a91649b..a91649b 100644
--- a/test/fixtures/_known_hosts_real/config_full_path
+++ b/test/fixtures/_known_hosts/config_full_path
diff --git a/test/fixtures/_known_hosts_real/config_include b/test/fixtures/_known_hosts/config_include
index a1ae763..d68b0d8 100644
--- a/test/fixtures/_known_hosts_real/config_include
+++ b/test/fixtures/_known_hosts/config_include
@@ -1,7 +1,9 @@
-#$HOME set to fixtures/_known_hosts_real in unit test
+#$HOME set to fixtures/_known_hosts in unit test
# Include with full path (recursive one)
Include ~/config_full_path
# Include with relative path
Include config_relative_path
# Include with wildcards, and more than one on same row
Include config_asterisk* config_?uestion_mark
+# Include a directory name. This is a misconfiguration, but ssh ignores it without errors.
+Include ../../_known_hosts
diff --git a/test/fixtures/_known_hosts_real/config_include_recursion b/test/fixtures/_known_hosts/config_include_recursion
index 2777069..2777069 100644
--- a/test/fixtures/_known_hosts_real/config_include_recursion
+++ b/test/fixtures/_known_hosts/config_include_recursion
diff --git a/test/fixtures/_known_hosts/config_tilde b/test/fixtures/_known_hosts/config_tilde
new file mode 100644
index 0000000..93e495b
--- /dev/null
+++ b/test/fixtures/_known_hosts/config_tilde
@@ -0,0 +1,4 @@
+# With quotes and tilde, and =
+UserKnownHostsFile = "~/_known_hosts/known_hosts2"
+# Without quotes, with tilde, and another on the same line
+UserKnownHostsFile ~/_known_hosts/known_hosts3 _known_hosts/known_hosts4
diff --git a/test/fixtures/_known_hosts_real/gee-filename-canary b/test/fixtures/_known_hosts/gee-filename-canary
index e69de29..e69de29 100644
--- a/test/fixtures/_known_hosts_real/gee-filename-canary
+++ b/test/fixtures/_known_hosts/gee-filename-canary
diff --git a/test/fixtures/_known_hosts_real/known_hosts b/test/fixtures/_known_hosts/known_hosts
index 646b5b6..646b5b6 100644
--- a/test/fixtures/_known_hosts_real/known_hosts
+++ b/test/fixtures/_known_hosts/known_hosts
diff --git a/test/fixtures/_known_hosts_real/known_hosts2 b/test/fixtures/_known_hosts/known_hosts2
index 2eb4d4f..2eb4d4f 100644
--- a/test/fixtures/_known_hosts_real/known_hosts2
+++ b/test/fixtures/_known_hosts/known_hosts2
diff --git a/test/fixtures/_known_hosts_real/known_hosts3 b/test/fixtures/_known_hosts/known_hosts3
index 2bdf67a..2bdf67a 100644
--- a/test/fixtures/_known_hosts_real/known_hosts3
+++ b/test/fixtures/_known_hosts/known_hosts3
diff --git a/test/fixtures/_known_hosts_real/known_hosts4 b/test/fixtures/_known_hosts/known_hosts4
index 8510665..8510665 100644
--- a/test/fixtures/_known_hosts_real/known_hosts4
+++ b/test/fixtures/_known_hosts/known_hosts4
diff --git a/test/fixtures/_known_hosts/localhost_config b/test/fixtures/_known_hosts/localhost_config
new file mode 100644
index 0000000..3c6a209
--- /dev/null
+++ b/test/fixtures/_known_hosts/localhost_config
@@ -0,0 +1 @@
+UserKnownHostsFile _known_hosts/localhost_hosts
diff --git a/test/fixtures/_known_hosts_real/localhost_hosts b/test/fixtures/_known_hosts/localhost_hosts
index ff752c2..ff752c2 100644
--- a/test/fixtures/_known_hosts_real/localhost_hosts
+++ b/test/fixtures/_known_hosts/localhost_hosts
diff --git a/test/fixtures/_known_hosts/spaced conf b/test/fixtures/_known_hosts/spaced conf
new file mode 100644
index 0000000..ac0891e
--- /dev/null
+++ b/test/fixtures/_known_hosts/spaced conf
@@ -0,0 +1,8 @@
+
+ # Unindented
+Host gee
+ UserKnownHostsFile "_known_hosts/spaced known_hosts"
+
+ # Indented
+ Host hus #not-a-comment
+ UserKnownHostsFile "_known_hosts/known_hosts2"
diff --git a/test/fixtures/_known_hosts_real/spaced known_hosts b/test/fixtures/_known_hosts/spaced known_hosts
index d54a04d..d54a04d 100644
--- a/test/fixtures/_known_hosts_real/spaced known_hosts
+++ b/test/fixtures/_known_hosts/spaced known_hosts
diff --git a/test/fixtures/_known_hosts_real/config b/test/fixtures/_known_hosts_real/config
deleted file mode 100644
index fe3fb54..0000000
--- a/test/fixtures/_known_hosts_real/config
+++ /dev/null
@@ -1,7 +0,0 @@
- UserKnownHostsFile _known_hosts_real/known_hosts
-
- # Unindented
-Host gee* jar?this-part-we-do-not-complete-at-least-yet
- HostName %h.example.com
- # Indented
- Host hus%%eth0 !negated #not-a-comment
diff --git a/test/fixtures/_known_hosts_real/config_tilde b/test/fixtures/_known_hosts_real/config_tilde
deleted file mode 100644
index 4181aaf..0000000
--- a/test/fixtures/_known_hosts_real/config_tilde
+++ /dev/null
@@ -1,4 +0,0 @@
-# With quotes and tilde
-UserKnownHostsFile "~/_known_hosts_real/known_hosts2"
-# Without quotes, with tilde, and another on the same line
-UserKnownHostsFile ~/_known_hosts_real/known_hosts3 _known_hosts_real/known_hosts4
diff --git a/test/fixtures/_known_hosts_real/localhost_config b/test/fixtures/_known_hosts_real/localhost_config
deleted file mode 100644
index 30b6623..0000000
--- a/test/fixtures/_known_hosts_real/localhost_config
+++ /dev/null
@@ -1 +0,0 @@
-UserKnownHostsFile _known_hosts_real/localhost_hosts
diff --git a/test/fixtures/_known_hosts_real/spaced conf b/test/fixtures/_known_hosts_real/spaced conf
deleted file mode 100644
index 566b92c..0000000
--- a/test/fixtures/_known_hosts_real/spaced conf
+++ /dev/null
@@ -1,8 +0,0 @@
-
- # Unindented
-Host gee
- UserKnownHostsFile "_known_hosts_real/spaced known_hosts"
-
- # Indented
- Host hus #not-a-comment
- UserKnownHostsFile "_known_hosts_real/known_hosts2"
diff --git a/test/fixtures/ant/.gitignore b/test/fixtures/ant/.gitignore
index 3a08258..3e28307 100644
--- a/test/fixtures/ant/.gitignore
+++ b/test/fixtures/ant/.gitignore
@@ -1 +1 @@
-.ant-targets-*.xml
+/.ant-targets-*.xml
diff --git a/test/fixtures/ant/imported-build.xml b/test/fixtures/ant/imported-build.xml
index 0cc438f..df6a5f3 100644
--- a/test/fixtures/ant/imported-build.xml
+++ b/test/fixtures/ant/imported-build.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<project default="imported-build" name="bash-completion">
+<project default="imported-build" name="bash-completion-imported">
<target name="imported-build">
<!-- ... -->
</target>
diff --git a/test/fixtures/evince/.BMP b/test/fixtures/apt-mark/example.conf
index e69de29..e69de29 100644
--- a/test/fixtures/evince/.BMP
+++ b/test/fixtures/apt-mark/example.conf
diff --git a/test/fixtures/evince/.CBR b/test/fixtures/bsdtar/test.pax
index e69de29..e69de29 100644
--- a/test/fixtures/evince/.CBR
+++ b/test/fixtures/bsdtar/test.pax
diff --git a/test/fixtures/evince/.CBZ b/test/fixtures/bsdtar/test.rar
index e69de29..e69de29 100644
--- a/test/fixtures/evince/.CBZ
+++ b/test/fixtures/bsdtar/test.rar
diff --git a/test/fixtures/evince/.DJV b/test/fixtures/bsdtar/test.shar
index e69de29..e69de29 100644
--- a/test/fixtures/evince/.DJV
+++ b/test/fixtures/bsdtar/test.shar
diff --git a/test/fixtures/evince/.DJVU b/test/fixtures/dot/test1.gv
index e69de29..e69de29 100644
--- a/test/fixtures/evince/.DJVU
+++ b/test/fixtures/dot/test1.gv
diff --git a/test/fixtures/evince/.DVI b/test/fixtures/dot/test2.dot
index e69de29..e69de29 100644
--- a/test/fixtures/evince/.DVI
+++ b/test/fixtures/dot/test2.dot
diff --git a/test/fixtures/evince/.DVI.gz b/test/fixtures/evince/.DVI.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.DVI.gz
+++ /dev/null
diff --git a/test/fixtures/evince/.EPS b/test/fixtures/evince/.EPS
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.EPS
+++ /dev/null
diff --git a/test/fixtures/evince/.EPS.BZ2 b/test/fixtures/evince/.EPS.BZ2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.EPS.BZ2
+++ /dev/null
diff --git a/test/fixtures/evince/.EPS.GZ b/test/fixtures/evince/.EPS.GZ
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.EPS.GZ
+++ /dev/null
diff --git a/test/fixtures/evince/.EPS.bz2 b/test/fixtures/evince/.EPS.bz2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.EPS.bz2
+++ /dev/null
diff --git a/test/fixtures/evince/.EPS.gz b/test/fixtures/evince/.EPS.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.EPS.gz
+++ /dev/null
diff --git a/test/fixtures/evince/.GIF b/test/fixtures/evince/.GIF
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.GIF
+++ /dev/null
diff --git a/test/fixtures/evince/.ICO b/test/fixtures/evince/.ICO
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.ICO
+++ /dev/null
diff --git a/test/fixtures/evince/.JPEG b/test/fixtures/evince/.JPEG
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.JPEG
+++ /dev/null
diff --git a/test/fixtures/evince/.JPG b/test/fixtures/evince/.JPG
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.JPG
+++ /dev/null
diff --git a/test/fixtures/evince/.MIFF b/test/fixtures/evince/.MIFF
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.MIFF
+++ /dev/null
diff --git a/test/fixtures/evince/.PBM b/test/fixtures/evince/.PBM
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PBM
+++ /dev/null
diff --git a/test/fixtures/evince/.PCX b/test/fixtures/evince/.PCX
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PCX
+++ /dev/null
diff --git a/test/fixtures/evince/.PDF b/test/fixtures/evince/.PDF
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PDF
+++ /dev/null
diff --git a/test/fixtures/evince/.PDF.BZ2 b/test/fixtures/evince/.PDF.BZ2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PDF.BZ2
+++ /dev/null
diff --git a/test/fixtures/evince/.PDF.GZ b/test/fixtures/evince/.PDF.GZ
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PDF.GZ
+++ /dev/null
diff --git a/test/fixtures/evince/.PDF.bz2 b/test/fixtures/evince/.PDF.bz2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PDF.bz2
+++ /dev/null
diff --git a/test/fixtures/evince/.PDF.gz b/test/fixtures/evince/.PDF.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PDF.gz
+++ /dev/null
diff --git a/test/fixtures/evince/.PGM b/test/fixtures/evince/.PGM
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PGM
+++ /dev/null
diff --git a/test/fixtures/evince/.PNG b/test/fixtures/evince/.PNG
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PNG
+++ /dev/null
diff --git a/test/fixtures/evince/.PNM b/test/fixtures/evince/.PNM
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PNM
+++ /dev/null
diff --git a/test/fixtures/evince/.PPM b/test/fixtures/evince/.PPM
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PPM
+++ /dev/null
diff --git a/test/fixtures/evince/.PS b/test/fixtures/evince/.PS
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PS
+++ /dev/null
diff --git a/test/fixtures/evince/.PS.BZ2 b/test/fixtures/evince/.PS.BZ2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PS.BZ2
+++ /dev/null
diff --git a/test/fixtures/evince/.PS.GZ b/test/fixtures/evince/.PS.GZ
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PS.GZ
+++ /dev/null
diff --git a/test/fixtures/evince/.PS.bz2 b/test/fixtures/evince/.PS.bz2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PS.bz2
+++ /dev/null
diff --git a/test/fixtures/evince/.PS.gz b/test/fixtures/evince/.PS.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.PS.gz
+++ /dev/null
diff --git a/test/fixtures/evince/.TGA b/test/fixtures/evince/.TGA
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.TGA
+++ /dev/null
diff --git a/test/fixtures/evince/.TIF b/test/fixtures/evince/.TIF
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.TIF
+++ /dev/null
diff --git a/test/fixtures/evince/.TIFF b/test/fixtures/evince/.TIFF
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.TIFF
+++ /dev/null
diff --git a/test/fixtures/evince/.XPM b/test/fixtures/evince/.XPM
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.XPM
+++ /dev/null
diff --git a/test/fixtures/evince/.XWD b/test/fixtures/evince/.XWD
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.XWD
+++ /dev/null
diff --git a/test/fixtures/evince/.bmp b/test/fixtures/evince/.bmp
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.bmp
+++ /dev/null
diff --git a/test/fixtures/evince/.cbr b/test/fixtures/evince/.cbr
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.cbr
+++ /dev/null
diff --git a/test/fixtures/evince/.cbz b/test/fixtures/evince/.cbz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.cbz
+++ /dev/null
diff --git a/test/fixtures/evince/.djv b/test/fixtures/evince/.djv
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.djv
+++ /dev/null
diff --git a/test/fixtures/evince/.djvu b/test/fixtures/evince/.djvu
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.djvu
+++ /dev/null
diff --git a/test/fixtures/evince/.dvi b/test/fixtures/evince/.dvi
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.dvi
+++ /dev/null
diff --git a/test/fixtures/evince/.dvi.BZ2 b/test/fixtures/evince/.dvi.BZ2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.dvi.BZ2
+++ /dev/null
diff --git a/test/fixtures/evince/.dvi.GZ b/test/fixtures/evince/.dvi.GZ
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.dvi.GZ
+++ /dev/null
diff --git a/test/fixtures/evince/.dvi.bz2 b/test/fixtures/evince/.dvi.bz2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.dvi.bz2
+++ /dev/null
diff --git a/test/fixtures/evince/.dvi.gz b/test/fixtures/evince/.dvi.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.dvi.gz
+++ /dev/null
diff --git a/test/fixtures/evince/.eps b/test/fixtures/evince/.eps
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.eps
+++ /dev/null
diff --git a/test/fixtures/evince/.eps.BZ2 b/test/fixtures/evince/.eps.BZ2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.eps.BZ2
+++ /dev/null
diff --git a/test/fixtures/evince/.eps.GZ b/test/fixtures/evince/.eps.GZ
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.eps.GZ
+++ /dev/null
diff --git a/test/fixtures/evince/.eps.bz2 b/test/fixtures/evince/.eps.bz2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.eps.bz2
+++ /dev/null
diff --git a/test/fixtures/evince/.eps.gz b/test/fixtures/evince/.eps.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.eps.gz
+++ /dev/null
diff --git a/test/fixtures/evince/.gif b/test/fixtures/evince/.gif
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.gif
+++ /dev/null
diff --git a/test/fixtures/evince/.ico b/test/fixtures/evince/.ico
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.ico
+++ /dev/null
diff --git a/test/fixtures/evince/.jpeg b/test/fixtures/evince/.jpeg
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.jpeg
+++ /dev/null
diff --git a/test/fixtures/evince/.jpg b/test/fixtures/evince/.jpg
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.jpg
+++ /dev/null
diff --git a/test/fixtures/evince/.miff b/test/fixtures/evince/.miff
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.miff
+++ /dev/null
diff --git a/test/fixtures/evince/.pbm b/test/fixtures/evince/.pbm
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.pbm
+++ /dev/null
diff --git a/test/fixtures/evince/.pcx b/test/fixtures/evince/.pcx
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.pcx
+++ /dev/null
diff --git a/test/fixtures/evince/.pdf b/test/fixtures/evince/.pdf
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.pdf
+++ /dev/null
diff --git a/test/fixtures/evince/.pdf.BZ2 b/test/fixtures/evince/.pdf.BZ2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.pdf.BZ2
+++ /dev/null
diff --git a/test/fixtures/evince/.pdf.GZ b/test/fixtures/evince/.pdf.GZ
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.pdf.GZ
+++ /dev/null
diff --git a/test/fixtures/evince/.pdf.bz2 b/test/fixtures/evince/.pdf.bz2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.pdf.bz2
+++ /dev/null
diff --git a/test/fixtures/evince/.pdf.gz b/test/fixtures/evince/.pdf.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.pdf.gz
+++ /dev/null
diff --git a/test/fixtures/evince/.pgm b/test/fixtures/evince/.pgm
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.pgm
+++ /dev/null
diff --git a/test/fixtures/evince/.png b/test/fixtures/evince/.png
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.png
+++ /dev/null
diff --git a/test/fixtures/evince/.pnm b/test/fixtures/evince/.pnm
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.pnm
+++ /dev/null
diff --git a/test/fixtures/evince/.ppm b/test/fixtures/evince/.ppm
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.ppm
+++ /dev/null
diff --git a/test/fixtures/evince/.ps b/test/fixtures/evince/.ps
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.ps
+++ /dev/null
diff --git a/test/fixtures/evince/.ps.BZ2 b/test/fixtures/evince/.ps.BZ2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.ps.BZ2
+++ /dev/null
diff --git a/test/fixtures/evince/.ps.GZ b/test/fixtures/evince/.ps.GZ
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.ps.GZ
+++ /dev/null
diff --git a/test/fixtures/evince/.ps.bz2 b/test/fixtures/evince/.ps.bz2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.ps.bz2
+++ /dev/null
diff --git a/test/fixtures/evince/.ps.gz b/test/fixtures/evince/.ps.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.ps.gz
+++ /dev/null
diff --git a/test/fixtures/evince/.tga b/test/fixtures/evince/.tga
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.tga
+++ /dev/null
diff --git a/test/fixtures/evince/.tif b/test/fixtures/evince/.tif
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.tif
+++ /dev/null
diff --git a/test/fixtures/evince/.tiff b/test/fixtures/evince/.tiff
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.tiff
+++ /dev/null
diff --git a/test/fixtures/evince/.txt b/test/fixtures/evince/.txt
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.txt
+++ /dev/null
diff --git a/test/fixtures/evince/.xpm b/test/fixtures/evince/.xpm
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.xpm
+++ /dev/null
diff --git a/test/fixtures/evince/.xwd b/test/fixtures/evince/.xwd
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/.xwd
+++ /dev/null
diff --git a/test/fixtures/evince/foo/.gitignore b/test/fixtures/evince/foo/.gitignore
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/evince/foo/.gitignore
+++ /dev/null
diff --git a/test/fixtures/kdvi/.DVI b/test/fixtures/kdvi/.DVI
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/.DVI
+++ /dev/null
diff --git a/test/fixtures/kdvi/.DVI.Z b/test/fixtures/kdvi/.DVI.Z
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/.DVI.Z
+++ /dev/null
diff --git a/test/fixtures/kdvi/.DVI.bz2 b/test/fixtures/kdvi/.DVI.bz2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/.DVI.bz2
+++ /dev/null
diff --git a/test/fixtures/kdvi/.DVI.gz b/test/fixtures/kdvi/.DVI.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/.DVI.gz
+++ /dev/null
diff --git a/test/fixtures/kdvi/.dvi b/test/fixtures/kdvi/.dvi
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/.dvi
+++ /dev/null
diff --git a/test/fixtures/kdvi/.dvi.Z b/test/fixtures/kdvi/.dvi.Z
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/.dvi.Z
+++ /dev/null
diff --git a/test/fixtures/kdvi/.dvi.bz2 b/test/fixtures/kdvi/.dvi.bz2
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/.dvi.bz2
+++ /dev/null
diff --git a/test/fixtures/kdvi/.dvi.gz b/test/fixtures/kdvi/.dvi.gz
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/.dvi.gz
+++ /dev/null
diff --git a/test/fixtures/kdvi/.txt b/test/fixtures/kdvi/.txt
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/.txt
+++ /dev/null
diff --git a/test/fixtures/kdvi/foo/.gitignore b/test/fixtures/kdvi/foo/.gitignore
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kdvi/foo/.gitignore
+++ /dev/null
diff --git a/test/fixtures/kpdf/.EPS b/test/fixtures/kpdf/.EPS
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kpdf/.EPS
+++ /dev/null
diff --git a/test/fixtures/kpdf/.PDF b/test/fixtures/kpdf/.PDF
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kpdf/.PDF
+++ /dev/null
diff --git a/test/fixtures/kpdf/.PS b/test/fixtures/kpdf/.PS
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kpdf/.PS
+++ /dev/null
diff --git a/test/fixtures/kpdf/.eps b/test/fixtures/kpdf/.eps
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kpdf/.eps
+++ /dev/null
diff --git a/test/fixtures/kpdf/.pdf b/test/fixtures/kpdf/.pdf
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kpdf/.pdf
+++ /dev/null
diff --git a/test/fixtures/kpdf/.ps b/test/fixtures/kpdf/.ps
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kpdf/.ps
+++ /dev/null
diff --git a/test/fixtures/kpdf/.txt b/test/fixtures/kpdf/.txt
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kpdf/.txt
+++ /dev/null
diff --git a/test/fixtures/kpdf/foo/.gitignore b/test/fixtures/kpdf/foo/.gitignore
deleted file mode 100644
index e69de29..0000000
--- a/test/fixtures/kpdf/foo/.gitignore
+++ /dev/null
diff --git a/test/fixtures/make/.gitignore b/test/fixtures/make/.gitignore
index 3d1325c..a9b67f8 100644
--- a/test/fixtures/make/.gitignore
+++ b/test/fixtures/make/.gitignore
@@ -1 +1 @@
-extra_makefile
+/extra_makefile
diff --git a/test/fixtures/make/test2/Makefile b/test/fixtures/make/test2/Makefile
new file mode 100644
index 0000000..835b514
--- /dev/null
+++ b/test/fixtures/make/test2/Makefile
@@ -0,0 +1,23 @@
+# makefile
+
+all: abc/xyz
+.PHONY: abc/xyz
+abc/xyz 123/xaa 123/xbb:
+ mkdir -p $(@:/%=)
+ date > $@
+
+sub1test/bar/alpha sub1test/bar/beta:
+ mkdir -p $(@:/%=)
+ date > $@
+
+sub2test/bar/alpha:
+ mkdir -p $(@:/%=)
+ date > $@
+
+sub3test/bar/alpha sub3test/foo/alpha:
+ mkdir -p $(@:/%=)
+ date > $@
+
+sub4test/bar/alpha sub4test/bar/beta sub4test2/foo/gamma:
+ mkdir -p $(@:/%=)
+ date > $@
diff --git a/test/fixtures/man/man3/bash-completion-zstd-testcase.3head.zst b/test/fixtures/man/man3/bash-completion-zstd-testcase.3head.zst
new file mode 100644
index 0000000..e58c09d
--- /dev/null
+++ b/test/fixtures/man/man3/bash-completion-zstd-testcase.3head.zst
Binary files differ
diff --git a/test/fixtures/pkgtools/ports/.gitignore b/test/fixtures/pkgtools/ports/.gitignore
index 71d2c0c..7925953 100644
--- a/test/fixtures/pkgtools/ports/.gitignore
+++ b/test/fixtures/pkgtools/ports/.gitignore
@@ -1,2 +1,2 @@
-INDEX
-INDEX-5
+/INDEX
+/INDEX-5
diff --git a/test/fixtures/evince/.DVI.BZ2 b/test/fixtures/python/bar.txt
index e69de29..e69de29 100644
--- a/test/fixtures/evince/.DVI.BZ2
+++ b/test/fixtures/python/bar.txt
diff --git a/test/fixtures/evince/.DVI.GZ b/test/fixtures/python/foo.py
index e69de29..e69de29 100644
--- a/test/fixtures/evince/.DVI.GZ
+++ b/test/fixtures/python/foo.py
diff --git a/test/fixtures/evince/.DVI.bz2 b/test/fixtures/sha256sum/foo
index e69de29..e69de29 100644
--- a/test/fixtures/evince/.DVI.bz2
+++ b/test/fixtures/sha256sum/foo
diff --git a/test/fixtures/sha256sum/foo.sha256 b/test/fixtures/sha256sum/foo.sha256
new file mode 100644
index 0000000..26d55dc
--- /dev/null
+++ b/test/fixtures/sha256sum/foo.sha256
@@ -0,0 +1 @@
+e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 foo
diff --git a/test/fixtures/shells/etc/shells b/test/fixtures/shells/etc/shells
new file mode 100644
index 0000000..0d979db
--- /dev/null
+++ b/test/fixtures/shells/etc/shells
@@ -0,0 +1,2 @@
+# /etc/shells: valid login shells
+/bash/completion/canary
diff --git a/test/fixtures/xrandr/xrandr b/test/fixtures/xrandr/xrandr
new file mode 100755
index 0000000..a4693b5
--- /dev/null
+++ b/test/fixtures/xrandr/xrandr
@@ -0,0 +1,207 @@
+#!/bin/sh
+
+if [ "$1" = --help ]; then
+
+ # xrandr 1.5.1 help
+ cat <<EOF
+usage: xrandr [options]
+ where options are:
+ --display <display> or -d <display>
+ --help
+ -o <normal,inverted,left,right,0,1,2,3>
+ or --orientation <normal,inverted,left,right,0,1,2,3>
+ -q or --query
+ -s <size>/<width>x<height> or --size <size>/<width>x<height>
+ -r <rate> or --rate <rate> or --refresh <rate>
+ -v or --version
+ -x (reflect in x)
+ -y (reflect in y)
+ --screen <screen>
+ --verbose
+ --current
+ --dryrun
+ --nograb
+ --prop or --properties
+ --fb <width>x<height>
+ --fbmm <width>x<height>
+ --dpi <dpi>/<output>
+ --output <output>
+ --auto
+ --mode <mode>
+ --preferred
+ --pos <x>x<y>
+ --rate <rate> or --refresh <rate>
+ --reflect normal,x,y,xy
+ --rotate normal,inverted,left,right
+ --left-of <output>
+ --right-of <output>
+ --above <output>
+ --below <output>
+ --same-as <output>
+ --set <property> <value>
+ --scale <x>[x<y>]
+ --scale-from <w>x<h>
+ --transform <a>,<b>,<c>,<d>,<e>,<f>,<g>,<h>,<i>
+ --filter nearest,bilinear
+ --off
+ --crtc <crtc>
+ --panning <w>x<h>[+<x>+<y>[/<track:w>x<h>+<x>+<y>[/<border:l>/<t>/<r>/<b>]]]
+ --gamma <r>[:<g>:<b>]
+ --brightness <value>
+ --primary
+ --noprimary
+ --newmode <name> <clock MHz>
+ <hdisp> <hsync-start> <hsync-end> <htotal>
+ <vdisp> <vsync-start> <vsync-end> <vtotal>
+ [flags...]
+ Valid flags: +HSync -HSync +VSync -VSync
+ +CSync -CSync CSync Interlace DoubleScan
+ --rmmode <name>
+ --addmode <output> <name>
+ --delmode <output> <name>
+ --listproviders
+ --setprovideroutputsource <prov-xid> <source-xid>
+ --setprovideroffloadsink <prov-xid> <sink-xid>
+ --listmonitors
+ --listactivemonitors
+ --setmonitor <name> {auto|<w>/<mmw>x<h>/<mmh>+<x>+<y>} {none|<output>,<output>,...}
+ --delmonitor <name>
+EOF
+
+elif
+ [ "$1" = --listmonitors ]
+then
+
+ cat <<EOF
+Monitors: 2
+ 0: +*eDP-1-1 1920/344x1080/193+0+1080 eDP-1-1
+ 1: +HDMI-0 1920/477x1080/268+0+0 HDMI-0
+EOF
+
+elif
+ [ "$1" = --listproviders ]
+then
+
+ cat <<EOF
+Providers: number : 2
+Provider 0: id: 0x1b8 cap: 0x1, Source Output crtcs: 4 outputs: 5 associated providers: 1 name:NVIDIA-0
+Provider 1: id: 0x1fe cap: 0xf, Source Output, Sink Output, Source Offload, Sink Offload crtcs: 3 outputs: 1 associated providers: 1 name:modesetting
+EOF
+
+else
+
+ cat <<EOF
+Screen 0: minimum 8 x 8, current 1920 x 2160, maximum 32767 x 32767
+DP-0 disconnected (normal left inverted right x axis y axis)
+DP-1 disconnected (normal left inverted right x axis y axis)
+DP-2 disconnected (normal left inverted right x axis y axis)
+DP-3 disconnected (normal left inverted right x axis y axis)
+HDMI-0 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 477mm x 268mm
+ 1920x1080 60.00*+ 59.94 50.00
+ 1680x1050 59.95
+ 1600x900 60.00
+ 1440x900 59.89
+ 1280x1024 75.02 60.02
+ 1280x800 59.81
+ 1280x720 60.00 59.94 50.00
+ 1152x864 75.00
+ 1024x768 75.03 70.07 60.00
+ 800x600 75.00 72.19 60.32 56.25
+ 720x576 50.00
+ 720x480 59.94
+ 640x480 75.00 72.81 59.94
+eDP-1-1 connected primary 1920x1080+0+1080 (normal left inverted right x axis y axis) 344mm x 193mm
+ 1920x1080 144.00*+ 60.01 59.97 59.96 59.93
+ 1680x1050 59.95 59.88
+ 1400x1050 74.76 59.98
+ 1600x900 59.99 59.94 59.95 59.82
+ 1280x1024 85.02 75.02 60.02
+ 1400x900 59.96 59.88
+ 1280x960 85.00 60.00
+ 1440x810 60.00 59.97
+ 1368x768 59.88 59.85
+ 1280x800 59.99 59.97 59.81 59.91
+ 1152x864 75.00
+ 1280x720 60.00 59.99 59.86 59.74
+ 1024x768 85.00 75.05 60.04 85.00 75.03 70.07 60.00
+ 1024x768i 86.96
+ 960x720 85.00 75.00 60.00
+ 928x696 75.00 60.05
+ 896x672 75.05 60.01
+ 1024x576 59.95 59.96 59.90 59.82
+ 960x600 59.93 60.00
+ 832x624 74.55
+ 960x540 59.96 59.99 59.63 59.82
+ 800x600 85.00 75.00 70.00 65.00 60.00 85.14 72.19 75.00 60.32 56.25
+ 840x525 60.01 59.88
+ 864x486 59.92 59.57
+ 700x525 74.76 59.98
+ 800x450 59.95 59.82
+ 640x512 85.02 75.02 60.02
+ 700x450 59.96 59.88
+ 640x480 85.09 60.00 85.01 72.81 75.00 59.94
+ 720x405 59.51 58.99
+ 720x400 85.04
+ 684x384 59.88 59.85
+ 640x400 59.88 59.98 85.08
+ 576x432 75.00
+ 640x360 59.86 59.83 59.84 59.32
+ 640x350 85.08
+ 512x384 85.00 75.03 70.07 60.00
+ 512x384i 87.06
+ 512x288 60.00 59.92
+ 416x312 74.66
+ 480x270 59.63 59.82
+ 400x300 85.27 72.19 75.12 60.32 56.34
+ 432x243 59.92 59.57
+ 320x240 85.18 72.81 75.00 60.05
+ 360x202 59.51 59.13
+ 360x200 85.04
+ 320x200 85.27
+ 320x180 59.84 59.32
+ 320x175 85.27
+ 1680x1050 (0x1c5) 146.250MHz -HSync +VSync
+ h: width 1680 start 1784 end 1960 total 2240 skew 0 clock 65.29KHz
+ v: height 1050 start 1053 end 1059 total 1089 clock 59.95Hz
+ 1280x1024 (0x1c8) 135.000MHz +HSync +VSync
+ h: width 1280 start 1296 end 1440 total 1688 skew 0 clock 79.98KHz
+ v: height 1024 start 1025 end 1028 total 1066 clock 75.02Hz
+ 1280x1024 (0x1c9) 108.000MHz +HSync +VSync
+ h: width 1280 start 1328 end 1440 total 1688 skew 0 clock 63.98KHz
+ v: height 1024 start 1025 end 1028 total 1066 clock 60.02Hz
+ 1280x800 (0x1ca) 83.500MHz -HSync +VSync
+ h: width 1280 start 1352 end 1480 total 1680 skew 0 clock 49.70KHz
+ v: height 800 start 803 end 809 total 831 clock 59.81Hz
+ 1152x864 (0x1ce) 108.000MHz +HSync +VSync
+ h: width 1152 start 1216 end 1344 total 1600 skew 0 clock 67.50KHz
+ v: height 864 start 865 end 868 total 900 clock 75.00Hz
+ 1024x768 (0x1cf) 78.750MHz +HSync +VSync
+ h: width 1024 start 1040 end 1136 total 1312 skew 0 clock 60.02KHz
+ v: height 768 start 769 end 772 total 800 clock 75.03Hz
+ 1024x768 (0x1d0) 75.000MHz -HSync -VSync
+ h: width 1024 start 1048 end 1184 total 1328 skew 0 clock 56.48KHz
+ v: height 768 start 771 end 777 total 806 clock 70.07Hz
+ 1024x768 (0x1d1) 65.000MHz -HSync -VSync
+ h: width 1024 start 1048 end 1184 total 1344 skew 0 clock 48.36KHz
+ v: height 768 start 771 end 777 total 806 clock 60.00Hz
+ 800x600 (0x1d3) 50.000MHz +HSync +VSync
+ h: width 800 start 856 end 976 total 1040 skew 0 clock 48.08KHz
+ v: height 600 start 637 end 643 total 666 clock 72.19Hz
+ 800x600 (0x1d2) 49.500MHz +HSync +VSync
+ h: width 800 start 816 end 896 total 1056 skew 0 clock 46.88KHz
+ v: height 600 start 601 end 604 total 625 clock 75.00Hz
+ 800x600 (0x1d4) 40.000MHz +HSync +VSync
+ h: width 800 start 840 end 968 total 1056 skew 0 clock 37.88KHz
+ v: height 600 start 601 end 605 total 628 clock 60.32Hz
+ 800x600 (0x1d5) 36.000MHz +HSync +VSync
+ h: width 800 start 824 end 896 total 1024 skew 0 clock 35.16KHz
+ v: height 600 start 601 end 603 total 625 clock 56.25Hz
+ 640x480 (0x1d8) 31.500MHz -HSync -VSync
+ h: width 640 start 656 end 720 total 840 skew 0 clock 37.50KHz
+ v: height 480 start 481 end 484 total 500 clock 75.00Hz
+ 640x480 (0x1da) 25.175MHz -HSync -VSync
+ h: width 640 start 656 end 752 total 800 skew 0 clock 31.47KHz
+ v: height 480 start 490 end 492 total 525 clock 59.94Hz
+EOF
+
+fi
diff --git a/test/generate b/test/generate
index 59f525b..ef0967d 100755
--- a/test/generate
+++ b/test/generate
@@ -30,7 +30,7 @@ import pytest
%s
class Test%s:
@pytest.mark.complete("%s %s")
- def test_1(self, completion):
+ def test_basic(self, completion):
assert completion"""
% (marker, name, cmd, args),
file=f,
diff --git a/test/requirements-dev.txt b/test/requirements-dev.txt
index f34f10f..7051ab7 100644
--- a/test/requirements-dev.txt
+++ b/test/requirements-dev.txt
@@ -1,4 +1,6 @@
-# Python >= 3.6.1 required here
+# Python >= 3.7 required here
+
-r requirements.txt
-black==19.10b0
-pre-commit>=2.4.0
+
+mypy==1.8.0
+ruff==0.2.2
diff --git a/test/requirements.txt b/test/requirements.txt
index df56f4e..ae9fe94 100644
--- a/test/requirements.txt
+++ b/test/requirements.txt
@@ -1,5 +1,5 @@
-# Python >= 3.4 required here
+# Python >= 3.6 required here
+
pexpect>=4
pytest>=3.6
-pytest-xdist
-typing;python_version<"3.5"
+ pytest-xdist
diff --git a/test/runLint b/test/runLint
index 95c3887..550995d 100755
--- a/test/runLint
+++ b/test/runLint
@@ -1,51 +1,72 @@
-#!/bin/bash -u
+#!/usr/bin/env bash
+set -u
gitgrep()
{
local out=$(git grep -I -P -n "$1" |
grep -E '^(bash_completion|completions/|test/)' |
grep -Ev "^test/runLint\>${filter_out:+|$filter_out}")
- if [ -n "$out" ]; then
+ if [[ $out ]]; then
printf '***** %s\n' "$2"
printf '%s\n\n' "$out"
fi
}
-unset CDPATH
-cd $(dirname "$0")/..
+unset -v CDPATH
+if ! cd "$(dirname "$0")/.."; then
+ echo 'test/runLint: failed to cd into the working tree of bash-completion' >&2
+ exit 1
+fi
-cmdstart='(^|[[:space:]]|\()'
+cmdstart='(^|[[:space:];&|]|\()'
filter_out=
-gitgrep $cmdstart"awk\b.*-F([[:space:]]|[[:space:]]*[\"'][^\"']{2,})" \
- 'awk with -F char or -F ERE, use -Fchar instead (Solaris)'
+# Note: Since we started to use _comp_awk, we do not have care about the small
+# feature set of Solaris awk anymore.
-gitgrep $cmdstart"awk\b.*\[:[a-z]*:\]" \
- 'awk with POSIX character class not supported in mawk (Debian/Ubuntu)'
+gitgrep "${cmdstart}(_comp_)?awk\b.*\[:[a-z]*:\]" \
+ 'awk with POSIX character class not supported in mawk-1.3.3-20090705 (Debian/Ubuntu)'
-gitgrep $cmdstart'sed\b.*\\[?+]' \
+gitgrep "$cmdstart"'sed\b.*\\[?+]' \
'sed with ? or +, use POSIX BRE instead (\{m,n\})'
-gitgrep $cmdstart'sed\b.*\\\|' \
+gitgrep "$cmdstart"'sed\b.*\\\|' \
"sed with \|, use POSIX BRE (possibly multiple sed invocations) or another tool instead"
# TODO: really nonportable? appears to work fine in Linux, FreeBSD, Solaris
#gitgrep $cmdstart'sed\b.*;' \
# 'sed with ;, use multiple -e options instead (POSIX?) (false positives?)'
-gitgrep $cmdstart'sed\b.*[[:space:]]-[^[:space:]]*[rE]' \
+gitgrep "$cmdstart"'sed\b.*[[:space:]]-[^[:space:]]*[rE]' \
'sed with -r or -E, drop and use POSIX BRE instead'
-gitgrep $cmdstart'[ef]grep\b' \
+gitgrep "$cmdstart"'[ef]grep\b' \
'[ef]grep, use grep -[EF] instead (historical/deprecated)'
# TODO: $ in sed subexpression used as an anchor (POSIX BRE optional, not in
# Solaris/FreeBSD)
-gitgrep '(?<!command)'$cmdstart'(grep|ls|sed)(\s|$)' \
- 'invoke grep, ls, and sed through "command", e.g. "command grep"'
+gitgrep '(?<!command)'"$cmdstart"'(grep|ls|sed|cd)(\s|$)' \
+ 'invoke grep, ls, sed, and cd through "command", e.g. "command grep"'
+
+gitgrep '(?<!command)'"$cmdstart"'awk(\s|$)' \
+ 'invoke awk through "_comp_awk"'
+
+#------------------------------------------------------------------------------
+# Bash pitfalls/styles/compatibilities (which are not detected by shellcheck)
gitgrep '<<<' 'herestrings use temp files, use some other way'
filter_out='^(test/|bash_completion\.sh)' gitgrep ' \[ ' \
'use [[ ]] instead of [ ]'
+
+gitgrep "$cmdstart"'unset [^-]' 'Explicitly specify "unset -v/-f"'
+
+gitgrep "$cmdstart"'((set|shopt)\s+[+-][a-z]+\s+posix\b|(local\s+)?POSIXLY_CORRECT\b)' \
+ 'fiddling with posix mode breaks keybindings with some bash versions'
+
+gitgrep '\$\{([^{}\n]|\{.*\})+/([^{}\n]|\{.*\})+/([^{}"\n]|\{.*\})*\$.*\}' \
+ '$rep of ${var/pat/$rep} needs to be double-quoted for shopt -s patsub_replacement (bash >= 5.2) [see Sec. of patsub_replacement in doc/styleguide.md]'
+
+gitgrep '"([^"\n]|\\.)*\$\{([^{}\n]|\{.*\})+/([^{}\n]|\{.*\})+/([^{}"\n]|\{.*\})*"([^{}"\n]|\{.*\})*\$.*\}' \
+ '$rep of "${var/pat/"$rep"}" should not be quoted for bash-4.2 or shopt -s compat42 (bash >= 4.3) [see Sec. of patsub_replacement in doc/styleguide.md]'
diff --git a/test/setup.cfg b/test/setup.cfg
index 6abd7d3..a377a59 100644
--- a/test/setup.cfg
+++ b/test/setup.cfg
@@ -5,14 +5,7 @@ markers =
complete
[mypy]
-python_version = 3.4
-ignore_missing_imports = true
-
-[isort]
-known_first_party = conftest
-known_third_party = pexpect,pytest
-profile = black
-line_length = 79
-
-[flake8]
-extend-ignore = D202,E203,E501
+python_version = 3.6
+mypy_path = $MYPY_CONFIG_FILE_DIR/t
+show_error_codes = true
+enable_error_code = ignore-without-code,redundant-self,truthy-iterable
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"]
diff --git a/test/test-cmd-list.txt b/test/test-cmd-list.txt
index eb8398e..5dda3e0 100644
--- a/test/test-cmd-list.txt
+++ b/test/test-cmd-list.txt
@@ -5,107 +5,54 @@ a2x
abook
aclocal
acpi
-acroread
adb
-add_members
-alias
alpine
-animate
ant
-apache2ctl
appdata-validate
-apt-build
apt-cache
-apt-get
aptitude
arch
arp
arping
arpspoof
asciidoc
-aspell
-autoconf
-autoheader
automake
-autoreconf
-autorpm
autoscan
autossh
-autoupdate
-avctrl
+avahi-browse
awk
+b2sum
badblocks
base64
bash
bc
-/bin/chroot
bind
-/bin/rmdir
bison
-bk
bmake
-brctl
-bsdtar
-btdownloadcurses.py
-btdownloadgui.py
-btdownloadheadless.py
-bts
-bzip2
-c++
cal
-cancel
-cardctl
carton
cat
-cc
ccache
ccze
-cd
-cdrecord
cfagent
-cfrun
chage
-change_pw
-check_db
-check_perms
checksec
-chfn
-chgrp
-chkconfig
chmod
-chown
chpasswd
-chromium-browser
chronyc
chroot
chrpath
chsh
-ci
-ciptool
civclient
civserver
cksfv
-cleanarch
-clisp
-clone_member
-co
colordiff
-compare
-compgen
-complete
-composite
-config_list
configure
-conjure
convert
-cowsay
cp
cpan2dist
-cpio
cppcheck
createdb
-createuser
-crontab
cryptsetup
csplit
curl
@@ -119,7 +66,6 @@ declare
deja-dup
desktop-file-validate
df
-dfutool
dhclient
dict
diff
@@ -127,80 +73,47 @@ dir
display
dmesg
dmypy
-dnssec-keygen
dnsspoof
-dot
dpkg
dpkg-deb
dpkg-query
-dpkg-reconfigure
-dpkg-source
dropdb
-dropuser
dselect
-dsniff
du
-dumpdb
dumpe2fs
e2freefrag
-e2label
-ebtables
ecryptfs-migrate-home
eject
enscript
env
eog
-etherwake
ether-wake
evince
expand
-explodepkg
export
faillog
-fbgs
-fbi
feh
file
filefrag
-file-roller
filesnarf
-find
-find_member
-finger
fio
firefox
flake8
fmt
fold
-freebsd-update
freeciv
freeciv-server
-function
fusermount
-g++
-g4
-g77
-gcc
-gcj
-gcl
-gdb
-genaliases
-gendiff
genisoimage
geoiplookup
-getconf
getent
gkrellm
gm
-gmplayer
-gnatmake
gnokii
gnome-mplayer
gnome-screenshot
gpasswd
-gpc
gperf
-gpg
gpg2
gpgv
gphoto2
@@ -210,125 +123,71 @@ groupadd
groupdel
groupmems
groupmod
-growisofs
grpck
grub
+gssdp-device-sniffer
gssdp-discover
gzip
-hciattach
-hciconfig
+hash
hcitool
-hddtemp
head
hexdump
-hid2hci
host
hostname
hping2
-hping3
htop
htpasswd
hunspell
hwclock
iconv
-id
identify
idn
-ifdown
ifstat
iftop
ifup
-import
influx
info
-inject
inotifywait
inotifywatch
-insmod
-installpkg
interdiff
-invoke-rc.d
ionice
ip
ipcalc
iperf
iperf3
ipmitool
-ipsec
iptables
ipv6calc
irb
-iscsiadm
isort
-isql
-iwconfig
-iwlist
-iwpriv
-iwspy
-jar
-jarsigner
java
-javac
-javadoc
javaws
jpegoptim
-jps
-jq
jshint
jsonschema
-json_xs
k3b
kcov
-kdvi
-kill
killall
-kldload
-kldunload
koji
-kpdf
-kplayer
-ktutil
l2ping
-larch
lastlog
ld
-ldapadd
-ldapcompare
-ldapdelete
-ldapmodrdn
-ldappasswd
-ldapsearch
ldapvi
-ldapwhoami
ldd
less
lftp
-lftpget
-lilo
links
-lintian
lintian-info
-lisp
-list_admins
-list_lists
-list_members
-list_owners
ln
locale-gen
-look
-lpq
-lpr
lrzip
ls
-lsof
lspci
lsscsi
-lsusb
lua
luac
luseradd
luserdel
-lusermod
lvchange
lvcreate
lvdisplay
@@ -343,11 +202,8 @@ lvs
lvscan
lz4
lzip
-lzma
-lzop
m4
macof
-mailmanctl
mailsnarf
make
makepkg
@@ -355,9 +211,6 @@ man
mc
mcrypt
md5sum
-mdadm
-mdecrypt
-mdtool
medusa
mencoder
mii-diag
@@ -365,63 +218,44 @@ mii-tool
minicom
mkdir
mkfifo
-mkinitrd
-mkisofs
mknod
-mktemp
-mmsitepass
mock
modinfo
-modprobe
-module
-mogrify
monodevelop
-montage
-mount
mplayer
mr
msgsnarf
-msynctool
-mtx
-munindoc
munin-node-configure
munin-run
+munindoc
mussh
mutt
-muttng
mv
mypy
-mysql
mysqladmin
nc
ncftp
nethogs
netstat
-newgrp
newlist
newusers
ngrep
nl
nm
nmap
-nmcli
nproc
-nslookup
nsupdate
ntpdate
objcopy
-objdump
od
oggdec
-op
openssl
opera
optipng
-p4
-pack200
passwd
paste
patch
+pdftoppm
pdftotext
perl
perlcritic
@@ -433,38 +267,23 @@ pidof
pine
pinfo
ping
-pkgadd
pkg-config
-pkg_deinstall
-pkg_delete
-pkg-get
-pkg_info
+pkgadd
+pkgconf
pkgrm
-pkgtool
-pkgutil
pkill
-plague-client
-pm-hibernate
-pm-is-supported
-pm-powersave
pngfix
-portinstall
-portsnap
-portupgrade
postcat
postconf
postfix
postmap
-postsuper
-povray
pr
prelink
printenv
protoc
+ps
psql
ptx
-puppet
-pushd
pv
pvchange
pvcreate
@@ -483,67 +302,47 @@ pydocstyle
pyflakes
pylint
pylint-3
+pyston
pytest
python
python3
-pyvenv
qemu
-qrunner
+qemu-system-x86_64
querybts
quota
quotacheck
quotaon
radvdump
-rcs
-rcsdiff
rdesktop
rdict
readelf
-readonly
-remove_members
-removepkg
-renice
repomanage
reportbug
reptyr
-resolvconf
-rfcomm
-rfkill
ri
-rlog
rm
rmdir
-rmlist
rmmod
-route
-rpcdebug
rpm
-rpm2tgz
rpmbuild
-rrdtool
rsync
rtcwake
-runuser
-sbcl
-sbcl-mt
sbopkg
-scp
screen
scrub
-sdptool
secret-tool
sed
seq
-service
-set
setquota
sftp
-sh
sha1sum
+sha224sum
+sha256sum
+sha384sum
+sha512sum
shar
shellcheck
sitecopy
-slackpkg
slapt-get
slapt-src
smartctl
@@ -557,67 +356,48 @@ smbtree
snownews
sort
split
-spovray
sqlite3
ss
ssh
ssh-add
ssh-copy-id
-sshfs
ssh-keygen
+ssh-keyscan
sshmitm
sshow
strace
-stream
strings
strip
su
sudo
sum
-svcadm
-svk
-svn
-svnadmin
-svnlook
synclient
-sync_members
sysbench
sysctl
tac
tail
-tar
tcpdump
-tcpkill
tcpnice
tee
texindex
-tightvncviewer
-time
timeout
-tipc
-totem
touch
tox
tr
tracepath
+tree
+truncate
tshark
tsig-keygen
tune2fs
udevadm
ulimit
-umount
-unace
uname
unexpand
uniq
units
-unpack200
-unrar
-unset
unshunt
update-alternatives
-update-rc.d
-upgradepkg
urlsnarf
uscan
useradd
@@ -643,45 +423,36 @@ vgrename
vgs
vgscan
vgsplit
-vi
vipw
vmstat
-vncviewer
vpnc
watch
wc
webmitm
wget
who
-wine
-withlist
-wodim
wol
-write
wsimport
wtf
wvdial
xdg-mime
xdg-settings
+xev
xfreerdp
xgamma
-xm
xmllint
xmlwf
xmms
xmodmap
-xpovray
xrandr
xrdb
xsltproc
xvfb-run
-xvnc4viewer
xxd
xz
xzdec
ypcat
ypmatch
yum
-yum-arch
zopfli
zopflipng
diff --git a/test/update-test-cmd-list b/test/update-test-cmd-list
index 115ae16..20224b4 100755
--- a/test/update-test-cmd-list
+++ b/test/update-test-cmd-list
@@ -1,4 +1,5 @@
-#!/bin/bash -eu
+#!/usr/bin/env bash
+set -eu
mydir=$(
cd "$(dirname "$0")"
@@ -7,7 +8,7 @@ mydir=$(
cat "$mydir"/t/test_*.py |
tr -d '\n' |
- grep -Eo '@pytest.mark.complete(\([^)]*\))' |
- sed -ne 's/^[^"]*"\\\?\([^_][^[:space:]"]*\)[[:space:]"].*/\1/p' |
- sort -u \
+ grep -Eo '@pytest.mark.complete\(([^)]*\<require_(cmd|longopt) *= *True\>[^)]*)\)' |
+ sed -ne 's/^[^"]*"\\\{0,1\}\([^_][^[:space:]"]*\)[[:space:]"].*/\1/p' |
+ LC_ALL=C sort -u \
>"$mydir"/test-cmd-list.txt