Skip to content

Commit

Permalink
Test libsolv_rs in rattler_solve
Browse files Browse the repository at this point in the history
  • Loading branch information
aochagavia committed Jul 3, 2023
1 parent 35bf127 commit 79a266b
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 64 deletions.
1 change: 1 addition & 0 deletions crates/rattler_solve/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
177 changes: 114 additions & 63 deletions crates/rattler_solve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TAvailablePackagesIterator> {
/// An iterator over all available packages
pub available_packages: TAvailablePackagesIterator,
Expand Down Expand Up @@ -80,11 +81,12 @@ pub struct SolverTask<TAvailablePackagesIterator> {
#[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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -171,17 +182,23 @@ mod test_libsolv {
}
}

#[test]
fn test_solve_python() {
fn solve_real_world(specs: Vec<&str>) -> (Vec<String>, Vec<String>) {
let specs = specs
.iter()
.map(|s| MatchSpec::from_str(s).unwrap())
.collect::<Vec<_>>();

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
Expand All @@ -193,23 +210,44 @@ mod test_libsolv {
virtual_packages: Default::default(),
};

let mut pkgs = LibsolvBackend
.solve(solver_task)
.unwrap()
.into_iter()
.map(|pkg| {
format!(
"{} {} {}",
pkg.package_record.name, pkg.package_record.version, pkg.package_record.build
)
})
.collect::<Vec<_>>();
let pkgs1 = LibsolvBackend.solve(solver_task.clone()).unwrap();
let pkgs2 = LibsolvRsBackend.solve(solver_task).unwrap();

let extract_pkgs = |records: Vec<RepoDataRecord>| {
let mut pkgs = records
.into_iter()
.map(|pkg| {
format!(
"{} {} {}",
pkg.package_record.name,
pkg.package_record.version,
pkg.package_record.build
)
})
.collect::<Vec<_>>();

// 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))
}

// The order of packages is nondeterministic, so we sort them to ensure we can compare them
// to a previous run
pkgs.sort();
#[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]
Expand All @@ -226,23 +264,28 @@ 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:?}"),
}
}

#[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];
Expand Down Expand Up @@ -274,19 +317,18 @@ 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];
Expand Down Expand Up @@ -318,12 +360,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";

Expand All @@ -333,17 +373,16 @@ 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",
Expand All @@ -359,20 +398,19 @@ mod test_libsolv {
Vec::new(),
&["foo<4"],
false,
)?;
)
.unwrap();

assert_eq!(1, pkgs.len());

// Install
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",
Expand All @@ -388,18 +426,17 @@ 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",
Expand All @@ -415,20 +452,19 @@ mod test_libsolv {
Vec::new(),
&["foo<4"],
false,
)?;
)
.unwrap();

assert_eq!(pkgs.len(), 1);

// Uninstall
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",
Expand All @@ -444,16 +480,15 @@ 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(),
Expand All @@ -464,15 +499,14 @@ 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]
Expand All @@ -485,7 +519,10 @@ mod test_libsolv {
false,
);

assert!(matches!(result.err(), Some(SolveError::Unsolvable(_))));
assert!(matches!(
result.err(),
Some((SolveError::Unsolvable(_), SolveError::Unsolvable(_)))
));
}

#[cfg(test)]
Expand All @@ -495,7 +532,7 @@ mod test_libsolv {
virtual_packages: Vec<GenericVirtualPackage>,
match_specs: &[&str],
with_solv_file: bool,
) -> Result<Vec<RepoDataRecord>, SolveError> {
) -> Result<Vec<RepoDataRecord>, (SolveError, SolveError)> {
let repo_data = read_repodata(&repo_path);

#[cfg(target_family = "unix")]
Expand Down Expand Up @@ -533,19 +570,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)
}
}
Loading

0 comments on commit 79a266b

Please sign in to comment.