rbx.cp 0.13.5__tar.gz → 0.13.7__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 (228) hide show
  1. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/PKG-INFO +1 -1
  2. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/pyproject.toml +1 -1
  3. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/code.py +2 -2
  4. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/header.py +15 -9
  5. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/latex_jinja.py +1 -1
  6. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testcase_extractors.py +2 -2
  7. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testcase_utils.py +2 -0
  8. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testing/testing_package.py +215 -2
  9. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testing/testing_shared.py +4 -1
  10. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/unit.py +4 -4
  11. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/caching.py +1 -1
  12. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/cacher.py +5 -3
  13. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/program.py +34 -2
  14. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/sandbox.py +0 -7
  15. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/sandboxes/stupid_sandbox.py +7 -1
  16. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/storage.py +7 -1
  17. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/steps.py +2 -2
  18. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/utils.py +53 -5
  19. rbx_cp-0.13.5/rbx/grading/judge/sandboxes/timeit.py +0 -358
  20. rbx_cp-0.13.5/rbx/grading/judge/test.py +0 -38
  21. rbx_cp-0.13.5/rbx/grading/processing_context.py +0 -71
  22. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/LICENSE +0 -0
  23. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/README.md +0 -0
  24. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/__init__.py +0 -0
  25. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/annotations.py +0 -0
  26. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/autoenum.py +0 -0
  27. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/__init__.py +0 -0
  28. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/builder.py +0 -0
  29. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/cd.py +0 -0
  30. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/checkers.py +0 -0
  31. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/cli.py +0 -0
  32. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/compile.py +0 -0
  33. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/__init__.py +0 -0
  34. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/build_contest_statements.py +0 -0
  35. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/contest_package.py +0 -0
  36. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/contest_utils.py +0 -0
  37. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/main.py +0 -0
  38. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/schema.py +0 -0
  39. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/statements.py +0 -0
  40. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/creation.py +0 -0
  41. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/deferred.py +0 -0
  42. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/download.py +0 -0
  43. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/dump_schemas.py +0 -0
  44. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/environment.py +0 -0
  45. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/extensions.py +0 -0
  46. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/fields.py +0 -0
  47. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/formatting.py +0 -0
  48. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/generators.py +0 -0
  49. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/git_utils.py +0 -0
  50. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/global_package.py +0 -0
  51. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/lang.py +0 -0
  52. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/lazy_importing_main.py +0 -0
  53. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/linting.py +0 -0
  54. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/main.py +0 -0
  55. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/naming.py +0 -0
  56. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/package.py +0 -0
  57. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/__init__.py +0 -0
  58. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/boca/__init__.py +0 -0
  59. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/boca/extension.py +0 -0
  60. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/boca/packager.py +0 -0
  61. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/contest_main.py +0 -0
  62. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/importer.py +0 -0
  63. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/main.py +0 -0
  64. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/moj/packager.py +0 -0
  65. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/packager.py +0 -0
  66. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/pkg/packager.py +0 -0
  67. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/importer.py +0 -0
  68. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/packager.py +0 -0
  69. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/polygon_api.py +0 -0
  70. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/test.py +0 -0
  71. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/upload.py +0 -0
  72. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/xml_schema.py +0 -0
  73. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/presets/__init__.py +0 -0
  74. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/presets/fetch.py +0 -0
  75. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/presets/lock_schema.py +0 -0
  76. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/presets/schema.py +0 -0
  77. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/remote.py +0 -0
  78. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/retries.py +0 -0
  79. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/sanitizers/warning_stack.py +0 -0
  80. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/schema.py +0 -0
  81. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/setter_config.py +0 -0
  82. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/solutions.py +0 -0
  83. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/state.py +0 -0
  84. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/__init__.py +0 -0
  85. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/build_statements.py +0 -0
  86. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/builders.py +0 -0
  87. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/expander.py +0 -0
  88. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/joiners.py +0 -0
  89. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/latex.py +0 -0
  90. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/schema.py +0 -0
  91. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stats.py +0 -0
  92. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stresses.py +0 -0
  93. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stressing/__init__.py +0 -0
  94. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stressing/finder_parser.py +0 -0
  95. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stressing/generator_parser.py +0 -0
  96. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tasks.py +0 -0
  97. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testcases/__init__.py +0 -0
  98. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testcases/main.py +0 -0
  99. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testing/__init__.py +0 -0
  100. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testing/testing_preset.py +0 -0
  101. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/__init__.py +0 -0
  102. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/boca/__init__.py +0 -0
  103. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/boca/main.py +0 -0
  104. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/boca/scrape.py +0 -0
  105. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/boca/scraper.py +0 -0
  106. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/converter.py +0 -0
  107. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/main.py +0 -0
  108. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/__init__.py +0 -0
  109. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/captured_log.py +0 -0
  110. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/css/app.tcss +0 -0
  111. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/main.py +0 -0
  112. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/__init__.py +0 -0
  113. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/build.py +0 -0
  114. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/command.py +0 -0
  115. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/differ.py +0 -0
  116. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/error.py +0 -0
  117. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/rich_log_modal.py +0 -0
  118. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/run.py +0 -0
  119. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/run_explorer.py +0 -0
  120. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/run_test_explorer.py +0 -0
  121. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/selector.py +0 -0
  122. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/test_explorer.py +0 -0
  123. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/utils/__init__.py +0 -0
  124. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/utils/run_ui.py +0 -0
  125. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/__init__.py +0 -0
  126. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/diff_box.py +0 -0
  127. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/file_log.py +0 -0
  128. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/interaction_box.py +0 -0
  129. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/rich_log_box.py +0 -0
  130. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/test_output_box.py +0 -0
  131. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/two_sided_test_output_box.py +0 -0
  132. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/validators.py +0 -0
  133. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/config.py +0 -0
  134. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/console.py +0 -0
  135. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/__init__.py +0 -0
  136. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/debug_context.py +0 -0
  137. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/grading_context.py +0 -0
  138. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/__init__.py +0 -0
  139. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/digester.py +0 -0
  140. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/sandboxes/__init__.py +0 -0
  141. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/sandboxes/tee.py +0 -0
  142. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/limits.py +0 -0
  143. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/profiling.py +0 -0
  144. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/steps_with_caching.py +0 -0
  145. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/providers/__init__.py +0 -0
  146. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/providers/codeforces.py +0 -0
  147. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/providers/provider.py +0 -0
  148. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/checkers/boilerplate.cpp +0 -0
  149. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/checkers/noop.cpp +0 -0
  150. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/default_config.json +0 -0
  151. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/default_setter_config.mac.yml +0 -0
  152. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/default_setter_config.yml +0 -0
  153. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/envs/default.rbx.yml +0 -0
  154. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/checker.sh +0 -0
  155. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compare.sh +0 -0
  156. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/c +0 -0
  157. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/cc +0 -0
  158. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/cpp +0 -0
  159. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/java +0 -0
  160. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/kt +0 -0
  161. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/py2 +0 -0
  162. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/py3 +0 -0
  163. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/c +0 -0
  164. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/cc +0 -0
  165. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/cpp +0 -0
  166. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/java +0 -0
  167. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/kt +0 -0
  168. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/py2 +0 -0
  169. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/py3 +0 -0
  170. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactor_compile.sh +0 -0
  171. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactor_run.sh +0 -0
  172. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/bkp +0 -0
  173. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/c +0 -0
  174. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/cc +0 -0
  175. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/cpp +0 -0
  176. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/java +0 -0
  177. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/kt +0 -0
  178. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/py2 +0 -0
  179. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/py3 +0 -0
  180. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/safeexec.c +0 -0
  181. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/safeexec_compile.sh +0 -0
  182. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/c/compile.sh +0 -0
  183. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/c/prep.sh +0 -0
  184. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/c/run.sh +0 -0
  185. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/compare.sh +0 -0
  186. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/cpp/compile.sh +0 -0
  187. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/cpp/prep.sh +0 -0
  188. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/cpp/run.sh +0 -0
  189. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/interactor_prep.sh +0 -0
  190. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/interactor_run.sh +0 -0
  191. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/java/compile.sh +0 -0
  192. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/java/prep.sh +0 -0
  193. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/java/run.sh +0 -0
  194. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py2/compile.sh +0 -0
  195. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py2/prep.sh +0 -0
  196. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py2/run.sh +0 -0
  197. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py3/compile.sh +0 -0
  198. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py3/prep.sh +0 -0
  199. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py3/run.sh +0 -0
  200. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/.gitignore +0 -0
  201. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
  202. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
  203. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/statement/instructions.tex +0 -0
  204. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/statement/logo.png +0 -0
  205. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/env.rbx.yml +0 -0
  206. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/preset.rbx.yml +0 -0
  207. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/.gitignore +0 -0
  208. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/gens/gen.cpp +0 -0
  209. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/manual_tests/samples/000.in +0 -0
  210. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/manual_tests/samples/001.in +0 -0
  211. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
  212. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/rbx.h +0 -0
  213. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
  214. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
  215. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
  216. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/testplan/random.py +0 -0
  217. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/testplan/random.txt +0 -0
  218. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/validator.cpp +0 -0
  219. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
  220. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/shared/contest_template.rbx.tex +0 -0
  221. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/shared/icpc.sty +0 -0
  222. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/shared/problem_template.rbx.tex +0 -0
  223. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/templates/rbx.h +0 -0
  224. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/templates/template.cpp +0 -0
  225. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/submitors/__init__.py +0 -0
  226. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/submitors/codeforces.py +0 -0
  227. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/submitors/submitor.py +0 -0
  228. {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/testing_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rbx.cp
3
- Version: 0.13.5
3
+ Version: 0.13.7
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9.1,<4.0.0
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "rbx.cp"
3
- version = "0.13.5"
3
+ version = "0.13.7"
4
4
  description = ""
5
5
  packages = [
6
6
  {include = "rbx"}
@@ -174,7 +174,7 @@ def _ignore_warning_in_cxx_input(input: GradingFileInput):
174
174
  input.src = preprocessed_path
175
175
 
176
176
 
177
- def _maybe_rename_java_class(
177
+ def maybe_rename_java_class(
178
178
  compilable_path: pathlib.Path, file_mapping: FileMapping
179
179
  ) -> pathlib.Path:
180
180
  mapped_path = PosixPath(file_mapping.compilable)
@@ -540,7 +540,7 @@ def compile_item(
540
540
  download.maybe_add_testlib(code, artifacts)
541
541
  download.maybe_add_jngen(code, artifacts)
542
542
  download.maybe_add_rbx_header(code, artifacts)
543
- compilable_path = _maybe_rename_java_class(compilable_path, file_mapping)
543
+ compilable_path = maybe_rename_java_class(compilable_path, file_mapping)
544
544
  artifacts.inputs.append(
545
545
  GradingFileInput(src=compilable_path, dest=PosixPath(file_mapping.compilable))
546
546
  )
@@ -49,7 +49,7 @@ def _get_string_var_block() -> str:
49
49
  return _get_var_block(_get_vars_of_type(str, _string_repr))
50
50
 
51
51
 
52
- def _check_int_bounds(x: int) -> None:
52
+ def check_int_bounds(x: int) -> None:
53
53
  if x >= 2**64:
54
54
  raise ValueError(
55
55
  f'Some variable you defined (value: {x}) is too large to fit in a C++ 64-bit integer (signed or unsigned)'
@@ -63,11 +63,19 @@ def _check_int_bounds(x: int) -> None:
63
63
  def _get_int_var_block() -> str:
64
64
  def _transform(x: Primitive) -> str:
65
65
  if isinstance(x, bool):
66
- return str(int(x))
67
- _check_int_bounds(int(x))
66
+ return f'static_cast<int64_t>({int(x)})'
67
+ check_int_bounds(int(x))
68
68
  return f'static_cast<int64_t>({x})'
69
69
 
70
- return _get_var_block(_get_vars_of_type(int, _transform))
70
+ # Get both int and bool variables for the int block
71
+ pkg = package.find_problem_package_or_die()
72
+ vars = pkg.expanded_vars
73
+ int_vars = {
74
+ name: _transform(value)
75
+ for name, value in vars.items()
76
+ if isinstance(value, (int, bool))
77
+ }
78
+ return _get_var_block(int_vars)
71
79
 
72
80
 
73
81
  def _get_float_var_block() -> str:
@@ -81,11 +89,9 @@ def _get_bool_var_block() -> str:
81
89
  def _get_vars_of_type(t: Type, transform: Callable[[Primitive], str]) -> Dict[str, str]:
82
90
  pkg = package.find_problem_package_or_die()
83
91
  vars = pkg.expanded_vars
84
-
85
- def is_valid(value: Primitive) -> bool:
86
- return isinstance(value, t)
87
-
88
- return {name: transform(value) for name, value in vars.items() if is_valid(value)}
92
+ return {
93
+ name: transform(value) for name, value in vars.items() if isinstance(value, t)
94
+ }
89
95
 
90
96
 
91
97
  def _get_var_block(mappings: Dict[str, str]) -> str:
@@ -119,7 +119,7 @@ def scientific_notation(
119
119
  mult, exp, rest = _process_zeroes(value)
120
120
  if exp < zeroes:
121
121
  return str(value)
122
- res = '10' if exp == 1 else f'10^{exp}'
122
+ res = '10' if exp == 1 else f'10^{{{exp}}}'
123
123
  if rest > 0 and len(str(rest)) + 1 >= len(str(value)):
124
124
  # Should not convert numbers like 532 to 5*10^2 + 32.
125
125
  return str(value)
@@ -30,7 +30,7 @@ def _get_group_output(
30
30
  return group_path / f'{subgroup_prefix}{i:03d}.out'
31
31
 
32
32
 
33
- async def _run_generator_script(testcase: TestcaseSubgroup) -> str:
33
+ async def run_generator_script(testcase: TestcaseSubgroup) -> str:
34
34
  assert testcase.generatorScript is not None
35
35
 
36
36
  cacher = package.get_file_cacher()
@@ -240,7 +240,7 @@ async def run_testcase_visitor(visitor: TestcaseVisitor):
240
240
 
241
241
  # Run generator script.
242
242
  if subgroup.generatorScript is not None:
243
- script = await _run_generator_script(subgroup)
243
+ script = await run_generator_script(subgroup)
244
244
 
245
245
  # Run each line from generator script.
246
246
  for generator_name, args, line_number in _extract_script_lines(script):
@@ -40,6 +40,8 @@ class TestcaseEntry(BaseModel):
40
40
 
41
41
 
42
42
  class TestcasePattern(BaseModel):
43
+ __test__ = False
44
+
43
45
  group_prefix: List[str]
44
46
  index: Optional[int] = None
45
47
 
@@ -1,19 +1,25 @@
1
1
  import pathlib
2
2
  from dataclasses import dataclass
3
- from typing import Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
 
5
5
  from rbx import console, utils
6
- from rbx.box import presets
6
+ from rbx.box import package, presets
7
7
  from rbx.box.fields import Primitive
8
8
  from rbx.box.schema import (
9
+ CheckerTest,
9
10
  CodeItem,
10
11
  ExpectedOutcome,
11
12
  Generator,
13
+ GeneratorCall,
12
14
  Interactor,
13
15
  Package,
14
16
  Solution,
15
17
  TaskType,
18
+ Testcase,
16
19
  TestcaseGroup,
20
+ TestcaseSubgroup,
21
+ ValidatorOutcome,
22
+ ValidatorTest,
17
23
  )
18
24
  from rbx.box.testing.testing_preset import TestingPreset
19
25
  from rbx.box.testing.testing_shared import PathOrStr, TestingShared
@@ -79,6 +85,9 @@ class TestingPackage(TestingShared):
79
85
 
80
86
  def save(self):
81
87
  self.yml_path.write_text(utils.model_to_yaml(self.yml))
88
+ # Clear internal cache and package cache to ensure the updated package is loaded fresh
89
+ self._yml = None
90
+ package.clear_package_cache()
82
91
 
83
92
  def set_type(self, type: TaskType):
84
93
  self.yml.type = type
@@ -223,6 +232,154 @@ class TestingPackage(TestingShared):
223
232
  ]
224
233
  self.save()
225
234
 
235
+ def add_testgroup_with_subgroups(
236
+ self,
237
+ name: str,
238
+ subgroups: List[Dict[str, Any]],
239
+ validator: Optional[PathOrStr] = None,
240
+ extra_validators: Optional[List[PathOrStr]] = None,
241
+ ):
242
+ """Add a testgroup with subgroups.
243
+
244
+ Args:
245
+ name: Name of the testgroup
246
+ subgroups: List of subgroup definitions, each containing fields like:
247
+ - name: subgroup name
248
+ - generators: list of generator calls
249
+ - testcases: list of testcase objects
250
+ - testcaseGlob: glob pattern
251
+ - generatorScript: generator script path
252
+ - extraValidators: list of extra validators
253
+ """
254
+
255
+ subgroup_objects = []
256
+ for subgroup_data in subgroups:
257
+ subgroup_dict = {'name': subgroup_data['name']}
258
+
259
+ if 'generators' in subgroup_data:
260
+ subgroup_dict['generators'] = [
261
+ GeneratorCall(name=gen['name'], args=gen.get('args'))
262
+ for gen in subgroup_data['generators']
263
+ ]
264
+
265
+ if 'testcases' in subgroup_data:
266
+ subgroup_dict['testcases'] = [
267
+ Testcase(
268
+ inputPath=pathlib.Path(tc['inputPath']),
269
+ outputPath=pathlib.Path(tc['outputPath'])
270
+ if tc.get('outputPath')
271
+ else None,
272
+ )
273
+ for tc in subgroup_data['testcases']
274
+ ]
275
+
276
+ if 'testcaseGlob' in subgroup_data:
277
+ subgroup_dict['testcaseGlob'] = subgroup_data['testcaseGlob']
278
+
279
+ if 'generatorScript' in subgroup_data:
280
+ subgroup_dict['generatorScript'] = CodeItem(
281
+ path=pathlib.Path(subgroup_data['generatorScript'])
282
+ )
283
+
284
+ if 'extraValidators' in subgroup_data:
285
+ subgroup_dict['extraValidators'] = [
286
+ CodeItem(path=pathlib.Path(v))
287
+ for v in subgroup_data['extraValidators']
288
+ ]
289
+
290
+ subgroup_objects.append(TestcaseSubgroup(**subgroup_dict))
291
+
292
+ self.yml.testcases = self.yml.testcases + [
293
+ TestcaseGroup(
294
+ name=name,
295
+ subgroups=subgroup_objects,
296
+ validator=CodeItem(path=pathlib.Path(validator)) if validator else None,
297
+ extraValidators=[
298
+ CodeItem(path=pathlib.Path(v)) for v in extra_validators
299
+ ]
300
+ if extra_validators
301
+ else [],
302
+ )
303
+ ]
304
+ self.save()
305
+
306
+ def add_testgroup_with_manual_testcases(
307
+ self,
308
+ name: str,
309
+ testcases: List[Dict[str, str]],
310
+ validator: Optional[PathOrStr] = None,
311
+ extra_validators: Optional[List[PathOrStr]] = None,
312
+ ):
313
+ """Add a testgroup with manually defined testcases.
314
+
315
+ Args:
316
+ name: Name of the testgroup
317
+ testcases: List of testcase definitions, each containing:
318
+ - inputPath: path to input file
319
+ - outputPath: optional path to output file
320
+ """
321
+
322
+ testcase_objects = []
323
+ for tc_data in testcases:
324
+ testcase_objects.append(
325
+ Testcase(
326
+ inputPath=pathlib.Path(tc_data['inputPath']),
327
+ outputPath=pathlib.Path(tc_data['outputPath'])
328
+ if tc_data.get('outputPath')
329
+ else None,
330
+ )
331
+ )
332
+
333
+ self.yml.testcases = self.yml.testcases + [
334
+ TestcaseGroup(
335
+ name=name,
336
+ testcases=testcase_objects,
337
+ validator=CodeItem(path=pathlib.Path(validator)) if validator else None,
338
+ extraValidators=[
339
+ CodeItem(path=pathlib.Path(v)) for v in extra_validators
340
+ ]
341
+ if extra_validators
342
+ else [],
343
+ )
344
+ ]
345
+ self.save()
346
+
347
+ def add_testgroup_with_generators(
348
+ self,
349
+ name: str,
350
+ generators: List[Dict[str, str]],
351
+ validator: Optional[PathOrStr] = None,
352
+ extra_validators: Optional[List[PathOrStr]] = None,
353
+ ):
354
+ """Add a testgroup with generator calls.
355
+
356
+ Args:
357
+ name: Name of the testgroup
358
+ generators: List of generator definitions, each containing:
359
+ - name: generator name
360
+ - args: optional generator arguments
361
+ """
362
+
363
+ generator_objects = []
364
+ for gen_data in generators:
365
+ generator_objects.append(
366
+ GeneratorCall(name=gen_data['name'], args=gen_data.get('args'))
367
+ )
368
+
369
+ self.yml.testcases = self.yml.testcases + [
370
+ TestcaseGroup(
371
+ name=name,
372
+ generators=generator_objects,
373
+ validator=CodeItem(path=pathlib.Path(validator)) if validator else None,
374
+ extraValidators=[
375
+ CodeItem(path=pathlib.Path(v)) for v in extra_validators
376
+ ]
377
+ if extra_validators
378
+ else [],
379
+ )
380
+ ]
381
+ self.save()
382
+
226
383
  def get_build_testgroup_path(self, name: str) -> pathlib.Path:
227
384
  return self.root / 'build' / 'tests' / name
228
385
 
@@ -244,3 +401,59 @@ class TestingPackage(TestingShared):
244
401
  if interactor_pipes_path.exists():
245
402
  contents.interactor_pipes = interactor_pipes_path.read_text()
246
403
  return contents
404
+
405
+ def add_validator_unit_test(
406
+ self,
407
+ glob: str,
408
+ outcome: ValidatorOutcome = ValidatorOutcome.VALID,
409
+ validator: Optional[PathOrStr] = None,
410
+ files: Optional[Dict[str, str]] = None,
411
+ ):
412
+ """Add a unit test for the validator.
413
+
414
+ Args:
415
+ glob: Glob pattern for input files
416
+ outcome: Expected validation outcome
417
+ validator: Optional validator to use (if not main validator)
418
+ files: Optional dict of {filename: content} to create test files
419
+ """
420
+ if files:
421
+ for filename, content in files.items():
422
+ self.add_file(filename).write_text(content)
423
+
424
+ validator_test = ValidatorTest(
425
+ glob=glob,
426
+ outcome=outcome,
427
+ validator=CodeItem(path=pathlib.Path(validator)) if validator else None,
428
+ )
429
+
430
+ # Explicitly set the unitTests field to mark it as dirty
431
+ unit_tests = self.yml.unitTests
432
+ unit_tests.validator = unit_tests.validator + [validator_test]
433
+ self.yml.unitTests = unit_tests
434
+ self.save()
435
+
436
+ def add_checker_unit_test(
437
+ self,
438
+ glob: str,
439
+ outcome: ExpectedOutcome = ExpectedOutcome.ACCEPTED,
440
+ files: Optional[Dict[str, str]] = None,
441
+ ):
442
+ """Add a unit test for the checker.
443
+
444
+ Args:
445
+ glob: Glob pattern for test files
446
+ outcome: Expected checker outcome
447
+ files: Optional dict of {filename: content} to create test files
448
+ """
449
+ if files:
450
+ for filename, content in files.items():
451
+ self.add_file(filename).write_text(content)
452
+
453
+ checker_test = CheckerTest(glob=glob, outcome=outcome)
454
+
455
+ # Explicitly set the unitTests field to mark it as dirty
456
+ unit_tests = self.yml.unitTests
457
+ unit_tests.checker = unit_tests.checker + [checker_test]
458
+ self.yml.unitTests = unit_tests
459
+ self.save()
@@ -55,7 +55,10 @@ class TestingShared:
55
55
  return filename
56
56
 
57
57
  def relpath(self, path: PathOrStr) -> pathlib.Path:
58
- return pathlib.Path(path).relative_to(self.root)
58
+ path = pathlib.Path(path)
59
+ if not path.is_relative_to(self.root):
60
+ return path
61
+ return path.relative_to(self.root)
59
62
 
60
63
  def add_from_testdata(self, path: PathOrStr, src: PathOrStr):
61
64
  testdata_path = get_testdata_path()
@@ -41,7 +41,7 @@ class CheckerTestEntry(BaseModel):
41
41
  return ', '.join(res)
42
42
 
43
43
 
44
- def _extract_validator_test_entries(
44
+ def extract_validator_test_entries(
45
45
  tests: List[ValidatorTest],
46
46
  ) -> List[ValidatorTestEntry]:
47
47
  res: List[ValidatorTestEntry] = []
@@ -57,7 +57,7 @@ def _extract_validator_test_entries(
57
57
  return sorted(res, key=lambda x: x.input.name)
58
58
 
59
59
 
60
- def _extract_checker_test_entries(tests: List[CheckerTest]) -> List[CheckerTestEntry]:
60
+ def extract_checker_test_entries(tests: List[CheckerTest]) -> List[CheckerTestEntry]:
61
61
  res: List[CheckerTestEntry] = []
62
62
  seen: Set[pathlib.Path] = set()
63
63
  for test in tests:
@@ -94,7 +94,7 @@ def _get_validator_for_test(test: ValidatorTestEntry) -> Optional[CodeItem]:
94
94
  async def run_validator_unit_tests(progress: StatusProgress):
95
95
  pkg = package.find_problem_package_or_die()
96
96
 
97
- entries = _extract_validator_test_entries(pkg.unitTests.validator)
97
+ entries = extract_validator_test_entries(pkg.unitTests.validator)
98
98
 
99
99
  vals: List[CodeItem] = []
100
100
  for test in entries:
@@ -158,7 +158,7 @@ async def run_checker_unit_tests(progress: StatusProgress):
158
158
 
159
159
  console.console.rule('Checker tests', style='info')
160
160
 
161
- entries = _extract_checker_test_entries(pkg.unitTests.checker)
161
+ entries = extract_checker_test_entries(pkg.unitTests.checker)
162
162
  if not entries:
163
163
  console.console.print('No checker unit tests found.')
164
164
  return
@@ -371,7 +371,7 @@ class DependencyCache:
371
371
  self.transient_db = SqliteDict(str(tmp_dir / '.cache_db'), autocommit=True)
372
372
  atexit.register(lambda: self.db.close())
373
373
  atexit.register(lambda: self.transient_db.close())
374
- atexit.register(lambda: shutil.rmtree(tmp_dir))
374
+ atexit.register(lambda: shutil.rmtree(tmp_dir, ignore_errors=True))
375
375
 
376
376
  def _cache_name(self) -> str:
377
377
  return str(self.root / '.cache_db')
@@ -73,7 +73,9 @@ class FileCacher:
73
73
  self.file_dir = pathlib.Path(tempfile.mkdtemp())
74
74
  # Delete this directory on exit since it has a random name and
75
75
  # won't be used again.
76
- atexit.register(lambda: shutil.rmtree(str(self.file_dir)))
76
+ atexit.register(
77
+ lambda: shutil.rmtree(str(self.file_dir), ignore_errors=True)
78
+ )
77
79
  else:
78
80
  assert folder is not None
79
81
  self.file_dir = folder / 'fs-cache-shared'
@@ -84,7 +86,7 @@ class FileCacher:
84
86
  self.temp_dir = pathlib.Path(
85
87
  tempfile.mkdtemp(dir=self.file_dir, prefix='_temp')
86
88
  )
87
- atexit.register(lambda: shutil.rmtree(str(self.temp_dir)))
89
+ atexit.register(lambda: shutil.rmtree(str(self.temp_dir), ignore_errors=True))
88
90
  # Just to make sure it was created.
89
91
 
90
92
  def is_shared(self) -> bool:
@@ -526,7 +528,7 @@ class FileCacher:
526
528
  """
527
529
  if self.is_shared():
528
530
  raise Exception('You may not destroy a shared cache.')
529
- shutil.rmtree(str(self.file_dir))
531
+ shutil.rmtree(str(self.file_dir), ignore_errors=True)
530
532
 
531
533
  def list(self) -> List[storage.FileWithMetadata]:
532
534
  """List the files available in the storage.
@@ -94,12 +94,40 @@ def get_preexec_fn(params: ProgramParams):
94
94
 
95
95
 
96
96
  def get_memory_usage(ru: resource.struct_rusage) -> int:
97
+ """Get memory usage in bytes from resource usage statistics.
98
+
99
+ Returns the total memory usage (RSS + shared memory segments) in bytes.
100
+
101
+ Platform differences in ru.ru_maxrss:
102
+ - macOS/Darwin: ru.ru_maxrss is in bytes
103
+ - Linux: ru.ru_maxrss is in kilobytes
104
+
105
+ This function normalizes the result to always return bytes.
106
+
107
+ Args:
108
+ ru: Resource usage statistics from os.wait4() or similar
109
+
110
+ Returns:
111
+ int: Total memory usage in bytes
112
+ """
97
113
  if sys.platform == 'darwin':
98
- return ru.ru_maxrss // 1024 + ru.ru_ixrss
99
- return ru.ru_maxrss + ru.ru_ixrss + ru.ru_idrss + ru.ru_isrss
114
+ # On macOS, ru.ru_maxrss is already in bytes
115
+ return ru.ru_maxrss + ru.ru_ixrss * 1024
116
+ # On Linux, ru.ru_maxrss is in kilobytes, so convert to bytes
117
+ return (ru.ru_maxrss + ru.ru_ixrss + ru.ru_idrss + ru.ru_isrss) * 1024
100
118
 
101
119
 
102
120
  def get_cpu_time(ru: resource.struct_rusage) -> float:
121
+ """Get CPU time in seconds from resource usage statistics.
122
+
123
+ Returns the total CPU time (user + system) in seconds.
124
+
125
+ Args:
126
+ ru: Resource usage statistics from os.wait4() or similar
127
+
128
+ Returns:
129
+ float: Total CPU time in seconds
130
+ """
103
131
  return ru.ru_utime + ru.ru_stime
104
132
 
105
133
 
@@ -239,6 +267,10 @@ class Program:
239
267
  ):
240
268
  program_codes.append(ProgramCode.WT)
241
269
  program_codes.append(ProgramCode.TO)
270
+ # Memory limit checking: Two ways a process can exceed memory limits:
271
+ # 1. Runtime monitoring (_handle_alarm) kills the process during execution
272
+ # 2. Post-execution check using ru.ru_maxrss detects peak memory usage exceeded limit
273
+ # Both memory_used (from ru.ru_maxrss) and memory_limit (converted to bytes) are in bytes
242
274
  if (
243
275
  self.params.memory_limit is not None
244
276
  and memory_used > self.params.memory_limit * 1024 * 1024
@@ -1,6 +1,5 @@
1
1
  import abc
2
2
  import asyncio
3
- import collections
4
3
  import dataclasses
5
4
  import io
6
5
  import json
@@ -146,12 +145,6 @@ class SandboxParams(pydantic.BaseModel):
146
145
  reverse_io: bool = False
147
146
  pgid: Optional[int] = None
148
147
 
149
- # For timeit
150
- timeit_dups: Dict[str, List[pathlib.Path]] = dataclasses.field(
151
- default_factory=lambda: collections.defaultdict(list)
152
- )
153
- timeit_prefix: Optional[str] = None
154
-
155
148
  def get_cacheable_params(self) -> Dict[str, Any]:
156
149
  return self.model_dump(mode='json', exclude_unset=True, exclude_none=True)
157
150
 
@@ -5,6 +5,7 @@ import logging
5
5
  import os
6
6
  import pathlib
7
7
  import shutil
8
+ import signal
8
9
  import subprocess
9
10
  import sys
10
11
  import tempfile
@@ -313,7 +314,12 @@ class StupidSandbox(SandboxBase):
313
314
  if should_tee:
314
315
  assert interactor_tee.pipes.output is not None
315
316
  interactor_tee.pipes.output.close()
316
- # TODO: kill in case of WA
317
+
318
+ if idx == 0 and program_result.exitcode != 0:
319
+ try:
320
+ os.killpg(group_id, signal.SIGKILL)
321
+ except Exception:
322
+ pass
317
323
  elif pid == program.pid:
318
324
  program_result = program.process_exit(status, ru)
319
325
  results[0] = self._get_sandbox_log(program_result, params)
@@ -277,8 +277,14 @@ class FilesystemStorage(Storage):
277
277
  return None
278
278
 
279
279
  # Create a temporary file in the same directory
280
+ # Use only the basename for the suffix to avoid issues with subdirectories
281
+ filename_basename = pathlib.Path(filename).name
280
282
  temp_file = tempfile.NamedTemporaryFile(
281
- 'wb', delete=False, prefix='.tmp.', suffix=filename, dir=self.path
283
+ 'wb',
284
+ delete=False,
285
+ prefix='.tmp.',
286
+ suffix=f'.{filename_basename}',
287
+ dir=self.path,
282
288
  )
283
289
  metadata: Dict[str, Optional[BaseModel]] = {'compression': None}
284
290
  if self.compress or grading_context.should_compress():
@@ -595,7 +595,7 @@ def _maybe_complain_about_sanitization(command: str) -> None:
595
595
  raise typer.Exit(1)
596
596
 
597
597
 
598
- def _check_for_sanitizer_warnings_in_line(line: str) -> bool:
598
+ def check_for_sanitizer_warnings_in_line(line: str) -> bool:
599
599
  line = line.lower()
600
600
  return 'runtime error:' in line or '==error' in line
601
601
 
@@ -608,7 +608,7 @@ def _check_for_sanitizer_warnings(
608
608
  if not sandbox.file_exists(stderr_file):
609
609
  return False
610
610
  with sandbox.get_file(stderr_file) as f:
611
- return any(_check_for_sanitizer_warnings_in_line(line.decode()) for line in f)
611
+ return any(check_for_sanitizer_warnings_in_line(line.decode()) for line in f)
612
612
 
613
613
 
614
614
  _WARNING_RE = re.compile(r'([^:]+):\d+:\d+:[ ]+warning:.*')
@@ -103,14 +103,62 @@ def uploaded_schema_path(model: Type[BaseModel]) -> str:
103
103
  return f'https://rsalesc.github.io/rbx/schemas/{model.__name__}.json'
104
104
 
105
105
 
106
- def model_to_yaml(model: BaseModel) -> str:
106
+ def model_to_yaml(model: BaseModel, **kwargs) -> str:
107
+ """Convert model to YAML string with proper boolean handling.
108
+
109
+ This function works around Pydantic's issue where Union[str, int, float, bool]
110
+ fields convert booleans to floats when using mode='json'.
111
+ """
112
+ # Use regular dump to preserve boolean types
113
+ data = model.model_dump(exclude_unset=True, exclude_none=True)
114
+
115
+ # Ensure the result is JSON-serializable by converting any non-JSON types
116
+ json_safe_data = _ensure_json_serializable(data)
117
+
118
+ # Add schema path comment and convert to YAML
107
119
  path = uploaded_schema_path(model.__class__)
108
- return f'# yaml-language-server: $schema={path}\n\n' + yaml.dump(
109
- model.model_dump(mode='json', exclude_unset=True, exclude_none=True),
110
- sort_keys=False,
111
- allow_unicode=True,
120
+ schema_comment = f'# yaml-language-server: $schema={path}\n\n'
121
+
122
+ yaml_content = yaml.safe_dump(
123
+ json_safe_data, sort_keys=False, allow_unicode=True, **kwargs
112
124
  )
113
125
 
126
+ return schema_comment + yaml_content
127
+
128
+
129
+ def _ensure_json_serializable(obj):
130
+ """Recursively ensure an object is JSON-serializable while preserving booleans."""
131
+ from datetime import date, datetime
132
+ from enum import Enum
133
+ from pathlib import Path
134
+ from uuid import UUID
135
+
136
+ from rbx.autoenum import AutoEnum
137
+
138
+ if isinstance(obj, dict):
139
+ return {k: _ensure_json_serializable(v) for k, v in obj.items()}
140
+ elif isinstance(obj, list):
141
+ return [_ensure_json_serializable(item) for item in obj]
142
+ elif isinstance(obj, tuple):
143
+ return [_ensure_json_serializable(item) for item in obj]
144
+ elif isinstance(obj, set):
145
+ return [_ensure_json_serializable(item) for item in obj]
146
+ elif isinstance(obj, AutoEnum):
147
+ return str(obj)
148
+ elif isinstance(obj, Enum):
149
+ return obj.value
150
+ elif isinstance(obj, (str, int, float, bool)) or obj is None:
151
+ return obj
152
+ elif isinstance(obj, (datetime, date)):
153
+ return obj.isoformat()
154
+ elif isinstance(obj, UUID):
155
+ return str(obj)
156
+ elif isinstance(obj, Path):
157
+ return str(obj)
158
+ else:
159
+ # For any other type, try to convert to string
160
+ return str(obj)
161
+
114
162
 
115
163
  def model_from_yaml(model: Type[T], s: str) -> T:
116
164
  return model(**yaml.safe_load(s))