From ffd037e11aab1a0fd37768c8e3fbcda24f87797f Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 24 Sep 2023 18:45:19 -0400 Subject: [PATCH 1/3] Drop custom runner support Fix compilation for use cases with imports, custom main etc. by using a terminal arm trick with `@_@`. --- test-runner/src/macros.rs | 88 ++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/test-runner/src/macros.rs b/test-runner/src/macros.rs index e198147..18875ad 100644 --- a/test-runner/src/macros.rs +++ b/test-runner/src/macros.rs @@ -5,10 +5,11 @@ // 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. +/// your normal doctest to enable running it with this crate's +/// [`GdbRunner`](crate::GdbRunner). /// -/// 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) +/// You 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 @@ -18,23 +19,18 @@ #[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); +/// let two = 2; +/// let four = 4; +/// assert_eq!(two + two, four); /// } /// ``` #[cfg_attr(not(doctest), doc = "````")] /// /// ## `should_panic` /// +/// Using `no_run` or `ignore` makes this macro somewhat irrelevant, but +/// `should_panic` is still supported: +/// #[cfg_attr(not(doctest), doc = "````")] /// ```should_panic /// test_runner::doctest! { @@ -43,23 +39,22 @@ /// ``` #[cfg_attr(not(doctest), doc = "````")] /// -/// ## Custom `fn main` +/// ## Custom `fn main`, crate attribute /// #[cfg_attr(not(doctest), doc = "````")] /// ``` -/// test_runner::doctest! { -/// fn main() { -/// assert_eq!(2 + 2, 4); -/// } -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] +/// #![allow(unused)] +/// +/// use std::error::Error; /// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` /// test_runner::doctest! { -/// fn main() -> Result<(), Box> { -/// assert_eq!(2 + 2, 4); +/// // imports can be added outside or inside the macro +/// use std::ops::Add; +/// +/// fn main() -> Result<(), Box> { +/// let two = 2; +/// let four = 4; +/// assert_eq!(Add::add(two, two), four); /// Ok(()) /// } /// } @@ -79,23 +74,40 @@ /// Ok::<(), std::io::Error>(()) /// ``` #[cfg_attr(not(doctest), doc = "````")] +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ```should_panic +/// test_runner::doctest! { +/// assert_eq!(2 + 2, 4); +/// Err::<(), &str>("uh oh") +/// } +/// ``` +#[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)* } + (@_@ $($body:tt)*) => { + fn main() -> impl std::process::Termination { + #[allow(unused_imports)] + use $crate::TestRunner as _; + + let mut _runner = $crate::GdbRunner::default(); + _runner.setup(); + + // Luckily, Rust allows $body to define an inner shadowing main() + // and call it, without resulting in infinite recursion. + let _result = { $($body)* }; + + _runner.cleanup(_result) } }; - ($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! { @_@ $($body)* } }; - ($($body:tt)*) => { - $crate::doctest!{ GdbRunner, - $($body)* + ( $($attrs:meta)* $($items:item)* ) => { + $(attrs)* + $crate::doctest! { @_@ + $($items)* + main() } }; } From 439a5f2cddefbaca53f0f5c9d6f53aaa9aed7c29 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Mon, 25 Sep 2023 20:19:57 -0400 Subject: [PATCH 2/3] Simplify macro a ton and don't bother tearing down We don't actually need to do teardown for the gdb runner to work, but redirecting output streams still works if the runner is in scope for the whole test. --- test-runner/src/macros.rs | 112 +++++++------------------------------- 1 file changed, 19 insertions(+), 93 deletions(-) diff --git a/test-runner/src/macros.rs b/test-runner/src/macros.rs index 18875ad..721faf8 100644 --- a/test-runner/src/macros.rs +++ b/test-runner/src/macros.rs @@ -1,113 +1,39 @@ //! 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. +// 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))] -/// Helper macro for writing doctests using this runner. Wrap this macro around -/// your normal doctest to enable running it with this crate's -/// [`GdbRunner`](crate::GdbRunner). -/// -/// You 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. +/// Helper macro for writing doctests using this runner. Call this macro at the +/// beginning of a doctest enables output from failing tests using this crate's +/// [`GdbRunner`](crate::GdbRunner). Without `setup_doctest!()`, doctests will +/// still fail on panic, but they won't display anything written to `stdout` or +/// `stderr`. /// /// # Examples /// -/// ## Basic usage -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::doctest! { -/// let two = 2; -/// let four = 4; -/// assert_eq!(two + two, four); -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## `should_panic` -/// -/// Using `no_run` or `ignore` makes this macro somewhat irrelevant, but -/// `should_panic` is still supported: -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ```should_panic -/// test_runner::doctest! { -/// assert_eq!(2 + 2, 5); -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## Custom `fn main`, crate attribute -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// #![allow(unused)] -/// -/// use std::error::Error; -/// -/// test_runner::doctest! { -/// // imports can be added outside or inside the macro -/// use std::ops::Add; -/// -/// fn main() -> Result<(), Box> { -/// let two = 2; -/// let four = 4; -/// assert_eq!(Add::add(two, two), four); -/// 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>(()) +/// test_runner::setup_doctest!(); +/// assert_eq!(2 + 2, 4); /// ``` #[cfg_attr(not(doctest), doc = "````")] /// #[cfg_attr(not(doctest), doc = "````")] /// ```should_panic -/// test_runner::doctest! { -/// assert_eq!(2 + 2, 4); -/// Err::<(), &str>("uh oh") -/// } +/// test_runner::setup_doctest!(); +/// assert_eq!(2 + 2, 5); /// ``` #[cfg_attr(not(doctest), doc = "````")] #[macro_export] -macro_rules! doctest { - (@_@ $($body:tt)*) => { - fn main() -> impl std::process::Termination { - #[allow(unused_imports)] - use $crate::TestRunner as _; +macro_rules! setup_doctest { + () => { + use $crate::TestRunner as _; - let mut _runner = $crate::GdbRunner::default(); - _runner.setup(); + let mut runner = $crate::GdbRunner::default(); + runner.setup(); - // Luckily, Rust allows $body to define an inner shadowing main() - // and call it, without resulting in infinite recursion. - let _result = { $($body)* }; - - _runner.cleanup(_result) - } - }; - ( $($body:tt)* ) => { - $crate::doctest! { @_@ $($body)* } - }; - ( $($attrs:meta)* $($items:item)* ) => { - $(attrs)* - $crate::doctest! { @_@ - $($items)* - main() - } + // We don't bother with cleanup here, since the macro is meant to be used + // in a doctest context (i.e. `fn main()`, not used as a test runner) }; } From 8ff3c1c63f89e6482d99c477b87ea525ea0e6be7 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Mon, 25 Sep 2023 21:39:51 -0400 Subject: [PATCH 3/3] Lots of refactor / doc comments --- test-runner/src/console.rs | 12 +++---- test-runner/src/gdb.rs | 65 ++++++++++++++++++++++++++++++-------- test-runner/src/lib.rs | 36 +++++++-------------- test-runner/src/macros.rs | 39 ----------------------- test-runner/src/socket.rs | 27 +++++++++------- 5 files changed, 84 insertions(+), 95 deletions(-) delete mode 100644 test-runner/src/macros.rs 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 721faf8..0000000 --- a/test-runner/src/macros.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Macros for working with test runners. - -// 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))] -/// Helper macro for writing doctests using this runner. Call this macro at the -/// beginning of a doctest enables output from failing tests using this crate's -/// [`GdbRunner`](crate::GdbRunner). Without `setup_doctest!()`, doctests will -/// still fail on panic, but they won't display anything written to `stdout` or -/// `stderr`. -/// -/// # Examples -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::setup_doctest!(); -/// assert_eq!(2 + 2, 4); -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ```should_panic -/// test_runner::setup_doctest!(); -/// assert_eq!(2 + 2, 5); -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -#[macro_export] -macro_rules! setup_doctest { - () => { - use $crate::TestRunner as _; - - let mut runner = $crate::GdbRunner::default(); - runner.setup(); - - // We don't bother with cleanup here, since the macro is meant to be used - // in a doctest context (i.e. `fn main()`, not used as a test runner) - }; -} 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 } }