Coverage for src / competitive_verifier / models / verification.py: 100%
102 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
1from abc import ABC, abstractmethod
2from typing import Annotated, Literal, Protocol
4from pydantic import BaseModel, Field
6from .path import ForcePosixPath
7from .problem import TestCaseProvider
8from .result import VerificationResult
9from .result_status import ResultStatus
10from .shell import ShellCommand, ShellCommandLike
13class VerificationTimeoutError(Exception):
14 pass
17# Deprecated typo alias kept for compatibility with previous releases.
18VerifcationTimeoutError = VerificationTimeoutError
21class VerificationParams(Protocol):
22 default_tle: float | None
23 default_mle: float | None
26class BaseVerification(BaseModel, ABC):
27 name: str | None = None
29 @abstractmethod
30 def run(
31 self,
32 params: VerificationParams | None = None,
33 *,
34 deadline: float = float("inf"),
35 ) -> ResultStatus | VerificationResult: ...
37 @abstractmethod
38 def run_compile_command(
39 self,
40 params: VerificationParams | None = None,
41 ) -> bool: ...
43 @property
44 def is_lightweight(self) -> bool:
45 """The verification is lightweight."""
46 return False
48 def is_testdata_cached(self) -> bool:
49 """Whether the test data is present locally (best effort).
51 Must not download anything (see ``TestCaseProvider.is_testdata_cached``).
52 """
53 return True
56class ConstVerification(BaseVerification):
57 type: Literal["const"] = "const"
58 status: ResultStatus = Field(description="The pre-defined result.")
59 """The pre-defined result.
60 """
62 @property
63 def is_lightweight(self) -> bool:
64 return True
66 def run(
67 self,
68 params: VerificationParams | None = None,
69 *,
70 deadline: float = float("inf"),
71 ) -> ResultStatus:
72 return self.status
74 def run_compile_command(
75 self,
76 params: VerificationParams | None = None,
77 ) -> bool:
78 return True
81class CommandVerification(BaseVerification):
82 type: Literal["command"] = "command"
84 command: ShellCommandLike = Field(description="The shell command for verification.")
85 """The shell command for verification.
86 """
87 compile: ShellCommandLike | None = Field(
88 default=None,
89 description="The shell command for compile.",
90 )
91 """The shell command for compile.
92 """
94 tempdir: ForcePosixPath | None = Field(
95 default=None,
96 description="The temporary directory for running verification.",
97 )
98 """The temporary directory for running verification.
99 """
101 def run(
102 self,
103 params: VerificationParams | None = None,
104 *,
105 deadline: float = float("inf"),
106 ) -> ResultStatus:
107 if self.tempdir:
108 self.tempdir.mkdir(parents=True, exist_ok=True)
109 c = ShellCommand.parse_command_like(self.command)
110 if c.exec_command(text=True).returncode == 0:
111 return ResultStatus.SUCCESS
112 return ResultStatus.FAILURE
114 def run_compile_command(
115 self,
116 params: VerificationParams | None = None,
117 ) -> bool:
118 if self.compile:
119 if self.tempdir:
120 self.tempdir.mkdir(parents=True, exist_ok=True)
121 c = ShellCommand.parse_command_like(self.compile)
122 return c.exec_command(text=True).returncode == 0
123 return True
126class BaseProblemVerification(BaseVerification, ABC):
127 command: ShellCommandLike = Field(description="The shell command for verification.")
128 """The shell command for verification.
129 """
130 compile: ShellCommandLike | None = Field(
131 default=None,
132 description="The shell command for compile.",
133 )
134 """The shell command for compile.
135 """
137 error: float | None = Field(
138 default=None,
139 examples=[1e-9],
140 description="The absolute or relative error to be considered as correct.",
141 )
142 """The absolute or relative error to be considered as correct.
143 """
144 tle: float | None = Field(
145 default=None,
146 examples=[10],
147 description="The TLE time in seconds.",
148 )
149 """The TLE time in seconds.
150 """
151 mle: float | None = Field(
152 default=None,
153 examples=[64],
154 description="The MLE memory size in megabytes.",
155 )
156 """The MLE memory size in megabytes.
157 """
159 @abstractmethod
160 def _problem(self) -> TestCaseProvider | None: ...
162 def is_testdata_cached(self) -> bool:
163 problem = self._problem()
164 return problem is None or problem.is_testdata_cached()
166 def run(
167 self,
168 params: VerificationParams | None = None,
169 *,
170 deadline: float = float("inf"),
171 ) -> VerificationResult | ResultStatus:
172 from competitive_verifier import oj # noqa: PLC0415
174 if not params:
175 raise ValueError("ProblemVerification.run requires VerificationParams")
177 problem = self._problem()
178 if not problem:
179 return ResultStatus.FAILURE
181 c = ShellCommand.parse_command_like(self.command)
182 result = oj.test(
183 problem=problem,
184 command=c.command,
185 env=c.env,
186 tle=self.tle or params.default_tle,
187 error=self.error,
188 mle=self.mle or params.default_mle,
189 deadline=deadline,
190 )
191 result.verification_name = self.name
192 return result
194 def run_compile_command(
195 self,
196 params: VerificationParams | None = None,
197 ) -> bool:
198 if self.compile:
199 c = ShellCommand.parse_command_like(self.compile)
200 return c.exec_command(text=True).returncode == 0
201 return True
204class ProblemVerification(BaseProblemVerification):
205 type: Literal["problem"] = "problem"
207 problem: str = Field(
208 description="The URL of problem.",
209 )
210 """
211 problem: URL of problem
212 """
214 def _problem(self) -> TestCaseProvider | None:
215 # circular dependency
216 from competitive_verifier.oj import problem_from_url # noqa: PLC0415
218 return problem_from_url(self.problem)
221class LocalProblemVerification(BaseProblemVerification):
222 type: Literal["local"] = "local"
224 input: ForcePosixPath = Field(
225 description="The file path of testcases.",
226 )
227 """
228 input: file path of testcases
229 """
231 tempdir: ForcePosixPath | None = Field(
232 default=None,
233 description="The temporary directory for running verification.",
234 )
235 """The temporary directory for running verification.
236 """
238 def _problem(self) -> TestCaseProvider | None:
239 # circular dependency
240 from competitive_verifier.oj import LocalProblem # noqa: PLC0415
242 return LocalProblem(self.input)
244 def run_compile_command(self, params: VerificationParams | None = None) -> bool:
245 if self.tempdir is not None:
246 self.tempdir.mkdir(parents=True, exist_ok=True)
248 return super().run_compile_command(params)
251Verification = Annotated[
252 ConstVerification
253 | CommandVerification
254 | ProblemVerification
255 | LocalProblemVerification,
256 Field(discriminator="type"),
257]