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