diff options
Diffstat (limited to 'src/philo')
| -rw-r--r-- | src/philo/__init__.py | 17 | ||||
| -rw-r--r-- | src/philo/error.py | 62 | ||||
| -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 |
6 files changed, 343 insertions, 0 deletions
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/philo/error.py b/src/philo/error.py new file mode 100644 index 0000000..51c3f7b --- /dev/null +++ b/src/philo/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 11:14:08 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._line, 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/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") |
