From 1b0f5fc7e158dbbec81183291ee7fb24ab4d862c Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 24 Sep 2023 14:57:38 -0400 Subject: [PATCH 1/5] Add doctest macro to setup doctests --- .github/workflows/ci.yml | 23 +++--- README.md | 2 - run-tests/Dockerfile | 4 + run-tests/action.yml | 8 +- test-runner/src/console.rs | 5 +- test-runner/src/gdb.rs | 17 +--- test-runner/src/lib.rs | 157 +++++++++++++++++++++++++++++++++---- test-runner/src/socket.rs | 2 - 8 files changed, 167 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f117c0c..298cbb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,18 +63,17 @@ jobs: working-directory: test-runner args: -- -v - # TODO(#4): run these suckers - # - name: Build and run doc tests - # # Let's still run doc tests even if lib/integration tests fail: - # if: ${{ !cancelled() }} - # env: - # # This ensures the citra logs and video output gets put in a directory - # # where we can upload as artifacts - # RUSTDOCFLAGS: " --persist-doctests ${{ env.GITHUB_WORKSPACE }}/target/armv6k-nintendo-3ds/debug/doctests" - # uses: ./run-tests - # with: - # working-directory: test-runner - # args: --doc -- -v + - name: Build and run doc tests + # Still run doc tests even if lib/integration tests fail: + if: ${{ !cancelled() }} + env: + # This ensures the citra logs and video output get put in a directory where the + # artifact upload can find them (instead of being removed from the tmpdir) + RUSTDOCFLAGS: " --persist-doctests ${{ env.GITHUB_WORKSPACE }}/target/armv6k-nintendo-3ds/debug/doctests" + uses: ./run-tests + with: + working-directory: test-runner + args: --doc -- -v - name: Upload citra logs and capture videos uses: actions/upload-artifact@v3 diff --git a/README.md b/README.md index 67018b1..e321dc3 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,6 @@ jobs: volumes: # This is required so the test action can `docker run` the runner: - '/var/run/docker.sock:/var/run/docker.sock' - # This is required so doctest artifacts are accessible to the action: - - '/tmp:/tmp' steps: - name: Checkout branch diff --git a/run-tests/Dockerfile b/run-tests/Dockerfile index aa95b1d..655195b 100644 --- a/run-tests/Dockerfile +++ b/run-tests/Dockerfile @@ -8,7 +8,11 @@ ARG CITRA_CHANNEL=nightly ARG CITRA_RELEASE=1995 RUN download_citra ${CITRA_CHANNEL} ${CITRA_RELEASE} +<<<<<<< Updated upstream:run-tests/Dockerfile FROM devkitpro/devkitarm:latest as devkitarm +======= +FROM devkitpro/devkitarm as devkitarm +>>>>>>> Stashed changes:Dockerfile # For some reason, citra isn't always happy when you try to run it for the first time, # so we build a simple dummy program to force it to create its directory structure diff --git a/run-tests/action.yml b/run-tests/action.yml index c9255dd..5f70f30 100644 --- a/run-tests/action.yml +++ b/run-tests/action.yml @@ -1,9 +1,9 @@ name: Cargo 3DS Test description: > Run `cargo 3ds test` executables using Citra. Note that to use this action, - you must mount `/var/run/docker.sock:/var/run/docker.sock` and `/tmp:/tmp` into - the container so that the runner image can be built and doctest artifacts can - be found, respectively. + you must use a container image of `devkitpro/devkitarm` and mount + `/var/run/docker.sock:/var/run/docker.sock` into the container so that the + runner image can be built by the action. inputs: args: @@ -34,6 +34,8 @@ runs: tags: ${{ inputs.runner-image }}:latest push: false load: true + cache-from: type=gha + cache-to: type=gha,mode=max - name: Ensure docker is installed in the container shell: bash diff --git a/test-runner/src/console.rs b/test-runner/src/console.rs index 5e61951..c3e53ed 100644 --- a/test-runner/src/console.rs +++ b/test-runner/src/console.rs @@ -2,6 +2,7 @@ use ctru::prelude::*; use ctru::services::gfx::{Flush, Swap}; use super::TestRunner; +use crate::TestResult; pub struct ConsoleRunner { gfx: Gfx, @@ -28,7 +29,7 @@ impl TestRunner for ConsoleRunner { Console::new(self.gfx.top_screen.borrow_mut()) } - fn cleanup(mut self, _test_result: std::io::Result) { + fn cleanup(mut self, result: T) -> T { // We don't actually care about the test result, either way we'll stop // and show the results to the user @@ -47,5 +48,7 @@ impl TestRunner for ConsoleRunner { break; } } + + result } } diff --git a/test-runner/src/gdb.rs b/test-runner/src/gdb.rs index d8a7c46..e62aa7b 100644 --- a/test-runner/src/gdb.rs +++ b/test-runner/src/gdb.rs @@ -1,6 +1,7 @@ use ctru::error::ResultCode; use super::TestRunner; +use crate::TestResult; #[derive(Default)] pub struct GdbRunner; @@ -27,22 +28,10 @@ impl TestRunner for GdbRunner { .expect("failed to redirect I/O streams to GDB"); } - fn cleanup(self, test_result: std::io::Result) { + fn cleanup(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. - match test_result { - Ok(success) => { - if success { - std::process::exit(0); - } else { - std::process::exit(101); - } - } - Err(err) => { - eprintln!("Error: {err}"); - std::process::exit(101); - } - } + std::process::exit(if test_result.succeeded() { 0 } else { 101 }) } } diff --git a/test-runner/src/lib.rs b/test-runner/src/lib.rs index 3f9597c..a9ba89c 100644 --- a/test-runner/src/lib.rs +++ b/test-runner/src/lib.rs @@ -6,6 +6,7 @@ #![feature(test)] #![feature(custom_test_frameworks)] +#![feature(exitcode_exit_method)] #![test_runner(run_gdb)] extern crate test; @@ -14,10 +15,13 @@ mod console; mod gdb; mod socket; -use console::ConsoleRunner; -use gdb::GdbRunner; -use socket::SocketRunner; +use std::any::Any; +use std::error::Error; +use std::fmt::Display; +pub use console::ConsoleRunner; +pub use gdb::GdbRunner; +pub use socket::SocketRunner; use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts}; /// Show test output in GDB, using the [File I/O Protocol] (called HIO in some 3DS @@ -25,7 +29,7 @@ use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts}; /// /// [File I/O Protocol]: https://sourceware.org/gdb/onlinedocs/gdb/File_002dI_002fO-Overview.html#File_002dI_002fO-Overview pub fn run_gdb(tests: &[&TestDescAndFn]) { - run::(tests) + run::(tests); } /// Run tests using the `ctru` [`Console`] (print results to the 3DS screen). @@ -33,7 +37,7 @@ pub fn run_gdb(tests: &[&TestDescAndFn]) { /// /// [`Console`]: ctru::console::Console pub fn run_console(tests: &[&TestDescAndFn]) { - run::(tests) + run::(tests); } /// Show test output via a network socket to `3dslink`. This runner is only useful @@ -43,9 +47,96 @@ pub fn run_console(tests: &[&TestDescAndFn]) { /// /// [`Soc::redirect_to_3dslink`]: ctru::services::soc::Soc::redirect_to_3dslink pub fn run_socket(tests: &[&TestDescAndFn]) { - run::(tests) + run::(tests); } +/// Helper macro for writing doctests using this runner. Wrap this macro around +/// your normal doctest to enable running it with the test runners in this crate. +/// +/// You may optionally specify a runner before the test body, and may use any of +/// the various [`fn main()`](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#using--in-doc-tests) +/// signatures allowed by documentation tests. +/// +/// # Examples +/// +/// ## Custom runner +/// +/// ```no_run +/// test_runner::doctest! { SocketRunner, +/// assert_eq!(2 + 2, 4); +/// } +/// ``` +/// +/// ## `should_panic` +/// +/// ```should_panic +/// test_runner::doctest! { +/// assert_eq!(2 + 2, 5); +/// } +/// ``` +/// +/// ## Custom `fn main` +/// +/// ``` +/// test_runner::doctest! { +/// fn main() { +/// assert_eq!(2 + 2, 4); +/// } +/// } +/// ``` +/// +/// ``` +/// test_runner::doctest! { +/// fn main() -> Result<(), Box> { +/// assert_eq!(2 + 2, 4); +/// Ok(()) +/// } +/// } +/// ``` +/// +/// ## Implicit return type +/// +/// Note that for the rustdoc preprocessor to understand the return type, the +/// `Ok(())` expression must be written _outside_ the `doctest!` invocation. +/// +/// ``` +/// test_runner::doctest! { +/// assert_eq!(2 + 2, 4); +/// } +/// Ok::<(), std::io::Error>(()) +/// ``` +#[macro_export] +macro_rules! doctest { + ($runner:ident, fn main() $(-> $ret:ty)? { $($body:tt)* } ) => { + fn main() $(-> $ret)? { + $crate::doctest!{ $runner, $($body)* } + } + }; + ($runner:ident, $($body:tt)*) => { + use $crate::TestRunner as _; + let mut _runner = $crate::$runner::default(); + _runner.setup(); + let _result = { $($body)* }; + _runner.cleanup(_result) + }; + ($($body:tt)*) => { + $crate::doctest!{ GdbRunner, + $($body)* + } + }; +} + +#[derive(Debug)] +struct TestsFailed; + +impl Display for TestsFailed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "some tests failed!") + } +} + +impl Error for TestsFailed {} + fn run(tests: &[&TestDescAndFn]) { std::env::set_var("RUST_BACKTRACE", "1"); @@ -71,7 +162,7 @@ fn run(tests: &[&TestDescAndFn]) { drop(ctx); - runner.cleanup(result); + let _ = runner.cleanup(result); } /// Adapted from [`test::make_owned_test`]. @@ -92,8 +183,19 @@ fn make_owned_test(test: &TestDescAndFn) -> TestDescAndFn { } } +mod private { + pub trait Sealed {} + + impl Sealed for super::ConsoleRunner {} + impl Sealed for super::GdbRunner {} + impl Sealed for super::SocketRunner {} + + impl Sealed for () {} + impl Sealed for Result {} +} + /// A helper trait to make the behavior of test runners consistent. -trait TestRunner: Sized + Default { +pub trait TestRunner: private::Sealed + Sized + Default { /// Any context the test runner needs to remain alive for the duration of /// the test. This can be used for things that need to borrow the test runner /// itself. @@ -107,7 +209,36 @@ trait TestRunner: Sized + Default { /// Handle the results of the test and perform any necessary cleanup. /// The [`Context`](Self::Context) will be dropped just before this is called. - fn cleanup(self, test_result: std::io::Result); + fn cleanup(self, test_result: T) -> T { + test_result + } +} + +// A helper trait to determine whether tests succeeded. This trait is implemented +// for +pub trait TestResult: private::Sealed { + fn succeeded(&self) -> bool; +} + +impl TestResult for () { + fn succeeded(&self) -> bool { + true + } +} + +impl TestResult for Result { + fn succeeded(&self) -> bool { + // This is sort of a hack workaround for lack of specialized trait impls. + // Basically, check if T is a boolean and use it if so. Otherwise default + // to mapping Ok to success and Err to failure. + match self + .as_ref() + .map(|val| (val as &dyn Any).downcast_ref::()) + { + Ok(Some(&result)) => result, + other => other.is_ok(), + } + } } /// This module has stubs needed to link the test library, but they do nothing @@ -132,14 +263,6 @@ mod link_fix { } } -/// Verify that doctests work as expected -/// ``` -/// assert_eq!(2 + 2, 4); -/// ``` -/// -/// ```should_panic -/// assert_eq!(2 + 2, 5); -/// ``` #[cfg(doctest)] struct Dummy; diff --git a/test-runner/src/socket.rs b/test-runner/src/socket.rs index dab54d1..1a78202 100644 --- a/test-runner/src/socket.rs +++ b/test-runner/src/socket.rs @@ -22,6 +22,4 @@ impl TestRunner for SocketRunner { .redirect_to_3dslink(true, true) .expect("failed to redirect to socket"); } - - fn cleanup(self, _test_result: std::io::Result) {} } From 6322b80c58cd472ec353891b7e1feef121ea1bf3 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 24 Sep 2023 15:42:39 -0400 Subject: [PATCH 2/5] Fix merge conflict and cleanup extraneous code --- run-tests/Dockerfile | 4 --- test-runner/src/console.rs | 10 +++---- test-runner/src/gdb.rs | 7 +++-- test-runner/src/lib.rs | 57 ++++++++------------------------------ 4 files changed, 20 insertions(+), 58 deletions(-) diff --git a/run-tests/Dockerfile b/run-tests/Dockerfile index 655195b..aa95b1d 100644 --- a/run-tests/Dockerfile +++ b/run-tests/Dockerfile @@ -8,11 +8,7 @@ ARG CITRA_CHANNEL=nightly ARG CITRA_RELEASE=1995 RUN download_citra ${CITRA_CHANNEL} ${CITRA_RELEASE} -<<<<<<< Updated upstream:run-tests/Dockerfile FROM devkitpro/devkitarm:latest as devkitarm -======= -FROM devkitpro/devkitarm as devkitarm ->>>>>>> Stashed changes:Dockerfile # For some reason, citra isn't always happy when you try to run it for the first time, # so we build a simple dummy program to force it to create its directory structure diff --git a/test-runner/src/console.rs b/test-runner/src/console.rs index c3e53ed..d062ae8 100644 --- a/test-runner/src/console.rs +++ b/test-runner/src/console.rs @@ -1,8 +1,9 @@ +use std::process::Termination; + use ctru::prelude::*; use ctru::services::gfx::{Flush, Swap}; use super::TestRunner; -use crate::TestResult; pub struct ConsoleRunner { gfx: Gfx, @@ -29,11 +30,10 @@ impl TestRunner for ConsoleRunner { Console::new(self.gfx.top_screen.borrow_mut()) } - fn cleanup(mut self, result: T) -> T { - // We don't actually care about the test result, either way we'll stop - // and show the results to the user + fn cleanup(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. - // Wait to make sure the user can actually see the results before we exit println!("Press START to exit."); while self.apt.main_loop() { diff --git a/test-runner/src/gdb.rs b/test-runner/src/gdb.rs index e62aa7b..c77ad86 100644 --- a/test-runner/src/gdb.rs +++ b/test-runner/src/gdb.rs @@ -1,7 +1,8 @@ +use std::process::Termination; + use ctru::error::ResultCode; use super::TestRunner; -use crate::TestResult; #[derive(Default)] pub struct GdbRunner; @@ -28,10 +29,10 @@ impl TestRunner for GdbRunner { .expect("failed to redirect I/O streams to GDB"); } - fn cleanup(self, test_result: T) -> T { + fn cleanup(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. - std::process::exit(if test_result.succeeded() { 0 } else { 101 }) + test_result.report().exit_process() } } diff --git a/test-runner/src/lib.rs b/test-runner/src/lib.rs index a9ba89c..a60079d 100644 --- a/test-runner/src/lib.rs +++ b/test-runner/src/lib.rs @@ -15,9 +15,7 @@ mod console; mod gdb; mod socket; -use std::any::Any; -use std::error::Error; -use std::fmt::Display; +use std::process::{ExitCode, Termination}; pub use console::ConsoleRunner; pub use gdb::GdbRunner; @@ -126,17 +124,6 @@ macro_rules! doctest { }; } -#[derive(Debug)] -struct TestsFailed; - -impl Display for TestsFailed { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "some tests failed!") - } -} - -impl Error for TestsFailed {} - fn run(tests: &[&TestDescAndFn]) { std::env::set_var("RUST_BACKTRACE", "1"); @@ -162,7 +149,13 @@ fn run(tests: &[&TestDescAndFn]) { drop(ctx); - let _ = runner.cleanup(result); + let reportable_result = match result { + Ok(true) => Ok(()), + // Try to match stdlib console test runner behavior as best we can + _ => Err(ExitCode::from(101)), + }; + + let _ = runner.cleanup(reportable_result); } /// Adapted from [`test::make_owned_test`]. @@ -189,9 +182,6 @@ mod private { impl Sealed for super::ConsoleRunner {} impl Sealed for super::GdbRunner {} impl Sealed for super::SocketRunner {} - - impl Sealed for () {} - impl Sealed for Result {} } /// A helper trait to make the behavior of test runners consistent. @@ -209,38 +199,13 @@ pub trait TestRunner: private::Sealed + Sized + Default { /// Handle the results of the test and perform any necessary cleanup. /// The [`Context`](Self::Context) will be dropped just before this is called. - fn cleanup(self, test_result: T) -> T { + /// + /// This returns `T` so that the result can be used in doctests. + fn cleanup(self, test_result: T) -> T { test_result } } -// A helper trait to determine whether tests succeeded. This trait is implemented -// for -pub trait TestResult: private::Sealed { - fn succeeded(&self) -> bool; -} - -impl TestResult for () { - fn succeeded(&self) -> bool { - true - } -} - -impl TestResult for Result { - fn succeeded(&self) -> bool { - // This is sort of a hack workaround for lack of specialized trait impls. - // Basically, check if T is a boolean and use it if so. Otherwise default - // to mapping Ok to success and Err to failure. - match self - .as_ref() - .map(|val| (val as &dyn Any).downcast_ref::()) - { - Ok(Some(&result)) => result, - other => other.is_ok(), - } - } -} - /// This module has stubs needed to link the test library, but they do nothing /// because we don't actually need them for the runner to work. mod link_fix { From b58a9bbf046496cd09f14474345d0cf6b2da8f8b Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 24 Sep 2023 15:53:05 -0400 Subject: [PATCH 3/5] Use runner.temp for doctest artifacts --- .github/workflows/ci.yml | 10 +++++----- README.md | 5 ++++- run-tests/action.yml | 18 +++++++++++------- test-runner/src/lib.rs | 17 ++++++++++++++--- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 298cbb1..d86c589 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,9 +67,9 @@ jobs: # Still run doc tests even if lib/integration tests fail: if: ${{ !cancelled() }} env: - # This ensures the citra logs and video output get put in a directory where the - # artifact upload can find them (instead of being removed from the tmpdir) - RUSTDOCFLAGS: " --persist-doctests ${{ env.GITHUB_WORKSPACE }}/target/armv6k-nintendo-3ds/debug/doctests" + # This ensures the citra logs and video output get persisted to a + # directory where the artifact upload can find them. + RUSTDOCFLAGS: " --persist-doctests target/armv6k-nintendo-3ds/debug/doctests" uses: ./run-tests with: working-directory: test-runner @@ -82,5 +82,5 @@ jobs: with: name: citra-logs-${{ matrix.toolchain }} path: | - target/armv6k-nintendo-3ds/debug/**/*.txt - target/armv6k-nintendo-3ds/debug/**/*.webm + test-runner/target/armv6k-nintendo-3ds/debug/**/*.txt + test-runner/target/armv6k-nintendo-3ds/debug/**/*.webm diff --git a/README.md b/README.md index e321dc3..f65b80f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ In `lib.rs` and any integration test files: ``` Then use the `setup` and `run-tests` actions in your github workflow. This -example shows the default value for each of the inputs: +example shows the default value for each of the inputs. ```yml jobs: @@ -61,3 +61,6 @@ jobs: # https://github.com/actions/runner/issues/2058 working-directory: ${GITHUB_WORKSPACE} ``` + +See [`ci.yml`](.github/workflows/ci.yml) to see a full lint and test workflow +using these actions (including uploading output artifacts from the tests). diff --git a/run-tests/action.yml b/run-tests/action.yml index 5f70f30..d61b6e7 100644 --- a/run-tests/action.yml +++ b/run-tests/action.yml @@ -44,20 +44,24 @@ runs: - name: Run cargo 3ds test shell: bash # Set a custom runner for `cargo test` commands to use. - # Use ${GITHUB_WORKSPACE} due to + # Use ${PWD} and ${RUNNER_TEMP} due to # https://github.com/actions/runner/issues/2058, which also means - # we have to export this instead of using the env: key + # we have to export this in `run` instead of using the `env` key run: | cd ${{ inputs.working-directory }} + + # Hopefully this still works if the input is an absolute path: + mounted_pwd="${{ github.workspace }}/${{ inputs.working-directory }}" + export CARGO_TARGET_ARMV6K_NINTENDO_3DS_RUNNER=" docker run --rm - -v ${{ runner.temp }}:${{ runner.temp }} - -v ${{ github.workspace }}/target:/app/target -v ${{ github.workspace }}:${GITHUB_WORKSPACE} + -v ${mounted_pwd}/target:/app/target + -v ${{ runner.temp }}:${RUNNER_TEMP} ${{ inputs.runner-image }}:latest" env cargo 3ds -v test ${{ inputs.args }} env: - # Ensure that doctests get built into a path which is mounted on the host - # as well as in this container (via the bind mount in the RUNNER command) - TMPDIR: ${{ runner.temp }} + # Make sure doctests are built into the shared tempdir instead of the + # container's /tmp which will be immediately removed + TMPDIR: ${{ env.RUNNER_TEMP }} diff --git a/test-runner/src/lib.rs b/test-runner/src/lib.rs index a60079d..f532134 100644 --- a/test-runner/src/lib.rs +++ b/test-runner/src/lib.rs @@ -48,6 +48,10 @@ pub fn run_socket(tests: &[&TestDescAndFn]) { run::(tests); } +// Use a neat little trick with cfg(doctest) to make code fences appear in +// rustdoc output, but still compile normally when doctesting. This raises warnings +// for invalid code though, so we also silence that lint here. +#[cfg_attr(not(doctest), allow(rustdoc::invalid_rust_codeblocks))] /// Helper macro for writing doctests using this runner. Wrap this macro around /// your normal doctest to enable running it with the test runners in this crate. /// @@ -59,22 +63,27 @@ pub fn run_socket(tests: &[&TestDescAndFn]) { /// /// ## Custom runner /// +#[cfg_attr(not(doctest), doc = "````")] /// ```no_run /// test_runner::doctest! { SocketRunner, /// assert_eq!(2 + 2, 4); /// } /// ``` +#[cfg_attr(not(doctest), doc = "````")] /// /// ## `should_panic` /// +#[cfg_attr(not(doctest), doc = "````")] /// ```should_panic /// test_runner::doctest! { /// assert_eq!(2 + 2, 5); /// } /// ``` +#[cfg_attr(not(doctest), doc = "````")] /// /// ## Custom `fn main` /// +#[cfg_attr(not(doctest), doc = "````")] /// ``` /// test_runner::doctest! { /// fn main() { @@ -82,7 +91,9 @@ pub fn run_socket(tests: &[&TestDescAndFn]) { /// } /// } /// ``` +#[cfg_attr(not(doctest), doc = "````")] /// +#[cfg_attr(not(doctest), doc = "````")] /// ``` /// test_runner::doctest! { /// fn main() -> Result<(), Box> { @@ -91,18 +102,21 @@ pub fn run_socket(tests: &[&TestDescAndFn]) { /// } /// } /// ``` +#[cfg_attr(not(doctest), doc = "````")] /// /// ## Implicit return type /// /// Note that for the rustdoc preprocessor to understand the return type, the /// `Ok(())` expression must be written _outside_ the `doctest!` invocation. /// +#[cfg_attr(not(doctest), doc = "````")] /// ``` /// test_runner::doctest! { /// assert_eq!(2 + 2, 4); /// } /// Ok::<(), std::io::Error>(()) /// ``` +#[cfg_attr(not(doctest), doc = "````")] #[macro_export] macro_rules! doctest { ($runner:ident, fn main() $(-> $ret:ty)? { $($body:tt)* } ) => { @@ -228,9 +242,6 @@ mod link_fix { } } -#[cfg(doctest)] -struct Dummy; - #[cfg(test)] mod tests { #[test] From efe858d54ba96cf16a183babdf2e616aa66e8d09 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 24 Sep 2023 17:24:54 -0400 Subject: [PATCH 4/5] Add basic success example doctest --- test-runner/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test-runner/src/lib.rs b/test-runner/src/lib.rs index f532134..23b30ce 100644 --- a/test-runner/src/lib.rs +++ b/test-runner/src/lib.rs @@ -61,6 +61,16 @@ pub fn run_socket(tests: &[&TestDescAndFn]) { /// /// # Examples /// +/// ## Basic usage +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ``` +/// test_runner::doctest! { +/// assert_eq!(2 + 2, 4); +/// } +/// ``` +#[cfg_attr(not(doctest), doc = "````")] +/// /// ## Custom runner /// #[cfg_attr(not(doctest), doc = "````")] From 7fd99460c2b4216315e9a1dbd9076192b9da470a Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 24 Sep 2023 17:27:13 -0400 Subject: [PATCH 5/5] Move macro to its own module This should make the diff a little nicer and wraps up all the doctests into their own module nicely. --- test-runner/src/lib.rs | 101 +------------------------------------- test-runner/src/macros.rs | 101 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 100 deletions(-) create mode 100644 test-runner/src/macros.rs diff --git a/test-runner/src/lib.rs b/test-runner/src/lib.rs index 23b30ce..a5a9abf 100644 --- a/test-runner/src/lib.rs +++ b/test-runner/src/lib.rs @@ -13,6 +13,7 @@ extern crate test; mod console; mod gdb; +mod macros; mod socket; use std::process::{ExitCode, Termination}; @@ -48,106 +49,6 @@ pub fn run_socket(tests: &[&TestDescAndFn]) { run::(tests); } -// Use a neat little trick with cfg(doctest) to make code fences appear in -// rustdoc output, but still compile normally when doctesting. This raises warnings -// for invalid code though, so we also silence that lint here. -#[cfg_attr(not(doctest), allow(rustdoc::invalid_rust_codeblocks))] -/// Helper macro for writing doctests using this runner. Wrap this macro around -/// your normal doctest to enable running it with the test runners in this crate. -/// -/// You may optionally specify a runner before the test body, and may use any of -/// the various [`fn main()`](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#using--in-doc-tests) -/// signatures allowed by documentation tests. -/// -/// # Examples -/// -/// ## Basic usage -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::doctest! { -/// assert_eq!(2 + 2, 4); -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## Custom runner -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ```no_run -/// test_runner::doctest! { SocketRunner, -/// assert_eq!(2 + 2, 4); -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## `should_panic` -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ```should_panic -/// test_runner::doctest! { -/// assert_eq!(2 + 2, 5); -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## Custom `fn main` -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::doctest! { -/// fn main() { -/// assert_eq!(2 + 2, 4); -/// } -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::doctest! { -/// fn main() -> Result<(), Box> { -/// assert_eq!(2 + 2, 4); -/// Ok(()) -/// } -/// } -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -/// -/// ## Implicit return type -/// -/// Note that for the rustdoc preprocessor to understand the return type, the -/// `Ok(())` expression must be written _outside_ the `doctest!` invocation. -/// -#[cfg_attr(not(doctest), doc = "````")] -/// ``` -/// test_runner::doctest! { -/// assert_eq!(2 + 2, 4); -/// } -/// Ok::<(), std::io::Error>(()) -/// ``` -#[cfg_attr(not(doctest), doc = "````")] -#[macro_export] -macro_rules! doctest { - ($runner:ident, fn main() $(-> $ret:ty)? { $($body:tt)* } ) => { - fn main() $(-> $ret)? { - $crate::doctest!{ $runner, $($body)* } - } - }; - ($runner:ident, $($body:tt)*) => { - use $crate::TestRunner as _; - let mut _runner = $crate::$runner::default(); - _runner.setup(); - let _result = { $($body)* }; - _runner.cleanup(_result) - }; - ($($body:tt)*) => { - $crate::doctest!{ GdbRunner, - $($body)* - } - }; -} - fn run(tests: &[&TestDescAndFn]) { std::env::set_var("RUST_BACKTRACE", "1"); diff --git a/test-runner/src/macros.rs b/test-runner/src/macros.rs new file mode 100644 index 0000000..e198147 --- /dev/null +++ b/test-runner/src/macros.rs @@ -0,0 +1,101 @@ +//! Macros for working with test runners. + +// Use a neat little trick with cfg(doctest) to make code fences appear in +// rustdoc output, but still compile normally when doctesting. This raises warnings +// for invalid code though, so we also silence that lint here. +#[cfg_attr(not(doctest), allow(rustdoc::invalid_rust_codeblocks))] +/// Helper macro for writing doctests using this runner. Wrap this macro around +/// your normal doctest to enable running it with the test runners in this crate. +/// +/// You may optionally specify a runner before the test body, and may use any of +/// the various [`fn main()`](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#using--in-doc-tests) +/// signatures allowed by documentation tests. +/// +/// # Examples +/// +/// ## Basic usage +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ``` +/// test_runner::doctest! { +/// assert_eq!(2 + 2, 4); +/// } +/// ``` +#[cfg_attr(not(doctest), doc = "````")] +/// +/// ## Custom runner +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ```no_run +/// test_runner::doctest! { SocketRunner, +/// assert_eq!(2 + 2, 4); +/// } +/// ``` +#[cfg_attr(not(doctest), doc = "````")] +/// +/// ## `should_panic` +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ```should_panic +/// test_runner::doctest! { +/// assert_eq!(2 + 2, 5); +/// } +/// ``` +#[cfg_attr(not(doctest), doc = "````")] +/// +/// ## Custom `fn main` +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ``` +/// test_runner::doctest! { +/// fn main() { +/// assert_eq!(2 + 2, 4); +/// } +/// } +/// ``` +#[cfg_attr(not(doctest), doc = "````")] +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ``` +/// test_runner::doctest! { +/// fn main() -> Result<(), Box> { +/// assert_eq!(2 + 2, 4); +/// Ok(()) +/// } +/// } +/// ``` +#[cfg_attr(not(doctest), doc = "````")] +/// +/// ## Implicit return type +/// +/// Note that for the rustdoc preprocessor to understand the return type, the +/// `Ok(())` expression must be written _outside_ the `doctest!` invocation. +/// +#[cfg_attr(not(doctest), doc = "````")] +/// ``` +/// test_runner::doctest! { +/// assert_eq!(2 + 2, 4); +/// } +/// Ok::<(), std::io::Error>(()) +/// ``` +#[cfg_attr(not(doctest), doc = "````")] +#[macro_export] +macro_rules! doctest { + ($runner:ident, fn main() $(-> $ret:ty)? { $($body:tt)* } ) => { + fn main() $(-> $ret)? { + $crate::doctest!{ $runner, $($body)* } + } + }; + ($runner:ident, $($body:tt)*) => { + use $crate::TestRunner as _; + let mut _runner = $crate::$runner::default(); + _runner.setup(); + let _result = { $($body)* }; + _runner.cleanup(_result) + }; + ($($body:tt)*) => { + $crate::doctest!{ GdbRunner, + $($body)* + } + }; +}