rbx.cp 0.13.2__py3-none-any.whl → 0.13.4__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/annotations.py +5 -5
- rbx/box/checkers.py +24 -13
- rbx/box/cli.py +1 -4
- rbx/box/contest/build_contest_statements.py +16 -3
- rbx/box/contest/schema.py +1 -2
- rbx/box/fields.py +25 -1
- rbx/box/generators.py +5 -2
- rbx/box/global_package.py +5 -1
- rbx/box/header.py +19 -11
- rbx/box/package.py +3 -1
- rbx/box/presets/__init__.py +2 -2
- rbx/box/schema.py +4 -25
- rbx/box/statements/build_statements.py +5 -1
- rbx/box/statements/builders.py +7 -7
- rbx/box/statements/schema.py +11 -2
- rbx/box/testcase_utils.py +2 -0
- rbx/box/testing/__init__.py +0 -0
- rbx/box/testing/testing_package.py +241 -0
- rbx/box/testing/testing_preset.py +36 -0
- rbx/box/testing/testing_shared.py +81 -0
- rbx/box/tooling/main.py +2 -1
- rbx/box/validators.py +2 -1
- rbx/grading/caching.py +3 -2
- rbx/resources/presets/default/shared/contest_template.rbx.tex +1 -1
- rbx/resources/presets/default/shared/problem_template.rbx.tex +5 -1
- rbx/testing_utils.py +1 -1
- rbx/utils.py +8 -0
- {rbx_cp-0.13.2.dist-info → rbx_cp-0.13.4.dist-info}/METADATA +2 -1
- {rbx_cp-0.13.2.dist-info → rbx_cp-0.13.4.dist-info}/RECORD +32 -50
- rbx/box/conftest.py +0 -42
- rbx/box/generators_test.py +0 -67
- rbx/box/lazy_importing_test.py +0 -25
- rbx/box/solutions_test.py +0 -47
- rbx/box/validators_test.py +0 -15
- rbx/checker.py +0 -128
- rbx/clone.py +0 -197
- rbx/conftest.py +0 -38
- rbx/create.py +0 -37
- rbx/edit.py +0 -24
- rbx/grading/conftest.py +0 -33
- rbx/grading/steps_with_caching_run_test.py +0 -707
- rbx/grading_utils.py +0 -148
- rbx/hydration.py +0 -101
- rbx/main.py +0 -118
- rbx/metadata.py +0 -105
- rbx/run.py +0 -45
- rbx/schema.py +0 -64
- rbx/submit.py +0 -61
- rbx/test.py +0 -349
- rbx/testcase.py +0 -70
- rbx/testcase_rendering.py +0 -79
- {rbx_cp-0.13.2.dist-info → rbx_cp-0.13.4.dist-info}/LICENSE +0 -0
- {rbx_cp-0.13.2.dist-info → rbx_cp-0.13.4.dist-info}/WHEEL +0 -0
- {rbx_cp-0.13.2.dist-info → rbx_cp-0.13.4.dist-info}/entry_points.txt +0 -0
rbx/box/conftest.py
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import pathlib
|
3
|
-
import shutil
|
4
|
-
from collections.abc import Iterator
|
5
|
-
|
6
|
-
import pytest
|
7
|
-
|
8
|
-
from rbx import testing_utils
|
9
|
-
from rbx.box import package
|
10
|
-
|
11
|
-
|
12
|
-
@pytest.fixture
|
13
|
-
def pkg_cleandir(cleandir: pathlib.Path) -> Iterator[pathlib.Path]:
|
14
|
-
old_temp_dir = package.TEMP_DIR
|
15
|
-
package.TEMP_DIR = cleandir
|
16
|
-
|
17
|
-
pkgdir = cleandir / 'pkg'
|
18
|
-
pkgdir.mkdir(exist_ok=True, parents=True)
|
19
|
-
lwd = pathlib.Path.cwd()
|
20
|
-
os.chdir(str(pkgdir))
|
21
|
-
try:
|
22
|
-
yield pkgdir.absolute()
|
23
|
-
finally:
|
24
|
-
os.chdir(str(lwd))
|
25
|
-
package.TEMP_DIR = old_temp_dir
|
26
|
-
|
27
|
-
|
28
|
-
@pytest.fixture
|
29
|
-
def pkg_from_testdata(
|
30
|
-
request, testdata_path: pathlib.Path, pkg_cleandir: pathlib.Path
|
31
|
-
) -> Iterator[pathlib.Path]:
|
32
|
-
marker = request.node.get_closest_marker('test_pkg')
|
33
|
-
if marker is None:
|
34
|
-
raise ValueError('test_pkg marker not found')
|
35
|
-
testdata = testdata_path / marker.args[0]
|
36
|
-
shutil.copytree(str(testdata), str(pkg_cleandir), dirs_exist_ok=True)
|
37
|
-
yield pkg_cleandir
|
38
|
-
|
39
|
-
|
40
|
-
@pytest.fixture(autouse=True)
|
41
|
-
def clear_cache():
|
42
|
-
testing_utils.clear_all_functools_cache()
|
rbx/box/generators_test.py
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
|
3
|
-
import pytest
|
4
|
-
|
5
|
-
from rbx.box import package
|
6
|
-
from rbx.box.generators import (
|
7
|
-
generate_outputs_for_testcases,
|
8
|
-
generate_testcases,
|
9
|
-
)
|
10
|
-
from rbx.box.testcase_extractors import extract_generation_testcases_from_groups
|
11
|
-
from rbx.testing_utils import print_directory_tree
|
12
|
-
|
13
|
-
|
14
|
-
@pytest.mark.test_pkg('box1')
|
15
|
-
async def test_generator_works(pkg_from_testdata: pathlib.Path):
|
16
|
-
await generate_testcases()
|
17
|
-
entries = [
|
18
|
-
entry.group_entry for entry in await extract_generation_testcases_from_groups()
|
19
|
-
]
|
20
|
-
await generate_outputs_for_testcases(entries)
|
21
|
-
|
22
|
-
# Debug when fail.
|
23
|
-
print_directory_tree(pkg_from_testdata)
|
24
|
-
|
25
|
-
assert (
|
26
|
-
package.get_build_testgroup_path('gen1') / '0-000.in'
|
27
|
-
).read_text() == '777\n'
|
28
|
-
assert (
|
29
|
-
package.get_build_testgroup_path('gen1') / '1-gen-000.in'
|
30
|
-
).read_text() == '123\n'
|
31
|
-
assert (
|
32
|
-
package.get_build_testgroup_path('gen1') / '1-gen-001.in'
|
33
|
-
).read_text() == '424242\n'
|
34
|
-
assert (
|
35
|
-
package.get_build_testgroup_path('gen1') / '2-genScript-000.in'
|
36
|
-
).read_text() == '25\n'
|
37
|
-
|
38
|
-
|
39
|
-
@pytest.mark.test_pkg('box1')
|
40
|
-
async def test_generator_cache_works(
|
41
|
-
pkg_from_testdata: pathlib.Path,
|
42
|
-
):
|
43
|
-
# Run the first time.
|
44
|
-
await generate_testcases()
|
45
|
-
assert (
|
46
|
-
package.get_build_testgroup_path('gen1') / '1-gen-000.in'
|
47
|
-
).read_text() == '123\n'
|
48
|
-
assert (
|
49
|
-
package.get_build_testgroup_path('gen1') / '1-gen-001.in'
|
50
|
-
).read_text() == '424242\n'
|
51
|
-
|
52
|
-
# Change the generator `gen1`, but keep `gen2` as is.
|
53
|
-
gen_path = pkg_from_testdata / 'gen1.cpp'
|
54
|
-
gen_path.write_text(gen_path.read_text().replace('123', '4567'))
|
55
|
-
|
56
|
-
# Run the second time.
|
57
|
-
await generate_testcases()
|
58
|
-
|
59
|
-
# Debug when fail.
|
60
|
-
print_directory_tree(pkg_from_testdata)
|
61
|
-
|
62
|
-
assert (
|
63
|
-
package.get_build_testgroup_path('gen1') / '1-gen-000.in'
|
64
|
-
).read_text() == '4567\n'
|
65
|
-
assert (
|
66
|
-
package.get_build_testgroup_path('gen1') / '1-gen-001.in'
|
67
|
-
).read_text() == '424242\n'
|
rbx/box/lazy_importing_test.py
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
import subprocess
|
2
|
-
import sys
|
3
|
-
from pathlib import Path
|
4
|
-
|
5
|
-
LAZY_MODULES = {
|
6
|
-
'gitpython',
|
7
|
-
'questionary',
|
8
|
-
'fastapi',
|
9
|
-
'requests',
|
10
|
-
'pydantic_xml',
|
11
|
-
'rbx.box.packaging.polygon.packager',
|
12
|
-
'rbx.box.stresses',
|
13
|
-
}
|
14
|
-
|
15
|
-
|
16
|
-
def test_rich_not_imported_unnecessary():
|
17
|
-
file_path = Path(__file__).parent / 'lazy_importing_main.py'
|
18
|
-
result = subprocess.run(
|
19
|
-
[sys.executable, '-m', 'coverage', 'run', str(file_path)],
|
20
|
-
capture_output=True,
|
21
|
-
encoding='utf-8',
|
22
|
-
)
|
23
|
-
modules = result.stdout.splitlines()
|
24
|
-
modules = [module for module in modules if module in LAZY_MODULES]
|
25
|
-
assert not modules
|
rbx/box/solutions_test.py
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
|
3
|
-
import pytest
|
4
|
-
|
5
|
-
from rbx.box.environment import VerificationLevel
|
6
|
-
from rbx.box.generators import (
|
7
|
-
generate_outputs_for_testcases,
|
8
|
-
generate_testcases,
|
9
|
-
)
|
10
|
-
from rbx.box.solutions import (
|
11
|
-
convert_list_of_solution_evaluations_to_dict,
|
12
|
-
run_solutions,
|
13
|
-
)
|
14
|
-
from rbx.box.testcase_extractors import extract_generation_testcases_from_groups
|
15
|
-
from rbx.grading.steps import Outcome
|
16
|
-
|
17
|
-
|
18
|
-
@pytest.mark.test_pkg('box1')
|
19
|
-
async def test_solutions(pkg_from_testdata: pathlib.Path):
|
20
|
-
await generate_testcases()
|
21
|
-
entries = [
|
22
|
-
entry.group_entry for entry in await extract_generation_testcases_from_groups()
|
23
|
-
]
|
24
|
-
await generate_outputs_for_testcases(entries)
|
25
|
-
|
26
|
-
result = run_solutions(verification=VerificationLevel.FULL)
|
27
|
-
res = await convert_list_of_solution_evaluations_to_dict(
|
28
|
-
result.skeleton, result.items
|
29
|
-
)
|
30
|
-
|
31
|
-
# First solution should pass all tests.
|
32
|
-
assert all(chk.result.outcome == Outcome.ACCEPTED for chk in res[0]['gen1'])
|
33
|
-
# 25 test should be WA for the second solution.
|
34
|
-
assert res[1]['gen1'][3].result.outcome == Outcome.WRONG_ANSWER
|
35
|
-
# Runtime error for third solution.
|
36
|
-
assert all(chk.result.outcome == Outcome.RUNTIME_ERROR for chk in res[2]['gen1'])
|
37
|
-
# 1e9 test should be TLE for the fourth solution (soft TLE)
|
38
|
-
assert res[3]['gen1'][4].result.outcome == Outcome.TIME_LIMIT_EXCEEDED
|
39
|
-
# no TLE outcome should be WA (soft TLE)
|
40
|
-
assert res[4]['gen1'][4].result.no_tle_outcome == Outcome.WRONG_ANSWER
|
41
|
-
# hard TLE
|
42
|
-
assert res[5]['gen1'][4].result.outcome == Outcome.TIME_LIMIT_EXCEEDED
|
43
|
-
assert res[5]['gen1'][4].result.no_tle_outcome is None
|
44
|
-
# OLE
|
45
|
-
assert all(
|
46
|
-
chk.result.outcome == Outcome.OUTPUT_LIMIT_EXCEEDED for chk in res[6]['gen1']
|
47
|
-
)
|
rbx/box/validators_test.py
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
|
3
|
-
import pytest
|
4
|
-
|
5
|
-
from rbx.box.generators import generate_testcases
|
6
|
-
from rbx.box.validators import validate_testcases
|
7
|
-
|
8
|
-
|
9
|
-
@pytest.mark.test_pkg('box1')
|
10
|
-
async def test_validators(pkg_from_testdata: pathlib.Path):
|
11
|
-
await generate_testcases()
|
12
|
-
validation_infos = await validate_testcases()
|
13
|
-
|
14
|
-
for info in validation_infos:
|
15
|
-
assert info.ok
|
rbx/checker.py
DELETED
@@ -1,128 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
from typing import Optional
|
3
|
-
|
4
|
-
import typer
|
5
|
-
from typing_extensions import Annotated
|
6
|
-
|
7
|
-
from rbx import annotations, config, metadata, utils
|
8
|
-
from rbx.config import get_builtin_checker, get_testlib
|
9
|
-
from rbx.console import console
|
10
|
-
|
11
|
-
app = typer.Typer(no_args_is_help=True)
|
12
|
-
|
13
|
-
|
14
|
-
@app.command('add, a')
|
15
|
-
def add(
|
16
|
-
problem: annotations.Problem,
|
17
|
-
template: Annotated[
|
18
|
-
Optional[str],
|
19
|
-
typer.Option(
|
20
|
-
'--template', '-t', help='Checker that should be used as template.'
|
21
|
-
),
|
22
|
-
] = None,
|
23
|
-
):
|
24
|
-
"""
|
25
|
-
Add a new checker for the problem.
|
26
|
-
"""
|
27
|
-
dumped_problem = metadata.find_problem_by_anything(problem)
|
28
|
-
if dumped_problem is None:
|
29
|
-
console.print(f'[error]Problem [item]{problem}[/item] not found.[/error]')
|
30
|
-
return
|
31
|
-
|
32
|
-
template_path = get_builtin_checker(template or 'boilerplate.cpp')
|
33
|
-
|
34
|
-
if not template_path.is_file():
|
35
|
-
console.print(f'[error]Template file {template} not found.[/error]')
|
36
|
-
return
|
37
|
-
|
38
|
-
testlib_path = get_testlib()
|
39
|
-
if not testlib_path.is_file():
|
40
|
-
console.print('[error]Testlib file not found.[/error]')
|
41
|
-
return
|
42
|
-
|
43
|
-
checker_name = f'{dumped_problem.code}.checker.cpp'
|
44
|
-
checker_path = pathlib.Path() / checker_name
|
45
|
-
|
46
|
-
# Create both files.
|
47
|
-
checker_path.write_text(template_path.read_text())
|
48
|
-
(checker_path.parent / 'testlib.h').write_text(testlib_path.read_text())
|
49
|
-
|
50
|
-
# Set checker.
|
51
|
-
problem_to_dump = dumped_problem.model_copy()
|
52
|
-
problem_to_dump.checker = checker_name
|
53
|
-
problem_path = metadata.find_problem_path_by_code(dumped_problem.code)
|
54
|
-
if not problem_path:
|
55
|
-
raise typer.Exit(1)
|
56
|
-
problem_path.write_text(utils.model_json(problem_to_dump))
|
57
|
-
console.print(
|
58
|
-
f'Checker [item]{checker_name}[/item] added to problem [item]{dumped_problem.pretty_name()}[/item].'
|
59
|
-
)
|
60
|
-
|
61
|
-
|
62
|
-
@app.command('set, s')
|
63
|
-
def set(problem: annotations.Problem, checker: annotations.Checker):
|
64
|
-
"""
|
65
|
-
Set a checker for the problem.
|
66
|
-
"""
|
67
|
-
dumped_problem = metadata.find_problem_by_anything(problem)
|
68
|
-
if dumped_problem is None:
|
69
|
-
console.print(f'[error]Problem [item]{problem}[/item] not found.[/error]')
|
70
|
-
return
|
71
|
-
|
72
|
-
problem_to_dump = dumped_problem.model_copy()
|
73
|
-
problem_to_dump.checker = checker
|
74
|
-
problem_path = metadata.find_problem_path_by_code(dumped_problem.code)
|
75
|
-
if not problem_path:
|
76
|
-
raise typer.Exit(1)
|
77
|
-
problem_path.write_text(utils.model_json(problem_to_dump))
|
78
|
-
console.print(
|
79
|
-
f'Checker [item]{checker}[/item] will be used for problem [item]{dumped_problem.pretty_name()}[/item].'
|
80
|
-
)
|
81
|
-
|
82
|
-
|
83
|
-
@app.command('unset, u')
|
84
|
-
def unset(problem: annotations.Problem):
|
85
|
-
"""
|
86
|
-
Use the default checker for a problem.
|
87
|
-
"""
|
88
|
-
dumped_problem = metadata.find_problem_by_anything(problem)
|
89
|
-
if dumped_problem is None:
|
90
|
-
console.print(f'[error]Problem [item]{problem}[/item] not found.[/error]')
|
91
|
-
return
|
92
|
-
|
93
|
-
problem_to_dump = dumped_problem.model_copy()
|
94
|
-
problem_to_dump.checker = None
|
95
|
-
problem_path = metadata.find_problem_path_by_code(dumped_problem.code)
|
96
|
-
if not problem_path:
|
97
|
-
raise typer.Exit(1)
|
98
|
-
problem_path.write_text(utils.model_json(problem_to_dump))
|
99
|
-
console.print(
|
100
|
-
f'Default checker will be used for problem [item]{dumped_problem.pretty_name()}[/item].'
|
101
|
-
)
|
102
|
-
|
103
|
-
|
104
|
-
@app.command('edit, e')
|
105
|
-
def edit(problem: annotations.Problem):
|
106
|
-
"""
|
107
|
-
Edit the checker for a problem.
|
108
|
-
"""
|
109
|
-
dumped_problem = metadata.find_problem_by_anything(problem)
|
110
|
-
if dumped_problem is None:
|
111
|
-
console.print(f'[error]Problem [item]{problem}[/item] not found.[/error]')
|
112
|
-
return
|
113
|
-
|
114
|
-
checker = dumped_problem.checker
|
115
|
-
if checker is None:
|
116
|
-
console.print(
|
117
|
-
f'[error]No checker set for problem [item]{dumped_problem.pretty_name()}[/item].[/error]'
|
118
|
-
)
|
119
|
-
return
|
120
|
-
|
121
|
-
checker_path = pathlib.Path() / checker
|
122
|
-
if not checker_path.is_file():
|
123
|
-
console.print(
|
124
|
-
f'[error]Checker [item]{checker}[/item] not found in the problems folder. You cannot edit a builtin checker.[/error]'
|
125
|
-
)
|
126
|
-
return
|
127
|
-
|
128
|
-
config.open_editor(checker_path)
|
rbx/clone.py
DELETED
@@ -1,197 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import pathlib
|
3
|
-
import threading
|
4
|
-
import time
|
5
|
-
from typing import List, Optional
|
6
|
-
|
7
|
-
import fastapi
|
8
|
-
import jinja2
|
9
|
-
import rich
|
10
|
-
import rich.prompt
|
11
|
-
import rich.status
|
12
|
-
import uvicorn
|
13
|
-
|
14
|
-
from rbx import hydration, metadata, providers, utils
|
15
|
-
from rbx.config import Language, get_config
|
16
|
-
from rbx.console import console
|
17
|
-
from rbx.schema import DumpedProblem, Problem
|
18
|
-
|
19
|
-
|
20
|
-
def clear_loggers():
|
21
|
-
for logger_name in [
|
22
|
-
'uvicorn',
|
23
|
-
'uvicorn.access',
|
24
|
-
'uvicorn.asgi',
|
25
|
-
]:
|
26
|
-
logging.getLogger(logger_name).handlers.clear()
|
27
|
-
logging.getLogger(logger_name).propagate = False
|
28
|
-
|
29
|
-
|
30
|
-
def create_problem_structure(
|
31
|
-
root: pathlib.Path,
|
32
|
-
problem: Problem,
|
33
|
-
lang: Language,
|
34
|
-
status: Optional[rich.status.Status],
|
35
|
-
should_simplify: bool = False,
|
36
|
-
verbose: bool = False,
|
37
|
-
) -> Optional[DumpedProblem]:
|
38
|
-
# Create directory structure.
|
39
|
-
root.parent.mkdir(parents=True, exist_ok=True)
|
40
|
-
|
41
|
-
problem_to_dump = DumpedProblem.from_problem(
|
42
|
-
problem,
|
43
|
-
code=providers.get_code(problem, simplify=should_simplify),
|
44
|
-
aliases=providers.get_aliases(problem),
|
45
|
-
)
|
46
|
-
|
47
|
-
if verbose:
|
48
|
-
console.print(
|
49
|
-
f'Creating problem structure for [item]{problem_to_dump.pretty_name()}[/item]...'
|
50
|
-
)
|
51
|
-
|
52
|
-
code_path = root / lang.get_file(problem_to_dump.code)
|
53
|
-
json_path = root / f'{problem_to_dump.code}.rbx.json'
|
54
|
-
|
55
|
-
existing_problem = metadata.find_problem_by_code(problem_to_dump.code, root)
|
56
|
-
if existing_problem:
|
57
|
-
console.print(
|
58
|
-
f'[error]Problem with identifier [item]{problem_to_dump.code}[/item] already exists in this folder.[/error]'
|
59
|
-
)
|
60
|
-
if not utils.confirm_on_status(
|
61
|
-
status, 'Do you want to overwrite it?', default=False
|
62
|
-
):
|
63
|
-
console.print(
|
64
|
-
f'Skipping problem [item]{problem_to_dump.pretty_name()}[/item].'
|
65
|
-
)
|
66
|
-
return None
|
67
|
-
|
68
|
-
json_path.write_text(utils.model_json(problem_to_dump))
|
69
|
-
code = jinja2.Template(lang.get_template()).render(**problem_to_dump.get_vars())
|
70
|
-
code_path.write_text(code)
|
71
|
-
|
72
|
-
if verbose:
|
73
|
-
console.print(
|
74
|
-
f'Problem structure for [item]{problem_to_dump.pretty_name()}[/item] created successfully.'
|
75
|
-
)
|
76
|
-
return problem_to_dump
|
77
|
-
|
78
|
-
|
79
|
-
def process_problems(
|
80
|
-
problems: List[Problem], lang: Language, status: rich.status.Status
|
81
|
-
):
|
82
|
-
console.print(
|
83
|
-
f'Creating problem structure for [item]{len(problems)}[/item] problems...'
|
84
|
-
)
|
85
|
-
|
86
|
-
should_simplify = False
|
87
|
-
if providers.should_simplify_contest_problems(problems):
|
88
|
-
console.print('Detected the parsed problems are from a contest.')
|
89
|
-
if utils.confirm_on_status(
|
90
|
-
status,
|
91
|
-
'Do you want to identify these problems by their letters?',
|
92
|
-
default=True,
|
93
|
-
):
|
94
|
-
should_simplify = True
|
95
|
-
|
96
|
-
root = pathlib.Path()
|
97
|
-
dumped_problems = []
|
98
|
-
for problem in problems:
|
99
|
-
dumped_problem = create_problem_structure(
|
100
|
-
root, problem, lang, status, should_simplify=should_simplify
|
101
|
-
)
|
102
|
-
if dumped_problem:
|
103
|
-
dumped_problems.append(dumped_problem)
|
104
|
-
console.print(f'Hydrating [item]{len(dumped_problems)}[/item] problems...')
|
105
|
-
for problem in dumped_problems:
|
106
|
-
hydration.hydrate_problem(root, problem)
|
107
|
-
|
108
|
-
|
109
|
-
def main(lang: Optional[str] = None):
|
110
|
-
if get_config().get_language(lang) is None:
|
111
|
-
console.print(
|
112
|
-
f'[error]Language {lang or get_config().defaultLanguage} not found in config. Please check your configuration.[/error]'
|
113
|
-
)
|
114
|
-
return
|
115
|
-
|
116
|
-
app = fastapi.FastAPI()
|
117
|
-
|
118
|
-
async def shutdown():
|
119
|
-
server.should_exit = True
|
120
|
-
|
121
|
-
batch_to_left_lock = threading.Lock()
|
122
|
-
batch_to_left = {}
|
123
|
-
ignored = set()
|
124
|
-
saved_status = None
|
125
|
-
problems_to_process = []
|
126
|
-
|
127
|
-
def process_batch_item(problem: Problem):
|
128
|
-
batch_to_left_lock.acquire()
|
129
|
-
if problem.batch.id in ignored:
|
130
|
-
batch_to_left_lock.release()
|
131
|
-
return True
|
132
|
-
if problem.batch.id not in batch_to_left:
|
133
|
-
if len(batch_to_left) > 0:
|
134
|
-
console.print(
|
135
|
-
f'[error]Ignoring extra batch [item]{problem.batch.id}[/item] since other batch is being parsed.[/error]'
|
136
|
-
)
|
137
|
-
ignored.add(problem.batch.id)
|
138
|
-
batch_to_left_lock.release()
|
139
|
-
return True
|
140
|
-
if problem.batch.size > 1 and saved_status:
|
141
|
-
saved_status.update(
|
142
|
-
f'[rbx]rbx[/rbx] is parsing problems from group [item]{problem.group}[/item]'
|
143
|
-
)
|
144
|
-
elif saved_status:
|
145
|
-
saved_status.update('[rbx]rbx[/rbx] is parsing problems...')
|
146
|
-
console.print(
|
147
|
-
f'Started parsing batch [item]{problem.batch.id}[/item] with size [item]{problem.batch.size}[/item].'
|
148
|
-
)
|
149
|
-
batch_to_left[problem.batch.id] = problem.batch.size
|
150
|
-
console.print(f'Parsing problem [item]{problem.name}[/item]...')
|
151
|
-
problems_to_process.append(problem)
|
152
|
-
finished = False
|
153
|
-
if batch_to_left[problem.batch.id] == 1:
|
154
|
-
finished = True
|
155
|
-
if problem.batch.size > 1:
|
156
|
-
console.print(
|
157
|
-
f'[status][rbx]rbx[/rbx] parsed all problems from group [item]{problem.group}[/item].[/status]'
|
158
|
-
)
|
159
|
-
else:
|
160
|
-
console.print(
|
161
|
-
f'[status][rbx]rbx[/rbx] parsed problem from [item]{problem.url}[/item][/status]'
|
162
|
-
)
|
163
|
-
else:
|
164
|
-
batch_to_left[problem.batch.id] -= 1
|
165
|
-
batch_to_left_lock.release()
|
166
|
-
return not finished
|
167
|
-
|
168
|
-
clock = None
|
169
|
-
|
170
|
-
@app.post('/')
|
171
|
-
async def parse(problem: Problem, background_tasks: fastapi.BackgroundTasks):
|
172
|
-
nonlocal clock
|
173
|
-
if clock is None:
|
174
|
-
clock = time.monotonic()
|
175
|
-
if not process_batch_item(problem):
|
176
|
-
duration = time.monotonic() - clock
|
177
|
-
console.print(
|
178
|
-
f'Parsed all problems in [item]{duration:.2f}[/item] seconds.'
|
179
|
-
)
|
180
|
-
background_tasks.add_task(shutdown)
|
181
|
-
return {}
|
182
|
-
|
183
|
-
config = uvicorn.Config(app, port=1327)
|
184
|
-
server = uvicorn.Server(config=config)
|
185
|
-
clear_loggers()
|
186
|
-
with console.status('Waiting for Competitive Companion request...') as status:
|
187
|
-
saved_status = status
|
188
|
-
server.run()
|
189
|
-
|
190
|
-
with console.status('Processing parsed problems...') as status:
|
191
|
-
language = get_config().get_language(lang)
|
192
|
-
if not language:
|
193
|
-
console.print(
|
194
|
-
f'[error]Language {lang or get_config().defaultLanguage} not found in config. Please check your configuration.[/error]'
|
195
|
-
)
|
196
|
-
return
|
197
|
-
process_problems(problems_to_process, language, status)
|
rbx/conftest.py
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import pathlib
|
3
|
-
import shutil
|
4
|
-
import tempfile
|
5
|
-
from collections.abc import Iterator
|
6
|
-
|
7
|
-
import pytest
|
8
|
-
|
9
|
-
from rbx.testing_utils import get_testdata_path
|
10
|
-
|
11
|
-
|
12
|
-
@pytest.fixture
|
13
|
-
def testdata_path() -> pathlib.Path:
|
14
|
-
return get_testdata_path()
|
15
|
-
|
16
|
-
|
17
|
-
@pytest.fixture
|
18
|
-
def cleandir() -> Iterator[pathlib.Path]:
|
19
|
-
with tempfile.TemporaryDirectory() as newpath:
|
20
|
-
abspath = pathlib.Path(newpath).absolute()
|
21
|
-
old_cwd = pathlib.Path.cwd()
|
22
|
-
os.chdir(newpath)
|
23
|
-
try:
|
24
|
-
yield abspath
|
25
|
-
finally:
|
26
|
-
os.chdir(str(old_cwd))
|
27
|
-
|
28
|
-
|
29
|
-
@pytest.fixture
|
30
|
-
def cleandir_with_testdata(
|
31
|
-
request, testdata_path: pathlib.Path, cleandir: pathlib.Path
|
32
|
-
) -> Iterator[pathlib.Path]:
|
33
|
-
marker = request.node.get_closest_marker('test_pkg')
|
34
|
-
if marker is None:
|
35
|
-
raise ValueError('test_pkg marker not found')
|
36
|
-
testdata = testdata_path / marker.args[0]
|
37
|
-
shutil.copytree(str(testdata), str(cleandir), dirs_exist_ok=True)
|
38
|
-
yield cleandir
|
rbx/create.py
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
|
3
|
-
from rbx import annotations
|
4
|
-
from rbx.clone import create_problem_structure
|
5
|
-
from rbx.config import get_config
|
6
|
-
from rbx.console import console
|
7
|
-
from rbx.schema import Batch, Problem
|
8
|
-
|
9
|
-
|
10
|
-
def main(
|
11
|
-
name: str,
|
12
|
-
lang: annotations.Language,
|
13
|
-
timelimit: annotations.Timelimit = 1000,
|
14
|
-
memorylimit: annotations.Memorylimit = 256,
|
15
|
-
multitest: annotations.Multitest = False,
|
16
|
-
):
|
17
|
-
language = get_config().get_language(lang)
|
18
|
-
if language is None:
|
19
|
-
console.print(
|
20
|
-
f'[error]Language {lang or get_config().defaultLanguage} not found in config. Please check your configuration.[/error]'
|
21
|
-
)
|
22
|
-
return
|
23
|
-
|
24
|
-
problem = Problem(
|
25
|
-
name=name,
|
26
|
-
timeLimit=timelimit,
|
27
|
-
memoryLimit=memorylimit,
|
28
|
-
testType='multiNumber' if multitest else 'single',
|
29
|
-
batch=Batch.create(),
|
30
|
-
)
|
31
|
-
create_problem_structure(
|
32
|
-
pathlib.Path(),
|
33
|
-
problem,
|
34
|
-
language,
|
35
|
-
status=None,
|
36
|
-
verbose=True,
|
37
|
-
)
|
rbx/edit.py
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
from typing import Optional
|
3
|
-
|
4
|
-
from rbx import annotations, console, metadata
|
5
|
-
from rbx.config import get_config, open_editor
|
6
|
-
|
7
|
-
|
8
|
-
def main(problem: str, language: Optional[annotations.LanguageWithDefault] = None):
|
9
|
-
lang = get_config().get_language(language)
|
10
|
-
if lang is None:
|
11
|
-
console.console.print(
|
12
|
-
f'[error]Language {language or get_config().defaultLanguage} not found in config. Please check your configuration.[/error]'
|
13
|
-
)
|
14
|
-
return
|
15
|
-
|
16
|
-
dumped_problem = metadata.find_problem_by_anything(problem)
|
17
|
-
if not dumped_problem:
|
18
|
-
console.console.print(
|
19
|
-
f'[error]Problem with identifier {problem} not found.[/error]'
|
20
|
-
)
|
21
|
-
return
|
22
|
-
|
23
|
-
filename = lang.get_file(dumped_problem.code)
|
24
|
-
open_editor(pathlib.Path(filename))
|
rbx/grading/conftest.py
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
from collections.abc import Iterator
|
3
|
-
|
4
|
-
import pytest
|
5
|
-
|
6
|
-
from rbx.grading.caching import DependencyCache
|
7
|
-
from rbx.grading.judge.cacher import FileCacher
|
8
|
-
from rbx.grading.judge.sandbox import SandboxBase
|
9
|
-
from rbx.grading.judge.sandboxes.stupid_sandbox import StupidSandbox
|
10
|
-
from rbx.grading.judge.storage import FilesystemStorage, Storage
|
11
|
-
|
12
|
-
|
13
|
-
@pytest.fixture
|
14
|
-
def storage(request, cleandir: pathlib.Path) -> Iterator[Storage]:
|
15
|
-
storage_path = cleandir / '.box' / '.storage'
|
16
|
-
yield FilesystemStorage(storage_path)
|
17
|
-
|
18
|
-
|
19
|
-
@pytest.fixture
|
20
|
-
def file_cacher(request, storage: Storage) -> Iterator[FileCacher]:
|
21
|
-
yield FileCacher(storage)
|
22
|
-
|
23
|
-
|
24
|
-
@pytest.fixture
|
25
|
-
def sandbox(request, file_cacher: FileCacher) -> Iterator[SandboxBase]:
|
26
|
-
yield StupidSandbox(file_cacher=file_cacher)
|
27
|
-
|
28
|
-
|
29
|
-
@pytest.fixture
|
30
|
-
def dependency_cache(
|
31
|
-
request, cleandir: pathlib.Path, file_cacher: FileCacher
|
32
|
-
) -> Iterator[DependencyCache]:
|
33
|
-
yield DependencyCache(cleandir / '.box', file_cacher)
|