diff --git a/crates/rattler_solve/Cargo.toml b/crates/rattler_solve/Cargo.toml index 3069d7b65..980b1b645 100644 --- a/crates/rattler_solve/Cargo.toml +++ b/crates/rattler_solve/Cargo.toml @@ -26,6 +26,7 @@ cfg-if = "1.0.0" libsolv_rs = { version = "0.1.0", path = "../libsolv_rs" } [dev-dependencies] +rattler_repodata_gateway = { version = "0.5.0", path = "../rattler_repodata_gateway", features = ["sparse"] } insta = { version = "1.29.0", features = ["yaml"] } rstest = "0.17.0" serde_json = "1.0.96" diff --git a/crates/rattler_solve/src/lib.rs b/crates/rattler_solve/src/lib.rs index ca61db534..638979268 100644 --- a/crates/rattler_solve/src/lib.rs +++ b/crates/rattler_solve/src/lib.rs @@ -48,6 +48,7 @@ impl fmt::Display for SolveError { /// Represents a dependency resolution task, to be solved by one of the backends (currently only /// libsolv is supported) +#[cfg_attr(test, derive(Clone))] pub struct SolverTask { /// An iterator over all available packages pub available_packages: TAvailablePackagesIterator, @@ -80,11 +81,12 @@ pub struct SolverTask { #[cfg(test)] mod test_libsolv { use crate::libsolv::LibsolvBackend; - use crate::{LibsolvRepoData, SolveError, SolverBackend, SolverTask}; + use crate::{LibsolvRepoData, LibsolvRsBackend, SolveError, SolverBackend, SolverTask}; use rattler_conda_types::{ Channel, ChannelConfig, GenericVirtualPackage, MatchSpec, NoArchType, PackageRecord, RepoData, RepoDataRecord, Version, }; + use rattler_repodata_gateway::sparse::SparseRepoData; use std::str::FromStr; use url::Url; @@ -134,6 +136,15 @@ mod test_libsolv { ) } + fn read_sparse_repodata(path: &str) -> SparseRepoData { + SparseRepoData::new( + Channel::from_str("dummy", &ChannelConfig::default()).unwrap(), + "dummy".to_string(), + path, + ) + .unwrap() + } + fn installed_package( channel: &str, subdir: &str, @@ -171,17 +182,23 @@ mod test_libsolv { } } - #[test] - fn test_solve_python() { + fn solve_real_world(specs: Vec<&str>) -> (Vec, Vec) { + let specs = specs + .iter() + .map(|s| MatchSpec::from_str(s).unwrap()) + .collect::>(); + let json_file = conda_json_path(); let json_file_noarch = conda_json_path_noarch(); - let repo_data = read_repodata(&json_file); - let repo_data_noarch = read_repodata(&json_file_noarch); - - let available_packages = vec![repo_data, repo_data_noarch]; + let sparse_repo_datas = vec![ + read_sparse_repodata(&json_file), + read_sparse_repodata(&json_file_noarch), + ]; - let specs = vec![MatchSpec::from_str("python=3.9").unwrap()]; + let names = specs.iter().map(|s| s.name.clone().unwrap()); + let available_packages = + SparseRepoData::load_records_recursive(&sparse_repo_datas, names).unwrap(); let solver_task = SolverTask { available_packages: available_packages @@ -193,9 +210,11 @@ mod test_libsolv { virtual_packages: Default::default(), }; - let mut pkgs = LibsolvBackend - .solve(solver_task) - .unwrap() + let pkgs1 = LibsolvBackend.solve(solver_task.clone()).unwrap(); + let pkgs2 = LibsolvRsBackend.solve(solver_task).unwrap(); + + let extract_pkgs = |records: Vec| { + let mut pkgs = records .into_iter() .map(|pkg| { format!( @@ -205,11 +224,27 @@ mod test_libsolv { }) .collect::>(); - // The order of packages is nondeterministic, so we sort them to ensure we can compare them - // to a previous run - pkgs.sort(); + // The order of packages is nondeterministic, so we sort them to ensure we can compare them + // to a previous run + pkgs.sort(); + pkgs + }; + + (extract_pkgs(pkgs1), extract_pkgs(pkgs2)) + } + + #[test] + fn test_solve_tensorboard() { + let (libsolv_pkgs, libsolv_rs_pkgs) = solve_real_world(vec!["tensorboard=2.1.1", "grpc-cpp=1.39.1"]); + insta::assert_yaml_snapshot!("solve_tensorboard_libsolv", libsolv_pkgs); + insta::assert_yaml_snapshot!("solve_tensorboard_libsolv_rs", libsolv_rs_pkgs); + } - insta::assert_yaml_snapshot!(pkgs); + #[test] + fn test_solve_python() { + let (libsolv_pkgs, libsolv_rs_pkgs) = solve_real_world(vec!["python=3.9"]); + insta::assert_yaml_snapshot!("solve_python_libsolv", libsolv_pkgs); + insta::assert_yaml_snapshot!("solve_python_libsolv_rs", libsolv_rs_pkgs); } #[test] @@ -226,8 +261,9 @@ mod test_libsolv { let err = result.err().unwrap(); match err { - SolveError::Unsolvable(errors) => { - assert_eq!(errors, vec!["nothing provides requested asdfasdf"]) + (SolveError::Unsolvable(libsolv_errors), SolveError::Unsolvable(libsolv_rs_errors)) => { + assert_eq!(libsolv_errors, vec!["nothing provides requested asdfasdf"]); + assert_eq!(libsolv_rs_errors, vec!["No candidates where found for asdfasdf.\n"]); } _ => panic!("Unexpected error: {err:?}"), } @@ -235,14 +271,14 @@ mod test_libsolv { #[test] #[cfg(target_family = "unix")] - fn test_solve_with_cached_solv_file_install_new() -> anyhow::Result<()> { + fn test_solve_with_cached_solv_file_install_new() { let pkgs = solve( dummy_channel_json_path(), Vec::new(), Vec::new(), &["foo<4"], true, - )?; + ).unwrap(); assert_eq!(1, pkgs.len()); let info = &pkgs[0]; @@ -274,19 +310,17 @@ mod test_libsolv { .unwrap(), info.package_record.md5.as_ref().unwrap() ); - - Ok(()) } #[test] - fn test_solve_dummy_repo_install_new() -> anyhow::Result<()> { + fn test_solve_dummy_repo_install_new() { let pkgs = solve( dummy_channel_json_path(), Vec::new(), Vec::new(), &["foo<4"], false, - )?; + ).unwrap(); assert_eq!(1, pkgs.len()); let info = &pkgs[0]; @@ -318,12 +352,10 @@ mod test_libsolv { .unwrap(), info.package_record.md5.as_ref().unwrap() ); - - Ok(()) } #[test] - fn test_solve_dummy_repo_prefers_conda_package() -> anyhow::Result<()> { + fn test_solve_dummy_repo_prefers_conda_package() { // There following package is provided as .tar.bz and as .conda in repodata.json let match_spec = "foo=3.0.2=py36h1af98f8_1"; @@ -333,17 +365,15 @@ mod test_libsolv { Vec::new(), &[match_spec], false, - )?; + ).unwrap(); // The .conda entry is selected for installing assert_eq!(operations.len(), 1); assert_eq!(operations[0].file_name, "foo-3.0.2-py36h1af98f8_1.conda"); - - Ok(()) } #[test] - fn test_solve_dummy_repo_install_noop() -> anyhow::Result<()> { + fn test_solve_dummy_repo_install_noop() { let already_installed = vec![installed_package( "conda-forge", "linux-64", @@ -359,7 +389,7 @@ mod test_libsolv { Vec::new(), &["foo<4"], false, - )?; + ).unwrap(); assert_eq!(1, pkgs.len()); @@ -367,12 +397,10 @@ mod test_libsolv { let info = &pkgs[0]; assert_eq!("foo", &info.package_record.name); assert_eq!("3.0.2", &info.package_record.version.to_string()); - - Ok(()) } #[test] - fn test_solve_dummy_repo_upgrade() -> anyhow::Result<()> { + fn test_solve_dummy_repo_upgrade() { let already_installed = vec![installed_package( "conda-forge", "linux-64", @@ -388,18 +416,16 @@ mod test_libsolv { Vec::new(), &["foo>=4"], false, - )?; + ).unwrap(); // Install let info = &pkgs[0]; assert_eq!("foo", &info.package_record.name); assert_eq!("4.0.2", &info.package_record.version.to_string()); - - Ok(()) } #[test] - fn test_solve_dummy_repo_downgrade() -> anyhow::Result<()> { + fn test_solve_dummy_repo_downgrade() { let already_installed = vec![installed_package( "conda-forge", "linux-64", @@ -415,7 +441,7 @@ mod test_libsolv { Vec::new(), &["foo<4"], false, - )?; + ).unwrap(); assert_eq!(pkgs.len(), 1); @@ -423,12 +449,10 @@ mod test_libsolv { let info = &pkgs[0]; assert_eq!("foo", &info.package_record.name); assert_eq!("3.0.2", &info.package_record.version.to_string()); - - Ok(()) } #[test] - fn test_solve_dummy_repo_remove() -> anyhow::Result<()> { + fn test_solve_dummy_repo_remove() { let already_installed = vec![installed_package( "conda-forge", "linux-64", @@ -444,16 +468,14 @@ mod test_libsolv { Vec::new(), &[], false, - )?; + ).unwrap(); // Should be no packages! assert_eq!(0, pkgs.len()); - - Ok(()) } #[test] - fn test_solve_dummy_repo_with_virtual_package() -> anyhow::Result<()> { + fn test_solve_dummy_repo_with_virtual_package() { let pkgs = solve( dummy_channel_json_path(), Vec::new(), @@ -464,15 +486,13 @@ mod test_libsolv { }], &["bar"], false, - )?; + ).unwrap(); assert_eq!(pkgs.len(), 1); let info = &pkgs[0]; assert_eq!("bar", &info.package_record.name); assert_eq!("1.2.3", &info.package_record.version.to_string()); - - Ok(()) } #[test] @@ -485,7 +505,7 @@ mod test_libsolv { false, ); - assert!(matches!(result.err(), Some(SolveError::Unsolvable(_)))); + assert!(matches!(result.err(), Some((SolveError::Unsolvable(_), SolveError::Unsolvable(_))))); } #[cfg(test)] @@ -495,7 +515,7 @@ mod test_libsolv { virtual_packages: Vec, match_specs: &[&str], with_solv_file: bool, - ) -> Result, SolveError> { + ) -> Result, (SolveError, SolveError)> { let repo_data = read_repodata(&repo_path); #[cfg(target_family = "unix")] @@ -533,19 +553,33 @@ mod test_libsolv { pinned_packages: Vec::new(), }; - let pkgs = LibsolvBackend.solve(task)?; + let pkgs_libsolv = LibsolvBackend.solve(task.clone()); + let pkgs_libsolv_rs = LibsolvRsBackend.solve(task); + if pkgs_libsolv.is_ok() != pkgs_libsolv_rs.is_ok() { + panic!("one of libsolv and libsolv_rs returns unsat, the other sat!"); + } + + if let Err(pkgs_libsolv) = pkgs_libsolv { + return Err((pkgs_libsolv, pkgs_libsolv_rs.err().unwrap())); + } + + let pkgs_libsolv = pkgs_libsolv.unwrap(); + let pkgs_libsolv_rs = pkgs_libsolv_rs.unwrap(); + if pkgs_libsolv != pkgs_libsolv_rs { + panic!("libsolv and libsolv_rs return different results!"); + } - for pkg in pkgs.iter() { + for pkg in pkgs_libsolv.iter() { println!( "{} {} {}", pkg.package_record.name, pkg.package_record.version, pkg.package_record.build ) } - if pkgs.is_empty() { + if pkgs_libsolv.is_empty() { println!("No packages in the environment!"); } - Ok(pkgs) + Ok(pkgs_libsolv) } } diff --git a/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_python.snap b/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_python_libsolv.snap similarity index 96% rename from crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_python.snap rename to crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_python_libsolv.snap index 44bf53ac2..5847bf573 100644 --- a/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_python.snap +++ b/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_python_libsolv.snap @@ -1,6 +1,6 @@ --- source: crates/rattler_solve/src/lib.rs -expression: pkgs +expression: libsolv_pkgs --- - _libgcc_mutex 0.1 conda_forge - _openmp_mutex 4.5 2_gnu diff --git a/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_python_libsolv_rs.snap b/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_python_libsolv_rs.snap new file mode 100644 index 000000000..4c0977ba1 --- /dev/null +++ b/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_python_libsolv_rs.snap @@ -0,0 +1,25 @@ +--- +source: crates/rattler_solve/src/lib.rs +expression: libsolv_rs_pkgs +--- +- _libgcc_mutex 0.1 conda_forge +- _openmp_mutex 4.5 2_gnu +- bzip2 1.0.8 h7f98852_4 +- ca-certificates 2022.6.15 ha878542_0 +- ld_impl_linux-64 2.36.1 hea4e1c9_2 +- libffi 3.4.2 h7f98852_5 +- libgcc-ng 12.1.0 h8d9b700_16 +- libgomp 12.1.0 h8d9b700_16 +- libnsl 2.0.0 h7f98852_0 +- libsqlite 3.39.2 h753d276_1 +- libuuid 2.32.1 h7f98852_1000 +- libzlib 1.2.12 h166bdaf_2 +- ncurses 6.3 h27087fc_1 +- openssl 3.0.5 h166bdaf_1 +- python 3.9.13 h2660328_0_cpython +- readline 8.1.2 h0f457ee_0 +- sqlite 3.39.2 h4ff8645_1 +- tk 8.6.12 h27826a3_0 +- tzdata 2021e he74cb21_0 +- xz 5.2.6 h166bdaf_0 + diff --git a/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_tensorboard_libsolv.snap b/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_tensorboard_libsolv.snap new file mode 100644 index 000000000..5201e76a2 --- /dev/null +++ b/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_tensorboard_libsolv.snap @@ -0,0 +1,83 @@ +--- +source: crates/rattler_solve/src/lib.rs +expression: libsolv_pkgs +--- +- _libgcc_mutex 0.1 conda_forge +- _openmp_mutex 4.5 2_gnu +- abseil-cpp 20210324.2 h9c3ff4c_0 +- absl-py 1.0.0 pyhd8ed1ab_0 +- aiohttp 3.8.1 py39hb9d737c_1 +- aiosignal 1.2.0 pyhd8ed1ab_0 +- async-timeout 4.0.2 pyhd8ed1ab_0 +- attrs 21.4.0 pyhd8ed1ab_0 +- blinker 1.4 py_1 +- brotlipy 0.7.0 py39hb9d737c_1004 +- bzip2 1.0.8 h7f98852_4 +- c-ares 1.18.1 h7f98852_0 +- ca-certificates 2022.6.15 ha878542_0 +- cachetools 5.0.0 pyhd8ed1ab_0 +- certifi 2022.6.15 py39hf3d152e_0 +- cffi 1.15.1 py39he91dace_0 +- charset-normalizer 2.0.11 pyhd8ed1ab_0 +- click 8.1.3 py39hf3d152e_0 +- cryptography 37.0.4 py39hd97740a_0 +- frozenlist 1.3.1 py39hb9d737c_0 +- google-auth 2.6.0 pyh6c4a22f_1 +- google-auth-oauthlib 0.4.1 py_2 +- grpc-cpp 1.39.1 h850795e_1 +- grpcio 1.46.3 py39h0f497a6_0 +- idna 3.3 pyhd8ed1ab_0 +- importlib-metadata 4.11.4 py39hf3d152e_0 +- ld_impl_linux-64 2.36.1 hea4e1c9_2 +- libblas 3.9.0 16_linux64_openblas +- libcblas 3.9.0 16_linux64_openblas +- libffi 3.4.2 h7f98852_5 +- libgcc-ng 12.1.0 h8d9b700_16 +- libgfortran-ng 12.1.0 h69a702a_16 +- libgfortran5 12.1.0 hdcd56e2_16 +- libgomp 12.1.0 h8d9b700_16 +- liblapack 3.9.0 16_linux64_openblas +- libnsl 2.0.0 h7f98852_0 +- libopenblas 0.3.21 pthreads_h78a6416_1 +- libprotobuf 3.16.0 h780b84a_0 +- libsqlite 3.39.2 h753d276_1 +- libstdcxx-ng 12.1.0 ha89aaad_16 +- libuuid 2.32.1 h7f98852_1000 +- libzlib 1.2.12 h166bdaf_2 +- markdown 3.3.6 pyhd8ed1ab_0 +- multidict 6.0.2 py39hb9d737c_1 +- ncurses 6.3 h27087fc_1 +- numpy 1.23.2 py39hba7629e_0 +- oauthlib 3.2.0 pyhd8ed1ab_0 +- openssl 1.1.1q h166bdaf_0 +- protobuf 3.16.0 py39he80948d_0 +- pyasn1 0.4.8 py_0 +- pyasn1-modules 0.2.7 py_0 +- pycparser 2.21 pyhd8ed1ab_0 +- pyjwt 2.3.0 pyhd8ed1ab_1 +- pyopenssl 22.0.0 pyhd8ed1ab_0 +- pysocks 1.7.1 py39hf3d152e_5 +- python 3.9.13 h9a8a25e_0_cpython +- python_abi 3.9 2_cp39 +- pyu2f 0.1.5 pyhd8ed1ab_0 +- re2 2021.09.01 h9c3ff4c_0 +- readline 8.1.2 h0f457ee_0 +- requests 2.27.1 pyhd8ed1ab_0 +- requests-oauthlib 1.3.1 pyhd8ed1ab_0 +- rsa 4.8 pyhd8ed1ab_0 +- setuptools 65.0.2 py39hf3d152e_0 +- six 1.16.0 pyh6c4a22f_0 +- sqlite 3.39.2 h4ff8645_1 +- tensorboard 2.1.1 py_1 +- tk 8.6.12 h27826a3_0 +- typing-extensions 4.0.1 hd8ed1ab_0 +- typing_extensions 4.0.1 pyha770c72_0 +- tzdata 2021e he74cb21_0 +- urllib3 1.26.8 pyhd8ed1ab_1 +- werkzeug 2.0.3 pyhd8ed1ab_1 +- wheel 0.37.1 pyhd8ed1ab_0 +- xz 5.2.6 h166bdaf_0 +- yarl 1.7.2 py39hb9d737c_2 +- zipp 3.7.0 pyhd8ed1ab_1 +- zlib 1.2.12 h166bdaf_2 + diff --git a/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_tensorboard_libsolv_rs.snap b/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_tensorboard_libsolv_rs.snap new file mode 100644 index 000000000..9618daa12 --- /dev/null +++ b/crates/rattler_solve/src/snapshots/rattler_solve__test_libsolv__solve_tensorboard_libsolv_rs.snap @@ -0,0 +1,83 @@ +--- +source: crates/rattler_solve/src/lib.rs +expression: libsolv_rs_pkgs +--- +- _libgcc_mutex 0.1 conda_forge +- _openmp_mutex 4.5 2_gnu +- abseil-cpp 20210324.2 h9c3ff4c_0 +- absl-py 1.0.0 pyhd8ed1ab_0 +- aiohttp 3.8.1 py39hb9d737c_1 +- aiosignal 1.2.0 pyhd8ed1ab_0 +- async-timeout 4.0.2 pyhd8ed1ab_0 +- attrs 21.4.0 pyhd8ed1ab_0 +- blinker 1.4 py_1 +- brotlipy 0.7.0 py39hb9d737c_1004 +- bzip2 1.0.8 h7f98852_4 +- c-ares 1.18.1 h7f98852_0 +- ca-certificates 2022.6.15 ha878542_0 +- cachetools 5.0.0 pyhd8ed1ab_0 +- certifi 2022.6.15 py39hf3d152e_0 +- cffi 1.15.1 py39he91dace_0 +- charset-normalizer 2.0.11 pyhd8ed1ab_0 +- click 8.1.3 py39hf3d152e_0 +- cryptography 37.0.4 py39hd97740a_0 +- frozenlist 1.3.1 py39hb9d737c_0 +- google-auth 2.6.0 pyh6c4a22f_1 +- google-auth-oauthlib 0.4.1 py_2 +- grpc-cpp 1.39.1 h850795e_1 +- grpcio 1.46.3 py39h0f497a6_0 +- idna 3.3 pyhd8ed1ab_0 +- importlib-metadata 4.11.4 py39hf3d152e_0 +- ld_impl_linux-64 2.36.1 hea4e1c9_2 +- libblas 3.9.0 16_linux64_openblas +- libcblas 3.9.0 16_linux64_openblas +- libffi 3.4.2 h7f98852_5 +- libgcc-ng 12.1.0 h8d9b700_16 +- libgfortran-ng 12.1.0 h69a702a_16 +- libgfortran5 12.1.0 hdcd56e2_16 +- libgomp 12.1.0 h8d9b700_16 +- liblapack 3.9.0 16_linux64_openblas +- libnsl 2.0.0 h7f98852_0 +- libopenblas 0.3.21 pthreads_h78a6416_1 +- libprotobuf 3.16.0 h780b84a_0 +- libsqlite 3.39.2 h753d276_1 +- libstdcxx-ng 12.1.0 ha89aaad_16 +- libuuid 2.32.1 h7f98852_1000 +- libzlib 1.2.12 h166bdaf_2 +- markdown 3.3.6 pyhd8ed1ab_0 +- multidict 6.0.2 py39hb9d737c_1 +- ncurses 6.3 h27087fc_1 +- numpy 1.23.2 py39hba7629e_0 +- oauthlib 3.2.0 pyhd8ed1ab_0 +- openssl 1.1.1q h166bdaf_0 +- protobuf 3.16.0 py39he80948d_0 +- pyasn1 0.4.8 py_0 +- pyasn1-modules 0.2.7 py_0 +- pycparser 2.21 pyhd8ed1ab_0 +- pyjwt 2.3.0 pyhd8ed1ab_1 +- pyopenssl 22.0.0 pyhd8ed1ab_0 +- pysocks 1.7.1 py39hf3d152e_5 +- python 3.9.13 h9a8a25e_0_cpython +- python_abi 3.9 2_cp39 +- pyu2f 0.1.5 pyhd8ed1ab_0 +- re2 2021.09.01 h9c3ff4c_0 +- readline 8.1.2 h0f457ee_0 +- requests 2.27.1 pyhd8ed1ab_0 +- requests-oauthlib 1.3.1 pyhd8ed1ab_0 +- rsa 4.8 pyhd8ed1ab_0 +- setuptools 65.0.2 py39hf3d152e_0 +- six 1.16.0 pyh6c4a22f_0 +- sqlite 3.39.2 h4ff8645_1 +- tensorboard 2.1.1 py_1 +- tk 8.6.12 h27826a3_0 +- typing-extensions 4.0.1 hd8ed1ab_0 +- typing_extensions 4.0.1 pyha770c72_0 +- tzdata 2021e he74cb21_0 +- urllib3 1.26.8 pyhd8ed1ab_1 +- werkzeug 2.0.3 pyhd8ed1ab_1 +- wheel 0.37.1 pyhd8ed1ab_0 +- xz 5.2.6 h166bdaf_0 +- yarl 1.7.2 py39hb9d737c_2 +- zipp 3.7.0 pyhd8ed1ab_1 +- zlib 1.2.12 h166bdaf_2 +