substance_framework/
lib.rs

1/***********************************************************************************
2 * MIT License                                                                     *
3 *                                                                                 *
4 * Copyright (c) 2022 Tutul                                                        *
5 *                                                                                 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy    *
7 * of this software and associated documentation files (the "Software"), to deal   *
8 * in the Software without restriction, including without limitation the rights    *
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell       *
10 * copies of the Software, and to permit persons to whom the Software is           *
11 * furnished to do so, subject to the following conditions:                        *
12 *                                                                                 *
13 * The above copyright notice and this permission notice shall be included in all  *
14 * copies or substantial portions of the Software.                                 *
15 *                                                                                 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR      *
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,        *
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE     *
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER          *
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,   *
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE   *
22 * SOFTWARE.                                                                       *
23 ***********************************************************************************/
24
25#![feature(lang_items)]
26#![feature(core_intrinsics)]
27#![feature(panic_internals)]
28#![feature(panic_info_message)]
29#![feature(stmt_expr_attributes)]
30#![no_std]
31
32//////////////////////////////////////////////
33
34use core::fmt::Arguments;
35pub use core::intrinsics::abort;
36pub use core::panic::{Location, PanicInfo};
37use spin::Mutex;
38use spin::RwLock;
39
40//////////////////////////////////////////////
41
42#[allow(unused_imports)]
43use crate::output::combined::Combined;
44#[allow(unused_imports)]
45use crate::output::pretty::Pretty;
46#[allow(unused_imports)]
47use crate::output::report::Report;
48use crate::output::Output;
49
50//////////////////////////////////////////////
51
52#[macro_use]
53extern crate lazy_static;
54#[macro_use]
55extern crate libc_print;
56
57#[doc(hidden)]
58pub mod macros;
59
60#[cfg(all(not(doc), not(test)))]
61pub mod panic;
62
63mod output;
64
65//////////////////////////////////////////////
66
67#[cfg(all(not(doc), not(test)))]
68pub fn crash(file: &str, line: u32, column: u32, arguments: Option<&Arguments>) -> ! {
69    panic::cx_panic(&PanicInfo::internal_constructor(
70        arguments,
71        &Location::internal_constructor(file, line, column),
72        false,
73    ))
74}
75
76#[cfg(any(doc, test))]
77#[doc(hidden)]
78pub fn crash(_: &str, _: u32, _: u32, _: Option<&Arguments>) -> ! {
79    loop {}
80}
81
82//////////////////////////////////////////////
83
84/// Represents the outcome of a particular test.
85///
86/// # Ignored
87///
88/// The test was ignored and didn't run.
89///
90/// # Passed
91///
92/// The test passed without any problem.
93///
94/// # Failed
95///
96/// The test didn't pass or an error occurred during its run.
97/// If `msg` is provided, a custom message will be added to be shown on the screen.
98/// You may find more details in `reason` if present.
99///
100/// # Aborted
101///
102/// The test was aborted for some reason.
103/// This is usually a sign that the test isn't parsable by Substance.
104/// You may find more details in `reason` if present.
105///
106/// # Skipped
107///
108/// The test wasn't able to run because the previous one was aborted.
109#[derive(Clone, Copy)]
110pub enum Outcome<'o> {
111    Ignored,
112    Passed,
113    Failed { reason: Option<&'o str> },
114    Aborted { reason: Option<&'o str> },
115    Skipped,
116}
117
118//////////////////////////////////////////////
119
120/// Struct that contain information about a test function to run.
121///
122/// - name: The name of the function to be printed.
123/// - test_func: The function to call for the test, must be like _() {} (without return value).
124/// - ignored: Indicates of the test should be ignored.
125/// - should_panic: Indicates if the test should panic, if true, then a panicking test will be marked as success
126pub struct TestBlock<'u> {
127    pub name: &'u str,
128    pub test_func: fn(),
129    pub ignored: bool,
130    pub should_panic: bool,
131}
132
133//////////////////////////////////////////////
134
135/// Struct that represent the final result of the test run to display some number.
136///
137/// - has_aborted: Indicates whether a true panic occurred.
138/// - num_ignored: The number of tests that were marked as non-running.
139/// - num_passed: The number of tests that passed with success.
140/// - num_failed; The number of tests that failed during they run.
141/// - num_skipped: The number of tests that were skipped because a previous one aborted.
142#[derive(Default)]
143pub struct Conclusion {
144    has_aborted: bool,
145    num_ignored: usize,
146    num_passed: usize,
147    num_failed: usize,
148    num_skipped: usize,
149}
150
151//////////////////////////////////////////////
152
153/// Represents the current state of the framework.
154///
155/// Used to keep track of some global objects that may be required by the panic system (as it's static).
156/// - panic: Indicates whether the current test triggered a (false) panic.
157/// - msg: An optional message to display with the assertion or panic print.
158/// - tests: The full array with all the tests, used also by panic to mark all aborted test as such.
159/// - current: The current running unit test.
160/// - printer: A Printer object so panic can still update the display with some messages and a result.
161/// - conclusion: The optional conclusion that represents the final tests state, used for xUnit report.
162/// - counter: The index of the current running unit test.
163pub struct State<'s, T: Output> {
164    pub panic: bool,
165    pub msg: Option<&'s str>,
166    printer: Mutex<T>,
167    tests: Option<&'s [&'s TestBlock<'s>]>,
168    current: Option<&'s TestBlock<'s>>,
169    conclusion: Option<Conclusion>,
170    counter: usize,
171}
172
173cfg_if::cfg_if! {
174    if #[cfg(test)] {
175        lazy_static! {
176            pub static ref STATE: RwLock<State<'static, output::dummy::Dummy>> = spin::RwLock::new(State {
177                panic: false,
178                msg: None,
179                printer: spin::Mutex::new(output::dummy::Dummy::new()),
180                tests: None,
181                current: None,
182                conclusion: None,
183                counter: 0
184            });
185        }
186    } else if #[cfg(feature = "pretty")] {
187        lazy_static! {
188            pub static ref STATE: RwLock<State<'static, output::pretty::Pretty>> = spin::RwLock::new(State {
189                panic: false,
190                msg: None,
191                printer: spin::Mutex::new(output::pretty::Pretty::new()),
192                tests: None,
193                current: None,
194                conclusion: None,
195                counter: 0
196            });
197        }
198    } else if #[cfg(feature = "xml")] {
199        lazy_static! {
200            pub static ref STATE: RwLock<State<'static, output::report::Report>> = spin::RwLock::new(State {
201                panic: false,
202                msg: None,
203                printer: spin::Mutex::new(output::report::Report::new()),
204                tests: None,
205                current: None,
206                conclusion: None,
207                counter: 0
208            });
209        }
210    } else {
211        lazy_static! {
212            pub static ref STATE: RwLock<State<'static, output::combined::Combined>> = spin::RwLock::new(State {
213                panic: false,
214                msg: None,
215                printer: spin::Mutex::new(output::combined::Combined::new()),
216                tests: None,
217                current: None,
218                conclusion: None,
219                counter: 0
220            });
221        }
222    }
223}
224
225//////////////////////////////////////////////
226
227pub fn test_runner(tests: &'static [&'static TestBlock<'static>]) {
228    // Initialization
229    {
230        let mut state = STATE.write();
231        state
232            .printer
233            .try_lock()
234            .expect("Output lock is already locked")
235            .init(tests);
236        state.tests = Some(tests);
237        state.conclusion = Some(Default::default());
238    }
239
240    // Print number of tests
241    STATE.read().printer.lock().print_title();
242
243    if tests.is_empty() {
244        STATE.read().printer.lock().no_test();
245        return;
246    }
247
248    tests.iter().for_each(|t| {
249        {
250            let mut state = STATE.write();
251            let _ = state.current.insert(t);
252            state.panic = false;
253        }
254
255        let test = STATE.read().current.unwrap();
256
257        STATE.read().printer.lock().print_test(test.name);
258
259        let outcome = if test.ignored {
260            Outcome::Ignored
261        } else {
262            // Run the given function
263            (test.test_func)();
264
265            if STATE.read().panic == test.should_panic {
266                Outcome::Passed
267            } else {
268                Outcome::Failed {
269                    reason: STATE.write().msg.take(),
270                }
271            }
272        };
273
274        STATE.read().printer.lock().print_single_outcome(&outcome);
275
276        {
277            let mut state = STATE.write();
278            match outcome {
279                Outcome::Ignored => state.conclusion.as_mut().unwrap().num_ignored += 1,
280                Outcome::Passed => state.conclusion.as_mut().unwrap().num_passed += 1,
281                Outcome::Failed { .. } => state.conclusion.as_mut().unwrap().num_failed += 1,
282                _ => state.conclusion.as_mut().unwrap().num_skipped -= 1,
283            }
284            state.current.take();
285            state.counter += 1;
286        }
287    });
288
289    STATE
290        .read()
291        .printer
292        .lock()
293        .print_summary(STATE.read().conclusion.as_ref().unwrap());
294}
295
296//////////////////////////////////////////////
297/*
298#[cfg(test)]
299mod test {
300    extern crate std;
301    use super::*;
302
303    fn dummy_test_fn() {}
304    const DUMMY: TestBlock = TestBlock {
305        name: "my dummy test",
306        test_func: dummy_test_fn,
307        ignored: false,
308        should_panic: false
309    };
310
311    #[test]
312    fn test_empty() {
313        test_runner(&[]);
314    }
315
316    #[test]
317    fn test_normal() {
318        test_runner(&[&DUMMY]);
319    }
320}*/