diff options
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | random_status.py | 7 | ||||
| l---------[-rwxr-xr-x] | run | 68 | ||||
| -rw-r--r-- | src/args.py (renamed from args.py) | 0 | ||||
| -rw-r--r-- | src/config.py (renamed from config.py) | 16 | ||||
| -rwxr-xr-x | src/main.py | 67 | ||||
| -rw-r--r-- | src/suite/__init__.py | 2 | ||||
| -rw-r--r-- | src/suite/decorator.py | 27 | ||||
| -rw-r--r-- | src/suite/suite.py (renamed from suite.py) | 17 | ||||
| -rw-r--r-- | src/suites/__init__.py (renamed from suites/__init__.py) | 0 | ||||
| -rw-r--r-- | src/suites/builtin.py (renamed from suites/builtin.py) | 0 | ||||
| -rw-r--r-- | src/suites/cmd.py (renamed from suites/cmd.py) | 0 | ||||
| -rw-r--r-- | src/suites/operation.py (renamed from suites/operation.py) | 0 | ||||
| -rw-r--r-- | src/suites/parenthesis.py (renamed from suites/parenthesis.py) | 0 | ||||
| -rw-r--r-- | src/suites/path.py (renamed from suites/path.py) | 0 | ||||
| -rw-r--r-- | src/suites/preprocess.py (renamed from suites/preprocess.py) | 0 | ||||
| -rw-r--r-- | src/suites/status.py (renamed from suites/status.py) | 0 | ||||
| -rw-r--r-- | src/test/__init__.py | 13 | ||||
| -rw-r--r-- | src/test/captured.py | 39 | ||||
| -rw-r--r-- | src/test/result.py (renamed from test.py) | 133 | ||||
| -rw-r--r-- | src/test/test.py | 113 |
21 files changed, 284 insertions, 224 deletions
@@ -6,7 +6,7 @@ Test for the minishell project of school 42. ## Usage -The default path to your project is `..` but you can change it the the [configuration](config.py). +The default path to your project is `..` but you can change it the the [configuration](src/config.py). * `> ./run --help` * `> ./run` @@ -31,11 +31,11 @@ This allows you to set the prompt to whatever you want. ## Python Version -This test works with python >= 3.4. The timeout detection only works with python >= 3.8. +This test works with python >= 3.5. ## Configuration -The default configuration can be changed in [config.py](config.py) +The default configuration can be changed in [config.py](src/config.py) ## Add new tests diff --git a/random_status.py b/random_status.py deleted file mode 100644 index 171467f..0000000 --- a/random_status.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys -import random - -if __name__ == "__main__": - status = random.randrange(-2_000_000, 2_000_000) - print(status) - sys.exit(status) @@ -1,67 +1 @@ -#!/usr/bin/python3 - -# ############################################################################ # -# # -# ::: :::::::: # -# main.py :+: :+: :+: # -# +:+ +:+ +:+ # -# By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ # -# +#+#+#+#+#+ +#+ # -# Created: 2020/07/15 15:11:52 by charles #+# #+# # -# Updated: 2020/07/15 15:11:52 by charles ### ########.fr # -# # -# ############################################################################ # - -import os -import sys -import shutil -import distutils.spawn -import subprocess - -import config -from args import parse_args -from suite import Suite -import suites.builtin -import suites.cmd -import suites.preprocess -import suites.operation -import suites.parenthesis -import suites.status -import suites.path - -def main(): - args = parse_args() - if args.list: - print("The available suites are:") - print('\n'.join([" - " + s.name for s in Suite.available])) - sys.exit(0) - - if config.MINISHELL_BUILD or args.build: - try: - subprocess.run(["make", "-C", config.MINISHELL_DIR], check=True) - except subprocess.CalledProcessError: - sys.exit(1) - if args.build: - sys.exit(0) - if os.path.exists(config.EXECUTABLES_PATH): - shutil.rmtree(config.EXECUTABLES_PATH) - os.mkdir(config.EXECUTABLES_PATH) - for cmd in config.AVAILABLE_COMMANDS: - shutil.copy(distutils.spawn.find_executable(cmd), # FIXME search whole PATH - os.path.join(config.EXECUTABLES_PATH, cmd)) - - - config.VERBOSE_LEVEL = args.verbose - Suite.setup(args.suites) - try: - Suite.run_all() - except KeyboardInterrupt: - shutil.rmtree(config.SANDBOX_PATH) - - Suite.summarize() - Suite.save_log() - print("See", config.LOG_PATH, "for more information") - - -if __name__ == "__main__": - main() +src/main.py
\ No newline at end of file diff --git a/config.py b/src/config.py index e05c36b..ca697f5 100644 --- a/config.py +++ b/src/config.py @@ -6,13 +6,20 @@ # By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/07/15 18:24:19 by charles #+# #+# # -# Updated: 2020/09/10 13:54:27 by charles ### ########.fr # +# Updated: 2020/09/11 12:21:14 by charles ### ########.fr # # # # ############################################################################ # -# Minishell configuration file + +################################################################################ +# Minishell configuration file # +################################################################################ + import os +# run the bonus tests +BONUS = False + # minishell dir path MINISHELL_DIR = ".." @@ -42,6 +49,9 @@ AVAILABLE_COMMANDS = ["rmdir", "env", "cat", "touch", "ls", "grep", "sh"] # $PATH environment variable passed to the shell PATH_VARIABLE = os.path.abspath(EXECUTABLES_PATH) +# default test timeout +TIMEOUT = 1 + LOREM = """ Mollitia asperiores assumenda excepturi et ipsa. Nihil corporis facere aut a rem consequatur. Quas molestiae corporis et quibusdam maiores. Molestiae sed unde aut at sed. @@ -59,8 +69,6 @@ Perspiciatis ut maxime et libero quo voluptas consequatur illum. Pariatur porro """ LOREM = ' '.join(LOREM.split('\n')) -TIMEOUT = 1 - ############################################################################### # do not edit ############################################################################### diff --git a/src/main.py b/src/main.py new file mode 100755 index 0000000..a71a485 --- /dev/null +++ b/src/main.py @@ -0,0 +1,67 @@ +#!/usr/bin/python3 + +# ############################################################################ # +# # +# ::: :::::::: # +# main.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/07/15 15:11:52 by charles #+# #+# # +# Updated: 2020/07/15 15:11:52 by charles ### ########.fr # +# # +# ############################################################################ # + +import os +import sys +import shutil +import distutils.spawn +import subprocess + +import config +from args import parse_args +from suite import Suite +import suites.builtin +import suites.cmd +import suites.preprocess +import suites.operation +import suites.parenthesis +import suites.status +import suites.path + +def main(): + args = parse_args() + if args.list: + print("The available suites are:") + print('\n'.join([" - " + s.name for s in Suite.available])) + sys.exit(0) + + if config.MINISHELL_BUILD or args.build: + try: + subprocess.run(["make", "-C", config.MINISHELL_DIR], check=True) + except subprocess.CalledProcessError: + sys.exit(1) + if args.build: + sys.exit(0) + if os.path.exists(config.EXECUTABLES_PATH): + shutil.rmtree(config.EXECUTABLES_PATH) + os.mkdir(config.EXECUTABLES_PATH) + for cmd in config.AVAILABLE_COMMANDS: + shutil.copy(distutils.spawn.find_executable(cmd), # FIXME search whole PATH + os.path.join(config.EXECUTABLES_PATH, cmd)) + + + config.VERBOSE_LEVEL = args.verbose + Suite.setup(args.suites) + try: + Suite.run_all() + except KeyboardInterrupt: + shutil.rmtree(config.SANDBOX_PATH) + + Suite.summarize() + Suite.save_log() + print("See", config.LOG_PATH, "for more information") + + +if __name__ == "__main__": + main() diff --git a/src/suite/__init__.py b/src/suite/__init__.py new file mode 100644 index 0000000..55beb35 --- /dev/null +++ b/src/suite/__init__.py @@ -0,0 +1,2 @@ +from suite.suite import Suite +from suite.decorator import suite diff --git a/src/suite/decorator.py b/src/suite/decorator.py new file mode 100644 index 0000000..55c9de6 --- /dev/null +++ b/src/suite/decorator.py @@ -0,0 +1,27 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# decorator.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: charles <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/09/11 12:28:00 by charles #+# #+# # +# Updated: 2020/09/11 12:28:14 by charles ### ########.fr # +# # +# ############################################################################ # + +from suite import Suite +from test import Test + +def suite(origin): + """ decorator for a suite function (fmt: suite_[name]) """ + + name = origin.__name__[len("suite_"):] + s = Suite(name) + def test_generator(): + def test(*args, **kwargs): + s.add(Test(*args, **kwargs)) + origin(test) + s.add_generator(test_generator) + Suite.available.append(s) + return test_generator diff --git a/suite.py b/src/suite/suite.py index 6b04dc5..fee4aa9 100644 --- a/suite.py +++ b/src/suite/suite.py @@ -6,13 +6,14 @@ # By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/07/15 18:24:29 by charles #+# #+# # -# Updated: 2020/07/19 15:29:36 by charles ### ########.fr # +# Updated: 2020/09/11 12:27:47 by charles ### ########.fr # # # # ############################################################################ # import config from test import Test + class Suite: available = [] @@ -91,17 +92,3 @@ class Suite: t.result.colored = False t.result.set_colors() log_file.write(t.result.full_diff() + '\n') - - -def suite(origin): - """ decorator for a suite function (fmt: suite_[name]) """ - - name = origin.__name__[len("suite_"):] - s = Suite(name) - def test_generator(): - def test(*args, **kwargs): - s.add(Test(*args, **kwargs)) - origin(test) - s.add_generator(test_generator) - Suite.available.append(s) - return test_generator diff --git a/suites/__init__.py b/src/suites/__init__.py index 68bad1f..68bad1f 100644 --- a/suites/__init__.py +++ b/src/suites/__init__.py diff --git a/suites/builtin.py b/src/suites/builtin.py index 30297e0..30297e0 100644 --- a/suites/builtin.py +++ b/src/suites/builtin.py diff --git a/suites/cmd.py b/src/suites/cmd.py index 1302ae3..1302ae3 100644 --- a/suites/cmd.py +++ b/src/suites/cmd.py diff --git a/suites/operation.py b/src/suites/operation.py index 3c89589..3c89589 100644 --- a/suites/operation.py +++ b/src/suites/operation.py diff --git a/suites/parenthesis.py b/src/suites/parenthesis.py index a06fdda..a06fdda 100644 --- a/suites/parenthesis.py +++ b/src/suites/parenthesis.py diff --git a/suites/path.py b/src/suites/path.py index b30215f..b30215f 100644 --- a/suites/path.py +++ b/src/suites/path.py diff --git a/suites/preprocess.py b/src/suites/preprocess.py index a34e18d..a34e18d 100644 --- a/suites/preprocess.py +++ b/src/suites/preprocess.py diff --git a/suites/status.py b/src/suites/status.py index 62c076e..62c076e 100644 --- a/suites/status.py +++ b/src/suites/status.py diff --git a/src/test/__init__.py b/src/test/__init__.py new file mode 100644 index 0000000..7601878 --- /dev/null +++ b/src/test/__init__.py @@ -0,0 +1,13 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# __init__.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: charles <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/09/11 12:18:14 by charles #+# #+# # +# Updated: 2020/09/11 12:26:30 by charles ### ########.fr # +# # +# ############################################################################ # + +from test.test import Test diff --git a/src/test/captured.py b/src/test/captured.py new file mode 100644 index 0000000..e47590b --- /dev/null +++ b/src/test/captured.py @@ -0,0 +1,39 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# captured.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: charles <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/09/11 12:16:25 by charles #+# #+# # +# Updated: 2020/09/11 12:16:51 by charles ### ########.fr # +# # +# ############################################################################ # + +import config + +class Captured: + def __init__(self, output: str, status: int, files_content: [str], is_timeout: bool = False): + lines = output.split('\n') + for i, l in enumerate(lines): + if l.find(config.REFERENCE_ERROR_BEGIN) == 0: + lines[i] = l.replace(config.REFERENCE_ERROR_BEGIN, config.MINISHELL_ERROR_BEGIN, 1) + elif l.find(config.REFERENCE_PATH + ": ") == 0: + lines[i] = l.replace(config.REFERENCE_PATH + ": ", config.MINISHELL_ERROR_BEGIN, 1) + + self.output = '\n'.join(lines) + + self.status = status + self.files_content = files_content + self.is_timeout = is_timeout + + def __eq__(self, other: 'Result') -> bool: + if self.is_timeout: + 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)])) + + @staticmethod + def timeout(): + return Captured("", 0, [], is_timeout = True) diff --git a/test.py b/src/test/result.py index 05bd2df..5e1349d 100644 --- a/test.py +++ b/src/test/result.py @@ -1,49 +1,17 @@ # ############################################################################ # # # # ::: :::::::: # -# test.py :+: :+: :+: # +# result.py :+: :+: :+: # # +:+ +:+ +:+ # -# By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ # +# By: charles <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # -# Created: 2020/06/16 21:48:50 by charles #+# #+# # -# Updated: 2020/09/10 09:38:26 by charles ### ########.fr # +# Created: 2020/09/11 12:17:34 by charles #+# #+# # +# Updated: 2020/09/11 12:24:57 by charles ### ########.fr # # # # ############################################################################ # -import os -import sys -import subprocess -import shutil -import glob - import config - -class Captured: - def __init__(self, output: str, status: int, files_content: [str], is_timeout: bool = False): - lines = output.split('\n') - for i, l in enumerate(lines): - if l.find(config.REFERENCE_ERROR_BEGIN) == 0: - lines[i] = l.replace(config.REFERENCE_ERROR_BEGIN, config.MINISHELL_ERROR_BEGIN, 1) - elif l.find(config.REFERENCE_PATH + ": ") == 0: - lines[i] = l.replace(config.REFERENCE_PATH + ": ", config.MINISHELL_ERROR_BEGIN, 1) - - self.output = '\n'.join(lines) - - self.status = status - self.files_content = files_content - self.is_timeout = is_timeout - - def __eq__(self, other: 'Result') -> bool: - if self.is_timeout: - 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)])) - - @staticmethod - def timeout(): - return Captured("", 0, [], is_timeout = True) - +from test.captured import Captured class Result: RED_CHARS = "\033[31m" @@ -189,94 +157,3 @@ class Result: .replace("\r", "\\r") .replace("\f", "\\f") ) - - -class Test: - def __init__(self, - cmd: str, - setup: str = "", - files: [str] = [], - exports: {str: str} = {}, - timeout: float = config.TIMEOUT): - self.cmd = cmd - self.setup = setup - self.files = files - self.exports = exports - self.result = None - self.timeout = timeout - - def run(self): - expected = self._run_sandboxed(config.REFERENCE_PATH, "-c") - actual = self._run_sandboxed(config.MINISHELL_PATH, "-c") - s = self.cmd - if self.setup != "": - s = "[SETUP {}] {}".format(self.setup, s) - if len(self.exports) != 0: - s = "[EXPORTS {}] {}".format( - ' '.join(["{}='{:.20}'".format(k, v) for k, v in self.exports.items()]), s) - self.result = Result(s, self.files, expected, actual) - self.result.put() - - def _run_sandboxed(self, shell_path: str, shell_option: str) -> Captured: - """ run the command in a sandbox environment - - capture the output (stdout and stderr) - capture the content of the watched files after the command is run - """ - - try: - os.mkdir(config.SANDBOX_PATH) - except OSError: - pass - if self.setup != "": - try: - setup_status = subprocess.run( - self.setup, - shell=True, - cwd=config.SANDBOX_PATH, - stderr=subprocess.STDOUT, - stdout=subprocess.PIPE, - check=True - ) - except subprocess.CalledProcessError as e: - print("Error: `{}` setup command failed for `{}`\n\twith '{}'" - .format(self.setup, - self.cmd, - "no stderr" if e.stdout is None else e.stdout.decode().strip())) - sys.exit(1) - - try: - process_status = subprocess.run( - [shell_path, shell_option, self.cmd], - stderr=subprocess.STDOUT, - stdout=subprocess.PIPE, - cwd=config.SANDBOX_PATH, - env={ - 'PATH': config.PATH_VARIABLE, - 'TERM': 'xterm-256color', - **self.exports - }, - timeout=self.timeout - ) - except subprocess.TimeoutExpired: - return Captured.timeout() - - try: - output = process_status.stdout.decode() - except UnicodeDecodeError: - output = "UNICODE ERROR: {}".format(process_status.stdout) - - # capture watched files content - files_content = [] - for file_name in self.files: - try: - with open(os.path.join(config.SANDBOX_PATH, file_name), "rb") as f: - files_content.append(f.read().decode()) - except FileNotFoundError as e: - files_content.append(None) - try: - shutil.rmtree(config.SANDBOX_PATH) - except: - subprocess.check_output(["chmod", "777", *glob.glob(config.SANDBOX_PATH + "/*")]) - subprocess.check_output(["rm", "-rf", config.SANDBOX_PATH]) - return Captured(output, process_status.returncode, files_content) diff --git a/src/test/test.py b/src/test/test.py new file mode 100644 index 0000000..9674240 --- /dev/null +++ b/src/test/test.py @@ -0,0 +1,113 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# test.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: charles <charles.cabergs@gmail.com> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/06/16 21:48:50 by charles #+# #+# # +# Updated: 2020/09/11 12:24:33 by charles ### ########.fr # +# # +# ############################################################################ # + +import os +import sys +import subprocess +import shutil +import glob + +import config +from test.captured import Captured +from test.result import Result + + +class Test: + def __init__(self, + cmd: str, + setup: str = "", + files: [str] = [], + exports: {str: str} = {}, + timeout: float = config.TIMEOUT): + self.cmd = cmd + self.setup = setup + self.files = files + self.exports = exports + self.result = None + self.timeout = timeout + + def run(self): + expected = self._run_sandboxed(config.REFERENCE_PATH, "-c") + actual = self._run_sandboxed(config.MINISHELL_PATH, "-c") + s = self.cmd + if self.setup != "": + s = "[SETUP {}] {}".format(self.setup, s) + if len(self.exports) != 0: + s = "[EXPORTS {}] {}".format( + ' '.join(["{}='{:.20}'".format(k, v) for k, v in self.exports.items()]), s) + self.result = Result(s, self.files, expected, actual) + self.result.put() + + def _run_sandboxed(self, shell_path: str, shell_option: str) -> Captured: + """ run the command in a sandbox environment + + capture the output (stdout and stderr) + capture the content of the watched files after the command is run + """ + + try: + os.mkdir(config.SANDBOX_PATH) + except OSError: + pass + if self.setup != "": + try: + setup_status = subprocess.run( + self.setup, + shell=True, + cwd=config.SANDBOX_PATH, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + check=True + ) + except subprocess.CalledProcessError as e: + print("Error: `{}` setup command failed for `{}`\n\twith '{}'" + .format(self.setup, + self.cmd, + "no stderr" if e.stdout is None else e.stdout.decode().strip())) + sys.exit(1) + + process = subprocess.Popen( + [shell_path, shell_option, self.cmd], + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + cwd=config.SANDBOX_PATH, + env={ + 'PATH': config.PATH_VARIABLE, + 'TERM': 'xterm-256color', + **self.exports, + }, + ) + try: + process.wait(timeout=self.timeout) + except subprocess.TimeoutExpired: + return Captured.timeout() + + try: + stdout, _ = process.communicate() + output = stdout.decode() + except UnicodeDecodeError: + output = "UNICODE ERROR: {}".format(process.stdout) + + # capture watched files content + files_content = [] + for file_name in self.files: + try: + with open(os.path.join(config.SANDBOX_PATH, file_name), "rb") as f: + files_content.append(f.read().decode()) + except FileNotFoundError as e: + files_content.append(None) + try: + shutil.rmtree(config.SANDBOX_PATH) + except: + subprocess.run(["chmod", "777", *glob.glob(config.SANDBOX_PATH + "/*")], check=True) + subprocess.run(["rm", "-rf", config.SANDBOX_PATH], check=True) + return Captured(output, process.returncode, files_content) |
