updated decorator and util

This commit is contained in:
2024-02-06 06:07:45 +01:00
parent c54518ed65
commit 5ae74983bb
4 changed files with 195 additions and 271 deletions

View File

@@ -1,16 +1,10 @@
import functools
import multiprocessing
from typing import Callable, Iterable, Iterator
from typing import Any, Callable, Iterator
class TimeoutException(Exception):
pass
def get_subtasks_for(cls: type) -> Iterator[type]:
"""get all tasks in a tasks aka subtasks"""
return [e for e in cls.__dict__.values() if hasattr(e, 'is_task')]
def get_tests_for(cls: type) -> Iterator[Callable[[], None]]:
"""get all functions that are declared as tests"""
return [e for e in cls.__dict__.values() if hasattr(e, 'is_test')]
@@ -23,7 +17,7 @@ def test_wrapper(test: Callable[[], None]):
test.cause = e
def run_test(test: Callable[[], None]):
def run_test(test: Callable[[], None]) -> None:
"""run a test and catch any unexpected behavior and mark it as failed with the exception as cause"""
if not hasattr(test, 'is_test'):
return
@@ -36,101 +30,28 @@ def run_test(test: Callable[[], None]):
test.cause = TimeoutException(f"test failed after {timeout} seconds")
def run_tests_for_task(task: type) -> None:
def run_tests_for_task(task: object) -> None:
for test in task.tests:
run_test(test)
for task in task.tasks:
run_tests_for_task(task)
def points_to_deduct(e: type | Callable) -> int:
match e:
case type() if hasattr(e, 'is_task'):
to_detuct = 0
for test in e.tests:
if hasattr(test, 'is_test'):
to_detuct += test.to_deduct()
for task in e.tasks:
to_detuct += points_to_deduct(task)
return e.max_points if to_detuct > e.max_points else to_detuct
case Callable(test):
return test.to_detuct()
case _:
return 0
class Exercise(object):
def __init__(self, id: str) -> None:
self.__tasks: list[type] = []
self.__id = id
self.__max_points = 0
self.__points = 0
def register(self, cls: type) -> None:
if hasattr(cls, 'is_task'):
self.__tasks.append(cls)
self.__max_points += cls.max_points
return cls
def run(self) -> str:
self.run_tests()
self.deduct_points()
@property
def id(self) -> str:
return self.__id
@property
def max_points(self) -> float:
return self.__max_points
@property
def points(self) -> float:
return self.__points
@property
def tasks(self) -> Iterable[object]:
return self.__tasks
def run_tests(self):
for task in self.__tasks:
run_tests_for_task(task)
def deduct_points(self):
for task in self.__tasks:
to_detuct = points_to_deduct(task)
task.points = 0 if to_detuct > task.max_points else task.max_points - to_detuct
self.__points = functools.reduce(
lambda a, b: a + b, map(lambda t: t.points, self.__tasks), 0.0)
def get_points(self) -> float:
return self.__points
def get_max_points(self) -> float:
return self.__max_points
def eip_task(header: str, max_points: float, ex: Exercise) -> Callable[[type], type]:
def eip_task(name: str, max_points: float) -> Callable[[type], type]:
def wrapper(cls: type) -> type:
cls.is_task = True
cls.header = header
cls.max_points = max_points
cls.tasks = get_subtasks_for(cls)
cls.tests = get_tests_for(cls)
ex.register(cls)
cls.is_task: bool = True
cls.name: str = name
cls.max_points: float = max_points
cls.tests: Callable[..., None] = get_tests_for(cls)
return cls
return wrapper
def eip_test(msg: str, to_deduct: float, timeout = 10) -> Callable[[], None]:
def eip_test(msg: str, points: float, timeout = 180) -> Callable[[], None]:
def wrapper(test: Callable[[], None]) -> Callable[[], None]:
test.is_test = True
test.msg = msg
test.has_failed = False
test.to_deduct = lambda: to_deduct if test.has_failed else 0
test.timeout = timeout
test.cause = None
test.is_test: bool = True
test.msg: str = msg
test.has_failed: bool = False
test.points: float = points
test.timeout: int = timeout
test.cause: Any | None = None
return test
return wrapper