1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! Custom test runner for building/running tests on the 3DS.
//!
//! This library can be used with
//! [`custom_test_frameworks`](https://doc.rust-lang.org/unstable-book/language-features/custom-test-frameworks.html)
//! to enable normal Rust testing workflows for 3DS homebrew.

#![feature(test)]
#![feature(custom_test_frameworks)]
#![feature(exitcode_exit_method)]
#![test_runner(run_gdb)]

extern crate test;

mod console;
mod gdb;
mod socket;

use std::process::{ExitCode, Termination};

pub use console::ConsoleRunner;
pub use gdb::GdbRunner;
pub use socket::SocketRunner;
use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts};

/// Run tests using the [`GdbRunner`].
/// This function can be used with the `#[test_runner]` attribute.
pub fn run_gdb(tests: &[&TestDescAndFn]) {
    run::<GdbRunner>(tests);
}

/// Run tests using the [`ConsoleRunner`].
/// This function can be used with the `#[test_runner]` attribute.
pub fn run_console(tests: &[&TestDescAndFn]) {
    run::<ConsoleRunner>(tests);
}

/// Run tests using the [`SocketRunner`].
/// This function can be used with the `#[test_runner]` attribute.
pub fn run_socket(tests: &[&TestDescAndFn]) {
    run::<SocketRunner>(tests);
}

fn run<Runner: TestRunner>(tests: &[&TestDescAndFn]) {
    std::env::set_var("RUST_BACKTRACE", "1");

    let mut runner = Runner::new();
    let ctx = runner.setup();

    let opts = TestOpts {
        force_run_in_process: true,
        run_tests: true,
        // TODO: color doesn't work because of TERM/TERMINFO.
        // With RomFS we might be able to fake this out nicely...
        color: ColorConfig::AlwaysColor,
        format: OutputFormat::Pretty,
        test_threads: Some(1),
        // Hopefully this interface is more stable vs specifying individual options,
        // and parsing the empty list of args should always work, I think.
        // TODO Ideally we could pass actual std::env::args() here too
        ..test::test::parse_opts(&[]).unwrap().unwrap()
    };

    let tests = tests.iter().map(|t| make_owned_test(t)).collect();
    let result = test::run_tests_console(&opts, tests);

    drop(ctx);

    let reportable_result = match result {
        Ok(true) => Ok(()),
        // Try to match stdlib console test runner behavior as best we can
        _ => Err(ExitCode::from(101)),
    };

    let _ = runner.cleanup(reportable_result);
}

/// Adapted from [`test::make_owned_test`].
/// Clones static values for putting into a dynamic vector, which `test_main()`
/// needs to hand out ownership of tests to parallel test runners.
///
/// This will panic when fed any dynamic tests, because they cannot be cloned.
fn make_owned_test(test: &TestDescAndFn) -> TestDescAndFn {
    let testfn = match test.testfn {
        TestFn::StaticTestFn(f) => TestFn::StaticTestFn(f),
        TestFn::StaticBenchFn(f) => TestFn::StaticBenchFn(f),
        _ => panic!("non-static tests passed to test::test_main_static"),
    };

    TestDescAndFn {
        testfn,
        desc: test.desc.clone(),
    }
}

/// A helper trait to make the behavior of test runners consistent.
trait TestRunner: Sized {
    /// Any context the test runner needs to remain alive for the duration of
    /// the test. This can be used for things that need to borrow the test runner
    /// itself.
    // TODO: with associated type defaults this could be `= ();`
    type Context<'this>
    where
        Self: 'this;

    /// Initialize the test runner.
    fn new() -> Self;

    /// Create the [`Context`](Self::Context), if any.
    fn setup(&mut self) -> Self::Context<'_>;

    /// Handle the results of the test and perform any necessary cleanup.
    /// The [`Context`](Self::Context) will be dropped just before this is called.
    ///
    /// This returns `T` so that the result can be used in doctests.
    fn cleanup<T: Termination>(self, test_result: T) -> T {
        test_result
    }
}

/// This module has stubs needed to link the test library, but they do nothing
/// because we don't actually need them for the runner to work.
mod link_fix {
    #[no_mangle]
    extern "C" fn execvp(
        _argc: *const libc::c_char,
        _argv: *mut *const libc::c_char,
    ) -> libc::c_int {
        -1
    }

    #[no_mangle]
    extern "C" fn pipe(_fildes: *mut libc::c_int) -> libc::c_int {
        -1
    }

    #[no_mangle]
    extern "C" fn sigemptyset(_arg1: *mut libc::sigset_t) -> ::libc::c_int {
        -1
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }

    #[test]
    #[should_panic]
    fn it_fails() {
        assert_eq!(2 + 2, 5);
    }
}