Skip to content

Commit

Permalink
test(api): test run_code in mod
Browse files Browse the repository at this point in the history
  • Loading branch information
alexis-langlet committed Nov 27, 2023
1 parent 1ae8a63 commit f3954b6
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 60 deletions.
127 changes: 104 additions & 23 deletions api/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,29 @@ use actix_web::{post, web, Responder};
use log::{debug, error, info, trace, warn};

use crate::{
api::service::LambdoApiService,
api::service::{LambdoApiService, LambdoApiServiceTrait},
model::{RunRequest, RunResponse},
vm_manager::{self, grpc_definitions::ExecuteResponse},
};
use std::error::Error;

#[post("/run")]
async fn run(
run_body: web::Json<RunRequest>,
service: web::Data<LambdoApiService>,
) -> Result<impl Responder, Box<dyn Error>> {
debug!(
"Received code execution request from http (language: {}, version: {})",
run_body.language, run_body.version
);
trace!("Request body: {:?}", run_body);
async fn run_code(run_resquest: RunRequest, service: &dyn LambdoApiServiceTrait) -> RunResponse {
let response = service.run_code(run_resquest).await;

let response = service.run_code(run_body.into_inner()).await;

let response = match response {
match response {
Ok(response) => {
info!("Execution ended for {:?}", response.id);
trace!("Response: {:?}", response);
parse_response(response)
}
// for the moment just signal an internal server error
Err(e) => match e {
vm_manager::Error::Timeout => {
warn!("Timeout while executing code");
return Ok(web::Json(RunResponse {
RunResponse {
status: 128,
stdout: "".to_string(),
stderr: "Timeout".to_string(),
}));
}
}
_ => {
error!("Error while executing code: {:?}", e);
Expand All @@ -48,12 +37,35 @@ async fn run(
}
}
},
};
}
}

#[post("/run")]
pub async fn post_run_route(
run_body: web::Json<RunRequest>,
api_service: web::Data<LambdoApiService>,
) -> Result<impl Responder, Box<dyn Error>> {
debug!(
"Received code execution request from http (language: {}, version: {})",
run_body.language, run_body.version
);
trace!("Request body: {:?}", run_body);

let service = api_service.get_ref();
let result = run_code(run_body.into_inner(), service);

Ok(web::Json(response))
Ok(web::Json(result.await))
}

fn parse_response(response: ExecuteResponse) -> RunResponse {
if response.steps.is_empty() {
return RunResponse {
status: 1,
stdout: "".to_string(),
stderr: "Nothing was run".to_string(),
};
}

let mut stdout = String::new();
let mut stderr = String::new();
for step in response.steps.as_slice() {
Expand All @@ -67,16 +79,23 @@ fn parse_response(response: ExecuteResponse) -> RunResponse {
status: response.steps[response.steps.len() - 1]
.exit_code
.try_into()
.unwrap(),
.unwrap_or(1),
stdout,
stderr,
}
}

#[cfg(test)]
mod test{
use crate::{vm_manager::grpc_definitions::{ExecuteResponse, ExecuteResponseStep}, api::parse_response};
mod test {
use std::vec;

use crate::{
api::{parse_response, run_code},
model::RunRequest,
vm_manager::grpc_definitions::{ExecuteResponse, ExecuteResponseStep, FileModel},
};

use super::service::MockLambdoApiServiceTrait;

#[test]
fn test_parse_response_stdout() {
Expand Down Expand Up @@ -131,4 +150,66 @@ mod test{
assert_eq!(parsed.stderr, "Error");
assert_eq!(parsed.status, 1);
}
}

#[tokio::test]
async fn test_run_code_with_no_steps() {
let mut mock_service = MockLambdoApiServiceTrait::new();
mock_service.expect_run_code().once().returning(|_| {
Ok(ExecuteResponse {
id: "test".to_string(),
steps: vec![],
})
});

let run_request = RunRequest {
language: "Node".to_string(),
version: "1".to_string(),
code: vec![],
input: "".to_string(),
};

let response = run_code(run_request, &mock_service).await;
assert_eq!(response.status, 1);
assert_eq!(response.stdout, "");
assert_eq!(response.stderr, "Nothing was run");
}

#[tokio::test]
async fn test_run_with_steps() {
let mut mock_service = MockLambdoApiServiceTrait::new();
mock_service.expect_run_code().once().returning(|_| {
Ok(ExecuteResponse {
id: "test".to_string(),
steps: vec![
ExecuteResponseStep {
command: "echo Hello".to_string(),
stdout: "Hello".to_string(),
stderr: "".to_string(),
exit_code: 0,
},
ExecuteResponseStep {
command: "echo World".to_string(),
stdout: "World".to_string(),
stderr: "".to_string(),
exit_code: 0,
},
],
})
});

let run_request = RunRequest {
language: "Node".to_string(),
version: "1".to_string(),
code: vec![FileModel {
filename: "test.js".to_string(),
content: "console.log('Hello World')".to_string(),
}],
input: "test.js".to_string(),
};

let response = run_code(run_request, &mock_service).await;
assert_eq!(response.status, 0);
assert_eq!(response.stdout, "HelloWorld");
assert_eq!(response.stderr, "");
}
}
74 changes: 42 additions & 32 deletions api/src/api/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ use crate::{
vm_manager::{state::LambdoStateRef, Error, VMManager},
};
use log::{debug, trace};
use mockall::automock;
use uuid::Uuid;

use crate::model::RunRequest;

pub struct LambdoApiService {
#[automock]
#[async_trait::async_trait]
pub trait LambdoApiServiceTrait: Send + Sync {
async fn run_code(&self, request: RunRequest) -> Result<ExecuteResponse, Error>;
}

pub struct LambdoApiService {
pub config: LambdoConfig,
pub vm_manager: Box<dyn VMManagerTrait>,
}
Expand All @@ -36,7 +43,39 @@ impl LambdoApiService {
})
}

pub async fn run_code(&self, request: RunRequest) -> Result<ExecuteResponse, Error> {
fn find_language(
&self,
language: &String,
) -> Result<LambdoLanguageConfig, Box<dyn std::error::Error>> {
let language_list = &self.config.languages;
for lang in language_list {
if &*lang.name == language {
return Ok(lang.clone());
}
}
Err("Language not found".into())
}

fn generate_steps(
language_settings: &LambdoLanguageConfig,
entrypoint: &str,
) -> Vec<ExecuteRequestStep> {
let mut steps: Vec<ExecuteRequestStep> = Vec::new();
for step in &language_settings.steps {
let command = step.command.replace("{{filename}}", entrypoint);

steps.push(ExecuteRequestStep {
command,
enable_output: step.output.enabled,
});
}
steps
}
}

#[async_trait::async_trait]
impl LambdoApiServiceTrait for LambdoApiService {
async fn run_code(&self, request: RunRequest) -> Result<ExecuteResponse, Error> {
let entrypoint = request.code[0].filename.clone();

let language_settings = self.find_language(&request.language).unwrap();
Expand Down Expand Up @@ -67,35 +106,6 @@ impl LambdoApiService {

response
}

fn find_language(
&self,
language: &String,
) -> Result<LambdoLanguageConfig, Box<dyn std::error::Error>> {
let language_list = &self.config.languages;
for lang in language_list {
if &*lang.name == language {
return Ok(lang.clone());
}
}
Err("Language not found".into())
}

fn generate_steps(
language_settings: &LambdoLanguageConfig,
entrypoint: &str,
) -> Vec<ExecuteRequestStep> {
let mut steps: Vec<ExecuteRequestStep> = Vec::new();
for step in &language_settings.steps {
let command = step.command.replace("{{filename}}", entrypoint);

steps.push(ExecuteRequestStep {
command,
enable_output: step.output.enabled,
});
}
steps
}
}

#[cfg(test)]
Expand All @@ -116,7 +126,7 @@ mod test {
grpc_definitions::{ExecuteRequest, ExecuteResponse, ExecuteResponseStep, FileModel},
state::LambdoState,
MockVMManagerTrait, VMManager,
},
}, api::service::LambdoApiServiceTrait,
};

fn generate_lambdo_test_config() -> LambdoConfig {
Expand Down
14 changes: 9 additions & 5 deletions api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use config::LambdoConfig;
use thiserror::Error;

use crate::{
api::{run, service::LambdoApiService},
api::{post_run_route, service::LambdoApiService},
vm_manager::grpc_definitions::lambdo_api_service_server::LambdoApiServiceServer,
vm_manager::state::LambdoState,
vm_manager::VMListener,
Expand Down Expand Up @@ -87,8 +87,12 @@ async fn main() -> std::io::Result<()> {
let http_port = config.api.web_port;
let app_state = web::Data::new(api_service);
info!("Starting web server on {}:{}", http_host, http_port);
HttpServer::new(move || App::new().app_data(app_state.clone()).service(run))
.bind((http_host.clone(), http_port))?
.run()
.await
HttpServer::new(move || {
App::new()
.app_data(app_state.clone())
.service(post_run_route)
})
.bind((http_host.clone(), http_port))?
.run()
.await
}

0 comments on commit f3954b6

Please sign in to comment.