aboutsummaryrefslogtreecommitdiff
path: root/src/philo
diff options
context:
space:
mode:
Diffstat (limited to 'src/philo')
-rw-r--r--src/philo/__init__.py17
-rw-r--r--src/philo/error.py62
-rw-r--r--src/philo/event.py41
-rw-r--r--src/philo/log.py47
-rw-r--r--src/philo/philo.py109
-rw-r--r--src/philo/table.py67
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")