1mod error;
2pub mod runner;
3mod sandbox;
4mod util;
5
6use std::time::Duration;
7
8use bon::Builder;
9
10pub use error::*;
11use nix::sys::resource::Resource;
12use runner::Runner;
13use sandbox::RlimitConfig;
14
15#[derive(Debug, Clone, Copy, Builder)]
16pub struct CommandArgs<'a> {
17 pub binary: &'a str,
18 pub args: &'a [&'a str],
19}
20
21const DEFAULT_RLIMIT_CONFIGS: &[RlimitConfig] = &[
22 RlimitConfig {
23 resource: Resource::RLIMIT_STACK,
24 soft_limit: 1024 * 1024 * 1024 * 1024,
25 hard_limit: 1024 * 1024 * 1024 * 1024,
26 },
27 RlimitConfig {
28 resource: Resource::RLIMIT_AS,
29 soft_limit: 1024 * 1024 * 1024 * 1024,
30 hard_limit: 1024 * 1024 * 1024 * 1024,
31 },
32 RlimitConfig {
33 resource: Resource::RLIMIT_CPU,
34 soft_limit: 60,
35 hard_limit: 90,
36 },
37 RlimitConfig {
43 resource: Resource::RLIMIT_FSIZE,
44 soft_limit: 1024,
45 hard_limit: 1024,
46 },
47];
48
49const DEFAULT_SCMP_BLACK_LIST: &[&str] = &["fork", "vfork"];
50
51pub const CPP_RUNNER: Runner = Runner {
52 main_file: "main.cpp",
53 compiler_args: Some(CommandArgs {
54 binary: "g++",
55 args: &["-o", "main", "main.cpp"],
56 }),
57 sandbox_config: sandbox::Config {
58 scmp_black_list: DEFAULT_SCMP_BLACK_LIST,
59 rlimit_configs: DEFAULT_RLIMIT_CONFIGS,
60 time_limit: Duration::from_secs(2),
61 args: CommandArgs {
62 binary: "./main",
63 args: &[],
64 },
65 },
66};
67
68pub const PYTHON_RUNNER: Runner = Runner {
69 main_file: "main.py",
70 compiler_args: None,
71 sandbox_config: sandbox::Config {
72 scmp_black_list: DEFAULT_SCMP_BLACK_LIST,
73 rlimit_configs: DEFAULT_RLIMIT_CONFIGS,
74 time_limit: Duration::from_secs(10),
75 args: CommandArgs {
76 binary: "python",
77 args: &["main.py"],
78 },
79 },
80};
81
82pub const JAVA_RUNNER: Runner = Runner {
83 main_file: "Main.java",
84 compiler_args: Some(CommandArgs {
85 binary: "javac",
86 args: &["Main.java"],
87 }),
88 sandbox_config: sandbox::Config {
89 scmp_black_list: DEFAULT_SCMP_BLACK_LIST,
90 rlimit_configs: DEFAULT_RLIMIT_CONFIGS,
91 time_limit: Duration::from_secs(4),
92 args: CommandArgs {
93 binary: "java",
94 args: &["Main"],
95 },
96 },
97};
98
99pub const RUST_RUNNER: Runner = Runner {
100 main_file: "main.rs",
101 compiler_args: Some(CommandArgs {
102 binary: "rustc",
103 args: &["-O", "main.rs"],
104 }),
105 sandbox_config: sandbox::Config {
106 scmp_black_list: DEFAULT_SCMP_BLACK_LIST,
107 rlimit_configs: DEFAULT_RLIMIT_CONFIGS,
108 time_limit: Duration::from_secs(2),
109 args: CommandArgs {
110 binary: "./main",
111 args: &[],
112 },
113 },
114};
115
116#[cfg(test)]
117mod tests {
118 use std::path::Path;
119 use std::{fs, io::Write};
120
121 use anyhow::{Error, Result};
122 use rstest::rstest;
123 use tempfile::NamedTempFile;
124
125 use crate::RUST_RUNNER;
126 use crate::{
127 CPP_RUNNER, JAVA_RUNNER, PYTHON_RUNNER,
128 runner::{self, Runner},
129 };
130
131 fn read_code(problem_path: &Path, runner: &Runner<'_>) -> Result<String> {
132 fs::read_to_string(problem_path.join(runner.main_file)).map_err(Error::from)
133 }
134
135 #[rstest]
136 fn test_hello_world(
137 #[values(CPP_RUNNER, RUST_RUNNER, JAVA_RUNNER, PYTHON_RUNNER)] runner: Runner,
138 ) -> Result<()> {
139 const CODE_PATH: &str = "test/hello-world";
140 const CORRECT_OUTPUT: &str = "Hello World";
141
142 let code_path = Path::new(CODE_PATH);
143 let input = NamedTempFile::new()?;
144
145 let code = read_code(code_path, &runner)?;
146
147 let metrics = runner.run(&code, input.path())?;
148 assert_eq!(
149 metrics.output,
150 runner::Output::Success(CORRECT_OUTPUT.to_string())
151 );
152
153 Ok(())
154 }
155
156 const RANDOM_ITER: usize = 10;
157
158 #[rstest]
159 fn test_add(#[values(CPP_RUNNER, RUST_RUNNER, JAVA_RUNNER, PYTHON_RUNNER)] runner: Runner) -> Result<()> {
160 const CODE_PATH: &str = "test/add";
161
162 let code_path = Path::new(CODE_PATH);
163 let code = read_code(code_path, &runner)?;
164
165 for _ in 0..RANDOM_ITER {
166 let [a, b]: [i16; 2] = rand::random();
167 let mut input = NamedTempFile::new()?;
168 input.write_fmt(format_args!("{}\n{}\n", a, b))?;
169
170 let metrics = runner.run(&code, input.path()).unwrap();
171 assert_eq!(
172 metrics.output,
173 runner::Output::Success((a as i32 + b as i32).to_string())
174 );
175 }
176
177 Ok(())
178 }
179}