diff options
| author | Charles Cabergs <me@cacharle.xyz> | 2021-03-03 12:24:41 +0100 |
|---|---|---|
| committer | Charles Cabergs <me@cacharle.xyz> | 2021-03-03 12:24:41 +0100 |
| commit | 820102f9ccdfdfe3ae78c22f803da4db87cb91ba (patch) | |
| tree | c06d6d1276a6216f7db156448eb0db0ed361ec52 | |
| parent | 716265929bf861d340c1e71e2f24359875520d3a (diff) | |
| download | minishell_test-820102f9ccdfdfe3ae78c22f803da4db87cb91ba.tar.gz minishell_test-820102f9ccdfdfe3ae78c22f803da4db87cb91ba.tar.bz2 minishell_test-820102f9ccdfdfe3ae78c22f803da4db87cb91ba.zip | |
Added test.test.Test tests
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | minishell_test/hooks.py | 3 | ||||
| -rw-r--r-- | minishell_test/sandbox.py | 3 | ||||
| -rw-r--r-- | minishell_test/test/test.py | 32 | ||||
| -rw-r--r-- | setup.cfg | 7 | ||||
| -rwxr-xr-x | tests/test/bin/minishell-echo | 3 | ||||
| -rwxr-xr-x | tests/test/bin/minishell-file | 4 | ||||
| -rwxr-xr-x | tests/test/bin/minishell-timeout | 3 | ||||
| -rw-r--r-- | tests/test/test_test.py | 137 | ||||
| -rw-r--r-- | tests/test_colors.py | 5 |
10 files changed, 173 insertions, 25 deletions
@@ -1,6 +1,7 @@ *__pycache__* *.log bin/ +!tests/test/bin/ tags dist/ *.egg-info diff --git a/minishell_test/hooks.py b/minishell_test/hooks.py index 54963c9..0890565 100644 --- a/minishell_test/hooks.py +++ b/minishell_test/hooks.py @@ -6,7 +6,7 @@ # By: charles <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/09/11 16:10:20 by charles #+# #+# # -# Updated: 2021/02/28 12:06:14 by cacharle ### ########.fr # +# Updated: 2021/03/03 09:25:48 by cacharle ### ########.fr # # # # ############################################################################ # @@ -27,7 +27,6 @@ def error_line0(output): """Replace "/bin/bash: -c: line n:" by "minishell:" and delete the second line""" if not Config.check_error_messages: return DISCARDED_TEXT - lines = output.split('\n') if len(lines) != 3: return output diff --git a/minishell_test/sandbox.py b/minishell_test/sandbox.py index 6fafc36..2608c9d 100644 --- a/minishell_test/sandbox.py +++ b/minishell_test/sandbox.py @@ -6,14 +6,13 @@ # By: charles <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/09/11 13:48:07 by charles #+# #+# # -# Updated: 2021/03/03 09:15:11 by cacharle ### ########.fr # +# Updated: 2021/03/03 10:00:16 by cacharle ### ########.fr # # # # ############################################################################ # import shutil import subprocess from contextlib import contextmanager -from pathlib import Path from minishell_test.config import Config diff --git a/minishell_test/test/test.py b/minishell_test/test/test.py index df149e2..821e3c2 100644 --- a/minishell_test/test/test.py +++ b/minishell_test/test/test.py @@ -6,7 +6,7 @@ # By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/06/16 21:48:50 by charles #+# #+# # -# Updated: 2021/03/02 19:08:25 by cacharle ### ########.fr # +# Updated: 2021/03/03 12:23:59 by cacharle ### ########.fr # # # # ############################################################################ # @@ -28,6 +28,8 @@ T = TypeVar('T') class Test: + __test__ = False # Tell pytest to ignore this class + def __init__( self, cmd: str, @@ -49,9 +51,9 @@ class Test: Exported variables to :param:`cmd` :param timeout: Maximum amount of time taken by the test - :param hook: + :param hooks: Function to execute on the test output - :param hook_status: + :param hooks_status: Function to execute on the test status code """ self._cmd = cmd @@ -75,11 +77,9 @@ class Test: def run(self) -> Union[Result, LeakResult]: """ Run the test for minishell and the reference shell and print the result out """ - if Config.check_leaks: captured = self._run_sandboxed(*Config.valgrind_cmd, "-c") return LeakResult(self._extended_cmd, captured) - expected = self._run_sandboxed(Config.shell_reference_path, *Config.shell_reference_args, "-c") actual = self._run_sandboxed(Config.minishell_exec_path, "-c") return Result(self._extended_cmd, self._files, expected, actual) @@ -98,7 +98,7 @@ class Test: check=True ) except subprocess.CalledProcessError as e: - raise TestSetupFailedException(self._setup, self._cmd, e.stdout) + raise TestSetupException(self._setup, self._cmd, e.stdout) return self._run_capture(*argv) def _run_capture(self, *argv: Union[str, Path]) -> CapturedType: @@ -127,7 +127,7 @@ class Test: try: output = stdout.decode() except UnicodeDecodeError: - output = "UNICODE ERROR: {process.stdout}" + output = "UNICODE DECODE ERROR: {process.stdout}" # capture watched files content files_content: List[Optional[str]] = [] @@ -138,8 +138,8 @@ class Test: except FileNotFoundError: files_content.append(None) - output = Test._apply_hook(output, self._hooks) - process.returncode = Test._apply_hook(process.returncode, self._hooks_status) + output = Test._apply_hooks(output, self._hooks) + process.returncode = Test._apply_hooks(process.returncode, self._hooks_status) # replace reference prefix with minishell prefix lines = output.split('\n') @@ -152,7 +152,7 @@ class Test: return CapturedCommand(output, process.returncode, files_content) @staticmethod - def _apply_hook(origin: T, hooks: List[Callable[[T], T]]) -> T: + def _apply_hooks(origin: T, hooks: List[Callable[[T], T]]) -> T: if Config.check_leaks: return origin for hook in hooks: @@ -167,7 +167,7 @@ class Test: exports = ' '.join(f"{k}='{v}'" for k, v in self._exports.items()) s = f"[EXPORTS {exports}] {s}" if self._setup != "": - s = "[SETUP {self._setup}] {s}" + s = f"[SETUP {self._setup}] {s}" return s @classmethod @@ -182,11 +182,15 @@ class Test: return "No output" -class TestSetupFailedException(Exception): - def __init__(self, setup: str, cmd: str, stdout: bytes): +class TestSetupException(Exception): + __test__ = False + + def __init__(self, setup: str, cmd: str, stdout: Optional[bytes]): self._setup = setup self._cmd = cmd - self._setup_output = "no output" if stdout is None else stdout.decode().strip() + if stdout is None or stdout.decode().strip() == "": + stdout = b"no output" + self._setup_output = stdout.decode().strip() def __str__(self): return f"Error: `{self._setup}` setup command failed for `{self._cmd}`\n\twith '{self._setup_output}'" @@ -43,3 +43,10 @@ max-cognitive-complexity = 6 [mypy] check_untyped_defs = true disallow_incomplete_defs = true + +[coverage:run] +omit = minishell_test/suites/* +source = minishell_test + +[coverage:report] +show_missing = true diff --git a/tests/test/bin/minishell-echo b/tests/test/bin/minishell-echo new file mode 100755 index 0000000..0f36d55 --- /dev/null +++ b/tests/test/bin/minishell-echo @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "$2" diff --git a/tests/test/bin/minishell-file b/tests/test/bin/minishell-file new file mode 100755 index 0000000..4f22ea2 --- /dev/null +++ b/tests/test/bin/minishell-file @@ -0,0 +1,4 @@ +#!/bin/sh + +echo bonjour > bonjour +echo aurevoir > aurevoir diff --git a/tests/test/bin/minishell-timeout b/tests/test/bin/minishell-timeout new file mode 100755 index 0000000..22d57d1 --- /dev/null +++ b/tests/test/bin/minishell-timeout @@ -0,0 +1,3 @@ +#!/bin/sh + +/usr/bin/yes diff --git a/tests/test/test_test.py b/tests/test/test_test.py index 5e2176b..15f3e9b 100644 --- a/tests/test/test_test.py +++ b/tests/test/test_test.py @@ -6,18 +6,20 @@ # By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2021/03/02 18:48:57 by cacharle #+# #+# # -# Updated: 2021/03/02 18:50:07 by cacharle ### ########.fr # +# Updated: 2021/03/03 12:22:32 by cacharle ### ########.fr # # # # ############################################################################ # import pytest +from pathlib import Path from minishell_test.config import Config from minishell_test import colors -from minishell_test.test.result import BaseResult, Result, LeakResult, LeakResultException +from minishell_test.test.result import LeakResult from minishell_test.test.captured import CapturedCommand, CapturedTimeout +from minishell_test.test.test import Test, TestSetupException from tests.helpers import config_context @@ -25,6 +27,133 @@ from tests.helpers import config_context colors.disable() Config.init([]) + class TestTest: - def test_run(self): - pass + def test_init_timeout(self): + assert Config.timeout_test == Test("")._timeout + assert Config.timeout_test == Test("", timeout=0)._timeout + assert 2 == Test("", timeout=2)._timeout + assert 100 == Test("", timeout=100)._timeout + with config_context(check_leaks=True): + assert Config.timeout_leaks == Test("", timeout=-10)._timeout + assert Config.timeout_leaks == Test("", timeout=0)._timeout + assert Config.timeout_leaks == Test("", timeout=1)._timeout + assert Config.timeout_leaks == Test("", timeout=100)._timeout + + def test_coerce_into_list(self): + assert [1] == Test._coerce_into_list(1) + assert [1] == Test._coerce_into_list([1]) + assert ["bonjour"] == Test._coerce_into_list("bonjour") + assert ["bonjour"] == Test._coerce_into_list(["bonjour"]) + assert ["bonjour", "foo", "bar"] == Test._coerce_into_list(["bonjour", "foo", "bar"]) + + def test_apply_hook(self): + bonjour_to_foo = lambda x: x.replace("bonjour", "foo") # noqa + foo_to_aurevoir = lambda x: x.replace("foo", "aurevoir") # noqa + assert "foo" == Test._apply_hooks("bonjour", [bonjour_to_foo]) + assert "aurevoir" == Test._apply_hooks("foo", [foo_to_aurevoir]) + assert "aurevoir" == Test._apply_hooks("bonjour", [bonjour_to_foo, foo_to_aurevoir]) + plus_one = lambda x: x + 1 # noqa + minus_one = lambda x: x - 1 # noqa + assert 1 == Test._apply_hooks(0, [plus_one]) + assert 5 == Test._apply_hooks(0, [plus_one, plus_one, plus_one, plus_one, plus_one]) + assert -1 == Test._apply_hooks(0, [minus_one]) + assert -5 == Test._apply_hooks(0, [minus_one, minus_one, minus_one, minus_one, minus_one]) + assert 0 == Test._apply_hooks(0, [plus_one, minus_one, plus_one, minus_one, plus_one, minus_one]) + with config_context(check_leaks=True): + assert "foo" == Test._apply_hooks("foo", [foo_to_aurevoir]) + assert "bonjour" == Test._apply_hooks("bonjour", [bonjour_to_foo, foo_to_aurevoir]) + assert 0 == Test._apply_hooks(0, [plus_one, plus_one, plus_one, plus_one, plus_one]) + assert 0 == Test._apply_hooks(0, [minus_one, minus_one, minus_one, minus_one, minus_one]) + + def test_extended_cmd(self): + assert "" == Test("")._extended_cmd + assert "echo bonjour" == Test("echo bonjour")._extended_cmd + assert "[SETUP echo foo > bar] echo bonjour" == Test("echo bonjour", setup="echo foo > bar")._extended_cmd + assert "[EXPORTS A='a' B='b' C='c'] echo bonjour" == Test("echo bonjour", exports={"A": "a", "B": "b", "C": "c"})._extended_cmd + assert "[SETUP echo foo > bar] [EXPORTS A='a' B='b' C='c'] echo bonjour" == Test("echo bonjour", setup="echo foo > bar", exports={"A": "a", "B": "b", "C": "c"})._extended_cmd + + BIN_DIR = Path(__file__).parent / "bin" + + def test_run_echo(self): + with config_context(minishell_exec_path=self.BIN_DIR / "minishell-echo"): + r = Test("bonjour").run() + assert r.actual == CapturedCommand("bonjour\n", 0, []) + assert r.expected.status != 0 + + @pytest.mark.parametrize( + "cmd", + [ + "echo bonjour", + "ls -la /", + "ls -la / | cat -e", + "cat somefile", + "ls somedir", + "echo $FOO", + ], + ) + @pytest.mark.parametrize( + "setup", + [ + "echo bonjour > somefile", + "mkdir somedir; echo bonjour > somedir/somefile", + ], + ) + @pytest.mark.parametrize( + "exports", + [ + {}, + {"FOO": "foo"} + ], + ) + def test_run_minishell_is_bash(self, cmd, setup, exports): + with config_context(minishell_exec_path=Path("/bin/bash")): + r = Test(cmd).run() + assert r.passed + + @pytest.mark.parametrize( + "setup", + [ + ("exit 1", "no output"), + ("echo ' ' ; exit 1", "no output"), + ("echo bonjour ; exit 1", "bonjour"), + ("echo aurevoir 2>&1 ; exit 1", "aurevoir"), + ("echo bonjour; echo aurevoir 2>&1 ; exit 1", "bonjour\naurevoir"), + ], + ) + def test_run_bad_setup(self, setup): + with pytest.raises(TestSetupException) as e: + Test("yes", setup=setup[0]).run() + assert setup[0] == e.value._setup + assert "yes" == e.value._cmd + assert f"Error: `{setup[0]}` setup command failed for `yes`\n\twith '{setup[1]}'" == e.value.__str__() + + def test_run_check_leaks(self): + with config_context(check_leaks=True, valgrind_cmd=["/bin/bash"]): + r = Test("echo bonjour").run() + assert isinstance(r, LeakResult) + assert "bonjour\n" == r._captured.output + assert "echo bonjour" == r._cmd + + @pytest.mark.parametrize("timeout", [0.05, 0.1, 0.15, 0.2, 0.3]) + def test_run_timeout(self, timeout): + with config_context(minishell_exec_path=self.BIN_DIR / "minishell-timeout"): + r = Test("echo bonjour", timeout=timeout).run() + assert "bonjour\n" == r.expected.output + assert 0 == r.expected.status + assert isinstance(r.actual, CapturedTimeout) + + @pytest.mark.parametrize("file", ["/dev/random", "/dev/urandom"]) + def test_run_decode_error(self, file): + with config_context(minishell_exec_path="/bin/bash"): + r = Test(f"/usr/bin/head -c 100 {file}").run() + assert r.expected.output.startswith("UNICODE DECODE ERROR: ") + assert r.actual.output.startswith("UNICODE DECODE ERROR: ") + assert 0 == r.expected.status + assert 0 == r.actual.status + + def test_run_files(self): + with config_context(minishell_exec_path=self.BIN_DIR / "minishell-file"): + r = Test("echo bonjour > bonjour", files=["bonjour", "aurevoir"]).run() + assert CapturedCommand("", 0, ["bonjour\n", None]) == r.expected + assert CapturedCommand("", 0, ["bonjour\n", "aurevoir\n"]) == r.actual diff --git a/tests/test_colors.py b/tests/test_colors.py index 2edcf27..787495a 100644 --- a/tests/test_colors.py +++ b/tests/test_colors.py @@ -6,7 +6,7 @@ # By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2021/03/03 07:56:43 by cacharle #+# #+# # -# Updated: 2021/03/03 08:07:46 by cacharle ### ########.fr # +# Updated: 2021/03/03 10:00:43 by cacharle ### ########.fr # # # # ############################################################################ # @@ -15,8 +15,6 @@ import pytest from minishell_test import colors - - @pytest.fixture def texts(): return [ @@ -51,6 +49,7 @@ def test_bold(texts): for text in texts: assert colors._DEFAULTS["bold"] + text + colors._DEFAULTS["close"] == colors.bold(text) + def test_toggling(texts): colors.disable() for text in texts: |
