diff --git a/test-runner/src/console.rs b/test-runner/src/console.rs index d062ae8..90ad76f 100644 --- a/test-runner/src/console.rs +++ b/test-runner/src/console.rs @@ -5,14 +5,18 @@ use ctru::services::gfx::{Flush, Swap}; use super::TestRunner; +/// Run tests using the [`ctru::console::Console`] (print results to the 3DS screen). +/// This is mostly useful for running tests manually, especially on real hardware. pub struct ConsoleRunner { gfx: Gfx, hid: Hid, apt: Apt, } -impl Default for ConsoleRunner { - fn default() -> Self { +impl TestRunner for ConsoleRunner { + type Context<'this> = Console<'this>; + + fn new() -> Self { let gfx = Gfx::new().unwrap(); let hid = Hid::new().unwrap(); let apt = Apt::new().unwrap(); @@ -21,10 +25,6 @@ impl Default for ConsoleRunner { Self { gfx, hid, apt } } -} - -impl TestRunner for ConsoleRunner { - type Context<'this> = Console<'this>; fn setup(&mut self) -> Self::Context<'_> { Console::new(self.gfx.top_screen.borrow_mut()) diff --git a/test-runner/src/gdb.rs b/test-runner/src/gdb.rs index c77ad86..03a84ad 100644 --- a/test-runner/src/gdb.rs +++ b/test-runner/src/gdb.rs @@ -4,21 +4,42 @@ use ctru::error::ResultCode; use super::TestRunner; -#[derive(Default)] -pub struct GdbRunner; +// We use a little trick with cfg(doctest) to make code fences appear in +// rustdoc output, but compile without them when doctesting. This raises warnings +// for invalid code, though, so silence that lint here. +#[cfg_attr(not(doctest), allow(rustdoc::invalid_rust_codeblocks))] +/// Show test output in GDB, using the [File I/O Protocol] (called HIO in some 3DS +/// homebrew resources). Both stdout and stderr will be printed to the GDB console. +/// +/// Creating this runner at the beginning of a doctest enables output from failing +/// tests. Without `GdbRunner`, tests will still fail on panic, but they won't display +/// anything written to `stdout` or `stderr`. +/// +/// The runner should remain in scope for the remainder of the test. +/// +/// [File I/O Protocol]: https://sourceware.org/gdb/onlinedocs/gdb/File_002dI_002fO-Overview.html#File_002dI_002fO-Overview +/// +/// # Examples +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ``` +/// let _runner = test_runner::GdbRunner::default(); +/// assert_eq!(2 + 2, 4); +/// ``` +#[cfg_attr(not(doctest), doc = "````")] +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ```should_panic +/// let _runner = test_runner::GdbRunner::default(); +/// assert_eq!(2 + 2, 5); +/// ``` +#[cfg_attr(not(doctest), doc = "````")] +pub struct GdbRunner(()); -impl Drop for GdbRunner { - fn drop(&mut self) { - unsafe { ctru_sys::gdbHioDevExit() } - } -} - -impl TestRunner for GdbRunner { - type Context<'this> = (); - - fn setup(&mut self) -> Self::Context<'_> { - // TODO: `ctru` expose safe API to do this and call that instead +impl Default for GdbRunner { + fn default() -> Self { || -> ctru::Result<()> { + // TODO: `ctru` expose safe API to do this and call that instead unsafe { ResultCode(ctru_sys::gdbHioDevInit())?; // TODO: should we actually redirect stdin or nah? @@ -27,7 +48,25 @@ impl TestRunner for GdbRunner { Ok(()) }() .expect("failed to redirect I/O streams to GDB"); + + Self(()) } +} + +impl Drop for GdbRunner { + fn drop(&mut self) { + unsafe { ctru_sys::gdbHioDevExit() } + } +} + +impl TestRunner for GdbRunner { + type Context<'this> = (); + + fn new() -> Self { + Self::default() + } + + fn setup(&mut self) -> Self::Context<'_> {} fn cleanup(self, test_result: T) -> T { // GDB actually has the opportunity to inspect the exit code, diff --git a/test-runner/src/lib.rs b/test-runner/src/lib.rs index a5a9abf..b4a3fcf 100644 --- a/test-runner/src/lib.rs +++ b/test-runner/src/lib.rs @@ -13,7 +13,6 @@ extern crate test; mod console; mod gdb; -mod macros; mod socket; use std::process::{ExitCode, Termination}; @@ -23,28 +22,20 @@ pub use gdb::GdbRunner; pub use socket::SocketRunner; use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts}; -/// Show test output in GDB, using the [File I/O Protocol] (called HIO in some 3DS -/// homebrew resources). Both stdout and stderr will be printed to the GDB console. -/// -/// [File I/O Protocol]: https://sourceware.org/gdb/onlinedocs/gdb/File_002dI_002fO-Overview.html#File_002dI_002fO-Overview +/// Run tests using the [`GdbRunner`]. +/// This function can be used with the `#[test_runner]` attribute. pub fn run_gdb(tests: &[&TestDescAndFn]) { run::(tests); } -/// Run tests using the `ctru` [`Console`] (print results to the 3DS screen). -/// This is mostly useful for running tests manually, especially on real hardware. -/// -/// [`Console`]: ctru::console::Console +/// Run tests using the [`ConsoleRunner`]. +/// This function can be used with the `#[test_runner]` attribute. pub fn run_console(tests: &[&TestDescAndFn]) { run::(tests); } -/// Show test output via a network socket to `3dslink`. This runner is only useful -/// on real hardware, since `3dslink` doesn't work with emulators. -/// -/// See [`Soc::redirect_to_3dslink`] for more details. -/// -/// [`Soc::redirect_to_3dslink`]: ctru::services::soc::Soc::redirect_to_3dslink +/// Run tests using the [`SocketRunner`]. +/// This function can be used with the `#[test_runner]` attribute. pub fn run_socket(tests: &[&TestDescAndFn]) { run::(tests); } @@ -52,7 +43,7 @@ pub fn run_socket(tests: &[&TestDescAndFn]) { fn run(tests: &[&TestDescAndFn]) { std::env::set_var("RUST_BACKTRACE", "1"); - let mut runner = Runner::default(); + let mut runner = Runner::new(); let ctx = runner.setup(); let opts = TestOpts { @@ -101,16 +92,8 @@ fn make_owned_test(test: &TestDescAndFn) -> TestDescAndFn { } } -mod private { - pub trait Sealed {} - - impl Sealed for super::ConsoleRunner {} - impl Sealed for super::GdbRunner {} - impl Sealed for super::SocketRunner {} -} - /// A helper trait to make the behavior of test runners consistent. -pub trait TestRunner: private::Sealed + Sized + Default { +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. @@ -119,6 +102,9 @@ pub trait TestRunner: private::Sealed + Sized + Default { where Self: 'this; + /// Initialize the test runner. + fn new() -> Self; + /// Create the [`Context`](Self::Context), if any. fn setup(&mut self) -> Self::Context<'_>; diff --git a/test-runner/src/macros.rs b/test-runner/src/macros.rs deleted file mode 100644 index e198147..0000000 --- a/test-runner/src/macros.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Macros for working with test runners. - -// Use a neat little trick with cfg(doctest) to make code fences appear in -// rustdoc output, but still compile normally when doctesting. This raises warnings -// for invalid code though, so we also silence that lint here. -#[cfg_attr(not(doctest), allow(rustdoc::invalid_rust_codeblocks))] -/// Helper macro for writing doctests using this runner. Wrap this macro around -/// your normal doctest to enable running it with the test runners in this crate. -/// -/// You may optionally specify a runner before the test body, and may use any of -/// the various [`fn main()`](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#using--in-doc-tests) -/// signatures allowed by documentation tests. -/// -/// # Examples -/// -/// ## Basic usage -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::doctest! { -/// assert_eq!(2 + 2, 4); -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## Custom runner -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ```no_run -/// test_runner::doctest! { SocketRunner, -/// assert_eq!(2 + 2, 4); -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## `should_panic` -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ```should_panic -/// test_runner::doctest! { -/// assert_eq!(2 + 2, 5); -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## Custom `fn main` -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::doctest! { -/// fn main() { -/// assert_eq!(2 + 2, 4); -/// } -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::doctest! { -/// fn main() -> Result<(), Box> { -/// assert_eq!(2 + 2, 4); -/// Ok(()) -/// } -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## Implicit return type -/// -/// Note that for the rustdoc preprocessor to understand the return type, the -/// `Ok(())` expression must be written _outside_ the `doctest!` invocation. -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::doctest! { -/// assert_eq!(2 + 2, 4); -/// } -/// Ok::<(), std::io::Error>(()) -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -#[macro_export] -macro_rules! doctest { - ($runner:ident, fn main() $(-> $ret:ty)? { $($body:tt)* } ) => { - fn main() $(-> $ret)? { - $crate::doctest!{ $runner, $($body)* } - } - }; - ($runner:ident, $($body:tt)*) => { - use $crate::TestRunner as _; - let mut _runner = $crate::$runner::default(); - _runner.setup(); - let _result = { $($body)* }; - _runner.cleanup(_result) - }; - ($($body:tt)*) => { - $crate::doctest!{ GdbRunner, - $($body)* - } - }; -} diff --git a/test-runner/src/socket.rs b/test-runner/src/socket.rs index 1a78202..cf24a00 100644 --- a/test-runner/src/socket.rs +++ b/test-runner/src/socket.rs @@ -2,24 +2,27 @@ use ctru::prelude::*; use super::TestRunner; +/// Show test output via a network socket to `3dslink`. This runner is only useful +/// on real hardware, since `3dslink` doesn't work with emulators. +/// +/// See [`Soc::redirect_to_3dslink`] for more details. +/// +/// [`Soc::redirect_to_3dslink`]: ctru::services::soc::Soc::redirect_to_3dslink pub struct SocketRunner { soc: Soc, } -impl Default for SocketRunner { - fn default() -> Self { - Self { - soc: Soc::new().expect("failed to initialize network service"), - } - } -} - impl TestRunner for SocketRunner { - type Context<'this> = (); + type Context<'this> = &'this Soc; - fn setup(&mut self) -> Self::Context<'_> { - self.soc - .redirect_to_3dslink(true, true) + fn new() -> Self { + let mut soc = Soc::new().expect("failed to initialize network service"); + soc.redirect_to_3dslink(true, true) .expect("failed to redirect to socket"); + Self { soc } + } + + fn setup(&mut self) -> Self::Context<'_> { + &self.soc } }