rbx.cp 0.5.39__py3-none-any.whl → 0.5.42__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 (53) hide show
  1. rbx/box/builder.py +6 -6
  2. rbx/box/checkers.py +105 -26
  3. rbx/box/cli.py +860 -0
  4. rbx/box/code.py +199 -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 -852
  9. rbx/box/package.py +42 -1
  10. rbx/box/packaging/boca/packager.py +2 -1
  11. rbx/box/packaging/main.py +24 -7
  12. rbx/box/packaging/moj/packager.py +164 -0
  13. rbx/box/retries.py +5 -5
  14. rbx/box/schema.py +86 -4
  15. rbx/box/solutions.py +46 -108
  16. rbx/box/solutions_test.py +5 -6
  17. rbx/box/statements/build_statements.py +4 -2
  18. rbx/box/stresses.py +23 -12
  19. rbx/box/tasks.py +258 -0
  20. rbx/box/testcase_extractors.py +21 -21
  21. rbx/box/testcases/main.py +19 -14
  22. rbx/box/unit.py +116 -0
  23. rbx/box/validators.py +27 -18
  24. rbx/box/validators_test.py +3 -3
  25. rbx/grading/judge/sandbox.py +8 -0
  26. rbx/grading/judge/sandboxes/stupid_sandbox.py +12 -7
  27. rbx/grading/judge/sandboxes/timeit.py +8 -2
  28. rbx/grading/steps.py +76 -2
  29. rbx/grading/steps_with_caching.py +45 -3
  30. rbx/grading/steps_with_caching_run_test.py +51 -49
  31. rbx/resources/packagers/moj/scripts/compare.sh +101 -0
  32. rbx/test.py +6 -4
  33. rbx/testdata/interactive/checker.cpp +21 -0
  34. rbx/testdata/interactive/gen.cpp +11 -0
  35. rbx/testdata/interactive/interactor.cpp +63 -0
  36. rbx/testdata/interactive/problem.rbx.yml +40 -0
  37. rbx/testdata/interactive/sols/af_ac_pe.cpp +75 -0
  38. rbx/testdata/interactive/sols/af_ac_re.cpp +76 -0
  39. rbx/testdata/interactive/sols/af_ac_too_many_iter.cpp +72 -0
  40. rbx/testdata/interactive/sols/af_inf_cout_with_flush.cpp +79 -0
  41. rbx/testdata/interactive/sols/af_inf_cout_without_flush.cpp +78 -0
  42. rbx/testdata/interactive/sols/af_ml.cpp +78 -0
  43. rbx/testdata/interactive/sols/af_tl_after_ans.cpp +74 -0
  44. rbx/testdata/interactive/sols/af_wa.cpp +74 -0
  45. rbx/testdata/interactive/sols/interactive-binary-search_mm_naive_cin.cpp +17 -0
  46. rbx/testdata/interactive/sols/main.cpp +26 -0
  47. rbx/testdata/interactive/testplan.txt +6 -0
  48. rbx/testdata/interactive/validator.cpp +16 -0
  49. {rbx_cp-0.5.39.dist-info → rbx_cp-0.5.42.dist-info}/METADATA +2 -1
  50. {rbx_cp-0.5.39.dist-info → rbx_cp-0.5.42.dist-info}/RECORD +53 -32
  51. {rbx_cp-0.5.39.dist-info → rbx_cp-0.5.42.dist-info}/LICENSE +0 -0
  52. {rbx_cp-0.5.39.dist-info → rbx_cp-0.5.42.dist-info}/WHEEL +0 -0
  53. {rbx_cp-0.5.39.dist-info → rbx_cp-0.5.42.dist-info}/entry_points.txt +0 -0
rbx/grading/steps.py CHANGED
@@ -1,10 +1,16 @@
1
+ import asyncio
2
+ import contextlib
3
+ import dataclasses
1
4
  import functools
5
+ import os
2
6
  import pathlib
3
7
  import re
4
8
  import shlex
5
9
  import shutil
6
10
  import subprocess
7
11
  import sys
12
+ import tempfile
13
+ import typing
8
14
  from enum import Enum
9
15
  from typing import IO, Any, Dict, Iterable, List, Optional, Tuple, Union
10
16
 
@@ -123,6 +129,8 @@ class GradingFileOutput(BaseModel):
123
129
  intermediate: bool = False
124
130
  # Whether to track file through its hash (disable for optimization).
125
131
  hash: bool = True
132
+ # Whether to touch the file before the command runs.
133
+ touch: bool = False
126
134
 
127
135
  def get_file(self, storage: Storage) -> Optional[IO[bytes]]:
128
136
  if self.dest is not None:
@@ -136,6 +144,15 @@ class GradingFileOutput(BaseModel):
136
144
  raise ValueError('No file to get')
137
145
 
138
146
 
147
+ class GradingFifo(BaseModel):
148
+ # Destination path relative to the sandbox.
149
+ path: pathlib.Path
150
+ # Symlink to the FIFO outside the sandbox.
151
+ symlink: Optional[pathlib.Path] = None
152
+ # Whether to create the FIFO if it does not exist.
153
+ create: bool = True
154
+
155
+
139
156
  class GradingArtifacts(BaseModel):
140
157
  # Root directory for the produced artifacts.
141
158
  root: pathlib.Path = pathlib.PosixPath('.')
@@ -143,6 +160,8 @@ class GradingArtifacts(BaseModel):
143
160
  inputs: List[GradingFileInput] = []
144
161
  # List of output files to copy from the sandbox.
145
162
  outputs: List[GradingFileOutput] = []
163
+ # List of FIFOs
164
+ fifos: List[GradingFifo] = []
146
165
  # Capture certain logs of the execution.
147
166
  logs: Optional[GradingLogsHolder] = None
148
167
 
@@ -241,6 +260,14 @@ def _process_input_artifacts(artifacts: GradingArtifacts, sandbox: SandboxBase):
241
260
  override=True,
242
261
  try_symlink=True,
243
262
  )
263
+ for output_artifact in artifacts.outputs:
264
+ if output_artifact.touch:
265
+ sandbox.create_file_from_string(
266
+ output_artifact.src,
267
+ '',
268
+ executable=output_artifact.executable,
269
+ override=True,
270
+ )
244
271
 
245
272
 
246
273
  def _process_output_artifacts(
@@ -278,6 +305,14 @@ def _process_output_artifacts(
278
305
  return True
279
306
 
280
307
 
308
+ def _process_fifos(artifacts: GradingArtifacts, sandbox: SandboxBase):
309
+ for fifo in artifacts.fifos:
310
+ if fifo.symlink is not None:
311
+ sandbox.create_symlink(fifo.path, fifo.symlink, override=True)
312
+ else:
313
+ sandbox.create_fifo(fifo.path, override=True)
314
+
315
+
281
316
  def testlib_grading_input() -> GradingFileInput:
282
317
  return GradingFileInput(src=get_testlib(), dest=pathlib.Path('testlib.h'))
283
318
 
@@ -553,7 +588,7 @@ def compile(
553
588
  return _process_output_artifacts(artifacts, sandbox)
554
589
 
555
590
 
556
- def run(
591
+ async def run(
557
592
  command: str,
558
593
  params: SandboxParams,
559
594
  sandbox: SandboxBase,
@@ -561,10 +596,11 @@ def run(
561
596
  metadata: Optional[RunLogMetadata] = None,
562
597
  ) -> Optional[RunLog]:
563
598
  _process_input_artifacts(artifacts, sandbox)
599
+ _process_fifos(artifacts, sandbox)
564
600
  cmd = _split_and_expand(command, sandbox)
565
601
  sandbox.set_params(params)
566
602
 
567
- if not sandbox.execute_without_std(cmd):
603
+ if not await asyncio.to_thread(sandbox.execute_without_std, cmd):
568
604
  console.print(
569
605
  '[error]Sandbox crashed while processing command:[/error]',
570
606
  utils.highlight_json_obj(cmd),
@@ -600,6 +636,34 @@ def run(
600
636
  return run_log
601
637
 
602
638
 
639
+ @dataclasses.dataclass
640
+ class CoordinatedRunParams:
641
+ command: str
642
+ params: SandboxParams
643
+ sandbox: SandboxBase
644
+ artifacts: GradingArtifacts
645
+ metadata: Optional[RunLogMetadata] = None
646
+
647
+
648
+ async def run_coordinated(
649
+ interactor: CoordinatedRunParams,
650
+ solution: CoordinatedRunParams,
651
+ ) -> Tuple[Optional[RunLog], Optional[RunLog]]:
652
+ runs = tuple(
653
+ run(
654
+ params.command,
655
+ params.params,
656
+ params.sandbox,
657
+ params.artifacts,
658
+ params.metadata,
659
+ )
660
+ for params in [interactor, solution]
661
+ )
662
+ return typing.cast(
663
+ Tuple[Optional[RunLog], Optional[RunLog]], tuple(await asyncio.gather(*runs))
664
+ )
665
+
666
+
603
667
  def _normalize_checked_words(s: str) -> Tuple[str, ...]:
604
668
  return tuple(s.split())
605
669
 
@@ -722,3 +786,13 @@ def evaluate(
722
786
  log=log,
723
787
  result=checker_result,
724
788
  )
789
+
790
+
791
+ @contextlib.contextmanager
792
+ def make_fifos():
793
+ with tempfile.TemporaryDirectory() as temp_dir:
794
+ fifo_in = pathlib.PosixPath(temp_dir) / 'fifo.in'
795
+ fifo_out = pathlib.PosixPath(temp_dir) / 'fifo.out'
796
+ os.mkfifo(fifo_in)
797
+ os.mkfifo(fifo_out)
798
+ yield fifo_in, fifo_out
@@ -1,4 +1,4 @@
1
- from typing import List, Optional
1
+ from typing import Any, Dict, List, Optional, Tuple
2
2
 
3
3
  from rbx.grading import steps
4
4
  from rbx.grading.caching import DependencyCache, NoCacheException
@@ -11,6 +11,12 @@ from rbx.grading.steps import (
11
11
  )
12
12
 
13
13
 
14
+ def _get_prefixed_cacheable_params(
15
+ params: Dict[str, Any], prefix: str
16
+ ) -> Dict[str, Any]:
17
+ return {f'{prefix}.{k}': v for k, v in params.items()}
18
+
19
+
14
20
  def compile(
15
21
  commands: List[str],
16
22
  params: SandboxParams,
@@ -36,7 +42,7 @@ def compile(
36
42
  return ok
37
43
 
38
44
 
39
- def run(
45
+ async def run(
40
46
  command: str,
41
47
  params: SandboxParams,
42
48
  sandbox: SandboxBase,
@@ -52,7 +58,7 @@ def run(
52
58
 
53
59
  with dependency_cache([command], [artifacts], cacheable_params) as is_cached:
54
60
  if not is_cached:
55
- steps.run(
61
+ await steps.run(
56
62
  command=command,
57
63
  params=params,
58
64
  artifacts=artifacts,
@@ -61,3 +67,39 @@ def run(
61
67
  )
62
68
 
63
69
  return artifacts.logs.run
70
+
71
+
72
+ async def run_coordinated(
73
+ interactor: steps.CoordinatedRunParams,
74
+ solution: steps.CoordinatedRunParams,
75
+ dependency_cache: DependencyCache,
76
+ ) -> Tuple[Optional[RunLog], Optional[RunLog]]:
77
+ interactor.artifacts.logs = GradingLogsHolder()
78
+ solution.artifacts.logs = GradingLogsHolder()
79
+
80
+ cacheable_params = {
81
+ **_get_prefixed_cacheable_params(
82
+ interactor.params.get_cacheable_params(), 'interactor'
83
+ ),
84
+ **_get_prefixed_cacheable_params(
85
+ solution.params.get_cacheable_params(), 'solution'
86
+ ),
87
+ }
88
+
89
+ if interactor.metadata is not None and interactor.metadata.retryIndex is not None:
90
+ cacheable_params['interactor.__retry_index__'] = interactor.metadata.retryIndex
91
+ if solution.metadata is not None and solution.metadata.retryIndex is not None:
92
+ cacheable_params['solution.__retry_index__'] = solution.metadata.retryIndex
93
+
94
+ with dependency_cache(
95
+ [interactor.command, solution.command],
96
+ [interactor.artifacts, solution.artifacts],
97
+ cacheable_params,
98
+ ) as is_cached:
99
+ if not is_cached:
100
+ await steps.run_coordinated(interactor, solution)
101
+
102
+ return (
103
+ interactor.artifacts.logs.run,
104
+ solution.artifacts.logs.run,
105
+ )
@@ -15,7 +15,7 @@ from rbx.grading.steps import (
15
15
  )
16
16
 
17
17
 
18
- def test_run_from_digest(
18
+ async def test_run_from_digest(
19
19
  cleandir: pathlib.Path,
20
20
  dependency_cache: DependencyCache,
21
21
  sandbox: SandboxBase,
@@ -29,7 +29,7 @@ def test_run_from_digest(
29
29
  artifacts.outputs.append(
30
30
  GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=pathlib.Path('out.txt'))
31
31
  )
32
- steps_with_caching.run(
32
+ await steps_with_caching.run(
33
33
  f'{sys.executable} executable.py',
34
34
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
35
35
  sandbox=sandbox,
@@ -45,7 +45,7 @@ def test_run_from_digest(
45
45
  assert not artifacts.logs.cached
46
46
 
47
47
 
48
- def test_run_from_disk(
48
+ async def test_run_from_disk(
49
49
  cleandir: pathlib.Path,
50
50
  dependency_cache: DependencyCache,
51
51
  sandbox: SandboxBase,
@@ -60,7 +60,7 @@ def test_run_from_disk(
60
60
  artifacts.outputs.append(
61
61
  GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=pathlib.Path('out.txt'))
62
62
  )
63
- steps_with_caching.run(
63
+ await steps_with_caching.run(
64
64
  f'{sys.executable} executable.py',
65
65
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
66
66
  sandbox=sandbox,
@@ -74,13 +74,13 @@ def test_run_from_disk(
74
74
  assert not artifacts.logs.cached
75
75
 
76
76
 
77
- def test_run_caches_intermediate_digest_if_dest_changes(
77
+ async def test_run_caches_intermediate_digest_if_dest_changes(
78
78
  cleandir: pathlib.Path,
79
79
  dependency_cache: DependencyCache,
80
80
  sandbox: SandboxBase,
81
81
  file_cacher: FileCacher,
82
82
  ):
83
- def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
83
+ async def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
84
84
  executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
85
85
  artifacts = GradingArtifacts()
86
86
  artifacts.inputs.append(
@@ -89,7 +89,7 @@ def test_run_caches_intermediate_digest_if_dest_changes(
89
89
  artifacts.outputs.append(
90
90
  GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=dest)
91
91
  )
92
- steps_with_caching.run(
92
+ await steps_with_caching.run(
93
93
  f'{sys.executable} executable.py',
94
94
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
95
95
  sandbox=sandbox,
@@ -98,24 +98,26 @@ def test_run_caches_intermediate_digest_if_dest_changes(
98
98
  )
99
99
  return artifacts
100
100
 
101
- artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
101
+ artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
102
102
  assert (cleandir / 'out.txt').read_text().strip() == '5'
103
103
  assert artifacts.logs is not None
104
104
  assert not artifacts.logs.cached
105
105
 
106
- another_artifacts = configure_and_run_with_dest(pathlib.Path('another-out.txt'))
106
+ another_artifacts = await configure_and_run_with_dest(
107
+ pathlib.Path('another-out.txt')
108
+ )
107
109
  assert (cleandir / 'out.txt').read_text().strip() == '5'
108
110
  assert another_artifacts.logs is not None
109
111
  assert another_artifacts.logs.cached
110
112
 
111
113
 
112
- def test_run_overwrite_changed_file_with_storage_value(
114
+ async def test_run_overwrite_changed_file_with_storage_value(
113
115
  cleandir: pathlib.Path,
114
116
  dependency_cache: DependencyCache,
115
117
  sandbox: SandboxBase,
116
118
  file_cacher: FileCacher,
117
119
  ):
118
- def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
120
+ async def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
119
121
  executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
120
122
  artifacts = GradingArtifacts()
121
123
  artifacts.inputs.append(
@@ -124,7 +126,7 @@ def test_run_overwrite_changed_file_with_storage_value(
124
126
  artifacts.outputs.append(
125
127
  GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=dest)
126
128
  )
127
- steps_with_caching.run(
129
+ await steps_with_caching.run(
128
130
  f'{sys.executable} executable.py',
129
131
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
130
132
  sandbox=sandbox,
@@ -133,26 +135,26 @@ def test_run_overwrite_changed_file_with_storage_value(
133
135
  )
134
136
  return artifacts
135
137
 
136
- artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
138
+ artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
137
139
  assert (cleandir / 'out.txt').read_text().strip() == '5'
138
140
  assert artifacts.logs is not None
139
141
  assert not artifacts.logs.cached
140
142
 
141
143
  pathlib.Path('out.txt').write_text('42')
142
144
 
143
- another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
145
+ another_artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
144
146
  assert (cleandir / 'out.txt').read_text().strip() == '5'
145
147
  assert another_artifacts.logs is not None
146
148
  assert another_artifacts.logs.cached
147
149
 
148
150
 
149
- def test_run_recreates_deleted_file_with_storage_value(
151
+ async def test_run_recreates_deleted_file_with_storage_value(
150
152
  cleandir: pathlib.Path,
151
153
  dependency_cache: DependencyCache,
152
154
  sandbox: SandboxBase,
153
155
  file_cacher: FileCacher,
154
156
  ):
155
- def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
157
+ async def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
156
158
  executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
157
159
  artifacts = GradingArtifacts()
158
160
  artifacts.inputs.append(
@@ -161,7 +163,7 @@ def test_run_recreates_deleted_file_with_storage_value(
161
163
  artifacts.outputs.append(
162
164
  GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=dest)
163
165
  )
164
- steps_with_caching.run(
166
+ await steps_with_caching.run(
165
167
  f'{sys.executable} executable.py',
166
168
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
167
169
  sandbox=sandbox,
@@ -170,26 +172,26 @@ def test_run_recreates_deleted_file_with_storage_value(
170
172
  )
171
173
  return artifacts
172
174
 
173
- artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
175
+ artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
174
176
  assert (cleandir / 'out.txt').read_text().strip() == '5'
175
177
  assert artifacts.logs is not None
176
178
  assert not artifacts.logs.cached
177
179
 
178
180
  pathlib.Path('out.txt').unlink()
179
181
 
180
- another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
182
+ another_artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
181
183
  assert (cleandir / 'out.txt').read_text().strip() == '5'
182
184
  assert another_artifacts.logs is not None
183
185
  assert another_artifacts.logs.cached
184
186
 
185
187
 
186
- def test_run_overwrite_exec_bit_when_changed(
188
+ async def test_run_overwrite_exec_bit_when_changed(
187
189
  cleandir: pathlib.Path,
188
190
  dependency_cache: DependencyCache,
189
191
  sandbox: SandboxBase,
190
192
  file_cacher: FileCacher,
191
193
  ):
192
- def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
194
+ async def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
193
195
  executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
194
196
  artifacts = GradingArtifacts()
195
197
  artifacts.inputs.append(
@@ -203,7 +205,7 @@ def test_run_overwrite_exec_bit_when_changed(
203
205
  src=pathlib.Path('box-out.txt'), dest=dest, executable=True
204
206
  )
205
207
  )
206
- steps_with_caching.run(
208
+ await steps_with_caching.run(
207
209
  f'{sys.executable} executable.py',
208
210
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
209
211
  sandbox=sandbox,
@@ -212,7 +214,7 @@ def test_run_overwrite_exec_bit_when_changed(
212
214
  )
213
215
  return artifacts
214
216
 
215
- artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
217
+ artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
216
218
  assert (cleandir / 'out.txt').read_text().strip() == '5'
217
219
  assert artifacts.logs is not None
218
220
  assert not artifacts.logs.cached
@@ -220,20 +222,20 @@ def test_run_overwrite_exec_bit_when_changed(
220
222
 
221
223
  pathlib.Path('out.txt').chmod(0o644)
222
224
 
223
- another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
225
+ another_artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
224
226
  assert (cleandir / 'out.txt').read_text().strip() == '5'
225
227
  assert another_artifacts.logs is not None
226
228
  assert another_artifacts.logs.cached
227
229
  assert os.access('out.txt', os.X_OK)
228
230
 
229
231
 
230
- def test_run_evicts_when_changed_file_and_no_hash(
232
+ async def test_run_evicts_when_changed_file_and_no_hash(
231
233
  cleandir: pathlib.Path,
232
234
  dependency_cache: DependencyCache,
233
235
  sandbox: SandboxBase,
234
236
  file_cacher: FileCacher,
235
237
  ):
236
- def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
238
+ async def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
237
239
  executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
238
240
  artifacts = GradingArtifacts()
239
241
  artifacts.inputs.append(
@@ -242,7 +244,7 @@ def test_run_evicts_when_changed_file_and_no_hash(
242
244
  artifacts.outputs.append(
243
245
  GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=dest, hash=False)
244
246
  )
245
- steps_with_caching.run(
247
+ await steps_with_caching.run(
246
248
  f'{sys.executable} executable.py',
247
249
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
248
250
  sandbox=sandbox,
@@ -251,26 +253,26 @@ def test_run_evicts_when_changed_file_and_no_hash(
251
253
  )
252
254
  return artifacts
253
255
 
254
- artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
256
+ artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
255
257
  assert (cleandir / 'out.txt').read_text().strip() == '5'
256
258
  assert artifacts.logs is not None
257
259
  assert not artifacts.logs.cached
258
260
 
259
261
  pathlib.Path('out.txt').write_text('42')
260
262
 
261
- another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
263
+ another_artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
262
264
  assert (cleandir / 'out.txt').read_text().strip() == '5'
263
265
  assert another_artifacts.logs is not None
264
266
  assert not another_artifacts.logs.cached
265
267
 
266
268
 
267
- def test_run_evicts_when_exec_bit_different_and_no_hash(
269
+ async def test_run_evicts_when_exec_bit_different_and_no_hash(
268
270
  cleandir: pathlib.Path,
269
271
  dependency_cache: DependencyCache,
270
272
  sandbox: SandboxBase,
271
273
  file_cacher: FileCacher,
272
274
  ):
273
- def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
275
+ async def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
274
276
  executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
275
277
  artifacts = GradingArtifacts()
276
278
  artifacts.inputs.append(
@@ -281,7 +283,7 @@ def test_run_evicts_when_exec_bit_different_and_no_hash(
281
283
  src=pathlib.Path('box-out.txt'), dest=dest, hash=False, executable=True
282
284
  )
283
285
  )
284
- steps_with_caching.run(
286
+ await steps_with_caching.run(
285
287
  f'{sys.executable} executable.py',
286
288
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
287
289
  sandbox=sandbox,
@@ -290,25 +292,25 @@ def test_run_evicts_when_exec_bit_different_and_no_hash(
290
292
  )
291
293
  return artifacts
292
294
 
293
- artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
295
+ artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
294
296
  assert (cleandir / 'out.txt').read_text().strip() == '5'
295
297
  assert artifacts.logs is not None
296
298
  assert not artifacts.logs.cached
297
299
 
298
300
  pathlib.Path('out.txt').chmod(0o0644)
299
301
 
300
- another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
302
+ another_artifacts = await configure_and_run_with_dest(pathlib.Path('out.txt'))
301
303
  assert (cleandir / 'out.txt').read_text().strip() == '5'
302
304
  assert another_artifacts.logs is not None
303
305
  assert not another_artifacts.logs.cached
304
306
 
305
307
 
306
- def test_run_evicts_when_input_fingerprint_changes(
308
+ async def test_run_evicts_when_input_fingerprint_changes(
307
309
  cleandir: pathlib.Path,
308
310
  dependency_cache: DependencyCache,
309
311
  sandbox: SandboxBase,
310
312
  ):
311
- def configure_and_run() -> GradingArtifacts:
313
+ async def configure_and_run() -> GradingArtifacts:
312
314
  executable = DigestOrSource.create(pathlib.Path('executable.py'))
313
315
  artifacts = GradingArtifacts()
314
316
  artifacts.inputs.append(
@@ -320,7 +322,7 @@ def test_run_evicts_when_input_fingerprint_changes(
320
322
  dest=pathlib.Path('out.txt'),
321
323
  )
322
324
  )
323
- steps_with_caching.run(
325
+ await steps_with_caching.run(
324
326
  f'{sys.executable} executable.py',
325
327
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
326
328
  sandbox=sandbox,
@@ -331,25 +333,25 @@ def test_run_evicts_when_input_fingerprint_changes(
331
333
 
332
334
  pathlib.Path('executable.py').write_text('print(5)')
333
335
 
334
- artifacts = configure_and_run()
336
+ artifacts = await configure_and_run()
335
337
  assert (cleandir / 'out.txt').read_text().strip() == '5'
336
338
  assert artifacts.logs is not None
337
339
  assert not artifacts.logs.cached
338
340
 
339
341
  pathlib.Path('executable.py').write_text('print(42)')
340
342
 
341
- another_artifacts = configure_and_run()
343
+ another_artifacts = await configure_and_run()
342
344
  assert (cleandir / 'out.txt').read_text().strip() == '42'
343
345
  assert another_artifacts.logs is not None
344
346
  assert not another_artifacts.logs.cached
345
347
 
346
348
 
347
- def test_run_evicts_when_output_is_deleted_and_no_hash(
349
+ async def test_run_evicts_when_output_is_deleted_and_no_hash(
348
350
  cleandir: pathlib.Path,
349
351
  dependency_cache: DependencyCache,
350
352
  sandbox: SandboxBase,
351
353
  ):
352
- def configure_and_run() -> GradingArtifacts:
354
+ async def configure_and_run() -> GradingArtifacts:
353
355
  executable = DigestOrSource.create(pathlib.Path('executable.py'))
354
356
  artifacts = GradingArtifacts()
355
357
  artifacts.inputs.append(
@@ -362,7 +364,7 @@ def test_run_evicts_when_output_is_deleted_and_no_hash(
362
364
  hash=False,
363
365
  )
364
366
  )
365
- steps_with_caching.run(
367
+ await steps_with_caching.run(
366
368
  f'{sys.executable} executable.py',
367
369
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
368
370
  sandbox=sandbox,
@@ -373,26 +375,26 @@ def test_run_evicts_when_output_is_deleted_and_no_hash(
373
375
 
374
376
  pathlib.Path('executable.py').write_text('print(5)')
375
377
 
376
- artifacts = configure_and_run()
378
+ artifacts = await configure_and_run()
377
379
  assert (cleandir / 'out.txt').read_text().strip() == '5'
378
380
  assert artifacts.logs is not None
379
381
  assert not artifacts.logs.cached
380
382
 
381
383
  pathlib.Path('out.txt').unlink()
382
384
 
383
- another_artifacts = configure_and_run()
385
+ another_artifacts = await configure_and_run()
384
386
  assert (cleandir / 'out.txt').read_text().strip() == '5'
385
387
  assert another_artifacts.logs is not None
386
388
  assert not another_artifacts.logs.cached
387
389
 
388
390
 
389
- def test_run_misses_when_input_file_changes(
391
+ async def test_run_misses_when_input_file_changes(
390
392
  cleandir: pathlib.Path,
391
393
  dependency_cache: DependencyCache,
392
394
  sandbox: SandboxBase,
393
395
  file_cacher: FileCacher,
394
396
  ):
395
- def configure_and_run(number: int) -> GradingArtifacts:
397
+ async def configure_and_run(number: int) -> GradingArtifacts:
396
398
  executable = DigestOrSource.create(
397
399
  file_cacher.put_file_text(f'print({number})')
398
400
  )
@@ -407,7 +409,7 @@ def test_run_misses_when_input_file_changes(
407
409
  hash=False,
408
410
  )
409
411
  )
410
- steps_with_caching.run(
412
+ await steps_with_caching.run(
411
413
  f'{sys.executable} executable.py',
412
414
  params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
413
415
  sandbox=sandbox,
@@ -416,14 +418,14 @@ def test_run_misses_when_input_file_changes(
416
418
  )
417
419
  return artifacts
418
420
 
419
- artifacts = configure_and_run(5)
421
+ artifacts = await configure_and_run(5)
420
422
  assert (cleandir / 'out.txt').read_text().strip() == '5'
421
423
  assert artifacts.logs is not None
422
424
  assert not artifacts.logs.cached
423
425
 
424
426
  pathlib.Path('out.txt').write_text('42')
425
427
 
426
- another_artifacts = configure_and_run(42)
428
+ another_artifacts = await configure_and_run(42)
427
429
  assert (cleandir / 'out.txt').read_text().strip() == '42'
428
430
  assert another_artifacts.logs is not None
429
431
  assert not another_artifacts.logs.cached