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

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 

43class Problem(TestCaseProvider): 

44 def __repr__(self) -> str: 

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

46 

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

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

49 

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 

54 

55 @property 

56 @abstractmethod 

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

58 

59 @classmethod 

60 @abstractmethod 

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

62 

63 @property 

64 def hash_id(self): 

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

66 

67 @property 

68 def problem_directory(self): 

69 return config.get_problem_cache_dir() / self.hash_id 

70 

71 @property 

72 def test_directory(self): 

73 return self.problem_directory / "test"