rune/modules/
test.rs

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