rbx.cp 0.5.38__tar.gz → 0.5.40__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 (179) hide show
  1. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/PKG-INFO +1 -1
  2. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/pyproject.toml +1 -1
  3. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/checkers.py +5 -1
  4. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/code.py +1 -1
  5. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/main.py +100 -19
  6. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/packaging/main.py +9 -0
  7. rbx_cp-0.5.40/rbx/box/packaging/moj/packager.py +125 -0
  8. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/schema.py +66 -0
  9. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/solutions.py +31 -10
  10. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/state.py +1 -0
  11. rbx_cp-0.5.40/rbx/box/unit.py +113 -0
  12. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/validators.py +17 -8
  13. rbx_cp-0.5.40/rbx/resources/packagers/moj/scripts/compare.sh +82 -0
  14. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/LICENSE +0 -0
  15. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/README.md +0 -0
  16. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/__init__.py +0 -0
  17. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/annotations.py +0 -0
  18. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/autoenum.py +0 -0
  19. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/__init__.py +0 -0
  20. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/builder.py +0 -0
  21. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/cd.py +0 -0
  22. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/compile.py +0 -0
  23. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/conftest.py +0 -0
  24. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/contest/__init__.py +0 -0
  25. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/contest/build_contest_statements.py +0 -0
  26. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/contest/contest_package.py +0 -0
  27. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/contest/contest_utils.py +0 -0
  28. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/contest/main.py +0 -0
  29. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/contest/schema.py +0 -0
  30. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/contest/statements.py +0 -0
  31. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/creation.py +0 -0
  32. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/deferred.py +0 -0
  33. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/download.py +0 -0
  34. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/environment.py +0 -0
  35. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/extensions.py +0 -0
  36. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/formatting.py +0 -0
  37. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/generators.py +0 -0
  38. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/generators_test.py +0 -0
  39. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/lazy_importing_main.py +0 -0
  40. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/lazy_importing_test.py +0 -0
  41. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/package.py +0 -0
  42. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/packaging/boca/extension.py +0 -0
  43. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/packaging/boca/packager.py +0 -0
  44. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/packaging/contest_main.py +0 -0
  45. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/packaging/packager.py +0 -0
  46. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/packaging/polygon/packager.py +0 -0
  47. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/packaging/polygon/test.py +0 -0
  48. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/packaging/polygon/xml_schema.py +0 -0
  49. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/presets/__init__.py +0 -0
  50. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/presets/fetch.py +0 -0
  51. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/presets/lock_schema.py +0 -0
  52. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/presets/schema.py +0 -0
  53. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/retries.py +0 -0
  54. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/sanitizers/warning_stack.py +0 -0
  55. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/setter_config.py +0 -0
  56. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/solutions_test.py +0 -0
  57. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/statements/__init__.py +0 -0
  58. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/statements/build_statements.py +0 -0
  59. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/statements/builders.py +0 -0
  60. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/statements/joiners.py +0 -0
  61. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/statements/latex.py +0 -0
  62. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/statements/latex_jinja.py +0 -0
  63. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/statements/schema.py +0 -0
  64. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/stresses.py +0 -0
  65. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/stressing/__init__.py +0 -0
  66. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/stressing/finder_parser.py +0 -0
  67. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/stressing/generator_parser.py +0 -0
  68. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/testcase_extractors.py +0 -0
  69. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/testcase_utils.py +0 -0
  70. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/testcases/__init__.py +0 -0
  71. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/testcases/main.py +0 -0
  72. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/ui/__init__.py +0 -0
  73. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/ui/captured_log.py +0 -0
  74. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/ui/css/app.tcss +0 -0
  75. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/ui/main.py +0 -0
  76. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/ui/run.py +0 -0
  77. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/box/validators_test.py +0 -0
  78. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/checker.py +0 -0
  79. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/clone.py +0 -0
  80. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/config.py +0 -0
  81. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/conftest.py +0 -0
  82. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/console.py +0 -0
  83. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/create.py +0 -0
  84. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/edit.py +0 -0
  85. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/__init__.py +0 -0
  86. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/caching.py +0 -0
  87. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/conftest.py +0 -0
  88. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/__init__.py +0 -0
  89. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/cacher.py +0 -0
  90. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/digester.py +0 -0
  91. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/sandbox.py +0 -0
  92. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/sandboxes/__init__.py +0 -0
  93. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/sandboxes/isolate.py +0 -0
  94. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/sandboxes/stupid_sandbox.py +0 -0
  95. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/sandboxes/timeit.py +0 -0
  96. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/storage.py +0 -0
  97. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/test.py +0 -0
  98. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/judge/testiso.py +0 -0
  99. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/steps.py +0 -0
  100. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/steps_with_caching.py +0 -0
  101. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading/steps_with_caching_run_test.py +0 -0
  102. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/grading_utils.py +0 -0
  103. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/hydration.py +0 -0
  104. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/main.py +0 -0
  105. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/metadata.py +0 -0
  106. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/providers/__init__.py +0 -0
  107. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/providers/codeforces.py +0 -0
  108. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/providers/provider.py +0 -0
  109. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/checkers/boilerplate.cpp +0 -0
  110. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/default_config.json +0 -0
  111. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/default_setter_config.mac.yml +0 -0
  112. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/default_setter_config.yml +0 -0
  113. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/envs/default.rbx.yml +0 -0
  114. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/envs/isolate.rbx.yml +0 -0
  115. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/checker.sh +0 -0
  116. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/compare +0 -0
  117. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/compile/c +0 -0
  118. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/compile/cc +0 -0
  119. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/compile/cpp +0 -0
  120. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/compile/java +0 -0
  121. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/compile/kt +0 -0
  122. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/compile/pas +0 -0
  123. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/compile/py2 +0 -0
  124. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/compile/py3 +0 -0
  125. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/run/c +0 -0
  126. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/run/cc +0 -0
  127. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/run/cpp +0 -0
  128. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/run/java +0 -0
  129. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/run/kt +0 -0
  130. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/run/py2 +0 -0
  131. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/packagers/boca/run/py3 +0 -0
  132. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
  133. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
  134. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
  135. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
  136. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/preset.rbx.yml +0 -0
  137. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/.gitignore +0 -0
  138. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/gen.cpp +0 -0
  139. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
  140. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/random.py +0 -0
  141. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/random.txt +0 -0
  142. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
  143. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
  144. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
  145. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
  146. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  147. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
  148. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
  149. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
  150. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
  151. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/validator.cpp +0 -0
  152. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
  153. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/resources/templates/template.cpp +0 -0
  154. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/run.py +0 -0
  155. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/schema.py +0 -0
  156. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/submit.py +0 -0
  157. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/submitors/__init__.py +0 -0
  158. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/submitors/codeforces.py +0 -0
  159. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/submitors/submitor.py +0 -0
  160. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/test.py +0 -0
  161. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testcase.py +0 -0
  162. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testcase_rendering.py +0 -0
  163. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/gen1.cpp +0 -0
  164. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/gen2.cpp +0 -0
  165. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/genScript.py +0 -0
  166. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/hard-tle.sol.cpp +0 -0
  167. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/ole.cpp +0 -0
  168. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/problem.rbx.yml +0 -0
  169. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/re.sol.cpp +0 -0
  170. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/sol.cpp +0 -0
  171. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/tests/1.in +0 -0
  172. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -0
  173. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/tle.sol.cpp +0 -0
  174. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/validator.cpp +0 -0
  175. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/box1/wa.sol.cpp +0 -0
  176. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/caching/executable.py +0 -0
  177. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testdata/compatible +0 -0
  178. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/testing_utils.py +0 -0
  179. {rbx_cp-0.5.38 → rbx_cp-0.5.40}/rbx/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rbx.cp
3
- Version: 0.5.38
3
+ Version: 0.5.40
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.38"
3
+ version = "0.5.40"
4
4
  description = ""
5
5
  packages = [
6
6
  {include = "rbx"}
@@ -17,11 +17,15 @@ from rbx.grading.steps import (
17
17
  Outcome,
18
18
  RunLog,
19
19
  )
20
+ from rbx.utils import StatusProgress
20
21
 
21
22
 
22
- def compile_checker() -> str:
23
+ def compile_checker(progress: Optional[StatusProgress] = None) -> str:
23
24
  checker = package.get_checker()
24
25
 
26
+ if progress:
27
+ progress.update('Compiling checker...')
28
+
25
29
  try:
26
30
  digest = compile_item(checker, sanitized=SanitizationLevel.PREFER)
27
31
  except Exception as e:
@@ -47,7 +47,7 @@ class SanitizationLevel(Enum):
47
47
 
48
48
  def should_sanitize(self) -> bool:
49
49
  cfg = setter_config.get_setter_config()
50
- if cfg.sanitizers.enabled:
50
+ if cfg.sanitizers.enabled or state.STATE.sanitized:
51
51
  return self.value >= SanitizationLevel.PREFER.value
52
52
  return self.value >= SanitizationLevel.FORCE.value
53
53
 
@@ -55,43 +55,70 @@ app.add_typer(
55
55
  setter_config.app,
56
56
  name='config, cfg',
57
57
  cls=annotations.AliasGroup,
58
- help='Manage setter configuration.',
58
+ help='Manage setter configuration (sub-command).',
59
+ rich_help_panel='Configuration',
59
60
  )
60
61
  app.add_typer(
61
62
  build_statements.app,
62
63
  name='statements, st',
63
64
  cls=annotations.AliasGroup,
64
- help='Manage statements.',
65
+ help='Manage statements (sub-command).',
66
+ rich_help_panel='Deploying',
65
67
  )
66
68
  app.add_typer(
67
69
  download.app,
68
70
  name='download',
69
71
  cls=annotations.AliasGroup,
70
- help='Download an asset from supported repositories.',
72
+ help='Download an asset from supported repositories (sub-command).',
73
+ rich_help_panel='Management',
71
74
  )
72
75
  app.add_typer(
73
- presets.app, name='presets', cls=annotations.AliasGroup, help='Manage presets.'
76
+ presets.app,
77
+ name='presets',
78
+ cls=annotations.AliasGroup,
79
+ help='Manage presets (sub-command).',
80
+ rich_help_panel='Configuration',
74
81
  )
75
82
  app.add_typer(
76
83
  packaging.app,
77
84
  name='package, pkg',
78
85
  cls=annotations.AliasGroup,
79
- help='Build problem packages.',
86
+ help='Build problem packages (sub-command).',
87
+ rich_help_panel='Deploying',
80
88
  )
81
89
  app.add_typer(
82
- contest.app, name='contest', cls=annotations.AliasGroup, help='Contest management.'
90
+ contest.app,
91
+ name='contest',
92
+ cls=annotations.AliasGroup,
93
+ help='Manage contests (sub-command).',
94
+ rich_help_panel='Management',
83
95
  )
84
96
  app.add_typer(
85
97
  testcases.app,
86
98
  name='testcases, tc, t',
87
99
  cls=annotations.AliasGroup,
88
- help='Testcase management.',
100
+ help='Manage testcases (sub-command).',
101
+ rich_help_panel='Management',
89
102
  )
90
103
 
91
104
 
92
105
  @app.callback()
93
- def main():
106
+ def main(
107
+ sanitized: bool = typer.Option(
108
+ False,
109
+ '--sanitized',
110
+ '-s',
111
+ help='Whether to compile and run testlib components with sanitizers enabled. '
112
+ 'If you want to run the solutions with sanitizers enabled, use the "-s" flag in the corresponding run command.',
113
+ ),
114
+ ):
94
115
  state.STATE.run_through_cli = True
116
+ state.STATE.sanitized = sanitized
117
+ if sanitized:
118
+ console.console.print(
119
+ '[warning]Sanitizers are running just for testlib components.\n'
120
+ 'If you want to run the solutions with sanitizers enabled, use the [item]-s[/item] flag in the corresponding run command.[/warning]'
121
+ )
95
122
 
96
123
 
97
124
  # @app.command('ui', hidden=True)
@@ -100,7 +127,11 @@ def main():
100
127
  # ui_pkg.start()
101
128
 
102
129
 
103
- @app.command('edit, e', help='Open problem.rbx.yml in your default editor.')
130
+ @app.command(
131
+ 'edit, e',
132
+ rich_help_panel='Configuration',
133
+ help='Open problem.rbx.yml in your default editor.',
134
+ )
104
135
  @package.within_problem
105
136
  def edit():
106
137
  console.console.print('Opening problem definition in editor...')
@@ -110,7 +141,9 @@ def edit():
110
141
  config.open_editor(package.find_problem_yaml() or pathlib.Path())
111
142
 
112
143
 
113
- @app.command('build, b', help='Build all tests for the problem.')
144
+ @app.command(
145
+ 'build, b', rich_help_panel='Deploying', help='Build all tests for the problem.'
146
+ )
114
147
  @package.within_problem
115
148
  def build(verification: environment.VerificationParam):
116
149
  from rbx.box import builder
@@ -118,7 +151,11 @@ def build(verification: environment.VerificationParam):
118
151
  builder.build(verification=verification)
119
152
 
120
153
 
121
- @app.command('run, r', help='Build and run solution(s).')
154
+ @app.command(
155
+ 'run, r',
156
+ rich_help_panel='Testing',
157
+ help='Build and run solution(s).',
158
+ )
122
159
  @package.within_problem
123
160
  def run(
124
161
  verification: environment.VerificationParam,
@@ -299,6 +336,7 @@ def _time_impl(check: bool, detailed: bool) -> Optional[int]:
299
336
 
300
337
  @app.command(
301
338
  'time, t',
339
+ rich_help_panel='Testing',
302
340
  help='Estimate a time limit for the problem based on a time limit formula and timings of accepted solutions.',
303
341
  )
304
342
  @package.within_problem
@@ -333,7 +371,9 @@ def time(
333
371
 
334
372
 
335
373
  @app.command(
336
- 'irun, ir', help='Build and run solution(s) by passing testcases in the CLI.'
374
+ 'irun, ir',
375
+ rich_help_panel='Testing',
376
+ help='Build and run solution(s) by passing testcases in the CLI.',
337
377
  )
338
378
  @package.within_problem
339
379
  def irun(
@@ -445,7 +485,11 @@ def irun(
445
485
  )
446
486
 
447
487
 
448
- @app.command('create, c', help='Create a new problem package.')
488
+ @app.command(
489
+ 'create, c',
490
+ rich_help_panel='Management',
491
+ help='Create a new problem package.',
492
+ )
449
493
  def create(
450
494
  name: str,
451
495
  preset: Annotated[
@@ -467,7 +511,11 @@ def create(
467
511
  creation.create(name)
468
512
 
469
513
 
470
- @app.command('stress', help='Run a stress test.')
514
+ @app.command(
515
+ 'stress',
516
+ rich_help_panel='Testing',
517
+ help='Run a stress test.',
518
+ )
471
519
  @package.within_problem
472
520
  def stress(
473
521
  name: Annotated[
@@ -621,7 +669,11 @@ def stress(
621
669
  break
622
670
 
623
671
 
624
- @app.command('compile', help='Compile an asset given its path.')
672
+ @app.command(
673
+ 'compile',
674
+ rich_help_panel='Testing',
675
+ help='Compile an asset given its path.',
676
+ )
625
677
  @package.within_problem
626
678
  def compile_command(
627
679
  path: Annotated[
@@ -652,7 +704,11 @@ def compile_command(
652
704
  compile.any(path, sanitized, warnings)
653
705
 
654
706
 
655
- @app.command('validate', help='Run the validator in a one-off fashion, interactively.')
707
+ @app.command(
708
+ 'validate',
709
+ rich_help_panel='Testing',
710
+ help='Run the validator in a one-off fashion, interactively.',
711
+ )
656
712
  @package.within_problem
657
713
  def validate(
658
714
  path: Annotated[
@@ -685,7 +741,23 @@ def validate(
685
741
  validators.print_validation_report([info])
686
742
 
687
743
 
688
- @app.command('environment, env', help='Set or show the current box environment.')
744
+ @app.command(
745
+ 'unit',
746
+ rich_help_panel='Testing',
747
+ help='Run unit tests for the validator and checker.',
748
+ )
749
+ def unit_tests():
750
+ from rbx.box import unit
751
+
752
+ with utils.StatusProgress('Running unit tests...') as s:
753
+ unit.run_unit_tests(s)
754
+
755
+
756
+ @app.command(
757
+ 'environment, env',
758
+ rich_help_panel='Configuration',
759
+ help='Set or show the current box environment.',
760
+ )
689
761
  def environment_command(
690
762
  env: Annotated[Optional[str], typer.Argument()] = None,
691
763
  install_from: Annotated[
@@ -730,6 +802,7 @@ def environment_command(
730
802
 
731
803
  @app.command(
732
804
  'activate',
805
+ rich_help_panel='Configuration',
733
806
  help='Activate the environment of the current preset used by the package.',
734
807
  )
735
808
  @cd.within_closest_package
@@ -764,7 +837,11 @@ def activate():
764
837
  console.console.print(f'[success]Preset [item]{preset.name}[/item] is activated.')
765
838
 
766
839
 
767
- @app.command('languages', help='List the languages available in this environment')
840
+ @app.command(
841
+ 'languages',
842
+ rich_help_panel='Configuration',
843
+ help='List the languages available in this environment',
844
+ )
768
845
  def languages():
769
846
  env = environment.get_environment()
770
847
 
@@ -780,7 +857,11 @@ def languages():
780
857
  console.console.print()
781
858
 
782
859
 
783
- @app.command('clear, clean', help='Clears cache and build directories.')
860
+ @app.command(
861
+ 'clear, clean',
862
+ rich_help_panel='Management',
863
+ help='Clears cache and build directories.',
864
+ )
784
865
  @cd.within_closest_package
785
866
  def clear():
786
867
  console.console.print('Cleaning cache and build directories...')
@@ -70,3 +70,12 @@ def boca(
70
70
  from rbx.box.packaging.boca.packager import BocaPackager
71
71
 
72
72
  run_packager(BocaPackager, verification=verification)
73
+
74
+
75
+ @app.command('moj', help='Build a package for MOJ.')
76
+ def moj(
77
+ verification: environment.VerificationParam,
78
+ ):
79
+ from rbx.box.packaging.moj.packager import MojPackager
80
+
81
+ run_packager(MojPackager, verification=verification)
@@ -0,0 +1,125 @@
1
+ import pathlib
2
+ import shutil
3
+ from typing import List
4
+
5
+ import typer
6
+
7
+ from rbx import console
8
+ from rbx.box import package
9
+ from rbx.box.environment import get_extension_or_default
10
+ from rbx.box.packaging.boca.extension import BocaExtension
11
+ from rbx.box.packaging.boca.packager import BocaPackager
12
+ from rbx.box.packaging.packager import BuiltStatement
13
+ from rbx.config import get_default_app_path
14
+ from rbx.grading.judge.digester import digest_cooperatively
15
+
16
+
17
+ class MojPackager(BocaPackager):
18
+ def _get_problem_info(self) -> str:
19
+ statement = self._get_main_statement()
20
+ return (
21
+ f'basename={self._get_problem_name()}\n'
22
+ f'fullname={statement.title}\n'
23
+ f'descfile={self._get_problem_name()}.pdf\n'
24
+ )
25
+
26
+ def _get_limits(self) -> str:
27
+ extension = get_extension_or_default('boca', BocaExtension)
28
+
29
+ pkg = package.find_problem_package_or_die()
30
+ tl = pkg.timeLimit
31
+ ml = pkg.memoryLimit
32
+ ol = pkg.outputLimit
33
+ conf = f'ULIMITS[-f]={ol}\n' f'ULIMITS[-v]={ml}\n' f'TL[default]={tl / 1000}\n'
34
+ for language in extension.languages:
35
+ conf += f'TL[{language}]={self._get_pkg_timelimit(language) / 1000}\n'
36
+ return conf
37
+
38
+ def _get_compare(self) -> str:
39
+ extension = get_extension_or_default('boca', BocaExtension)
40
+
41
+ compare_path = (
42
+ get_default_app_path() / 'packagers' / 'moj' / 'scripts' / 'compare.sh'
43
+ )
44
+ if not compare_path.exists():
45
+ console.console.print(
46
+ '[error]MOJ template compare script not found.[/error]'
47
+ )
48
+ raise typer.Exit(1)
49
+ with package.get_checker().path.open('rb') as f:
50
+ checker_hash = digest_cooperatively(f)
51
+ return (
52
+ compare_path.read_text()
53
+ .replace('{{rbxFlags}}', extension.flags_with_defaults()['cc'])
54
+ .replace('{{checkerHash}}', checker_hash)
55
+ )
56
+
57
+ def _get_checker(self) -> str:
58
+ return package.get_checker().path.read_text()
59
+
60
+ def name(self) -> str:
61
+ return 'moj'
62
+
63
+ def package(
64
+ self,
65
+ build_path: pathlib.Path,
66
+ into_path: pathlib.Path,
67
+ built_statements: List[BuiltStatement],
68
+ ) -> pathlib.Path:
69
+ # Prepare dummy files
70
+ author_path = into_path / 'author'
71
+ author_path.parent.mkdir(parents=True, exist_ok=True)
72
+ author_path.write_text('Unknown\n')
73
+
74
+ tags_path = into_path / 'tags'
75
+ tags_path.parent.mkdir(parents=True, exist_ok=True)
76
+ tags_path.write_text('')
77
+
78
+ # Prepare limits
79
+ limits_path = into_path / 'conf'
80
+ limits_path.parent.mkdir(parents=True, exist_ok=True)
81
+ limits_path.write_text(self._get_limits())
82
+
83
+ # Prepare compare
84
+ compare_path = into_path / 'scripts' / 'compare.sh'
85
+ compare_path.parent.mkdir(parents=True, exist_ok=True)
86
+ compare_path.write_text(self._get_compare())
87
+
88
+ # Prepare checker
89
+ checker_path = into_path / 'scripts' / 'checker.cpp'
90
+ checker_path.parent.mkdir(parents=True, exist_ok=True)
91
+ checker_path.write_text(self._get_checker())
92
+
93
+ # Problem statement
94
+ enunciado_path = into_path / 'docs' / 'enunciado.pdf'
95
+ enunciado_path.parent.mkdir(parents=True, exist_ok=True)
96
+ shutil.copyfile(
97
+ self._get_main_built_statement(built_statements).path,
98
+ enunciado_path,
99
+ )
100
+
101
+ # Copy solutions
102
+ solutions_path = into_path / 'solutions'
103
+ solutions_path.mkdir(parents=True, exist_ok=True)
104
+ self._copy_solutions(solutions_path)
105
+
106
+ # Prepare IO
107
+ inputs_path = into_path / 'tests' / 'input'
108
+ inputs_path.mkdir(parents=True, exist_ok=True)
109
+ outputs_path = into_path / 'tests' / 'output'
110
+ outputs_path.mkdir(parents=True, exist_ok=True)
111
+
112
+ testcases = self.get_flattened_built_testcases()
113
+ for i, testcase in enumerate(testcases):
114
+ shutil.copyfile(testcase.inputPath, inputs_path / f'{i+1:03d}')
115
+ if testcase.outputPath is not None:
116
+ shutil.copyfile(testcase.outputPath, outputs_path / f'{i+1:03d}')
117
+ else:
118
+ (outputs_path / f'{i+1:03d}').touch()
119
+
120
+ # Zip all.
121
+ shutil.make_archive(
122
+ str(build_path / self._get_problem_name()), 'zip', into_path
123
+ )
124
+
125
+ return (build_path / self._get_problem_name()).with_suffix('.zip')
@@ -148,6 +148,14 @@ class ExpectedOutcome(AutoEnum):
148
148
  return bool(set(self.get_matches()) & set(rhs.get_matches()))
149
149
 
150
150
 
151
+ class ValidatorOutcome(AutoEnum):
152
+ VALID = alias('valid') # type: ignore
153
+ """Expected outcome for valid tests."""
154
+
155
+ INVALID = alias('invalid') # type: ignore
156
+ """Expected outcome for invalid tests."""
157
+
158
+
151
159
  class CodeItem(BaseModel):
152
160
  model_config = ConfigDict(extra='forbid')
153
161
 
@@ -337,6 +345,59 @@ class LimitModifiers(BaseModel):
337
345
  )
338
346
 
339
347
 
348
+ class ValidatorTest(BaseModel):
349
+ model_config = ConfigDict(extra='forbid')
350
+
351
+ input: pathlib.Path = Field(
352
+ description='The input file to be used as unit test input for the validator.'
353
+ )
354
+ outcome: ValidatorOutcome = Field(
355
+ default=ValidatorOutcome.VALID,
356
+ description='The expected outcome of the validator.',
357
+ )
358
+
359
+ validator: Optional[CodeItem] = Field(
360
+ default=None,
361
+ description='The validator to use for this test. If not specified, will use the package-level validator.',
362
+ )
363
+
364
+
365
+ class CheckerTest(BaseModel):
366
+ model_config = ConfigDict(extra='forbid')
367
+
368
+ input: Optional[pathlib.Path] = Field(
369
+ default=None,
370
+ description='The input file to be used as unit test input for the checker. If not specified, will pass an empty file.',
371
+ )
372
+ output: Optional[pathlib.Path] = Field(
373
+ default=None,
374
+ description='The solution output file to be used as unit test output for the checker. If not specified, will pass an empty file.',
375
+ )
376
+ answer: Optional[pathlib.Path] = Field(
377
+ default=None,
378
+ description='The answer file to be used as unit test answer for the checker. If not specified, will pass an empty file.',
379
+ )
380
+
381
+ outcome: ExpectedOutcome = Field(
382
+ default=ExpectedOutcome.ACCEPTED,
383
+ description='The expected outcome of the checker.',
384
+ )
385
+
386
+
387
+ class UnitTests(BaseModel):
388
+ model_config = ConfigDict(extra='forbid')
389
+
390
+ validator: List[ValidatorTest] = Field(
391
+ default=[],
392
+ description='Unit tests for the validator.',
393
+ )
394
+
395
+ checker: List[CheckerTest] = Field(
396
+ default=[],
397
+ description='Unit tests for the checker.',
398
+ )
399
+
400
+
340
401
  class Package(BaseModel):
341
402
  model_config = ConfigDict(extra='forbid')
342
403
 
@@ -399,6 +460,11 @@ that is correct and used as reference -- and should have the `accepted` outcome.
399
460
  default={}, description='Variables to be re-used across the package.'
400
461
  )
401
462
 
463
+ unitTests: UnitTests = Field(
464
+ default_factory=UnitTests,
465
+ description='Unit tests for components of this problem.',
466
+ )
467
+
402
468
  @property
403
469
  def expanded_vars(self) -> Dict[str, Primitive]:
404
470
  return {key: expand_var(value) for key, value in self.vars.items()}
@@ -635,13 +635,22 @@ async def run_and_print_interactive_solutions(
635
635
 
636
636
  for item in items:
637
637
  sol = pkg.solutions[item.solution_index]
638
- with utils.no_progress(progress):
639
- _print_solution_header(sol, console.console, is_irun=True)
638
+
639
+ if progress:
640
+ progress.update(f'Running [item]{sol.path}[/item]...')
640
641
 
641
642
  eval = await item.eval()
642
643
 
644
+ with utils.no_progress(progress):
645
+ console.console.print(get_testcase_markup_verdict(eval), end=' ')
646
+ _print_solution_header(sol, console.console, is_irun=True)
647
+ _print_solution_outcome(
648
+ sol, [eval], console.console, verification, subset=True
649
+ )
650
+
643
651
  stdout_path = eval.log.stdout_absolute_path
644
652
  if print:
653
+ console.console.rule('Output', style='status')
645
654
  if (
646
655
  eval.testcase.output is not None
647
656
  and stdout_path is not None
@@ -780,14 +789,17 @@ def _print_solution_outcome(
780
789
  evals: List[Evaluation],
781
790
  console: rich.console.Console,
782
791
  verification: VerificationLevel = VerificationLevel.NONE,
792
+ subset: bool = False,
783
793
  ) -> bool:
784
794
  pkg = package.find_problem_package_or_die()
785
795
 
786
796
  has_plain_tle = False
797
+ all_verdicts = set()
787
798
  bad_verdicts = set()
788
799
  no_tle_bad_verdicts = set()
789
800
  has_sanitizer_warnings = False
790
801
  for eval in evals:
802
+ all_verdicts.add(eval.result.outcome)
791
803
  if eval.result.outcome != Outcome.ACCEPTED:
792
804
  bad_verdicts.add(eval.result.outcome)
793
805
  if (
@@ -808,18 +820,27 @@ def _print_solution_outcome(
808
820
  matched_bad_verdicts = bad_verdicts - unmatched_bad_verdicts
809
821
  expected_outcome_is_bad = not solution.outcome.match(Outcome.ACCEPTED)
810
822
 
811
- if unmatched_bad_verdicts or (expected_outcome_is_bad and not matched_bad_verdicts):
823
+ has_failed = unmatched_bad_verdicts or (
824
+ expected_outcome_is_bad and not matched_bad_verdicts and not subset
825
+ )
826
+ if has_failed:
812
827
  console.print('[error]FAILED[/error]', end=' ')
813
828
  else:
814
829
  console.print('[success]OK[/success]', end=' ')
815
830
 
816
- console.print(f'Expected: {solution.outcome}', end='')
817
-
818
- if unmatched_bad_verdicts:
819
- unmatched_bad_verdicts_names = set(v.name for v in unmatched_bad_verdicts)
820
- console.print(f', got: {" ".join(unmatched_bad_verdicts_names)}', end='')
821
- elif expected_outcome_is_bad and not matched_bad_verdicts:
822
- console.print(f', got: {Outcome.ACCEPTED.name}', end='')
831
+ if has_failed or not subset:
832
+ console.print(f'Expected: {solution.outcome}', end='')
833
+ elif subset:
834
+ all_verdicts_names = ' '.join(v.name for v in all_verdicts)
835
+ console.print(f'Got: {all_verdicts_names}', end='')
836
+
837
+ if has_failed or not subset:
838
+ # Only print verdicts if not subset.
839
+ if unmatched_bad_verdicts:
840
+ unmatched_bad_verdicts_names = set(v.name for v in unmatched_bad_verdicts)
841
+ console.print(f', got: {" ".join(unmatched_bad_verdicts_names)}', end='')
842
+ elif expected_outcome_is_bad and not matched_bad_verdicts and not subset:
843
+ console.print(f', got: {Outcome.ACCEPTED.name}', end='')
823
844
 
824
845
  console.print()
825
846
  evals_time = _get_evals_time_in_ms(evals)
@@ -4,6 +4,7 @@ import dataclasses
4
4
  @dataclasses.dataclass
5
5
  class State:
6
6
  run_through_cli: bool = False
7
+ sanitized: bool = False
7
8
 
8
9
 
9
10
  STATE = State()