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}*/