diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12da4dce..6f412db1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,3 +156,12 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/install@cargo-outdated - run: cargo outdated --workspace --exit-code 1 + + lockstep: + name: Lockstep + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - run: cd test_suite/assert_lockstep && cargo run -- dummy_dependant diff --git a/Cargo.toml b/Cargo.toml index 727dc484..dbc25d0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "serde_derive", "serde_derive_internals", "test_suite", + "test_suite/assert_lockstep", ] [patch.crates-io] diff --git a/serde/Cargo.toml b/serde/Cargo.toml index 13d33724..95de00de 100644 --- a/serde/Cargo.toml +++ b/serde/Cargo.toml @@ -31,6 +31,10 @@ features = ["derive"] targets = ["x86_64-unknown-linux-gnu"] rustdoc-args = ["--generate-link-to-definition"] +# Even though this `cfg` can never be enabled, it still forces cargo to keep `serde_derive` in lockstep with `serde`. +[target.'cfg(any())'.dependencies] +serde_derive = { version = "=1.0.185", path = "../serde_derive" } + ### FEATURES ################################################################# diff --git a/test_suite/assert_lockstep/Cargo.toml b/test_suite/assert_lockstep/Cargo.toml new file mode 100644 index 00000000..62dab258 --- /dev/null +++ b/test_suite/assert_lockstep/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "assert_lockstep" +version = "0.1.0" +edition = "2018" +rust-version = "1.70" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cargo-lock = "8.0.0" +toml = "0.5" +thiserror = "1.0.39" diff --git a/test_suite/assert_lockstep/dummy_dependant/Cargo.toml b/test_suite/assert_lockstep/dummy_dependant/Cargo.toml new file mode 100644 index 00000000..cff0938c --- /dev/null +++ b/test_suite/assert_lockstep/dummy_dependant/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dummy_dependant" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { path = "../../../serde" } + +# `workspace.exclude` does not seem to work with doubly nested paths. +[workspace] \ No newline at end of file diff --git a/test_suite/assert_lockstep/dummy_dependant/src/lib.rs b/test_suite/assert_lockstep/dummy_dependant/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test_suite/assert_lockstep/dummy_dependant/src/lib.rs @@ -0,0 +1 @@ + diff --git a/test_suite/assert_lockstep/src/main.rs b/test_suite/assert_lockstep/src/main.rs new file mode 100644 index 00000000..efe155d5 --- /dev/null +++ b/test_suite/assert_lockstep/src/main.rs @@ -0,0 +1,110 @@ +use cargo_lock::{Lockfile, Package, Version}; +use std::convert::Infallible; +use std::fmt; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::str::FromStr; +use thiserror::Error; + +#[derive(Debug, Error)] +enum TestError { + #[error("Package \"{name}\" not found in the lockfile")] + NoPackageFound { name: String }, + #[error("Package \"{first}\" and \"{second}\" had different versions in the lockfile")] + VersionMismatch { first: PackageId, second: PackageId }, + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + #[error("Error parsing lockfile: {0}")] + Lock(#[from] cargo_lock::Error), +} + +impl From for TestError { + fn from(value: Infallible) -> Self { + match value {} + } +} + +#[derive(Debug)] +struct PackageId { + name: String, + version: Version, +} + +impl PackageId { + fn new(package: &Package) -> Self { + Self { + name: package.name.to_string(), + version: package.version.clone(), + } + } +} + +impl fmt::Display for PackageId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{name}@{version}", + name = self.name, + version = self.version, + ) + } +} + +fn find_package<'a>(lockfile: &'a Lockfile, package_name: &str) -> Result<&'a Package, TestError> { + lockfile + .packages + .iter() + .find(|package| package.name.as_str() == package_name) + .ok_or_else(|| TestError::NoPackageFound { + name: package_name.to_owned(), + }) +} + +fn main() { + if let Err(error) = main_inner() { + panic!("{}", error); + } +} + +fn main_inner() -> Result<(), TestError> { + let path = PathBuf::from_str(&std::env::args().nth(1).unwrap())?; + + Command::new("cargo") + .arg("clean") + .current_dir(&path) + .stdout(Stdio::inherit()) + .output()?; + + Command::new("cargo") + .arg("update") + .current_dir(&path) + .stdout(Stdio::inherit()) + .output()?; + + let lockfile = Lockfile::load(path.join("Cargo.lock"))?; + + let serde = find_package(&lockfile, "serde")?; + + println!("packages should match {id}", id = PackageId::new(&serde)); + + let package_names = &["serde_derive"]; + + let packages = package_names + .iter() + .map(|name| find_package(&lockfile, name)); + + for package in packages { + let package = package?; + + println!("discovered package {id}", id = PackageId::new(&package)); + + if package.version != serde.version { + return Err(TestError::VersionMismatch { + first: PackageId::new(&serde), + second: PackageId::new(&package), + }); + } + } + + Ok(()) +}