rbx.cp 0.5.27__tar.gz → 0.5.28__tar.gz

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 (169) hide show
  1. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/PKG-INFO +1 -1
  2. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/pyproject.toml +1 -1
  3. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/code.py +2 -0
  4. rbx_cp-0.5.28/rbx/box/retries.py +143 -0
  5. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/setter_config.py +22 -0
  6. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/solutions.py +59 -51
  7. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/stresses.py +51 -29
  8. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/stressing/finder_parser.py +2 -2
  9. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/steps.py +1 -0
  10. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/steps_with_caching.py +5 -3
  11. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/default_setter_config.mac.yml +8 -0
  12. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/default_setter_config.yml +8 -0
  13. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/LICENSE +0 -0
  14. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/README.md +0 -0
  15. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/__init__.py +0 -0
  16. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/annotations.py +0 -0
  17. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/autoenum.py +0 -0
  18. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/__init__.py +0 -0
  19. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/builder.py +0 -0
  20. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/cd.py +0 -0
  21. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/checkers.py +0 -0
  22. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/compile.py +0 -0
  23. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/conftest.py +0 -0
  24. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/contest/__init__.py +0 -0
  25. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/contest/build_contest_statements.py +0 -0
  26. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/contest/contest_package.py +0 -0
  27. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/contest/contest_utils.py +0 -0
  28. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/contest/main.py +0 -0
  29. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/contest/schema.py +0 -0
  30. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/contest/statements.py +0 -0
  31. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/creation.py +0 -0
  32. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/deferred.py +0 -0
  33. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/download.py +0 -0
  34. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/environment.py +0 -0
  35. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/extensions.py +0 -0
  36. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/generators.py +0 -0
  37. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/generators_test.py +0 -0
  38. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/main.py +0 -0
  39. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/package.py +0 -0
  40. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/packaging/boca/extension.py +0 -0
  41. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/packaging/boca/packager.py +0 -0
  42. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/packaging/contest_main.py +0 -0
  43. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/packaging/main.py +0 -0
  44. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/packaging/packager.py +0 -0
  45. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/packaging/polygon/packager.py +0 -0
  46. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/packaging/polygon/test.py +0 -0
  47. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/packaging/polygon/xml_schema.py +0 -0
  48. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/presets/__init__.py +0 -0
  49. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/presets/fetch.py +0 -0
  50. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/presets/lock_schema.py +0 -0
  51. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/presets/schema.py +0 -0
  52. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/sanitizers/warning_stack.py +0 -0
  53. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/schema.py +0 -0
  54. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/solutions_test.py +0 -0
  55. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/statements/__init__.py +0 -0
  56. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/statements/build_statements.py +0 -0
  57. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/statements/builders.py +0 -0
  58. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/statements/joiners.py +0 -0
  59. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/statements/latex.py +0 -0
  60. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/statements/latex_jinja.py +0 -0
  61. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/statements/schema.py +0 -0
  62. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/stressing/__init__.py +0 -0
  63. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/stressing/generator_parser.py +0 -0
  64. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/testcases.py +0 -0
  65. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/ui/__init__.py +0 -0
  66. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/ui/captured_log.py +0 -0
  67. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/ui/css/app.tcss +0 -0
  68. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/ui/main.py +0 -0
  69. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/ui/run.py +0 -0
  70. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/validators.py +0 -0
  71. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/box/validators_test.py +0 -0
  72. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/checker.py +0 -0
  73. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/clone.py +0 -0
  74. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/config.py +0 -0
  75. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/conftest.py +0 -0
  76. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/console.py +0 -0
  77. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/create.py +0 -0
  78. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/edit.py +0 -0
  79. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/__init__.py +0 -0
  80. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/caching.py +0 -0
  81. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/conftest.py +0 -0
  82. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/__init__.py +0 -0
  83. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/cacher.py +0 -0
  84. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/digester.py +0 -0
  85. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/sandbox.py +0 -0
  86. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/sandboxes/__init__.py +0 -0
  87. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/sandboxes/isolate.py +0 -0
  88. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/sandboxes/stupid_sandbox.py +0 -0
  89. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/sandboxes/timeit.py +0 -0
  90. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/storage.py +0 -0
  91. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/test.py +0 -0
  92. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/judge/testiso.py +0 -0
  93. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading/steps_with_caching_run_test.py +0 -0
  94. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/grading_utils.py +0 -0
  95. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/hydration.py +0 -0
  96. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/main.py +0 -0
  97. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/metadata.py +0 -0
  98. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/providers/__init__.py +0 -0
  99. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/providers/codeforces.py +0 -0
  100. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/providers/provider.py +0 -0
  101. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/checkers/boilerplate.cpp +0 -0
  102. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/default_config.json +0 -0
  103. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/envs/default.rbx.yml +0 -0
  104. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/envs/isolate.rbx.yml +0 -0
  105. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/checker.sh +0 -0
  106. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/compare +0 -0
  107. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/compile/c +0 -0
  108. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/compile/cc +0 -0
  109. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/compile/cpp +0 -0
  110. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/compile/java +0 -0
  111. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/compile/kt +0 -0
  112. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/compile/pas +0 -0
  113. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/compile/py2 +0 -0
  114. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/compile/py3 +0 -0
  115. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/run/c +0 -0
  116. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/run/cc +0 -0
  117. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/run/cpp +0 -0
  118. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/run/java +0 -0
  119. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/run/kt +0 -0
  120. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/run/py2 +0 -0
  121. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/packagers/boca/run/py3 +0 -0
  122. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
  123. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
  124. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
  125. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
  126. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/preset.rbx.yml +0 -0
  127. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/.gitignore +0 -0
  128. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/gen.cpp +0 -0
  129. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
  130. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/random.py +0 -0
  131. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/random.txt +0 -0
  132. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
  133. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
  134. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
  135. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
  136. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  137. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
  138. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
  139. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
  140. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
  141. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/validator.cpp +0 -0
  142. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
  143. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/resources/templates/template.cpp +0 -0
  144. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/run.py +0 -0
  145. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/schema.py +0 -0
  146. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/submit.py +0 -0
  147. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/submitors/__init__.py +0 -0
  148. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/submitors/codeforces.py +0 -0
  149. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/submitors/submitor.py +0 -0
  150. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/test.py +0 -0
  151. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testcase.py +0 -0
  152. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testcase_rendering.py +0 -0
  153. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/gen1.cpp +0 -0
  154. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/gen2.cpp +0 -0
  155. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/genScript.py +0 -0
  156. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/hard-tle.sol.cpp +0 -0
  157. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/ole.cpp +0 -0
  158. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/problem.rbx.yml +0 -0
  159. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/re.sol.cpp +0 -0
  160. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/sol.cpp +0 -0
  161. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/tests/1.in +0 -0
  162. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -0
  163. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/tle.sol.cpp +0 -0
  164. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/validator.cpp +0 -0
  165. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/box1/wa.sol.cpp +0 -0
  166. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/caching/executable.py +0 -0
  167. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testdata/compatible +0 -0
  168. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/testing_utils.py +0 -0
  169. {rbx_cp-0.5.27 → rbx_cp-0.5.28}/rbx/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rbx.cp
3
- Version: 0.5.27
3
+ Version: 0.5.28
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "rbx.cp"
3
- version = "0.5.27"
3
+ version = "0.5.28"
4
4
  description = ""
5
5
  packages = [
6
6
  {include = "rbx"}
@@ -266,6 +266,7 @@ def run_item(
266
266
  outputs: Optional[List[GradingFileOutput]] = None,
267
267
  extra_args: Optional[str] = None,
268
268
  extra_config: Optional[ExecutionConfig] = None,
269
+ retry_index: Optional[int] = None,
269
270
  ) -> Optional[RunLog]:
270
271
  language = find_language_name(code)
271
272
  execution_options = get_execution_config(language)
@@ -350,6 +351,7 @@ def run_item(
350
351
  is_sanitized=sanitized,
351
352
  timeLimit=sandbox_params.timeout,
352
353
  memoryLimit=sandbox_params.address_space,
354
+ retryIndex=retry_index,
353
355
  ),
354
356
  )
355
357
 
@@ -0,0 +1,143 @@
1
+ import dataclasses
2
+ import pathlib
3
+ import shutil
4
+ import tempfile
5
+ from contextlib import contextmanager
6
+ from typing import Callable, List, Optional
7
+
8
+ from rbx.box import package
9
+ from rbx.box.setter_config import RepeatsConfig, get_setter_config
10
+ from rbx.grading.steps import Evaluation, Outcome
11
+
12
+
13
+ def _both_accepted(eval_a: Evaluation, eval_b: Evaluation) -> bool:
14
+ return (
15
+ eval_a.result.outcome == Outcome.ACCEPTED
16
+ and eval_b.result.outcome == Outcome.ACCEPTED
17
+ )
18
+
19
+
20
+ def _any_tle(eval_a: Evaluation, eval_b: Evaluation) -> bool:
21
+ return (
22
+ eval_a.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
23
+ or eval_b.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
24
+ )
25
+
26
+
27
+ def _get_faster(eval_a: Evaluation, eval_b: Evaluation) -> Evaluation:
28
+ if eval_a.log.time is None:
29
+ return eval_b
30
+ if eval_b.log.time is None:
31
+ return eval_a
32
+ if eval_a.log.time < eval_b.log.time:
33
+ return eval_a
34
+ return eval_b
35
+
36
+
37
+ def _merge_evaluations(eval_a: Evaluation, eval_b: Evaluation) -> Evaluation:
38
+ if _both_accepted(eval_a, eval_b) or _any_tle(eval_a, eval_b):
39
+ return _get_faster(eval_a, eval_b)
40
+ if eval_a.result.outcome != Outcome.ACCEPTED:
41
+ return eval_a
42
+ if eval_b.result.outcome != Outcome.ACCEPTED:
43
+ return eval_b
44
+ return _get_faster(eval_a, eval_b)
45
+
46
+
47
+ @contextmanager
48
+ def _temp_retry_dir():
49
+ """Create a temporary directory for retry artifacts."""
50
+ temp_dir = tempfile.mkdtemp(prefix='rbx_retry_')
51
+ try:
52
+ yield pathlib.Path(temp_dir)
53
+ finally:
54
+ shutil.rmtree(temp_dir, ignore_errors=True)
55
+
56
+
57
+ @dataclasses.dataclass
58
+ class FileToRecover:
59
+ from_path: pathlib.Path
60
+ to_path: pathlib.Path
61
+
62
+
63
+ def _move_to_temp_dir(path: pathlib.Path, temp_dir: pathlib.Path) -> FileToRecover:
64
+ problem_path = package.find_problem()
65
+ path = path.resolve()
66
+ temp_dir = temp_dir.resolve()
67
+ relative = path.relative_to(problem_path)
68
+
69
+ temp_path = temp_dir / relative
70
+ temp_path.parent.mkdir(parents=True, exist_ok=True)
71
+ shutil.move(path, temp_path)
72
+ return FileToRecover(temp_path, path)
73
+
74
+
75
+ def _move_logs_to_temp_dir(
76
+ eval: Evaluation, temp_dir: pathlib.Path
77
+ ) -> List[FileToRecover]:
78
+ recover = []
79
+ if (
80
+ eval.log.stdout_absolute_path is not None
81
+ and eval.log.stdout_absolute_path.exists()
82
+ ):
83
+ recover.append(_move_to_temp_dir(eval.log.stdout_absolute_path, temp_dir))
84
+ if (
85
+ eval.log.stderr_absolute_path is not None
86
+ and eval.log.stderr_absolute_path.exists()
87
+ ):
88
+ recover.append(_move_to_temp_dir(eval.log.stderr_absolute_path, temp_dir))
89
+ if eval.log.log_absolute_path is not None and eval.log.log_absolute_path.exists():
90
+ recover.append(_move_to_temp_dir(eval.log.log_absolute_path, temp_dir))
91
+ return recover
92
+
93
+
94
+ class Retrier:
95
+ def __init__(self, config: Optional[RepeatsConfig] = None, is_stress: bool = False):
96
+ self.config = config or get_setter_config().repeats
97
+ self.is_stress = is_stress
98
+
99
+ self.reset()
100
+
101
+ def reset(self):
102
+ self.reps = self.config.reps - 1
103
+ self.retries = self.config.retries
104
+ self.retries_for_stress = self.config.retries_for_stress
105
+ self.retry_index = 0
106
+
107
+ def repeat(
108
+ self,
109
+ func: Callable[[int], Evaluation],
110
+ ) -> Evaluation:
111
+ self.retry_index += 1
112
+ eval = func(self.retry_index)
113
+ if self.should_repeat(eval):
114
+ with _temp_retry_dir() as temp_dir:
115
+ # Move files to temp dir to open run for repeat.
116
+ recover = _move_logs_to_temp_dir(eval, temp_dir)
117
+ # Actually repeat and choose the best evaluation.
118
+ next_eval = self.repeat(func)
119
+ chosen_eval = _merge_evaluations(eval, next_eval)
120
+
121
+ if id(chosen_eval) == id(eval):
122
+ # Must recover originally moved files.
123
+ for file in recover:
124
+ file.to_path.parent.mkdir(parents=True, exist_ok=True)
125
+ shutil.move(file.from_path, file.to_path)
126
+ return chosen_eval
127
+ return eval
128
+
129
+ def should_repeat(self, eval: Evaluation) -> bool:
130
+ if self.is_stress:
131
+ if (
132
+ eval.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
133
+ and self.retries_for_stress > 0
134
+ ):
135
+ self.retries_for_stress -= 1
136
+ return True
137
+ if eval.result.outcome == Outcome.TIME_LIMIT_EXCEEDED and self.retries > 0:
138
+ self.retries -= 1
139
+ return True
140
+ if self.reps > 0:
141
+ self.reps -= 1
142
+ return True
143
+ return False
@@ -36,6 +36,23 @@ class WarningsConfig(BaseModel):
36
36
  )
37
37
 
38
38
 
39
+ class RepeatsConfig(BaseModel):
40
+ reps: int = Field(
41
+ 1,
42
+ description='Number of times to repeat the solution.',
43
+ )
44
+
45
+ retries: int = Field(
46
+ 0,
47
+ description='Number of times to retry if the solution TLs.',
48
+ )
49
+
50
+ retries_for_stress: int = Field(
51
+ 0,
52
+ description='Number of times to retry in stress mode if the solution TLs.',
53
+ )
54
+
55
+
39
56
  class SetterConfig(BaseModel):
40
57
  sanitizers: SanitizersConfig = Field(
41
58
  default_factory=SanitizersConfig, # type: ignore
@@ -46,6 +63,11 @@ class SetterConfig(BaseModel):
46
63
  description='Configuration for warnings.',
47
64
  )
48
65
 
66
+ repeats: RepeatsConfig = Field(
67
+ default_factory=RepeatsConfig, # type: ignore
68
+ description='Configuration for repeats.',
69
+ )
70
+
49
71
  command_substitutions: Dict[str, str] = Field(
50
72
  {},
51
73
  description='Substitutions to apply to commands before running them.',
@@ -26,6 +26,7 @@ from rbx.box.environment import (
26
26
  VerificationLevel,
27
27
  )
28
28
  from rbx.box.generators import generate_output_for_testcase, generate_standalone
29
+ from rbx.box.retries import Retrier
29
30
  from rbx.box.schema import (
30
31
  ExpectedOutcome,
31
32
  GeneratorCall,
@@ -169,63 +170,70 @@ def _run_solution_on_testcase(
169
170
  verification: VerificationLevel = VerificationLevel.NONE,
170
171
  timelimit_override: Optional[int] = None,
171
172
  ) -> Evaluation:
172
- actual_sandbox = package.get_singleton_sandbox()
173
+ def run_fn(retry_index: int) -> Evaluation:
174
+ actual_sandbox = package.get_singleton_sandbox()
173
175
 
174
- limits = get_limits_for_language(
175
- solution.language, verification, timelimit_override
176
- )
176
+ limits = get_limits_for_language(
177
+ solution.language, verification, timelimit_override
178
+ )
177
179
 
178
- sandbox = EnvironmentSandbox()
179
- sandbox.timeLimit = limits.time
180
- if limits.isDoubleTL and sandbox.timeLimit is not None:
181
- # Double TL.
182
- sandbox.timeLimit = sandbox.timeLimit * 2
183
- sandbox.wallTimeLimit = sandbox.timeLimit
184
- if sandbox.timeLimit is not None and actual_sandbox.use_soft_timeout():
185
- sandbox.wallTimeLimit = sandbox.timeLimit * 2
186
- sandbox.memoryLimit = limits.memory
187
- sandbox.fileSizeLimit = limits.output
188
- extra_config = ExecutionConfig(sandbox=sandbox)
189
-
190
- output_path = output_dir / testcase.inputPath.with_suffix('.out').name
191
- error_path = output_path.with_suffix('.err')
192
- log_path = output_path.with_suffix('.log')
193
- output_path.parent.mkdir(parents=True, exist_ok=True)
194
-
195
- run_log = run_item(
196
- solution,
197
- DigestOrSource.create(compiled_digest),
198
- stdin=DigestOrSource.create(testcase.inputPath),
199
- stdout=DigestOrDest.create(output_path),
200
- stderr=DigestOrDest.create(error_path),
201
- extra_config=extra_config,
202
- )
180
+ sandbox = EnvironmentSandbox()
181
+ sandbox.timeLimit = limits.time
182
+ if limits.isDoubleTL and sandbox.timeLimit is not None:
183
+ # Double TL.
184
+ sandbox.timeLimit = sandbox.timeLimit * 2
185
+ sandbox.wallTimeLimit = sandbox.timeLimit
186
+ if sandbox.timeLimit is not None and actual_sandbox.use_soft_timeout():
187
+ sandbox.wallTimeLimit = sandbox.timeLimit * 2
188
+ sandbox.memoryLimit = limits.memory
189
+ sandbox.fileSizeLimit = limits.output
190
+ extra_config = ExecutionConfig(sandbox=sandbox)
191
+
192
+ output_path = output_dir / testcase.inputPath.with_suffix('.out').name
193
+ error_path = output_path.with_suffix('.err')
194
+ log_path = output_path.with_suffix('.log')
195
+ output_path.parent.mkdir(parents=True, exist_ok=True)
196
+
197
+ run_log = run_item(
198
+ solution,
199
+ DigestOrSource.create(compiled_digest),
200
+ stdin=DigestOrSource.create(testcase.inputPath),
201
+ stdout=DigestOrDest.create(output_path),
202
+ stderr=DigestOrDest.create(error_path),
203
+ extra_config=extra_config,
204
+ retry_index=retry_index,
205
+ )
203
206
 
204
- if checker_digest is not None:
205
- checker_result = checkers.check(
206
- checker_digest,
207
- run_log,
208
- testcase,
209
- program_output=output_path,
207
+ if checker_digest is not None:
208
+ checker_result = checkers.check(
209
+ checker_digest,
210
+ run_log,
211
+ testcase,
212
+ program_output=output_path,
213
+ )
214
+ else:
215
+ checker_result = checkers.check_with_no_output(run_log)
216
+
217
+ eval = Evaluation(
218
+ result=checker_result,
219
+ testcase=TestcaseIO(
220
+ index=testcase_index,
221
+ input=testcase.inputPath,
222
+ output=testcase.outputPath,
223
+ ),
224
+ log=TestcaseLog(
225
+ **(run_log.model_dump() if run_log is not None else {}),
226
+ stdout_absolute_path=output_path.absolute(),
227
+ stderr_absolute_path=error_path.absolute(),
228
+ log_absolute_path=log_path.absolute(),
229
+ ),
210
230
  )
211
- else:
212
- checker_result = checkers.check_with_no_output(run_log)
213
231
 
214
- eval = Evaluation(
215
- result=checker_result,
216
- testcase=TestcaseIO(
217
- index=testcase_index, input=testcase.inputPath, output=testcase.outputPath
218
- ),
219
- log=TestcaseLog(
220
- **(run_log.model_dump() if run_log is not None else {}),
221
- stdout_absolute_path=output_path.absolute(),
222
- stderr_absolute_path=error_path.absolute(),
223
- log_absolute_path=log_path.absolute(),
224
- ),
225
- )
232
+ log_path.write_text(model_to_yaml(eval))
233
+ return eval
226
234
 
227
- log_path.write_text(model_to_yaml(eval))
228
- return eval
235
+ retrier = Retrier()
236
+ return retrier.repeat(run_fn)
229
237
 
230
238
 
231
239
  def _run_solution(
@@ -10,13 +10,17 @@ from rbx import console
10
10
  from rbx.box import checkers, package, validators
11
11
  from rbx.box.code import SanitizationLevel, compile_item, run_item
12
12
  from rbx.box.generators import generate_standalone
13
+ from rbx.box.retries import Retrier
13
14
  from rbx.box.schema import CodeItem, GeneratorCall, Stress, Testcase
14
15
  from rbx.box.solutions import compile_solutions, get_outcome_style_verdict
15
16
  from rbx.box.stressing import finder_parser
16
17
  from rbx.grading.steps import (
17
18
  DigestOrDest,
18
19
  DigestOrSource,
20
+ Evaluation,
19
21
  Outcome,
22
+ TestcaseIO,
23
+ TestcaseLog,
20
24
  )
21
25
  from rbx.utils import StatusProgress
22
26
 
@@ -131,8 +135,9 @@ def run_stress(
131
135
  @functools.cache
132
136
  def run_solution_fn(
133
137
  solution: str,
138
+ retry_index: Optional[int] = None,
134
139
  input_path=input_path,
135
- ) -> finder_parser.FinderSolutionResult:
140
+ ) -> TestcaseLog:
136
141
  index = solution_indices[solution]
137
142
  sol = solutions[index]
138
143
  output_path = input_path.with_stem(f'{index}').with_suffix('.out')
@@ -144,29 +149,30 @@ def run_stress(
144
149
  stdin=DigestOrSource.create(input_path),
145
150
  stdout=DigestOrDest.create(output_path),
146
151
  stderr=DigestOrDest.create(stderr_path),
152
+ retry_index=retry_index,
147
153
  )
148
154
 
149
- return finder_parser.FinderSolutionResult(
150
- output_path=output_path,
151
- stderr_path=stderr_path,
152
- run_log=run_log,
155
+ return TestcaseLog(
156
+ **(run_log.model_dump() if run_log is not None else {}),
157
+ stdout_absolute_path=output_path.absolute(),
158
+ stderr_absolute_path=stderr_path.absolute(),
153
159
  )
154
160
 
155
161
  # Get main solution output.
156
162
  expected_output_path = empty_path
157
163
  if needs_expected_output:
158
- main_result = run_solution_fn(str(solutions[0].path))
159
- main_checker_result = checkers.check_with_no_output(main_result.run_log)
164
+ main_testcase_log = run_solution_fn(str(solutions[0].path))
165
+ main_checker_result = checkers.check_with_no_output(main_testcase_log)
160
166
  if main_checker_result.outcome != Outcome.ACCEPTED:
161
167
  console.console.print(
162
168
  '[error]Error while generating main solution output.[/error]'
163
169
  )
164
170
  console.console.print(f'Input written at [item]{input_path}[/item]')
165
171
  console.console.print(
166
- f'Output written at [item]{main_result.output_path}[/item]'
172
+ f'Output written at [item]{main_testcase_log.stdout_absolute_path}[/item]'
167
173
  )
168
174
  console.console.print(
169
- f'Stderr written at [item]{main_result.stderr_path}[/item]'
175
+ f'Stderr written at [item]{main_testcase_log.stderr_absolute_path}[/item]'
170
176
  )
171
177
  console.console.print()
172
178
  console.console.print(
@@ -174,7 +180,7 @@ def run_stress(
174
180
  "use the two-way modifier in your finder expression (':2')."
175
181
  )
176
182
  raise typer.Exit(1)
177
- expected_output_path = main_result.output_path
183
+ expected_output_path = main_testcase_log.stdout_absolute_path
178
184
 
179
185
  @functools.cache
180
186
  def run_solution_and_checker_fn(
@@ -182,27 +188,43 @@ def run_stress(
182
188
  input_path=input_path,
183
189
  expected_output_path=expected_output_path,
184
190
  ) -> finder_parser.FinderResult:
185
- solution = call.solution
186
- checker = call.checker
187
-
188
- solution_result = run_solution_fn(solution)
189
-
190
- if checker is None:
191
- checker_result = checkers.check_with_no_output(solution_result.run_log)
192
- else:
193
- checker_digest = finders_digest[checker.path]
194
- checker_result = checkers.check(
195
- checker_digest,
196
- solution_result.run_log,
197
- Testcase(inputPath=input_path, outputPath=expected_output_path),
198
- program_output=solution_result.output_path,
191
+ def run_fn(retry_index: int) -> Evaluation:
192
+ solution = call.solution
193
+ checker = call.checker
194
+
195
+ testcase_log = run_solution_fn(solution, retry_index=retry_index)
196
+ assert testcase_log.stdout_absolute_path is not None
197
+
198
+ if checker is None:
199
+ checker_result = checkers.check_with_no_output(testcase_log)
200
+ else:
201
+ checker_digest = finders_digest[checker.path]
202
+ checker_result = checkers.check(
203
+ checker_digest,
204
+ testcase_log,
205
+ Testcase(inputPath=input_path, outputPath=expected_output_path),
206
+ program_output=testcase_log.stdout_absolute_path,
207
+ )
208
+
209
+ return Evaluation(
210
+ result=checker_result,
211
+ testcase=TestcaseIO(
212
+ index=0,
213
+ input=input_path,
214
+ output=expected_output_path,
215
+ ),
216
+ log=testcase_log,
199
217
  )
218
+
219
+ retrier = Retrier(is_stress=True)
220
+ eval = retrier.repeat(run_fn)
221
+
200
222
  return finder_parser.FinderResult(
201
- solution=solution,
202
- outcome=checker_result.outcome,
203
- checker=checker,
204
- solution_result=solution_result,
205
- checker_result=checker_result,
223
+ solution=call.solution,
224
+ outcome=eval.result.outcome,
225
+ checker=call.checker,
226
+ solution_log=eval.log,
227
+ checker_result=eval.result,
206
228
  )
207
229
 
208
230
  runner = finder_parser.FinderTreeRunner(runner=run_solution_and_checker_fn)
@@ -10,7 +10,7 @@ import typer
10
10
  from rbx import console
11
11
  from rbx.box import package
12
12
  from rbx.box.schema import CodeItem, ExpectedOutcome
13
- from rbx.grading.steps import CheckerResult, Outcome, RunLog
13
+ from rbx.grading.steps import CheckerResult, Outcome, RunLog, TestcaseLog
14
14
 
15
15
  LARK_GRAMMAR = r"""
16
16
  // A bunch of words
@@ -102,7 +102,7 @@ class FinderResult:
102
102
  checker: Optional[FinderChecker]
103
103
 
104
104
  # Auxiliary information.
105
- solution_result: Optional[FinderSolutionResult] = None
105
+ solution_log: Optional[TestcaseLog] = None
106
106
  checker_result: Optional[CheckerResult] = None
107
107
 
108
108
 
@@ -170,6 +170,7 @@ class RunLogMetadata(BaseModel):
170
170
  is_sanitized: bool = False
171
171
  timeLimit: Optional[int] = None
172
172
  memoryLimit: Optional[int] = None
173
+ retryIndex: Optional[int] = None
173
174
 
174
175
 
175
176
  class RunLog(BaseModel):
@@ -46,9 +46,11 @@ def run(
46
46
  ) -> Optional[RunLog]:
47
47
  artifacts.logs = GradingLogsHolder()
48
48
 
49
- with dependency_cache(
50
- [command], [artifacts], params.get_cacheable_params()
51
- ) as is_cached:
49
+ cacheable_params = params.get_cacheable_params()
50
+ if metadata is not None and metadata.retryIndex is not None:
51
+ cacheable_params['__retry_index__'] = metadata.retryIndex
52
+
53
+ with dependency_cache([command], [artifacts], cacheable_params) as is_cached:
52
54
  if not is_cached:
53
55
  steps.run(
54
56
  command=command,
@@ -14,6 +14,14 @@ command_substitutions:
14
14
  python2: python2
15
15
  python3: python3
16
16
 
17
+ repeats:
18
+ # Number of times to run the solution.
19
+ reps: 1
20
+ # Number of times to retry if the solution TLs.
21
+ retries: 0
22
+ # Number of times to retry in stress mode if the solution TLs.
23
+ retries_for_stress: 0
24
+
17
25
  # Whether sanitizers will be enabled by default
18
26
  # when running testlib components.
19
27
  # This flag has no effect on running solutions with
@@ -14,6 +14,14 @@ command_substitutions:
14
14
  python2: python2
15
15
  python3: python3
16
16
 
17
+ repeats:
18
+ # Number of times to run the solution.
19
+ reps: 1
20
+ # Number of times to retry if the solution TLs.
21
+ retries: 0
22
+ # Number of times to retry in stress mode if the solution TLs.
23
+ retries_for_stress: 0
24
+
17
25
  # Whether sanitizers will be enabled by default
18
26
  # when running testlib components.
19
27
  # This flag has no effect on running solutions with
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes