test_runner/gdb.rs
1use std::process::Termination;
2
3use ctru::error::ResultCode;
4
5use super::TestRunner;
6
7// We use a little trick with cfg(doctest) to make code fences appear in
8// rustdoc output, but compile without them when doctesting. This raises warnings
9// for invalid code, though, so silence that lint here.
10#[cfg_attr(not(doctest), allow(rustdoc::invalid_rust_codeblocks))]
11/// Show test output in GDB, using the [File I/O Protocol] (called HIO in some 3DS
12/// homebrew resources). Both stdout and stderr will be printed to the GDB console.
13///
14/// Creating this runner at the beginning of a doctest enables output from failing
15/// tests. Without `GdbRunner`, tests will still fail on panic, but they won't display
16/// anything written to `stdout` or `stderr`.
17///
18/// The runner should remain in scope for the remainder of the test.
19///
20/// [File I/O Protocol]: https://sourceware.org/gdb/onlinedocs/gdb/File_002dI_002fO-Overview.html#File_002dI_002fO-Overview
21///
22/// # Examples
23///
24#[cfg_attr(not(doctest), doc = "````")]
25/// ```
26/// let _runner = test_runner::GdbRunner::default();
27/// assert_eq!(2 + 2, 4);
28/// ```
29#[cfg_attr(not(doctest), doc = "````")]
30///
31#[cfg_attr(not(doctest), doc = "````")]
32/// ```should_panic
33/// let _runner = test_runner::GdbRunner::default();
34/// assert_eq!(2 + 2, 5);
35/// ```
36#[cfg_attr(not(doctest), doc = "````")]
37pub struct GdbRunner(());
38
39impl Default for GdbRunner {
40 fn default() -> Self {
41 || -> ctru::Result<()> {
42 // TODO: `ctru` expose safe API to do this and call that instead
43 unsafe {
44 ResultCode(ctru_sys::gdbHioDevInit())?;
45 // TODO: should we actually redirect stdin or nah?
46 ResultCode(ctru_sys::gdbHioDevRedirectStdStreams(true, true, true))?;
47 }
48 Ok(())
49 }()
50 .expect("failed to redirect I/O streams to GDB");
51
52 Self(())
53 }
54}
55
56impl Drop for GdbRunner {
57 fn drop(&mut self) {
58 unsafe { ctru_sys::gdbHioDevExit() }
59 }
60}
61
62impl TestRunner for GdbRunner {
63 type Context<'this> = ();
64
65 fn new() -> Self {
66 Self::default()
67 }
68
69 fn setup(&mut self) -> Self::Context<'_> {}
70
71 fn cleanup<T: Termination>(self, test_result: T) -> T {
72 // GDB actually has the opportunity to inspect the exit code,
73 // unlike other runners, so let's follow the default behavior of the
74 // stdlib test runner.
75 test_result.report().exit_process()
76 }
77}