diff --git a/agent/Cargo.toml b/agent/Cargo.toml index df8a98f..13ab045 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -31,4 +31,4 @@ name = "agent_lib" path = "lib/src/lib.rs" [dev-dependencies] -rand = "0.8.5" +tempfile = "3.8.1" diff --git a/agent/lib/src/runner_engine/model.rs b/agent/lib/src/runner_engine/model.rs index 47358be..84d3037 100644 --- a/agent/lib/src/runner_engine/model.rs +++ b/agent/lib/src/runner_engine/model.rs @@ -1,4 +1,10 @@ -use std::path::PathBuf; +use anyhow::Result; +use std::{ + fs::File, + io::{Read, Write}, + path::Path, + path::PathBuf, +}; use serde::{Deserialize, Serialize}; @@ -49,3 +55,32 @@ impl CodeReturn { } } } + +/// A trait responsible for CRU operations on files and dir +/// Mainly used in tests to mock file operations +trait FileHandler: Read + Write { + fn create>(path: P) -> Result + where + Self: Sized; + fn read>(path: P) -> Result + where + Self: Sized; +} + +impl FileHandler for File { + fn create>(path: P) -> Result + where + Self: Sized, + { + let file = File::create(path)?; + Ok(file) + } + + fn read>(path: P) -> Result + where + Self: Sized, + { + let file = File::open(path)?; + Ok(file) + } +} diff --git a/agent/lib/src/runner_engine/service.rs b/agent/lib/src/runner_engine/service.rs index b4ec073..6592fe7 100644 --- a/agent/lib/src/runner_engine/service.rs +++ b/agent/lib/src/runner_engine/service.rs @@ -10,16 +10,17 @@ use std::{ process::Command, }; -/// The path where the workspace will be created -const WORKSPACE_PATH: &str = "/tmp"; - -/// The RunnerEngine API +/// The RunnerEngine API, responsible for running a request message +#[derive(Clone, Debug)] pub struct RunnerEngine { + /// the request message pub request_message: ExecuteRequest, + /// The root path of the workspace + pub root_path: PathBuf, } impl RunnerEngine { - /// Create a new instance of RunnerEngine + /// Create a new instance of RunnerEngine and set the root path to your system's temp folder/directory /// /// # Arguments /// @@ -29,7 +30,27 @@ impl RunnerEngine { /// /// * `Self` - The new instance of RunnerEngine pub fn new(request_message: ExecuteRequest) -> Self { - Self { request_message } + Self { + request_message, + root_path: std::env::temp_dir(), + } + } + + /// Create a new instance of RunnerEngine that uses another root path than /tmp + /// + /// # Arguments + /// + /// * `request_message` - The request message + /// * `root_path` - The root path of the workspace + /// + /// # Returns + /// + /// * `Self` - The new instance of RunnerEngine + pub fn new_with_path(request_message: ExecuteRequest, root_path: &str) -> Self { + Self { + request_message, + root_path: PathBuf::from(root_path), + } } /// Create the workspace for the code execution @@ -42,14 +63,13 @@ impl RunnerEngine { // Create a vector of FileModel and a root path let mut file_models: Vec = Vec::new(); - let root_path = PathBuf::from(WORKSPACE_PATH); self.request_message.files.iter().for_each(|file| { let mut file_path = PathBuf::from(&file.filename); file_path.pop(); // Add `/tmp` before each path - file_path = root_path.join(file_path); + file_path = self.root_path.join(file_path); // Take the file name and add it to the vector of files let file_name = Path::file_name(Path::new(&file.filename)); @@ -169,7 +189,7 @@ impl RunnerEngine { let child_process = Command::new("/bin/sh") .args(["-c", command]) - .current_dir(WORKSPACE_PATH) + .current_dir(self.root_path.clone()) .output() .map_err(|e| anyhow!("Failed to spawn command : {}", e))?; @@ -194,42 +214,17 @@ mod tests { use crate::api::grpc_definitions::{ExecuteRequestStep, FileModel}; use super::*; - use rand::random; use std::fs::File; use std::io::Read; - /// Generate a random string - /// - /// # Arguments - /// - /// * `len` - The length of the string - /// - /// # Returns - /// - /// * `String` - The random string - fn native_rand_string(len: usize) -> String { - let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; - let mut string = String::new(); - - for _ in 0..len { - string.push( - chars - .chars() - .nth(random::() % (chars.len() - 1)) - .unwrap(), - ); - } - - string - } - #[test] fn run_one_works_with_ouputs_and_code() { - let res = RunnerEngine::new(ExecuteRequest { + let res = RunnerEngine::new(ExecuteRequest { id: "".to_string(), - files: vec![], - steps: vec![] - }).run_one("echo -n 'This is stdout' && echo -n 'This is stderr' >&2 && exit 1"); + files: vec![], + steps: vec![], + }) + .run_one("echo -n 'This is stdout' && echo -n 'This is stderr' >&2 && exit 1"); assert!(res.is_ok()); @@ -269,24 +264,24 @@ mod tests { assert_eq!(res.steps[0].stderr, "This is stderr\n"); assert_eq!(res.steps[0].stdout, "This is stdout\n"); - println!("{:?}", res.steps[1]); assert_eq!(res.steps[1].exit_code, 1); assert_eq!(res.steps[1].stderr, "This is stderr\n"); assert!(res.steps[1].stdout.is_empty()); - + assert_eq!(res.id, "4bf68974-c315-4c41-aee2-3dc2920e76e9"); } /// Test the execution of a command with a workspace #[test] fn workspace_created_sucessfully() { - let mut base_dir = PathBuf::from(WORKSPACE_PATH); - base_dir.push(native_rand_string(20)); - base_dir.push("main.sh"); - let path = base_dir.into_os_string().into_string().unwrap(); + let tempdir = tempfile::tempdir(); + let temp_dir = &tempdir.unwrap(); + let path = temp_dir.path(); + + let filename = path.join("main.sh").to_str().unwrap().to_string(); let files: Vec = vec![FileModel { - filename: path.clone(), + filename: filename.clone(), content: "Hello World!".to_string(), }]; let steps: Vec = Vec::new(); @@ -296,12 +291,14 @@ mod tests { steps, }; - RunnerEngine::new(request_data).create_workspace().unwrap(); + RunnerEngine::new_with_path(request_data, path.as_os_str().to_str().unwrap()) + .create_workspace() + .unwrap(); assert!(Path::new(&path).exists()); //Check that the file contains the specified content - let mut file = File::open(&path).unwrap(); + let mut file = File::open(filename).unwrap(); let mut buffer = [0; 12]; file.read_exact(&mut buffer[..]).unwrap();