Better tests

This commit is contained in:
Adrian Heine 2022-01-10 11:56:06 +01:00
parent 395fb1c9fa
commit 0d41e8a833
2 changed files with 112 additions and 63 deletions

View file

@ -2,12 +2,13 @@ use std::cell::RefCell;
use std::cmp::min; use std::cmp::min;
use std::io::stderr; use std::io::stderr;
use std::io::Write; use std::io::Write;
use std::rc::Rc;
// The log crate defines // The log crate defines
// 1 - Error, 2 - Warn, 3 - Info, 4 - Debug, 5 - Trace // 1 - Error, 2 - Warn, 3 - Info, 4 - Debug, 5 - Trace
pub type Level = usize; pub type Level = usize;
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub struct Entry<S>(pub Level, pub S); pub struct Entry<S>(pub Level, pub S);
pub trait Logger { pub trait Logger {
@ -100,7 +101,7 @@ impl<'a, L: Logger> Logger for FilteringLogger<'a, L> {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct StoringLogger { pub struct StoringLogger {
log: RefCell<(bool, Vec<Entry<String>>)>, log: Rc<RefCell<(bool, Vec<Entry<String>>)>>,
} }
impl StoringLogger { impl StoringLogger {
@ -110,7 +111,7 @@ impl StoringLogger {
} }
pub fn release(self) -> Vec<Entry<String>> { pub fn release(self) -> Vec<Entry<String>> {
self.log.into_inner().1 Rc::try_unwrap(self.log).unwrap().into_inner().1
} }
} }

View file

@ -1,20 +1,25 @@
use async_trait::async_trait; use async_trait::async_trait;
use regex::Regex;
use schematics::async_utils::{run, sleep}; use schematics::async_utils::{run, sleep};
use schematics::loggers::StoringLogger; use schematics::loggers::{Entry, StoringLogger};
use schematics::resources::{AcmeUser, Cert, Csr, GitCheckout}; use schematics::resources::{AcmeUser, Cert, Csr, GitCheckout};
use schematics::symbols::Symbol; use schematics::symbols::Symbol;
use schematics::Setup; use schematics::Setup;
use schematics::SymbolRunner; use schematics::SymbolRunner;
use slog::Logger as SlogLogger; use slog::{info, Logger as SlogLogger};
use std::cell::RefCell;
use std::error::Error; use std::error::Error;
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct TestSymbolRunner { struct TestSymbolRunner {
count: Rc<RefCell<usize>>, run: bool,
}
impl TestSymbolRunner {
fn new(run: bool) -> Self {
Self { run }
}
} }
#[async_trait(?Send)] #[async_trait(?Send)]
@ -22,80 +27,123 @@ impl SymbolRunner for TestSymbolRunner {
async fn run_symbol<S: Symbol + Debug>( async fn run_symbol<S: Symbol + Debug>(
&self, &self,
_symbol: &S, _symbol: &S,
_logger: &SlogLogger, logger: &SlogLogger,
_force: bool, _force: bool,
) -> Result<bool, Box<dyn Error>> { ) -> Result<bool, Box<dyn Error>> {
*self.count.borrow_mut() += 1; info!(logger, "run_symbol");
sleep(Duration::from_millis(0)).await; sleep(Duration::from_millis(0)).await;
Ok(false) Ok(self.run)
} }
} }
fn test(
count: usize,
body: fn(setup: Setup<TestSymbolRunner, StoringLogger>) -> (),
) -> Vec<Entry<String>> {
let runner = TestSymbolRunner::new(false);
let logger = StoringLogger::new();
{
let setup = Setup::new(runner, logger.clone());
body(setup);
}
assert_eq!(logger.release(), vec![Entry(3, ".".repeat(count))]);
let runner = TestSymbolRunner::new(true);
let logger = StoringLogger::new();
{
let setup = Setup::new(runner, logger.clone());
body(setup);
}
logger.release()
}
#[test] #[test]
fn can_create_an_acme_user() { fn can_create_an_acme_user() {
let count = Rc::new(RefCell::new(0)); let mut result = test(1, |setup| {
let runner = TestSymbolRunner { assert_eq!((run(setup.add(AcmeUser)).unwrap().0).0, "acme");
count: Rc::clone(&count), });
}; let entry = result
let setup = Setup::new(runner, StoringLogger::new()); .pop()
assert_eq!((run(setup.add(AcmeUser)).unwrap().0).0, "acme"); .expect("log is empty but should contain one entry");
assert_eq!(result.len(), 0, "log has more than one entry");
assert_eq!(entry.0, 3, "log entry has wrong level");
let re =
Regex::new(r"^resource: AcmeUser\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run_symbol\n$")
.unwrap();
assert!(
re.is_match(&entry.1),
"log output {} does not match {}",
entry.1,
re
);
} }
#[test] #[test]
fn runs_only_once() { fn runs_only_once() {
run(async { let mut result = test(2, |setup| {
let count = Rc::new(RefCell::new(0)); run(async {
let runner = TestSymbolRunner { assert_eq!(
count: Rc::clone(&count), (setup.add(Csr("somehost")).await.unwrap().0)
}; .as_ref()
let setup = Setup::new(runner, StoringLogger::new()); .to_str()
assert_eq!( .unwrap(),
(setup.add(Csr("somehost")).await.unwrap().0) "/etc/ssl/local_certs/somehost.csr",
.as_ref() );
.to_str() assert_eq!(
.unwrap(), (setup.add(Csr("somehost")).await.unwrap().0)
"/etc/ssl/local_certs/somehost.csr", .as_ref()
); .to_str()
assert_eq!( .unwrap(),
(setup.add(Csr("somehost")).await.unwrap().0) "/etc/ssl/local_certs/somehost.csr",
.as_ref() );
.to_str() });
.unwrap(),
"/etc/ssl/local_certs/somehost.csr",
);
assert_eq!(*count.borrow(), 2 + 5); // Key and CSR + 5 dirs
}); });
let entry = result
.pop()
.expect("log is empty but should contain entries");
assert_eq!(entry.0, 3, "log entry has wrong level");
assert_eq!(entry.1, ".", "log entry has wrong content");
let entry = result
.pop()
.expect("log is empty but should contain entries");
assert_eq!(entry.0, 3, "log entry has wrong level");
assert_eq!(entry.1.matches("run_symbol").count(), 7); // Key and CSR + 5 dirs
assert_eq!(result.len(), 0, "log has more than one entry");
} }
#[test] #[test]
fn can_create_an_acme_cert() { fn can_create_an_acme_cert() {
let count = Rc::new(RefCell::new(0)); let mut result = test(1, |setup| {
let runner = TestSymbolRunner { assert_eq!(
count: Rc::clone(&count), (run(setup.add(Cert("somehost"))).unwrap().0)
}; .as_ref()
let setup = Setup::new(runner, StoringLogger::new()); .to_str()
assert_eq!( .unwrap(),
(run(setup.add(Cert("somehost"))).unwrap().0) "/etc/ssl/local_certs/somehost.crt",
.as_ref() );
.to_str() });
.unwrap(), let entry = result
"/etc/ssl/local_certs/somehost.crt", .pop()
); .expect("log is empty but should contain one entry");
assert_eq!(*count.borrow(), 19); assert_eq!(entry.0, 3, "log entry has wrong level");
assert_eq!(entry.1.matches("run_symbol").count(), 19);
assert_eq!(result.len(), 0, "log has more than one entry");
} }
#[test] #[test]
fn can_create_a_git_checkout() { fn can_create_a_git_checkout() {
let count = Rc::new(RefCell::new(0)); let mut result = test(1, |setup| {
let runner = TestSymbolRunner { run(setup.add(GitCheckout(
count: Rc::clone(&count), "/tmp/somepath".into(),
}; "/tmp/some_src_repo",
let setup = Setup::new(runner, StoringLogger::new()); "master",
run(setup.add(GitCheckout( )))
"/tmp/somepath".into(), .unwrap();
"/tmp/some_src_repo", });
"master", let entry = result
))) .pop()
.unwrap(); .expect("log is empty but should contain one entry");
assert_eq!(*count.borrow(), 3); assert_eq!(entry.0, 3, "log entry has wrong level");
assert_eq!(entry.1.matches("run_symbol").count(), 3);
assert_eq!(result.len(), 0, "log has more than one entry");
} }