aboutsummaryrefslogtreecommitdiff
path: root/src/test/philo.py
blob: 743f0cb4f7ad43c49f82ef5d68afa3fc27e70b83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# ############################################################################ #
#                                                                              #
#                                                         :::      ::::::::    #
#    philo.py                                           :+:      :+:    :+:    #
#                                                     +:+ +:+         +:+      #
#    By: charles <me@cacharle.xyz>                  +#+  +:+       +#+         #
#                                                 +#+#+#+#+#+   +#+            #
#    Created: 2020/09/27 17:49:41 by charles           #+#    #+#              #
#    Updated: 2020/09/27 17:53:10 by charles          ###   ########.fr        #
#                                                                              #
# ############################################################################ #

import re
import time
import enum
import itertools


class Event(enum.Enum):
    EATING = 1
    SLEEPING = 2
    THINKING = 3
    DIED = 4
    NONE = 5


class Log:
    def __init__(self, log, philo_num):
        match = re.match(
            "^(?P<timestamp>\d+) "
            "(?P<id>\d+) "
            "(?P<event>is thinking|is eating|is sleeping|died)$",
            log
        )
        if match is None:
            raise ValueError("Bad line format |{}|".format(log))

        curr = int(time.time() * 1000)
        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)

        self.event = {
            "is thinking": Event.THINKING,
            "is eating":   Event.EATING,
            "is sleeping": Event.SLEEPING,
            "died":        Event.DIED,
        }[match.group('event')]

    @staticmethod
    def _parse_ranged_int(s, lo, hi):
        try:
            value = int(s)
            if not (lo <= value <= hi):
                raise ValueError("Invalid value range {}".format(s))
        except ValueError:
            raise ValueError("Invalid value {}".format(s))
        return value

    def __repr__(self):
        return "{} {} {}".format(self.timestamp, self.id, self.event)


class Philo:
    def __init__(self, id_: int, timeout_eat: int, meal_num: int = 1):
        self._logs = []
        self.id = id_
        self.meal_num = meal_num
        self._timeout_eat = timeout_eat

    def add_log(self, log):
        self._logs.append(log)

    def check(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 RuntimeError("lala")
            else:
                if len(g) != 1:
                    raise RuntimeError("1lala")

        events = [e for e, _ in grouped]
        for e1, e2 in zip(events, events[1:]):
            if e2 is Event.DIED:
                break
            if e1 is Event.THINKING and e2 is not Event.EATING:
                raise RuntimeError("2lala")
            elif e1 is Event.EATING and e2 is not Event.SLEEPING:
                raise RuntimeError("2lala")
            elif e1 is Event.SLEEPING and e2 is not Event.EATING:
                raise RuntimeError("2lala")

        last_eat_time = int(time.time() * 1000)
        for l in reversed(self._logs):
            if l.event is Event.EATING:
                last_eat_time = l.timestamp
                break

        if int(time.time() * 1000) - last_eat_time > self._timeout_eat + 20:
            raise RuntimeError("should be dead")

    @property
    def last_event(self):
        if len(self._logs) == 0:
            return Event.NONE
        return self._logs[-1].event


class Table:
    def __init__(self, timeout_eat, philo_num):
        self._philos = [Philo(id_, timeout_eat) for id_ in range(1, philo_num + 1)]
        self._logs = []
        self._philo_num = philo_num
        self.dead = False

    def add_log(self, log):
        if self.dead:
            raise RuntimeError("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)

    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 RuntimeError("too much fork")
        for p in self._philos:
            p.check()
        for l1, l2 in zip(self._logs, self._logs[1:]):
            if l1.timestamp > l2.timestamp:
                raise RuntimeError("timestamp not ordered")