aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/__init__.py (renamed from src/test/__init__.py)0
-rw-r--r--src/args.py50
-rw-r--r--src/config.py10
-rw-r--r--src/helper.py17
-rwxr-xr-xsrc/main.py91
-rw-r--r--src/philo/__init__.py17
-rw-r--r--src/philo/error.py (renamed from src/test/error.py)0
-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
-rw-r--r--src/suite.py59
-rw-r--r--src/test.py (renamed from src/test/test.py)77
-rw-r--r--src/test/philo.py194
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()