rbx.cp 0.5.16__py3-none-any.whl → 0.5.17__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 +2 -2
- rbx/box/cd.py +12 -2
- rbx/box/checkers.py +38 -4
- rbx/box/code.py +126 -18
- rbx/box/compile.py +40 -8
- rbx/box/contest/build_contest_statements.py +5 -3
- rbx/box/contest/contest_package.py +2 -1
- rbx/box/contest/main.py +2 -2
- rbx/box/contest/statements.py +3 -3
- rbx/box/deferred.py +26 -0
- rbx/box/extensions.py +0 -8
- rbx/box/generators.py +6 -4
- rbx/box/main.py +185 -16
- rbx/box/package.py +2 -2
- rbx/box/packaging/contest_main.py +3 -3
- rbx/box/sanitizers/warning_stack.py +90 -0
- rbx/box/schema.py +15 -1
- rbx/box/setter_config.py +132 -0
- rbx/box/solutions.py +237 -149
- rbx/box/solutions_test.py +2 -1
- rbx/box/stresses.py +5 -3
- rbx/box/validators.py +5 -5
- rbx/config.py +3 -0
- rbx/grading/caching.py +4 -0
- rbx/grading/judge/sandboxes/stupid_sandbox.py +2 -0
- rbx/grading/steps.py +125 -15
- rbx/grading/steps_with_caching.py +2 -0
- rbx/resources/default_setter_config.mac.yml +31 -0
- rbx/resources/default_setter_config.yml +29 -0
- rbx/utils.py +1 -1
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.17.dist-info}/METADATA +1 -1
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.17.dist-info}/RECORD +35 -30
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.17.dist-info}/LICENSE +0 -0
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.17.dist-info}/WHEEL +0 -0
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.17.dist-info}/entry_points.txt +0 -0
rbx/box/solutions.py
CHANGED
@@ -5,17 +5,17 @@ import dataclasses
|
|
5
5
|
import pathlib
|
6
6
|
import shutil
|
7
7
|
from collections.abc import Iterator
|
8
|
-
from typing import Dict, List, Optional, Set
|
8
|
+
from typing import Dict, Iterable, List, Optional, Set
|
9
9
|
|
10
10
|
import rich
|
11
11
|
import rich.live
|
12
12
|
import rich.table
|
13
|
-
from more_itertools import seekable
|
14
13
|
from pydantic import BaseModel
|
15
14
|
|
16
15
|
from rbx import console
|
17
16
|
from rbx.box import checkers, environment, package
|
18
|
-
from rbx.box.code import compile_item, run_item
|
17
|
+
from rbx.box.code import SanitizationLevel, compile_item, find_language_name, run_item
|
18
|
+
from rbx.box.deferred import Deferred
|
19
19
|
from rbx.box.environment import EnvironmentSandbox, ExecutionConfig, VerificationLevel
|
20
20
|
from rbx.box.generators import generate_output_for_testcase, generate_standalone
|
21
21
|
from rbx.box.schema import (
|
@@ -36,14 +36,15 @@ from rbx.grading.steps import (
|
|
36
36
|
)
|
37
37
|
from rbx.utils import StatusProgress, model_to_yaml
|
38
38
|
|
39
|
-
StructuredEvaluation = Dict[str, Dict[str, List[Optional[Evaluation]]]]
|
39
|
+
StructuredEvaluation = Dict[str, Dict[str, List[Optional[Deferred[Evaluation]]]]]
|
40
40
|
|
41
41
|
|
42
|
-
|
42
|
+
@dataclasses.dataclass(frozen=True)
|
43
|
+
class EvaluationItem:
|
43
44
|
solution_index: int
|
44
45
|
group_name: str
|
45
46
|
testcase_index: int
|
46
|
-
eval: Evaluation
|
47
|
+
eval: Deferred[Evaluation]
|
47
48
|
|
48
49
|
|
49
50
|
class GroupSkeleton(BaseModel):
|
@@ -54,7 +55,6 @@ class GroupSkeleton(BaseModel):
|
|
54
55
|
class SolutionReportSkeleton(BaseModel):
|
55
56
|
solutions: List[Solution]
|
56
57
|
groups: List[GroupSkeleton]
|
57
|
-
group_first: bool
|
58
58
|
|
59
59
|
def find_group_skeleton(self, group_name: str) -> Optional[GroupSkeleton]:
|
60
60
|
groups = [group for group in self.groups if group.name == group_name]
|
@@ -74,7 +74,7 @@ class SolutionReportSkeleton(BaseModel):
|
|
74
74
|
@dataclasses.dataclass
|
75
75
|
class RunSolutionResult:
|
76
76
|
skeleton: SolutionReportSkeleton
|
77
|
-
items:
|
77
|
+
items: List[EvaluationItem]
|
78
78
|
|
79
79
|
def empty_structured_evaluation(self) -> StructuredEvaluation:
|
80
80
|
return self.skeleton.empty_structured_evaluation()
|
@@ -94,9 +94,18 @@ def get_matching_solutions(expected_outcome: ExpectedOutcome) -> List[Solution]:
|
|
94
94
|
return res
|
95
95
|
|
96
96
|
|
97
|
+
def get_exact_matching_solutions(expected_outcome: ExpectedOutcome) -> List[Solution]:
|
98
|
+
res = []
|
99
|
+
for solution in package.get_solutions():
|
100
|
+
if solution.outcome == expected_outcome:
|
101
|
+
res.append(solution)
|
102
|
+
return res
|
103
|
+
|
104
|
+
|
97
105
|
def compile_solutions(
|
98
106
|
progress: Optional[StatusProgress] = None,
|
99
107
|
tracked_solutions: Optional[Set[str]] = None,
|
108
|
+
sanitized: bool = False,
|
100
109
|
) -> Dict[pathlib.Path, str]:
|
101
110
|
pkg = package.find_problem_package_or_die()
|
102
111
|
|
@@ -111,7 +120,12 @@ def compile_solutions(
|
|
111
120
|
if progress:
|
112
121
|
progress.update(f'Compiling solution [item]{solution.path}[/item]...')
|
113
122
|
try:
|
114
|
-
compiled_solutions[solution.path] = compile_item(
|
123
|
+
compiled_solutions[solution.path] = compile_item(
|
124
|
+
solution,
|
125
|
+
sanitized=SanitizationLevel.FORCE
|
126
|
+
if sanitized
|
127
|
+
else SanitizationLevel.NONE,
|
128
|
+
)
|
115
129
|
except:
|
116
130
|
console.console.print(
|
117
131
|
f'[error]Failed compiling solution [item]{solution.path}[/item][/error]'
|
@@ -129,11 +143,12 @@ def _run_solution_on_testcase(
|
|
129
143
|
output_dir: pathlib.Path,
|
130
144
|
testcase_index: int = 0,
|
131
145
|
verification: VerificationLevel = VerificationLevel.NONE,
|
146
|
+
timelimit_override: Optional[int] = None,
|
132
147
|
) -> Evaluation:
|
133
148
|
pkg = package.find_problem_package_or_die()
|
134
149
|
actual_sandbox = package.get_singleton_sandbox()
|
135
150
|
|
136
|
-
timelimit = pkg.timelimit_for_language(solution.language)
|
151
|
+
timelimit = timelimit_override or pkg.timelimit_for_language(solution.language)
|
137
152
|
|
138
153
|
sandbox = EnvironmentSandbox()
|
139
154
|
sandbox.timeLimit = timelimit
|
@@ -196,11 +211,13 @@ def _run_solution(
|
|
196
211
|
group_name: str,
|
197
212
|
progress: Optional[StatusProgress] = None,
|
198
213
|
verification: VerificationLevel = VerificationLevel.NONE,
|
199
|
-
|
214
|
+
timelimit_override: Optional[int] = None,
|
215
|
+
) -> List[Deferred[Evaluation]]:
|
200
216
|
runs_dir = package.get_problem_runs_dir()
|
201
217
|
|
202
218
|
group = package.get_testgroup(group_name)
|
203
219
|
testcases = find_built_testcases(group)
|
220
|
+
res: List[Deferred[Evaluation]] = []
|
204
221
|
for i, testcase in enumerate(testcases):
|
205
222
|
assert testcase.outputPath is not None
|
206
223
|
output_path = runs_dir / f'{solution_index}' / group.name
|
@@ -210,19 +227,25 @@ def _run_solution(
|
|
210
227
|
f'Running solution [item]{solution.path}[/item] on test [item]{group.name}[/item] / [item]{i}[/item]...'
|
211
228
|
)
|
212
229
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
230
|
+
async def run_fn(i=i, testcase=testcase, output_path=output_path):
|
231
|
+
return _run_solution_on_testcase(
|
232
|
+
solution,
|
233
|
+
compiled_digest,
|
234
|
+
checker_digest,
|
235
|
+
testcase,
|
236
|
+
output_path,
|
237
|
+
testcase_index=i,
|
238
|
+
verification=verification,
|
239
|
+
timelimit_override=timelimit_override,
|
240
|
+
)
|
241
|
+
|
242
|
+
res.append(Deferred(run_fn))
|
243
|
+
|
244
|
+
return res
|
222
245
|
|
223
246
|
|
224
|
-
def convert_list_of_solution_evaluations_to_dict(
|
225
|
-
items:
|
247
|
+
async def convert_list_of_solution_evaluations_to_dict(
|
248
|
+
items: Iterable[EvaluationItem],
|
226
249
|
) -> List[Dict[str, List[Evaluation]]]:
|
227
250
|
pkg = package.find_problem_package_or_die()
|
228
251
|
res: List[Dict[str, List[Evaluation]]] = [
|
@@ -230,14 +253,13 @@ def convert_list_of_solution_evaluations_to_dict(
|
|
230
253
|
]
|
231
254
|
|
232
255
|
for item in items:
|
233
|
-
res[item.solution_index][item.group_name].append(item.eval)
|
256
|
+
res[item.solution_index][item.group_name].append(await item.eval())
|
234
257
|
|
235
258
|
return res
|
236
259
|
|
237
260
|
|
238
261
|
def _get_report_skeleton(
|
239
262
|
tracked_solutions: Optional[Set[str]] = None,
|
240
|
-
group_first: bool = False,
|
241
263
|
verification: VerificationLevel = VerificationLevel.NONE,
|
242
264
|
) -> SolutionReportSkeleton:
|
243
265
|
pkg = package.find_problem_package_or_die()
|
@@ -258,7 +280,8 @@ def _get_report_skeleton(
|
|
258
280
|
testcases = find_built_testcases(group)
|
259
281
|
groups.append(GroupSkeleton(name=group.name, testcases=testcases))
|
260
282
|
return SolutionReportSkeleton(
|
261
|
-
solutions=solutions,
|
283
|
+
solutions=solutions,
|
284
|
+
groups=groups,
|
262
285
|
)
|
263
286
|
|
264
287
|
|
@@ -267,13 +290,14 @@ def _produce_solution_items(
|
|
267
290
|
tracked_solutions: Optional[Set[str]] = None,
|
268
291
|
verification: VerificationLevel = VerificationLevel.NONE,
|
269
292
|
check: bool = True,
|
270
|
-
|
271
|
-
|
293
|
+
timelimit_override: Optional[int] = None,
|
294
|
+
sanitized: bool = False,
|
295
|
+
) -> List[EvaluationItem]:
|
272
296
|
pkg = package.find_problem_package_or_die()
|
273
297
|
|
274
298
|
checker_digest = checkers.compile_checker() if check else None
|
275
299
|
compiled_solutions = compile_solutions(
|
276
|
-
progress=progress, tracked_solutions=tracked_solutions
|
300
|
+
progress=progress, tracked_solutions=tracked_solutions, sanitized=sanitized
|
277
301
|
)
|
278
302
|
|
279
303
|
# Clear run directory and rely on cache to
|
@@ -293,7 +317,8 @@ def _produce_solution_items(
|
|
293
317
|
|
294
318
|
def yield_items(
|
295
319
|
solution_index: int, solution: Solution, group_name: str
|
296
|
-
) ->
|
320
|
+
) -> List[EvaluationItem]:
|
321
|
+
res: List[EvaluationItem] = []
|
297
322
|
for i, eval in enumerate(
|
298
323
|
_run_solution(
|
299
324
|
solution,
|
@@ -303,25 +328,28 @@ def _produce_solution_items(
|
|
303
328
|
group_name,
|
304
329
|
progress=progress,
|
305
330
|
verification=verification,
|
331
|
+
timelimit_override=timelimit_override,
|
306
332
|
)
|
307
333
|
):
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
334
|
+
res.append(
|
335
|
+
EvaluationItem(
|
336
|
+
solution_index=solution_index,
|
337
|
+
group_name=group_name,
|
338
|
+
testcase_index=i,
|
339
|
+
eval=eval,
|
340
|
+
)
|
313
341
|
)
|
314
342
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
for i, solution in solutions:
|
319
|
-
yield from yield_items(i, solution, group.name)
|
320
|
-
return
|
343
|
+
return res
|
344
|
+
|
345
|
+
res: List[EvaluationItem] = []
|
321
346
|
|
347
|
+
groups = pkg.testcases
|
322
348
|
for i, solution in solutions:
|
323
349
|
for group in groups:
|
324
|
-
|
350
|
+
res.extend(yield_items(i, solution, group.name))
|
351
|
+
|
352
|
+
return res
|
325
353
|
|
326
354
|
|
327
355
|
def run_solutions(
|
@@ -329,18 +357,18 @@ def run_solutions(
|
|
329
357
|
tracked_solutions: Optional[Set[str]] = None,
|
330
358
|
verification: VerificationLevel = VerificationLevel.NONE,
|
331
359
|
check: bool = True,
|
332
|
-
|
360
|
+
timelimit_override: Optional[int] = None,
|
361
|
+
sanitized: bool = False,
|
333
362
|
) -> RunSolutionResult:
|
334
363
|
return RunSolutionResult(
|
335
|
-
skeleton=_get_report_skeleton(
|
336
|
-
tracked_solutions, group_first, verification=verification
|
337
|
-
),
|
364
|
+
skeleton=_get_report_skeleton(tracked_solutions, verification=verification),
|
338
365
|
items=_produce_solution_items(
|
339
366
|
progress=progress,
|
340
367
|
tracked_solutions=tracked_solutions,
|
341
368
|
verification=verification,
|
342
369
|
check=check,
|
343
|
-
|
370
|
+
timelimit_override=timelimit_override,
|
371
|
+
sanitized=sanitized,
|
344
372
|
),
|
345
373
|
)
|
346
374
|
|
@@ -350,18 +378,26 @@ def _run_interactive_solutions(
|
|
350
378
|
verification: VerificationLevel = VerificationLevel.NONE,
|
351
379
|
generator: Optional[GeneratorCall] = None,
|
352
380
|
check: bool = True,
|
381
|
+
sanitized: bool = False,
|
353
382
|
) -> Iterator[EvaluationItem]:
|
354
383
|
pkg = package.find_problem_package_or_die()
|
355
384
|
main_solution = package.get_main_solution()
|
356
385
|
check = check and main_solution is not None
|
357
386
|
|
358
387
|
checker_digest = checkers.compile_checker() if check else None
|
359
|
-
compiled_solutions = compile_solutions(
|
388
|
+
compiled_solutions = compile_solutions(
|
389
|
+
tracked_solutions=tracked_solutions, sanitized=sanitized
|
390
|
+
)
|
360
391
|
|
361
392
|
main_solution_digest = None
|
362
393
|
if check and main_solution is not None:
|
363
394
|
try:
|
364
|
-
main_solution_digest = compile_item(
|
395
|
+
main_solution_digest = compile_item(
|
396
|
+
main_solution,
|
397
|
+
sanitized=SanitizationLevel.FORCE
|
398
|
+
if sanitized
|
399
|
+
else SanitizationLevel.NONE,
|
400
|
+
)
|
365
401
|
except:
|
366
402
|
console.console.print(
|
367
403
|
'[error]Failed compiling main solution. If you do not want to check against a main solution, run with --nocheck flag.[/error]'
|
@@ -399,27 +435,31 @@ def _run_interactive_solutions(
|
|
399
435
|
for i, solution in solutions:
|
400
436
|
output_dir = irun_dir / f'{i}'
|
401
437
|
|
402
|
-
|
403
|
-
|
404
|
-
group_name='irun',
|
405
|
-
testcase_index=0,
|
406
|
-
eval=_run_solution_on_testcase(
|
438
|
+
async def run_fn(solution=solution, output_dir=output_dir):
|
439
|
+
return _run_solution_on_testcase(
|
407
440
|
solution,
|
408
441
|
compiled_solutions[solution.path],
|
409
442
|
checker_digest,
|
410
443
|
testcase,
|
411
444
|
output_dir,
|
412
445
|
verification=verification,
|
413
|
-
)
|
446
|
+
)
|
447
|
+
|
448
|
+
yield EvaluationItem(
|
449
|
+
solution_index=i,
|
450
|
+
group_name='irun',
|
451
|
+
testcase_index=0,
|
452
|
+
eval=Deferred(run_fn),
|
414
453
|
)
|
415
454
|
|
416
455
|
|
417
|
-
def run_and_print_interactive_solutions(
|
456
|
+
async def run_and_print_interactive_solutions(
|
418
457
|
tracked_solutions: Optional[Set[str]] = None,
|
419
458
|
verification: VerificationLevel = VerificationLevel.NONE,
|
420
459
|
generator: Optional[GeneratorCall] = None,
|
421
460
|
check: bool = True,
|
422
461
|
print: bool = False,
|
462
|
+
sanitized: bool = False,
|
423
463
|
):
|
424
464
|
pkg = package.find_problem_package_or_die()
|
425
465
|
items = _run_interactive_solutions(
|
@@ -427,16 +467,19 @@ def run_and_print_interactive_solutions(
|
|
427
467
|
verification=verification,
|
428
468
|
check=check,
|
429
469
|
generator=generator,
|
470
|
+
sanitized=sanitized,
|
430
471
|
)
|
431
472
|
|
432
473
|
for item in items:
|
433
474
|
sol = pkg.solutions[item.solution_index]
|
434
475
|
_print_solution_header(sol, console.console)
|
435
476
|
|
436
|
-
|
477
|
+
eval = await item.eval()
|
478
|
+
|
479
|
+
stdout_path = eval.log.stdout_absolute_path
|
437
480
|
if print:
|
438
481
|
if (
|
439
|
-
|
482
|
+
eval.testcase.output is not None
|
440
483
|
and stdout_path is not None
|
441
484
|
and stdout_path.is_file()
|
442
485
|
):
|
@@ -485,12 +528,6 @@ def _get_evals_time_in_ms(evals: List[Evaluation]) -> int:
|
|
485
528
|
return max(int((eval.log.time or 0.0) * 1000) for eval in evals)
|
486
529
|
|
487
530
|
|
488
|
-
def _get_evals_memory_in_mb(evals: List[Evaluation]) -> int:
|
489
|
-
if not evals:
|
490
|
-
return 0
|
491
|
-
return max(int(eval.log.memory or 0) // (1024 * 1024) for eval in evals)
|
492
|
-
|
493
|
-
|
494
531
|
def _get_evals_memory_in_bytes(evals: List[Evaluation]) -> int:
|
495
532
|
if not evals:
|
496
533
|
return 0
|
@@ -522,6 +559,7 @@ def _print_solution_outcome(
|
|
522
559
|
has_plain_tle = False
|
523
560
|
bad_verdicts = set()
|
524
561
|
no_tle_bad_verdicts = set()
|
562
|
+
has_sanitizer_warnings = False
|
525
563
|
for eval in evals:
|
526
564
|
if eval.result.outcome != Outcome.ACCEPTED:
|
527
565
|
bad_verdicts.add(eval.result.outcome)
|
@@ -534,7 +572,9 @@ def _print_solution_outcome(
|
|
534
572
|
eval.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
|
535
573
|
and eval.result.no_tle_outcome is None
|
536
574
|
)
|
537
|
-
|
575
|
+
has_sanitizer_warnings = (
|
576
|
+
has_sanitizer_warnings or eval.result.sanitizer_warnings
|
577
|
+
)
|
538
578
|
unmatched_bad_verdicts = set(
|
539
579
|
v for v in bad_verdicts if not solution.outcome.match(v)
|
540
580
|
)
|
@@ -556,7 +596,9 @@ def _print_solution_outcome(
|
|
556
596
|
|
557
597
|
console.print()
|
558
598
|
evals_time = _get_evals_time_in_ms(evals)
|
559
|
-
expected_outcome_is_tle = solution.outcome.match(
|
599
|
+
expected_outcome_is_tle = solution.outcome.match(
|
600
|
+
Outcome.TIME_LIMIT_EXCEEDED
|
601
|
+
) and not solution.outcome.match(Outcome.ACCEPTED)
|
560
602
|
if (
|
561
603
|
# Running verification with double TL.
|
562
604
|
verification.value >= VerificationLevel.FULL.value
|
@@ -583,15 +625,21 @@ def _print_solution_outcome(
|
|
583
625
|
console.print(
|
584
626
|
f'[yellow]WARNING[/yellow] The solution could still run under double TL, but failed with [item]{other_verdicts_names}[/item].'
|
585
627
|
)
|
628
|
+
|
629
|
+
if has_sanitizer_warnings:
|
630
|
+
console.print(
|
631
|
+
'[warning]WARNING[/warning] The solution had sanitizer errors or warnings, marked with [item]*[/item]. See their stderr for more details.'
|
632
|
+
)
|
633
|
+
|
586
634
|
console.print(f'Time: {get_evals_formatted_time(evals)}')
|
587
635
|
console.print(f'Memory: {get_evals_formatted_memory(evals)}')
|
588
636
|
return len(unmatched_bad_verdicts) == 0
|
589
637
|
|
590
638
|
|
591
639
|
def _consume_and_key_evaluation_items(
|
592
|
-
items:
|
640
|
+
items: Iterable[EvaluationItem],
|
593
641
|
skeleton: SolutionReportSkeleton,
|
594
|
-
) ->
|
642
|
+
) -> StructuredEvaluation:
|
595
643
|
"""
|
596
644
|
Consumes EvaluationItems from a run_solutions call and build a view
|
597
645
|
with them, possibly marking with optional unprocessed items.
|
@@ -602,7 +650,8 @@ def _consume_and_key_evaluation_items(
|
|
602
650
|
for item in items:
|
603
651
|
solution = pkg.solutions[item.solution_index]
|
604
652
|
res[str(solution.path)][item.group_name][item.testcase_index] = item.eval
|
605
|
-
|
653
|
+
|
654
|
+
return res
|
606
655
|
|
607
656
|
|
608
657
|
def _print_solution_header(solution: Solution, console: rich.console.Console):
|
@@ -615,18 +664,17 @@ def _print_solution_header(solution: Solution, console: rich.console.Console):
|
|
615
664
|
console.print(f'({solution_testdir})')
|
616
665
|
|
617
666
|
|
618
|
-
def _print_timing(
|
667
|
+
async def _print_timing(
|
619
668
|
console: rich.console.Console,
|
620
669
|
skeleton: SolutionReportSkeleton,
|
621
|
-
|
670
|
+
evaluations: StructuredEvaluation,
|
622
671
|
):
|
623
672
|
slowest_good = None
|
624
673
|
fastest_slow = None
|
625
674
|
for solution in skeleton.solutions:
|
626
|
-
evals_per_group = structured_evaluation[str(solution.path)]
|
627
675
|
all_evals = []
|
628
|
-
for evals in
|
629
|
-
all_evals.extend([eval for eval in evals if eval is not None])
|
676
|
+
for evals in evaluations[str(solution.path)].values():
|
677
|
+
all_evals.extend([await eval() for eval in evals if eval is not None])
|
630
678
|
solution_time = _get_evals_time_in_ms(all_evals)
|
631
679
|
if solution.outcome.match(Outcome.ACCEPTED):
|
632
680
|
if slowest_good is None or solution_time > slowest_good:
|
@@ -645,16 +693,16 @@ def _print_timing(
|
|
645
693
|
console.print(f'Fastest [error]slow[/error] solution: {fastest_slow} ms')
|
646
694
|
|
647
695
|
|
648
|
-
def _render_detailed_group_table(
|
696
|
+
async def _render_detailed_group_table(
|
649
697
|
group: TestcaseGroup,
|
650
698
|
skeleton: SolutionReportSkeleton,
|
651
|
-
structured_evaluations:
|
699
|
+
structured_evaluations: StructuredEvaluation,
|
652
700
|
console: rich.console.Console,
|
653
701
|
):
|
654
702
|
group_skeleton = skeleton.find_group_skeleton(group.name)
|
655
703
|
assert group_skeleton is not None
|
656
704
|
|
657
|
-
def generate_table(
|
705
|
+
async def generate_table(
|
658
706
|
structured_evaluation: StructuredEvaluation, group_name: str
|
659
707
|
) -> rich.table.Table:
|
660
708
|
table = rich.table.Table()
|
@@ -666,12 +714,19 @@ def _render_detailed_group_table(
|
|
666
714
|
row = []
|
667
715
|
for solution in skeleton.solutions:
|
668
716
|
eval = structured_evaluation[str(solution.path)][group_name][tc]
|
669
|
-
evals_per_solution[str(solution.path)].append(eval)
|
670
717
|
if eval is None:
|
671
718
|
row.append('...')
|
672
719
|
continue
|
720
|
+
eval = eval.peek()
|
721
|
+
if eval is None:
|
722
|
+
row.append('...')
|
723
|
+
continue
|
724
|
+
|
673
725
|
verdict = get_testcase_markup_verdict(eval)
|
674
726
|
time = get_evals_formatted_time([eval])
|
727
|
+
if eval.result.sanitizer_warnings:
|
728
|
+
time = f'{time} [item]*[/item]'
|
729
|
+
evals_per_solution[str(solution.path)].append(eval)
|
675
730
|
row.append(f'{verdict} {time}')
|
676
731
|
table.add_row(*row)
|
677
732
|
|
@@ -689,28 +744,30 @@ def _render_detailed_group_table(
|
|
689
744
|
return table
|
690
745
|
|
691
746
|
with rich.live.Live(
|
692
|
-
generate_table(skeleton.empty_structured_evaluation(), group.name),
|
747
|
+
await generate_table(skeleton.empty_structured_evaluation(), group.name),
|
693
748
|
refresh_per_second=5,
|
694
749
|
console=console,
|
695
750
|
) as live:
|
696
|
-
for
|
697
|
-
for _ in group_skeleton.testcases:
|
698
|
-
|
699
|
-
|
751
|
+
for solution in skeleton.solutions:
|
752
|
+
for tc, _ in enumerate(group_skeleton.testcases):
|
753
|
+
eval = structured_evaluations[str(solution.path)][group.name][tc]
|
754
|
+
if eval is None:
|
755
|
+
continue
|
756
|
+
await eval()
|
757
|
+
live.update(await generate_table(structured_evaluations, group.name))
|
700
758
|
live.refresh()
|
701
759
|
|
702
760
|
|
703
|
-
def _print_detailed_run_report(
|
761
|
+
async def _print_detailed_run_report(
|
704
762
|
result: RunSolutionResult,
|
705
763
|
console: rich.console.Console,
|
706
|
-
structured_evaluations:
|
764
|
+
structured_evaluations: StructuredEvaluation,
|
707
765
|
timing: bool = True,
|
708
766
|
):
|
709
|
-
structured_evaluations = seekable(structured_evaluations)
|
710
767
|
for group in result.skeleton.groups:
|
711
768
|
console.print(f'[bold][status]{group.name}[/status][/bold]')
|
712
769
|
|
713
|
-
_render_detailed_group_table(
|
770
|
+
await _render_detailed_group_table(
|
714
771
|
package.get_testgroup(group.name),
|
715
772
|
result.skeleton,
|
716
773
|
structured_evaluations,
|
@@ -719,12 +776,13 @@ def _print_detailed_run_report(
|
|
719
776
|
continue
|
720
777
|
|
721
778
|
ok = True
|
722
|
-
structured_evaluations.seek(-1)
|
723
|
-
structured_evaluation = next(structured_evaluations)
|
724
779
|
for solution in result.skeleton.solutions:
|
725
780
|
all_evals = []
|
726
|
-
for evals in
|
781
|
+
for evals in structured_evaluations[str(solution.path)].values():
|
727
782
|
all_evals.extend(evals)
|
783
|
+
|
784
|
+
# Resolve futures.
|
785
|
+
all_evals = [await eval() for eval in all_evals if eval is not None]
|
728
786
|
_print_solution_header(solution, console)
|
729
787
|
cur_ok = _print_solution_outcome(
|
730
788
|
solution,
|
@@ -737,93 +795,123 @@ def _print_detailed_run_report(
|
|
737
795
|
console.print()
|
738
796
|
|
739
797
|
if timing:
|
740
|
-
_print_timing(console, result.skeleton,
|
798
|
+
await _print_timing(console, result.skeleton, structured_evaluations)
|
741
799
|
return ok
|
742
800
|
|
743
801
|
|
744
|
-
def print_run_report(
|
802
|
+
async def print_run_report(
|
745
803
|
result: RunSolutionResult,
|
746
804
|
console: rich.console.Console,
|
747
805
|
verification: environment.VerificationParam,
|
748
806
|
detailed: bool = False,
|
749
807
|
timing: bool = True,
|
750
808
|
) -> bool:
|
751
|
-
|
752
|
-
|
753
|
-
|
809
|
+
structured_evaluations = _consume_and_key_evaluation_items(
|
810
|
+
result.items, result.skeleton
|
811
|
+
)
|
754
812
|
if detailed:
|
755
|
-
return _print_detailed_run_report(
|
813
|
+
return await _print_detailed_run_report(
|
756
814
|
result, console, structured_evaluations, timing=timing
|
757
815
|
)
|
758
816
|
|
759
|
-
assert not result.skeleton.group_first
|
760
|
-
# Since we're now streaming the evaluation results, the for-loop is a bit
|
761
|
-
# confusing. We must keep state across the iteration to understand whether
|
762
|
-
# we're seeing a new solution or a new testgroup.
|
763
817
|
ok = True
|
764
|
-
last_solution: Optional[Solution] = None
|
765
|
-
last_group: Optional[str] = None
|
766
|
-
test_index = 0
|
767
|
-
all_evals = []
|
768
|
-
group_evals = []
|
769
|
-
|
770
|
-
def print_last_solution():
|
771
|
-
nonlocal ok
|
772
|
-
if last_solution is None:
|
773
|
-
return
|
774
|
-
cur_ok = _print_solution_outcome(
|
775
|
-
last_solution,
|
776
|
-
all_evals,
|
777
|
-
console,
|
778
|
-
verification=VerificationLevel(verification),
|
779
|
-
)
|
780
|
-
console.print()
|
781
|
-
ok = ok and cur_ok
|
782
818
|
|
783
|
-
for
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
819
|
+
for solution in result.skeleton.solutions:
|
820
|
+
_print_solution_header(solution, console)
|
821
|
+
solution_evals = []
|
822
|
+
for group in result.skeleton.groups:
|
823
|
+
console.print(f'[bold][status]{group.name}[/status][/bold] ', end='')
|
824
|
+
group_evals = []
|
825
|
+
for i, _ in enumerate(group.testcases):
|
826
|
+
eval = structured_evaluations[str(solution.path)][group.name][i]
|
827
|
+
if eval is None:
|
828
|
+
continue
|
829
|
+
eval = await eval()
|
830
|
+
console.print(f'{i}/', end='')
|
831
|
+
console.print(get_testcase_markup_verdict(eval), end='')
|
832
|
+
if eval.result.sanitizer_warnings:
|
833
|
+
console.print('[item]*[/item]', end='')
|
834
|
+
console.print('', end=' ')
|
835
|
+
group_evals.append(eval)
|
836
|
+
solution_evals.append(eval)
|
789
837
|
|
790
|
-
if is_closing_group:
|
791
838
|
console.print(
|
792
839
|
f'({get_evals_formatted_time(group_evals)}, {get_evals_formatted_memory(group_evals)})',
|
793
840
|
end='',
|
794
841
|
)
|
795
842
|
console.print()
|
796
843
|
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
844
|
+
ok = ok and _print_solution_outcome(
|
845
|
+
solution,
|
846
|
+
solution_evals,
|
847
|
+
console,
|
848
|
+
verification=VerificationLevel(verification),
|
849
|
+
)
|
850
|
+
console.print()
|
802
851
|
|
803
|
-
|
804
|
-
group_evals = []
|
805
|
-
last_group = item.group_name
|
806
|
-
test_index = 0
|
807
|
-
console.print(f'[bold][status]{item.group_name}[/status][/bold]', end=' ')
|
852
|
+
await _print_timing(console, result.skeleton, structured_evaluations)
|
808
853
|
|
809
|
-
|
810
|
-
group_evals.append(eval)
|
811
|
-
console.print(f'{test_index}/', end='')
|
812
|
-
console.print(get_testcase_markup_verdict(eval), end=' ')
|
854
|
+
return ok
|
813
855
|
|
814
|
-
test_index += 1
|
815
856
|
|
816
|
-
|
817
|
-
|
818
|
-
|
857
|
+
async def estimate_time_limit(
|
858
|
+
console: rich.console.Console,
|
859
|
+
result: RunSolutionResult,
|
860
|
+
) -> Optional[int]:
|
861
|
+
structured_evaluations = _consume_and_key_evaluation_items(
|
862
|
+
result.items, result.skeleton
|
819
863
|
)
|
820
|
-
console.print()
|
821
|
-
print_last_solution()
|
822
864
|
|
823
|
-
|
824
|
-
|
865
|
+
timing_per_solution = {}
|
866
|
+
timing_per_language = {}
|
825
867
|
|
826
|
-
if
|
827
|
-
|
868
|
+
if not result.skeleton.solutions:
|
869
|
+
console.print('[error]No solutions to estimate time limit from.[/error]')
|
870
|
+
return None
|
828
871
|
|
829
|
-
|
872
|
+
for solution in result.skeleton.solutions:
|
873
|
+
timings = []
|
874
|
+
for evals in structured_evaluations[str(solution.path)].values():
|
875
|
+
for eval in evals:
|
876
|
+
if eval is None:
|
877
|
+
continue
|
878
|
+
eval = await eval()
|
879
|
+
if eval.log.time is not None:
|
880
|
+
timings.append(int(eval.log.time * 1000))
|
881
|
+
|
882
|
+
if not timings:
|
883
|
+
console.print(
|
884
|
+
f'[warning]No timings for solution [item]{solution.path}[/item].[/warning]'
|
885
|
+
)
|
886
|
+
continue
|
887
|
+
|
888
|
+
timing_per_solution[str(solution.path)] = max(timings)
|
889
|
+
lang = find_language_name(solution)
|
890
|
+
if lang not in timing_per_language:
|
891
|
+
timing_per_language[lang] = 0
|
892
|
+
timing_per_language[lang] = max(timing_per_language[lang], max(timings))
|
893
|
+
|
894
|
+
console.rule('[status]Time estimation[/status]', style='status')
|
895
|
+
|
896
|
+
fastest_time = min(timing_per_solution.values())
|
897
|
+
slowest_time = max(timing_per_solution.values())
|
898
|
+
|
899
|
+
console.print(f'Fastest solution: {fastest_time} ms')
|
900
|
+
console.print(f'Slowest solution: {slowest_time} ms')
|
901
|
+
|
902
|
+
if len(timing_per_language) > 0:
|
903
|
+
timing_language_list = [(t, lang) for lang, t in timing_per_language.items()]
|
904
|
+
fastest_language_time, fastest_language = min(timing_language_list)
|
905
|
+
slowest_language_time, slowest_language = max(timing_language_list)
|
906
|
+
|
907
|
+
console.print(
|
908
|
+
f'Fastest language: {fastest_language} ({fastest_language_time} ms)'
|
909
|
+
)
|
910
|
+
console.print(
|
911
|
+
f'Slowest language: {slowest_language} ({slowest_language_time} ms)'
|
912
|
+
)
|
913
|
+
|
914
|
+
estimated_tl = int(max(fastest_time * 3, slowest_time * 1.5))
|
915
|
+
console.print(f'[success]Estimated time limit:[/success] {estimated_tl} ms')
|
916
|
+
|
917
|
+
return estimated_tl
|