//! Lockfile integration test use std::fs; use std::str::FromStr; // TODO(tarcieri): add more example `Cargo.lock` files which cover more scenarios use cargo_lock::{ Lockfile, MetadataKey, ResolveVersion, Version, package::{GitReference, SourceKind}, }; /// Path to a V1 `Cargo.lock` file. const V1_LOCKFILE_PATH: &str = "tests/examples/Cargo.lock.v1"; /// Path to a V2 `Cargo.lock` file. const V2_LOCKFILE_PATH: &str = "tests/examples/Cargo.lock.v2"; /// Path to a V3 `Cargo.lock` file. const V3_LOCKFILE_PATH: &str = "tests/examples/Cargo.lock.v3"; /// Path to a V4 `Cargo.lock` file. const V4_LOCKFILE_PATH: &str = "tests/examples/Cargo.lock.v4"; /// Load example V1 `Cargo.lock` file (from the Cargo project itself) #[test] fn load_example_v1_lockfile() { let lockfile = Lockfile::load(V1_LOCKFILE_PATH).unwrap(); assert_eq!(lockfile.version, ResolveVersion::V1); assert_eq!(lockfile.packages.len(), 141); assert_eq!(lockfile.metadata.len(), 136); let package = &lockfile.packages[0]; assert_eq!(package.name.as_ref(), "adler32"); assert_eq!(package.version, Version::parse("1.0.4").unwrap()); let metadata_key: MetadataKey = "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" .parse() .unwrap(); let metadata_value = &lockfile.metadata[&metadata_key]; assert_eq!( metadata_value.as_ref(), "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" ); } /// Load example V2 `Cargo.lock` file #[test] fn load_example_v2_lockfile() { let lockfile = Lockfile::load(V2_LOCKFILE_PATH).unwrap(); assert_eq!(lockfile.version, ResolveVersion::V2); assert_eq!(lockfile.packages.len(), 25); assert_eq!(lockfile.metadata.len(), 0); } /// Load example V3 `Cargo.lock` file #[test] fn load_example_v3_lockfile() { let lockfile = Lockfile::load(V3_LOCKFILE_PATH).unwrap(); assert_eq!(lockfile.version, ResolveVersion::V3); assert_eq!(lockfile.packages.len(), 25); assert_eq!(lockfile.metadata.len(), 0); } /// Ensure V3 lockfiles encode their version correctly. #[test] fn serialize_v3() { let lockfile = Lockfile::load(V3_LOCKFILE_PATH).unwrap(); let reserialized = lockfile.to_string(); let lockfile2 = reserialized.parse::().unwrap(); assert_eq!(lockfile2.version, ResolveVersion::V3); assert_eq!(lockfile2.packages, lockfile.packages); } /// Load example V4 `Cargo.lock` file #[test] fn load_example_v4_lockfile() { let lockfile = Lockfile::load(V4_LOCKFILE_PATH).unwrap(); assert_eq!(lockfile.version, ResolveVersion::V4); assert_eq!(lockfile.packages.len(), 25); assert_eq!(lockfile.metadata.len(), 0); let source_kind = lockfile .packages .iter() .find(|pkg| pkg.name.as_str() == "url") .and_then(|pkg| pkg.source.as_ref()) .map(|id| id.kind()) .unwrap(); assert_eq!( source_kind, &SourceKind::Git(GitReference::Tag("a-_+#$)z".into())) ); let source_kind = lockfile .packages .iter() .find(|pkg| pkg.name.as_str() == "toml") .and_then(|pkg| pkg.source.as_ref()) .map(|id| id.kind()) .unwrap(); assert_eq!( source_kind, &SourceKind::Git(GitReference::Branch("a-_+#$)z".into())) ); } /// Ensure V4 lockfiles encode their version correctly. #[test] fn serialize_v4() { let lockfile = Lockfile::load(V4_LOCKFILE_PATH).unwrap(); let reserialized = lockfile.to_string(); let lockfile2 = reserialized.parse::().unwrap(); assert_eq!(lockfile2.version, ResolveVersion::V4); assert_eq!(lockfile2.packages, lockfile.packages); } /// Ensure we can serialize a V2 lockfile as a V1 lockfile #[test] fn serialize_v2_to_v1() { let mut lockfile = Lockfile::load(V2_LOCKFILE_PATH).unwrap(); lockfile.version = ResolveVersion::V1; let reserialized = lockfile.to_string(); let lockfile2 = reserialized.parse::().unwrap(); assert_eq!(lockfile2.version, ResolveVersion::V1); assert_eq!(lockfile2.packages, lockfile.packages); } /// Ensure we can serialize a V1 lockfile as a V2 lockfile #[test] fn serialize_v1_to_v2() { let mut lockfile = Lockfile::load(V1_LOCKFILE_PATH).unwrap(); lockfile.version = ResolveVersion::V2; let reserialized = lockfile.to_string(); let lockfile2 = reserialized.parse::().unwrap(); assert_eq!(lockfile.packages, lockfile2.packages); } /// Test that encoded V1 lockfiles match what Cargo would normally write. #[test] fn serde_matches_v1() { let lockfile = Lockfile::load(V1_LOCKFILE_PATH).unwrap(); let reserialized = lockfile.to_string(); let file_content = fs::read_to_string(V1_LOCKFILE_PATH).unwrap(); assert_eq!(reserialized, file_content); } /// Test that encoded V2 lockfiles match what Cargo would normally write. #[test] fn serde_matches_v2() { let lockfile = Lockfile::load(V2_LOCKFILE_PATH).unwrap(); let reserialized = lockfile.to_string(); let file_content = fs::read_to_string(V2_LOCKFILE_PATH).unwrap(); assert_eq!(reserialized, file_content); } /// Test that encoded V3 lockfiles match what Cargo would normally write. #[test] fn serde_matches_v3() { let lockfile = Lockfile::load(V3_LOCKFILE_PATH).unwrap(); let reserialized = lockfile.to_string(); let file_content = fs::read_to_string(V3_LOCKFILE_PATH).unwrap(); assert_eq!(reserialized, file_content); } /// Test that encoded lockfiles with unused patch match what Cargo would normally write. #[test] fn serde_matches_unused_patch() { let file_path = "tests/examples/Cargo.lock.unused-patch"; let lockfile = Lockfile::load(file_path).unwrap(); let reserialized = lockfile.to_string(); let file_content = fs::read_to_string(file_path).unwrap(); assert_eq!(reserialized, file_content); } /// Dependency tree tests #[cfg(feature = "dependency-tree")] mod tree { use super::{Lockfile, V1_LOCKFILE_PATH, V2_LOCKFILE_PATH}; /// Compute a dependency graph from a non-trivial example V1 `Cargo.lock` #[test] fn compute_from_v1_example_lockfile() { let tree = Lockfile::load(V1_LOCKFILE_PATH) .unwrap() .dependency_tree() .unwrap(); assert_eq!(tree.nodes().len(), 141); } /// Compute a dependency graph from a non-trivial example V2 `Cargo.lock` #[test] fn compute_from_v2_example_lockfile() { let tree = Lockfile::load(V2_LOCKFILE_PATH) .unwrap() .dependency_tree() .unwrap(); assert_eq!(tree.nodes().len(), 25); } } #[test] fn source_disambiguation_single_version_different_registries() { source_disambiguation("single_version_different_registries"); } #[test] fn source_disambiguation_single_version_different_source_types() { source_disambiguation("single_version_different_source_types"); } #[test] fn source_disambiguation_two_versions_different_registries() { source_disambiguation("two_versions_different_registries"); } #[test] fn source_disambiguation_two_versions_different_source_types() { source_disambiguation("two_versions_different_source_types"); } #[test] fn source_disambiguation_two_versions_same_registry() { source_disambiguation("two_versions_same_registry"); } /// Test logic related to the disambiguation of dependency specifications when /// the lock file contains multiple versions of the same crate and/or multiple /// sources for the same crate. fn source_disambiguation(test_file_suffix: &str) { let original = fs::read_to_string(format!( "tests/examples/source_disambiguation/Cargo.lock.{}", test_file_suffix )) .unwrap(); let re_encoded = Lockfile::from_str(&original).unwrap().to_string(); assert_eq!(original, re_encoded); } /// Test that a lockfile with git sources are correctly encoded #[test] fn encoding_registry_and_git() { let lockfile = r#"# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "tower-buffer" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4887dc2a65d464c8b9b66e0e4d51c2fd6cf5b3373afc72805b0a60bce00446a" dependencies = [ "tracing 0.1.35", ] [[package]] name = "tracing" version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" [[package]] name = "tracing" version = "0.2.0" source = "git+https://github.com/tokio-rs/tracing.git?rev=1e09e50e8d15580b5929adbade9c782a6833e4a0#1e09e50e8d15580b5929adbade9c782a6833e4a0" [[package]] name = "example" version = "0.1.0" dependencies = [ "tower-buffer", "tracing 0.2.0", ] "#; assert_eq!(lockfile, Lockfile::from_str(lockfile).unwrap().to_string(),); } #[cfg(feature = "dependency-tree")] #[test] fn hash_fragment_dep() { use cargo_lock::Lockfile; let lockfile_str = r#"# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "parent" version = "0.1.0" source = "git+https://github.com/nope/parent?rev=xxxx#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" dependencies = [ "child 0.1.0 (git+https://github.com/nope/child?rev=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)", "child 0.1.0 (git+https://github.com/nope/child?rev=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)", ] [[package]] name = "child" version = "0.1.0" source = "git+https://github.com/nope/child?rev=aaaa#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" [[package]] name = "child" version = "0.1.0" source = "git+https://github.com/nope/child?rev=bbbb#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "#; let lockfile = Lockfile::from_str(lockfile_str).unwrap(); assert_eq!(lockfile_str, lockfile.to_string(),); // This will fail to resolve if child source URLs aren't normalized when deriving // dependencies from packges. let _tree = cargo_lock::dependency::tree::Tree::new(&lockfile).unwrap(); }