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

test: Verify build-std final crate graph against library lock file. #13404

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/cargo/core/resolver/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ impl EncodableResolve {
HashMap::new(),
))
}

pub fn package(&self) -> Option<&Vec<EncodableDependency>> {
self.package.as_ref()
}
}

fn build_path_deps(ws: &Workspace<'_>) -> CargoResult<HashMap<String, SourceId>> {
Expand Down Expand Up @@ -489,6 +493,20 @@ pub struct EncodableDependency {
replace: Option<EncodablePackageId>,
}

impl EncodableDependency {
pub fn name(&self) -> &str {
&self.name
}

pub fn version(&self) -> &str {
&self.version
}

pub fn dependencies(&self) -> Option<&Vec<EncodablePackageId>> {
self.dependencies.as_ref()
}
}

/// Pretty much equivalent to [`SourceId`] with a different serialization method.
///
/// The serialization for `SourceId` doesn't do URL encode for parameters.
Expand Down Expand Up @@ -574,6 +592,16 @@ pub struct EncodablePackageId {
source: Option<EncodableSourceId>,
}

impl EncodablePackageId {
pub fn name(&self) -> &str {
&self.name
}

pub fn version(&self) -> Option<&str> {
self.version.as_ref().map(|x| x.as_str())
}
}

impl fmt::Display for EncodablePackageId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)?;
Expand Down
150 changes: 149 additions & 1 deletion tests/build-std/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
#![allow(clippy::disallowed_methods)]

use cargo_test_support::*;
use std::env;
use std::path::Path;
use std::{env, fs};

fn enable_build_std(e: &mut Execs, arg: Option<&str>) {
e.env_remove("CARGO_HOME");
Expand Down Expand Up @@ -129,6 +129,154 @@ fn basic() {
assert_eq!(p.glob(deps_dir.join("*.dylib")).count(), 0);
}

#[cargo_test(build_std_real)]
fn validate_std_crate_graph() {
let p = project()
.file(
"src/main.rs",
"
fn main() {
foo::f();
}

#[test]
fn smoke_bin_unit() {
foo::f();
}
",
)
.file(
"src/lib.rs",
"
extern crate alloc;
extern crate proc_macro;

/// ```
/// foo::f();
/// ```
pub fn f() {
}

#[test]
fn smoke_lib_unit() {
f();
}
",
)
.file(
"tests/smoke.rs",
"
#[test]
fn smoke_integration() {
foo::f();
}
",
)
.build();

// Extract serialized crate graph from build.
let serialized_graph = p
.cargo("build -Zunstable-options --unit-graph")
.build_std()
.target_host()
.exec_with_output()
.unwrap();
let crate_graph: serde_json::Value =
serde_json::from_slice(serialized_graph.stdout.as_slice()).unwrap();

// Find the stdlib sysroot.
let sysroot = String::from_utf8(
p.cargo("rustc -Zunstable-options --print sysroot")
.masquerade_as_nightly_cargo(&["unstable-options"])
.exec_with_output()
.unwrap()
.stdout,
)
.unwrap();

// Load stdlib lockfile.
let s = fs::read_to_string(Path::new(sysroot.trim()).join("lib/rustlib/src/rust/Cargo.lock"))
.unwrap();
let encoded_lockfile: cargo::core::resolver::EncodableResolve = toml::from_str(&s).unwrap();

// Extract the graph from both of the versions.
let out_graph = crate_graph
.as_object()
.unwrap()
.get("units")
.unwrap()
.as_array()
.unwrap();
let lockfile_graph = encoded_lockfile.package().unwrap();

// Check that resolved graph is subgraph of lockfile.
for out_unit in out_graph.iter() {
// Extract package name, version from ID.
let mut id_elems = out_unit.get("pkg_id").unwrap().as_str().unwrap().split(" ");
let pkg_id = id_elems.next().unwrap();
let pkg_version = id_elems.next().unwrap();

// Ignore if this is our dummy package.
if pkg_id == "foo" {
continue;
}

// Extract package dependencies.
let dependencies = out_unit.get("dependencies").unwrap().as_array().unwrap();

// Ensure this package exists in the lockfile & versions match.
let lockfile_pkg = lockfile_graph.iter().find(|pkg| pkg.name() == pkg_id);
if lockfile_pkg.is_none() {
panic!(
"Package '{}' ({}) was present in build-std unit graph, but not in lockfile.",
pkg_id, pkg_version
);
}
let lockfile_pkg = lockfile_pkg.unwrap();
if lockfile_pkg.version() != pkg_version {
panic!("Package '{}' ({}) from build-std unit graph does not match version in lockfile ({}).", pkg_id, pkg_version, lockfile_pkg.version());
}

// Check dependencies match (resolved must be subset of lockfile).
if lockfile_pkg.dependencies().is_some() && dependencies.len() > 0 {
let lockfile_deps = lockfile_pkg
.dependencies()
.unwrap()
.iter()
.map(|x| x.name().replace("-", "_"))
// Lockfile dependencies may have a prefix.
.map(|x| {
x.strip_prefix("rustc_std_workspace_")
.unwrap_or(&x)
.to_string()
})
.collect::<Vec<String>>();

// When collecting resolved dependencies, we ignore the build script unit.
let dep_names = dependencies
.iter()
.map(|x| x.get("extern_crate_name").unwrap().as_str().unwrap())
// Resolved dependencies may start with a prefix.
.map(|x| {
x.strip_prefix("rustc_std_workspace_")
.unwrap_or(&x)
.to_string()
})
.filter(|x| *x != "build_script_build");

for dep in dep_names {
if !lockfile_deps.contains(&dep.to_string()) {
panic!("Package '{}' from build-std unit graph lists differing dependencies in unit graph from lockfile.", pkg_id);
}
}
} else if lockfile_pkg.dependencies().is_some()
&& dependencies.len() > lockfile_pkg.dependencies().unwrap().len()
{
panic!("Package '{}' from build-std unit graph lists differing dependencies in unit graph from lockfile.", pkg_id);
}
}
}

#[cargo_test(build_std_real)]
fn cross_custom() {
let p = project()
Expand Down