test_dsl/
condition.rs

1//! Foobar
2
3use std::any::Any;
4use std::marker::PhantomData;
5
6use crate::arguments::VerbArgument;
7use crate::error::TestErrorCase;
8
9/// A condition check for a given property
10///
11/// Conditions allow to check for anything you would find useful. For example, in a HTTP library,
12/// you can check that your cache contains a valid entry from a previous request.
13pub trait TestCondition<H>: 'static {
14    /// Run the check now, may or may not actually be implemented
15    ///
16    /// This is only useful for non-transient properties. For example "is this connected". It is
17    /// not a way to check for events.
18    ///
19    /// If the condition cannot properly support the concept of 'checking now' it is ok to simply
20    /// return an error.
21    fn check_now(&self, harness: &H, node: &kdl::KdlNode) -> Result<bool, TestErrorCase>;
22
23    /// Wait until a given condition evaluates to a meaningful value
24    ///
25    /// Some properties of a system are not meaningful other than 'that they happened'. This could
26    /// be an event or some value changing.
27    ///
28    /// If the condition cannot properly support the concept of 'waiting until it has a value', it
29    /// is ok to simply return an error.
30    fn wait_until(&self, harness: &H, node: &kdl::KdlNode) -> Result<bool, TestErrorCase>;
31
32    /// Clone the condition
33    fn clone_box(&self) -> Box<dyn TestCondition<H>>;
34}
35
36impl<H: 'static> Clone for Box<dyn TestCondition<H>> {
37    fn clone(&self) -> Self {
38        let this: &dyn TestCondition<H> = &**self;
39        this.clone_box()
40    }
41}
42
43/// A [`Checker`] is the actual instance that executes when a condition evaluates.
44///
45/// It is mostly used with the [`Condition`] struct when given a closure/function.
46///
47/// It is implemented for closures of up to 16 arguments and their
48pub trait Checker<H, T>: Clone + 'static {
49    /// Execute the check with the given node
50    fn check(&self, harness: &H, node: &kdl::KdlNode) -> Result<bool, TestErrorCase>;
51}
52
53struct BoxedChecker<H> {
54    checker: Box<dyn Any>,
55    check_fn: fn(&dyn Any, harness: &H, node: &kdl::KdlNode) -> Result<bool, TestErrorCase>,
56    clone_fn: fn(&dyn Any) -> Box<dyn Any>,
57}
58
59impl<H> BoxedChecker<H> {
60    fn new<C, T>(checker: C) -> Self
61    where
62        C: Checker<H, T>,
63    {
64        BoxedChecker {
65            checker: Box::new(checker),
66            check_fn: |this, harness, node| {
67                let this: &C = this.downcast_ref().unwrap();
68
69                this.check(harness, node)
70            },
71            clone_fn: |this| {
72                let this: &C = this.downcast_ref().unwrap();
73
74                Box::new(this.clone())
75            },
76        }
77    }
78
79    fn check(&self, harness: &H, node: &kdl::KdlNode) -> Result<bool, TestErrorCase> {
80        (self.check_fn)(&*self.checker, harness, node)
81    }
82}
83
84impl<H> Clone for BoxedChecker<H> {
85    fn clone(&self) -> Self {
86        BoxedChecker {
87            checker: (self.clone_fn)(&*self.checker),
88            check_fn: self.check_fn,
89            clone_fn: self.clone_fn,
90        }
91    }
92}
93
94/// A condition that can be used in test-cases
95///
96/// Depending on how it is constructed, it may or may not be able to be used in direct or waiting
97/// contexts
98pub struct Condition<H> {
99    now: Option<BoxedChecker<H>>,
100    wait: Option<BoxedChecker<H>>,
101    _pd: PhantomData<fn(H)>,
102}
103
104impl<H> Condition<H> {
105    /// Create a new [`Condition`] that can be called in direct contexts
106    ///
107    /// For example the `assert` verb allows you to verify multiple [`TestCondition`]s (of which [`Condition`] is one way to create one).
108    pub fn new_now<C, T>(now: C) -> Self
109    where
110        C: Checker<H, T>,
111    {
112        Condition {
113            now: Some(BoxedChecker::new(now)),
114            wait: None,
115            _pd: PhantomData,
116        }
117    }
118}
119
120impl<H> Clone for Condition<H> {
121    fn clone(&self) -> Self {
122        Condition {
123            now: self.now.clone(),
124            wait: self.wait.clone(),
125            _pd: PhantomData,
126        }
127    }
128}
129
130impl<H, F> Checker<H, ((),)> for F
131where
132    F: Fn(&H) -> miette::Result<bool>,
133    F: Clone + 'static,
134{
135    fn check(&self, harness: &H, node: &kdl::KdlNode) -> Result<bool, TestErrorCase> {
136        self(harness).map_err(|error| TestErrorCase::Error {
137            error,
138            label: node.span(),
139        })
140    }
141}
142
143#[rustfmt::skip]
144macro_rules! all_the_tuples {
145    ($name:ident) => {
146        $name!([], T1);
147        $name!([T1], T2);
148        $name!([T1, T2], T3);
149        $name!([T1, T2, T3], T4);
150        $name!([T1, T2, T3, T4], T5);
151        $name!([T1, T2, T3, T4, T5], T6);
152        $name!([T1, T2, T3, T4, T5, T6], T7);
153        $name!([T1, T2, T3, T4, T5, T6, T7], T8);
154        $name!([T1, T2, T3, T4, T5, T6, T7, T8], T9);
155        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10);
156        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11);
157        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12);
158        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13);
159        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14);
160        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15);
161        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16);
162    };
163}
164
165macro_rules! impl_callable {
166    (
167        [$($ty:ident),*], $last:ident
168    ) => {
169        #[allow(non_snake_case, unused_mut)]
170        impl<H, F, $($ty,)* $last> Checker<H, ($($ty,)* $last,)> for F
171            where
172                F: Fn(&H, $($ty,)* $last,) -> miette::Result<bool>,
173                F: Clone + 'static,
174                $( $ty: VerbArgument, )*
175                $last: VerbArgument,
176        {
177            fn check(&self, harness: &H, node: &kdl::KdlNode) -> Result<bool, TestErrorCase> {
178                let mut args = node.iter();
179
180                let total_count = 1
181                    $(
182                        + {
183                            const _: () = {
184                                #[allow(unused)]
185                                let $ty = ();
186                            };
187                            1
188                        }
189
190                    )*;
191
192                let mut running_count = 1;
193
194                $(
195                    let arg = args.next().ok_or_else(|| TestErrorCase::MissingArgument {
196                        parent: node.span(),
197                        missing: format!("This condition takes {} arguments, you're missing the {}th argument.", total_count, running_count),
198                    })?;
199
200                    let $ty = <$ty as VerbArgument>::from_value(arg).ok_or_else(|| {
201                        TestErrorCase::WrongArgumentType {
202                            parent: node.name().span(),
203                            argument: arg.span(),
204                            expected: format!("This condition takes a '{}' as its argument here.", <$ty as VerbArgument>::TYPE_NAME),
205                        }
206                    })?;
207                    running_count += 1;
208                )*
209
210                let _ = running_count;
211
212                let arg = args.next().ok_or_else(|| TestErrorCase::MissingArgument {
213                    parent: node.span(),
214                    missing: format!("This condition takes {tc} arguments, you're missing the {tc}th argument.", tc = total_count),
215                })?;
216                let $last = <$last as VerbArgument>::from_value(arg).ok_or_else(|| {
217                    TestErrorCase::WrongArgumentType {
218                        parent: node.name().span(),
219                        argument: arg.span(),
220                        expected: format!("This condition takes a '{}' as its argument here.", <$last as VerbArgument>::TYPE_NAME),
221                    }
222                })?;
223
224                self(harness, $($ty,)* $last,).map_err(|error| TestErrorCase::Error {
225                    error,
226                    label: node.span()
227                })
228            }
229        }
230    };
231}
232
233all_the_tuples!(impl_callable);
234
235impl<H> TestCondition<H> for Condition<H>
236where
237    H: 'static,
238{
239    fn check_now(&self, harness: &H, node: &kdl::KdlNode) -> Result<bool, TestErrorCase> {
240        let Some(check) = self.now.as_ref().map(|now| now.check(harness, node)) else {
241            return Err(TestErrorCase::Error {
242                error: miette::miette!("Condition does not implement checking now"),
243                label: node.span(),
244            });
245        };
246
247        check
248    }
249
250    fn wait_until(&self, harness: &H, node: &kdl::KdlNode) -> Result<bool, TestErrorCase> {
251        let Some(check) = self.wait.as_ref().map(|wait| wait.check(harness, node)) else {
252            return Err(TestErrorCase::Error {
253                error: miette::miette!("Condition does not implement waiting"),
254                label: node.span(),
255            });
256        };
257
258        check
259    }
260
261    fn clone_box(&self) -> Box<dyn TestCondition<H>> {
262        Box::new(self.clone())
263    }
264}