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