code_executor/
lib.rs

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 {
38    //     resource: Resource::RLIMIT_NPROC,
39    //     soft_limit: 1,
40    //     hard_limit: 1,
41    // },
42    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}