Skip to content

Commit 58f02b9

Browse files
committed
test(conftest): add new context manager "bash_env_saved"
1 parent 05b70ec commit 58f02b9

File tree

5 files changed

+204
-117
lines changed

5 files changed

+204
-117
lines changed

test/t/conftest.py

Lines changed: 156 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@
88
import tempfile
99
import time
1010
from pathlib import Path
11-
from typing import Callable, Iterable, Iterator, List, Optional, Tuple
11+
from types import TracebackType
12+
from typing import (
13+
Callable,
14+
Dict,
15+
Iterable,
16+
Iterator,
17+
List,
18+
Optional,
19+
Tuple,
20+
Type,
21+
)
1222

1323
import pexpect # type: ignore[import]
1424
import pytest
1525

1626
PS1 = "/@"
1727
MAGIC_MARK = "__MaGiC-maRKz!__"
28+
MAGIC_MARK2 = "Re8SCgEdfN"
1829

1930

2031
def find_unique_completion_pair(
@@ -387,26 +398,139 @@ def assert_bash_exec(
387398
return output
388399

389400

390-
def _bash_copy_variable(bash: pexpect.spawn, src_var: str, dst_var: str):
391-
assert_bash_exec(
392-
bash,
393-
"if [[ ${%s+set} ]]; then %s=${%s}; else unset -v %s; fi"
394-
% (src_var, dst_var, src_var, dst_var),
395-
)
401+
class bash_env_saved:
402+
def __init__(self, bash: pexpect.spawn, sendintr: bool = False):
403+
self.bash = bash
404+
self.cwd: Optional[str] = None
405+
self.saved_shopt: Dict[str, int] = {}
406+
self.saved_variables: Dict[str, int] = {}
407+
self.sendintr = sendintr
408+
409+
def __enter__(self):
410+
return self
396411

412+
def __exit__(
413+
self,
414+
exc_type: Optional[Type[BaseException]],
415+
exc_value: Optional[BaseException],
416+
exc_traceback: Optional[TracebackType],
417+
) -> None:
418+
self._restore_env()
419+
return None
397420

398-
def bash_save_variable(
399-
bash: pexpect.spawn, varname: str, new_value: Optional[str] = None
400-
):
401-
_bash_copy_variable(bash, varname, "_bash_completion_test_" + varname)
402-
if new_value:
421+
def _copy_variable(self, src_var: str, dst_var: str):
403422
assert_bash_exec(
404-
bash, "%s=%s" % (varname, shlex.quote(str(new_value)))
423+
self.bash,
424+
"if [[ ${%s+set} ]]; then %s=${%s}; else unset -v %s; fi"
425+
% (src_var, dst_var, src_var, dst_var),
405426
)
406427

428+
def _unset_variable(self, varname: str):
429+
assert_bash_exec(self.bash, "unset -v %s" % varname)
430+
431+
def _save_cwd(self):
432+
if not self.cwd:
433+
self.cwd = self.bash.cwd
434+
435+
def _check_shopt(self, name: str):
436+
assert_bash_exec(
437+
self.bash,
438+
'[[ $(shopt -p %s) == "${_BASHCOMP_TEST_NEWSHOPT_%s}" ]]'
439+
% (name, name),
440+
)
441+
442+
def _unprotect_shopt(self, name: str):
443+
if name not in self.saved_shopt:
444+
self.saved_shopt[name] = 1
445+
assert_bash_exec(
446+
self.bash,
447+
"_BASHCOMP_TEST_OLDSHOPT_%s=$(shopt -p %s; true)"
448+
% (name, name),
449+
)
450+
else:
451+
self._check_shopt(name)
407452

408-
def bash_restore_variable(bash: pexpect.spawn, varname: str):
409-
_bash_copy_variable(bash, "_bash_completion_test_" + varname, varname)
453+
def _protect_shopt(self, name: str):
454+
assert_bash_exec(
455+
self.bash,
456+
"_BASHCOMP_TEST_NEWSHOPT_%s=$(shopt -p %s; true)" % (name, name),
457+
)
458+
459+
def _check_variable(self, varname: str):
460+
assert_bash_exec(
461+
self.bash,
462+
'[[ ${%s-%s} == "${_BASHCOMP_TEST_NEWVAR_%s-%s}" ]]'
463+
% (varname, MAGIC_MARK2, varname, MAGIC_MARK2),
464+
)
465+
466+
def _unprotect_variable(self, varname: str):
467+
if varname not in self.saved_variables:
468+
self.saved_variables[varname] = 1
469+
self._copy_variable(varname, "_BASHCOMP_TEST_OLDVAR_" + varname)
470+
else:
471+
self._check_variable(varname)
472+
473+
def _protect_variable(self, varname: str):
474+
self._copy_variable(varname, "_BASHCOMP_TEST_NEWVAR_" + varname)
475+
476+
def _restore_env(self):
477+
if self.sendintr:
478+
self.bash.sendintr()
479+
self.bash.expect_exact(PS1)
480+
481+
# We first go back to the original directory before restoring
482+
# variables because "cd" affects "OLDPWD".
483+
if self.cwd:
484+
self._unprotect_variable("OLDPWD")
485+
assert_bash_exec(self.bash, "cd %s" % shlex.quote(str(self.cwd)))
486+
self._protect_variable("OLDPWD")
487+
self.cwd = None
488+
489+
for name in self.saved_shopt:
490+
self._check_shopt(name)
491+
assert_bash_exec(
492+
self.bash, 'eval "$_BASHCOMP_TEST_OLDSHOPT_%s"' % name
493+
)
494+
self._unset_variable("_BASHCOMP_TEST_OLDSHOPT_" + name)
495+
self._unset_variable("_BASHCOMP_TEST_NEWSHOPT_" + name)
496+
self.saved_shopt = {}
497+
498+
for varname in self.saved_variables:
499+
self._check_variable(varname)
500+
self._copy_variable("_BASHCOMP_TEST_OLDVAR_" + varname, varname)
501+
self._unset_variable("_BASHCOMP_TEST_OLDVAR_" + varname)
502+
self._unset_variable("_BASHCOMP_TEST_NEWVAR_" + varname)
503+
self.saved_variables = {}
504+
505+
def chdir(self, path: str):
506+
self._save_cwd()
507+
self._unprotect_variable("OLDPWD")
508+
assert_bash_exec(self.bash, "cd %s" % shlex.quote(path))
509+
self._protect_variable("OLDPWD")
510+
511+
def shopt(self, name: str, value: bool):
512+
self._unprotect_shopt(name)
513+
if value:
514+
assert_bash_exec(self.bash, "shopt -s %s" % name)
515+
else:
516+
assert_bash_exec(self.bash, "shopt -u %s" % name)
517+
self._protect_shopt(name)
518+
519+
def write_variable(self, varname: str, new_value: str, quote: bool = True):
520+
if quote:
521+
new_value = shlex.quote(new_value)
522+
self._unprotect_variable(varname)
523+
assert_bash_exec(self.bash, "%s=%s" % (varname, new_value))
524+
self._protect_variable(varname)
525+
526+
# TODO: We may restore the "export" attribute as well though it is
527+
# not currently tested in "diff_env"
528+
def write_env(self, envname: str, new_value: str, quote: bool = True):
529+
if quote:
530+
new_value = shlex.quote(new_value)
531+
self._unprotect_variable(envname)
532+
assert_bash_exec(self.bash, "export %s=%s" % (envname, new_value))
533+
self._protect_variable(envname)
410534

411535

412536
def get_env(bash: pexpect.spawn) -> List[str]:
@@ -433,7 +557,7 @@ def diff_env(before: List[str], after: List[str], ignore: str):
433557
if not re.search(r"^(---|\+\+\+|@@ )", x)
434558
# Ignore variables expected to change:
435559
and not re.search(
436-
r"^[-+](_|PPID|BASH_REMATCH|_bash_completion_test_\w+)=",
560+
r"^[-+](_|PPID|BASH_REMATCH|_BASHCOMP_TEST_\w+)=",
437561
x,
438562
re.ASCII,
439563
)
@@ -520,23 +644,19 @@ def assert_complete(
520644
pass
521645
else:
522646
pytest.xfail(xfail)
523-
cwd = kwargs.get("cwd")
524-
if cwd:
525-
bash_save_variable(bash, "OLDPWD")
526-
assert_bash_exec(bash, "cd '%s'" % cwd)
527-
env_prefix = "_BASHCOMP_TEST_"
528-
env = kwargs.get("env", {})
529-
if env:
530-
# Back up environment and apply new one
531-
assert_bash_exec(
532-
bash,
533-
" ".join('%s%s="${%s-}"' % (env_prefix, k, k) for k in env.keys()),
534-
)
535-
assert_bash_exec(
536-
bash,
537-
"export %s" % " ".join("%s=%s" % (k, v) for k, v in env.items()),
538-
)
539-
try:
647+
648+
with bash_env_saved(bash, sendintr=True) as bash_env:
649+
650+
cwd = kwargs.get("cwd")
651+
if cwd:
652+
bash_env.chdir(str(cwd))
653+
654+
for k, v in kwargs.get("env", {}).items():
655+
bash_env.write_env(k, v, quote=False)
656+
657+
for k, v in kwargs.get("shopt", {}).items():
658+
bash_env.shopt(k, v)
659+
540660
bash.send(cmd + "\t")
541661
# Sleep a bit if requested, to avoid `.*` matching too early
542662
time.sleep(kwargs.get("sleep_after_tab", 0))
@@ -558,37 +678,13 @@ def assert_complete(
558678
output = bash.before
559679
if output.endswith(MAGIC_MARK):
560680
output = bash.before[: -len(MAGIC_MARK)]
561-
result = CompletionResult(output)
681+
return CompletionResult(output)
562682
elif got == 2:
563683
output = bash.match.group(1)
564-
result = CompletionResult(output)
684+
return CompletionResult(output)
565685
else:
566686
# TODO: warn about EOF/TIMEOUT?
567-
result = CompletionResult()
568-
finally:
569-
bash.sendintr()
570-
bash.expect_exact(PS1)
571-
if env:
572-
# Restore environment, and clean up backup
573-
# TODO: Test with declare -p if a var was set, backup only if yes, and
574-
# similarly restore only backed up vars. Should remove some need
575-
# for ignore_env.
576-
assert_bash_exec(
577-
bash,
578-
"export %s"
579-
% " ".join(
580-
'%s="$%s%s"' % (k, env_prefix, k) for k in env.keys()
581-
),
582-
)
583-
assert_bash_exec(
584-
bash,
585-
"unset -v %s"
586-
% " ".join("%s%s" % (env_prefix, k) for k in env.keys()),
587-
)
588-
if cwd:
589-
assert_bash_exec(bash, "cd - >/dev/null")
590-
bash_restore_variable(bash, "OLDPWD")
591-
return result
687+
return CompletionResult()
592688

593689

594690
@pytest.fixture

test/t/test_man.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
from conftest import (
44
assert_bash_exec,
55
assert_complete,
6-
bash_restore_variable,
7-
bash_save_variable,
6+
bash_env_saved,
87
prepare_fixture_dir,
98
)
109

@@ -101,21 +100,22 @@ def test_8(self, completion):
101100
"man %s" % assumed_present,
102101
require_cmd=True,
103102
cwd="shared/empty_dir",
104-
pre_cmds=("shopt -s failglob",),
103+
shopt=dict(failglob=True),
105104
)
106105
def test_9(self, bash, completion):
107106
assert self.assumed_present in completion
108-
assert_bash_exec(bash, "shopt -u failglob")
109107

110108
@pytest.mark.complete(require_cmd=True)
111109
def test_10(self, request, bash, colonpath):
112-
bash_save_variable(
113-
bash, "MANPATH", "%s:%s/man" % (TestMan.manpath, colonpath)
114-
)
115-
assert_bash_exec(bash, "export MANPATH")
116-
completion = assert_complete(bash, "man Bash::C")
117-
bash_restore_variable(bash, "MANPATH")
118-
assert completion == "ompletion"
110+
with bash_env_saved(bash) as bash_env:
111+
bash_env.write_env(
112+
"MANPATH",
113+
"%s:%s/man" % (TestMan.manpath, colonpath),
114+
quote=False,
115+
)
116+
117+
completion = assert_complete(bash, "man Bash::C")
118+
assert completion == "ompletion"
119119

120120
@pytest.mark.complete("man -", require_cmd=True)
121121
def test_11(self, completion):

test/t/test_tar.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ def gnu_tar(self, bash):
1313
if not re.search(r"\bGNU ", got):
1414
pytest.skip("Not GNU tar")
1515

16-
@pytest.mark.complete("tar ", pre_cmds=("shopt -s failglob",))
16+
@pytest.mark.complete("tar ", shopt=dict(failglob=True))
1717
def test_1(self, bash, completion):
1818
assert completion
19-
assert_bash_exec(bash, "shopt -u failglob")
2019

2120
# Test "f" when mode is not as first option
2221
@pytest.mark.complete("tar zfc ", cwd="tar")

test/t/unit/test_unit_known_hosts_real.py

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
import pytest
44

5-
from conftest import (
6-
assert_bash_exec,
7-
bash_restore_variable,
8-
bash_save_variable,
9-
)
5+
from conftest import assert_bash_exec, bash_env_saved
106

117

128
@pytest.mark.bashcomp(
@@ -130,31 +126,28 @@ def test_included_configs(self, bash, hosts):
130126
# fixtures/_known_hosts_real/.ssh/config_question_mark
131127
expected.append("question_mark")
132128

133-
bash_save_variable(bash, "HOME", "%s/_known_hosts_real" % bash.cwd)
134-
output = assert_bash_exec(
135-
bash,
136-
"unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
137-
"_known_hosts_real -aF _known_hosts_real/config_include ''; "
138-
r'printf "%s\n" "${COMPREPLY[@]}"',
139-
want_output=True,
140-
)
141-
bash_restore_variable(bash, "HOME")
129+
with bash_env_saved(bash) as bash_env:
130+
bash_env.write_variable("HOME", "%s/_known_hosts_real" % bash.cwd)
131+
output = assert_bash_exec(
132+
bash,
133+
"unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
134+
"_known_hosts_real -aF _known_hosts_real/config_include ''; "
135+
r'printf "%s\n" "${COMPREPLY[@]}"',
136+
want_output=True,
137+
)
142138
assert sorted(set(output.strip().split())) == sorted(expected)
143139

144140
def test_no_globbing(self, bash):
145-
bash_save_variable(bash, "HOME", "%s/_known_hosts_real" % bash.cwd)
146-
bash_save_variable(bash, "OLDPWD")
147-
output = assert_bash_exec(
148-
bash,
149-
"cd _known_hosts_real; "
150-
"unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
151-
"_known_hosts_real -aF config ''; "
152-
r'printf "%s\n" "${COMPREPLY[@]}"; '
153-
"cd - &>/dev/null",
154-
want_output=True,
155-
)
156-
bash_restore_variable(bash, "OLDPWD")
157-
bash_restore_variable(bash, "HOME")
141+
with bash_env_saved(bash) as bash_env:
142+
bash_env.write_variable("HOME", "%s/_known_hosts_real" % bash.cwd)
143+
bash_env.chdir("_known_hosts_real")
144+
output = assert_bash_exec(
145+
bash,
146+
"unset -v COMPREPLY COMP_KNOWN_HOSTS_WITH_HOSTFILE; "
147+
"_known_hosts_real -aF config ''; "
148+
r'printf "%s\n" "${COMPREPLY[@]}"',
149+
want_output=True,
150+
)
158151
completion = sorted(set(output.strip().split()))
159152
assert "gee" in completion
160153
assert "gee-filename-canary" not in completion

0 commit comments

Comments
 (0)