rbx.cp 0.7.0__py3-none-any.whl → 0.8.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 (45) hide show
  1. rbx/box/cli.py +79 -31
  2. rbx/box/code.py +131 -82
  3. rbx/box/global_package.py +74 -0
  4. rbx/box/package.py +6 -19
  5. rbx/box/remote.py +19 -0
  6. rbx/box/sanitizers/warning_stack.py +3 -3
  7. rbx/box/solutions.py +13 -7
  8. rbx/box/stats.py +10 -0
  9. rbx/box/stresses.py +45 -64
  10. rbx/box/stressing/finder_parser.py +11 -16
  11. rbx/box/tasks.py +33 -22
  12. rbx/box/tooling/boca/scraper.py +1 -1
  13. rbx/grading/caching.py +98 -47
  14. rbx/grading/debug_context.py +31 -0
  15. rbx/grading/grading_context.py +96 -0
  16. rbx/grading/judge/cacher.py +93 -21
  17. rbx/grading/judge/sandbox.py +6 -3
  18. rbx/grading/judge/sandboxes/timeit.py +1 -1
  19. rbx/grading/judge/storage.py +169 -35
  20. rbx/grading/profiling.py +126 -0
  21. rbx/grading/steps.py +44 -16
  22. rbx/grading/steps_with_caching.py +52 -26
  23. rbx/resources/presets/default/contest/.gitignore +2 -0
  24. rbx/resources/presets/default/contest/contest.rbx.yml +14 -1
  25. rbx/resources/presets/default/contest/statement/contest.rbx.tex +25 -86
  26. rbx/resources/presets/default/contest/statement/icpc.sty +322 -0
  27. rbx/resources/presets/default/contest/statement/instructions.tex +40 -0
  28. rbx/resources/presets/default/contest/statement/logo.png +0 -0
  29. rbx/resources/presets/default/contest/statement/template.rbx.tex +45 -36
  30. rbx/resources/presets/default/preset.rbx.yml +2 -2
  31. rbx/resources/presets/default/problem/problem.rbx.yml +12 -8
  32. rbx/resources/presets/default/problem/statement/icpc.sty +322 -0
  33. rbx/resources/presets/default/problem/statement/template.rbx.tex +47 -79
  34. {rbx_cp-0.7.0.dist-info → rbx_cp-0.8.0.dist-info}/METADATA +3 -1
  35. {rbx_cp-0.7.0.dist-info → rbx_cp-0.8.0.dist-info}/RECORD +43 -36
  36. rbx/resources/presets/default/contest/statement/olymp.sty +0 -250
  37. rbx/resources/presets/default/problem/statement/olymp.sty +0 -250
  38. /rbx/resources/presets/default/problem/{gen.cpp → gens/gen.cpp} +0 -0
  39. /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/000.in +0 -0
  40. /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/001.in +0 -0
  41. /rbx/resources/presets/default/problem/{random.py → testplan/random.py} +0 -0
  42. /rbx/resources/presets/default/problem/{random.txt → testplan/random.txt} +0 -0
  43. {rbx_cp-0.7.0.dist-info → rbx_cp-0.8.0.dist-info}/LICENSE +0 -0
  44. {rbx_cp-0.7.0.dist-info → rbx_cp-0.8.0.dist-info}/WHEEL +0 -0
  45. {rbx_cp-0.7.0.dist-info → rbx_cp-0.8.0.dist-info}/entry_points.txt +0 -0
rbx/box/remote.py CHANGED
@@ -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,6 +79,7 @@ class BocaExpander(Expander):
69
79
 
70
80
 
71
81
  REGISTERED_EXPANDERS: List['Expander'] = [
82
+ MainExpander(),
72
83
  BocaExpander(),
73
84
  ]
74
85
 
@@ -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 path.resolve().is_relative_to(remote_dir.resolve())
@@ -4,7 +4,7 @@ import shutil
4
4
 
5
5
  from rbx import console
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:
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
@@ -245,7 +246,7 @@ async def convert_list_of_solution_evaluations_to_dict(
245
246
 
246
247
 
247
248
  def _get_solutions_for_skeleton(
248
- tracked_solutions: Optional[Set[str]] = None,
249
+ tracked_solutions: Optional[Iterable[str]] = None,
249
250
  verification: VerificationLevel = VerificationLevel.NONE,
250
251
  ) -> List[Solution]:
251
252
  pkg = package.find_problem_package_or_die()
@@ -260,7 +261,7 @@ def _get_solutions_for_skeleton(
260
261
 
261
262
 
262
263
  def _get_report_skeleton(
263
- tracked_solutions: Optional[Set[str]] = None,
264
+ tracked_solutions: Optional[Iterable[str]] = None,
264
265
  verification: VerificationLevel = VerificationLevel.NONE,
265
266
  timelimit_override: Optional[int] = None,
266
267
  ) -> SolutionReportSkeleton:
@@ -393,7 +394,7 @@ def print_best_output(output_files: List[pathlib.Path], empty_warning: bool = Fa
393
394
 
394
395
  def run_solutions(
395
396
  progress: Optional[StatusProgress] = None,
396
- tracked_solutions: Optional[Set[str]] = None,
397
+ tracked_solutions: Optional[Iterable[str]] = None,
397
398
  verification: VerificationLevel = VerificationLevel.NONE,
398
399
  check: bool = True,
399
400
  timelimit_override: Optional[int] = None,
@@ -605,7 +606,7 @@ def _run_interactive_solutions(
605
606
 
606
607
 
607
608
  def _get_interactive_skeleton(
608
- tracked_solutions: Optional[Set[str]] = None,
609
+ tracked_solutions: Optional[Iterable[str]] = None,
609
610
  verification: VerificationLevel = VerificationLevel.NONE,
610
611
  ) -> SolutionReportSkeleton:
611
612
  solutions = _get_solutions_for_skeleton(tracked_solutions, verification)
@@ -645,7 +646,7 @@ def _get_interactive_skeleton(
645
646
 
646
647
  async def run_and_print_interactive_solutions(
647
648
  progress: Optional[StatusProgress] = None,
648
- tracked_solutions: Optional[Set[str]] = None,
649
+ tracked_solutions: Optional[Iterable[str]] = None,
649
650
  verification: VerificationLevel = VerificationLevel.NONE,
650
651
  generator: Optional[GeneratorCall] = None,
651
652
  testcase_entry: Optional[TestcaseEntry] = None,
@@ -739,7 +740,12 @@ def expand_solutions_with_source(sols: List[str]) -> List[Tuple[Solution, bool]]
739
740
  path_sols = remote.expand_files(sols)
740
741
 
741
742
  # Ensure sols exist.
742
- path_sols = [sol for sol in path_sols if sol.is_file()]
743
+ for sol in path_sols:
744
+ if not sol.is_file():
745
+ console.console.print(
746
+ f'[error]Solution [item]{sol}[/item] could not be found.[/error]'
747
+ )
748
+ raise typer.Exit(1)
743
749
 
744
750
  seen_sols = set()
745
751
  res: List[Tuple[Solution, bool]] = []
@@ -762,7 +768,7 @@ def expand_solutions(sols: List[str]) -> List[Solution]:
762
768
 
763
769
 
764
770
  async def pick_solutions(
765
- tracked_solutions: Optional[Set[str]],
771
+ tracked_solutions: Optional[OrderedSet[str]],
766
772
  extra_solutions: Optional[List[str]] = None,
767
773
  ) -> List[str]:
768
774
  pkg = package.find_problem_package_or_die()
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
@@ -8,24 +10,19 @@ import typer
8
10
  from pydantic import BaseModel
9
11
 
10
12
  from rbx import console
11
- from rbx.box import checkers, generators, package, validators
12
- from rbx.box.code import SanitizationLevel, compile_item, run_item
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,
@@ -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
 
rbx/box/tasks.py CHANGED
@@ -5,7 +5,8 @@ from rbx.box import checkers, package, state
5
5
  from rbx.box.code import CommunicationItem, run_communication, run_item
6
6
  from rbx.box.environment import EnvironmentSandbox, ExecutionConfig, VerificationLevel
7
7
  from rbx.box.retries import Retrier, get_retrier_config
8
- from rbx.box.schema import Solution, Testcase
8
+ from rbx.box.schema import CodeItem, Testcase
9
+ from rbx.grading import profiling
9
10
  from rbx.grading.judge.sandbox import SandboxBase
10
11
  from rbx.grading.limits import Limits
11
12
  from rbx.grading.steps import (
@@ -39,7 +40,7 @@ def get_limits_for_language(
39
40
 
40
41
 
41
42
  async def run_solution_on_testcase(
42
- solution: Solution,
43
+ solution: CodeItem,
43
44
  compiled_digest: str,
44
45
  checker_digest: Optional[str],
45
46
  testcase: Testcase,
@@ -52,6 +53,8 @@ async def run_solution_on_testcase(
52
53
  use_timelimit: bool = True,
53
54
  capture_pipes: bool = False,
54
55
  nruns: int = 0,
56
+ filestem: Optional[str] = None,
57
+ is_stress: bool = False,
55
58
  ) -> Evaluation:
56
59
  if interactor_digest is not None:
57
60
  return await _run_communication_solution_on_testcase(
@@ -68,6 +71,8 @@ async def run_solution_on_testcase(
68
71
  use_timelimit=use_timelimit,
69
72
  capture_pipes=capture_pipes,
70
73
  nruns=nruns,
74
+ filestem=filestem,
75
+ is_stress=is_stress,
71
76
  )
72
77
 
73
78
  async def run_fn(retry_index: int) -> Evaluation:
@@ -85,29 +90,32 @@ async def run_solution_on_testcase(
85
90
  assert testcase.outputPath is not None
86
91
  output_path = testcase.outputPath
87
92
  else:
88
- output_path = output_dir / testcase.inputPath.with_suffix('.out').name
93
+ stem = filestem or testcase.inputPath.stem
94
+ output_path = output_dir / pathlib.PosixPath(stem).with_suffix('.out')
89
95
  error_path = output_path.with_suffix('.err')
90
96
  log_path = output_path.with_suffix('.log')
91
97
  eval_path = output_path.with_suffix('.eval')
92
98
  output_path.parent.mkdir(parents=True, exist_ok=True)
93
99
 
94
- run_log = await run_item(
95
- solution,
96
- DigestOrSource.create(compiled_digest),
97
- stdin=DigestOrSource.create(testcase.inputPath),
98
- stdout=DigestOrDest.create(output_path),
99
- stderr=DigestOrDest.create(error_path),
100
- extra_config=extra_config,
101
- retry_index=retry_index,
102
- )
100
+ with profiling.PushContext('tasks.run_solution_on_testcase'):
101
+ run_log = await run_item(
102
+ solution,
103
+ DigestOrSource.create(compiled_digest),
104
+ stdin=DigestOrSource.create(testcase.inputPath),
105
+ stdout=DigestOrDest.create(output_path),
106
+ stderr=DigestOrDest.create(error_path),
107
+ extra_config=extra_config,
108
+ retry_index=retry_index,
109
+ )
103
110
 
104
111
  if checker_digest is not None:
105
- checker_result = await checkers.check(
106
- checker_digest,
107
- run_log,
108
- testcase,
109
- program_output=output_path,
110
- )
112
+ with profiling.PushContext('tasks.run_solution_on_testcase.check'):
113
+ checker_result = await checkers.check(
114
+ checker_digest,
115
+ run_log,
116
+ testcase,
117
+ program_output=output_path,
118
+ )
111
119
  else:
112
120
  checker_result = checkers.check_with_no_output(run_log)
113
121
 
@@ -134,7 +142,7 @@ async def run_solution_on_testcase(
134
142
  if not use_retries:
135
143
  return await run_fn(0)
136
144
 
137
- retrier = Retrier(get_retrier_config(nruns))
145
+ retrier = Retrier(get_retrier_config(nruns), is_stress=is_stress)
138
146
  return await retrier.repeat(run_fn)
139
147
 
140
148
 
@@ -156,7 +164,7 @@ def _get_execution_config(
156
164
 
157
165
 
158
166
  async def _run_communication_solution_on_testcase(
159
- solution: Solution,
167
+ solution: CodeItem,
160
168
  compiled_digest: str,
161
169
  interactor_digest: str,
162
170
  checker_digest: Optional[str],
@@ -169,6 +177,8 @@ async def _run_communication_solution_on_testcase(
169
177
  use_timelimit: bool = True,
170
178
  capture_pipes: bool = False,
171
179
  nruns: int = 0,
180
+ filestem: Optional[str] = None,
181
+ is_stress: bool = False,
172
182
  ) -> Evaluation:
173
183
  capture_pipes = capture_pipes or state.STATE.debug_logs
174
184
 
@@ -200,7 +210,8 @@ async def _run_communication_solution_on_testcase(
200
210
  assert testcase.outputPath is not None
201
211
  output_path = testcase.outputPath
202
212
  else:
203
- output_path = output_dir / testcase.inputPath.with_suffix('.out').name
213
+ stem = filestem or testcase.inputPath.stem
214
+ output_path = output_dir / pathlib.PosixPath(stem).with_suffix('.out')
204
215
  solution_error_path = output_path.with_suffix('.sol.err')
205
216
  interactor_error_path = output_path.with_suffix('.int.err')
206
217
  log_path = output_path.with_suffix('.log')
@@ -294,5 +305,5 @@ async def _run_communication_solution_on_testcase(
294
305
  if not use_retries:
295
306
  return await run_fn(0)
296
307
 
297
- retrier = Retrier(get_retrier_config(nruns))
308
+ retrier = Retrier(get_retrier_config(nruns), is_stress=is_stress)
298
309
  return await retrier.repeat(run_fn)
@@ -364,7 +364,7 @@ class BocaScraper:
364
364
  return final_path
365
365
 
366
366
 
367
- @functools.lru_cache
367
+ @functools.cache
368
368
  def get_boca_scraper(
369
369
  base_url: Optional[str] = None,
370
370
  username: Optional[str] = None,