diff options
| author | Charles Cabergs <me@cacharle.xyz> | 2020-10-01 11:49:53 +0200 |
|---|---|---|
| committer | Charles Cabergs <me@cacharle.xyz> | 2020-10-01 11:49:53 +0200 |
| commit | 1f18e740539aed751865ecff9d0f3cba44230e54 (patch) | |
| tree | e23254751cc5a3be551233efb979a00571f40dc6 | |
| parent | 763f02a8b1e69c0e26a088824981d23ba1e5386d (diff) | |
| download | philosophers_test-1f18e740539aed751865ecff9d0f3cba44230e54.tar.gz philosophers_test-1f18e740539aed751865ecff9d0f3cba44230e54.tar.bz2 philosophers_test-1f18e740539aed751865ecff9d0f3cba44230e54.zip | |
Refactoring file structure, Added summary
| -rw-r--r-- | src/__init__.py (renamed from src/test/__init__.py) | 0 | ||||
| -rw-r--r-- | src/args.py | 50 | ||||
| -rw-r--r-- | src/config.py | 10 | ||||
| -rw-r--r-- | src/helper.py | 17 | ||||
| -rwxr-xr-x | src/main.py | 91 | ||||
| -rw-r--r-- | src/philo/__init__.py | 17 | ||||
| -rw-r--r-- | src/philo/error.py (renamed from src/test/error.py) | 0 | ||||
| -rw-r--r-- | src/philo/event.py | 41 | ||||
| -rw-r--r-- | src/philo/log.py | 47 | ||||
| -rw-r--r-- | src/philo/philo.py | 109 | ||||
| -rw-r--r-- | src/philo/table.py | 67 | ||||
| -rw-r--r-- | src/suite.py | 59 | ||||
| -rw-r--r-- | src/test.py (renamed from src/test/test.py) | 77 | ||||
| -rw-r--r-- | src/test/philo.py | 194 |
14 files changed, 463 insertions, 316 deletions
diff --git a/src/test/__init__.py b/src/__init__.py index 4cc06f8..4cc06f8 100644 --- a/src/test/__init__.py +++ b/src/__init__.py diff --git a/src/args.py b/src/args.py new file mode 100644 index 0000000..899ef3f --- /dev/null +++ b/src/args.py @@ -0,0 +1,50 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# args.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/10/01 10:23:09 by cacharle #+# #+# # +# Updated: 2020/10/01 10:33:00 by cacharle ### ########.fr # +# # +# ############################################################################ # + + +import argparse + +import config + +def parse_args(): + parser = argparse.ArgumentParser( + description="Philosophers test", + formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument( + "-p", "--philo", + help="Id of the philosopher program to test \n" + "- 1: philo_one\n" + "- 2: philo_two\n" + "- 3: philo_three\n" + "- 0: all programs\n", + required=True, + type=int, + choices=[0, 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" + ) + parser.add_argument( + "-t", "--timeout", + help="Change the philosopher process time (in seconds)", + type=float, + default=config.TIMEOUT + ) + return parser.parse_args() diff --git a/src/config.py b/src/config.py index f50b3da..8ac5e0d 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/29 14:33:35 by cacharle ### ########.fr # +# Updated: 2020/10/01 10:15:26 by cacharle ### ########.fr # # # # ############################################################################ # @@ -24,17 +24,11 @@ BUILD_BEFORE = True BUILD_CMD = "make --no-print-directory -C {path}" # Timeout for non infinite test -TIMEOUT = 1 +TIMEOUT = 1.0 # Timeout for error test TIMEOUT_ERROR = 0.2 -# Timeout for infinite test -INFINITE_WAIT_TIME = 0.2 - -# Maximum number of lines for infinite test -INFINITE_MAX_LINE = 1000 - # Destination of the full summary of failed tests RESULT_FILE = "result.log" diff --git a/src/helper.py b/src/helper.py index 56014d6..bc211c9 100644 --- a/src/helper.py +++ b/src/helper.py @@ -6,12 +6,25 @@ # By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/09/29 11:19:32 by cacharle #+# #+# # -# Updated: 2020/09/29 11:19:50 by cacharle ### ########.fr # +# Updated: 2020/10/01 11:27:48 by cacharle ### ########.fr # # # # ############################################################################ # import time - def current_ms(): return int(time.time() * 1000) + +RED_CHARS = "\033[31m" +GREEN_CHARS = "\033[32m" +BLUE_CHARS = "\033[34m" +CLOSE_CHARS = "\033[0m" + +def red(string): + return RED_CHARS + string + CLOSE_CHARS + +def green(string): + return GREEN_CHARS + string + CLOSE_CHARS + +def blue(string): + return BLUE_CHARS + string + CLOSE_CHARS diff --git a/src/main.py b/src/main.py index 6aee6c9..1bb0286 100755 --- a/src/main.py +++ b/src/main.py @@ -24,92 +24,40 @@ import sys import subprocess -import argparse import config from test import Test +from args import parse_args +from suite import suite +from helper import blue 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", - 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() + args = parse_args() if config.BUILD_BEFORE or args.build: try: - print("=====================================BUILD======================================") - subprocess.run(config.BUILD_CMD.format(path=config.PHILO_PATHS[0]).split(' '), check=True) - print("================================================================================") + print(blue("=====================================BUILD======================================")) + subprocess.run( + config.BUILD_CMD.format(path=config.PHILO_PATHS[0]).split(' '), + check=True + ) + print(blue("================================================================================")) except subprocess.CalledProcessError: sys.exit(1) if args.build: sys.exit(0) - if args.philo != 1: - sys.exit(1) + config.TIMEOUT = args.timeout - Test.new_error([]) - Test.new_error(["a", "a", "a", "a"]) - Test.new_error(["aaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "a", "a", "a"]) - Test.new_error(["10"]) - Test.new_error(["10", "10"]) - Test.new_error(["10", "10", "10"]) - Test.new_error(["10", "10", "10", "10", "10", "10"]) - Test.new_error(["-1", "10", "10", "10"]) - Test.new_error(["10", "-1", "10", "10"]) - Test.new_error(["10", "10", "-1", "10"]) - Test.new_error(["10", "10", "10", "-1"]) - Test.new_error(["10", "10", "10", "10", "-1"]) - - Test.new_error([str(config.UINT_MAX + 1), "10", "10", "10"]) - Test.new_error(["10", str(config.UINT_MAX + 1), "10", "10"]) - Test.new_error(["10", "10", str(config.UINT_MAX + 1), "10"]) - Test.new_error(["10", "10", "10", str(config.UINT_MAX + 1)]) - Test.new_error(["10", "10", "10", "10", str(config.UINT_MAX + 1)]) - - Test.new_error([str(-config.UINT_MAX), "10", "10", "10"]) - Test.new_error(["10", str(-config.UINT_MAX), "10", "10"]) - Test.new_error(["10", "10", str(-config.UINT_MAX), "10"]) - Test.new_error(["10", "10", "10", str(-config.UINT_MAX)]) - Test.new_error(["10", "10", "10", "10", str(-config.UINT_MAX)]) - - Test(0, 100, 10, 10) - Test(1, 100, 10, 10) - - Test(2, 100, 50, 50) - Test(3, 100, 50, 50) - Test(4, 100, 50, 50) - Test(5, 100, 50, 50) - Test(6, 100, 50, 50) - Test(7, 100, 50, 50) - - Test(100, 100, 50, 50) - - Test(10, 100, 100, 10) - - # Test(2, 50, 10, 10, infinite=True) - # Test(10, 50, 10, 10, infinite=True) - # Test(10, 100, 10, 10, infinite=True) - # Test(10, 200, 10, 10, infinite=True) + suite() try: - Test.run_all(config.PHILO_EXEC_PATHS[0]) + if args.philo == 0: + for philo in range(3): + Test.run_all(config.PHILO_EXEC_PATHS[philo]) + else: + Test.run_all(config.PHILO_EXEC_PATHS[args.philo - 1]) except KeyboardInterrupt: pass finally: @@ -117,11 +65,10 @@ def main(): if args.pager: subprocess.run([*config.PAGER_CMD, config.RESULT_FILE]) else: + print() + Test.print_summary() print("Read {} for more information".format(config.RESULT_FILE)) if __name__ == "__main__": main() - - - diff --git a/src/philo/__init__.py b/src/philo/__init__.py new file mode 100644 index 0000000..5915e2b --- /dev/null +++ b/src/philo/__init__.py @@ -0,0 +1,17 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# __init__.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/10/01 10:50:41 by cacharle #+# #+# # +# Updated: 2020/10/01 11:08:00 by cacharle ### ########.fr # +# # +# ############################################################################ # + +from philo.philo import Philo +from philo.table import Table +from philo.log import Log +from philo.event import Event +import philo.error as error diff --git a/src/test/error.py b/src/philo/error.py index 51c3f7b..51c3f7b 100644 --- a/src/test/error.py +++ b/src/philo/error.py diff --git a/src/philo/event.py b/src/philo/event.py new file mode 100644 index 0000000..ddcbe59 --- /dev/null +++ b/src/philo/event.py @@ -0,0 +1,41 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# event.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/10/01 10:51:13 by cacharle #+# #+# # +# Updated: 2020/10/01 11:21:00 by cacharle ### ########.fr # +# # +# ############################################################################ # + +import enum + +class Event(enum.Enum): + FORK = 1 + EAT = 2 + SLEEP = 3 + THINK = 4 + DIE = 5 + NONE = 6 + + @staticmethod + def from_string(representation: str) -> "Event": + return { + "has taken fork": Event.FORK, + "is thinking": Event.THINK, + "is eating": Event.EAT, + "is sleeping": Event.SLEEP, + "died": Event.DIE, + }[representation] + + @staticmethod + def to_string(event: "Event") -> str: + return { + Event.FORK: "has taken fork", + Event.THINK: "is thinking", + Event.EAT: "is eating", + Event.SLEEP: "is sleeping", + Event.DIE: "died" + }[event] diff --git a/src/philo/log.py b/src/philo/log.py new file mode 100644 index 0000000..f5817d0 --- /dev/null +++ b/src/philo/log.py @@ -0,0 +1,47 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# log.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/10/01 10:51:39 by cacharle #+# #+# # +# Updated: 2020/10/01 11:30:53 by cacharle ### ########.fr # +# # +# ############################################################################ # + +import re + +from .event import Event +from . import error + +class Log: + def __init__(self, line: str, philo_num, start_time, end_time): + match = re.match( + r"^(?P<timestamp>\d+) " + r"(?P<id>\d+) " + r"(?P<event>is thinking|is eating|is sleeping|died|has taken fork)$", + line + ) + if match is None: + raise error.Format(line, "wrong format") + + self._line = line + self.id = self._parse_ranged_int(match.group("id"), 1, philo_num) + self.timestamp = self._parse_ranged_int( + match.group("timestamp"), start_time, end_time) + + self.event = Event.from_string(match.group('event')) + + def _parse_ranged_int(self, s, lo, hi): + try: + value = int(s) + if not (lo <= value <= hi): + raise error.Format(self._line, + "{} should be between {} - {}".format(s, lo, hi)) + except ValueError: + raise error.Format(self._line, "{} sould be an integer".format(s)) + return value + + def __repr__(self): + return "{}ms #{} {}".format(self.timestamp, self.id, self.event) diff --git a/src/philo/philo.py b/src/philo/philo.py new file mode 100644 index 0000000..afb6362 --- /dev/null +++ b/src/philo/philo.py @@ -0,0 +1,109 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# philo.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/10/01 10:52:56 by cacharle #+# #+# # +# Updated: 2020/10/01 11:19:23 by cacharle ### ########.fr # +# # +# ############################################################################ # + +import itertools + +from . import error +from philo.event import Event + + +class Philo: + def __init__( + self, + id_: int, + timeout_die: int, + timeout_eat: int, + timeout_sleep: int, + meal_num: int + ): + self.logs = [] + self.id = id_ + self._timeout_die = timeout_die + self._timeout_eat = timeout_eat + self._timeout_sleep = timeout_sleep + self._meal_num = meal_num + + @property + def used_forks(self): + """ The number of forks currently used by the philosopher """ + if len(self.logs) < 1: + return 0 + if self.logs[-1].event is Event.EAT: + return 2 + if self.logs[-1].event is Event.FORK: + if len(self.logs) > 1 and self.logs[-2].event is Event.FORK: + return 2 + else: + return 1 + return 0 + + def check(self): + """ Check log for errors + + - Must take 2 forks before eating + - The delay between the taking of the second fork and eating should be almost 0 + - State switch should be + thinking -> take fork -> take fork -> eat * meal_num -> sleep -> repeat + - Should die when starving: last log timestamp - timeout_death > last_time_eat + """ + + if len(self.logs) == 0: + return + + # def _check_fork_taking(self): + # for l1, l2, l3 in zip(self.logs, self.logs[1:], self.logs[2:]): + # if l1.event is Event.FORK and (l2.event is not Event.FORK or l2.event is not Event.EAT): + # self._raise("should take 2 forks then eat") + # + # def _check_meal(self): + # 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 error.Log(self.logs, "should eat {} times".format(self._meal_num)) + # else: + # if len(g) != 1: + # raise error.Log(self.logs, "event {} should occur 1 time".format(Event.to_string(e))) + # + # def _check_order(self): + # for l1, l2 in zip(self.logs, self.logs[1:]): + # if l2.event is Event.DIED: + # break + # if l1.event is Event.EATING and l2.event is Event.EATING: + # if l2.timestamp - l1.timestamp > self._timeout_eat: + # raise ValueError + # second, timeout = { + # Event.THINKING: (Event.EATING, None), + # Event.EATING: (Event.SLEEPING, self._timeout_eat), + # Event.SLEEPING: (Event.THINKING, self._timeout_sleep) + # }[l1.event] + # if l2.event is not second: + # raise error.Log(self.logs, "invalid switch {} -> {}".format(l1.event, l2.event)) + # if timeout is not None and l2.timestamp - l1.timestamp > timeout: + # raise ValueError + # + # def _check_death_timeout(self): + # last_eat = None + # for log in reversed(self.logs): + # if log.event is Event.EATING: + # last_eat = log + # break + # last = self.logs[-1] + # if last_eat is not None and last_eat is not last: + # if last.timestamp - last_eat.timestamp > self._timeout_die + 10: + # raise error.Log(self.logs, "{} should be dead {} - {} > {}".format( + # self.id, last.timestamp, last_eat.timestamp, self._timeout_die + 10)) + + def _raise(self, msg): + """ Helper to raise Log errrors""" + raise error.Log(self.logs, msg) + diff --git a/src/philo/table.py b/src/philo/table.py new file mode 100644 index 0000000..ea93391 --- /dev/null +++ b/src/philo/table.py @@ -0,0 +1,67 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# table.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/10/01 10:53:29 by cacharle #+# #+# # +# Updated: 2020/10/01 11:31:44 by cacharle ### ########.fr # +# # +# ############################################################################ # + + +import itertools + +from philo import Philo +from philo import error +from philo.event import Event + +class Table: + def __init__( + self, + philo_num: int, + timeout_die: int, + timeout_eat: int, + timeout_sleep: int, + meal_num: int + ): + self._philos = [Philo(id_, timeout_die, timeout_eat, timeout_sleep, meal_num) + for id_ in range(1, philo_num + 1)] + self._logs = [] + self._philo_num = philo_num + self.dead = False + + def add_log(self, log): + """ Add a log to the correct philosopher + Set the dead flag if it's a death log + """ + self._logs.append(log) + philo = next(p for p in self._philos if p.id == log.id) + philo.logs.append(log) + # move + if self.dead: + raise error.Log(self._logs, "should not output after death") + if log.event is Event.DIE: + self.dead = True + + def check(self): + """ Check global logs and all philosophers logs for errors + + - Should not output after one philosopher died + - Should not take non existant forks + - Timestamps should be in increasing order + """ + + if self.dead: + return + + for p in self._philos: + p.check() + + fork_used = sum([p.used_forks for p in self._philos]) + if fork_used > self._philo_num: + raise error.Log(self._logs, "using nonexistant forks") + for l1, l2 in zip(self._logs, self._logs[1:]): + if l1.timestamp > l2.timestamp: + raise error.Log(self._logs, "timestamps not in ordered") diff --git a/src/suite.py b/src/suite.py new file mode 100644 index 0000000..d4734a9 --- /dev/null +++ b/src/suite.py @@ -0,0 +1,59 @@ +# ############################################################################ # +# # +# ::: :::::::: # +# suite.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2020/10/01 10:41:43 by cacharle #+# #+# # +# Updated: 2020/10/01 11:37:50 by cacharle ### ########.fr # +# # +# ############################################################################ # + +import config +from test import Test + +def suite(): + Test.new_error([]) + Test.new_error(["a", "a", "a", "a"]) + Test.new_error(["aaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "a", "a", "a"]) + Test.new_error(["10"]) + Test.new_error(["10", "10"]) + Test.new_error(["10", "10", "10"]) + Test.new_error(["10", "10", "10", "10", "10", "10"]) + Test.new_error(["-1", "10", "10", "10"]) + Test.new_error(["10", "-1", "10", "10"]) + Test.new_error(["10", "10", "-1", "10"]) + Test.new_error(["10", "10", "10", "-1"]) + Test.new_error(["10", "10", "10", "10", "-1"]) + + Test.new_error([str(config.UINT_MAX + 1), "10", "10", "10"]) + Test.new_error(["10", str(config.UINT_MAX + 1), "10", "10"]) + Test.new_error(["10", "10", str(config.UINT_MAX + 1), "10"]) + Test.new_error(["10", "10", "10", str(config.UINT_MAX + 1)]) + Test.new_error(["10", "10", "10", "10", str(config.UINT_MAX + 1)]) + + Test.new_error([str(-config.UINT_MAX), "10", "10", "10"]) + Test.new_error(["10", str(-config.UINT_MAX), "10", "10"]) + Test.new_error(["10", "10", str(-config.UINT_MAX), "10"]) + Test.new_error(["10", "10", "10", str(-config.UINT_MAX)]) + Test.new_error(["10", "10", "10", "10", str(-config.UINT_MAX)]) + + Test(0, 100, 10, 10) + Test(1, 100, 10, 10) + # + # Test(2, 100, 50, 50) + # Test(3, 100, 50, 50) + # Test(4, 100, 50, 50) + # Test(5, 100, 50, 50) + # Test(6, 100, 50, 50) + # Test(7, 100, 50, 50) + # + # Test(100, 100, 50, 50) + # + # Test(10, 100, 100, 10) + # + # Test(2, 50, 10, 10) + # Test(10, 50, 10, 10) + # Test(10, 100, 10, 10) + # Test(10, 200, 10, 10) diff --git a/src/test/test.py b/src/test.py index 8a63bd7..49059f6 100644 --- a/src/test/test.py +++ b/src/test.py @@ -6,7 +6,7 @@ # By: charles <me@cacharle.xyz> +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2020/09/27 11:36:32 by charles #+# #+# # -# Updated: 2020/09/29 15:15:46 by cacharle ### ########.fr # +# Updated: 2020/10/01 11:41:21 by cacharle ### ########.fr # # # # ############################################################################ # @@ -15,9 +15,8 @@ import time import subprocess import config -import test.philo as philo -import test.error as error -from helper import current_ms +import philo +from helper import current_ms, red, green class Test: @@ -43,7 +42,6 @@ class Test: timeout_sleep: int = None, meal_num: int = None, error_cmd: [str] = None, - infinite: bool = False ): self._philo_num = philo_num self._timeout_die = timeout_die @@ -51,13 +49,12 @@ class Test: self._timeout_sleep = timeout_sleep self._meal_num = meal_num self._error_cmd = error_cmd - self._infinite = infinite Test._tests.append(self) def run(self): try: self._run_tested() - except error.Philo as e: + except philo.error.Philo as e: self._print_fail(e.summary) Test._fail_summaries.append(self._argv_str + '\n' + e.full_summary) else: @@ -68,7 +65,18 @@ class Test: with open(config.RESULT_FILE, "w") as f: f.write('\n\n'.join(cls._fail_summaries)) + @classmethod + def print_summary(cls): + fail_total = len(cls._fail_summaries) + pass_total = len(cls._tests) - fail_total + print("Summary: Total {} {} {}".format( + len(cls._tests), + green("[PASS] {:3}".format(pass_total)), + red("[FAIL] {:3}".format(fail_total)) + )) + def _run_tested(self): + start_time = current_ms() process = subprocess.Popen( self._argv(), stdout=subprocess.PIPE, @@ -76,36 +84,29 @@ class Test: ) if self._error_cmd is not None: self._check_error(process) - elif self._infinite: - try: - self._check_output(process.stdout, died=False) - process.wait(timeout=config.INFINITE_WAIT_TIME) - except subprocess.TimeoutExpired: - pass - else: - raise ShouldBeInfinite - else: - self._check_output(process.stdout) - process.wait(timeout=config.TIMEOUT) + return + + out = "" + try: + out, _ = process.communicate(timeout=1) + except subprocess.TimeoutExpired as e: + out = e.stdout + end_time = current_ms() + try: + out = out.decode() + except UnicodeDecodeError: + pass # TODO - def _check_output(self, stream, died: bool = True): - start_time = current_ms() table = philo.Table( - self._philo_num, self._timeout_die, self._timeout_eat, self._timeout_sleep, - 1 if self._meal_num is None else self._meal_num) - for i, line in enumerate(stream): - line = line.decode()[:-1] - table.add_log(philo.Log(line, self._philo_num, start_time)) + self._philo_num, + self._timeout_die, + self._timeout_eat, + self._timeout_sleep, + 1 if self._meal_num is None else self._meal_num + ) + for line in out.split('\n')[:-1]: + table.add_log(philo.Log(line, self._philo_num, start_time, end_time)) table.check() - if i > 1000: - break - if died: - if not table.dead and self._philo_num != 0: - raise philo.error.Log(table._logs, "one philosopher should have died") - else: - if table.dead: - raise philo.error.Log(table._logs, "infinite shouldn't die") - def _check_error(self, process): try: @@ -137,12 +138,8 @@ class Test: 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)) + print(red("[FAIL] {}: {}".format(self._argv_str, msg))) def _print_pass(self): - print("{}[PASS] {}{}".format(Test.GREEN_CHARS, self._argv_str, Test.CLOSE_CHARS)) + print(green("[PASS] {}".format(self._argv_str))) diff --git a/src/test/philo.py b/src/test/philo.py deleted file mode 100644 index 3b49057..0000000 --- a/src/test/philo.py +++ /dev/null @@ -1,194 +0,0 @@ -# ############################################################################ # -# # -# ::: :::::::: # -# philo.py :+: :+: :+: # -# +:+ +:+ +:+ # -# By: charles <me@cacharle.xyz> +#+ +:+ +#+ # -# +#+#+#+#+#+ +#+ # -# Created: 2020/09/27 17:49:41 by charles #+# #+# # -# Updated: 2020/09/30 15:02:10 by cacharle ### ########.fr # -# # -# ############################################################################ # - -import re -import enum -import itertools - -import test.error as error -from helper import current_ms - -class Event(enum.Enum): - FORK = 1 - EATING = 2 - SLEEPING = 3 - THINKING = 4 - DIED = 5 - NONE = 6 - - @staticmethod - def from_string(representation: str) -> Event: - return { - "has taken fork": Event.FORK, - "is thinking": Event.THINKING, - "is eating": Event.EATING, - "is sleeping": Event.SLEEPING, - "died": Event.DIED, - }[representation] - - @staticmethod - def to_string(event: Event) -> str: - return { - Event.FORK: "has taken fork", - Event.THINKING: "is thinking", - Event.EATING: "is eating", - Event.SLEEPING: "is sleeping", - Event.DIED: "died" - }[event] - - - -class Log: - def __init__(self, line: str, philo_num, start_time): - match = re.match( - r"^(?P<timestamp>\d+) " - r"(?P<id>\d+) " - r"(?P<event>is thinking|is eating|is sleeping|died|has taken fork)$", - line - ) - if match is None: - raise error.Format(line, "wrong format") - - self._line = line - self.id = self._parse_ranged_int(match.group("id"), 1, philo_num) - self.timestamp = self._parse_ranged_int( - match.group("timestamp"), start_time, current_ms()) - - self.event = Event.from_string(match.group('event')) - - def _parse_ranged_int(self, s, lo, hi): - try: - value = int(s) - if not (lo <= value <= hi): - raise error.Format(self._line, - "{} should be between {} - {}".format(s, lo, hi)) - except ValueError: - raise error.Format(self._line, "{} sould be an integer".format(s)) - return value - - def __repr__(self): - return "{}ms #{} {}".format(self.timestamp, self.id, self.event) - - -class Philo: - def __init__( - self, - id_: int, - timeout_die: int, - timeout_eat: int, - timeout_sleep: int, - meal_num: int - ): - self._logs = [] - self.id = id_ - self._timeout_die = timeout_die - self._timeout_eat = timeout_eat - self._timeout_sleep = timeout_sleep - self._meal_num = meal_num - - 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 error.Log(self._logs, "should eat {} times".format(self._meal_num)) - else: - if len(g) != 1: - raise error.Log(self._logs, "event {} should occur 1 time".format(Event.to_string(e))) - - # events = [e for e, _ in grouped] - for l1, l2 in zip(self._logs, self._logs[1:]): - if l2.event is Event.DIED: - break - if l1.event is Event.EATING and l2.event is Event.EATING: - if l2.timestamp - l1.timestamp > self._timeout_eat: - raise ValueError - second, timeout = { - Event.THINKING: (Event.EATING, None), - Event.EATING: (Event.SLEEPING, self._timeout_eat), - Event.SLEEPING: (Event.THINKING, self._timeout_sleep) - }[l1.event] - if l2.event is not second: - raise error.Log(self._logs, "invalid switch {} -> {}".format(l1.event, l2.event)) - if timeout is not None and l2.timestamp - l1.timestamp > timeout: - raise ValueError - - last_eat = None - for log in reversed(self._logs): - if log.event is Event.EATING: - last_eat = log - break - last = self._logs[-1] - if last_eat is not None and last_eat is not last: - if last.timestamp - last_eat.timestamp > self._timeout_die + 10: - raise error.Log(self._logs, "{} should be dead {} - {} > {}".format( - self.id, last.timestamp, last_eat.timestamp, self._timeout_die + 10)) - - def _check_fork_taking(self): - for l1, l2, l3 in zip(self._logs, self._logs[1:], self._logs[2:]): - if l1.event is Event.FORK and (l2.event is not Event.FORK or l2.event is not Event.EAT): - raise ValueError("take fork but no eat") - - def _check_meal(self): - pass - - def _check_order(self): - pass - - - @property - def last_event(self): - if len(self._logs) == 0: - return Event.NONE - return self._logs[-1].event - - -class Table: - def __init__( - self, - philo_num: int, - timeout_die: int, - timeout_eat: int, - timeout_sleep: int, - meal_num: int - ): - self._philos = [Philo(id_, timeout_die, timeout_eat, timeout_sleep, meal_num) - for id_ in range(1, philo_num + 1)] - self._logs = [] - self._philo_num = philo_num - self.dead = False - - def add_log(self, log): - 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 error.Log(self._logs, "using nonexistant forks") - for l1, l2 in zip(self._logs, self._logs[1:]): - if l1.timestamp > l2.timestamp: - raise error.Log(self._logs, "timestamp not in ordered") - for p in self._philos: - p.check() |
