diff --git a/ctru-rs/src/services/ps.rs b/ctru-rs/src/services/ps.rs index 5e9adfa..ec9b2d5 100644 --- a/ctru-rs/src/services/ps.rs +++ b/ctru-rs/src/services/ps.rs @@ -27,3 +27,48 @@ impl Drop for Ps { } } } + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use super::*; + + #[test] + fn construct_hash_map() { + let _ps = Ps::init().unwrap(); + + let mut m: HashMap = HashMap::from_iter([ + (1_i32, String::from("123")), + (2, String::from("2")), + (6, String::from("six")), + ]); + + println!("{:?}", m); + + m.remove(&2); + m.insert(5, "ok".into()); + + println!("{:#?}", m); + } + + #[test] + #[should_panic] + fn construct_hash_map_no_rand() { + // Without initializing PS, we can't use `libc::getrandom` and constructing + // a HashMap panics at runtime. + + let mut m: HashMap = HashMap::from_iter([ + (1_i32, String::from("123")), + (2, String::from("2")), + (6, String::from("six")), + ]); + + println!("{:?}", m); + + m.remove(&2); + m.insert(5, "ok".into()); + + println!("{:#?}", m); + } +} diff --git a/ctru-rs/src/test_runner.rs b/ctru-rs/src/test_runner.rs index 6ce2955..f400d32 100644 --- a/ctru-rs/src/test_runner.rs +++ b/ctru-rs/src/test_runner.rs @@ -2,7 +2,9 @@ extern crate test; -use test::TestFn; +use std::io; + +use test::{ColorConfig, Options, OutputFormat, RunIgnored, TestDescAndFn, TestFn, TestOpts}; use crate::console::Console; use crate::gfx::Gfx; @@ -11,31 +13,57 @@ use crate::services::hid::{Hid, KeyPad}; /// A custom runner to be used with `#[test_runner]`. This simple implementation /// runs all tests in series, "failing" on the first one to panic (really, the /// panic is just treated the same as any normal application panic). -pub(crate) fn test_runner(test_cases: &[&test::TestDescAndFn]) { +pub(crate) fn test_runner(tests: &[&TestDescAndFn]) { crate::init(); let gfx = Gfx::default(); - let hid = Hid::init().expect("Couldn't obtain HID controller"); - let _console = Console::init(gfx.top_screen.borrow_mut()); - - // TODO: may want to use some more features of standard testing framework, - // like output capture, filtering, panic handling, etc. - // For now this is works without too much setup. - for test_info in test_cases { - if let TestFn::StaticTestFn(testfn) = test_info.testfn { - println!("Running test {}", test_info.desc.name); - testfn(); - } else { - println!( - "unsupported test type for {}: {:?}", - test_info.desc.name, test_info.testfn - ); - } - } + let hid = Hid::init().unwrap(); + + let mut top_screen = gfx.top_screen.borrow_mut(); + top_screen.set_wide_mode(true); + let _console = Console::init(top_screen); + + // Start printing from the top left + print!("\x1b[1;1H"); + + // TODO: it would be nice to have a way of specifying argv to make these + // configurable at runtime, but I can't figure out how to do it easily, + // so for now, just hardcode everything. + let opts = TestOpts { + list: false, + filters: Vec::new(), + filter_exact: false, + // Forking is not supported + force_run_in_process: true, + exclude_should_panic: false, + run_ignored: RunIgnored::No, + run_tests: true, + // Benchmarks are not supported + bench_benchmarks: false, + logfile: None, + nocapture: false, + // 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, + shuffle: false, + shuffle_seed: None, + // tweak values? This seems to work out of the box + test_threads: Some(3), + skip: Vec::new(), + time_options: None, + options: Options::new(), + }; - println!("All tests passed! Press START to exit."); + // Use the default test implementation with our hardcoded options + let _success = run_static_tests(&opts, tests).unwrap(); + + // Make sure the user can actually see the results before we exit + println!("Press START to exit."); + + gfx.flush_buffers(); + gfx.swap_buffers(); - // TODO: do we need apt.main_loop() here? loop { hid.scan_input(); @@ -44,3 +72,64 @@ pub(crate) fn test_runner(test_cases: &[&test::TestDescAndFn]) { } } } + +/// Adapted from [`test::test_main_static`], along with [`make_owned_test`]. +fn run_static_tests(opts: &TestOpts, tests: &[&TestDescAndFn]) -> io::Result { + let tests = tests.iter().map(make_owned_test).collect(); + test::run_tests_console(opts, tests) +} + +/// 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 { + match test.testfn { + TestFn::StaticTestFn(f) => TestDescAndFn { + testfn: TestFn::StaticTestFn(f), + desc: test.desc.clone(), + }, + TestFn::StaticBenchFn(f) => TestDescAndFn { + testfn: TestFn::StaticBenchFn(f), + desc: test.desc.clone(), + }, + _ => panic!("non-static tests passed to test::test_main_static"), + } +} + +/// The following functions are stubs needed to link the test library, +/// but do nothing because we don't actually need them for it to work (hopefully). +// TODO: move to linker-fix-3ds ? +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 pthread_sigmask( + _how: ::libc::c_int, + _set: *const libc::sigset_t, + _oldset: *mut libc::sigset_t, + ) -> ::libc::c_int { + -1 + } + + #[no_mangle] + extern "C" fn sigemptyset(_arg1: *mut libc::sigset_t) -> ::libc::c_int { + -1 + } + + #[no_mangle] + extern "C" fn sysconf(_name: libc::c_int) -> libc::c_long { + -1 + } +}