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);
}
}