Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

guarantee serde is in lockstep with serde_derive #2588

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Expand Up @@ -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
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -4,6 +4,7 @@ members = [
"serde_derive",
"serde_derive_internals",
"test_suite",
"test_suite/assert_lockstep",
]

[patch.crates-io]
Expand Down
4 changes: 4 additions & 0 deletions serde/Cargo.toml
Expand Up @@ -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 #################################################################

Expand Down
12 changes: 12 additions & 0 deletions 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"
12 changes: 12 additions & 0 deletions 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]
1 change: 1 addition & 0 deletions test_suite/assert_lockstep/dummy_dependant/src/lib.rs
@@ -0,0 +1 @@

110 changes: 110 additions & 0 deletions 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<Infallible> 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(())
}