rbx.cp 0.7.0__py3-none-any.whl → 0.9.0__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.
Files changed (71) hide show
  1. rbx/box/cd.py +2 -2
  2. rbx/box/cli.py +87 -33
  3. rbx/box/code.py +133 -84
  4. rbx/box/contest/build_contest_statements.py +2 -2
  5. rbx/box/contest/contest_package.py +1 -1
  6. rbx/box/contest/main.py +29 -2
  7. rbx/box/environment.py +140 -80
  8. rbx/box/formatting.py +2 -1
  9. rbx/box/global_package.py +74 -0
  10. rbx/box/package.py +11 -24
  11. rbx/box/packaging/__init__.py +0 -0
  12. rbx/box/packaging/boca/__init__.py +0 -0
  13. rbx/box/packaging/polygon/packager.py +3 -3
  14. rbx/box/presets/__init__.py +369 -53
  15. rbx/box/presets/lock_schema.py +42 -2
  16. rbx/box/presets/schema.py +4 -0
  17. rbx/box/remote.py +21 -2
  18. rbx/box/retries.py +3 -2
  19. rbx/box/sanitizers/warning_stack.py +5 -5
  20. rbx/box/solutions.py +37 -25
  21. rbx/box/statements/build_statements.py +6 -6
  22. rbx/box/statements/builders.py +1 -1
  23. rbx/box/stats.py +10 -0
  24. rbx/box/stresses.py +47 -66
  25. rbx/box/stressing/finder_parser.py +11 -16
  26. rbx/box/tasks.py +33 -22
  27. rbx/box/testcase_utils.py +3 -3
  28. rbx/box/tooling/boca/scraper.py +1 -1
  29. rbx/grading/caching.py +98 -47
  30. rbx/grading/debug_context.py +31 -0
  31. rbx/grading/grading_context.py +96 -0
  32. rbx/grading/judge/cacher.py +93 -21
  33. rbx/grading/judge/sandbox.py +8 -4
  34. rbx/grading/judge/sandboxes/isolate.py +3 -2
  35. rbx/grading/judge/sandboxes/stupid_sandbox.py +3 -2
  36. rbx/grading/judge/sandboxes/timeit.py +1 -1
  37. rbx/grading/judge/storage.py +170 -35
  38. rbx/grading/profiling.py +126 -0
  39. rbx/grading/steps.py +46 -17
  40. rbx/grading/steps_with_caching.py +52 -26
  41. rbx/resources/envs/default.rbx.yml +2 -3
  42. rbx/resources/envs/isolate.rbx.yml +2 -3
  43. rbx/resources/presets/default/contest/.gitignore +6 -0
  44. rbx/resources/presets/default/contest/contest.rbx.yml +14 -1
  45. rbx/resources/presets/default/contest/statement/contest.rbx.tex +24 -86
  46. rbx/resources/presets/default/contest/statement/instructions.tex +40 -0
  47. rbx/resources/presets/default/contest/statement/logo.png +0 -0
  48. rbx/resources/presets/default/env.rbx.yml +67 -0
  49. rbx/resources/presets/default/preset.rbx.yml +6 -2
  50. rbx/resources/presets/default/problem/.gitignore +1 -1
  51. rbx/resources/presets/default/problem/problem.rbx.yml +12 -8
  52. rbx/resources/presets/default/shared/contest_template.rbx.tex +57 -0
  53. rbx/resources/presets/default/shared/icpc.sty +322 -0
  54. rbx/resources/presets/default/shared/problem_template.rbx.tex +57 -0
  55. rbx/submitors/codeforces.py +3 -2
  56. rbx/test.py +1 -1
  57. rbx/utils.py +6 -1
  58. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/METADATA +4 -1
  59. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/RECORD +67 -58
  60. rbx/resources/presets/default/contest/statement/olymp.sty +0 -250
  61. rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -42
  62. rbx/resources/presets/default/problem/statement/olymp.sty +0 -250
  63. rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -89
  64. /rbx/resources/presets/default/problem/{gen.cpp → gens/gen.cpp} +0 -0
  65. /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/000.in +0 -0
  66. /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/001.in +0 -0
  67. /rbx/resources/presets/default/problem/{random.py → testplan/random.py} +0 -0
  68. /rbx/resources/presets/default/problem/{random.txt → testplan/random.txt} +0 -0
  69. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/LICENSE +0 -0
  70. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/WHEEL +0 -0
  71. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/entry_points.txt +0 -0
rbx/box/remote.py CHANGED
@@ -5,7 +5,7 @@ from typing import List, Optional, Tuple, Union
5
5
 
6
6
  import typer
7
7
 
8
- from rbx import console
8
+ from rbx import console, utils
9
9
  from rbx.box import cd, package
10
10
  from rbx.box.formatting import href, ref
11
11
 
@@ -27,6 +27,16 @@ class Expander(ABC):
27
27
  pass
28
28
 
29
29
 
30
+ class MainExpander(Expander):
31
+ def expand(self, path: pathlib.Path) -> Optional[pathlib.Path]:
32
+ if str(path) != '@main':
33
+ return None
34
+ sol = package.get_main_solution()
35
+ if sol is None:
36
+ return None
37
+ return sol.path
38
+
39
+
30
40
  class BocaExpander(Expander):
31
41
  BOCA_REGEX = re.compile(r'\@boca\/(\d+)(?:\-(\d+))?')
32
42
 
@@ -69,12 +79,13 @@ class BocaExpander(Expander):
69
79
 
70
80
 
71
81
  REGISTERED_EXPANDERS: List['Expander'] = [
82
+ MainExpander(),
72
83
  BocaExpander(),
73
84
  ]
74
85
 
75
86
 
76
87
  def _relative_to_pkg(path: pathlib.Path) -> pathlib.Path:
77
- return path.resolve().relative_to(pathlib.Path.cwd())
88
+ return utils.abspath(path).relative_to(pathlib.Path.cwd())
78
89
 
79
90
 
80
91
  def _try_cacheable_paths(
@@ -132,6 +143,9 @@ def _expand_paths(paths: List[pathlib.Path]) -> List[pathlib.Path]:
132
143
  continue
133
144
  expanded = _expand_path(path)
134
145
  if expanded is None:
146
+ console.console.print(
147
+ f'[warning]Remote solution [item]{path}[/item] could not be expanded. Skipping.[/warning]'
148
+ )
135
149
  continue
136
150
  res.append(expanded)
137
151
  return res
@@ -149,3 +163,8 @@ def expand_file(file: str) -> pathlib.Path:
149
163
  )
150
164
  raise typer.Exit(1)
151
165
  return res[0]
166
+
167
+
168
+ def is_path_remote(path: pathlib.Path) -> bool:
169
+ remote_dir = package.get_problem_remote_dir()
170
+ return utils.abspath(path).is_relative_to(utils.abspath(remote_dir))
rbx/box/retries.py CHANGED
@@ -5,6 +5,7 @@ import tempfile
5
5
  from contextlib import contextmanager
6
6
  from typing import Awaitable, Callable, List, Optional
7
7
 
8
+ from rbx import utils
8
9
  from rbx.box import package
9
10
  from rbx.box.setter_config import RepeatsConfig, get_setter_config
10
11
  from rbx.grading.steps import Evaluation, Outcome
@@ -59,8 +60,8 @@ class FileToRecover:
59
60
 
60
61
  def _move_to_temp_dir(path: pathlib.Path, temp_dir: pathlib.Path) -> FileToRecover:
61
62
  problem_path = package.find_problem()
62
- path = path.resolve()
63
- temp_dir = temp_dir.resolve()
63
+ path = utils.abspath(path)
64
+ temp_dir = utils.abspath(temp_dir)
64
65
  relative = path.relative_to(problem_path)
65
66
 
66
67
  temp_path = temp_dir / relative
@@ -2,9 +2,9 @@ import functools
2
2
  import pathlib
3
3
  import shutil
4
4
 
5
- from rbx import console
5
+ from rbx import console, utils
6
6
  from rbx.box.schema import CodeItem
7
- from rbx.grading.judge.storage import Storage
7
+ from rbx.grading.judge.cacher import FileCacher
8
8
  from rbx.grading.steps import GradingFileOutput
9
9
 
10
10
 
@@ -18,7 +18,7 @@ class WarningStack:
18
18
  self.warnings.add(code.path)
19
19
 
20
20
  def add_sanitizer_warning(
21
- self, storage: Storage, code: CodeItem, reference: GradingFileOutput
21
+ self, cacher: FileCacher, code: CodeItem, reference: GradingFileOutput
22
22
  ):
23
23
  if code.path in self.sanitizer_warnings:
24
24
  return
@@ -26,7 +26,7 @@ class WarningStack:
26
26
  code.path.with_suffix(code.path.suffix + '.log')
27
27
  )
28
28
  dest_path.parent.mkdir(parents=True, exist_ok=True)
29
- f = reference.get_file(storage)
29
+ f = reference.get_file(cacher)
30
30
  if f is None:
31
31
  return
32
32
  with dest_path.open('wb') as fout:
@@ -60,7 +60,7 @@ def _get_warning_runs_dir(root: pathlib.Path) -> pathlib.Path:
60
60
 
61
61
 
62
62
  def get_warning_stack() -> WarningStack:
63
- current_root = pathlib.Path.cwd().resolve()
63
+ current_root = utils.abspath(pathlib.Path.cwd())
64
64
  return _get_warning_stack(current_root)
65
65
 
66
66
 
rbx/box/solutions.py CHANGED
@@ -13,6 +13,7 @@ import rich.markup
13
13
  import rich.table
14
14
  import rich.text
15
15
  import typer
16
+ from ordered_set import OrderedSet
16
17
  from pydantic import BaseModel
17
18
 
18
19
  from rbx import console, utils
@@ -53,6 +54,7 @@ from rbx.box.testcase_utils import (
53
54
  parse_interaction,
54
55
  print_interaction,
55
56
  )
57
+ from rbx.grading import grading_context
56
58
  from rbx.grading.limits import Limits
57
59
  from rbx.grading.steps import (
58
60
  Evaluation,
@@ -245,7 +247,7 @@ async def convert_list_of_solution_evaluations_to_dict(
245
247
 
246
248
 
247
249
  def _get_solutions_for_skeleton(
248
- tracked_solutions: Optional[Set[str]] = None,
250
+ tracked_solutions: Optional[Iterable[str]] = None,
249
251
  verification: VerificationLevel = VerificationLevel.NONE,
250
252
  ) -> List[Solution]:
251
253
  pkg = package.find_problem_package_or_die()
@@ -260,7 +262,7 @@ def _get_solutions_for_skeleton(
260
262
 
261
263
 
262
264
  def _get_report_skeleton(
263
- tracked_solutions: Optional[Set[str]] = None,
265
+ tracked_solutions: Optional[Iterable[str]] = None,
264
266
  verification: VerificationLevel = VerificationLevel.NONE,
265
267
  timelimit_override: Optional[int] = None,
266
268
  ) -> SolutionReportSkeleton:
@@ -393,7 +395,7 @@ def print_best_output(output_files: List[pathlib.Path], empty_warning: bool = Fa
393
395
 
394
396
  def run_solutions(
395
397
  progress: Optional[StatusProgress] = None,
396
- tracked_solutions: Optional[Set[str]] = None,
398
+ tracked_solutions: Optional[Iterable[str]] = None,
397
399
  verification: VerificationLevel = VerificationLevel.NONE,
398
400
  check: bool = True,
399
401
  timelimit_override: Optional[int] = None,
@@ -497,7 +499,7 @@ async def _generate_testcase_interactively(
497
499
  console.console.print(testcase.inputPath.read_text())
498
500
  else:
499
501
  console.console.print(
500
- f'Input was written to [item]{testcase.inputPath.resolve()}[/item]'
502
+ f'Input was written to [item]{utils.abspath(testcase.inputPath)}[/item]'
501
503
  )
502
504
  console.console.print()
503
505
 
@@ -605,7 +607,7 @@ def _run_interactive_solutions(
605
607
 
606
608
 
607
609
  def _get_interactive_skeleton(
608
- tracked_solutions: Optional[Set[str]] = None,
610
+ tracked_solutions: Optional[Iterable[str]] = None,
609
611
  verification: VerificationLevel = VerificationLevel.NONE,
610
612
  ) -> SolutionReportSkeleton:
611
613
  solutions = _get_solutions_for_skeleton(tracked_solutions, verification)
@@ -645,7 +647,7 @@ def _get_interactive_skeleton(
645
647
 
646
648
  async def run_and_print_interactive_solutions(
647
649
  progress: Optional[StatusProgress] = None,
648
- tracked_solutions: Optional[Set[str]] = None,
650
+ tracked_solutions: Optional[Iterable[str]] = None,
649
651
  verification: VerificationLevel = VerificationLevel.NONE,
650
652
  generator: Optional[GeneratorCall] = None,
651
653
  testcase_entry: Optional[TestcaseEntry] = None,
@@ -659,23 +661,28 @@ async def run_and_print_interactive_solutions(
659
661
  tracked_solutions,
660
662
  verification=verification,
661
663
  )
662
- testcase = await _generate_testcase_interactively(
663
- progress=progress,
664
- generator=generator,
665
- testcase_entry=testcase_entry,
666
- check=check,
667
- custom_output=custom_output,
668
- sanitized=sanitized,
669
- print=print,
670
- )
671
- items = _run_interactive_solutions(
672
- testcase,
673
- skeleton=skeleton,
674
- progress=progress,
675
- verification=verification,
676
- check=check,
677
- sanitized=sanitized,
678
- )
664
+
665
+ should_cache = testcase_entry is not None
666
+ with grading_context.cache_level(
667
+ grading_context.CacheLevel.CACHE_COMPILATION, when=not should_cache
668
+ ):
669
+ testcase = await _generate_testcase_interactively(
670
+ progress=progress,
671
+ generator=generator,
672
+ testcase_entry=testcase_entry,
673
+ check=check,
674
+ custom_output=custom_output,
675
+ sanitized=sanitized,
676
+ print=print,
677
+ )
678
+ items = _run_interactive_solutions(
679
+ testcase,
680
+ skeleton=skeleton,
681
+ progress=progress,
682
+ verification=verification,
683
+ check=check,
684
+ sanitized=sanitized,
685
+ )
679
686
 
680
687
  for item in items:
681
688
  sol = skeleton.find_solution_skeleton(item.solution)
@@ -739,7 +746,12 @@ def expand_solutions_with_source(sols: List[str]) -> List[Tuple[Solution, bool]]
739
746
  path_sols = remote.expand_files(sols)
740
747
 
741
748
  # Ensure sols exist.
742
- path_sols = [sol for sol in path_sols if sol.is_file()]
749
+ for sol in path_sols:
750
+ if not sol.is_file():
751
+ console.console.print(
752
+ f'[error]Solution [item]{sol}[/item] could not be found.[/error]'
753
+ )
754
+ raise typer.Exit(1)
743
755
 
744
756
  seen_sols = set()
745
757
  res: List[Tuple[Solution, bool]] = []
@@ -762,7 +774,7 @@ def expand_solutions(sols: List[str]) -> List[Solution]:
762
774
 
763
775
 
764
776
  async def pick_solutions(
765
- tracked_solutions: Optional[Set[str]],
777
+ tracked_solutions: Optional[OrderedSet[str]],
766
778
  extra_solutions: Optional[List[str]] = None,
767
779
  ) -> List[str]:
768
780
  pkg = package.find_problem_package_or_die()
@@ -6,7 +6,7 @@ from typing import Annotated, Any, Dict, List, Optional, Tuple
6
6
  import syncer
7
7
  import typer
8
8
 
9
- from rbx import annotations, console
9
+ from rbx import annotations, console, utils
10
10
  from rbx.box import environment, naming, package
11
11
  from rbx.box.formatting import href
12
12
  from rbx.box.schema import Package, expand_any_vars
@@ -46,7 +46,7 @@ def get_environment_languages_for_statement() -> List[StatementCodeLanguage]:
46
46
  res.append(
47
47
  StatementCodeLanguage(
48
48
  id=language.name,
49
- name=language.readable_name or language.name,
49
+ name=language.readableName or language.name,
50
50
  command=cmd or '',
51
51
  )
52
52
  )
@@ -173,7 +173,7 @@ def get_relative_assets(
173
173
  relative_to: pathlib.Path,
174
174
  assets: List[str],
175
175
  ) -> List[Tuple[pathlib.Path, pathlib.Path]]:
176
- relative_to = relative_to.resolve()
176
+ relative_to = utils.abspath(relative_to)
177
177
  if not relative_to.is_dir():
178
178
  relative_to = relative_to.parent
179
179
  res = []
@@ -192,7 +192,7 @@ def get_relative_assets(
192
192
  raise typer.Exit(1)
193
193
  res.extend(get_relative_assets(relative_to, list(map(str, globbed))))
194
194
  continue
195
- if not relative_path.resolve().is_relative_to(relative_to):
195
+ if not utils.abspath(relative_path).is_relative_to(relative_to):
196
196
  console.console.print(
197
197
  f'[error]Asset [item]{asset}[/item] is not relative to your statement.[/error]'
198
198
  )
@@ -200,8 +200,8 @@ def get_relative_assets(
200
200
 
201
201
  res.append(
202
202
  (
203
- relative_path.resolve(),
204
- relative_path.resolve().relative_to(relative_to),
203
+ utils.abspath(relative_path),
204
+ utils.abspath(relative_path).relative_to(relative_to),
205
205
  )
206
206
  )
207
207
 
@@ -319,7 +319,7 @@ class rbxTeXBuilder(StatementBuilder):
319
319
  params = typing.cast(rbxToTeX, params)
320
320
  if not params.template:
321
321
  return []
322
- return [((root / params.template).resolve(), params.template)]
322
+ return [(utils.abspath(root / params.template), params.template)]
323
323
 
324
324
  def build(
325
325
  self,
rbx/box/stats.py CHANGED
@@ -78,6 +78,14 @@ def print_package_stats(root: pathlib.Path = pathlib.Path()) -> int:
78
78
  return cache_size + build_size
79
79
 
80
80
 
81
+ def print_global_stats() -> int:
82
+ cache_size = get_cache_size()
83
+ console.console.print(
84
+ f'[status]Global cache size[/status]: [item]{get_formatted_memory(cache_size)}[/item]'
85
+ )
86
+ return cache_size
87
+
88
+
81
89
  def print_reachable_package_stats(root: pathlib.Path = pathlib.Path()) -> None:
82
90
  contest_packages, problem_packages = find_and_group_all_reachable_packages(root)
83
91
  total_size = 0
@@ -87,6 +95,8 @@ def print_reachable_package_stats(root: pathlib.Path = pathlib.Path()) -> None:
87
95
  for pkg in problem_packages:
88
96
  total_size += print_package_stats(pkg)
89
97
  console.console.print()
98
+
99
+ total_size += print_global_stats()
90
100
  console.console.print(
91
101
  f'[status]Total size[/status]: [item]{get_formatted_memory(total_size)}[/item]'
92
102
  )
rbx/box/stresses.py CHANGED
@@ -1,3 +1,5 @@
1
+ import pathlib
2
+ import shutil
1
3
  import time
2
4
  from shutil import rmtree
3
5
  from typing import List, Optional
@@ -7,25 +9,20 @@ import syncer
7
9
  import typer
8
10
  from pydantic import BaseModel
9
11
 
10
- from rbx import console
11
- from rbx.box import checkers, generators, package, validators
12
- from rbx.box.code import SanitizationLevel, compile_item, run_item
12
+ from rbx import console, utils
13
+ from rbx.box import checkers, generators, package, tasks, validators
14
+ from rbx.box.code import SanitizationLevel, compile_item
13
15
  from rbx.box.generators import (
14
16
  GenerationMetadata,
15
17
  expand_generator_call,
16
18
  generate_standalone,
17
19
  )
18
- from rbx.box.retries import Retrier
19
20
  from rbx.box.schema import CodeItem, GeneratorCall, Stress, TaskType, Testcase
20
21
  from rbx.box.solutions import compile_solutions, get_outcome_style_verdict
21
22
  from rbx.box.stressing import finder_parser
22
23
  from rbx.grading.steps import (
23
- DigestOrDest,
24
- DigestOrSource,
25
24
  Evaluation,
26
25
  Outcome,
27
- TestcaseIO,
28
- TestcaseLog,
29
26
  )
30
27
  from rbx.utils import StatusProgress
31
28
 
@@ -61,11 +58,6 @@ async def run_stress(
61
58
  sanitized: bool = False,
62
59
  ) -> StressReport:
63
60
  pkg = package.find_problem_package_or_die()
64
- if pkg.type == TaskType.COMMUNICATION:
65
- console.console.print(
66
- '[error]Communication problems do not support stress testing.[/error]'
67
- )
68
- raise typer.Exit(1)
69
61
 
70
62
  if finder:
71
63
  if generator_call is None:
@@ -90,6 +82,8 @@ async def run_stress(
90
82
  call = stress.generator
91
83
  generator = package.get_generator(call.name)
92
84
 
85
+ if progress:
86
+ progress.update('Compiling generator...')
93
87
  try:
94
88
  generator_digest = compile_item(generator, sanitized=SanitizationLevel.PREFER)
95
89
  except:
@@ -109,11 +103,18 @@ async def run_stress(
109
103
  solutions_digest = compile_solutions(
110
104
  tracked_solutions=set(str(solution.path) for solution in solutions),
111
105
  sanitized=sanitized,
106
+ progress=progress,
112
107
  )
113
108
  if progress:
114
109
  progress.update('Compiling finders...')
115
110
  finders_digest = {str(finder.path): _compile_finder(finder) for finder in finders}
116
111
 
112
+ interactor_digest = None
113
+ if pkg.type == TaskType.COMMUNICATION:
114
+ interactor_digest = checkers.compile_interactor(progress=progress)
115
+
116
+ if progress:
117
+ progress.update('Compiling validator...')
117
118
  compiled_validator = validators.compile_main_validator()
118
119
 
119
120
  # Erase old stress directory
@@ -158,47 +159,40 @@ async def run_stress(
158
159
  else None,
159
160
  )
160
161
 
161
- @async_lru.alru_cache
162
+ @async_lru.alru_cache(maxsize=None)
162
163
  async def run_solution_fn(
163
164
  solution: str,
164
- retry_index: Optional[int] = None,
165
+ checker_digest: Optional[str] = None,
165
166
  input_path=input_path,
166
- ) -> TestcaseLog:
167
+ output_path: Optional[pathlib.Path] = None,
168
+ ) -> Evaluation:
167
169
  index = solution_indices[solution]
168
170
  sol = solutions[index]
169
- output_path = input_path.with_stem(f'{index}').with_suffix('.out')
170
- stderr_path = output_path.with_suffix('.err')
171
-
172
- run_log = await run_item(
173
- sol,
174
- DigestOrSource.create(solutions_digest[sol.path]),
175
- stdin=DigestOrSource.create(input_path),
176
- stdout=DigestOrDest.create(output_path),
177
- stderr=DigestOrDest.create(stderr_path),
178
- retry_index=retry_index,
179
- )
180
-
181
- return TestcaseLog(
182
- **(run_log.model_dump() if run_log is not None else {}),
183
- stdout_absolute_path=output_path.absolute(),
184
- stderr_absolute_path=stderr_path.absolute(),
171
+ return await tasks.run_solution_on_testcase(
172
+ solutions[index],
173
+ compiled_digest=solutions_digest[sol.path],
174
+ checker_digest=checker_digest,
175
+ interactor_digest=interactor_digest,
176
+ testcase=Testcase(inputPath=input_path, outputPath=output_path),
177
+ output_dir=input_path.parent,
178
+ filestem=f'{index}',
179
+ is_stress=True,
185
180
  )
186
181
 
187
182
  # Get main solution output.
188
183
  expected_output_path = empty_path
189
184
  if needs_expected_output:
190
- main_testcase_log = await run_solution_fn(str(solutions[0].path))
191
- main_checker_result = checkers.check_with_no_output(main_testcase_log)
192
- if main_checker_result.outcome != Outcome.ACCEPTED:
185
+ eval = await run_solution_fn(str(solutions[0].path))
186
+ if eval.result.outcome != Outcome.ACCEPTED:
193
187
  console.console.print(
194
188
  '[error]Error while generating main solution output.[/error]'
195
189
  )
196
190
  console.console.print(f'Input written at [item]{input_path}[/item]')
197
191
  console.console.print(
198
- f'Output written at [item]{main_testcase_log.stdout_absolute_path}[/item]'
192
+ f'Output written at [item]{eval.log.stdout_absolute_path}[/item]'
199
193
  )
200
194
  console.console.print(
201
- f'Stderr written at [item]{main_testcase_log.stderr_absolute_path}[/item]'
195
+ f'Stderr written at [item]{eval.log.stderr_absolute_path}[/item]'
202
196
  )
203
197
  console.console.print()
204
198
  console.console.print(
@@ -206,44 +200,31 @@ async def run_stress(
206
200
  "use the two-way modifier in your finder expression (':2')."
207
201
  )
208
202
  raise typer.Exit(1)
209
- expected_output_path = main_testcase_log.stdout_absolute_path
203
+ if eval.log.stdout_absolute_path is not None:
204
+ expected_output_path = input_path.with_suffix('.ans')
205
+ shutil.copyfile(eval.log.stdout_absolute_path, expected_output_path)
206
+ else:
207
+ expected_output_path = None
210
208
 
211
- @async_lru.alru_cache
209
+ @async_lru.alru_cache(maxsize=None)
212
210
  async def run_solution_and_checker_fn(
213
211
  call: finder_parser.FinderCall,
214
- input_path=input_path,
215
212
  expected_output_path=expected_output_path,
216
213
  ) -> finder_parser.FinderResult:
217
- async def run_fn(retry_index: int) -> Evaluation:
214
+ async def run_fn() -> Evaluation:
218
215
  solution = call.solution
219
216
  checker = call.checker
220
217
 
221
- testcase_log = await run_solution_fn(solution, retry_index=retry_index)
222
- assert testcase_log.stdout_absolute_path is not None
223
-
224
- if checker is None:
225
- checker_result = checkers.check_with_no_output(testcase_log)
226
- else:
227
- checker_digest = finders_digest[checker.path]
228
- checker_result = await checkers.check(
229
- checker_digest,
230
- testcase_log,
231
- Testcase(inputPath=input_path, outputPath=expected_output_path),
232
- program_output=testcase_log.stdout_absolute_path,
233
- )
234
-
235
- return Evaluation(
236
- result=checker_result,
237
- testcase=TestcaseIO(
238
- index=0,
239
- input=input_path,
240
- output=expected_output_path,
241
- ),
242
- log=testcase_log,
218
+ checker_digest = (
219
+ finders_digest[checker.path] if checker is not None else None
220
+ )
221
+ return await run_solution_fn(
222
+ solution,
223
+ checker_digest=checker_digest,
224
+ output_path=expected_output_path,
243
225
  )
244
226
 
245
- retrier = Retrier(is_stress=True)
246
- eval = await retrier.repeat(run_fn)
227
+ eval = await run_fn()
247
228
 
248
229
  return finder_parser.FinderResult(
249
230
  solution=call.solution,
@@ -330,7 +311,7 @@ def print_stress_report(report: StressReport):
330
311
  console.console.print(f'Found [item]{len(report.findings)}[/item] testcases.')
331
312
 
332
313
  findings_dir = package.get_problem_runs_dir() / '.stress' / 'findings'
333
- console.console.print(f'Findings: {findings_dir.resolve()}')
314
+ console.console.print(f'Findings: {utils.abspath(findings_dir)}')
334
315
  console.console.print()
335
316
 
336
317
  for i, finding in enumerate(report.findings):
@@ -10,6 +10,7 @@ import typer
10
10
  from rbx import console
11
11
  from rbx.box import package
12
12
  from rbx.box.schema import CodeItem, ExpectedOutcome
13
+ from rbx.box.solutions import expand_solutions
13
14
  from rbx.grading.steps import CheckerResult, Outcome, RunLog, TestcaseLog
14
15
 
15
16
  LARK_GRAMMAR = r"""
@@ -26,11 +27,12 @@ negation: _NOT "(" disjunction ")"
26
27
  // Expressions
27
28
  logical: eval matcher expected_outcome -> matching
28
29
  | eval equality (eval | outcome) -> equating
30
+ | eval -> eval_only
29
31
 
30
- eval: "[" solution checking? "]"
32
+ eval: "[" solution checking? "]" | solution
31
33
 
32
34
  // Eval
33
- solution: _filename | WILDCARD
35
+ solution: _solution_filename | WILDCARD
34
36
  checking: "ON"i (checking_mode? checker | ":nil")
35
37
  checking_mode: MODE ":"
36
38
  MODE: "2" | "3"
@@ -55,6 +57,7 @@ WILDCARD: "$"
55
57
 
56
58
  // File name
57
59
  _filename: FILENAME | "\"" FILENAME "\""
60
+ _solution_filename: _filename | "@" _filename
58
61
  FILENAME: /[\/A-Za-z0-9\-_\.]/+
59
62
 
60
63
  // Names (Variables)
@@ -217,20 +220,7 @@ def get_all_solutions(tree: lark.ParseTree) -> List[str]:
217
220
 
218
221
  def get_all_solution_items(tree: lark.ParseTree) -> List[CodeItem]:
219
222
  solution_names = get_all_solutions(tree)
220
- res = []
221
-
222
- for solution_name in solution_names:
223
- found_solution = package.get_solution_or_nil(solution_name)
224
- if found_solution is None:
225
- res.append(
226
- CodeItem(
227
- path=pathlib.Path(solution_name),
228
- language=None,
229
- compilationFiles=None,
230
- )
231
- )
232
- continue
233
- res.append(found_solution)
223
+ res = typing.cast(List[CodeItem], expand_solutions(solution_names))
234
224
 
235
225
  main_solution = package.get_main_solution()
236
226
  if main_solution is None:
@@ -391,6 +381,11 @@ class FinderTreeRunner(lark.Transformer):
391
381
 
392
382
  return FinderOutcome(truth_value=truth_value, results=results)
393
383
 
384
+ def eval_only(self, eval_result: FinderResult) -> FinderOutcome:
385
+ return self.matching(
386
+ eval_result, is_positive=True, expected_outcome=ExpectedOutcome.INCORRECT
387
+ )
388
+
394
389
  def negation(self, value: FinderOutcome) -> FinderOutcome:
395
390
  return dataclasses.replace(value, truth_value=not value.truth_value)
396
391