aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Cabergs <me@cacharle.xyz>2020-09-29 10:56:17 +0200
committerCharles Cabergs <me@cacharle.xyz>2020-09-29 10:56:17 +0200
commit5784ad5c7c8216e0334766cdfbf11f9bdbd4b001 (patch)
treeabbd4168e38fc65c1ea691156d13a40366e03cd8
parent56125041e0720754651ae561f68eb186b71b331a (diff)
downloadphilosophers_test-5784ad5c7c8216e0334766cdfbf11f9bdbd4b001.tar.gz
philosophers_test-5784ad5c7c8216e0334766cdfbf11f9bdbd4b001.tar.bz2
philosophers_test-5784ad5c7c8216e0334766cdfbf11f9bdbd4b001.zip
Added fail log file, Added pager after run
-rw-r--r--.gitignore1
-rw-r--r--src/config.py8
-rwxr-xr-xsrc/main.py32
-rw-r--r--src/test/error.py62
-rw-r--r--src/test/philo.py66
-rw-r--r--src/test/test.py48
6 files changed, 153 insertions, 64 deletions
diff --git a/.gitignore b/.gitignore
index 0d20b64..4a2714b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
*.pyc
+result.log
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))