specimen 0.1.0

A data-driven testing library as well as a yaml data format.
Documentation
use regex::Regex;
use specimen::Writable;
use std::collections::HashMap;

fn indent(text: &str, spaces: usize) -> String {
    let indent = " ".repeat(spaces);
    return text
        .lines()
        .map(|line| format!("{}{}", indent, line))
        .collect::<Vec<String>>()
        .join("\n");
}

fn to_hashmap(data: &yaml_rust::Yaml) -> specimen::Dict {
    return match data {
        yaml_rust::Yaml::Hash(key_value_vec) => {
            let mut hashmap = HashMap::new();
            for (yaml_key, yaml_value) in key_value_vec {
                let key = match yaml_key {
                    yaml_rust::Yaml::String(key) => key,
                    _ => panic!("Expected the key to be a YAML string"),
                };
                let value = match yaml_value {
                    yaml_rust::Yaml::String(value) => value,
                    _ => panic!("Expected the value to be a YAML string"),
                };
                hashmap.insert(key.clone().into(), value.clone().into());
            }
            hashmap
        }
        _ => panic!("Expected a YAML mapping"),
    };
}

fn call_logger(tile: &specimen::Dict) -> Result<(), Box<str>> {
    let call_string = &tile["calls"];
    let call_yaml = &yaml_rust::YamlLoader::load_from_str(call_string).unwrap()[0];
    let call_vec = match call_yaml {
        yaml_rust::Yaml::Array(vec) => vec,
        _ => panic!("Expected a YAML array"),
    };
    let mut index = 0;
    let mut error = "".to_string();
    let result = specimen::ioless_run(
        &mut |spec_tile: &specimen::Dict| -> Result<(), Box<str>> {
            let mut expected_tile = to_hashmap(&call_vec[index]);
            expected_tile.insert("filepath".into(), spec_tile["filepath"].clone());

            if error.len() == 0 && expected_tile != *spec_tile {
                error = format!(
                    "[Call {}]\nExpected: {:?}\nActual__: {:?}",
                    index, expected_tile, spec_tile,
                );
            }
            index += 1;
            Ok(())
        },
        &[specimen::file::File {
            path: tile["filepath"].clone(),
            content: tile["spec"].clone(),
        }],
        &mut Writable::Vec(Vec::new()),
    );

    if let Err(err) = result {
        return Err(err.to_string().into());
    }

    if index != call_vec.len() {
        return Err(format!("Expected {} calls, but got {}", call_vec.len(), index).into());
    }

    return if error != "" {
        return Err(error.into());
    } else {
        Ok(())
    };
}

fn report(tile: &specimen::Dict) -> Result<(), Box<str>> {
    let behavior_yaml = &yaml_rust::YamlLoader::load_from_str(&tile["behavior"]).unwrap()[0];
    let behavior = to_hashmap(behavior_yaml);
    let mut writable_buffer = Writable::Vec(Vec::new());

    let result = specimen::ioless_run(
        &mut |spec_tile: &specimen::Dict| -> Result<(), Box<str>> {
            let outcome = &behavior[&spec_tile["letter"]];
            return match &**outcome {
                "pass" => Ok(()),
                "fail" => Err("failure".into()),
                "abort" => Err("ABORTaborted".into()),
                other => Err(format!("ABORT: unknown outcome: {}", other).into()),
            };
        },
        &[specimen::file::File {
            path: tile["filepath"].clone(),
            content: tile["spec"].clone(),
        }],
        &mut writable_buffer,
    );

    if let Err(error) = result {
        return Err(error.to_string().into());
    }

    let buffer = match writable_buffer {
        Writable::Vec(vec) => vec,
        _ => panic!("Expected a Vec"),
    };
    let output = String::from_utf8(buffer).unwrap();
    let report_regex = Regex::new(&tile["report"]).unwrap();
    if !report_regex.is_match(&output) {
        Err(format!(
            "Expected:\n{}\nActual:\n{}",
            indent(&*tile["report"], 4),
            indent(&*output, 4)
        )
        .into())
    } else {
        Ok(())
    }
}

#[test]
fn test_spec() {
    let test_passed = specimen::run(
        &mut |tile: &specimen::Dict| -> Result<(), Box<str>> {
            return match tile.get("box") {
                Some(box_name) if **box_name == *"call-logger" => call_logger(tile),
                Some(box_name) if **box_name == *"report" => report(tile),
                Some(box_name) => Err(format!("Unknown box: {}", box_name).into()),
                None => Err("No box specified".into()),
            };
        },
        &[
            specimen::file::File::read_local_file("../spec/about.yaml"),
            specimen::file::File::read_local_file("../spec/flag.yaml"),
            specimen::file::File::read_local_file("../spec/matrix.yaml"),
            specimen::file::File::read_local_file("../spec/report.yaml"),
        ],
    );

    assert!(test_passed);
}