test_runner/
lib.rs

1//! Custom test runner for building/running tests on the 3DS.
2//!
3//! This library can be used with
4//! [`custom_test_frameworks`](https://doc.rust-lang.org/unstable-book/language-features/custom-test-frameworks.html)
5//! to enable normal Rust testing workflows for 3DS homebrew.
6
7#![feature(test)]
8#![feature(custom_test_frameworks)]
9#![feature(exitcode_exit_method)]
10#![test_runner(run_gdb)]
11
12extern crate test;
13
14mod console;
15mod gdb;
16mod socket;
17
18use std::process::{ExitCode, Termination};
19
20pub use console::ConsoleRunner;
21pub use gdb::GdbRunner;
22pub use socket::SocketRunner;
23use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts};
24
25/// Run tests using the [`GdbRunner`].
26/// This function can be used with the `#[test_runner]` attribute.
27pub fn run_gdb(tests: &[&TestDescAndFn]) {
28    run::<GdbRunner>(tests);
29}
30
31/// Run tests using the [`ConsoleRunner`].
32/// This function can be used with the `#[test_runner]` attribute.
33pub fn run_console(tests: &[&TestDescAndFn]) {
34    run::<ConsoleRunner>(tests);
35}
36
37/// Run tests using the [`SocketRunner`].
38/// This function can be used with the `#[test_runner]` attribute.
39pub fn run_socket(tests: &[&TestDescAndFn]) {
40    run::<SocketRunner>(tests);
41}
42
43fn run<Runner: TestRunner>(tests: &[&TestDescAndFn]) {
44    std::env::set_var("RUST_BACKTRACE", "1");
45
46    let mut runner = Runner::new();
47    let ctx = runner.setup();
48
49    let opts = TestOpts {
50        force_run_in_process: true,
51        run_tests: true,
52        // TODO: color doesn't work because of TERM/TERMINFO.
53        // With RomFS we might be able to fake this out nicely...
54        color: ColorConfig::AlwaysColor,
55        format: OutputFormat::Pretty,
56        test_threads: Some(1),
57        // Hopefully this interface is more stable vs specifying individual options,
58        // and parsing the empty list of args should always work, I think.
59        // TODO Ideally we could pass actual std::env::args() here too
60        ..test::test::parse_opts(&[]).unwrap().unwrap()
61    };
62
63    let tests = tests.iter().map(|t| make_owned_test(t)).collect();
64    let result = test::run_tests_console(&opts, tests);
65
66    drop(ctx);
67
68    let reportable_result = match result {
69        Ok(true) => Ok(()),
70        // Try to match stdlib console test runner behavior as best we can
71        _ => Err(ExitCode::from(101)),
72    };
73
74    let _ = runner.cleanup(reportable_result);
75}
76
77/// Adapted from [`test::make_owned_test`].
78/// Clones static values for putting into a dynamic vector, which `test_main()`
79/// needs to hand out ownership of tests to parallel test runners.
80///
81/// This will panic when fed any dynamic tests, because they cannot be cloned.
82fn make_owned_test(test: &TestDescAndFn) -> TestDescAndFn {
83    let testfn = match test.testfn {
84        TestFn::StaticTestFn(f) => TestFn::StaticTestFn(f),
85        TestFn::StaticBenchFn(f) => TestFn::StaticBenchFn(f),
86        _ => panic!("non-static tests passed to test::test_main_static"),
87    };
88
89    TestDescAndFn {
90        testfn,
91        desc: test.desc.clone(),
92    }
93}
94
95/// A helper trait to make the behavior of test runners consistent.
96trait TestRunner: Sized {
97    /// Any context the test runner needs to remain alive for the duration of
98    /// the test. This can be used for things that need to borrow the test runner
99    /// itself.
100    // TODO: with associated type defaults this could be `= ();`
101    type Context<'this>
102    where
103        Self: 'this;
104
105    /// Initialize the test runner.
106    fn new() -> Self;
107
108    /// Create the [`Context`](Self::Context), if any.
109    fn setup(&mut self) -> Self::Context<'_>;
110
111    /// Handle the results of the test and perform any necessary cleanup.
112    /// The [`Context`](Self::Context) will be dropped just before this is called.
113    ///
114    /// This returns `T` so that the result can be used in doctests.
115    fn cleanup<T: Termination>(self, test_result: T) -> T {
116        test_result
117    }
118}
119
120/// This module has stubs needed to link the test library, but they do nothing
121/// because we don't actually need them for the runner to work.
122mod link_fix {
123    #[no_mangle]
124    extern "C" fn execvp(
125        _argc: *const libc::c_char,
126        _argv: *mut *const libc::c_char,
127    ) -> libc::c_int {
128        -1
129    }
130
131    #[no_mangle]
132    extern "C" fn pipe(_fildes: *mut libc::c_int) -> libc::c_int {
133        -1
134    }
135
136    #[no_mangle]
137    extern "C" fn sigemptyset(_arg1: *mut libc::sigset_t) -> ::libc::c_int {
138        -1
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    #[test]
145    fn it_works() {
146        assert_eq!(2 + 2, 4);
147    }
148
149    #[test]
150    #[should_panic]
151    fn it_fails() {
152        assert_eq!(2 + 2, 5);
153    }
154}