Coverage for src / competitive_verifier / models / problem.py: 100%
34 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-04 07:40 +0900
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-04 07: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
42 def sync_testdata(self) -> None:
43 """Fetch the latest upstream test data identity so ``testdata_hash`` reflects it.
45 Only for providers whose test data can change upstream; no-op otherwise.
46 """
47 return
49 def testdata_hash(self) -> str | None:
50 """Digest identifying the test data currently present in the local cache.
52 Must not download anything. ``None`` if the provider never expects test
53 data to change, or it isn't downloaded yet; the hash-based prev-result
54 check then assumes the test data is unchanged.
55 """
56 return None
59class Problem(TestCaseProvider):
60 def __repr__(self) -> str:
61 return f"{self.__class__.__name__}.from_url({self.url!r})"
63 def __hash__(self) -> int: # pragma: no cover
64 return hash(self.url) ^ hash(type(self))
66 def __eq__(self, value: object) -> bool:
67 if type(self) is not type(value):
68 return False
69 return self.url == cast("Problem", value).url
71 @property
72 @abstractmethod
73 def url(self) -> str: ...
75 @classmethod
76 @abstractmethod
77 def from_url(cls, url: str) -> Optional["Problem"]: ...
79 @property
80 def hash_id(self):
81 return hashlib.md5(self.url.encode(), usedforsecurity=False).hexdigest()
83 @property
84 def problem_directory(self):
85 return config.get_problem_cache_dir() / self.hash_id
87 @property
88 def test_directory(self):
89 return self.problem_directory / "test"