aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--requirements.txt7
-rw-r--r--setup.cfg2
-rwxr-xr-xsrc/main.py5
-rw-r--r--src/suite/decorator.py9
-rw-r--r--src/suite/suite.py10
-rw-r--r--src/suites/cmd.py8
-rw-r--r--src/suites/path.py5
-rw-r--r--src/test/captured.py4
-rw-r--r--src/test/result.py55
-rw-r--r--src/test/test.py53
-rwxr-xr-xtry5
11 files changed, 93 insertions, 70 deletions
diff --git a/requirements.txt b/requirements.txt
index 0a35244..eefaf4e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,7 @@
-flake8==3.8.4
mypy==0.800
+flake8==3.8.4
+flake8_comprehensions==3.3.1
+pep8_naming==0.11.1
+flake8-expression-complexity
+flake8-isort
+flake8-cognitive-complexity
diff --git a/setup.cfg b/setup.cfg
index b6f3073..d9fec95 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,6 +26,8 @@ python_requires = >=3.6
[flake8]
ignore = E501,E221,W503,E241
+select = E,F,W,C4,N,ECE,CCR
+max-cognitive-complexity = 6
[mypy]
check_untyped_defs = true
diff --git a/src/main.py b/src/main.py
index 05e9158..fe48b5e 100755
--- a/src/main.py
+++ b/src/main.py
@@ -46,7 +46,10 @@ def main():
shutil.rmtree(config.EXECUTABLES_PATH)
os.mkdir(config.EXECUTABLES_PATH)
for cmd in config.AVAILABLE_COMMANDS:
- shutil.copy(distutils.spawn.find_executable(cmd),
+ cmd_path = distutils.spawn.find_executable(cmd)
+ if cmd_path is None:
+ raise RuntimeError
+ shutil.copy(cmd_path,
os.path.join(config.EXECUTABLES_PATH, cmd))
reference_args = os.environ.get("MINISHELL_TEST_ARGS")
diff --git a/src/suite/decorator.py b/src/suite/decorator.py
index 87787de..fdc7fb6 100644
--- a/src/suite/decorator.py
+++ b/src/suite/decorator.py
@@ -6,7 +6,7 @@
# By: charles <me@cacharle.xyz> +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ #
# Created: 2020/09/11 12:28:00 by charles #+# #+# #
-# Updated: 2021/01/31 04:45:08 by charles ### ########.fr #
+# Updated: 2021/02/04 16:18:11 by charles ### ########.fr #
# #
# ############################################################################ #
@@ -17,13 +17,16 @@ from suite import Suite
from test import Test
-def suite(groups: List[str] = [], bonus: bool = False):
+def suite(groups: List[str] = [], bonus: bool = False): # type: ignore
"""Decorator generator for suites arguments"""
def suite_wrapper(origin):
"""Decorator for a suite function (fmt: suite_[name]) """
- mod_name = inspect.getmodule(origin).__name__[len("suites."):]
+ mod = inspect.getmodule(origin)
+ if mod is None:
+ raise NotImplementedError
+ mod_name = mod.__name__[len("suites."):]
name = "{}/{}".format(mod_name, origin.__name__[len("suite_"):])
description = origin.__doc__
if description is None:
diff --git a/src/suite/suite.py b/src/suite/suite.py
index f3d58b1..836cac0 100644
--- a/src/suite/suite.py
+++ b/src/suite/suite.py
@@ -6,7 +6,7 @@
# By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ #
# Created: 2020/07/15 18:24:29 by charles #+# #+# #
-# Updated: 2021/01/31 04:40:22 by charles ### ########.fr #
+# Updated: 2021/02/04 16:13:08 by charles ### ########.fr #
# #
# ############################################################################ #
@@ -28,7 +28,7 @@ class Suite:
break
@classmethod
- def setup(cls, asked_names: List[str]):
+ def setup(cls, asked_names: List[str]) -> None:
""" Remove not asked suite from available suites
Tries to autocomplete the asked names
"""
@@ -49,7 +49,7 @@ class Suite:
or n.startswith(name)]
if len(matches) == 1:
names.append(matches[0])
- elif len(matches) != 0 and all([n.startswith(name) for n in matches]):
+ elif len(matches) != 0 and all(n.startswith(name) for n in matches):
names.extend(matches)
elif len(matches) > 2:
print(("Ambiguous name `{}` match the following suites\n\t{}\n"
@@ -64,7 +64,7 @@ class Suite:
cls.available = list(set(
[s for s in cls.available if s.name in names]
- + [s for s in cls.available if any([g for g in s.groups if g in names])]
+ + [s for s in cls.available if any(g for g in s.groups if g in names)]
))
cls.available.sort(key=lambda s: s.name)
for s in cls.available:
@@ -79,7 +79,7 @@ class Suite:
@classmethod
def list(cls):
print("Groups:")
- print("\n".join(set([" - " + ', '.join(s.groups) for s in Suite.available])))
+ print("\n".join({" - " + ', '.join(s.groups) for s in Suite.available}))
print("The available suites are:")
max_name_width = max(len(s.name) for s in Suite.available) + 5
lines = [
diff --git a/src/suites/cmd.py b/src/suites/cmd.py
index 19dd698..53b1f97 100644
--- a/src/suites/cmd.py
+++ b/src/suites/cmd.py
@@ -6,7 +6,7 @@
# By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ #
# Created: 2020/07/15 15:11:46 by charles #+# #+# #
-# Updated: 2020/10/15 10:40:11 by cacharle ### ########.fr #
+# Updated: 2021/02/04 16:14:54 by charles ### ########.fr #
# #
# ############################################################################ #
@@ -127,7 +127,13 @@ def suite_status(test):
def suite_cmd_path(test):
""" cmd is a relative path, permissions on executable """
ls_path = distutils.spawn.find_executable("ls")
+ if ls_path is None:
+ print("Couldn't find `ls` in your PATH: Skipping suite")
+ return
cat_path = distutils.spawn.find_executable("cat")
+ if cat_path is None:
+ print("Couldn't find `cat` in your PATH: Skipping suite")
+ return
test(ls_path, setup="touch a b c")
test(ls_path + " -l", setup="touch a b c")
test("./bonjour", setup="touch a b c; cp {} bonjour".format(ls_path))
diff --git a/src/suites/path.py b/src/suites/path.py
index 5db1e36..93d4232 100644
--- a/src/suites/path.py
+++ b/src/suites/path.py
@@ -6,7 +6,7 @@
# By: charles <me@cacharle.xyz> +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ #
# Created: 2020/09/09 15:12:58 by charles #+# #+# #
-# Updated: 2020/10/15 09:17:09 by cacharle ### ########.fr #
+# Updated: 2021/02/04 16:14:20 by charles ### ########.fr #
# #
# ############################################################################ #
@@ -19,6 +19,9 @@ from suite import suite
def suite_path(test):
""" searching a command in the path tests """
whoami_path = distutils.spawn.find_executable("which")
+ if whoami_path is None:
+ print("Couldn't find `whoami` in your PATH: Skipping suite")
+ return
mode_fmt = ("mkdir path && cp "
+ whoami_path
+ " ./path/a && chmod {} ./path/a")
diff --git a/src/test/captured.py b/src/test/captured.py
index 4cf9184..f7dae3e 100644
--- a/src/test/captured.py
+++ b/src/test/captured.py
@@ -6,7 +6,7 @@
# By: charles <me@cacharle.xyz> +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ #
# Created: 2020/09/11 12:16:25 by charles #+# #+# #
-# Updated: 2021/01/31 04:23:03 by charles ### ########.fr #
+# Updated: 2021/02/04 15:52:19 by charles ### ########.fr #
# #
# ############################################################################ #
@@ -48,7 +48,7 @@ class Captured:
return self.is_timeout == other.is_timeout
return (self.output == other.output
and self.status == other.status
- and all([x == y for x, y in zip(self.files_content, other.files_content)]))
+ and all(x == y for x, y in zip(self.files_content, other.files_content)))
@staticmethod
def timeout():
diff --git a/src/test/result.py b/src/test/result.py
index e4512ac..eff7b8b 100644
--- a/src/test/result.py
+++ b/src/test/result.py
@@ -6,13 +6,13 @@
# By: charles <me@cacharle.xyz> +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ #
# Created: 2020/09/11 12:17:34 by charles #+# #+# #
-# Updated: 2021/01/31 04:10:31 by charles ### ########.fr #
+# Updated: 2021/02/05 01:36:44 by charles ### ########.fr #
# #
# ############################################################################ #
import sys
import re
-from typing import Match, List
+from typing import Match, List, Optional
import config
from test.captured import Captured
@@ -25,7 +25,8 @@ class BaseResult:
BOLD_CHARS = "\033[1m"
CLOSE_CHARS = "\033[0m"
- def __init__(self):
+ def __init__(self, cmd: str):
+ self.cmd = cmd
self.colored = True
self.set_colors()
@@ -43,7 +44,7 @@ class BaseResult:
"""Returns a representation of the result based on the verbosity"""
if config.VERBOSE_LEVEL == 0:
return self.green('.') if self.passed else self.red('!')
- elif config.VERBOSE_LEVEL == 1:
+ if config.VERBOSE_LEVEL == 1:
printed = self._escaped_cmd[:]
if config.SHOW_RANGE:
printed = "{:2}: ".format(self.index) + printed
@@ -56,7 +57,7 @@ class BaseResult:
else:
raise RuntimeError("Invalid verbose level")
- def put(self, index: int):
+ def put(self, index: int) -> None:
"""Print a summary of the result"""
if config.VERBOSE_LEVEL == 2 and self.passed:
return
@@ -76,12 +77,13 @@ class BaseResult:
@property
def _escaped_cmd(self):
"""Escape common control characters"""
- return (self.cmd
- .replace("\t", "\\t")
- .replace("\n", "\\n")
- .replace("\v", "\\v")
- .replace("\r", "\\r")
- .replace("\f", "\\f"))
+ c = self.cmd
+ c = c.replace("\t", "\\t")
+ c = c.replace("\n", "\\n")
+ c = c.replace("\v", "\\v")
+ c = c.replace("\r", "\\r")
+ c = c.replace("\f", "\\f")
+ return c
@property
def _header_with(self):
@@ -129,11 +131,10 @@ class Result(BaseResult):
expected: expected capture
actual: actual capture
"""
- self.cmd = cmd
self.file_names = file_names
self.expected = expected
self.actual = actual
- super().__init__()
+ super().__init__(cmd)
@property
def passed(self):
@@ -144,14 +145,16 @@ class Result(BaseResult):
@property
def expected_header(self) -> str:
- return self.green(self.header("EXPECTED"))
+ return self.green(self.header("EXPECTED")) + '\n'
@property
def actual_header(self) -> str:
- return self.red(self.header("ACTUAL"))
+ return self.red(self.header("ACTUAL")) + '\n'
- def cat_e(self, s: str) -> str:
+ def cat_e(self, s: Optional[str]) -> str:
"""Pass a string through a cat -e like output"""
+ if s is None:
+ return "FROM TEST: File not created\n"
s = s.replace("\n", "$\n")
if len(s) < 2:
return s
@@ -159,16 +162,17 @@ class Result(BaseResult):
s += '\n'
return s
- def file_diff(self, file_name: str, expected: str, actual: str) -> str:
+ def file_diff(self, file_name: str, expected: Optional[str], actual: Optional[str]) -> str:
"""Difference between 2 files"""
if expected == actual:
return ""
+ file_header = self.indicator("FILE {}".format(file_name), "|#") + '\n'
return (
- self.indicator("FILE {}".format(file_name), "|#") + '\n'
- + self.expected_header + '\n'
- + ("FROM TEST: File not created\n" if expected is None else self.cat_e(expected))
- + self.actual_header + '\n'
- + ("FROM TEST: File not created\n" if actual is None else self.cat_e(actual))
+ file_header
+ + self.expected_header
+ + self.cat_e(expected)
+ + self.actual_header
+ + self.cat_e(actual)
)
def files_diff(self):
@@ -190,9 +194,9 @@ class Result(BaseResult):
.format(self.expected.status, self.actual.status), "| "
) + '\n'
if self.expected.output != self.actual.output:
- out += (self.expected_header + '\n'
+ out += (self.expected_header
+ self.cat_e(self.expected.output)
- + self.actual_header + '\n'
+ + self.actual_header
+ self.cat_e(self.actual.output))
return out
@@ -203,9 +207,8 @@ class Result(BaseResult):
class LeakResult(BaseResult):
def __init__(self, cmd: str, captured: Captured):
- self.cmd = cmd
self.captured = captured
- super().__init__()
+ super().__init__(cmd)
def _search_leak_kind(self, kind: str) -> Match:
match = re.search(
diff --git a/src/test/test.py b/src/test/test.py
index 1f8fa82..ab68d1e 100644
--- a/src/test/test.py
+++ b/src/test/test.py
@@ -6,38 +6,41 @@
# By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ #
# Created: 2020/06/16 21:48:50 by charles #+# #+# #
-# Updated: 2021/01/31 04:41:43 by charles ### ########.fr #
+# Updated: 2021/02/05 01:37:44 by charles ### ########.fr #
# #
# ############################################################################ #
import os
import sys
import subprocess
-from typing import Optional, List, Dict, Union
+from typing import Optional, List, Dict, Union, Callable
import config
from test.captured import Captured
from test.result import Result, LeakResult
import sandbox
+HookType = Union[Callable[[str], str], List[Callable[[str], str]]]
+HookStatusType = Union[Callable[[int], int], List[Callable[[int], int]]]
+
class Test:
- def __init__(self,
- cmd: str,
- setup: str = "",
- files: List[str] = [],
- exports: Dict[str, str] = {},
- timeout: float = config.TIMEOUT,
- signal=None,
- hook=[],
- hook_status=[]):
+ def __init__(
+ self,
+ cmd: str,
+ setup: str = "",
+ files: List[str] = [],
+ exports: Dict[str, str] = {},
+ timeout: float = config.TIMEOUT,
+ hook: HookType = [],
+ hook_status: HookStatusType = [],
+ ):
""" Test class
cmd: command to execute
setup: command to execute before tested command
files: files to watch (check content after test)
exports: exported variables
timeout: maximum amount of time taken by the test
- signal: signal to send to the test
hook: function to execute on the output of the test
hook_status: function to execute on status code
"""
@@ -47,15 +50,14 @@ class Test:
self.exports = exports
self.result: Optional[Union[Result, LeakResult]] = None
self.timeout = timeout
- self.signal = signal
- self.hook = hook
- self.hook_status = hook_status
- if type(self.hook) is not list:
- self.hook = [self.hook]
- if type(self.hook_status) is not list:
- self.hook_status = [self.hook_status]
+ if type(hook) is not list:
+ hook = [hook] # type: ignore
+ if type(hook_status) is not list:
+ hook_status = [hook_status] # type: ignore
+ self.hook: List[Callable[[str], str]] = hook # type: ignore
+ self.hook_status: List[Callable[[int], int]] = hook_status # type: ignore
- def run(self, index: int):
+ def run(self, index: int) -> None:
""" Run the test for minishell and the reference shell and print the result out """
if config.CHECK_LEAKS:
@@ -111,9 +113,6 @@ class Test:
**self.exports,
},
)
- # if self.signal is not None:
- # time.sleep(0.1)
- # process.send_signal(self.signal)
# https://docs.python.org/3/library/subprocess.html#subprocess.Popen.communicate
try:
@@ -138,10 +137,10 @@ class Test:
files_content.append(None)
# apply output/status hooks
- for h in self.hook:
- output = h(output)
- for h in self.hook_status:
- process.returncode = h(process.returncode)
+ for hook in self.hook:
+ output = hook(output)
+ for hook_status in self.hook_status:
+ process.returncode = hook_status(process.returncode)
return Captured(output, process.returncode, files_content)
@property
diff --git a/try b/try
index 6ae8cb9..1d96563 100755
--- a/try
+++ b/try
@@ -1,11 +1,10 @@
#!/usr/bin/env python3
import os
-import sys
import subprocess
+import sys
-from src.config import MINISHELL_DIR, MINISHELL_EXEC, EXECUTABLES_PATH
-
+from src.config import EXECUTABLES_PATH, MINISHELL_DIR, MINISHELL_EXEC
if __name__ == "__main__":
if len(sys.argv) != 2: