diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.py | 8 | ||||
| -rwxr-xr-x | src/main.py | 32 | ||||
| -rw-r--r-- | src/test/error.py | 62 | ||||
| -rw-r--r-- | src/test/philo.py | 66 | ||||
| -rw-r--r-- | src/test/test.py | 48 |
5 files changed, 152 insertions, 64 deletions
diff --git a/src/config.py b/src/config.py index bbc9d25..b8811f3 100644 --- a/src/config.py +++ b/src/config.py @@ -6,7 +6,7 @@ # By: charles <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/09/27 11:05:38 by charles #+# #+# # -# Updated: 2020/09/28 16:07:03 by cacharle ### ########.fr # +# Updated: 2020/09/29 10:55:23 by cacharle ### ########.fr # # # # ############################################################################ # @@ -32,6 +32,12 @@ TIMEOUT_ERROR = 0.2 # Timeout for infinite test INFINITE_WAIT_TIME = 0.2 +# Destination of the full summary of failed tests +RESULT_FILE = "result.log" + +# Pager command +PAGER_CMD = ["less"] + ################################################################################ # Do not edit ################################################################################ diff --git a/src/main.py b/src/main.py index dcee90e..17d8b52 100755 --- a/src/main.py +++ b/src/main.py @@ -32,9 +32,23 @@ from test import Test def main(): parser = argparse.ArgumentParser(description="Philosophers test") - parser.add_argument("-p", "--philo", help="Id of the philosophers to test ", - required=True, type=int, choices=[1, 2, 3]) - parser.add_argument("-b", "--build", help="Build and exit") + parser.add_argument( + "-p", "--philo", + help="Id of the philosophers to test ", + required=True, + type=int, + choices=[1, 2, 3] + ) + parser.add_argument( + "-b", "--build", + help="Build and exit", + action="store_true" + ) + parser.add_argument( + "-g", "--pager", + help="Open {} in a pager after the test".format(config.RESULT_FILE), + action="store_true" + ) args = parser.parse_args() if config.BUILD_BEFORE or args.build: @@ -81,8 +95,16 @@ def main(): Test(10, 100, 100, 10) Test(10, 200, 10, 10, infinite=True) - Test.run_all(config.PHILO_EXEC_PATHS[0]) - # print("yo") + try: + Test.run_all(config.PHILO_EXEC_PATHS[0]) + except KeyboardInterrupt: + pass + finally: + Test.write_failed() + if args.pager: + subprocess.run([*config.PAGER_CMD, config.RESULT_FILE]) + else: + print("Read {} for more information".format(config.RESULT_FILE)) if __name__ == "__main__": diff --git a/src/test/error.py b/src/test/error.py new file mode 100644 index 0000000..4bfa0e3 --- /dev/null +++ b/src/test/error.py @@ -0,0 +1,62 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# error.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/09/29 09:09:31 by cacharle #+# #+# # +# Updated: 2020/09/29 10:08:24 by cacharle ### ########.fr # +# # +# ############################################################################ # + +class Philo(Exception): + pass + + +class ShouldFail(Philo): + def __init__(self, msg: str): + self._msg = msg + Philo.__init__(self) + + @property + def full_summary(self): + return self.summary + + @property + def summary(self): + return "Should fail: {}".format(self._msg) + + +class Format(Philo): + def __init__(self, line: str, msg: str): + self._line = line + self._msg = msg + Philo.__init__(self) + + @property + def full_summary(self): + return """FORMAT ERROR: {} +{} +""".format(self._msg, self._line) + + @property + def summary(self): + return "format: {}".format(self._msg) + + +class Log(Philo): + def __init__(self, logs: [str], msg: str): + self._logs = logs + self._msg = msg + Philo.__init__(self) + + @property + def full_summary(self): + return """LOG ERROR: {} +{} +""".format(self._msg, '\n'.join([str(l) for l in self._logs])) + + @property + def summary(self): + return "log: {}".format(self._msg) diff --git a/src/test/philo.py b/src/test/philo.py index cb8d369..9039ac0 100644 --- a/src/test/philo.py +++ b/src/test/philo.py @@ -6,7 +6,7 @@ # By: charles <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/09/27 17:49:41 by charles #+# #+# # -# Updated: 2020/09/27 18:35:25 by charles ### ########.fr # +# Updated: 2020/09/29 10:54:03 by cacharle ### ########.fr # # # # ############################################################################ # @@ -15,14 +15,10 @@ import time import enum import itertools +import test.error as error -class FormatError(Exception): - pass - - -class LogError(Exception): - pass - +def current_ms(): + return int(time.time() * 1000) class Event(enum.Enum): EATING = 1 @@ -34,11 +30,11 @@ class Event(enum.Enum): @staticmethod def to_verb(event): return { - Event.EATING: "eat", + Event.EATING: "eat", Event.SLEEPING: "sleep", Event.THINKING: "think", - Event.DIED: "die", - Event.NONE: "none", + Event.DIED: "die", + Event.NONE: "none", }[event] @@ -51,9 +47,9 @@ class Log: line ) if match is None: - raise FormatError("couldn't parse line") + raise error.Format(line, "wrong format") - curr = int(time.time() * 1000) + curr = current_ms() self.timestamp = Log._parse_ranged_int(match.group("timestamp"), curr - 100, curr + 100) self.id = Log._parse_ranged_int(match.group("id"), 1, philo_num) @@ -69,13 +65,13 @@ class Log: try: value = int(s) if not (lo <= value <= hi): - raise FormatError("`{}` should be between {} - {}".format(s, lo, hi)) + raise error.Format(s, "should be between {} - {}".format(lo, hi)) except ValueError: - raise FormatError("`{}` sould be an integer".format(s)) + raise error.Format(s, "sould be an integer".format(s)) return value def __repr__(self): - return "Log({}ms #{} {})".format(self.timestamp, self.id, self.event) + return "{}ms #{} {}".format(self.timestamp, self.id, self.event) class Philo: @@ -84,40 +80,44 @@ class Philo: self.id = id_ self.meal_num = meal_num self._timeout_eat = timeout_eat + # self._start_time = current_ms() def add_log(self, log): self._logs.append(log) def check(self): + if len(self._logs) == 0: + return grouped = [(e, list(g)) for e, g in itertools.groupby(self._logs, (lambda x: x.event))] for e, g in grouped: if e is Event.EATING: if len(g) != self.meal_num: - raise LogError("should eat {} times".format(self.meal_num)) + raise error.Log(self._logs, "should eat {} times".format(self.meal_num)) else: if len(g) != 1: - raise LogError("should {} 1 time".format(Event.to_verb(e))) + raise error.Log(self._logs, "should {} 1 time".format(Event.to_verb(e))) events = [e for e, _ in grouped] for e1, e2 in zip(events, events[1:]): if e2 is Event.DIED: break second = { - Event.THINKING: Event.EATING, + Event.THINKING: Event.EATING, Event.EATING: Event.SLEEPING, - Event.SLEEPING: Event.EATING + Event.SLEEPING: Event.THINKING }[e1] - if second is not e2: - raise LogError("{} should switch to {}, actual {}".format(e1, second, e2)) + if e2 is not second: + raise error.Log(self._logs, "invalid switch {} -> {}".format(e1, e2)) - last_eat_time = int(time.time() * 1000) + last_eat = None for log in reversed(self._logs): if log.event is Event.EATING: - last_eat_time = log.timestamp + last_eat = log break - - if int(time.time() * 1000) - last_eat_time > self._timeout_eat + 20: - raise LogError("should be dead") + last = self._logs[-1] + if last_eat is not None and last_eat is not last: + if last.timestamp - last_eat.timestamp > self._timeout_eat + 20: + raise error.Log(self._logs, "{} should be dead {}".format(self.id, last.timestamp)) @property def last_event(self): @@ -134,22 +134,22 @@ class Table: self.dead = False def add_log(self, log): - if self.dead: - raise LogError("should not output after one died") - if log.event is Event.DIED: - self.dead = True self._logs.append(log) philo = next(p for p in self._philos if p.id == log.id) philo.add_log(log) + if self.dead: + raise error.Log(self._logs, "should not output after death") + if log.event is Event.DIED: + self.dead = True def check(self): if self.dead: return fork_used = 2 * len([p for p in self._philos if p.last_event == Event.EATING]) if fork_used > self._philo_num: - raise LogError("using nonexistant forks") + raise error.Log(self._logs, "using nonexistant forks") for l1, l2 in zip(self._logs, self._logs[1:]): if l1.timestamp > l2.timestamp: - raise LogError("timestamp not in ordered") + raise error.Log("timestamp not in ordered") for p in self._philos: p.check() diff --git a/src/test/test.py b/src/test/test.py index 91fbbca..506bfff 100644 --- a/src/test/test.py +++ b/src/test/test.py @@ -6,7 +6,7 @@ # By: charles <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/09/27 11:36:32 by charles #+# #+# # -# Updated: 2020/09/28 16:08:51 by cacharle ### ########.fr # +# Updated: 2020/09/29 10:53:14 by cacharle ### ########.fr # # # # ############################################################################ # @@ -15,15 +15,13 @@ import subprocess import config import test.philo as philo - - -class ShouldFailError(Exception): - pass +import test.error as error class Test: _tests = [] _exec_path = None + _fail_summaries = [] @classmethod def run_all(cls, exec_path: str): @@ -57,16 +55,17 @@ class Test: def run(self): try: self._run_tested() - except ShouldFailError as e: - self._print_fail("not failed: " + str(e)) - except philo.FormatError as e: - self._print_fail("format: " + str(e)) - except philo.LogError as e: - self._print_fail("log: " + str(e)) - # except Time + except error.Philo as e: + self._print_fail(e.summary) + Test._fail_summaries.append(self._argv_str + '\n' + e.full_summary) else: self._print_pass() + @classmethod + def write_failed(cls): + with open(config.RESULT_FILE, "w") as f: + f.write('\n\n'.join(cls._fail_summaries)) + def _run_tested(self): process = subprocess.Popen( self._argv(), @@ -90,22 +89,17 @@ class Test: table.add_log(philo.Log(line, self._philo_num)) table.check() if died and not table.dead: - raise philo.LogError("one philosopher should have died") + raise philo.error.Log("one philosopher should have died") def _check_error(self, process): try: out, _ = process.communicate(timeout=config.TIMEOUT_ERROR) except subprocess.TimeoutExpired: - raise ShouldFailError("no error message") + raise error.ShouldFail("no error message") if process.returncode == 0: - raise ShouldFailError("non zero status code: {}".format(process.returncode)) + raise error.ShouldFail("non zero status code: {}".format(process.returncode)) if out.decode().count('\n') != 1: - raise ShouldFailError("no error message") - - RED_CHARS = "\033[31m" - GREEN_CHARS = "\033[32m" - BOLD_CHARS = "\033[1m" - CLOSE_CHARS = "\033[0m" + raise error.ShouldFail("no error message") def _argv(self, basename=False): exec_path = os.path.basename(Test._exec_path) if basename else Test._exec_path @@ -123,12 +117,16 @@ class Test: argv.append(str(self._meal_num)) return argv + @property + def _argv_str(self): + return ' '.join(self._argv(basename=True)) + + RED_CHARS = "\033[31m" + GREEN_CHARS = "\033[32m" + CLOSE_CHARS = "\033[0m" + def _print_fail(self, msg): print("{}[FAIL] {}: {}{}".format(Test.RED_CHARS, self._argv_str, msg, Test.CLOSE_CHARS)) def _print_pass(self): print("{}[PASS] {}{}".format(Test.GREEN_CHARS, self._argv_str, Test.CLOSE_CHARS)) - - @property - def _argv_str(self): - return ' '.join(self._argv(basename=True)) |
