Skip to content
This repository has been archived by the owner on Aug 13, 2024. It is now read-only.

Commit

Permalink
Add --dry-run switch to run command (closes #60)
Browse files Browse the repository at this point in the history
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
ninjabear committed Sep 30, 2016
1 parent 8574ac9 commit 2d59750
Show file tree
Hide file tree
Showing 12 changed files with 971 additions and 270 deletions.
102 changes: 102 additions & 0 deletions src/factotum/executor/execution_strategy/mod.rs
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
}
}
}
125 changes: 125 additions & 0 deletions src/factotum/executor/execution_strategy/tests.rs
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);
}
Loading

0 comments on commit 2d59750

Please sign in to comment.