[go: up one dir, main page]

rstest 0.3.0

A simple `pytest` clone for Rust. It use procedural macro to implement fixtures and table based tests.
Documentation
use std::path::{Path, PathBuf};
use std::borrow::Cow;
use std::thread;


pub fn resources<O: AsRef<Path>>(name: O) -> PathBuf {
    Path::new("resources").join(name)
}

#[derive(Clone)]
pub enum TestResult<S: AsRef<str>> {
    Ok(S),
    Fail(S),
}

#[macro_export]
macro_rules! assert_in {
    ($text:expr, $message:expr) => ({
        match (&$text, &$message) {
            (text_val, message_val) => {
                if !text_val.contains(message_val) {
                    panic!(r#"assertion failed: `text not contains message`
         text: `{}`,
         message: `{}`"#, text_val, message_val)
                }
            }
        }
        });
    ($message:expr, $expected:expr, ) => (
        assert_in!($message, $expected)
    );
    ($text:expr, $message:expr, $($arg:tt)+) => ({
        match (&$text, &$message) {
            (text_val, message_val) => {
                if !text_val.contains(message_val) {
                    panic!(r#"assertion failed: `text not contains message`
         text: `{}`,
         message: `{}`: {}"#, text_val, message_val, format_args!($($arg)+))
                }
            }
        }
        });
}

impl<S: AsRef<str>> TestResult<S> {
    pub fn is_fail(&self) -> bool {
        use self::TestResult::*;
        match *self {
            Fail(_) => true,
            _ => false
        }
    }

    pub fn is_ok(&self) -> bool {
        use self::TestResult::*;
        match *self {
            Ok(_) => true,
            _ => false
        }
    }

    pub fn name(&self) -> String {
        use self::TestResult::*;
        match self {
            &Ok(ref s) => s.as_ref().to_owned(),
            &Fail(ref s) => s.as_ref().to_owned()
        }
    }

    pub fn msg(&self) -> &'static str {
        use self::TestResult::*;
        match *self {
            Ok(_) => "ok",
            Fail(_) => "FAILED",
        }
    }
}

#[derive(Default, Clone)]
pub struct TestResults<S>(Vec<TestResult<S>>) where S: AsRef<str> + Clone;

impl<S> TestResults<S>
    where S: AsRef<str> + Clone
{
    pub fn new() -> Self {
        TestResults(vec![])
    }

    pub fn ok(self, name: S) -> Self {
        self.append(TestResult::Ok(name))
    }

    pub fn fail(self, name: S) -> Self {
        self.append(TestResult::Fail(name))
    }

    pub fn append(mut self, test: TestResult<S>) -> Self {
        self.0.push(test);
        self
    }

    pub fn assert(&self, output: ::std::process::Output) {
        let tests = &self.0;

        let (expected_code, msg) = if !self.should_fail()
            { (0, "Unexpected fails!") } else { (101, "Some test should fail!") };
        assert_eq!(Some(expected_code), output.status.code(),
                   "{}\n Console: \nOUT:\n{}\nERR:\n{}\n",
                   msg, output.stdout.str(), output.stderr.str());

        let stderr = output.stderr.str();
        let output = output.stdout.str();
        if output.is_empty() {
            eprintln!("Stderr: {}", stderr);
            panic!("Empty stdout!");
        }

        assert_in!(output, format!("running {} test", tests.len()));

        self.for_each(
            |t| assert_in!(output, format!("test {} ... {}", t.name(), t.msg()))
        );

        if self.should_fail() {
            assert_in!(output, format!("failures:"));
        }

        self.for_each_failed(
            |t| assert_in!(output, format!("    {}", t.name()))
        );
    }

    fn should_fail(&self) -> bool {
        self.0.iter().filter(|r| r.is_fail()).next().is_some()
    }

    fn for_each<F: FnMut(&TestResult<S>) -> ()>(&self, action: F) {
        self.0.iter().for_each(action)
    }

    fn for_each_failed<F: FnMut(&TestResult<S>) -> ()>(&self, action: F) {
        self.0.iter().filter(|r| r.is_fail()).for_each(action)
    }
}

pub trait Stringable {
    fn str(&self) -> Cow<str>;
}

impl<B: AsRef<[u8]>> Stringable for B {
    fn str(&self) -> Cow<str> {
        String::from_utf8_lossy(self.as_ref())
    }
}

pub fn testname() -> String {
    thread::current().name().unwrap().to_string()
}

pub mod deindent {
    pub trait Deindent {
        fn deindent(self) -> String;
    }

    impl <S: AsRef<str>> Deindent for S {
        fn deindent(self) -> String {
            use std::fmt::Write;
            let message = self.as_ref();
            let skip = min_indent_size(message);
            let mut output = String::new();
            message.lines().for_each(|l|
                writeln!(&mut output, "{}", &l[skip.min(l.len())..])
                    .unwrap());
            output
        }
    }

    fn min_indent_size(message: &str) -> usize {
        let skip = message.lines()
            .filter(|l| l.len() > 0)
            .map(|l| count_start(l, ' '))
            .min().unwrap_or(0);
        skip
    }

    fn count_start(l: &str, ch: char) -> usize {
        l.chars().take_while(|&c| c == ch ).count()
    }
}

pub trait CountMessageOccurrence {
    fn count<S: AsRef<str>>(&self, message: S) -> usize;
}

impl<ST> CountMessageOccurrence for ST where ST: AsRef<str> {
    fn count<S: AsRef<str>>(&self, message: S) -> usize {
        self.as_ref().lines()
            .filter(|line| line.contains(
                message.as_ref()))
            .count()
    }
}