tytanic_filter/lib.rs
1//! A functional set-based DSL for filtering tests in the `tytanic` test runner.
2//! See the language [reference] and [guide] for more info.
3//!
4//! Note that this is generic over the test type because it is also used in
5//! internal projects of the author. This means that contribution is welcome,
6//! but may be rejected for various reasons not apparent in `tytanic` only.
7//!
8//! This libary is still unstable to some degree, the inner test set types are
9//! very opaque and not easily printed or inspected because of this, this may
10//! change in the future.
11//!
12//! [reference]: https://tingerrr.github.io/tytanic/reference/test-sets/index.html
13//! [guide]: https://tingerrr.github.io/tytanic/guides/test-sets.html
14
15use ecow::EcoString;
16use eval::Value;
17use thiserror::Error;
18
19use crate::eval::{Eval, Test};
20
21pub mod ast;
22pub mod eval;
23
24/// A generic test set expression filter, this filter checks whether a test
25/// should be filtered out by checking it against the inner test set within its
26/// evaluation context.
27///
28/// This also includes extra parsing logic for the special `all:` modifier
29/// prefix, which is not part of the test set grammar, but can be used by the
30/// caller to handle instances where multiple tests match but only one is
31/// usually expected.
32#[derive(Debug, Clone)]
33pub struct ExpressionFilter<T: 'static> {
34 input: EcoString,
35 all: bool,
36 ctx: eval::Context<T>,
37 set: eval::Set<T>,
38}
39
40impl<T: Test> ExpressionFilter<T> {
41 /// Parse and evaluate a string into a test set with the given context.
42 pub fn new<S: Into<EcoString>>(ctx: eval::Context<T>, input: S) -> Result<Self, Error> {
43 let input = input.into();
44
45 let (all, expr) = input
46 .strip_prefix("all:")
47 .map(|rest| (true, rest))
48 .unwrap_or((false, &input));
49
50 let set = ast::parse(expr)?.eval(&ctx).and_then(Value::expect_type)?;
51
52 Ok(Self {
53 input,
54 all,
55 ctx,
56 set,
57 })
58 }
59}
60
61impl<T> ExpressionFilter<T> {
62 /// The input expression the inner test set was parsed from.
63 pub fn input(&self) -> &str {
64 &self.input
65 }
66
67 /// Whether this test set expression has the special `all:` modifier.
68 ///
69 /// Handling this is up to the caller and has no impact on the inner test
70 /// set.
71 pub fn all(&self) -> bool {
72 self.all
73 }
74
75 /// The context used to evaluate the inner test set.
76 pub fn ctx(&self) -> &eval::Context<T> {
77 &self.ctx
78 }
79
80 /// The inner test set.
81 pub fn set(&self) -> &eval::Set<T> {
82 &self.set
83 }
84}
85
86impl<T> ExpressionFilter<T> {
87 /// Applies a function to the inner test set, this is useful for
88 /// optimization or for adding implicit test sets like wrapping a test set
89 /// in `(...) ~ skip()`.
90 pub fn map<F>(self, f: F) -> Self
91 where
92 F: FnOnce(eval::Set<T>) -> eval::Set<T>,
93 {
94 Self {
95 set: f(self.set),
96 ..self
97 }
98 }
99}
100
101impl<T> ExpressionFilter<T> {
102 /// Whether the given test is contained in this test set. Note that this
103 /// means a return value of `true` should _not_ be filtered out, but
104 /// included in the set of tests to operate on.
105 pub fn contains(&self, test: &T) -> Result<bool, eval::Error> {
106 self.set.contains(&self.ctx, test)
107 }
108}
109
110/// Returned by [`ExpressionFilter::new`].
111#[derive(Debug, Error)]
112pub enum Error {
113 /// A error occurred during parsing.
114 #[error(transparent)]
115 Parse(#[from] ast::Error),
116
117 /// An error occurred during evaluation.
118 #[error(transparent)]
119 Eval(#[from] eval::Error),
120}