rbx.cp 0.5.16__py3-none-any.whl → 0.5.18__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 +128 -18
- rbx/box/compile.py +40 -8
- rbx/box/contest/build_contest_statements.py +7 -6
- rbx/box/contest/contest_package.py +15 -2
- rbx/box/contest/contest_utils.py +0 -9
- rbx/box/contest/main.py +35 -24
- rbx/box/contest/statements.py +4 -5
- rbx/box/deferred.py +26 -0
- rbx/box/extensions.py +0 -8
- rbx/box/generators.py +6 -4
- rbx/box/main.py +195 -19
- rbx/box/package.py +24 -2
- rbx/box/packaging/contest_main.py +5 -5
- 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/judge/sandboxes/timeit.py +3 -3
- rbx/grading/steps.py +143 -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 +8 -1
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.18.dist-info}/METADATA +2 -1
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.18.dist-info}/RECORD +37 -32
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.18.dist-info}/LICENSE +0 -0
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.18.dist-info}/WHEEL +0 -0
- {rbx_cp-0.5.16.dist-info → rbx_cp-0.5.18.dist-info}/entry_points.txt +0 -0
rbx/box/deferred.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import Awaitable, Callable, Generic, Optional, TypeVar
|
2
|
+
|
3
|
+
T = TypeVar('T')
|
4
|
+
U = TypeVar('U')
|
5
|
+
|
6
|
+
|
7
|
+
class Deferred(Generic[T]):
|
8
|
+
def __init__(self, func: Callable[[], Awaitable[T]]):
|
9
|
+
self.func = func
|
10
|
+
self.cache: Optional[T] = None
|
11
|
+
|
12
|
+
def __call__(self) -> Awaitable[T]:
|
13
|
+
async def async_wrapper():
|
14
|
+
if self.cache is None:
|
15
|
+
self.cache = await self.func()
|
16
|
+
return self.cache
|
17
|
+
|
18
|
+
return async_wrapper()
|
19
|
+
|
20
|
+
def peek(self) -> Optional[T]:
|
21
|
+
return self.cache
|
22
|
+
|
23
|
+
def wrap_with(
|
24
|
+
self, wrapper: Callable[[Awaitable[T]], Awaitable[U]]
|
25
|
+
) -> 'Deferred[U]':
|
26
|
+
return Deferred(lambda: wrapper(self()))
|
rbx/box/extensions.py
CHANGED
@@ -5,16 +5,8 @@ from pydantic import BaseModel, Field
|
|
5
5
|
from rbx.box.packaging.boca.extension import BocaExtension, BocaLanguageExtension
|
6
6
|
|
7
7
|
|
8
|
-
# List of extensions defined in-place.
|
9
|
-
class MacExtension(BaseModel):
|
10
|
-
gpp_alternative: Optional[str] = None
|
11
|
-
|
12
|
-
|
13
8
|
# Extension abstractions.
|
14
9
|
class Extensions(BaseModel):
|
15
|
-
mac: Optional[MacExtension] = Field(
|
16
|
-
None, description='Extension for setting mac-only configuration.'
|
17
|
-
)
|
18
10
|
boca: Optional[BocaExtension] = Field(
|
19
11
|
None, description='Environment-level extensions for BOCA packaging.'
|
20
12
|
)
|
rbx/box/generators.py
CHANGED
@@ -8,7 +8,7 @@ import typer
|
|
8
8
|
|
9
9
|
from rbx import console
|
10
10
|
from rbx.box import checkers, package, testcases, validators
|
11
|
-
from rbx.box.code import compile_item, run_item
|
11
|
+
from rbx.box.code import SanitizationLevel, compile_item, run_item
|
12
12
|
from rbx.box.environment import (
|
13
13
|
EnvironmentSandbox,
|
14
14
|
ExecutionConfig,
|
@@ -32,7 +32,7 @@ from rbx.utils import StatusProgress
|
|
32
32
|
|
33
33
|
|
34
34
|
def _compile_generator(generator: CodeItem) -> str:
|
35
|
-
return compile_item(generator)
|
35
|
+
return compile_item(generator, sanitized=SanitizationLevel.PREFER)
|
36
36
|
|
37
37
|
|
38
38
|
def _get_group_input(
|
@@ -322,7 +322,7 @@ def generate_standalone(
|
|
322
322
|
# Get generator item
|
323
323
|
generator = package.get_generator(call.name)
|
324
324
|
if generator_digest is None:
|
325
|
-
generator_digest =
|
325
|
+
generator_digest = _compile_generator(generator)
|
326
326
|
|
327
327
|
generation_log = run_item(
|
328
328
|
generator,
|
@@ -351,7 +351,9 @@ def generate_standalone(
|
|
351
351
|
# Run validator, if it is available.
|
352
352
|
if validator is not None and validate:
|
353
353
|
if validator_digest is None:
|
354
|
-
|
354
|
+
validator_tp = validators.compile_main_validator()
|
355
|
+
assert validator_tp is not None
|
356
|
+
_, validator_digest = validator_tp
|
355
357
|
ok, message, *_ = validators.validate_test(output, validator, validator_digest)
|
356
358
|
if not ok:
|
357
359
|
console.console.print(
|
rbx/box/main.py
CHANGED
@@ -3,10 +3,11 @@ from gevent import monkey
|
|
3
3
|
|
4
4
|
monkey.patch_all()
|
5
5
|
|
6
|
+
|
7
|
+
import asyncio
|
6
8
|
import tempfile
|
7
9
|
import shlex
|
8
10
|
import sys
|
9
|
-
import typing
|
10
11
|
|
11
12
|
from rbx.box.schema import CodeItem, ExpectedOutcome, TestcaseGroup
|
12
13
|
|
@@ -24,6 +25,7 @@ from rbx import annotations, config, console, utils
|
|
24
25
|
from rbx.box import (
|
25
26
|
builder,
|
26
27
|
cd,
|
28
|
+
setter_config,
|
27
29
|
creation,
|
28
30
|
download,
|
29
31
|
environment,
|
@@ -38,6 +40,8 @@ from rbx.box.contest import main as contest
|
|
38
40
|
from rbx.box.environment import VerificationLevel, get_environment_path
|
39
41
|
from rbx.box.packaging import main as packaging
|
40
42
|
from rbx.box.solutions import (
|
43
|
+
estimate_time_limit,
|
44
|
+
get_exact_matching_solutions,
|
41
45
|
get_matching_solutions,
|
42
46
|
print_run_report,
|
43
47
|
run_and_print_interactive_solutions,
|
@@ -46,6 +50,12 @@ from rbx.box.solutions import (
|
|
46
50
|
from rbx.box.statements import build_statements
|
47
51
|
|
48
52
|
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
53
|
+
app.add_typer(
|
54
|
+
setter_config.app,
|
55
|
+
name='config, cfg',
|
56
|
+
cls=annotations.AliasGroup,
|
57
|
+
help='Manage setter configuration.',
|
58
|
+
)
|
49
59
|
app.add_typer(
|
50
60
|
build_statements.app,
|
51
61
|
name='statements, st',
|
@@ -122,6 +132,18 @@ def run(
|
|
122
132
|
'-d',
|
123
133
|
help='Whether to print a detailed view of the tests using tables.',
|
124
134
|
),
|
135
|
+
timeit: bool = typer.Option(
|
136
|
+
False,
|
137
|
+
'--time',
|
138
|
+
'-t',
|
139
|
+
help='Whether to use estimate a time limit based on accepted solutions.',
|
140
|
+
),
|
141
|
+
sanitized: bool = typer.Option(
|
142
|
+
False,
|
143
|
+
'--sanitized',
|
144
|
+
'-s',
|
145
|
+
help='Whether to compile the solutions with sanitizers enabled.',
|
146
|
+
),
|
125
147
|
):
|
126
148
|
main_solution = package.get_main_solution()
|
127
149
|
if check and main_solution is None:
|
@@ -139,7 +161,27 @@ def run(
|
|
139
161
|
)
|
140
162
|
return
|
141
163
|
|
164
|
+
override_tl = None
|
165
|
+
if timeit:
|
166
|
+
if sanitized:
|
167
|
+
console.console.print(
|
168
|
+
'[error]Sanitizers are known to be time-hungry, so they cannot be used for time estimation.\n'
|
169
|
+
'Remove either the [item]-s[/item] flag or the [item]-t[/item] flag to run solutions without sanitizers.[/error]'
|
170
|
+
)
|
171
|
+
raise typer.Exit(1)
|
172
|
+
|
173
|
+
# Never use sanitizers for time estimation.
|
174
|
+
override_tl = _time_impl(check=check, detailed=False)
|
175
|
+
if override_tl is None:
|
176
|
+
raise typer.Exit(1)
|
177
|
+
|
142
178
|
with utils.StatusProgress('Running solutions...') as s:
|
179
|
+
if sanitized:
|
180
|
+
console.console.print(
|
181
|
+
'[warning]Sanitizers are running, so the time limit for the problem will be dropped, '
|
182
|
+
'and the environment default time limit will be used instead.[/warning]'
|
183
|
+
)
|
184
|
+
|
143
185
|
tracked_solutions = None
|
144
186
|
if outcome is not None:
|
145
187
|
tracked_solutions = {
|
@@ -148,23 +190,113 @@ def run(
|
|
148
190
|
}
|
149
191
|
if solution:
|
150
192
|
tracked_solutions = {solution}
|
193
|
+
|
194
|
+
if sanitized and tracked_solutions is None:
|
195
|
+
console.console.print(
|
196
|
+
'[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
|
197
|
+
)
|
198
|
+
tracked_solutions = {
|
199
|
+
str(solution.path)
|
200
|
+
for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
|
201
|
+
}
|
202
|
+
|
151
203
|
solution_result = run_solutions(
|
152
204
|
progress=s,
|
153
205
|
tracked_solutions=tracked_solutions,
|
154
206
|
check=check,
|
155
|
-
group_first=detailed,
|
156
207
|
verification=VerificationLevel(verification),
|
208
|
+
timelimit_override=override_tl,
|
209
|
+
sanitized=sanitized,
|
157
210
|
)
|
158
211
|
|
159
212
|
console.console.print()
|
160
213
|
console.console.rule('[status]Run report[/status]', style='status')
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
214
|
+
asyncio.run(
|
215
|
+
print_run_report(
|
216
|
+
solution_result,
|
217
|
+
console.console,
|
218
|
+
verification,
|
219
|
+
detailed=detailed,
|
220
|
+
)
|
221
|
+
)
|
222
|
+
|
223
|
+
|
224
|
+
def _time_impl(check: bool, detailed: bool) -> Optional[int]:
|
225
|
+
if package.get_main_solution() is None:
|
226
|
+
console.console.print(
|
227
|
+
'[warning]No main solution found, so cannot estimate a time limit.[/warning]'
|
228
|
+
)
|
229
|
+
return None
|
230
|
+
|
231
|
+
verification = VerificationLevel.ALL_SOLUTIONS.value
|
232
|
+
|
233
|
+
with utils.StatusProgress('Running ACCEPTED solutions...') as s:
|
234
|
+
tracked_solutions = {
|
235
|
+
str(solution.path)
|
236
|
+
for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
|
237
|
+
}
|
238
|
+
solution_result = run_solutions(
|
239
|
+
progress=s,
|
240
|
+
tracked_solutions=tracked_solutions,
|
241
|
+
check=check,
|
242
|
+
verification=VerificationLevel(verification),
|
243
|
+
)
|
244
|
+
|
245
|
+
console.console.print()
|
246
|
+
console.console.rule(
|
247
|
+
'[status]Run report (for time estimation)[/status]', style='status'
|
248
|
+
)
|
249
|
+
ok = asyncio.run(
|
250
|
+
print_run_report(
|
251
|
+
solution_result,
|
252
|
+
console.console,
|
253
|
+
verification,
|
254
|
+
detailed=detailed,
|
255
|
+
)
|
166
256
|
)
|
167
257
|
|
258
|
+
if not ok:
|
259
|
+
console.console.print(
|
260
|
+
'[error]Failed to run ACCEPTED solutions, so cannot estimate a reliable time limit.[/error]'
|
261
|
+
)
|
262
|
+
return None
|
263
|
+
|
264
|
+
console.console.print()
|
265
|
+
return asyncio.run(estimate_time_limit(console.console, solution_result))
|
266
|
+
|
267
|
+
|
268
|
+
@app.command(
|
269
|
+
'time, t',
|
270
|
+
help='Estimate a time limit for the problem based on a time limit formula and timings of accepted solutions.',
|
271
|
+
)
|
272
|
+
@package.within_problem
|
273
|
+
def time(
|
274
|
+
check: bool = typer.Option(
|
275
|
+
True,
|
276
|
+
'--nocheck',
|
277
|
+
flag_value=False,
|
278
|
+
help='Whether to not build outputs for tests and run checker.',
|
279
|
+
),
|
280
|
+
detailed: bool = typer.Option(
|
281
|
+
False,
|
282
|
+
'--detailed',
|
283
|
+
'-d',
|
284
|
+
help='Whether to print a detailed view of the tests using tables.',
|
285
|
+
),
|
286
|
+
):
|
287
|
+
main_solution = package.get_main_solution()
|
288
|
+
if check and main_solution is None:
|
289
|
+
console.console.print(
|
290
|
+
'[warning]No main solution found, running without checkers.[/warning]'
|
291
|
+
)
|
292
|
+
check = False
|
293
|
+
|
294
|
+
verification = VerificationLevel.ALL_SOLUTIONS.value
|
295
|
+
if not builder.build(verification=verification, output=check):
|
296
|
+
return None
|
297
|
+
|
298
|
+
_time_impl(check, detailed)
|
299
|
+
|
168
300
|
|
169
301
|
@app.command(
|
170
302
|
'irun, ir', help='Build and run solution(s) by passing testcases in the CLI.'
|
@@ -199,6 +331,12 @@ def irun(
|
|
199
331
|
print: bool = typer.Option(
|
200
332
|
False, '--print', '-p', help='Whether to print outputs to terminal.'
|
201
333
|
),
|
334
|
+
sanitized: bool = typer.Option(
|
335
|
+
False,
|
336
|
+
'--sanitized',
|
337
|
+
'-s',
|
338
|
+
help='Whether to compile the solutions with sanitizers enabled.',
|
339
|
+
),
|
202
340
|
):
|
203
341
|
if not print:
|
204
342
|
console.console.print(
|
@@ -225,14 +363,26 @@ def irun(
|
|
225
363
|
}
|
226
364
|
if solution:
|
227
365
|
tracked_solutions = {solution}
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
366
|
+
if sanitized and tracked_solutions is None:
|
367
|
+
console.console.print(
|
368
|
+
'[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
|
369
|
+
)
|
370
|
+
tracked_solutions = {
|
371
|
+
str(solution.path)
|
372
|
+
for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
|
373
|
+
}
|
374
|
+
|
375
|
+
asyncio.run(
|
376
|
+
run_and_print_interactive_solutions(
|
377
|
+
tracked_solutions=tracked_solutions,
|
378
|
+
check=check,
|
379
|
+
verification=VerificationLevel(verification),
|
380
|
+
generator=generators.get_call_from_string(generator)
|
381
|
+
if generator is not None
|
382
|
+
else None,
|
383
|
+
print=print,
|
384
|
+
sanitized=sanitized,
|
385
|
+
)
|
236
386
|
)
|
237
387
|
|
238
388
|
|
@@ -294,6 +444,12 @@ def stress(
|
|
294
444
|
'--verbose',
|
295
445
|
help='Whether to print verbose output for checkers and finders.',
|
296
446
|
),
|
447
|
+
sanitized: bool = typer.Option(
|
448
|
+
False,
|
449
|
+
'--sanitized',
|
450
|
+
'-s',
|
451
|
+
help='Whether to compile the solutions with sanitizers enabled.',
|
452
|
+
),
|
297
453
|
):
|
298
454
|
if finder and not generator_args or generator_args and not finder:
|
299
455
|
console.console.print(
|
@@ -310,6 +466,7 @@ def stress(
|
|
310
466
|
findingsLimit=findings,
|
311
467
|
progress=s,
|
312
468
|
verbose=verbose,
|
469
|
+
sanitized=sanitized,
|
313
470
|
)
|
314
471
|
|
315
472
|
stresses.print_stress_report(report)
|
@@ -351,10 +508,17 @@ def stress(
|
|
351
508
|
groups_by_name[testgroup] = TestcaseGroup(
|
352
509
|
name=testgroup, generatorScript=CodeItem(path=new_script_path)
|
353
510
|
)
|
354
|
-
|
355
|
-
|
356
|
-
|
511
|
+
ru, problem_yml = package.get_ruyaml()
|
512
|
+
problem_yml['testcases'].append(
|
513
|
+
{
|
514
|
+
'name': testgroup,
|
515
|
+
'generatorScript': new_script_path.name,
|
516
|
+
}
|
357
517
|
)
|
518
|
+
dest = package.find_problem_yaml()
|
519
|
+
assert dest is not None
|
520
|
+
utils.save_ruyaml(dest, ru, problem_yml)
|
521
|
+
package.clear_package_cache()
|
358
522
|
|
359
523
|
if testgroup not in groups_by_name:
|
360
524
|
break
|
@@ -387,8 +551,20 @@ def stress(
|
|
387
551
|
@package.within_problem
|
388
552
|
def compile_command(
|
389
553
|
path: Annotated[str, typer.Argument(help='Path to the asset to compile.')],
|
554
|
+
sanitized: bool = typer.Option(
|
555
|
+
False,
|
556
|
+
'--sanitized',
|
557
|
+
'-s',
|
558
|
+
help='Whether to compile the asset with sanitizers enabled.',
|
559
|
+
),
|
560
|
+
warnings: bool = typer.Option(
|
561
|
+
False,
|
562
|
+
'--warnings',
|
563
|
+
'-w',
|
564
|
+
help='Whether to compile the asset with warnings enabled.',
|
565
|
+
),
|
390
566
|
):
|
391
|
-
compile.any(path)
|
567
|
+
compile.any(path, sanitized, warnings)
|
392
568
|
|
393
569
|
|
394
570
|
@app.command('validate', help='Run the validator in a one-off fashion, interactively.')
|
rbx/box/package.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
import functools
|
2
2
|
import pathlib
|
3
|
+
import sys
|
3
4
|
from typing import Dict, List, Optional, Tuple
|
4
5
|
|
6
|
+
import ruyaml
|
5
7
|
import typer
|
6
8
|
from pydantic import ValidationError
|
7
9
|
|
8
10
|
from rbx import config, console, utils
|
9
|
-
from rbx.box import environment
|
11
|
+
from rbx.box import cd, environment
|
10
12
|
from rbx.box.environment import get_sandbox_type
|
11
13
|
from rbx.box.presets import get_installed_preset_or_null, get_preset_lock
|
12
14
|
from rbx.box.schema import (
|
@@ -102,7 +104,7 @@ def find_problem(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
|
102
104
|
def within_problem(func):
|
103
105
|
@functools.wraps(func)
|
104
106
|
def wrapper(*args, **kwargs):
|
105
|
-
with
|
107
|
+
with cd.new_package_cd(find_problem()):
|
106
108
|
return func(*args, **kwargs)
|
107
109
|
|
108
110
|
return wrapper
|
@@ -119,6 +121,17 @@ def save_package(
|
|
119
121
|
problem_yaml_path.write_text(utils.model_to_yaml(package))
|
120
122
|
|
121
123
|
|
124
|
+
def get_ruyaml() -> Tuple[ruyaml.YAML, ruyaml.Any]:
|
125
|
+
problem_yaml_path = find_problem_yaml()
|
126
|
+
if problem_yaml_path is None:
|
127
|
+
console.console.print(
|
128
|
+
f'Problem not found in {pathlib.Path().absolute()}', style='error'
|
129
|
+
)
|
130
|
+
raise typer.Exit(1)
|
131
|
+
res = ruyaml.YAML()
|
132
|
+
return res, res.load(problem_yaml_path.read_text())
|
133
|
+
|
134
|
+
|
122
135
|
@functools.cache
|
123
136
|
def get_problem_cache_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
124
137
|
cache_dir = find_problem(root) / '.box'
|
@@ -328,3 +341,12 @@ def get_compilation_files(code: CodeItem) -> List[Tuple[pathlib.Path, pathlib.Pa
|
|
328
341
|
)
|
329
342
|
)
|
330
343
|
return res
|
344
|
+
|
345
|
+
|
346
|
+
def clear_package_cache():
|
347
|
+
pkgs = [sys.modules[__name__]]
|
348
|
+
|
349
|
+
for pkg in pkgs:
|
350
|
+
for fn in pkg.__dict__.values():
|
351
|
+
if hasattr(fn, 'cache_clear'):
|
352
|
+
fn.cache_clear()
|
@@ -4,9 +4,9 @@ from typing import Type
|
|
4
4
|
|
5
5
|
import typer
|
6
6
|
|
7
|
-
from rbx import annotations, console
|
8
|
-
from rbx.box import environment, package
|
9
|
-
from rbx.box.contest import build_contest_statements, contest_package
|
7
|
+
from rbx import annotations, console
|
8
|
+
from rbx.box import cd, environment, package
|
9
|
+
from rbx.box.contest import build_contest_statements, contest_package
|
10
10
|
from rbx.box.packaging.main import run_packager
|
11
11
|
from rbx.box.packaging.packager import (
|
12
12
|
BaseContestPackager,
|
@@ -32,8 +32,8 @@ def run_contest_packager(
|
|
32
32
|
console.console.print(
|
33
33
|
f'Processing problem [item]{problem.short_name}[/item]...'
|
34
34
|
)
|
35
|
-
with
|
36
|
-
|
35
|
+
with cd.new_package_cd(problem.get_path()):
|
36
|
+
package.clear_package_cache()
|
37
37
|
package_path = run_packager(packager_cls, verification=verification)
|
38
38
|
built_packages.append(
|
39
39
|
BuiltProblemPackage(
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import functools
|
2
|
+
import pathlib
|
3
|
+
import shutil
|
4
|
+
|
5
|
+
from rbx import console
|
6
|
+
from rbx.box.schema import CodeItem
|
7
|
+
from rbx.grading.judge.storage import Storage
|
8
|
+
from rbx.grading.steps import GradingFileOutput
|
9
|
+
|
10
|
+
|
11
|
+
class WarningStack:
|
12
|
+
def __init__(self, root: pathlib.Path):
|
13
|
+
self.root = root
|
14
|
+
self.warnings = set()
|
15
|
+
self.sanitizer_warnings = {}
|
16
|
+
|
17
|
+
def add_warning(self, code: CodeItem):
|
18
|
+
self.warnings.add(code.path)
|
19
|
+
|
20
|
+
def add_sanitizer_warning(
|
21
|
+
self, storage: Storage, code: CodeItem, reference: GradingFileOutput
|
22
|
+
):
|
23
|
+
if code.path in self.sanitizer_warnings:
|
24
|
+
return
|
25
|
+
dest_path = _get_warning_runs_dir(self.root).joinpath(
|
26
|
+
code.path.with_suffix(code.path.suffix + '.log')
|
27
|
+
)
|
28
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
29
|
+
f = reference.get_file(storage)
|
30
|
+
if f is None:
|
31
|
+
return
|
32
|
+
with dest_path.open('wb') as fout:
|
33
|
+
shutil.copyfileobj(f, fout)
|
34
|
+
f.close()
|
35
|
+
self.sanitizer_warnings[code.path] = dest_path
|
36
|
+
|
37
|
+
def clear(self):
|
38
|
+
self.warnings.clear()
|
39
|
+
self.sanitizer_warnings.clear()
|
40
|
+
|
41
|
+
|
42
|
+
@functools.cache
|
43
|
+
def _get_warning_stack(root: pathlib.Path) -> WarningStack:
|
44
|
+
return WarningStack(root)
|
45
|
+
|
46
|
+
|
47
|
+
@functools.cache
|
48
|
+
def _get_cache_dir(root: pathlib.Path) -> pathlib.Path:
|
49
|
+
dir = root / '.box'
|
50
|
+
dir.mkdir(parents=True, exist_ok=True)
|
51
|
+
return dir
|
52
|
+
|
53
|
+
|
54
|
+
@functools.cache
|
55
|
+
def _get_warning_runs_dir(root: pathlib.Path) -> pathlib.Path:
|
56
|
+
dir = _get_cache_dir(root) / 'warnings'
|
57
|
+
shutil.rmtree(dir, ignore_errors=True)
|
58
|
+
dir.mkdir(parents=True, exist_ok=True)
|
59
|
+
return dir
|
60
|
+
|
61
|
+
|
62
|
+
def get_warning_stack() -> WarningStack:
|
63
|
+
current_root = pathlib.Path.cwd().resolve()
|
64
|
+
return _get_warning_stack(current_root)
|
65
|
+
|
66
|
+
|
67
|
+
def print_warning_stack_report():
|
68
|
+
stack = get_warning_stack()
|
69
|
+
if not stack.warnings and not stack.sanitizer_warnings:
|
70
|
+
return
|
71
|
+
console.console.rule('[status]Warning stack[/status]')
|
72
|
+
console.console.print(
|
73
|
+
f'[warning]There were some warnings within the code that run at [item]{stack.root.absolute()}[/item][/warning]'
|
74
|
+
)
|
75
|
+
if stack.warnings:
|
76
|
+
console.console.print(f'{len(stack.warnings)} compilation warnings')
|
77
|
+
console.console.print(
|
78
|
+
'You can use [item]rbx compile[/item] to reproduce the issues with the files below.'
|
79
|
+
)
|
80
|
+
for path in sorted(stack.warnings):
|
81
|
+
console.console.print(f'- [item]{path}[/item]')
|
82
|
+
console.console.print()
|
83
|
+
|
84
|
+
if stack.sanitizer_warnings:
|
85
|
+
console.console.print(f'{len(stack.sanitizer_warnings)} sanitizer warnings')
|
86
|
+
for path in sorted(stack.sanitizer_warnings):
|
87
|
+
console.console.print(
|
88
|
+
f'- [item]{path}[/item], example log at [item]{stack.sanitizer_warnings[path]}[/item]'
|
89
|
+
)
|
90
|
+
console.console.print()
|
rbx/box/schema.py
CHANGED
@@ -54,6 +54,18 @@ class ExpectedOutcome(AutoEnum):
|
|
54
54
|
ACCEPTED = alias('accepted', 'ac', 'correct') # type: ignore
|
55
55
|
"""Expected outcome for correct solutions (AC)."""
|
56
56
|
|
57
|
+
ACCEPTED_OR_TLE = alias(
|
58
|
+
'accepted or time limit exceeded',
|
59
|
+
'accepted or tle',
|
60
|
+
'ac or tle',
|
61
|
+
'ac/tle',
|
62
|
+
'ac+tle',
|
63
|
+
) # type: ignore
|
64
|
+
"""Expected outcome for solutions that finish with either AC or TLE.
|
65
|
+
|
66
|
+
Especially useful when you do not care about the running time of this solution, and
|
67
|
+
want it to not be considered when calculating the timelimit for the problem."""
|
68
|
+
|
57
69
|
WRONG_ANSWER = alias('wrong answer', 'wa') # type: ignore
|
58
70
|
"""Expected outcome for solutions that finish successfully,
|
59
71
|
but the produced output are incorrect (WA)."""
|
@@ -75,7 +87,7 @@ class ExpectedOutcome(AutoEnum):
|
|
75
87
|
|
76
88
|
TLE_OR_RTE = alias('tle or rte', 'tle/rte', 'tle+rte') # type: ignore
|
77
89
|
"""Expected outcome for solutions that finish with either TLE or RTE.
|
78
|
-
|
90
|
+
|
79
91
|
Especially useful for environments where TLE and RTE are indistinguishable."""
|
80
92
|
|
81
93
|
def style(self) -> str:
|
@@ -99,6 +111,8 @@ class ExpectedOutcome(AutoEnum):
|
|
99
111
|
def match(self, outcome: Outcome) -> bool:
|
100
112
|
if self == ExpectedOutcome.ACCEPTED:
|
101
113
|
return outcome == Outcome.ACCEPTED
|
114
|
+
if self == ExpectedOutcome.ACCEPTED_OR_TLE:
|
115
|
+
return outcome in {Outcome.ACCEPTED, Outcome.TIME_LIMIT_EXCEEDED}
|
102
116
|
if self == ExpectedOutcome.WRONG_ANSWER:
|
103
117
|
return outcome == Outcome.WRONG_ANSWER
|
104
118
|
if self == ExpectedOutcome.INCORRECT:
|