use std::collections::BTreeMap;
use std::fs;
use std::io::BufRead;
use std::path::Path;
use std::path::PathBuf;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use clap::Parser;
use dialoguer::console::style;
use scrut::config::DocumentConfig;
use scrut::config::TestCaseConfig;
use scrut::executors::bash_script_executor::BashScriptExecutor;
use scrut::executors::context::ContextBuilder;
use scrut::executors::executor::Executor;
use scrut::generators::cram::CramTestCaseGenerator;
use scrut::generators::generator::TestCaseGenerator;
use scrut::generators::markdown::MarkdownTestCaseGenerator;
use scrut::outcome::Outcome;
use scrut::parsers::parser::ParserType;
use scrut::testcase::TestCase;
use super::root::GlobalSharedParameters;
use crate::utils::ProgressWriter;
use crate::utils::TestEnvironment;
use crate::utils::canonical_shell;
use crate::utils::get_log_level;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(required = true)]
shell_expression: Vec<String>,
#[clap(long, short, default_value = "markdown", value_enum)]
format: ParserType,
#[clap(long, short, default_value = "-")]
output: String,
#[clap(long, short, default_value = "Command executes successfully")]
title: String,
#[clap(flatten)]
global: GlobalSharedParameters,
}
impl Args {
pub(crate) fn run(&self) -> Result<()> {
let expression = if self.shell_expression.len() == 1 && self.shell_expression[0] == "-" {
std::io::stdin()
.lock()
.lines()
.map(|l| l.context("failed to read STDIN line"))
.collect::<Result<Vec<_>>>()?
.join("\n")
} else {
self.shell_expression.join(" ")
};
let shell_path = canonical_shell(self.global.shell.as_ref().map(|p| p as &Path))?;
let executor = BashScriptExecutor::new(&shell_path);
let pw: ProgressWriter = ProgressWriter::try_new(
1,
get_log_level() <= tracing::Level::WARN,
self.global.no_color || !console::colors_enabled(),
)?;
pw.set_message(format!(
"⭐️ Creating test for {}{}{}",
style("`").blue().bold(),
style(&expression.replace('\n', "\\n").replace('\r', "\\r")).blue(),
style("`").blue().bold(),
));
let mut test_environment = TestEnvironment::new(
&shell_path,
self.global.work_directory.as_deref(),
self.global.keep_temporary_directories,
)?;
let test_file_path = PathBuf::from(&test_environment.work_directory).join("testfile.tmp");
let (test_work_directory, environment) =
test_environment.init_test_file(&test_file_path, self.format == ParserType::Cram)?;
let env_vars = BTreeMap::from_iter(environment.iter().map(|(k, v)| (k as &str, v as &str)));
let (document_config, testcase_config) = if self.format == ParserType::Markdown {
(
DocumentConfig::default_markdown(),
TestCaseConfig::default_markdown(),
)
} else {
(
DocumentConfig::default_cram(),
TestCaseConfig::default_cram(),
)
};
let testcase_config = testcase_config
.with_overrides_from(&self.to_testcase_config())
.with_environment(&env_vars);
let outputs = executor
.execute_all(
&[&TestCase {
shell_expression: expression.clone(),
config: testcase_config.clone(),
..Default::default()
}],
&ContextBuilder::default()
.work_directory(PathBuf::from(&test_work_directory))
.temp_directory(test_environment.tmp_directory.as_path_buf())
.file("testfile.tmp".into())
.config(document_config.with_overrides_from(&self.to_document_config()))
.build()
.context("construct build execution context")?,
)
.map_err(|err| anyhow!("{}", err))?;
assert_eq!(1, outputs.len(), "execution yielded result");
let testcase = TestCase {
title: self.title.clone(),
shell_expression: expression,
expectations: vec![],
exit_code: None,
line_number: 0,
config: testcase_config.without_environment(&env_vars),
};
let result = testcase.validate(&outputs[0]);
let generator: Box<dyn TestCaseGenerator> = match self.format {
ParserType::Cram => Box::<CramTestCaseGenerator>::default(),
ParserType::Markdown => Box::<MarkdownTestCaseGenerator>::default(),
};
let generated = generator
.generate_testcases(&[&Outcome {
location: None,
output: outputs[0].clone(),
testcase,
escaping: self.global.output_escaping(Some(self.format)),
format: self.format,
result,
}])
.context("generate formatted test document content")?;
pw.finish_and_clear();
if self.output == "-" {
pw.println(format!(
"✍️ {}: Writing generated test document",
style("STDOUT").bold()
));
print!("{generated}");
} else {
pw.println(format!(
"✍️ {}: Writing generated test document",
style(&self.output).blue(),
));
fs::write(&self.output, &generated).context("write to output")?;
}
Ok(())
}
fn to_document_config(&self) -> DocumentConfig {
self.global.to_document_config()
}
fn to_testcase_config(&self) -> TestCaseConfig {
self.global.to_testcase_config()
}
}