Skip to content

Commit

Permalink
Merge pull request #178 from rust3ds/feat/restore-test-runner
Browse files Browse the repository at this point in the history
  • Loading branch information
ian-h-chamberlain committed May 3, 2024
2 parents e3250bb + 4a6df96 commit 30d663f
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 7 deletions.
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["ctru-rs", "ctru-sys"]
members = ["ctru-rs", "ctru-sys", "test-runner"]
default-members = ["ctru-rs", "ctru-sys"]
resolver = "2"

Expand All @@ -8,3 +8,8 @@ resolver = "2"
# like pthread-3ds that rely on ctru-sys, and test-runner which relies on ctru-rs
ctru-rs = { path = "ctru-rs" }
ctru-sys = { path = "ctru-sys" }
test-runner = { path = "test-runner" }

# This was the previous git repo for test-runner
[patch."https://github.com/rust3ds/test-runner"]
test-runner = { path = "test-runner" }
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ This repository is home of the `ctru-rs` project, which aims to bring full contr

This repository is organized as follows:

* [`ctru-rs`](./ctru-rs) - Safe, idiomatic wrapper around [`ctru-sys`](./ctru-sys).
* [`ctru-sys`](./ctru-sys) - Low-level, unsafe bindings to [`libctru`](https://github.com/devkitPro/libctru).
* [`ctru-rs`](./ctru-rs) - Safe, idiomatic wrapper around [`ctru-sys`](./ctru-sys).
* [`ctru-sys`](./ctru-sys) - Low-level, unsafe bindings to [`libctru`](https://github.com/devkitPro/libctru).
* [`test-runner`](./test-runner) - A helper crate for running Rust tests on 3DS (hardware or emulator).

## Getting Started

Expand All @@ -23,8 +24,9 @@ installed.
## Original version

This project is based on the efforts of the original authors:
* [Eidolon](https://github.com/HybridEidolon)
* [FenrirWolf](https://github.com/FenrirWolf)

* [Eidolon](https://github.com/HybridEidolon)
* [FenrirWolf](https://github.com/FenrirWolf)

The old version is archived [here](https://github.com/rust3ds/ctru-rs-old).

Expand Down
2 changes: 1 addition & 1 deletion ctru-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ cfg-if = "1.0.0"
ferris-says = "0.2.1"
futures = "0.3"
lewton = "0.10.2"
test-runner = { git = "https://github.com/rust3ds/test-runner.git" }
test-runner = { git = "https://github.com/rust3ds/ctru-rs.git" }
time = "0.3.7"
tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] }

Expand Down
2 changes: 1 addition & 1 deletion ctru-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ which = "4.4.0"

[dev-dependencies]
shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" }
test-runner = { git = "https://github.com/rust3ds/test-runner.git" }
test-runner = { git = "https://github.com/rust3ds/ctru-rs.git" }

[package.metadata.docs.rs]
default-target = "armv6k-nintendo-3ds"
Expand Down
14 changes: 14 additions & 0 deletions test-runner/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "test-runner"
version = "0.1.0"
edition = "2021"

[features]
console = []
gdb = []
socket = []

[dependencies]
ctru-rs = { git = "https://github.com/rust3ds/ctru-rs" }
ctru-sys = { git = "https://github.com/rust3ds/ctru-rs" }
libc = "0.2.147"
41 changes: 41 additions & 0 deletions test-runner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# test-runner

A helper crate for running automated Rust tests on a 3DS. Since the builtin
Rust test framework expects a more traditional OS with subprocesses etc.,
it's necessary to use `custom_test_frameworks` when running tests on 3DS
hardware or in an emulator to get a similar experience as a usual `cargo test`.

## Usage

First the test runner to your crate:

```sh
cargo add --dev test-runner --git https://github.com/rust3ds/ctru-rs
```

In `lib.rs` and any integration test files:

```rs
#![feature(custom_test_frameworks)]
#![test_runner(test_runner::run_gdb)]
```

<!-- TODO document the different runners -->

## Caveats

* GDB doesn't seem to support separate output streams for `stdout` and `stderr`,
so all test output to `stderr` will end up combined with `stdout` and both will be
printed to the runner's `stdout`. If you know a workaround for this that doesn't
require patching + building GDB itself please open an issue about it!

* Doctests require a bit of extra setup to work with the runner, since they don't
use the crate's `#![test_runner]`. To write doctests, add the following to the
beginning of the doctest (or `fn main()` if the test defines it):

```rust
let _runner = test_runner::GdbRunner::default();
```

The runner must remain in scope for the duration of the test in order for
the test output to be printed.
54 changes: 54 additions & 0 deletions test-runner/src/console.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use std::process::Termination;

use ctru::prelude::*;
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 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();

gfx.top_screen.borrow_mut().set_wide_mode(true);

Self { gfx, hid, apt }
}

fn setup(&mut self) -> Self::Context<'_> {
Console::new(self.gfx.top_screen.borrow_mut())
}

fn cleanup<T: Termination>(mut self, result: T) -> T {
// We don't actually care about the output of the test result, either
// way we'll stop and show the results to the user.

println!("Press START to exit.");

while self.apt.main_loop() {
let mut screen = self.gfx.top_screen.borrow_mut();
screen.flush_buffers();
screen.swap_buffers();

self.gfx.wait_for_vblank();

self.hid.scan_input();
if self.hid.keys_down().contains(KeyPad::START) {
break;
}
}

result
}
}
77 changes: 77 additions & 0 deletions test-runner/src/gdb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,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()
}
}

0 comments on commit 30d663f

Please sign in to comment.