Browse Source

Merge pull request #41 from ian-h-chamberlain/testing/custom-test-framework

Use custom test framework to `cargo test` !
pull/47/head
Meziu 3 years ago committed by GitHub
parent
commit
56f4b3352a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      ctru-rs/src/lib.rs
  2. 56
      ctru-rs/src/services/ps.rs
  3. 123
      ctru-rs/src/test_runner.rs

6
ctru-rs/src/lib.rs

@ -1,5 +1,8 @@
#![crate_type = "rlib"] #![crate_type = "rlib"]
#![crate_name = "ctru"] #![crate_name = "ctru"]
#![feature(test)]
#![feature(custom_test_frameworks)]
#![test_runner(test_runner::run)]
/// Call this somewhere to force Rust to link some required crates /// Call this somewhere to force Rust to link some required crates
/// This is also a setup for some crate integration only available at runtime /// This is also a setup for some crate integration only available at runtime
@ -61,6 +64,9 @@ cfg_if::cfg_if! {
} }
} }
#[cfg(test)]
mod test_runner;
pub use crate::error::{Error, Result}; pub use crate::error::{Error, Result};
pub use crate::gfx::Gfx; pub use crate::gfx::Gfx;

56
ctru-rs/src/services/ps.rs

@ -83,3 +83,59 @@ 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 input = vec![
(1_i32, String::from("123")),
(2, String::from("2")),
(6, String::from("six")),
];
let map: HashMap<i32, String> = HashMap::from_iter(input.clone());
let mut actual: Vec<_> = map.into_iter().collect();
input.sort();
actual.sort();
assert_eq!(input, actual);
}
#[test]
fn construct_hash_map_no_rand() {
// Without initializing PS, we can't use `libc::getrandom` and constructing
// a HashMap panics at runtime.
//
// If any test case successfully creates a HashMap before this test,
// the thread-local RandomState in std will be initialized. We spawn
// a new thread to actually create the hash map, since even in multi-threaded
// test environment there's a chance this test wouldn't panic because
// some other test case ran before it.
//
// One downside of this approach is that the panic handler for the panicking
// thread prints to the console, which is not captured by the default test
// harness and prints even when the test passes.
crate::thread::Builder::new()
.stack_size(0x20_0000)
.spawn(|| {
let map: HashMap<i32, String> = HashMap::from_iter([
(1_i32, String::from("123")),
(2, String::from("2")),
(6, String::from("six")),
]);
dbg!(map);
})
.unwrap()
.join()
.expect_err("should have panicked");
}
}

123
ctru-rs/src/test_runner.rs

@ -0,0 +1,123 @@
//! Custom test runner for building/running unit tests on the 3DS.
extern crate test;
use std::io;
use test::{ColorConfig, Options, OutputFormat, RunIgnored, TestDescAndFn, TestFn, TestOpts};
use crate::console::Console;
use crate::gfx::Gfx;
use crate::services::hid::{Hid, KeyPad};
use crate::services::Apt;
/// 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 run(tests: &[&TestDescAndFn]) {
crate::init();
let gfx = Gfx::default();
let hid = Hid::init().unwrap();
let apt = Apt::init().unwrap();
let mut top_screen = gfx.top_screen.borrow_mut();
top_screen.set_wide_mode(true);
let _console = Console::init(top_screen);
// 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,
// Don't run benchmarks. We may want to create a separate runner for them in the future
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::AutoColor,
format: OutputFormat::Pretty,
shuffle: false,
shuffle_seed: None,
test_threads: None,
skip: Vec::new(),
time_options: None,
options: Options::new(),
};
// 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.");
while apt.main_loop() {
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank();
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
break;
}
}
}
/// Adapted from [`test::test_main_static`] and [`test::make_owned_test`].
fn run_static_tests(opts: &TestOpts, tests: &[&TestDescAndFn]) -> io::Result<bool> {
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 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
}
#[no_mangle]
extern "C" fn sysconf(_name: libc::c_int) -> libc::c_long {
-1
}
}
Loading…
Cancel
Save