diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml deleted file mode 100644 index a86d6f67..00000000 --- a/.github/actions/setup/action.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Setup -description: Set up CI environment for Rust + 3DS development - -inputs: - toolchain: - description: The Rust toolchain to use for the steps - required: true - default: nightly - -runs: - using: composite - steps: - # https://github.com/nektos/act/issues/917#issuecomment-1074421318 - - if: ${{ env.ACT }} - shell: bash - name: Hack container for local development - run: | - curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - - sudo apt-get install -y nodejs - - - name: Setup default Rust toolchain - # Use this helper action so we get matcher support - # https://github.com/actions-rust-lang/setup-rust-toolchain/pull/15 - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - components: clippy, rustfmt, rust-src - toolchain: ${{ inputs.toolchain }} - - - name: Install build tools for host - shell: bash - run: sudo apt-get update && sudo apt-get install -y build-essential - - - name: Install cargo-3ds - uses: actions-rs/cargo@v1 - with: - command: install - # TODO: this should probably just be a released version from crates.io - # once cargo-3ds gets published somewhere... - args: >- - --git https://github.com/rust3ds/cargo-3ds - --rev 78a652fdfb01e2614a792d1a56b10c980ee1dae9 - - - name: Set PATH to include devkitARM - shell: bash - # For some reason devkitARM/bin is not part of the default PATH in the container - run: echo "${DEVKITARM}/bin" >> $GITHUB_PATH diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee97cccc..69a3978c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,19 +9,13 @@ on: - master workflow_dispatch: -env: - # https://blog.rust-lang.org/2022/06/22/sparse-registry-testing.html - CARGO_UNSTABLE_SPARSE_REGISTRY: "true" - # actions-rust-lang/setup-rust-toolchain sets some default RUSTFLAGS - RUSTFLAGS: "" - jobs: lint: strategy: matrix: toolchain: - # Run against a "known good" nightly - - nightly-2023-05-31 + # Run against a "known good" nightly. Rustc version is 1 day behind the toolchain date + - nightly-2023-06-01 # Check for breakage on latest nightly - nightly @@ -33,11 +27,14 @@ jobs: - name: Checkout branch uses: actions/checkout@v2 - - uses: ./.github/actions/setup + - uses: rust3ds/test-runner/setup@v1 with: toolchain: ${{ matrix.toolchain }} - - name: Hide duplicate warnings from lint job + # https://github.com/actions/runner/issues/504 + # Removing the matchers won't keep the job from failing if there are errors, + # but will at least declutter pull request annotations (especially for warnings). + - name: Hide duplicate annotations from nightly if: ${{ matrix.toolchain == 'nightly' }} run: | echo "::remove-matcher owner=clippy::" @@ -46,18 +43,17 @@ jobs: - name: Check formatting run: cargo fmt --all --verbose -- --check - - name: Cargo check - run: cargo 3ds clippy --color=always --verbose --all-targets - # --deny=warnings would be nice, but can easily break CI for new clippy - # lints getting added. I'd also like to use Github's "inline warnings" - # feature, but https://github.com/actions/runner/issues/2341 means we - # can't have both that *and* colored output. + - name: Cargo check ctru-sys (without tests) + run: cargo 3ds clippy --package ctru-sys --color=always --verbose + + - name: Cargo check ctru-rs (including tests) + run: cargo 3ds clippy --package ctru-rs --color=always --verbose --all-targets - doctests: + test: strategy: matrix: toolchain: - - nightly-2023-05-31 + - nightly-2023-06-01 - nightly continue-on-error: ${{ matrix.toolchain == 'nightly' }} runs-on: ubuntu-latest @@ -66,15 +62,36 @@ jobs: - name: Checkout branch uses: actions/checkout@v2 - - uses: ./.github/actions/setup + - uses: rust3ds/test-runner/setup@v1 with: toolchain: ${{ matrix.toolchain }} - name: Hide duplicated warnings from lint job run: echo "::remove-matcher owner=clippy::" - - name: Build doc tests - run: cargo 3ds test --doc --verbose + # This needs to be done separately from running the tests to ensure the + # lib tests' .3dsx is built before the test is run (for romfs). We don't + # really have a good way to build the 3dsx in between the build + test, + # unless cargo-3ds actually runs them as separate commands. See + # https://github.com/rust3ds/cargo-3ds/issues/44 for more details + - name: Build lib and integration tests + run: cargo 3ds test --no-run --tests --package ctru-rs + + - name: Run lib and integration tests + uses: rust3ds/test-runner/run-tests@v1 + with: + args: --tests --package ctru-rs - # TODO: it would be nice to actually build 3dsx for examples/tests, etc. - # and run it somehow, but exactly how remains to be seen. + - name: Build and run doc tests + uses: rust3ds/test-runner/run-tests@v1 + with: + args: --doc --package ctru-rs + + - name: Upload citra logs and capture videos + uses: actions/upload-artifact@v3 + if: success() || failure() # always run unless the workflow was cancelled + with: + name: citra-logs-${{ matrix.toolchain }} + path: | + target/armv6k-nintendo-3ds/debug/deps/*.txt + target/armv6k-nintendo-3ds/debug/deps/*.webm diff --git a/Cargo.toml b/Cargo.toml index 889c913d..d88e1371 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,7 @@ default-members = ["ctru-rs", "ctru-sys"] resolver = "2" [patch.'https://github.com/rust3ds/ctru-rs'] -# Make sure all dependencies use the local ctru-sys package +# Make sure all dependencies use the local packages. This is needed for things +# 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" } diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index 0523b27a..ac105869 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -29,13 +29,14 @@ widestring = "0.2.2" toml = "0.5" [dev-dependencies] +bytemuck = "1.12.3" +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" } time = "0.3.7" tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] } -cfg-if = "1.0.0" -bytemuck = "1.12.3" -lewton = "0.10.2" [features] default = ["romfs", "big-stack"] diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 8861aced..84972339 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -160,7 +160,8 @@ impl SoftwareKeyboard { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # /// use ctru::applets::swkbd::{SoftwareKeyboard, Kind}; @@ -191,7 +192,8 @@ impl SoftwareKeyboard { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -235,7 +237,8 @@ impl SoftwareKeyboard { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -266,7 +269,8 @@ impl SoftwareKeyboard { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # /// use ctru::applets::swkbd::{SoftwareKeyboard, Features}; @@ -286,7 +290,8 @@ impl SoftwareKeyboard { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # /// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters}; @@ -309,7 +314,8 @@ impl SoftwareKeyboard { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # /// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters}; @@ -336,7 +342,8 @@ impl SoftwareKeyboard { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # /// use ctru::applets::swkbd::SoftwareKeyboard; @@ -363,7 +370,8 @@ impl SoftwareKeyboard { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind}; @@ -402,7 +410,8 @@ impl SoftwareKeyboard { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind}; diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 6768d47f..6d25d93a 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -52,12 +52,13 @@ impl<'screen> Console<'screen> { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::gfx::Gfx; /// use ctru::console::Console; + /// use ctru::services::gfx::Gfx; /// /// // Initialize graphics. /// let gfx = Gfx::new()?; @@ -94,7 +95,8 @@ impl<'screen> Console<'screen> { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -131,7 +133,8 @@ impl<'screen> Console<'screen> { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 62a4646a..0dd7cc5b 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -21,10 +21,11 @@ pub type Result = ::std::result::Result; /// /// # Example /// -/// ```no_run +/// ``` /// use ctru::error::{Result, ResultCode}; /// -/// pub fn hid_init() -> Result<()> { +/// pub fn main() -> Result<()> { +/// # let _runner = test_runner::GdbRunner::default(); /// // We run an unsafe function which returns a `ctru_sys::Result`. /// let result: ctru_sys::Result = unsafe { ctru_sys::hidInit() }; /// diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index baa99364..dce70116 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -18,11 +18,10 @@ #![crate_type = "rlib"] #![crate_name = "ctru"] #![warn(missing_docs)] -#![feature(test)] #![feature(custom_test_frameworks)] #![feature(try_trait_v2)] #![feature(allocator_api)] -#![test_runner(test_runner::run)] +#![test_runner(test_runner::run_gdb)] // TODO: does this make sense to have configurable? #![doc( html_favicon_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png" )] @@ -40,7 +39,11 @@ extern crate shim_3ds; /// /// This value was chosen to support crate dependencies which expected more stack than provided. It's suggested to use less stack if possible. #[no_mangle] -#[cfg(feature = "big-stack")] +// When building lib tests, we don't want to redefine the same symbol twice, +// since ctru-rs is both the crate under test and a dev-dependency (non-test). +// We might also be able to use #[linkage] for similar effect, but this way +// works without depending on another unstable feature. +#[cfg(all(feature = "big-stack", not(test)))] static __stacksize__: usize = 2 * 1024 * 1024; // 2MB macro_rules! from_impl { @@ -111,7 +114,4 @@ pub mod os; pub mod prelude; pub mod services; -#[cfg(test)] -mod test_runner; - pub use crate::error::{Error, Result}; diff --git a/ctru-rs/src/os.rs b/ctru-rs/src/os.rs index 8eea6970..baf654ce 100644 --- a/ctru-rs/src/os.rs +++ b/ctru-rs/src/os.rs @@ -4,7 +4,7 @@ /// /// # Example /// ``` -/// # // let _runner = test_runner::GdbRunner::default(); +/// # let _runner = test_runner::GdbRunner::default(); /// let firm_version = ctru::os::firm_version(); /// assert_ne!(firm_version.major(), 0); /// @@ -61,7 +61,7 @@ pub fn kernel_version() -> Version { /// /// # Example /// ``` -/// # // let _runner = test_runner::GdbRunner::default(); +/// # let _runner = test_runner::GdbRunner::default(); /// let all_memory = ctru::os::MemRegion::All; /// /// assert!(all_memory.size() > 0); @@ -111,7 +111,7 @@ impl MemRegion { /// # Example /// /// ``` -/// # // let _runner = test_runner::GdbRunner::default(); +/// let _runner = test_runner::GdbRunner::default(); /// let strength = ctru::os::WifiStrength::current(); /// assert!((strength as u8) < 4); /// ``` diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index 68b3af3e..cab13b35 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -61,7 +61,8 @@ impl Am { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -84,11 +85,13 @@ impl Am { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::{fs::FsMediaType, am::Am}; + /// use ctru::services::am::Am; + /// use ctru::services::fs::FsMediaType; /// let app_manager = Am::new()?; /// /// // Number of titles installed on the Nand storage. @@ -113,11 +116,13 @@ impl Am { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::{fs::FsMediaType, am::Am}; + /// use ctru::services::am::Am; + /// use ctru::services::fs::FsMediaType; /// let app_manager = Am::new()?; /// /// // Number of apps installed on the SD card storage diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index b30be858..89ad2555 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -16,7 +16,8 @@ impl Apt { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -44,7 +45,8 @@ impl Apt { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// use std::error::Error; /// use ctru::services::apt::Apt; /// diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index 8d4af7bc..449972d6 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -343,7 +343,8 @@ pub trait Camera { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -372,7 +373,8 @@ pub trait Camera { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -841,12 +843,13 @@ pub trait Camera { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # use std::time::Duration; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::cam::{Cam, Camera, ViewSize, OutputFormat}; + /// use ctru::services::cam::{Cam, Camera, OutputFormat, ViewSize}; /// let mut cam = Cam::new()?; /// /// // We borrow the inward facing `Camera`. @@ -951,7 +954,8 @@ impl Cam { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -984,7 +988,8 @@ impl Cam { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/services/cfgu.rs b/ctru-rs/src/services/cfgu.rs index b1cbe76f..981f8e65 100644 --- a/ctru-rs/src/services/cfgu.rs +++ b/ctru-rs/src/services/cfgu.rs @@ -84,7 +84,8 @@ impl Cfgu { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -105,7 +106,8 @@ impl Cfgu { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -129,7 +131,8 @@ impl Cfgu { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -153,7 +156,8 @@ impl Cfgu { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -177,7 +181,8 @@ impl Cfgu { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -205,7 +210,8 @@ impl Cfgu { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/services/fs.rs b/ctru-rs/src/services/fs.rs index 821b8b95..0be54250 100644 --- a/ctru-rs/src/services/fs.rs +++ b/ctru-rs/src/services/fs.rs @@ -136,7 +136,8 @@ pub struct Fs(()); /// /// # Examples /// -/// ```no_run +/// ``` +/// # let _runner = test_runner::GdbRunner::default(); /// use ctru::services::fs::Fs; /// /// let mut fs = Fs::new().unwrap(); @@ -158,12 +159,14 @@ pub struct Archive { /// /// Create a new file and write bytes to it: /// -/// ```no_run +/// ``` +/// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # /// use std::io::prelude::*; -/// use ctru::services::fs::{Fs, File}; +/// +/// use ctru::services::fs::{File, Fs}; /// /// let mut fs = Fs::new()?; /// let mut sdmc = fs.sdmc()?; @@ -174,12 +177,14 @@ pub struct Archive { /// /// Read the contents of a file into a `String`:: /// -/// ```no_run +/// ``` +/// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # /// use std::io::prelude::*; -/// use ctru::services::fs::{Fs, File}; +/// +/// use ctru::services::fs::{File, Fs}; /// /// let mut fs = Fs::new()?; /// let mut sdmc = fs.sdmc()?; @@ -196,13 +201,15 @@ pub struct Archive { /// It can be more efficient to read the contents of a file with a buffered /// `Read`er. This can be accomplished with `BufReader`: /// -/// ```no_run +/// ``` +/// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # -/// use std::io::BufReader; /// use std::io::prelude::*; -/// use ctru::services::fs::{Fs, File}; +/// use std::io::BufReader; +/// +/// use ctru::services::fs::{File, Fs}; /// /// let mut fs = Fs::new()?; /// let mut sdmc = fs.sdmc()?; @@ -247,22 +254,25 @@ pub struct Metadata { /// /// Opening a file to read: /// -/// ```no_run +/// ``` +/// # let _runner = test_runner::GdbRunner::default(); /// use ctru::services::fs::{Fs, OpenOptions}; /// /// let mut fs = Fs::new().unwrap(); /// let mut sdmc_archive = fs.sdmc().unwrap(); -/// let file = OpenOptions::new() +/// let result = OpenOptions::new() /// .read(true) /// .archive(&sdmc_archive) -/// .open("foo.txt") -/// .unwrap(); +/// .open("foo.txt"); +/// +/// assert!(result.is_err()); /// ``` /// /// Opening a file for both reading and writing, as well as creating it if it /// doesn't exist: /// -/// ```no_run +/// ``` +/// # let _runner = test_runner::GdbRunner::default(); /// use ctru::services::fs::{Fs, OpenOptions}; /// /// let mut fs = Fs::new().unwrap(); @@ -272,7 +282,7 @@ pub struct Metadata { /// .write(true) /// .create(true) /// .archive(&sdmc_archive) -/// .open("foo.txt") +/// .open("/foo.txt") /// .unwrap(); /// ``` #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -380,12 +390,14 @@ impl File { /// /// # Examples /// - /// ```no_run - /// use ctru::services::fs::{Fs, File}; + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// use ctru::services::fs::{File, Fs}; /// /// let mut fs = Fs::new().unwrap(); /// let mut sdmc_archive = fs.sdmc().unwrap(); - /// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap(); + /// // Non-existent file: + /// assert!(File::open(&sdmc_archive, "/foo.txt").is_err()); /// ``` pub fn open>(arch: &Archive, path: P) -> IoResult { OpenOptions::new() @@ -407,8 +419,9 @@ impl File { /// /// # Examples /// - /// ```no_run - /// use ctru::services::fs::{Fs, File}; + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// use ctru::services::fs::{File, Fs}; /// /// let mut fs = Fs::new().unwrap(); /// let mut sdmc_archive = fs.sdmc().unwrap(); diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 68ac9578..4c64c2be 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -253,7 +253,8 @@ impl Gfx { /// /// It's the same as calling: /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -268,7 +269,8 @@ impl Gfx { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -290,11 +292,13 @@ impl Gfx { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::{gfx::Gfx, gspgpu::FramebufferFormat}; + /// use ctru::services::gfx::Gfx; + /// use ctru::services::gspgpu::FramebufferFormat; /// /// // Top screen uses RGBA8, bottom screen uses RGB565. /// // The screen buffers are allocated in the standard HEAP memory, and not in VRAM. @@ -333,18 +337,20 @@ impl Gfx { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::{apt::Apt, gfx::Gfx}; + /// use ctru::services::apt::Apt; + /// use ctru::services::gfx::Gfx; /// let apt = Apt::new()?; /// let gfx = Gfx::new()?; /// /// // Simple main loop. /// while apt.main_loop() { /// // Main program logic - /// + /// /// // Wait for the screens to refresh. /// // This blocks the current thread to make it run at 60Hz. /// gfx.wait_for_vblank(); @@ -376,7 +382,8 @@ impl TopScreen3D<'_> { /// /// # Example /// -/// ```no_run +/// ``` +/// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -499,7 +506,10 @@ mod tests { #[test] fn gfx_duplicate() { - // We don't need to build a `Gfx` because the test runner has one already + // NOTE: this is expected to fail if using the console test runner, since + // that necessarily creates a Gfx as part of its test setup: + let _gfx = Gfx::new().unwrap(); + assert!(matches!(Gfx::new(), Err(Error::ServiceAlreadyActive))); } } diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 56a60352..1e0ceeeb 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -87,7 +87,8 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -113,7 +114,8 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -135,7 +137,8 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -164,7 +167,8 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -193,7 +197,8 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -225,7 +230,8 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -258,7 +264,8 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs index 8cda8025..987881a6 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -3,8 +3,17 @@ //! The NDSP service is used to handle communications to the DSP processor present on the console's motherboard. //! Thanks to the DSP processor the program can play sound effects and music on the console's built-in speakers or to any audio device //! connected via the audio jack. +//! +//! To use NDSP audio, you will need to dump DSP firmware from a real 3DS using +//! something like [DSP1](https://www.gamebrew.org/wiki/DSP1_3DS). +//! +//! `libctru` expects to find it at `sdmc:/3ds/dspfirm.cdc` when initializing the NDSP service. #![doc(alias = "audio")] +// As a result of requiring DSP firmware to initialize, all of the doctests in +// this module are `no_run`, since Citra doesn't provide a stub for the DSP firmware: +// https://github.com/citra-emu/citra/issues/6111 + pub mod wave; use wave::{Status, Wave}; @@ -114,7 +123,8 @@ impl Ndsp { /// # Errors /// /// This function will return an error if an instance of the [`Ndsp`] struct already exists - /// or if there are any issues during initialization. + /// or if there are any issues during initialization (for example, DSP firmware + /// cannot be found. See [module documentation](super::ndsp) for more details.). /// /// # Example /// @@ -499,14 +509,15 @@ impl Channel<'_> { /// # fn main() -> Result<(), Box> { /// # /// # use ctru::linear::LinearAllocator; - /// use ctru::services::ndsp::{AudioFormat, Ndsp, wave::Wave}; + /// use ctru::services::ndsp::wave::Wave; + /// use ctru::services::ndsp::{AudioFormat, Ndsp}; /// let ndsp = Ndsp::new()?; /// let mut channel_0 = ndsp.channel(0)?; /// - /// # let _audio_data = Box::new_in([0u8; 96], LinearAllocator); + /// # let audio_data = Box::new_in([0u8; 96], LinearAllocator); /// /// // Provide your own audio data. - /// let mut wave = Wave::new(_audio_data, AudioFormat::PCM16Stereo, false); + /// let mut wave = Wave::new(audio_data, AudioFormat::PCM16Stereo, false); /// /// // Clear the audio queue and stop playback. /// channel_0.queue_wave(&mut wave); diff --git a/ctru-rs/src/services/ndsp/wave.rs b/ctru-rs/src/services/ndsp/wave.rs index 1a383ca2..9c467e04 100644 --- a/ctru-rs/src/services/ndsp/wave.rs +++ b/ctru-rs/src/services/ndsp/wave.rs @@ -36,9 +36,10 @@ impl Wave { /// /// # Example /// - /// ```no_run + /// ``` /// # #![feature(allocator_api)] /// # fn main() { + /// # let _runner = test_runner::GdbRunner::default(); /// # /// use ctru::linear::LinearAllocator; /// use ctru::services::ndsp::{AudioFormat, wave::Wave}; @@ -110,9 +111,10 @@ impl Wave { /// /// # Example /// - /// ```no_run + /// ``` /// # #![feature(allocator_api)] /// # fn main() { + /// # let _runner = test_runner::GdbRunner::default(); /// # /// # use ctru::linear::LinearAllocator; /// # let _audio_data = Box::new_in([0u8; 96], LinearAllocator); diff --git a/ctru-rs/src/services/ps.rs b/ctru-rs/src/services/ps.rs index fc20a78c..27f21a23 100644 --- a/ctru-rs/src/services/ps.rs +++ b/ctru-rs/src/services/ps.rs @@ -63,7 +63,8 @@ impl Ps { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -86,7 +87,8 @@ impl Ps { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -110,7 +112,8 @@ impl Ps { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -134,7 +137,8 @@ impl Ps { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/services/romfs.rs b/ctru-rs/src/services/romfs.rs index e6f1c3bb..4018a7c5 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -45,7 +45,8 @@ impl RomFS { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -84,6 +85,8 @@ mod tests { use super::*; #[test] + // NOTE: this test only passes when run with a .3dsx, which for now requires separate build + // and run steps so the 3dsx is built before the runner looks for the executable fn romfs_counter() { let _romfs = RomFS::new().unwrap(); let value = *ROMFS_ACTIVE.lock().unwrap(); diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index d4189193..548e44d9 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -30,7 +30,8 @@ impl Soc { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -56,7 +57,8 @@ impl Soc { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -97,7 +99,8 @@ impl Soc { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -130,7 +133,8 @@ impl Soc { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/services/sslc.rs b/ctru-rs/src/services/sslc.rs index af8b65df..81674ad0 100644 --- a/ctru-rs/src/services/sslc.rs +++ b/ctru-rs/src/services/sslc.rs @@ -12,7 +12,8 @@ impl SslC { /// /// # Example /// - /// ```no_run + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/test_runner.rs b/ctru-rs/src/test_runner.rs deleted file mode 100644 index 97aa5f41..00000000 --- a/ctru-rs/src/test_runner.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! Custom test runner for building/running unit tests on the 3DS. - -extern crate test; - -use std::io; - -use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts}; - -use crate::prelude::*; - -/// A custom runner to be used with `#[test_runner]`. This simple implementation -/// runs all tests in series, "failing" on the first one to panic (really, the -/// panic is just treated the same as any normal application panic). -pub(crate) fn run(tests: &[&TestDescAndFn]) { - let gfx = Gfx::new().unwrap(); - let mut hid = Hid::new().unwrap(); - let apt = Apt::new().unwrap(); - - let mut top_screen = gfx.top_screen.borrow_mut(); - top_screen.set_wide_mode(true); - let _console = Console::new(top_screen); - - let opts = TestOpts { - force_run_in_process: true, - run_tests: true, - // TODO: color doesn't work because of TERM/TERMINFO. - // With RomFS we might be able to fake this out nicely... - color: ColorConfig::AutoColor, - format: OutputFormat::Pretty, - // Hopefully this interface is more stable vs specifying individual options, - // and parsing the empty list of args should always work, I think. - // TODO Ideally we could pass actual std::env::args() here too - ..test::test::parse_opts(&[]).unwrap().unwrap() - }; - // Use the default test implementation with our hardcoded options - let _success = run_static_tests(&opts, tests).unwrap(); - - // Make sure the user can actually see the results before we exit - println!("Press START to exit."); - - while apt.main_loop() { - gfx.wait_for_vblank(); - - hid.scan_input(); - if hid.keys_down().contains(KeyPad::START) { - break; - } - } -} - -/// Adapted from [`test::test_main_static`] and [`test::make_owned_test`]. -fn run_static_tests(opts: &TestOpts, tests: &[&TestDescAndFn]) -> io::Result { - let tests = tests.iter().map(make_owned_test).collect(); - test::run_tests_console(opts, tests) -} - -/// Clones static values for putting into a dynamic vector, which test_main() -/// needs to hand out ownership of tests to parallel test runners. -/// -/// This will panic when fed any dynamic tests, because they cannot be cloned. -fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn { - match test.testfn { - TestFn::StaticTestFn(f) => TestDescAndFn { - testfn: TestFn::StaticTestFn(f), - desc: test.desc.clone(), - }, - TestFn::StaticBenchFn(f) => TestDescAndFn { - testfn: TestFn::StaticBenchFn(f), - desc: test.desc.clone(), - }, - _ => panic!("non-static tests passed to test::test_main_static"), - } -} diff --git a/ctru-sys/build.rs b/ctru-sys/build.rs index 1643b8b8..b1a48b3f 100644 --- a/ctru-sys/build.rs +++ b/ctru-sys/build.rs @@ -171,7 +171,8 @@ fn check_libctru_version() -> Result<(String, String, String), Box> { if lib_version != crate_built_version { return Err(format!( "libctru version is {lib_version} but this crate was built for {crate_built_version}" - ))?; + ) + .into()); } let Output { stdout, .. } = Command::new(pacman) diff --git a/ctru-sys/src/lib.rs b/ctru-sys/src/lib.rs index 8801af31..32252e6f 100644 --- a/ctru-sys/src/lib.rs +++ b/ctru-sys/src/lib.rs @@ -14,3 +14,11 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs")); pub unsafe fn errno() -> s32 { *__errno() } + +// TODO: not sure if there's a better way to do this, but I have gotten myself +// with this a couple times so having the hint seems nice to have. +#[cfg(test)] +compile_error!(concat!( + "ctru-sys doesn't have tests and its lib test will fail to build at link time. ", + "Try specifying `--package ctru-rs` to build those tests.", +));