aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Cabergs <me@cacharle.xyz>2021-03-03 12:24:41 +0100
committerCharles Cabergs <me@cacharle.xyz>2021-03-03 12:24:41 +0100
commit820102f9ccdfdfe3ae78c22f803da4db87cb91ba (patch)
treec06d6d1276a6216f7db156448eb0db0ed361ec52
parent716265929bf861d340c1e71e2f24359875520d3a (diff)
downloadminishell_test-820102f9ccdfdfe3ae78c22f803da4db87cb91ba.tar.gz
minishell_test-820102f9ccdfdfe3ae78c22f803da4db87cb91ba.tar.bz2
minishell_test-820102f9ccdfdfe3ae78c22f803da4db87cb91ba.zip
Added test.test.Test tests
-rw-r--r--.gitignore1
-rw-r--r--minishell_test/hooks.py3
-rw-r--r--minishell_test/sandbox.py3
-rw-r--r--minishell_test/test/test.py32
-rw-r--r--setup.cfg7
-rwxr-xr-xtests/test/bin/minishell-echo3
-rwxr-xr-xtests/test/bin/minishell-file4
-rwxr-xr-xtests/test/bin/minishell-timeout3
-rw-r--r--tests/test/test_test.py137
-rw-r--r--tests/test_colors.py5
10 files changed, 173 insertions, 25 deletions
diff --git a/.gitignore b/.gitignore
index 5a14103..24a3287 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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}'"
diff --git a/setup.cfg b/setup.cfg
index 3920b03..0fbfd85 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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: