Coverage for src / competitive_verifier / models / problem.py: 100%
30 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-03 23:40 +0900
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-03 23:40 +0900
1import hashlib
2import pathlib
3from abc import ABC, abstractmethod
4from collections.abc import Iterable
5from typing import NamedTuple, Optional, cast
7from competitive_verifier import config
10class TestCaseFile(NamedTuple):
11 name: str
12 input_path: pathlib.Path
13 output_path: pathlib.Path
16class TestCaseData(NamedTuple):
17 name: str
18 input_data: bytes
19 output_data: bytes
22class TestCaseProvider(ABC):
23 @abstractmethod
24 def download_system_cases(self) -> Iterable[TestCaseData] | bool: ...
26 @abstractmethod
27 def iter_system_cases(self) -> Iterable[TestCaseFile]: ...
29 def is_testdata_cached(self) -> bool:
30 """Whether the test data is present locally (best effort).
32 Must not download anything. ``True`` only means that some test data
33 exists in the local cache, not that it's up to date:
34 ``download_system_cases`` may still refresh it.
35 """
36 return True
38 @property
39 def checker(self) -> pathlib.Path | None:
40 return None
43class Problem(TestCaseProvider):
44 def __repr__(self) -> str:
45 return f"{self.__class__.__name__}.from_url({self.url!r})"
47 def __hash__(self) -> int: # pragma: no cover
48 return hash(self.url) ^ hash(type(self))
50 def __eq__(self, value: object) -> bool:
51 if type(self) is not type(value):
52 return False
53 return self.url == cast("Problem", value).url
55 @property
56 @abstractmethod
57 def url(self) -> str: ...
59 @classmethod
60 @abstractmethod
61 def from_url(cls, url: str) -> Optional["Problem"]: ...
63 @property
64 def hash_id(self):
65 return hashlib.md5(self.url.encode(), usedforsecurity=False).hexdigest()
67 @property
68 def problem_directory(self):
69 return config.get_problem_cache_dir() / self.hash_id
71 @property
72 def test_directory(self):
73 return self.problem_directory / "test"