Browse Source

Merge branch 'master' into camera-and-more

pull/137/head
Meziu 1 year ago committed by GitHub
parent
commit
222e37ced7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 46
      .github/actions/setup/action.yml
  2. 63
      .github/workflows/ci.yml
  3. 4
      Cargo.toml
  4. 7
      ctru-rs/Cargo.toml
  5. 54
      ctru-rs/src/applets/swkbd.rs
  6. 11
      ctru-rs/src/console.rs
  7. 9
      ctru-rs/src/error.rs
  8. 13
      ctru-rs/src/lib.rs
  9. 154
      ctru-rs/src/os.rs
  10. 15
      ctru-rs/src/services/am.rs
  11. 6
      ctru-rs/src/services/apt.rs
  12. 15
      ctru-rs/src/services/cam.rs
  13. 18
      ctru-rs/src/services/cfgu.rs
  14. 51
      ctru-rs/src/services/fs.rs
  15. 28
      ctru-rs/src/services/gfx.rs
  16. 21
      ctru-rs/src/services/hid.rs
  17. 19
      ctru-rs/src/services/ndsp/mod.rs
  18. 6
      ctru-rs/src/services/ndsp/wave.rs
  19. 12
      ctru-rs/src/services/ps.rs
  20. 7
      ctru-rs/src/services/romfs.rs
  21. 12
      ctru-rs/src/services/soc.rs
  22. 3
      ctru-rs/src/services/sslc.rs
  23. 73
      ctru-rs/src/test_runner.rs
  24. 3
      ctru-sys/build.rs
  25. 8
      ctru-sys/src/lib.rs

46
.github/actions/setup/action.yml

@ -1,46 +0,0 @@ @@ -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

63
.github/workflows/ci.yml

@ -9,19 +9,13 @@ on: @@ -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: @@ -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: @@ -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: @@ -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

4
Cargo.toml

@ -4,5 +4,7 @@ default-members = ["ctru-rs", "ctru-sys"] @@ -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" }

7
ctru-rs/Cargo.toml

@ -29,13 +29,14 @@ widestring = "0.2.2" @@ -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"]

54
ctru-rs/src/applets/swkbd.rs

@ -7,7 +7,8 @@ @@ -7,7 +7,8 @@
use bitflags::bitflags;
use ctru_sys::{
self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText, SwkbdState,
self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText,
swkbdSetInitialText, SwkbdState,
};
use libc;
use std::fmt::Display;
@ -160,7 +161,8 @@ impl SoftwareKeyboard { @@ -160,7 +161,8 @@ impl SoftwareKeyboard {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, Kind};
@ -191,7 +193,8 @@ impl SoftwareKeyboard { @@ -191,7 +193,8 @@ impl SoftwareKeyboard {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -235,7 +238,8 @@ impl SoftwareKeyboard { @@ -235,7 +238,8 @@ impl SoftwareKeyboard {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -266,7 +270,8 @@ impl SoftwareKeyboard { @@ -266,7 +270,8 @@ impl SoftwareKeyboard {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, Features};
@ -286,7 +291,8 @@ impl SoftwareKeyboard { @@ -286,7 +291,8 @@ impl SoftwareKeyboard {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters};
@ -309,7 +315,8 @@ impl SoftwareKeyboard { @@ -309,7 +315,8 @@ impl SoftwareKeyboard {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters};
@ -330,13 +337,38 @@ impl SoftwareKeyboard { @@ -330,13 +337,38 @@ impl SoftwareKeyboard {
self.state.max_digits = digits;
}
/// Set the initial text for this software keyboard.
///
/// The initial text is the text already written when you open the software keyboard.
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # fn main() {
/// #
/// use ctru::applets::swkbd::SoftwareKeyboard;
/// let mut keyboard = SoftwareKeyboard::default();
///
/// keyboard.set_initial_text("Write here what you like!");
/// #
/// # }
#[doc(alias = "swkbdSetInitialText")]
pub fn set_initial_text(&mut self, text: &str) {
unsafe {
let nul_terminated: String = text.chars().chain(once('\0')).collect();
swkbdSetInitialText(self.state.as_mut(), nul_terminated.as_ptr());
}
}
/// Set the hint text for this software keyboard.
///
/// The hint text is the text shown in gray before any text gets written in the input box.
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # fn main() {
/// #
/// use ctru::applets::swkbd::SoftwareKeyboard;
@ -363,7 +395,8 @@ impl SoftwareKeyboard { @@ -363,7 +395,8 @@ impl SoftwareKeyboard {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind};
@ -402,7 +435,8 @@ impl SoftwareKeyboard { @@ -402,7 +435,8 @@ impl SoftwareKeyboard {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind};

11
ctru-rs/src/console.rs

@ -82,12 +82,13 @@ impl<'screen> Console<'screen> { @@ -82,12 +82,13 @@ impl<'screen> Console<'screen> {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::gfx::Gfx;
/// use ctru::console::Console;
/// use ctru::services::gfx::Gfx;
///
/// // Initialize graphics (using framebuffers allocated on the HEAP).
/// let gfx = Gfx::new()?;
@ -121,7 +122,8 @@ impl<'screen> Console<'screen> { @@ -121,7 +122,8 @@ impl<'screen> Console<'screen> {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -158,7 +160,8 @@ impl<'screen> Console<'screen> { @@ -158,7 +160,8 @@ impl<'screen> Console<'screen> {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #

9
ctru-rs/src/error.rs

@ -21,13 +21,14 @@ pub type Result<T> = ::std::result::Result<T, Error>; @@ -21,13 +21,14 @@ pub type Result<T> = ::std::result::Result<T, Error>;
///
/// # 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() };
///
///
/// // The result code is parsed and any possible error gets returned by the function.
/// ResultCode(result)?;
/// Ok(())
@ -152,6 +153,8 @@ impl fmt::Debug for Error { @@ -152,6 +153,8 @@ impl fmt::Debug for Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
// TODO: should we consider using ctru_sys::osStrError here as well?
// It might do some of the work for us or provide additional details
&Self::Os(err) => write!(
f,
"libctru result code 0x{err:08X}: [{} {}] {}: {}",

13
ctru-rs/src/lib.rs

@ -18,11 +18,10 @@ @@ -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; @@ -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 {
@ -116,10 +119,8 @@ pub mod console; @@ -116,10 +119,8 @@ pub mod console;
pub mod error;
pub mod linear;
pub mod mii;
pub mod os;
pub mod prelude;
pub mod services;
#[cfg(test)]
mod test_runner;
pub use crate::error::{Error, Result};

154
ctru-rs/src/os.rs

@ -0,0 +1,154 @@ @@ -0,0 +1,154 @@
//! Utilities to get information about the operating system and hardware state.
/// System version information. This struct is used for both kernel and firmware versions.
///
/// # Example
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// let firm_version = ctru::os::firm_version();
/// assert_ne!(firm_version.major(), 0);
///
/// let kernel_version = ctru::os::kernel_version();
/// assert_ne!(kernel_version.major(), 0);
/// ```
#[derive(Clone, Copy)]
pub struct Version(u32);
impl Version {
/// Pack a system version from its components
pub fn new(major: u8, minor: u8, revision: u8) -> Self {
let major = u32::from(major);
let minor = u32::from(minor);
let revision = u32::from(revision);
Self(major << 24 | minor << 16 | revision << 8)
}
/// Get the major version from a packed system version.
pub fn major(&self) -> u8 {
(self.0 >> 24).try_into().unwrap()
}
/// Get the minor version from a packed system version.
pub fn minor(&self) -> u8 {
(self.0 >> 16 & 0xFF).try_into().unwrap()
}
/// Get the revision from a packed system version.
pub fn revision(&self) -> u8 {
(self.0 >> 8 & 0xFF).try_into().unwrap()
}
}
/// Get the system's FIRM version.
pub fn firm_version() -> Version {
Version(unsafe { ctru_sys::osGetFirmVersion() })
}
/// Get the system's kernel version.
pub fn kernel_version() -> Version {
Version(unsafe { ctru_sys::osGetKernelVersion() })
}
// TODO: I can't seem to find good documentation on it, but we could probably
// define enums for firmware type (NATIVE_FIRM, SAFE_FIRM etc.) as well as
// application memory layout. Leaving those as future enhancements for now
/// A region of memory. Most applications will only use [`Application`](MemRegion::Application)
/// memory, but the other types can be used to query memory usage information.
/// See <https://www.3dbrew.org/wiki/Memory_layout#FCRAM_memory-regions_layout>
/// for more details on the different types of memory.
///
/// # Example
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// let all_memory = ctru::os::MemRegion::All;
///
/// assert!(all_memory.size() > 0);
/// assert!(all_memory.used() > 0);
/// assert!(all_memory.free() > 0);
/// ```
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
#[repr(u32)]
pub enum MemRegion {
/// All memory regions.
All = ctru_sys::MEMREGION_ALL,
/// APPLICATION memory.
Application = ctru_sys::MEMREGION_APPLICATION,
/// SYSTEM memory.
System = ctru_sys::MEMREGION_SYSTEM,
/// BASE memory.
Base = ctru_sys::MEMREGION_BASE,
}
impl MemRegion {
/// Get the total size of this memory region, in bytes.
pub fn size(&self) -> usize {
unsafe { ctru_sys::osGetMemRegionSize(*self as u32) }
.try_into()
.unwrap()
}
/// Get the number of bytes used within this memory region.
pub fn used(&self) -> usize {
unsafe { ctru_sys::osGetMemRegionUsed(*self as u32) }
.try_into()
.unwrap()
}
/// Get the number of bytes free within this memory region.
pub fn free(&self) -> usize {
unsafe { ctru_sys::osGetMemRegionFree(*self as u32) }
.try_into()
.unwrap()
}
}
/// WiFi signal strength. This enum's `u8` representation corresponds with
/// the number of bars displayed in the Home menu.
///
/// # Example
///
/// ```
/// let _runner = test_runner::GdbRunner::default();
/// let strength = ctru::os::WifiStrength::current();
/// assert!((strength as u8) < 4);
/// ```
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
#[repr(u8)]
pub enum WifiStrength {
/// This may indicate a very poor signal quality even worse than `Bad`,
/// or that no network is connected at all.
Disconnected = 0,
/// Poor signal strength.
Bad = 1,
/// Medium signal strength.
Decent = 2,
/// Good signal strength.
Good = 3,
}
impl WifiStrength {
/// Get the current WiFi signal strength.
pub fn current() -> Self {
match unsafe { ctru_sys::osGetWifiStrength() } {
0 => Self::Disconnected,
1 => Self::Bad,
2 => Self::Decent,
3 => Self::Good,
other => panic!("Got unexpected WiFi strength value {other}"),
}
}
}
/// Get the current value of the stereoscopic 3D slider on a scale from 0.0­­1.0.
pub fn current_3d_slider_state() -> f32 {
unsafe { ctru_sys::osGet3DSliderState() }
}
/// Whether or not a headset is currently plugged into the device.
pub fn is_headset_connected() -> bool {
unsafe { ctru_sys::osIsHeadsetConnected() }
}

15
ctru-rs/src/services/am.rs

@ -61,7 +61,8 @@ impl Am { @@ -61,7 +61,8 @@ impl Am {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -84,11 +85,13 @@ impl Am { @@ -84,11 +85,13 @@ impl Am {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { @@ -113,11 +116,13 @@ impl Am {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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

6
ctru-rs/src/services/apt.rs

@ -16,7 +16,8 @@ impl Apt { @@ -16,7 +16,8 @@ impl Apt {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -44,7 +45,8 @@ impl Apt { @@ -44,7 +45,8 @@ impl Apt {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// use std::error::Error;
/// use ctru::services::apt::Apt;
///

15
ctru-rs/src/services/cam.rs

@ -561,7 +561,8 @@ pub trait Camera: private::ConfigurableCamera { @@ -561,7 +561,8 @@ pub trait Camera: private::ConfigurableCamera {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -595,7 +596,8 @@ pub trait Camera: private::ConfigurableCamera { @@ -595,7 +596,8 @@ pub trait Camera: private::ConfigurableCamera {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -981,7 +983,8 @@ pub trait Camera: private::ConfigurableCamera { @@ -981,7 +983,8 @@ pub trait Camera: private::ConfigurableCamera {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # use std::time::Duration;
/// # fn main() -> Result<(), Box<dyn Error>> {
@ -1103,7 +1106,8 @@ impl Cam { @@ -1103,7 +1106,8 @@ impl Cam {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -1158,7 +1162,8 @@ impl Cam { @@ -1158,7 +1162,8 @@ impl Cam {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #

18
ctru-rs/src/services/cfgu.rs

@ -84,7 +84,8 @@ impl Cfgu { @@ -84,7 +84,8 @@ impl Cfgu {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -105,7 +106,8 @@ impl Cfgu { @@ -105,7 +106,8 @@ impl Cfgu {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -129,7 +131,8 @@ impl Cfgu { @@ -129,7 +131,8 @@ impl Cfgu {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -153,7 +156,8 @@ impl Cfgu { @@ -153,7 +156,8 @@ impl Cfgu {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -177,7 +181,8 @@ impl Cfgu { @@ -177,7 +181,8 @@ impl Cfgu {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -205,7 +210,8 @@ impl Cfgu { @@ -205,7 +210,8 @@ impl Cfgu {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #

51
ctru-rs/src/services/fs.rs

@ -136,7 +136,8 @@ pub struct Fs(()); @@ -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 { @@ -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<dyn Error>> {
/// #
/// 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 { @@ -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<dyn Error>> {
/// #
/// 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 { @@ -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<R>`:
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { @@ -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 { @@ -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 { @@ -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<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> {
OpenOptions::new()
@ -407,8 +419,9 @@ impl File { @@ -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();

28
ctru-rs/src/services/gfx.rs

@ -260,7 +260,8 @@ impl Gfx { @@ -260,7 +260,8 @@ impl Gfx {
/// The new `Gfx` instance will allocate the needed framebuffers in the CPU-GPU shared memory region (to ensure compatibiltiy with all possible uses of the `Gfx` service).
/// As such, it's the same as calling:
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -277,7 +278,8 @@ impl Gfx { @@ -277,7 +278,8 @@ impl Gfx {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -299,11 +301,13 @@ impl Gfx { @@ -299,11 +301,13 @@ impl Gfx {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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.
@ -391,18 +395,20 @@ impl Gfx { @@ -391,18 +395,20 @@ impl Gfx {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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();
@ -434,7 +440,8 @@ impl TopScreen3D<'_> { @@ -434,7 +440,8 @@ impl TopScreen3D<'_> {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -557,7 +564,10 @@ mod tests { @@ -557,7 +564,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)));
}
}

21
ctru-rs/src/services/hid.rs

@ -106,7 +106,8 @@ impl Hid { @@ -106,7 +106,8 @@ impl Hid {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -148,7 +149,8 @@ impl Hid { @@ -148,7 +149,8 @@ impl Hid {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -170,7 +172,8 @@ impl Hid { @@ -170,7 +172,8 @@ impl Hid {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -199,7 +202,8 @@ impl Hid { @@ -199,7 +202,8 @@ impl Hid {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -228,7 +232,8 @@ impl Hid { @@ -228,7 +232,8 @@ impl Hid {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -260,7 +265,8 @@ impl Hid { @@ -260,7 +265,8 @@ impl Hid {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -293,7 +299,8 @@ impl Hid { @@ -293,7 +299,8 @@ impl Hid {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #

19
ctru-rs/src/services/ndsp/mod.rs

@ -3,8 +3,17 @@ @@ -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};
@ -124,7 +133,8 @@ impl Ndsp { @@ -124,7 +133,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
///
@ -508,14 +518,15 @@ impl Channel<'_> { @@ -508,14 +518,15 @@ impl Channel<'_> {
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// # 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);

6
ctru-rs/src/services/ndsp/wave.rs

@ -36,9 +36,10 @@ impl Wave { @@ -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 { @@ -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);

12
ctru-rs/src/services/ps.rs

@ -63,7 +63,8 @@ impl Ps { @@ -63,7 +63,8 @@ impl Ps {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -86,7 +87,8 @@ impl Ps { @@ -86,7 +87,8 @@ impl Ps {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -110,7 +112,8 @@ impl Ps { @@ -110,7 +112,8 @@ impl Ps {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -134,7 +137,8 @@ impl Ps { @@ -134,7 +137,8 @@ impl Ps {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #

7
ctru-rs/src/services/romfs.rs

@ -45,7 +45,8 @@ impl RomFS { @@ -45,7 +45,8 @@ impl RomFS {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -82,11 +83,13 @@ impl RomFS { @@ -82,11 +83,13 @@ impl RomFS {
mod tests {
use super::*;
// 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
#[test]
#[should_panic]
fn romfs_lock() {
let romfs = RomFS::new().unwrap();
ROMFS_ACTIVE.try_lock().unwrap();
drop(romfs);

12
ctru-rs/src/services/soc.rs

@ -30,7 +30,8 @@ impl Soc { @@ -30,7 +30,8 @@ impl Soc {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -56,7 +57,8 @@ impl Soc { @@ -56,7 +57,8 @@ impl Soc {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -96,7 +98,8 @@ impl Soc { @@ -96,7 +98,8 @@ impl Soc {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
@ -129,7 +132,8 @@ impl Soc { @@ -129,7 +132,8 @@ impl Soc {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #

3
ctru-rs/src/services/sslc.rs

@ -12,7 +12,8 @@ impl SslC { @@ -12,7 +12,8 @@ impl SslC {
///
/// # Example
///
/// ```no_run
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #

73
ctru-rs/src/test_runner.rs

@ -1,73 +0,0 @@ @@ -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<bool> {
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"),
}
}

3
ctru-sys/build.rs

@ -171,7 +171,8 @@ fn check_libctru_version() -> Result<(String, String, String), Box<dyn Error>> { @@ -171,7 +171,8 @@ fn check_libctru_version() -> Result<(String, String, String), Box<dyn Error>> {
if lib_version != crate_built_version {
Err(format!(
"libctru version is {lib_version} but this crate was built for {crate_built_version}"
))?;
)
.into());
}
let Output { stdout, .. } = Command::new(pacman)

8
ctru-sys/src/lib.rs

@ -14,3 +14,11 @@ include!(concat!(env!("OUT_DIR"), "/bindings.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.",
));

Loading…
Cancel
Save