rbx.cp 0.5.28__py3-none-any.whl → 0.5.30__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rbx/box/builder.py +5 -5
- rbx/box/code.py +50 -1
- rbx/box/contest/schema.py +5 -5
- rbx/box/creation.py +10 -0
- rbx/box/extensions.py +2 -2
- rbx/box/formatting.py +10 -0
- rbx/box/generators.py +405 -276
- rbx/box/generators_test.py +1 -1
- rbx/box/main.py +17 -7
- rbx/box/schema.py +51 -34
- rbx/box/setter_config.py +7 -7
- rbx/box/solutions.py +151 -62
- rbx/box/state.py +9 -0
- rbx/box/statements/schema.py +6 -4
- rbx/box/stresses.py +11 -4
- rbx/box/testcases.py +17 -1
- rbx/grading/judge/sandboxes/stupid_sandbox.py +0 -1
- rbx/utils.py +8 -2
- {rbx_cp-0.5.28.dist-info → rbx_cp-0.5.30.dist-info}/METADATA +1 -4
- {rbx_cp-0.5.28.dist-info → rbx_cp-0.5.30.dist-info}/RECORD +23 -21
- {rbx_cp-0.5.28.dist-info → rbx_cp-0.5.30.dist-info}/LICENSE +0 -0
- {rbx_cp-0.5.28.dist-info → rbx_cp-0.5.30.dist-info}/WHEEL +0 -0
- {rbx_cp-0.5.28.dist-info → rbx_cp-0.5.30.dist-info}/entry_points.txt +0 -0
rbx/box/generators_test.py
CHANGED
@@ -19,7 +19,7 @@ def test_generator_works(pkg_from_testdata: pathlib.Path):
|
|
19
19
|
print_directory_tree(pkg_from_testdata)
|
20
20
|
|
21
21
|
assert (
|
22
|
-
package.get_build_testgroup_path('gen1') / '0-
|
22
|
+
package.get_build_testgroup_path('gen1') / '0-000.in'
|
23
23
|
).read_text() == '777\n'
|
24
24
|
assert (
|
25
25
|
package.get_build_testgroup_path('gen1') / '1-gen-000.in'
|
rbx/box/main.py
CHANGED
@@ -25,6 +25,7 @@ from rbx.box import (
|
|
25
25
|
builder,
|
26
26
|
cd,
|
27
27
|
setter_config,
|
28
|
+
state,
|
28
29
|
creation,
|
29
30
|
download,
|
30
31
|
environment,
|
@@ -48,6 +49,7 @@ from rbx.box.solutions import (
|
|
48
49
|
run_solutions,
|
49
50
|
)
|
50
51
|
from rbx.box.statements import build_statements
|
52
|
+
from rbx.box.testcases import TestcaseEntry
|
51
53
|
|
52
54
|
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
53
55
|
app.add_typer(
|
@@ -82,6 +84,11 @@ app.add_typer(
|
|
82
84
|
)
|
83
85
|
|
84
86
|
|
87
|
+
@app.callback()
|
88
|
+
def main():
|
89
|
+
state.STATE.run_through_cli = True
|
90
|
+
|
91
|
+
|
85
92
|
# @app.command('ui', hidden=True)
|
86
93
|
# @package.within_problem
|
87
94
|
# def ui():
|
@@ -254,6 +261,7 @@ def _time_impl(check: bool, detailed: bool) -> Optional[int]:
|
|
254
261
|
tracked_solutions=tracked_solutions,
|
255
262
|
check=check,
|
256
263
|
verification=VerificationLevel(verification),
|
264
|
+
timelimit_override=600, # 10 minute time limit for estimation
|
257
265
|
)
|
258
266
|
|
259
267
|
console.console.print()
|
@@ -343,6 +351,14 @@ def irun(
|
|
343
351
|
'-g',
|
344
352
|
help='Generator call to use to generate a single test for execution.',
|
345
353
|
),
|
354
|
+
testcase: Optional[str] = typer.Option(
|
355
|
+
None,
|
356
|
+
'--testcase',
|
357
|
+
'--test',
|
358
|
+
'-tc',
|
359
|
+
'-t',
|
360
|
+
help='Testcase to run, in the format "[group]/[index]". If not specified, will run interactively.',
|
361
|
+
),
|
346
362
|
print: bool = typer.Option(
|
347
363
|
False, '--print', '-p', help='Whether to print outputs to terminal.'
|
348
364
|
),
|
@@ -370,13 +386,6 @@ def irun(
|
|
370
386
|
)
|
371
387
|
return
|
372
388
|
|
373
|
-
main_solution = package.get_main_solution()
|
374
|
-
if check and main_solution is None:
|
375
|
-
console.console.print(
|
376
|
-
'[warning]No main solution found, running without checkers.[/warning]'
|
377
|
-
)
|
378
|
-
check = False
|
379
|
-
|
380
389
|
tracked_solutions = None
|
381
390
|
if outcome is not None:
|
382
391
|
tracked_solutions = {
|
@@ -411,6 +420,7 @@ def irun(
|
|
411
420
|
generator=generators.get_call_from_string(generator)
|
412
421
|
if generator is not None
|
413
422
|
else None,
|
423
|
+
testcase_entry=TestcaseEntry.parse(testcase) if testcase else None,
|
414
424
|
print=print,
|
415
425
|
sanitized=sanitized,
|
416
426
|
)
|
rbx/box/schema.py
CHANGED
@@ -20,6 +20,12 @@ def NameField(**kwargs):
|
|
20
20
|
)
|
21
21
|
|
22
22
|
|
23
|
+
def FNameField(**kwargs):
|
24
|
+
return Field(
|
25
|
+
pattern=r'^[a-zA-Z0-9][a-zA-Z0-9\-_]*$', min_length=3, max_length=128, **kwargs
|
26
|
+
)
|
27
|
+
|
28
|
+
|
23
29
|
def _check_oneof(model_obj: BaseModel, fields: List[str]):
|
24
30
|
has = []
|
25
31
|
for field in fields:
|
@@ -100,9 +106,9 @@ class ExpectedOutcome(AutoEnum):
|
|
100
106
|
if self.match(Outcome.TIME_LIMIT_EXCEEDED):
|
101
107
|
return 'yellow'
|
102
108
|
if self.match(Outcome.RUNTIME_ERROR):
|
103
|
-
return '
|
109
|
+
return 'blue'
|
104
110
|
if self.match(Outcome.MEMORY_LIMIT_EXCEEDED):
|
105
|
-
return '
|
111
|
+
return 'yellow'
|
106
112
|
return 'magenta'
|
107
113
|
|
108
114
|
def is_slow(self) -> bool:
|
@@ -150,11 +156,11 @@ class CodeItem(BaseModel):
|
|
150
156
|
)
|
151
157
|
|
152
158
|
language: Optional[str] = Field(
|
153
|
-
None, description="""The language of the code file."""
|
159
|
+
default=None, description="""The language of the code file."""
|
154
160
|
)
|
155
161
|
|
156
162
|
compilationFiles: Optional[List[str]] = Field(
|
157
|
-
[],
|
163
|
+
default=[],
|
158
164
|
description="""
|
159
165
|
Extra files that should be placed alongside the code file during its compilation,
|
160
166
|
such as testlib.h, jngen.h, etc.
|
@@ -173,17 +179,17 @@ class Testcase(BaseModel):
|
|
173
179
|
inputPath: pathlib.Path = Field(description="""The path of the input file.""")
|
174
180
|
|
175
181
|
outputPath: Optional[pathlib.Path] = Field(
|
176
|
-
None, description="""The path of the output file."""
|
182
|
+
default=None, description="""The path of the output file."""
|
177
183
|
)
|
178
184
|
|
179
185
|
|
180
186
|
class GeneratorCall(BaseModel):
|
181
187
|
model_config = ConfigDict(extra='forbid')
|
182
188
|
|
183
|
-
name: str =
|
189
|
+
name: str = FNameField(description='The name of the generator to call.')
|
184
190
|
|
185
191
|
args: Optional[str] = Field(
|
186
|
-
None, description='The arguments to pass to the generator.'
|
192
|
+
default=None, description='The arguments to pass to the generator.'
|
187
193
|
)
|
188
194
|
|
189
195
|
|
@@ -193,31 +199,31 @@ class TestcaseSubgroup(BaseModel):
|
|
193
199
|
name: str = NameField(description='The name of the test group.')
|
194
200
|
|
195
201
|
testcases: List[Testcase] = Field(
|
196
|
-
[],
|
202
|
+
default=[],
|
197
203
|
description="""
|
198
204
|
The path of testcases to add to this group,
|
199
205
|
in the order they're defined.""",
|
200
206
|
)
|
201
207
|
|
202
208
|
testcaseGlob: Optional[str] = Field(
|
203
|
-
None,
|
209
|
+
default=None,
|
204
210
|
description="""
|
205
211
|
A Python glob that matches input file paths relative to the
|
206
212
|
package directory. The globbed files should end with the extension
|
207
213
|
".in", and their corresponding outputs, if defined, should have the same file name,
|
208
|
-
but ending with ".
|
214
|
+
but ending with ".ans".
|
209
215
|
""",
|
210
216
|
)
|
211
217
|
|
212
218
|
generators: List[GeneratorCall] = Field(
|
213
|
-
[],
|
219
|
+
default=[],
|
214
220
|
description="""
|
215
221
|
A list of generators to call to generate testcases for this group.
|
216
222
|
""",
|
217
223
|
)
|
218
224
|
|
219
225
|
generatorScript: Optional[CodeItem] = Field(
|
220
|
-
None,
|
226
|
+
default=None,
|
221
227
|
description="""
|
222
228
|
A generator script to call to generate testcases for this group.
|
223
229
|
""",
|
@@ -241,14 +247,14 @@ class TestcaseGroup(TestcaseSubgroup):
|
|
241
247
|
model_config = ConfigDict(extra='forbid')
|
242
248
|
|
243
249
|
subgroups: List[TestcaseSubgroup] = Field(
|
244
|
-
[],
|
250
|
+
default=[],
|
245
251
|
description="""
|
246
252
|
A list of test subgroups to define for this group.
|
247
253
|
""",
|
248
254
|
)
|
249
255
|
|
250
256
|
validator: Optional[CodeItem] = Field(
|
251
|
-
None,
|
257
|
+
default=None,
|
252
258
|
description="""
|
253
259
|
A validator to use to validate the testcases of this group.
|
254
260
|
If not specified, will use the package-level validator.
|
@@ -257,7 +263,7 @@ Useful in cases where the constraints vary across test groups.
|
|
257
263
|
)
|
258
264
|
|
259
265
|
weight: Optional[float] = Field(
|
260
|
-
1.0,
|
266
|
+
default=1.0,
|
261
267
|
description="""
|
262
268
|
The weight of this group in the final score. Useful for
|
263
269
|
problems that have points.
|
@@ -295,29 +301,29 @@ class Stress(BaseModel):
|
|
295
301
|
|
296
302
|
class Limits(BaseModel):
|
297
303
|
time: Optional[int] = Field(
|
298
|
-
None, description='Value to override time limit with, in milliseconds.'
|
304
|
+
default=None, description='Value to override time limit with, in milliseconds.'
|
299
305
|
)
|
300
306
|
memory: Optional[int] = Field(
|
301
|
-
None, description='Value to override memory limit with, in MB.'
|
307
|
+
default=None, description='Value to override memory limit with, in MB.'
|
302
308
|
)
|
303
309
|
output: Optional[int] = Field(
|
304
|
-
None, description='Value to override output limit with, in KB.'
|
310
|
+
default=None, description='Value to override output limit with, in KB.'
|
305
311
|
)
|
306
312
|
|
307
313
|
isDoubleTL: bool = Field(
|
308
|
-
False, description='Whether to use double TL for this language.'
|
314
|
+
default=False, description='Whether to use double TL for this language.'
|
309
315
|
)
|
310
316
|
|
311
317
|
|
312
318
|
class LimitModifiers(BaseModel):
|
313
319
|
timeMultiplier: Optional[float] = Field(
|
314
|
-
None, description='Multiplier for time limit.'
|
320
|
+
default=None, description='Multiplier for time limit.'
|
315
321
|
)
|
316
322
|
time: Optional[int] = Field(
|
317
|
-
None, description='Value to override time limit with, in milliseconds.'
|
323
|
+
default=None, description='Value to override time limit with, in milliseconds.'
|
318
324
|
)
|
319
325
|
memory: Optional[int] = Field(
|
320
|
-
None, description='Value to override memory limit with, in MB.'
|
326
|
+
default=None, description='Value to override memory limit with, in MB.'
|
321
327
|
)
|
322
328
|
|
323
329
|
|
@@ -332,28 +338,30 @@ class Package(BaseModel):
|
|
332
338
|
memoryLimit: int = Field(description='Memory limit of the problem, in MB.')
|
333
339
|
|
334
340
|
outputLimit: int = Field(
|
335
|
-
4 * 1024, description='Output limit of the problem, in KB.'
|
341
|
+
default=4 * 1024, description='Output limit of the problem, in KB.'
|
336
342
|
)
|
337
343
|
|
338
344
|
modifiers: Dict[str, LimitModifiers] = Field(
|
339
|
-
{},
|
345
|
+
default={},
|
340
346
|
description="""
|
341
347
|
Limit modifiers that can be specified per language.
|
342
348
|
""",
|
343
349
|
)
|
344
350
|
|
345
351
|
checker: Optional[CodeItem] = Field(
|
346
|
-
None, description='The checker for this problem.'
|
352
|
+
default=None, description='The checker for this problem.'
|
347
353
|
)
|
348
354
|
|
349
355
|
validator: Optional[CodeItem] = Field(
|
350
|
-
None, description='The validator for this problem.'
|
356
|
+
default=None, description='The validator for this problem.'
|
351
357
|
)
|
352
358
|
|
353
|
-
generators: List[Generator] = Field(
|
359
|
+
generators: List[Generator] = Field(
|
360
|
+
default=[], description='Generators for this problem.'
|
361
|
+
)
|
354
362
|
|
355
363
|
solutions: List[Solution] = Field(
|
356
|
-
[],
|
364
|
+
default=[],
|
357
365
|
description="""
|
358
366
|
All tested solutions for this problem.
|
359
367
|
|
@@ -362,17 +370,23 @@ that is correct and used as reference -- and should have the `accepted` outcome.
|
|
362
370
|
""",
|
363
371
|
)
|
364
372
|
|
365
|
-
testcases: List[TestcaseGroup] = Field(
|
373
|
+
testcases: List[TestcaseGroup] = Field(
|
374
|
+
default=[], description='Testcases for the problem.'
|
375
|
+
)
|
366
376
|
|
367
|
-
stresses: List[Stress] = Field(
|
377
|
+
stresses: List[Stress] = Field(
|
378
|
+
default=[], description='Stress tests for the problem.'
|
379
|
+
)
|
368
380
|
|
369
|
-
statements: List[Statement] = Field(
|
381
|
+
statements: List[Statement] = Field(
|
382
|
+
default=[], description='Statements for the problem.'
|
383
|
+
)
|
370
384
|
|
371
385
|
# Vars to be re-used across the package.
|
372
386
|
# - It will be passed as --key=value arguments to the validator.
|
373
387
|
# - It will be available as \VAR{key} variables in the rbx statement.
|
374
388
|
vars: Dict[str, Primitive] = Field(
|
375
|
-
{}, description='Variables to be re-used across the package.'
|
389
|
+
default={}, description='Variables to be re-used across the package.'
|
376
390
|
)
|
377
391
|
|
378
392
|
@property
|
@@ -403,12 +417,15 @@ that is correct and used as reference -- and should have the `accepted` outcome.
|
|
403
417
|
return res
|
404
418
|
|
405
419
|
@model_validator(mode='after')
|
406
|
-
def
|
420
|
+
def check_first_solution_is_main_if_there_is_ac(self):
|
421
|
+
if all(sol.outcome != Outcome.ACCEPTED for sol in self.solutions):
|
422
|
+
# No main solution.
|
423
|
+
return self
|
407
424
|
if self.solutions:
|
408
425
|
if self.solutions[0].outcome != ExpectedOutcome.ACCEPTED:
|
409
426
|
raise PydanticCustomError(
|
410
427
|
'MISSING_MAIN_SOLUTION',
|
411
|
-
'The first solution in the package must have the "ACCEPTED" outcome.',
|
428
|
+
'The first solution in the package must have the "ACCEPTED" outcome if there are ACCEPTED solutions.',
|
412
429
|
)
|
413
430
|
return self
|
414
431
|
|
rbx/box/setter_config.py
CHANGED
@@ -19,36 +19,36 @@ _CONFIG_FILE_NAME_MAC = 'default_setter_config.mac.yml'
|
|
19
19
|
|
20
20
|
class SanitizersConfig(BaseModel):
|
21
21
|
enabled: bool = Field(
|
22
|
-
False,
|
22
|
+
default=False,
|
23
23
|
description='Whether to use sanitizers when running solutions.',
|
24
24
|
)
|
25
25
|
|
26
26
|
command_substitutions: Dict[str, str] = Field(
|
27
|
-
{},
|
27
|
+
default={},
|
28
28
|
description='Substitutions to apply to commands before running them with sanitizers.',
|
29
29
|
)
|
30
30
|
|
31
31
|
|
32
32
|
class WarningsConfig(BaseModel):
|
33
33
|
enabled: bool = Field(
|
34
|
-
False,
|
34
|
+
default=False,
|
35
35
|
description='Whether to use warning flags when running solutions.',
|
36
36
|
)
|
37
37
|
|
38
38
|
|
39
39
|
class RepeatsConfig(BaseModel):
|
40
40
|
reps: int = Field(
|
41
|
-
1,
|
41
|
+
default=1,
|
42
42
|
description='Number of times to repeat the solution.',
|
43
43
|
)
|
44
44
|
|
45
45
|
retries: int = Field(
|
46
|
-
0,
|
46
|
+
default=0,
|
47
47
|
description='Number of times to retry if the solution TLs.',
|
48
48
|
)
|
49
49
|
|
50
50
|
retries_for_stress: int = Field(
|
51
|
-
0,
|
51
|
+
default=0,
|
52
52
|
description='Number of times to retry in stress mode if the solution TLs.',
|
53
53
|
)
|
54
54
|
|
@@ -69,7 +69,7 @@ class SetterConfig(BaseModel):
|
|
69
69
|
)
|
70
70
|
|
71
71
|
command_substitutions: Dict[str, str] = Field(
|
72
|
-
{},
|
72
|
+
default={},
|
73
73
|
description='Substitutions to apply to commands before running them.',
|
74
74
|
)
|
75
75
|
|
rbx/box/solutions.py
CHANGED
@@ -16,7 +16,7 @@ import rich.text
|
|
16
16
|
import typer
|
17
17
|
from pydantic import BaseModel
|
18
18
|
|
19
|
-
from rbx import console
|
19
|
+
from rbx import console, utils
|
20
20
|
from rbx.box import checkers, package
|
21
21
|
from rbx.box.code import SanitizationLevel, compile_item, find_language_name, run_item
|
22
22
|
from rbx.box.deferred import Deferred
|
@@ -25,7 +25,14 @@ from rbx.box.environment import (
|
|
25
25
|
ExecutionConfig,
|
26
26
|
VerificationLevel,
|
27
27
|
)
|
28
|
-
from rbx.box.
|
28
|
+
from rbx.box.formatting import get_formatted_memory, get_formatted_time
|
29
|
+
from rbx.box.generators import (
|
30
|
+
GenerationMetadata,
|
31
|
+
expand_generator_call,
|
32
|
+
extract_generation_testcases,
|
33
|
+
generate_output_for_testcase,
|
34
|
+
generate_standalone,
|
35
|
+
)
|
29
36
|
from rbx.box.retries import Retrier
|
30
37
|
from rbx.box.schema import (
|
31
38
|
ExpectedOutcome,
|
@@ -35,7 +42,7 @@ from rbx.box.schema import (
|
|
35
42
|
Testcase,
|
36
43
|
TestcaseGroup,
|
37
44
|
)
|
38
|
-
from rbx.box.testcases import find_built_testcases
|
45
|
+
from rbx.box.testcases import TestcaseEntry, find_built_testcases
|
39
46
|
from rbx.grading.steps import (
|
40
47
|
DigestOrDest,
|
41
48
|
DigestOrSource,
|
@@ -419,26 +426,97 @@ def run_solutions(
|
|
419
426
|
)
|
420
427
|
|
421
428
|
|
422
|
-
def
|
429
|
+
async def _generate_testcase_interactively(
|
423
430
|
progress: Optional[StatusProgress] = None,
|
424
|
-
tracked_solutions: Optional[Set[str]] = None,
|
425
|
-
verification: VerificationLevel = VerificationLevel.NONE,
|
426
431
|
generator: Optional[GeneratorCall] = None,
|
432
|
+
testcase_entry: Optional[TestcaseEntry] = None,
|
427
433
|
check: bool = True,
|
428
|
-
print: bool = False,
|
429
434
|
sanitized: bool = False,
|
430
|
-
|
431
|
-
|
435
|
+
print: bool = False,
|
436
|
+
) -> Testcase:
|
432
437
|
main_solution = package.get_main_solution()
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
+
irun_dir = package.get_problem_iruns_dir()
|
439
|
+
inputs_dir = irun_dir / 'inputs'
|
440
|
+
inputs_dir.mkdir(parents=True, exist_ok=True)
|
441
|
+
testcase = Testcase(
|
442
|
+
inputPath=inputs_dir / '000.in',
|
443
|
+
outputPath=(inputs_dir / '000.out') if check else None,
|
438
444
|
)
|
439
445
|
|
446
|
+
is_manual = False
|
447
|
+
generation_metadata = None
|
448
|
+
if generator is not None:
|
449
|
+
generation_metadata = GenerationMetadata(
|
450
|
+
generator_call=expand_generator_call(generator),
|
451
|
+
copied_to=testcase,
|
452
|
+
)
|
453
|
+
elif testcase_entry is not None:
|
454
|
+
extracted = extract_generation_testcases([testcase_entry])
|
455
|
+
if not extracted:
|
456
|
+
console.console.print(
|
457
|
+
f'[error]Failed searching for testcase [item]{testcase_entry}[/item].[/error]'
|
458
|
+
)
|
459
|
+
raise typer.Exit(1)
|
460
|
+
generation_metadata = extracted[0].metadata
|
461
|
+
# Replace destination with the irun testcase we're using.
|
462
|
+
generation_metadata.copied_to = testcase
|
463
|
+
else:
|
464
|
+
with utils.no_progress(progress):
|
465
|
+
input = console.multiline_prompt('Testcase input')
|
466
|
+
testcase.inputPath.write_text(input)
|
467
|
+
console.console.print()
|
468
|
+
|
469
|
+
if (
|
470
|
+
testcase.outputPath is not None
|
471
|
+
and not testcase.outputPath.is_file()
|
472
|
+
and main_solution is None
|
473
|
+
):
|
474
|
+
with utils.no_progress(progress):
|
475
|
+
output = console.multiline_prompt('Testcase output')
|
476
|
+
testcase.outputPath.write_text(output)
|
477
|
+
console.console.print()
|
478
|
+
|
479
|
+
generation_metadata = GenerationMetadata(
|
480
|
+
copied_to=testcase,
|
481
|
+
)
|
482
|
+
is_manual = True
|
483
|
+
|
484
|
+
# 1. Generate testcase.
|
485
|
+
if generation_metadata is not None:
|
486
|
+
generate_standalone(
|
487
|
+
generation_metadata,
|
488
|
+
progress=progress,
|
489
|
+
validate=True,
|
490
|
+
)
|
491
|
+
if testcase_entry is not None:
|
492
|
+
console.console.print(
|
493
|
+
f'Using input from testcase [item]{testcase_entry}[/item].'
|
494
|
+
)
|
495
|
+
elif generation_metadata.generator_call is not None:
|
496
|
+
console.console.print(
|
497
|
+
f'Using input from generator call [item]{generation_metadata.generator_call.name} {generation_metadata.generator_call.args}[/item].'
|
498
|
+
)
|
499
|
+
if print and not is_manual:
|
500
|
+
console.console.print(testcase.inputPath.read_text())
|
501
|
+
else:
|
502
|
+
console.console.print(
|
503
|
+
f'Input was written to [item]{testcase.inputPath.resolve()}[/item]'
|
504
|
+
)
|
505
|
+
console.console.print()
|
506
|
+
|
507
|
+
# 2. Generate test output from reference
|
440
508
|
main_solution_digest = None
|
441
|
-
if check and
|
509
|
+
if check and not (
|
510
|
+
testcase.outputPath is not None and testcase.outputPath.is_file()
|
511
|
+
):
|
512
|
+
if main_solution is None:
|
513
|
+
console.console.print(
|
514
|
+
'[error]Checking is enabled but no main solution or custom output was specified.[/error]'
|
515
|
+
)
|
516
|
+
raise typer.Exit(1)
|
517
|
+
|
518
|
+
if progress:
|
519
|
+
progress.update('Compiling main solution...')
|
442
520
|
try:
|
443
521
|
main_solution_digest = compile_item(
|
444
522
|
main_solution,
|
@@ -452,6 +530,42 @@ def _run_interactive_solutions(
|
|
452
530
|
)
|
453
531
|
raise
|
454
532
|
|
533
|
+
if main_solution_digest is not None:
|
534
|
+
if progress:
|
535
|
+
progress.update('Generating output for test...')
|
536
|
+
# TODO: Add stderr path
|
537
|
+
generate_output_for_testcase(main_solution_digest, testcase)
|
538
|
+
|
539
|
+
if check and testcase.outputPath is not None and not testcase.outputPath.is_file():
|
540
|
+
# Output was not created, throw an error.
|
541
|
+
console.console.print(
|
542
|
+
'[error]Checking is enabled but no output could be generated for this testcase.[/error]'
|
543
|
+
)
|
544
|
+
console.console.print(
|
545
|
+
'[error]Either specify it explicitly or provide a main solution.[/error]'
|
546
|
+
)
|
547
|
+
raise typer.Exit(1)
|
548
|
+
|
549
|
+
return testcase
|
550
|
+
|
551
|
+
|
552
|
+
def _run_interactive_solutions(
|
553
|
+
testcase: Testcase,
|
554
|
+
progress: Optional[StatusProgress] = None,
|
555
|
+
tracked_solutions: Optional[Set[str]] = None,
|
556
|
+
verification: VerificationLevel = VerificationLevel.NONE,
|
557
|
+
check: bool = True,
|
558
|
+
sanitized: bool = False,
|
559
|
+
) -> Iterator[EvaluationItem]:
|
560
|
+
pkg = package.find_problem_package_or_die()
|
561
|
+
|
562
|
+
if check and progress:
|
563
|
+
progress.update('Compiling checker...')
|
564
|
+
checker_digest = checkers.compile_checker() if check else None
|
565
|
+
compiled_solutions = compile_solutions(
|
566
|
+
progress=progress, tracked_solutions=tracked_solutions, sanitized=sanitized
|
567
|
+
)
|
568
|
+
|
455
569
|
solutions = list(enumerate(pkg.solutions))
|
456
570
|
if tracked_solutions is not None:
|
457
571
|
solutions = [
|
@@ -459,33 +573,9 @@ def _run_interactive_solutions(
|
|
459
573
|
]
|
460
574
|
|
461
575
|
irun_dir = package.get_problem_iruns_dir()
|
462
|
-
shutil.rmtree(str(irun_dir), ignore_errors=True)
|
463
|
-
irun_dir.mkdir(parents=True, exist_ok=True)
|
464
|
-
inputs_dir = irun_dir / 'inputs'
|
465
|
-
inputs_dir.mkdir(parents=True, exist_ok=True)
|
466
|
-
input_path = inputs_dir / '000.in'
|
467
|
-
output_path = input_path.with_suffix('.out')
|
468
576
|
|
469
|
-
if
|
470
|
-
|
471
|
-
console.console.print(
|
472
|
-
f'Using input from generator call [item]{expanded_call.name} {expanded_call.args}[/item].'
|
473
|
-
)
|
474
|
-
if print:
|
475
|
-
console.console.print(input_path.read_text())
|
476
|
-
else:
|
477
|
-
console.console.print(
|
478
|
-
f'Input was written to [item]{input_path.resolve()}[/item]'
|
479
|
-
)
|
480
|
-
console.console.print()
|
481
|
-
else:
|
482
|
-
input = console.multiline_prompt('Testcase input')
|
483
|
-
input_path.write_text(input)
|
484
|
-
testcase = Testcase(inputPath=input_path, outputPath=output_path if check else None)
|
485
|
-
|
486
|
-
if main_solution_digest is not None:
|
487
|
-
# TODO: Add stderr path
|
488
|
-
generate_output_for_testcase(main_solution_digest, testcase)
|
577
|
+
if progress:
|
578
|
+
progress.update('Running solutions...')
|
489
579
|
|
490
580
|
for i, solution in solutions:
|
491
581
|
output_dir = irun_dir / f'{i}'
|
@@ -513,27 +603,38 @@ async def run_and_print_interactive_solutions(
|
|
513
603
|
tracked_solutions: Optional[Set[str]] = None,
|
514
604
|
verification: VerificationLevel = VerificationLevel.NONE,
|
515
605
|
generator: Optional[GeneratorCall] = None,
|
606
|
+
testcase_entry: Optional[TestcaseEntry] = None,
|
516
607
|
check: bool = True,
|
517
608
|
print: bool = False,
|
518
609
|
sanitized: bool = False,
|
519
610
|
):
|
611
|
+
# Ensure path is new.
|
612
|
+
irun_dir = package.get_problem_iruns_dir()
|
613
|
+
shutil.rmtree(str(irun_dir), ignore_errors=True)
|
614
|
+
irun_dir.mkdir(parents=True, exist_ok=True)
|
615
|
+
|
520
616
|
pkg = package.find_problem_package_or_die()
|
617
|
+
testcase = await _generate_testcase_interactively(
|
618
|
+
progress=progress,
|
619
|
+
generator=generator,
|
620
|
+
testcase_entry=testcase_entry,
|
621
|
+
check=check,
|
622
|
+
sanitized=sanitized,
|
623
|
+
print=print,
|
624
|
+
)
|
521
625
|
items = _run_interactive_solutions(
|
626
|
+
testcase,
|
522
627
|
progress=progress,
|
523
628
|
tracked_solutions=tracked_solutions,
|
524
629
|
verification=verification,
|
525
630
|
check=check,
|
526
|
-
generator=generator,
|
527
631
|
sanitized=sanitized,
|
528
|
-
print=print,
|
529
632
|
)
|
530
633
|
|
531
|
-
if progress:
|
532
|
-
progress.stop()
|
533
|
-
|
534
634
|
for item in items:
|
535
635
|
sol = pkg.solutions[item.solution_index]
|
536
|
-
|
636
|
+
with utils.no_progress(progress):
|
637
|
+
_print_solution_header(sol, console.console, is_irun=True)
|
537
638
|
|
538
639
|
eval = await item.eval()
|
539
640
|
|
@@ -557,7 +658,7 @@ async def run_and_print_interactive_solutions(
|
|
557
658
|
|
558
659
|
|
559
660
|
def _get_solution_repr(sol: Solution) -> List[Tuple[str, str]]:
|
560
|
-
fg_color = sol.outcome.style()
|
661
|
+
fg_color = sol.outcome.style()
|
561
662
|
return [
|
562
663
|
('', f'{str(sol.path)} '),
|
563
664
|
(f'fg:{fg_color}', sol.outcome.name),
|
@@ -590,9 +691,9 @@ def get_outcome_style_verdict(outcome: Outcome) -> str:
|
|
590
691
|
if outcome == Outcome.TIME_LIMIT_EXCEEDED:
|
591
692
|
return 'yellow'
|
592
693
|
if outcome == Outcome.RUNTIME_ERROR:
|
593
|
-
return '
|
694
|
+
return 'blue'
|
594
695
|
if outcome == Outcome.MEMORY_LIMIT_EXCEEDED:
|
595
|
-
return '
|
696
|
+
return 'yellow'
|
596
697
|
return 'magenta'
|
597
698
|
|
598
699
|
|
@@ -629,10 +730,6 @@ def _get_evals_memory_in_bytes(evals: List[Evaluation]) -> int:
|
|
629
730
|
return max(int(eval.log.memory or 0) for eval in evals)
|
630
731
|
|
631
732
|
|
632
|
-
def get_formatted_time(time_in_ms: int) -> str:
|
633
|
-
return f'{time_in_ms} ms'
|
634
|
-
|
635
|
-
|
636
733
|
def get_evals_formatted_time(evals: List[Evaluation]) -> str:
|
637
734
|
max_time = _get_evals_time_in_ms(evals)
|
638
735
|
return get_formatted_time(max_time)
|
@@ -665,14 +762,6 @@ def get_capped_evals_formatted_time(
|
|
665
762
|
return f'{max_time} ms'
|
666
763
|
|
667
764
|
|
668
|
-
def get_formatted_memory(memory_in_bytes: int) -> str:
|
669
|
-
if memory_in_bytes < 1024 * 1024:
|
670
|
-
if memory_in_bytes < 1024:
|
671
|
-
return f'{memory_in_bytes} B'
|
672
|
-
return f'{memory_in_bytes // 1024} KiB'
|
673
|
-
return f'{memory_in_bytes // (1024 * 1024)} MiB'
|
674
|
-
|
675
|
-
|
676
765
|
def get_evals_formatted_memory(evals: List[Evaluation]) -> str:
|
677
766
|
max_memory = _get_evals_memory_in_bytes(evals)
|
678
767
|
return get_formatted_memory(max_memory)
|