gba_test/
test_case.rs

1//! All types related to defining a test.
2//!
3//! This module provides the [`TestCase`] trait and associated types, allowing the user to define a
4//! test to be run by the test [`runner`]. It also provides a [`Test`] struct, which is used by the
5//! [`test`] macro to create a default implementer of the `TestCase` trait. Note that the `Test`
6//! struct is not considered part of the public API.
7//!
8//! [`runner`]: crate::runner()
9//! [`test`]: crate::test
10
11use crate::Termination;
12use core::{mem::MaybeUninit, str};
13
14/// Defines whether a test should be ignored or not.
15///
16/// The easiest way to define if a test should be ignored is to use the `#[ignore]` attribute when
17/// defining the test.
18///
19/// ```
20/// #[cfg(test)]
21/// mod tests {
22///     use gba_test_macros::test;
23///
24///     #[test]
25///     #[ignore]
26///     fn ignored_test() {
27///         assert!(false);
28///     }
29/// }
30/// ```
31#[derive(Clone, Copy, Debug)]
32pub enum Ignore {
33    /// The test should be run.
34    No,
35    /// The test should not be run.
36    Yes,
37    /// The test should not be run, and a message should be displayed.
38    YesWithMessage(&'static str),
39}
40
41/// Whether a test is expected to panic.
42///
43/// The easiest way to define a test that should panic is to use the `#[should_panic]` attribute
44/// when defining the test.
45///
46/// ```
47/// #[cfg(test)]
48/// mod tests {
49///     use gba_test_macros::test;
50///
51///     #[test]
52///     #[should_panic]
53///     fn ignored_test() {
54///         panic!("something was expected to go wrong");
55///     }
56/// }
57/// ```
58#[derive(Clone, Copy, Debug)]
59pub enum ShouldPanic {
60    /// The test is expected to run successfully.
61    No,
62    /// The test is expected to panic during execution.
63    Yes,
64    /// The test is expected to panic with the given substring present in the panic message.
65    YesWithMessage(&'static str),
66}
67
68/// Defines a test case executable by the test runner.
69///
70/// Any type implementing this trait can be passed to the test runner using the `#[test_case]`
71/// attribute. For most cases, using the `#[test]` attribute provided by this crate is sufficient.
72///
73/// See the [`custom_test_frameworks`](https://doc.rust-lang.org/unstable-book/language-features/custom-test-frameworks.html)
74/// language feature for more information about using the `#[test_case]` attribute.
75pub trait TestCase {
76    /// The name of the test.
77    fn name(&self) -> &str;
78
79    /// The module the test is in.
80    fn modules(&self) -> &[&str];
81
82    /// The actual test itself.
83    ///
84    /// If this method panics, the test is considered a failure. Otherwise, the test is considered
85    /// to have passed.
86    fn run(&self);
87
88    /// Whether the test should be excluded or not.
89    ///
90    /// If this method returns `Ignore::Yes`, the test function will not be run at all (but it will
91    /// still be compiled). This allows for time-consuming or expensive tests to be conditionally
92    /// disabled.
93    fn ignore(&self) -> Ignore;
94
95    /// Whether the test is expected to panic.
96    fn should_panic(&self) -> ShouldPanic;
97
98    /// Returns the ignore message, if it exists.
99    fn message(&self) -> Option<&'static str>;
100}
101
102/// Determines the amount of module sections in a given module path.
103///
104/// This function is used by the `#[test]` attribute. It is not considered a part of the public API.
105#[doc(hidden)]
106pub const fn split_module_path_len(module_path: &'static str) -> usize {
107    let mut len = 1;
108
109    let mut i = 1;
110    while i < module_path.len() {
111        if module_path.as_bytes()[i - 1] == b':' && module_path.as_bytes()[i] == b':' {
112            len += 1;
113            i += 1;
114        }
115        i += 1;
116    }
117
118    len
119}
120
121/// Splits a module path into its individual parts.
122///
123/// This function is used by the `#[test]` attribute. It is not considered a part of the public API.
124#[doc(hidden)]
125pub const fn split_module_path<const LEN: usize>(module_path: &'static str) -> [&'static str; LEN] {
126    let mut result: MaybeUninit<[&'static str; LEN]> = MaybeUninit::uninit();
127    let mut result_index = 0;
128    let mut module_path_start = 0;
129    // Look at two bytes at a time.
130    let mut module_path_index = 1;
131    while module_path_index < module_path.len() {
132        if module_path.as_bytes()[module_path_index - 1] == b':'
133            && module_path.as_bytes()[module_path_index] == b':'
134        {
135            let module = unsafe {
136                str::from_utf8_unchecked(core::slice::from_raw_parts(
137                    module_path.as_ptr().add(module_path_start),
138                    module_path_index - 1 - module_path_start,
139                ))
140            };
141            // Check that we have not already filled in the full result.
142            if result_index >= LEN {
143                panic!("module path was split into too many parts")
144            }
145            unsafe {
146                (result.as_mut_ptr() as *mut &str)
147                    .add(result_index)
148                    .write(module);
149            }
150            result_index += 1;
151            module_path_index += 1;
152            module_path_start = module_path_index;
153        }
154        module_path_index += 1;
155    }
156    // Add the final path.
157    let module = unsafe {
158        str::from_utf8_unchecked(core::slice::from_raw_parts(
159            module_path.as_ptr().add(module_path_start),
160            module_path.len() - module_path_start,
161        ))
162    };
163    // Check that we have not already filled in the full result.
164    if result_index >= LEN {
165        panic!("module path was split into too many parts")
166    }
167    unsafe {
168        (result.as_mut_ptr() as *mut &str)
169            .add(result_index)
170            .write(module);
171    }
172    result_index += 1;
173
174    // Check that we actually filled the result.
175    if result_index < LEN {
176        panic!("unable to split module path into enough separate parts")
177    }
178
179    unsafe { result.assume_init() }
180}
181
182/// A standard test.
183///
184/// This struct is created by the `#[test]` attribute. This struct is not to be used directly and
185/// is not considered part of the public API. If you want to use a similar struct, you should
186/// define one locally and implement `TestCase` for it directly.
187#[doc(hidden)]
188pub struct Test<T> {
189    /// The name of the test.
190    pub name: &'static str,
191    /// The modules the test is in.
192    pub modules: &'static [&'static str],
193    /// The test function itself.
194    pub test: fn() -> T,
195    /// Whether the test should be excluded.
196    ///
197    /// This is set by the `#[ignore]` attribute.
198    pub ignore: Ignore,
199    /// Whether the test is expected to panic.
200    ///
201    /// This is set by the `#[should_panic]` attribute.
202    pub should_panic: ShouldPanic,
203}
204
205impl<T> TestCase for Test<T>
206where
207    T: Termination,
208{
209    fn name(&self) -> &str {
210        self.name
211    }
212
213    fn modules(&self) -> &[&str] {
214        if self.modules.len() <= 1 {
215            self.modules
216        } else {
217            &self.modules[1..]
218        }
219    }
220
221    fn run(&self) {
222        (self.test)().terminate()
223    }
224
225    fn ignore(&self) -> Ignore {
226        self.ignore
227    }
228
229    fn should_panic(&self) -> ShouldPanic {
230        self.should_panic
231    }
232
233    fn message(&self) -> Option<&'static str> {
234        if let Ignore::YesWithMessage(message) = self.ignore {
235            Some(message)
236        } else {
237            None
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::{Ignore, ShouldPanic, Test, TestCase, split_module_path, split_module_path_len};
245
246    use claims::{assert_matches, assert_none, assert_some_eq};
247    use gba_test_macros::test;
248
249    #[test]
250    fn test_name() {
251        let test = Test {
252            name: "foo",
253            modules: &[""],
254            test: || {},
255            ignore: Ignore::No,
256            should_panic: ShouldPanic::No,
257        };
258
259        assert_eq!(test.name(), "foo")
260    }
261
262    #[test]
263    fn test_module_split() {
264        let test = Test {
265            name: "",
266            modules: &["foo", "bar"],
267            test: || {},
268            ignore: Ignore::No,
269            should_panic: ShouldPanic::No,
270        };
271
272        assert_eq!(test.modules(), &["bar"]);
273    }
274
275    #[test]
276    fn test_module_no_split() {
277        let test = Test {
278            name: "",
279            modules: &["foo"],
280            test: || {},
281            ignore: Ignore::No,
282            should_panic: ShouldPanic::No,
283        };
284
285        assert_eq!(test.modules(), &["foo"]);
286    }
287
288    #[test]
289    fn test_run_no_panic() {
290        let test = Test {
291            name: "",
292            modules: &[""],
293            test: || {
294                assert!(true);
295            },
296            ignore: Ignore::No,
297            should_panic: ShouldPanic::No,
298        };
299
300        test.run();
301    }
302
303    #[test]
304    #[should_panic(expected = "assertion failed: false")]
305    fn test_run_panic() {
306        let test = Test {
307            name: "",
308            modules: &[""],
309            test: || {
310                assert!(false);
311            },
312            ignore: Ignore::No,
313            should_panic: ShouldPanic::No,
314        };
315
316        test.run();
317    }
318
319    #[test]
320    fn test_ignore() {
321        let test = Test {
322            name: "",
323            modules: &[""],
324            test: || {},
325            ignore: Ignore::Yes,
326            should_panic: ShouldPanic::No,
327        };
328
329        assert_matches!(test.ignore(), Ignore::Yes);
330    }
331
332    #[test]
333    fn test_should_panic() {
334        let test = Test {
335            name: "",
336            modules: &[""],
337            test: || {},
338            ignore: Ignore::No,
339            should_panic: ShouldPanic::Yes,
340        };
341
342        assert_matches!(test.should_panic(), ShouldPanic::Yes);
343    }
344
345    #[test]
346    fn test_message() {
347        let test = Test {
348            name: "",
349            modules: &[""],
350            test: || {},
351            ignore: Ignore::YesWithMessage("foo"),
352            should_panic: ShouldPanic::No,
353        };
354
355        assert_some_eq!(test.message(), "foo");
356    }
357
358    #[test]
359    fn test_no_message() {
360        let test = Test {
361            name: "",
362            modules: &[""],
363            test: || {},
364            ignore: Ignore::Yes,
365            should_panic: ShouldPanic::No,
366        };
367
368        assert_none!(test.message());
369    }
370
371    #[test]
372    fn split_module_path_len_empty() {
373        assert_eq!(split_module_path_len(""), 1);
374    }
375
376    #[test]
377    fn split_module_path_len_single() {
378        assert_eq!(split_module_path_len("foo"), 1);
379    }
380
381    #[test]
382    fn split_module_path_len_single_colon() {
383        assert_eq!(split_module_path_len(":"), 1);
384    }
385
386    #[test]
387    fn split_module_path_len_empty_with_separator() {
388        assert_eq!(split_module_path_len("::"), 2);
389    }
390
391    #[test]
392    fn split_module_path_len_separator_with_extra_colon() {
393        assert_eq!(split_module_path_len(":::"), 2);
394    }
395
396    #[test]
397    fn split_module_path_len_modules_split_by_separator() {
398        assert_eq!(split_module_path_len("foo::bar"), 2);
399    }
400
401    #[test]
402    fn split_module_path_len_many_modules_split_by_separators() {
403        assert_eq!(split_module_path_len("foo::bar::baz::quux"), 4);
404    }
405
406    #[test]
407    fn split_module_path_len_modules_leading_separator() {
408        assert_eq!(split_module_path_len("::foo::bar"), 3);
409    }
410
411    #[test]
412    fn split_module_path_len_modules_trailing_separator() {
413        assert_eq!(split_module_path_len("foo::bar::"), 3);
414    }
415
416    #[test]
417    fn split_module_path_empty() {
418        assert_eq!(split_module_path::<1>(""), [""]);
419    }
420
421    #[test]
422    fn split_module_path_single() {
423        assert_eq!(split_module_path::<1>("foo"), ["foo"]);
424    }
425
426    #[test]
427    fn split_module_path_single_colon() {
428        assert_eq!(split_module_path::<1>(":"), [":"]);
429    }
430
431    #[test]
432    fn split_module_path_empty_with_separator() {
433        assert_eq!(split_module_path::<2>("::"), ["", ""]);
434    }
435
436    #[test]
437    fn split_module_path_separator_with_extra_colon() {
438        assert_eq!(split_module_path::<2>(":::"), ["", ":"]);
439    }
440
441    #[test]
442    fn split_module_path_modules_split_by_separator() {
443        assert_eq!(split_module_path::<2>("foo::bar"), ["foo", "bar"]);
444    }
445
446    #[test]
447    fn split_module_path_many_modules_split_by_separators() {
448        assert_eq!(
449            split_module_path::<4>("foo::bar::baz::quux"),
450            ["foo", "bar", "baz", "quux"]
451        );
452    }
453
454    #[test]
455    fn split_module_path_modules_leading_separator() {
456        assert_eq!(split_module_path::<3>("::foo::bar"), ["", "foo", "bar"]);
457    }
458
459    #[test]
460    fn split_module_path_modules_trailing_separator() {
461        assert_eq!(split_module_path::<3>("foo::bar::"), ["foo", "bar", ""]);
462    }
463
464    #[test]
465    #[should_panic(expected = "module path was split into too many parts")]
466    fn split_module_path_size_too_small() {
467        split_module_path::<0>("foo");
468    }
469
470    #[test]
471    #[should_panic(expected = "module path was split into too many parts")]
472    fn split_module_path_size_too_small_multiple_parts() {
473        split_module_path::<2>("foo::bar::baz");
474    }
475
476    #[test]
477    #[should_panic(expected = "unable to split module path into enough separate parts")]
478    fn split_module_path_size_too_large() {
479        split_module_path::<2>("foo");
480    }
481}