├── tests ├── tasks │ ├── classic │ │ ├── gen │ │ │ ├── hard.txt │ │ │ ├── limiti.py │ │ │ ├── valida.py │ │ │ ├── generatore.cpp │ │ │ └── GEN │ │ ├── testo │ │ │ └── input.txt │ │ ├── sol │ │ │ ├── noop.py │ │ │ ├── nonzero.cpp │ │ │ ├── not_compile.cpp │ │ │ ├── .ignoreme.cpp │ │ │ ├── bash.sh │ │ │ ├── float_error.cpp │ │ │ ├── wa.cpp │ │ │ ├── pascal.pas │ │ │ ├── soluzione.py │ │ │ ├── wrong_file.cpp │ │ │ ├── mle.cpp │ │ │ ├── rust.rs │ │ │ ├── tle.cpp │ │ │ └── sigsegv.c │ │ └── task.yaml │ ├── missing_shebang │ │ ├── gen │ │ │ ├── GEN │ │ │ └── generatore.sh │ │ ├── sol │ │ │ └── soluzione.py │ │ └── task.yaml │ ├── with_bugged_gen │ │ ├── gen │ │ │ ├── GEN │ │ │ ├── valida.py │ │ │ └── generatore.py │ │ ├── sol │ │ │ └── soluzione.py │ │ └── task.yaml │ ├── with_bugged_sol │ │ ├── gen │ │ │ ├── GEN │ │ │ ├── valida.py │ │ │ └── generatore.py │ │ ├── sol │ │ │ ├── ok.py │ │ │ └── soluzione.py │ │ └── task.yaml │ ├── without_st │ │ ├── gen │ │ │ ├── hard.txt │ │ │ ├── limiti.py │ │ │ ├── GEN │ │ │ ├── generatore.py │ │ │ └── valida.py │ │ ├── testo │ │ │ └── input.txt │ │ ├── task.yaml │ │ └── sol │ │ │ ├── soluzione.py │ │ │ └── 40_points.py │ ├── communication │ │ ├── input │ │ │ ├── input1.txt │ │ │ ├── input2.txt │ │ │ └── input0.txt │ │ ├── task.yaml │ │ ├── sol │ │ │ ├── solution.c │ │ │ ├── solution.cpp │ │ │ ├── wrong.cpp │ │ │ ├── stub.c │ │ │ └── stub.cpp │ │ └── check │ │ │ └── manager.cpp │ ├── deps │ │ ├── sol │ │ │ ├── solution.cpp │ │ │ ├── sol-.cpp │ │ │ ├── sol-012.cpp │ │ │ ├── sol-1.cpp │ │ │ ├── sol-12.cpp │ │ │ ├── sol-2.cpp │ │ │ ├── sol-0.cpp │ │ │ ├── sol-01.cpp │ │ │ └── sol-02.cpp │ │ ├── gen │ │ │ ├── generator.sh │ │ │ ├── validator.py │ │ │ └── GEN │ │ └── task.yaml.orig │ ├── task-yaml-orig │ │ ├── gen │ │ │ ├── hard.txt │ │ │ ├── limiti.py │ │ │ ├── valida.py │ │ │ ├── generatore.cpp │ │ │ └── GEN │ │ ├── task.yaml.orig │ │ └── sol │ │ │ ├── solution.cpp │ │ │ └── wrong_file.cpp │ ├── with_bugged_val │ │ ├── gen │ │ │ ├── GEN │ │ │ ├── generatore.py │ │ │ └── valida.py │ │ ├── task.yaml │ │ └── sol │ │ │ └── soluzione.cpp │ ├── with_checker │ │ ├── gen │ │ │ ├── hard.txt │ │ │ ├── limiti.py │ │ │ ├── generatore.py │ │ │ ├── GEN │ │ │ └── valida.py │ │ ├── testo │ │ │ └── input.txt │ │ ├── sol │ │ │ ├── soluzione.sh │ │ │ └── wrong.sh │ │ ├── task.yaml │ │ └── cor │ │ │ └── correttore.cpp │ ├── with_invalid_shebang │ │ ├── gen │ │ │ ├── GEN │ │ │ └── generatore.sh │ │ ├── sol │ │ │ └── soluzione.py │ │ └── task.yaml │ ├── without_gen │ │ ├── input │ │ │ ├── input0.txt │ │ │ ├── input1.txt │ │ │ ├── input2.txt │ │ │ └── input3.txt │ │ ├── output │ │ │ ├── output0.txt │ │ │ ├── output1.txt │ │ │ ├── output2.txt │ │ │ ├── output3.txt │ │ │ └── output4.txt │ │ ├── task.yaml │ │ └── sol │ │ │ ├── soluzione.cpp │ │ │ ├── wa.cpp │ │ │ └── wrong_file.cpp │ ├── communication_stdio │ │ ├── input │ │ │ ├── input0.txt │ │ │ ├── input1.txt │ │ │ └── input2.txt │ │ ├── sol │ │ │ ├── no_output.cpp │ │ │ ├── solution.py │ │ │ ├── solution.c │ │ │ ├── solution.cpp │ │ │ ├── wrong.cpp │ │ │ └── tle.cpp │ │ ├── task.yaml │ │ └── check │ │ │ └── manager.cpp │ ├── with_bugged_checker │ │ ├── gen │ │ │ ├── hard.txt │ │ │ ├── limiti.py │ │ │ ├── generatore.py │ │ │ ├── GEN │ │ │ └── valida.py │ │ ├── testo │ │ │ └── input.txt │ │ ├── sol │ │ │ ├── soluzione.sh │ │ │ └── wrong.sh │ │ ├── task.yaml │ │ └── cor │ │ │ └── correttore.cpp │ ├── with_constraints_py │ │ ├── gen │ │ │ ├── hard.txt │ │ │ ├── constraints.py │ │ │ ├── generatore.cpp │ │ │ ├── GEN │ │ │ └── valida.py │ │ ├── testo │ │ │ ├── input.txt │ │ │ └── english.tex │ │ ├── sol │ │ │ ├── noop.py │ │ │ ├── nonzero.cpp │ │ │ ├── not_compile.cpp │ │ │ ├── .ignoreme.cpp │ │ │ ├── bash.sh │ │ │ ├── wa.cpp │ │ │ ├── float_error.cpp │ │ │ ├── pascal.pas │ │ │ ├── soluzione.py │ │ │ ├── wrong_file.cpp │ │ │ ├── mle.cpp │ │ │ ├── rust.rs │ │ │ ├── tle.cpp │ │ │ └── sigsegv.c │ │ └── task.yaml │ ├── invalid_copy │ │ ├── gen │ │ │ ├── GEN │ │ │ ├── generatore.py │ │ │ └── valida.py │ │ ├── sol │ │ │ └── soluzione.py │ │ └── task.yaml │ ├── with_stdio │ │ ├── gen │ │ │ ├── GEN │ │ │ ├── valida.py │ │ │ └── generatore.py │ │ ├── sol │ │ │ ├── noop.py │ │ │ ├── soluzione.cpp │ │ │ ├── wa.cpp │ │ │ └── wrong_file.cpp │ │ └── task.yaml │ ├── .gitignore │ └── communication_with_gen │ │ ├── gen │ │ ├── GEN │ │ ├── generatore.py │ │ └── valida.py │ │ ├── task.yaml │ │ ├── sol │ │ ├── solution.c │ │ ├── solution.cpp │ │ ├── wrong.cpp │ │ ├── stub.c │ │ └── stub.cpp │ │ └── check │ │ └── manager.cpp ├── invalid_copy.rs ├── with_bugged_checker.rs ├── missing_shebang.rs ├── with_invalid_shebang.rs ├── with_bugged_gen.rs ├── task-yaml-orig.rs ├── with_bugged_sol.rs ├── common │ └── mod.rs ├── with_bugged_val.rs ├── with_checker.rs ├── communication.rs ├── communication_with_gen.rs ├── without_st.rs ├── deps.rs ├── without_gen.rs ├── with_stdio.rs ├── communication_stdio.rs ├── sandbox.rs ├── fifo.rs ├── with_constraints_py.rs └── classic.rs ├── tools ├── docker │ ├── .gitignore │ ├── .dockerignore │ ├── healthcheck.sh │ ├── entrypoint.sh │ └── Dockerfile ├── vim │ ├── ftdetect │ │ └── cases_gen.vim │ └── syntax │ │ └── cases_gen.vim ├── aur │ ├── .gitignore │ ├── task-maker-rust │ │ ├── gen.sh │ │ └── PKGBUILD │ └── task-maker-rust-git │ │ └── PKGBUILD ├── build_readme.sh ├── clippy.sh ├── ubuntu │ └── apparmor-task-maker-rust ├── update_version.sh ├── release │ └── linux │ │ ├── build_release.sh │ │ └── Dockerfile └── publish_aur.sh ├── .gitignore ├── rust-toolchain.toml ├── data ├── bad_outputs │ ├── 1k_urandom.txt │ ├── invalid_case_number.txt │ ├── str_as_output.txt │ ├── very_long_str.txt │ ├── lorem_ipsum.txt │ └── very_long_int.txt └── statements │ ├── limiti.py │ ├── constraints.py │ └── locale │ ├── dutch.tex │ ├── english.tex │ ├── french.tex │ ├── portuguese.tex │ ├── german.tex │ ├── hungarian.tex │ ├── italian.tex │ └── spanish.tex ├── task-maker-exec ├── fonts │ ├── majalla.ttf │ ├── majallab.ttf │ ├── lmmono-italic.ttf │ ├── lmmono-regular.ttf │ ├── lmroman-bold.ttf │ ├── lmroman-italic.ttf │ ├── lmroman-regular.ttf │ └── lmroman-bolditalic.ttf ├── src │ ├── find_tools.rs │ ├── executors │ │ └── mod.rs │ └── detect_exe.rs └── Cargo.toml ├── codecov.yml ├── task-maker-format ├── src │ ├── ioi │ │ ├── format │ │ │ ├── mod.rs │ │ │ └── italian_yaml │ │ │ │ ├── GEN.pest │ │ │ │ └── cases.gen.pest │ │ ├── statement │ │ │ └── statement │ │ │ │ └── templates │ │ │ │ ├── task.tex │ │ │ │ └── booklet.tex │ │ ├── sanity_checks │ │ │ └── mod.rs │ │ └── dag │ │ │ └── task_type │ │ │ └── mod.rs │ ├── ui │ │ ├── silent.rs │ │ ├── json.rs │ │ └── raw.rs │ ├── terry │ │ ├── sanity_checks │ │ │ ├── mod.rs │ │ │ ├── task.rs │ │ │ └── statement.rs │ │ ├── task_info.rs │ │ └── statement │ │ │ └── mod.rs │ ├── tag.rs │ ├── testcase_score_status.rs │ ├── detect_format.rs │ └── task_format.rs ├── askama.toml ├── Cargo.toml └── tests │ └── ioi_task_clean.rs ├── src ├── tools │ ├── fuzz_checker │ │ ├── checker_header.h │ │ └── README │ ├── mod.rs │ ├── find_bad_case │ │ └── finish_ui.rs │ ├── clear.rs │ ├── task_info.rs │ ├── server.rs │ ├── gen_autocompletion.rs │ ├── main.rs │ ├── reset.rs │ ├── worker.rs │ └── opt.rs ├── lib.rs ├── error.rs ├── local.rs └── sandbox.rs ├── .config └── nextest.toml ├── task-maker-diagnostics └── Cargo.toml ├── task-maker-dag ├── Cargo.toml └── src │ └── lib.rs ├── .github ├── dependabot.yml └── workflows │ ├── docs.yml │ ├── rust.yml │ └── release.yml ├── task-maker-cache └── Cargo.toml ├── task-maker-store └── Cargo.toml └── task-maker-lang ├── Cargo.toml └── src └── languages ├── javascript.rs ├── shell.rs ├── mod.rs └── csharp.rs /tests/tasks/classic/gen/hard.txt: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /tests/tasks/classic/testo/input.txt: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /tests/tasks/missing_shebang/gen/GEN: -------------------------------------------------------------------------------- 1 | 42 2 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_gen/gen/GEN: -------------------------------------------------------------------------------- 1 | 42 2 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_sol/gen/GEN: -------------------------------------------------------------------------------- 1 | 42 2 | -------------------------------------------------------------------------------- /tests/tasks/without_st/gen/hard.txt: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /tests/tasks/communication/input/input1.txt: -------------------------------------------------------------------------------- 1 | 1 2 3 -------------------------------------------------------------------------------- /tests/tasks/communication/input/input2.txt: -------------------------------------------------------------------------------- 1 | 2 3 4 -------------------------------------------------------------------------------- /tests/tasks/deps/sol/solution.cpp: -------------------------------------------------------------------------------- 1 | sol-012.cpp -------------------------------------------------------------------------------- /tests/tasks/task-yaml-orig/gen/hard.txt: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_val/gen/GEN: -------------------------------------------------------------------------------- 1 | 2000 2 | -------------------------------------------------------------------------------- /tests/tasks/with_checker/gen/hard.txt: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /tests/tasks/with_checker/testo/input.txt: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /tests/tasks/with_invalid_shebang/gen/GEN: -------------------------------------------------------------------------------- 1 | 42 2 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/input/input0.txt: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/input/input1.txt: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/output/output0.txt: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /tests/tasks/without_st/testo/input.txt: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /tools/docker/.gitignore: -------------------------------------------------------------------------------- 1 | tmserver.* 2 | tmworker.* -------------------------------------------------------------------------------- /tests/tasks/communication/input/input0.txt: -------------------------------------------------------------------------------- 1 | 10 2 30 -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/input/input0.txt: -------------------------------------------------------------------------------- 1 | 10 2 -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/input/input1.txt: -------------------------------------------------------------------------------- 1 | 1 2 -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/input/input2.txt: -------------------------------------------------------------------------------- 1 | 2 3 -------------------------------------------------------------------------------- /tests/tasks/missing_shebang/sol/soluzione.py: -------------------------------------------------------------------------------- 1 | print(42) -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/gen/hard.txt: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/gen/hard.txt: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/input/input2.txt: -------------------------------------------------------------------------------- 1 | 1300 2 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/input/input3.txt: -------------------------------------------------------------------------------- 1 | 2000 2 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/output/output1.txt: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/output/output2.txt: -------------------------------------------------------------------------------- 1 | 1300 2 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/output/output3.txt: -------------------------------------------------------------------------------- 1 | 2000 2 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/output/output4.txt: -------------------------------------------------------------------------------- 1 | 2000 2 | -------------------------------------------------------------------------------- /tools/docker/.dockerignore: -------------------------------------------------------------------------------- 1 | tmserver.* 2 | tmworker.* -------------------------------------------------------------------------------- /tests/tasks/classic/sol/noop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /tests/tasks/invalid_copy/gen/GEN: -------------------------------------------------------------------------------- 1 | #COPY: where/am/i.txt 2 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/testo/input.txt: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/testo/input.txt: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/sol/no_output.cpp: -------------------------------------------------------------------------------- 1 | int main() {} -------------------------------------------------------------------------------- /tests/tasks/missing_shebang/gen/generatore.sh: -------------------------------------------------------------------------------- 1 | echo NOPE 2 | -------------------------------------------------------------------------------- /tests/tasks/with_stdio/gen/GEN: -------------------------------------------------------------------------------- 1 | 5 2 | 10 3 | 1300 4 | 2000 5 | -------------------------------------------------------------------------------- /tests/tasks/with_stdio/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /tests/tasks/with_stdio/sol/noop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/nonzero.cpp: -------------------------------------------------------------------------------- 1 | int main() { return 1; } 2 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/not_compile.cpp: -------------------------------------------------------------------------------- 1 | EhVolevi! // NOLINT 2 | -------------------------------------------------------------------------------- /tests/tasks/deps/gen/generator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "$@" 4 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_gen/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_sol/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_gen/sol/soluzione.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/noop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_sol/sol/ok.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | print(42) -------------------------------------------------------------------------------- /tests/tasks/with_checker/sol/soluzione.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cat 4 | -------------------------------------------------------------------------------- /tests/tasks/with_checker/sol/wrong.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 42 4 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/nonzero.cpp: -------------------------------------------------------------------------------- 1 | int main() { return 1; } 2 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/not_compile.cpp: -------------------------------------------------------------------------------- 1 | EhVolevi! // NOLINT 2 | -------------------------------------------------------------------------------- /tests/tasks/classic/gen/limiti.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | MAX_N = 5000 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/*.rs.bk 3 | *.sqlite3 4 | .idea 5 | *.svg 6 | perf.data* 7 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/.ignoreme.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | int main() { abort(); } 3 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/bash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cp input.txt output.txt 4 | -------------------------------------------------------------------------------- /tests/tasks/invalid_copy/gen/generatore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | print(42) 4 | -------------------------------------------------------------------------------- /tests/tasks/invalid_copy/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | print(42) 4 | -------------------------------------------------------------------------------- /tests/tasks/invalid_copy/sol/soluzione.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | print(42) 4 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/sol/soluzione.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cat 4 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/sol/wrong.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 42 4 | -------------------------------------------------------------------------------- /tests/tasks/with_checker/gen/limiti.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | MAX_N = 5000 4 | -------------------------------------------------------------------------------- /tests/tasks/without_st/gen/limiti.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | MAX_N = 5000 4 | -------------------------------------------------------------------------------- /tools/vim/ftdetect/cases_gen.vim: -------------------------------------------------------------------------------- 1 | autocmd BufRead,BufNewFile cases.gen setf cases_gen 2 | -------------------------------------------------------------------------------- /tests/tasks/task-yaml-orig/gen/limiti.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | MAX_N = 5000 4 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/gen/limiti.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | MAX_N = 5000 4 | -------------------------------------------------------------------------------- /tests/tasks/with_invalid_shebang/gen/generatore.sh: -------------------------------------------------------------------------------- 1 | #!/this/does/not/exists 2 | 3 | echo NOPE 4 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/.ignoreme.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | int main() { abort(); } 3 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/bash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cp input.txt output.txt 4 | -------------------------------------------------------------------------------- /tests/tasks/without_st/gen/GEN: -------------------------------------------------------------------------------- 1 | #COPY: testo/input.txt 2 | #COPY: gen/hard.txt 3 | 500 4 | 900 5 | 1300 -------------------------------------------------------------------------------- /tests/tasks/.gitignore: -------------------------------------------------------------------------------- 1 | input/ 2 | output/ 3 | correttore 4 | checker 5 | manager 6 | *.pdf 7 | task.yaml 8 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_gen/gen/generatore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | raise RuntimeError(":(") 4 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_sol/sol/soluzione.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | raise RuntimeError("No buono") 4 | -------------------------------------------------------------------------------- /tests/tasks/with_checker/gen/generatore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | print(sys.argv[1]) 5 | -------------------------------------------------------------------------------- /tests/tasks/with_stdio/gen/generatore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | print(sys.argv[1]) 5 | -------------------------------------------------------------------------------- /tests/tasks/without_st/gen/generatore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | print(sys.argv[1]) 5 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.88" 3 | components = [ "rustfmt", "clippy", "rust-analyzer" ] 4 | -------------------------------------------------------------------------------- /tests/tasks/deps/gen/validator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | 4 | print(sys.argv) 5 | sys.exit(0) 6 | -------------------------------------------------------------------------------- /tests/tasks/task-yaml-orig/task.yaml.orig: -------------------------------------------------------------------------------- 1 | title: Testing task.yaml.orig 2 | time_limit: 1 3 | memory_limit: 64 4 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_sol/gen/generatore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | print(sys.argv[1]) 5 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_val/gen/generatore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | print(sys.argv[1]) 5 | -------------------------------------------------------------------------------- /tests/tasks/with_checker/gen/GEN: -------------------------------------------------------------------------------- 1 | #COPY: testo/input.txt 2 | #COPY: gen/hard.txt 3 | 500 4 | 900 5 | 1300 6 | 2000 7 | -------------------------------------------------------------------------------- /tests/tasks/with_invalid_shebang/sol/soluzione.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | raise RuntimeError("No buono") 4 | -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/gen/GEN: -------------------------------------------------------------------------------- 1 | #ST: 50 2 | 0 10 20 3 | 1 20 30 4 | 5 | #ST: 50 6 | 0 100 100 7 | 1 150 120 8 | -------------------------------------------------------------------------------- /tests/tasks/deps/task.yaml.orig: -------------------------------------------------------------------------------- 1 | title: Testing task-maker subtask dependencies 2 | time_limit: 1 3 | memory_limit: 64 4 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/gen/generatore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | print(sys.argv[1]) 5 | -------------------------------------------------------------------------------- /tests/tasks/without_st/task.yaml: -------------------------------------------------------------------------------- 1 | name: without_st 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | -------------------------------------------------------------------------------- /tests/tasks/invalid_copy/task.yaml: -------------------------------------------------------------------------------- 1 | name: invalid_copy 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/gen/GEN: -------------------------------------------------------------------------------- 1 | #COPY: testo/input.txt 2 | #COPY: gen/hard.txt 3 | 500 4 | 900 5 | 1300 6 | 2000 7 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_gen/task.yaml: -------------------------------------------------------------------------------- 1 | name: bugged_gen 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_sol/task.yaml: -------------------------------------------------------------------------------- 1 | name: bugged_sol 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | -------------------------------------------------------------------------------- /tests/tasks/missing_shebang/task.yaml: -------------------------------------------------------------------------------- 1 | name: invalid_shebang 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | -------------------------------------------------------------------------------- /tools/aur/.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | src/ 3 | *.tar.zst 4 | task-maker-rust-git/task-maker-rust/ 5 | task-maker-rust/task-maker-rust/ 6 | -------------------------------------------------------------------------------- /data/bad_outputs/1k_urandom.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olimpiadi-informatica/task-maker-rust/HEAD/data/bad_outputs/1k_urandom.txt -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/sol/solution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | a, b = map(int, input().strip().split()) 4 | print(a + b) -------------------------------------------------------------------------------- /tests/tasks/with_invalid_shebang/task.yaml: -------------------------------------------------------------------------------- 1 | name: invalid_shebang 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | -------------------------------------------------------------------------------- /task-maker-exec/fonts/majalla.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olimpiadi-informatica/task-maker-rust/HEAD/task-maker-exec/fonts/majalla.ttf -------------------------------------------------------------------------------- /task-maker-exec/fonts/majallab.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olimpiadi-informatica/task-maker-rust/HEAD/task-maker-exec/fonts/majallab.ttf -------------------------------------------------------------------------------- /task-maker-exec/fonts/lmmono-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olimpiadi-informatica/task-maker-rust/HEAD/task-maker-exec/fonts/lmmono-italic.ttf -------------------------------------------------------------------------------- /task-maker-exec/fonts/lmmono-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olimpiadi-informatica/task-maker-rust/HEAD/task-maker-exec/fonts/lmmono-regular.ttf -------------------------------------------------------------------------------- /task-maker-exec/fonts/lmroman-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olimpiadi-informatica/task-maker-rust/HEAD/task-maker-exec/fonts/lmroman-bold.ttf -------------------------------------------------------------------------------- /task-maker-exec/fonts/lmroman-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olimpiadi-informatica/task-maker-rust/HEAD/task-maker-exec/fonts/lmroman-italic.ttf -------------------------------------------------------------------------------- /tests/tasks/classic/sol/float_error.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | volatile int x = 0; 4 | 5 | int main() { std::cout << (42 % x) << std::endl; } 6 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/wa.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { std::ofstream("output.txt") << 42 << std::endl; } 5 | -------------------------------------------------------------------------------- /tests/tasks/with_stdio/task.yaml: -------------------------------------------------------------------------------- 1 | name: with_stdio 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | infile: "" 6 | outfile: "" 7 | -------------------------------------------------------------------------------- /tools/build_readme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROOT=$(git rev-parse --show-toplevel) 4 | cd "$ROOT" 5 | cargo readme -i src/main.rs > README.md 6 | -------------------------------------------------------------------------------- /task-maker-exec/fonts/lmroman-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olimpiadi-informatica/task-maker-rust/HEAD/task-maker-exec/fonts/lmroman-regular.ttf -------------------------------------------------------------------------------- /tests/tasks/task-yaml-orig/sol/solution.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int n; 5 | std::cin >> n; 6 | std::cout << n; 7 | } 8 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_val/task.yaml: -------------------------------------------------------------------------------- 1 | name: bugged_val 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | infile: "" 6 | outfile: "" 7 | -------------------------------------------------------------------------------- /tests/tasks/with_checker/task.yaml: -------------------------------------------------------------------------------- 1 | name: with_checker 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | infile: "" 6 | outfile: "" 7 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/task.yaml: -------------------------------------------------------------------------------- 1 | name: without_gen 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | infile: "" 6 | outfile: "" 7 | -------------------------------------------------------------------------------- /data/bad_outputs/invalid_case_number.txt: -------------------------------------------------------------------------------- 1 | Case #ciaone: 42 2 | Case ##: 666 3 | Case#1: 42 4 | Case #4.2: 42 5 | Case #: 42 6 | Case #0: 42 7 | Case #-1: 20 8 | -------------------------------------------------------------------------------- /task-maker-exec/fonts/lmroman-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olimpiadi-informatica/task-maker-rust/HEAD/task-maker-exec/fonts/lmroman-bolditalic.ttf -------------------------------------------------------------------------------- /tests/tasks/classic/task.yaml: -------------------------------------------------------------------------------- 1 | name: classic 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | infile: input.txt 6 | outfile: output.txt 7 | -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/gen/generatore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | a, b, c = map(int, sys.argv[1:]) 6 | print(a, b, c) 7 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/wa.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { std::ofstream("output.txt") << 42 << std::endl; } 5 | -------------------------------------------------------------------------------- /tests/tasks/with_stdio/sol/soluzione.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int N; 5 | std::cin >> N; 6 | std::cout << N << std::endl; 7 | } 8 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/sol/soluzione.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int N; 5 | std::cin >> N; 6 | std::cout << N << std::endl; 7 | } 8 | -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/sol/solution.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int a, b; 5 | scanf("%d %d", &a, &b); 6 | printf("%d\n", a + b); 7 | } -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/task.yaml: -------------------------------------------------------------------------------- 1 | name: bugged_checker 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | infile: "" 6 | outfile: "" 7 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_val/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pylint: disable=wildcard-import 4 | # pylint: disable=invalid-name 5 | 6 | assert False 7 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_val/sol/soluzione.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int N; 5 | std::cin >> N; 6 | std::cout << N << std::endl; 7 | } 8 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/float_error.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | volatile int x = 0; 4 | 5 | int main() { std::cout << (42 % x) << std::endl; } 6 | -------------------------------------------------------------------------------- /tests/tasks/communication/task.yaml: -------------------------------------------------------------------------------- 1 | name: communication 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | infile: input.txt 6 | num_processes: 2 7 | -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/sol/solution.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int a, b; 5 | std::cin >> a >> b; 6 | std::cout << a + b << std::endl; 7 | } -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/sol/wrong.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int a, b; 5 | std::cin >> a >> b; 6 | std::cout << a + b + 1 << std::endl; 7 | } -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/task.yaml: -------------------------------------------------------------------------------- 1 | name: communication_stdio 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | infile: input.txt 6 | user_io: std_io -------------------------------------------------------------------------------- /data/bad_outputs/str_as_output.txt: -------------------------------------------------------------------------------- 1 | Case #1: fuffarbacco 2 | Case #2: hola hola! 3 | Case #3: !! 4 | Case #4: 43.1.2 5 | Case #5: \0 6 | Case #6: %d 7 | Case #7: $0 8 | Case #8: @@@ 9 | -------------------------------------------------------------------------------- /tests/tasks/deps/gen/GEN: -------------------------------------------------------------------------------- 1 | #ST: 30 2 | #STNAME: st-0 3 | #STDEP: st-1 4 | 0 5 | 6 | #ST: 30 7 | #STNAME: st-1 8 | 1 9 | 10 | #ST: 40 11 | #STNAME: st-2 12 | #STDEP: * 13 | 2 14 | -------------------------------------------------------------------------------- /tests/tasks/with_stdio/sol/wa.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | unsigned int N; 5 | std::cin >> N; 6 | std::cout << N*99999999u/100000000u+1u << std::endl; 7 | } 8 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/sol/wa.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | unsigned int N; 5 | std::cin >> N; 6 | std::cout << N*99999999u/100000000u+1u << std::endl; 7 | } 8 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 50% 6 | threshold: 1% 7 | patch: no 8 | changes: no 9 | 10 | comment: false -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/task.yaml: -------------------------------------------------------------------------------- 1 | name: communication_with_gen 2 | title: Testing task-maker 3 | time_limit: 1 4 | memory_limit: 64 5 | infile: input.txt 6 | num_processes: 2 7 | -------------------------------------------------------------------------------- /task-maker-format/src/ioi/format/mod.rs: -------------------------------------------------------------------------------- 1 | //! Known parsing formats for the IOI tasks. 2 | //! 3 | //! A _format_ here is intended as the layout on disk of task files. 4 | 5 | pub mod italian_yaml; 6 | -------------------------------------------------------------------------------- /tests/tasks/deps/sol/sol-.cpp: -------------------------------------------------------------------------------- 1 | // @check-wrong-answer: st-0 st-1 st-2 2 | #include 3 | 4 | int main() { 5 | int x; 6 | std::cin >> x; 7 | std::cout << "000"[x] << '\n'; 8 | } 9 | -------------------------------------------------------------------------------- /tests/tasks/deps/sol/sol-012.cpp: -------------------------------------------------------------------------------- 1 | // @check-accepted: st-0 st-1 st-2 2 | #include 3 | 4 | int main() { 5 | int x; 6 | std::cin >> x; 7 | std::cout << "111"[x] << '\n'; 8 | } 9 | -------------------------------------------------------------------------------- /tests/tasks/deps/sol/sol-1.cpp: -------------------------------------------------------------------------------- 1 | // @check-wrong-answer: st-0 st-1 st-2 2 | #include 3 | 4 | int main() { 5 | int x; 6 | std::cin >> x; 7 | std::cout << "010"[x] << '\n'; 8 | } 9 | -------------------------------------------------------------------------------- /tests/tasks/deps/sol/sol-12.cpp: -------------------------------------------------------------------------------- 1 | // @check-wrong-answer: st-0 st-1 st-2 2 | #include 3 | 4 | int main() { 5 | int x; 6 | std::cin >> x; 7 | std::cout << "011"[x] << '\n'; 8 | } 9 | -------------------------------------------------------------------------------- /tests/tasks/deps/sol/sol-2.cpp: -------------------------------------------------------------------------------- 1 | // @check-wrong-answer: st-0 st-1 st-2 2 | #include 3 | 4 | int main() { 5 | int x; 6 | std::cin >> x; 7 | std::cout << "001"[x] << '\n'; 8 | } 9 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/pascal.pas: -------------------------------------------------------------------------------- 1 | var i : longint; 2 | begin 3 | assign(input, 'input.txt'); reset(input); 4 | assign(output, 'output.txt'); rewrite(output); 5 | readln(i); 6 | writeln(i); 7 | end. -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | with open(sys.argv[1], "r") as f: 6 | a, b, c = map(int, f.read().strip().split()) 7 | assert a in {0, 1} 8 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/soluzione.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import print_function 4 | 5 | with open("input.txt") as f: 6 | print(int(f.read().splitlines()[0]), file=open("output.txt", "w")) 7 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/pascal.pas: -------------------------------------------------------------------------------- 1 | var i : longint; 2 | begin 3 | assign(input, 'input.txt'); reset(input); 4 | assign(output, 'output.txt'); rewrite(output); 5 | readln(i); 6 | writeln(i); 7 | end. -------------------------------------------------------------------------------- /tests/tasks/without_st/sol/soluzione.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import print_function 4 | 5 | with open("input.txt") as f: 6 | print(int(f.read().splitlines()[0]), file=open("output.txt", "w")) 7 | -------------------------------------------------------------------------------- /task-maker-format/src/ioi/statement/statement/templates/task.tex: -------------------------------------------------------------------------------- 1 | \begin{problem}{%{title}%}{%{name}%}{%{infile}%}{%{outfile}%}{%{time_limit}%}{%{memory_limit}%}{%{difficulty}%}{%{syllabus_level}%} 2 | %{content}% 3 | \end{problem} -------------------------------------------------------------------------------- /tests/tasks/classic/sol/wrong_file.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int N; 5 | std::ifstream in("imput.txt"); 6 | std::ofstream out("oufput.txt"); 7 | in >> N; 8 | out << N << std::endl; 9 | } 10 | -------------------------------------------------------------------------------- /tests/tasks/deps/sol/sol-0.cpp: -------------------------------------------------------------------------------- 1 | // @check-accepted: st-0 2 | // @check-wrong-answer: st-1 st-2 3 | #include 4 | 5 | int main() { 6 | int x; 7 | std::cin >> x; 8 | std::cout << "100"[x] << '\n'; 9 | } 10 | -------------------------------------------------------------------------------- /tests/tasks/deps/sol/sol-01.cpp: -------------------------------------------------------------------------------- 1 | // @check-accepted: st-0 st-1 2 | // @check-wrong-answer: st-2 3 | #include 4 | 5 | int main() { 6 | int x; 7 | std::cin >> x; 8 | std::cout << "110"[x] << '\n'; 9 | } 10 | -------------------------------------------------------------------------------- /tests/tasks/deps/sol/sol-02.cpp: -------------------------------------------------------------------------------- 1 | // @check-accepted: st-0 2 | // @check-wrong-answer: st-1 st-2 3 | #include 4 | 5 | int main() { 6 | int x; 7 | std::cin >> x; 8 | std::cout << "101"[x] << '\n'; 9 | } 10 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/gen/constraints.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | MAXN = 5000 4 | 5 | subtasks = [ 6 | dict(MAXN=MAXN), 7 | dict(MAXN=MAXN), 8 | dict(MAXN=10), 9 | dict(MAXN=MAXN), 10 | ] 11 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/soluzione.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import print_function 4 | 5 | with open("input.txt") as f: 6 | print(int(f.read().splitlines()[0]), file=open("output.txt", "w")) 7 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/wrong_file.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int N; 5 | std::ifstream in("imput.txt"); 6 | std::ofstream out("oufput.txt"); 7 | in >> N; 8 | out << N << std::endl; 9 | } 10 | -------------------------------------------------------------------------------- /tests/tasks/task-yaml-orig/sol/wrong_file.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | std::ifstream in("input.txt"); 5 | std::ofstream out("output.txt"); 6 | 7 | int n; 8 | in >> n; 9 | out << n << '\n'; 10 | } 11 | -------------------------------------------------------------------------------- /tests/tasks/communication/sol/solution.c: -------------------------------------------------------------------------------- 1 | #include 2 | int op(int code, int a, int b) { 3 | if (code == 0) 4 | return a + b; 5 | else if (code == 1) 6 | return a * b; 7 | else 8 | assert(0); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tests/tasks/communication/sol/solution.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | int op(int code, int a, int b) { 3 | if (code == 0) 4 | return a + b; 5 | else if (code == 1) 6 | return a * b; 7 | else 8 | assert(false); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tests/tasks/communication/sol/wrong.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | int op(int code, int a, int b) { 3 | if (code == 0) 4 | return a * b; 5 | else if (code == 1) 6 | return a + b; 7 | else 8 | assert(false); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tests/tasks/with_stdio/sol/wrong_file.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | int N; 6 | std::ifstream in("input.txt"); 7 | std::ofstream out("output.txt"); 8 | 9 | in >> N; 10 | out << N << std::endl; 11 | } 12 | -------------------------------------------------------------------------------- /tests/tasks/without_gen/sol/wrong_file.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | int N; 6 | std::ifstream in("input.txt"); 7 | std::ofstream out("output.txt"); 8 | 9 | in >> N; 10 | out << N << std::endl; 11 | } 12 | -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/sol/solution.c: -------------------------------------------------------------------------------- 1 | #include 2 | int op(int code, int a, int b) { 3 | if (code == 0) 4 | return a + b; 5 | else if (code == 1) 6 | return a * b; 7 | else 8 | assert(0); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/sol/solution.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | int op(int code, int a, int b) { 3 | if (code == 0) 4 | return a + b; 5 | else if (code == 1) 6 | return a * b; 7 | else 8 | assert(false); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/sol/wrong.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | int op(int code, int a, int b) { 3 | if (code == 0) 4 | return a * b; 5 | else if (code == 1) 6 | return a + b; 7 | else 8 | assert(false); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/mle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | std::vector vec; 6 | while (vec.size() < 400000000) 7 | vec.push_back(vec.size() / (1 + vec.size() / 42)); 8 | std::cout << vec.back() << std::endl; 9 | } 10 | -------------------------------------------------------------------------------- /tools/clippy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd "$(git rev-parse --show-toplevel)" 4 | 5 | cargo clippy --all-targets --all-features --tests --all -- \ 6 | -D warnings \ 7 | `# https://rust-lang.github.io/rust-clippy/master/#string_slice` \ 8 | -D clippy::string_slice 9 | -------------------------------------------------------------------------------- /tests/tasks/without_st/sol/40_points.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import print_function 4 | 5 | with open("input.txt") as f: 6 | x = int(f.read().splitlines()[0]) 7 | if x >= 100: 8 | x = -1234 9 | print(x, file=open("output.txt", "w")) 10 | -------------------------------------------------------------------------------- /tests/tasks/with_checker/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pylint: disable=wildcard-import 4 | # pylint: disable=invalid-name 5 | 6 | import sys 7 | from limiti import * 8 | 9 | infile = open(sys.argv[1]).read().splitlines() 10 | assert 0 <= int(infile[0]) <= MAX_N 11 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/mle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | std::vector vec; 6 | while (vec.size() < 400000000) 7 | vec.push_back(vec.size() / (1 + vec.size() / 42)); 8 | std::cout << vec.back() << std::endl; 9 | } 10 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pylint: disable=wildcard-import 4 | # pylint: disable=invalid-name 5 | 6 | import sys 7 | from limiti import * 8 | 9 | infile = open(sys.argv[1]).read().splitlines() 10 | assert 0 <= int(infile[0]) <= MAX_N 11 | -------------------------------------------------------------------------------- /task-maker-format/askama.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | dirs = ["src/ioi/statement/statement/templates"] 3 | default_syntax = "tex" 4 | 5 | [[syntax]] 6 | name = "tex" 7 | block_start = "%X" 8 | comment_start = "%Y" 9 | expr_start = "%{" 10 | block_end = "X%" 11 | comment_end = "Y%" 12 | expr_end = "}%" 13 | -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/sol/tle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | volatile unsigned long long X; 4 | 5 | int main() { 6 | int a, b; 7 | std::cin >> a >> b; 8 | std::cout << a + b << std::endl; 9 | for (unsigned long long i = 0;; i++) { 10 | X = i + X / 2; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tools/aur/task-maker-rust/gen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version="$(grep '^version' ../../../Cargo.toml | cut -d'"' -f 2)" 4 | hash=$(curl -L "https://github.com/olimpiadi-informatica/task-maker-rust/archive/v${version}.tar.gz" | sha256sum | cut -d' ' -f 1) 5 | sed "s/@@VERSION@@/$version/g" < PKGBUILD | sed "s/@@SHA256@@/$hash/" 6 | -------------------------------------------------------------------------------- /src/tools/fuzz_checker/checker_header.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Override the common exit functions, EXIT will throw an exception caught by 4 | // the fuzzer 5 | #define _exit EXIT 6 | #define exit EXIT 7 | 8 | struct Exit { 9 | int status; 10 | }; 11 | 12 | [[noreturn]] inline void EXIT(int status) { throw Exit{status}; } 13 | -------------------------------------------------------------------------------- /tools/ubuntu/apparmor-task-maker-rust: -------------------------------------------------------------------------------- 1 | # Allow user namespaces for task-maker-rust 2 | 3 | abi , 4 | 5 | profile task-maker-rust /usr/bin/task-maker-tools flags=(default_allow) { 6 | userns, 7 | 8 | # Site-specific additions and overrides. See local/README for details. 9 | include if exists 10 | } 11 | -------------------------------------------------------------------------------- /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | slow-timeout = { period = "10s", terminate-after = 6 } 3 | 4 | [[profile.default.overrides]] 5 | platform = 'cfg(target_os = "macos")' 6 | retries = 3 7 | 8 | [[profile.default.overrides]] 9 | filter = 'test(classic) or test(communication)' 10 | slow-timeout = { period = "20s", terminate-after = 20 } 11 | -------------------------------------------------------------------------------- /tests/tasks/classic/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pylint: disable=wildcard-import 4 | # pylint: disable=invalid-name 5 | 6 | import sys 7 | from limiti import * 8 | 9 | assert len(sys.argv) == 3 10 | infile = open(sys.argv[1]).read().splitlines() 11 | assert 0 <= int(infile[0]) <= MAX_N 12 | assert 1 <= int(sys.argv[2]) <= 3 13 | -------------------------------------------------------------------------------- /tests/tasks/without_st/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pylint: disable=wildcard-import 4 | # pylint: disable=invalid-name 5 | 6 | import sys 7 | from limiti import * 8 | 9 | assert len(sys.argv) == 3 10 | infile = open(sys.argv[1]).read().splitlines() 11 | assert 0 <= int(infile[0]) <= MAX_N 12 | assert 1 <= int(sys.argv[2]) <= 3 13 | -------------------------------------------------------------------------------- /tests/tasks/task-yaml-orig/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pylint: disable=wildcard-import 4 | # pylint: disable=invalid-name 5 | 6 | import sys 7 | from limiti import * 8 | 9 | assert len(sys.argv) == 3 10 | infile = open(sys.argv[1]).read().splitlines() 11 | assert 0 <= int(infile[0]) <= MAX_N 12 | assert 1 <= int(sys.argv[2]) <= 3 13 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/task.yaml: -------------------------------------------------------------------------------- 1 | # General info 2 | name: with-constraints-py 3 | title: Testing constraints.py 4 | 5 | # Technical info 6 | memory_limit: 64 7 | time_limit: 1 8 | infile: input.txt 9 | outfile: output.txt 10 | 11 | # Other stuff 12 | score_mode: max_subtask 13 | token_mode: disabled 14 | public_testcases: all 15 | feedback_level: full 16 | -------------------------------------------------------------------------------- /tests/tasks/classic/gen/generatore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char** argv) { 4 | if (argc != 2) { 5 | std::cerr << "Usage: " << argv[0] << " N" << std::endl; // NOLINT 6 | return 1; 7 | } 8 | std::cout << argv[1] << std::endl; // NOLINT 9 | std::cerr << "This string should not appear in the input.txt" << std::endl; 10 | } 11 | -------------------------------------------------------------------------------- /tests/tasks/task-yaml-orig/gen/generatore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char** argv) { 4 | if (argc != 2) { 5 | std::cerr << "Usage: " << argv[0] << " N" << std::endl; // NOLINT 6 | return 1; 7 | } 8 | std::cout << argv[1] << std::endl; // NOLINT 9 | std::cerr << "This string should not appear in the input.txt" << std::endl; 10 | } 11 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/rust.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{Read, Write}; 3 | 4 | fn main() { 5 | let mut input = File::open("input.txt").unwrap(); 6 | let mut output = File::create("output.txt").unwrap(); 7 | let mut buffer = String::new(); 8 | input.read_to_string(&mut buffer).unwrap(); 9 | output.write_all(buffer.as_bytes()).unwrap(); 10 | } 11 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/gen/generatore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char** argv) { 4 | if (argc != 2) { 5 | std::cerr << "Usage: " << argv[0] << " N" << std::endl; // NOLINT 6 | return 1; 7 | } 8 | std::cout << argv[1] << std::endl; // NOLINT 9 | std::cerr << "This string should not appear in the input.txt" << std::endl; 10 | } 11 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/rust.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{Read, Write}; 3 | 4 | fn main() { 5 | let mut input = File::open("input.txt").unwrap(); 6 | let mut output = File::create("output.txt").unwrap(); 7 | let mut buffer = String::new(); 8 | input.read_to_string(&mut buffer).unwrap(); 9 | output.write_all(buffer.as_bytes()).unwrap(); 10 | } 11 | -------------------------------------------------------------------------------- /task-maker-diagnostics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "task-maker-diagnostics" 3 | version = "0.6.20" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | # Serialization/Deserialization 8 | serde = { workspace = true, features = ["derive"] } 9 | # Utilities for writing to the terminal with colors 10 | colored = { workspace = true } 11 | # Generic error utilities 12 | anyhow = { workspace = true } 13 | -------------------------------------------------------------------------------- /tests/tasks/task-yaml-orig/gen/GEN: -------------------------------------------------------------------------------- 1 | # This is a comment, this line should be ignored 2 | 3 | # The task is to echo the first parameter 4 | # If you exit fast enough and don't use much memory you will score 100 :P 5 | 6 | # Copy some file from some random folder 7 | #ST: 5 8 | #COPY: gen/hard.txt 9 | 10 | # Small N 11 | #ST: 45 12 | 500 13 | 900 14 | 15 | # Big N 16 | #ST: 50 17 | 1300 18 | 2000 19 | -------------------------------------------------------------------------------- /tests/tasks/with_bugged_checker/cor/correttore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char** argv) { 6 | if(argc != 4) { 7 | std::cerr << "Usage: correttore " 8 | << std::endl; 9 | return 1; 10 | } 11 | std::cout << "not a number" << std::endl; 12 | std::cerr << "oh no!" << std::endl; 13 | } 14 | -------------------------------------------------------------------------------- /task-maker-format/src/ui/silent.rs: -------------------------------------------------------------------------------- 1 | use crate::ui::*; 2 | 3 | /// This UI will never print anything. 4 | #[derive(Default)] 5 | pub struct SilentUI; 6 | 7 | impl SilentUI { 8 | /// Make a new SilentUI. 9 | pub fn new() -> SilentUI { 10 | SilentUI {} 11 | } 12 | } 13 | 14 | impl UI for SilentUI { 15 | fn on_message(&mut self, _message: UIMessage) {} 16 | 17 | fn finish(&mut self) {} 18 | } 19 | -------------------------------------------------------------------------------- /tests/tasks/classic/gen/GEN: -------------------------------------------------------------------------------- 1 | # This is a comment, this line should be ignored 2 | 3 | # The task is to echo the first parameter 4 | # If you exit fast enough and don't use much memory you will score 100 :P 5 | 6 | # Copy some file from some random folder 7 | #ST: 5 8 | #COPY: testo/input.txt 9 | #COPY: gen/hard.txt 10 | 11 | # Small N 12 | #ST: 45 13 | 500 14 | 900 15 | 16 | # Big N 17 | #ST: 50 18 | 1300 19 | 2000 20 | -------------------------------------------------------------------------------- /src/tools/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_solution_checks; 2 | pub mod booklet; 3 | pub mod clear; 4 | pub mod copy_competition_files; 5 | pub mod export_booklet; 6 | pub mod export_solution_checks; 7 | pub mod find_bad_case; 8 | pub mod fuzz_checker; 9 | pub mod gen_autocompletion; 10 | pub mod opt; 11 | pub mod reset; 12 | pub mod sandbox; 13 | pub mod server; 14 | pub mod task_info; 15 | pub mod terry_statement; 16 | pub mod worker; 17 | -------------------------------------------------------------------------------- /tests/invalid_copy.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | #[test] 5 | fn invalid_copy_local() { 6 | better_panic::install(); 7 | 8 | TestInterface::run_local("invalid_copy").fail("COPY from not existing file"); 9 | } 10 | 11 | #[test] 12 | fn invalid_copy_remote() { 13 | better_panic::install(); 14 | 15 | TestInterface::run_remote("invalid_copy").fail("COPY from not existing file"); 16 | } 17 | -------------------------------------------------------------------------------- /src/tools/find_bad_case/finish_ui.rs: -------------------------------------------------------------------------------- 1 | use crate::tools::find_bad_case::state::UIState; 2 | 3 | /// This UI cannot do much because it is executed before the evaluation was completely done (e.g. 4 | /// the file callbacks may not have completed yet). Instead, the actual printing is done in the main 5 | /// of this tool. 6 | pub struct FinishUI; 7 | 8 | impl task_maker_format::ui::FinishUI for FinishUI { 9 | fn print(_state: &UIState) {} 10 | } 11 | -------------------------------------------------------------------------------- /src/tools/clear.rs: -------------------------------------------------------------------------------- 1 | use crate::FindTaskOpt; 2 | use anyhow::{Context, Error}; 3 | use clap::Parser; 4 | 5 | #[derive(Parser, Debug)] 6 | pub struct ClearOpt { 7 | #[clap(flatten, next_help_heading = Some("TASK SEARCH"))] 8 | pub find_task: FindTaskOpt, 9 | } 10 | 11 | pub fn main_clear(opt: ClearOpt) -> Result<(), Error> { 12 | let task = opt.find_task.find_task(&Default::default())?; 13 | task.clean().context("Cannot clear the task directory")?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/gen/GEN: -------------------------------------------------------------------------------- 1 | # This is a comment, this line should be ignored 2 | 3 | # The task is to echo the first parameter 4 | # If you exit fast enough and don't use much memory you will score 100 :P 5 | 6 | # Copy some file from some random folder 7 | #ST: 5 8 | #STNAME: samples 9 | #COPY: testo/input.txt 10 | #COPY: gen/hard.txt 11 | 12 | # Small N 13 | #ST: 45 14 | #STNAME: small 15 | 5 16 | 10 17 | 18 | # Big N 19 | #ST: 50 20 | #STNAME: big 21 | 1300 22 | 2000 23 | 5000 24 | -------------------------------------------------------------------------------- /src/tools/fuzz_checker/README: -------------------------------------------------------------------------------- 1 | This directory contains the following files: 2 | 3 | - input.txt is the testcase input file 4 | - output.txt is the official output of the testcase 5 | - output-crash.txt is the contestant output that led to a fail of the checker 6 | - artifact.bin is the input passed to the fuzzer 7 | 8 | 9 | 10 | To test the checker run: 11 | 12 | @@CHECKER@@ input.txt output.txt output-crash.txt 13 | 14 | 15 | 16 | To test the fuzzed version: 17 | 18 | ../../fuzzer/fuzzer artifact.bin 19 | -------------------------------------------------------------------------------- /tools/update_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROOT="$(realpath "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/..")" 4 | 5 | if [[ $# -ne 1 ]]; then 6 | echo "Usage: $0 version" >&2 7 | echo "Example: $0 0.1.2" >&2 8 | exit 1 9 | fi 10 | 11 | version=$1 12 | 13 | IFS=$'\n' find "${ROOT}" -name Cargo.toml | while read -r f; do 14 | echo "Update $f" 15 | sed -i "s/^version =.*/version = \"${version}\"/" "$f" 16 | done 17 | 18 | # Update Cargo.lock 19 | cargo update -w 20 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/gen/valida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pylint: disable=wildcard-import 4 | # pylint: disable=invalid-name 5 | 6 | import sys 7 | from constraints import * 8 | 9 | def run(file: list[str], st: int): 10 | assert len(file) == 1 11 | N = int(file[0].strip()) 12 | assert 1 <= N <= subtasks[st]['MAXN'] 13 | 14 | assert len(sys.argv) >= 2 15 | file = open(sys.argv[1]).read().splitlines() 16 | 17 | st = 0 18 | if len(sys.argv) >= 3: 19 | st = int(sys.argv[2]) 20 | 21 | run(file, st) 22 | -------------------------------------------------------------------------------- /data/statements/limiti.py: -------------------------------------------------------------------------------- 1 | # If limiti.py is not present, but limiti.yaml is, 2 | # then this file is automatically provided for booklet compilation. 3 | # 4 | # The script stores the entries of the two YAML files as global variables 5 | 6 | import yaml 7 | import sys 8 | 9 | try: 10 | with open("limiti.yaml", "r") as constraints: 11 | constraints = yaml.safe_load(constraints) 12 | 13 | global_variables = globals() 14 | global_variables |= constraints 15 | except FileNotFoundError: 16 | sys.stderr.write("No limiti.yaml file found") 17 | -------------------------------------------------------------------------------- /task-maker-format/src/ioi/statement/statement/templates/booklet.tex: -------------------------------------------------------------------------------- 1 | \documentclass[% 2 | %{language}%,% 3 | %{show_solutions}%,% 4 | %{show_summary}%,% 5 | ]{cms-contest} 6 | 7 | \usepackage[%{font_enc}%]{fontenc} 8 | \usepackage[%{input_enc}%]{inputenc} 9 | \usepackage[%{language}%]{babel} 10 | \usepackage{bookmark} 11 | \usepackage{import} 12 | 13 | %{packages}% 14 | 15 | \begin{document} 16 | %{intro_page}% 17 | \begin{contest}{%{description}%}{%{location}%}{%{date}%} 18 | \setContestLogo{%{logo}%} 19 | %{tasks}% 20 | \end{contest} 21 | \end{document} 22 | -------------------------------------------------------------------------------- /task-maker-format/src/ui/json.rs: -------------------------------------------------------------------------------- 1 | use crate::ui::*; 2 | 3 | /// This UI will print to stdout the UI messages as json. 4 | #[derive(Default)] 5 | pub struct JsonUI; 6 | 7 | impl JsonUI { 8 | /// Make a new `JsonUI`. 9 | pub fn new() -> JsonUI { 10 | JsonUI {} 11 | } 12 | } 13 | 14 | impl UI for JsonUI { 15 | fn on_message(&mut self, message: UIMessage) { 16 | let message = serde_json::to_string(&message).expect("Failed to serialize message"); 17 | println!("{message}"); 18 | } 19 | 20 | fn finish(&mut self) {} 21 | } 22 | -------------------------------------------------------------------------------- /task-maker-format/src/ui/raw.rs: -------------------------------------------------------------------------------- 1 | use crate::ui::*; 2 | 3 | /// This UI will print to stdout all the raw information it gets, it's very 4 | /// verbose and useful only for debug purpuses. 5 | #[derive(Default)] 6 | pub struct RawUI; 7 | 8 | impl RawUI { 9 | /// Make a new RawUI. 10 | pub fn new() -> RawUI { 11 | RawUI {} 12 | } 13 | } 14 | 15 | impl UI for RawUI { 16 | fn on_message(&mut self, message: UIMessage) { 17 | println!("{message:?}"); 18 | } 19 | 20 | fn finish(&mut self) { 21 | println!("UI finished"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/tasks/communication/sol/stub.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int op(int code, int a, int b); 6 | 7 | int main(int argc, char **argv) { 8 | FILE *fifo_in, *fifo_out; 9 | fifo_in = fopen(argv[1], "r"); 10 | fifo_out = fopen(argv[2], "w"); 11 | int code = atoi(argv[3]); 12 | 13 | int a, b; 14 | assert(fscanf(fifo_in, "%d %d", &a, &b) == 2); 15 | fprintf(fifo_out, "%d\n", op(code, a, b)); 16 | fflush(fifo_out); 17 | 18 | fclose(fifo_in); 19 | fclose(fifo_out); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/tasks/communication/sol/stub.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int op(int code, int a, int b); 6 | 7 | int main(int argc, char **argv) { 8 | FILE *fifo_in, *fifo_out; 9 | fifo_in = fopen(argv[1], "r"); 10 | fifo_out = fopen(argv[2], "w"); 11 | int code = atoi(argv[3]); 12 | 13 | int a, b; 14 | assert(fscanf(fifo_in, "%d %d", &a, &b) == 2); 15 | fprintf(fifo_out, "%d\n", op(code, a, b)); 16 | fflush(fifo_out); 17 | 18 | fclose(fifo_in); 19 | fclose(fifo_out); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/with_bugged_checker.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | fn with_bugged_checker(test: TestInterface) { 5 | test.success() 6 | .has_diagnostic("Checker returned an invalid score"); 7 | } 8 | 9 | #[test] 10 | fn with_bugged_checker_local() { 11 | better_panic::install(); 12 | 13 | with_bugged_checker(TestInterface::run_local("with_bugged_checker")); 14 | } 15 | 16 | #[test] 17 | fn with_bugged_checker_remote() { 18 | better_panic::install(); 19 | 20 | with_bugged_checker(TestInterface::run_remote("with_bugged_checker")); 21 | } 22 | -------------------------------------------------------------------------------- /data/statements/constraints.py: -------------------------------------------------------------------------------- 1 | # If constraints.py is not present, but constraints.yaml is, 2 | # then this file is automatically provided for booklet compilation. 3 | # 4 | # The script stores the entries of the two YAML files as global variables 5 | 6 | import yaml 7 | import sys 8 | 9 | try: 10 | with open("constraints.yaml", "r") as constraints: 11 | constraints = yaml.safe_load(constraints) 12 | 13 | global_variables = globals() 14 | global_variables |= constraints 15 | except FileNotFoundError: 16 | sys.stderr.write("No constraints.yaml file found") 17 | -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/sol/stub.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int op(int code, int a, int b); 6 | 7 | int main(int argc, char **argv) { 8 | FILE *fifo_in, *fifo_out; 9 | fifo_in = fopen(argv[1], "r"); 10 | fifo_out = fopen(argv[2], "w"); 11 | int code = atoi(argv[3]); 12 | 13 | int a, b; 14 | assert(fscanf(fifo_in, "%d %d", &a, &b) == 2); 15 | fprintf(fifo_out, "%d\n", op(code, a, b)); 16 | fflush(fifo_out); 17 | 18 | fclose(fifo_in); 19 | fclose(fifo_out); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/sol/stub.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int op(int code, int a, int b); 6 | 7 | int main(int argc, char **argv) { 8 | FILE *fifo_in, *fifo_out; 9 | fifo_in = fopen(argv[1], "r"); 10 | fifo_out = fopen(argv[2], "w"); 11 | int code = atoi(argv[3]); 12 | 13 | int a, b; 14 | assert(fscanf(fifo_in, "%d %d", &a, &b) == 2); 15 | fprintf(fifo_out, "%d\n", op(code, a, b)); 16 | fflush(fifo_out); 17 | 18 | fclose(fifo_in); 19 | fclose(fifo_out); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /task-maker-dag/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "task-maker-dag" 3 | version = "0.6.20" 4 | authors = ["Edoardo Morassutto "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | task-maker-store = { path = "../task-maker-store" } 9 | 10 | # Serialization/Deserialization 11 | serde = { workspace = true, features = ["derive"] } 12 | # UUID generation 13 | uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] } 14 | # Generic error utilities 15 | anyhow = { workspace = true, features = ["backtrace"] } 16 | 17 | [dev-dependencies] 18 | tempfile = { workspace = true } 19 | approx = { workspace = true } 20 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # task-maker-rust 2 | //! 3 | //! This is both an application and a library, the library can be used to achieve the same 4 | //! functionalities of the task-maker binary, inside your application. 5 | #![allow(dead_code)] 6 | 7 | #[macro_use] 8 | extern crate log; 9 | #[macro_use] 10 | extern crate lazy_static; 11 | #[macro_use] 12 | extern crate scopeguard; 13 | 14 | pub use copy_dag::*; 15 | pub use local::*; 16 | pub use opt::*; 17 | pub use sandbox::*; 18 | 19 | pub mod context; 20 | pub mod copy_dag; 21 | pub mod error; 22 | pub mod local; 23 | pub mod opt; 24 | pub mod remote; 25 | pub mod sandbox; 26 | pub mod tools; 27 | -------------------------------------------------------------------------------- /tools/release/linux/build_release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | # where the data files will be put 6 | export TM_DATA_DIR=/usr/share/task-maker-rust 7 | 8 | # rustup 9 | source $HOME/.cargo/env 10 | 11 | # move to the directory where the source code will be present 12 | cd /source 13 | if [ ! -f "Cargo.toml" ]; then 14 | echo "Mount the repo inside /source!" >&2 15 | exit 1 16 | fi 17 | 18 | # build the binary 19 | cargo build --bin task-maker --release 20 | 21 | # build the autocompletion files 22 | cargo run --release --bin task-maker-tools gen-autocompletion 23 | 24 | # prepare the .deb file 25 | cargo deb --no-build -------------------------------------------------------------------------------- /tools/docker/healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | processes=$(ps aux) 4 | 5 | # check server 6 | echo $processes | grep task-maker-rust | grep -- --server 2>/dev/null >/dev/null 7 | server_ok=$? 8 | 9 | # check worker 10 | echo $processes | grep task-maker-rust | grep -- --worker 2>/dev/null >/dev/null 11 | worker_ok=$? 12 | 13 | if [[ $server_ok == 0 && $worker_ok == 0 ]]; then 14 | echo "server & worker ok" 15 | exit 0 16 | fi 17 | if [[ $server_ok == 0 ]]; then 18 | echo "worker down" 19 | exit 1 20 | fi 21 | if [[ $worker_ok == 0 ]]; then 22 | echo "server down" 23 | exit 1 24 | fi 25 | echo "server & worker down" 26 | exit 1 27 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/tle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | volatile unsigned int x; 7 | 8 | int main() { 9 | std::clock_t startcputime = std::clock(); 10 | int N; 11 | std::ifstream in("input.txt"); 12 | std::ofstream out("output.txt"); 13 | in >> N; 14 | 15 | const constexpr int sz = 100 * 1024; 16 | std::vector v; 17 | v.resize(sz, 0); 18 | int i = 0; 19 | while ((std::clock() - startcputime) * 1000 < N * CLOCKS_PER_SEC) { 20 | for (int j = 0; j < i; j++) { 21 | v[j] += i; 22 | } 23 | i = (i + 1) % sz; 24 | } 25 | out << N << std::endl; 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/tle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | volatile unsigned int x; 7 | 8 | int main() { 9 | std::clock_t startcputime = std::clock(); 10 | int N; 11 | std::ifstream in("input.txt"); 12 | std::ofstream out("output.txt"); 13 | in >> N; 14 | 15 | const constexpr int sz = 100 * 1024; 16 | std::vector v; 17 | v.resize(sz, 0); 18 | int i = 0; 19 | while ((std::clock() - startcputime) * 1000 < N * CLOCKS_PER_SEC) { 20 | for (int j = 0; j < i; j++) { 21 | v[j] += i; 22 | } 23 | i = (i + 1) % sz; 24 | } 25 | out << N << std::endl; 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /tests/missing_shebang.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | use task_maker_format::ioi::TestcaseGenerationStatus::Failed; 5 | 6 | fn missing_shebang(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![100.0]) 12 | .generation_statuses(vec![Failed]); 13 | } 14 | 15 | #[test] 16 | fn missing_shebang_local() { 17 | better_panic::install(); 18 | 19 | missing_shebang(TestInterface::run_local("missing_shebang")); 20 | } 21 | 22 | #[test] 23 | fn missing_shebang_remote() { 24 | better_panic::install(); 25 | 26 | missing_shebang(TestInterface::run_remote("missing_shebang")); 27 | } 28 | -------------------------------------------------------------------------------- /tests/tasks/with_checker/cor/correttore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char** argv) { 5 | if (argc != 4) { 6 | std::cerr << "Usage: correttore " 7 | << std::endl; 8 | return 1; 9 | } 10 | std::ifstream in(argv[1]); // NOLINT 11 | std::ifstream cor(argv[2]); // NOLINT 12 | std::ifstream test(argv[3]); // NOLINT 13 | 14 | int N, N_cor, N_test; 15 | in >> N; 16 | cor >> N_cor; 17 | test >> N_test; 18 | 19 | if (N_cor == N_test) { 20 | std::cout << 1.0 << std::endl; 21 | std::cerr << "Ok!" << std::endl; 22 | } else { 23 | std::cout << 0.0 << std::endl; 24 | std::cerr << "Ko!" << std::endl; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: / 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "cargo" 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "weekly" 16 | ignore: 17 | - dependency-name: "*" 18 | update-types: 19 | - "version-update:semver-patch" 20 | -------------------------------------------------------------------------------- /tests/with_invalid_shebang.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | use task_maker_format::ioi::TestcaseGenerationStatus::Failed; 5 | 6 | fn with_invalid_shebang(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![100.0]) 12 | .generation_statuses(vec![Failed]); 13 | } 14 | 15 | #[test] 16 | fn with_invalid_shebang_local() { 17 | better_panic::install(); 18 | 19 | with_invalid_shebang(TestInterface::run_local("with_invalid_shebang")); 20 | } 21 | 22 | #[test] 23 | fn with_invalid_shebang_remote() { 24 | better_panic::install(); 25 | 26 | with_invalid_shebang(TestInterface::run_remote("with_invalid_shebang")); 27 | } 28 | -------------------------------------------------------------------------------- /task-maker-cache/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "task-maker-cache" 3 | version = "0.6.20" 4 | authors = ["Edoardo Morassutto "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | task-maker-dag = { path = "../task-maker-dag" } 9 | task-maker-store = { path = "../task-maker-store" } 10 | 11 | # General iterator utilities 12 | itertools = { workspace = true } 13 | # Serialization/Deserialization 14 | serde = { workspace = true, features = ["derive"] } 15 | bincode = { workspace = true } 16 | # Generic error utilities 17 | anyhow = { workspace = true, features = ["backtrace"] } 18 | # Logging 19 | log = { workspace = true } 20 | # Compile time string format 21 | const_format = { workspace = true } 22 | 23 | [dev-dependencies] 24 | tempfile = { workspace = true } 25 | -------------------------------------------------------------------------------- /tests/with_bugged_gen.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | use task_maker_format::ioi::TestcaseGenerationStatus::Failed; 5 | 6 | fn with_bugged_gen(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![100.0]) 12 | .generation_statuses(vec![Failed]) 13 | .generation_fails(vec![Some(":(".into())]); 14 | } 15 | 16 | #[test] 17 | fn with_bugged_gen_local() { 18 | better_panic::install(); 19 | 20 | with_bugged_gen(TestInterface::run_local("with_bugged_gen")); 21 | } 22 | 23 | #[test] 24 | fn with_bugged_gen_remote() { 25 | better_panic::install(); 26 | 27 | with_bugged_gen(TestInterface::run_remote("with_bugged_gen")); 28 | } 29 | -------------------------------------------------------------------------------- /task-maker-store/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "task-maker-store" 3 | version = "0.6.20" 4 | authors = ["Edoardo Morassutto "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | # Hashing function 9 | blake3 = { workspace = true } 10 | # Generic error utilities 11 | anyhow = { workspace = true, features = ["backtrace"] } 12 | # File locking 13 | fslock = { workspace = true } 14 | # Serialization/Deserialization 15 | serde = { workspace = true, features = ["derive"] } 16 | bincode = { workspace = true } 17 | # Logging 18 | log = { workspace = true } 19 | # Temporary directory creation 20 | tempfile = { workspace = true } 21 | # Compile time string format 22 | const_format = { workspace = true } 23 | 24 | [dev-dependencies] 25 | pretty_assertions = { workspace = true } 26 | -------------------------------------------------------------------------------- /src/tools/task_info.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Error}; 2 | use clap::Parser; 3 | 4 | use crate::FindTaskOpt; 5 | 6 | #[derive(Parser, Debug, Clone)] 7 | pub struct TaskInfoOpt { 8 | #[clap(flatten, next_help_heading = Some("TASK SEARCH"))] 9 | pub find_task: FindTaskOpt, 10 | /// Produce JSON output. 11 | #[clap(long, short)] 12 | pub json: bool, 13 | } 14 | 15 | pub fn main_task_info(opt: TaskInfoOpt) -> Result<(), Error> { 16 | let task = opt.find_task.find_task(&Default::default())?; 17 | let info = task.task_info().context("Cannot produce task info")?; 18 | if opt.json { 19 | let json = serde_json::to_string(&info).context("Non-serializable task info")?; 20 | println!("{json}"); 21 | } else { 22 | println!("{info:#?} "); 23 | } 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /tests/tasks/communication_stdio/check/manager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main(int argc, char **argv) { 10 | signal(SIGPIPE, SIG_IGN); 11 | 12 | FILE *fin, *fifo_in, *fifo_out; 13 | 14 | fin = fopen("input.txt", "r"); 15 | fifo_in = fopen(argv[2], "w"); 16 | fifo_out = fopen(argv[1], "r"); 17 | 18 | int a, b, res; 19 | assert(2 == fscanf(fin, "%d %d", &a, &b)); 20 | 21 | fprintf(fifo_in, "%d %d\n", a, b); 22 | fflush(fifo_in); 23 | fscanf(fifo_out, "%d", &res); 24 | 25 | if (a + b == res) { 26 | fprintf(stderr, "Ok!\n"); 27 | printf("1.0\n"); 28 | } else { 29 | fprintf(stderr, "Ko!\n"); 30 | printf("0.0\n"); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /task-maker-exec/src/find_tools.rs: -------------------------------------------------------------------------------- 1 | //! Finds the location of the `task-maker-tools` executable. 2 | use std::path::PathBuf; 3 | 4 | /// Locates the `task-maker-tools` executable. 5 | pub fn find_tools_path() -> PathBuf { 6 | // Check environment variable. 7 | if let Some(path) = std::env::var_os("TASK_MAKER_TOOLS_PATH") { 8 | return path.into(); 9 | } 10 | // Check in the directory of the current executable. 11 | let current_exe = std::env::current_exe(); 12 | if let Ok(current_exe) = current_exe { 13 | let candidate_tools_path = current_exe.with_file_name("task-maker-tools"); 14 | if candidate_tools_path.exists() { 15 | return candidate_tools_path; 16 | } 17 | } 18 | 19 | // Default to looking in PATH. 20 | "task-maker-tools".to_owned().into() 21 | } 22 | -------------------------------------------------------------------------------- /tests/task-yaml-orig.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | fn classic(test: TestInterface) { 5 | test.success() 6 | .time_limit(1.0) 7 | .memory_limit(64) 8 | .max_score(100.0) 9 | .subtask_scores(vec![5.0, 45.0, 50.0]) 10 | .must_compile("generatore.cpp") 11 | .must_compile("solution.cpp") 12 | .must_compile("wrong_file.cpp") 13 | .solution_score("solution.cpp", vec![5.0, 45.0, 50.0]) 14 | .solution_score("wrong_file.cpp", vec![0.0, 0.0, 0.0]); 15 | } 16 | 17 | #[test] 18 | fn classic_local() { 19 | better_panic::install(); 20 | classic(TestInterface::run_local("task-yaml-orig")); 21 | } 22 | 23 | #[test] 24 | fn classic_remote() { 25 | better_panic::install(); 26 | classic(TestInterface::run_remote("task-yaml-orig")); 27 | } 28 | -------------------------------------------------------------------------------- /tests/with_bugged_sol.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | use task_maker_format::ioi::TestcaseEvaluationStatus::Solved; 5 | use task_maker_format::ioi::TestcaseGenerationStatus::Failed; 6 | 7 | fn with_bugged_sol(test: TestInterface) { 8 | test.success() 9 | .time_limit(1.0) 10 | .memory_limit(64) 11 | .max_score(100.0) 12 | .subtask_scores(vec![100.0]) 13 | .generation_statuses(vec![Failed]) 14 | .solution_statuses("ok.py", vec![Solved]); 15 | } 16 | 17 | #[test] 18 | fn with_bugged_sol_local() { 19 | better_panic::install(); 20 | 21 | with_bugged_sol(TestInterface::run_local("with_bugged_sol")); 22 | } 23 | 24 | #[test] 25 | fn with_bugged_sol_remote() { 26 | better_panic::install(); 27 | 28 | with_bugged_sol(TestInterface::run_remote("with_bugged_sol")); 29 | } 30 | -------------------------------------------------------------------------------- /tools/publish_aur.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | AUR_REPO="ssh://aur@aur.archlinux.org/task-maker-rust.git" 6 | 7 | version=$1 8 | if [ -z "$version" ]; then 9 | echo "Usage: $0 version" >&2 10 | exit 1 11 | fi 12 | if [[ "$version" == v* ]]; then 13 | echo "Version should not start with 'v'" >&2 14 | exit 1 15 | fi 16 | 17 | WORKDIR=$(mktemp -d) 18 | function cleanup() { 19 | rm -rf "${WORKDIR}" 20 | } 21 | trap cleanup EXIT 22 | 23 | cd "$WORKDIR" 24 | 25 | git clone "${AUR_REPO}" . 26 | sed -i "s/pkgver=.*/pkgver=$version/" PKGBUILD 27 | sed -i "s/pkgrel=.*/pkgrel=1/" PKGBUILD 28 | updpkgsums 29 | makepkg --printsrcinfo > .SRCINFO 30 | git diff 31 | read -p "Continue (y/n)?" -r choice 32 | case "$choice" in 33 | y|Y ) ;; 34 | * ) echo "Aborting"; exit 3;; 35 | esac 36 | 37 | git commit -am "Version $version" 38 | git push origin master 39 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | #[allow(unused_imports)] 4 | pub use test_interface::*; 5 | 6 | mod test_interface; 7 | 8 | use task_maker_dag::ExecutionDAG; 9 | use task_maker_exec::eval_dag_locally; 10 | use task_maker_rust::ToolsSandboxRunner; 11 | 12 | pub fn setup() { 13 | let _ = env_logger::Builder::from_default_env() 14 | .format_timestamp_nanos() 15 | .is_test(true) 16 | .try_init(); 17 | std::env::set_var( 18 | "TASK_MAKER_TOOLS_PATH", 19 | env!("CARGO_BIN_EXE_task-maker-tools"), 20 | ); 21 | } 22 | 23 | pub fn eval_dag(dag: ExecutionDAG) { 24 | let cwd = tempfile::TempDir::new().unwrap(); 25 | eval_dag_locally( 26 | dag, 27 | cwd.path(), 28 | 2, 29 | cwd.path(), 30 | 1000, 31 | 1000, 32 | ToolsSandboxRunner::default(), 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /tools/release/linux/Dockerfile: -------------------------------------------------------------------------------- 1 | # Ubuntu 16.04 to keep the library versions backward-compatible 2 | FROM ubuntu:16.04 3 | 4 | LABEL org.opencontainers.image.source="https://github.com/olimpiadi-informatica/task-maker-rust" 5 | LABEL maintainer="Edoardo Morassutto " 6 | 7 | # we want to use bash, not sh 8 | SHELL ["/bin/bash", "-c"] 9 | 10 | # install dependencies 11 | RUN apt update && \ 12 | apt install -yy curl build-essential libseccomp-dev 13 | 14 | # install rustup and rust stable 15 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal 16 | 17 | # install cargo-deb 18 | RUN source $HOME/.cargo/env && cargo install cargo-deb 19 | 20 | # add the build script 21 | ADD build_release.sh / 22 | 23 | # where the source code will be mounted 24 | VOLUME /source 25 | 26 | # build the release on `docker run` 27 | CMD /build_release.sh 28 | -------------------------------------------------------------------------------- /tests/with_bugged_val.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | use task_maker_format::ioi::TestcaseEvaluationStatus::Skipped; 5 | use task_maker_format::ioi::TestcaseGenerationStatus::Failed; 6 | 7 | fn with_bugged_val(test: TestInterface) { 8 | test.success() 9 | .time_limit(1.0) 10 | .memory_limit(64) 11 | .max_score(100.0) 12 | .subtask_scores(vec![100.0]) 13 | .generation_statuses(vec![Failed]) 14 | .validation_fails(vec![Some("assert False".into())]) 15 | .solution_statuses("soluzione.cpp", vec![Skipped]); 16 | } 17 | 18 | #[test] 19 | fn with_bugged_val_local() { 20 | better_panic::install(); 21 | 22 | with_bugged_val(TestInterface::run_local("with_bugged_val")); 23 | } 24 | 25 | #[test] 26 | fn with_bugged_val_remote() { 27 | better_panic::install(); 28 | 29 | with_bugged_val(TestInterface::run_remote("with_bugged_val")); 30 | } 31 | -------------------------------------------------------------------------------- /tests/tasks/classic/sol/sigsegv.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define handle_error(msg) \ 8 | do { \ 9 | perror(msg); \ 10 | exit(EXIT_FAILURE); \ 11 | } while (0) 12 | 13 | char *buffer; 14 | 15 | int main() { 16 | char *p; 17 | int pagesize; 18 | 19 | pagesize = sysconf(_SC_PAGE_SIZE); 20 | if (pagesize == -1) handle_error("sysconf"); 21 | 22 | /* Allocate a buffer aligned on a page boundary; 23 | initial protection is PROT_READ | PROT_WRITE */ 24 | if (posix_memalign((void**)&buffer, pagesize, 4 * pagesize)) handle_error("memalign"); 25 | if (mprotect(buffer + pagesize * 2, pagesize, PROT_READ) == -1) 26 | handle_error("mprotect"); 27 | 28 | for (p = buffer; p < buffer + 1000 * pagesize;) *(p++) = 'a'; 29 | exit(2); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /task-maker-lang/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "task-maker-lang" 3 | version = "0.6.20" 4 | authors = ["Edoardo Morassutto "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | task-maker-dag = { path = "../task-maker-dag" } 9 | 10 | # Serialization/Deserialization 11 | serde = { workspace = true, features = ["derive", "rc"] } 12 | # Regular expressions 13 | regex = { workspace = true } 14 | # Generic error utilities 15 | anyhow = { workspace = true, features = ["backtrace"] } 16 | # Global constants 17 | lazy_static = { workspace = true } 18 | # Resolve executable names in $PATH 19 | which = { workspace = true } 20 | # Split command line arguments 21 | shell-words = { workspace = true } 22 | 23 | [dev-dependencies] 24 | task-maker-exec = { path = "../task-maker-exec" } 25 | 26 | tempfile = { workspace = true } 27 | # assert_that! macro 28 | speculoos = { workspace = true } 29 | tabox = { workspace = true } 30 | -------------------------------------------------------------------------------- /task-maker-format/src/terry/sanity_checks/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::sanity_checks::{SanityCheck, SanityCheckBuilder, SanityChecks}; 2 | use crate::terry::TerryTask; 3 | 4 | mod checker; 5 | mod statement; 6 | mod task; 7 | 8 | inventory::collect!(&'static SanityCheckBuilder); 9 | 10 | /// Make a new `SanityChecks` for a IOI task skipping the checks with the provided names. 11 | pub fn get_sanity_checks(skip: &[&str]) -> SanityChecks { 12 | SanityChecks::new(get_sanity_check_list(skip)) 13 | } 14 | 15 | /// Return the list of sanity checks excluding the ones with their name in the provided list. 16 | pub fn get_sanity_check_list(skip: &[&str]) -> Vec>> { 17 | inventory::iter::<&SanityCheckBuilder>() 18 | .cloned() 19 | .map(|b| b.build()) 20 | .filter(|s| !skip.contains(&s.name()) && !skip.contains(&s.category().as_str())) 21 | .collect() 22 | } 23 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/sol/sigsegv.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define handle_error(msg) \ 8 | do { \ 9 | perror(msg); \ 10 | exit(EXIT_FAILURE); \ 11 | } while (0) 12 | 13 | char *buffer; 14 | 15 | int main() { 16 | char *p; 17 | int pagesize; 18 | 19 | pagesize = sysconf(_SC_PAGE_SIZE); 20 | if (pagesize == -1) handle_error("sysconf"); 21 | 22 | /* Allocate a buffer aligned on a page boundary; 23 | initial protection is PROT_READ | PROT_WRITE */ 24 | if (posix_memalign((void**)&buffer, pagesize, 4 * pagesize)) handle_error("memalign"); 25 | if (mprotect(buffer + pagesize * 2, pagesize, PROT_READ) == -1) 26 | handle_error("mprotect"); 27 | 28 | for (p = buffer; p < buffer + 1000 * pagesize;) *(p++) = 'a'; 29 | exit(2); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /task-maker-format/src/terry/sanity_checks/task.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use task_maker_diagnostics::Diagnostic; 3 | 4 | use crate::sanity_checks::{make_sanity_check, SanityCheck, SanityCheckCategory}; 5 | use crate::terry::TerryTask; 6 | use crate::EvaluationData; 7 | 8 | /// Check that the validator is present. 9 | #[derive(Debug, Default)] 10 | pub struct ValidatorPresent; 11 | make_sanity_check!(ValidatorPresent); 12 | 13 | impl SanityCheck for ValidatorPresent { 14 | type Task = TerryTask; 15 | 16 | fn name(&self) -> &'static str { 17 | "ValidatorPresent" 18 | } 19 | 20 | fn category(&self) -> SanityCheckCategory { 21 | SanityCheckCategory::Io 22 | } 23 | 24 | fn pre_hook(&self, task: &TerryTask, eval: &mut EvaluationData) -> Result<(), Error> { 25 | if task.validator.is_none() { 26 | eval.add_diagnostic(Diagnostic::warning("Validator not present"))?; 27 | } 28 | Ok(()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /task-maker-format/src/terry/task_info.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::terry::TerryTask; 5 | 6 | /// Task information structure. 7 | #[derive(Debug, Clone, Serialize, Deserialize)] 8 | pub struct TerryTaskInfo { 9 | /// The version of the `TaskInfo` structure. 10 | version: u64, 11 | /// The name of the task (the short one). 12 | pub name: String, 13 | /// The title of the task (the long one). 14 | pub description: String, 15 | /// The maximum score for this task. 16 | pub max_score: f64, 17 | } 18 | 19 | impl TerryTaskInfo { 20 | /// Generate the task information from the provided `Task`. 21 | pub fn new(task: &TerryTask) -> Result { 22 | Ok(TerryTaskInfo { 23 | version: 1, 24 | name: task.name.clone(), 25 | description: task.description.clone(), 26 | max_score: task.max_score, 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /data/bad_outputs/very_long_str.txt: -------------------------------------------------------------------------------- 1 | Case #1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -------------------------------------------------------------------------------- /tests/with_checker.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | use task_maker_format::ioi::TestcaseEvaluationStatus::*; 5 | 6 | fn with_checker(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![100.0]) 12 | .not_compiled("soluzione.sh") 13 | .not_compiled("wrong.sh") 14 | .solution_score("soluzione.sh", vec![100.0]) 15 | .solution_score("wrong.sh", vec![0.0]) 16 | .solution_statuses("soluzione.sh", vec![Accepted("Ok!".into())]) 17 | .solution_statuses("wrong.sh", vec![WrongAnswer("Ko!".into())]) 18 | .file_exists("cor/correttore"); 19 | } 20 | 21 | #[test] 22 | fn with_checker_local() { 23 | better_panic::install(); 24 | 25 | with_checker(TestInterface::run_local("with_checker")); 26 | } 27 | 28 | #[test] 29 | fn with_checker_remote() { 30 | better_panic::install(); 31 | 32 | with_checker(TestInterface::run_remote("with_checker")); 33 | } 34 | -------------------------------------------------------------------------------- /tests/tasks/communication/check/manager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main(int argc, char **argv) { 10 | signal(SIGPIPE, SIG_IGN); 11 | 12 | FILE *fin, *fifo_in1, *fifo_out1, *fifo_in2, *fifo_out2; 13 | 14 | fin = fopen("input.txt", "r"); 15 | fifo_in1 = fopen(argv[2], "w"); 16 | fifo_out1 = fopen(argv[1], "r"); 17 | fifo_in2 = fopen(argv[4], "w"); 18 | fifo_out2 = fopen(argv[3], "r"); 19 | 20 | int a, b, c, res; 21 | assert(3 == fscanf(fin, "%d %d %d", &a, &b, &c)); 22 | 23 | fprintf(fifo_in1, "%d %d\n", a, b); 24 | fflush(fifo_in1); 25 | fscanf(fifo_out1, "%d", &res); 26 | 27 | fprintf(fifo_in2, "%d %d\n", res, c); 28 | fflush(fifo_in2); 29 | fscanf(fifo_out2, "%d", &res); 30 | 31 | if ((a + b) * c == res) { 32 | fprintf(stderr, "Ok!\n"); 33 | printf("1.0\n"); 34 | } else { 35 | fprintf(stderr, "Ko!\n"); 36 | printf("0.0\n"); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /tests/tasks/communication_with_gen/check/manager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main(int argc, char **argv) { 10 | signal(SIGPIPE, SIG_IGN); 11 | 12 | FILE *fin, *fifo_in1, *fifo_out1, *fifo_in2, *fifo_out2; 13 | 14 | fin = fopen("input.txt", "r"); 15 | fifo_in1 = fopen(argv[2], "w"); 16 | fifo_out1 = fopen(argv[1], "r"); 17 | fifo_in2 = fopen(argv[4], "w"); 18 | fifo_out2 = fopen(argv[3], "r"); 19 | 20 | int a, b, c, res; 21 | assert(3 == fscanf(fin, "%d %d %d", &a, &b, &c)); 22 | 23 | fprintf(fifo_in1, "%d %d\n", a, b); 24 | fflush(fifo_in1); 25 | fscanf(fifo_out1, "%d", &res); 26 | 27 | fprintf(fifo_in2, "%d %d\n", res, c); 28 | fflush(fifo_in2); 29 | fscanf(fifo_out2, "%d", &res); 30 | 31 | if ((a + b) * c == res) { 32 | fprintf(stderr, "Ok!\n"); 33 | printf("1.0\n"); 34 | } else { 35 | fprintf(stderr, "Ko!\n"); 36 | printf("0.0\n"); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /tools/docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | server_args=${SERVER_ARGS:-} 4 | worker_args=${WORKER_ARGS:-} 5 | server_addr=${SERVER_ADDR:-127.0.0.1:27183} 6 | spawn_server=${SPAWN_SERVER:-true} 7 | spawn_worker=${SPAWN_WORKER:-true} 8 | 9 | export RUST_LOG=info 10 | export RUST_BACKTRACE=1 11 | 12 | server_store=$(mktemp -d tmserver.XXXXXXX -p /tmp) 13 | worker_store=$(mktemp -d tmworker.XXXXXXX -p /tmp) 14 | 15 | function spawn_server() { 16 | task-maker-rust --store-dir "$server_store" $server_args --server 17 | } 18 | function spawn_worker() { 19 | task-maker-rust --store-dir "$worker_store" $worker_args --worker $server_addr 20 | } 21 | 22 | # worker only 23 | if [[ $spawn_server != true && $spawn_worker == true ]]; then 24 | spawn_worker 25 | # server only 26 | elif [[ $spawn_server == true && $spawn_worker != true ]]; then 27 | spawn_server 28 | # server+worker 29 | elif [[ $spawn_server == true && $spawn_worker == true ]]; then 30 | # run the workers in background, but wait for the server 31 | ( sleep 2s && spawn_worker ) & 32 | spawn_server 33 | # nothing to spawn 34 | else 35 | bash 36 | fi -------------------------------------------------------------------------------- /task-maker-format/src/tag.rs: -------------------------------------------------------------------------------- 1 | use task_maker_dag::ExecutionTag; 2 | 3 | lazy_static! { 4 | /// The list of all the ExecutionTags used for the evaluation. 5 | pub static ref VALID_TAGS: Vec = [ 6 | "compilation", 7 | "generation", 8 | "evaluation", 9 | "checking", 10 | "booklet" 11 | ] 12 | .iter() 13 | .map(|s| String::from(*s)) 14 | .collect(); 15 | } 16 | 17 | /// Tags of the various executions inside a IOI task. 18 | pub enum Tag { 19 | /// Generation of a testcase. 20 | Generation, 21 | /// Evaluation of a solution. 22 | Evaluation, 23 | /// Checking of a solution. 24 | Checking, 25 | /// Compilation of the booklet. 26 | Booklet, 27 | } 28 | 29 | impl From for ExecutionTag { 30 | fn from(tag: Tag) -> Self { 31 | match tag { 32 | Tag::Generation => ExecutionTag::from("generation"), 33 | Tag::Evaluation => ExecutionTag::from("evaluation"), 34 | Tag::Checking => ExecutionTag::from("checking"), 35 | Tag::Booklet => ExecutionTag::from("booklet"), 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/communication.rs: -------------------------------------------------------------------------------- 1 | use task_maker_format::ioi::TestcaseEvaluationStatus::*; 2 | 3 | mod common; 4 | use common::TestInterface; 5 | 6 | fn communication(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![100.0]) 12 | .must_compile("solution.cpp") 13 | .must_compile("solution.c") 14 | .must_compile("wrong.cpp") 15 | .solution_score("solution.cpp", vec![100.0]) 16 | .solution_score("solution.c", vec![100.0]) 17 | .solution_score("wrong.cpp", vec![0.0]) 18 | .solution_statuses("solution.cpp", vec![Accepted("Ok!".into())]) 19 | .solution_statuses("wrong.cpp", vec![WrongAnswer("Ko!".into())]) 20 | .file_exists("check/manager"); 21 | } 22 | 23 | #[test] 24 | fn communication_local() { 25 | better_panic::install(); 26 | 27 | communication(TestInterface::run_local("communication")); 28 | } 29 | 30 | #[test] 31 | fn communication_remote() { 32 | better_panic::install(); 33 | 34 | communication(TestInterface::run_remote("communication")); 35 | } 36 | -------------------------------------------------------------------------------- /task-maker-format/src/terry/sanity_checks/statement.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use task_maker_diagnostics::Diagnostic; 3 | 4 | use crate::sanity_checks::{make_sanity_check, SanityCheck, SanityCheckCategory}; 5 | use crate::terry::TerryTask; 6 | use crate::EvaluationData; 7 | 8 | /// Check that the statement file is present. 9 | #[derive(Debug, Default)] 10 | pub struct StatementPresent; 11 | make_sanity_check!(StatementPresent); 12 | 13 | impl SanityCheck for StatementPresent { 14 | type Task = TerryTask; 15 | 16 | fn name(&self) -> &'static str { 17 | "StatementPresent" 18 | } 19 | 20 | fn category(&self) -> SanityCheckCategory { 21 | SanityCheckCategory::Statement 22 | } 23 | 24 | fn pre_hook(&self, task: &TerryTask, eval: &mut EvaluationData) -> Result<(), Error> { 25 | if !task.path.join("statement/statement.md").exists() 26 | && !task.path.join("statement/statement.in.md").exists() 27 | { 28 | eval.add_diagnostic(Diagnostic::error( 29 | "Neither statement/statement.md nor statement/statement.in.md exists", 30 | ))?; 31 | } 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | jobs: 18 | docs: 19 | name: Build documentation 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v5 23 | - name: Setup Pages 24 | uses: actions/configure-pages@v5 25 | - name: Install Deps 26 | run: | 27 | sudo apt update 28 | sudo apt install -yy libseccomp-dev libssl-dev build-essential 29 | - uses: dtolnay/rust-toolchain@stable 30 | - name: cargo doc 31 | run: cargo doc --color=always --no-deps --all 32 | - name: Make index.html 33 | run: | 34 | echo '' > target/doc/index.html 35 | - name: Upload artifact 36 | uses: actions/upload-pages-artifact@v4 37 | with: 38 | path: target/doc 39 | - name: Deploy to GitHub Pages 40 | id: deployment 41 | uses: actions/deploy-pages@v4 42 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Error}; 2 | 3 | /// Adds methods for failing without panic. Like `expect` but without panic. 4 | pub trait NiceError { 5 | /// Fail exiting with `1` if the value is not present. Otherwise return the content. 6 | fn nice_unwrap(self) -> T; 7 | } 8 | 9 | fn print_error(error: Error) { 10 | debug!("{error:?}"); 11 | let mut fail: &dyn std::error::Error = error.as_ref(); 12 | eprintln!("Error: {fail}"); 13 | while let Some(cause) = fail.source() { 14 | eprintln!("\nCaused by:\n {cause}"); 15 | fail = cause; 16 | } 17 | } 18 | 19 | impl NiceError for Result { 20 | fn nice_unwrap(self) -> T { 21 | match self { 22 | Ok(x) => x, 23 | Err(e) => { 24 | print_error(e); 25 | std::process::exit(1); 26 | } 27 | } 28 | } 29 | } 30 | 31 | impl NiceError for Option { 32 | fn nice_unwrap(self) -> T { 33 | match self { 34 | Some(x) => x, 35 | None => { 36 | print_error(anyhow!("Option is None")); 37 | std::process::exit(1); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/communication_with_gen.rs: -------------------------------------------------------------------------------- 1 | use task_maker_format::ioi::TestcaseEvaluationStatus::*; 2 | 3 | mod common; 4 | use common::TestInterface; 5 | 6 | fn communication_with_gen(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![50.0, 50.0]) 12 | .must_compile("solution.cpp") 13 | .must_compile("solution.c") 14 | .must_compile("wrong.cpp") 15 | .solution_score("solution.cpp", vec![50.0, 50.0]) 16 | .solution_score("solution.c", vec![50.0, 50.0]) 17 | .solution_score("wrong.cpp", vec![0.0, 0.0]) 18 | .solution_statuses("solution.cpp", vec![Accepted("Ok!".into())]) 19 | .solution_statuses("wrong.cpp", vec![WrongAnswer("Ko!".into())]) 20 | .file_exists("check/manager"); 21 | } 22 | 23 | #[test] 24 | fn communication_with_gen_local() { 25 | better_panic::install(); 26 | 27 | communication_with_gen(TestInterface::run_local("communication_with_gen")); 28 | } 29 | 30 | #[test] 31 | fn communication_with_gen_remote() { 32 | better_panic::install(); 33 | 34 | communication_with_gen(TestInterface::run_remote("communication_with_gen")); 35 | } 36 | -------------------------------------------------------------------------------- /task-maker-format/src/testcase_score_status.rs: -------------------------------------------------------------------------------- 1 | /// The status of a testcase that got scored. 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 3 | pub enum ScoreStatus { 4 | /// The testcase scored the maximum amount of points. 5 | Accepted, 6 | /// The testcase scored more than zero, but not the maximum. 7 | PartialScore, 8 | /// The testcase scored zero points. 9 | WrongAnswer, 10 | } 11 | 12 | impl ScoreStatus { 13 | /// Select the correct status based on the score of a testacase and its maximum possible value. 14 | pub fn from_score(score: f64, max_score: f64) -> Self { 15 | if score == 0.0 { 16 | Self::WrongAnswer 17 | } else if (score - max_score).abs() < 0.001 { 18 | Self::Accepted 19 | } else { 20 | Self::PartialScore 21 | } 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use crate::ScoreStatus; 28 | 29 | #[test] 30 | fn test_score_status() { 31 | assert_eq!(ScoreStatus::from_score(0.0, 1.0), ScoreStatus::WrongAnswer); 32 | assert_eq!(ScoreStatus::from_score(0.1, 1.0), ScoreStatus::PartialScore); 33 | assert_eq!(ScoreStatus::from_score(1.0, 1.0), ScoreStatus::Accepted); 34 | assert_eq!(ScoreStatus::from_score(0.99999, 1.0), ScoreStatus::Accepted); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/without_st.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | use task_maker_format::ioi::TestcaseEvaluationStatus::*; 5 | 6 | fn without_st(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![100.0]) 12 | .not_compiled("soluzione.py") 13 | .not_compiled("40_points.py") 14 | .solution_score("soluzione.py", vec![100.0]) 15 | .solution_score("40_points.py", vec![40.0]) 16 | .solution_statuses("soluzione.py", vec![Accepted("Output is correct".into())]) 17 | .solution_statuses( 18 | "40_points.py", 19 | vec![ 20 | Accepted("Output is correct".into()), 21 | Accepted("Output is correct".into()), 22 | WrongAnswer("Output is incorrect".into()), 23 | WrongAnswer("Output is incorrect".into()), 24 | WrongAnswer("Output is incorrect".into()), 25 | ], 26 | ); 27 | } 28 | 29 | #[test] 30 | fn without_st_local() { 31 | better_panic::install(); 32 | 33 | without_st(TestInterface::run_local("without_st")); 34 | } 35 | 36 | #[test] 37 | fn without_st_remote() { 38 | better_panic::install(); 39 | 40 | without_st(TestInterface::run_remote("without_st")); 41 | } 42 | -------------------------------------------------------------------------------- /tests/deps.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | fn classic(test: TestInterface) { 5 | test.success() 6 | .time_limit(1.0) 7 | .memory_limit(64) 8 | .max_score(100.0) 9 | .subtask_scores(vec![30.0, 30.0, 40.0]) 10 | .must_compile("sol-.cpp") 11 | .must_compile("sol-0.cpp") 12 | .must_compile("sol-1.cpp") 13 | .must_compile("sol-2.cpp") 14 | .must_compile("sol-01.cpp") 15 | .must_compile("sol-02.cpp") 16 | .must_compile("sol-12.cpp") 17 | .must_compile("sol-012.cpp") 18 | .solution_score("sol-.cpp", vec![0.0, 0.0, 0.0]) 19 | .solution_score("sol-0.cpp", vec![0.0, 0.0, 0.0]) 20 | .solution_score("sol-1.cpp", vec![0.0, 30.0, 0.0]) 21 | .solution_score("sol-2.cpp", vec![0.0, 0.0, 0.0]) 22 | .solution_score("sol-01.cpp", vec![30.0, 30.0, 0.0]) 23 | .solution_score("sol-02.cpp", vec![0.0, 0.0, 0.0]) 24 | .solution_score("sol-12.cpp", vec![0.0, 30.0, 0.0]) 25 | .solution_score("sol-012.cpp", vec![30.0, 30.0, 40.0]); 26 | } 27 | 28 | #[test] 29 | fn deps_local() { 30 | better_panic::install(); 31 | classic(TestInterface::run_local("deps")); 32 | } 33 | 34 | #[test] 35 | fn deps_remote() { 36 | better_panic::install(); 37 | classic(TestInterface::run_remote("deps")); 38 | } 39 | -------------------------------------------------------------------------------- /task-maker-format/src/ioi/format/italian_yaml/GEN.pest: -------------------------------------------------------------------------------- 1 | // Grammar of the gen/GEN file format: 2 | // It's line based, each line can be one of the following types: 3 | // * comment: # followed by a space, followed by anything till end of line 4 | // * subtask: #ST: XX where XX is a positive integer 5 | // * subtask_name: #STNAME: XX where XX is a string 6 | // * subtask_dep: #STDEP: XX where XX is a subtask name 7 | // * copy: #COPY: XX where XX is a path 8 | // * command: a list of arguments not starting with # 9 | 10 | whitespace = _{ " " | "\t" } 11 | non_newline = _{ !NEWLINE ~ ANY } 12 | number = { ASCII_DIGIT+ } 13 | 14 | word = { (!(whitespace | "#") ~ non_newline)+ } 15 | spaces = _{ whitespace+ } 16 | 17 | comment = { "#" ~ spaces ~ non_newline* | "#" } 18 | risky_comment = { "#" ~ non_newline* | "#" } 19 | subtask = { "#ST:" ~ whitespace* ~ number ~ whitespace* } 20 | subtask_name = { "#STNAME:" ~ whitespace* ~ word ~ whitespace* } 21 | subtask_dep = { "#STDEP:" ~ (whitespace* ~ word)* ~ whitespace* } 22 | copy = { "#COPY:" ~ whitespace* ~ word ~ whitespace* } 23 | command = { !"#" ~ whitespace* ~ word ~ (spaces ~ word)* ~ whitespace* } 24 | empty = { whitespace* } 25 | 26 | line = { (subtask | subtask_name | subtask_dep | copy | comment | command | empty) ~ risky_comment? } 27 | 28 | // allow the last line to be without the NEWLINE 29 | file = { SOI ~ (line ~ NEWLINE)* ~ line ~ NEWLINE? ~ EOI } 30 | -------------------------------------------------------------------------------- /task-maker-format/src/ioi/format/italian_yaml/cases.gen.pest: -------------------------------------------------------------------------------- 1 | whitespace = _{ " " | "\t" } 2 | non_newline = _{ !NEWLINE ~ ANY } 3 | 4 | number = { ("-" | "+")? ~ASCII_DIGIT+ } 5 | float = { ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? } 6 | word = { (!(whitespace | "#") ~ non_newline)+ } 7 | rest = { (!"#" ~ non_newline)+ } 8 | variable = { "$" ~ word } 9 | variables_list = { (whitespace+ ~ word)* } 10 | comp_operator = { "<=" | "<" | ">=" | ">" | "=" } 11 | 12 | comment = { "#" ~ whitespace* ~ non_newline* } 13 | GEN = { "GEN" ~ whitespace+ ~ word ~ (whitespace+ ~ word ~ variables_list)? ~ whitespace* } 14 | VAL = { "VAL" ~ whitespace+ ~ word ~ (whitespace+ ~ word ~ variables_list)? ~ whitespace* } 15 | CONSTRAINT = { "CONSTRAINT" ~ whitespace+ ~ (number | variable) ~ (whitespace* ~ comp_operator ~ whitespace* ~ (number | variable))+ ~ whitespace* } 16 | SUBTASK = { "SUBTASK" ~ whitespace+ ~ float ~ (whitespace+ ~ rest)? ~ whitespace* } 17 | STDEP = { "STDEP" ~ (whitespace+ ~ word)* ~ whitespace* } 18 | COPY = { "COPY" ~ whitespace+ ~ rest ~ whitespace* } 19 | RUN = { "RUN" ~ whitespace+ ~ word ~ whitespace+ ~ rest } 20 | 21 | command = { ":" ~ whitespace* ~ (GEN | VAL | CONSTRAINT | SUBTASK | STDEP | COPY | RUN) } 22 | testcase = { !("#"|":") ~ rest } 23 | empty = { whitespace* } 24 | 25 | line = { (comment | command | testcase | empty) ~ comment? } 26 | 27 | file = { SOI ~ (line ~ NEWLINE)* ~ line ~ NEWLINE? ~ EOI } 28 | -------------------------------------------------------------------------------- /task-maker-lang/src/languages/javascript.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use task_maker_dag::*; 4 | 5 | use crate::language::Language; 6 | 7 | /// The JavaScript language. 8 | #[derive(Debug)] 9 | pub struct LanguageJS; 10 | 11 | impl LanguageJS { 12 | /// Make a new LanguageJS 13 | pub fn new() -> LanguageJS { 14 | LanguageJS 15 | } 16 | } 17 | 18 | impl Language for LanguageJS { 19 | fn name(&self) -> &'static str { 20 | "JavaScript" 21 | } 22 | 23 | fn extensions(&self) -> Vec<&'static str> { 24 | vec!["js", "cjs", "mjs"] 25 | } 26 | 27 | fn need_compilation(&self) -> bool { 28 | false 29 | } 30 | 31 | fn inline_comment_prefix(&self) -> Option<&'static str> { 32 | Some("//") 33 | } 34 | 35 | fn runtime_command(&self, _path: &Path, _write_to: Option<&Path>) -> ExecutionCommand { 36 | ExecutionCommand::system("node") 37 | } 38 | 39 | fn runtime_args( 40 | &self, 41 | path: &Path, 42 | write_to: Option<&Path>, 43 | mut args: Vec, 44 | ) -> Vec { 45 | args.push( 46 | self.executable_name(path, write_to) 47 | .to_string_lossy() 48 | .to_string(), 49 | ); 50 | args 51 | } 52 | 53 | fn custom_limits(&self, limits: &mut ExecutionLimits) { 54 | limits.allow_multiprocess(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/tasks/with_constraints_py/testo/english.tex: -------------------------------------------------------------------------------- 1 | \usepackage{xcolor} 2 | \usepackage{afterpage} 3 | \usepackage{pifont,mdframed} 4 | \usepackage[bottom]{footmisc} 5 | \usepackage{caption} 6 | \usepackage{minted} 7 | 8 | \newcommand{\inputfile}{\texttt{stdin}} 9 | \newcommand{\outputfile}{\texttt{stdout}} 10 | \makeatletter 11 | \renewcommand{\this@inputfilename}{\texttt{stdin}} 12 | \renewcommand{\this@outputfilename}{\texttt{stdout}} 13 | \makeatother 14 | 15 | % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % 16 | % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % 17 | 18 | Test statement. 19 | 20 | % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % 21 | % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % 22 | 23 | \Constraints 24 | 25 | \begin{itemize}[nolistsep, itemsep=2mm] 26 | \item $1 \le N \le \constraints{MAXN}$. 27 | \item The graph is disconnected. 28 | \end{itemize} 29 | 30 | % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % 31 | % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % 32 | 33 | \Scoring 34 | 35 | \begin{itemize}[nolistsep, itemsep=2mm] 36 | \item \subtask Sample cases. 37 | \item \subtask $N \le \stconstraints{MAXN}$. 38 | \item \subtask No additional constraints. 39 | \end{itemize} 40 | -------------------------------------------------------------------------------- /tools/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | LABEL maintainer="Edoardo Morassutto " 3 | 4 | ARG UID=1000 5 | ARG GID=1000 6 | 7 | # install dependencies 8 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yy \ 9 | asymptote \ 10 | build-essential \ 11 | fpc \ 12 | latexmk \ 13 | libseccomp-dev \ 14 | python \ 15 | python-sortedcontainers \ 16 | python3 \ 17 | python3-sortedcontainers \ 18 | texlive \ 19 | texlive-latex-extra \ 20 | wget \ 21 | && rm -rf /var/lib/apt/lists/* 22 | 23 | # task-maker-rust version (required) 24 | ARG TM_VERSION 25 | 26 | # install task-maker-rust 27 | RUN (test -n "$TM_VERSION" || (echo "Please use --build-arg TM_VERSION=0.3.X" >&2 && exit 1)) \ 28 | && wget https://github.com/olimpiadi-informatica/task-maker-rust/releases/download/v${TM_VERSION}/task-maker-rust_${TM_VERSION}_amd64.deb \ 29 | && dpkg -i task-maker-rust_${TM_VERSION}_amd64.deb \ 30 | && rm task-maker-rust_${TM_VERSION}_amd64.deb 31 | 32 | # drop root privileges 33 | RUN groupadd -g $GID user \ 34 | && useradd -m -g $GID -u $UID user 35 | USER user 36 | 37 | # server-client port 38 | EXPOSE 27182 39 | # server-worker port 40 | EXPOSE 27183 41 | 42 | # start task-maker-rust server and worker 43 | ADD entrypoint.sh healthcheck.sh / 44 | CMD /entrypoint.sh 45 | 46 | # check the status of the server and the workers 47 | HEALTHCHECK --interval=5s CMD /healthcheck.sh 48 | -------------------------------------------------------------------------------- /tests/without_gen.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | use task_maker_format::ioi::TestcaseEvaluationStatus::*; 5 | 6 | fn without_gen(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![100.0]) 12 | .must_compile("soluzione.cpp") 13 | .must_compile("wa.cpp") 14 | .must_compile("wrong_file.cpp") 15 | .solution_score("soluzione.cpp", vec![100.0]) 16 | .solution_score("wa.cpp", vec![50.0]) 17 | .solution_score("wrong_file.cpp", vec![0.0]) 18 | .solution_statuses("soluzione.cpp", vec![Accepted("Output is correct".into())]) 19 | .solution_statuses( 20 | "wa.cpp", 21 | vec![ 22 | Accepted("Output is correct".into()), 23 | Accepted("Output is correct".into()), 24 | WrongAnswer("Output is incorrect".into()), 25 | WrongAnswer("Output is incorrect".into()), 26 | ], 27 | ) 28 | .solution_statuses( 29 | "wrong_file.cpp", 30 | vec![WrongAnswer("Output is incorrect".into())], 31 | ); 32 | } 33 | 34 | #[test] 35 | fn without_gen_local() { 36 | better_panic::install(); 37 | 38 | without_gen(TestInterface::run_local("without_gen")); 39 | } 40 | 41 | #[test] 42 | fn without_gen_remote() { 43 | better_panic::install(); 44 | 45 | without_gen(TestInterface::run_remote("without_gen")); 46 | } 47 | -------------------------------------------------------------------------------- /task-maker-lang/src/languages/shell.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use task_maker_dag::ExecutionLimits; 4 | 5 | use crate::language::Language; 6 | 7 | /// The Shell language 8 | #[derive(Debug)] 9 | pub struct LanguageShell; 10 | 11 | impl LanguageShell { 12 | /// Make a new LanguageShell using the specified version. 13 | pub fn new() -> LanguageShell { 14 | LanguageShell {} 15 | } 16 | } 17 | 18 | impl Language for LanguageShell { 19 | fn name(&self) -> &'static str { 20 | "Shell" 21 | } 22 | 23 | fn extensions(&self) -> Vec<&'static str> { 24 | vec!["sh"] 25 | } 26 | 27 | fn need_compilation(&self) -> bool { 28 | false 29 | } 30 | 31 | fn inline_comment_prefix(&self) -> Option<&'static str> { 32 | Some("#") 33 | } 34 | 35 | fn custom_limits(&self, limits: &mut ExecutionLimits) { 36 | limits.allow_multiprocess(); 37 | } 38 | 39 | fn executable_name(&self, path: &Path, _write_to: Option<&Path>) -> PathBuf { 40 | // force the original extension for the file name 41 | PathBuf::from(path.file_name().expect("Invalid file name")) 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | 49 | #[test] 50 | fn test_allow_fork() { 51 | let lang = LanguageShell::new(); 52 | let mut limits = ExecutionLimits::unrestricted(); 53 | limits.block_multiprocess(); 54 | lang.custom_limits(&mut limits); 55 | assert!(limits.allow_multiprocess); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /data/bad_outputs/lorem_ipsum.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam facilisis facilisis neque, in dictum risus lacinia eget. Quisque laoreet justo et quam imperdiet, nec maximus velit interdum. Aliquam commodo facilisis quam sed fringilla. In consequat elit et libero rhoncus, semper fringilla elit rutrum. Maecenas id dui non arcu aliquam iaculis nec eget leo. In maximus vulputate diam sit amet aliquet. Curabitur fermentum fringilla metus, vitae mattis tortor vehicula sit amet. Nam suscipit leo in neque faucibus, et pellentesque nibh mattis. Mauris sit amet faucibus ipsum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Mauris varius orci sit amet risus vestibulum facilisis. Fusce in aliquam leo. Ut nec condimentum augue, id ultricies ipsum. Fusce dapibus convallis neque. Proin est sapien, commodo hendrerit est sed, faucibus porttitor dolor. In vitae leo eget velit semper condimentum eget eget mi. 2 | 3 | Donec laoreet ullamcorper ligula, vitae maximus metus tristique in. Pellentesque tempus porta eros ut malesuada. Aliquam at metus gravida, accumsan ligula non, consequat sem. Nunc quis est ut tortor fermentum malesuada non eu ante. Duis suscipit dictum orci id gravida. Nam facilisis egestas urna in fringilla. Aliquam sed sollicitudin erat. Morbi at dui molestie, ultrices dolor id, mollis mi. Nam nunc velit, dignissim a magna non, porttitor fermentum mauris. Fusce sodales egestas lacus non scelerisque. Fusce et nisl a neque finibus ultricies. Fusce ex ligula, imperdiet sit amet dictum et, molestie sit amet eros. Cras ut velit diam. Nam sollicitudin odio sed ipsum accumsan, eget placerat elit aliquet. 4 | -------------------------------------------------------------------------------- /task-maker-exec/src/executors/mod.rs: -------------------------------------------------------------------------------- 1 | //! The supported executors. 2 | //! 3 | //! An executor is something that implements the communication protocol for evaluating DAGs. 4 | //! For example the `LocalExecutor` is an implementation of a thread-based executor that will listen 5 | //! on the client channel and will spawn a list of local workers. 6 | //! 7 | //! # Example 8 | //! 9 | //! ``` 10 | //! use task_maker_store::FileStore; 11 | //! use task_maker_exec::executors::LocalExecutor; 12 | //! use std::sync::{Arc, Mutex, mpsc::channel}; 13 | //! # use std::thread; 14 | //! # use tempfile::TempDir; 15 | //! use task_maker_cache::Cache; 16 | //! use task_maker_exec::SuccessSandboxRunner; 17 | //! use ductile::new_local_channel; 18 | //! 19 | //! # let tmpdir = TempDir::new().unwrap(); 20 | //! # let path = tmpdir.path(); 21 | //! let store = FileStore::new(path, 1000, 1000).unwrap(); 22 | //! let cache = Cache::new(path).unwrap(); 23 | //! let num_cores = 4; 24 | //! # let sandbox_runner = Arc::new(SuccessSandboxRunner::default()); 25 | //! let mut executor = LocalExecutor::new(Arc::new(store), cache, num_cores, path, sandbox_runner).expect("failed to start executor"); 26 | //! // the communication channels for the client 27 | //! let (tx, rx_remote) = new_local_channel(); 28 | //! let (tx_remote, rx) = new_local_channel(); 29 | //! 30 | //! # let server = thread::spawn(move || { 31 | //! executor.evaluate(tx_remote, rx_remote).unwrap(); // this will block!! 32 | //! # }); 33 | //! # drop(tx); 34 | //! # drop(rx); 35 | //! # server.join().unwrap(); 36 | //! ``` 37 | 38 | mod local_executor; 39 | mod remote_executor; 40 | 41 | pub use local_executor::*; 42 | pub use remote_executor::*; 43 | -------------------------------------------------------------------------------- /tests/with_stdio.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::TestInterface; 3 | 4 | use task_maker_format::ioi::TestcaseEvaluationStatus::*; 5 | 6 | fn with_stdio(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![100.0]) 12 | .must_compile("soluzione.cpp") 13 | .must_compile("wa.cpp") 14 | .must_compile("wrong_file.cpp") 15 | .not_compiled("noop.py") 16 | .solution_score("soluzione.cpp", vec![100.0]) 17 | .solution_score("noop.py", vec![0.0]) 18 | .solution_score("wa.cpp", vec![50.0]) 19 | .solution_score("wrong_file.cpp", vec![0.0]) 20 | .solution_statuses("soluzione.cpp", vec![Accepted("Output is correct".into())]) 21 | .solution_statuses( 22 | "wa.cpp", 23 | vec![ 24 | Accepted("Output is correct".into()), 25 | Accepted("Output is correct".into()), 26 | WrongAnswer("Output is incorrect".into()), 27 | WrongAnswer("Output is incorrect".into()), 28 | ], 29 | ) 30 | .solution_statuses( 31 | "wrong_file.cpp", 32 | vec![WrongAnswer("Output is incorrect".into())], 33 | ) 34 | .solution_statuses("noop.py", vec![WrongAnswer("Output is incorrect".into())]); 35 | } 36 | 37 | #[test] 38 | fn with_stdio_local() { 39 | better_panic::install(); 40 | 41 | with_stdio(TestInterface::run_local("with_stdio")); 42 | } 43 | 44 | #[test] 45 | fn with_stdio_remote() { 46 | better_panic::install(); 47 | 48 | with_stdio(TestInterface::run_remote("with_stdio")); 49 | } 50 | -------------------------------------------------------------------------------- /tests/communication_stdio.rs: -------------------------------------------------------------------------------- 1 | use task_maker_format::ioi::TestcaseEvaluationStatus::*; 2 | 3 | mod common; 4 | use common::TestInterface; 5 | 6 | fn communication_stdio(test: TestInterface) { 7 | test.success() 8 | .time_limit(1.0) 9 | .memory_limit(64) 10 | .max_score(100.0) 11 | .subtask_scores(vec![100.0]) 12 | .must_compile("solution.cpp") 13 | .must_compile("solution.c") 14 | .must_compile("no_output.cpp") 15 | .must_compile("wrong.cpp") 16 | .must_compile("tle.cpp") 17 | .solution_score("solution.cpp", vec![100.0]) 18 | .solution_score("solution.c", vec![100.0]) 19 | .solution_score("solution.py", vec![100.0]) 20 | .solution_score("no_output.cpp", vec![0.0]) 21 | .solution_score("wrong.cpp", vec![0.0]) 22 | .solution_score("tle.cpp", vec![0.0]) 23 | .solution_statuses("solution.cpp", vec![Accepted("Ok!".into())]) 24 | .solution_statuses("solution.c", vec![Accepted("Ok!".into())]) 25 | .solution_statuses("solution.py", vec![Accepted("Ok!".into())]) 26 | .solution_statuses("no_output.cpp", vec![WrongAnswer("Ko!".into())]) 27 | .solution_statuses("wrong.cpp", vec![WrongAnswer("Ko!".into())]) 28 | .solution_statuses("tle.cpp", vec![TimeLimitExceeded]) 29 | .file_exists("check/manager"); 30 | } 31 | 32 | #[test] 33 | fn communication_stdio_local() { 34 | better_panic::install(); 35 | 36 | communication_stdio(TestInterface::run_local("communication_stdio")); 37 | } 38 | 39 | #[test] 40 | fn communication_stdio_remote() { 41 | better_panic::install(); 42 | 43 | communication_stdio(TestInterface::run_remote("communication_stdio")); 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | strategy: 9 | matrix: 10 | os: [ubuntu-22.04, macos-latest] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - uses: actions/checkout@v5 14 | 15 | - name: Install Deps 16 | if: startsWith(matrix.os, 'ubuntu') 17 | run: | 18 | sudo apt update 19 | sudo apt install -yy libseccomp-dev libssl-dev build-essential fpc 20 | 21 | - name: Install Rust 22 | uses: dtolnay/rust-toolchain@stable 23 | id: install-rust 24 | 25 | - uses: taiki-e/install-action@nextest 26 | 27 | - name: cargo nextest run (tests) 28 | env: 29 | RUST_BACKTRACE: 1 30 | run: cargo nextest run --workspace --no-fail-fast 31 | 32 | clippy: 33 | name: Clippy 34 | runs-on: ubuntu-22.04 35 | steps: 36 | - uses: actions/checkout@v5 37 | 38 | - name: Install Deps 39 | if: startsWith(matrix.os, 'ubuntu') 40 | run: | 41 | sudo apt update 42 | sudo apt install -yy libseccomp-dev libssl-dev build-essential fpc 43 | 44 | - name: Install Rust 45 | uses: dtolnay/rust-toolchain@stable 46 | id: install-rust 47 | 48 | - name: cargo clippy 49 | run: tools/clippy.sh 50 | 51 | rustfmt: 52 | name: Format 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v5 56 | 57 | - name: Install Rust 58 | uses: dtolnay/rust-toolchain@stable 59 | id: install-rust 60 | with: 61 | components: rustfmt 62 | 63 | - name: cargo fmt 64 | run: cargo fmt --all -- --check 65 | -------------------------------------------------------------------------------- /task-maker-exec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "task-maker-exec" 3 | version = "0.6.20" 4 | authors = ["Edoardo Morassutto "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | task-maker-dag = { path = "../task-maker-dag" } 9 | task-maker-store = { path = "../task-maker-store" } 10 | task-maker-cache = { path = "../task-maker-cache" } 11 | 12 | # Generic error utilities 13 | anyhow = { workspace = true, features = ["backtrace"] } 14 | thiserror = { workspace = true } 15 | # Serialization/Deserialization 16 | serde = { workspace = true, features = ["derive"] } 17 | serde_json = { workspace = true } 18 | bincode = { workspace = true } 19 | # Logging 20 | log = { workspace = true } 21 | # UUID generation 22 | uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] } 23 | # Temporary directory for sandboxes and FIFO directory 24 | tempfile = { workspace = true } 25 | # Resolve executable names in $PATH 26 | which = { workspace = true } 27 | # General iterator utilities 28 | itertools = { workspace = true } 29 | # defer! macro 30 | scopeguard = { workspace = true } 31 | # Sandbox 32 | tabox = { workspace = true } 33 | # For killing processes and making FIFOs 34 | nix = { workspace = true } 35 | # In-memory and remote channels 36 | ductile = { workspace = true } 37 | # Key Derivation Function from a password 38 | blake3 = { workspace = true } 39 | crossbeam-channel = { workspace = true } 40 | typst = "0.13.1" 41 | time = "0.3.41" 42 | typst-pdf = "0.13.1" 43 | reqwest = { version = "0.12.22", features = ["blocking"] } 44 | zune-inflate = "0.2.54" 45 | tar = "0.4.44" 46 | typst-assets = "0.14.1" 47 | 48 | [dev-dependencies] 49 | pretty_assertions = { workspace = true } 50 | env_logger = { workspace = true } 51 | -------------------------------------------------------------------------------- /data/statements/locale/dutch.tex: -------------------------------------------------------------------------------- 1 | %****************************************************************** 2 | % DUTCH * 3 | %****************************************************************** 4 | 5 | \def\kw@ProblemX#1{Probleem~#1} 6 | \def\kw@ProblemAuthor{Auteur:} 7 | \def\kw@ProblemDeveloper{Ontwikkelaar:} 8 | \def\kw@ProblemOrigin{Bron:} 9 | \def\kw@InputFileName{Input file:} 10 | \def\kw@OutputFileName{Output file:} 11 | \def\kw@TimeLimit{Tijdslimiet:} 12 | \def\kw@MemoryLimit{Geheugenlimiet:} 13 | \def\kw@Difficulty{Moeilijkheid} 14 | \def\kw@Feedback{Feedback:} 15 | \def\kw@stdin{standard input} 16 | \def\kw@stdout{standard output} 17 | \def\kw@Specification{Specificatie} 18 | \def\kw@Input{Invoer} 19 | \def\kw@Output{Uitvoer} 20 | \def\kw@Example{Voorbeeld} 21 | \def\kw@Examples{Voorbeelden} 22 | \def\kw@ExampleNotes{Let op} 23 | \def\kw@Explanation{Uitleg} 24 | \def\kw@Explanations{Uitleg} 25 | \def\kw@Illustration{Illustratie} 26 | \def\kw@Implementation{Implementatie} 27 | \def\kw@Scoring{Scoring} 28 | \def\kw@Note{Let op} 29 | \def\kw@Notes{Let op} 30 | \def\kw@Constraints{Randvoorwaarden} 31 | \def\kw@SubtaskOne{Subtask 1} 32 | \def\kw@SubtaskTwo{Subtask 2} 33 | \def\kw@SubtaskThree{Subtask 3} 34 | \def\kw@SubtaskFour{Subtask 4} 35 | \def\kw@SubtaskFive{Subtask 5} 36 | \def\kw@SubtaskSix{Subtask 6} 37 | \def\kw@SubtaskX#1{Subtask~#1} 38 | \def\kw@points{punten} 39 | \def\kw@PageXofY#1#2{Pagina\ #1\ van\ #2} 40 | \def\kw@notstated{niet vermeld} 41 | \def\kw@IntentionallyBlankPage{Deze pagina is bewust leeg} 42 | \def\kw@defaultinputname{test} 43 | \def\kw@defaultoutputname{antwoord} 44 | \def\kw@DayX#1{Dag~#1} 45 | \def\kw@revision{v} 46 | \def\kw@seconds{seconden} 47 | \def\kw@SampleGrader{Voorbeeld Grader} 48 | -------------------------------------------------------------------------------- /data/statements/locale/english.tex: -------------------------------------------------------------------------------- 1 | %****************************************************************** 2 | % ENGLISH * 3 | %****************************************************************** 4 | 5 | \def\kw@ProblemX#1{Problem~#1} 6 | \def\kw@ProblemAuthor{Author:} 7 | \def\kw@ProblemDeveloper{Developer:} 8 | \def\kw@ProblemOrigin{Origin:} 9 | \def\kw@InputFileName{Input file:} 10 | \def\kw@OutputFileName{Output file:} 11 | \def\kw@TimeLimit{Time limit:} 12 | \def\kw@MemoryLimit{Memory limit:} 13 | \def\kw@Difficulty{Difficulty} 14 | \def\kw@Feedback{Feedback:} 15 | \def\kw@stdin{standard input} 16 | \def\kw@stdout{standard output} 17 | \def\kw@Specification{Specification} 18 | \def\kw@Input{Input} 19 | \def\kw@Output{Output} 20 | \def\kw@Example{Example} 21 | \def\kw@Examples{Examples} 22 | \def\kw@ExampleNotes{Notes} 23 | \def\kw@Explanation{Explanation} 24 | \def\kw@Explanations{Explanations} 25 | \def\kw@Illustration{Illustration} 26 | \def\kw@Implementation{Implementation} 27 | \def\kw@Scoring{Scoring} 28 | \def\kw@Note{Note} 29 | \def\kw@Notes{Notes} 30 | \def\kw@Constraints{Constraints} 31 | \def\kw@SubtaskOne{Subtask 1} 32 | \def\kw@SubtaskTwo{Subtask 2} 33 | \def\kw@SubtaskThree{Subtask 3} 34 | \def\kw@SubtaskFour{Subtask 4} 35 | \def\kw@SubtaskFive{Subtask 5} 36 | \def\kw@SubtaskSix{Subtask 6} 37 | \def\kw@SubtaskX#1{Subtask~#1} 38 | \def\kw@points{points} 39 | \def\kw@PageXofY#1#2{Page\ #1\ of\ #2} 40 | \def\kw@notstated{not stated} 41 | \def\kw@IntentionallyBlankPage{This page is intentionally left blank} 42 | \def\kw@defaultinputname{test} 43 | \def\kw@defaultoutputname{answer} 44 | \def\kw@DayX#1{Day~#1} 45 | \def\kw@revision{v} 46 | \def\kw@seconds{seconds} 47 | \def\kw@SampleGrader{Sample Grader} 48 | \def\kw@Solution{Solution} 49 | -------------------------------------------------------------------------------- /data/statements/locale/french.tex: -------------------------------------------------------------------------------- 1 | %****************************************************************** 2 | % FRENCH * 3 | %****************************************************************** 4 | 5 | \def\kw@ProblemX#1{Problème~#1} 6 | \def\kw@ProblemAuthor{Auteur :} 7 | \def\kw@ProblemDeveloper{Developpeur :} 8 | \def\kw@ProblemOrigin{Origine :} 9 | \def\kw@InputFileName{Fichier d'entrée :} 10 | \def\kw@OutputFileName{Fichier de sortie :} 11 | \def\kw@TimeLimit{Limite de temps :} 12 | \def\kw@MemoryLimit{Limite de mémoire :} 13 | \def\kw@Difficulty{Difficulté} 14 | \def\kw@Feedback{Retour :} 15 | \def\kw@stdin{entrée standard} 16 | \def\kw@stdout{sortie standard} 17 | \def\kw@Specification{Spécification} 18 | \def\kw@Input{Entrée} 19 | \def\kw@Output{Sortie} 20 | \def\kw@Example{Exemple} 21 | \def\kw@Examples{Exemples} 22 | \def\kw@ExampleNotes{Notes} 23 | \def\kw@Explanation{Explication} 24 | \def\kw@Explanations{Explications} 25 | \def\kw@Illustration{Illustration} 26 | \def\kw@Implementation{Implémentation} 27 | \def\kw@Scoring{Score} 28 | \def\kw@Note{Note} 29 | \def\kw@Notes{Notes} 30 | \def\kw@Constraints{Contraintes} 31 | \def\kw@SubtaskOne{Sous-tâche 1} 32 | \def\kw@SubtaskTwo{Sous-tâche 2} 33 | \def\kw@SubtaskThree{Sous-tâche 3} 34 | \def\kw@SubtaskFour{Sous-tâche 4} 35 | \def\kw@SubtaskFive{Sous-tâche 5} 36 | \def\kw@SubtaskSix{Sous-tâche 6} 37 | \def\kw@SubtaskX#1{Sous-tâche~#1} 38 | \def\kw@points{points} 39 | \def\kw@PageXofY#1#2{Page\ #1\ de\ #2} 40 | \def\kw@notstated{non déclaré} 41 | \def\kw@IntentionallyBlankPage{Cette page est intentionnellement laissée vide} 42 | \def\kw@defaultinputname{test} 43 | \def\kw@defaultoutputname{answer} 44 | \def\kw@DayX#1{Jour~#1} 45 | \def\kw@revision{v} 46 | \def\kw@seconds{secondes} 47 | \def\kw@SampleGrader{Évaluateur} 48 | -------------------------------------------------------------------------------- /data/statements/locale/portuguese.tex: -------------------------------------------------------------------------------- 1 | %****************************************************************** 2 | % PORTUGUESE * 3 | %****************************************************************** 4 | 5 | \def\kw@ProblemX#1{Problema~#1} 6 | \def\kw@ProblemAuthor{Autor:} 7 | \def\kw@ProblemDeveloper{Desenvolvedor:} 8 | \def\kw@ProblemOrigin{Origem:} 9 | \def\kw@InputFileName{Ficheiro de input:} 10 | \def\kw@OutputFileName{Ficheiro de output:} 11 | \def\kw@TimeLimit{Limite de tempo:} 12 | \def\kw@MemoryLimit{Limite de memória:} 13 | \def\kw@Difficulty{Dificuldade} 14 | \def\kw@Feedback{Feedback:} 15 | \def\kw@stdin{standard input} 16 | \def\kw@stdout{standard output} 17 | \def\kw@Specification{Especificação} 18 | \def\kw@Input{Input} 19 | \def\kw@Output{Output} 20 | \def\kw@Example{Exemplo} 21 | \def\kw@Examples{Exemplos} 22 | \def\kw@ExampleNotes{Notas} 23 | \def\kw@Explanation{Explicação} 24 | \def\kw@Explanations{Explicações} 25 | \def\kw@Illustration{Ilustração} 26 | \def\kw@Implementation{Implementação} 27 | \def\kw@Scoring{Pontuação} 28 | \def\kw@Note{Nota} 29 | \def\kw@Notes{Notas} 30 | \def\kw@Constraints{Restrições} 31 | \def\kw@SubtaskOne{Subtarefa 1} 32 | \def\kw@SubtaskTwo{Subtarefa 2} 33 | \def\kw@SubtaskThree{Subtarefa 3} 34 | \def\kw@SubtaskFour{Subtarefa 4} 35 | \def\kw@SubtaskFive{Subtarefa 5} 36 | \def\kw@SubtaskSix{Subtarefa 6} 37 | \def\kw@SubtaskX#1{Subtarefa~#1} 38 | \def\kw@points{pontos} 39 | \def\kw@PageXofY#1#2{Página\ #1\ de\ #2} 40 | \def\kw@notstated{não indicado} 41 | \def\kw@IntentionallyBlankPage{Esta página foi intencionalmente deixada em branco} 42 | \def\kw@defaultinputname{teste} 43 | \def\kw@defaultoutputname{resposta} 44 | \def\kw@DayX#1{Dia~#1} 45 | \def\kw@revision{v} 46 | \def\kw@seconds{segundos} 47 | \def\kw@SampleGrader{Avaliador Padrão} 48 | -------------------------------------------------------------------------------- /data/statements/locale/german.tex: -------------------------------------------------------------------------------- 1 | %****************************************************************** 2 | % GERMAN * 3 | %****************************************************************** 4 | 5 | \def\kw@ProblemX#1{Aufgabe~#1} 6 | \def\kw@ProblemAuthor{Autor:} 7 | \def\kw@ProblemDeveloper{Developer:} 8 | \def\kw@ProblemOrigin{Herkunft:} 9 | \def\kw@InputFileName{Eingabedatei:} 10 | \def\kw@OutputFileName{Ausgabedatei:} 11 | \def\kw@TimeLimit{Zeitlimit:} 12 | \def\kw@MemoryLimit{Speicherlimit:} 13 | \def\kw@Difficulty{Schwierigkeitsgrad} 14 | \def\kw@Feedback{Feedback:} 15 | \def\kw@stdin{Standardeigabe} 16 | \def\kw@stdout{Standardausgabe} 17 | \def\kw@Specification{Spezifikation} 18 | \def\kw@Input{Eingabe} 19 | \def\kw@Output{Ausgabe} 20 | \def\kw@Example{Beispiel} 21 | \def\kw@Examples{Beispiele} 22 | \def\kw@ExampleNotes{Bemerkungen} 23 | \def\kw@Explanation{Erklärung} 24 | \def\kw@Explanations{Erklärungen} 25 | \def\kw@Illustration{Abbildung} 26 | \def\kw@Implementation{Implementierung} 27 | \def\kw@Scoring{Punktevergabe} 28 | \def\kw@Note{Bemerkung} 29 | \def\kw@Notes{Bemerkungen} 30 | \def\kw@Constraints{Einschränkungen} 31 | \def\kw@SubtaskOne{Teilaufgabe 1} 32 | \def\kw@SubtaskTwo{Teilaufgabe 2} 33 | \def\kw@SubtaskThree{Teilaufgabe 3} 34 | \def\kw@SubtaskFour{Teilaufgabe 4} 35 | \def\kw@SubtaskFive{Teilaufgabe 5} 36 | \def\kw@SubtaskSix{Teilaufgabe 6} 37 | \def\kw@SubtaskX#1{Teilaufgabe~#1} 38 | \def\kw@points{Punkte} 39 | \def\kw@PageXofY#1#2{Seite\ #1\ von\ #2} 40 | \def\kw@notstated{not stated} 41 | \def\kw@IntentionallyBlankPage{Diese Seite ist absichtlich leer gelassen} 42 | \def\kw@defaultinputname{Test} 43 | \def\kw@defaultoutputname{Antwort} 44 | \def\kw@DayX#1{Tag~#1} 45 | \def\kw@revision{v} 46 | \def\kw@seconds{Sekunden} 47 | \def\kw@SampleGrader{Beispielgrader} 48 | -------------------------------------------------------------------------------- /data/statements/locale/hungarian.tex: -------------------------------------------------------------------------------- 1 | %****************************************************************** 2 | % HUNGARIAN * 3 | %****************************************************************** 4 | 5 | \def\kw@ProblemX#1{#1.~Feladat} 6 | \def\kw@ProblemAuthor{Szerző:} 7 | \def\kw@ProblemDeveloper{Kidolgozta:} 8 | \def\kw@ProblemOrigin{Forrás:} 9 | \def\kw@InputFileName{Bemeneti fájl:} 10 | \def\kw@OutputFileName{Kimeneti fájl:} 11 | \def\kw@TimeLimit{Időlimit:} 12 | \def\kw@MemoryLimit{Memória limit:} 13 | \def\kw@Difficulty{Nehézségi szint:} 14 | \def\kw@Feedback{Visszajelzés:} 15 | \def\kw@stdin{standard bemenet} 16 | \def\kw@stdout{standard kimenet} 17 | \def\kw@Specification{Specifikáció} 18 | \def\kw@Input{Bemenet} 19 | \def\kw@Output{Kimenet} 20 | \def\kw@Example{Példa} 21 | \def\kw@Examples{Példák} 22 | \def\kw@ExampleNotes{Megjegyzések} 23 | \def\kw@Explanation{Magyarázat} 24 | \def\kw@Explanations{Magyarázat} 25 | \def\kw@Illustration{Szemléltetés} 26 | \def\kw@Implementation{Implementáció} 27 | \def\kw@Scoring{Pontozás} 28 | \def\kw@Note{Megjegyzés} 29 | \def\kw@Notes{Megjegyzések} 30 | \def\kw@Constraints{Korlátok} 31 | \def\kw@SubtaskOne{1. Részfeladat} 32 | \def\kw@SubtaskTwo{2. Részfeladat} 33 | \def\kw@SubtaskThree{3. Részfeladat} 34 | \def\kw@SubtaskFour{4. Részfeladat} 35 | \def\kw@SubtaskFive{5. Részfeladat} 36 | \def\kw@SubtaskSix{6. Részfeladat} 37 | \def\kw@SubtaskX#1{#1.~Részfeladat} 38 | \def\kw@points{pont} 39 | \def\kw@PageXofY#1#2{#1\ /\ #2. oldal} 40 | \def\kw@notstated{nincs tisztázva} 41 | \def\kw@IntentionallyBlankPage{Ez az oldal szándékosan maradt üresen} 42 | \def\kw@defaultinputname{teszteset} 43 | \def\kw@defaultoutputname{válasz} 44 | \def\kw@DayX#1{#1.~Nap} 45 | \def\kw@revision{v} 46 | \def\kw@seconds{másodperc} 47 | \def\kw@SampleGrader{Minta Értékelő} 48 | \def\kw@Solution{Megoldás} 49 | -------------------------------------------------------------------------------- /data/statements/locale/italian.tex: -------------------------------------------------------------------------------- 1 | %****************************************************************** 2 | % ITALIAN * 3 | %****************************************************************** 4 | 5 | \def\kw@ProblemX#1{Problema~#1} 6 | \def\kw@ProblemAuthor{Autore:} 7 | \def\kw@ProblemDeveloper{Developer:} 8 | \def\kw@ProblemOrigin{Origine:} 9 | \def\kw@InputFileName{File di input:} 10 | \def\kw@OutputFileName{File di output:} 11 | \def\kw@TimeLimit{Limite di tempo:} 12 | \def\kw@MemoryLimit{Limite di memoria:} 13 | \def\kw@Difficulty{Difficolt\`{a}:} 14 | \def\kw@Feedback{Feedback:} 15 | \def\kw@stdin{standard input} 16 | \def\kw@stdout{standard output} 17 | \def\kw@Specification{Descrizione} 18 | \def\kw@Input{Dati di input} 19 | \def\kw@Output{Dati di output} 20 | \def\kw@Example{Esempio di input/output} 21 | \def\kw@Examples{Esempi di input/output} 22 | \def\kw@ExampleNotes{Note} 23 | \def\kw@Explanation{Spiegazione} 24 | \def\kw@Explanations{Spiegazioni} 25 | \def\kw@Illustration{Illustrazione} 26 | \def\kw@Implementation{Implementazione} 27 | \def\kw@Scoring{Assegnazione del punteggio} 28 | \def\kw@Note{Note} 29 | \def\kw@Notes{Note} 30 | \def\kw@Constraints{Assunzioni} 31 | \def\kw@SubtaskOne{Subtask 1} 32 | \def\kw@SubtaskTwo{Subtask 2} 33 | \def\kw@SubtaskThree{Subtask 3} 34 | \def\kw@SubtaskFour{Subtask 4} 35 | \def\kw@SubtaskFive{Subtask 5} 36 | \def\kw@SubtaskSix{Subtask 6} 37 | \def\kw@SubtaskX#1{Subtask~#1} 38 | \def\kw@points{punti} 39 | \def\kw@PageXofY#1#2{Pagina\ #1\ di\ #2} 40 | \def\kw@notstated{non specificato} 41 | \def\kw@IntentionallyBlankPage{pagina intenzionalmente vuota} 42 | \def\kw@defaultinputname{test} 43 | \def\kw@defaultoutputname{risposta} 44 | \def\kw@DayX#1{Giorno~#1} 45 | \def\kw@revision{v} 46 | \def\kw@seconds{secondi} 47 | \def\kw@Solution{Soluzione} 48 | \def\kw@SampleGrader{Grader di prova} 49 | -------------------------------------------------------------------------------- /src/tools/server.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::{Context, Error}; 4 | use clap::Parser; 5 | 6 | use task_maker_cache::Cache; 7 | use task_maker_exec::executors::RemoteExecutor; 8 | use task_maker_store::FileStore; 9 | 10 | use crate::StorageOpt; 11 | 12 | #[derive(Parser, Debug, Clone)] 13 | pub struct ServerOpt { 14 | /// Address to bind the server on for listening for the clients 15 | #[clap(default_value = "0.0.0.0:27182")] 16 | pub client_addr: String, 17 | 18 | /// Address to bind the server on for listening for the workers 19 | #[clap(default_value = "0.0.0.0:27183")] 20 | pub worker_addr: String, 21 | 22 | /// Password for the connection of the clients 23 | #[clap(long = "client-password")] 24 | pub client_password: Option, 25 | 26 | /// Password for the connection of the workers 27 | #[clap(long = "worker-password")] 28 | pub worker_password: Option, 29 | 30 | #[clap(flatten, next_help_heading = Some("STORAGE"))] 31 | pub storage: StorageOpt, 32 | } 33 | 34 | /// Entry point for the server. 35 | pub fn main_server(opt: ServerOpt) -> Result<(), Error> { 36 | // setup the executor 37 | let store_path = opt.storage.store_dir(); 38 | let file_store = Arc::new( 39 | FileStore::new( 40 | store_path.join("store"), 41 | opt.storage.max_cache * 1024 * 1024, 42 | opt.storage.min_cache * 1024 * 1024, 43 | ) 44 | .context("Cannot create the file store")?, 45 | ); 46 | let cache = Cache::new(store_path.join("cache")).context("Cannot create the cache")?; 47 | 48 | let remote_executor = RemoteExecutor::new(file_store); 49 | 50 | remote_executor.start( 51 | &opt.client_addr, 52 | &opt.worker_addr, 53 | opt.client_password, 54 | opt.worker_password, 55 | cache, 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /tools/vim/syntax/cases_gen.vim: -------------------------------------------------------------------------------- 1 | " Quit when a syntax file was already loaded. 2 | if exists('b:current_syntax') | finish | endif 3 | 4 | syntax match GenName /.\+/ contained 5 | syntax match GenParams /.\+/ contained 6 | syntax match GenExe /[^ \t]\+/ contained 7 | syntax match GenNameGV /\i\+/ contained nextgroup=GenExe skipwhite 8 | syntax match GenRunV /\i\+/ contained nextgroup=GenParams skipwhite 9 | syntax match GenScore /\d\+/ contained nextgroup=GenName skipwhite 10 | syntax match GenNumber /\d\+/ contained 11 | syntax match GenVariable /\$\i\+/ 12 | syntax match GenDollar /\$\$/ 13 | syntax keyword GenGeneratorK contained GEN VAL nextgroup=GenNameGV skipwhite 14 | syntax keyword GenRunK contained RUN nextgroup=GenRunV skipwhite 15 | syntax keyword GenCopyK contained COPY nextgroup=GenExe skipwhite 16 | syntax keyword GenSubtaskK contained SUBTASK nextgroup=GenScore skipwhite 17 | syntax keyword GenSubtaskK contained STDEP skipwhite 18 | syntax keyword GenConstraintK contained CONSTRAINT nextgroup=GenNumber 19 | syntax match GenCommand /^:.*/ contains=GenGeneratorK,GenSubtaskK,GenRunK,GenCopyK 20 | syntax match GenConstraintCommand /^:\s*CONSTRAINT.*/ contains=GenConstraintK,GenNumber,GenVariable 21 | syntax match GenComment /^#.*/ 22 | 23 | hi def link GenComment Comment 24 | hi def link GenScore Number 25 | hi def link GenNumber Number 26 | hi def link GenName Identifier 27 | hi def link GenNameGV Identifier 28 | hi def link GenRunV Identifier 29 | hi def link GenCommand Statement 30 | hi def link GenConstraintCommand Statement 31 | hi def link GenExe Special 32 | hi def link GenGeneratorK vimCommand 33 | hi def link GenRunK vimCommand 34 | hi def link GenCopyK vimCommand 35 | hi def link GenSubtaskK vimCommand 36 | hi def link GenConstraintK vimCommand 37 | hi def link GenVariable Macro 38 | hi def link GenDollar Macro 39 | 40 | let b:current_syntax = 'cases_gen' 41 | -------------------------------------------------------------------------------- /src/tools/gen_autocompletion.rs: -------------------------------------------------------------------------------- 1 | //! Tool that generates the autocompletion scripts inside the target/autocompletion directory. 2 | 3 | use std::fs::File; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use anyhow::{Context, Error}; 7 | use clap::{Command, CommandFactory, Parser}; 8 | use clap_complete::{Generator, Shell}; 9 | 10 | #[derive(Parser, Debug)] 11 | pub struct GenAutocompletionOpt { 12 | /// Where to write the autocompletion files 13 | #[clap(short = 't', long = "target")] 14 | pub target: Option, 15 | } 16 | 17 | pub fn main_get_autocompletion(opt: GenAutocompletionOpt) -> Result<(), Error> { 18 | let target = if let Some(target) = opt.target { 19 | target 20 | } else { 21 | Path::new(env!("CARGO_MANIFEST_DIR")).join("target/autocompletion") 22 | }; 23 | std::fs::create_dir_all(&target) 24 | .with_context(|| format!("Failed to create target dir: {}", target.display()))?; 25 | for shell in [ 26 | Shell::Bash, 27 | Shell::Zsh, 28 | Shell::Fish, 29 | Shell::Elvish, 30 | Shell::PowerShell, 31 | ] { 32 | generate(shell, crate::Opt::command(), &target, "task-maker-rust")?; 33 | generate( 34 | shell, 35 | crate::tools::opt::Opt::command(), 36 | &target, 37 | "task-maker-tools", 38 | )?; 39 | } 40 | Ok(()) 41 | } 42 | 43 | fn generate(shell: Shell, mut command: Command, target: &Path, name: &str) -> Result<(), Error> { 44 | let file_name = shell.file_name(name); 45 | let target = target.join(file_name); 46 | let mut file = File::create(&target).with_context(|| { 47 | format!( 48 | "Failed to create completion for {} at {}", 49 | shell, 50 | target.display() 51 | ) 52 | })?; 53 | clap_complete::generate(shell, &mut command, name, &mut file); 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /data/statements/locale/spanish.tex: -------------------------------------------------------------------------------- 1 | %****************************************************************** 2 | % SPANISH * 3 | %****************************************************************** 4 | 5 | \def\kw@ProblemX#1{Problema~#1} 6 | \def\kw@ProblemAuthor{Autor:} 7 | \def\kw@ProblemDeveloper{Desarrollador:} 8 | \def\kw@ProblemOrigin{Origen:} 9 | \def\kw@InputFileName{Archivo de entrada} 10 | \def\kw@OutputFileName{Archivo de salida} 11 | \def\kw@TimeLimit{L\'{i}mite de tiempo:} 12 | \def\kw@MemoryLimit{L\'{i}mite de memoria:} 13 | \def\kw@Difficulty{Dificultad:} 14 | \def\kw@Feedback{Feedback:} 15 | \def\kw@stdin{standard input} 16 | \def\kw@stdout{standard output} 17 | \def\kw@Specification{Descripci\'{o}n} 18 | \def\kw@Input{Datos de entrada} 19 | \def\kw@Output{Datos de salida} 20 | \def\kw@Example{Ejemplo de entrada/salida} 21 | \def\kw@Examples{Ejemplos de entrada/salida} 22 | \def\kw@ExampleNotes{Notas} 23 | \def\kw@Explanation{Explicaci\'{o}n} 24 | \def\kw@Explanations{Explicaciones} 25 | \def\kw@Illustration{Ilustraci\'{o}n} 26 | \def\kw@Implementation{Implementaci\'{o}n} 27 | \def\kw@Scoring{Puntuaci\'{o}n} 28 | \def\kw@Note{Notas} 29 | \def\kw@Notes{Notas} 30 | \def\kw@Constraints{Restricciones} 31 | \def\kw@SubtaskOne{Subtask 1} 32 | \def\kw@SubtaskTwo{Subtask 2} 33 | \def\kw@SubtaskThree{Subtask 3} 34 | \def\kw@SubtaskFour{Subtask 4} 35 | \def\kw@SubtaskFive{Subtask 5} 36 | \def\kw@SubtaskSix{Subtask 6} 37 | \def\kw@SubtaskX#1{Subtask~#1} 38 | \def\kw@points{puntos} 39 | \def\kw@PageXofY#1#2{P\'{a}gina\ #1\ de\ #2} 40 | \def\kw@notstated{no especificado} 41 | \def\kw@IntentionallyBlankPage{p\'{a}gina intencionalmente en blanco} 42 | \def\kw@defaultinputname{test} 43 | \def\kw@defaultoutputname{respuesta} 44 | \def\kw@DayX#1{D\'{i}a~#1} 45 | \def\kw@revision{v} 46 | \def\kw@seconds{segundos} 47 | \def\kw@Solution{Soluci\'{o}n} 48 | \def\kw@SampleGrader{Sample Grader} 49 | -------------------------------------------------------------------------------- /data/bad_outputs/very_long_int.txt: -------------------------------------------------------------------------------- 1 | Case #1: 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 2 | Case #1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111: 42 3 | -------------------------------------------------------------------------------- /task-maker-lang/src/languages/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet, VecDeque}; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use task_maker_dag::File; 5 | 6 | use crate::Dependency; 7 | 8 | pub(crate) mod c; 9 | pub(crate) mod cpp; 10 | pub(crate) mod csharp; 11 | pub(crate) mod javascript; 12 | pub(crate) mod pascal; 13 | pub(crate) mod python; 14 | pub(crate) mod rust; 15 | pub(crate) mod shell; 16 | 17 | /// Extract all the dependencies of a source file recursively. The file can include/import many 18 | /// other files, even cyclically. Each import is included only once in the result. 19 | /// 20 | /// ## Params 21 | /// 22 | /// - `path`: the root source file from which extract the dependencies. 23 | /// - `extract_imports`: a function called with the path of a source file, which returns a list of 24 | /// all the imported files (pair local_path, sandbox_path). 25 | pub(crate) fn find_dependencies(path: &Path, mut extract_imports: F) -> Vec 26 | where 27 | F: FnMut(&Path) -> Vec<(PathBuf, PathBuf)>, 28 | { 29 | let base = path.parent().expect("Invalid path"); 30 | let filename = path.file_name().expect("Invalid path"); 31 | let mut result = vec![]; 32 | let mut result_files = HashSet::new(); 33 | let mut pending = VecDeque::new(); 34 | let mut done = HashSet::new(); 35 | 36 | pending.push_back(path.to_owned()); 37 | while !pending.is_empty() { 38 | let path = pending.pop_front().unwrap(); 39 | done.insert(path.clone()); 40 | for (local_path, sandbox_path) in extract_imports(&path) { 41 | let local_path = base.join(local_path); 42 | if local_path.exists() 43 | && !done.contains(&local_path) 44 | && !result_files.contains(&sandbox_path) 45 | { 46 | result_files.insert(sandbox_path.clone()); 47 | pending.push_back(local_path.clone()); 48 | result.push(Dependency { 49 | file: File::new(format!( 50 | "Dependency {sandbox_path:?} at {local_path:?} of {filename:?}" 51 | )), 52 | local_path, 53 | sandbox_path, 54 | executable: false, 55 | }); 56 | } 57 | } 58 | } 59 | 60 | result 61 | } 62 | -------------------------------------------------------------------------------- /src/tools/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | use task_maker_rust::error::NiceError; 4 | use task_maker_rust::tools::add_solution_checks::main_add_solution_checks; 5 | use task_maker_rust::tools::booklet::main_booklet; 6 | use task_maker_rust::tools::clear::main_clear; 7 | use task_maker_rust::tools::copy_competition_files::copy_competition_files_main; 8 | use task_maker_rust::tools::export_booklet::main_export_booklet; 9 | use task_maker_rust::tools::export_solution_checks::main_export_solution_checks; 10 | use task_maker_rust::tools::find_bad_case::main_find_bad_case; 11 | use task_maker_rust::tools::fuzz_checker::main_fuzz_checker; 12 | use task_maker_rust::tools::gen_autocompletion::main_get_autocompletion; 13 | use task_maker_rust::tools::opt::{Opt, Tool}; 14 | use task_maker_rust::tools::reset::main_reset; 15 | use task_maker_rust::tools::sandbox::main_sandbox; 16 | use task_maker_rust::tools::server::main_server; 17 | use task_maker_rust::tools::task_info::main_task_info; 18 | use task_maker_rust::tools::terry_statement::main_terry_statement; 19 | use task_maker_rust::tools::worker::main_worker; 20 | 21 | fn main() { 22 | let base_opt = Opt::parse(); 23 | base_opt.logger.enable_log(); 24 | 25 | match base_opt.tool { 26 | Tool::Clear(opt) => main_clear(opt), 27 | Tool::GenAutocompletion(opt) => main_get_autocompletion(opt), 28 | Tool::Server(opt) => main_server(opt), 29 | Tool::Worker(opt) => main_worker(opt), 30 | Tool::Reset(opt) => main_reset(opt), 31 | Tool::Sandbox(opt) => main_sandbox(opt), 32 | Tool::TaskInfo(opt) => main_task_info(opt), 33 | Tool::Booklet(opt) => main_booklet(opt, base_opt.logger), 34 | Tool::TerryStatement(opt) => main_terry_statement(opt, base_opt.logger), 35 | Tool::CopyCompetitionFiles(opt) => copy_competition_files_main(opt, base_opt.logger), 36 | Tool::FuzzChecker(opt) => main_fuzz_checker(opt), 37 | Tool::FindBadCase(opt) => main_find_bad_case(opt), 38 | Tool::AddSolutionChecks(opt) => main_add_solution_checks(opt, base_opt.logger), 39 | Tool::ExportSolutionChecks(opt) => main_export_solution_checks(opt), 40 | Tool::ExportBooklet(opt) => main_export_booklet(opt), 41 | Tool::InternalSandbox => return task_maker_rust::main_sandbox(), 42 | } 43 | .nice_unwrap() 44 | } 45 | -------------------------------------------------------------------------------- /task-maker-lang/src/languages/csharp.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use task_maker_dag::*; 4 | 5 | use crate::language::{ 6 | CompilationSettings, CompiledLanguageBuilder, Language, SimpleCompiledLanguageBuilder, 7 | }; 8 | 9 | /// The C# language. 10 | #[derive(Debug)] 11 | pub struct LanguageCSharp; 12 | 13 | impl LanguageCSharp { 14 | /// Make a new LanguageCSharp 15 | pub fn new() -> LanguageCSharp { 16 | LanguageCSharp 17 | } 18 | } 19 | 20 | impl Language for LanguageCSharp { 21 | fn name(&self) -> &'static str { 22 | "C#" 23 | } 24 | 25 | fn extensions(&self) -> Vec<&'static str> { 26 | vec!["cs"] 27 | } 28 | 29 | fn need_compilation(&self) -> bool { 30 | true 31 | } 32 | 33 | fn inline_comment_prefix(&self) -> Option<&'static str> { 34 | Some("//") 35 | } 36 | 37 | fn compilation_builder( 38 | &self, 39 | source: &Path, 40 | settings: CompilationSettings, 41 | ) -> Option> { 42 | let mut metadata = SimpleCompiledLanguageBuilder::new( 43 | self, 44 | source, 45 | settings, 46 | ExecutionCommand::system("mcs"), 47 | ); 48 | let binary_name = metadata.binary_name.clone(); 49 | metadata 50 | .add_arg("-define:EVAL") 51 | .add_arg("-optimize+") 52 | .add_arg(format!("-out:{binary_name}")); 53 | 54 | metadata.callback(move |comp| { 55 | comp.limits_mut().add_extra_readable_dir("/etc/mono"); 56 | }); 57 | 58 | Some(Box::new(metadata)) 59 | } 60 | 61 | fn runtime_command(&self, _path: &Path, _write_to: Option<&Path>) -> ExecutionCommand { 62 | ExecutionCommand::system("mono") 63 | } 64 | 65 | fn runtime_args( 66 | &self, 67 | path: &Path, 68 | write_to: Option<&Path>, 69 | mut args: Vec, 70 | ) -> Vec { 71 | args.push( 72 | self.executable_name(path, write_to) 73 | .to_string_lossy() 74 | .to_string(), 75 | ); 76 | args 77 | } 78 | 79 | fn custom_limits(&self, limits: &mut ExecutionLimits) { 80 | limits 81 | .add_extra_readable_dir("/etc/mono") 82 | .mount_proc(true) 83 | .allow_multiprocess(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /task-maker-dag/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! DAG generation and resulting structures for [`Execution`](struct.Execution.html)s and 2 | //! [`File`](struct.File.html)s. 3 | //! 4 | //! A DAG is a series of executions linked together in a non-cyclical way. Every execution has a 5 | //! list of files as dependencies, when all of them are ready the execution can start. When an 6 | //! execution is run inside a sandbox by a worker (under some [`ExecutionLimits`](struct.ExecutionLimits.html)) 7 | //! it will produce some output files (including `stdout` and `stderr`). Those outputs can be used 8 | //! as inputs for the next executions. 9 | //! 10 | //! The sandbox should also be able to limit the available resources and measure the used ones, like 11 | //! the execution time and the used memory. 12 | //! 13 | //! When some events about the execution occur the client is notified via callbacks, the supported 14 | //! ones are: 15 | //! 16 | //! - the start of an execution; 17 | //! - the completion of an execution; 18 | //! - if an execution is skipped because a required file cannot be get (the execution which should 19 | //! have generated it has failed); 20 | //! - a file has been generated. 21 | //! 22 | //! # Example 23 | //! 24 | //! Creating a simple [`Execution`](struct.Execution.html) which will run `date` to get the current 25 | //! time. That command will print to `stdout` the current date, we capture it by calling 26 | //! [`stdout`](struct.Execution.html#method.stdout). We also bound some callbacks to the DAG, one 27 | //! for when the execution completes, and one for when the output is ready. 28 | //! ``` 29 | //! use task_maker_dag::{ExecutionDAG, Execution, ExecutionCommand}; 30 | //! 31 | //! let mut dag = ExecutionDAG::new(); 32 | //! let mut exec = Execution::new("Get the date", ExecutionCommand::system("date")); 33 | //! let exec_id = exec.uuid; 34 | //! let output = exec.stdout(); 35 | //! dag.add_execution(exec); 36 | //! dag.on_execution_done(&exec_id, |result| Ok(println!("Elapsed time: {} seconds", result.resources.cpu_time))); 37 | //! dag.get_file_content(&output, 1000, |date| Ok(println!("The date is: {}", std::str::from_utf8(&date).unwrap()))); 38 | //! ``` 39 | 40 | #![deny(missing_docs)] 41 | #![allow(clippy::upper_case_acronyms)] 42 | 43 | #[cfg(test)] 44 | #[macro_use] 45 | extern crate approx; 46 | 47 | mod dag; 48 | mod execution; 49 | mod execution_group; 50 | mod file; 51 | 52 | pub use dag::*; 53 | pub use execution::*; 54 | pub use execution_group::*; 55 | pub use file::*; 56 | -------------------------------------------------------------------------------- /task-maker-format/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "task-maker-format" 3 | version = "0.6.20" 4 | authors = ["Edoardo Morassutto "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | task-maker-dag = { path = "../task-maker-dag" } 9 | task-maker-lang = { path = "../task-maker-lang" } 10 | task-maker-exec = { path = "../task-maker-exec" } 11 | task-maker-diagnostics = { path = "../task-maker-diagnostics" } 12 | 13 | # Serialization/Deserialization 14 | serde = { workspace = true, features = ["derive"] } 15 | serde_json = { workspace = true } 16 | serde_yaml = { workspace = true } 17 | # Generic error utilities 18 | anyhow = { workspace = true, features = ["backtrace"] } 19 | # General iterator utilities 20 | itertools = { workspace = true } 21 | # Utilities for writing to the terminal with colors 22 | termcolor = { workspace = true } 23 | # Checks if the terminal supports the colors 24 | supports-color = { workspace = true } 25 | # Logging 26 | log = { workspace = true } 27 | # Globbing files 28 | glob = { workspace = true } 29 | # Text parser for parsing the gen/GEN file 30 | pest = { workspace = true } 31 | pest_derive = { workspace = true } 32 | # Curses UI 33 | ratatui = { workspace = true, features = ["termion"] } 34 | termion = { workspace = true } 35 | # Global constants 36 | lazy_static = { workspace = true } 37 | # Checking equalness between floats 38 | approx = { workspace = true } 39 | # Regular expressions 40 | regex = { workspace = true } 41 | # File templating (for building statement tex file) 42 | askama = { workspace = true } 43 | askama_derive = { workspace = true } 44 | # Detecting the content type of a file 45 | mime_guess = { workspace = true } 46 | # Geenrating random numbers (the seed in terry) 47 | fastrand = { workspace = true } 48 | # Split command line arguments 49 | shell-words = { workspace = true } 50 | # Nicer derive macros 51 | derivative = { workspace = true } 52 | # For sending ^C to the process 53 | nix = { workspace = true } 54 | # Unicode for subtask names 55 | unic = { workspace = true } 56 | # Wildcard match for subtask names. 57 | wildmatch = { workspace = true } 58 | # Plugin system for the sanity checks. 59 | inventory = { workspace = true } 60 | # For the plugin system. 61 | paste = { workspace = true } 62 | # Hashing function 63 | blake3 = { workspace = true } 64 | locale-codes = "0.3.0" 65 | 66 | [dev-dependencies] 67 | tempfile = { workspace = true } 68 | pretty_assertions = { workspace = true } 69 | # assert_that! macro 70 | speculoos = { workspace = true } 71 | -------------------------------------------------------------------------------- /src/tools/reset.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::os::unix::fs::PermissionsExt; 3 | 4 | use anyhow::{bail, Context, Error}; 5 | use clap::Parser; 6 | use walkdir::WalkDir; 7 | 8 | use crate::StorageOpt; 9 | 10 | #[derive(Parser, Debug, Clone)] 11 | pub struct ResetOpt { 12 | #[clap(flatten, next_help_heading = Some("STORAGE"))] 13 | pub storage: StorageOpt, 14 | } 15 | 16 | /// Handler of the `reset` tool. This tool will prompt the user with a warning message and read his 17 | /// confirmation before removing the storage directory. 18 | /// 19 | /// Note that because some sandbox directories are read-only it's required to chmod them before 20 | /// deleting the directory tree. 21 | pub fn main_reset(opt: ResetOpt) -> Result<(), Error> { 22 | let path = opt.storage.store_dir(); 23 | 24 | println!( 25 | "WARNING: you are going to wipe the internal storage of task-maker, doing so while \ 26 | running another instance of task-maker can affect the other instance." 27 | ); 28 | println!( 29 | "This will wipe the cache and all the temporary directories, the following \ 30 | directories will be removed:" 31 | ); 32 | println!(" - {}", path.display()); 33 | print!("Are you sure? (y/n) "); 34 | let _ = std::io::stdout().flush(); 35 | let mut line = String::new(); 36 | std::io::stdin() 37 | .read_line(&mut line) 38 | .context("Failed to read stdin")?; 39 | if line.trim().to_lowercase() != "y" { 40 | println!("Aborting..."); 41 | return Ok(()); 42 | } 43 | if !path.exists() { 44 | bail!("Path {} does not exist", path.display()); 45 | } 46 | 47 | println!("Removing {}...", path.display()); 48 | // first pass to make everything writable 49 | WalkDir::new(&path) 50 | .contents_first(false) 51 | .into_iter() 52 | .filter_entry(|e| { 53 | let path = e.path(); 54 | if path.is_dir() { 55 | let mut permisions = std::fs::metadata(path).unwrap().permissions(); 56 | permisions.set_mode(0o755); 57 | if let Err(e) = std::fs::set_permissions(path, permisions) { 58 | eprintln!("Failed to chmod 755 {}: {}", path.display(), e); 59 | } 60 | } 61 | true 62 | }) 63 | .last(); 64 | // second pass to remove everything 65 | if let Err(e) = std::fs::remove_dir_all(&path) { 66 | eprintln!("Failed to remove {}: {}", path.display(), e); 67 | } 68 | Ok(()) 69 | } 70 | -------------------------------------------------------------------------------- /tests/sandbox.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::{eval_dag, setup}; 3 | 4 | use task_maker_dag::{Execution, ExecutionCommand, ExecutionDAG, ExecutionGroup}; 5 | 6 | #[test] 7 | fn test_remove_output_file() { 8 | setup(); 9 | 10 | let mut dag = ExecutionDAG::new(); 11 | let mut exec = Execution::new("exec", ExecutionCommand::system("rm")); 12 | exec.args(vec!["file1"]) 13 | .capture_stdout(1000) 14 | .capture_stderr(1000) 15 | .output("file1"); 16 | 17 | dag.on_execution_done(&exec.uuid, |res| { 18 | assert!(!res.status.is_success(), "rm didn't fail: {res:?}"); 19 | Ok(()) 20 | }); 21 | dag.add_execution(exec); 22 | eval_dag(dag); 23 | } 24 | 25 | #[cfg(not(target_os = "macos"))] 26 | #[test] 27 | fn test_chmod_dir() { 28 | setup(); 29 | 30 | let mut dag = ExecutionDAG::new(); 31 | let mut exec = Execution::new("exec", ExecutionCommand::system("chmod")); 32 | exec.args(vec!["777", "."]) 33 | .capture_stdout(1000) 34 | .capture_stderr(1000) 35 | .output("file1"); 36 | 37 | dag.on_execution_done(&exec.uuid, |res| { 38 | assert!(!res.status.is_success(), "chmod didn't fail: {res:?}"); 39 | Ok(()) 40 | }); 41 | dag.add_execution(exec); 42 | eval_dag(dag); 43 | } 44 | 45 | #[test] 46 | fn test_create_files() { 47 | setup(); 48 | 49 | let mut dag = ExecutionDAG::new(); 50 | let mut exec = Execution::new("exec", ExecutionCommand::system("touch")); 51 | exec.args(vec!["lolnope"]) 52 | .capture_stdout(1000) 53 | .capture_stderr(1000); 54 | 55 | dag.on_execution_done(&exec.uuid, |res| { 56 | assert!(!res.status.is_success(), "touch didn't fail: {res:?}"); 57 | Ok(()) 58 | }); 59 | dag.add_execution(exec); 60 | eval_dag(dag); 61 | } 62 | 63 | #[test] 64 | fn test_list_fifo() { 65 | setup(); 66 | 67 | let mut dag = ExecutionDAG::new(); 68 | let mut group = ExecutionGroup::new("group"); 69 | let fifo = group.new_fifo(); 70 | let fifo_dir = fifo.sandbox_path().parent().unwrap().to_owned(); 71 | group.new_fifo(); 72 | let mut exec = Execution::new("exec", ExecutionCommand::system("ls")); 73 | exec.args(vec![fifo_dir.to_str().unwrap()]) 74 | .capture_stdout(1000) 75 | .capture_stderr(1000) 76 | .output("file1"); 77 | 78 | dag.on_execution_done(&exec.uuid, |res| { 79 | assert!(!res.status.is_success(), "ls didn't fail: {res:?}"); 80 | Ok(()) 81 | }); 82 | dag.add_execution(exec); 83 | eval_dag(dag); 84 | } 85 | -------------------------------------------------------------------------------- /tests/fifo.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::{eval_dag, setup}; 3 | 4 | use task_maker_dag::{Execution, ExecutionCommand, ExecutionDAG, ExecutionGroup, File}; 5 | 6 | #[test] 7 | fn test_fifo() { 8 | setup(); 9 | let mut dag = ExecutionDAG::new(); 10 | 11 | let mut group = ExecutionGroup::new("group"); 12 | let fifo1 = group.new_fifo().sandbox_path(); 13 | let fifo1 = fifo1.to_str().unwrap(); 14 | let fifo2 = group.new_fifo().sandbox_path(); 15 | let fifo2 = fifo2.to_str().unwrap(); 16 | 17 | // exec1 will write 42 in fifo1 18 | // then read it back from fifo2 19 | // if it is 42 exits with 0, otherwise exit with 1 20 | let mut exec1 = Execution::new("exec1", ExecutionCommand::local("script.sh")); 21 | let src1 = File::new("source 1"); 22 | exec1 23 | .args(vec![fifo1, fifo2]) 24 | .capture_stdout(1000) 25 | .capture_stderr(1000) 26 | .input(src1.uuid, "script.sh", true); 27 | exec1.limits_mut().wall_time(3.0).allow_multiprocess(); 28 | dag.provide_content( 29 | src1, 30 | "#!/usr/bin/env bash\n\ 31 | echo 42 > $1\n\ 32 | res=$(cat $2)\n\ 33 | [[ $res == 42 ]] && exit 0 || exit 1\n" 34 | .as_bytes() 35 | .to_owned(), 36 | ); 37 | dag.on_execution_done(&exec1.uuid, |res| { 38 | assert!(res.status.is_success(), "Process 1 crashed: {res:?}"); 39 | Ok(()) 40 | }); 41 | dag.on_execution_skip(&exec1.uuid, || panic!("Process 1 has been skipped")); 42 | group.add_execution(exec1); 43 | 44 | // exec2 will read from fifo1 45 | // then write it back into fifo2 46 | let mut exec2 = Execution::new("exec2", ExecutionCommand::local("script.sh")); 47 | let src2 = File::new("source 2"); 48 | exec2 49 | .args(vec![fifo1, fifo2]) 50 | .capture_stdout(1000) 51 | .capture_stderr(1000) 52 | .input(src2.uuid, "script.sh", true); 53 | exec2.limits_mut().wall_time(3.0).allow_multiprocess(); 54 | dag.provide_content( 55 | src2, 56 | "#!/usr/bin/env bash\n\ 57 | res=$(cat $1)\n\ 58 | echo $res > $2\n" 59 | .as_bytes() 60 | .to_owned(), 61 | ); 62 | dag.on_execution_done(&exec2.uuid, |res| { 63 | assert!(res.status.is_success(), "Process 2 crashed: {res:?}"); 64 | Ok(()) 65 | }); 66 | dag.on_execution_skip(&exec2.uuid, || panic!("Process 2 has been skipped")); 67 | group.add_execution(exec2); 68 | 69 | dag.add_execution_group(group); 70 | eval_dag(dag); 71 | } 72 | -------------------------------------------------------------------------------- /src/local.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Error}; 2 | 3 | use task_maker_format::ui::{UIMessage, UI}; 4 | 5 | use crate::context::RuntimeContext; 6 | use crate::error::NiceError; 7 | use crate::opt::Opt; 8 | 9 | /// The result of an evaluation. 10 | pub enum Evaluation { 11 | /// The evaluation has completed. 12 | Done, 13 | /// The task directory has been cleaned. 14 | Clean, 15 | } 16 | 17 | /// Run the local evaluation of some actions (either building a task or cleaning its directory). 18 | /// 19 | /// The instructions on what to do are expressed via the "command line" options passed as arguments. 20 | /// This method will block until the execution ends, but it accepts a function as parameter used as 21 | /// callback for when UI messages are produced. 22 | /// 23 | /// The callback takes 2 parameters, a reference to the current UI and the message produced. 24 | /// Typically the only thing that function should do is to send the message to the UI, this 25 | /// behaviour can be changed. 26 | /// 27 | /// ```no_run 28 | /// # use clap::Parser; 29 | /// # use task_maker_rust::local::run_evaluation; 30 | /// # let opt = task_maker_rust::opt::Opt::parse(); 31 | /// run_evaluation(opt, move |ui, mex| ui.on_message(mex)); 32 | /// ``` 33 | pub fn run_evaluation(opt: Opt, on_message: F) -> Result 34 | where 35 | F: FnMut(&mut dyn UI, UIMessage) + Send + 'static, 36 | { 37 | if opt.exclusive { 38 | bail!("This option is not implemented yet"); 39 | } 40 | 41 | // setup the task 42 | let eval_config = opt.to_config(); 43 | let task = opt.find_task.find_task(&eval_config)?; 44 | 45 | // clean the task 46 | if opt.clean { 47 | warn!("--clean is deprecated: use `task-maker-tools clear`"); 48 | task.clean().context("Cannot clear the task directory")?; 49 | return Ok(Evaluation::Clean); 50 | } 51 | 52 | // setup the configuration and the evaluation metadata 53 | let context = RuntimeContext::new(task, &opt.execution, |task, eval| { 54 | // build the DAG for the task 55 | task.build_dag(eval, &eval_config) 56 | .context("Cannot build the task DAG") 57 | })?; 58 | 59 | // start the execution 60 | let executor = context.connect_executor(&opt.execution, &opt.storage)?; 61 | let executor = executor.start_ui(&opt.ui.ui, on_message)?; 62 | executor.execute()?; 63 | 64 | Ok(Evaluation::Done) 65 | } 66 | 67 | /// Entry point of the local execution. 68 | pub fn main_local(opt: Opt) { 69 | run_evaluation(opt, |ui, mex| ui.on_message(mex)).nice_unwrap(); 70 | } 71 | -------------------------------------------------------------------------------- /tools/aur/task-maker-rust/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Edoardo Morassutto 2 | 3 | pkgname=task-maker-rust 4 | pkgver=@@VERSION@@ 5 | pkgrel=1 6 | pkgdesc="Tool for building tasks for informatics competitions, with support for cache, distributed computations and more" 7 | arch=('i686' 'x86_64' 'armv7h' 'aarch64') 8 | url="https://github.com/olimpiadi-informatica/task-maker-rust" 9 | license=('MPL2') 10 | makedepends=('cargo') 11 | optdepends=('texlive-core: for compiling tex statements' 12 | 'gcc: for running C/C++ solutions' 13 | 'mono: for running C# solutions' 14 | 'nodejs: for running JavaScript solutions' 15 | 'fpc: for running Pascal solutions' 16 | 'python: for running Python solutions' 17 | 'cargo: for running Rust solutions' 18 | 'bash: for running Bash solutions') 19 | source=("https://github.com/olimpiadi-informatica/task-maker-rust/archive/v${pkgver}.tar.gz") 20 | sha256sums=('@@SHA256@@') 21 | 22 | build() { 23 | cd "$srcdir/task-maker-rust-${pkgver}" 24 | TM_DATA_DIR=/usr/share/task-maker-rust cargo build --release --bins 25 | target/release/task-maker-tools gen-autocompletion 26 | } 27 | 28 | package() { 29 | cd "$srcdir/task-maker-rust-${pkgver}" 30 | # main binary 31 | install -Dm755 "target/release/task-maker" "$pkgdir/usr/bin/task-maker-rust" 32 | install -Dm755 "target/release/task-maker-tools" "$pkgdir/usr/bin/task-maker-tools" 33 | # runtime data 34 | install -dDm755 "$pkgdir/usr/share/task-maker-rust" 35 | cp -rT data "$pkgdir/usr/share/task-maker-rust" 36 | # autocompletion files 37 | install -Dm644 "target/autocompletion/task-maker-rust.bash" "$pkgdir/usr/share/bash-completion/completions/task-maker-rust" 38 | install -Dm644 "target/autocompletion/_task-maker-rust" "$pkgdir/usr/share/zsh/site-functions/_task-maker-rust" 39 | install -Dm644 "target/autocompletion/task-maker-rust.fish" "$pkgdir/usr/share/fish/completions/task-maker-rust.fish" 40 | install -Dm644 "target/autocompletion/task-maker-tools.bash" "$pkgdir/usr/share/bash-completion/completions/task-maker-tools" 41 | install -Dm644 "target/autocompletion/_task-maker-tools" "$pkgdir/usr/share/zsh/site-functions/_task-maker-tools" 42 | install -Dm644 "target/autocompletion/task-maker-tools.fish" "$pkgdir/usr/share/fish/completions/task-maker-tools.fish" 43 | # vim syntax highlight 44 | install -Dm644 "tools/vim/ftdetect/cases_gen.vim" "$pkgdir/usr/share/vim/vimfiles/ftdetect/cases_gen.vim" 45 | install -Dm644 "tools/vim/syntax/cases_gen.vim" "$pkgdir/usr/share/vim/vimfiles/syntax/cases_gen.vim" 46 | } 47 | -------------------------------------------------------------------------------- /task-maker-format/src/detect_format.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | use std::path::PathBuf; 3 | 4 | use anyhow::{anyhow, bail, Context, Result}; 5 | 6 | use crate::{ioi, terry, EvaluationConfig, TaskFormat}; 7 | 8 | /// Search for a valid task directory, starting from base and going _at most_ `max_depth` times up. 9 | pub fn find_task( 10 | base: Option, 11 | max_depth: u32, 12 | eval_config: &EvaluationConfig, 13 | ) -> Result { 14 | let mut base = base.unwrap_or_else(getcwd); 15 | if !base.is_absolute() { 16 | base = getcwd().join(base); 17 | } 18 | let mut fails = vec![]; 19 | for _ in 0..max_depth { 20 | let mut task = None; 21 | // try to parse a IOI task 22 | if ioi::IOITask::is_valid(&base) { 23 | match ioi::IOITask::new(&base, eval_config) { 24 | Ok(ioi_task) => task = Some(ioi_task.into()), 25 | Err(err) => fails.push(("IOI", base.clone(), err)), 26 | } 27 | } 28 | // try to parse a Terry task 29 | if terry::TerryTask::is_valid(&base) { 30 | match terry::TerryTask::new(&base, eval_config) { 31 | Ok(terry_task) => { 32 | if task.is_some() { 33 | bail!("Ambiguous task directory, can be either IOI and terry") 34 | } 35 | task = Some(terry_task.into()) 36 | } 37 | Err(err) => fails.push(("Terry", base.clone(), err)), 38 | } 39 | } 40 | // if a task is found, return it 41 | if let Some(task) = task { 42 | return Ok(task); 43 | } 44 | // not task found yet, try on the parent folder 45 | base = match base.parent() { 46 | Some(parent) => parent.into(), 47 | _ => break, 48 | }; 49 | } 50 | 51 | let mut message = "\n".to_string(); 52 | for (format, path, error) in fails { 53 | let _ = writeln!( 54 | message, 55 | " - Not a valid {} task at {}", 56 | format, 57 | path.display() 58 | ); 59 | error.chain().for_each(|cause| { 60 | let _ = write!(message, " Caused by:\n {cause}\n"); 61 | }); 62 | } 63 | 64 | Err(anyhow!("{}", message)).context("Cannot find a valid task directory") 65 | } 66 | 67 | /// Return the current working directory. 68 | /// 69 | /// `std::env::current_dir()` resolves the symlinks of the cwd's hierarchy, `$PWD` is used instead. 70 | fn getcwd() -> PathBuf { 71 | std::env::var("PWD") 72 | .map(PathBuf::from) 73 | .unwrap_or_else(|_| std::env::current_dir().expect("Cannot get current working directory")) 74 | } 75 | -------------------------------------------------------------------------------- /src/tools/worker.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::{bail, Context, Error}; 4 | use clap::Parser; 5 | 6 | use task_maker_exec::executors::{RemoteEntityMessage, RemoteEntityMessageResponse}; 7 | use task_maker_exec::Worker; 8 | use task_maker_store::FileStore; 9 | 10 | use crate::remote::connect_to_remote_server; 11 | use crate::sandbox::ToolsSandboxRunner; 12 | use crate::StorageOpt; 13 | 14 | #[derive(Parser, Debug, Clone)] 15 | pub struct WorkerOpt { 16 | /// Address to use to connect to a remote server 17 | pub server_addr: String, 18 | 19 | /// ID of the worker (to differentiate between multiple workers on the same machine). 20 | pub worker_id: Option, 21 | 22 | /// The name to use for the worker in remote executions 23 | #[clap(long)] 24 | pub name: Option, 25 | 26 | #[clap(flatten, next_help_heading = Some("STORAGE"))] 27 | pub storage: StorageOpt, 28 | } 29 | 30 | /// Version of task-maker 31 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 32 | 33 | /// Entry point for the worker. 34 | pub fn main_worker(opt: WorkerOpt) -> Result<(), Error> { 35 | let store_path = opt.storage.store_dir(); 36 | let file_store = Arc::new( 37 | FileStore::new( 38 | store_path.join("store"), 39 | opt.storage.max_cache * 1024 * 1024, 40 | opt.storage.min_cache * 1024 * 1024, 41 | ) 42 | .context("Cannot create the file store")?, 43 | ); 44 | let sandbox_path = store_path.join("sandboxes"); 45 | 46 | let name = opt.name.unwrap_or_else(|| { 47 | format!( 48 | "{}@{}", 49 | whoami::username(), 50 | whoami::fallible::hostname().unwrap() 51 | ) 52 | }); 53 | let (executor_tx, executor_rx) = connect_to_remote_server(&opt.server_addr, 27183) 54 | .context("Failed to connect to the server")?; 55 | executor_tx 56 | .send(RemoteEntityMessage::Welcome { 57 | name: name.clone(), 58 | version: VERSION.into(), 59 | }) 60 | .context("Cannot send welcome to the server")?; 61 | if let RemoteEntityMessageResponse::Rejected(err) = executor_rx 62 | .recv() 63 | .context("Remote executor didn't reply to the welcome message")? 64 | { 65 | bail!("The server rejected the worker connection: {}", err); 66 | } 67 | 68 | let name = if let Some(wid) = opt.worker_id { 69 | format!("{name} {wid}") 70 | } else { 71 | name 72 | }; 73 | 74 | let worker = Worker::new_with_channel( 75 | name, 76 | file_store, 77 | sandbox_path, 78 | executor_tx.change_type(), 79 | executor_rx.change_type(), 80 | Arc::new(ToolsSandboxRunner::default()), 81 | ) 82 | .context("Failed to start worker")?; 83 | worker.work() 84 | } 85 | -------------------------------------------------------------------------------- /task-maker-format/src/terry/statement/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::path::PathBuf; 3 | use task_maker_dag::{Execution, ExecutionCommand, File}; 4 | use task_maker_diagnostics::Diagnostic; 5 | 6 | use crate::{EvaluationData, Tag, UISender}; 7 | use anyhow::Error; 8 | 9 | /// A statement is a markdown template together with subtask data 10 | #[derive(Debug, Clone, Serialize, Deserialize)] 11 | pub struct Statement { 12 | /// The path to the statement template 13 | pub path: PathBuf, 14 | /// The subtasks if they exist 15 | pub subtasks: Option, 16 | /// The output path 17 | pub output: PathBuf, 18 | } 19 | 20 | impl Statement { 21 | pub fn generate_and_bind(&self, eval: &mut EvaluationData) -> Result<(), Error> { 22 | let mut exec = Execution::new( 23 | "Generation of the statement", 24 | ExecutionCommand::system("task-maker-tools"), 25 | ); 26 | 27 | exec.limits_mut() 28 | .read_only(false) 29 | .allow_multiprocess() 30 | .mount_tmpfs(true); 31 | exec.tag(Tag::Booklet.into()); 32 | 33 | let output = exec.output("output.md"); 34 | 35 | let statement = File::new("Statement template"); 36 | exec.input(&statement, "statement.in.md", false); 37 | eval.dag.provide_file(statement, &self.path)?; 38 | 39 | if let Some(subtasks_path) = &self.subtasks { 40 | let subtasks = File::new("Subtasks"); 41 | exec.input(&subtasks, "subtasks.yaml", false); 42 | eval.dag.provide_file(subtasks, subtasks_path)?; 43 | 44 | exec.args(vec![ 45 | "terry-statement", 46 | "-s", 47 | "statement.in.md", 48 | "-t", 49 | "subtasks.yaml", 50 | "-o", 51 | "output.md", 52 | ]); 53 | } else { 54 | exec.args(vec![ 55 | "terry-statement", 56 | "-s", 57 | "statement.in.md", 58 | "-o", 59 | "output.md", 60 | ]); 61 | } 62 | 63 | let sender = eval.sender.clone(); 64 | 65 | exec.capture_stderr(1024); 66 | eval.dag.on_execution_done(&exec.uuid, move |res| { 67 | if !res.status.is_success() { 68 | sender.add_diagnostic(Diagnostic::error(format!( 69 | "Failed to generate statement.md. Generation stderr: {}", 70 | String::from_utf8_lossy(&res.stderr.expect("Failed to capture stderr")) 71 | )))?; 72 | } 73 | Ok(()) 74 | }); 75 | 76 | eval.dag.add_execution(exec); 77 | eval.dag.write_file_to(output, &self.output, false); 78 | 79 | Ok(()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tools/aur/task-maker-rust-git/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Edoardo Morassutto 2 | 3 | pkgname=task-maker-rust-git 4 | pkgver=r1130.52939a7 5 | pkgrel=1 6 | pkgdesc="Tool for building tasks for informatics competitions, with support for cache, distributed computations and more" 7 | arch=('i686' 'x86_64' 'armv7h' 'aarch64') 8 | url="https://github.com/olimpiadi-informatica/task-maker-rust" 9 | license=('MPL2') 10 | depends=() 11 | makedepends=('cargo') 12 | optdepends=('texlive-core: for compiling tex statements' 13 | 'gcc: for running C/C++ solutions' 14 | 'mono: for running C# solutions' 15 | 'nodejs: for running JavaScript solutions' 16 | 'fpc: for running Pascal solutions' 17 | 'python: for running Python solutions' 18 | 'cargo: for running Rust solutions' 19 | 'bash: for running Bash solutions') 20 | options=() 21 | provides=('task-maker-rust') 22 | conflicts=('task-maker-rust') 23 | source=("git+https://github.com/olimpiadi-informatica/task-maker-rust.git") 24 | sha384sums=('SKIP') 25 | 26 | pkgver() { 27 | cd "$srcdir/task-maker-rust" 28 | echo "r$(git rev-list --count HEAD).$(git rev-parse --short HEAD)" 29 | } 30 | 31 | build() { 32 | cd "$srcdir/task-maker-rust" 33 | TM_DATA_DIR=/usr/share/task-maker-rust cargo build --release --bins 34 | target/release/task-maker-tools gen-autocompletion 35 | } 36 | 37 | package() { 38 | cd "$srcdir/task-maker-rust" 39 | # main binaries 40 | install -Dm755 "target/release/task-maker" "$pkgdir/usr/bin/task-maker-rust" 41 | install -Dm755 "target/release/task-maker-tools" "$pkgdir/usr/bin/task-maker-tools" 42 | # runtime data 43 | install -dDm755 "$pkgdir/usr/share/task-maker-rust" 44 | cp -rT data "$pkgdir/usr/share/task-maker-rust" 45 | # autocompletion files 46 | install -Dm644 "target/autocompletion/task-maker-rust.bash" "$pkgdir/usr/share/bash-completion/completions/task-maker-rust" 47 | install -Dm644 "target/autocompletion/_task-maker-rust" "$pkgdir/usr/share/zsh/site-functions/_task-maker-rust" 48 | install -Dm644 "target/autocompletion/task-maker-rust.fish" "$pkgdir/usr/share/fish/completions/task-maker-rust.fish" 49 | install -Dm644 "target/autocompletion/task-maker-tools.bash" "$pkgdir/usr/share/bash-completion/completions/task-maker-tools" 50 | install -Dm644 "target/autocompletion/_task-maker-tools" "$pkgdir/usr/share/zsh/site-functions/_task-maker-tools" 51 | install -Dm644 "target/autocompletion/task-maker-tools.fish" "$pkgdir/usr/share/fish/completions/task-maker-tools.fish" 52 | # vim syntax highlight 53 | install -Dm644 "tools/vim/ftdetect/cases_gen.vim" "$pkgdir/usr/share/vim/vimfiles/ftdetect/cases_gen.vim" 54 | install -Dm644 "tools/vim/syntax/cases_gen.vim" "$pkgdir/usr/share/vim/vimfiles/syntax/cases_gen.vim" 55 | } 56 | -------------------------------------------------------------------------------- /task-maker-exec/src/detect_exe.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | use std::path::Path; 4 | 5 | use anyhow::{Context, Error}; 6 | 7 | /// The platform of an executable. 8 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] 9 | pub enum ExecutablePlatform { 10 | Linux, 11 | Windows, 12 | MacOs, 13 | MacOsFat, 14 | } 15 | 16 | /// The number of bits of the platform. 17 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] 18 | pub enum ExecutableBits { 19 | Unknown, 20 | Bits32, 21 | Bits64, 22 | } 23 | 24 | /// A list of patterns for matching the header of the executables of the various platforms. 25 | const PATTERNS: [(&[u8], (ExecutablePlatform, ExecutableBits)); 8] = [ 26 | ( 27 | b"\x4D\x5A", 28 | (ExecutablePlatform::Windows, ExecutableBits::Unknown), 29 | ), 30 | (b"#!", (ExecutablePlatform::Linux, ExecutableBits::Unknown)), 31 | ( 32 | b"\xCE\xFA\xED\xFE", 33 | (ExecutablePlatform::MacOs, ExecutableBits::Bits32), 34 | ), 35 | ( 36 | b"\xCF\xFA\xED\xFE", 37 | (ExecutablePlatform::MacOs, ExecutableBits::Bits64), 38 | ), 39 | ( 40 | b"\xBE\xBA\xFE\xCA", 41 | (ExecutablePlatform::MacOsFat, ExecutableBits::Bits32), 42 | ), 43 | ( 44 | b"\xBF\xBA\xFE\xCA", 45 | (ExecutablePlatform::MacOsFat, ExecutableBits::Bits64), 46 | ), 47 | ( 48 | b"\x7F\x45\x4C\x46\x01", 49 | (ExecutablePlatform::Linux, ExecutableBits::Bits32), 50 | ), 51 | ( 52 | b"\x7F\x45\x4C\x46\x02", 53 | (ExecutablePlatform::Linux, ExecutableBits::Bits64), 54 | ), 55 | ]; 56 | 57 | /// Given a path to a file, check if the file is a valid executable. 58 | /// 59 | /// - If there is an error reading the file, `Err(_)` is returned. 60 | /// - If the file is not recognized as an executable, `Ok(None)` is returned. 61 | /// - Otherwise `Ok(Some((platform, bits)))` is returned. 62 | pub fn detect_exe>( 63 | path: P, 64 | ) -> Result, Error> { 65 | let path = path.as_ref(); 66 | let mut file = File::open(path) 67 | .with_context(|| format!("Failed to open file {} for detecting exe", path.display()))?; 68 | let mut header = vec![]; 69 | for (bytes, res) in &PATTERNS { 70 | if header.len() < bytes.len() { 71 | let mut missing = vec![0u8; bytes.len() - header.len()]; 72 | file.read_exact(&mut missing).with_context(|| { 73 | format!( 74 | "Failed to read file content of {} for detecting exe", 75 | path.display() 76 | ) 77 | })?; 78 | header.append(&mut missing); 79 | } 80 | if header.starts_with(bytes) { 81 | return Ok(Some(*res)); 82 | } 83 | } 84 | Ok(None) 85 | } 86 | -------------------------------------------------------------------------------- /src/tools/opt.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | use crate::tools::add_solution_checks::AddSolutionChecksOpt; 4 | use crate::tools::booklet::BookletOpt; 5 | use crate::tools::clear::ClearOpt; 6 | use crate::tools::copy_competition_files::CopyCompetitionFilesOpt; 7 | use crate::tools::export_booklet::ExportBookletOpt; 8 | use crate::tools::export_solution_checks::ExportSolutionChecksOpt; 9 | use crate::tools::find_bad_case::FindBadCaseOpt; 10 | use crate::tools::fuzz_checker::FuzzCheckerOpt; 11 | use crate::tools::gen_autocompletion::GenAutocompletionOpt; 12 | use crate::tools::reset::ResetOpt; 13 | use crate::tools::sandbox::SandboxOpt; 14 | use crate::tools::server::ServerOpt; 15 | use crate::tools::task_info::TaskInfoOpt; 16 | use crate::tools::terry_statement::TerryStatementOpt; 17 | use crate::tools::worker::WorkerOpt; 18 | use crate::LoggerOpt; 19 | 20 | #[derive(Parser, Debug)] 21 | #[clap(name = "task-maker-tools")] 22 | pub struct Opt { 23 | #[clap(flatten, next_help_heading = Some("LOGGING"))] 24 | pub logger: LoggerOpt, 25 | 26 | /// Which tool to use 27 | #[clap(subcommand)] 28 | pub tool: Tool, 29 | } 30 | 31 | #[derive(Parser, Debug)] 32 | pub enum Tool { 33 | /// Clear a task directory 34 | Clear(ClearOpt), 35 | /// Generate the autocompletion files for the shell 36 | GenAutocompletion(GenAutocompletionOpt), 37 | /// Spawn an instance of the server 38 | Server(ServerOpt), 39 | /// Spawn an instance of a worker 40 | Worker(WorkerOpt), 41 | /// Wipe the internal storage of task-maker 42 | /// 43 | /// Warning: no other instances of task-maker should be running when this flag is provided. 44 | Reset(ResetOpt), 45 | /// Run a command inside a sandbox similar to the one used by task-maker 46 | Sandbox(SandboxOpt), 47 | /// Obtain the information about a task. 48 | TaskInfo(TaskInfoOpt), 49 | /// Compile just the booklet for a task or a contest. 50 | Booklet(BookletOpt), 51 | /// Copy statements and attachments of a contest in a separate directory 52 | CopyCompetitionFiles(CopyCompetitionFilesOpt), 53 | /// Build terry statements by adding the subtask table 54 | TerryStatement(TerryStatementOpt), 55 | /// Fuzz the checker of a task. 56 | FuzzChecker(FuzzCheckerOpt), 57 | /// Generate and search for an input file that make a solution fail. 58 | FindBadCase(FindBadCaseOpt), 59 | /// Add the @check comments to the solutions. 60 | AddSolutionChecks(AddSolutionChecksOpt), 61 | /// Exports solution checks to json. 62 | ExportSolutionChecks(ExportSolutionChecksOpt), 63 | /// Exports internal booklet structure as a zip. 64 | ExportBooklet(ExportBookletOpt), 65 | /// Run the sandbox instead of the normal task-maker. 66 | /// 67 | /// This option is left as undocumented as it's not part of the public API. 68 | #[clap(hide = true)] 69 | InternalSandbox, 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [created] 7 | 8 | jobs: 9 | deb: 10 | name: Create deb packages 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: 16 | - ubuntu:25.10 17 | - ubuntu:25.04 18 | - ubuntu:24.04 19 | - ubuntu:22.04 20 | - debian:bullseye 21 | - debian:bookworm 22 | - debian:trixie 23 | container: 24 | image: ${{ matrix.os }} 25 | env: 26 | TM_DATA_DIR: /usr/share/task-maker-rust 27 | steps: 28 | - uses: actions/checkout@v5 29 | - name: Install Deps 30 | run: | 31 | apt update 32 | apt install -yy libseccomp-dev libssl-dev build-essential fpc 33 | - name: Install Rust 34 | uses: dtolnay/rust-toolchain@stable 35 | id: install-rust 36 | - name: Install cargo-deb 37 | run: cargo install cargo-deb 38 | 39 | - name: Calculate artifact suffix 40 | shell: python3 {0} 41 | run: | 42 | import os 43 | artifact_suffix="${{ matrix.os }}".replace(':', '-') 44 | open(os.getenv("GITHUB_ENV"), "a").write(f"ARTIFACT_SUFFIX={artifact_suffix}\n") 45 | 46 | - name: Release build 47 | run: cargo build --release 48 | 49 | - name: Generate autocompletion files 50 | run: target/release/task-maker-tools gen-autocompletion 51 | 52 | - name: Build deb file 53 | run: cargo deb --deb-revision 1~${{ env.ARTIFACT_SUFFIX }} 54 | 55 | - name: Package release tarball 56 | run: | 57 | ROOT=`pwd` 58 | cd target/debian/ 59 | tar -zcvf ${ROOT}/tmr-debs-${{ env.ARTIFACT_SUFFIX }}.tar.gz *task-maker-rust*.* 60 | 61 | - name: Upload release tarball 62 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 63 | with: 64 | name: tmr-debs-${{ env.ARTIFACT_SUFFIX }} 65 | path: "target/debian/*.deb" 66 | compression-level: 0 67 | 68 | - name: Upload the artifacts 69 | uses: skx/github-action-publish-binaries@master 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | with: 73 | args: "target/debian/*.deb" 74 | 75 | aur: 76 | name: AUR 77 | runs-on: ubuntu-latest 78 | steps: 79 | - uses: actions/checkout@v5 80 | - name: Build PKGBUILD 81 | working-directory: tools/aur/task-maker-rust 82 | run: ./gen.sh > ../../../PKGBUILD 83 | - name: Publish PKGBUILD 84 | if: ${{ github.event_name == 'release' }} # skip on manual runs. 85 | uses: KSXGitHub/github-actions-deploy-aur@v4.1.1 86 | with: 87 | pkgname: task-maker-rust 88 | pkgbuild: ./PKGBUILD 89 | commit_username: ${{ secrets.AUR_USERNAME }} 90 | commit_email: ${{ secrets.AUR_EMAIL }} 91 | ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} 92 | -------------------------------------------------------------------------------- /task-maker-format/src/task_format.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::Error; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use task_maker_dag::ExecutionDAGConfig; 7 | 8 | use crate::{ui, EvaluationConfig, EvaluationData, IOITask, TaskInfo, TerryTask, UI}; 9 | 10 | /// The format of the task. 11 | /// A task format, providing a UI and the parsing and execution abilities. 12 | #[allow(clippy::large_enum_variant)] 13 | #[derive(Debug, Clone, Serialize, Deserialize)] 14 | pub enum TaskFormat { 15 | /// The task is IOI-like. 16 | IOI(IOITask), 17 | /// The task is Terry-like. 18 | Terry(TerryTask), 19 | } 20 | 21 | impl TaskFormat { 22 | /// Get the root directory of the task. 23 | pub fn path(&self) -> &Path { 24 | match self { 25 | TaskFormat::IOI(task) => task.path(), 26 | TaskFormat::Terry(task) => task.path(), 27 | } 28 | } 29 | 30 | /// Get an appropriate `UI` for this task. 31 | pub fn ui( 32 | &self, 33 | ui_type: &ui::UIType, 34 | config: ExecutionDAGConfig, 35 | ) -> Result, Error> { 36 | match self { 37 | TaskFormat::IOI(task) => task.ui(ui_type, config), 38 | TaskFormat::Terry(task) => task.ui(ui_type, config), 39 | } 40 | } 41 | 42 | /// Add the executions required for evaluating this task to the execution DAG. 43 | pub fn build_dag( 44 | &mut self, 45 | eval: &mut EvaluationData, 46 | config: &EvaluationConfig, 47 | ) -> Result<(), Error> { 48 | match self { 49 | TaskFormat::IOI(task) => task.build_dag(eval, config), 50 | TaskFormat::Terry(task) => task.build_dag(eval, config), 51 | } 52 | } 53 | 54 | /// Hook called after the execution completed, useful for sending messages to the UI about the 55 | /// results of the sanity checks with data available only after the evaluation. 56 | pub fn sanity_check_post_hook(&self, eval: &mut EvaluationData) -> Result<(), Error> { 57 | match self { 58 | TaskFormat::IOI(task) => task.sanity_check_post_hook(eval), 59 | TaskFormat::Terry(task) => task.sanity_check_post_hook(eval), 60 | } 61 | } 62 | 63 | /// Clean the task folder removing the files that can be generated automatically. 64 | pub fn clean(&self) -> Result<(), Error> { 65 | match self { 66 | TaskFormat::IOI(task) => task.clean(), 67 | TaskFormat::Terry(task) => task.clean(), 68 | } 69 | } 70 | 71 | /// Get the task information. 72 | pub fn task_info(&self) -> Result { 73 | match self { 74 | TaskFormat::IOI(task) => task.task_info(), 75 | TaskFormat::Terry(task) => task.task_info(), 76 | } 77 | } 78 | } 79 | 80 | impl From for TaskFormat { 81 | fn from(task: IOITask) -> Self { 82 | Self::IOI(task) 83 | } 84 | } 85 | 86 | impl From for TaskFormat { 87 | fn from(task: TerryTask) -> Self { 88 | Self::Terry(task) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/with_constraints_py.rs: -------------------------------------------------------------------------------- 1 | use task_maker_format::ioi::TestcaseEvaluationStatus::*; 2 | 3 | mod common; 4 | use common::TestInterface; 5 | 6 | fn classic(test: TestInterface) { 7 | let test = test 8 | .success() 9 | .time_limit(1.0) 10 | .memory_limit(64) 11 | .max_score(100.0) 12 | .subtask_scores(vec![5.0, 45.0, 50.0]) 13 | .must_compile("generatore.cpp") 14 | .must_compile("mle.cpp") 15 | .must_compile("float_error.cpp") 16 | .must_compile("nonzero.cpp") 17 | .must_compile("sigsegv.c") 18 | .must_compile("tle.cpp") 19 | .must_compile("wa.cpp") 20 | .must_compile("wrong_file.cpp") 21 | .must_not_compile("not_compile.cpp") 22 | // .not_compiled(".ignoreme.cpp") 23 | .not_compiled("bash.sh") 24 | .not_compiled("noop.py") 25 | .not_compiled("soluzione.py") 26 | .solution_score("soluzione.py", vec![5.0, 45.0, 50.0]) 27 | .solution_score("bash.sh", vec![5.0, 45.0, 50.0]) 28 | .solution_score("float_error.cpp", vec![0.0, 0.0, 0.0]) 29 | .solution_score("mle.cpp", vec![0.0, 0.0, 0.0]) 30 | .solution_score("noop.py", vec![0.0, 0.0, 0.0]) 31 | .solution_score("nonzero.cpp", vec![0.0, 0.0, 0.0]) 32 | .solution_score("sigsegv.c", vec![0.0, 0.0, 0.0]) 33 | .solution_score("tle.cpp", vec![5.0, 45.0, 0.0]) 34 | .solution_score("wa.cpp", vec![0.0, 0.0, 0.0]) 35 | .solution_score("wrong_file.cpp", vec![0.0, 0.0, 0.0]) 36 | .solution_statuses("soluzione.py", vec![Accepted("Output is correct".into())]) 37 | .solution_statuses("bash.sh", vec![Accepted("Output is correct".into())]) 38 | // .solution_statuses("mle.cpp", vec![RuntimeError]) // pretty unreliable 39 | .solution_statuses("nonzero.cpp", vec![RuntimeError]) 40 | .solution_statuses("sigsegv.c", vec![RuntimeError]) 41 | .solution_statuses( 42 | "tle.cpp", 43 | vec![ 44 | Accepted("Output is correct".into()), 45 | Accepted("Output is correct".into()), 46 | Accepted("Output is correct".into()), 47 | Accepted("Output is correct".into()), 48 | TimeLimitExceeded, 49 | TimeLimitExceeded, 50 | ], 51 | ) 52 | .solution_statuses("wa.cpp", vec![WrongAnswer("Output is incorrect".into())]) 53 | .solution_statuses( 54 | "wrong_file.cpp", 55 | vec![WrongAnswer("Output is incorrect".into())], 56 | ) 57 | .not_has_diagnostic("Found Latex errors"); 58 | 59 | if which::which("fpc").is_ok() { 60 | test.must_compile("pascal.pas") 61 | .solution_score("pascal.pas", vec![5.0, 45.0, 50.0]) 62 | .solution_statuses("pascal.pas", vec![Accepted("Output is correct".into())]); 63 | } 64 | } 65 | 66 | #[test] 67 | fn classic_local() { 68 | better_panic::install(); 69 | classic(TestInterface::run_local("with_constraints_py")); 70 | } 71 | 72 | #[test] 73 | fn classic_remote() { 74 | better_panic::install(); 75 | classic(TestInterface::run_remote("with_constraints_py")); 76 | } 77 | -------------------------------------------------------------------------------- /tests/classic.rs: -------------------------------------------------------------------------------- 1 | use task_maker_format::ioi::TestcaseEvaluationStatus::*; 2 | 3 | mod common; 4 | use common::TestInterface; 5 | 6 | fn classic(test: TestInterface) { 7 | let test = test 8 | .success() 9 | .time_limit(1.0) 10 | .memory_limit(64) 11 | .max_score(100.0) 12 | .subtask_scores(vec![5.0, 45.0, 50.0]) 13 | .must_compile("generatore.cpp") 14 | .must_compile("mle.cpp") 15 | .must_compile("float_error.cpp") 16 | .must_compile("nonzero.cpp") 17 | .must_compile("sigsegv.c") 18 | .must_compile("tle.cpp") 19 | .must_compile("wa.cpp") 20 | .must_compile("wrong_file.cpp") 21 | .must_compile("rust.rs") 22 | .must_not_compile("not_compile.cpp") 23 | // .not_compiled(".ignoreme.cpp") 24 | .not_compiled("bash.sh") 25 | .not_compiled("noop.py") 26 | .not_compiled("soluzione.py") 27 | .solution_score("soluzione.py", vec![5.0, 45.0, 50.0]) 28 | .solution_score("bash.sh", vec![5.0, 45.0, 50.0]) 29 | .solution_score("float_error.cpp", vec![0.0, 0.0, 0.0]) 30 | .solution_score("mle.cpp", vec![0.0, 0.0, 0.0]) 31 | .solution_score("noop.py", vec![0.0, 0.0, 0.0]) 32 | .solution_score("nonzero.cpp", vec![0.0, 0.0, 0.0]) 33 | .solution_score("sigsegv.c", vec![0.0, 0.0, 0.0]) 34 | .solution_score("tle.cpp", vec![5.0, 45.0, 0.0]) 35 | .solution_score("wa.cpp", vec![0.0, 0.0, 0.0]) 36 | .solution_score("wrong_file.cpp", vec![0.0, 0.0, 0.0]) 37 | .solution_score("rust.rs", vec![5.0, 45.0, 50.0]) 38 | .solution_statuses("soluzione.py", vec![Accepted("Output is correct".into())]) 39 | .solution_statuses("bash.sh", vec![Accepted("Output is correct".into())]) 40 | // .solution_statuses("mle.cpp", vec![RuntimeError]) // pretty unreliable 41 | .solution_statuses("nonzero.cpp", vec![RuntimeError]) 42 | .solution_statuses("sigsegv.c", vec![RuntimeError]) 43 | .solution_statuses( 44 | "tle.cpp", 45 | vec![ 46 | Accepted("Output is correct".into()), 47 | Accepted("Output is correct".into()), 48 | Accepted("Output is correct".into()), 49 | Accepted("Output is correct".into()), 50 | TimeLimitExceeded, 51 | TimeLimitExceeded, 52 | ], 53 | ) 54 | .solution_statuses("wa.cpp", vec![WrongAnswer("Output is incorrect".into())]) 55 | .solution_statuses( 56 | "wrong_file.cpp", 57 | vec![WrongAnswer("Output is incorrect".into())], 58 | ); 59 | 60 | if which::which("fpc").is_ok() { 61 | test.must_compile("pascal.pas") 62 | .solution_score("pascal.pas", vec![5.0, 45.0, 50.0]) 63 | .solution_statuses("pascal.pas", vec![Accepted("Output is correct".into())]); 64 | } 65 | } 66 | 67 | #[test] 68 | fn classic_local() { 69 | better_panic::install(); 70 | classic(TestInterface::run_local("classic")); 71 | } 72 | 73 | #[test] 74 | fn classic_remote() { 75 | better_panic::install(); 76 | classic(TestInterface::run_remote("classic")); 77 | } 78 | -------------------------------------------------------------------------------- /task-maker-format/tests/ioi_task_clean.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::sync::Arc; 3 | use task_maker_format::ioi::{Checker, InputGenerator, TaskType}; 4 | use task_maker_format::SourceFile; 5 | 6 | mod utils; 7 | 8 | #[test] 9 | fn test_ioi_task_clean() { 10 | let tmpdir = tempfile::TempDir::new().unwrap(); 11 | let task = utils::new_task_with_context(tmpdir.path()); 12 | let input = tmpdir.path().join("input"); 13 | let output = tmpdir.path().join("output"); 14 | std::fs::create_dir(&input).unwrap(); 15 | std::fs::create_dir(&output).unwrap(); 16 | for i in 0..3 { 17 | std::fs::write(input.join(format!("input{i}.txt")), "x").unwrap(); 18 | std::fs::write(output.join(format!("output{i}.txt")), "x").unwrap(); 19 | } 20 | task.clean().unwrap(); 21 | assert!(!input.exists()); 22 | assert!(!output.exists()); 23 | } 24 | 25 | #[test] 26 | fn test_ioi_task_clean_skip_static() { 27 | let tmpdir = tempfile::TempDir::new().unwrap(); 28 | let mut task = utils::new_task_with_context(tmpdir.path()); 29 | let input = tmpdir.path().join("input"); 30 | let output = tmpdir.path().join("output"); 31 | std::fs::create_dir(&input).unwrap(); 32 | std::fs::create_dir(&output).unwrap(); 33 | for i in 0..3 { 34 | std::fs::write(input.join(format!("input{i}.txt")), "x").unwrap(); 35 | std::fs::write(output.join(format!("output{i}.txt")), "x").unwrap(); 36 | } 37 | task.testcases.get_mut(&0).unwrap().input_generator = 38 | InputGenerator::StaticFile(input.join("input0.txt")); 39 | 40 | task.clean().unwrap(); 41 | assert!(input.exists()); 42 | assert!(input.join("input0.txt").exists()); 43 | assert!(!input.join("input1.txt").exists()); 44 | assert!(!input.join("input2.txt").exists()); 45 | assert!(!output.exists()); 46 | } 47 | 48 | #[test] 49 | fn test_ioi_task_clean_bin() { 50 | let tmpdir = tempfile::TempDir::new().unwrap(); 51 | let task = utils::new_task_with_context(tmpdir.path()); 52 | let bin = tmpdir.path().join("bin"); 53 | std::fs::create_dir(&bin).unwrap(); 54 | std::fs::write(bin.join("foo"), "x").unwrap(); 55 | 56 | task.clean().unwrap(); 57 | 58 | assert!(!bin.exists()); 59 | } 60 | 61 | #[test] 62 | fn test_ioi_task_clean_checker() { 63 | let tmpdir = tempfile::TempDir::new().unwrap(); 64 | let mut task = utils::new_task_with_context(tmpdir.path()); 65 | let check = tmpdir.path().join("check"); 66 | let cor = tmpdir.path().join("cor"); 67 | std::fs::create_dir(&check).unwrap(); 68 | std::fs::create_dir(&cor).unwrap(); 69 | std::fs::write(check.join("checker"), "x").unwrap(); 70 | std::fs::write(cor.join("correttore"), "x").unwrap(); 71 | std::fs::write(tmpdir.path().join("check.py"), "x").unwrap(); 72 | let source = SourceFile::new( 73 | tmpdir.path().join("check.py"), 74 | "", 75 | "", 76 | None, 77 | None::, 78 | ) 79 | .unwrap(); 80 | if let TaskType::Batch(data) = &mut task.task_type { 81 | data.checker = Checker::Custom(Arc::new(source)); 82 | } 83 | task.clean().unwrap(); 84 | 85 | assert!(!check.join("checker").exists()); 86 | assert!(!cor.join("correttore").exists()); 87 | } 88 | -------------------------------------------------------------------------------- /task-maker-format/src/ioi/sanity_checks/mod.rs: -------------------------------------------------------------------------------- 1 | //! Sanity checks for IOI-like tasks. 2 | 3 | use std::path::Path; 4 | 5 | use anyhow::Error; 6 | 7 | use task_maker_lang::LanguageManager; 8 | 9 | use crate::ioi::IOITask; 10 | use crate::sanity_checks::{SanityCheck, SanityCheckBuilder, SanityChecks}; 11 | use crate::{list_files, EvaluationData}; 12 | use std::collections::HashMap; 13 | use task_maker_diagnostics::Diagnostic; 14 | 15 | mod att; 16 | mod checker; 17 | mod io; 18 | mod sol; 19 | mod statement; 20 | mod subtasks; 21 | mod task; 22 | 23 | inventory::collect!(&'static SanityCheckBuilder); 24 | 25 | /// Make a new `SanityChecks` for a IOI task skipping the checks with the provided names. 26 | pub fn get_sanity_checks(skip: &[&str]) -> SanityChecks { 27 | SanityChecks::new(get_sanity_check_list(skip)) 28 | } 29 | 30 | /// Return the list of sanity checks excluding the ones with their name in the provided list. 31 | pub fn get_sanity_check_list(skip: &[&str]) -> Vec>> { 32 | inventory::iter::<&SanityCheckBuilder>() 33 | .cloned() 34 | .map(|b| b.build()) 35 | .filter(|s| !skip.contains(&s.name()) && !skip.contains(&s.category().as_str())) 36 | .collect() 37 | } 38 | 39 | /// Check that all the source file inside `folder` have the corresponding grader, if at least one 40 | /// grader is present in the grader map. 41 | fn check_missing_graders>( 42 | task: &IOITask, 43 | eval: &mut EvaluationData, 44 | folder: P, 45 | ) -> Result<(), Error> { 46 | if !has_grader(task) { 47 | return Ok(()); 48 | } 49 | // some task formats use stub.* others use grader.* 50 | // To avoid confusion emit warnings only either for stubs or graders. 51 | let is_stub = task 52 | .grader_map 53 | .all_paths() 54 | .filter_map(|p| p.file_stem()) 55 | .any(|p| p == "stub"); 56 | let mut by_ext = HashMap::new(); 57 | for file in list_files(task.path.join(folder.as_ref()), vec!["*.*"]) { 58 | let file = task.path_of(&file); 59 | let stem = match file.file_stem() { 60 | Some(stem) => stem, 61 | None => continue, 62 | }; 63 | // do not check the graders 64 | if stem == "grader" || stem == "stub" { 65 | continue; 66 | } 67 | if let Some(lang) = LanguageManager::detect_language(file) { 68 | let ext = lang.extensions()[0]; 69 | let name = format!("{}.{}", if is_stub { "stub" } else { "grader" }, ext); 70 | let grader_name = file.with_file_name(name); 71 | let grader_path = task.path.join(&grader_name); 72 | by_ext.insert(ext, (grader_path, grader_name, file.to_owned())); 73 | } 74 | } 75 | for (_ext, (grader_path, grader_name, cause_name)) in by_ext { 76 | if !grader_path.exists() { 77 | eval.add_diagnostic( 78 | Diagnostic::error(format!("Missing grader at {}", grader_name.display())) 79 | .with_note(format!("Because of {}", cause_name.display())), 80 | )?; 81 | } 82 | } 83 | Ok(()) 84 | } 85 | 86 | /// Check if the task uses the graders. 87 | fn has_grader(task: &IOITask) -> bool { 88 | task.grader_map.all_paths().count() != 0 89 | } 90 | -------------------------------------------------------------------------------- /task-maker-format/src/ioi/dag/task_type/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use anyhow::Error; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | pub use batch::BatchTypeData; 7 | pub use communication::{CommunicationTypeData, UserIo}; 8 | use task_maker_dag::FileUuid; 9 | 10 | use crate::ioi::{Checker, IOITask, ScoreManager, SubtaskId, TestcaseId}; 11 | use crate::{EvaluationData, SourceFile}; 12 | 13 | mod batch; 14 | mod communication; 15 | 16 | /// The type of the task. This changes the behavior of the solutions. 17 | #[derive(Debug, Clone, Serialize, Deserialize)] 18 | pub enum TaskType { 19 | /// The solution is a single file that will be executed once per testcase, feeding in the input 20 | /// file and reading the output file. The solution may be compiled with additional graders 21 | /// (called `grader.LANG`). The output is checked with an external program. 22 | Batch(BatchTypeData), 23 | /// The solution is executed in parallel with a manager and communicate using FIFO pipes. There 24 | /// are only input files since the manager computes the score of the solution. 25 | Communication(CommunicationTypeData), 26 | /// Not an actual task. 27 | None, 28 | } 29 | 30 | impl TaskType { 31 | /// Evaluate a solution on a testcase, eventually adding to the `ScoreManager` the result of the 32 | /// evaluation. This will add both the execution as well as the checking to the DAG. 33 | #[allow(clippy::too_many_arguments)] 34 | pub(crate) fn evaluate( 35 | &self, 36 | task: &IOITask, 37 | eval: &mut EvaluationData, 38 | subtask_id: SubtaskId, 39 | testcase_id: TestcaseId, 40 | source_file: &SourceFile, 41 | input: FileUuid, 42 | validation_handle: Option, 43 | correct_output: Option, 44 | score_manager: Arc>, 45 | ) -> Result<(), Error> { 46 | match self { 47 | TaskType::Batch(data) => batch::evaluate( 48 | task, 49 | eval, 50 | subtask_id, 51 | testcase_id, 52 | source_file, 53 | input, 54 | validation_handle, 55 | correct_output, 56 | score_manager, 57 | data, 58 | ), 59 | TaskType::Communication(data) => communication::evaluate( 60 | task, 61 | eval, 62 | subtask_id, 63 | testcase_id, 64 | source_file, 65 | input, 66 | validation_handle, 67 | correct_output, 68 | score_manager, 69 | data, 70 | ), 71 | TaskType::None => Ok(()), 72 | } 73 | } 74 | 75 | /// Add to the DAG more executions based on the current task type. 76 | /// 77 | /// For example this will force the compilation of the checker in a batch task. 78 | pub(crate) fn prepare_dag(&self, eval: &mut EvaluationData) -> Result<(), Error> { 79 | match self { 80 | TaskType::Batch(batch) => match &batch.checker { 81 | Checker::Custom(checker) => { 82 | checker.prepare(eval)?; 83 | } 84 | Checker::WhiteDiff => {} 85 | }, 86 | TaskType::Communication(communication) => { 87 | communication.manager.prepare(eval)?; 88 | } 89 | TaskType::None => {} 90 | } 91 | Ok(()) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/sandbox.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdin, stdout}; 2 | use std::path::{Path, PathBuf}; 3 | use std::process::{Command, Stdio}; 4 | use std::sync::atomic::{AtomicU32, Ordering}; 5 | use std::sync::Arc; 6 | 7 | use anyhow::{bail, Context, Error}; 8 | use tabox::configuration::SandboxConfiguration; 9 | use tabox::result::SandboxExecutionResult; 10 | use tabox::{Sandbox, SandboxImplementation}; 11 | 12 | use task_maker_exec::find_tools::find_tools_path; 13 | use task_maker_exec::{RawSandboxResult, SandboxRunner}; 14 | 15 | /// Actually parse the input and return the result. 16 | fn run_sandbox() -> Result { 17 | let config = 18 | serde_json::from_reader(stdin()).context("Cannot read configuration from stdin")?; 19 | let sandbox = SandboxImplementation::run(config).context("Failed to create sandbox")?; 20 | let res = sandbox.wait().context("Failed to wait sandbox")?; 21 | Ok(res) 22 | } 23 | 24 | /// Run the sandbox for an execution. 25 | /// 26 | /// It takes a `SandboxConfiguration`, JSON serialized via standard input and prints to standard 27 | /// output a `RawSandboxResult`, JSON serialized. 28 | pub fn main_sandbox() { 29 | match run_sandbox() { 30 | Ok(res) => { 31 | serde_json::to_writer(stdout(), &RawSandboxResult::Success(res)) 32 | .expect("Failed to print result"); 33 | } 34 | Err(e) => { 35 | let err = format!("Error: {e:?}"); 36 | serde_json::to_writer(stdout(), &RawSandboxResult::Error(err)) 37 | .expect("Failed to print result"); 38 | } 39 | } 40 | } 41 | 42 | /// Run the sandbox integrated in the task-maker-tools binary. 43 | #[derive(Clone, Debug)] 44 | pub struct ToolsSandboxRunner { 45 | /// Path to the tools executable. 46 | tools_path: PathBuf, 47 | } 48 | 49 | impl Default for ToolsSandboxRunner { 50 | fn default() -> Self { 51 | ToolsSandboxRunner { 52 | tools_path: find_tools_path(), 53 | } 54 | } 55 | } 56 | 57 | impl SandboxRunner for ToolsSandboxRunner { 58 | fn run(&self, config: SandboxConfiguration, pid: Arc) -> RawSandboxResult { 59 | match tools_sandbox_internal(&self.tools_path, config, pid) { 60 | Ok(res) => res, 61 | Err(e) => RawSandboxResult::Error(e.to_string()), 62 | } 63 | } 64 | } 65 | 66 | /// Actually run the sandbox, but with a return type that supports the `?` operator. 67 | fn tools_sandbox_internal( 68 | tools_path: &Path, 69 | config: SandboxConfiguration, 70 | pid: Arc, 71 | ) -> Result { 72 | let mut cmd = Command::new(tools_path) 73 | .arg("internal-sandbox") 74 | .stdin(Stdio::piped()) 75 | .stdout(Stdio::piped()) 76 | .stderr(Stdio::piped()) 77 | .spawn() 78 | .context("Cannot spawn the sandbox")?; 79 | pid.store(cmd.id(), Ordering::SeqCst); 80 | { 81 | let stdin = cmd.stdin.as_mut().context("Failed to open stdin")?; 82 | serde_json::to_writer(stdin, &config.build()).context("Failed to write config to stdin")?; 83 | } 84 | let output = cmd 85 | .wait_with_output() 86 | .context("Failed to wait for the process")?; 87 | if !output.status.success() { 88 | bail!( 89 | "Sandbox process failed: {}\n{}", 90 | output.status.to_string(), 91 | String::from_utf8_lossy(&output.stderr) 92 | ); 93 | } 94 | serde_json::from_slice(&output.stdout).context("Invalid output from sandbox") 95 | } 96 | --------------------------------------------------------------------------------