Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ctru-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#![crate_type = "rlib"]
#![crate_name = "ctru"]
#![feature(test)]
#![feature(custom_test_frameworks)]
#![test_runner(test_runner::test_runner)]

/// Call this somewhere to force Rust to link some required crates
/// This is also a setup for some crate integration only available at runtime
Expand Down Expand Up @@ -61,6 +64,9 @@ cfg_if::cfg_if! {
}
}

#[cfg(test)]
mod test_runner;

pub use crate::error::{Error, Result};

pub use crate::gfx::Gfx;
Expand Down
45 changes: 45 additions & 0 deletions ctru-rs/src/services/ps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<i32, String> = 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<i32, String> = 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);
}
}
135 changes: 135 additions & 0 deletions ctru-rs/src/test_runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//! 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};

/// 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(tests: &[&TestDescAndFn]) {
crate::init();

let gfx = Gfx::default();
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(),
};

// 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();

loop {
hid.scan_input();

if hid.keys_down().contains(KeyPad::KEY_START) {
break;
}
}
}

/// Adapted from [`test::test_main_static`], along with [`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 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
}
}