#![warn(missing_docs)]
#![warn(clippy::if_then_some_else_none)]
#![warn(clippy::indexing_slicing)]
#![warn(clippy::missing_const_for_fn)]
#![warn(clippy::missing_docs_in_private_items)]
#![warn(clippy::missing_inline_in_public_items)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::panic)]
#![warn(clippy::unnecessary_wraps)]
#![warn(clippy::single_char_lifetime_names)]
#![allow(clippy::panic_in_result_fn)]
#![allow(clippy::print_stdout)]
#![allow(clippy::print_stderr)]
#![allow(clippy::implicit_return)]
#![allow(clippy::use_debug)]
use std::env;
use std::error::Error;
use expect_exit::{ExpectationFailed, Expected, ExpectedResult, ExpectedWithError};
struct UnwindTest {
value: u32,
}
impl Drop for UnwindTest {
fn drop(&mut self) {
println!("UnwindTest.drop() invoked for {}", self.value);
}
}
#[derive(Debug, Clone, Copy)]
#[allow(clippy::missing_docs_in_private_items)]
enum OpMode {
RNUOk,
RNUFail,
RUOk,
RUFail,
RFNUOk,
RFNUFail,
RFUOk,
RFUFail,
ONUOk,
ONUFail,
OUOk,
OUFail,
OFNUOk,
OFNUFail,
OFUOk,
OFUFail,
BNUTrue,
BNUFalse,
BUTrue,
BUFalse,
BFNUTrue,
BFNUFalse,
BFUTrue,
BFUFalse,
RENBOk,
RENBFail,
REBOk,
REBFail,
REFNBOk,
REFNBFail,
REFBOk,
REFBFail,
OENBOk,
OENBFail,
OEBOk,
OEBFail,
OEFNBOk,
OEFNBFail,
OEFBOk,
OEFBFail,
BENBOk,
BENBFail,
BEBOk,
BEBFail,
BEFNBOk,
BEFNBFail,
BEFBOk,
BEFBFail,
}
#[allow(clippy::unnecessary_wraps)]
const fn return_ok() -> Result<(), String> {
Ok(())
}
fn return_err() -> Result<(), String> {
Err(String::from("this error message should be displayed"))
}
#[allow(clippy::unnecessary_wraps)]
const fn o_return_some() -> Option<()> {
Some(())
}
const fn o_return_none() -> Option<()> {
None
}
const fn return_true() -> bool {
true
}
const fn return_false() -> bool {
false
}
fn e_res_handle(success: bool) -> Result<(), String> {
#[allow(clippy::unnecessary_lazy_evaluations)]
success.then(|| ()).ok_or_else(|| "oof".to_owned())
}
fn e_opt_handle(success: bool) -> Option<u32> {
#[allow(clippy::unnecessary_lazy_evaluations)]
success.then(|| 0)
}
fn e_res_nb(success: bool) -> Result<(), ExpectationFailed> {
e_res_handle(success).expect_result_nb_("this error message should be displayed")
}
fn e_res(success: bool) -> Result<(), Box<dyn Error>> {
e_res_handle(success).expect_result_("this error message should be displayed")
}
fn e_opt_nb(success: bool) -> Result<(), ExpectationFailed> {
let res = e_opt_handle(success).expect_result_nb_("this error message should be displayed")?;
assert!(res == 0, "e_opt_handle() did not return 0");
Ok(())
}
fn e_opt(success: bool) -> Result<(), Box<dyn Error>> {
let res = e_opt_handle(success).expect_result_("this error message should be displayed")?;
assert!(res == 0, "e_opt_handle() did not return 0");
Ok(())
}
fn e_bool_nb(success: bool) -> Result<(), ExpectationFailed> {
success.expect_result_nb_("this error message should be displayed")
}
fn e_bool(success: bool) -> Result<(), Box<dyn Error>> {
success.expect_result_("this error message should be displayed")
}
fn usage() -> ! {
expect_exit::die("Usage: expect-exit <mode>");
}
fn parse_args() -> OpMode {
let args: Vec<String> = env::args().collect();
match &*args {
&[_, ref req] => {
let action = match req.as_str() {
"nu_ok" => OpMode::RNUOk,
"nu_fail" => OpMode::RNUFail,
"ok" => OpMode::RUOk,
"fail" => OpMode::RUFail,
"f_nu_ok" => OpMode::RFNUOk,
"f_nu_fail" => OpMode::RFNUFail,
"f_ok" => OpMode::RFUOk,
"f_fail" => OpMode::RFUFail,
"o_nu_ok" => OpMode::ONUOk,
"o_nu_fail" => OpMode::ONUFail,
"o_ok" => OpMode::OUOk,
"o_fail" => OpMode::OUFail,
"o_f_nu_ok" => OpMode::OFNUOk,
"o_f_nu_fail" => OpMode::OFNUFail,
"o_f_ok" => OpMode::OFUOk,
"o_f_fail" => OpMode::OFUFail,
"b_n_u_true" => OpMode::BNUTrue,
"b_n_u_false" => OpMode::BNUFalse,
"b_u_true" => OpMode::BUTrue,
"b_u_false" => OpMode::BUFalse,
"b_f_n_u_true" => OpMode::BFNUTrue,
"b_f_n_u_false" => OpMode::BFNUFalse,
"b_f_u_true" => OpMode::BFUTrue,
"b_f_u_false" => OpMode::BFUFalse,
"e_nb_ok" => OpMode::RENBOk,
"e_nb_fail" => OpMode::RENBFail,
"e_b_ok" => OpMode::REBOk,
"e_b_fail" => OpMode::REBFail,
"e_f_nb_ok" => OpMode::REFNBOk,
"e_f_nb_fail" => OpMode::REFNBFail,
"e_f_b_ok" => OpMode::REFBOk,
"e_f_b_fail" => OpMode::REFBFail,
"o_e_nb_ok" => OpMode::OENBOk,
"o_e_nb_fail" => OpMode::OENBFail,
"o_e_b_ok" => OpMode::OEBOk,
"o_e_b_fail" => OpMode::OEBFail,
"o_e_f_nb_ok" => OpMode::OEFNBOk,
"o_e_f_nb_fail" => OpMode::OEFNBFail,
"o_e_f_b_ok" => OpMode::OEFBOk,
"o_e_f_b_fail" => OpMode::OEFBFail,
"b_e_nb_ok" => OpMode::BENBOk,
"b_e_nb_fail" => OpMode::BENBFail,
"b_e_b_ok" => OpMode::BEBOk,
"b_e_b_fail" => OpMode::BEBFail,
"b_e_f_nb_ok" => OpMode::BEFNBOk,
"b_e_f_nb_fail" => OpMode::BEFNBFail,
"b_e_f_b_ok" => OpMode::BEFBOk,
"b_e_f_b_fail" => OpMode::BEFBFail,
_ => usage(),
};
action
}
_ => usage(),
}
}
fn handle(mode: OpMode) {
let val = UnwindTest { value: 616 };
println!("Created a test variable with the value {}", val.value);
let cb = || {
println!("The format callback was invoked.");
format!("something about {}", val.value)
};
match mode {
OpMode::RNUOk => return_ok().or_die_e_("This should not be triggered"),
OpMode::RNUFail => return_err().or_die_e_("This should be triggered"),
OpMode::RUOk => return_ok().or_exit_e_("This should not be triggered"),
OpMode::RUFail => return_err().or_exit_e_("This should be triggered"),
OpMode::RFNUOk => return_ok().or_die_e(cb),
OpMode::RFNUFail => return_err().or_die_e(cb),
OpMode::RFUOk => return_ok().or_exit_e(cb),
OpMode::RFUFail => return_err().or_exit_e(cb),
OpMode::ONUOk => o_return_some().or_die_("This should not be triggered"),
OpMode::ONUFail => o_return_none().or_die_("This should be triggered"),
OpMode::OUOk => o_return_some().or_exit_("This should not be triggered"),
OpMode::OUFail => o_return_none().or_exit_("This should be triggered"),
OpMode::OFNUOk => o_return_some().or_die(cb),
OpMode::OFNUFail => o_return_none().or_die(cb),
OpMode::OFUOk => o_return_some().or_exit(cb),
OpMode::OFUFail => o_return_none().or_exit(cb),
OpMode::BNUTrue => {
return_true().or_die_("This should not be triggered");
}
OpMode::BNUFalse => {
return_false().or_die_("This should be triggered");
}
OpMode::BUTrue => {
return_true().or_exit_("This should not be triggered");
}
OpMode::BUFalse => {
return_false().or_exit_("This should be triggered");
}
OpMode::BFNUTrue => {
return_true().or_die(cb);
}
OpMode::BFNUFalse => {
return_false().or_die(cb);
}
OpMode::BFUTrue => {
return_true().or_exit(cb);
}
OpMode::BFUFalse => {
return_false().or_exit(cb);
}
OpMode::RENBOk => e_res_nb(true).or_die_e_("This should not be triggered"),
OpMode::RENBFail => e_res_nb(false).or_die_e_("This should be triggered"),
OpMode::REBOk => e_res(true).or_die_e_("This should not be triggered"),
OpMode::REBFail => e_res(false).or_die_e_("This should be triggered"),
OpMode::REFNBOk => e_res_nb(true).or_die_e(cb),
OpMode::REFNBFail => e_res_nb(false).or_die_e(cb),
OpMode::REFBOk => e_res(true).or_die_e(cb),
OpMode::REFBFail => e_res(false).or_die_e(cb),
OpMode::OENBOk => e_opt_nb(true).or_die_e_("This should not be triggered"),
OpMode::OENBFail => e_opt_nb(false).or_die_e_("This should be triggered"),
OpMode::OEBOk => e_opt(true).or_die_e_("This should not be triggered"),
OpMode::OEBFail => e_opt(false).or_die_e_("This should be triggered"),
OpMode::OEFNBOk => e_opt_nb(true).or_die_e(cb),
OpMode::OEFNBFail => e_opt_nb(false).or_die_e(cb),
OpMode::OEFBOk => e_opt(true).or_die_e(cb),
OpMode::OEFBFail => e_opt(false).or_die_e(cb),
OpMode::BENBOk => e_bool_nb(true).or_die_e_("This should not be triggered"),
OpMode::BENBFail => e_bool_nb(false).or_die_e_("This should be triggered"),
OpMode::BEBOk => e_bool(true).or_die_e_("This should not be triggered"),
OpMode::BEBFail => e_bool(false).or_die_e_("This should be triggered"),
OpMode::BEFNBOk => e_bool_nb(true).or_die_e(cb),
OpMode::BEFNBFail => e_bool_nb(false).or_die_e(cb),
OpMode::BEFBOk => e_bool(true).or_die_e(cb),
OpMode::BEFBFail => e_bool(false).or_die_e(cb),
};
println!("This is the end.");
}
fn main() {
let mode = parse_args();
handle(mode);
}
#[cfg(test)]
mod tests {
#![warn(clippy::panic_in_result_fn)]
#![allow(clippy::panic)]
use std::env;
use std::error::Error;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use once_cell::sync::Lazy;
struct TestCase<'data> {
arg: &'data str,
stdout: Vec<&'data str>,
stderr: Vec<&'data str>,
result: i32,
}
const EXP_FIRST: &str = "with the value 616";
const EXP_LAST: &str = "the end";
const EXP_DROP: &str = "UnwindTest.drop";
const EXP_ERROR: &str = "should be displayed";
const EXP_TRIG: &str = "should be triggered";
const EXP_FMT_CB: &str = "format callback";
const EXP_FMT_RES: &str = "something about 616";
fn get_exe_path(name: &str) -> Result<PathBuf, Box<dyn Error>> {
let current = env::current_exe()?;
let exe_dir = {
let basedir = Path::new(¤t).parent().ok_or_else(|| {
format!(
"Could not get the parent directory of {}",
current.display()
)
})?;
if basedir
.file_name()
.ok_or_else(|| format!("Could not get the base name of {}", basedir.display()))?
== "deps"
{
basedir.parent().ok_or_else(|| {
format!(
"Could not get the parent directory of {}",
basedir.display()
)
})?
} else {
basedir
}
};
Ok(exe_dir.join(name))
}
#[allow(clippy::too_many_lines)]
fn get_test_cases() -> &'static [TestCase<'static>] {
static TEST_CASES: Lazy<Vec<TestCase<'static>>> = Lazy::new(|| {
vec![
TestCase {
arg: "oof",
stdout: vec![],
stderr: vec!["Usage: "],
result: 1,
},
TestCase {
arg: "nu_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "nu_fail",
stdout: vec![EXP_FIRST],
stderr: vec![EXP_ERROR],
result: 1,
},
TestCase {
arg: "f_nu_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "f_nu_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB],
stderr: vec![EXP_ERROR],
result: 1,
},
TestCase {
arg: "ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "fail",
stdout: vec![EXP_FIRST, EXP_DROP],
stderr: vec![EXP_ERROR],
result: 1,
},
TestCase {
arg: "f_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "f_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB, EXP_DROP],
stderr: vec![EXP_ERROR],
result: 1,
},
TestCase {
arg: "o_nu_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "o_nu_fail",
stdout: vec![EXP_FIRST],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "o_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "o_fail",
stdout: vec![EXP_FIRST, EXP_DROP],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "o_f_nu_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "o_f_nu_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB],
stderr: vec![EXP_FMT_RES],
result: 1,
},
TestCase {
arg: "o_f_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "o_f_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB, EXP_DROP],
stderr: vec![EXP_FMT_RES],
result: 1,
},
TestCase {
arg: "b_n_u_true",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "b_n_u_false",
stdout: vec![EXP_FIRST],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "b_u_true",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "b_u_false",
stdout: vec![EXP_FIRST, EXP_DROP],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "b_f_n_u_true",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "b_f_n_u_false",
stdout: vec![EXP_FIRST, EXP_FMT_CB],
stderr: vec![EXP_FMT_RES],
result: 1,
},
TestCase {
arg: "b_f_u_true",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "b_f_u_false",
stdout: vec![EXP_FIRST, EXP_FMT_CB, EXP_DROP],
stderr: vec![EXP_FMT_RES],
result: 1,
},
TestCase {
arg: "e_nb_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "e_nb_fail",
stdout: vec![EXP_FIRST],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "e_b_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "e_b_fail",
stdout: vec![EXP_FIRST],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "e_f_nb_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "e_f_nb_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB],
stderr: vec![EXP_FMT_RES],
result: 1,
},
TestCase {
arg: "e_f_b_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "e_f_b_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB],
stderr: vec![EXP_FMT_RES],
result: 1,
},
TestCase {
arg: "o_e_nb_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "o_e_nb_fail",
stdout: vec![EXP_FIRST],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "o_e_b_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "o_e_b_fail",
stdout: vec![EXP_FIRST],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "o_e_f_nb_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "o_e_f_nb_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB],
stderr: vec![EXP_FMT_RES],
result: 1,
},
TestCase {
arg: "o_e_f_b_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "o_e_f_b_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB],
stderr: vec![EXP_FMT_RES],
result: 1,
},
TestCase {
arg: "b_e_nb_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "b_e_nb_fail",
stdout: vec![EXP_FIRST],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "b_e_b_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "b_e_b_fail",
stdout: vec![EXP_FIRST],
stderr: vec![EXP_TRIG],
result: 1,
},
TestCase {
arg: "b_e_f_nb_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "b_e_f_nb_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB],
stderr: vec![EXP_FMT_RES],
result: 1,
},
TestCase {
arg: "b_e_f_b_ok",
stdout: vec![EXP_FIRST, EXP_LAST, EXP_DROP],
stderr: vec![],
result: 0,
},
TestCase {
arg: "b_e_f_b_fail",
stdout: vec![EXP_FIRST, EXP_FMT_CB],
stderr: vec![EXP_FMT_RES],
result: 1,
},
]
});
&TEST_CASES
}
fn check_output_contains(expected: &[&str], actual: &[&str]) -> bool {
expected.len() == actual.len()
&& expected
.iter()
.zip(actual.iter())
.all(|(&exp, &act)| act.contains(exp))
}
fn check_test_output(
case: &TestCase<'_>,
stdout_lines: &[&str],
stderr_lines: &[&str],
) -> bool {
check_output_contains(&case.stdout, stdout_lines)
&& check_output_contains(&case.stderr, stderr_lines)
}
fn run_test(program: &str) {
println!("Running a test on {}", program);
for case in get_test_cases() {
let desc = &format!("'{} {}'", program, case.arg);
println!(
"\n=====\nrun {} stdout {:?} stderr {:?}",
desc, case.stdout, case.stderr
);
let mut proc = Command::new(program)
.arg(case.arg)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap_or_else(|err| panic!("Could not run {}: {}", desc, err));
let mut stdout_text = String::new();
proc.stdout
.take()
.unwrap_or_else(|| panic!("Could not open the standard output stream of {}", desc))
.read_to_string(&mut stdout_text)
.unwrap_or_else(|err| panic!("Could not read the output of {}: {}", desc, err));
let stdout_lines: Vec<&str> = stdout_text.split_terminator('\n').collect();
let mut stderr_text = String::new();
proc.stderr
.take()
.unwrap_or_else(|| panic!("Could not open the standard error stream of {}", desc))
.read_to_string(&mut stderr_text)
.unwrap_or_else(|err| {
panic!("Could not read the error output of {}: {}", desc, err)
});
let stderr_lines: Vec<&str> = stderr_text.split_terminator('\n').collect();
println!(
"Got {} line(s) of output, {} line(s) of error output.",
stdout_lines.len(),
stderr_lines.len()
);
assert!(
check_test_output(case, &stdout_lines, &stderr_lines),
"Output mismatch for {}: ({:?}, {:?}) vs ({:?}, {:?})",
desc,
case.stdout,
case.stderr,
stdout_lines,
stderr_lines
);
let res = proc
.wait()
.unwrap_or_else(|err| panic!("Could not wait for {}: {}", desc, err))
.code()
.unwrap_or_else(|| panic!("Could not fetch the exit code for {}", desc));
println!("\n{} exited with code {}", desc, res);
assert!(
res == case.result,
"{} exited with code {}, expected {}",
desc,
res,
case.result
);
}
println!("\n=====\nThe self-test seems to have run successfully.");
}
#[test]
fn test_expect_exit() -> Result<(), Box<dyn Error>> {
let exe_path = get_exe_path("test_expect_exit")?;
run_test(
exe_path
.to_str()
.ok_or_else(|| format!("Could not convert {:?} to a string", exe_path))?,
);
Ok(())
}
}