rbx.cp 0.5.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.
- rbx/__init__.py +0 -0
- rbx/annotations.py +127 -0
- rbx/autoenum.py +333 -0
- rbx/box/__init__.py +0 -0
- rbx/box/builder.py +77 -0
- rbx/box/cd.py +37 -0
- rbx/box/checkers.py +134 -0
- rbx/box/code.py +185 -0
- rbx/box/compile.py +56 -0
- rbx/box/conftest.py +42 -0
- rbx/box/contest/__init__.py +0 -0
- rbx/box/contest/build_contest_statements.py +347 -0
- rbx/box/contest/contest_package.py +76 -0
- rbx/box/contest/contest_utils.py +20 -0
- rbx/box/contest/main.py +179 -0
- rbx/box/contest/schema.py +155 -0
- rbx/box/contest/statements.py +82 -0
- rbx/box/creation.py +72 -0
- rbx/box/download.py +64 -0
- rbx/box/environment.py +345 -0
- rbx/box/extensions.py +26 -0
- rbx/box/generators.py +478 -0
- rbx/box/generators_test.py +63 -0
- rbx/box/main.py +449 -0
- rbx/box/package.py +316 -0
- rbx/box/packaging/boca/extension.py +27 -0
- rbx/box/packaging/boca/packager.py +245 -0
- rbx/box/packaging/contest_main.py +82 -0
- rbx/box/packaging/main.py +68 -0
- rbx/box/packaging/packager.py +117 -0
- rbx/box/packaging/polygon/packager.py +320 -0
- rbx/box/packaging/polygon/test.py +81 -0
- rbx/box/packaging/polygon/xml_schema.py +106 -0
- rbx/box/presets/__init__.py +503 -0
- rbx/box/presets/fetch.py +70 -0
- rbx/box/presets/lock_schema.py +20 -0
- rbx/box/presets/schema.py +59 -0
- rbx/box/schema.py +394 -0
- rbx/box/solutions.py +792 -0
- rbx/box/solutions_test.py +41 -0
- rbx/box/statements/__init__.py +0 -0
- rbx/box/statements/build_statements.py +359 -0
- rbx/box/statements/builders.py +375 -0
- rbx/box/statements/joiners.py +113 -0
- rbx/box/statements/latex.py +47 -0
- rbx/box/statements/latex_jinja.py +214 -0
- rbx/box/statements/schema.py +138 -0
- rbx/box/stresses.py +292 -0
- rbx/box/stressing/__init__.py +0 -0
- rbx/box/stressing/finder_parser.py +359 -0
- rbx/box/stressing/generator_parser.py +258 -0
- rbx/box/testcases.py +54 -0
- rbx/box/ui/__init__.py +0 -0
- rbx/box/ui/captured_log.py +372 -0
- rbx/box/ui/css/app.tcss +48 -0
- rbx/box/ui/main.py +38 -0
- rbx/box/ui/run.py +209 -0
- rbx/box/validators.py +245 -0
- rbx/box/validators_test.py +15 -0
- rbx/checker.py +128 -0
- rbx/clone.py +197 -0
- rbx/config.py +271 -0
- rbx/conftest.py +38 -0
- rbx/console.py +27 -0
- rbx/create.py +37 -0
- rbx/edit.py +24 -0
- rbx/grading/__init__.py +0 -0
- rbx/grading/caching.py +356 -0
- rbx/grading/conftest.py +33 -0
- rbx/grading/judge/__init__.py +0 -0
- rbx/grading/judge/cacher.py +503 -0
- rbx/grading/judge/digester.py +35 -0
- rbx/grading/judge/sandbox.py +748 -0
- rbx/grading/judge/sandboxes/__init__.py +0 -0
- rbx/grading/judge/sandboxes/isolate.py +683 -0
- rbx/grading/judge/sandboxes/stupid_sandbox.py +310 -0
- rbx/grading/judge/sandboxes/timeit.py +217 -0
- rbx/grading/judge/storage.py +284 -0
- rbx/grading/judge/test.py +38 -0
- rbx/grading/judge/testiso.py +54 -0
- rbx/grading/steps.py +522 -0
- rbx/grading/steps_with_caching.py +59 -0
- rbx/grading/steps_with_caching_run_test.py +429 -0
- rbx/grading_utils.py +148 -0
- rbx/hydration.py +101 -0
- rbx/main.py +122 -0
- rbx/metadata.py +105 -0
- rbx/providers/__init__.py +43 -0
- rbx/providers/codeforces.py +73 -0
- rbx/providers/provider.py +26 -0
- rbx/resources/checkers/boilerplate.cpp +20 -0
- rbx/resources/default_config.json +48 -0
- rbx/resources/envs/default.rbx.yml +37 -0
- rbx/resources/envs/isolate.rbx.yml +37 -0
- rbx/resources/packagers/boca/checker.sh +43 -0
- rbx/resources/packagers/boca/compare +53 -0
- rbx/resources/packagers/boca/compile/c +172 -0
- rbx/resources/packagers/boca/compile/cc +173 -0
- rbx/resources/packagers/boca/compile/cpp +172 -0
- rbx/resources/packagers/boca/compile/java +194 -0
- rbx/resources/packagers/boca/compile/kt +155 -0
- rbx/resources/packagers/boca/compile/pas +172 -0
- rbx/resources/packagers/boca/compile/py2 +173 -0
- rbx/resources/packagers/boca/compile/py3 +173 -0
- rbx/resources/packagers/boca/run/c +128 -0
- rbx/resources/packagers/boca/run/cc +128 -0
- rbx/resources/packagers/boca/run/cpp +128 -0
- rbx/resources/packagers/boca/run/java +194 -0
- rbx/resources/packagers/boca/run/kt +159 -0
- rbx/resources/packagers/boca/run/py2 +166 -0
- rbx/resources/packagers/boca/run/py3 +166 -0
- rbx/resources/presets/default/contest/contest.rbx.yml +14 -0
- rbx/resources/presets/default/contest/statement/contest.rbx.tex +97 -0
- rbx/resources/presets/default/contest/statement/olymp.sty +250 -0
- rbx/resources/presets/default/contest/statement/template.rbx.tex +42 -0
- rbx/resources/presets/default/preset.rbx.yml +12 -0
- rbx/resources/presets/default/problem/.gitignore +6 -0
- rbx/resources/presets/default/problem/gen.cpp +9 -0
- rbx/resources/presets/default/problem/problem.rbx.yml +44 -0
- rbx/resources/presets/default/problem/random.py +3 -0
- rbx/resources/presets/default/problem/random.txt +2 -0
- rbx/resources/presets/default/problem/sols/main.cpp +9 -0
- rbx/resources/presets/default/problem/sols/slow.cpp +15 -0
- rbx/resources/presets/default/problem/sols/wa.cpp +9 -0
- rbx/resources/presets/default/problem/statement/olymp.sty +250 -0
- rbx/resources/presets/default/problem/statement/projecao.png +0 -0
- rbx/resources/presets/default/problem/statement/statement.rbx.tex +18 -0
- rbx/resources/presets/default/problem/statement/template.rbx.tex +89 -0
- rbx/resources/presets/default/problem/tests/samples/000.in +1 -0
- rbx/resources/presets/default/problem/tests/samples/001.in +1 -0
- rbx/resources/presets/default/problem/validator.cpp +16 -0
- rbx/resources/presets/default/problem/wcmp.cpp +34 -0
- rbx/resources/templates/template.cpp +19 -0
- rbx/run.py +45 -0
- rbx/schema.py +64 -0
- rbx/submit.py +61 -0
- rbx/submitors/__init__.py +18 -0
- rbx/submitors/codeforces.py +120 -0
- rbx/submitors/submitor.py +25 -0
- rbx/test.py +347 -0
- rbx/testcase.py +70 -0
- rbx/testcase_rendering.py +79 -0
- rbx/testdata/box1/gen1.cpp +7 -0
- rbx/testdata/box1/gen2.cpp +9 -0
- rbx/testdata/box1/genScript.py +2 -0
- rbx/testdata/box1/hard-tle.sol.cpp +26 -0
- rbx/testdata/box1/ole.cpp +17 -0
- rbx/testdata/box1/problem.rbx.yml +39 -0
- rbx/testdata/box1/re.sol.cpp +23 -0
- rbx/testdata/box1/sol.cpp +22 -0
- rbx/testdata/box1/tests/1.in +1 -0
- rbx/testdata/box1/tle-and-incorrect.sol.cpp +33 -0
- rbx/testdata/box1/tle.sol.cpp +35 -0
- rbx/testdata/box1/validator.cpp +11 -0
- rbx/testdata/box1/wa.sol.cpp +22 -0
- rbx/testdata/caching/executable.py +1 -0
- rbx/testdata/compatible +0 -0
- rbx/testing_utils.py +65 -0
- rbx/utils.py +162 -0
- rbx_cp-0.5.0.dist-info/LICENSE +201 -0
- rbx_cp-0.5.0.dist-info/METADATA +89 -0
- rbx_cp-0.5.0.dist-info/RECORD +164 -0
- rbx_cp-0.5.0.dist-info/WHEEL +4 -0
- rbx_cp-0.5.0.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,429 @@
|
|
1
|
+
import os
|
2
|
+
import pathlib
|
3
|
+
import sys
|
4
|
+
|
5
|
+
from rbx.grading import steps_with_caching
|
6
|
+
from rbx.grading.caching import DependencyCache
|
7
|
+
from rbx.grading.judge.cacher import FileCacher
|
8
|
+
from rbx.grading.judge.sandbox import SandboxBase, SandboxParams
|
9
|
+
from rbx.grading.steps import (
|
10
|
+
DigestOrSource,
|
11
|
+
GradingArtifacts,
|
12
|
+
GradingFileInput,
|
13
|
+
GradingFileOutput,
|
14
|
+
RunLogMetadata,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
def test_run_from_digest(
|
19
|
+
cleandir: pathlib.Path,
|
20
|
+
dependency_cache: DependencyCache,
|
21
|
+
sandbox: SandboxBase,
|
22
|
+
file_cacher: FileCacher,
|
23
|
+
):
|
24
|
+
executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
|
25
|
+
artifacts = GradingArtifacts()
|
26
|
+
artifacts.inputs.append(
|
27
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
28
|
+
)
|
29
|
+
artifacts.outputs.append(
|
30
|
+
GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=pathlib.Path('out.txt'))
|
31
|
+
)
|
32
|
+
steps_with_caching.run(
|
33
|
+
f'{sys.executable} executable.py',
|
34
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
35
|
+
sandbox=sandbox,
|
36
|
+
artifacts=artifacts,
|
37
|
+
dependency_cache=dependency_cache,
|
38
|
+
metadata=RunLogMetadata(),
|
39
|
+
)
|
40
|
+
|
41
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
42
|
+
assert artifacts.logs is not None
|
43
|
+
assert artifacts.logs.run is not None
|
44
|
+
assert artifacts.logs.run.metadata is not None
|
45
|
+
assert not artifacts.logs.cached
|
46
|
+
|
47
|
+
|
48
|
+
def test_run_from_disk(
|
49
|
+
cleandir: pathlib.Path,
|
50
|
+
dependency_cache: DependencyCache,
|
51
|
+
sandbox: SandboxBase,
|
52
|
+
):
|
53
|
+
pathlib.Path('executable.py').write_text('print(42)')
|
54
|
+
|
55
|
+
executable = DigestOrSource.create(pathlib.Path('executable.py'))
|
56
|
+
artifacts = GradingArtifacts()
|
57
|
+
artifacts.inputs.append(
|
58
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
59
|
+
)
|
60
|
+
artifacts.outputs.append(
|
61
|
+
GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=pathlib.Path('out.txt'))
|
62
|
+
)
|
63
|
+
steps_with_caching.run(
|
64
|
+
f'{sys.executable} executable.py',
|
65
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
66
|
+
sandbox=sandbox,
|
67
|
+
artifacts=artifacts,
|
68
|
+
dependency_cache=dependency_cache,
|
69
|
+
)
|
70
|
+
|
71
|
+
assert (cleandir / 'out.txt').read_text().strip() == '42'
|
72
|
+
assert artifacts.logs is not None
|
73
|
+
assert artifacts.logs.run is not None
|
74
|
+
assert not artifacts.logs.cached
|
75
|
+
|
76
|
+
|
77
|
+
def test_run_caches_intermediate_digest_if_dest_changes(
|
78
|
+
cleandir: pathlib.Path,
|
79
|
+
dependency_cache: DependencyCache,
|
80
|
+
sandbox: SandboxBase,
|
81
|
+
file_cacher: FileCacher,
|
82
|
+
):
|
83
|
+
def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
|
84
|
+
executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
|
85
|
+
artifacts = GradingArtifacts()
|
86
|
+
artifacts.inputs.append(
|
87
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
88
|
+
)
|
89
|
+
artifacts.outputs.append(
|
90
|
+
GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=dest)
|
91
|
+
)
|
92
|
+
steps_with_caching.run(
|
93
|
+
f'{sys.executable} executable.py',
|
94
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
95
|
+
sandbox=sandbox,
|
96
|
+
artifacts=artifacts,
|
97
|
+
dependency_cache=dependency_cache,
|
98
|
+
)
|
99
|
+
return artifacts
|
100
|
+
|
101
|
+
artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
102
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
103
|
+
assert artifacts.logs is not None
|
104
|
+
assert not artifacts.logs.cached
|
105
|
+
|
106
|
+
another_artifacts = configure_and_run_with_dest(pathlib.Path('another-out.txt'))
|
107
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
108
|
+
assert another_artifacts.logs is not None
|
109
|
+
assert another_artifacts.logs.cached
|
110
|
+
|
111
|
+
|
112
|
+
def test_run_overwrite_changed_file_with_storage_value(
|
113
|
+
cleandir: pathlib.Path,
|
114
|
+
dependency_cache: DependencyCache,
|
115
|
+
sandbox: SandboxBase,
|
116
|
+
file_cacher: FileCacher,
|
117
|
+
):
|
118
|
+
def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
|
119
|
+
executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
|
120
|
+
artifacts = GradingArtifacts()
|
121
|
+
artifacts.inputs.append(
|
122
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
123
|
+
)
|
124
|
+
artifacts.outputs.append(
|
125
|
+
GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=dest)
|
126
|
+
)
|
127
|
+
steps_with_caching.run(
|
128
|
+
f'{sys.executable} executable.py',
|
129
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
130
|
+
sandbox=sandbox,
|
131
|
+
artifacts=artifacts,
|
132
|
+
dependency_cache=dependency_cache,
|
133
|
+
)
|
134
|
+
return artifacts
|
135
|
+
|
136
|
+
artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
137
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
138
|
+
assert artifacts.logs is not None
|
139
|
+
assert not artifacts.logs.cached
|
140
|
+
|
141
|
+
pathlib.Path('out.txt').write_text('42')
|
142
|
+
|
143
|
+
another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
144
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
145
|
+
assert another_artifacts.logs is not None
|
146
|
+
assert another_artifacts.logs.cached
|
147
|
+
|
148
|
+
|
149
|
+
def test_run_recreates_deleted_file_with_storage_value(
|
150
|
+
cleandir: pathlib.Path,
|
151
|
+
dependency_cache: DependencyCache,
|
152
|
+
sandbox: SandboxBase,
|
153
|
+
file_cacher: FileCacher,
|
154
|
+
):
|
155
|
+
def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
|
156
|
+
executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
|
157
|
+
artifacts = GradingArtifacts()
|
158
|
+
artifacts.inputs.append(
|
159
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
160
|
+
)
|
161
|
+
artifacts.outputs.append(
|
162
|
+
GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=dest)
|
163
|
+
)
|
164
|
+
steps_with_caching.run(
|
165
|
+
f'{sys.executable} executable.py',
|
166
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
167
|
+
sandbox=sandbox,
|
168
|
+
artifacts=artifacts,
|
169
|
+
dependency_cache=dependency_cache,
|
170
|
+
)
|
171
|
+
return artifacts
|
172
|
+
|
173
|
+
artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
174
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
175
|
+
assert artifacts.logs is not None
|
176
|
+
assert not artifacts.logs.cached
|
177
|
+
|
178
|
+
pathlib.Path('out.txt').unlink()
|
179
|
+
|
180
|
+
another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
181
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
182
|
+
assert another_artifacts.logs is not None
|
183
|
+
assert another_artifacts.logs.cached
|
184
|
+
|
185
|
+
|
186
|
+
def test_run_overwrite_exec_bit_when_changed(
|
187
|
+
cleandir: pathlib.Path,
|
188
|
+
dependency_cache: DependencyCache,
|
189
|
+
sandbox: SandboxBase,
|
190
|
+
file_cacher: FileCacher,
|
191
|
+
):
|
192
|
+
def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
|
193
|
+
executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
|
194
|
+
artifacts = GradingArtifacts()
|
195
|
+
artifacts.inputs.append(
|
196
|
+
GradingFileInput(
|
197
|
+
**executable.expand(),
|
198
|
+
dest=pathlib.Path('executable.py'),
|
199
|
+
)
|
200
|
+
)
|
201
|
+
artifacts.outputs.append(
|
202
|
+
GradingFileOutput(
|
203
|
+
src=pathlib.Path('box-out.txt'), dest=dest, executable=True
|
204
|
+
)
|
205
|
+
)
|
206
|
+
steps_with_caching.run(
|
207
|
+
f'{sys.executable} executable.py',
|
208
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
209
|
+
sandbox=sandbox,
|
210
|
+
artifacts=artifacts,
|
211
|
+
dependency_cache=dependency_cache,
|
212
|
+
)
|
213
|
+
return artifacts
|
214
|
+
|
215
|
+
artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
216
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
217
|
+
assert artifacts.logs is not None
|
218
|
+
assert not artifacts.logs.cached
|
219
|
+
assert os.access('out.txt', os.X_OK)
|
220
|
+
|
221
|
+
pathlib.Path('out.txt').chmod(0o644)
|
222
|
+
|
223
|
+
another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
224
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
225
|
+
assert another_artifacts.logs is not None
|
226
|
+
assert another_artifacts.logs.cached
|
227
|
+
assert os.access('out.txt', os.X_OK)
|
228
|
+
|
229
|
+
|
230
|
+
def test_run_evicts_when_changed_file_and_no_hash(
|
231
|
+
cleandir: pathlib.Path,
|
232
|
+
dependency_cache: DependencyCache,
|
233
|
+
sandbox: SandboxBase,
|
234
|
+
file_cacher: FileCacher,
|
235
|
+
):
|
236
|
+
def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
|
237
|
+
executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
|
238
|
+
artifacts = GradingArtifacts()
|
239
|
+
artifacts.inputs.append(
|
240
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
241
|
+
)
|
242
|
+
artifacts.outputs.append(
|
243
|
+
GradingFileOutput(src=pathlib.Path('box-out.txt'), dest=dest, hash=False)
|
244
|
+
)
|
245
|
+
steps_with_caching.run(
|
246
|
+
f'{sys.executable} executable.py',
|
247
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
248
|
+
sandbox=sandbox,
|
249
|
+
artifacts=artifacts,
|
250
|
+
dependency_cache=dependency_cache,
|
251
|
+
)
|
252
|
+
return artifacts
|
253
|
+
|
254
|
+
artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
255
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
256
|
+
assert artifacts.logs is not None
|
257
|
+
assert not artifacts.logs.cached
|
258
|
+
|
259
|
+
pathlib.Path('out.txt').write_text('42')
|
260
|
+
|
261
|
+
another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
262
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
263
|
+
assert another_artifacts.logs is not None
|
264
|
+
assert not another_artifacts.logs.cached
|
265
|
+
|
266
|
+
|
267
|
+
def test_run_evicts_when_exec_bit_different_and_no_hash(
|
268
|
+
cleandir: pathlib.Path,
|
269
|
+
dependency_cache: DependencyCache,
|
270
|
+
sandbox: SandboxBase,
|
271
|
+
file_cacher: FileCacher,
|
272
|
+
):
|
273
|
+
def configure_and_run_with_dest(dest: pathlib.Path) -> GradingArtifacts:
|
274
|
+
executable = DigestOrSource.create(file_cacher.put_file_text('print(5)'))
|
275
|
+
artifacts = GradingArtifacts()
|
276
|
+
artifacts.inputs.append(
|
277
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
278
|
+
)
|
279
|
+
artifacts.outputs.append(
|
280
|
+
GradingFileOutput(
|
281
|
+
src=pathlib.Path('box-out.txt'), dest=dest, hash=False, executable=True
|
282
|
+
)
|
283
|
+
)
|
284
|
+
steps_with_caching.run(
|
285
|
+
f'{sys.executable} executable.py',
|
286
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
287
|
+
sandbox=sandbox,
|
288
|
+
artifacts=artifacts,
|
289
|
+
dependency_cache=dependency_cache,
|
290
|
+
)
|
291
|
+
return artifacts
|
292
|
+
|
293
|
+
artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
294
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
295
|
+
assert artifacts.logs is not None
|
296
|
+
assert not artifacts.logs.cached
|
297
|
+
|
298
|
+
pathlib.Path('out.txt').chmod(0o0644)
|
299
|
+
|
300
|
+
another_artifacts = configure_and_run_with_dest(pathlib.Path('out.txt'))
|
301
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
302
|
+
assert another_artifacts.logs is not None
|
303
|
+
assert not another_artifacts.logs.cached
|
304
|
+
|
305
|
+
|
306
|
+
def test_run_evicts_when_input_fingerprint_changes(
|
307
|
+
cleandir: pathlib.Path,
|
308
|
+
dependency_cache: DependencyCache,
|
309
|
+
sandbox: SandboxBase,
|
310
|
+
):
|
311
|
+
def configure_and_run() -> GradingArtifacts:
|
312
|
+
executable = DigestOrSource.create(pathlib.Path('executable.py'))
|
313
|
+
artifacts = GradingArtifacts()
|
314
|
+
artifacts.inputs.append(
|
315
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
316
|
+
)
|
317
|
+
artifacts.outputs.append(
|
318
|
+
GradingFileOutput(
|
319
|
+
src=pathlib.Path('box-out.txt'),
|
320
|
+
dest=pathlib.Path('out.txt'),
|
321
|
+
)
|
322
|
+
)
|
323
|
+
steps_with_caching.run(
|
324
|
+
f'{sys.executable} executable.py',
|
325
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
326
|
+
sandbox=sandbox,
|
327
|
+
artifacts=artifacts,
|
328
|
+
dependency_cache=dependency_cache,
|
329
|
+
)
|
330
|
+
return artifacts
|
331
|
+
|
332
|
+
pathlib.Path('executable.py').write_text('print(5)')
|
333
|
+
|
334
|
+
artifacts = configure_and_run()
|
335
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
336
|
+
assert artifacts.logs is not None
|
337
|
+
assert not artifacts.logs.cached
|
338
|
+
|
339
|
+
pathlib.Path('executable.py').write_text('print(42)')
|
340
|
+
|
341
|
+
another_artifacts = configure_and_run()
|
342
|
+
assert (cleandir / 'out.txt').read_text().strip() == '42'
|
343
|
+
assert another_artifacts.logs is not None
|
344
|
+
assert not another_artifacts.logs.cached
|
345
|
+
|
346
|
+
|
347
|
+
def test_run_evicts_when_output_is_deleted_and_no_hash(
|
348
|
+
cleandir: pathlib.Path,
|
349
|
+
dependency_cache: DependencyCache,
|
350
|
+
sandbox: SandboxBase,
|
351
|
+
):
|
352
|
+
def configure_and_run() -> GradingArtifacts:
|
353
|
+
executable = DigestOrSource.create(pathlib.Path('executable.py'))
|
354
|
+
artifacts = GradingArtifacts()
|
355
|
+
artifacts.inputs.append(
|
356
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
357
|
+
)
|
358
|
+
artifacts.outputs.append(
|
359
|
+
GradingFileOutput(
|
360
|
+
src=pathlib.Path('box-out.txt'),
|
361
|
+
dest=pathlib.Path('out.txt'),
|
362
|
+
hash=False,
|
363
|
+
)
|
364
|
+
)
|
365
|
+
steps_with_caching.run(
|
366
|
+
f'{sys.executable} executable.py',
|
367
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
368
|
+
sandbox=sandbox,
|
369
|
+
artifacts=artifacts,
|
370
|
+
dependency_cache=dependency_cache,
|
371
|
+
)
|
372
|
+
return artifacts
|
373
|
+
|
374
|
+
pathlib.Path('executable.py').write_text('print(5)')
|
375
|
+
|
376
|
+
artifacts = configure_and_run()
|
377
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
378
|
+
assert artifacts.logs is not None
|
379
|
+
assert not artifacts.logs.cached
|
380
|
+
|
381
|
+
pathlib.Path('out.txt').unlink()
|
382
|
+
|
383
|
+
another_artifacts = configure_and_run()
|
384
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
385
|
+
assert another_artifacts.logs is not None
|
386
|
+
assert not another_artifacts.logs.cached
|
387
|
+
|
388
|
+
|
389
|
+
def test_run_misses_when_input_file_changes(
|
390
|
+
cleandir: pathlib.Path,
|
391
|
+
dependency_cache: DependencyCache,
|
392
|
+
sandbox: SandboxBase,
|
393
|
+
file_cacher: FileCacher,
|
394
|
+
):
|
395
|
+
def configure_and_run(number: int) -> GradingArtifacts:
|
396
|
+
executable = DigestOrSource.create(
|
397
|
+
file_cacher.put_file_text(f'print({number})')
|
398
|
+
)
|
399
|
+
artifacts = GradingArtifacts()
|
400
|
+
artifacts.inputs.append(
|
401
|
+
GradingFileInput(**executable.expand(), dest=pathlib.Path('executable.py'))
|
402
|
+
)
|
403
|
+
artifacts.outputs.append(
|
404
|
+
GradingFileOutput(
|
405
|
+
src=pathlib.Path('box-out.txt'),
|
406
|
+
dest=pathlib.Path('out.txt'),
|
407
|
+
hash=False,
|
408
|
+
)
|
409
|
+
)
|
410
|
+
steps_with_caching.run(
|
411
|
+
f'{sys.executable} executable.py',
|
412
|
+
params=SandboxParams(stdout_file=pathlib.Path('box-out.txt')),
|
413
|
+
sandbox=sandbox,
|
414
|
+
artifacts=artifacts,
|
415
|
+
dependency_cache=dependency_cache,
|
416
|
+
)
|
417
|
+
return artifacts
|
418
|
+
|
419
|
+
artifacts = configure_and_run(5)
|
420
|
+
assert (cleandir / 'out.txt').read_text().strip() == '5'
|
421
|
+
assert artifacts.logs is not None
|
422
|
+
assert not artifacts.logs.cached
|
423
|
+
|
424
|
+
pathlib.Path('out.txt').write_text('42')
|
425
|
+
|
426
|
+
another_artifacts = configure_and_run(42)
|
427
|
+
assert (cleandir / 'out.txt').read_text().strip() == '42'
|
428
|
+
assert another_artifacts.logs is not None
|
429
|
+
assert not another_artifacts.logs.cached
|
rbx/grading_utils.py
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
import pathlib
|
2
|
+
from pathlib import PosixPath
|
3
|
+
from typing import List
|
4
|
+
|
5
|
+
from rbx import config
|
6
|
+
from rbx.config import Artifact, Language, format_vars
|
7
|
+
from rbx.grading import steps
|
8
|
+
from rbx.grading.judge.sandbox import MERGE_STDERR, SandboxParams
|
9
|
+
from rbx.grading.steps import (
|
10
|
+
GradingArtifacts,
|
11
|
+
GradingFileInput,
|
12
|
+
GradingFileOutput,
|
13
|
+
TestcaseIO,
|
14
|
+
)
|
15
|
+
from rbx.schema import DumpedProblem, Problem
|
16
|
+
|
17
|
+
|
18
|
+
def build_formatted_command(
|
19
|
+
command: str, problem: DumpedProblem, lang: Language
|
20
|
+
) -> str:
|
21
|
+
return format_vars(
|
22
|
+
command,
|
23
|
+
**problem.get_vars(),
|
24
|
+
file=lang.get_file(problem.code),
|
25
|
+
submit_file=lang.get_submit_file(problem.code),
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
def build_preprocess_commands(problem: DumpedProblem, lang: Language) -> List[str]:
|
30
|
+
return [
|
31
|
+
build_formatted_command(cmd, problem, lang) for cmd in (lang.preprocess or [])
|
32
|
+
]
|
33
|
+
|
34
|
+
|
35
|
+
def build_preprocess_sandbox_params() -> SandboxParams:
|
36
|
+
params = SandboxParams(
|
37
|
+
max_processes=None,
|
38
|
+
preserve_env=True,
|
39
|
+
)
|
40
|
+
params.add_mapped_directory(pathlib.Path('/usr'))
|
41
|
+
params.add_mapped_directory(pathlib.Path('/etc'))
|
42
|
+
return params
|
43
|
+
|
44
|
+
|
45
|
+
def build_compile_grading_artifacts(
|
46
|
+
problem: DumpedProblem, lang: Language
|
47
|
+
) -> GradingArtifacts:
|
48
|
+
res = GradingArtifacts(root=PosixPath('.'))
|
49
|
+
file = lang.get_file(problem.code)
|
50
|
+
submit_file = lang.get_submit_file(problem.code)
|
51
|
+
# Copy input file.
|
52
|
+
res.inputs.append(GradingFileInput(src=PosixPath(file), dest=PosixPath(file)))
|
53
|
+
# Copy output file.
|
54
|
+
if lang.has_submit_file():
|
55
|
+
res.outputs.append(
|
56
|
+
GradingFileOutput(
|
57
|
+
src=PosixPath(submit_file),
|
58
|
+
dest=PosixPath(submit_file),
|
59
|
+
)
|
60
|
+
)
|
61
|
+
# Copy other artifacts.
|
62
|
+
for artifact_name, artifact_cfg in lang.artifacts.items():
|
63
|
+
artifact_cfg = artifact_cfg or Artifact()
|
64
|
+
artifact_path = format_vars(
|
65
|
+
artifact_name, **problem.get_vars(), file=file, submit_file=submit_file
|
66
|
+
)
|
67
|
+
res.outputs.append(
|
68
|
+
GradingFileOutput(
|
69
|
+
src=PosixPath(artifact_path),
|
70
|
+
dest=PosixPath(artifact_cfg.filename or artifact_path),
|
71
|
+
optional=artifact_cfg.optional,
|
72
|
+
executable=artifact_cfg.executable,
|
73
|
+
)
|
74
|
+
)
|
75
|
+
|
76
|
+
return res
|
77
|
+
|
78
|
+
|
79
|
+
def build_run_sandbox_params(problem: Problem, has_input: bool) -> SandboxParams:
|
80
|
+
params = SandboxParams()
|
81
|
+
params.timeout = problem.timeLimit * 2
|
82
|
+
params.wallclock_timeout = problem.timeLimit * 5
|
83
|
+
params.address_space = problem.memoryLimit or 1024 # 1 GB
|
84
|
+
params.set_stdall(
|
85
|
+
stdin=PosixPath('stdin.txt') if has_input else None,
|
86
|
+
stdout=PosixPath('stdout.txt'),
|
87
|
+
stderr=MERGE_STDERR,
|
88
|
+
)
|
89
|
+
return params
|
90
|
+
|
91
|
+
|
92
|
+
def build_run_grading_artifacts(
|
93
|
+
testcase: TestcaseIO, persist_root: pathlib.Path
|
94
|
+
) -> GradingArtifacts:
|
95
|
+
res = GradingArtifacts(root=PosixPath('.'))
|
96
|
+
res.inputs.append(
|
97
|
+
GradingFileInput(
|
98
|
+
src=testcase.input,
|
99
|
+
dest=PosixPath('stdin.txt'),
|
100
|
+
)
|
101
|
+
)
|
102
|
+
res.outputs.append(
|
103
|
+
GradingFileOutput(
|
104
|
+
src=PosixPath('stdout.txt'),
|
105
|
+
dest=persist_root / f'stdout-{testcase.index}.txt',
|
106
|
+
maxlen=steps.MAX_STDOUT_LEN,
|
107
|
+
)
|
108
|
+
)
|
109
|
+
return res
|
110
|
+
|
111
|
+
|
112
|
+
def build_checker_compile_grading_artifacts(
|
113
|
+
problem: DumpedProblem, persist_root: pathlib.Path
|
114
|
+
) -> GradingArtifacts:
|
115
|
+
res = GradingArtifacts(root=PosixPath('.'))
|
116
|
+
if not problem.checker:
|
117
|
+
return res
|
118
|
+
|
119
|
+
checker_path = PosixPath(problem.checker)
|
120
|
+
if not checker_path.is_file():
|
121
|
+
checker_path = config.get_builtin_checker(problem.checker)
|
122
|
+
if not checker_path:
|
123
|
+
return res
|
124
|
+
|
125
|
+
res.inputs.append(GradingFileInput(src=checker_path, dest=PosixPath('checker.cpp')))
|
126
|
+
testlib = config.get_testlib()
|
127
|
+
if testlib.is_file():
|
128
|
+
res.inputs.append(GradingFileInput(src=testlib, dest=PosixPath('testlib.h')))
|
129
|
+
res.outputs.append(
|
130
|
+
GradingFileOutput(
|
131
|
+
src=PosixPath('checker'), dest=persist_root / 'checker', executable=True
|
132
|
+
)
|
133
|
+
)
|
134
|
+
return res
|
135
|
+
|
136
|
+
|
137
|
+
def build_checker_run_grading_artifacts(
|
138
|
+
problem: DumpedProblem, persist_root: pathlib.Path
|
139
|
+
) -> GradingArtifacts:
|
140
|
+
res = GradingArtifacts(root=PosixPath('.'))
|
141
|
+
if not problem.checker:
|
142
|
+
return res
|
143
|
+
res.inputs.append(
|
144
|
+
GradingFileInput(
|
145
|
+
src=persist_root / 'checker', dest=PosixPath('checker'), executable=True
|
146
|
+
)
|
147
|
+
)
|
148
|
+
return res
|
rbx/hydration.py
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
import pathlib
|
2
|
+
from typing import List, Optional, Tuple
|
3
|
+
|
4
|
+
from rbx import config, hydration, metadata
|
5
|
+
from rbx.console import console
|
6
|
+
from rbx.schema import DumpedProblem, Testcase
|
7
|
+
from rbx.test import get_testcases_io
|
8
|
+
|
9
|
+
|
10
|
+
def get_testcase_paths(
|
11
|
+
root: pathlib.Path, problem: DumpedProblem, i: int
|
12
|
+
) -> Tuple[pathlib.Path, pathlib.Path]:
|
13
|
+
return (root / f'{problem.code}.{i}.in', root / f'{problem.code}.{i}.out')
|
14
|
+
|
15
|
+
|
16
|
+
def hydrate_problem(root: pathlib.Path, problem: DumpedProblem):
|
17
|
+
for i, testcase in enumerate(problem.tests or []):
|
18
|
+
in_path, out_path = get_testcase_paths(root, problem, i)
|
19
|
+
in_path.write_text(testcase.input)
|
20
|
+
out_path.write_text(testcase.output)
|
21
|
+
|
22
|
+
|
23
|
+
def add_testcase(root: pathlib.Path, problem: DumpedProblem, testcase: Testcase):
|
24
|
+
problem_path = metadata.find_problem_path_by_code(problem.code, root)
|
25
|
+
if not problem_path or not problem_path.is_file():
|
26
|
+
console.print(
|
27
|
+
f'[error]Problem [item]{problem.pretty_name()}[/item] not found.[/error]'
|
28
|
+
)
|
29
|
+
return
|
30
|
+
|
31
|
+
# Pick next number.
|
32
|
+
i = max([tc.index for tc in get_testcases_io(problem, root)] + [-1]) + 1
|
33
|
+
in_path, out_path = get_testcase_paths(root, problem, i)
|
34
|
+
in_path.write_text(testcase.input)
|
35
|
+
out_path.write_text(testcase.output)
|
36
|
+
|
37
|
+
console.print(
|
38
|
+
f'Added testcase [item]{i}[/item] to problem [item]{problem.pretty_name()}[/item].'
|
39
|
+
)
|
40
|
+
|
41
|
+
|
42
|
+
def remove_testcase(root: pathlib.Path, problem: DumpedProblem, i: int):
|
43
|
+
problem_path = metadata.find_problem_path_by_code(problem.code, root)
|
44
|
+
if not problem_path or not problem_path.is_file():
|
45
|
+
console.print(
|
46
|
+
f'[error]Problem [item]{problem.pretty_name()}[/item] not found.[/error]'
|
47
|
+
)
|
48
|
+
return
|
49
|
+
|
50
|
+
testcases = get_testcases_io(problem, root)
|
51
|
+
testcases = [testcase for testcase in testcases if testcase.index == i]
|
52
|
+
if not testcases:
|
53
|
+
console.print(
|
54
|
+
f'[error]Testcase [item]{i}[/item] not found in problem [item]{problem.pretty_name()}[/item].[/error]'
|
55
|
+
)
|
56
|
+
return
|
57
|
+
if testcases[0].input:
|
58
|
+
testcases[0].input.unlink(missing_ok=True)
|
59
|
+
if testcases[0].output:
|
60
|
+
testcases[0].output.unlink(missing_ok=True)
|
61
|
+
|
62
|
+
console.print(
|
63
|
+
f'Removed testcase [item]{i}[/item] from problem [item]{problem.pretty_name()}[/item].'
|
64
|
+
)
|
65
|
+
|
66
|
+
|
67
|
+
def edit_testcase(root: pathlib.Path, problem: DumpedProblem, i: int):
|
68
|
+
problem_path = metadata.find_problem_path_by_code(problem.code, root)
|
69
|
+
if not problem_path or not problem_path.is_file():
|
70
|
+
console.print(
|
71
|
+
f'[error]Problem [item]{problem.pretty_name()}[/item] not found.[/error]'
|
72
|
+
)
|
73
|
+
return
|
74
|
+
|
75
|
+
testcases = get_testcases_io(problem, root)
|
76
|
+
testcases = [testcase for testcase in testcases if testcase.index == i]
|
77
|
+
if not testcases:
|
78
|
+
console.print(
|
79
|
+
f'[error]Testcase [item]{i}[/item] not found in problem [item]{problem.pretty_name()}[/item].[/error]'
|
80
|
+
)
|
81
|
+
return
|
82
|
+
|
83
|
+
paths: List[Optional[pathlib.Path]] = [testcases[0].input, testcases[0].output]
|
84
|
+
config.open_editor(*[path for path in paths if path is not None and path.is_file()])
|
85
|
+
|
86
|
+
|
87
|
+
def main(problem: Optional[str] = None):
|
88
|
+
problems_to_hydrate = []
|
89
|
+
if not problem:
|
90
|
+
problems_to_hydrate = metadata.find_problems()
|
91
|
+
else:
|
92
|
+
dumped_problem = metadata.find_problem_by_anything(problem)
|
93
|
+
problems_to_hydrate.append(dumped_problem)
|
94
|
+
|
95
|
+
root = pathlib.Path()
|
96
|
+
|
97
|
+
for dumped_problem in problems_to_hydrate:
|
98
|
+
console.print(
|
99
|
+
f'Hydrating problem [item]{dumped_problem.pretty_name()}[/item]...'
|
100
|
+
)
|
101
|
+
hydration.hydrate_problem(root, dumped_problem)
|