From 36cfc70020443a1ab9c9fe30599c4c4cb2376794 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sat, 22 May 2021 19:38:21 +0900 Subject: [PATCH 1/5] test(kdvi,kpdf,evince): use @pytest.mark.bashcomp(temp_cwd=True) Now we can use "@pytest.mark.bashcomp(temp_cwd=True)" for a cleaner handling of the test-purpose temporary directory. We now create dummy directories and files in the directory provided by "temp_cwd". For this purpose, the code in "prepare_fixture_dir" (test/t/conftest.py) has been extracted to an independent function "create_dummy_filedirs". Tests at "test/t/test_{kdvi,kpdf,evince}.py" and "prepare_fixture_dir" now call "create_dummy_filedirs". --- test/t/conftest.py | 26 +++++++++++++++++++++++--- test/t/test_evince.py | 23 +++++------------------ test/t/test_kdvi.py | 23 +++++------------------ test/t/test_kpdf.py | 23 +++++------------------ 4 files changed, 38 insertions(+), 57 deletions(-) diff --git a/test/t/conftest.py b/test/t/conftest.py index 9c31e99f043..87b5993ad24 100644 --- a/test/t/conftest.py +++ b/test/t/conftest.py @@ -676,21 +676,41 @@ def prepare_fixture_dir( 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 = tempdir / dir_ + path = Path(dir_) if not path.exists(): path.mkdir() new_dirs.append(dir_) for file_ in files: - path = tempdir / file_ + path = Path(file_) if not path.exists(): path.touch() new_files.append(file_) - return tempdir, sorted(new_files), sorted(new_dirs) + return sorted(new_files), sorted(new_dirs) class TestUnitBase: diff --git a/test/t/test_evince.py b/test/t/test_evince.py index 8de1b7c75b4..74a3ccf9611 100644 --- a/test/t/test_evince.py +++ b/test/t/test_evince.py @@ -1,17 +1,12 @@ -import shlex -from pathlib import Path -from typing import List, Tuple - import pytest -from conftest import assert_bash_exec, assert_complete, prepare_fixture_dir +from conftest import assert_complete, create_dummy_filedirs +@pytest.mark.bashcomp(temp_cwd=True) class TestEvince: - @pytest.fixture(scope="class") - def setup_fixture(self, request) -> Tuple[Path, List[str], List[str]]: - return prepare_fixture_dir( - request, + def test_1(self, bash): + files, dirs = create_dummy_filedirs( ( ".bmp .BMP .cbr .CBR .cbz .CBZ .djv .DJV .djvu .DJVU .dvi " ".DVI .dvi.bz2 .dvi.BZ2 .DVI.bz2 .DVI.BZ2 .dvi.gz .dvi.GZ " @@ -27,15 +22,7 @@ def setup_fixture(self, request) -> Tuple[Path, List[str], List[str]]: "foo".split(), ) - def test_1(self, bash, setup_fixture): - fixture_dir, files, dirs = setup_fixture - - assert_bash_exec(bash, "cd %s" % shlex.quote(str(fixture_dir))) - try: - completion = assert_complete(bash, "evince ") - finally: - assert_bash_exec(bash, "cd -", want_output=None) - + completion = assert_complete(bash, "evince ") assert completion == [ x for x in sorted(files + ["%s/" % d for d in dirs]) diff --git a/test/t/test_kdvi.py b/test/t/test_kdvi.py index 03ab476f4cc..114e024e400 100644 --- a/test/t/test_kdvi.py +++ b/test/t/test_kdvi.py @@ -1,17 +1,12 @@ -import shlex -from pathlib import Path -from typing import List, Tuple - import pytest -from conftest import assert_bash_exec, assert_complete, prepare_fixture_dir +from conftest import assert_complete, create_dummy_filedirs +@pytest.mark.bashcomp(temp_cwd=True) class TestKdvi: - @pytest.fixture(scope="class") - def setup_fixture(self, request) -> Tuple[Path, List[str], List[str]]: - return prepare_fixture_dir( - request, + 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" @@ -19,15 +14,7 @@ def setup_fixture(self, request) -> Tuple[Path, List[str], List[str]]: "foo".split(), ) - def test_1(self, bash, setup_fixture): - fixture_dir, files, dirs = setup_fixture - - assert_bash_exec(bash, "cd %s" % shlex.quote(str(fixture_dir))) - try: - completion = assert_complete(bash, "kdvi ") - finally: - assert_bash_exec(bash, "cd -", want_output=None) - + completion = assert_complete(bash, "kdvi ") assert completion == [ x for x in sorted(files + ["%s/" % d for d in dirs]) diff --git a/test/t/test_kpdf.py b/test/t/test_kpdf.py index 87f7d61ca63..b7e658fd625 100644 --- a/test/t/test_kpdf.py +++ b/test/t/test_kpdf.py @@ -1,30 +1,17 @@ -import shlex -from pathlib import Path -from typing import List, Tuple - import pytest -from conftest import assert_bash_exec, assert_complete, prepare_fixture_dir +from conftest import assert_complete, create_dummy_filedirs +@pytest.mark.bashcomp(temp_cwd=True) class TestKpdf: - @pytest.fixture(scope="class") - def setup_fixture(self, request) -> Tuple[Path, List[str], List[str]]: - return prepare_fixture_dir( - request, + def test_1(self, bash): + files, dirs = create_dummy_filedirs( ".eps .EPS .pdf .PDF .ps .PS .txt".split(), "foo".split(), ) - def test_1(self, bash, setup_fixture): - fixture_dir, files, dirs = setup_fixture - - assert_bash_exec(bash, "cd %s" % shlex.quote(str(fixture_dir))) - try: - completion = assert_complete(bash, "kpdf ") - finally: - assert_bash_exec(bash, "cd -", want_output=None) - + completion = assert_complete(bash, "kpdf ") assert completion == [ x for x in sorted(files + ["%s/" % d for d in dirs]) From 397e6b07ca65c87b4b03a20852737a2364fa20de Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sat, 22 May 2021 19:40:49 +0900 Subject: [PATCH 2/5] test(diff_env): detect unwanted changes of OLDPWD in test To properly detect unwanted changes of OLDPWD by completions, we remove OLDPWD from the white list of mutable variables. Now, we need to properly save and restore the value of OLDPWD when we change the testing directory on purpose. We add variable names "_bash_completion_test_*" in the white list and use "_bash_completion_test_OLDPWD" to save the value of "OLDPWD". Remark: Another suggested solution was to use "pushd" and "popd" in order to save/restore the directory, but this idea was discarded because they turned out to affect "OLDPWD" too. --- test/t/conftest.py | 15 ++++++++++++++- test/t/unit/test_unit_known_hosts_real.py | 8 ++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/test/t/conftest.py b/test/t/conftest.py index 87b5993ad24..9916b5f698f 100644 --- a/test/t/conftest.py +++ b/test/t/conftest.py @@ -410,7 +410,10 @@ 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( + "^[-+](_|PPID|BASH_REMATCH|_bash_completion_test_[a-zA-Z_0-9]*)=", + x, + ) # Ignore likely completion functions added by us: and not re.search(r"^\+declare -f _.+", x) # ...and additional specified things: @@ -496,6 +499,11 @@ def assert_complete( pytest.xfail(xfail) cwd = kwargs.get("cwd") if cwd: + assert_bash_exec( + bash, + "if [[ ${OLDPWD+set} ]]; then _bash_completion_test_OLDPWD=$OLDPWD; else unset -v _bash_completion_test_OLDPWD; fi", + want_output=None, + ) assert_bash_exec(bash, "cd '%s'" % cwd) env_prefix = "_BASHCOMP_TEST_" env = kwargs.get("env", {}) @@ -560,6 +568,11 @@ def assert_complete( ) if cwd: assert_bash_exec(bash, "cd - >/dev/null") + assert_bash_exec( + bash, + "if [[ ${_bash_completion_test_OLDPWD+set} ]]; then OLDPWD=$_bash_completion_test_OLDPWD; else unset -v OLDPWD; fi", + want_output=None, + ) return result diff --git a/test/t/unit/test_unit_known_hosts_real.py b/test/t/unit/test_unit_known_hosts_real.py index ac5205e1478..51b3badb92f 100644 --- a/test/t/unit/test_unit_known_hosts_real.py +++ b/test/t/unit/test_unit_known_hosts_real.py @@ -143,6 +143,10 @@ def test_no_globbing(self, bash): assert_bash_exec( bash, 'OLDHOME="$HOME"; HOME="%s/_known_hosts_real"' % bash.cwd ) + assert_bash_exec( + bash, + "if [[ ${OLDPWD+set} ]]; then _bash_completion_test_OLDPWD=$OLDPWD; else unset -v _bash_completion_test_OLDPWD; fi", + ) output = assert_bash_exec( bash, "cd _known_hosts_real; " @@ -152,6 +156,10 @@ def test_no_globbing(self, bash): "cd - &>/dev/null", want_output=True, ) + assert_bash_exec( + bash, + "if [[ ${_bash_completion_test_OLDPWD+set} ]]; then OLDPWD=$_bash_completion_test_OLDPWD; else unset -v OLDPWD; fi", + ) assert_bash_exec(bash, 'HOME="$OLDHOME"') completion = sorted(set(output.strip().split())) assert "gee" in completion From 18ab3b7d6d466191b48d2a0cae3afca2d3096a63 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Tue, 25 May 2021 08:35:34 +0900 Subject: [PATCH 3/5] test(conftest): add utilities "bash_{save,restore}_variable(bash,var)" --- test/t/conftest.py | 34 ++++++++++++++++------- test/t/test_man.py | 19 +++++++------ test/t/unit/test_unit_known_hosts_real.py | 28 ++++++++----------- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/test/t/conftest.py b/test/t/conftest.py index 9916b5f698f..ee2fa116b97 100644 --- a/test/t/conftest.py +++ b/test/t/conftest.py @@ -387,6 +387,28 @@ def assert_bash_exec( return output +def _bash_copy_variable(bash: pexpect.spawn, src_var: str, dst_var: str): + assert_bash_exec( + bash, + "if [[ ${%s+set} ]]; then %s=${%s}; else unset -v %s; fi" + % (src_var, dst_var, src_var, dst_var), + ) + + +def bash_save_variable( + bash: pexpect.spawn, varname: str, new_value: Optional[str] = None +): + _bash_copy_variable(bash, varname, "_bash_completion_test_" + varname) + if new_value: + assert_bash_exec( + bash, "%s=%s" % (varname, shlex.quote(str(new_value))) + ) + + +def bash_restore_variable(bash: pexpect.spawn, varname: str): + _bash_copy_variable(bash, "_bash_completion_test_" + varname, varname) + + def get_env(bash: pexpect.spawn) -> List[str]: return [ x @@ -499,11 +521,7 @@ def assert_complete( pytest.xfail(xfail) cwd = kwargs.get("cwd") if cwd: - assert_bash_exec( - bash, - "if [[ ${OLDPWD+set} ]]; then _bash_completion_test_OLDPWD=$OLDPWD; else unset -v _bash_completion_test_OLDPWD; fi", - want_output=None, - ) + bash_save_variable(bash, "OLDPWD") assert_bash_exec(bash, "cd '%s'" % cwd) env_prefix = "_BASHCOMP_TEST_" env = kwargs.get("env", {}) @@ -568,11 +586,7 @@ def assert_complete( ) if cwd: assert_bash_exec(bash, "cd - >/dev/null") - assert_bash_exec( - bash, - "if [[ ${_bash_completion_test_OLDPWD+set} ]]; then OLDPWD=$_bash_completion_test_OLDPWD; else unset -v OLDPWD; fi", - want_output=None, - ) + bash_restore_variable(bash, "OLDPWD") return result diff --git a/test/t/test_man.py b/test/t/test_man.py index 015e85fb714..0cbb8712b75 100644 --- a/test/t/test_man.py +++ b/test/t/test_man.py @@ -1,6 +1,12 @@ import pytest -from conftest import assert_bash_exec, assert_complete, prepare_fixture_dir +from conftest import ( + assert_bash_exec, + assert_complete, + bash_restore_variable, + bash_save_variable, + prepare_fixture_dir, +) @pytest.mark.bashcomp( @@ -103,15 +109,12 @@ def test_9(self, bash, completion): @pytest.mark.complete(require_cmd=True) def test_10(self, request, bash, colonpath): - assert_bash_exec( - bash, - 'manpath=${MANPATH-}; export MANPATH="%s:%s/man"' - % (TestMan.manpath, colonpath), - ) - request.addfinalizer( - lambda: assert_bash_exec(bash, "MANPATH=$manpath") + bash_save_variable( + bash, "MANPATH", "%s:%s/man" % (TestMan.manpath, colonpath) ) + assert_bash_exec(bash, "export MANPATH") completion = assert_complete(bash, "man Bash::C") + bash_restore_variable(bash, "MANPATH") assert completion == "ompletion" @pytest.mark.complete("man -", require_cmd=True) diff --git a/test/t/unit/test_unit_known_hosts_real.py b/test/t/unit/test_unit_known_hosts_real.py index 51b3badb92f..2d9e5936365 100644 --- a/test/t/unit/test_unit_known_hosts_real.py +++ b/test/t/unit/test_unit_known_hosts_real.py @@ -2,7 +2,11 @@ import pytest -from conftest import assert_bash_exec +from conftest import ( + assert_bash_exec, + bash_restore_variable, + bash_save_variable, +) @pytest.mark.bashcomp( @@ -126,9 +130,7 @@ def test_included_configs(self, bash, hosts): # fixtures/_known_hosts_real/.ssh/config_question_mark expected.append("question_mark") - assert_bash_exec( - bash, 'OLDHOME="$HOME"; HOME="%s/_known_hosts_real"' % bash.cwd - ) + bash_save_variable(bash, "HOME", "%s/_known_hosts_real" % bash.cwd) output = assert_bash_exec( bash, "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; " @@ -136,17 +138,12 @@ def test_included_configs(self, bash, hosts): r'printf "%s\n" "${COMPREPLY[@]}"', want_output=True, ) - assert_bash_exec(bash, 'HOME="$OLDHOME"') + bash_restore_variable(bash, "HOME") 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 - ) - assert_bash_exec( - bash, - "if [[ ${OLDPWD+set} ]]; then _bash_completion_test_OLDPWD=$OLDPWD; else unset -v _bash_completion_test_OLDPWD; fi", - ) + bash_save_variable(bash, "HOME", "%s/_known_hosts_real" % bash.cwd) + bash_save_variable(bash, "OLDPWD") output = assert_bash_exec( bash, "cd _known_hosts_real; " @@ -156,11 +153,8 @@ def test_no_globbing(self, bash): "cd - &>/dev/null", want_output=True, ) - assert_bash_exec( - bash, - "if [[ ${_bash_completion_test_OLDPWD+set} ]]; then OLDPWD=$_bash_completion_test_OLDPWD; else unset -v OLDPWD; fi", - ) - assert_bash_exec(bash, 'HOME="$OLDHOME"') + bash_restore_variable(bash, "OLDPWD") + bash_restore_variable(bash, "HOME") completion = sorted(set(output.strip().split())) assert "gee" in completion assert "gee-filename-canary" not in completion From 05b70ec3e073668fae519c589a5c911d4fcca63a Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 26 May 2021 09:52:39 +0900 Subject: [PATCH 4/5] test(diff_env): use r"\w" in a regular expression --- test/t/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/t/conftest.py b/test/t/conftest.py index ee2fa116b97..a7eabfa8cf0 100644 --- a/test/t/conftest.py +++ b/test/t/conftest.py @@ -433,8 +433,9 @@ def diff_env(before: List[str], after: List[str], ignore: str): if not re.search(r"^(---|\+\+\+|@@ )", x) # Ignore variables expected to change: and not re.search( - "^[-+](_|PPID|BASH_REMATCH|_bash_completion_test_[a-zA-Z_0-9]*)=", + r"^[-+](_|PPID|BASH_REMATCH|_bash_completion_test_\w+)=", x, + re.ASCII, ) # Ignore likely completion functions added by us: and not re.search(r"^\+declare -f _.+", x) From 58f02b9c56c6d9685a77d336669a8f0955cc69fb Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 26 May 2021 12:21:31 +0900 Subject: [PATCH 5/5] test(conftest): add new context manager "bash_env_saved" --- test/t/conftest.py | 216 ++++++++++++++++------ test/t/test_man.py | 22 +-- test/t/test_tar.py | 3 +- test/t/unit/test_unit_known_hosts_real.py | 47 ++--- test/t/unit/test_unit_quote_readline.py | 33 ++-- 5 files changed, 204 insertions(+), 117 deletions(-) diff --git a/test/t/conftest.py b/test/t/conftest.py index a7eabfa8cf0..da6d293dddf 100644 --- a/test/t/conftest.py +++ b/test/t/conftest.py @@ -8,13 +8,24 @@ import tempfile import time from pathlib import Path -from typing import Callable, Iterable, Iterator, List, Optional, Tuple +from types import TracebackType +from typing import ( + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Tuple, + Type, +) import pexpect # type: ignore[import] import pytest PS1 = "/@" MAGIC_MARK = "__MaGiC-maRKz!__" +MAGIC_MARK2 = "Re8SCgEdfN" def find_unique_completion_pair( @@ -387,26 +398,139 @@ def assert_bash_exec( return output -def _bash_copy_variable(bash: pexpect.spawn, src_var: str, dst_var: str): - assert_bash_exec( - bash, - "if [[ ${%s+set} ]]; then %s=${%s}; else unset -v %s; fi" - % (src_var, dst_var, src_var, dst_var), - ) +class bash_env_saved: + def __init__(self, bash: pexpect.spawn, sendintr: bool = False): + self.bash = bash + self.cwd: Optional[str] = None + self.saved_shopt: Dict[str, int] = {} + self.saved_variables: Dict[str, int] = {} + self.sendintr = sendintr + + 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 bash_save_variable( - bash: pexpect.spawn, varname: str, new_value: Optional[str] = None -): - _bash_copy_variable(bash, varname, "_bash_completion_test_" + varname) - if new_value: + def _copy_variable(self, src_var: str, dst_var: str): assert_bash_exec( - bash, "%s=%s" % (varname, shlex.quote(str(new_value))) + self.bash, + "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): + assert_bash_exec(self.bash, "unset -v %s" % varname) + + def _save_cwd(self): + if not self.cwd: + self.cwd = self.bash.cwd + + def _check_shopt(self, name: str): + assert_bash_exec( + self.bash, + '[[ $(shopt -p %s) == "${_BASHCOMP_TEST_NEWSHOPT_%s}" ]]' + % (name, name), + ) + + def _unprotect_shopt(self, name: str): + if name not in self.saved_shopt: + self.saved_shopt[name] = 1 + assert_bash_exec( + self.bash, + "_BASHCOMP_TEST_OLDSHOPT_%s=$(shopt -p %s; true)" + % (name, name), + ) + else: + self._check_shopt(name) -def bash_restore_variable(bash: pexpect.spawn, varname: str): - _bash_copy_variable(bash, "_bash_completion_test_" + varname, varname) + def _protect_shopt(self, name: str): + assert_bash_exec( + self.bash, + "_BASHCOMP_TEST_NEWSHOPT_%s=$(shopt -p %s; true)" % (name, name), + ) + + def _check_variable(self, varname: str): + assert_bash_exec( + self.bash, + '[[ ${%s-%s} == "${_BASHCOMP_TEST_NEWVAR_%s-%s}" ]]' + % (varname, MAGIC_MARK2, varname, MAGIC_MARK2), + ) + + def _unprotect_variable(self, varname: str): + if varname not in self.saved_variables: + self.saved_variables[varname] = 1 + self._copy_variable(varname, "_BASHCOMP_TEST_OLDVAR_" + varname) + else: + self._check_variable(varname) + + def _protect_variable(self, varname: str): + self._copy_variable(varname, "_BASHCOMP_TEST_NEWVAR_" + varname) + + def _restore_env(self): + if self.sendintr: + self.bash.sendintr() + self.bash.expect_exact(PS1) + + # We first go back to the original directory before restoring + # variables because "cd" affects "OLDPWD". + if self.cwd: + self._unprotect_variable("OLDPWD") + assert_bash_exec(self.bash, "cd %s" % shlex.quote(str(self.cwd))) + self._protect_variable("OLDPWD") + self.cwd = None + + for name in self.saved_shopt: + self._check_shopt(name) + assert_bash_exec( + self.bash, 'eval "$_BASHCOMP_TEST_OLDSHOPT_%s"' % name + ) + self._unset_variable("_BASHCOMP_TEST_OLDSHOPT_" + name) + self._unset_variable("_BASHCOMP_TEST_NEWSHOPT_" + name) + self.saved_shopt = {} + + for varname in self.saved_variables: + self._check_variable(varname) + self._copy_variable("_BASHCOMP_TEST_OLDVAR_" + varname, varname) + self._unset_variable("_BASHCOMP_TEST_OLDVAR_" + varname) + self._unset_variable("_BASHCOMP_TEST_NEWVAR_" + varname) + self.saved_variables = {} + + def chdir(self, path: str): + self._save_cwd() + self._unprotect_variable("OLDPWD") + assert_bash_exec(self.bash, "cd %s" % shlex.quote(path)) + self._protect_variable("OLDPWD") + + def shopt(self, name: str, value: bool): + self._unprotect_shopt(name) + if value: + assert_bash_exec(self.bash, "shopt -s %s" % name) + else: + assert_bash_exec(self.bash, "shopt -u %s" % name) + self._protect_shopt(name) + + def write_variable(self, varname: str, new_value: str, quote: bool = True): + if quote: + new_value = shlex.quote(new_value) + self._unprotect_variable(varname) + assert_bash_exec(self.bash, "%s=%s" % (varname, new_value)) + self._protect_variable(varname) + + # 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) + assert_bash_exec(self.bash, "export %s=%s" % (envname, new_value)) + self._protect_variable(envname) def get_env(bash: pexpect.spawn) -> List[str]: @@ -433,7 +557,7 @@ def diff_env(before: List[str], after: List[str], ignore: str): if not re.search(r"^(---|\+\+\+|@@ )", x) # Ignore variables expected to change: and not re.search( - r"^[-+](_|PPID|BASH_REMATCH|_bash_completion_test_\w+)=", + r"^[-+](_|PPID|BASH_REMATCH|_BASHCOMP_TEST_\w+)=", x, re.ASCII, ) @@ -520,23 +644,19 @@ def assert_complete( pass else: pytest.xfail(xfail) - cwd = kwargs.get("cwd") - if cwd: - bash_save_variable(bash, "OLDPWD") - 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: + + 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) + bash.send(cmd + "\t") # Sleep a bit if requested, to avoid `.*` matching too early time.sleep(kwargs.get("sleep_after_tab", 0)) @@ -558,37 +678,13 @@ def assert_complete( output = bash.before if output.endswith(MAGIC_MARK): output = bash.before[: -len(MAGIC_MARK)] - result = CompletionResult(output) + return CompletionResult(output) elif got == 2: output = bash.match.group(1) - result = CompletionResult(output) + return CompletionResult(output) else: # TODO: warn about EOF/TIMEOUT? - result = CompletionResult() - finally: - bash.sendintr() - bash.expect_exact(PS1) - if env: - # Restore environment, and clean up backup - # TODO: Test with declare -p if a var was set, backup only if yes, and - # similarly restore only backed up vars. Should remove some need - # for ignore_env. - assert_bash_exec( - bash, - "export %s" - % " ".join( - '%s="$%s%s"' % (k, env_prefix, k) for k in env.keys() - ), - ) - assert_bash_exec( - bash, - "unset -v %s" - % " ".join("%s%s" % (env_prefix, k) for k in env.keys()), - ) - if cwd: - assert_bash_exec(bash, "cd - >/dev/null") - bash_restore_variable(bash, "OLDPWD") - return result + return CompletionResult() @pytest.fixture diff --git a/test/t/test_man.py b/test/t/test_man.py index 0cbb8712b75..366b54d29cc 100644 --- a/test/t/test_man.py +++ b/test/t/test_man.py @@ -3,8 +3,7 @@ from conftest import ( assert_bash_exec, assert_complete, - bash_restore_variable, - bash_save_variable, + bash_env_saved, prepare_fixture_dir, ) @@ -101,21 +100,22 @@ def test_8(self, completion): "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(require_cmd=True) def test_10(self, request, bash, colonpath): - bash_save_variable( - bash, "MANPATH", "%s:%s/man" % (TestMan.manpath, colonpath) - ) - assert_bash_exec(bash, "export MANPATH") - completion = assert_complete(bash, "man Bash::C") - bash_restore_variable(bash, "MANPATH") - assert completion == "ompletion" + with bash_env_saved(bash) as bash_env: + bash_env.write_env( + "MANPATH", + "%s:%s/man" % (TestMan.manpath, colonpath), + quote=False, + ) + + completion = assert_complete(bash, "man Bash::C") + assert completion == "ompletion" @pytest.mark.complete("man -", require_cmd=True) def test_11(self, completion): diff --git a/test/t/test_tar.py b/test/t/test_tar.py index 3fbaa54a1f8..4d526900350 100644 --- a/test/t/test_tar.py +++ b/test/t/test_tar.py @@ -13,10 +13,9 @@ def gnu_tar(self, bash): if not re.search(r"\bGNU ", got): pytest.skip("Not GNU tar") - @pytest.mark.complete("tar ", pre_cmds=("shopt -s failglob",)) + @pytest.mark.complete("tar ", shopt=dict(failglob=True)) def test_1(self, bash, completion): assert completion - assert_bash_exec(bash, "shopt -u failglob") # Test "f" when mode is not as first option @pytest.mark.complete("tar zfc ", cwd="tar") diff --git a/test/t/unit/test_unit_known_hosts_real.py b/test/t/unit/test_unit_known_hosts_real.py index 2d9e5936365..327f0d5983e 100644 --- a/test/t/unit/test_unit_known_hosts_real.py +++ b/test/t/unit/test_unit_known_hosts_real.py @@ -2,11 +2,7 @@ import pytest -from conftest import ( - assert_bash_exec, - bash_restore_variable, - bash_save_variable, -) +from conftest import assert_bash_exec, bash_env_saved @pytest.mark.bashcomp( @@ -130,31 +126,28 @@ def test_included_configs(self, bash, hosts): # fixtures/_known_hosts_real/.ssh/config_question_mark expected.append("question_mark") - bash_save_variable(bash, "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, - ) - bash_restore_variable(bash, "HOME") + with bash_env_saved(bash) as bash_env: + bash_env.write_variable("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 sorted(set(output.strip().split())) == sorted(expected) def test_no_globbing(self, bash): - bash_save_variable(bash, "HOME", "%s/_known_hosts_real" % bash.cwd) - bash_save_variable(bash, "OLDPWD") - 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, - ) - bash_restore_variable(bash, "OLDPWD") - bash_restore_variable(bash, "HOME") + with bash_env_saved(bash) as bash_env: + bash_env.write_variable("HOME", "%s/_known_hosts_real" % bash.cwd) + bash_env.chdir("_known_hosts_real") + output = assert_bash_exec( + bash, + "unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; " + "_known_hosts_real -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_quote_readline.py b/test/t/unit/test_unit_quote_readline.py index a7ac52ec5d1..1df70bd92a6 100644 --- a/test/t/unit/test_unit_quote_readline.py +++ b/test/t/unit/test_unit_quote_readline.py @@ -2,7 +2,7 @@ import pytest -from conftest import assert_bash_exec, assert_complete +from conftest import assert_bash_exec, assert_complete, bash_env_saved @pytest.mark.bashcomp(cmd=None, temp_cwd=True) @@ -75,6 +75,21 @@ def test_github_issue_492_3(self, bash): os.mkdir("./ret=$(echo injected >&2)") assert_bash_exec(bash, "quote_readline $'\\'$*' >/dev/null") + def test_github_issue_492_4(self, bash): + """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, "quote_readline $'a\\\\\\tb*' >/dev/null") + def test_github_issue_526_1(self, bash): r"""Regression tests for unprocessed escape sequences after quotes @@ -103,19 +118,3 @@ def test_github_issue_526_1(self, bash): ) == "eta/" ) - - -@pytest.mark.bashcomp(cmd=None, temp_cwd=True, pre_cmds=("shopt -s failglob",)) -class TestUnitQuoteReadlineWithFailglob: - def test_github_issue_492_4(self, bash): - """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] - - """ - assert_bash_exec(bash, "quote_readline $'a\\\\\\tb*' >/dev/null")