Skip to main content

rune/modules/
test.rs

1//! Testing and benchmarking.
2
3use crate as rune;
4use crate::alloc::{self, try_format, Vec};
5use crate::compile;
6use crate::macros::{quote, FormatArgs, MacroContext, TokenStream};
7use crate::runtime::Function;
8use crate::{docstring, Any, ContextError, Module};
9
10/// Testing and benchmarking.
11#[rune::module(::std::test)]
12pub fn module() -> Result<Module, ContextError> {
13    let mut m = Module::from_meta(self::module__meta)?.with_unique("std::test");
14
15    m.macro_meta(assert)?;
16    m.macro_meta(assert_eq)?;
17    m.macro_meta(assert_ne)?;
18
19    m.ty::<Bencher>()?.docs(docstring! {
20        /// A type to perform benchmarks.
21        ///
22        /// This is the type of the argument to any function which is annotated with `#[bench]`
23    })?;
24
25    m.function_meta(Bencher::iter)?;
26    Ok(m)
27}
28
29/// A helper type to capture benchmarks.
30#[derive(Default, Any)]
31#[rune(module = crate, item = ::std::test)]
32pub struct Bencher {
33    fns: Vec<Function>,
34}
35
36impl Bencher {
37    /// Coerce bencher into its underlying functions.
38    pub fn into_functions(self) -> Vec<Function> {
39        self.fns
40    }
41
42    /// Run a benchmark using the given closure.
43    #[rune::function]
44    fn iter(&mut self, f: Function) -> alloc::Result<()> {
45        self.fns.try_push(f)
46    }
47}
48
49/// Assert that the expression provided as an argument is true, or cause a vm
50/// panic.
51///
52/// The second argument can optionally be used to format a panic message.
53///
54/// This is useful when writing test cases.
55///
56/// # Examples
57///
58/// ```rune
59/// let value = 42;
60///
61/// assert!(value == 42, "Value was not what was expected, instead it was {}", value);
62/// ```
63#[rune::macro_]
64pub(crate) fn assert(
65    cx: &mut MacroContext<'_, '_, '_>,
66    stream: &TokenStream,
67) -> compile::Result<TokenStream> {
68    use crate as rune;
69
70    let mut it = cx.exprs(stream)?.into_iter().peekable();
71
72    let Some(expr) = it.next() else {
73        return Err(compile::Error::msg(
74            cx.input_span(),
75            "expected an expression to assert",
76        ));
77    };
78
79    let message = if it.peek().is_some() {
80        Some(FormatArgs::from_exprs(cx, it)?)
81    } else {
82        None
83    };
84
85    let output = if let Some(message) = &message {
86        let expanded = message.expand(cx)?;
87
88        quote!(if !(#expr) {
89            ::std::panic("assertion failed: " + (#expanded));
90        })
91    } else {
92        let message = try_format!("assertion failed: {}", cx.stringify(&expr)?);
93        let message = cx.lit(&message)?;
94
95        quote!(if !(#expr) {
96            ::std::panic(#message);
97        })
98    };
99
100    Ok(output.into_token_stream(cx)?)
101}
102
103/// Assert that the two arguments provided are equal, or cause a vm panic.
104///
105/// The third argument can optionally be used to format a panic message.
106///
107/// # Examples
108///
109/// ```rune
110/// let value = 42;
111///
112/// assert_eq!(value, 42, "Value was not 42, instead it was {}", value);
113/// ```
114#[rune::macro_]
115pub(crate) fn assert_eq(
116    cx: &mut MacroContext<'_, '_, '_>,
117    stream: &TokenStream,
118) -> compile::Result<TokenStream> {
119    use crate as rune;
120
121    let mut it = cx.exprs(stream)?.into_iter().peekable();
122
123    let (Some(left), Some(right)) = (it.next(), it.next()) else {
124        return Err(compile::Error::msg(
125            cx.input_span(),
126            "expected two expressions to compare",
127        ));
128    };
129
130    let message = if it.peek().is_some() {
131        Some(FormatArgs::from_exprs(cx, it)?)
132    } else {
133        None
134    };
135
136    let output = if let Some(message) = &message {
137        let message = message.expand(cx)?;
138
139        quote! {{
140            let left = #left;
141            let right = #right;
142
143            if !(left == right) {
144                let message = #message;
145                message += ::std::fmt::format!("\nleft: {:?}", left);
146                message += ::std::fmt::format!("\nright: {:?}", right);
147                ::std::panic("assertion failed (left == right): " + message);
148            }
149        }}
150    } else {
151        let message = cx.lit("assertion failed (left == right):")?;
152
153        quote! {{
154            let left = #left;
155            let right = #right;
156
157            if !(left == right) {
158                let message = ::std::string::String::from(#message);
159                message += ::std::fmt::format!("\nleft: {:?}", left);
160                message += ::std::fmt::format!("\nright: {:?}", right);
161                ::std::panic(message);
162            }
163        }}
164    };
165
166    Ok(output.into_token_stream(cx)?)
167}
168
169/// Assert that the two arguments provided are not equal, or cause a vm panic.
170///
171/// The third argument can optionally be used to format a panic message.
172///
173/// # Examples
174///
175/// ```rune
176/// let value = 42;
177///
178/// assert_ne!(value, 10, "Value was 10");
179/// ```
180#[rune::macro_]
181pub(crate) fn assert_ne(
182    cx: &mut MacroContext<'_, '_, '_>,
183    stream: &TokenStream,
184) -> compile::Result<TokenStream> {
185    use crate as rune;
186
187    let mut it = cx.exprs(stream)?.into_iter().peekable();
188
189    let (Some(left), Some(right)) = (it.next(), it.next()) else {
190        return Err(compile::Error::msg(
191            cx.input_span(),
192            "expected two expressions to compare",
193        ));
194    };
195
196    let message = if it.peek().is_some() {
197        Some(FormatArgs::from_exprs(cx, it)?)
198    } else {
199        None
200    };
201
202    let output = if let Some(message) = &message {
203        let message = message.expand(cx)?;
204
205        quote! {{
206            let left = #left;
207            let right = #right;
208
209            if !(left != right) {
210                let message = #message;
211                message += ::std::fmt::format!("\nleft: {:?}", left);
212                message += ::std::fmt::format!("\nright: {:?}", right);
213                ::std::panic("assertion failed (left != right): " + message);
214            }
215        }}
216    } else {
217        let message = cx.lit("assertion failed (left != right):")?;
218
219        quote! {{
220            let left = #left;
221            let right = #right;
222
223            if !(left != right) {
224                let message = ::std::string::String::from(#message);
225                message += ::std::fmt::format!("\nleft: {:?}", left);
226                message += ::std::fmt::format!("\nright: {:?}", right);
227                ::std::panic(message);
228            }
229        }}
230    };
231
232    Ok(output.into_token_stream(cx)?)
233}