rbx.cp 0.8.0__py3-none-any.whl → 0.9.1__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/box/cd.py +2 -2
- rbx/box/cli.py +11 -2
- rbx/box/code.py +2 -2
- rbx/box/contest/build_contest_statements.py +2 -2
- rbx/box/contest/contest_package.py +1 -1
- rbx/box/contest/main.py +29 -2
- rbx/box/environment.py +143 -83
- rbx/box/formatting.py +2 -1
- rbx/box/package.py +5 -5
- rbx/box/packaging/__init__.py +0 -0
- rbx/box/packaging/boca/__init__.py +0 -0
- rbx/box/packaging/polygon/packager.py +3 -3
- rbx/box/presets/__init__.py +369 -53
- rbx/box/presets/lock_schema.py +42 -2
- rbx/box/presets/schema.py +4 -0
- rbx/box/remote.py +3 -3
- rbx/box/retries.py +3 -2
- rbx/box/sanitizers/warning_stack.py +2 -2
- rbx/box/setter_config.py +18 -0
- rbx/box/solutions.py +24 -18
- rbx/box/statements/build_statements.py +6 -6
- rbx/box/statements/builders.py +1 -1
- rbx/box/stresses.py +2 -2
- rbx/box/testcase_utils.py +3 -3
- rbx/config.py +7 -0
- rbx/grading/caching.py +24 -2
- rbx/grading/conftest.py +2 -2
- rbx/grading/grading_context.py +25 -0
- rbx/grading/judge/sandbox.py +2 -1
- rbx/grading/judge/sandboxes/isolate.py +3 -2
- rbx/grading/judge/sandboxes/stupid_sandbox.py +3 -2
- rbx/grading/judge/storage.py +2 -1
- rbx/grading/steps.py +2 -1
- rbx/grading/steps_with_caching_run_test.py +281 -5
- rbx/resources/default_setter_config.mac.yml +15 -0
- rbx/resources/default_setter_config.yml +15 -0
- rbx/resources/envs/default.rbx.yml +43 -13
- rbx/resources/envs/isolate.rbx.yml +2 -3
- rbx/resources/presets/default/contest/.gitignore +5 -1
- rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -1
- rbx/resources/presets/default/env.rbx.yml +67 -0
- rbx/resources/presets/default/preset.rbx.yml +6 -2
- rbx/resources/presets/default/problem/.gitignore +1 -1
- rbx/resources/presets/default/{contest/statement/template.rbx.tex → shared/problem_template.rbx.tex} +13 -7
- rbx/submitors/codeforces.py +3 -2
- rbx/test.py +1 -1
- rbx/utils.py +6 -1
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/METADATA +2 -1
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/RECORD +54 -52
- rbx/resources/presets/default/problem/statement/icpc.sty +0 -322
- /rbx/resources/presets/default/{problem/statement/template.rbx.tex → shared/contest_template.rbx.tex} +0 -0
- /rbx/resources/presets/default/{contest/statement → shared}/icpc.sty +0 -0
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/LICENSE +0 -0
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/WHEEL +0 -0
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/entry_points.txt +0 -0
rbx/box/remote.py
CHANGED
@@ -5,7 +5,7 @@ from typing import List, Optional, Tuple, Union
|
|
5
5
|
|
6
6
|
import typer
|
7
7
|
|
8
|
-
from rbx import console
|
8
|
+
from rbx import console, utils
|
9
9
|
from rbx.box import cd, package
|
10
10
|
from rbx.box.formatting import href, ref
|
11
11
|
|
@@ -85,7 +85,7 @@ REGISTERED_EXPANDERS: List['Expander'] = [
|
|
85
85
|
|
86
86
|
|
87
87
|
def _relative_to_pkg(path: pathlib.Path) -> pathlib.Path:
|
88
|
-
return
|
88
|
+
return utils.abspath(path).relative_to(pathlib.Path.cwd())
|
89
89
|
|
90
90
|
|
91
91
|
def _try_cacheable_paths(
|
@@ -167,4 +167,4 @@ def expand_file(file: str) -> pathlib.Path:
|
|
167
167
|
|
168
168
|
def is_path_remote(path: pathlib.Path) -> bool:
|
169
169
|
remote_dir = package.get_problem_remote_dir()
|
170
|
-
return
|
170
|
+
return utils.abspath(path).is_relative_to(utils.abspath(remote_dir))
|
rbx/box/retries.py
CHANGED
@@ -5,6 +5,7 @@ import tempfile
|
|
5
5
|
from contextlib import contextmanager
|
6
6
|
from typing import Awaitable, Callable, List, Optional
|
7
7
|
|
8
|
+
from rbx import utils
|
8
9
|
from rbx.box import package
|
9
10
|
from rbx.box.setter_config import RepeatsConfig, get_setter_config
|
10
11
|
from rbx.grading.steps import Evaluation, Outcome
|
@@ -59,8 +60,8 @@ class FileToRecover:
|
|
59
60
|
|
60
61
|
def _move_to_temp_dir(path: pathlib.Path, temp_dir: pathlib.Path) -> FileToRecover:
|
61
62
|
problem_path = package.find_problem()
|
62
|
-
path =
|
63
|
-
temp_dir =
|
63
|
+
path = utils.abspath(path)
|
64
|
+
temp_dir = utils.abspath(temp_dir)
|
64
65
|
relative = path.relative_to(problem_path)
|
65
66
|
|
66
67
|
temp_path = temp_dir / relative
|
@@ -2,7 +2,7 @@ import functools
|
|
2
2
|
import pathlib
|
3
3
|
import shutil
|
4
4
|
|
5
|
-
from rbx import console
|
5
|
+
from rbx import console, utils
|
6
6
|
from rbx.box.schema import CodeItem
|
7
7
|
from rbx.grading.judge.cacher import FileCacher
|
8
8
|
from rbx.grading.steps import GradingFileOutput
|
@@ -60,7 +60,7 @@ def _get_warning_runs_dir(root: pathlib.Path) -> pathlib.Path:
|
|
60
60
|
|
61
61
|
|
62
62
|
def get_warning_stack() -> WarningStack:
|
63
|
-
current_root = pathlib.Path.cwd()
|
63
|
+
current_root = utils.abspath(pathlib.Path.cwd())
|
64
64
|
return _get_warning_stack(current_root)
|
65
65
|
|
66
66
|
|
rbx/box/setter_config.py
CHANGED
@@ -9,6 +9,7 @@ import typer
|
|
9
9
|
from pydantic import BaseModel, Field
|
10
10
|
|
11
11
|
from rbx import config, console, utils
|
12
|
+
from rbx.grading.grading_context import CacheLevel
|
12
13
|
|
13
14
|
app = typer.Typer(no_args_is_help=True)
|
14
15
|
|
@@ -52,6 +53,19 @@ class RepeatsConfig(BaseModel):
|
|
52
53
|
)
|
53
54
|
|
54
55
|
|
56
|
+
class CachingConfig(BaseModel):
|
57
|
+
level: CacheLevel = Field(
|
58
|
+
default=CacheLevel.CACHE_ALL,
|
59
|
+
description='Whether to enable caching and which caching level to use.',
|
60
|
+
)
|
61
|
+
|
62
|
+
check_integrity: bool = Field(
|
63
|
+
default=True,
|
64
|
+
description='Whether to check the integrity of the cached result, and evict it'
|
65
|
+
'if file has changed since it was cached.',
|
66
|
+
)
|
67
|
+
|
68
|
+
|
55
69
|
class SetterConfig(BaseModel):
|
56
70
|
sanitizers: SanitizersConfig = Field(
|
57
71
|
default_factory=SanitizersConfig, # type: ignore
|
@@ -75,6 +89,10 @@ class SetterConfig(BaseModel):
|
|
75
89
|
default=True,
|
76
90
|
description='Whether to use hyperlinks in the terminal output.',
|
77
91
|
)
|
92
|
+
caching: CachingConfig = Field(
|
93
|
+
default_factory=CachingConfig, # type: ignore
|
94
|
+
description='Configuration for caching.',
|
95
|
+
)
|
78
96
|
|
79
97
|
def substitute_command(self, command: str, sanitized: bool = False) -> str:
|
80
98
|
exe = shlex.split(command)[0]
|
rbx/box/solutions.py
CHANGED
@@ -54,6 +54,7 @@ from rbx.box.testcase_utils import (
|
|
54
54
|
parse_interaction,
|
55
55
|
print_interaction,
|
56
56
|
)
|
57
|
+
from rbx.grading import grading_context
|
57
58
|
from rbx.grading.limits import Limits
|
58
59
|
from rbx.grading.steps import (
|
59
60
|
Evaluation,
|
@@ -498,7 +499,7 @@ async def _generate_testcase_interactively(
|
|
498
499
|
console.console.print(testcase.inputPath.read_text())
|
499
500
|
else:
|
500
501
|
console.console.print(
|
501
|
-
f'Input was written to [item]{testcase.inputPath
|
502
|
+
f'Input was written to [item]{utils.abspath(testcase.inputPath)}[/item]'
|
502
503
|
)
|
503
504
|
console.console.print()
|
504
505
|
|
@@ -660,23 +661,28 @@ async def run_and_print_interactive_solutions(
|
|
660
661
|
tracked_solutions,
|
661
662
|
verification=verification,
|
662
663
|
)
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
664
|
+
|
665
|
+
should_cache = testcase_entry is not None
|
666
|
+
with grading_context.cache_level(
|
667
|
+
grading_context.CacheLevel.CACHE_COMPILATION, when=not should_cache
|
668
|
+
):
|
669
|
+
testcase = await _generate_testcase_interactively(
|
670
|
+
progress=progress,
|
671
|
+
generator=generator,
|
672
|
+
testcase_entry=testcase_entry,
|
673
|
+
check=check,
|
674
|
+
custom_output=custom_output,
|
675
|
+
sanitized=sanitized,
|
676
|
+
print=print,
|
677
|
+
)
|
678
|
+
items = _run_interactive_solutions(
|
679
|
+
testcase,
|
680
|
+
skeleton=skeleton,
|
681
|
+
progress=progress,
|
682
|
+
verification=verification,
|
683
|
+
check=check,
|
684
|
+
sanitized=sanitized,
|
685
|
+
)
|
680
686
|
|
681
687
|
for item in items:
|
682
688
|
sol = skeleton.find_solution_skeleton(item.solution)
|
@@ -6,7 +6,7 @@ from typing import Annotated, Any, Dict, List, Optional, Tuple
|
|
6
6
|
import syncer
|
7
7
|
import typer
|
8
8
|
|
9
|
-
from rbx import annotations, console
|
9
|
+
from rbx import annotations, console, utils
|
10
10
|
from rbx.box import environment, naming, package
|
11
11
|
from rbx.box.formatting import href
|
12
12
|
from rbx.box.schema import Package, expand_any_vars
|
@@ -46,7 +46,7 @@ def get_environment_languages_for_statement() -> List[StatementCodeLanguage]:
|
|
46
46
|
res.append(
|
47
47
|
StatementCodeLanguage(
|
48
48
|
id=language.name,
|
49
|
-
name=language.
|
49
|
+
name=language.readableName or language.name,
|
50
50
|
command=cmd or '',
|
51
51
|
)
|
52
52
|
)
|
@@ -173,7 +173,7 @@ def get_relative_assets(
|
|
173
173
|
relative_to: pathlib.Path,
|
174
174
|
assets: List[str],
|
175
175
|
) -> List[Tuple[pathlib.Path, pathlib.Path]]:
|
176
|
-
relative_to =
|
176
|
+
relative_to = utils.abspath(relative_to)
|
177
177
|
if not relative_to.is_dir():
|
178
178
|
relative_to = relative_to.parent
|
179
179
|
res = []
|
@@ -192,7 +192,7 @@ def get_relative_assets(
|
|
192
192
|
raise typer.Exit(1)
|
193
193
|
res.extend(get_relative_assets(relative_to, list(map(str, globbed))))
|
194
194
|
continue
|
195
|
-
if not
|
195
|
+
if not utils.abspath(relative_path).is_relative_to(relative_to):
|
196
196
|
console.console.print(
|
197
197
|
f'[error]Asset [item]{asset}[/item] is not relative to your statement.[/error]'
|
198
198
|
)
|
@@ -200,8 +200,8 @@ def get_relative_assets(
|
|
200
200
|
|
201
201
|
res.append(
|
202
202
|
(
|
203
|
-
|
204
|
-
|
203
|
+
utils.abspath(relative_path),
|
204
|
+
utils.abspath(relative_path).relative_to(relative_to),
|
205
205
|
)
|
206
206
|
)
|
207
207
|
|
rbx/box/statements/builders.py
CHANGED
@@ -319,7 +319,7 @@ class rbxTeXBuilder(StatementBuilder):
|
|
319
319
|
params = typing.cast(rbxToTeX, params)
|
320
320
|
if not params.template:
|
321
321
|
return []
|
322
|
-
return [((root / params.template)
|
322
|
+
return [(utils.abspath(root / params.template), params.template)]
|
323
323
|
|
324
324
|
def build(
|
325
325
|
self,
|
rbx/box/stresses.py
CHANGED
@@ -9,7 +9,7 @@ import syncer
|
|
9
9
|
import typer
|
10
10
|
from pydantic import BaseModel
|
11
11
|
|
12
|
-
from rbx import console
|
12
|
+
from rbx import console, utils
|
13
13
|
from rbx.box import checkers, generators, package, tasks, validators
|
14
14
|
from rbx.box.code import SanitizationLevel, compile_item
|
15
15
|
from rbx.box.generators import (
|
@@ -311,7 +311,7 @@ def print_stress_report(report: StressReport):
|
|
311
311
|
console.console.print(f'Found [item]{len(report.findings)}[/item] testcases.')
|
312
312
|
|
313
313
|
findings_dir = package.get_problem_runs_dir() / '.stress' / 'findings'
|
314
|
-
console.console.print(f'Findings: {
|
314
|
+
console.console.print(f'Findings: {utils.abspath(findings_dir)}')
|
315
315
|
console.console.print()
|
316
316
|
|
317
317
|
for i, finding in enumerate(report.findings):
|
rbx/box/testcase_utils.py
CHANGED
@@ -7,7 +7,7 @@ import rich.text
|
|
7
7
|
import typer
|
8
8
|
from pydantic import BaseModel
|
9
9
|
|
10
|
-
from rbx import console
|
10
|
+
from rbx import console, utils
|
11
11
|
from rbx.box import package
|
12
12
|
from rbx.box.package import get_build_testgroup_path, get_build_tests_path
|
13
13
|
from rbx.box.schema import Testcase, TestcaseGroup
|
@@ -141,8 +141,8 @@ def get_samples() -> List[Testcase]:
|
|
141
141
|
tcs = find_built_testcases(package.get_testgroup('samples'))
|
142
142
|
return [
|
143
143
|
Testcase(
|
144
|
-
inputPath=tc.inputPath
|
145
|
-
outputPath=tc.outputPath
|
144
|
+
inputPath=utils.abspath(tc.inputPath),
|
145
|
+
outputPath=utils.abspath(tc.outputPath)
|
146
146
|
if tc.outputPath is not None and tc.outputPath.is_file()
|
147
147
|
else None,
|
148
148
|
)
|
rbx/config.py
CHANGED
@@ -90,6 +90,13 @@ def get_empty_app_persist_path() -> pathlib.Path:
|
|
90
90
|
return app_dir
|
91
91
|
|
92
92
|
|
93
|
+
def get_resources_file(path: pathlib.Path) -> pathlib.Path:
|
94
|
+
file_path = importlib.resources.files('rbx') / 'resources' / path # type: ignore
|
95
|
+
if file_path.is_file():
|
96
|
+
return file_path
|
97
|
+
raise FileNotFoundError(f'File {path} not found in {_RESOURCES_PKG}.')
|
98
|
+
|
99
|
+
|
93
100
|
def get_app_file(path: pathlib.Path) -> pathlib.Path:
|
94
101
|
file_path = get_app_path() / path
|
95
102
|
if file_path.is_file():
|
rbx/grading/caching.py
CHANGED
@@ -15,7 +15,12 @@ from rbx.grading.judge.cacher import FileCacher
|
|
15
15
|
from rbx.grading.judge.digester import digest_cooperatively
|
16
16
|
from rbx.grading.judge.storage import copyfileobj
|
17
17
|
from rbx.grading.profiling import Profiler
|
18
|
-
from rbx.grading.steps import
|
18
|
+
from rbx.grading.steps import (
|
19
|
+
DigestHolder,
|
20
|
+
GradingArtifacts,
|
21
|
+
GradingFileOutput,
|
22
|
+
GradingLogsHolder,
|
23
|
+
)
|
19
24
|
|
20
25
|
VERBOSE = False
|
21
26
|
|
@@ -109,10 +114,27 @@ def _build_fingerprint_list(
|
|
109
114
|
return fingerprints
|
110
115
|
|
111
116
|
|
117
|
+
def _maybe_check_integrity(output: GradingFileOutput):
|
118
|
+
if not grading_context.should_check_integrity():
|
119
|
+
return
|
120
|
+
if output.dest is None or not output.dest.is_symlink():
|
121
|
+
return
|
122
|
+
if output.digest is None or output.digest.value is None:
|
123
|
+
return
|
124
|
+
with output.dest.open('rb') as f:
|
125
|
+
fingerprint = digest_cooperatively(f)
|
126
|
+
if fingerprint != output.digest.value:
|
127
|
+
raise ValueError(
|
128
|
+
f'Cache was tampered with, file {output.dest} has changed since it was cached.\nPlease run `rbx clean` to reset the cache.'
|
129
|
+
)
|
130
|
+
|
131
|
+
|
112
132
|
def _build_output_fingerprint_list(artifacts_list: List[GradingArtifacts]) -> List[str]:
|
113
133
|
fingerprints = []
|
114
134
|
for artifacts in artifacts_list:
|
115
135
|
for output in artifacts.outputs:
|
136
|
+
if output.hash:
|
137
|
+
_maybe_check_integrity(output)
|
116
138
|
if output.dest is None or output.intermediate or output.hash:
|
117
139
|
continue
|
118
140
|
if not output.dest.is_file():
|
@@ -313,7 +335,7 @@ class DependencyCache:
|
|
313
335
|
self.cacher = cacher
|
314
336
|
self.db = shelve.open(self._cache_name())
|
315
337
|
tmp_dir = pathlib.Path(tempfile.mkdtemp())
|
316
|
-
self.transient_db = shelve.open(tmp_dir / '.cache_db')
|
338
|
+
self.transient_db = shelve.open(str(tmp_dir / '.cache_db'))
|
317
339
|
atexit.register(lambda: self.db.close())
|
318
340
|
atexit.register(lambda: self.transient_db.close())
|
319
341
|
atexit.register(lambda: shutil.rmtree(tmp_dir))
|
rbx/grading/conftest.py
CHANGED
@@ -28,6 +28,6 @@ def sandbox(request, file_cacher: FileCacher) -> Iterator[SandboxBase]:
|
|
28
28
|
|
29
29
|
@pytest.fixture
|
30
30
|
def dependency_cache(
|
31
|
-
request, cleandir: pathlib.Path,
|
31
|
+
request, cleandir: pathlib.Path, file_cacher: FileCacher
|
32
32
|
) -> Iterator[DependencyCache]:
|
33
|
-
yield DependencyCache(cleandir / '.box',
|
33
|
+
yield DependencyCache(cleandir / '.box', file_cacher)
|
rbx/grading/grading_context.py
CHANGED
@@ -94,3 +94,28 @@ class compression(ConditionedContext):
|
|
94
94
|
if self.use_compression_token is not None:
|
95
95
|
use_compression_var.reset(self.use_compression_token)
|
96
96
|
return None
|
97
|
+
|
98
|
+
|
99
|
+
check_integrity_var = contextvars.ContextVar('check_integrity', default=True)
|
100
|
+
|
101
|
+
|
102
|
+
def should_check_integrity() -> bool:
|
103
|
+
return check_integrity_var.get()
|
104
|
+
|
105
|
+
|
106
|
+
class check_integrity(ConditionedContext):
|
107
|
+
def __init__(self, enabled: bool, when: Condition = True):
|
108
|
+
super().__init__(when)
|
109
|
+
self.enabled = enabled
|
110
|
+
self.token = None
|
111
|
+
|
112
|
+
def __enter__(self):
|
113
|
+
if not self.should_enter():
|
114
|
+
return self
|
115
|
+
self.token = check_integrity_var.set(self.enabled)
|
116
|
+
return self
|
117
|
+
|
118
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
119
|
+
if self.token is not None:
|
120
|
+
check_integrity_var.reset(self.token)
|
121
|
+
return None
|
rbx/grading/judge/sandbox.py
CHANGED
@@ -16,6 +16,7 @@ from typing import IO, Any, Dict, List, Optional
|
|
16
16
|
|
17
17
|
import pydantic
|
18
18
|
|
19
|
+
from rbx import utils
|
19
20
|
from rbx.grading.judge import cacher, storage
|
20
21
|
|
21
22
|
logger = logging.getLogger(__name__)
|
@@ -468,7 +469,7 @@ class SandboxBase(abc.ABC):
|
|
468
469
|
if override:
|
469
470
|
real_path.unlink(missing_ok=True)
|
470
471
|
try:
|
471
|
-
real_path.symlink_to(
|
472
|
+
real_path.symlink_to(utils.abspath(from_path))
|
472
473
|
except NotImplementedError:
|
473
474
|
return None
|
474
475
|
return real_path
|
@@ -9,6 +9,7 @@ import subprocess
|
|
9
9
|
import tempfile
|
10
10
|
from typing import IO, Any, Dict, List, Optional
|
11
11
|
|
12
|
+
from rbx import utils
|
12
13
|
from rbx.config import get_app_path
|
13
14
|
from rbx.grading.judge.cacher import FileCacher
|
14
15
|
from rbx.grading.judge.sandbox import (
|
@@ -180,10 +181,10 @@ class IsolateSandbox(SandboxBase):
|
|
180
181
|
"""
|
181
182
|
outer_paths: List[pathlib.Path] = []
|
182
183
|
for inner_path in inner_paths:
|
183
|
-
abs_inner_path = (self._home_dest / inner_path)
|
184
|
+
abs_inner_path = utils.abspath(self._home_dest / inner_path)
|
184
185
|
# If an inner path is absolute (e.g., /fifo0/u0_to_m) then
|
185
186
|
# it may be outside home and we should ignore it.
|
186
|
-
if not abs_inner_path.is_relative_to(self._home_dest
|
187
|
+
if not abs_inner_path.is_relative_to(utils.abspath(self._home_dest)):
|
187
188
|
continue
|
188
189
|
rel_inner_path = abs_inner_path.relative_to(self._home_dest)
|
189
190
|
outer_path = self._home / rel_inner_path
|
@@ -11,6 +11,7 @@ import sys
|
|
11
11
|
import tempfile
|
12
12
|
from typing import Any, Dict, List, Optional
|
13
13
|
|
14
|
+
from rbx import utils
|
14
15
|
from rbx.grading.judge.cacher import FileCacher
|
15
16
|
from rbx.grading.judge.sandbox import (
|
16
17
|
SandboxBase,
|
@@ -303,8 +304,8 @@ class StupidSandbox(SandboxBase):
|
|
303
304
|
real_command = (
|
304
305
|
[
|
305
306
|
sys.executable,
|
306
|
-
str(self.get_timeit_executable()
|
307
|
-
str(self.relative_path(self.get_current_log_name())
|
307
|
+
str(utils.abspath(self.get_timeit_executable())),
|
308
|
+
str(utils.abspath(self.relative_path(self.get_current_log_name()))),
|
308
309
|
]
|
309
310
|
+ self.get_timeit_args()
|
310
311
|
+ command
|
rbx/grading/judge/storage.py
CHANGED
@@ -10,6 +10,7 @@ from typing import IO, AnyStr, Dict, List, Optional, Type, TypeVar
|
|
10
10
|
import lz4.frame
|
11
11
|
from pydantic import BaseModel
|
12
12
|
|
13
|
+
from rbx import utils
|
13
14
|
from rbx.grading import grading_context
|
14
15
|
|
15
16
|
logger = logging.getLogger(__name__)
|
@@ -408,7 +409,7 @@ class FilesystemStorage(Storage):
|
|
408
409
|
def filename_from_symlink(self, link: pathlib.Path) -> Optional[str]:
|
409
410
|
if not link.is_symlink():
|
410
411
|
return None
|
411
|
-
filename = link.readlink()
|
412
|
+
filename = utils.abspath(link.readlink())
|
412
413
|
if not filename.is_file():
|
413
414
|
return None
|
414
415
|
return str(filename.relative_to(self.path))
|
rbx/grading/steps.py
CHANGED
@@ -523,7 +523,8 @@ def _get_system_bits_stdcpp(command: str) -> Optional[GradingFileInput]:
|
|
523
523
|
if not bits_candidate.is_file():
|
524
524
|
continue
|
525
525
|
return GradingFileInput(
|
526
|
-
src=
|
526
|
+
src=utils.abspath(bits_candidate),
|
527
|
+
dest=pathlib.Path('bits/stdc++.h'),
|
527
528
|
)
|
528
529
|
return None
|
529
530
|
|