rbx.cp 0.13.7__py3-none-any.whl → 0.14.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 (72) hide show
  1. rbx/box/cli.py +74 -70
  2. rbx/box/code.py +3 -0
  3. rbx/box/contest/build_contest_statements.py +65 -23
  4. rbx/box/contest/contest_package.py +8 -1
  5. rbx/box/contest/main.py +9 -3
  6. rbx/box/contest/schema.py +17 -13
  7. rbx/box/contest/statements.py +12 -8
  8. rbx/box/dump_schemas.py +2 -1
  9. rbx/box/environment.py +1 -1
  10. rbx/box/fields.py +22 -4
  11. rbx/box/generators.py +32 -13
  12. rbx/box/limits_info.py +161 -0
  13. rbx/box/package.py +18 -1
  14. rbx/box/packaging/boca/boca_language_utils.py +26 -0
  15. rbx/box/packaging/boca/boca_outcome_utils.py +10 -0
  16. rbx/box/packaging/boca/packager.py +7 -5
  17. rbx/box/packaging/contest_main.py +20 -12
  18. rbx/box/packaging/packager.py +24 -14
  19. rbx/box/packaging/polygon/packager.py +7 -3
  20. rbx/box/packaging/polygon/upload.py +9 -2
  21. rbx/box/presets/__init__.py +64 -64
  22. rbx/box/remote.py +3 -3
  23. rbx/box/sanitizers/issue_stack.py +124 -0
  24. rbx/box/schema.py +87 -27
  25. rbx/box/solutions.py +74 -117
  26. rbx/box/statements/build_statements.py +12 -1
  27. rbx/box/statements/builders.py +5 -3
  28. rbx/box/statements/latex_jinja.py +73 -23
  29. rbx/box/statements/schema.py +7 -9
  30. rbx/box/stressing/generator_parser.py +3 -1
  31. rbx/box/tasks.py +10 -10
  32. rbx/box/testcase_extractors.py +8 -0
  33. rbx/box/testcase_utils.py +7 -7
  34. rbx/box/testing/testing_preset.py +129 -2
  35. rbx/box/testing/testing_shared.py +3 -1
  36. rbx/box/timing.py +305 -0
  37. rbx/box/tooling/boca/debug_utils.py +88 -0
  38. rbx/box/tooling/boca/manual_scrape.py +20 -0
  39. rbx/box/tooling/boca/scraper.py +660 -57
  40. rbx/box/unit.py +0 -2
  41. rbx/box/validators.py +0 -4
  42. rbx/grading/judge/cacher.py +36 -0
  43. rbx/grading/judge/program.py +12 -2
  44. rbx/grading/judge/sandbox.py +1 -1
  45. rbx/grading/judge/sandboxes/stupid_sandbox.py +2 -1
  46. rbx/grading/judge/storage.py +36 -3
  47. rbx/grading/limits.py +4 -0
  48. rbx/grading/steps.py +3 -2
  49. rbx/resources/presets/default/contest/contest.rbx.yml +11 -1
  50. rbx/resources/presets/default/contest/statement/info.rbx.tex +54 -0
  51. rbx/resources/presets/default/problem/.gitignore +1 -0
  52. rbx/resources/presets/default/problem/problem.rbx.yml +21 -3
  53. rbx/resources/presets/default/problem/rbx.h +52 -5
  54. rbx/resources/presets/default/problem/statement/statement.rbx.tex +6 -2
  55. rbx/resources/presets/default/problem/testlib.h +6299 -0
  56. rbx/resources/presets/default/problem/validator.cpp +4 -3
  57. rbx/resources/presets/default/shared/contest_template.rbx.tex +13 -3
  58. rbx/resources/presets/default/shared/icpc.sty +33 -5
  59. rbx/resources/presets/default/shared/problem_template.rbx.tex +10 -1
  60. rbx/testing_utils.py +17 -1
  61. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/METADATA +4 -2
  62. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/RECORD +66 -63
  63. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/WHEEL +1 -1
  64. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/entry_points.txt +0 -1
  65. rbx/providers/__init__.py +0 -43
  66. rbx/providers/codeforces.py +0 -73
  67. rbx/providers/provider.py +0 -26
  68. rbx/submitors/__init__.py +0 -18
  69. rbx/submitors/codeforces.py +0 -121
  70. rbx/submitors/submitor.py +0 -25
  71. /rbx/resources/presets/default/problem/sols/{wa.cpp → wa-overflow.cpp} +0 -0
  72. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/LICENSE +0 -0
rbx/box/unit.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import pathlib
2
2
  from typing import List, Optional, Set
3
3
 
4
- import syncer
5
4
  from pydantic import BaseModel
6
5
 
7
6
  from rbx import console
@@ -232,7 +231,6 @@ async def run_checker_unit_tests(progress: StatusProgress):
232
231
  console.console.print(f' [status]Message[/status] {result.message}')
233
232
 
234
233
 
235
- @syncer.sync
236
234
  async def run_unit_tests(progress: StatusProgress):
237
235
  await run_validator_unit_tests(progress)
238
236
  await run_checker_unit_tests(progress)
rbx/box/validators.py CHANGED
@@ -94,10 +94,6 @@ async def _validate_testcase(
94
94
  vars: Optional[Dict[str, Primitive]] = None,
95
95
  ) -> Tuple[bool, Optional[str], HitBounds]:
96
96
  vars = vars or {}
97
- for var in vars:
98
- assert (
99
- var.isidentifier()
100
- ), f'Variable {var} should be a valid Python identifier.'
101
97
  # TODO: check if needs to do some escaping
102
98
  var_args = [f'--{k}={v}' for k, v in vars.items()]
103
99
  var_args.extend(['--testOverviewLogFileName', 'validator.log'])
@@ -243,6 +243,12 @@ class FileCacher:
243
243
  return typing.cast(IO[bytes], self._load(digest, False))
244
244
 
245
245
  def path_for_symlink(self, digest: str) -> Optional[pathlib.Path]:
246
+ """Retrieve the symlink path that points to the backend file.
247
+
248
+ If the file is not in the cache, or it is stored in a transformed
249
+ form (such as a compressed file), this function will return None,
250
+ as creating a symlink to it would not be meaningful.
251
+ """
246
252
  if digest == storage.TOMBSTONE:
247
253
  raise TombstoneError()
248
254
 
@@ -252,7 +258,37 @@ class FileCacher:
252
258
  logger.debug('Getting symlink file path %s.', digest)
253
259
  return self.backend.path_for_symlink(digest)
254
260
 
261
+ def transient_path_for_symlink(self, digest: str):
262
+ """Retrieve a file from the storage and return a path to it.
263
+
264
+ Notice that this is different than `path_for_symlink', which
265
+ returns the path to the file in the backend itself, not a path
266
+ inside the cacher.
267
+
268
+ `path_for_symlink` should be used for persistent links, such as
269
+ those you create in the package folder and should live across
270
+ multiple runs of the tool.
271
+
272
+ `transient_path_for_symlink` should be used for transient links, such as
273
+ those created in the sandbox, like test inputs and compiled
274
+ binaries.
275
+ """
276
+ if digest == storage.TOMBSTONE:
277
+ raise TombstoneError()
278
+ self._load(digest, cache_only=True)
279
+
280
+ cache_file_path = self.file_dir / digest
281
+ if not cache_file_path.exists():
282
+ raise FileNotFoundError(f'File {digest} not found in cache.')
283
+ return cache_file_path
284
+
255
285
  def digest_from_symlink(self, link: pathlib.Path) -> Optional[str]:
286
+ """Retrieve the digest of the file that the symlink points to.
287
+
288
+ This is supposed to be used as a counterpart to `path_for_symlink'.
289
+ `digest_from_symlink(path_for_symlink(digest))` should be equivalent to
290
+ `digest`.
291
+ """
256
292
  if grading_context.is_transient():
257
293
  return None
258
294
 
@@ -89,6 +89,14 @@ def get_preexec_fn(params: ProgramParams):
89
89
  if params.fs_limit is not None:
90
90
  fs_limit = params.fs_limit * 1024 # in bytes
91
91
  resource.setrlimit(resource.RLIMIT_FSIZE, (fs_limit + 1, fs_limit * 2))
92
+ if sys.platform != 'darwin':
93
+ try:
94
+ resource.setrlimit(
95
+ resource.RLIMIT_STACK,
96
+ (resource.RLIM_INFINITY, resource.RLIM_INFINITY),
97
+ )
98
+ except ValueError:
99
+ pass
92
100
 
93
101
  return preexec_fn
94
102
 
@@ -236,7 +244,6 @@ class Program:
236
244
  cwd=self.params.chdir,
237
245
  env={**os.environ, **self.params.env},
238
246
  preexec_fn=get_preexec_fn(self.params),
239
- close_fds=True,
240
247
  )
241
248
  self.start_time = monotonic()
242
249
 
@@ -244,6 +251,8 @@ class Program:
244
251
  threading.Thread(target=self._handle_alarm, daemon=True).start()
245
252
 
246
253
  def process_exit(self, exitstatus, ru) -> ProgramResult:
254
+ _maybe_close_files(self._files)
255
+
247
256
  wall_time = monotonic() - self.start_time
248
257
  cpu_time = get_cpu_time(ru)
249
258
  memory_used = get_memory_usage(ru)
@@ -283,7 +292,7 @@ class Program:
283
292
  ):
284
293
  program_codes.append(ProgramCode.OL)
285
294
 
286
- return ProgramResult(
295
+ result = ProgramResult(
287
296
  exitcode=exitcode,
288
297
  wall_time=wall_time,
289
298
  cpu_time=cpu_time,
@@ -293,6 +302,7 @@ class Program:
293
302
  killing_signal=killing_signal,
294
303
  alarm_msg=self._alarm_msg or None,
295
304
  )
305
+ return result
296
306
 
297
307
  def wait(self):
298
308
  assert self.popen is not None
@@ -359,7 +359,7 @@ class SandboxBase(abc.ABC):
359
359
 
360
360
  """
361
361
  if try_symlink and executable:
362
- symlink_path = self.file_cacher.path_for_symlink(digest)
362
+ symlink_path = self.file_cacher.transient_path_for_symlink(digest)
363
363
  if symlink_path is not None:
364
364
  created = self.create_symlink(
365
365
  path,
@@ -155,7 +155,7 @@ class StupidSandbox(SandboxBase):
155
155
  ) -> SandboxLog:
156
156
  return SandboxLog(
157
157
  params=params.model_copy(deep=True),
158
- execution_time=result.wall_time,
158
+ execution_time=result.cpu_time,
159
159
  memory_used=result.memory_used,
160
160
  exitcode=result.exitcode,
161
161
  exitstatus=self._get_exit_status(result),
@@ -163,6 +163,7 @@ class StupidSandbox(SandboxBase):
163
163
  other_logs={
164
164
  'program_codes': [code.value for code in result.program_codes],
165
165
  'alarm_msg': result.alarm_msg,
166
+ 'wall_time': result.wall_time,
166
167
  },
167
168
  )
168
169
 
@@ -415,7 +415,40 @@ class FilesystemStorage(Storage):
415
415
  def filename_from_symlink(self, link: pathlib.Path) -> Optional[str]:
416
416
  if not link.is_symlink():
417
417
  return None
418
- filename = utils.abspath(link.readlink())
419
- if not filename.is_file():
418
+
419
+ # Track visited symlinks to detect circular references
420
+ visited = set()
421
+ current = link
422
+ max_depth = 100 # Reasonable limit to prevent infinite loops
423
+ depth = 0
424
+
425
+ while current.is_symlink() and depth < max_depth:
426
+ # Convert to absolute path for consistent comparison
427
+ abs_current = utils.abspath(current)
428
+
429
+ # Check for circular reference
430
+ if abs_current in visited:
431
+ return None
432
+
433
+ visited.add(abs_current)
434
+
435
+ # Read the target of the symlink
436
+ target = current.readlink()
437
+
438
+ # If target is relative, resolve it relative to the symlink's parent directory
439
+ if not target.is_absolute():
440
+ current = utils.abspath(current.parent / target)
441
+ else:
442
+ current = utils.abspath(target)
443
+
444
+ depth += 1
445
+
446
+ # If we hit the depth limit, assume circular reference
447
+ if depth >= max_depth:
448
+ return None
449
+
450
+ if not current.is_file():
451
+ return None
452
+ if not current.is_relative_to(self.path):
420
453
  return None
421
- return str(filename.relative_to(self.path))
454
+ return str(current.relative_to(self.path))
rbx/grading/limits.py CHANGED
@@ -18,6 +18,10 @@ class Limits(BaseModel):
18
18
  default=False, description='Whether to use double TL for this language.'
19
19
  )
20
20
 
21
+ profile: Optional[str] = Field(
22
+ default=None, description='The profile that was used to get these limits.'
23
+ )
24
+
21
25
  def get_expanded_tl(self) -> Optional[int]:
22
26
  if self.time is None:
23
27
  return None
rbx/grading/steps.py CHANGED
@@ -14,7 +14,7 @@ import typer
14
14
  from pydantic import BaseModel, Field
15
15
  from rich.text import Text
16
16
 
17
- from rbx import utils
17
+ from rbx import testing_utils, utils
18
18
  from rbx.config import get_bits_stdcpp, get_jngen, get_testlib
19
19
  from rbx.console import console
20
20
  from rbx.grading import grading_context
@@ -389,7 +389,7 @@ def jngen_grading_input() -> GradingFileInput:
389
389
  def _expand_part(part: str, sandbox: SandboxBase) -> List[str]:
390
390
  part = part.strip()
391
391
  if part.startswith('@glob:'):
392
- return [shlex.quote(str(path)) for path in sandbox.glob(part[6:])]
392
+ return [str(path) for path in sandbox.glob(part[6:])]
393
393
  return [part]
394
394
 
395
395
 
@@ -736,6 +736,7 @@ def compile(
736
736
  )
737
737
  console.print(f'[error]Summary:[/error] {logs[-1].get_summary()}')
738
738
  console.print(Text.from_ansi(logs[-1].log), style='default')
739
+ testing_utils.print_directory_tree(sandbox.get_root_path())
739
740
  return False
740
741
 
741
742
  return _process_output_artifacts(artifacts, sandbox)
@@ -17,16 +17,26 @@ statements:
17
17
  configure:
18
18
  - type: "rbx-tex" # Convert rbxTeX to TeX
19
19
  template: "statement/template.rbx.tex"
20
+ vars:
21
+ # Turn into false to hide time limits and memory limits in the problem statement.
22
+ # Useful for ICPC-style contests where you distribute a separate info sheet.
23
+ show_limits: true
20
24
  - name: "editorial-en"
21
25
  extends: "statement-en"
22
26
  override:
23
27
  vars:
24
28
  # Whether to show the problem statement in the editorial.
25
29
  show_problem: false
30
+ show_limits: true
26
31
  editorial: true
27
32
  vars:
28
- editorial: true
33
+ # Whether to render an "EDITORIAL" watermark.
29
34
  watermark: false
35
+ editorial: true
36
+ - name: "info-en"
37
+ extends: "statement-en"
38
+ path: "statement/info.rbx.tex"
39
+ joiner: null
30
40
  vars:
31
41
  year: 2025
32
42
  date: "2025-06-21"
@@ -0,0 +1,54 @@
1
+ \documentclass{article}
2
+ \usepackage[a4paper, margin=1in]{geometry}
3
+ \usepackage{multirow}
4
+ \usepackage{multicol}
5
+ \usepackage{graphicx}
6
+ \usepackage{amsfonts}
7
+ \usepackage[brazil]{babel}
8
+ \usepackage[utf8]{inputenc}
9
+ \usepackage[T1]{fontenc}
10
+
11
+ \usepackage[utf8]{inputenc}
12
+ \usepackage{icpc}
13
+
14
+ %\pagestyle{myheadings}
15
+ \markright{\VAR{contest.title}}
16
+
17
+ \begin{document}
18
+
19
+ \begin{titlepage}
20
+ \vspace*{\fill}
21
+ \begin{center}
22
+ \includegraphics[width=4cm]{logo.png}
23
+
24
+ \vspace{1cm}
25
+ {\huge{\bf \VAR{vars.year}}} \\
26
+
27
+ \vspace{0.5cm}
28
+ {\huge \bf \strInfoSheet}\\[12pt]
29
+ {\Large \bf \VAR{contest.title}}
30
+ \end{center}
31
+
32
+ \vspace{2cm}
33
+
34
+ %\subsection*{\strLimits}
35
+
36
+ \begin{center}
37
+ \begin{tabular}{c|ccc|c}
38
+ & \multicolumn{3}{c|}{\strTimeLimit (ms)} & \multirow{2}{*}{\strMemoryLimit (MiB)} \\
39
+ %\cline{2-4}
40
+ {\sf \strProblem} & {\sf C/C++} &{\sf Java} & {\sf Python3} & \\
41
+ \hline
42
+ %- for problem in problems:
43
+ \VAR{problem.short_name}
44
+ & \VAR{problem.limits.timelimit_for_language('cpp')}
45
+ & \VAR{problem.limits.timelimit_for_language('java')}
46
+ & \VAR{problem.limits.timelimit_for_language('py')}
47
+ & \VAR{problem.limits.memorylimit_for_language()}
48
+ \\ \hline
49
+ %- endfor
50
+ \end{tabular}
51
+ \end{center}
52
+ \vspace*{\fill}
53
+ \end{titlepage}
54
+ \end{document}
@@ -1,5 +1,6 @@
1
1
  .box/
2
2
  build/
3
+ .limits/local.yml
3
4
  __pycache__/
4
5
 
5
6
  .DS_Store
@@ -17,8 +17,18 @@ testcases:
17
17
  solutions:
18
18
  - path: "sols/main.cpp"
19
19
  outcome: "ACCEPTED"
20
- - path: "sols/wa.cpp"
20
+ - path: "sols/ac-*.cpp"
21
+ outcome: "ACCEPTED"
22
+ - path: "sols/wa-*.cpp"
21
23
  outcome: "WRONG_ANSWER"
24
+ - path: "sols/tle-*.cpp"
25
+ outcome: "TIME_LIMIT_EXCEEDED"
26
+ - path: "sols/mle-*.cpp"
27
+ outcome: "MEMORY_LIMIT_EXCEEDED"
28
+ - path: "sols/re-*.cpp"
29
+ outcome: "RUNTIME_ERROR"
30
+ - path: "sols/fail-*.cpp"
31
+ outcome: "INCORRECT"
22
32
  statements:
23
33
  - name: "statement-en"
24
34
  title: "New Problem"
@@ -31,11 +41,14 @@ statements:
31
41
  configure:
32
42
  - type: "rbx-tex" # Convert rbxTeX to TeX
33
43
  template: "statement/template.rbx.tex"
44
+ vars:
45
+ # Set to false to hide time limits and memory limits in the problem statement.
46
+ show_limits: true
34
47
  stresses:
35
48
  - name: "stress"
36
49
  generator:
37
50
  name: "gens/gen"
38
- args: "[1..<MAX_N>] @" # `@` generates a random string
51
+ args: "[1..<N.max>] @" # `@` generates a random string
39
52
  finder: "[sols/wa.cpp] ~ INCORRECT"
40
53
  unitTests:
41
54
  validator:
@@ -51,4 +64,9 @@ unitTests:
51
64
  # Can be used in the validator, checker, interactor, stress tests
52
65
  # and in the statement.
53
66
  vars:
54
- MAX_N: 1000000000
67
+ # Author name to be displayed in the editorial.
68
+ author: "John Doe"
69
+ # Constraints of the problem.
70
+ N:
71
+ min: 1
72
+ max: 1000000000
@@ -1,17 +1,25 @@
1
1
  #ifndef _RBX_H
2
2
  #define _RBX_H
3
+ #include <cstdint>
4
+ #include <limits>
3
5
  #include <optional>
4
6
  #include <stdexcept>
5
7
  #include <string>
6
8
 
7
9
  std::optional<std::string> getStringVar(std::string name) {
8
-
10
+ if (name == "author") {
11
+ return "John Doe";
12
+ }
13
+
9
14
  return std::nullopt;
10
15
  }
11
16
 
12
- std::optional<int> getIntVar(std::string name) {
13
- if (name == "MAX_N") {
14
- return 1000000000;
17
+ std::optional<int64_t> getIntVar(std::string name) {
18
+ if (name == "N.max") {
19
+ return static_cast<int64_t>(1000000000);
20
+ }
21
+ if (name == "N.min") {
22
+ return static_cast<int64_t>(1);
15
23
  }
16
24
 
17
25
  return std::nullopt;
@@ -29,7 +37,46 @@ std::optional<bool> getBoolVar(std::string name) {
29
37
 
30
38
  template <typename T> T getVar(std::string name);
31
39
 
32
- template <> int getVar<int>(std::string name) {
40
+ template <> int32_t getVar<int32_t>(std::string name) {
41
+ auto opt = getIntVar(name);
42
+ if (!opt.has_value()) {
43
+ throw std::runtime_error("Variable " + name +
44
+ " is not an integer or could not be found");
45
+ }
46
+ if (opt.value() < std::numeric_limits<int32_t>::min() ||
47
+ opt.value() > std::numeric_limits<int32_t>::max()) {
48
+ throw std::runtime_error("Variable " + name + " of value " +
49
+ std::to_string(opt.value()) +
50
+ " does not fit in int32_t");
51
+ }
52
+ return opt.value();
53
+ }
54
+
55
+ template <> uint32_t getVar<uint32_t>(std::string name) {
56
+ auto opt = getIntVar(name);
57
+ if (!opt.has_value()) {
58
+ throw std::runtime_error("Variable " + name +
59
+ " is not an integer or could not be found");
60
+ }
61
+ if (opt.value() < std::numeric_limits<uint32_t>::min() ||
62
+ opt.value() > std::numeric_limits<uint32_t>::max()) {
63
+ throw std::runtime_error("Variable " + name + " of value " +
64
+ std::to_string(opt.value()) +
65
+ " does not fit in uint32_t");
66
+ }
67
+ return opt.value();
68
+ }
69
+
70
+ template <> int64_t getVar<int64_t>(std::string name) {
71
+ auto opt = getIntVar(name);
72
+ if (!opt.has_value()) {
73
+ throw std::runtime_error("Variable " + name +
74
+ " is not an integer or could not be found");
75
+ }
76
+ return opt.value();
77
+ }
78
+
79
+ template <> uint64_t getVar<uint64_t>(std::string name) {
33
80
  auto opt = getIntVar(name);
34
81
  if (!opt.has_value()) {
35
82
  throw std::runtime_error("Variable " + name +
@@ -3,7 +3,7 @@ Given two integers $A$ and $B$, determine the value of $A + B$.
3
3
  %- endblock
4
4
 
5
5
  %- block input
6
- The input is a single line containing two integers $A$ and $B$ ($1 \leq A, B \leq \VAR{vars.MAX_N | sci}$).
6
+ The input is a single line containing two integers $A$ and $B$ ($\VAR{vars.N.max} \leq A, B \leq \VAR{vars.N.max | sci}$).
7
7
  %- endblock
8
8
 
9
9
  %- block output
@@ -12,4 +12,8 @@ The output must contain only one integer, the sum of $A$ and $B$.
12
12
 
13
13
  %- block notes
14
14
  No notes.
15
- %- endblock
15
+ %- endblock
16
+
17
+ %- block editorial
18
+ This is the editorial.
19
+ %- endblock