]> git.proxmox.com Git - cargo.git/commitdiff
Merge pull request #10761 from Muscraft/reduce-parsing
authorJosh Triplett <josh@joshtriplett.org>
Mon, 20 Jun 2022 17:46:11 +0000 (10:46 -0700)
committerGitHub <noreply@github.com>
Mon, 20 Jun 2022 17:46:11 +0000 (10:46 -0700)
Add preloading for workspace packages in `resolve_with_previous`

14 files changed:
CHANGELOG.md
benches/benchsuite/Cargo.toml
benches/benchsuite/benches/resolve.rs
benches/benchsuite/benches/workspace_initialization.rs [new file with mode: 0644]
benches/benchsuite/src/lib.rs [new file with mode: 0644]
benches/workspaces/rust-ws-inherit.tgz [new file with mode: 0644]
src/cargo/core/features.rs
src/cargo/core/resolver/errors.rs
src/cargo/core/source/source_id.rs
src/cargo/sources/config.rs
src/cargo/sources/registry/http_remote.rs
src/doc/contrib/src/tests/writing.md
src/doc/src/reference/unstable.md
tests/testsuite/registry.rs

index 669474c01fda549a96930a485ff02ffed48695ea..658c08198b13a58011a6199d7f523a966ed754e5 100644 (file)
 - Start work on inheriting manifest values in a workspace.
   [#10497](https://github.com/rust-lang/cargo/pull/10497)
   [#10517](https://github.com/rust-lang/cargo/pull/10517)
-- Added support for HTTP registries.
+- Added support for sparse HTTP registries.
   [#10470](https://github.com/rust-lang/cargo/pull/10470)
   [#10064](https://github.com/rust-lang/cargo/pull/10064)
 - Fixed panic when artifact target is used for `[target.'cfg(<target>)'.dependencies]`
index c16f4f8664688cdfc74b80692b7e2e4563725671..f4bc3583a256d6876292aaae22281d9c3aeb0ff9 100644 (file)
@@ -16,6 +16,13 @@ flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
 tar = { version = "0.4.38", default-features = false }
 url = "2.2.2"
 
+[lib]
+bench = false
+
 [[bench]]
 name = "resolve"
 harness = false
+
+[[bench]]
+name = "workspace_initialization"
+harness = false
index 84455232eb989b3a2582c2803f81ec6b4303298b..d03cd620e27057043387418a2317efdcb76d4048 100644 (file)
+use benchsuite::fixtures;
 use cargo::core::compiler::{CompileKind, RustcTargetData};
-use cargo::core::resolver::features::{CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets};
-use cargo::core::resolver::{HasDevUnits, ResolveBehavior};
+use cargo::core::resolver::features::{FeatureOpts, FeatureResolver};
+use cargo::core::resolver::{CliFeatures, ForceAllTargets, HasDevUnits, ResolveBehavior};
 use cargo::core::{PackageIdSpec, Workspace};
 use cargo::ops::WorkspaceResolve;
 use cargo::Config;
 use criterion::{criterion_group, criterion_main, Criterion};
-use std::fs;
-use std::path::{Path, PathBuf};
-use std::process::Command;
-use url::Url;
-
-// This is an arbitrary commit that existed when I started. This helps
-// ensure consistent results. It can be updated if needed, but that can
-// make it harder to compare results with older versions of cargo.
-const CRATES_IO_COMMIT: &str = "85f7bfd61ea4fee08ec68c468762e886b2aebec6";
-
-fn setup() {
-    create_home();
-    create_target_dir();
-    clone_index();
-    unpack_workspaces();
-}
-
-fn root() -> PathBuf {
-    let mut p = PathBuf::from(env!("CARGO_TARGET_TMPDIR"));
-    p.push("bench");
-    p
-}
-
-fn target_dir() -> PathBuf {
-    let mut p = root();
-    p.push("target");
-    p
-}
-
-fn cargo_home() -> PathBuf {
-    let mut p = root();
-    p.push("chome");
-    p
-}
-
-fn index() -> PathBuf {
-    let mut p = root();
-    p.push("index");
-    p
-}
-
-fn workspaces_path() -> PathBuf {
-    let mut p = root();
-    p.push("workspaces");
-    p
-}
-
-fn registry_url() -> Url {
-    Url::from_file_path(index()).unwrap()
-}
-
-fn create_home() {
-    let home = cargo_home();
-    if !home.exists() {
-        fs::create_dir_all(&home).unwrap();
-    }
-    fs::write(
-        home.join("config.toml"),
-        format!(
-            r#"
-                [source.crates-io]
-                replace-with = 'local-snapshot'
-
-                [source.local-snapshot]
-                registry = '{}'
-            "#,
-            registry_url()
-        ),
-    )
-    .unwrap();
-}
-
-fn create_target_dir() {
-    // This is necessary to ensure the .rustc_info.json file is written.
-    // Otherwise it won't be written, and it is very expensive to create.
-    if !target_dir().exists() {
-        std::fs::create_dir_all(target_dir()).unwrap();
-    }
-}
-
-/// This clones crates.io at a specific point in time into tmp/index.
-fn clone_index() {
-    let index = index();
-    let maybe_git = |command: &str| {
-        let status = Command::new("git")
-            .current_dir(&index)
-            .args(command.split_whitespace().collect::<Vec<_>>())
-            .status()
-            .expect("git should be installed");
-        status.success()
-    };
-    let git = |command: &str| {
-        if !maybe_git(command) {
-            panic!("failed to run git command: {}", command);
-        }
-    };
-    if index.exists() {
-        if maybe_git(&format!(
-            "rev-parse -q --verify {}^{{commit}}",
-            CRATES_IO_COMMIT
-        )) {
-            // Already fetched.
-            return;
-        }
-    } else {
-        fs::create_dir_all(&index).unwrap();
-        git("init --bare");
-        git("remote add origin https://github.com/rust-lang/crates.io-index");
-    }
-    git(&format!("fetch origin {}", CRATES_IO_COMMIT));
-    git("branch -f master FETCH_HEAD");
-}
-
-/// This unpacks the compressed workspace skeletons into tmp/workspaces.
-fn unpack_workspaces() {
-    let ws_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
-        .parent()
-        .unwrap()
-        .join("workspaces");
-    let archives = fs::read_dir(ws_dir)
-        .unwrap()
-        .map(|e| e.unwrap().path())
-        .filter(|p| p.extension() == Some(std::ffi::OsStr::new("tgz")));
-    for archive in archives {
-        let name = archive.file_stem().unwrap();
-        let f = fs::File::open(&archive).unwrap();
-        let f = flate2::read::GzDecoder::new(f);
-        let dest = workspaces_path().join(&name);
-        if dest.exists() {
-            fs::remove_dir_all(&dest).unwrap();
-        }
-        let mut archive = tar::Archive::new(f);
-        archive.unpack(workspaces_path()).unwrap();
-    }
-}
+use std::path::Path;
 
 struct ResolveInfo<'cfg> {
     ws: Workspace<'cfg>,
@@ -152,36 +19,12 @@ struct ResolveInfo<'cfg> {
     ws_resolve: WorkspaceResolve<'cfg>,
 }
 
-/// Vec of `(ws_name, ws_root)`.
-fn workspaces() -> Vec<(String, PathBuf)> {
-    // CARGO_BENCH_WORKSPACES can be used to override, otherwise it just uses
-    // the workspaces in the workspaces directory.
-    let mut ps: Vec<_> = match std::env::var_os("CARGO_BENCH_WORKSPACES") {
-        Some(s) => std::env::split_paths(&s).collect(),
-        None => fs::read_dir(workspaces_path())
-            .unwrap()
-            .map(|e| e.unwrap().path())
-            // These currently fail in most cases on Windows due to long
-            // filenames in the git checkouts.
-            .filter(|p| {
-                !(cfg!(windows)
-                    && matches!(p.file_name().unwrap().to_str().unwrap(), "servo" | "tikv"))
-            })
-            .collect(),
-    };
-    // Sort so it is consistent.
-    ps.sort();
-    ps.into_iter()
-        .map(|p| (p.file_name().unwrap().to_str().unwrap().to_owned(), p))
-        .collect()
-}
-
 /// Helper for resolving a workspace. This will run the resolver once to
 /// download everything, and returns all the data structures that are used
 /// during resolution.
 fn do_resolve<'cfg>(config: &'cfg Config, ws_root: &Path) -> ResolveInfo<'cfg> {
     let requested_kinds = [CompileKind::Host];
-    let ws = cargo::core::Workspace::new(&ws_root.join("Cargo.toml"), config).unwrap();
+    let ws = Workspace::new(&ws_root.join("Cargo.toml"), config).unwrap();
     let target_data = RustcTargetData::new(&ws, &requested_kinds).unwrap();
     let cli_features = CliFeatures::from_command_line(&[], false, true).unwrap();
     let pkgs = cargo::ops::Packages::Default;
@@ -212,38 +55,14 @@ fn do_resolve<'cfg>(config: &'cfg Config, ws_root: &Path) -> ResolveInfo<'cfg> {
     }
 }
 
-/// Creates a new Config.
-///
-/// This is separate from `do_resolve` to deal with the ownership and lifetime.
-fn make_config(ws_root: &Path) -> Config {
-    let shell = cargo::core::Shell::new();
-    let mut config = cargo::util::Config::new(shell, ws_root.to_path_buf(), cargo_home());
-    // Configure is needed to set the target_dir which is needed to write
-    // the .rustc_info.json file which is very expensive.
-    config
-        .configure(
-            0,
-            false,
-            None,
-            false,
-            false,
-            false,
-            &Some(target_dir()),
-            &[],
-            &[],
-        )
-        .unwrap();
-    config
-}
-
 /// Benchmark of the full `resolve_ws_with_opts` which runs the resolver
 /// twice, the feature resolver, and more. This is a major component of a
 /// regular cargo build.
 fn resolve_ws(c: &mut Criterion) {
-    setup();
+    let fixtures = fixtures!();
     let mut group = c.benchmark_group("resolve_ws");
-    for (ws_name, ws_root) in workspaces() {
-        let config = make_config(&ws_root);
+    for (ws_name, ws_root) in fixtures.workspaces() {
+        let config = fixtures.make_config(&ws_root);
         // The resolver info is initialized only once in a lazy fashion. This
         // allows criterion to skip this workspace if the user passes a filter
         // on the command-line (like `cargo bench -- resolve_ws/tikv`).
@@ -282,10 +101,10 @@ fn resolve_ws(c: &mut Criterion) {
 
 /// Benchmark of the feature resolver.
 fn feature_resolver(c: &mut Criterion) {
-    setup();
+    let fixtures = fixtures!();
     let mut group = c.benchmark_group("feature_resolver");
-    for (ws_name, ws_root) in workspaces() {
-        let config = make_config(&ws_root);
+    for (ws_name, ws_root) in fixtures.workspaces() {
+        let config = fixtures.make_config(&ws_root);
         let mut lazy_info = None;
         group.bench_function(&ws_name, |b| {
             let ResolveInfo {
diff --git a/benches/benchsuite/benches/workspace_initialization.rs b/benches/benchsuite/benches/workspace_initialization.rs
new file mode 100644 (file)
index 0000000..af68efe
--- /dev/null
@@ -0,0 +1,27 @@
+use benchsuite::fixtures;
+use cargo::core::Workspace;
+use criterion::{criterion_group, criterion_main, Criterion};
+
+fn workspace_initialization(c: &mut Criterion) {
+    let fixtures = fixtures!();
+    let mut group = c.benchmark_group("workspace_initialization");
+    for (ws_name, ws_root) in fixtures.workspaces() {
+        let config = fixtures.make_config(&ws_root);
+        // The resolver info is initialized only once in a lazy fashion. This
+        // allows criterion to skip this workspace if the user passes a filter
+        // on the command-line (like `cargo bench -- workspace_initialization/tikv`).
+        group.bench_function(ws_name, |b| {
+            b.iter(|| Workspace::new(&ws_root.join("Cargo.toml"), &config).unwrap())
+        });
+    }
+    group.finish();
+}
+
+// Criterion complains about the measurement time being too small, but the
+// measurement time doesn't seem important to me, what is more important is
+// the number of iterations which defaults to 100, which seems like a
+// reasonable default. Otherwise, the measurement time would need to be
+// changed per workspace. We wouldn't want to spend 60s on every workspace,
+// that would take too long and isn't necessary for the smaller workspaces.
+criterion_group!(benches, workspace_initialization);
+criterion_main!(benches);
diff --git a/benches/benchsuite/src/lib.rs b/benches/benchsuite/src/lib.rs
new file mode 100644 (file)
index 0000000..42e0f51
--- /dev/null
@@ -0,0 +1,197 @@
+use cargo::Config;
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use url::Url;
+
+#[macro_export]
+macro_rules! fixtures {
+    () => {
+        $crate::Fixtures::new(env!("CARGO_TARGET_TMPDIR"))
+    };
+}
+
+// This is an arbitrary commit that existed when I started. This helps
+// ensure consistent results. It can be updated if needed, but that can
+// make it harder to compare results with older versions of cargo.
+const CRATES_IO_COMMIT: &str = "85f7bfd61ea4fee08ec68c468762e886b2aebec6";
+
+pub struct Fixtures {
+    cargo_target_tmpdir: PathBuf,
+}
+
+impl Fixtures {
+    pub fn new(cargo_target_tmpdir: &str) -> Self {
+        let bench = Self {
+            cargo_target_tmpdir: PathBuf::from(cargo_target_tmpdir),
+        };
+        bench.create_home();
+        bench.create_target_dir();
+        bench.clone_index();
+        bench.unpack_workspaces();
+        bench
+    }
+
+    fn root(&self) -> PathBuf {
+        self.cargo_target_tmpdir.join("bench")
+    }
+
+    fn target_dir(&self) -> PathBuf {
+        let mut p = self.root();
+        p.push("target");
+        p
+    }
+
+    fn cargo_home(&self) -> PathBuf {
+        let mut p = self.root();
+        p.push("chome");
+        p
+    }
+
+    fn index(&self) -> PathBuf {
+        let mut p = self.root();
+        p.push("index");
+        p
+    }
+
+    fn workspaces_path(&self) -> PathBuf {
+        let mut p = self.root();
+        p.push("workspaces");
+        p
+    }
+
+    fn registry_url(&self) -> Url {
+        Url::from_file_path(self.index()).unwrap()
+    }
+
+    fn create_home(&self) {
+        let home = self.cargo_home();
+        if !home.exists() {
+            fs::create_dir_all(&home).unwrap();
+        }
+        fs::write(
+            home.join("config.toml"),
+            format!(
+                r#"
+                [source.crates-io]
+                replace-with = 'local-snapshot'
+
+                [source.local-snapshot]
+                registry = '{}'
+            "#,
+                self.registry_url()
+            ),
+        )
+        .unwrap();
+    }
+
+    fn create_target_dir(&self) {
+        // This is necessary to ensure the .rustc_info.json file is written.
+        // Otherwise it won't be written, and it is very expensive to create.
+        if !self.target_dir().exists() {
+            fs::create_dir_all(self.target_dir()).unwrap();
+        }
+    }
+
+    /// This clones crates.io at a specific point in time into tmp/index.
+    fn clone_index(&self) {
+        let index = self.index();
+        let maybe_git = |command: &str| {
+            let status = Command::new("git")
+                .current_dir(&index)
+                .args(command.split_whitespace().collect::<Vec<_>>())
+                .status()
+                .expect("git should be installed");
+            status.success()
+        };
+        let git = |command: &str| {
+            if !maybe_git(command) {
+                panic!("failed to run git command: {}", command);
+            }
+        };
+        if index.exists() {
+            if maybe_git(&format!(
+                "rev-parse -q --verify {}^{{commit}}",
+                CRATES_IO_COMMIT
+            )) {
+                // Already fetched.
+                return;
+            }
+        } else {
+            fs::create_dir_all(&index).unwrap();
+            git("init --bare");
+            git("remote add origin https://github.com/rust-lang/crates.io-index");
+        }
+        git(&format!("fetch origin {}", CRATES_IO_COMMIT));
+        git("branch -f master FETCH_HEAD");
+    }
+
+    /// This unpacks the compressed workspace skeletons into tmp/workspaces.
+    fn unpack_workspaces(&self) {
+        let ws_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
+            .parent()
+            .unwrap()
+            .join("workspaces");
+        let archives = fs::read_dir(ws_dir)
+            .unwrap()
+            .map(|e| e.unwrap().path())
+            .filter(|p| p.extension() == Some(std::ffi::OsStr::new("tgz")));
+        for archive in archives {
+            let name = archive.file_stem().unwrap();
+            let f = fs::File::open(&archive).unwrap();
+            let f = flate2::read::GzDecoder::new(f);
+            let dest = self.workspaces_path().join(&name);
+            if dest.exists() {
+                fs::remove_dir_all(&dest).unwrap();
+            }
+            let mut archive = tar::Archive::new(f);
+            archive.unpack(self.workspaces_path()).unwrap();
+        }
+    }
+
+    /// Vec of `(ws_name, ws_root)`.
+    pub fn workspaces(&self) -> Vec<(String, PathBuf)> {
+        // CARGO_BENCH_WORKSPACES can be used to override, otherwise it just uses
+        // the workspaces in the workspaces directory.
+        let mut ps: Vec<_> = match std::env::var_os("CARGO_BENCH_WORKSPACES") {
+            Some(s) => std::env::split_paths(&s).collect(),
+            None => fs::read_dir(self.workspaces_path())
+                .unwrap()
+                .map(|e| e.unwrap().path())
+                // These currently fail in most cases on Windows due to long
+                // filenames in the git checkouts.
+                .filter(|p| {
+                    !(cfg!(windows)
+                        && matches!(p.file_name().unwrap().to_str().unwrap(), "servo" | "tikv"))
+                })
+                .collect(),
+        };
+        // Sort so it is consistent.
+        ps.sort();
+        ps.into_iter()
+            .map(|p| (p.file_name().unwrap().to_str().unwrap().to_owned(), p))
+            .collect()
+    }
+
+    /// Creates a new Config.
+    pub fn make_config(&self, ws_root: &Path) -> Config {
+        let shell = cargo::core::Shell::new();
+        let mut config = Config::new(shell, ws_root.to_path_buf(), self.cargo_home());
+        // Configure is needed to set the target_dir which is needed to write
+        // the .rustc_info.json file which is very expensive.
+        config
+            .configure(
+                0,
+                false,
+                None,
+                false,
+                false,
+                false,
+                &Some(self.target_dir()),
+                &[],
+                &[],
+            )
+            .unwrap();
+        config
+    }
+}
diff --git a/benches/workspaces/rust-ws-inherit.tgz b/benches/workspaces/rust-ws-inherit.tgz
new file mode 100644 (file)
index 0000000..6e7b669
Binary files /dev/null and b/benches/workspaces/rust-ws-inherit.tgz differ
index 53786eff3ff033427ffc0dcca44839110efb9e85..779bca4ce0237020ba50354160eed9783b0f68db 100644 (file)
@@ -653,7 +653,7 @@ unstable_cli_options!(
     no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
     panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
     host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"),
-    http_registry: bool = ("Support HTTP-based crate registries"),
+    sparse_registry: bool = ("Support plain-HTTP-based crate registries"),
     target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
     rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
     separate_nightlies: bool = (HIDDEN),
@@ -907,7 +907,7 @@ impl CliUnstable {
             "multitarget" => self.multitarget = parse_empty(k, v)?,
             "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
             "terminal-width" => self.terminal_width = Some(parse_usize_opt(v)?),
-            "http-registry" => self.http_registry = parse_empty(k, v)?,
+            "sparse-registry" => self.sparse_registry = parse_empty(k, v)?,
             "namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES),
             "weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES),
             "credential-process" => self.credential_process = parse_empty(k, v)?,
index 711a2d734ff3d9a213e564289d5631f0e001f5a5..306da17ef6cd822e0cc33b0871e9c5a494e90687 100644 (file)
@@ -182,7 +182,7 @@ pub(super) fn activation_error(
                     msg.push_str("` does not have these features.\n");
                     msg.push_str(
                         " It has an optional dependency with that name, \
-                         but but that dependency uses the \"dep:\" \
+                         but that dependency uses the \"dep:\" \
                          syntax in the features table, so it does not have an \
                          implicit feature with that name.\n",
                     );
index 653685b8becaa2774dccb4f0fbede9a2cbd12a3d..c51a67c5074f72769fc3639a463e68990699e28e 100644 (file)
@@ -208,9 +208,9 @@ impl SourceId {
     }
 
     /// Returns the `SourceId` corresponding to the main repository, using the
-    /// http index if allowed.
-    pub fn crates_io_maybe_http(config: &Config) -> CargoResult<SourceId> {
-        if config.cli_unstable().http_registry {
+    /// sparse HTTP index if allowed.
+    pub fn crates_io_maybe_sparse_http(config: &Config) -> CargoResult<SourceId> {
+        if config.cli_unstable().sparse_registry {
             config.check_registry_index_not_set()?;
             let url = CRATES_IO_HTTP_INDEX.into_url().unwrap();
             SourceId::new(SourceKind::Registry, url, Some(CRATES_IO_REGISTRY))
index ff3b9ccb9578e9fc5207df46d514eee5de15117f..6d6c7ee3944a2e3ece125d8857324bb742543429 100644 (file)
@@ -91,11 +91,11 @@ impl<'cfg> SourceConfigMap<'cfg> {
                 replace_with: None,
             },
         )?;
-        if config.cli_unstable().http_registry {
+        if config.cli_unstable().sparse_registry {
             base.add(
                 CRATES_IO_REGISTRY,
                 SourceConfig {
-                    id: SourceId::crates_io_maybe_http(config)?,
+                    id: SourceId::crates_io_maybe_sparse_http(config)?,
                     replace_with: None,
                 },
             )?;
@@ -257,7 +257,7 @@ restore the source replacement configuration to continue the build
             check_not_set("rev", def.rev)?;
         }
         if name == CRATES_IO_REGISTRY && srcs.is_empty() {
-            srcs.push(SourceId::crates_io_maybe_http(self.config)?);
+            srcs.push(SourceId::crates_io_maybe_sparse_http(self.config)?);
         }
 
         match srcs.len() {
index 18734ef6ba06ee01d4c4c5c77f41c427e1bf4479..0a6de7c1bcfa3eb320cb650b668167941a1e3295 100644 (file)
@@ -131,8 +131,8 @@ impl<'cfg> HttpRegistry<'cfg> {
         config: &'cfg Config,
         name: &str,
     ) -> CargoResult<HttpRegistry<'cfg>> {
-        if !config.cli_unstable().http_registry {
-            anyhow::bail!("usage of HTTP-based registries requires `-Z http-registry`");
+        if !config.cli_unstable().sparse_registry {
+            anyhow::bail!("usage of sparse registries requires `-Z sparse-registry`");
         }
         let url = source_id.url().as_str();
         // Ensure the url ends with a slash so we can concatenate paths.
index 3d9e1b267534a9489b92f5e2a907508444d9e28b..156ce2b594f552f01b55bc4c53d8c27402688fd5 100644 (file)
@@ -6,65 +6,69 @@ tests is also encouraged!
 ## Testsuite
 
 Cargo has a wide variety of integration tests that execute the `cargo` binary
-and verify its behavior, located in the [`testsuite`] directory. The
-[`support`] crate contains many helpers to make this process easy.
+and verify its behavior, located in the [`testsuite`] directory.  The
+[`support`] crate and [`snapbox`] contain many helpers to make this process easy.
+
+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
+  - 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
+  - Easier to review the expected behavior of the command as more details are included
+  - Easier to get up and running from an existing project
+  - Easier to reason about as everything is just files in the repo
 
 These tests typically work by creating a temporary "project" with a
 `Cargo.toml` file, executing the `cargo` binary process, and checking the
 stdout and stderr output against the expected output.
 
-### `cargo_test` attribute
-
-Cargo's tests use the `#[cargo_test]` attribute instead of `#[test]`. This
-attribute injects some code which does some setup before starting the test,
-creating the little "sandbox" described below.
-
-### Basic test structure
-
-The general form of a test involves creating a "project", running `cargo`, and
-checking the result. Projects are created with the [`ProjectBuilder`] where
-you specify some files to create. The general form looks like this:
-
-```rust,ignore
-let p = project()
-    .file("src/main.rs", r#"fn main() { println!("hi!"); }"#)
-    .build();
-```
-
-The project creates a mini sandbox under the "cargo integration test"
-directory with each test getting a separate directory such as
-`/path/to/cargo/target/cit/t123/`. Each project appears as a separate
-directory. There is also an empty `home` directory created that will be used
-as a home directory instead of your normal home directory.
-
-If you do not specify a `Cargo.toml` manifest using `file()`, one is
-automatically created with a project name of `foo` using `basic_manifest()`.
-
-To run Cargo, call the `cargo` method and make assertions on the execution:
+### Functional Tests
 
+Generally, a functional test will be placed in `tests/testsuite/<command>.rs` and will look roughly like:
 ```rust,ignore
-p.cargo("run --bin foo")
-    .with_stderr(
-        "\
-[COMPILING] foo [..]
-[FINISHED] [..]
-[RUNNING] `target/debug/foo`
-",
-    )
-    .with_stdout("hi!")
-    .run();
+#[cargo_test]
+fn <description>() {
+    let p = project()
+        .file("src/main.rs", r#"fn main() { println!("hi!"); }"#)
+        .build();
+
+    p.cargo("run --bin foo")
+        .with_stderr(
+            "\
+    [COMPILING] foo [..]
+    [FINISHED] [..]
+    [RUNNING] `target/debug/foo`
+    ",
+        )
+        .with_stdout("hi!")
+        .run();
+    }
+}
 ```
 
-This uses the [`Execs`] struct to build up a command to execute, along with
-the expected output.
+`#[cargo_test]`:
+- This is used in place of `#[test]`
+- This attribute injects code which does some setup before starting the
+  test, creating a filesystem "sandbox" under the "cargo integration test"
+  directory for each test such as
+  `/path/to/cargo/target/cit/t123/`
+- The sandbox will contain a `home` directory that will be used instead of your normal home directory
 
-See [`support::compare`] for an explanation of the string pattern matching.
-Patterns are used to make it easier to match against the expected output.
+[`ProjectBuilder`] via `project()`:
+- Each project is in a separate directory in the sandbox
+- If you do not specify a `Cargo.toml` manifest using `file()`, one is
+  automatically created with a project name of `foo` using `basic_manifest()`.
 
-Browse the `pub` functions and modules in the [`support`] crate for a variety
-of other helpful utilities.
+[`Execs`] via `p.cargo(...)`:
+- This executes the command and evaluates different assertions
+  - See [`support::compare`] for an explanation of the string pattern matching.
+    Patterns are used to make it easier to match against the expected output.
 
-### Testing Nightly Features
+#### Testing Nightly Features
 
 If you are testing a Cargo feature that only works on "nightly" Cargo, then
 you need to call `masquerade_as_nightly_cargo` on the process builder like
@@ -85,17 +89,7 @@ if !is_nightly() {
 }
 ```
 
-### Platform-specific Notes
-
-When checking output, use `/` for paths even on Windows: the actual output
-of `\` on Windows will be replaced with `/`.
-
-Be careful when executing binaries on Windows. You should not rename, delete,
-or overwrite a binary immediately after running it. Under some conditions
-Windows will fail with errors like "directory not empty" or "failed to remove"
-or "access is denied".
-
-### Specifying Dependencies
+#### Specifying Dependencies
 
 You should not write any tests that use the network such as contacting
 crates.io. Typically, simple path dependencies are the easiest way to add a
@@ -123,6 +117,110 @@ If you need to test with registry dependencies, see
 If you need to test git dependencies, see [`support::git`] to create a git
 dependency.
 
+### UI Tests
+
+UI Tests are a bit more spread out and generally look like:
+
+`tests/testsuite/<command>/mod.rs`:
+```rust,ignore
+mod <case>;
+```
+
+`tests/testsuite/<command>/<case>/mod.rs`:
+```rust,ignore
+use cargo_test_support::prelude::*;
+use cargo_test_support::compare::assert;
+use cargo_test_support::Project;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn <name>() {
+    let project = Project::from_template(curr_dir!().join("in"));
+    let project_root = project.root();
+    let cwd = &project_root;
+
+    snapbox::cmd::Command::cargo()
+        .arg("run")
+        .arg_line("--bin foo")
+        .current_dir(cwd)
+        .assert()
+        .success()
+        .stdout_matches_path(curr_dir!().join("stdout.log"))
+        .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+    assert().subset_matches(curr_dir!().join("out"), &project_root);
+}
+```
+
+Then populate
+- `tests/testsuite/<command>/<case>/in` with the project's directory structure
+- `tests/testsuite/<command>/<case>/out` with the files you want verified
+- `tests/testsuite/<command>/<case>/stdout.log` with nothing
+- `tests/testsuite/<command>/<case>/stderr.log` with nothing
+
+`#[cargo_test]`:
+- This is used in place of `#[test]`
+- This attribute injects code which does some setup before starting the
+  test, creating a filesystem "sandbox" under the "cargo integration test"
+  directory for each test such as
+  `/path/to/cargo/target/cit/t123/`
+- The sandbox will contain a `home` directory that will be used instead of your normal home directory
+
+`Project`:
+- The project is copied from a directory in the repo
+- Each project is in a separate directory in the sandbox
+
+[`Command`] via `Command::cargo()`:
+- Set up and run a command.
+
+[`OutputAssert`] via `Command::assert()`:
+- Perform assertions on the result of the [`Command`]
+
+[`Assert`] via `assert()`:
+- Verify the command modified the file system as expected
+
+#### Updating Snapshots
+
+The project, stdout, and stderr snapshots can be updated by running with the
+`SNAPSHOTS=overwrite` environment variable, like:
+```console
+$ SNAPSHOTS=overwrite cargo test
+```
+
+Be sure to check the snapshots to make sure they make sense.
+
+#### Testing Nightly Features
+
+If you are testing a Cargo feature that only works on "nightly" Cargo, then
+you need to call `masquerade_as_nightly_cargo` on the process builder like
+this:
+
+```rust,ignore
+    snapbox::cmd::Command::cargo()
+        .masquerade_as_nightly_cargo()
+```
+
+If you are testing a feature that only works on *nightly rustc* (such as
+benchmarks), then you should exit the test if it is not running with nightly
+rust, like this:
+
+```rust,ignore
+if !is_nightly() {
+    // Add a comment here explaining why this is necessary.
+    return;
+}
+```
+
+### Platform-specific Notes
+
+When checking output, use `/` for paths even on Windows: the actual output
+of `\` on Windows will be replaced with `/`.
+
+Be careful when executing binaries on Windows. You should not rename, delete,
+or overwrite a binary immediately after running it. Under some conditions
+Windows will fail with errors like "directory not empty" or "failed to remove"
+or "access is denied".
+
 ## Debugging tests
 
 In some cases, you may need to dig into a test that is not working as you
@@ -152,10 +250,14 @@ environment. The general process is:
         3. Run with arguments: `r check`
 
 [`testsuite`]: https://github.com/rust-lang/cargo/tree/master/tests/testsuite/
-[`ProjectBuilder`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/crates/cargo-test-support/src/lib.rs#L225-L231
-[`Execs`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/crates/cargo-test-support/src/lib.rs#L558-L579
+[`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
 [`support`]: https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/lib.rs
 [`support::compare`]: https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/compare.rs
-[`support::registry::Package`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/crates/cargo-test-support/src/registry.rs#L73-L149
+[`support::registry::Package`]: https://github.com/rust-lang/cargo/blob/d847468768446168b596f721844193afaaf9d3f2/crates/cargo-test-support/src/registry.rs#L311-L389
 [`support::git`]: https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/git.rs
 [Running Cargo]: ../process/working-on-cargo.md#running-cargo
+[`snapbox`]: https://docs.rs/snapbox/latest/snapbox/
+[`Command`]: https://docs.rs/snapbox/latest/snapbox/cmd/struct.Command.html
+[`OutputAssert`]: https://docs.rs/snapbox/latest/snapbox/cmd/struct.OutputAssert.html
+[`Assert`]: https://docs.rs/snapbox/latest/snapbox/struct.Assert.html
index 6bd68b8a24fc7a3399e82bfb600ec9b5e5d7a697..dd40f25d4e3c2d50b8cf5aa929bc6eea6902c7ac 100644 (file)
@@ -102,7 +102,7 @@ Each new feature described below should explain how to use it.
 * Registries
     * [credential-process](#credential-process) — Adds support for fetching registry tokens from an external authentication program.
     * [`cargo logout`](#cargo-logout) — Adds the `logout` command to remove the currently saved registry token.
-    * [http-registry](#http-registry) — Adds support for fetching from http registries (`sparse+`)
+    * [sparse-registry](#sparse-registry) — Adds support for fetching from static-file HTTP registries (`sparse+`)
 
 ### allow-features
 
@@ -909,18 +909,18 @@ fn main() {
 }
 ```
 
-### http-registry
+### sparse-registry
 * Tracking Issue: [9069](https://github.com/rust-lang/cargo/issues/9069)
 * RFC: [#2789](https://github.com/rust-lang/rfcs/pull/2789)
 
-The `http-registry` feature allows cargo to interact with remote registries served
-over http rather than git. These registries can be identified by urls starting with
+The `sparse-registry` feature allows cargo to interact with remote registries served
+over plain HTTP rather than git. These registries can be identified by urls starting with
 `sparse+http://` or `sparse+https://`.
 
-When fetching index metadata over http, cargo only downloads the metadata for relevant
+When fetching index metadata over HTTP, Cargo only downloads the metadata for relevant
 crates, which can save significant time and bandwidth.
 
-The format of the http index is identical to a checkout of a git-based index.
+The format of the sparse index is identical to a checkout of a git-based index.
 
 ### credential-process
 * Tracking Issue: [#8933](https://github.com/rust-lang/cargo/issues/8933)
@@ -1597,4 +1597,4 @@ See the [Features chapter](features.md#dependency-features) for more information
 
 The `-Ztimings` option has been stabilized as `--timings` in the 1.60 release.
 (`--timings=html` and the machine-readable `--timings=json` output remain
-unstable and require `-Zunstable-options`.)
\ No newline at end of file
+unstable and require `-Zunstable-options`.)
index bf0335cc4cc7402cf10964aea1eae384a8c2bc5f..2f80f778c40d3c5fb6874c76480779fcdd5ef9f5 100644 (file)
@@ -14,7 +14,7 @@ use std::path::Path;
 
 fn cargo_http(p: &Project, s: &str) -> Execs {
     let mut e = p.cargo(s);
-    e.arg("-Zhttp-registry").masquerade_as_nightly_cargo();
+    e.arg("-Zsparse-registry").masquerade_as_nightly_cargo();
     e
 }
 
@@ -2643,13 +2643,13 @@ fn http_requires_z_flag() {
 
     p.cargo("build")
         .with_status(101)
-        .with_stderr_contains("  usage of HTTP-based registries requires `-Z http-registry`")
+        .with_stderr_contains("  usage of sparse registries requires `-Z sparse-registry`")
         .run();
 }
 
 #[cargo_test]
 fn http_requires_trailing_slash() {
-    cargo_process("-Z http-registry install bar --index sparse+https://index.crates.io")
+    cargo_process("-Z sparse-registry install bar --index sparse+https://index.crates.io")
         .masquerade_as_nightly_cargo()
         .with_status(101)
         .with_stderr("[ERROR] registry url must end in a slash `/`: sparse+https://index.crates.io")