rbx.cp 0.5.34__tar.gz → 0.5.36__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 (174) hide show
  1. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/PKG-INFO +1 -1
  2. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/pyproject.toml +1 -1
  3. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/builder.py +10 -2
  4. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/contest/build_contest_statements.py +1 -1
  5. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/contest/main.py +2 -1
  6. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/creation.py +7 -12
  7. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/generators.py +102 -52
  8. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/generators_test.py +5 -1
  9. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/main.py +33 -16
  10. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/package.py +2 -2
  11. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/schema.py +3 -0
  12. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/solutions.py +1 -1
  13. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/solutions_test.py +5 -1
  14. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/statements/build_statements.py +1 -1
  15. rbx_cp-0.5.36/rbx/box/testcase_utils.py +135 -0
  16. rbx_cp-0.5.36/rbx/box/testcases/main.py +158 -0
  17. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/validators.py +1 -1
  18. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/config.py +29 -1
  19. rbx_cp-0.5.36/rbx/testdata/compatible +0 -0
  20. rbx_cp-0.5.34/rbx/box/testcases.py +0 -70
  21. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/LICENSE +0 -0
  22. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/README.md +0 -0
  23. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/__init__.py +0 -0
  24. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/annotations.py +0 -0
  25. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/autoenum.py +0 -0
  26. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/__init__.py +0 -0
  27. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/cd.py +0 -0
  28. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/checkers.py +0 -0
  29. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/code.py +0 -0
  30. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/compile.py +0 -0
  31. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/conftest.py +0 -0
  32. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/contest/__init__.py +0 -0
  33. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/contest/contest_package.py +0 -0
  34. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/contest/contest_utils.py +0 -0
  35. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/contest/schema.py +0 -0
  36. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/contest/statements.py +0 -0
  37. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/deferred.py +0 -0
  38. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/download.py +0 -0
  39. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/environment.py +0 -0
  40. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/extensions.py +0 -0
  41. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/formatting.py +0 -0
  42. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/packaging/boca/extension.py +0 -0
  43. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/packaging/boca/packager.py +0 -0
  44. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/packaging/contest_main.py +0 -0
  45. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/packaging/main.py +0 -0
  46. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/packaging/packager.py +0 -0
  47. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/packaging/polygon/packager.py +0 -0
  48. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/packaging/polygon/test.py +0 -0
  49. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/packaging/polygon/xml_schema.py +0 -0
  50. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/presets/__init__.py +0 -0
  51. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/presets/fetch.py +0 -0
  52. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/presets/lock_schema.py +0 -0
  53. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/presets/schema.py +0 -0
  54. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/retries.py +0 -0
  55. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/sanitizers/warning_stack.py +0 -0
  56. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/setter_config.py +0 -0
  57. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/state.py +0 -0
  58. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/statements/__init__.py +0 -0
  59. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/statements/builders.py +0 -0
  60. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/statements/joiners.py +0 -0
  61. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/statements/latex.py +0 -0
  62. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/statements/latex_jinja.py +0 -0
  63. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/statements/schema.py +0 -0
  64. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/stresses.py +0 -0
  65. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/stressing/__init__.py +0 -0
  66. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/stressing/finder_parser.py +0 -0
  67. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/stressing/generator_parser.py +0 -0
  68. {rbx_cp-0.5.34/rbx/box/ui → rbx_cp-0.5.36/rbx/box/testcases}/__init__.py +0 -0
  69. {rbx_cp-0.5.34/rbx/grading → rbx_cp-0.5.36/rbx/box/ui}/__init__.py +0 -0
  70. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/ui/captured_log.py +0 -0
  71. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/ui/css/app.tcss +0 -0
  72. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/ui/main.py +0 -0
  73. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/ui/run.py +0 -0
  74. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/box/validators_test.py +0 -0
  75. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/checker.py +0 -0
  76. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/clone.py +0 -0
  77. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/conftest.py +0 -0
  78. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/console.py +0 -0
  79. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/create.py +0 -0
  80. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/edit.py +0 -0
  81. {rbx_cp-0.5.34/rbx/grading/judge → rbx_cp-0.5.36/rbx/grading}/__init__.py +0 -0
  82. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/caching.py +0 -0
  83. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/conftest.py +0 -0
  84. {rbx_cp-0.5.34/rbx/grading/judge/sandboxes → rbx_cp-0.5.36/rbx/grading/judge}/__init__.py +0 -0
  85. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/judge/cacher.py +0 -0
  86. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/judge/digester.py +0 -0
  87. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/judge/sandbox.py +0 -0
  88. /rbx_cp-0.5.34/rbx/testdata/compatible → /rbx_cp-0.5.36/rbx/grading/judge/sandboxes/__init__.py +0 -0
  89. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/judge/sandboxes/isolate.py +0 -0
  90. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/judge/sandboxes/stupid_sandbox.py +0 -0
  91. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/judge/sandboxes/timeit.py +0 -0
  92. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/judge/storage.py +0 -0
  93. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/judge/test.py +0 -0
  94. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/judge/testiso.py +0 -0
  95. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/steps.py +0 -0
  96. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/steps_with_caching.py +0 -0
  97. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading/steps_with_caching_run_test.py +0 -0
  98. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/grading_utils.py +0 -0
  99. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/hydration.py +0 -0
  100. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/main.py +0 -0
  101. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/metadata.py +0 -0
  102. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/providers/__init__.py +0 -0
  103. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/providers/codeforces.py +0 -0
  104. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/providers/provider.py +0 -0
  105. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/checkers/boilerplate.cpp +0 -0
  106. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/default_config.json +0 -0
  107. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/default_setter_config.mac.yml +0 -0
  108. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/default_setter_config.yml +0 -0
  109. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/envs/default.rbx.yml +0 -0
  110. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/envs/isolate.rbx.yml +0 -0
  111. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/checker.sh +0 -0
  112. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/compare +0 -0
  113. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/compile/c +0 -0
  114. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/compile/cc +0 -0
  115. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/compile/cpp +0 -0
  116. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/compile/java +0 -0
  117. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/compile/kt +0 -0
  118. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/compile/pas +0 -0
  119. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/compile/py2 +0 -0
  120. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/compile/py3 +0 -0
  121. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/run/c +0 -0
  122. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/run/cc +0 -0
  123. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/run/cpp +0 -0
  124. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/run/java +0 -0
  125. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/run/kt +0 -0
  126. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/run/py2 +0 -0
  127. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/packagers/boca/run/py3 +0 -0
  128. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
  129. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
  130. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
  131. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
  132. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/preset.rbx.yml +0 -0
  133. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/.gitignore +0 -0
  134. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/gen.cpp +0 -0
  135. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
  136. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/random.py +0 -0
  137. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/random.txt +0 -0
  138. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
  139. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
  140. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
  141. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
  142. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  143. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
  144. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
  145. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
  146. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
  147. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/validator.cpp +0 -0
  148. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
  149. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/resources/templates/template.cpp +0 -0
  150. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/run.py +0 -0
  151. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/schema.py +0 -0
  152. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/submit.py +0 -0
  153. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/submitors/__init__.py +0 -0
  154. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/submitors/codeforces.py +0 -0
  155. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/submitors/submitor.py +0 -0
  156. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/test.py +0 -0
  157. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testcase.py +0 -0
  158. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testcase_rendering.py +0 -0
  159. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/gen1.cpp +0 -0
  160. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/gen2.cpp +0 -0
  161. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/genScript.py +0 -0
  162. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/hard-tle.sol.cpp +0 -0
  163. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/ole.cpp +0 -0
  164. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/problem.rbx.yml +0 -0
  165. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/re.sol.cpp +0 -0
  166. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/sol.cpp +0 -0
  167. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/tests/1.in +0 -0
  168. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -0
  169. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/tle.sol.cpp +0 -0
  170. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/validator.cpp +0 -0
  171. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/box1/wa.sol.cpp +0 -0
  172. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testdata/caching/executable.py +0 -0
  173. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/testing_utils.py +0 -0
  174. {rbx_cp-0.5.34 → rbx_cp-0.5.36}/rbx/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rbx.cp
3
- Version: 0.5.34
3
+ Version: 0.5.36
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.34"
3
+ version = "0.5.36"
4
4
  description = ""
5
5
  packages = [
6
6
  {include = "rbx"}
@@ -3,7 +3,11 @@ from typing import Optional, Set
3
3
  from rbx import console, utils
4
4
  from rbx.box import environment, package
5
5
  from rbx.box.environment import VerificationLevel
6
- from rbx.box.generators import generate_outputs_for_testcases, generate_testcases
6
+ from rbx.box.generators import (
7
+ extract_generation_testcases_from_groups,
8
+ generate_outputs_for_testcases,
9
+ generate_testcases,
10
+ )
7
11
  from rbx.box.solutions import (
8
12
  is_fast,
9
13
  print_run_report,
@@ -61,7 +65,11 @@ def build(
61
65
  keep=True,
62
66
  ) as s:
63
67
  if output:
64
- generate_outputs_for_testcases(s, groups=groups)
68
+ entries = [
69
+ entry.group_entry
70
+ for entry in extract_generation_testcases_from_groups(groups)
71
+ ]
72
+ generate_outputs_for_testcases(entries, s)
65
73
 
66
74
  console.console.print(
67
75
  '[success]Problem built.[/success] '
@@ -31,7 +31,7 @@ from rbx.box.statements.joiners import (
31
31
  StatementJoinerContext,
32
32
  )
33
33
  from rbx.box.statements.schema import Statement, StatementType
34
- from rbx.box.testcases import get_samples
34
+ from rbx.box.testcase_utils import get_samples
35
35
 
36
36
 
37
37
  @dataclasses.dataclass
@@ -128,7 +128,8 @@ def edit():
128
128
  @app.command('add, a', help='Add new problem to contest.')
129
129
  @within_contest
130
130
  def add(path: str, short_name: str, preset: Optional[str] = None):
131
- name = pathlib.Path(path).stem
131
+ problem_path = pathlib.Path(path)
132
+ name = problem_path.stem
132
133
  utils.validate_field(ContestProblem, 'short_name', short_name)
133
134
  utils.validate_field(Package, 'name', name)
134
135
 
@@ -4,9 +4,8 @@ from typing import Annotated, Optional
4
4
 
5
5
  import typer
6
6
 
7
- from rbx import console
8
- from rbx.box import presets
9
- from rbx.box.contest.contest_package import find_contest_yaml
7
+ from rbx import console, utils
8
+ from rbx.box import package, presets
10
9
  from rbx.box.presets.fetch import get_preset_fetch_info
11
10
 
12
11
 
@@ -27,15 +26,6 @@ def create(
27
26
  ] = None,
28
27
  path: Optional[pathlib.Path] = None,
29
28
  ):
30
- if find_contest_yaml() is not None:
31
- console.console.print(
32
- '[error]Cannot [item]rbx create[/item] a problem inside a contest.[/error]'
33
- )
34
- console.console.print(
35
- '[error]Instead, use [item]rbx contest add[/item] to add a problem to a contest.[/error]'
36
- )
37
- raise typer.Exit(1)
38
-
39
29
  preset = preset or 'default'
40
30
  console.console.print(f'Creating new problem [item]{name}[/item]...')
41
31
 
@@ -80,4 +70,9 @@ def create(
80
70
  for lock in dest_path.rglob('.preset-lock.yml'):
81
71
  lock.unlink(missing_ok=True)
82
72
 
73
+ # Change problem name.
74
+ ru, problem = package.get_ruyaml(dest_path)
75
+ problem['name'] = name
76
+ utils.save_ruyaml(dest_path / 'problem.rbx.yml', ru, problem)
77
+
83
78
  presets.generate_lock(preset, root=dest_path)
@@ -3,13 +3,13 @@ import pathlib
3
3
  import shlex
4
4
  import shutil
5
5
  from pathlib import PosixPath
6
- from typing import Dict, List, Optional, Set
6
+ from typing import Dict, Iterable, List, Optional, Set, Tuple
7
7
 
8
8
  import typer
9
9
  from pydantic import BaseModel
10
10
 
11
11
  from rbx import console
12
- from rbx.box import checkers, package, testcases, validators
12
+ from rbx.box import checkers, package, testcase_utils, validators
13
13
  from rbx.box.code import SanitizationLevel, compile_item, run_item
14
14
  from rbx.box.environment import (
15
15
  EnvironmentSandbox,
@@ -22,7 +22,7 @@ from rbx.box.schema import (
22
22
  TestcaseSubgroup,
23
23
  )
24
24
  from rbx.box.stressing import generator_parser
25
- from rbx.box.testcases import TestcaseEntry, find_built_testcases
25
+ from rbx.box.testcase_utils import TestcaseEntry, TestcasePattern, find_built_testcases
26
26
  from rbx.grading.steps import (
27
27
  DigestHolder,
28
28
  DigestOrDest,
@@ -141,15 +141,20 @@ def _run_generator_script(testcase: TestcaseSubgroup) -> str:
141
141
  return script
142
142
 
143
143
 
144
- def _extract_script_lines(script: str):
144
+ def _extract_script_lines(script: str) -> Iterable[Tuple[str, str, int]]:
145
145
  lines = script.splitlines()
146
- for line in lines:
146
+ for i, line in enumerate(lines):
147
147
  line = line.strip()
148
148
  if not line:
149
149
  continue
150
150
  if line.startswith('#'):
151
151
  continue
152
- yield shlex.split(line)[0], shlex.join(shlex.split(line)[1:])
152
+ yield shlex.split(line)[0], shlex.join(shlex.split(line)[1:]), i + 1
153
+
154
+
155
+ class GeneratorScriptEntry(BaseModel):
156
+ path: pathlib.Path
157
+ line: int
153
158
 
154
159
 
155
160
  class GenerationMetadata(BaseModel):
@@ -157,6 +162,7 @@ class GenerationMetadata(BaseModel):
157
162
 
158
163
  copied_from: Optional[Testcase] = None
159
164
  generator_call: Optional[GeneratorCall] = None
165
+ generator_script: Optional[GeneratorScriptEntry] = None
160
166
 
161
167
 
162
168
  class GenerationTestcaseEntry(BaseModel):
@@ -280,7 +286,7 @@ def run_testcase_visitor(visitor: TestcaseVisitor):
280
286
  script = _run_generator_script(subgroup)
281
287
 
282
288
  # Run each line from generator script.
283
- for generator_name, args in _extract_script_lines(script):
289
+ for generator_name, args, line_number in _extract_script_lines(script):
284
290
  call = GeneratorCall(name=generator_name, args=args)
285
291
  visitor.visit(
286
292
  GenerationTestcaseEntry(
@@ -288,6 +294,10 @@ def run_testcase_visitor(visitor: TestcaseVisitor):
288
294
  subgroup_entry=_sub_entry(i),
289
295
  metadata=GenerationMetadata(
290
296
  generator_call=call,
297
+ generator_script=GeneratorScriptEntry(
298
+ path=subgroup.generatorScript.path,
299
+ line=line_number,
300
+ ),
291
301
  copied_to=_copied_to(i),
292
302
  ),
293
303
  )
@@ -457,7 +467,7 @@ def generate_testcases(
457
467
  else None,
458
468
  )
459
469
 
460
- testcases.clear_built_testcases()
470
+ testcase_utils.clear_built_testcases()
461
471
 
462
472
  class BuildTestcaseVisitor(TestcaseGroupVisitor):
463
473
  def visit(self, entry: GenerationTestcaseEntry):
@@ -487,6 +497,8 @@ def generate_output_for_testcase(
487
497
  stderr_path: Optional[pathlib.Path] = None,
488
498
  ):
489
499
  assert testcase.outputPath is not None
500
+ testcase.inputPath.parent.mkdir(parents=True, exist_ok=True)
501
+ testcase.outputPath.parent.mkdir(parents=True, exist_ok=True)
490
502
 
491
503
  if testcase.outputPath.is_file():
492
504
  # Output file was already copied over from manual tests.
@@ -540,8 +552,70 @@ def generate_output_for_testcase(
540
552
  raise typer.Exit(1)
541
553
 
542
554
 
555
+ def extract_generation_testcases(
556
+ entries: List[TestcaseEntry],
557
+ ) -> List[GenerationTestcaseEntry]:
558
+ # TODO: support subgroups.
559
+ groups = set(entry.group for entry in entries)
560
+ entry_keys = set(entry.key() for entry in entries)
561
+
562
+ res: List[GenerationTestcaseEntry] = []
563
+
564
+ class ExtractGenerationTestcasesVisitor(TestcaseVisitor):
565
+ def should_visit_group(self, group_name: str) -> bool:
566
+ return group_name in groups
567
+
568
+ def visit(self, entry: GenerationTestcaseEntry):
569
+ # TODO: support subgroups.
570
+ if entry.group_entry.key() not in entry_keys:
571
+ return
572
+ res.append(entry)
573
+
574
+ run_testcase_visitor(ExtractGenerationTestcasesVisitor())
575
+ return res
576
+
577
+
578
+ def extract_generation_testcases_from_groups(
579
+ groups: Optional[Set[str]] = None,
580
+ ) -> List[GenerationTestcaseEntry]:
581
+ res: List[GenerationTestcaseEntry] = []
582
+
583
+ class ExtractGenerationTestcasesVisitor(TestcaseGroupVisitor):
584
+ def visit(self, entry: GenerationTestcaseEntry):
585
+ res.append(entry)
586
+
587
+ run_testcase_visitor(ExtractGenerationTestcasesVisitor(groups))
588
+ return res
589
+
590
+
591
+ def extract_generation_testcases_from_patterns(
592
+ patterns: List[TestcasePattern],
593
+ ) -> List[GenerationTestcaseEntry]:
594
+ res: List[GenerationTestcaseEntry] = []
595
+
596
+ class ExtractGenerationTestcasesVisitor(TestcaseVisitor):
597
+ def should_visit_group(self, group_name: str) -> bool:
598
+ return any(pattern.intersecting_group(group_name) for pattern in patterns)
599
+
600
+ def should_visit_subgroup(self, subgroup_path: str) -> bool:
601
+ return any(
602
+ pattern.intersecting_group(subgroup_path) for pattern in patterns
603
+ )
604
+
605
+ def visit(self, entry: GenerationTestcaseEntry):
606
+ if not any(
607
+ pattern.match(entry.group_entry) for pattern in patterns
608
+ ) and not any(pattern.match(entry.subgroup_entry) for pattern in patterns):
609
+ return
610
+ res.append(entry)
611
+
612
+ run_testcase_visitor(ExtractGenerationTestcasesVisitor())
613
+ return res
614
+
615
+
543
616
  def generate_outputs_for_testcases(
544
- progress: Optional[StatusProgress] = None, groups: Optional[Set[str]] = None
617
+ entries: List[TestcaseEntry],
618
+ progress: Optional[StatusProgress] = None,
545
619
  ):
546
620
  def step():
547
621
  if progress is not None:
@@ -563,50 +637,26 @@ def generate_outputs_for_testcases(
563
637
  shutil.rmtree(str(gen_runs_dir), ignore_errors=True)
564
638
  gen_runs_dir.mkdir(parents=True, exist_ok=True)
565
639
 
566
- class GenerateOutputsVisitor(TestcaseGroupVisitor):
567
- def visit(self, entry: GenerationTestcaseEntry):
568
- tc = entry.metadata.copied_to
569
- if not tc.inputPath.is_file():
570
- return
571
- assert tc.outputPath is not None
640
+ generation_entries = extract_generation_testcases(entries)
572
641
 
573
- if (
574
- main_solution is None or solution_digest is None
575
- ) and not tc.outputPath.is_file():
576
- console.console.print(
577
- '[error]No main solution found to generate outputs for testcases.[/error]',
578
- )
579
- raise typer.Exit(1)
642
+ for entry in generation_entries:
643
+ tc = entry.metadata.copied_to
644
+ if not tc.inputPath.is_file():
645
+ return
646
+ assert tc.outputPath is not None
580
647
 
581
- assert solution_digest is not None
582
- generate_output_for_testcase(
583
- solution_digest,
584
- tc,
585
- gen_runs_dir / 'main.stderr',
648
+ if (
649
+ main_solution is None or solution_digest is None
650
+ ) and not tc.outputPath.is_file():
651
+ console.console.print(
652
+ '[error]No main solution found to generate outputs for testcases.[/error]',
586
653
  )
587
- step()
588
-
589
- run_testcase_visitor(GenerateOutputsVisitor(groups))
590
-
591
-
592
- def extract_generation_testcases(
593
- entries: List[TestcaseEntry],
594
- ) -> List[GenerationTestcaseEntry]:
595
- # TODO: support subgroups.
596
- groups = set(entry.group for entry in entries)
597
- entry_keys = set(entry.key() for entry in entries)
598
-
599
- res: List[GenerationTestcaseEntry] = []
600
-
601
- class ExtractGenerationTestcasesVisitor(TestcaseVisitor):
602
- def should_visit_group(self, group_name: str) -> bool:
603
- return group_name in groups
604
-
605
- def visit(self, entry: GenerationTestcaseEntry):
606
- # TODO: support subgroups.
607
- if entry.group_entry.key() not in entry_keys:
608
- return
609
- res.append(entry)
654
+ raise typer.Exit(1)
610
655
 
611
- run_testcase_visitor(ExtractGenerationTestcasesVisitor())
612
- return res
656
+ assert solution_digest is not None
657
+ generate_output_for_testcase(
658
+ solution_digest,
659
+ tc,
660
+ gen_runs_dir / 'main.stderr',
661
+ )
662
+ step()
@@ -4,6 +4,7 @@ import pytest
4
4
 
5
5
  from rbx.box import package
6
6
  from rbx.box.generators import (
7
+ extract_generation_testcases_from_groups,
7
8
  generate_outputs_for_testcases,
8
9
  generate_testcases,
9
10
  )
@@ -13,7 +14,10 @@ from rbx.testing_utils import print_directory_tree
13
14
  @pytest.mark.test_pkg('box1')
14
15
  def test_generator_works(pkg_from_testdata: pathlib.Path):
15
16
  generate_testcases()
16
- generate_outputs_for_testcases()
17
+ entries = [
18
+ entry.group_entry for entry in extract_generation_testcases_from_groups()
19
+ ]
20
+ generate_outputs_for_testcases(entries)
17
21
 
18
22
  # Debug when fail.
19
23
  print_directory_tree(pkg_from_testdata)
@@ -37,8 +37,10 @@ from rbx.box import (
37
37
  validators,
38
38
  )
39
39
  from rbx.box.contest import main as contest
40
+ from rbx.box.contest.contest_package import find_contest_yaml
40
41
  from rbx.box.environment import VerificationLevel, get_environment_path
41
42
  from rbx.box.packaging import main as packaging
43
+ from rbx.box.testcases import main as testcases
42
44
  from rbx.box.solutions import (
43
45
  estimate_time_limit,
44
46
  get_exact_matching_solutions,
@@ -49,7 +51,7 @@ from rbx.box.solutions import (
49
51
  run_solutions,
50
52
  )
51
53
  from rbx.box.statements import build_statements
52
- from rbx.box.testcases import TestcaseEntry
54
+ from rbx.box.testcase_utils import TestcaseEntry
53
55
 
54
56
  app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
55
57
  app.add_typer(
@@ -82,6 +84,12 @@ app.add_typer(
82
84
  app.add_typer(
83
85
  contest.app, name='contest', cls=annotations.AliasGroup, help='Contest management.'
84
86
  )
87
+ app.add_typer(
88
+ testcases.app,
89
+ name='testcases, tc, t',
90
+ cls=annotations.AliasGroup,
91
+ help='Testcase management.',
92
+ )
85
93
 
86
94
 
87
95
  @app.callback()
@@ -166,6 +174,21 @@ def run(
166
174
  )
167
175
  check = False
168
176
 
177
+ tracked_solutions = None
178
+ if outcome is not None:
179
+ tracked_solutions = {
180
+ str(solution.path)
181
+ for solution in get_matching_solutions(ExpectedOutcome(outcome))
182
+ }
183
+ if solution:
184
+ tracked_solutions = {solution}
185
+
186
+ if choice:
187
+ tracked_solutions = set(pick_solutions(tracked_solutions))
188
+ if not tracked_solutions:
189
+ console.console.print('[error]No solutions selected. Exiting.[/error]')
190
+ raise typer.Exit(1)
191
+
169
192
  if not builder.build(verification=verification, output=check):
170
193
  return
171
194
 
@@ -195,21 +218,6 @@ def run(
195
218
  'and the environment default time limit will be used instead.[/warning]'
196
219
  )
197
220
 
198
- tracked_solutions = None
199
- if outcome is not None:
200
- tracked_solutions = {
201
- str(solution.path)
202
- for solution in get_matching_solutions(ExpectedOutcome(outcome))
203
- }
204
- if solution:
205
- tracked_solutions = {solution}
206
-
207
- if choice:
208
- tracked_solutions = set(pick_solutions(tracked_solutions))
209
- if not tracked_solutions:
210
- console.console.print('[error]No solutions selected. Exiting.[/error]')
211
- raise typer.Exit(1)
212
-
213
221
  if sanitized and tracked_solutions is None:
214
222
  console.console.print(
215
223
  '[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
@@ -441,6 +449,15 @@ def create(
441
449
  Optional[str], typer.Option(help='Preset to use when creating the problem.')
442
450
  ] = None,
443
451
  ):
452
+ if find_contest_yaml() is not None:
453
+ console.console.print(
454
+ '[error]Cannot [item]rbx create[/item] a problem inside a contest.[/error]'
455
+ )
456
+ console.console.print(
457
+ '[error]Instead, use [item]rbx contest add[/item] to add a problem to a contest.[/error]'
458
+ )
459
+ raise typer.Exit(1)
460
+
444
461
  if preset is not None:
445
462
  creation.create(name, preset=preset)
446
463
  return
@@ -121,8 +121,8 @@ def save_package(
121
121
  problem_yaml_path.write_text(utils.model_to_yaml(package))
122
122
 
123
123
 
124
- def get_ruyaml() -> Tuple[ruyaml.YAML, ruyaml.Any]:
125
- problem_yaml_path = find_problem_yaml()
124
+ def get_ruyaml(root: pathlib.Path = pathlib.Path()) -> Tuple[ruyaml.YAML, ruyaml.Any]:
125
+ problem_yaml_path = find_problem_yaml(root)
126
126
  if problem_yaml_path is None:
127
127
  console.console.print(
128
128
  f'Problem not found in {pathlib.Path().absolute()}', style='error'
@@ -192,6 +192,9 @@ class GeneratorCall(BaseModel):
192
192
  default=None, description='The arguments to pass to the generator.'
193
193
  )
194
194
 
195
+ def __str__(self) -> str:
196
+ return f'{self.name} {self.args}'
197
+
195
198
 
196
199
  class TestcaseSubgroup(BaseModel):
197
200
  model_config = ConfigDict(extra='forbid')
@@ -42,7 +42,7 @@ from rbx.box.schema import (
42
42
  Testcase,
43
43
  TestcaseGroup,
44
44
  )
45
- from rbx.box.testcases import TestcaseEntry, find_built_testcases
45
+ from rbx.box.testcase_utils import TestcaseEntry, find_built_testcases
46
46
  from rbx.grading.steps import (
47
47
  DigestOrDest,
48
48
  DigestOrSource,
@@ -5,6 +5,7 @@ import pytest
5
5
 
6
6
  from rbx.box.environment import VerificationLevel
7
7
  from rbx.box.generators import (
8
+ extract_generation_testcases_from_groups,
8
9
  generate_outputs_for_testcases,
9
10
  generate_testcases,
10
11
  )
@@ -18,7 +19,10 @@ from rbx.grading.steps import Outcome
18
19
  @pytest.mark.test_pkg('box1')
19
20
  def test_solutions(pkg_from_testdata: pathlib.Path):
20
21
  generate_testcases()
21
- generate_outputs_for_testcases()
22
+ entries = [
23
+ entry.group_entry for entry in extract_generation_testcases_from_groups()
24
+ ]
25
+ generate_outputs_for_testcases(entries)
22
26
 
23
27
  result = run_solutions(verification=VerificationLevel.FULL)
24
28
  res = asyncio.run(convert_list_of_solution_evaluations_to_dict(result.items))
@@ -24,7 +24,7 @@ from rbx.box.statements.schema import (
24
24
  Statement,
25
25
  StatementType,
26
26
  )
27
- from rbx.box.testcases import get_samples
27
+ from rbx.box.testcase_utils import get_samples
28
28
 
29
29
  app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
30
30
 
@@ -0,0 +1,135 @@
1
+ import pathlib
2
+ import shutil
3
+ from typing import List, Optional, Tuple
4
+
5
+ import typer
6
+ from pydantic import BaseModel
7
+
8
+ from rbx import console
9
+ from rbx.box import package
10
+ from rbx.box.package import get_build_testgroup_path, get_build_tests_path
11
+ from rbx.box.schema import Testcase, TestcaseGroup
12
+
13
+
14
+ class TestcaseEntry(BaseModel):
15
+ group: str
16
+ index: int
17
+
18
+ def key(self) -> Tuple[str, int]:
19
+ return self.group, self.index
20
+
21
+ def __str__(self) -> str:
22
+ return f'{self.group}/{self.index}'
23
+
24
+ @classmethod
25
+ def parse(cls, spec: str) -> 'TestcaseEntry':
26
+ if spec.count('/') != 1:
27
+ console.console.print(
28
+ f'[error]Invalid testcase spec [item]{spec}[/item]. Format should be [item]<group>/<index>[/item].[/error]',
29
+ )
30
+ raise typer.Exit(1)
31
+ group, index = spec.split('/')
32
+ return TestcaseEntry(group=group.strip(), index=int(index))
33
+
34
+
35
+ class TestcasePattern(BaseModel):
36
+ group_prefix: List[str]
37
+ index: Optional[int] = None
38
+
39
+ def group(self) -> str:
40
+ return '/'.join(self.group_prefix)
41
+
42
+ def match(self, group_entry: TestcaseEntry) -> bool:
43
+ # TODO: support subgroups.
44
+ entry_parts = tuple(group_entry.group.split('/'))
45
+ if self.index is not None:
46
+ if self.index != group_entry.index:
47
+ return False
48
+ if tuple(self.group_prefix) != entry_parts:
49
+ return False
50
+ return True
51
+
52
+ if len(self.group_prefix) > len(entry_parts):
53
+ return False
54
+
55
+ return tuple(self.group_prefix) == entry_parts[: len(self.group_prefix)]
56
+
57
+ def with_no_index(self) -> 'TestcasePattern':
58
+ return self.model_copy(update={'index': None})
59
+
60
+ def intersecting_group(self, group: str) -> bool:
61
+ if self.with_no_index().match(TestcaseEntry(group=group, index=0)):
62
+ # If the group is inside the pattern, then it is a match.
63
+ return True
64
+ if TestcasePattern.parse(group).match(
65
+ TestcaseEntry(group=self.group(), index=0)
66
+ ):
67
+ # If the group is a prefix of the pattern, then it is a match.
68
+ return True
69
+ return False
70
+
71
+ def __str__(self) -> str:
72
+ prefix = '/'.join(self.group_prefix)
73
+ if not prefix:
74
+ return '*'
75
+ if self.index is None:
76
+ return f'{prefix}/'
77
+ return f'{prefix}/{self.index}'
78
+
79
+ @classmethod
80
+ def parse(cls, spec: str) -> 'TestcasePattern':
81
+ spec = spec.strip()
82
+ if spec == '*':
83
+ return cls(group_prefix=[], index=None)
84
+
85
+ parts = spec.split('/')
86
+ if len(parts) <= 1:
87
+ return cls(group_prefix=parts, index=None)
88
+
89
+ if parts[-1].isdigit():
90
+ return cls(group_prefix=parts[:-1], index=int(parts[-1]))
91
+
92
+ return cls(group_prefix=parts, index=None)
93
+
94
+
95
+ class TestcaseData(BaseModel):
96
+ input: str
97
+ output: str
98
+
99
+
100
+ def find_built_testcases(group: TestcaseGroup) -> List[Testcase]:
101
+ inputs = find_built_testcase_inputs(group)
102
+
103
+ testcases = []
104
+ for input in inputs:
105
+ output = input.with_suffix('.out')
106
+ testcases.append(Testcase(inputPath=input, outputPath=output))
107
+ return testcases
108
+
109
+
110
+ def find_built_testcase_inputs(group: TestcaseGroup) -> List[pathlib.Path]:
111
+ testgroup_path = get_build_testgroup_path(group.name)
112
+ if not testgroup_path.is_dir():
113
+ console.console.print(
114
+ f'Testgroup {group.name} is not generated in build folder'
115
+ )
116
+ raise typer.Exit(1)
117
+
118
+ return sorted(testgroup_path.glob('*.in'))
119
+
120
+
121
+ def clear_built_testcases():
122
+ shutil.rmtree(str(get_build_tests_path()), ignore_errors=True)
123
+
124
+
125
+ def get_samples() -> List[Testcase]:
126
+ tcs = find_built_testcases(package.get_testgroup('samples'))
127
+ return [
128
+ Testcase(
129
+ inputPath=tc.inputPath.resolve(),
130
+ outputPath=tc.outputPath.resolve()
131
+ if tc.outputPath is not None and tc.outputPath.is_file()
132
+ else None,
133
+ )
134
+ for tc in tcs
135
+ ]