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.
Files changed (164) hide show
  1. rbx/__init__.py +0 -0
  2. rbx/annotations.py +127 -0
  3. rbx/autoenum.py +333 -0
  4. rbx/box/__init__.py +0 -0
  5. rbx/box/builder.py +77 -0
  6. rbx/box/cd.py +37 -0
  7. rbx/box/checkers.py +134 -0
  8. rbx/box/code.py +185 -0
  9. rbx/box/compile.py +56 -0
  10. rbx/box/conftest.py +42 -0
  11. rbx/box/contest/__init__.py +0 -0
  12. rbx/box/contest/build_contest_statements.py +347 -0
  13. rbx/box/contest/contest_package.py +76 -0
  14. rbx/box/contest/contest_utils.py +20 -0
  15. rbx/box/contest/main.py +179 -0
  16. rbx/box/contest/schema.py +155 -0
  17. rbx/box/contest/statements.py +82 -0
  18. rbx/box/creation.py +72 -0
  19. rbx/box/download.py +64 -0
  20. rbx/box/environment.py +345 -0
  21. rbx/box/extensions.py +26 -0
  22. rbx/box/generators.py +478 -0
  23. rbx/box/generators_test.py +63 -0
  24. rbx/box/main.py +449 -0
  25. rbx/box/package.py +316 -0
  26. rbx/box/packaging/boca/extension.py +27 -0
  27. rbx/box/packaging/boca/packager.py +245 -0
  28. rbx/box/packaging/contest_main.py +82 -0
  29. rbx/box/packaging/main.py +68 -0
  30. rbx/box/packaging/packager.py +117 -0
  31. rbx/box/packaging/polygon/packager.py +320 -0
  32. rbx/box/packaging/polygon/test.py +81 -0
  33. rbx/box/packaging/polygon/xml_schema.py +106 -0
  34. rbx/box/presets/__init__.py +503 -0
  35. rbx/box/presets/fetch.py +70 -0
  36. rbx/box/presets/lock_schema.py +20 -0
  37. rbx/box/presets/schema.py +59 -0
  38. rbx/box/schema.py +394 -0
  39. rbx/box/solutions.py +792 -0
  40. rbx/box/solutions_test.py +41 -0
  41. rbx/box/statements/__init__.py +0 -0
  42. rbx/box/statements/build_statements.py +359 -0
  43. rbx/box/statements/builders.py +375 -0
  44. rbx/box/statements/joiners.py +113 -0
  45. rbx/box/statements/latex.py +47 -0
  46. rbx/box/statements/latex_jinja.py +214 -0
  47. rbx/box/statements/schema.py +138 -0
  48. rbx/box/stresses.py +292 -0
  49. rbx/box/stressing/__init__.py +0 -0
  50. rbx/box/stressing/finder_parser.py +359 -0
  51. rbx/box/stressing/generator_parser.py +258 -0
  52. rbx/box/testcases.py +54 -0
  53. rbx/box/ui/__init__.py +0 -0
  54. rbx/box/ui/captured_log.py +372 -0
  55. rbx/box/ui/css/app.tcss +48 -0
  56. rbx/box/ui/main.py +38 -0
  57. rbx/box/ui/run.py +209 -0
  58. rbx/box/validators.py +245 -0
  59. rbx/box/validators_test.py +15 -0
  60. rbx/checker.py +128 -0
  61. rbx/clone.py +197 -0
  62. rbx/config.py +271 -0
  63. rbx/conftest.py +38 -0
  64. rbx/console.py +27 -0
  65. rbx/create.py +37 -0
  66. rbx/edit.py +24 -0
  67. rbx/grading/__init__.py +0 -0
  68. rbx/grading/caching.py +356 -0
  69. rbx/grading/conftest.py +33 -0
  70. rbx/grading/judge/__init__.py +0 -0
  71. rbx/grading/judge/cacher.py +503 -0
  72. rbx/grading/judge/digester.py +35 -0
  73. rbx/grading/judge/sandbox.py +748 -0
  74. rbx/grading/judge/sandboxes/__init__.py +0 -0
  75. rbx/grading/judge/sandboxes/isolate.py +683 -0
  76. rbx/grading/judge/sandboxes/stupid_sandbox.py +310 -0
  77. rbx/grading/judge/sandboxes/timeit.py +217 -0
  78. rbx/grading/judge/storage.py +284 -0
  79. rbx/grading/judge/test.py +38 -0
  80. rbx/grading/judge/testiso.py +54 -0
  81. rbx/grading/steps.py +522 -0
  82. rbx/grading/steps_with_caching.py +59 -0
  83. rbx/grading/steps_with_caching_run_test.py +429 -0
  84. rbx/grading_utils.py +148 -0
  85. rbx/hydration.py +101 -0
  86. rbx/main.py +122 -0
  87. rbx/metadata.py +105 -0
  88. rbx/providers/__init__.py +43 -0
  89. rbx/providers/codeforces.py +73 -0
  90. rbx/providers/provider.py +26 -0
  91. rbx/resources/checkers/boilerplate.cpp +20 -0
  92. rbx/resources/default_config.json +48 -0
  93. rbx/resources/envs/default.rbx.yml +37 -0
  94. rbx/resources/envs/isolate.rbx.yml +37 -0
  95. rbx/resources/packagers/boca/checker.sh +43 -0
  96. rbx/resources/packagers/boca/compare +53 -0
  97. rbx/resources/packagers/boca/compile/c +172 -0
  98. rbx/resources/packagers/boca/compile/cc +173 -0
  99. rbx/resources/packagers/boca/compile/cpp +172 -0
  100. rbx/resources/packagers/boca/compile/java +194 -0
  101. rbx/resources/packagers/boca/compile/kt +155 -0
  102. rbx/resources/packagers/boca/compile/pas +172 -0
  103. rbx/resources/packagers/boca/compile/py2 +173 -0
  104. rbx/resources/packagers/boca/compile/py3 +173 -0
  105. rbx/resources/packagers/boca/run/c +128 -0
  106. rbx/resources/packagers/boca/run/cc +128 -0
  107. rbx/resources/packagers/boca/run/cpp +128 -0
  108. rbx/resources/packagers/boca/run/java +194 -0
  109. rbx/resources/packagers/boca/run/kt +159 -0
  110. rbx/resources/packagers/boca/run/py2 +166 -0
  111. rbx/resources/packagers/boca/run/py3 +166 -0
  112. rbx/resources/presets/default/contest/contest.rbx.yml +14 -0
  113. rbx/resources/presets/default/contest/statement/contest.rbx.tex +97 -0
  114. rbx/resources/presets/default/contest/statement/olymp.sty +250 -0
  115. rbx/resources/presets/default/contest/statement/template.rbx.tex +42 -0
  116. rbx/resources/presets/default/preset.rbx.yml +12 -0
  117. rbx/resources/presets/default/problem/.gitignore +6 -0
  118. rbx/resources/presets/default/problem/gen.cpp +9 -0
  119. rbx/resources/presets/default/problem/problem.rbx.yml +44 -0
  120. rbx/resources/presets/default/problem/random.py +3 -0
  121. rbx/resources/presets/default/problem/random.txt +2 -0
  122. rbx/resources/presets/default/problem/sols/main.cpp +9 -0
  123. rbx/resources/presets/default/problem/sols/slow.cpp +15 -0
  124. rbx/resources/presets/default/problem/sols/wa.cpp +9 -0
  125. rbx/resources/presets/default/problem/statement/olymp.sty +250 -0
  126. rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  127. rbx/resources/presets/default/problem/statement/statement.rbx.tex +18 -0
  128. rbx/resources/presets/default/problem/statement/template.rbx.tex +89 -0
  129. rbx/resources/presets/default/problem/tests/samples/000.in +1 -0
  130. rbx/resources/presets/default/problem/tests/samples/001.in +1 -0
  131. rbx/resources/presets/default/problem/validator.cpp +16 -0
  132. rbx/resources/presets/default/problem/wcmp.cpp +34 -0
  133. rbx/resources/templates/template.cpp +19 -0
  134. rbx/run.py +45 -0
  135. rbx/schema.py +64 -0
  136. rbx/submit.py +61 -0
  137. rbx/submitors/__init__.py +18 -0
  138. rbx/submitors/codeforces.py +120 -0
  139. rbx/submitors/submitor.py +25 -0
  140. rbx/test.py +347 -0
  141. rbx/testcase.py +70 -0
  142. rbx/testcase_rendering.py +79 -0
  143. rbx/testdata/box1/gen1.cpp +7 -0
  144. rbx/testdata/box1/gen2.cpp +9 -0
  145. rbx/testdata/box1/genScript.py +2 -0
  146. rbx/testdata/box1/hard-tle.sol.cpp +26 -0
  147. rbx/testdata/box1/ole.cpp +17 -0
  148. rbx/testdata/box1/problem.rbx.yml +39 -0
  149. rbx/testdata/box1/re.sol.cpp +23 -0
  150. rbx/testdata/box1/sol.cpp +22 -0
  151. rbx/testdata/box1/tests/1.in +1 -0
  152. rbx/testdata/box1/tle-and-incorrect.sol.cpp +33 -0
  153. rbx/testdata/box1/tle.sol.cpp +35 -0
  154. rbx/testdata/box1/validator.cpp +11 -0
  155. rbx/testdata/box1/wa.sol.cpp +22 -0
  156. rbx/testdata/caching/executable.py +1 -0
  157. rbx/testdata/compatible +0 -0
  158. rbx/testing_utils.py +65 -0
  159. rbx/utils.py +162 -0
  160. rbx_cp-0.5.0.dist-info/LICENSE +201 -0
  161. rbx_cp-0.5.0.dist-info/METADATA +89 -0
  162. rbx_cp-0.5.0.dist-info/RECORD +164 -0
  163. rbx_cp-0.5.0.dist-info/WHEEL +4 -0
  164. rbx_cp-0.5.0.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,120 @@
1
+ import pathlib
2
+ from typing import Any
3
+
4
+ import mechanize
5
+
6
+ from rbx.console import console
7
+ from rbx.schema import Problem
8
+ from rbx.submitors.submitor import Submitor
9
+
10
+ _SUBMITOR_KEY = 'codeforces'
11
+
12
+
13
+ class Session:
14
+ def __init__(self):
15
+ self.br = mechanize.Browser()
16
+ self.br.set_handle_robots(False)
17
+ self.br.addheaders = [ # type: ignore
18
+ (
19
+ 'User-agent',
20
+ 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1',
21
+ )
22
+ ]
23
+
24
+ def select_form_by_id(self, id, forms):
25
+ for form in forms:
26
+ if form.attrs.get('id') == id:
27
+ return form
28
+ return None
29
+
30
+ def select_form_by_class(self, cl, forms):
31
+ for form in forms:
32
+ if form.attrs.get('class') == cl:
33
+ return form
34
+ return None
35
+
36
+ def login(self, handle, password):
37
+ response = self.br.open('http://codeforces.com/enter')
38
+ if response is None:
39
+ raise Exception('Response was not received')
40
+ if not response.geturl().endswith('enter'):
41
+ return
42
+ form = self.select_form_by_id('enterForm', self.br.forms())
43
+ if form is None:
44
+ raise Exception('Login form was not found')
45
+
46
+ form['handleOrEmail'] = handle
47
+ form['password'] = password
48
+ self.br.form = form
49
+ response = self.br.submit()
50
+ if response is None:
51
+ raise Exception('Response was not received')
52
+ if response.geturl().endswith('enter'):
53
+ raise Exception(
54
+ 'Login attempt was not successful. Check your credentials or your internet connection'
55
+ )
56
+
57
+ def submit_from_contest(self, url: str, file: pathlib.Path, typeid: int):
58
+ filename = str(file.resolve())
59
+ self.br.open(url)
60
+ form = self.select_form_by_class('submit-form', self.br.forms())
61
+ if form is None:
62
+ raise Exception('You are not logged in or problem does not exist')
63
+
64
+ form['programTypeId'] = [str(typeid)]
65
+ form.add_file(file.open(), 'plain/text', filename)
66
+ self.br.form = form
67
+ self.br.submit()
68
+
69
+ def submit(self, url: str, file: pathlib.Path, typeid: int):
70
+ filename = str(file.resolve())
71
+ response = self.br.open(url)
72
+ if response is None:
73
+ raise Exception('Response was not received')
74
+ if response.geturl().endswith('attachments'):
75
+ self.submit_from_contest(url.replace('/problem/', '/submit/'), file, typeid)
76
+ return
77
+ form = self.select_form_by_class('submitForm', self.br.forms())
78
+ if form is None:
79
+ raise Exception('You are not logged in or problem does not exist')
80
+
81
+ form['programTypeId'] = [str(typeid)]
82
+ form.add_file(file.open(), 'plain/text', filename)
83
+ self.br.form = form
84
+ self.br.submit()
85
+
86
+
87
+ class CodeforcesSubmitor(Submitor):
88
+ def key(self) -> str:
89
+ return _SUBMITOR_KEY
90
+
91
+ def should_handle(self, problem: Problem) -> bool:
92
+ return 'codeforces.com/' in problem.url
93
+
94
+ def submit(
95
+ self,
96
+ file: pathlib.Path,
97
+ problem: Problem,
98
+ submitor_config: Any,
99
+ credentials: Any,
100
+ ) -> bool:
101
+ typeid = submitor_config['typeid']
102
+ handle = credentials['handle']
103
+ password = credentials['password']
104
+
105
+ session = Session()
106
+ console.print(
107
+ f'Trying to login with handle [item]{handle}[/item] in Codeforces.'
108
+ )
109
+ try:
110
+ session.login(handle, password)
111
+ except Exception as e:
112
+ console.print(f'[error]Failed to log in: {str(e)}[/error]')
113
+ return False
114
+ console.print('[italic]Possibly[/italic] logged in!')
115
+ try:
116
+ session.submit(problem.url, file, typeid)
117
+ except Exception as e:
118
+ console.print(f'[error]Failed to submit: {str(e)}[/error]')
119
+ return False
120
+ return True
@@ -0,0 +1,25 @@
1
+ import abc
2
+ import pathlib
3
+ from typing import Any
4
+
5
+ from rbx.schema import Problem
6
+
7
+
8
+ class Submitor(abc.ABC):
9
+ @abc.abstractmethod
10
+ def key(self) -> str:
11
+ pass
12
+
13
+ @abc.abstractmethod
14
+ def should_handle(self, problem: Problem) -> bool:
15
+ pass
16
+
17
+ @abc.abstractmethod
18
+ def submit(
19
+ self,
20
+ file: pathlib.Path,
21
+ problem: Problem,
22
+ submitor_config: Any,
23
+ credentials: Any,
24
+ ) -> bool:
25
+ pass
rbx/test.py ADDED
@@ -0,0 +1,347 @@
1
+ import atexit
2
+ import pathlib
3
+ import tempfile
4
+ from typing import Dict, List, Optional
5
+
6
+ from rich.columns import Columns
7
+ from rich.panel import Panel
8
+ from rich.progress import MofNCompleteColumn, Progress, SpinnerColumn
9
+ from rich.text import Text
10
+
11
+ from rbx import annotations, config, grading_utils, metadata, testcase_rendering
12
+ from rbx.config import Language, get_config
13
+ from rbx.console import console, multiline_prompt
14
+ from rbx.grading import steps
15
+ from rbx.grading.judge.sandbox import SandboxBase
16
+ from rbx.grading.judge.sandboxes import stupid_sandbox
17
+ from rbx.schema import DumpedProblem, Problem
18
+
19
+
20
+ def get_testcase_index(path: pathlib.Path) -> int:
21
+ return int(path.stem.split('.')[-1])
22
+
23
+
24
+ def get_testcases_io(
25
+ problem: DumpedProblem, root: pathlib.Path = pathlib.Path()
26
+ ) -> List[steps.TestcaseIO]:
27
+ testcases_per_index: Dict[int, steps.TestcaseIO] = {}
28
+ for input_file in root.glob(f'{problem.code}.*.in'):
29
+ try:
30
+ index = get_testcase_index(input_file)
31
+ except ValueError:
32
+ continue
33
+ testcases_per_index[index] = steps.TestcaseIO(index=index, input=input_file)
34
+
35
+ for output_file in root.glob(f'{problem.code}.*.out'):
36
+ index = get_testcase_index(output_file)
37
+ try:
38
+ index = get_testcase_index(output_file)
39
+ except ValueError:
40
+ continue
41
+ if index in testcases_per_index:
42
+ testcases_per_index[index].output = output_file
43
+ continue
44
+ testcases_per_index[index] = steps.TestcaseIO(index=index, output=output_file)
45
+
46
+ return sorted(testcases_per_index.values(), key=lambda x: x.index)
47
+
48
+
49
+ def _run_testcases(
50
+ problem: Problem,
51
+ lang: Language,
52
+ lang_name: Optional[str],
53
+ sandbox: SandboxBase,
54
+ testcases: List[steps.TestcaseIO],
55
+ persist_root: pathlib.Path = pathlib.Path(),
56
+ ) -> Dict[int, Optional[steps.TestcaseLog]]:
57
+ logs: Dict[int, Optional[steps.TestcaseLog]] = {}
58
+
59
+ # Ensure persist dir exists.
60
+ persist_root.mkdir(parents=True, exist_ok=True)
61
+
62
+ progress = Progress(
63
+ SpinnerColumn(),
64
+ *Progress.get_default_columns(),
65
+ MofNCompleteColumn(),
66
+ transient=True,
67
+ )
68
+ with progress:
69
+ for testcase in progress.track(testcases, description='Running testcases...'):
70
+ params = grading_utils.build_run_sandbox_params(
71
+ problem, testcase.input is not None
72
+ )
73
+ artifacts = grading_utils.build_run_grading_artifacts(
74
+ testcase, persist_root
75
+ )
76
+ run_log = steps.run(
77
+ lang.exec,
78
+ params,
79
+ sandbox,
80
+ artifacts,
81
+ metadata=steps.RunLogMetadata(language=lang_name),
82
+ )
83
+ if not run_log:
84
+ logs[testcase.index] = None
85
+ continue
86
+ logs[testcase.index] = steps.TestcaseLog(
87
+ **run_log.__dict__,
88
+ stdout_absolute_path=persist_root / f'stdout-{testcase.index}.txt',
89
+ stderr_absolute_path=persist_root / f'stderr-{testcase.index}.txt',
90
+ )
91
+
92
+ return logs
93
+
94
+
95
+ def _evaluate_testcases(
96
+ problem: DumpedProblem,
97
+ sandbox: SandboxBase,
98
+ testcases: List[steps.TestcaseIO],
99
+ testcase_logs: Dict[int, Optional[steps.TestcaseLog]],
100
+ persist_root: pathlib.Path = pathlib.Path(),
101
+ ) -> List[steps.Evaluation]:
102
+ evaluations = []
103
+ artifacts = grading_utils.build_checker_run_grading_artifacts(
104
+ problem,
105
+ persist_root,
106
+ )
107
+ for testcase in testcases:
108
+ if testcase.index not in testcase_logs:
109
+ continue
110
+
111
+ log = testcase_logs[testcase.index]
112
+ evaluations.append(
113
+ steps.evaluate(
114
+ sandbox,
115
+ testcase,
116
+ log,
117
+ artifacts,
118
+ should_use_python_checker=not problem.checker,
119
+ )
120
+ )
121
+
122
+ return evaluations
123
+
124
+
125
+ def _pretty_print_output_on_panel(file: Optional[pathlib.Path], title: str) -> Panel:
126
+ if not file:
127
+ return Panel('[error]No file to read from.[/error]', title=title, expand=False)
128
+ return Panel(
129
+ testcase_rendering.render_from_file(file),
130
+ title=title,
131
+ expand=False,
132
+ )
133
+
134
+
135
+ def _pretty_print_side_by_side(result: steps.Evaluation):
136
+ if not result.testcase.output:
137
+ return _pretty_print_output_on_panel(result.log.stdout_absolute_path, 'Output')
138
+ return Columns(
139
+ [
140
+ _pretty_print_output_on_panel(result.testcase.output, 'Expected'),
141
+ _pretty_print_output_on_panel(result.log.stdout_absolute_path, 'Actual'),
142
+ ],
143
+ equal=True,
144
+ expand=False,
145
+ )
146
+
147
+
148
+ def _get_outcome_style(outcome: steps.Outcome) -> str:
149
+ if outcome == steps.Outcome.ACCEPTED:
150
+ return 'success'
151
+ if outcome == steps.Outcome.JUDGE_FAILED or outcome == steps.Outcome.INTERNAL_ERROR:
152
+ return 'warning'
153
+ return 'error'
154
+
155
+
156
+ def _pretty_print_outcome_panel(
157
+ problem: DumpedProblem, eval: steps.Evaluation
158
+ ) -> Panel:
159
+ result: steps.CheckerResult = eval.result
160
+ is_tle = result.outcome == steps.Outcome.TIME_LIMIT_EXCEEDED or (
161
+ problem.timeLimit and eval.log.time * 1000 > problem.timeLimit
162
+ )
163
+
164
+ text = Text()
165
+ text.append('Outcome: ')
166
+ text.append(
167
+ result.outcome.value,
168
+ style=_get_outcome_style(result.outcome),
169
+ )
170
+ text.append(' ' * 4)
171
+ text.append('Time: ')
172
+ text.append(f'{eval.log.time:.2f}s', style='error' if is_tle else 'item')
173
+ text.append('\n')
174
+ if eval.testcase.input:
175
+ text.append(f'Input path: {eval.testcase.input.absolute()}')
176
+ text.append('\n')
177
+ if eval.testcase.output:
178
+ text.append(f'Expected path: {eval.testcase.output.absolute()}')
179
+ text.append('\n')
180
+ text.append(f'Answer path: {eval.log.stdout_absolute_path}')
181
+ return Panel(
182
+ text,
183
+ title=f'[bold]Testcase [item]#{eval.testcase.index}[/item]',
184
+ expand=False,
185
+ )
186
+
187
+
188
+ def _pretty_print_evaluation_result(
189
+ problem: DumpedProblem,
190
+ eval: steps.Evaluation,
191
+ interactive: bool = False,
192
+ ):
193
+ console.print(_pretty_print_outcome_panel(problem, eval))
194
+ if eval.result.outcome != steps.Outcome.ACCEPTED:
195
+ if interactive:
196
+ console.print(
197
+ _pretty_print_output_on_panel(eval.log.stdout_absolute_path, 'Output')
198
+ )
199
+ else:
200
+ console.print(_pretty_print_side_by_side(eval))
201
+ if eval.result.message:
202
+ console.print(
203
+ f'[error]Checker message:[/error] {eval.result.message.strip()}'
204
+ )
205
+ console.print()
206
+
207
+
208
+ def pretty_print_summary(
209
+ problem: DumpedProblem,
210
+ lang: Language,
211
+ evals: List[steps.Evaluation],
212
+ root: pathlib.Path = pathlib.Path(),
213
+ ):
214
+ submission_file = root / lang.get_submit_file(problem.code)
215
+ passed = sum(1 for eval in evals if eval.result.outcome == steps.Outcome.ACCEPTED)
216
+ total = len(evals)
217
+ console.print(f'Summary for problem [item]{problem.pretty_name()}[/item]:')
218
+
219
+ # Test summary.
220
+ text = Text()
221
+ text.append('Passed tests: ')
222
+ text.append(f'{passed}/{total}', style='success' if passed == total else 'error')
223
+ console.print(text)
224
+
225
+ console.print(f'Submission file: {submission_file.absolute()}')
226
+
227
+
228
+ def pretty_print_evaluation_results(
229
+ problem: DumpedProblem,
230
+ evals: List[steps.Evaluation],
231
+ interactive: bool = False,
232
+ ):
233
+ for eval in evals:
234
+ _pretty_print_evaluation_result(problem, eval, interactive=interactive)
235
+
236
+
237
+ def main(
238
+ problem: annotations.Problem,
239
+ language: annotations.LanguageWithDefault = None,
240
+ keep_sandbox: bool = False,
241
+ interactive: bool = False,
242
+ index: Optional[annotations.TestcaseIndex] = None,
243
+ ):
244
+ dumped_problem = metadata.find_problem_by_anything(problem)
245
+ if not dumped_problem:
246
+ console.print(
247
+ f'[error]Problem with identifier [item]{problem}[/item] not found.[/error]'
248
+ )
249
+ return
250
+
251
+ lang = get_config().get_language(language)
252
+ if not lang:
253
+ console.print(
254
+ f'[error]Language {language or get_config().defaultLanguage} not found in config. Please check your configuration.[/error]'
255
+ )
256
+ return
257
+
258
+ if interactive:
259
+ testcases = []
260
+ while True:
261
+ console.print(
262
+ f'Providing IO for testcase [item]#{len(testcases)}[/item]...'
263
+ )
264
+ input = multiline_prompt('Testcase input')
265
+ if not input.strip():
266
+ break
267
+ output = multiline_prompt('Testcase output')
268
+ input_path = pathlib.Path(tempfile.mktemp())
269
+ output_path = pathlib.Path(tempfile.mktemp())
270
+ input_path.write_text(input)
271
+ output_path.write_text(output)
272
+ testcases.append(
273
+ steps.TestcaseIO(
274
+ index=len(testcases), input=input_path, output=output_path
275
+ )
276
+ )
277
+ else:
278
+ testcases = get_testcases_io(dumped_problem)
279
+
280
+ if index is not None:
281
+ testcases = [tc for tc in testcases if tc.index == index]
282
+
283
+ if not testcases:
284
+ console.print(
285
+ f'[error]No testcases found for the problem [item]{dumped_problem.pretty_name()}[/item].[/error]'
286
+ )
287
+ return
288
+
289
+ box = stupid_sandbox.StupidSandbox()
290
+ atexit.register(lambda: box.cleanup(delete=not keep_sandbox))
291
+ persist_root = config.get_empty_app_persist_path()
292
+
293
+ with console.status(
294
+ f'Preprocessing code for problem [item]{dumped_problem.pretty_name()}[/item] in language [item]{language or get_config().defaultLanguage}[/item]...'
295
+ ):
296
+ if lang.preprocess:
297
+ preprocess_cmds = grading_utils.build_preprocess_commands(
298
+ dumped_problem, lang
299
+ )
300
+ sandbox_params = grading_utils.build_preprocess_sandbox_params()
301
+ artifacts = grading_utils.build_compile_grading_artifacts(
302
+ dumped_problem, lang
303
+ )
304
+ if not steps.compile(preprocess_cmds, sandbox_params, box, artifacts):
305
+ console.print(
306
+ f'[error]Failed to preprocess problem [item]{dumped_problem.pretty_name()}[/item].[/error]'
307
+ )
308
+ return
309
+
310
+ with console.status(
311
+ f'Compiling checker for problem [item]{dumped_problem.pretty_name()}[/item]...'
312
+ ):
313
+ command = '/usr/bin/g++ -std=c++17 -o checker checker.cpp'
314
+ artifacts = grading_utils.build_checker_compile_grading_artifacts(
315
+ dumped_problem, persist_root
316
+ )
317
+ if dumped_problem.checker and not steps.compile(
318
+ [command], grading_utils.build_preprocess_sandbox_params(), box, artifacts
319
+ ):
320
+ console.print(
321
+ f'[error]Failed to compile checker for problem [item]{dumped_problem.pretty_name()}[/item].[/error]'
322
+ )
323
+ return
324
+
325
+ testcase_logs = _run_testcases(
326
+ dumped_problem, lang, language, box, testcases, persist_root
327
+ )
328
+
329
+ if not testcase_logs:
330
+ console.print(
331
+ f'[error]Failed to run testcases for problem [item]{dumped_problem.pretty_name()}[/item]. Sandbox probably crashed.[/error]'
332
+ )
333
+ return
334
+
335
+ with console.status(
336
+ f'Evaluating testcases for problem [item]{dumped_problem.pretty_name()}[/item]...'
337
+ ):
338
+ evals = _evaluate_testcases(
339
+ dumped_problem, box, testcases, testcase_logs, persist_root
340
+ )
341
+ if not evals:
342
+ console.print(
343
+ f'[error]Failed to evaluate testcases for problem [item]{dumped_problem.pretty_name()}[/item].[/error]'
344
+ )
345
+ return
346
+ pretty_print_evaluation_results(dumped_problem, evals, interactive=interactive)
347
+ pretty_print_summary(dumped_problem, lang, evals)
rbx/testcase.py ADDED
@@ -0,0 +1,70 @@
1
+ import pathlib
2
+
3
+ import typer
4
+
5
+ from rbx import annotations, hydration, metadata
6
+ from rbx.console import console, multiline_prompt
7
+ from rbx.schema import Testcase
8
+
9
+ app = typer.Typer()
10
+
11
+
12
+ @app.command()
13
+ def hydrate(problem: annotations.ProblemOption = None):
14
+ """
15
+ Populate all samples of a problem (or of all problems in the folder).
16
+ """
17
+ hydration.main(problem=problem)
18
+
19
+
20
+ @app.command('add, a')
21
+ def add(problem: annotations.Problem):
22
+ """
23
+ Add a testcase to a problem.
24
+ """
25
+ dumped_problem = metadata.find_problem_by_anything(problem)
26
+ if dumped_problem is None:
27
+ console.print(f'[error]Problem [item]{problem}[/item] not found.[/error]')
28
+ return
29
+
30
+ input = multiline_prompt('Testcase input')
31
+ output = multiline_prompt('Testcase output')
32
+
33
+ hydration.add_testcase(
34
+ pathlib.Path(), dumped_problem, Testcase(input=input, output=output)
35
+ )
36
+
37
+
38
+ @app.command('delete, d')
39
+ def delete(
40
+ problem: annotations.Problem,
41
+ i: annotations.TestcaseIndex,
42
+ ):
43
+ """
44
+ Remove the i-th testcase from a problem.
45
+ """
46
+ if i is None:
47
+ console.print(f'[error]Index [item]{i}[/item] is invalid.[/error]')
48
+ return
49
+ dumped_problem = metadata.find_problem_by_anything(problem)
50
+ if dumped_problem is None:
51
+ console.print(f'[error]Problem [item]{problem}[/item] not found.[/error]')
52
+ return
53
+
54
+ hydration.remove_testcase(pathlib.Path(), dumped_problem, i)
55
+
56
+
57
+ @app.command('edit, e')
58
+ def edit(problem: annotations.Problem, i: annotations.TestcaseIndex):
59
+ """
60
+ Edit the testcases of a problem.
61
+ """
62
+ if i is None:
63
+ console.print(f'[error]Index [item]{i}[/item] is invalid.[/error]')
64
+ return
65
+ dumped_problem = metadata.find_problem_by_anything(problem)
66
+ if dumped_problem is None:
67
+ console.print(f'[error]Problem [item]{problem}[/item] not found.[/error]')
68
+ return
69
+
70
+ hydration.edit_testcase(pathlib.Path(), dumped_problem, i)
@@ -0,0 +1,79 @@
1
+ import dataclasses
2
+ import pathlib
3
+ import string
4
+ from typing import List, Tuple
5
+
6
+ from rich.text import Text
7
+
8
+
9
+ @dataclasses.dataclass
10
+ class TruncatedOutput:
11
+ truncate: bool = False
12
+ lines: List[Tuple[int, str]] = dataclasses.field(default_factory=list)
13
+
14
+
15
+ def split_and_truncate_in_lines(
16
+ s: str, max_line_length: int = 64, max_lines: int = 30
17
+ ) -> TruncatedOutput:
18
+ lines: List[Tuple[int, str]] = []
19
+ current_line = []
20
+ current_line_idx = 1
21
+
22
+ def end_line(wrap: bool = False):
23
+ nonlocal current_line, current_line_idx
24
+ lines.append((current_line_idx, ''.join(current_line)))
25
+ current_line = []
26
+ if not wrap:
27
+ current_line_idx += 1
28
+
29
+ printable = set(string.printable)
30
+ truncate = False
31
+ for c in s:
32
+ if c == '\n':
33
+ end_line()
34
+ continue
35
+ if c not in printable:
36
+ # TODO: handle better
37
+ continue
38
+ if len(current_line) >= max_line_length:
39
+ end_line(wrap=True)
40
+ if current_line_idx > max_lines:
41
+ truncate = True
42
+ break
43
+ current_line.append(c)
44
+
45
+ if current_line:
46
+ end_line()
47
+
48
+ return TruncatedOutput(truncate=truncate, lines=lines)
49
+
50
+
51
+ def _largest_line_number_length(lines: List[Tuple[int, str]]) -> int:
52
+ return max([len(str(line[0])) for line in lines] + [1])
53
+
54
+
55
+ def render(s: str):
56
+ truncated = split_and_truncate_in_lines(s)
57
+ number_len = _largest_line_number_length(truncated.lines)
58
+
59
+ text = Text()
60
+
61
+ last_number = 0
62
+ for line in truncated.lines:
63
+ number, content = line
64
+ number_str = '' if last_number == number else str(number)
65
+ text.append(f'{number_str:>{number_len}}', style='lnumber')
66
+ text.append(' ' * 3)
67
+ text.append(content)
68
+ text.append('\n')
69
+
70
+ last_number = number
71
+ if truncated.truncate:
72
+ text.append(f"{'':>{number_len}}", style='lnumber')
73
+ text.append(' ' * 3)
74
+ text.append('... (truncated)')
75
+ return text
76
+
77
+
78
+ def render_from_file(file: pathlib.Path):
79
+ return render(file.read_text())
@@ -0,0 +1,7 @@
1
+ #include <bits/stdc++.h>
2
+
3
+ using namespace std;
4
+
5
+ int32_t main() {
6
+ cout << "123" << endl;
7
+ }
@@ -0,0 +1,9 @@
1
+ #include "testlib.h"
2
+
3
+ using namespace std;
4
+
5
+ int main(int argc, char *argv[]) {
6
+ registerGen(argc, argv, 1);
7
+
8
+ println(opt<int>(1));
9
+ }
@@ -0,0 +1,2 @@
1
+ print('gen2 25')
2
+ print('gen2 1000000000')
@@ -0,0 +1,26 @@
1
+ #include <bits/stdc++.h>
2
+ #include <ctime>
3
+
4
+ using namespace std;
5
+
6
+ void busy_loop() {
7
+ double wait = 5.0;
8
+ int start = clock();
9
+ int end = clock();
10
+ while (((double) (end - start)) / CLOCKS_PER_SEC < wait)
11
+ {
12
+ end = clock();
13
+ }
14
+ }
15
+
16
+ int32_t main() {
17
+ ios::sync_with_stdio(false);
18
+ cin.tie(0);
19
+
20
+ int n; cin >> n;
21
+
22
+ busy_loop();
23
+
24
+ cout << "hard" << endl;
25
+ return 0;
26
+ }