1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
pub mod diff;
pub mod model;
mod parser;
use crate::{
diff::ExpressionDiff,
model::{Expression, ModelError, TestCase},
parser::{ParserError, Rule, TestParser},
};
use pest::{error::Error as PestError, Parser, RuleType};
use std::{
collections::HashSet, fs::read_to_string, io::Error as IOError, marker::PhantomData,
path::PathBuf,
};
use thiserror::Error;
pub fn default_test_dir() -> PathBuf {
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap().as_str())
.join("tests")
.join("pest")
}
#[derive(Error, Debug)]
pub enum TestError<R> {
#[error("Error reading test case from file")]
IO { source: IOError },
#[error("Error parsing test case")]
Parser { source: ParserError<Rule> },
#[error("Error building model from test case parse tree")]
Model { source: ModelError },
#[error("Error parsing code with target parser")]
Target { source: PestError<R> },
#[error("Expected and actual parse trees are different:\n{diff}")]
Diff { diff: ExpressionDiff },
}
pub struct PestTester<R: RuleType, P: Parser<R>> {
test_dir: PathBuf,
test_ext: String,
rule: R,
skip_rules: HashSet<R>,
parser: PhantomData<P>,
}
impl<R: RuleType, P: Parser<R>> PestTester<R, P> {
pub fn new<D: Into<PathBuf>, S: AsRef<str>>(
test_dir: D,
test_ext: S,
rule: R,
skip_rules: HashSet<R>,
) -> Self {
Self {
test_dir: test_dir.into(),
test_ext: test_ext.as_ref().to_owned(),
rule,
skip_rules,
parser: PhantomData::<P>,
}
}
pub fn from_defaults(rule: R, skip_rules: HashSet<R>) -> Self {
Self::new(default_test_dir(), ".txt", rule, skip_rules)
}
pub fn evaluate<N: AsRef<str>>(
&self,
name: N,
ignore_missing_expected_values: bool,
) -> Result<(), TestError<R>> {
let path = self
.test_dir
.join(format!("{}.{}", name.as_ref(), self.test_ext));
let text = read_to_string(path).map_err(|source| TestError::IO { source })?;
let pair =
TestParser::parse(text.as_ref()).map_err(|source| TestError::Parser { source })?;
let test_case =
TestCase::try_from_pair(pair).map_err(|source| TestError::Model { source })?;
let code_pair =
parser::parse(test_case.code.as_ref(), self.rule, self.parser).map_err(|source| {
match source {
ParserError::Empty => TestError::Parser {
source: ParserError::Empty,
},
ParserError::Pest { source } => TestError::Target { source },
}
})?;
let code_expr = Expression::try_from_code(code_pair, &self.skip_rules)
.map_err(|source| TestError::Model { source })?;
match ExpressionDiff::from_expressions(
&test_case.expression,
&code_expr,
ignore_missing_expected_values,
) {
ExpressionDiff::Equal(_) => Ok(()),
diff => Err(TestError::Diff { diff }),
}
}
pub fn evaluate_strict<N: AsRef<str>>(&self, name: N) -> Result<(), TestError<R>> {
self.evaluate(name, false)
}
}