code_executor/
compiler.rs

1use std::{path::PathBuf, process::Stdio};
2
3use tokio::{fs, io::AsyncWriteExt, process::Command};
4
5use crate::{CommandArgs, Error, Result, util};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct Compiler<'a> {
9    pub main_file: &'a str,
10    pub args: Option<CommandArgs<'a>>,
11}
12
13impl Compiler<'_> {
14    #[tracing::instrument(err)]
15    async fn create_project(&self, code: &[u8]) -> Result<PathBuf> {
16        let project_path = util::generate_unique_path();
17
18        fs::create_dir_all(&project_path).await?;
19
20        let mut main_file_path = project_path.clone();
21        main_file_path.push(self.main_file);
22
23        let mut main_file = fs::OpenOptions::new()
24            .create_new(true)
25            .write(true)
26            .open(&main_file_path)
27            .await?;
28
29        main_file.write_all(code).await?;
30
31        Ok(project_path)
32    }
33
34    #[tracing::instrument(err)]
35    pub async fn compile(&self, code: &[u8]) -> Result<PathBuf> {
36        let project_path = self.create_project(code).await?;
37
38        let Some(CommandArgs {
39            binary: compiler,
40            args,
41        }) = self.args
42        else {
43            return Ok(project_path);
44        };
45
46        let process = Command::new(compiler)
47            .args(args)
48            .current_dir(&project_path)
49            .stderr(Stdio::piped())
50            .spawn()?;
51
52        let compilation_error = process.wait_with_output().await?.stderr;
53
54        if !compilation_error.is_empty() {
55            return Err(Error::Compilation {
56                message: String::from_utf8(compilation_error)?,
57            });
58        }
59
60        Ok(project_path)
61    }
62}
63
64#[cfg(test)]
65mod test {
66    use std::path::Path;
67
68    use bstr::ByteSlice;
69    use rstest::rstest;
70
71    use crate::{CPP, JAVA, Language, PYTHON, RUST, test::read_code};
72
73    const EXAMPLE_CODE_DIR: &str = "tests/data/timeout";
74
75    #[rstest]
76    #[tokio::test]
77    async fn should_create_valid_project_directory(
78        #[values(CPP, RUST, JAVA, PYTHON)] language: Language<'static>,
79    ) {
80        let code = read_code(language, Path::new(EXAMPLE_CODE_DIR));
81        let project_path = language
82            .compiler
83            .create_project(code.as_bytes())
84            .await
85            .unwrap();
86        let main_path = project_path.join(language.compiler.main_file);
87
88        assert!(main_path.exists())
89    }
90
91    #[rstest]
92    #[tokio::test]
93    async fn should_compile_successfully(
94        #[values(CPP, RUST, JAVA, PYTHON)] language: Language<'static>,
95    ) {
96        let code = read_code(language, Path::new(EXAMPLE_CODE_DIR));
97        assert!(language.compiler.compile(code.as_bytes()).await.is_ok());
98    }
99}