rbx.cp 0.5.40__py3-none-any.whl → 0.5.45__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 (57) hide show
  1. rbx/box/builder.py +6 -6
  2. rbx/box/checkers.py +100 -25
  3. rbx/box/cli.py +868 -0
  4. rbx/box/code.py +272 -84
  5. rbx/box/contest/statements.py +4 -2
  6. rbx/box/generators.py +55 -49
  7. rbx/box/generators_test.py +7 -7
  8. rbx/box/main.py +1 -868
  9. rbx/box/package.py +57 -2
  10. rbx/box/packaging/boca/packager.py +2 -1
  11. rbx/box/packaging/main.py +17 -9
  12. rbx/box/packaging/moj/packager.py +49 -10
  13. rbx/box/retries.py +5 -5
  14. rbx/box/schema.py +20 -4
  15. rbx/box/solutions.py +46 -108
  16. rbx/box/solutions_test.py +5 -6
  17. rbx/box/state.py +1 -0
  18. rbx/box/statements/build_statements.py +4 -2
  19. rbx/box/stresses.py +23 -12
  20. rbx/box/tasks.py +277 -0
  21. rbx/box/testcase_extractors.py +21 -21
  22. rbx/box/testcases/main.py +19 -14
  23. rbx/box/unit.py +10 -7
  24. rbx/box/validators.py +10 -10
  25. rbx/box/validators_test.py +3 -3
  26. rbx/grading/judge/cacher.py +0 -4
  27. rbx/grading/judge/digester.py +0 -3
  28. rbx/grading/judge/sandbox.py +15 -0
  29. rbx/grading/judge/sandboxes/stupid_sandbox.py +20 -6
  30. rbx/grading/judge/sandboxes/timeit.py +117 -7
  31. rbx/grading/judge/storage.py +0 -4
  32. rbx/grading/steps.py +76 -2
  33. rbx/grading/steps_with_caching.py +45 -3
  34. rbx/grading/steps_with_caching_run_test.py +51 -49
  35. rbx/main.py +0 -4
  36. rbx/resources/packagers/moj/scripts/compare.sh +25 -6
  37. rbx/test.py +6 -4
  38. {rbx_cp-0.5.40.dist-info → rbx_cp-0.5.45.dist-info}/METADATA +2 -2
  39. {rbx_cp-0.5.40.dist-info → rbx_cp-0.5.45.dist-info}/RECORD +42 -55
  40. {rbx_cp-0.5.40.dist-info → rbx_cp-0.5.45.dist-info}/WHEEL +1 -1
  41. rbx/testdata/box1/gen1.cpp +0 -7
  42. rbx/testdata/box1/gen2.cpp +0 -9
  43. rbx/testdata/box1/genScript.py +0 -2
  44. rbx/testdata/box1/hard-tle.sol.cpp +0 -26
  45. rbx/testdata/box1/ole.cpp +0 -17
  46. rbx/testdata/box1/problem.rbx.yml +0 -39
  47. rbx/testdata/box1/re.sol.cpp +0 -23
  48. rbx/testdata/box1/sol.cpp +0 -22
  49. rbx/testdata/box1/tests/1.in +0 -1
  50. rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -33
  51. rbx/testdata/box1/tle.sol.cpp +0 -35
  52. rbx/testdata/box1/validator.cpp +0 -11
  53. rbx/testdata/box1/wa.sol.cpp +0 -22
  54. rbx/testdata/caching/executable.py +0 -1
  55. rbx/testdata/compatible +0 -0
  56. {rbx_cp-0.5.40.dist-info → rbx_cp-0.5.45.dist-info}/LICENSE +0 -0
  57. {rbx_cp-0.5.40.dist-info → rbx_cp-0.5.45.dist-info}/entry_points.txt +0 -0
rbx/box/code.py CHANGED
@@ -1,3 +1,4 @@
1
+ import dataclasses
1
2
  import pathlib
2
3
  import re
3
4
  import resource
@@ -14,6 +15,7 @@ from rbx import console
14
15
  from rbx.box import download, package, setter_config, state
15
16
  from rbx.box.environment import (
16
17
  ExecutionConfig,
18
+ FileMapping,
17
19
  get_compilation_config,
18
20
  get_execution_config,
19
21
  get_file_mapping,
@@ -26,7 +28,8 @@ from rbx.box.environment import (
26
28
  from rbx.box.formatting import get_formatted_memory
27
29
  from rbx.box.sanitizers import warning_stack
28
30
  from rbx.box.schema import CodeItem
29
- from rbx.grading import steps_with_caching
31
+ from rbx.grading import steps, steps_with_caching
32
+ from rbx.grading.judge.sandbox import SandboxParams
30
33
  from rbx.grading.steps import (
31
34
  DigestHolder,
32
35
  DigestOrDest,
@@ -202,6 +205,163 @@ function rbx() {{
202
205
  raise typer.Exit(1)
203
206
 
204
207
 
208
+ @dataclasses.dataclass
209
+ class PreparedRun:
210
+ command: str
211
+ sandbox_params: SandboxParams
212
+ artifacts: GradingArtifacts
213
+ sanitized: bool
214
+
215
+ file_mapping: FileMapping
216
+ metadata: RunLogMetadata
217
+
218
+
219
+ @dataclasses.dataclass
220
+ class CaptureSpec:
221
+ prefix: str
222
+ output: Optional[DigestOrDest] = None
223
+ merged_capture: Optional[pathlib.Path] = None
224
+
225
+
226
+ def _prepare_for_communication(
227
+ run: PreparedRun,
228
+ stdin: pathlib.Path,
229
+ stdout: pathlib.Path,
230
+ reverse_io: bool = False,
231
+ capture: Optional[CaptureSpec] = None,
232
+ ):
233
+ run.sandbox_params.set_stdio(
234
+ stdin=stdin,
235
+ stdout=stdout,
236
+ )
237
+ run.sandbox_params.reverse_io = reverse_io
238
+ if capture is not None:
239
+ run.sandbox_params.timeit_prefix = capture.prefix
240
+
241
+ if capture.output is not None:
242
+ output_path = PosixPath('capture')
243
+ run.sandbox_params.timeit_dups['do'].append(output_path)
244
+
245
+ run.artifacts.outputs.append(
246
+ GradingFileOutput(
247
+ src=output_path,
248
+ **capture.output.expand(),
249
+ touch=True,
250
+ )
251
+ )
252
+
253
+ if capture.merged_capture is not None:
254
+ merged_output_path = package.get_merged_capture_path().resolve()
255
+ run.sandbox_params.timeit_dups['Do'].append(merged_output_path)
256
+
257
+ run.artifacts.outputs.append(
258
+ GradingFileOutput(
259
+ src=merged_output_path,
260
+ dest=capture.merged_capture,
261
+ )
262
+ )
263
+
264
+
265
+ def _prepare_run(
266
+ code: CodeItem,
267
+ executable: DigestOrSource,
268
+ stdin: Optional[DigestOrSource] = None,
269
+ stdout: Optional[DigestOrDest] = None,
270
+ stderr: Optional[DigestOrDest] = None,
271
+ inputs: Optional[List[GradingFileInput]] = None,
272
+ outputs: Optional[List[GradingFileOutput]] = None,
273
+ extra_args: Optional[str] = None,
274
+ extra_config: Optional[ExecutionConfig] = None,
275
+ retry_index: Optional[int] = None,
276
+ ):
277
+ language = find_language_name(code)
278
+ execution_options = get_execution_config(language)
279
+ if extra_config is not None:
280
+ execution_options = merge_execution_configs([execution_options, extra_config])
281
+ file_mapping = get_file_mapping(language)
282
+ sandbox_params = get_sandbox_params_from_config(execution_options.sandbox)
283
+
284
+ # Sanitization parameters.
285
+ sanitized = False
286
+ if is_executable_sanitized(executable):
287
+ # Remove any memory constraints for a sanitized executable.
288
+ # Sanitizers are known to be memory-hungry.
289
+ sandbox_params.address_space = None
290
+
291
+ # Reset timeout configs since sanitizers are known to be time-hungry.
292
+ sandbox_params.timeout = None
293
+ sandbox_params.wallclock_timeout = None
294
+ sanitized = True
295
+
296
+ sandbox_params.set_stdall(
297
+ stdin=PosixPath(file_mapping.input) if stdin is not None else None,
298
+ stdout=PosixPath(file_mapping.output) if stdout is not None else None,
299
+ stderr=PosixPath(file_mapping.error)
300
+ if stderr is not None or sanitized
301
+ else None,
302
+ )
303
+
304
+ assert execution_options.command
305
+ command = get_mapped_command(execution_options.command, file_mapping)
306
+ command = substitute_commands([command], sanitized=sanitized)[0]
307
+
308
+ if extra_args is not None:
309
+ splitted_command = shlex.split(command)
310
+ splitted_command.extend(shlex.split(extra_args))
311
+ command = shlex.join(splitted_command)
312
+
313
+ artifacts = GradingArtifacts()
314
+ artifacts.inputs.append(
315
+ GradingFileInput(
316
+ **executable.expand(),
317
+ dest=PosixPath(file_mapping.executable),
318
+ executable=True,
319
+ )
320
+ )
321
+ if stdin is not None:
322
+ artifacts.inputs.append(
323
+ GradingFileInput(
324
+ **stdin.expand(),
325
+ dest=PosixPath(file_mapping.input),
326
+ )
327
+ )
328
+ if stdout is not None:
329
+ artifacts.outputs.append(
330
+ GradingFileOutput(
331
+ src=PosixPath(file_mapping.output),
332
+ **stdout.expand(),
333
+ touch=True,
334
+ )
335
+ )
336
+ if stderr is not None:
337
+ artifacts.outputs.append(
338
+ GradingFileOutput(
339
+ src=PosixPath(file_mapping.error),
340
+ **stderr.expand(),
341
+ touch=True,
342
+ )
343
+ )
344
+ if inputs:
345
+ artifacts.inputs.extend(inputs)
346
+ if outputs:
347
+ artifacts.outputs.extend(outputs)
348
+
349
+ return PreparedRun(
350
+ command=command,
351
+ sandbox_params=sandbox_params,
352
+ artifacts=artifacts,
353
+ sanitized=sanitized,
354
+ file_mapping=file_mapping,
355
+ metadata=RunLogMetadata(
356
+ language=code.language,
357
+ is_sanitized=sanitized,
358
+ timeLimit=sandbox_params.timeout,
359
+ memoryLimit=sandbox_params.address_space,
360
+ retryIndex=retry_index,
361
+ ),
362
+ )
363
+
364
+
205
365
  # Compile code item and return its digest in the storage.
206
366
  def compile_item(
207
367
  code: CodeItem,
@@ -310,7 +470,7 @@ def compile_item(
310
470
  return compiled_digest.value
311
471
 
312
472
 
313
- def run_item(
473
+ async def run_item(
314
474
  code: CodeItem,
315
475
  executable: DigestOrSource,
316
476
  stdin: Optional[DigestOrSource] = None,
@@ -324,99 +484,127 @@ def run_item(
324
484
  ) -> Optional[RunLog]:
325
485
  _check_stack_limit()
326
486
 
327
- language = find_language_name(code)
328
- execution_options = get_execution_config(language)
329
- if extra_config is not None:
330
- execution_options = merge_execution_configs([execution_options, extra_config])
331
- file_mapping = get_file_mapping(language)
332
487
  dependency_cache = package.get_dependency_cache()
333
- sandbox = package.get_singleton_sandbox()
334
- sandbox_params = get_sandbox_params_from_config(execution_options.sandbox)
335
-
336
- # Sanitization parameters.
337
- sanitized = False
338
- if is_executable_sanitized(executable):
339
- # Remove any memory constraints for a sanitized executable.
340
- # Sanitizers are known to be memory-hungry.
341
- sandbox_params.address_space = None
342
-
343
- # Reset timeout configs since sanitizers are known to be time-hungry.
344
- sandbox_params.timeout = None
345
- sandbox_params.wallclock_timeout = None
346
- sanitized = True
347
488
 
348
- sandbox_params.set_stdall(
349
- stdin=PosixPath(file_mapping.input) if stdin is not None else None,
350
- stdout=PosixPath(file_mapping.output) if stdout is not None else None,
351
- stderr=PosixPath(file_mapping.error)
352
- if stderr is not None or sanitized
353
- else None,
489
+ prepared = _prepare_run(
490
+ code,
491
+ executable,
492
+ stdin,
493
+ stdout,
494
+ stderr,
495
+ inputs,
496
+ outputs,
497
+ extra_args,
498
+ extra_config,
499
+ retry_index,
354
500
  )
355
501
 
356
- assert execution_options.command
357
- command = get_mapped_command(execution_options.command, file_mapping)
358
- command = substitute_commands([command], sanitized=sanitized)[0]
359
-
360
- if extra_args is not None:
361
- splitted_command = shlex.split(command)
362
- splitted_command.extend(shlex.split(extra_args))
363
- command = shlex.join(splitted_command)
364
-
365
- artifacts = GradingArtifacts()
366
- artifacts.inputs.append(
367
- GradingFileInput(
368
- **executable.expand(),
369
- dest=PosixPath(file_mapping.executable),
370
- executable=True,
371
- )
372
- )
373
- if stdin is not None:
374
- artifacts.inputs.append(
375
- GradingFileInput(
376
- **stdin.expand(),
377
- dest=PosixPath(file_mapping.input),
378
- )
379
- )
380
- if stdout is not None:
381
- artifacts.outputs.append(
382
- GradingFileOutput(
383
- src=PosixPath(file_mapping.output),
384
- **stdout.expand(),
385
- )
386
- )
387
- if stderr is not None:
388
- artifacts.outputs.append(
389
- GradingFileOutput(
390
- src=PosixPath(file_mapping.error),
391
- **stderr.expand(),
392
- )
393
- )
394
- if inputs:
395
- artifacts.inputs.extend(inputs)
396
- if outputs:
397
- artifacts.outputs.extend(outputs)
398
-
399
- run_log = steps_with_caching.run(
400
- command,
401
- params=sandbox_params,
402
- sandbox=sandbox,
403
- artifacts=artifacts,
502
+ run_log = await steps_with_caching.run(
503
+ prepared.command,
504
+ params=prepared.sandbox_params,
505
+ sandbox=package.get_singleton_sandbox(),
506
+ artifacts=prepared.artifacts,
404
507
  dependency_cache=dependency_cache,
405
- metadata=RunLogMetadata(
406
- language=code.language,
407
- is_sanitized=sanitized,
408
- timeLimit=sandbox_params.timeout,
409
- memoryLimit=sandbox_params.address_space,
410
- retryIndex=retry_index,
411
- ),
508
+ metadata=prepared.metadata,
412
509
  )
413
510
 
414
511
  # Find sanitizer logs.
415
512
  if run_log is not None and run_log.warnings:
416
- assert sandbox_params.stderr_file is not None
417
- stderr_output = artifacts.get_output_file_for_src(sandbox_params.stderr_file)
513
+ assert prepared.sandbox_params.stderr_file is not None
514
+ stderr_output = prepared.artifacts.get_output_file_for_src(
515
+ prepared.sandbox_params.stderr_file
516
+ )
418
517
  if stderr_output is not None:
419
518
  warning_stack.get_warning_stack().add_sanitizer_warning(
420
519
  package.get_cache_storage(), code, stderr_output
421
520
  )
422
521
  return run_log
522
+
523
+
524
+ @dataclasses.dataclass
525
+ class CommunicationItem:
526
+ code: CodeItem
527
+ executable: DigestOrSource
528
+ stderr: Optional[DigestOrDest] = None
529
+ inputs: Optional[List[GradingFileInput]] = None
530
+ outputs: Optional[List[GradingFileOutput]] = None
531
+ extra_args: Optional[str] = None
532
+ extra_config: Optional[ExecutionConfig] = None
533
+ capture: Optional[DigestOrDest] = None
534
+
535
+ def prepare(self) -> PreparedRun:
536
+ return _prepare_run(
537
+ self.code,
538
+ self.executable,
539
+ stderr=self.stderr,
540
+ inputs=self.inputs,
541
+ outputs=self.outputs,
542
+ extra_args=self.extra_args,
543
+ extra_config=self.extra_config,
544
+ )
545
+
546
+
547
+ async def run_communication(
548
+ interactor: CommunicationItem,
549
+ solution: CommunicationItem,
550
+ merged_capture: Optional[pathlib.Path] = None,
551
+ retry_index: Optional[int] = None,
552
+ ):
553
+ fifo_in, fifo_out = package.get_fifos()
554
+ interactor_prepared = interactor.prepare()
555
+ solution_prepared = solution.prepare()
556
+
557
+ # Prepare retry index.
558
+ interactor_prepared.metadata.retryIndex = retry_index
559
+ solution_prepared.metadata.retryIndex = retry_index
560
+
561
+ interactor_prefix = 'INTERACTOR:'
562
+ solution_prefix = 'SOLUTION:'
563
+
564
+ if merged_capture is not None:
565
+ package.get_merged_capture_path().write_text(
566
+ f'{interactor_prefix}\n{solution_prefix}\n'
567
+ )
568
+
569
+ _prepare_for_communication(
570
+ interactor_prepared,
571
+ fifo_out,
572
+ fifo_in,
573
+ capture=CaptureSpec(
574
+ prefix=interactor_prefix,
575
+ output=interactor.capture,
576
+ merged_capture=merged_capture,
577
+ ),
578
+ )
579
+ _prepare_for_communication(
580
+ solution_prepared,
581
+ fifo_in,
582
+ fifo_out,
583
+ reverse_io=True,
584
+ capture=CaptureSpec(
585
+ prefix=solution_prefix,
586
+ output=solution.capture,
587
+ merged_capture=merged_capture,
588
+ ),
589
+ )
590
+
591
+ interactor_run_params = steps.CoordinatedRunParams(
592
+ command=interactor_prepared.command,
593
+ params=interactor_prepared.sandbox_params,
594
+ sandbox=package.get_singleton_interactor_sandbox(),
595
+ artifacts=interactor_prepared.artifacts,
596
+ metadata=interactor_prepared.metadata,
597
+ )
598
+ solution_run_params = steps.CoordinatedRunParams(
599
+ command=solution_prepared.command,
600
+ params=solution_prepared.sandbox_params,
601
+ sandbox=package.get_singleton_sandbox(),
602
+ artifacts=solution_prepared.artifacts,
603
+ metadata=solution_prepared.metadata,
604
+ )
605
+
606
+ return await steps_with_caching.run_coordinated(
607
+ interactor_run_params,
608
+ solution_run_params,
609
+ dependency_cache=package.get_dependency_cache(),
610
+ )
@@ -1,5 +1,6 @@
1
1
  from typing import Annotated, List, Optional
2
2
 
3
+ import syncer
3
4
  import typer
4
5
 
5
6
  from rbx import annotations, console
@@ -16,7 +17,8 @@ app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
16
17
 
17
18
  @app.command('build, b', help='Build statements.')
18
19
  @within_contest
19
- def build(
20
+ @syncer.sync
21
+ async def build(
20
22
  verification: environment.VerificationParam,
21
23
  languages: Annotated[
22
24
  Optional[List[str]],
@@ -53,7 +55,7 @@ def build(
53
55
  with cd.new_package_cd(problem.get_path()):
54
56
  package.clear_package_cache()
55
57
 
56
- if not builder.build(
58
+ if not await builder.build(
57
59
  verification=verification, groups=set(['samples']), output=None
58
60
  ):
59
61
  console.console.print(
rbx/box/generators.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import pathlib
2
2
  import shutil
3
+ import tempfile
3
4
  from typing import Dict, List, Optional, Set
4
5
 
5
6
  import typer
@@ -7,15 +8,13 @@ import typer
7
8
  from rbx import console
8
9
  from rbx.box import checkers, package, testcase_utils, validators
9
10
  from rbx.box.code import SanitizationLevel, compile_item, run_item
10
- from rbx.box.environment import (
11
- EnvironmentSandbox,
12
- ExecutionConfig,
13
- )
14
11
  from rbx.box.schema import (
15
12
  CodeItem,
16
13
  GeneratorCall,
14
+ TaskType,
17
15
  Testcase,
18
16
  )
17
+ from rbx.box.tasks import run_solution_on_testcase
19
18
  from rbx.box.testcase_extractors import (
20
19
  GenerationMetadata,
21
20
  GenerationTestcaseEntry,
@@ -32,6 +31,8 @@ from rbx.grading.steps import (
32
31
  DigestHolder,
33
32
  DigestOrDest,
34
33
  DigestOrSource,
34
+ Evaluation,
35
+ Outcome,
35
36
  )
36
37
  from rbx.utils import StatusProgress
37
38
 
@@ -73,7 +74,7 @@ def get_call_from_string(call_str: str) -> GeneratorCall:
73
74
  return GeneratorCall(name=name, args=args)
74
75
 
75
76
 
76
- def _get_necessary_generators_for_groups(
77
+ async def _get_necessary_generators_for_groups(
77
78
  groups: Optional[Set[str]] = None,
78
79
  ) -> Set[str]:
79
80
  pkg = package.find_problem_package_or_die()
@@ -81,11 +82,11 @@ def _get_necessary_generators_for_groups(
81
82
  necessary_generators = set()
82
83
 
83
84
  class NecessaryGeneratorsVisitor(TestcaseGroupVisitor):
84
- def visit(self, entry: GenerationTestcaseEntry):
85
+ async def visit(self, entry: GenerationTestcaseEntry):
85
86
  if entry.metadata.generator_call is not None:
86
87
  necessary_generators.add(entry.metadata.generator_call.name)
87
88
 
88
- run_testcase_visitor(NecessaryGeneratorsVisitor(groups))
89
+ await run_testcase_visitor(NecessaryGeneratorsVisitor(groups))
89
90
 
90
91
  return existing_generators.intersection(necessary_generators)
91
92
 
@@ -126,7 +127,7 @@ def expand_generator_call(call: GeneratorCall) -> GeneratorCall:
126
127
  return call.model_copy(update={'args': generator_for_args.generate(parsed_args)})
127
128
 
128
129
 
129
- def generate_standalone(
130
+ async def generate_standalone(
130
131
  spec: GenerationMetadata,
131
132
  validate: bool = True,
132
133
  group_entry: Optional[TestcaseEntry] = None,
@@ -166,7 +167,7 @@ def generate_standalone(
166
167
  progress.update(
167
168
  f'Generating testcase [status]{generator.name} {call.args}[/status]...'
168
169
  )
169
- generation_log = run_item(
170
+ generation_log = await run_item(
170
171
  generator,
171
172
  DigestOrSource.create(generator_digest),
172
173
  stdout=DigestOrDest.create(spec.copied_to.inputPath),
@@ -200,7 +201,7 @@ def generate_standalone(
200
201
  _, validator_digest = validator_tp
201
202
  if progress:
202
203
  progress.update('Validating test...')
203
- validation_info = validators.validate_one_off(
204
+ validation_info = await validators.validate_one_off(
204
205
  spec.copied_to.inputPath,
205
206
  validator,
206
207
  validator_digest,
@@ -214,7 +215,7 @@ def generate_standalone(
214
215
  raise typer.Exit(1)
215
216
 
216
217
 
217
- def generate_testcases(
218
+ async def generate_testcases(
218
219
  progress: Optional[StatusProgress] = None, groups: Optional[Set[str]] = None
219
220
  ):
220
221
  def step():
@@ -223,7 +224,7 @@ def generate_testcases(
223
224
 
224
225
  compiled_generators = compile_generators(
225
226
  progress=progress,
226
- tracked_generators=_get_necessary_generators_for_groups(groups)
227
+ tracked_generators=await _get_necessary_generators_for_groups(groups)
227
228
  if groups is not None
228
229
  else None,
229
230
  )
@@ -231,7 +232,7 @@ def generate_testcases(
231
232
  testcase_utils.clear_built_testcases()
232
233
 
233
234
  class BuildTestcaseVisitor(TestcaseGroupVisitor):
234
- def visit(self, entry: GenerationTestcaseEntry):
235
+ async def visit(self, entry: GenerationTestcaseEntry):
235
236
  if entry.metadata.copied_from is not None:
236
237
  _copy_testcase_over(
237
238
  entry.metadata.copied_from,
@@ -239,7 +240,7 @@ def generate_testcases(
239
240
  )
240
241
 
241
242
  if entry.metadata.generator_call is not None:
242
- generate_standalone(
243
+ await generate_standalone(
243
244
  entry.metadata,
244
245
  group_entry=entry.group_entry,
245
246
  validate=False,
@@ -249,13 +250,14 @@ def generate_testcases(
249
250
  )
250
251
  step()
251
252
 
252
- run_testcase_visitor(BuildTestcaseVisitor(groups))
253
+ await run_testcase_visitor(BuildTestcaseVisitor(groups))
253
254
 
254
255
 
255
- def generate_output_for_testcase(
256
+ async def generate_output_for_testcase(
256
257
  main_solution_digest: str,
257
258
  testcase: Testcase,
258
259
  stderr_path: Optional[pathlib.Path] = None,
260
+ interactor_digest: Optional[str] = None,
259
261
  ):
260
262
  assert testcase.outputPath is not None
261
263
  testcase.inputPath.parent.mkdir(parents=True, exist_ok=True)
@@ -265,55 +267,51 @@ def generate_output_for_testcase(
265
267
  # Output file was already copied over from manual tests.
266
268
  return
267
269
 
268
- pkg = package.find_problem_package_or_die()
269
270
  main_solution = package.get_main_solution()
270
271
  if main_solution is None:
271
272
  return
272
273
 
273
- # Obey no limits when generating testcases.
274
- sandbox = EnvironmentSandbox()
275
- sandbox.fileSizeLimit = pkg.outputLimit
276
- extra_config = ExecutionConfig(sandbox=sandbox)
274
+ with tempfile.TemporaryDirectory() as dir:
275
+ output_dir = pathlib.Path(dir)
277
276
 
278
- try:
279
- run_log = run_item(
277
+ eval: Evaluation = await run_solution_on_testcase(
280
278
  main_solution,
281
- DigestOrSource.create(main_solution_digest),
282
- stdin=DigestOrSource.create(testcase.inputPath),
283
- stdout=DigestOrDest.create(testcase.outputPath),
284
- stderr=DigestOrDest.create(stderr_path)
285
- if stderr_path is not None
286
- else None,
287
- extra_config=extra_config,
288
- )
289
- except:
290
- console.console.print(
291
- '[error]Failed running main solution to generate testcase.[/error]'
279
+ main_solution_digest,
280
+ None,
281
+ testcase,
282
+ output_dir,
283
+ interactor_digest=interactor_digest,
284
+ use_retries=False,
285
+ use_timelimit=False,
292
286
  )
293
- raise
294
287
 
295
- if run_log is None or run_log.exitcode != 0:
296
- console.console.print(
297
- f'[error]Failed generating output for [item]{testcase.inputPath}[/item][/error]',
298
- )
299
- if run_log is not None:
300
- console.console.print(f'[error]Summary:[/error] {run_log.get_summary()}')
301
- checker_result = checkers.check_with_no_output(run_log)
288
+ if eval.log.stdout_absolute_path is not None:
289
+ shutil.copy(eval.log.stdout_absolute_path, testcase.outputPath)
290
+ if eval.log.stderr_absolute_path is not None and stderr_path is not None:
291
+ shutil.copy(eval.log.stderr_absolute_path, stderr_path)
292
+
293
+ if eval.result.outcome != Outcome.ACCEPTED:
294
+ console.console.print(
295
+ f'[error]Failed generating output for [item]{testcase.inputPath}[/item][/error]',
296
+ )
297
+ console.console.print(f'[error]Summary:[/error] {eval.log.get_summary()}')
302
298
  console.console.print(
303
- f'[warning]Verdict: [item]{checker_result.outcome.value}[/item][/warning]',
299
+ f'[warning]Verdict: [item]{eval.result.outcome.value}[/item][/warning]',
304
300
  )
305
301
  console.console.print(
306
- f'[warning]Message: [info]{checker_result.message}[/info][/warning]',
302
+ f'[warning]Message: [info]{eval.result.message}[/info][/warning]',
307
303
  )
308
304
  console.console.print(f'Input written at [item]{testcase.inputPath}[/item]')
309
305
  console.console.print(
310
306
  f'Output written at [item]{testcase.outputPath}[/item]'
311
307
  )
312
- console.console.print(f'Stderr written at [item]{stderr_path}[/item]')
313
- raise typer.Exit(1)
308
+ if stderr_path is not None:
309
+ console.console.print(f'Stderr written at [item]{stderr_path}[/item]')
310
+
311
+ raise typer.Exit(1)
314
312
 
315
313
 
316
- def generate_outputs_for_testcases(
314
+ async def generate_outputs_for_testcases(
317
315
  entries: List[TestcaseEntry],
318
316
  progress: Optional[StatusProgress] = None,
319
317
  ):
@@ -324,6 +322,13 @@ def generate_outputs_for_testcases(
324
322
  main_solution = package.get_main_solution()
325
323
  solution_digest: Optional[str] = None
326
324
 
325
+ pkg = package.find_problem_package_or_die()
326
+
327
+ if pkg.type == TaskType.COMMUNICATION:
328
+ interactor_digest = checkers.compile_interactor(progress)
329
+ else:
330
+ interactor_digest = None
331
+
327
332
  if main_solution is not None:
328
333
  if progress:
329
334
  progress.update('Compiling main solution...')
@@ -337,7 +342,7 @@ def generate_outputs_for_testcases(
337
342
  shutil.rmtree(str(gen_runs_dir), ignore_errors=True)
338
343
  gen_runs_dir.mkdir(parents=True, exist_ok=True)
339
344
 
340
- generation_entries = extract_generation_testcases(entries)
345
+ generation_entries = await extract_generation_testcases(entries)
341
346
 
342
347
  for entry in generation_entries:
343
348
  tc = entry.metadata.copied_to
@@ -354,9 +359,10 @@ def generate_outputs_for_testcases(
354
359
  raise typer.Exit(1)
355
360
 
356
361
  assert solution_digest is not None
357
- generate_output_for_testcase(
362
+ await generate_output_for_testcase(
358
363
  solution_digest,
359
364
  tc,
360
365
  gen_runs_dir / 'main.stderr',
366
+ interactor_digest=interactor_digest,
361
367
  )
362
368
  step()