rbx.cp 0.5.11__py3-none-any.whl → 0.5.13__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 CHANGED
@@ -9,14 +9,23 @@ from rbx.box.solutions import (
9
9
  print_run_report,
10
10
  run_solutions,
11
11
  )
12
- from rbx.box.validators import print_validation_report, validate_testcases
12
+ from rbx.box.validators import (
13
+ has_validation_errors,
14
+ print_validation_report,
15
+ validate_testcases,
16
+ )
13
17
 
14
18
 
15
19
  def build(
16
20
  verification: environment.VerificationParam,
17
21
  groups: Optional[Set[str]] = None,
18
- output: bool = True,
19
- ) -> None:
22
+ output: Optional[bool] = True,
23
+ ) -> bool:
24
+ no_main_solution_report = False
25
+ if output is None:
26
+ output = package.get_main_solution() is not None
27
+ no_main_solution_report = not output
28
+
20
29
  with utils.StatusProgress(
21
30
  'Building testcases...',
22
31
  'Built [item]{processed}[/item] testcases...',
@@ -24,6 +33,28 @@ def build(
24
33
  ) as s:
25
34
  generate_testcases(s, groups=groups)
26
35
 
36
+ if verification > 0:
37
+ validator = package.get_validator_or_nil()
38
+ if validator is None:
39
+ console.console.print(
40
+ '[warning]No validator found, skipping validation.[/warning]'
41
+ )
42
+
43
+ if validator is not None:
44
+ with utils.StatusProgress(
45
+ 'Validating testcases...',
46
+ 'Validated [item]{processed}[/item] testcases...',
47
+ keep=True,
48
+ ) as s:
49
+ infos = validate_testcases(s, groups=groups)
50
+ print_validation_report(infos)
51
+
52
+ if has_validation_errors(infos):
53
+ console.console.print(
54
+ '[error]Validation failed, check the report above.[/error]'
55
+ )
56
+ return False
57
+
27
58
  with utils.StatusProgress(
28
59
  'Building outputs for testcases...',
29
60
  'Built [item]{processed}[/item] outputs...',
@@ -32,23 +63,22 @@ def build(
32
63
  if output:
33
64
  generate_outputs_for_testcases(s, groups=groups)
34
65
 
35
- if verification > 0:
36
- with utils.StatusProgress(
37
- 'Validating testcases...',
38
- 'Validated [item]{processed}[/item] testcases...',
39
- keep=True,
40
- ) as s:
41
- infos = validate_testcases(s, groups=groups)
42
- print_validation_report(infos)
43
-
44
66
  console.console.print(
45
67
  '[success]Problem built.[/success] '
46
68
  '[warning]Check the output for verification errors![/warning]'
47
69
  )
48
70
 
71
+ if no_main_solution_report:
72
+ console.console.print(
73
+ '[warning]No main solution found, skipping generating samples for the statement.[/warning]'
74
+ )
75
+
76
+ return True
77
+
49
78
 
50
79
  def verify(verification: environment.VerificationParam) -> bool:
51
- build(verification=verification)
80
+ if not build(verification=verification):
81
+ return False
52
82
 
53
83
  if verification < VerificationLevel.FAST_SOLUTIONS.value:
54
84
  return True
rbx/box/cd.py CHANGED
@@ -3,9 +3,8 @@ import pathlib
3
3
  from typing import Optional
4
4
 
5
5
  import typer
6
- from rich import console
7
6
 
8
- from rbx import utils
7
+ from rbx import console, utils
9
8
 
10
9
 
11
10
  def find_package(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
rbx/box/checkers.py CHANGED
@@ -119,6 +119,31 @@ def check(
119
119
  )
120
120
  message = package.get_digest_as_string(error.value or '') or ''
121
121
 
122
+ if (
123
+ checker_run_log is not None
124
+ and checker_run_log.exitcode != 0
125
+ and (
126
+ checker_run_log.exitstatus != SandboxBase.EXIT_NONZERO_RETURN
127
+ or checker_run_log.exitcode not in [0, 1, 2, 3]
128
+ )
129
+ ):
130
+ console.console.print(
131
+ f'[error]Checker [item]{package.get_checker().path}[/item] failed unexpectedly.[/error]'
132
+ )
133
+ console.console.print(
134
+ f'[error]Summary:[/error] {checker_run_log.get_summary()}'
135
+ )
136
+ console.console.print(
137
+ f'[error]Testcase input:[/error] [item]{testcase.inputPath}[/item]'
138
+ )
139
+ console.console.print(
140
+ f'[error]Testcase output:[/error] [item]{testcase.outputPath}[/item]'
141
+ )
142
+ console.console.print(
143
+ f'[error]Program output:[/error] [item]{program_output}[/item]'
144
+ )
145
+ raise typer.Exit(1)
146
+
122
147
  if checker_run_log is None or checker_run_log.exitcode not in [0, 1, 2, 3]:
123
148
  return CheckerResult(outcome=Outcome.INTERNAL_ERROR)
124
149
 
rbx/box/compile.py CHANGED
@@ -22,35 +22,29 @@ def _compile(item: CodeItem):
22
22
  out_path.chmod(0o755)
23
23
 
24
24
  console.console.print(
25
- f'[success]Compiled file written at [item]{out_path}[/item].[/success]'
25
+ f'[success]Compiled file written at [item]{out_path}[/item][/success]'
26
26
  )
27
27
 
28
28
 
29
- @app.command('any, a', help='Compile an asset given its path.')
30
- @package.within_problem
31
29
  def any(path: str):
32
- _compile(CodeItem(path=pathlib.Path(path)))
33
-
34
-
35
- @app.command('solution, s', help='Compile a solution given its path.')
36
- @package.within_problem
37
- def solution(path: str):
38
- _compile(package.get_solution(path))
30
+ pkg = package.find_problem_package_or_die()
39
31
 
32
+ solution = package.get_solution_or_nil(path)
33
+ if solution is not None:
34
+ _compile(solution)
35
+ return
40
36
 
41
- @app.command('generator, gen, g', help='Compile a generator given its name.')
42
- @package.within_problem
43
- def generator(name: str):
44
- _compile(package.get_generator(name))
37
+ for generator in pkg.generators:
38
+ if generator.path == pathlib.Path(path) or generator.name == path:
39
+ _compile(generator)
40
+ return
45
41
 
42
+ if pkg.checker is not None and pkg.checker.path == pathlib.Path(path):
43
+ _compile(pkg.checker)
44
+ return
46
45
 
47
- @app.command('checker, c', help='Compile the checker.')
48
- @package.within_problem
49
- def checker():
50
- _compile(package.get_checker())
46
+ if pkg.validator is not None and pkg.validator.path == pathlib.Path(path):
47
+ _compile(pkg.validator)
48
+ return
51
49
 
52
-
53
- @app.command('validator, v', help='Compile the main validator.')
54
- @package.within_problem
55
- def validator():
56
- _compile(package.get_validator())
50
+ _compile(CodeItem(path=pathlib.Path(path)))
@@ -3,6 +3,7 @@ import pathlib
3
3
  from typing import List, Optional
4
4
 
5
5
  import typer
6
+ from pydantic import ValidationError
6
7
 
7
8
  from rbx import console, utils
8
9
  from rbx.box.contest.schema import Contest
@@ -30,7 +31,12 @@ def find_contest_package(root: pathlib.Path = pathlib.Path()) -> Optional[Contes
30
31
  contest_yaml_path = find_contest_yaml(root)
31
32
  if not contest_yaml_path:
32
33
  return None
33
- return utils.model_from_yaml(Contest, contest_yaml_path.read_text())
34
+ try:
35
+ return utils.model_from_yaml(Contest, contest_yaml_path.read_text())
36
+ except ValidationError as e:
37
+ console.console.print(e)
38
+ console.console.print('[error]Error parsing contest.rbx.yml.[/error]')
39
+ raise typer.Exit(1) from e
34
40
 
35
41
 
36
42
  def find_contest_package_or_die(root: pathlib.Path = pathlib.Path()) -> Contest:
rbx/box/contest/main.py CHANGED
@@ -160,6 +160,32 @@ def add(path: str, short_name: str, preset: Optional[str] = None):
160
160
  )
161
161
 
162
162
 
163
+ @app.command('remove, r', help='Remove problem from contest.')
164
+ @within_contest
165
+ def remove(path_or_short_name: str):
166
+ contest = find_contest_package_or_die()
167
+
168
+ kept_problems = []
169
+ removed_problems = []
170
+ for problem in contest.problems:
171
+ if (
172
+ problem.path == pathlib.Path(path_or_short_name)
173
+ or problem.short_name == path_or_short_name
174
+ ):
175
+ removed_problems.append(problem)
176
+ else:
177
+ kept_problems.append(problem)
178
+
179
+ contest.problems = kept_problems
180
+ save_contest(contest)
181
+
182
+ for problem in removed_problems:
183
+ shutil.rmtree(str(problem.path), ignore_errors=True)
184
+ console.console.print(
185
+ f'Problem [item]{problem.short_name}[/item] removed from contest at [item]{problem.path}[/item].'
186
+ )
187
+
188
+
163
189
  @app.command(
164
190
  'each',
165
191
  help='Run a command for each problem in the contest.',
@@ -51,7 +51,14 @@ def build(
51
51
  )
52
52
  with utils.new_cd(problem.get_path()):
53
53
  contest_utils.clear_package_cache()
54
- builder.build(verification=verification, groups=set(['samples']))
54
+
55
+ if not builder.build(
56
+ verification=verification, groups=set(['samples']), output=None
57
+ ):
58
+ console.console.print(
59
+ '[error]Failed to build statements with samples, aborting.[/error]'
60
+ )
61
+ raise typer.Exit(1)
55
62
 
56
63
  contest = find_contest_package_or_die()
57
64
  candidate_languages = languages
rbx/box/environment.py CHANGED
@@ -4,7 +4,7 @@ from enum import Enum
4
4
  from typing import Annotated, List, Optional, Type, TypeVar
5
5
 
6
6
  import typer
7
- from pydantic import BaseModel, ConfigDict
7
+ from pydantic import BaseModel, ConfigDict, ValidationError
8
8
 
9
9
  from rbx import config, console, utils
10
10
  from rbx.box.extensions import Extensions, LanguageExtensions
@@ -19,9 +19,8 @@ class VerificationLevel(Enum):
19
19
  NONE = 0
20
20
  VALIDATE = 1
21
21
  FAST_SOLUTIONS = 2
22
- ASAN = 3
23
- ALL_SOLUTIONS = 4
24
- FULL = 5
22
+ ALL_SOLUTIONS = 3
23
+ FULL = 4
25
24
 
26
25
 
27
26
  VerificationParam = Annotated[
@@ -31,7 +30,7 @@ VerificationParam = Annotated[
31
30
  '--verification',
32
31
  '-v',
33
32
  help='Verification level to use when building package.',
34
- default_factory=lambda: VerificationLevel.ALL_SOLUTIONS.value,
33
+ default_factory=lambda: VerificationLevel.FULL.value,
35
34
  ),
36
35
  ]
37
36
 
@@ -193,7 +192,14 @@ def get_environment(env: Optional[str] = None) -> Environment:
193
192
  f'Environment file [item]{env_path}[/item] not found.', style='error'
194
193
  )
195
194
  raise typer.Exit()
196
- return utils.model_from_yaml(Environment, env_path.read_text())
195
+ try:
196
+ return utils.model_from_yaml(Environment, env_path.read_text())
197
+ except ValidationError as e:
198
+ console.console.print(e)
199
+ console.console.print(
200
+ f'[error]Error parsing environment file [item]{env_path}[/item].[/error]'
201
+ )
202
+ raise typer.Exit(1) from e
197
203
 
198
204
 
199
205
  @functools.cache
rbx/box/generators.py CHANGED
@@ -82,6 +82,8 @@ def _run_generator(
82
82
  console.console.print(
83
83
  f'[error]Failed generating test {i} from group path {group_path}[/error]',
84
84
  )
85
+ if run_log is not None:
86
+ console.console.print(f'[error]Summary:[/error] {run_log.get_summary()}')
85
87
  if generation_stderr.value is not None:
86
88
  console.console.print('[error]Stderr:[/error]')
87
89
  console.console.print(
@@ -142,9 +144,7 @@ def generate_output_for_testcase(
142
144
  f'[error]Failed generating output for [item]{testcase.inputPath}[/item][/error]',
143
145
  )
144
146
  if run_log is not None:
145
- console.console.print(
146
- f'[error]Main solution exited with code [item]{-run_log.exitcode}[/item][/error]',
147
- )
147
+ console.console.print(f'[error]Summary:[/error] {run_log.get_summary()}')
148
148
  checker_result = checkers.check_with_no_output(run_log)
149
149
  console.console.print(
150
150
  f'[warning]Time: [item]{run_log.time:.2f}s[/item][/warning]',
@@ -238,7 +238,7 @@ def _run_generator_script(testcase: TestcaseSubgroup, cacher: FileCacher) -> str
238
238
  )
239
239
  if run_log is not None:
240
240
  console.console.print(
241
- f'[error]Script exited with code [item]{-run_log.exitcode}[/item][/error]',
241
+ f'[error]Summary:[/error] {run_log.get_summary()}'
242
242
  )
243
243
  if run_stderr.value is not None:
244
244
  console.console.print('[error]Stderr:[/error]')
@@ -341,6 +341,10 @@ def generate_standalone(
341
341
  console.console.print(
342
342
  f'[error]Failed generating test using generator call [info]{call.name} {expanded_args_str}[/info].[/error]',
343
343
  )
344
+ if generation_log is not None:
345
+ console.console.print(
346
+ f'[error]Summary:[/error] {generation_log.get_summary()}'
347
+ )
344
348
  if generation_stderr.value is not None:
345
349
  console.console.print('[error]Stderr:[/error]')
346
350
  console.console.print(
rbx/box/main.py CHANGED
@@ -3,11 +3,12 @@ from gevent import monkey
3
3
 
4
4
  monkey.patch_all()
5
5
 
6
+ import tempfile
6
7
  import shlex
7
8
  import sys
8
9
  import typing
9
10
 
10
- from rbx.box.schema import CodeItem, ExpectedOutcome
11
+ from rbx.box.schema import CodeItem, ExpectedOutcome, TestcaseGroup
11
12
 
12
13
 
13
14
  import pathlib
@@ -31,6 +32,7 @@ from rbx.box import (
31
32
  compile,
32
33
  presets,
33
34
  stresses,
35
+ validators,
34
36
  )
35
37
  from rbx.box.contest import main as contest
36
38
  from rbx.box.environment import VerificationLevel, get_environment_path
@@ -69,9 +71,6 @@ app.add_typer(
69
71
  app.add_typer(
70
72
  contest.app, name='contest', cls=annotations.AliasGroup, help='Contest management.'
71
73
  )
72
- app.add_typer(
73
- compile.app, name='compile', cls=annotations.AliasGroup, help='Compile assets.'
74
- )
75
74
 
76
75
 
77
76
  @app.command('ui', hidden=True)
@@ -96,13 +95,6 @@ def build(verification: environment.VerificationParam):
96
95
  builder.build(verification=verification)
97
96
 
98
97
 
99
- @app.command('verify, v', help='Build and verify all the tests for the problem.')
100
- @package.within_problem
101
- def verify(verification: environment.VerificationParam):
102
- if not builder.verify(verification=verification):
103
- console.console.print('[error]Verification failed, check the report.[/error]')
104
-
105
-
106
98
  @app.command('run, r', help='Build and run solution(s).')
107
99
  @package.within_problem
108
100
  def run(
@@ -139,7 +131,14 @@ def run(
139
131
  )
140
132
  check = False
141
133
 
142
- builder.build(verification=verification, output=check)
134
+ if not builder.build(verification=verification, output=check):
135
+ return
136
+
137
+ if verification <= VerificationLevel.VALIDATE.value:
138
+ console.console.print(
139
+ '[warning]Verification level is set to [item]validate (-v1)[/item], so rbx only build tests and validated them.[/warning]'
140
+ )
141
+ return
143
142
 
144
143
  with utils.StatusProgress('Running solutions...') as s:
145
144
  tracked_solutions = None
@@ -206,6 +205,12 @@ def irun(
206
205
  console.console.print(
207
206
  '[warning]Outputs will be written to files. If you wish to print them to the terminal, use the "-p" parameter.'
208
207
  )
208
+ if verification < VerificationLevel.ALL_SOLUTIONS.value:
209
+ console.console.print(
210
+ '[warning]Verification level should be at least [item]all solutions (-v4)[/item] to run solutions interactively.'
211
+ )
212
+ return
213
+
209
214
  main_solution = package.get_main_solution()
210
215
  if check and main_solution is None:
211
216
  console.console.print(
@@ -248,7 +253,13 @@ def create(
248
253
  @app.command('stress', help='Run a stress test.')
249
254
  @package.within_problem
250
255
  def stress(
251
- name: str,
256
+ name: Annotated[
257
+ str,
258
+ typer.Argument(
259
+ help='Name of the stress test to run (specified in problem.rbx.yml), '
260
+ 'or the generator to run, in case -g is specified.'
261
+ ),
262
+ ],
252
263
  generator_args: Annotated[
253
264
  Optional[str],
254
265
  typer.Option(
@@ -325,9 +336,27 @@ def stress(
325
336
 
326
337
  testgroup = questionary.select(
327
338
  'Choose the testgroup to add the tests to.\nOnly test groups that have a .txt generatorScript are shown below: ',
328
- choices=list(groups_by_name) + ['(skip)'],
339
+ choices=list(groups_by_name) + ['(create new script)', '(skip)'],
329
340
  ).ask()
330
341
 
342
+ if testgroup == '(create new script)':
343
+ new_script_name = questionary.text(
344
+ 'Enter the name of the new .txt generatorScript file: '
345
+ ).ask()
346
+ new_script_path = pathlib.Path(new_script_name).with_suffix('.txt')
347
+ new_script_path.parent.mkdir(parents=True, exist_ok=True)
348
+ new_script_path.touch()
349
+
350
+ # Temporarily create a new testgroup with the new script.
351
+ testgroup = new_script_path.stem
352
+ groups_by_name[testgroup] = TestcaseGroup(
353
+ name=testgroup, generatorScript=CodeItem(path=new_script_path)
354
+ )
355
+ console.console.print(
356
+ f'[warning]A testgroup for [item]{new_script_path}[/item] will not be automatically added to the problem.rbx.yml file for you.\n'
357
+ 'Please add it manually. [/warning]'
358
+ )
359
+
331
360
  if testgroup not in groups_by_name:
332
361
  break
333
362
  try:
@@ -355,6 +384,47 @@ def stress(
355
384
  break
356
385
 
357
386
 
387
+ @app.command('compile', help='Compile an asset given its path.')
388
+ @package.within_problem
389
+ def compile_command(
390
+ path: Annotated[str, typer.Argument(help='Path to the asset to compile.')],
391
+ ):
392
+ compile.any(path)
393
+
394
+
395
+ @app.command('validate', help='Run the validator in a one-off fashion, interactively.')
396
+ @package.within_problem
397
+ def validate(
398
+ path: Annotated[
399
+ Optional[str],
400
+ typer.Option('--path', '-p', help='Path to the testcase to validate.'),
401
+ ] = None,
402
+ ):
403
+ validator_tuple = validators.compile_main_validator()
404
+ if validator_tuple is None:
405
+ console.console.print('[error]No validator found for this problem.[/error]')
406
+ raise typer.Exit(1)
407
+
408
+ validator, validator_digest = validator_tuple
409
+
410
+ input = console.multiline_prompt('Testcase input')
411
+
412
+ if path is None:
413
+ with tempfile.TemporaryDirectory() as tmpdir:
414
+ tmppath = pathlib.Path(tmpdir) / '000.in'
415
+ tmppath.write_text(input)
416
+
417
+ info = validators.validate_one_off(
418
+ pathlib.Path(tmppath), validator, validator_digest
419
+ )
420
+ else:
421
+ info = validators.validate_one_off(
422
+ pathlib.Path(path), validator, validator_digest
423
+ )
424
+
425
+ validators.print_validation_report([info])
426
+
427
+
358
428
  @app.command('environment, env', help='Set or show the current box environment.')
359
429
  def environment_command(
360
430
  env: Annotated[Optional[str], typer.Argument()] = None,
@@ -417,10 +487,17 @@ def activate():
417
487
  console.console.print(
418
488
  '[error]Preset is not installed. Install it manually, or specify a URI in [item].preset-lock.yml[/item].[/error]'
419
489
  )
420
- raise
490
+ raise typer.Exit(1)
421
491
  presets.install(preset_lock.uri)
422
492
 
423
493
  preset = presets.get_installed_preset(preset_lock.preset_name)
494
+
495
+ # Install the environment from the preset if it's not already installed.
496
+ presets.optionally_install_environment_from_preset(
497
+ preset, root=presets.get_preset_installation_path(preset_lock.name)
498
+ )
499
+
500
+ # Activate the environment.
424
501
  if preset.env is not None:
425
502
  environment_command(preset.name)
426
503
 
rbx/box/package.py CHANGED
@@ -3,6 +3,7 @@ import pathlib
3
3
  from typing import Dict, List, Optional, Tuple
4
4
 
5
5
  import typer
6
+ from pydantic import ValidationError
6
7
 
7
8
  from rbx import config, console, utils
8
9
  from rbx.box import environment
@@ -74,7 +75,12 @@ def find_problem_package(root: pathlib.Path = pathlib.Path()) -> Optional[Packag
74
75
  problem_yaml_path = find_problem_yaml(root)
75
76
  if not problem_yaml_path:
76
77
  return None
77
- return utils.model_from_yaml(Package, problem_yaml_path.read_text())
78
+ try:
79
+ return utils.model_from_yaml(Package, problem_yaml_path.read_text())
80
+ except ValidationError as e:
81
+ console.console.print(e)
82
+ console.console.print('[error]Error parsing problem.rbx.yml.[/error]')
83
+ raise typer.Exit(1) from e
78
84
 
79
85
 
80
86
  def find_problem_package_or_die(root: pathlib.Path = pathlib.Path()) -> Package:
@@ -294,6 +294,18 @@ def get_installed_preset(name: str, root: pathlib.Path = pathlib.Path()) -> Pres
294
294
  return preset
295
295
 
296
296
 
297
+ def optionally_install_environment_from_preset(
298
+ preset: Preset, root: pathlib.Path = pathlib.Path()
299
+ ):
300
+ if preset.env is None:
301
+ return
302
+ env_path = get_environment_path(preset.name)
303
+ if env_path.is_file():
304
+ return
305
+ env_path.parent.mkdir(parents=True, exist_ok=True)
306
+ shutil.copyfile(str(root / preset.env), env_path)
307
+
308
+
297
309
  def _install(root: pathlib.Path = pathlib.Path(), force: bool = False):
298
310
  preset = get_preset_yaml(root)
299
311
 
rbx/box/solutions.py CHANGED
@@ -378,6 +378,7 @@ def _run_interactive_solutions(
378
378
  shutil.rmtree(str(irun_dir), ignore_errors=True)
379
379
  irun_dir.mkdir(parents=True, exist_ok=True)
380
380
  inputs_dir = irun_dir / 'inputs'
381
+ inputs_dir.mkdir(parents=True, exist_ok=True)
381
382
  input_path = inputs_dir / '000.in'
382
383
  output_path = input_path.with_suffix('.out')
383
384
 
@@ -479,10 +480,14 @@ def get_testcase_markup_verdict(eval: Evaluation) -> str:
479
480
 
480
481
 
481
482
  def _get_evals_time_in_ms(evals: List[Evaluation]) -> int:
483
+ if not evals:
484
+ return 0
482
485
  return max(int((eval.log.time or 0.0) * 1000) for eval in evals)
483
486
 
484
487
 
485
488
  def _get_evals_memory_in_mb(evals: List[Evaluation]) -> int:
489
+ if not evals:
490
+ return 0
486
491
  return max(int(eval.log.memory or 0) // (1024 * 1024) for eval in evals)
487
492
 
488
493
 
@@ -504,6 +509,7 @@ def _print_solution_outcome(
504
509
  ) -> bool:
505
510
  pkg = package.find_problem_package_or_die()
506
511
 
512
+ has_plain_tle = False
507
513
  bad_verdicts = set()
508
514
  no_tle_bad_verdicts = set()
509
515
  for eval in evals:
@@ -514,6 +520,10 @@ def _print_solution_outcome(
514
520
  and eval.result.no_tle_outcome != Outcome.ACCEPTED
515
521
  ):
516
522
  no_tle_bad_verdicts.add(eval.result.no_tle_outcome)
523
+ has_plain_tle = has_plain_tle or (
524
+ eval.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
525
+ and eval.result.no_tle_outcome is None
526
+ )
517
527
 
518
528
  unmatched_bad_verdicts = set(
519
529
  v for v in bad_verdicts if not solution.outcome.match(v)
@@ -542,16 +552,27 @@ def _print_solution_outcome(
542
552
  verification.value >= VerificationLevel.FULL.value
543
553
  # Solution expects a TLE.
544
554
  and expected_outcome_is_tle
545
- # A TLE (or similar) has happened.
546
- and matched_bad_verdicts
547
- # The solution has no other bad verdicts except for TLEs in double TL.
548
- and not ((bad_verdicts | no_tle_bad_verdicts) - {Outcome.TIME_LIMIT_EXCEEDED})
549
- # The solution passes in double TL.
555
+ # Solution does not have a plain TLE.
556
+ and not has_plain_tle
557
+ # A TLE has happened.
558
+ and Outcome.TIME_LIMIT_EXCEEDED in matched_bad_verdicts
559
+ # The solution runs in double TL.
550
560
  and evals_time < pkg.timelimit_for_language(solution.language) * 2
551
561
  ):
552
- console.print(
553
- '[yellow]WARNING[/yellow] The solution still passed in double TL.'
554
- )
562
+ other_verdicts = (bad_verdicts | no_tle_bad_verdicts) - {
563
+ Outcome.TIME_LIMIT_EXCEEDED
564
+ }
565
+ if not other_verdicts:
566
+ # The solution has no other bad verdicts except for TLEs in double TL.
567
+ console.print(
568
+ '[yellow]WARNING[/yellow] The solution still passed in double TL.'
569
+ )
570
+ elif not (bad_verdicts - {Outcome.TIME_LIMIT_EXCEEDED}):
571
+ # The solution has other bad soft TLE outcomes.
572
+ other_verdicts_names = ' '.join(v.name for v in other_verdicts)
573
+ console.print(
574
+ f'[yellow]WARNING[/yellow] The solution could still run under double TL, but failed with [item]{other_verdicts_names}[/item].'
575
+ )
555
576
  console.print(f'Time: {get_evals_formatted_time(evals)}')
556
577
  console.print(f'Memory: {get_evals_formatted_memory(evals)}')
557
578
  return len(unmatched_bad_verdicts) == 0
@@ -787,6 +808,9 @@ def print_run_report(
787
808
  print_last_solution()
788
809
 
789
810
  items.seek(0)
790
- _print_timing(console, result.skeleton, list(structured_evaluations)[-1])
811
+ structured_evaluations_list = list(structured_evaluations)
812
+
813
+ if structured_evaluations_list:
814
+ _print_timing(console, result.skeleton, structured_evaluations_list[-1])
791
815
 
792
816
  return ok
@@ -330,7 +330,15 @@ def build(
330
330
  ):
331
331
  # At most run the validators, only in samples.
332
332
  if samples:
333
- builder.build(verification=verification, groups=set(['samples']))
333
+ if not builder.build(
334
+ verification=verification,
335
+ groups=set(['samples']),
336
+ output=None,
337
+ ):
338
+ console.console.print(
339
+ '[error]Failed to build statements with samples, aborting.[/error]'
340
+ )
341
+ raise typer.Exit(1)
334
342
 
335
343
  pkg = package.find_problem_package_or_die()
336
344
  candidate_languages = languages
rbx/box/stresses.py CHANGED
@@ -104,6 +104,8 @@ def run_stress(
104
104
  if time.monotonic() - startTime > timeoutInSeconds:
105
105
  break
106
106
 
107
+ executed += 1
108
+
107
109
  if progress:
108
110
  seconds = timeoutInSeconds - int(time.monotonic() - startTime)
109
111
  progress.update(
@@ -259,7 +261,6 @@ def run_stress(
259
261
  )
260
262
 
261
263
  # Be cooperative.
262
- executed += 1
263
264
  time.sleep(0.001)
264
265
 
265
266
  return StressReport(findings=findings, executed=executed)
rbx/box/validators.py CHANGED
@@ -2,6 +2,7 @@ import pathlib
2
2
  import shlex
3
3
  from typing import Dict, List, Optional, Set, Tuple
4
4
 
5
+ import typer
5
6
  from pydantic import BaseModel
6
7
 
7
8
  from rbx import console
@@ -9,6 +10,7 @@ from rbx.box import package
9
10
  from rbx.box.code import compile_item, run_item
10
11
  from rbx.box.schema import CodeItem, Primitive
11
12
  from rbx.box.testcases import find_built_testcase_inputs
13
+ from rbx.grading.judge.sandbox import SandboxBase
12
14
  from rbx.grading.steps import (
13
15
  DigestHolder,
14
16
  DigestOrDest,
@@ -52,6 +54,9 @@ def _process_bounds(log: str) -> HitBounds:
52
54
  k, v = items
53
55
  v = v.strip()
54
56
 
57
+ if 'constant-bounds' in k:
58
+ continue
59
+
55
60
  hit = ('min-value-hit' in v, 'max-value-hit' in v)
56
61
  if k not in bounds:
57
62
  bounds[k] = hit
@@ -108,6 +113,18 @@ def _validate_testcase(
108
113
  ],
109
114
  extra_args=shlex.join(var_args) if var_args else None,
110
115
  )
116
+
117
+ if (
118
+ run_log is not None
119
+ and run_log.exitcode != 0
120
+ and run_log.exitstatus != SandboxBase.EXIT_NONZERO_RETURN
121
+ ):
122
+ console.console.print(
123
+ f'[error]Validator [item]{validator.path}[/item] failed unexpectedly.[/error]'
124
+ )
125
+ console.console.print(f'[error]Summary:[/error] {run_log.get_summary()}')
126
+ raise typer.Exit(1)
127
+
111
128
  log_overview = ''
112
129
  if log_digest.value is not None:
113
130
  log_overview = package.get_digest_as_string(log_digest.value or '')
@@ -138,6 +155,22 @@ def compile_main_validator() -> Optional[Tuple[CodeItem, str]]:
138
155
  return pkg.validator, _compile_validator(pkg.validator)
139
156
 
140
157
 
158
+ def validate_one_off(
159
+ testcase: pathlib.Path,
160
+ validator: CodeItem,
161
+ validator_digest: str,
162
+ ) -> TestcaseValidationInfo:
163
+ ok, message, _ = validate_test(testcase, validator, validator_digest)
164
+ info = TestcaseValidationInfo(
165
+ group='interactive',
166
+ path=testcase,
167
+ ok=ok,
168
+ hit_bounds={},
169
+ message=message,
170
+ )
171
+ return info
172
+
173
+
141
174
  def compile_validators(
142
175
  progress: Optional[StatusProgress] = None,
143
176
  ) -> Dict[str, str]:
@@ -202,6 +235,10 @@ def validate_testcases(
202
235
  return validation_info
203
236
 
204
237
 
238
+ def has_validation_errors(infos: List[TestcaseValidationInfo]) -> bool:
239
+ return any(not info.ok for info in infos)
240
+
241
+
205
242
  def print_validation_report(infos: List[TestcaseValidationInfo]):
206
243
  console.console.rule('Validation report', style='status')
207
244
  hit_bounds_per_group: Dict[str, HitBounds] = {}
@@ -218,6 +255,10 @@ def print_validation_report(infos: List[TestcaseValidationInfo]):
218
255
  [hit_bounds_per_group[info.group], info.hit_bounds]
219
256
  )
220
257
 
258
+ if not hit_bounds_per_group:
259
+ console.console.print()
260
+ return
261
+
221
262
  if not _has_group_specific_validator():
222
263
  hit_bounds_per_group = {None: _merge_hit_bounds(hit_bounds_per_group.values())}
223
264
 
rbx/grading/steps.py CHANGED
@@ -1,5 +1,8 @@
1
+ import functools
2
+ import os
1
3
  import pathlib
2
4
  import shlex
5
+ import shutil
3
6
  import subprocess
4
7
  from enum import Enum
5
8
  from typing import Any, Dict, List, Optional, Tuple, Union
@@ -151,6 +154,13 @@ class RunLog(BaseModel):
151
154
  return None
152
155
  return self.metadata.language
153
156
 
157
+ def get_summary(self) -> str:
158
+ if self.exitcode == 0:
159
+ return 'OK'
160
+ time = self.time or 0.0
161
+ memory = self.memory or 0
162
+ return f'FAILED with exit code {self.exitcode} and sandbox status {self.exitstatus} (time: {time}s, memory: {memory//(1024*1024)}MB)'
163
+
154
164
 
155
165
  class TestcaseLog(RunLog):
156
166
  stdout_absolute_path: Optional[pathlib.Path] = None
@@ -250,10 +260,26 @@ def _split_and_expand(command: str, sandbox: SandboxBase) -> List[str]:
250
260
  return res
251
261
 
252
262
 
263
+ def _is_c_command(exe_command: str) -> bool:
264
+ return exe_command.endswith('gcc') or exe_command.endswith('clang')
265
+
266
+
253
267
  def _is_cpp_command(exe_command: str) -> bool:
254
268
  return exe_command.endswith('g++') or exe_command.endswith('clang++')
255
269
 
256
270
 
271
+ @functools.cache
272
+ def _complain_about_clang() -> None:
273
+ console.print(
274
+ '[warning]Notice your C++ files are being compiled with [item]clang[/item] instead of [item]g++[/item].[/warning]'
275
+ )
276
+ console.print('[warning]This may lead to unexpected behavior.[/warning]')
277
+ console.print('[warning]Consider using [item]g++[/item] instead.[/warning]')
278
+ console.print(
279
+ '[warning]See [item]https://rsalesc.github.io/rbx/cpp-on-macos[/item] for instructions on how to use [item]g++[/item] on MacOS.'
280
+ )
281
+
282
+
257
283
  def _maybe_get_bits_stdcpp_for_clang(command: str) -> Optional[GradingFileInput]:
258
284
  cmds = shlex.split(command)
259
285
  if not cmds:
@@ -274,6 +300,7 @@ def _maybe_get_bits_stdcpp_for_clang(command: str) -> Optional[GradingFileInput]
274
300
  if 'clang' not in lines[0]:
275
301
  return None
276
302
 
303
+ _complain_about_clang()
277
304
  bits = get_bits_stdcpp()
278
305
  return GradingFileInput(src=bits, dest=pathlib.Path('bits/stdc++.h'))
279
306
 
@@ -288,12 +315,45 @@ def _maybe_get_bits_stdcpp_for_commands(
288
315
  return None
289
316
 
290
317
 
318
+ @functools.cache
319
+ def _try_following_alias_for_exe(exe: str) -> Optional[str]:
320
+ if _is_c_command(exe) and os.environ.get('RBX_C_PATH'):
321
+ return os.environ['RBX_C_PATH']
322
+ if _is_cpp_command(exe) and os.environ.get('RBX_CXX_PATH'):
323
+ return os.environ['RBX_CXX_PATH']
324
+ output = subprocess.run(
325
+ f'which {exe}', shell=True, executable=shutil.which('bash'), capture_output=True
326
+ )
327
+ if output.returncode != 0:
328
+ return None
329
+ return output.stdout.decode().strip()
330
+
331
+
332
+ def _try_following_alias_for_command(command: str) -> str:
333
+ cmds = shlex.split(command)
334
+ if not cmds:
335
+ return command
336
+ exe = cmds[0]
337
+ new_exe = _try_following_alias_for_exe(exe)
338
+ if new_exe is None:
339
+ return command
340
+ return shlex.join([new_exe, *cmds[1:]])
341
+
342
+
343
+ def _try_following_alias_for_commands(commands: List[str]) -> List[str]:
344
+ res: List[str] = []
345
+ for command in commands:
346
+ res.append(_try_following_alias_for_command(command))
347
+ return res
348
+
349
+
291
350
  def compile(
292
351
  commands: List[str],
293
352
  params: SandboxParams,
294
353
  sandbox: SandboxBase,
295
354
  artifacts: GradingArtifacts,
296
355
  ) -> bool:
356
+ commands = _try_following_alias_for_commands(commands)
297
357
  bits_artifact = _maybe_get_bits_stdcpp_for_commands(commands)
298
358
  if bits_artifact is not None:
299
359
  _process_input_artifacts(GradingArtifacts(inputs=[bits_artifact]), sandbox)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rbx.cp
3
- Version: 0.5.11
3
+ Version: 0.5.13
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -2,27 +2,27 @@ rbx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  rbx/annotations.py,sha256=Z3jBUyZoXkrz34jko3Rft0bnMME6nWb0vsV5I3HlgR0,3064
3
3
  rbx/autoenum.py,sha256=cusv8ClXRlDVvhZ8eDrtYcL_2peXlHugAey_ht8roXk,12025
4
4
  rbx/box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- rbx/box/builder.py,sha256=rClPi3p4dYDHAjy8nE6YZtXwB19In1xvMBxvZQY0up4,2330
6
- rbx/box/cd.py,sha256=2MJjcO-4sxF7HoAI9Suxny7veR-27fA4pP1DxQNQkBI,972
7
- rbx/box/checkers.py,sha256=3luGMotNvDzqwbnYaxlAWzsUL7iW24gGG0wy28re0bo,4484
5
+ rbx/box/builder.py,sha256=QVKp7VL7YC8xs4oevcsam8DT_tjkSqu76nUoad1EwVY,3229
6
+ rbx/box/cd.py,sha256=NF0gqQso7a1IZzzZ6YJU8ip744AYAQ8Hp7vuzBRmZak,956
7
+ rbx/box/checkers.py,sha256=u5Pk-xViWxb_-KqAdEvnydbfppkYg0lBYoRQl5m_U_4,5380
8
8
  rbx/box/code.py,sha256=0UfxzemrObXQq-cvAtSAImCm4sKV_4Xog-TMUZBRFwc,5761
9
- rbx/box/compile.py,sha256=2xc8c6yDcEof-z0XmUdQHN4PrR9fXDGWeHKuXxDUKj8,1436
9
+ rbx/box/compile.py,sha256=DHf9MNhfigXufe3Q5LghuTNsAYcpCoFho-6nG8F0t38,1315
10
10
  rbx/box/conftest.py,sha256=sEmciXSeDC-wmrZ1JSxbsUenKNP_VWW32mrCun2pY3I,1070
11
11
  rbx/box/contest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  rbx/box/contest/build_contest_statements.py,sha256=qar6h5u1xAlOKHl3P7-A1d0aYtaqu4PspGw_DYUGOLY,11260
13
- rbx/box/contest/contest_package.py,sha256=-eHk6CfJ083jZptWCjR0F4x_r9qsmKeSOUvK0QnDyA4,2393
13
+ rbx/box/contest/contest_package.py,sha256=GhZZSOC95wbypyeKFDgUIaaTjhldASudQBvjvurHbDw,2623
14
14
  rbx/box/contest/contest_utils.py,sha256=vWv4iNWdJT5motAznfdNzl8o-tEoCU4xmdyaPTPJZuY,490
15
- rbx/box/contest/main.py,sha256=BKq_n60BstfSHakvNDUP9lu5LNptx38EN8fYPduS8pU,6171
15
+ rbx/box/contest/main.py,sha256=FeTe_Qz9t233dTe1QQ6DxMDq5mcXHx59x9vgaLM8vTY,6976
16
16
  rbx/box/contest/schema.py,sha256=rxzjJasMWPKKhvSJs4eW4A2oCiA4gXgfF-MzqsbPslQ,4914
17
- rbx/box/contest/statements.py,sha256=iG_E4bqhc7hof4LlAliw4m9VO8HcDzvnhsWmxzVRQM4,2690
17
+ rbx/box/contest/statements.py,sha256=cRK5ndsywotUm66QqvtReqVAc-fW4c-2OZufK3Xd8_c,2947
18
18
  rbx/box/creation.py,sha256=mVHVVj8ozX9D5qpkLewE5WSjF6HtTm74Pkwubk-bATg,2259
19
19
  rbx/box/download.py,sha256=MFP-R26JiYGAP89I0TK-0fYc69Fsd20tsBqgtRCy5AE,2234
20
- rbx/box/environment.py,sha256=i-IGHDyFDaYF1fyr0QHdGZ3z4NWp5sj6nxsbnxhAYq4,10924
20
+ rbx/box/environment.py,sha256=ocUKoVAxa-vAun0lv_BLJdYj4uqLh3mknHTxIssTDP0,11160
21
21
  rbx/box/extensions.py,sha256=p0iLaU28KswOBDX2HGVO_dR2gk-JSAWb6sXC6GZ1d0w,738
22
- rbx/box/generators.py,sha256=27Q8sVWTG4d8UT6Si-YJ83zZ3jEnu1Urmk-audRYoFk,16111
22
+ rbx/box/generators.py,sha256=XiPg3uJuhBeJNN363_aSlvMwmrH5XNYSiTAEjL1GDYI,16304
23
23
  rbx/box/generators_test.py,sha256=mQqHepAMYa6zV_PseQALI0nIX6AdQktt6lh94muFhNw,1758
24
- rbx/box/main.py,sha256=DA0B_OmWmeWj_xpNEKs5syQEmg2_zbOrTGZVVK3eW0o,13639
25
- rbx/box/package.py,sha256=UR3BxVbTZ6eYJfdsTTx4l8FfdZ1wsnaHAmAdMr07lww,10302
24
+ rbx/box/main.py,sha256=2CcJ1xRk-VV7snd3zu1w58-3LP7Dlhn7ZJslmowWDA4,16441
25
+ rbx/box/package.py,sha256=Hds0WIvhAWXnYOmIXGULcLLVeFM1YkllOPSKHB5QSkk,10532
26
26
  rbx/box/packaging/boca/extension.py,sha256=hQhcbocNfW2ESv5RalS1wf6uvOoOfOnR_gHvbXUbSzY,852
27
27
  rbx/box/packaging/boca/packager.py,sha256=FOhSRg5K5Y4qNB0WyTR3DKgrpObf9I0JbyGpJHOtxpo,10673
28
28
  rbx/box/packaging/contest_main.py,sha256=ypiBS8dd0yCqoFJIqiK1Fo02dQmUB_G-Z7G926jomrk,2746
@@ -31,21 +31,21 @@ rbx/box/packaging/packager.py,sha256=suCT_SLnWa915rV2j8VFqzH43HGKRTr9mGGlrvj45aw
31
31
  rbx/box/packaging/polygon/packager.py,sha256=CcjHzDr4MwSyp270gsPY6RWoz8bUJeykDqXPvQ3XZ1U,10773
32
32
  rbx/box/packaging/polygon/test.py,sha256=bgEju5PwudgyfwxXJagm8fM6CJVlWM6l_-2q1V-oKaQ,3069
33
33
  rbx/box/packaging/polygon/xml_schema.py,sha256=-r24bCeRMGLrGGoT9FIgmqr87xHL-JzrFaR6bztbYtw,2703
34
- rbx/box/presets/__init__.py,sha256=6IHzqczfiuCdvuXWInimYd9sVMYUQR12BlFaxdr39hI,16721
34
+ rbx/box/presets/__init__.py,sha256=ic-lLHXHeHKE7yov_pq_ec8pcfhMuik8HGHxeqE3Cvk,17072
35
35
  rbx/box/presets/fetch.py,sha256=F-BCOlvEBEyDqtOhiDuGPn4EDtA4Bwm-fqHJ7zZGlW8,1975
36
36
  rbx/box/presets/lock_schema.py,sha256=6sRPnyePOC8yy-5WcD5JRZdDJHf8loqbvpQ1IPiOU9s,349
37
37
  rbx/box/presets/schema.py,sha256=mZmSPkQsw7eQM0lQN6er1MO_LiW1ObwwAZFDK0F5fxE,1962
38
38
  rbx/box/schema.py,sha256=RBQmFxr35LfaRwbxR6WezeQQ3twqh9VS3tn20rlRm_I,12580
39
- rbx/box/solutions.py,sha256=snhPubO6TN3DkYD_51GAUbOZJ8iB863B43-mIQELNmY,26389
39
+ rbx/box/solutions.py,sha256=3WRhLcD_xknQb6KSjNVj7L-QHtsAmqZzHQ-LaU9oobI,27358
40
40
  rbx/box/solutions_test.py,sha256=DetQj7ZVnV3tBXBpCrFeK_Yv3XUtdEf29y_6qnyeyfY,1496
41
41
  rbx/box/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
- rbx/box/statements/build_statements.py,sha256=YInK5Upc8rT-DCVCXe_3ZBosHn0Zw19m3Pv4wUmqKL4,11678
42
+ rbx/box/statements/build_statements.py,sha256=24ZUM5H33NhmqnDL0ofA1anUKWxuZG1paA1tCV4AKns,11911
43
43
  rbx/box/statements/builders.py,sha256=W3VkmtjfzrT5MkIVXgfR9fM-OWK007ihm5hfzvp9cfc,10474
44
44
  rbx/box/statements/joiners.py,sha256=ZbxomnMjEFT8yf5WSWUB4tBa3DL3AhjGEuh8uqHyDdg,2837
45
45
  rbx/box/statements/latex.py,sha256=LkcHwXjMFxbw--Gj9T1VkFKQFsXhY9dN7xZHpZycNW8,1346
46
46
  rbx/box/statements/latex_jinja.py,sha256=7WBfn1h8DpqCAmSE6Av64HfURMnJ2AO4QX1CD72sz5E,7096
47
47
  rbx/box/statements/schema.py,sha256=g3KgBn4nIqx-0utH8R2FCqPmJP969chhYfn96chQgd4,3851
48
- rbx/box/stresses.py,sha256=mbKa70QyPOmgXfhGcArTKsdXjH0PFDVmydY3W8bgZlk,10596
48
+ rbx/box/stresses.py,sha256=vENwbFO9QPR5MAarfW5D8g3CNr8AC_DewIP9EmT3he0,10597
49
49
  rbx/box/stressing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  rbx/box/stressing/finder_parser.py,sha256=Hw9m2viRGnVqVgO39D51s2qAEjZK1LKWe94rkqCx7VA,11919
51
51
  rbx/box/stressing/generator_parser.py,sha256=oHZryjR3YohgaSO9WEirQ7b2e-98WgZStF0N99W4Thw,7380
@@ -55,7 +55,7 @@ rbx/box/ui/captured_log.py,sha256=ptICDPViVnz-_2NfrcB0SSBXNW5L74zI-vAZNN7kSok,11
55
55
  rbx/box/ui/css/app.tcss,sha256=apd5PkPEvl5jK3kE2qrxPyVED1VnvSsj08QQwzUPwEA,786
56
56
  rbx/box/ui/main.py,sha256=b0rHcBF42W4AOCv7WhtiGf_rUnY0yxpqO5oj3wfR4R4,984
57
57
  rbx/box/ui/run.py,sha256=wMEXrEFdQvMHz2hRKAFIithTnTtaL0kNQZu0jKmb8jI,7060
58
- rbx/box/validators.py,sha256=KFpBFtyTfIwjQjW3_qvyBoGQqJ06ELo5kuQGNz4Pi0s,7222
58
+ rbx/box/validators.py,sha256=RvDukBK_lA-321cDNIXS2dy9cv9iz-z_f4xcFMFE7a0,8310
59
59
  rbx/box/validators_test.py,sha256=hriR6rD32Ouu64eKYYTPLZVvqMxXj7Q2h1l_JAefL7U,344
60
60
  rbx/checker.py,sha256=pj1jO3my48ru-qugbER5onccANCjoR0-PaFe3H3VGEY,4118
61
61
  rbx/clone.py,sha256=Fyjnr3Bp0xmUIZH6DkAGcGc2n3P5PDdoVszIYJbUmwM,6745
@@ -78,7 +78,7 @@ rbx/grading/judge/sandboxes/timeit.py,sha256=rOQnxf90hhTrWOQ1Mt57HNhZAAn1jDULtMj
78
78
  rbx/grading/judge/storage.py,sha256=FirqjwDqb0m0h2OTFyWrZL7CQ4XjZNxhqB4JpnDIhZY,9485
79
79
  rbx/grading/judge/test.py,sha256=ll0Iw7zyOpGdKPD_PGH7dvUkb4stQLu-ikbQnqJvuAc,944
80
80
  rbx/grading/judge/testiso.py,sha256=v14DtkWiZFJ9AKMzrb0_vZKPWDt8jz8iIw1Z2O-Advk,1397
81
- rbx/grading/steps.py,sha256=VyJnbWl7S0rpkMupN2Os5zjfvEvI2XlcaSt8cNRjgUQ,16398
81
+ rbx/grading/steps.py,sha256=s5oII5MPTSQgoYVOQsLeLoKkpCzbkzyDTgCkj5TAfHE,18466
82
82
  rbx/grading/steps_with_caching.py,sha256=C_IA_dStxp6poJyGggFFyEouU9y_739UOLKUiJITb8M,1489
83
83
  rbx/grading/steps_with_caching_run_test.py,sha256=nRzB4OcXkb-kQ4WCj0iTGVfBACllxZ0Ek5RSwfoJRgo,15262
84
84
  rbx/grading_utils.py,sha256=lL2KtSkOsMElqrRoApQTbFcqVOeHVWUDTMCa3IsLpC4,4484
@@ -157,8 +157,8 @@ rbx/testdata/caching/executable.py,sha256=WKRHNf_fprFJd1Fq1ubmQtR3mZzTYVNwKPLWuZ
157
157
  rbx/testdata/compatible,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
158
158
  rbx/testing_utils.py,sha256=ZZLKMUHlZ4HwsuNY50jqSBJ9HhpnFdba7opjDsvXE1U,2084
159
159
  rbx/utils.py,sha256=wy8zQRb97n3lptilK7UxM4A44KjXb1W5Z1tD61a-K4A,4173
160
- rbx_cp-0.5.11.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
161
- rbx_cp-0.5.11.dist-info/METADATA,sha256=nkpPrvqVmR4TjtcE-E_dV4GGbaewa0SBSF_170-8V6M,3254
162
- rbx_cp-0.5.11.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
163
- rbx_cp-0.5.11.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
164
- rbx_cp-0.5.11.dist-info/RECORD,,
160
+ rbx_cp-0.5.13.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
161
+ rbx_cp-0.5.13.dist-info/METADATA,sha256=cgpJkqgDicWnI2ApTi9-DFK-ulkKvg2ePYynguZL9wg,3254
162
+ rbx_cp-0.5.13.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
163
+ rbx_cp-0.5.13.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
164
+ rbx_cp-0.5.13.dist-info/RECORD,,