This repository has been archived by the owner on Aug 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add --dry-run switch to run command (closes #60)
This also does a lot of refactoring, primarily around decoupling execution strategies from actual execution. This includes adding a layer over a dag to model which tasks can be run together, and keep a log of which tasks have been run. This puts the foundation down for #59 and #61.
- Loading branch information
Showing
12 changed files
with
971 additions
and
270 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* | ||
* Copyright (c) 2016 Snowplow Analytics Ltd. All rights reserved. | ||
* | ||
* This program is licensed to you under the Apache License Version 2.0, and | ||
* you may not use this file except in compliance with the Apache License | ||
* Version 2.0. You may obtain a copy of the Apache License Version 2.0 at | ||
* http://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the Apache License Version 2.0 is distributed on an "AS | ||
* IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
* implied. See the Apache License Version 2.0 for the specific language | ||
* governing permissions and limitations there under. | ||
*/ | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
use std::process::Command; | ||
use chrono::UTC; | ||
use std::time::{Instant, Duration}; | ||
use chrono::DateTime; | ||
|
||
pub struct RunResult { | ||
pub run_started: DateTime<UTC>, | ||
pub duration: Duration, | ||
pub task_execution_error: Option<String>, | ||
pub stdout: Option<String>, | ||
pub stderr: Option<String>, | ||
pub return_code: i32 | ||
} | ||
|
||
pub fn simulation_text(name:&str, command: &Command) -> String { | ||
|
||
use std::cmp; | ||
let command_text = format!("{:?}", command); | ||
|
||
let col_task_title = "TASK"; | ||
let col_command_title = "COMMAND"; | ||
let col_padding = 2; | ||
let task_col_width = cmp::max(name.len()+col_padding, col_task_title.len()+col_padding); | ||
let command_col_width = cmp::max(command_text.len()+col_padding, col_command_title.len()+col_padding); | ||
|
||
let lines = vec![ | ||
format!("/{fill:->taskwidth$}|{fill:->cmdwidth$}\\", fill="-", taskwidth=task_col_width, cmdwidth=command_col_width), | ||
format!("| {:taskwidth$} | {:cmdwidth$} |", "TASK", "COMMAND", taskwidth=task_col_width-col_padding, cmdwidth=command_col_width-col_padding), | ||
format!("|{fill:-<taskwidth$}|{fill:-<cmdwidth$}|", fill="-", taskwidth=task_col_width, cmdwidth=command_col_width), | ||
format!("| {:taskwidth$} | {:-<cmdwidth$} |", name, command_text, taskwidth=task_col_width-col_padding, cmdwidth=command_col_width-col_padding), | ||
format!("\\{fill:-<taskwidth$}|{fill:-<cmdwidth$}/\n", fill="-", taskwidth=task_col_width, cmdwidth=command_col_width), | ||
]; | ||
|
||
lines.join("\n") | ||
} | ||
|
||
pub fn execute_simulation(name:&str, command:&mut Command) -> RunResult { | ||
info!("Simulating execution for {} with command {:?}", name, command); | ||
RunResult { | ||
run_started: UTC::now(), | ||
duration: Duration::from_secs(0), | ||
task_execution_error: None, | ||
stdout: Some(simulation_text(name, &command)), | ||
stderr: None, | ||
return_code: 0 | ||
} | ||
} | ||
|
||
pub fn execute_os(name:&str, command:&mut Command) -> RunResult { | ||
let run_start = Instant::now(); | ||
let start_time_utc = UTC::now(); | ||
info!("Executing sh {:?}", command); | ||
match command.output() { | ||
Ok(r) => { | ||
let run_duration = run_start.elapsed(); | ||
let return_code = r.status.code().unwrap_or(1); // 1 will be returned if the process was killed by a signal | ||
|
||
let task_stdout: String = String::from_utf8_lossy(&r.stdout).trim_right().into(); | ||
let task_stderr: String = String::from_utf8_lossy(&r.stderr).trim_right().into(); | ||
|
||
info!("task '{}' stdout:\n'{}'", name, task_stdout); | ||
info!("task '{}' stderr:\n'{}'", name, task_stderr); | ||
|
||
let task_stdout_opt = if task_stdout.is_empty() { None } else { Some(task_stdout) }; | ||
let task_stderr_opt = if task_stderr.is_empty() { None } else { Some(task_stderr) }; | ||
|
||
RunResult { | ||
run_started: start_time_utc, | ||
duration: run_duration, | ||
task_execution_error: None, | ||
stdout: task_stdout_opt, | ||
stderr: task_stderr_opt, | ||
return_code: return_code | ||
} | ||
}, | ||
Err(message) => RunResult { | ||
run_started: start_time_utc, | ||
duration: Duration::from_secs(0), | ||
task_execution_error: Some(format!("Error executing process - {}", message)), | ||
stdout: None, | ||
stderr: None, | ||
return_code: -1 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* | ||
* Copyright (c) 2016 Snowplow Analytics Ltd. All rights reserved. | ||
* | ||
* This program is licensed to you under the Apache License Version 2.0, and | ||
* you may not use this file except in compliance with the Apache License | ||
* Version 2.0. You may obtain a copy of the Apache License Version 2.0 at | ||
* http://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the Apache License Version 2.0 is distributed on an "AS | ||
* IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
* implied. See the Apache License Version 2.0 for the specific language | ||
* governing permissions and limitations there under. | ||
*/ | ||
|
||
use factotum::executor::execution_strategy::*; | ||
use std::process::Command; | ||
use chrono::UTC; | ||
use chrono::duration::Duration; | ||
use std::cmp; | ||
use std::iter; | ||
|
||
fn fill(fillstr:&str, times:usize) -> String { | ||
iter::repeat(fillstr).take(times).collect::<String>() | ||
} | ||
|
||
#[test] | ||
fn simulation_text_good() { | ||
|
||
let mut command = Command::new("sh"); | ||
command.arg("-c"); | ||
command.arg("does_something.sh"); | ||
let task_name = "Simulation Task!"; | ||
let command_text = format!("{:?}", command); | ||
|
||
let text = simulation_text(task_name, &command); | ||
|
||
// FACTOTUM SIMULATION ONLY. THE TASK HAS NOT BEEN EXECUTED. | ||
|
||
// /--------|------------------------------------------------------------------------------\ | ||
// | TASK | COMMAND | | ||
// |--------|------------------------------------------------------------------------------| | ||
// | ABC | sh -c 'potato' | | ||
// \--------|------------------------------------------------------------------------------/ | ||
|
||
let task_name_width = cmp::max(task_name.len()+2, "TASK".len()+2); | ||
let command_width = cmp::max("COMMAND".len()+2, command_text.len()+2); | ||
|
||
println!("task width:{} command width: {}", task_name_width, command_width); | ||
|
||
let lines = vec![ format!("/{}|{}\\", fill("-", task_name_width), fill("-", command_width)), | ||
format!("| TASK {}| COMMAND {}|", fill(" ", task_name_width-" TASK ".len()), fill(" ", command_width-" COMMAND ".len())), | ||
format!("|{}|{}|", fill("-", task_name_width), fill("-", command_width)), | ||
format!("| {:taskwidth$} | {:commandwidth$} |", task_name, command_text, taskwidth=task_name_width-2, commandwidth=command_width-2), | ||
format!("\\{}|{}/\n", fill("-", task_name_width), fill("-", command_width)) ]; | ||
|
||
let expected = lines.join("\n"); | ||
|
||
println!("*** EXPECTED ***"); | ||
println!("{}", expected); | ||
println!("*** ACTUAL ***"); | ||
println!("{}", text); | ||
|
||
assert_eq!(expected, text); | ||
} | ||
|
||
#[test] | ||
fn simulation_returns_good() { | ||
let mut command:Command = Command::new("banana"); | ||
command.arg("hello_world"); | ||
let result = execute_simulation("hello-world", &mut command); | ||
|
||
assert_eq!(result.return_code, 0); | ||
assert!(result.run_started > UTC::now().checked_sub(Duration::seconds(60)).unwrap()); | ||
assert_eq!(result.duration, Duration::seconds(0).to_std().ok().unwrap()); | ||
assert_eq!(result.stdout.unwrap(), simulation_text("hello-world", &command)); | ||
assert!(result.stderr.is_some()==false) | ||
} | ||
|
||
#[test] | ||
fn os_execution_notfound() { | ||
let mut command:Command = Command::new("sh"); | ||
command.arg("-c"); | ||
command.arg("banana"); | ||
let result = execute_os("hello-world", &mut command); | ||
|
||
assert_eq!(result.return_code, 127); | ||
assert!(result.run_started > UTC::now().checked_sub(Duration::seconds(60)).unwrap()); | ||
assert_eq!(result.duration.as_secs(), 0); | ||
let stderr = result.stderr.unwrap(); | ||
println!("{}", stderr); | ||
assert!(stderr.contains("banana")); | ||
assert!(stderr.contains("not found")); | ||
assert_eq!(result.stdout, None); | ||
assert_eq!(result.task_execution_error, None) | ||
} | ||
|
||
#[test] | ||
fn os_execution_task_exec_failed() { | ||
let mut command:Command = Command::new("this-doesn't-exist"); | ||
let result = execute_os("hello-world", &mut command); | ||
|
||
assert_eq!(result.return_code, -1); | ||
assert!(result.run_started > UTC::now().checked_sub(Duration::seconds(60)).unwrap()); | ||
assert_eq!(result.duration.as_secs(), 0); | ||
assert_eq!(result.stderr, None); | ||
assert_eq!(result.stdout, None); | ||
let expected_msg = "Error executing process - No such file or directory".to_string(); | ||
assert_eq!(result.task_execution_error.unwrap()[..expected_msg.len()], expected_msg); | ||
} | ||
|
||
#[test] | ||
fn os_execution_good() { | ||
let mut command:Command = Command::new("sh"); | ||
command.arg("-c"); | ||
command.arg("type echo"); | ||
let result = execute_os("hello-world", &mut command); | ||
|
||
assert_eq!(result.return_code, 0); | ||
assert!(result.run_started > UTC::now().checked_sub(Duration::seconds(60)).unwrap()); | ||
assert_eq!(result.duration.as_secs(), 0); | ||
assert_eq!(result.stderr, None); | ||
assert_eq!(result.stdout.unwrap(), "echo is a shell builtin"); | ||
assert_eq!(result.task_execution_error, None); | ||
} |
Oops, something went wrong.