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.
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/PKG-INFO +1 -1
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/pyproject.toml +1 -1
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/code.py +2 -2
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/header.py +15 -9
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/latex_jinja.py +1 -1
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testcase_extractors.py +2 -2
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testcase_utils.py +2 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testing/testing_package.py +215 -2
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testing/testing_shared.py +4 -1
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/unit.py +4 -4
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/caching.py +1 -1
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/cacher.py +5 -3
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/program.py +34 -2
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/sandbox.py +0 -7
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/sandboxes/stupid_sandbox.py +7 -1
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/storage.py +7 -1
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/steps.py +2 -2
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/utils.py +53 -5
- rbx_cp-0.13.5/rbx/grading/judge/sandboxes/timeit.py +0 -358
- rbx_cp-0.13.5/rbx/grading/judge/test.py +0 -38
- rbx_cp-0.13.5/rbx/grading/processing_context.py +0 -71
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/LICENSE +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/README.md +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/annotations.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/autoenum.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/builder.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/cd.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/checkers.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/cli.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/compile.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/build_contest_statements.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/contest_package.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/contest_utils.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/main.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/schema.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/contest/statements.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/creation.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/deferred.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/download.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/dump_schemas.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/environment.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/extensions.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/fields.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/formatting.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/generators.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/git_utils.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/global_package.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/lang.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/lazy_importing_main.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/linting.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/main.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/naming.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/package.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/boca/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/boca/extension.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/boca/packager.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/contest_main.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/importer.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/main.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/moj/packager.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/packager.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/pkg/packager.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/importer.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/packager.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/polygon_api.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/test.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/upload.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/packaging/polygon/xml_schema.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/presets/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/presets/fetch.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/presets/lock_schema.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/presets/schema.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/remote.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/retries.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/sanitizers/warning_stack.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/schema.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/setter_config.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/solutions.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/state.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/build_statements.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/builders.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/expander.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/joiners.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/latex.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/statements/schema.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stats.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stresses.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stressing/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stressing/finder_parser.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/stressing/generator_parser.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tasks.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testcases/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testcases/main.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testing/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/testing/testing_preset.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/boca/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/boca/main.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/boca/scrape.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/boca/scraper.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/converter.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/tooling/main.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/captured_log.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/css/app.tcss +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/main.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/build.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/command.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/differ.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/error.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/rich_log_modal.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/run.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/run_explorer.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/run_test_explorer.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/selector.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/screens/test_explorer.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/utils/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/utils/run_ui.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/diff_box.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/file_log.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/interaction_box.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/rich_log_box.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/test_output_box.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/ui/widgets/two_sided_test_output_box.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/box/validators.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/config.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/console.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/debug_context.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/grading_context.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/digester.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/sandboxes/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/judge/sandboxes/tee.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/limits.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/profiling.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/grading/steps_with_caching.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/providers/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/providers/codeforces.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/providers/provider.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/checkers/boilerplate.cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/checkers/noop.cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/default_config.json +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/default_setter_config.mac.yml +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/default_setter_config.yml +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/envs/default.rbx.yml +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/checker.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compare.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/c +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/cc +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/java +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/kt +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/py2 +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/compile/py3 +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/c +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/cc +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/java +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/kt +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/py2 +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactive/py3 +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactor_compile.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/interactor_run.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/bkp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/c +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/cc +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/java +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/kt +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/py2 +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/run/py3 +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/safeexec.c +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/boca/safeexec_compile.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/c/compile.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/c/prep.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/c/run.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/compare.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/cpp/compile.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/cpp/prep.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/cpp/run.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/interactor_prep.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/interactor_run.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/java/compile.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/java/prep.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/java/run.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py2/compile.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py2/prep.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py2/run.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py3/compile.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py3/prep.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/packagers/moj/scripts/py3/run.sh +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/.gitignore +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/statement/instructions.tex +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/contest/statement/logo.png +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/env.rbx.yml +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/preset.rbx.yml +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/.gitignore +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/gens/gen.cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/manual_tests/samples/000.in +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/manual_tests/samples/001.in +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/rbx.h +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/testplan/random.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/testplan/random.txt +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/validator.cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/shared/contest_template.rbx.tex +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/shared/icpc.sty +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/presets/default/shared/problem_template.rbx.tex +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/templates/rbx.h +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/resources/templates/template.cpp +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/submitors/__init__.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/submitors/codeforces.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/submitors/submitor.py +0 -0
- {rbx_cp-0.13.5 → rbx_cp-0.13.7}/rbx/testing_utils.py +0 -0
@@ -174,7 +174,7 @@ def _ignore_warning_in_cxx_input(input: GradingFileInput):
|
|
174
174
|
input.src = preprocessed_path
|
175
175
|
|
176
176
|
|
177
|
-
def
|
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 =
|
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
|
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
|
67
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
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
|
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
|
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):
|
@@ -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
|
-
|
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
|
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
|
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 =
|
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 =
|
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(
|
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
|
-
|
99
|
-
|
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
|
-
|
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',
|
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
|
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(
|
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
|
-
|
109
|
-
|
110
|
-
|
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))
|