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()
    }
}