1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
use std::process::Termination;
use ctru::error::ResultCode;
use super::TestRunner;
// 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 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?
ResultCode(ctru_sys::gdbHioDevRedirectStdStreams(true, true, true))?;
}
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<T: Termination>(self, test_result: T) -> T {
// GDB actually has the opportunity to inspect the exit code,
// unlike other runners, so let's follow the default behavior of the
// stdlib test runner.
test_result.report().exit_process()
}
}