- 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]`
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
+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>,
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;
}
}
-/// 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`).
/// 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 {
--- /dev/null
+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);
--- /dev/null
+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
+ }
+}
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),
"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)?,
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",
);
}
/// 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))
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,
},
)?;
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() {
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.
## 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
}
```
-### 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
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
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
* 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
}
```
-### 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)
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`.)
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
}
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")