use super::*;
const BEFORE: &str = r#"version = 4
[[package]]
name = "app"
version = "0.1.0"
dependencies = ["dependency"]
[[package]]
name = "app"
version = "9.0.0"
source = "registry+https://example.invalid"
checksum = "fixed"
[[package]]
name = "dependency"
version = "1.0.0"
source = "registry+https://example.invalid"
checksum = "dependency-checksum"
"#;
fn projection() -> WorkspaceVersionProjection<'static> {
WorkspaceVersionProjection {
package: "app",
previous: "0.1.0",
next: "0.1.1",
}
}
fn after() -> String {
BEFORE.replacen("0.1.0", "0.1.1", 1)
}
#[test]
fn projects_only_workspace_versions_and_accepts_comments() {
let after = format!("# retained source comment\n{}", after());
verify_workspace_version_projection(BEFORE, &after, &[projection()]).unwrap();
assert!(matches!(
verify_workspace_version_projection(BEFORE, BEFORE, &[projection()]),
Err(LockProjectionError::ChangedInputs)
));
}
#[test]
fn dependency_source_checksum_and_other_fields_cannot_change() {
for after in [
after().replace("dependency-checksum", "changed"),
after().replace("example.invalid", "different.invalid"),
after().replace("9.0.0", "9.0.1"),
after().replace("[\"dependency\"]", "[]"),
after().replace("version = 4", "version = 3"),
] {
assert!(matches!(
verify_workspace_version_projection(BEFORE, &after, &[projection()]),
Err(LockProjectionError::ChangedInputs)
));
}
}
#[test]
fn missing_duplicate_and_wrong_prior_workspace_records_refuse() {
assert!(matches!(
verify_workspace_version_projection(
BEFORE,
&after(),
&[WorkspaceVersionProjection {
package: "missing",
..projection()
}]
),
Err(LockProjectionError::PackageSelection { matches: 0, .. })
));
let duplicate = format!("{BEFORE}\n[[package]]\nname = \"app\"\nversion = \"0.1.0\"\n");
assert!(matches!(
verify_workspace_version_projection(&duplicate, &after(), &[projection()]),
Err(LockProjectionError::PackageSelection { matches: 2, .. })
));
assert!(matches!(
verify_workspace_version_projection(
BEFORE,
&after(),
&[WorkspaceVersionProjection {
previous: "0.0.9",
..projection()
}]
),
Err(LockProjectionError::PreviousVersion { .. })
));
assert!(matches!(
verify_workspace_version_projection(BEFORE, &after(), &[]),
Err(LockProjectionError::InvalidSelection)
));
assert!(matches!(
verify_workspace_version_projection(BEFORE, &after(), &[projection(), projection()]),
Err(LockProjectionError::InvalidSelection)
));
assert!(matches!(
verify_workspace_version_projection("not valid [", &after(), &[projection()]),
Err(LockProjectionError::Parse {
input: "before",
..
})
));
assert!(matches!(
verify_workspace_version_projection(BEFORE, "not valid [", &[projection()]),
Err(LockProjectionError::Parse { input: "after", .. })
));
}
#[test]
fn projects_a_coherent_multi_package_family() {
let before = "version=4\n[[package]]\nname=\"one\"\nversion=\"0.1.0\"\n[[package]]\nname=\"two\"\nversion=\"0.1.0\"\n";
let changes = [
WorkspaceVersionProjection {
package: "one",
..projection()
},
WorkspaceVersionProjection {
package: "two",
..projection()
},
];
verify_workspace_version_projection(before, &before.replace("0.1.0", "0.1.1"), &changes)
.unwrap();
}
#[test]
fn cargo_generated_workspace_update_preserves_external_selections() {
use crate::artifacts::test_support::unique_temp_directory;
use std::{fs, process::Command};
let root = unique_temp_directory("cargo-lock-projection");
fs::create_dir_all(root.join("external/src")).unwrap();
fs::write(root.join("external/src/lib.rs"), "").unwrap();
fs::write(
root.join("external/Cargo.toml"),
"[package]\nname = 'projection-external'\nversion = '9.0.0'\nedition = '2024'\n[workspace]\n",
)
.unwrap();
for name in ["a", "b"] {
fs::create_dir_all(root.join(name).join("src")).unwrap();
fs::write(root.join(name).join("src/lib.rs"), "").unwrap();
let dependency = if name == "a" {
"projection-b = { path = '../b' }"
} else {
"projection-external = { path = '../external' }"
};
fs::write(
root.join(name).join("Cargo.toml"),
format!(
"[package]\nname = 'projection-{name}'\nversion.workspace = true\n\
edition = '2024'\n[dependencies]\n{dependency}\n"
),
)
.unwrap();
}
let manifest = "[workspace]\nmembers = ['a', 'b']\nexclude = ['external']\nresolver = '3'\n\
[workspace.package]\nversion = '0.1.0'\n";
fs::write(root.join("Cargo.toml"), manifest).unwrap();
let generate = || {
let output = Command::new(env!("CARGO"))
.current_dir(&root)
.args(["generate-lockfile", "--offline"])
.env("RUSTUP_AUTO_INSTALL", "0")
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
fs::read_to_string(root.join("Cargo.lock")).unwrap()
};
let before = generate();
fs::write(root.join("Cargo.toml"), manifest.replace("0.1.0", "0.1.1")).unwrap();
let after = generate();
let changes = ["projection-a", "projection-b"].map(|package| WorkspaceVersionProjection {
package,
previous: "0.1.0",
next: "0.1.1",
});
verify_workspace_version_projection(&before, &after, &changes).unwrap();
let mut drift: toml::Value = toml::from_str(&after).unwrap();
let external = drift["package"]
.as_array_mut()
.unwrap()
.iter_mut()
.find(|package| package["name"].as_str() == Some("projection-external"))
.unwrap();
external["version"] = "9.0.1".into();
assert!(matches!(
verify_workspace_version_projection(&before, &toml::to_string(&drift).unwrap(), &changes),
Err(LockProjectionError::ChangedInputs)
));
fs::remove_dir_all(root).unwrap();
}