use serde_json::Value;
use std::fmt;
use jmespatch::{compile, Expression, Rcvar, RuntimeError, Variable};
pub enum BenchType {
Parse,
Interpret,
Full,
}
impl BenchType {
fn from_json(bench_type: &Value) -> Result<Self, TestCaseError> {
bench_type
.as_str()
.ok_or(TestCaseError::BenchIsNotString)
.and_then(|b| match b {
"parse" => Ok(BenchType::Parse),
"interpret" => Ok(BenchType::Interpret),
"full" => Ok(BenchType::Full),
s @ _ => Err(TestCaseError::UnknownBenchType(s.to_string())),
})
}
}
impl fmt::Display for BenchType {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
&BenchType::Parse => write!(fmt, "parse"),
&BenchType::Interpret => write!(fmt, "interpret"),
&BenchType::Full => write!(fmt, "full"),
}
}
}
pub enum ErrorType {
InvalidArity,
InvalidType,
InvalidSlice,
UnknownFunction,
SyntaxError,
}
impl ErrorType {
fn from_json(error_type: &Value) -> Result<Self, TestCaseError> {
error_type
.as_str()
.ok_or(TestCaseError::ErrorIsNotString)
.and_then(|b| match b {
"syntax" => Ok(ErrorType::SyntaxError),
"invalid-type" => Ok(ErrorType::InvalidType),
"invalid-value" => Ok(ErrorType::InvalidSlice),
"invalid-arity" => Ok(ErrorType::InvalidArity),
"unknown-function" => Ok(ErrorType::UnknownFunction),
e @ _ => Err(TestCaseError::UnknownErrorType(e.to_string())),
})
}
}
impl fmt::Display for ErrorType {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
use self::ErrorType::*;
match self {
&InvalidArity => write!(fmt, "invalid-arity"),
&InvalidType => write!(fmt, "invalid-type"),
&InvalidSlice => write!(fmt, "invalid-value"),
&UnknownFunction => write!(fmt, "unknown-function"),
&SyntaxError => write!(fmt, "syntax"),
}
}
}
pub enum Assertion {
Error(ErrorType),
Bench(BenchType),
ValidResult(Rcvar),
}
impl Assertion {
pub fn assert(&self, suite: &str, case: &TestCase, given: Rcvar) -> Result<(), String> {
match self {
&Assertion::Bench(_) => Ok(()),
&Assertion::ValidResult(ref expected_result) => {
let expr = self.try_parse(suite, case)?;
match expr.search(given) {
Err(e) => Err(self.err_message(suite, case, format!("{}", e))),
Ok(r) => {
if *r == **expected_result {
Ok(())
} else {
Err(self.err_message(
suite,
case,
format!("{:?}, {}", r, expr.as_ast()),
))
}
}
}
}
&Assertion::Error(ref error_type) => {
use jmespatch::ErrorReason::*;
let result = self.try_parse(suite, case);
match error_type {
&ErrorType::InvalidArity => match result?.search(given).map_err(|e| e.reason) {
Err(Runtime(RuntimeError::NotEnoughArguments { .. })) => Ok(()),
Err(Runtime(RuntimeError::TooManyArguments { .. })) => Ok(()),
Err(e) => Err(self.err_message(suite, case, format!("{}", e))),
Ok(r) => Err(self.err_message(suite, case, r.to_string())),
},
&ErrorType::InvalidType => match result?.search(given).map_err(|e| e.reason) {
Err(Runtime(RuntimeError::InvalidType { .. })) => Ok(()),
Err(Runtime(RuntimeError::InvalidReturnType { .. })) => Ok(()),
Err(e) => Err(self.err_message(suite, case, format!("{}", e))),
Ok(r) => Err(self.err_message(suite, case, r.to_string())),
},
&ErrorType::InvalidSlice => match result?.search(given).map_err(|e| e.reason) {
Err(Runtime(RuntimeError::InvalidSlice)) => Ok(()),
Err(e) => Err(self.err_message(suite, case, format!("{}", e))),
Ok(r) => Err(self.err_message(suite, case, r.to_string())),
},
&ErrorType::UnknownFunction => {
match result?.search(given).map_err(|e| e.reason) {
Err(Runtime(RuntimeError::UnknownFunction(_))) => Ok(()),
Err(e) => Err(self.err_message(suite, case, format!("{}", e))),
Ok(r) => Err(self.err_message(suite, case, r.to_string())),
}
}
&ErrorType::SyntaxError => match result {
Err(_) => Ok(()),
Ok(expr) => {
Err(self.err_message(suite, case, format!("Parsed {:?}", expr)))
}
},
}
}
}
}
fn try_parse(&self, suite: &str, case: &TestCase) -> Result<Expression<'_>, String> {
match compile(&case.expression) {
Err(e) => Err(self.err_message(suite, case, format!("{}", e))),
Ok(expr) => Ok(expr),
}
}
fn err_message(&self, suite: &str, case: &TestCase, message: String) -> String {
format!(
"Test suite: {}\nExpression: {}\nAssertion: {}\nResult: {}\n==============",
suite, case.expression, self, message
)
.to_string()
}
}
impl fmt::Display for Assertion {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
&Assertion::Error(ref e) => write!(fmt, "expects error({})", e),
&Assertion::Bench(ref b) => write!(fmt, "expects bench({})", b),
&Assertion::ValidResult(ref r) => write!(fmt, "expects result({:?})", r),
}
}
}
#[allow(dead_code)]
pub struct TestSuite<'a> {
filename: &'a str,
given: Rcvar,
cases: Vec<TestCase>,
}
impl<'a> TestSuite<'a> {
pub fn from_str(filename: &'a str, suite: &str) -> Result<TestSuite<'a>, String> {
serde_json::from_str::<Value>(suite)
.map_err(|e| e.to_string())
.and_then(|j| TestSuite::from_json(filename, &j))
}
fn from_json(filename: &'a str, suite: &Value) -> Result<TestSuite<'a>, String> {
let suite = suite.as_object().ok_or("test suite is not an object")?;
let test_case = suite.get("cases").ok_or("No cases value".to_string())?;
let case_array = test_case
.as_array()
.ok_or("cases is not an array".to_string())?;
let mut cases = vec![];
for case in case_array {
cases.push(TestCase::from_json(case).map_err(|e| e.to_string())?);
}
let value = suite
.get("given")
.ok_or("No given value".to_string())?
.clone();
let given = serde_json::from_value::<Variable>(value).map_err(|e| format!("{}", e))?;
Ok(TestSuite {
filename: filename,
given: Rcvar::new(given),
cases: cases,
})
}
}
pub enum TestCaseError {
InvalidJSON(String),
NoCaseType,
NoResult,
ResultCannotToString,
NoExpression,
ExpressionIsNotString,
ErrorIsNotString,
UnknownErrorType(String),
UnknownBenchType(String),
BenchIsNotString,
}
impl fmt::Display for TestCaseError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
use self::TestCaseError::*;
match self {
&InvalidJSON(ref msg) => write!(fmt, "invalid test case JSON: {}", msg),
&NoCaseType => write!(fmt, "case has no result, error, or bench"),
&NoResult => write!(fmt, "test case has no result key"),
&ResultCannotToString => write!(fmt, "result could not be cast to string"),
&NoExpression => write!(fmt, "test case has no expression key"),
&ExpressionIsNotString => write!(fmt, "test case expression is not a string"),
&ErrorIsNotString => write!(fmt, "test case error value is not a string"),
&UnknownErrorType(ref t) => write!(fmt, "unknown error type: {}", t),
&BenchIsNotString => write!(fmt, "bench value is not a string"),
&UnknownBenchType(ref bench) => write!(
fmt,
"unknown bench value: {}, expected one of of parse|full",
bench
),
}
}
}
impl fmt::Debug for TestCaseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
pub struct TestCase {
pub expression: String,
pub assertion: Assertion,
}
impl TestCase {
pub fn from_str(case: &str) -> Result<TestCase, TestCaseError> {
serde_json::from_str::<Value>(case)
.map_err(|e| TestCaseError::InvalidJSON(e.to_string()))
.and_then(|json| TestCase::from_json(&json))
}
fn from_json(case: &Value) -> Result<TestCase, TestCaseError> {
use crate::TestCaseError::*;
let case = case
.as_object()
.ok_or(InvalidJSON("not an object".to_string()))?;
Ok(TestCase {
expression: case
.get("expression")
.ok_or(NoExpression)
.and_then(|expression| {
expression
.as_str()
.ok_or(ExpressionIsNotString)
.map(|expression_str| expression_str.to_string())
})?,
assertion: match case.get("error") {
Some(err) => Assertion::Error(ErrorType::from_json(err)?),
None if case.contains_key("result") => {
let value = case.get("result").unwrap();
let var = serde_json::from_value::<Variable>(value.clone()).unwrap();
Assertion::ValidResult(Rcvar::new(var))
}
None if case.contains_key("bench") => {
Assertion::Bench(BenchType::from_json(case.get("bench").unwrap())?)
}
_ => return Err(NoCaseType),
},
})
}
pub fn assert(&self, suite_filename: &str, given: Rcvar) -> Result<(), String> {
self.assertion.assert(suite_filename, self, given)
}
}
include!(concat!(env!("OUT_DIR"), "/compliance_tests.rs"));