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

1import hashlib 

2import pathlib 

3from abc import ABC, abstractmethod 

4from collections.abc import Iterable 

5from typing import NamedTuple, Optional, cast 

6 

7from competitive_verifier import config 

8 

9 

10class TestCaseFile(NamedTuple): 

11 name: str 

12 input_path: pathlib.Path 

13 output_path: pathlib.Path 

14 

15 

16class TestCaseData(NamedTuple): 

17 name: str 

18 input_data: bytes 

19 output_data: bytes 

20 

21 

22class TestCaseProvider(ABC): 

23 @abstractmethod 

24 def download_system_cases(self) -> Iterable[TestCaseData] | bool: ... 

25 

26 @abstractmethod 

27 def iter_system_cases(self) -> Iterable[TestCaseFile]: ... 

28 

29 def is_testdata_cached(self) -> bool: 

30 """Whether the test data is present locally (best effort). 

31 

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 

37 

38 @property 

39 def checker(self) -> pathlib.Path | None: 

40 return None 

41 

42 def sync_testdata(self) -> None: 

43 """Fetch the latest upstream test data identity so ``testdata_hash`` reflects it. 

44 

45 Only for providers whose test data can change upstream; no-op otherwise. 

46 """ 

47 return 

48 

49 def testdata_hash(self) -> str | None: 

50 """Digest identifying the test data currently present in the local cache. 

51 

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 

57 

58 

59class Problem(TestCaseProvider): 

60 def __repr__(self) -> str: 

61 return f"{self.__class__.__name__}.from_url({self.url!r})" 

62 

63 def __hash__(self) -> int: # pragma: no cover 

64 return hash(self.url) ^ hash(type(self)) 

65 

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 

70 

71 @property 

72 @abstractmethod 

73 def url(self) -> str: ... 

74 

75 @classmethod 

76 @abstractmethod 

77 def from_url(cls, url: str) -> Optional["Problem"]: ... 

78 

79 @property 

80 def hash_id(self): 

81 return hashlib.md5(self.url.encode(), usedforsecurity=False).hexdigest() 

82 

83 @property 

84 def problem_directory(self): 

85 return config.get_problem_cache_dir() / self.hash_id 

86 

87 @property 

88 def test_directory(self): 

89 return self.problem_directory / "test"