diff options
| -rw-r--r-- | requirements.txt | 7 | ||||
| -rw-r--r-- | setup.cfg | 2 | ||||
| -rwxr-xr-x | src/main.py | 5 | ||||
| -rw-r--r-- | src/suite/decorator.py | 9 | ||||
| -rw-r--r-- | src/suite/suite.py | 10 | ||||
| -rw-r--r-- | src/suites/cmd.py | 8 | ||||
| -rw-r--r-- | src/suites/path.py | 5 | ||||
| -rw-r--r-- | src/test/captured.py | 4 | ||||
| -rw-r--r-- | src/test/result.py | 55 | ||||
| -rw-r--r-- | src/test/test.py | 53 | ||||
| -rwxr-xr-x | try | 5 |
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 @@ -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 @@ -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: |
