use futures::StreamExt;
use structopt::StructOpt;
use std::path::{Path, PathBuf};
use anyhow::Error;
use log::{debug, trace};
use lorikeet::runner::run_steps;
use lorikeet::step::{ExpectType, Outcome, RetryPolicy, RunType, Step};
use lorikeet::submitter::StepResult;
use lorikeet::yaml::get_steps;
use std::time::Duration;
#[derive(StructOpt, Debug)]
#[structopt(name = "lorikeet", about = "a parallel test runner for devops")]
struct Arguments {
#[structopt(short = "q", long = "quiet", help = "Don't output results to console")]
quiet: bool,
#[structopt(short = "c", long = "config", help = "Configuration File")]
config: Option<String>,
#[structopt(short = "h", long = "hostname", help = "Hostname")]
hostname: Option<String>,
#[structopt(short = "t", long = "terminal", help = "Force terminal colours")]
term: bool,
#[structopt(help = "Test Plan", default_value = "test.yml")]
test_plan: String,
#[structopt(
short = "w",
long = "webhook",
help = "Webhook submission URL (multiple values allowed)"
)]
webhook: Vec<String>,
#[structopt(
short = "s",
long = "slack",
help = "Slack Webhook submission URL (multiple values allowed)"
)]
slack: Vec<String>,
#[structopt(
short = "j",
long = "junit",
help = "Output a JUnit XML Report to this file",
parse(from_os_str)
)]
junit: Option<PathBuf>,
}
#[tokio::main]
async fn main() {
let opt = Arguments::from_args();
env_logger::init();
debug!("Loading Steps from `{}`", opt.test_plan);
let colours = atty::is(atty::Stream::Stdout) || opt.term;
let results = run_steps_or_error(&opt.test_plan, &opt.config, opt.quiet, colours).await;
let has_errors = results.iter().any(|val| !val.pass);
debug!("Steps finished!");
if !opt.webhook.is_empty() {
let hostname = opt.hostname.clone().unwrap_or_else(|| {
hostname::get()
.map(|val| val.to_string_lossy().to_string())
.unwrap_or_else(|_| "".into())
});
for url in opt.webhook {
debug!("Sending webhook to: {}", url);
lorikeet::submitter::submit_webhook(&results, &url, &hostname)
.await
.expect("Could not send webhook")
}
}
if !opt.slack.is_empty() {
let hostname = opt.hostname.unwrap_or_else(|| {
hostname::get()
.map(|val| val.to_string_lossy().to_string())
.unwrap_or_else(|_| "".into())
});
for url in opt.slack {
debug!("Sending slack webhook to: {}", url);
lorikeet::submitter::submit_slack(&results, &url, &hostname)
.await
.expect("Could not send webhook")
}
}
if let Some(path) = opt.junit {
debug!("Creating junit file at `{}`", path.display());
lorikeet::junit::create_junit(&results, &path, None).expect("Coult not create junit file");
}
if has_errors {
std::process::exit(1)
}
}
async fn run_steps_or_error<P: AsRef<Path>, Q: AsRef<Path>>(
file_path: P,
config_path: &Option<Q>,
quiet: bool,
colours: bool,
) -> Vec<StepResult> {
let steps = match get_steps(file_path, config_path) {
Ok(steps) => steps,
Err(err) => return vec![step_from_error(err, quiet, colours)],
};
trace!("Steps:{:?}", steps);
match run_steps(steps) {
Ok(mut stream) => {
let mut results = Vec::new();
while let Some(step) = stream.next().await {
let result: StepResult = step.into();
if !quiet {
result.terminal_print(&colours);
}
results.push(result);
}
results
}
Err(err) => vec![step_from_error(err, quiet, colours)],
}
}
fn step_from_error(err: Error, quiet: bool, colours: bool) -> StepResult {
let outcome = Outcome {
output: None,
error: Some(err.to_string()),
duration: Duration::default(),
on_fail_output: None,
on_fail_error: None,
};
let result: StepResult = Step {
name: "lorikeet".into(),
run: RunType::Value(String::new()),
do_output: true,
expect: ExpectType::Anything,
on_fail: None,
description: Some(
"This step is shown if there was an error when reading, parsing or running steps"
.into(),
),
filters: vec![],
require: vec![],
required_by: vec![],
retry: RetryPolicy::default(),
outcome: Some(outcome),
}
.into();
if !quiet {
result.terminal_print(&colours);
}
result
}