test_dsl/
verb.rs

1//! Definition of verbs
2//!
3//! Verbs are the bread and butter of `test-dsl`. They define the behaviour that is then run
4//! against your test harness.
5
6use std::any::Any;
7use std::marker::PhantomData;
8
9use crate::arguments::VerbArgument;
10use crate::error::TestErrorCase;
11
12/// A verb is anything that 'does' things in a [`TestCase`](crate::test_case::TestCase)
13pub trait TestVerb<H>: 'static {
14    /// Run the verb, and do its thing
15    fn run(&self, harness: &mut H, node: &kdl::KdlNode) -> Result<(), TestErrorCase>;
16
17    /// Clone the verb
18    fn clone_box(&self) -> Box<dyn TestVerb<H>>;
19}
20
21impl<H: 'static> Clone for Box<dyn TestVerb<H>> {
22    fn clone(&self) -> Self {
23        let this: &dyn TestVerb<H> = &**self;
24        this.clone_box()
25    }
26}
27
28/// A verb defined through a closure/function
29///
30/// See the [`CallableVerb`] trait for what can be used
31pub struct FunctionVerb<H> {
32    func: BoxedCallable<H>,
33    _pd: PhantomData<fn(H)>,
34}
35
36impl<H> Clone for FunctionVerb<H> {
37    fn clone(&self) -> Self {
38        Self {
39            func: self.func.clone(),
40            _pd: self._pd,
41        }
42    }
43}
44
45impl<H> FunctionVerb<H> {
46    /// Create a new verb using a closure/function
47    pub fn new<F, T>(func: F) -> Self
48    where
49        F: CallableVerb<H, T>,
50    {
51        FunctionVerb {
52            func: BoxedCallable::new(func),
53            _pd: PhantomData,
54        }
55    }
56}
57
58struct BoxedCallable<H> {
59    callable: Box<dyn Any>,
60    call_fn: fn(&dyn Any, &mut H, &kdl::KdlNode) -> Result<(), TestErrorCase>,
61    clone_fn: fn(&dyn Any) -> Box<dyn Any>,
62}
63
64impl<H> Clone for BoxedCallable<H> {
65    fn clone(&self) -> Self {
66        BoxedCallable {
67            callable: (self.clone_fn)(&*self.callable),
68            call_fn: self.call_fn,
69            clone_fn: self.clone_fn,
70        }
71    }
72}
73
74impl<H> BoxedCallable<H> {
75    fn new<F, T>(callable: F) -> Self
76    where
77        F: CallableVerb<H, T>,
78    {
79        BoxedCallable {
80            callable: Box::new(callable),
81            call_fn: |this, harness, node| {
82                let this: &F = this.downcast_ref().unwrap();
83                this.call(harness, node)
84            },
85            clone_fn: |this| {
86                let this: &F = this.downcast_ref().unwrap();
87                Box::new(this.clone())
88            },
89        }
90    }
91
92    fn call(&self, harness: &mut H, node: &kdl::KdlNode) -> Result<(), TestErrorCase> {
93        (self.call_fn)(&*self.callable, harness, node)
94    }
95}
96
97/// Closure/functions that can be used as a Verb
98///
99/// This trait is implemented for closures with up to 16 arguments. They all have to be [`VerbArgument`]s.
100pub trait CallableVerb<H, T>: Clone + 'static {
101    /// Call the underlying closure
102    fn call(&self, harness: &mut H, node: &kdl::KdlNode) -> Result<(), TestErrorCase>;
103}
104
105impl<H, F> CallableVerb<H, ((),)> for F
106where
107    F: Fn(&mut H) -> miette::Result<()>,
108    F: Clone + 'static,
109{
110    fn call(&self, harness: &mut H, node: &kdl::KdlNode) -> Result<(), TestErrorCase> {
111        self(harness).map_err(|error| TestErrorCase::Error {
112            error,
113            label: node.span(),
114        })
115    }
116}
117
118#[rustfmt::skip]
119macro_rules! all_the_tuples {
120    ($name:ident) => {
121        $name!([], T1);
122        $name!([T1], T2);
123        $name!([T1, T2], T3);
124        $name!([T1, T2, T3], T4);
125        $name!([T1, T2, T3, T4], T5);
126        $name!([T1, T2, T3, T4, T5], T6);
127        $name!([T1, T2, T3, T4, T5, T6], T7);
128        $name!([T1, T2, T3, T4, T5, T6, T7], T8);
129        $name!([T1, T2, T3, T4, T5, T6, T7, T8], T9);
130        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10);
131        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11);
132        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12);
133        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13);
134        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14);
135        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15);
136        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16);
137    };
138}
139
140macro_rules! impl_callable {
141    (
142        [$($ty:ident),*], $last:ident
143    ) => {
144        #[allow(non_snake_case, unused_mut)]
145        impl<H, F, $($ty,)* $last> CallableVerb<H, ($($ty,)* $last,)> for F
146            where
147                F: Fn(&mut H, $($ty,)* $last,) -> miette::Result<()>,
148                F: Clone + 'static,
149                $( $ty: VerbArgument, )*
150                $last: VerbArgument,
151        {
152            fn call(&self, harness: &mut H, node: &kdl::KdlNode) -> Result<(), TestErrorCase> {
153                let mut args = node.iter();
154
155                let total_count = 1
156                    $(
157                        + {
158                            const _: () = {
159                                #[allow(unused)]
160                                let $ty = ();
161                            };
162                            1
163                        }
164
165                    )*;
166
167                let mut running_count = 1;
168
169                $(
170                    let arg = args.next().ok_or_else(|| TestErrorCase::MissingArgument {
171                        parent: node.span(),
172                        missing: format!("This verb takes {} arguments, you're missing the {}th argument.", total_count, running_count),
173                    })?;
174
175                    let $ty = <$ty as VerbArgument>::from_value(arg).ok_or_else(|| {
176                        TestErrorCase::WrongArgumentType {
177                            parent: node.name().span(),
178                            argument: arg.span(),
179                            expected: format!("This verb takes a '{}' as its argument here.", <$ty as VerbArgument>::TYPE_NAME),
180                        }
181                    })?;
182                    running_count += 1;
183                )*
184
185                let _ = running_count;
186
187                let arg = args.next().ok_or_else(|| TestErrorCase::MissingArgument {
188                    parent: node.span(),
189                    missing: format!("This verb takes {tc} arguments, you're missing the {tc}th argument.", tc = total_count),
190                })?;
191                let $last = <$last as VerbArgument>::from_value(arg).ok_or_else(|| {
192                    TestErrorCase::WrongArgumentType {
193                        parent: node.name().span(),
194                        argument: arg.span(),
195                        expected: format!("This verb takes a '{}' as its argument here.", <$last as VerbArgument>::TYPE_NAME),
196                    }
197                })?;
198
199                self(harness, $($ty,)* $last,).map_err(|error| TestErrorCase::Error {
200                    error,
201                    label: node.span()
202                })
203            }
204        }
205    };
206}
207
208all_the_tuples!(impl_callable);
209
210impl<H: 'static> TestVerb<H> for FunctionVerb<H> {
211    fn run(&self, harness: &mut H, node: &kdl::KdlNode) -> Result<(), TestErrorCase> {
212        let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
213            self.func.call(harness, node)
214        }));
215
216        match res {
217            Ok(res) => res,
218            Err(error) => {
219                let mut message = "Something went wrong".to_string();
220
221                let payload = error;
222
223                if let Some(msg) = payload.downcast_ref::<&str>() {
224                    message = msg.to_string();
225                }
226
227                if let Some(msg) = payload.downcast_ref::<String>() {
228                    message.clone_from(msg);
229                }
230
231                Err(TestErrorCase::Panic {
232                    error: miette::Error::msg(message),
233                    label: node.span(),
234                })
235            }
236        }
237    }
238
239    fn clone_box(&self) -> Box<dyn TestVerb<H>> {
240        Box::new(self.clone())
241    }
242}