Skip to content

Commit

Permalink
fakecmd and print errors into the env
Browse files Browse the repository at this point in the history
  • Loading branch information
tertsdiepraam committed Nov 14, 2024
1 parent 094d5e2 commit e3281fc
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/bin/ldns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ fn main() -> ExitCode {
let args =
try_ldns_compatibility(args).map(|args| args.expect("ldns commmand is not recognized"));

match args.and_then(|args| args.execute(env)) {
match args.and_then(|args| args.execute(&env)) {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
err.pretty_print();
err.pretty_print(env);
ExitCode::FAILURE
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/commands/nsec3hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,36 @@ where
// For normal hash algorithms this should not fail.
OwnerHash::from_octets(h.as_ref().to_vec()).expect("should not fail")
}

#[cfg(test)]
mod test {
use crate::env::fake::FakeCmd;

#[test]
fn dnst_parse() {
let cmd = FakeCmd::new(["dnst", "nsec3-hash"]);

assert!(cmd.parse().is_err());
assert!(cmd.args(["-a"]).parse().is_err());
}

#[test]
fn dnst_run() {
let cmd = FakeCmd::new(["dnst", "nsec3-hash"]);

let res = cmd.run();
assert_eq!(res.exit_code, 2);

let res = cmd.args(["example.test"]).run();
assert_eq!(res.exit_code, 0);
assert_eq!(res.stdout, "o09614ibh1cq1rcc86289olr22ea0fso.\n")
}

#[test]
fn ldns_parse() {
let cmd = FakeCmd::new(["ldns-nsec3-hash"]);

assert!(cmd.parse().is_err());
assert!(cmd.args(["-a"]).parse().is_err());
}
}
83 changes: 74 additions & 9 deletions src/env/fake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,36 @@ use std::ffi::OsString;
use std::fmt;
use std::sync::Arc;

use crate::{error::Error, parse_args, run, Args};

use super::Env;

#[derive(Clone)]
pub struct FakeCmd {
/// The (sub)command to run, including `argv[0]`
pub cmd: Vec<OsString>,

/// The arguments for the commands
pub args: Vec<OsString>,
}

pub struct FakeResult {
pub exit_code: u8,
pub stdout: String,
pub stderr: String,
}

/// Use fake I/O and Stelline for testing
#[derive(Default)]
pub struct FakeEnv {
// pub stelline: Option<Stelline>,
// pub curr_step_value: Option<Arc<CurrStepValue>>,
pub args: Vec<OsString>,
pub stdout: FakeOutput,
pub stderr: FakeOutput,
pub cmd: FakeCmd,

/// The collected stdout
pub stdout: FakeStream,

/// The collected stderr
pub stderr: FakeStream,
}

impl Env for FakeEnv {
Expand All @@ -25,7 +45,11 @@ impl Env for FakeEnv {
// }

fn args_os(&self) -> impl Iterator<Item = OsString> {
self.args.clone().into_iter()
self.cmd
.cmd
.iter()
.map(Into::into)
.chain(self.cmd.args.clone())
}

fn stdout(&self) -> impl fmt::Write {
Expand All @@ -37,29 +61,70 @@ impl Env for FakeEnv {
}
}

impl FakeCmd {
pub fn new<S: Into<OsString>>(cmd: impl IntoIterator<Item = S>) -> Self {
Self {
cmd: cmd.into_iter().map(Into::into).collect(),
args: Vec::new(),
}
}

pub fn args<S: Into<OsString>>(&self, args: impl IntoIterator<Item = S>) -> Self {
Self {
args: args.into_iter().map(Into::into).collect(),
..self.clone()
}
}

pub fn parse(&self) -> Result<Args, Error> {
let env = FakeEnv {
cmd: self.clone(),
stdout: Default::default(),
stderr: Default::default(),
};
parse_args(env)
}

pub fn run(&self) -> FakeResult {
let env = FakeEnv {
cmd: self.clone(),
stdout: Default::default(),
stderr: Default::default(),
};

let exit_code = run(&env);

FakeResult {
exit_code,
stdout: env.get_stdout(),
stderr: env.get_stderr(),
}
}
}

impl FakeEnv {
pub fn get_stdout(&self) -> String {
let r: &RefCell<_> = &self.stdout.0;
r.borrow().clone()
}

pub fn get_stderr(&self) -> String {
let r: &RefCell<_> = &self.stdout.0;
r.borrow().clone()
}
}

#[derive(Clone, Default)]
pub struct FakeOutput(Arc<RefCell<String>>);
pub struct FakeStream(Arc<RefCell<String>>);

impl fmt::Write for FakeOutput {
impl fmt::Write for FakeStream {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0.borrow_mut().push_str(s);
Ok(())
}
}

impl fmt::Display for FakeOutput {
impl fmt::Display for FakeStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let r: &RefCell<_> = &self.0;
f.write_str(r.borrow().as_ref())
Expand Down
37 changes: 23 additions & 14 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::{error, fmt, io};
use crate::env::Env;
use std::fmt::{self, Write};
use std::{error, io};

//------------ Error ---------------------------------------------------------

Expand All @@ -24,15 +26,6 @@ enum PrimaryError {
Other(Box<str>),
}

impl PrimaryError {
fn print(&self) {
match self {
PrimaryError::Clap(e) => e.print().unwrap(),
PrimaryError::Other(e) => eprint!("{e}"),
};
}
}

impl fmt::Display for PrimaryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expand Down Expand Up @@ -60,8 +53,17 @@ impl Error {
}

/// Pretty-print this error.
pub fn pretty_print(self) {
pub fn pretty_print(&self, env: impl Env) {
use std::io::IsTerminal;
let mut err = env.stderr();

let error = match &self.0.primary {
PrimaryError::Clap(e) => {
let _ = writeln!(err, "{}", e.render().ansi());
return;
}
PrimaryError::Other(error) => error,
};

// NOTE: This is a multicall binary, so argv[0] is necessary for
// program operation. We would fail very early if it didn't exist.
Expand All @@ -74,10 +76,17 @@ impl Error {
"ERROR:"
};

eprint!("[{prog}] {error_marker} ");
self.0.primary.print();
let _ = write!(err, "[{prog}] {error_marker} {error}");
for context in &self.0.context {
eprint!("\n... while {context}");
let _ = writeln!(err, "\n... while {context}");
}
}

pub fn exit_code(&self) -> u8 {
if let PrimaryError::Clap(_) = self.0.primary {
2
} else {
1
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ fn parse_args(env: impl Env) -> Result<Args, Error> {
}

pub fn run(env: impl Env) -> u8 {
let res = parse_args(&env).and_then(|args| args.execute(env));
let res = parse_args(&env).and_then(|args| args.execute(&env));
match res {
Ok(()) => 0,
Err(err) => {
err.pretty_print();
1
err.pretty_print(&env);
err.exit_code()
}
}
}

0 comments on commit e3281fc

Please sign in to comment.