mod bundle_box;
use std::io::{Write, stderr};
use bundle_box::{BundleBoxProvider, Pack};
use insta::assert_snapshot;
use itertools::Itertools;
use resolvo::{
ConditionalRequirement, DependencyProvider, Interner, Problem, SolvableId, Solver,
UnsolvableOrCancelled, VersionSetId,
};
use tracing_test::traced_test;
fn transaction_to_string(interner: &impl Interner, solvables: &[SolvableId]) -> String {
use std::fmt::Write;
let mut buf = String::new();
for solvable in solvables
.iter()
.copied()
.map(|s| interner.display_solvable(s).to_string())
.sorted()
{
writeln!(buf, "{solvable}").unwrap();
}
buf
}
fn solve_unsat(mut provider: BundleBoxProvider, specs: &[&str]) -> String {
let requirements = provider.requirements(specs);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
match solver.solve(problem) {
Ok(_) => panic!("expected unsat, but a solution was found"),
Err(UnsolvableOrCancelled::Unsolvable(conflict)) => {
let graph = conflict.graph(&solver);
let mut output = stderr();
writeln!(output, "UNSOLVABLE:").unwrap();
graph
.graphviz(&mut output, solver.provider(), true)
.unwrap();
writeln!(output, "\n").unwrap();
conflict.display_user_friendly(&solver).to_string()
}
Err(UnsolvableOrCancelled::Cancelled(reason)) => *reason.downcast().unwrap(),
}
}
fn solve_snapshot(mut provider: BundleBoxProvider, specs: &[&str]) -> String {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap();
provider.sleep_before_return = true;
let requirements = provider.requirements(specs);
let mut solver = Solver::new(provider).with_runtime(runtime);
let problem = Problem::new().requirements(requirements);
match solver.solve(problem) {
Ok(solvables) => transaction_to_string(solver.provider(), &solvables),
Err(UnsolvableOrCancelled::Unsolvable(conflict)) => {
let graph = conflict.graph(&solver);
let mut output = stderr();
writeln!(output, "UNSOLVABLE:").unwrap();
graph
.graphviz(&mut output, solver.provider(), true)
.unwrap();
writeln!(output, "\n").unwrap();
conflict.display_user_friendly(&solver).to_string()
}
Err(UnsolvableOrCancelled::Cancelled(reason)) => *reason.downcast().unwrap(),
}
}
#[test]
fn test_unit_propagation_1() {
let mut provider = BundleBoxProvider::from_packages(&[("asdf", 1, vec![])]);
let requirements = provider.requirements(&["asdf"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let pool = &solver.provider().pool;
assert_eq!(solved.len(), 1);
let solvable = pool.resolve_solvable(solved[0]);
assert_eq!(pool.resolve_package_name(solvable.name), "asdf");
assert_eq!(solvable.record.version, 1);
}
#[test]
fn test_unit_propagation_nested() {
let mut provider = BundleBoxProvider::from_packages(&[
("asdf", 1u32, vec!["efgh"]),
("efgh", 4u32, vec![]),
("dummy", 6u32, vec![]),
]);
let requirements = provider.requirements(&["asdf"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let pool = &solver.provider().pool;
assert_eq!(solved.len(), 2);
let solvable = pool.resolve_solvable(solved[0]);
assert_eq!(pool.resolve_package_name(solvable.name), "asdf");
assert_eq!(solvable.record.version, 1);
let solvable = pool.resolve_solvable(solved[1]);
assert_eq!(pool.resolve_package_name(solvable.name), "efgh");
assert_eq!(solvable.record.version, 4);
}
#[test]
fn test_resolve_multiple() {
let mut provider = BundleBoxProvider::from_packages(&[
("asdf", 1, vec![]),
("asdf", 2, vec![]),
("efgh", 4, vec![]),
("efgh", 5, vec![]),
]);
let requirements = provider.requirements(&["asdf", "efgh"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let mut solved = solver.solve(problem).unwrap();
let pool = &solver.provider().pool;
assert_eq!(solved.len(), 2);
solved.sort_by_key(|&s| pool.resolve_package_name(pool.resolve_solvable(s).name));
let solvable = pool.resolve_solvable(solved[0]);
assert_eq!(pool.resolve_package_name(solvable.name), "asdf");
assert_eq!(solvable.record.version, 2);
let solvable = pool.resolve_solvable(solved[1]);
assert_eq!(pool.resolve_package_name(solvable.name), "efgh");
assert_eq!(solvable.record.version, 5);
}
#[test]
fn test_resolve_with_concurrent_metadata_fetching() {
let provider = BundleBoxProvider::from_packages(&[
("parent", 4, vec!["child1", "child2"]),
("child1", 3, vec![]),
("child2", 2, vec![]),
]);
let max_concurrent_requests = provider.concurrent_requests_max.clone();
let result = solve_snapshot(provider, &["parent"]);
insta::assert_snapshot!(result);
assert_eq!(2, max_concurrent_requests.get());
}
#[test]
#[traced_test]
fn test_resolve_with_conflict() {
let provider = BundleBoxProvider::from_packages(&[
("asdf", 4, vec!["conflicting 1"]),
("asdf", 3, vec!["conflicting 0"]),
("efgh", 7, vec!["conflicting 0"]),
("efgh", 6, vec!["conflicting 0"]),
("conflicting", 1, vec![]),
("conflicting", 0, vec![]),
]);
let result = solve_snapshot(provider, &["asdf", "efgh"]);
insta::assert_snapshot!(result);
}
#[test]
#[traced_test]
fn test_resolve_with_nonexisting() {
let mut provider = BundleBoxProvider::from_packages(&[
("asdf", 4, vec!["b"]),
("asdf", 3, vec![]),
("b", 1, vec!["idontexist"]),
]);
let requirements = provider.requirements(&["asdf"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let pool = &solver.provider().pool;
assert_eq!(solved.len(), 1);
let solvable = pool.resolve_solvable(solved[0]);
assert_eq!(pool.resolve_package_name(solvable.name), "asdf");
assert_eq!(solvable.record.version, 3);
}
#[test]
#[traced_test]
fn test_resolve_with_nested_deps() {
let mut provider = BundleBoxProvider::from_packages(&[
(
"apache-airflow",
3,
vec!["opentelemetry-api 2..4", "opentelemetry-exporter-otlp"],
),
(
"apache-airflow",
2,
vec!["opentelemetry-api 2..4", "opentelemetry-exporter-otlp"],
),
("apache-airflow", 1, vec![]),
("opentelemetry-api", 3, vec!["opentelemetry-sdk"]),
("opentelemetry-api", 2, vec![]),
("opentelemetry-api", 1, vec![]),
("opentelemetry-exporter-otlp", 1, vec!["opentelemetry-grpc"]),
("opentelemetry-grpc", 1, vec!["opentelemetry-api 1"]),
]);
let requirements = provider.requirements(&["apache-airflow"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let pool = &solver.provider().pool;
assert_eq!(solved.len(), 1);
let solvable = pool.resolve_solvable(solved[0]);
assert_eq!(pool.resolve_package_name(solvable.name), "apache-airflow");
assert_eq!(solvable.record.version, 1);
}
#[test]
#[traced_test]
fn test_resolve_with_unknown_deps() {
let mut provider = BundleBoxProvider::new();
provider.add_package(
"opentelemetry-api",
Pack::new(3).with_unknown_deps(),
&[],
&[],
);
provider.add_package("opentelemetry-api", Pack::new(2), &[], &[]);
let requirements = provider.requirements(&["opentelemetry-api"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let pool = &solver.provider().pool;
assert_eq!(solved.len(), 1);
let solvable = pool.resolve_solvable(solved[0]);
assert_eq!(
pool.resolve_package_name(solvable.name),
"opentelemetry-api"
);
assert_eq!(solvable.record.version, 2);
}
#[test]
#[traced_test]
fn test_resolve_and_cancel() {
let mut provider = BundleBoxProvider::new();
provider.add_package(
"opentelemetry-api",
Pack::new(3).with_unknown_deps(),
&[],
&[],
);
provider.add_package(
"opentelemetry-api",
Pack::new(2).cancel_during_get_dependencies(),
&[],
&[],
);
let error = solve_unsat(provider, &["opentelemetry-api"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_resolve_locked_top_level() {
let mut provider =
BundleBoxProvider::from_packages(&[("asdf", 4, vec![]), ("asdf", 3, vec![])]);
provider.set_locked("asdf", 3);
let requirements = provider.requirements(&["asdf"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let pool = &solver.provider().pool;
assert_eq!(solved.len(), 1);
let solvable_id = solved[0];
assert_eq!(pool.resolve_solvable(solvable_id).record.version, 3);
}
#[test]
fn test_resolve_ignored_locked_top_level() {
let mut provider = BundleBoxProvider::from_packages(&[
("asdf", 4, vec![]),
("asdf", 3, vec!["fgh"]),
("fgh", 1, vec![]),
]);
provider.set_locked("fgh", 1);
let requirements = provider.requirements(&["asdf"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let pool = &solver.provider().pool;
assert_eq!(solved.len(), 1);
let solvable = pool.resolve_solvable(solved[0]);
assert_eq!(pool.resolve_package_name(solvable.name), "asdf");
assert_eq!(solvable.record.version, 4);
}
#[test]
fn test_resolve_favor_without_conflict() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec![]),
("a", 2, vec![]),
("b", 1, vec![]),
("b", 2, vec![]),
]);
provider.set_favored("a", 1);
provider.set_favored("b", 1);
let result = solve_snapshot(provider, &["a", "b 2"]);
insta::assert_snapshot!(result, @r###"
a=1
b=2
"###);
}
#[test]
fn test_resolve_favor_with_conflict() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec!["c 1"]),
("a", 2, vec![]),
("b", 1, vec!["c 1"]),
("b", 2, vec!["c 2"]),
("c", 1, vec![]),
("c", 2, vec![]),
]);
provider.set_favored("a", 1);
provider.set_favored("b", 1);
provider.set_favored("c", 1);
let result = solve_snapshot(provider, &["a", "b 2"]);
insta::assert_snapshot!(result, @r###"
a=2
b=2
c=2
"###);
}
#[test]
fn test_resolve_cyclic() {
let mut provider =
BundleBoxProvider::from_packages(&[("a", 2, vec!["b 0..10"]), ("b", 5, vec!["a 2..4"])]);
let requirements = provider.requirements(&["a 0..100"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let result = transaction_to_string(solver.provider(), &solved);
insta::assert_snapshot!(result, @r###"
a=2
b=5
"###);
}
#[test]
fn test_resolve_union_requirements() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec![]),
("b", 1, vec![]),
("c", 1, vec!["a"]),
("d", 1, vec!["b"]),
("e", 1, vec!["a | b"]),
]);
provider.add_package("f", 1.into(), &["b"], &["a 2"]);
let result = solve_snapshot(provider, &["c | d", "e", "f"]);
assert_snapshot!(result, @r###"
b=1
d=1
e=1
f=1
"###);
}
#[test]
fn test_unsat_locked_and_excluded() {
let mut provider = BundleBoxProvider::from_packages(&[
("asdf", 1, vec!["c 2"]),
("c", 2, vec![]),
("c", 1, vec![]),
]);
provider.set_locked("c", 1);
insta::assert_snapshot!(solve_snapshot(provider, &["asdf"]));
}
#[test]
#[tracing_test::traced_test]
fn test_unsat_no_candidates_for_child_1() {
let provider = BundleBoxProvider::from_packages(&[("asdf", 1, vec!["c 2"]), ("c", 1, vec![])]);
let error = solve_unsat(provider, &["asdf"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_no_candidates_for_child_2() {
let provider = BundleBoxProvider::from_packages(&[("a", 41, vec!["B 0..20"])]);
let error = solve_unsat(provider, &["a 0..1000"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_missing_top_level_dep_1() {
let provider = BundleBoxProvider::from_packages(&[("asdf", 1, vec![])]);
let error = solve_unsat(provider, &["fghj"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_missing_top_level_dep_2() {
let provider = BundleBoxProvider::from_packages(&[("a", 41, vec!["b 15"]), ("b", 15, vec![])]);
let error = solve_unsat(provider, &["a 41", "b 14"]);
insta::assert_snapshot!(error);
}
#[test]
#[tracing_test::traced_test]
fn test_unsat_after_backtracking() {
let provider = BundleBoxProvider::from_packages(&[
("b", 7, vec!["d 1"]),
("b", 6, vec!["d 1"]),
("c", 1, vec!["d 2"]),
("c", 2, vec!["d 2"]),
("d", 2, vec![]),
("d", 1, vec![]),
("e", 1, vec![]),
("e", 2, vec![]),
]);
let error = solve_unsat(provider, &["b", "c", "e"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_incompatible_root_requirements() {
let provider = BundleBoxProvider::from_packages(&[("a", 2, vec![]), ("a", 5, vec![])]);
let error = solve_unsat(provider, &["a 0..4", "a 5..10"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_bluesky_conflict() {
let provider = BundleBoxProvider::from_packages(&[
("suitcase-utils", 54, vec![]),
("suitcase-utils", 53, vec![]),
(
"bluesky-widgets",
42,
vec![
"bluesky-live 0..10",
"numpy 0..10",
"python 0..10",
"suitcase-utils 0..54",
],
),
("bluesky-live", 1, vec![]),
("numpy", 1, vec![]),
("python", 1, vec![]),
]);
let error = solve_unsat(
provider,
&["bluesky-widgets 0..100", "suitcase-utils 54..100"],
);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_pubgrub_article() {
let provider = BundleBoxProvider::from_packages(&[
("menu", 15, vec!["dropdown 2..3"]),
("menu", 10, vec!["dropdown 1..2"]),
("dropdown", 2, vec!["icons 2"]),
("dropdown", 1, vec!["intl 3"]),
("icons", 2, vec![]),
("icons", 1, vec![]),
("intl", 5, vec![]),
("intl", 3, vec![]),
]);
let error = solve_unsat(provider, &["menu", "icons 1", "intl 5"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_applies_graph_compression() {
let provider = BundleBoxProvider::from_packages(&[
("a", 10, vec!["b"]),
("a", 9, vec!["b"]),
("b", 100, vec!["c 0..100"]),
("b", 42, vec!["c 0..100"]),
("c", 103, vec![]),
("c", 101, vec![]),
("c", 100, vec![]),
("c", 99, vec![]),
]);
let error = solve_unsat(provider, &["a", "c 101..104"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_constrains() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 10, vec!["b 50..100"]),
("a", 9, vec!["b 50..100"]),
("b", 50, vec![]),
("b", 42, vec![]),
]);
provider.add_package("c", 10.into(), &[], &["b 0..50"]);
provider.add_package("c", 8.into(), &[], &["b 0..50"]);
let error = solve_unsat(provider, &["a", "c"]);
insta::assert_snapshot!(error);
}
#[test]
#[tracing_test::traced_test]
fn test_unsat_constrains_2() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec!["b"]),
("a", 2, vec!["b"]),
("b", 1, vec!["c 1"]),
("b", 2, vec!["c 2"]),
]);
provider.add_package("c", 1.into(), &[], &["a 3"]);
provider.add_package("c", 2.into(), &[], &["a 3"]);
let error = solve_unsat(provider, &["a"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_missing_dep() {
let provider = BundleBoxProvider::from_packages(&[("a", 2, vec!["missing"]), ("a", 1, vec![])]);
insta::assert_snapshot!(solve_snapshot(provider, &["a"]));
}
#[test]
#[tracing_test::traced_test]
fn test_no_backtracking() {
let provider = BundleBoxProvider::from_packages(&[
("quetz-server", 2, vec!["pydantic 10..20"]),
("quetz-server", 1, vec!["pydantic 0..10"]),
("pydantic", 1, vec![]),
("pydantic", 2, vec![]),
("pydantic", 3, vec![]),
("pydantic", 4, vec![]),
("pydantic", 5, vec![]),
("pydantic", 6, vec![]),
("pydantic", 7, vec![]),
("pydantic", 8, vec![]),
("pydantic", 9, vec![]),
("pydantic", 10, vec![]),
("pydantic", 11, vec![]),
("pydantic", 12, vec![]),
("pydantic", 13, vec![]),
("pydantic", 14, vec![]),
]);
insta::assert_snapshot!(solve_snapshot(
provider,
&["quetz-server", "pydantic 0..10"]
));
}
#[test]
#[tracing_test::traced_test]
fn test_incremental_crash() {
let provider = BundleBoxProvider::from_packages(&[
("a", 3, vec!["missing"]),
("a", 2, vec!["missing"]),
("a", 1, vec!["b"]),
("b", 2, vec!["a 2..4"]),
("b", 1, vec![]),
]);
insta::assert_snapshot!(solve_snapshot(provider, &["a"]));
}
#[test]
#[traced_test]
fn test_excluded() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 2, vec!["b"]),
("a", 1, vec!["c"]),
("b", 1, vec![]),
("c", 1, vec![]),
]);
provider.exclude("b", 1, "it is externally excluded");
provider.exclude("c", 1, "it is externally excluded");
insta::assert_snapshot!(solve_snapshot(provider, &["a"]));
}
#[test]
fn test_merge_excluded() {
let mut provider = BundleBoxProvider::from_packages(&[("a", 1, vec![]), ("a", 2, vec![])]);
provider.exclude("a", 1, "it is externally excluded");
provider.exclude("a", 2, "it is externally excluded");
insta::assert_snapshot!(solve_snapshot(provider, &["a"]));
}
#[test]
#[traced_test]
fn test_merge_installable() {
let provider = BundleBoxProvider::from_packages(&[
("a", 1, vec![]),
("a", 2, vec![]),
("a", 3, vec![]),
("a", 4, vec![]),
]);
insta::assert_snapshot!(solve_snapshot(provider, &["a 0..3", "a 3..5"]));
}
#[test]
fn test_root_excluded() {
let mut provider = BundleBoxProvider::from_packages(&[("a", 1, vec![])]);
provider.exclude("a", 1, "it is externally excluded");
insta::assert_snapshot!(solve_snapshot(provider, &["a"]));
}
#[test]
fn test_constraints() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec!["b 0..10"]),
("b", 1, vec![]),
("b", 2, vec![]),
("c", 1, vec![]),
]);
let requirements = provider.requirements(&["a 0..10"]);
let constraints = provider.version_sets(&["b 1..2", "c"]);
let mut solver = Solver::new(provider);
let problem = Problem::new()
.requirements(requirements)
.constraints(constraints);
let solved = solver.solve(problem).unwrap();
let result = transaction_to_string(solver.provider(), &solved);
insta::assert_snapshot!(result, @r###"
a=1
b=1
"###);
}
#[test]
fn test_solve_with_additional() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec!["b 0..10"]),
("b", 1, vec![]),
("b", 2, vec![]),
("c", 1, vec![]),
("d", 1, vec![]),
("e", 1, vec!["d"]),
("locked", 1, vec![]),
("locked", 2, vec![]),
]);
provider.set_locked("locked", 2);
let requirements = provider.requirements(&["a 0..10"]);
let constraints = provider.version_sets(&["b 1..2", "c"]);
let extra_solvables = [
provider.solvable_id("b", 2),
provider.solvable_id("c", 1),
provider.solvable_id("e", 1),
provider.solvable_id("locked", 1),
provider.solvable_id("unknown-deps", Pack::new(1).with_unknown_deps()),
];
let mut solver = Solver::new(provider);
let problem = Problem::new()
.requirements(requirements)
.constraints(constraints)
.soft_requirements(extra_solvables);
let solved = solver.solve(problem).unwrap();
let result = transaction_to_string(solver.provider(), &solved);
assert_snapshot!(result, @r###"
a=1
b=1
c=1
d=1
e=1
locked=1
"###);
}
#[test]
fn test_solve_with_additional_with_constrains() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec!["b 0..10"]),
("b", 1, vec![]),
("b", 2, vec![]),
("b", 3, vec![]),
("c", 1, vec![]),
("d", 1, vec!["f"]),
("e", 1, vec!["c"]),
]);
provider.add_package("f", 1.into(), &[], &["c 2..3"]);
provider.add_package("g", 1.into(), &[], &["b 2..3"]);
provider.add_package("h", 1.into(), &[], &["b 1..2"]);
provider.add_package("i", 1.into(), &[], &[]);
provider.add_package("j", 1.into(), &["i"], &[]);
provider.add_package("k", 1.into(), &["i"], &[]);
provider.add_package("l", 1.into(), &["j", "k"], &[]);
let requirements = provider.requirements(&["a 0..10", "e"]);
let constraints = provider.version_sets(&["b 1..2", "c", "k 2..3"]);
let extra_solvables = [
provider.solvable_id("d", 1),
provider.solvable_id("g", 1),
provider.solvable_id("h", 1),
provider.solvable_id("j", 1),
provider.solvable_id("l", 1),
provider.solvable_id("k", 1),
];
let mut solver = Solver::new(provider);
let problem = Problem::new()
.requirements(requirements)
.constraints(constraints)
.soft_requirements(extra_solvables);
let solved = solver.solve(problem).unwrap();
let result = transaction_to_string(solver.provider(), &solved);
assert_snapshot!(result, @r###"
a=1
b=1
c=1
e=1
h=1
i=1
j=1
"###);
}
#[test]
fn test_snapshot() {
let provider = BundleBoxProvider::from_packages(&[
("menu", 15, vec!["dropdown 2..3"]),
("menu", 10, vec!["dropdown 1..2"]),
("dropdown", 2, vec!["icons 2"]),
("dropdown", 1, vec!["intl 3; if menu"]),
("icons", 2, vec![]),
("icons", 1, vec![]),
("intl", 5, vec![]),
("intl", 3, vec![]),
]);
let menu_name_id = provider.package_name("menu");
let snapshot = provider.into_snapshot();
#[cfg(feature = "serde")]
serialize_snapshot(&snapshot, "snapshot_pubgrub_menu.json");
let mut snapshot_provider = snapshot.provider();
let menu_req = snapshot_provider
.add_package_requirement(menu_name_id, "*")
.into();
assert_snapshot!(solve_for_snapshot(snapshot_provider, &[menu_req], &[]));
}
#[test]
fn test_snapshot_union_requirements() {
let mut provider = BundleBoxProvider::from_packages(&[
("icons", 2, vec![]),
("icons", 1, vec![]),
("intl", 5, vec![]),
("intl", 3, vec![]),
("union", 1, vec!["icons 2 | intl"]),
]);
let requirements = provider.requirements(&["intl", "union"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]));
}
#[test]
fn test_union_empty_requirements() {
let provider = BundleBoxProvider::from_packages(&[("a", 1, vec!["b 1 | c"]), ("b", 1, vec![])]);
let result = solve_snapshot(provider, &["a"]);
assert_snapshot!(result, @r"
a=1
b=1
");
}
#[test]
fn test_root_constraints() {
let mut provider =
BundleBoxProvider::from_packages(&[("icons", 1, vec![]), ("union", 1, vec!["icons"])]);
let requirements = provider.requirements(&["union"]);
let constraints = provider.version_sets(&["union 5"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &constraints));
}
#[test]
fn test_explicit_root_requirements() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec!["b"]),
("b", 1, vec!["c"]),
("b", 2, vec!["c 1..2"]),
("c", 1, vec![]),
("c", 2, vec![]),
("c", 3, vec![]),
("c", 4, vec![]),
("c", 5, vec![]),
]);
let requirements = provider.requirements(&["a", "c"]);
let mut solver = Solver::new(provider);
let problem = Problem::default().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let result = transaction_to_string(solver.provider(), &solved);
assert_snapshot!(result, @r###"
a=1
b=1
c=5
"###);
}
#[test]
fn test_conditional_requirements() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["baz; if bar"]),
("bar", 1, vec![]),
("baz", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=1
baz=1
foo=1
"###);
}
#[test]
fn test_conditional_unsolvable() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["baz 2; if bar"]),
("bar", 1, vec![]),
("baz", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
foo * cannot be installed because there are no viable options:
└─ foo 1 would require
└─ baz >=2, <3, for which no candidates were found.
The following packages are incompatible
└─ bar * can be installed with any of the following options:
└─ bar 1
"###);
}
#[test]
fn test_conditional_unsolvable_without_condition() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec![]),
("foo", 2, vec!["baz 2; if bar"]),
("bar", 1, vec![]),
("baz", 1, vec![]),
("baz", 2, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar", "baz 1"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=1
baz=1
foo=1
"###);
}
#[test]
fn test_conditional_requirements_version_set() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["baz; if bar 1"]),
("bar", 1, vec![]),
("bar", 2, vec![]),
("baz", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=2
foo=1
"###);
}
#[test]
fn test_conditional_and() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["icon; if bar and baz"]),
("bar", 1, vec![]),
("bar", 2, vec![]),
("baz", 1, vec![]),
("icon", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar", "baz"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=2
baz=1
foo=1
icon=1
"###);
}
#[test]
fn test_conditional_and_mismatch() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["icon; if bar and baz"]),
("bar", 1, vec![]),
("baz", 1, vec![]),
("icon", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=1
foo=1
"###);
}
#[test]
fn test_conditional_or() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["icon; if bar or baz"]),
("bar", 1, vec![]),
("baz", 1, vec![]),
("icon", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=1
foo=1
icon=1
"###);
}
#[test]
fn test_conditional_complex() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["icon; if bar and baz or menu"]),
("bar", 1, vec![]),
("baz", 1, vec![]),
("icon", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar", "baz"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=1
baz=1
foo=1
icon=1
"###);
}
#[test]
#[traced_test]
fn test_condition_missing_requirement() {
let mut provider =
BundleBoxProvider::from_packages(&[("menu", 1, vec!["bla; if intl"]), ("intl", 1, vec![])]);
let requirements = provider.requirements(&["menu"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @"menu=1");
}
#[cfg(feature = "serde")]
fn serialize_snapshot(
snapshot: &resolvo::snapshot::DependencySnapshot,
destination: impl AsRef<std::path::Path>,
) {
let file = std::io::BufWriter::new(std::fs::File::create(destination.as_ref()).unwrap());
serde_json::to_writer_pretty(file, snapshot).unwrap()
}
fn solve_for_snapshot<D: DependencyProvider>(
provider: D,
root_reqs: &[ConditionalRequirement],
root_constraints: &[VersionSetId],
) -> String {
let mut solver = Solver::new(provider);
let problem = Problem::new()
.requirements(root_reqs.iter().cloned().collect())
.constraints(root_constraints.iter().copied().map(Into::into).collect());
match solver.solve(problem) {
Ok(solvables) => transaction_to_string(solver.provider(), &solvables),
Err(UnsolvableOrCancelled::Unsolvable(conflict)) => {
let graph = conflict.graph(&solver);
let mut output = stderr();
writeln!(output, "UNSOLVABLE:").unwrap();
graph
.graphviz(&mut output, solver.provider(), true)
.unwrap();
writeln!(output, "\n").unwrap();
conflict.display_user_friendly(&solver).to_string()
}
Err(UnsolvableOrCancelled::Cancelled(reason)) => *reason.downcast().unwrap(),
}
}