From 742defba13fe5d129d251a2f6873f78dd645e684 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:27:24 -0500 Subject: [PATCH 01/28] docs(contrib): Update functional/ui comparison --- src/doc/contrib/src/tests/writing.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/doc/contrib/src/tests/writing.md b/src/doc/contrib/src/tests/writing.md index 8994dd3f945..9986c7f0087 100644 --- a/src/doc/contrib/src/tests/writing.md +++ b/src/doc/contrib/src/tests/writing.md @@ -12,9 +12,8 @@ and verify its behavior, located in the [`testsuite`] directory. The There are two styles of tests that can roughly be categorized as - functional tests - The fixture is programmatically defined - - The assertions are regular string comparisons + - The assertions may be in-source snapshots, hard-coded strings, or programmatically generated - Easier to share in an issue as a code block is completely self-contained - - More resilient to insignificant changes though ui tests are easy to update when a change does occur - ui tests - The fixture is file-based - The assertions use file-backed snapshots that can be updated with an env variable From ba94d70dd0c5dd794978c58b48496d00de18ed8d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:28:00 -0500 Subject: [PATCH 02/28] docs(contrib): Update functional example --- src/doc/contrib/src/tests/writing.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/doc/contrib/src/tests/writing.md b/src/doc/contrib/src/tests/writing.md index 9986c7f0087..c7cacbd6fbc 100644 --- a/src/doc/contrib/src/tests/writing.md +++ b/src/doc/contrib/src/tests/writing.md @@ -30,6 +30,8 @@ stdout and stderr output against the expected output. Generally, a functional test will be placed in `tests/testsuite/.rs` and will look roughly like: ```rust,ignore use cargo_test_support::prelude::*; +use cargo_test_support::str; +use cargo_test_support::project; #[cargo_test] fn () { @@ -38,16 +40,13 @@ fn () { .build(); p.cargo("run --bin foo") - .with_stderr( - "\ - [COMPILING] foo [..] - [FINISHED] [..] - [RUNNING] `target/debug/foo` - ", - ) - .with_stdout("hi!") + .with_stderr_data(str![[r#" +[COMPILING] foo [..] +[FINISHED] [..] +[RUNNING] `target/debug/foo` +"#]]) + .with_stdout_data(str![["hi!"]]) .run(); - } } ``` From f05eba0ac56ec55c1d921ecc0a96df97cd0e66dd Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 11 Jul 2024 23:23:57 -0500 Subject: [PATCH 03/28] docs(test): Migrate cargo_test docs from contrib to API reference This will make them more easily discovered and make it easier to remember to update them. --- crates/cargo-test-macro/src/lib.rs | 43 ++++++++++++++++++++++++ src/doc/contrib/src/tests/writing.md | 49 +++------------------------- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/crates/cargo-test-macro/src/lib.rs b/crates/cargo-test-macro/src/lib.rs index 9d4a5fbaa99..36f253c919d 100644 --- a/crates/cargo-test-macro/src/lib.rs +++ b/crates/cargo-test-macro/src/lib.rs @@ -14,6 +14,49 @@ use std::path::Path; use std::process::Command; use std::sync::Once; +/// Replacement for `#[test]` +/// +/// The `#[cargo_test]` attribute extends `#[test]` with some setup before starting the test. +/// It will create a filesystem "sandbox" under the "cargo integration test" directory for each test, such as `/path/to/cargo/target/tmp/cit/t123/`. +/// The sandbox will contain a `home` directory that will be used instead of your normal home directory. +/// +/// The `#[cargo_test]` attribute takes several options that will affect how the test is generated. +/// They are listed in parentheses separated with commas, such as: +/// +/// ```rust,ignore +/// #[cargo_test(nightly, reason = "-Zfoo is unstable")] +/// ``` +/// +/// The options it supports are: +/// +/// * `>=1.64` --- This indicates that the test will only run with the given version of `rustc` or newer. +/// This can be used when a new `rustc` feature has been stabilized that the test depends on. +/// If this is specified, a `reason` is required to explain why it is being checked. +/// * `nightly` --- This will cause the test to be ignored if not running on the nightly toolchain. +/// This is useful for tests that use unstable options in `rustc` or `rustdoc`. +/// These tests are run in Cargo's CI, but are disabled in rust-lang/rust's CI due to the difficulty of updating both repos simultaneously. +/// A `reason` field is required to explain why it is nightly-only. +/// * `requires_` --- This indicates a command that is required to be installed to be run. +/// For example, `requires_rustfmt` means the test will only run if the executable `rustfmt` is installed. +/// These tests are *always* run on CI. +/// This is mainly used to avoid requiring contributors from having every dependency installed. +/// * `build_std_real` --- This is a "real" `-Zbuild-std` test (in the `build_std` integration test). +/// This only runs on nightly, and only if the environment variable `CARGO_RUN_BUILD_STD_TESTS` is set (these tests on run on Linux). +/// * `build_std_mock` --- This is a "mock" `-Zbuild-std` test (which uses a mock standard library). +/// This only runs on nightly, and is disabled for windows-gnu. +/// * `public_network_test` --- This tests contacts the public internet. +/// These tests are disabled unless the `CARGO_PUBLIC_NETWORK_TESTS` environment variable is set. +/// Use of this should be *extremely rare*, please avoid using it if possible. +/// The hosts it contacts should have a relatively high confidence that they are reliable and stable (such as github.com), especially in CI. +/// The tests should be carefully considered for developer security and privacy as well. +/// * `container_test` --- This indicates that it is a test that uses Docker. +/// These tests are disabled unless the `CARGO_CONTAINER_TESTS` environment variable is set. +/// This requires that you have Docker installed. +/// The SSH tests also assume that you have OpenSSH installed. +/// These should work on Linux, macOS, and Windows where possible. +/// Unfortunately these tests are not run in CI for macOS or Windows (no Docker on macOS, and Windows does not support Linux images). +/// See [`crates/cargo-test-support/src/containers.rs`](https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/containers.rs) for more on writing these tests. +/// * `ignore_windows="reason"` --- Indicates that the test should be ignored on windows for the given reason. #[proc_macro_attribute] pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream { // Ideally these options would be embedded in the test itself. However, I diff --git a/src/doc/contrib/src/tests/writing.md b/src/doc/contrib/src/tests/writing.md index c7cacbd6fbc..be92184af84 100644 --- a/src/doc/contrib/src/tests/writing.md +++ b/src/doc/contrib/src/tests/writing.md @@ -50,7 +50,9 @@ fn () { } ``` -The [`#[cargo_test]` attribute](#cargo_test-attribute) is used in place of `#[test]` to inject some setup code. +The [`#[cargo_test]` attribute][cargo_test attribute] is used in place of +`#[test]` to inject some setup code and declare requirements for running the +test. [`ProjectBuilder`] via `project()`: - Each project is in a separate directory in the sandbox @@ -62,50 +64,6 @@ The [`#[cargo_test]` attribute](#cargo_test-attribute) is used in place of `#[te - See [`support::compare`] for an explanation of the string pattern matching. Patterns are used to make it easier to match against the expected output. -#### `#[cargo_test]` attribute - -The `#[cargo_test]` attribute injects code which does some setup before starting the test. -It will create a filesystem "sandbox" under the "cargo integration test" directory for each test, such as `/path/to/cargo/target/tmp/cit/t123/`. -The sandbox will contain a `home` directory that will be used instead of your normal home directory. - -The `#[cargo_test]` attribute takes several options that will affect how the test is generated. -They are listed in parentheses separated with commas, such as: - -```rust,ignore -#[cargo_test(nightly, reason = "-Zfoo is unstable")] -``` - -The options it supports are: - -* `nightly` --- This will cause the test to be ignored if not running on the nightly toolchain. - This is useful for tests that use unstable options in `rustc` or `rustdoc`. - These tests are run in Cargo's CI, but are disabled in rust-lang/rust's CI due to the difficulty of updating both repos simultaneously. - A `reason` field is required to explain why it is nightly-only. -* `build_std_real` --- This is a "real" `-Zbuild-std` test (in the `build_std` integration test). - This only runs on nightly, and only if the environment variable `CARGO_RUN_BUILD_STD_TESTS` is set (these tests on run on Linux). -* `build_std_mock` --- This is a "mock" `-Zbuild-std` test (which uses a mock standard library). - This only runs on nightly, and is disabled for windows-gnu. -* `requires_` --- This indicates a command that is required to be installed to be run. - For example, `requires_rustfmt` means the test will only run if the executable `rustfmt` is installed. - These tests are *always* run on CI. - This is mainly used to avoid requiring contributors from having every dependency installed. -* `>=1.64` --- This indicates that the test will only run with the given version of `rustc` or newer. - This can be used when a new `rustc` feature has been stabilized that the test depends on. - If this is specified, a `reason` is required to explain why it is being checked. -* `public_network_test` --- This tests contacts the public internet. - These tests are disabled unless the `CARGO_PUBLIC_NETWORK_TESTS` environment variable is set. - Use of this should be *extremely rare*, please avoid using it if possible. - The hosts it contacts should have a relatively high confidence that they are reliable and stable (such as github.com), especially in CI. - The tests should be carefully considered for developer security and privacy as well. -* `container_test` --- This indicates that it is a test that uses Docker. - These tests are disabled unless the `CARGO_CONTAINER_TESTS` environment variable is set. - This requires that you have Docker installed. - The SSH tests also assume that you have OpenSSH installed. - These should work on Linux, macOS, and Windows where possible. - Unfortunately these tests are not run in CI for macOS or Windows (no Docker on macOS, and Windows does not support Linux images). - See [`crates/cargo-test-support/src/containers.rs`](https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/containers.rs) for more on writing these tests. -* `ignore_windows="reason"` --- Indicates that the test should be ignored on windows for the given reason. - #### Testing Nightly Features If you are testing a Cargo feature that only works on "nightly" Cargo, then @@ -328,6 +286,7 @@ environment. The general process is: 2. Set a breakpoint, for example: `b generate_root_units` 3. Run with arguments: `r check` +[cargo_test attribute]: https://doc.rust-lang.org/nightly/nightly-rustc/cargo_test_macro/attr.cargo_test.html [`testsuite`]: https://github.com/rust-lang/cargo/tree/master/tests/testsuite/ [`ProjectBuilder`]: https://github.com/rust-lang/cargo/blob/d847468768446168b596f721844193afaaf9d3f2/crates/cargo-test-support/src/lib.rs#L196-L202 [`Execs`]: https://github.com/rust-lang/cargo/blob/d847468768446168b596f721844193afaaf9d3f2/crates/cargo-test-support/src/lib.rs#L531-L550 From 83d1c14b4e3a6ee41a30760b45f0ae02d0e6757e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Jul 2024 08:58:42 -0500 Subject: [PATCH 04/28] docs(test): Point to docs, rather than source I pointed to the nightly docs because I figured that was the most likely one for users to be looking at. --- crates/cargo-test-macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cargo-test-macro/src/lib.rs b/crates/cargo-test-macro/src/lib.rs index 36f253c919d..2e62f6a32ff 100644 --- a/crates/cargo-test-macro/src/lib.rs +++ b/crates/cargo-test-macro/src/lib.rs @@ -55,7 +55,7 @@ use std::sync::Once; /// The SSH tests also assume that you have OpenSSH installed. /// These should work on Linux, macOS, and Windows where possible. /// Unfortunately these tests are not run in CI for macOS or Windows (no Docker on macOS, and Windows does not support Linux images). -/// See [`crates/cargo-test-support/src/containers.rs`](https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/containers.rs) for more on writing these tests. +/// See [`cargo-test-support::containers`](https://doc.rust-lang.org/nightly/nightly-rustc/cargo_test_support/containers) for more on writing these tests. /// * `ignore_windows="reason"` --- Indicates that the test should be ignored on windows for the given reason. #[proc_macro_attribute] pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream { From 8e524ae56165f75dd84482903eac5e6a2bef2abe Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:39:54 -0500 Subject: [PATCH 05/28] docs(test): Link to two different docs builds --- crates/cargo-test-support/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 6509c71d509..2b436f00870 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -2,7 +2,16 @@ //! //! See for a guide on writing tests. //! -//! WARNING: You might not want to use this outside of Cargo. +//! There are two places you can find API documentation +//! +//! - : +//! targeted at external tool developers testing cargo-related code +//! - Released with every rustc release +//! - : +//! targeted at cargo contributors +//! - Updated on each update of the `cargo` submodule in `rust-lang/rust` +//! +//! **WARNING:** You might not want to use this outside of Cargo. //! //! * This is designed for testing Cargo itself. Use at your own risk. //! * No guarantee on any stability across versions. From ad6abb54be124913bd27cfe5ebcb4fa90a902402 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:43:03 -0500 Subject: [PATCH 06/28] docs(test): Verify they work --- crates/cargo-test-support/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/cargo-test-support/Cargo.toml b/crates/cargo-test-support/Cargo.toml index f6e1ffa2d39..42e0d64658e 100644 --- a/crates/cargo-test-support/Cargo.toml +++ b/crates/cargo-test-support/Cargo.toml @@ -8,9 +8,6 @@ homepage.workspace = true repository.workspace = true description = "Testing framework for Cargo's testsuite." -[lib] -doctest = false - [dependencies] anstream.workspace = true anstyle.workspace = true From ca9fc47fe0ac44c6f2a41808802e03d207d6270e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:43:54 -0500 Subject: [PATCH 07/28] docs(test): Add high level example --- crates/cargo-test-support/src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 2b436f00870..b5b77617051 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -16,6 +16,30 @@ //! * This is designed for testing Cargo itself. Use at your own risk. //! * No guarantee on any stability across versions. //! * No feature request would be accepted unless proved useful for testing Cargo. +//! +//! # Example +//! +//! ```rust,no_run +//! use cargo_test_support::prelude::*; +//! use cargo_test_support::str; +//! use cargo_test_support::project; +//! +//! #[cargo_test] +//! fn some_test() { +//! let p = project() +//! .file("src/main.rs", r#"fn main() { println!("hi!"); }"#) +//! .build(); +//! +//! p.cargo("run --bin foo") +//! .with_stderr_data(str![[r#" +//! [COMPILING] foo [..] +//! [FINISHED] [..] +//! [RUNNING] `target/debug/foo` +//! "#]]) +//! .with_stdout_data(str![["hi!"]]) +//! .run(); +//! } +//! ``` #![allow(clippy::disallowed_methods)] #![allow(clippy::print_stderr)] From 86945a211ee35acb8da3b558274d9aed9457c85e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:41:44 -0500 Subject: [PATCH 08/28] docs(test): Document t! --- crates/cargo-test-support/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index b5b77617051..28e8feff3da 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -63,6 +63,14 @@ use url::Url; use self::paths::CargoPathExt; +/// Unwrap a `Result` with a useful panic message +/// +/// # Example +/// +/// ```rust +/// use cargo_test_support::t; +/// t!(std::fs::read_to_string("Cargo.toml")); +/// ``` #[macro_export] macro_rules! t { ($e:expr) => { From 504d377c8e3157ce53e7170f8ed825831cc0f8a9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:50:37 -0500 Subject: [PATCH 09/28] docs(test): Document cargo_process --- crates/cargo-test-support/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 28e8feff3da..7e2df8f099d 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -1469,11 +1469,12 @@ impl ArgLineCommandExt for snapbox::cmd::Command { } } -pub fn cargo_process(s: &str) -> Execs { +/// Run `cargo $arg_line`, see [`Execs`] +pub fn cargo_process(arg_line: &str) -> Execs { let cargo = cargo_exe(); let mut p = process(&cargo); p.env("CARGO", cargo); - p.arg_line(s); + p.arg_line(arg_line); execs().with_process_builder(p) } From fa0e66e7387adee1bc4f6f0f0455c4187aa72a25 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:51:57 -0500 Subject: [PATCH 10/28] docs(test): Document git_process --- crates/cargo-test-support/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 7e2df8f099d..a17e95e9899 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -1478,9 +1478,10 @@ pub fn cargo_process(arg_line: &str) -> Execs { execs().with_process_builder(p) } -pub fn git_process(s: &str) -> ProcessBuilder { +/// Run `git $arg_line`, see [`ProcessBuilder`] +pub fn git_process(arg_line: &str) -> ProcessBuilder { let mut p = process("git"); - p.arg_line(s); + p.arg_line(arg_line); p } From 7762d1f98c95b82b2ee68fd7770fad85ac5c2431 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:54:56 -0500 Subject: [PATCH 11/28] docs(test): Document cargo_exe --- crates/cargo-test-support/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index a17e95e9899..42bc8708a13 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -554,6 +554,7 @@ pub fn main_file(println: &str, deps: &[&str]) -> String { buf } +/// Path to the cargo binary pub fn cargo_exe() -> PathBuf { snapbox::cmd::cargo_bin("cargo") } From f42ae4c9c156ce77cda6f67dac76e7be241e591a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 16:58:36 -0500 Subject: [PATCH 12/28] docs(test): Pull RawOutput from API Its not used anywhere --- crates/cargo-test-support/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 42bc8708a13..d1412f7313b 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -566,10 +566,12 @@ pub fn cargo_exe() -> PathBuf { /// does not have access to the raw `ExitStatus` because `ProcessError` needs /// to be serializable (for the Rustc cache), and `ExitStatus` does not /// provide a constructor. -pub struct RawOutput { - pub code: Option, - pub stdout: Vec, - pub stderr: Vec, +struct RawOutput { + #[allow(dead_code)] + code: Option, + stdout: Vec, + #[allow(dead_code)] + stderr: Vec, } #[must_use] From 4674f2b84d2897c389672ce52c3ab5d6f913338c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Jul 2024 17:03:55 -0500 Subject: [PATCH 13/28] docs(test): Organize docs for Execs --- crates/cargo-test-support/src/lib.rs | 37 +++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index d1412f7313b..1bea2cc38b2 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -604,7 +604,10 @@ impl Execs { self.process_builder = Some(p); self } +} +/// # Configure assertions +impl Execs { /// Verifies that stdout is equal to the given lines. /// See [`compare`] for supported patterns. #[deprecated(note = "replaced with `Execs::with_stdout_data(expected)`")] @@ -834,7 +837,10 @@ impl Execs { } self } +} +/// # Configure the process +impl Execs { /// Forward subordinate process stdout/stderr to the terminal. /// Useful for printf debugging of the tests. /// CAUTION: CI will fail if you leave this in your test! @@ -888,20 +894,6 @@ impl Execs { self } - pub fn exec_with_output(&mut self) -> Result { - self.ran = true; - // TODO avoid unwrap - let p = (&self.process_builder).clone().unwrap(); - p.exec_with_output() - } - - pub fn build_command(&mut self) -> Command { - self.ran = true; - // TODO avoid unwrap - let p = (&self.process_builder).clone().unwrap(); - p.build_command() - } - /// Enables nightly features for testing /// /// The list of reasons should be why nightly cargo is needed. If it is @@ -950,6 +942,23 @@ impl Execs { } self } +} + +/// # Run and verify the process +impl Execs { + pub fn exec_with_output(&mut self) -> Result { + self.ran = true; + // TODO avoid unwrap + let p = (&self.process_builder).clone().unwrap(); + p.exec_with_output() + } + + pub fn build_command(&mut self) -> Command { + self.ran = true; + // TODO avoid unwrap + let p = (&self.process_builder).clone().unwrap(); + p.build_command() + } #[track_caller] pub fn run(&mut self) { From 1fe8ae6c3e11a3d2dfb9afa08a4b63d9ad560cd2 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 16 Jul 2024 10:19:27 -0500 Subject: [PATCH 14/28] docs(test): Document Execs I'm intentionally being light as I want to pull in a lot of "lessons learned" from the port to snapbox into the docs and want that as a dedicated PR to make it easier for the contributors to that effort to review it. --- crates/cargo-test-support/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 1bea2cc38b2..ebbf42843e4 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -574,6 +574,12 @@ struct RawOutput { stderr: Vec, } +/// Run and verify a [`ProcessBuilder`] +/// +/// Construct with +/// - [`execs`] +/// - [`cargo_process`] +/// - [`Project`] methods #[must_use] #[derive(Clone)] pub struct Execs { @@ -1166,6 +1172,7 @@ impl Drop for Execs { } } +/// Run and verify a process, see [`Execs`] pub fn execs() -> Execs { Execs { ran: false, From b4b56d4d65b7f4f737c8f4d947c56deca3a5843c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 14:22:09 -0500 Subject: [PATCH 15/28] docs(test): Document basic_*manifest functions --- crates/cargo-test-support/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index ebbf42843e4..9d01fdf3225 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -1198,6 +1198,7 @@ pub fn execs() -> Execs { } } +/// Generate a basic `Cargo.toml` pub fn basic_manifest(name: &str, version: &str) -> String { format!( r#" @@ -1211,6 +1212,7 @@ pub fn basic_manifest(name: &str, version: &str) -> String { ) } +/// Generate a `Cargo.toml` with the specified `bin.name` pub fn basic_bin_manifest(name: &str) -> String { format!( r#" @@ -1229,6 +1231,7 @@ pub fn basic_bin_manifest(name: &str) -> String { ) } +/// Generate a `Cargo.toml` with the specified `lib.name` pub fn basic_lib_manifest(name: &str) -> String { format!( r#" From 170756601c957c78b345ab9113903b4be2f41774 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 14:24:53 -0500 Subject: [PATCH 16/28] docs(test): Document 'process' --- crates/cargo-test-support/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 9d01fdf3225..f710ada17b0 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -1300,8 +1300,13 @@ pub fn is_nightly() -> bool { && (vv.contains("-nightly") || vv.contains("-dev")) } -pub fn process>(t: T) -> ProcessBuilder { - _process(t.as_ref()) +/// Run `$bin` in the test's environment, see [`ProcessBuilder`] +/// +/// For more on the test environment, see +/// - [`paths::root`] +/// - [`TestEnvCommandExt`] +pub fn process>(bin: T) -> ProcessBuilder { + _process(bin.as_ref()) } fn _process(t: &OsStr) -> ProcessBuilder { From c9dddd27c866a030a55ca7e0b7c7db47650e6662 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 14:33:12 -0500 Subject: [PATCH 17/28] docs(test): Document main_file function --- crates/cargo-test-support/src/lib.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index f710ada17b0..51c808213c0 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -540,10 +540,24 @@ pub fn project_in_home(name: &str) -> ProjectBuilder { // === Helpers === -pub fn main_file(println: &str, deps: &[&str]) -> String { +/// Generate a `main.rs` printing the specified text +/// +/// ```rust +/// # use cargo_test_support::main_file; +/// # mod dep { +/// # fn bar() -> &'static str { +/// # "world" +/// # } +/// # } +/// main_file( +/// r#""hello {}", dep::bar()"#, +/// &[] +/// ); +/// ``` +pub fn main_file(println: &str, externed_deps: &[&str]) -> String { let mut buf = String::new(); - for dep in deps.iter() { + for dep in externed_deps.iter() { buf.push_str(&format!("extern crate {};\n", dep)); } From e53f2aff5560ad3d6ad65fa32c7e10de448ec3ed Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 14:49:50 -0500 Subject: [PATCH 18/28] docs(test): Document panic_error function --- crates/cargo-test-support/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 51c808213c0..929f86f005c 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -86,6 +86,7 @@ pub use snapbox::file; pub use snapbox::str; pub use snapbox::utils::current_dir; +/// `panic!`, reporting the specified error , see also [`t!`] #[track_caller] pub fn panic_error(what: &str, err: impl Into) -> ! { let err = err.into(); From 3dff0ec945a44fd10f2875f1589c133f27e546db Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 16:37:18 -0500 Subject: [PATCH 19/28] docs(test): Document project* functions --- crates/cargo-test-support/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 929f86f005c..51feea40892 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -524,17 +524,17 @@ impl Project { } } -// Generates a project layout +/// Generates a project layout, see [`ProjectBuilder`] pub fn project() -> ProjectBuilder { ProjectBuilder::new(paths::root().join("foo")) } -// Generates a project layout in given directory +/// Generates a project layout in given directory, see [`ProjectBuilder`] pub fn project_in(dir: &str) -> ProjectBuilder { ProjectBuilder::new(paths::root().join(dir).join("foo")) } -// Generates a project layout inside our fake home dir +/// Generates a project layout inside our fake home dir, see [`ProjectBuilder`] pub fn project_in_home(name: &str) -> ProjectBuilder { ProjectBuilder::new(paths::home().join(name)) } From 45f61ccfa8c3166a5afff219d18757d37efe5200 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 20:20:17 -0500 Subject: [PATCH 20/28] docs(test): Expand docs for Project --- crates/cargo-test-support/src/lib.rs | 74 +++++++++++++++++++++------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 51feea40892..a89234b7411 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -243,12 +243,16 @@ pub struct ProjectBuilder { } impl ProjectBuilder { - /// Root of the project, ex: `/path/to/cargo/target/cit/t0/foo` + /// Root of the project + /// + /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo` pub fn root(&self) -> PathBuf { self.root.root() } - /// Project's debug dir, ex: `/path/to/cargo/target/cit/t0/foo/target/debug` + /// Project's debug dir + /// + /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug` pub fn target_debug_dir(&self) -> PathBuf { self.root.target_debug_dir() } @@ -366,30 +370,40 @@ impl Project { Self { root: project_root } } - /// Root of the project, ex: `/path/to/cargo/target/cit/t0/foo` + /// Root of the project + /// + /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo` pub fn root(&self) -> PathBuf { self.root.clone() } - /// Project's target dir, ex: `/path/to/cargo/target/cit/t0/foo/target` + /// Project's target dir + /// + /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target` pub fn build_dir(&self) -> PathBuf { self.root().join("target") } - /// Project's debug dir, ex: `/path/to/cargo/target/cit/t0/foo/target/debug` + /// Project's debug dir + /// + /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug` pub fn target_debug_dir(&self) -> PathBuf { self.build_dir().join("debug") } - /// File url for root, ex: `file:///path/to/cargo/target/cit/t0/foo` + /// File url for root + /// + /// ex: `file://$CARGO_TARGET_TMPDIR/cit/t0/foo` pub fn url(&self) -> Url { use paths::CargoPathExt; self.root().to_url() } /// Path to an example built as a library. + /// /// `kind` should be one of: "lib", "rlib", "staticlib", "dylib", "proc-macro" - /// ex: `/path/to/cargo/target/cit/t0/foo/target/debug/examples/libex.rlib` + /// + /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug/examples/libex.rlib` pub fn example_lib(&self, name: &str, kind: &str) -> PathBuf { self.target_debug_dir() .join("examples") @@ -397,7 +411,8 @@ impl Project { } /// Path to a debug binary. - /// ex: `/path/to/cargo/target/cit/t0/foo/target/debug/foo` + /// + /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug/foo` pub fn bin(&self, b: &str) -> PathBuf { self.build_dir() .join("debug") @@ -405,7 +420,8 @@ impl Project { } /// Path to a release binary. - /// ex: `/path/to/cargo/target/cit/t0/foo/target/release/foo` + /// + /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/release/foo` pub fn release_bin(&self, b: &str) -> PathBuf { self.build_dir() .join("release") @@ -413,7 +429,8 @@ impl Project { } /// Path to a debug binary for a specific target triple. - /// ex: `/path/to/cargo/target/cit/t0/foo/target/i686-apple-darwin/debug/foo` + /// + /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/i686-apple-darwin/debug/foo` pub fn target_bin(&self, target: &str, b: &str) -> PathBuf { self.build_dir().join(target).join("debug").join(&format!( "{}{}", @@ -422,25 +439,36 @@ impl Project { )) } - /// Returns an iterator of paths matching the glob pattern, which is - /// relative to the project root. + /// Returns an iterator of paths within [`Project::root`] matching the glob pattern pub fn glob>(&self, pattern: P) -> glob::Paths { let pattern = self.root().join(pattern); glob::glob(pattern.to_str().expect("failed to convert pattern to str")) .expect("failed to glob") } - /// Changes the contents of an existing file. + /// Overwrite a file with new content + /// + // # Example: + /// + /// ```no_run + /// # let p = cargo_test_support::project().build(); + /// p.change_file("src/lib.rs", "fn new_fn() {}"); + /// ``` pub fn change_file(&self, path: &str, body: &str) { FileBuilder::new(self.root().join(path), body, false).mk() } /// Creates a `ProcessBuilder` to run a program in the project /// and wrap it in an Execs to assert on the execution. - /// Example: - /// p.process(&p.bin("foo")) - /// .with_stdout("bar\n") - /// .run(); + /// + /// # Example: + /// + /// ```no_run + /// # let p = cargo_test_support::project().build(); + /// p.process(&p.bin("foo")) + /// .with_stdout("bar\n") + /// .run(); + /// ``` pub fn process>(&self, program: T) -> Execs { let mut p = process(program); p.cwd(self.root()); @@ -448,9 +476,17 @@ impl Project { } /// Creates a `ProcessBuilder` to run cargo. + /// /// Arguments can be separated by spaces. - /// Example: - /// p.cargo("build --bin foo").run(); + /// + /// For `cargo run`, see [`Project::rename_run`]. + /// + /// # Example: + /// + /// ```no_run + /// # let p = cargo_test_support::project().build(); + /// p.cargo("build --bin foo").run(); + /// ``` pub fn cargo(&self, cmd: &str) -> Execs { let cargo = cargo_exe(); let mut execs = self.process(&cargo); From cb528403fa1e68914a5fa14a9c6753156c43a948 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 20:26:53 -0500 Subject: [PATCH 21/28] docs(test): Expand docs for ProjectBuilder --- crates/cargo-test-support/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index a89234b7411..a9cafdd0b7a 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -233,7 +233,13 @@ pub struct Project { /// Create a project to run tests against /// -/// The project can be constructed programmatically or from the filesystem with [`Project::from_template`] +/// - Creates a [`basic_manifest`] if one isn't supplied +/// +/// To get started, see: +/// - [`project`] +/// - [`project_in`] +/// - [`project_in_home`] +/// - [`Project::from_template`] #[must_use] pub struct ProjectBuilder { root: Project, @@ -257,6 +263,7 @@ impl ProjectBuilder { self.root.target_debug_dir() } + /// Create project in `root` pub fn new(root: PathBuf) -> ProjectBuilder { ProjectBuilder { root: Project { root }, @@ -266,6 +273,7 @@ impl ProjectBuilder { } } + /// Create project, relative to [`paths::root`] pub fn at>(mut self, path: P) -> Self { self.root = Project { root: paths::root().join(path), From 8f88a8af310075d68a4d1454709fb3c67f6e8dc9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 20:36:43 -0500 Subject: [PATCH 22/28] docs(test): Expand 'compare' documentation --- crates/cargo-test-support/src/compare.rs | 34 ++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/cargo-test-support/src/compare.rs b/crates/cargo-test-support/src/compare.rs index dae405a1c9e..e822bb3822c 100644 --- a/crates/cargo-test-support/src/compare.rs +++ b/crates/cargo-test-support/src/compare.rs @@ -1,6 +1,11 @@ //! Routines for comparing and diffing output. //! -//! # Patterns +//! # Deprecated comparisons +//! +//! Cargo's tests are in transition from internal-only pattern and normalization routines used in +//! asserts like [`crate::Execs::with_stdout`] to [`assert_e2e`] and [`assert_ui`]. +//! +//! ## Patterns //! //! Many of these functions support special markup to assist with comparing //! text that may vary or is otherwise uninteresting for the test at hand. The @@ -22,7 +27,7 @@ //! can use this to avoid duplicating the `with_stderr` call like: //! `if cfg!(target_env = "msvc") {e.with_stderr("...[DIRTY]...");} else {e.with_stderr("...");}`. //! -//! # Normalization +//! ## Normalization //! //! In addition to the patterns described above, the strings are normalized //! in such a way to avoid unwanted differences. The normalizations are: @@ -86,6 +91,19 @@ macro_rules! regex { /// Other heuristics are applied to try to ensure Windows-style paths aren't /// a problem. /// - Carriage returns are removed, which can help when running on Windows. +/// +/// # Example +/// +/// ```no_run +/// # use cargo_test_support::compare::assert_e2e; +/// # use cargo_test_support::file; +/// # let p = cargo_test_support::project().build(); +/// # let stdout = ""; +/// assert_e2e().eq(stdout, file!["stderr.term.svg"]); +/// ``` +/// ```console +/// $ SNAPSHOTS=overwrite cargo test +/// ``` pub fn assert_ui() -> snapbox::Assert { let mut subs = snapbox::Redactions::new(); subs.extend(MIN_LITERAL_REDACTIONS.into_iter().cloned()) @@ -129,6 +147,18 @@ pub fn assert_ui() -> snapbox::Assert { /// Other heuristics are applied to try to ensure Windows-style paths aren't /// a problem. /// - Carriage returns are removed, which can help when running on Windows. +/// +/// # Example +/// +/// ```no_run +/// # use cargo_test_support::compare::assert_e2e; +/// # use cargo_test_support::str; +/// # let p = cargo_test_support::project().build(); +/// assert_e2e().eq(p.read_lockfile(), str![]); +/// ``` +/// ```console +/// $ SNAPSHOTS=overwrite cargo test +/// ``` pub fn assert_e2e() -> snapbox::Assert { let mut subs = snapbox::Redactions::new(); subs.extend(MIN_LITERAL_REDACTIONS.into_iter().cloned()) From 97ccb653b0284e9c0591ba23dc3dc7cd74f7e33b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 20:39:18 -0500 Subject: [PATCH 23/28] docs(test): Expose `git` documentation --- crates/cargo-test-support/src/git.rs | 78 ++++++++++++++-------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/crates/cargo-test-support/src/git.rs b/crates/cargo-test-support/src/git.rs index 9863c5045ba..e0b0c7a477a 100644 --- a/crates/cargo-test-support/src/git.rs +++ b/crates/cargo-test-support/src/git.rs @@ -1,42 +1,42 @@ -/* -# Git Testing Support - -## Creating a git dependency -`git::new()` is an easy way to create a new git repository containing a -project that you can then use as a dependency. It will automatically add all -the files you specify in the project and commit them to the repository. -Example: - -``` -let git_project = git::new("dep1", |project| { - project - .file("Cargo.toml", &basic_manifest("dep1", "1.0.0")) - .file("src/lib.rs", r#"pub fn f() { println!("hi!"); } "#) -}); - -// Use the `url()` method to get the file url to the new repository. -let p = project() - .file("Cargo.toml", &format!(r#" - [package] - name = "a" - version = "1.0.0" - - [dependencies] - dep1 = {{ git = '{}' }} - "#, git_project.url())) - .file("src/lib.rs", "extern crate dep1;") - .build(); -``` - -## Manually creating repositories -`git::repo()` can be used to create a `RepoBuilder` which provides a way of -adding files to a blank repository and committing them. - -If you want to then manipulate the repository (such as adding new files or -tags), you can use `git2::Repository::open()` to open the repository and then -use some of the helper functions in this file to interact with the repository. - -*/ +//! # Git Testing Support +//! +//! ## Creating a git dependency +//! `git::new()` is an easy way to create a new git repository containing a +//! project that you can then use as a dependency. It will automatically add all +//! the files you specify in the project and commit them to the repository. +//! Example: +//! +//! ```no_run +//! # use cargo_test_support::project; +//! # use cargo_test_support::basic_manifest; +//! # use cargo_test_support::git; +//! let git_project = git::new("dep1", |project| { +//! project +//! .file("Cargo.toml", &basic_manifest("dep1", "1.0.0")) +//! .file("src/lib.rs", r#"pub fn f() { println!("hi!"); } "#) +//! }); +//! +//! // Use the `url()` method to get the file url to the new repository. +//! let p = project() +//! .file("Cargo.toml", &format!(r#" +//! [package] +//! name = "a" +//! version = "1.0.0" +//! +//! [dependencies] +//! dep1 = {{ git = '{}' }} +//! "#, git_project.url())) +//! .file("src/lib.rs", "extern crate dep1;") +//! .build(); +//! ``` +//! +//! ## Manually creating repositories +//! `git::repo()` can be used to create a `RepoBuilder` which provides a way of +//! adding files to a blank repository and committing them. +//! +//! If you want to then manipulate the repository (such as adding new files or +//! tags), you can use `git2::Repository::open()` to open the repository and then +//! use some of the helper functions in this file to interact with the repository. use crate::{paths::CargoPathExt, project, Project, ProjectBuilder, SymlinkBuilder}; use std::fs; From 64a62ff8fe9fc826a8337a8e38b1886efbf53c99 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 20:48:10 -0500 Subject: [PATCH 24/28] docs(test): Expand 'git' documentation --- crates/cargo-test-support/src/git.rs | 31 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/crates/cargo-test-support/src/git.rs b/crates/cargo-test-support/src/git.rs index e0b0c7a477a..803e4dafd49 100644 --- a/crates/cargo-test-support/src/git.rs +++ b/crates/cargo-test-support/src/git.rs @@ -1,10 +1,11 @@ //! # Git Testing Support //! //! ## Creating a git dependency -//! `git::new()` is an easy way to create a new git repository containing a +//! [`new()`] is an easy way to create a new git repository containing a //! project that you can then use as a dependency. It will automatically add all //! the files you specify in the project and commit them to the repository. -//! Example: +//! +//! ### Example: //! //! ```no_run //! # use cargo_test_support::project; @@ -31,7 +32,8 @@ //! ``` //! //! ## Manually creating repositories -//! `git::repo()` can be used to create a `RepoBuilder` which provides a way of +//! +//! [`repo()`] can be used to create a [`RepoBuilder`] which provides a way of //! adding files to a blank repository and committing them. //! //! If you want to then manipulate the repository (such as adding new files or @@ -44,17 +46,21 @@ use std::path::{Path, PathBuf}; use std::sync::Once; use url::Url; +/// Manually construct a [`Repository`] +/// +/// See also [`new`], [`repo`] #[must_use] pub struct RepoBuilder { repo: git2::Repository, files: Vec, } +/// See [`new`] pub struct Repository(git2::Repository); -/// Create a `RepoBuilder` to build a new git repository. +/// Create a [`RepoBuilder`] to build a new git repository. /// -/// Call `build()` to finalize and create the repository. +/// Call [`RepoBuilder::build()`] to finalize and create the repository. pub fn repo(p: &Path) -> RepoBuilder { RepoBuilder::init(p) } @@ -130,7 +136,7 @@ impl Repository { } } -/// Initialize a new repository at the given path. +/// *(`git2`)* Initialize a new repository at the given path. pub fn init(path: &Path) -> git2::Repository { default_search_path(); let repo = t!(git2::Repository::init(path)); @@ -158,7 +164,7 @@ fn default_repo_cfg(repo: &git2::Repository) { t!(cfg.set_str("user.name", "Foo Bar")); } -/// Create a new git repository with a project. +/// Create a new [`Project`] in a git [`Repository`] pub fn new(name: &str, callback: F) -> Project where F: FnOnce(ProjectBuilder) -> ProjectBuilder, @@ -166,8 +172,7 @@ where new_repo(name, callback).0 } -/// Create a new git repository with a project. -/// Returns both the Project and the git Repository. +/// Create a new [`Project`] with access to the [`Repository`] pub fn new_repo(name: &str, callback: F) -> (Project, git2::Repository) where F: FnOnce(ProjectBuilder) -> ProjectBuilder, @@ -182,14 +187,14 @@ where (git_project, repo) } -/// Add all files in the working directory to the git index. +/// *(`git2`)* Add all files in the working directory to the git index pub fn add(repo: &git2::Repository) { let mut index = t!(repo.index()); t!(index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)); t!(index.write()); } -/// Add a git submodule to the repository. +/// *(`git2`)* Add a git submodule to the repository pub fn add_submodule<'a>( repo: &'a git2::Repository, url: &str, @@ -207,7 +212,7 @@ pub fn add_submodule<'a>( s } -/// Commit changes to the git repository. +/// *(`git2`)* Commit changes to the git repository pub fn commit(repo: &git2::Repository) -> git2::Oid { let tree_id = t!(t!(repo.index()).write_tree()); let sig = t!(repo.signature()); @@ -226,7 +231,7 @@ pub fn commit(repo: &git2::Repository) -> git2::Oid { )) } -/// Create a new tag in the git repository. +/// *(`git2`)* Create a new tag in the git repository pub fn tag(repo: &git2::Repository, name: &str) { let head = repo.head().unwrap().target().unwrap(); t!(repo.tag( From 062652c856a16bf351af2f0de08c3d9b20544ef6 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jul 2024 20:50:36 -0500 Subject: [PATCH 25/28] docs(test): Expand 'install' documentation --- crates/cargo-test-support/src/install.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/cargo-test-support/src/install.rs b/crates/cargo-test-support/src/install.rs index d71bdcfe9db..3a8503d75d8 100644 --- a/crates/cargo-test-support/src/install.rs +++ b/crates/cargo-test-support/src/install.rs @@ -1,3 +1,5 @@ +//! Helpers for testing `cargo install` + use std::env::consts::EXE_SUFFIX; use std::path::Path; @@ -23,6 +25,7 @@ fn check_has_installed_exe>(path: P, name: &'static str) -> bool path.as_ref().join("bin").join(exe(name)).is_file() } +/// `$name$EXE` pub fn exe(name: &str) -> String { format!("{}{}", name, EXE_SUFFIX) } From 6d0eea0e33272aee3b639b24d4fe7cc430824483 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jul 2024 12:19:42 -0500 Subject: [PATCH 26/28] refactor(test): Document 'paths' mod I considered hiding `init_root` as an implementation detail of `#[cargo_test]` but decided to be conservative about that for now. --- crates/cargo-test-support/src/paths.rs | 41 ++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/crates/cargo-test-support/src/paths.rs b/crates/cargo-test-support/src/paths.rs index 8328992fb62..e9d51502aad 100644 --- a/crates/cargo-test-support/src/paths.rs +++ b/crates/cargo-test-support/src/paths.rs @@ -1,3 +1,5 @@ +//! Access common paths and manipulate the filesystem + use filetime::FileTime; use std::cell::RefCell; @@ -41,6 +43,9 @@ fn set_global_root(tmp_dir: Option<&'static str>) { } } +/// Path to the parent directory of all test [`root`]s +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit` pub fn global_root() -> PathBuf { let lock = GLOBAL_ROOT .get_or_init(|| Default::default()) @@ -64,10 +69,12 @@ thread_local! { static TEST_ID: RefCell> = RefCell::new(None); } +/// See [`init_root`] pub struct TestIdGuard { _private: (), } +/// For test harnesses like [`crate::cargo_test`] pub fn init_root(tmp_dir: Option<&'static str>) -> TestIdGuard { static NEXT_ID: AtomicUsize = AtomicUsize::new(0); @@ -90,6 +97,9 @@ impl Drop for TestIdGuard { } } +/// Path to the test's filesystem scratchpad +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit/t0` pub fn root() -> PathBuf { let id = TEST_ID.with(|n| { n.borrow().expect( @@ -103,6 +113,9 @@ pub fn root() -> PathBuf { root } +/// Path to the current test's `$HOME` +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/home` pub fn home() -> PathBuf { let mut path = root(); path.push("home"); @@ -110,10 +123,14 @@ pub fn home() -> PathBuf { path } +/// Path to the current test's `$CARGO_HOME` +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/home/.cargo` pub fn cargo_home() -> PathBuf { home().join(".cargo") } +/// Common path and file operations pub trait CargoPathExt { fn to_url(&self) -> url::Url; @@ -275,18 +292,29 @@ where /// Get the filename for a library. /// -/// `kind` should be one of: "lib", "rlib", "staticlib", "dylib", "proc-macro" +/// `kind` should be one of: +/// - `lib` +/// - `rlib` +/// - `staticlib` +/// - `dylib` +/// - `proc-macro` /// -/// For example, dynamic library named "foo" would return: -/// - macOS: "libfoo.dylib" -/// - Windows: "foo.dll" -/// - Unix: "libfoo.so" +/// # Examples +/// ``` +/// # use cargo_test_support::paths::get_lib_filename; +/// get_lib_filename("foo", "dylib"); +/// ``` +/// would return: +/// - macOS: `"libfoo.dylib"` +/// - Windows: `"foo.dll"` +/// - Unix: `"libfoo.so"` pub fn get_lib_filename(name: &str, kind: &str) -> String { let prefix = get_lib_prefix(kind); let extension = get_lib_extension(kind); format!("{}{}.{}", prefix, name, extension) } +/// See [`get_lib_filename`] for more details pub fn get_lib_prefix(kind: &str) -> &str { match kind { "lib" | "rlib" => "lib", @@ -301,6 +329,7 @@ pub fn get_lib_prefix(kind: &str) -> &str { } } +/// See [`get_lib_filename`] for more details pub fn get_lib_extension(kind: &str) -> &str { match kind { "lib" | "rlib" => "rlib", @@ -324,7 +353,7 @@ pub fn get_lib_extension(kind: &str) -> &str { } } -/// Returns the sysroot as queried from rustc. +/// Path to `rustc`s sysroot pub fn sysroot() -> String { let output = Command::new("rustc") .arg("--print=sysroot") From b931d98b907b56618c124cf3081427028e603c12 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jul 2024 12:23:46 -0500 Subject: [PATCH 27/28] refactor(test): Document 'publish' mod --- crates/cargo-test-support/src/publish.rs | 65 ++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/crates/cargo-test-support/src/publish.rs b/crates/cargo-test-support/src/publish.rs index f850330c191..a673c466b94 100644 --- a/crates/cargo-test-support/src/publish.rs +++ b/crates/cargo-test-support/src/publish.rs @@ -1,3 +1,62 @@ +//! Helpers for testing `cargo package` / `cargo publish` +//! +//! # Example +//! +//! ```no_run +//! # use cargo_test_support::registry::RegistryBuilder; +//! # use cargo_test_support::publish::validate_upload; +//! # use cargo_test_support::project; +//! // This replaces `registry::init()` and must be called before `Package::new().publish()` +//! let registry = RegistryBuilder::new().http_api().http_index().build(); +//! +//! let p = project() +//! .file( +//! "Cargo.toml", +//! r#" +//! [package] +//! name = "foo" +//! version = "0.0.1" +//! edition = "2015" +//! authors = [] +//! license = "MIT" +//! description = "foo" +//! "#, +//! ) +//! .file("src/main.rs", "fn main() {}") +//! .build(); +//! +//! p.cargo("publish --no-verify") +//! .replace_crates_io(registry.index_url()) +//! .run(); +//! +//! validate_upload( +//! r#" +//! { +//! "authors": [], +//! "badges": {}, +//! "categories": [], +//! "deps": [], +//! "description": "foo", +//! "documentation": null, +//! "features": {}, +//! "homepage": null, +//! "keywords": [], +//! "license": "MIT", +//! "license_file": null, +//! "links": null, +//! "name": "foo", +//! "readme": null, +//! "readme_file": null, +//! "repository": null, +//! "rust_version": null, +//! "vers": "0.0.1" +//! } +//! "#, +//! "foo-0.0.1.crate", +//! &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], +//! ); +//! ``` + use crate::compare::{assert_match_exact, find_json_mismatch}; use crate::registry::{self, alt_api_path, FeatureMap}; use flate2::read::GzDecoder; @@ -17,7 +76,7 @@ where Ok(u32::from_le_bytes(buf)) } -/// Checks the result of a crate publish. +/// Check the `cargo publish` API call pub fn validate_upload(expected_json: &str, expected_crate_name: &str, expected_files: &[&str]) { let new_path = registry::api_path().join("api/v1/crates/new"); _validate_upload( @@ -29,7 +88,7 @@ pub fn validate_upload(expected_json: &str, expected_crate_name: &str, expected_ ); } -/// Checks the result of a crate publish, along with the contents of the files. +/// Check the `cargo publish` API call, with file contents pub fn validate_upload_with_contents( expected_json: &str, expected_crate_name: &str, @@ -46,7 +105,7 @@ pub fn validate_upload_with_contents( ); } -/// Checks the result of a crate publish to an alternative registry. +/// Check the `cargo publish` API call to the alternative test registry pub fn validate_alt_upload( expected_json: &str, expected_crate_name: &str, From 83cbca01419f5b0cedd3b95ad46ddba9f76a6e76 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jul 2024 13:56:37 -0500 Subject: [PATCH 28/28] refactor(test): Document 'registry' mod --- crates/cargo-test-support/src/registry.rs | 161 ++++++++++++++-------- 1 file changed, 102 insertions(+), 59 deletions(-) diff --git a/crates/cargo-test-support/src/registry.rs b/crates/cargo-test-support/src/registry.rs index b63ee44350e..b69e981c844 100644 --- a/crates/cargo-test-support/src/registry.rs +++ b/crates/cargo-test-support/src/registry.rs @@ -1,3 +1,46 @@ +//! Interact with the [`TestRegistry`] +//! +//! # Example +//! +//! ```no_run +//! use cargo_test_support::registry::Package; +//! use cargo_test_support::project; +//! +//! // Publish package "a" depending on "b". +//! Package::new("a", "1.0.0") +//! .dep("b", "1.0.0") +//! .file("src/lib.rs", r#" +//! extern crate b; +//! pub fn f() -> i32 { b::f() * 2 } +//! "#) +//! .publish(); +//! +//! // Publish package "b". +//! Package::new("b", "1.0.0") +//! .file("src/lib.rs", r#" +//! pub fn f() -> i32 { 12 } +//! "#) +//! .publish(); +//! +//! // Create a project that uses package "a". +//! let p = project() +//! .file("Cargo.toml", r#" +//! [package] +//! name = "foo" +//! version = "0.0.1" +//! +//! [dependencies] +//! a = "1.0" +//! "#) +//! .file("src/main.rs", r#" +//! extern crate a; +//! fn main() { println!("{}", a::f()); } +//! "#) +//! .build(); +//! +//! p.cargo("run").with_stdout("24").run(); +//! ``` + use crate::git::repo; use crate::paths; use crate::publish::{create_index_line, write_to_index}; @@ -20,39 +63,64 @@ use time::format_description::well_known::Rfc3339; use time::{Duration, OffsetDateTime}; use url::Url; -/// Gets the path to the local index pretending to be crates.io. This is a Git repo +/// Path to the local index for psuedo-crates.io. +/// +/// This is a Git repo /// initialized with a `config.json` file pointing to `dl_path` for downloads /// and `api_path` for uploads. +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/registry` pub fn registry_path() -> PathBuf { generate_path("registry") } -/// Gets the path for local web API uploads. Cargo will place the contents of a web API + +/// Path to the local web API uploads +/// +/// Cargo will place the contents of a web API /// request here. For example, `api/v1/crates/new` is the result of publishing a crate. +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/api` pub fn api_path() -> PathBuf { generate_path("api") } -/// Gets the path where crates can be downloaded using the web API endpoint. Crates + +/// Path to download `.crate` files using the web API endpoint. +/// +/// Crates /// should be organized as `{name}/{version}/download` to match the web API /// endpoint. This is rarely used and must be manually set up. -fn dl_path() -> PathBuf { +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/dl` +pub fn dl_path() -> PathBuf { generate_path("dl") } -/// Gets the alternative-registry version of `registry_path`. -fn alt_registry_path() -> PathBuf { + +/// Path to the alternative-registry version of [`registry_path`] +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/alternative-registry` +pub fn alt_registry_path() -> PathBuf { generate_path("alternative-registry") } -/// Gets the alternative-registry version of `registry_url`. + +/// URL to the alternative-registry version of `registry_url` fn alt_registry_url() -> Url { generate_url("alternative-registry") } -/// Gets the alternative-registry version of `dl_path`. + +/// Path to the alternative-registry version of [`dl_path`] +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/alternative-dl` pub fn alt_dl_path() -> PathBuf { generate_path("alternative-dl") } -/// Gets the alternative-registry version of `api_path`. + +/// Path to the alternative-registry version of [`api_path`] +/// +/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/alternative-api` pub fn alt_api_path() -> PathBuf { generate_path("alternative-api") } + fn generate_path(name: &str) -> PathBuf { paths::root().join(name) } @@ -60,6 +128,7 @@ fn generate_url(name: &str) -> Url { Url::from_file_path(generate_path(name)).ok().unwrap() } +/// Auth-token for publishing, see [`RegistryBuilder::token`] #[derive(Clone)] pub enum Token { Plaintext(String), @@ -68,6 +137,7 @@ pub enum Token { impl Token { /// This is a valid PASETO secret key. + /// /// This one is already publicly available as part of the text of the RFC so is safe to use for tests. pub fn rfc_key() -> Token { Token::Keys( @@ -80,7 +150,9 @@ impl Token { type RequestCallback = Box Response>; -/// A builder for initializing registries. +/// Prepare a local [`TestRegistry`] fixture +/// +/// See also [`init`] and [`alt_init`] pub struct RegistryBuilder { /// If set, configures an alternate registry with the given name. alternative: Option, @@ -108,6 +180,9 @@ pub struct RegistryBuilder { credential_provider: Option, } +/// A local registry fixture +/// +/// Most tests won't need to call this directly but instead interact with [`Package`] pub struct TestRegistry { server: Option, index_url: Url, @@ -459,71 +534,31 @@ impl RegistryBuilder { } } -/// A builder for creating a new package in a registry. +/// Published package builder for [`TestRegistry`] /// /// This uses "source replacement" using an automatically generated /// `.cargo/config` file to ensure that dependencies will use these packages /// instead of contacting crates.io. See `source-replacement.md` for more /// details on how source replacement works. /// -/// Call `publish` to finalize and create the package. +/// Call [`Package::publish`] to finalize and create the package. /// /// If no files are specified, an empty `lib.rs` file is automatically created. /// /// The `Cargo.toml` file is automatically generated based on the methods -/// called on `Package` (for example, calling `dep()` will add to the +/// called on `Package` (for example, calling [`Package::dep()`] will add to the /// `[dependencies]` automatically). You may also specify a `Cargo.toml` file /// to override the generated one. /// /// This supports different registry types: /// - Regular source replacement that replaces `crates.io` (the default). /// - A "local registry" which is a subset for vendoring (see -/// `Package::local`). +/// [`Package::local`]). /// - An "alternative registry" which requires specifying the registry name -/// (see `Package::alternative`). +/// (see [`Package::alternative`]). /// /// This does not support "directory sources". See `directory.rs` for /// `VendorPackage` which implements directory sources. -/// -/// # Example -/// ```no_run -/// use cargo_test_support::registry::Package; -/// use cargo_test_support::project; -/// -/// // Publish package "a" depending on "b". -/// Package::new("a", "1.0.0") -/// .dep("b", "1.0.0") -/// .file("src/lib.rs", r#" -/// extern crate b; -/// pub fn f() -> i32 { b::f() * 2 } -/// "#) -/// .publish(); -/// -/// // Publish package "b". -/// Package::new("b", "1.0.0") -/// .file("src/lib.rs", r#" -/// pub fn f() -> i32 { 12 } -/// "#) -/// .publish(); -/// -/// // Create a project that uses package "a". -/// let p = project() -/// .file("Cargo.toml", r#" -/// [package] -/// name = "foo" -/// version = "0.0.1" -/// -/// [dependencies] -/// a = "1.0" -/// "#) -/// .file("src/main.rs", r#" -/// extern crate a; -/// fn main() { println!("{}", a::f()); } -/// "#) -/// .build(); -/// -/// p.cargo("run").with_stdout("24").run(); -/// ``` #[must_use] pub struct Package { name: String, @@ -544,6 +579,7 @@ pub struct Package { pub(crate) type FeatureMap = BTreeMap>; +/// Published package dependency builder, see [`Package::add_dep`] #[derive(Clone)] pub struct Dependency { name: String, @@ -582,14 +618,18 @@ struct PackageFile { const DEFAULT_MODE: u32 = 0o644; -/// Initializes the on-disk registry and sets up the config so that crates.io -/// is replaced with the one on disk. +/// Setup a local psuedo-crates.io [`TestRegistry`] +/// +/// This is implicitly called by [`Package::new`]. +/// +/// When calling `cargo publish`, see instead [`crate::publish`]. pub fn init() -> TestRegistry { RegistryBuilder::new().build() } -/// Variant of `init` that initializes the "alternative" registry and crates.io -/// replacement. +/// Setup a local "alternative" [`TestRegistry`] +/// +/// When calling `cargo publish`, see instead [`crate::publish`]. pub fn alt_init() -> TestRegistry { init(); RegistryBuilder::new().alternative().build() @@ -1234,6 +1274,8 @@ impl Package { /// See `src/doc/src/reference/registries.md` for more details on /// alternative registries. See `alt_registry.rs` for the tests that use /// this. + /// + /// **Requires:** [`alt_init`] pub fn alternative(&mut self, alternative: bool) -> &mut Package { self.alternative = alternative; self @@ -1666,6 +1708,7 @@ impl Package { } } +/// Generate a checksum pub fn cksum(s: &[u8]) -> String { Sha256::new().update(s).finish_hex() }