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<SolvableId = SolvableId>,
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_no_candidates_distinct_requirements() {
let provider = BundleBoxProvider::from_packages(&[
("a", 1, vec!["b 41..42"]),
("a", 2, vec!["b 41..42"]),
("a", 3, vec!["b 42..43"]),
("a", 4, vec!["b 43..44"]),
]);
let error = solve_unsat(provider, &["a"]);
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_soft_requirement_forbid_clause_conflict() {
let mut provider = BundleBoxProvider::from_packages(&[("a", 1, vec![]), ("a", 2, vec!["a"])]);
let requirements = provider.requirements(&["a 1..2"]);
let extra_solvables = [provider.solvable_id("a", 2)];
let mut solver = Solver::new(provider);
let problem = Problem::new()
.requirements(requirements)
.soft_requirements(extra_solvables);
let solved = solver.solve(problem).unwrap();
let result = transaction_to_string(solver.provider(), &solved);
assert_snapshot!(result, @r###"
a=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_highest_version_set_not_shadowed() {
let provider = BundleBoxProvider::from_packages(&[("a", 1, vec!["b 1"]), ("b", 1, vec![])]);
let a_name_id = provider.package_name("a");
let snapshot = provider.into_snapshot();
let mut snapshot_provider = snapshot.provider();
let a_req = snapshot_provider
.add_package_requirement(a_name_id, "*")
.into();
assert_snapshot!(solve_for_snapshot(snapshot_provider, &[a_req], &[]), @r###"
a=1
b=1
"###);
}
#[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");
}
#[test]
#[traced_test]
fn test_lazy_conditional_skips_unreached_candidates() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["bla; if bar"]),
("bla", 1, vec!["dep"]),
("dep", 1, vec![]),
("bar", 1, vec![]),
]);
let requirements = provider.requirements(&["foo"]);
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);
assert_snapshot!(result, @"foo=1");
let fetched = solver.provider().requested_package_names();
assert!(
!fetched.iter().any(|name| name == "bla"),
"expected `bla` to remain unfetched, got {fetched:?}"
);
assert!(
!fetched.iter().any(|name| name == "dep"),
"expected `dep` to remain unfetched, got {fetched:?}"
);
}
#[test]
#[traced_test]
fn test_lazy_conditional_fires_when_condition_holds() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["bla; if bar"]),
("bla", 1, vec![]),
("bar", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=1
bla=1
foo=1
"###);
}
#[test]
#[traced_test]
fn test_lazy_conditional_multi_disjunct_fires_per_disjunct() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["bla; if a or b"]),
("bla", 1, vec![]),
("a", 1, vec![]),
("b", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "a"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
a=1
bla=1
foo=1
"###);
}
#[test]
#[traced_test]
fn test_lazy_conditional_shared_condition_fires_all() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["bla; if cond", "ext; if cond"]),
("bla", 1, vec![]),
("ext", 1, vec![]),
("cond", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "cond"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bla=1
cond=1
ext=1
foo=1
"###);
}
#[test]
#[traced_test]
fn test_lazy_conditional_late_arriving_conflict_is_reported() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["bla 2; if bar"]),
("bar", 1, vec![]),
("bla", 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
└─ bla >=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]
#[traced_test]
fn test_lazy_conditional_survives_backtracking() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 2, vec!["bla; if cond", "bla 2"]),
("foo", 1, vec!["bla; if cond"]),
("bla", 1, vec![]),
("cond", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "cond"]);
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);
assert_snapshot!(result, @r###"
bla=1
cond=1
foo=1
"###);
assert_eq!(
solver.deferred_requirements_count(),
0,
"deferred entries must be drained on first fire and never re-added"
);
let fresh_clauses = {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 2, vec!["bla; if cond", "bla 2"]),
("foo", 1, vec!["bla; if cond"]),
("bla", 1, vec![]),
("cond", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "cond"]);
let mut fresh_solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let _ = fresh_solver.solve(problem).unwrap();
fresh_solver.clause_count()
};
assert_eq!(
solver.clause_count(),
fresh_clauses,
"clause count must match a fresh re-solve; a mismatch would mean a \
deferred entry was encoded more than once"
);
}
#[test]
#[traced_test]
fn test_lazy_conditional_prefetches_forbidden_candidate() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec!["gated; if cond"]),
("trigger", 1, vec!["cond"]),
("cond", 1, vec![]),
("gated", 2, vec![]),
("dep", 1, vec![]),
("z", 1, vec![]),
("w", 1, vec![]),
("w", 2, vec![]),
("w", 3, vec![]),
("w", 4, vec![]),
]);
provider.hint_dependencies_available = true;
provider.add_package("c", 1.into(), &[], &["gated 2..3"]);
provider.add_package("gated", 1.into(), &["dep"], &["z 2..3", "w 5..6"]);
let requirements = provider.requirements(&["c", "a", "trigger"]);
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);
assert_snapshot!(result, @r###"
a=1
c=1
cond=1
gated=2
trigger=1
"###);
}
#[test]
#[traced_test]
fn test_lazy_conditional_unconditional_unaffected() {
let mut provider =
BundleBoxProvider::from_packages(&[("foo", 1, vec!["bla"]), ("bla", 1, vec![])]);
let requirements = provider.requirements(&["foo"]);
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);
assert_snapshot!(result, @r###"
bla=1
foo=1
"###);
let fetched = solver.provider().requested_package_names();
assert!(
fetched.iter().any(|name| name == "bla"),
"unconditional requirements must still fetch their candidates; got {fetched:?}"
);
}
#[test]
#[traced_test]
fn test_lazy_conditional_empty_complement_fires() {
let mut provider = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["bla; if bar"]),
("bar", 1, vec![]),
("bar", 2, vec![]),
("bla", 1, vec![]),
]);
let requirements = provider.requirements(&["foo", "bar"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=2
bla=1
foo=1
"###);
}
#[test]
fn test_lazy_conditional_determinism() {
fn build_provider() -> BundleBoxProvider {
BundleBoxProvider::from_packages(&[
("foo", 1, vec!["bla; if a", "ext; if b"]),
("foo", 2, vec!["bla; if a", "ext; if b"]),
("bla", 1, vec![]),
("ext", 1, vec![]),
("a", 1, vec![]),
("b", 1, vec![]),
])
}
let baseline = {
let mut provider = build_provider();
let requirements = provider.requirements(&["foo", "a", "b"]);
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
transaction_to_string(solver.provider(), &solved)
};
for _ in 0..9 {
let mut provider = build_provider();
let requirements = provider.requirements(&["foo", "a", "b"]);
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);
assert_eq!(result, baseline, "non-deterministic solve");
}
}
mod test_lazy_conditional_multi_conjunct_requires_all {
use super::*;
fn build_provider() -> BundleBoxProvider {
BundleBoxProvider::from_packages(&[
("a", 1, vec!["b; if cond_a and cond_b"]),
("b", 1, vec![]),
("cond_a", 1, vec![]),
("cond_b", 1, vec![]),
])
}
#[test]
#[traced_test]
fn neither_selected() {
let mut provider = build_provider();
let requirements = provider.requirements(&["a"]);
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);
assert_snapshot!(result, @"a=1");
let fetched = solver.provider().requested_package_names();
assert!(
!fetched.iter().any(|n| n == "b"),
"expected `b` to remain unfetched when neither conjunct holds, \
got {fetched:?}"
);
assert_eq!(
solver.deferred_requirements_count(),
1,
"the AND-disjunct never held, its deferred entry must remain"
);
}
#[test]
#[traced_test]
fn only_first_selected() {
let mut provider = build_provider();
let requirements = provider.requirements(&["a", "cond_a"]);
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);
assert_snapshot!(result, @r###"
a=1
cond_a=1
"###);
let fetched = solver.provider().requested_package_names();
assert!(
!fetched.iter().any(|n| n == "b"),
"expected `b` to remain unfetched when only one conjunct holds, \
got {fetched:?}"
);
assert_eq!(
solver.deferred_requirements_count(),
1,
"second conjunct (cond_b) did not hold, deferred entry must remain"
);
}
#[test]
#[traced_test]
fn both_selected() {
let mut provider = build_provider();
let requirements = provider.requirements(&["a", "cond_a", "cond_b"]);
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);
assert_snapshot!(result, @r###"
a=1
b=1
cond_a=1
cond_b=1
"###);
assert_eq!(
solver.deferred_requirements_count(),
0,
"both conjuncts hold, the deferred entry must have been drained"
);
}
}
#[test]
fn test_lazy_conditional_determinism_async() {
fn build_provider() -> BundleBoxProvider {
let mut p = BundleBoxProvider::from_packages(&[
("foo", 1, vec!["bla; if a", "ext; if b", "tail; if c"]),
("foo", 2, vec!["bla; if a", "ext; if b", "tail; if c"]),
("bla", 1, vec!["inner; if a"]),
("ext", 1, vec![]),
("tail", 1, vec![]),
("inner", 1, vec![]),
("a", 1, vec![]),
("b", 1, vec![]),
("c", 1, vec![]),
]);
p.sleep_before_return = true;
p
}
fn run() -> (String, Vec<String>) {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap();
let mut provider = build_provider();
let requirements = provider.requirements(&["foo", "a", "b", "c"]);
let mut solver = Solver::new(provider).with_runtime(runtime);
let problem = Problem::new().requirements(requirements);
let solved = solver.solve(problem).unwrap();
let result = transaction_to_string(solver.provider(), &solved);
let mut fetched = solver.provider().requested_package_names();
fetched.sort();
(result, fetched)
}
let baseline = run();
for i in 0..9 {
let next = run();
assert_eq!(
next.0, baseline.0,
"non-deterministic resolution under async ordering at iteration {i}"
);
assert_eq!(
next.1, baseline.1,
"non-deterministic fetched-packages set under async ordering at \
iteration {i}"
);
}
}
#[test]
#[traced_test]
fn test_lazy_conditional_externally_excluded_complement_does_not_fire() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 1, vec!["b; if cond 2..3"]),
("b", 1, vec![]),
("cond", 1, vec![]),
("cond", 2, vec![]),
]);
provider.exclude("cond", 1, "it is externally excluded");
let requirements = provider.requirements(&["a"]);
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);
assert_snapshot!(result, @"a=1");
assert_eq!(
solver.deferred_requirements_count(),
1,
"the `if cond 2..3` disjunct never fired, so its deferred entry \
must remain"
);
}
#[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<NameId = resolvo::NameId, SolvableId = SolvableId>>(
provider: D,
root_reqs: &[ConditionalRequirement],
root_constraints: &[VersionSetId],
) -> String {
let mut solver = Solver::new(provider);
let problem = Problem::new()
.requirements(root_reqs.to_vec())
.constraints(root_constraints.to_vec());
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_constrains_forward_basic() {
let mut provider = BundleBoxProvider::new();
provider.add_package("a", 1.into(), &["b"], &["b 3..100"]);
provider.add_package("b", 1.into(), &[], &[]);
provider.add_package("b", 2.into(), &[], &[]);
provider.add_package("b", 3.into(), &[], &[]);
provider.add_package("b", 4.into(), &[], &[]);
let requirements = provider.requirements(&["a"]);
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);
assert_snapshot!(result, @r###"
a=1
b=4
"###);
}
#[test]
fn test_constrains_forward_all_forbidden() {
let mut provider = BundleBoxProvider::new();
provider.add_package("a", 1.into(), &["b"], &["b 100..200"]);
provider.add_package("b", 1.into(), &[], &[]);
provider.add_package("b", 2.into(), &[], &[]);
let error = solve_unsat(provider, &["a"]);
assert_snapshot!(error);
}
#[test]
fn test_constrains_reverse_direction() {
let mut provider = BundleBoxProvider::new();
provider.add_package("x", 1.into(), &["b 2..3"], &[]);
provider.add_package("b", 1.into(), &[], &[]);
provider.add_package("b", 2.into(), &[], &[]);
provider.add_package("b", 3.into(), &[], &[]);
provider.add_package("a", 2.into(), &[], &[]);
provider.add_package("a", 3.into(), &[], &["b 3..100"]);
let requirements = provider.requirements(&["x", "a"]);
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);
assert_snapshot!(result, @r###"
a=2
b=2
x=1
"###);
}
#[test]
fn test_constrains_reverse_unsat() {
let mut provider = BundleBoxProvider::new();
provider.add_package("x", 1.into(), &["b 1..2"], &[]);
provider.add_package("b", 1.into(), &[], &[]);
provider.add_package("b", 2.into(), &[], &[]);
provider.add_package("a", 1.into(), &[], &["b 2..100"]);
let error = solve_unsat(provider, &["x", "a"]);
assert_snapshot!(error);
}
#[test]
fn test_constrains_no_version_selected() {
let mut provider = BundleBoxProvider::new();
provider.add_package("a", 1.into(), &[], &["b 100..200"]);
provider.add_package("b", 1.into(), &[], &[]);
provider.add_package("b", 2.into(), &[], &[]);
let requirements = provider.requirements(&["a"]);
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);
assert_snapshot!(result, @r###"
a=1
"###);
}
#[test]
fn test_constrains_matching_version_ok() {
let mut provider = BundleBoxProvider::new();
provider.add_package("x", 1.into(), &["b"], &[]);
provider.add_package("b", 1.into(), &[], &[]);
provider.add_package("b", 2.into(), &[], &[]);
provider.add_package("b", 3.into(), &[], &[]);
provider.add_package("a", 1.into(), &[], &["b 0..2"]);
let requirements = provider.requirements(&["x", "a"]);
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);
assert_snapshot!(result, @r###"
a=1
b=1
x=1
"###);
}
#[test]
fn test_constrains_backtracking() {
let mut provider = BundleBoxProvider::new();
provider.add_package("a", 1.into(), &["b"], &[]);
provider.add_package("b", 1.into(), &[], &[]);
provider.add_package("b", 2.into(), &[], &[]);
provider.add_package("b", 3.into(), &[], &[]);
provider.add_package("c", 1.into(), &[], &["b 0..2"]);
let requirements = provider.requirements(&["a", "c"]);
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);
assert_snapshot!(result, @r###"
a=1
b=1
c=1
"###);
}
#[test]
fn test_constrains_diamond() {
let mut provider = BundleBoxProvider::new();
provider.add_package("a", 1.into(), &["c"], &["c 3..100"]);
provider.add_package("b", 1.into(), &["c"], &["c 0..5"]);
provider.add_package("c", 1.into(), &[], &[]);
provider.add_package("c", 2.into(), &[], &[]);
provider.add_package("c", 3.into(), &[], &[]);
provider.add_package("c", 4.into(), &[], &[]);
provider.add_package("c", 5.into(), &[], &[]);
let requirements = provider.requirements(&["a", "b"]);
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);
assert_snapshot!(result, @r###"
a=1
b=1
c=4
"###);
}
#[test]
fn test_constrains_many_versions() {
let mut provider = BundleBoxProvider::new();
for v in 1..=50u32 {
provider.add_package("big", v.into(), &[], &[]);
}
provider.add_package("a", 1.into(), &["big"], &["big 46..100"]);
let requirements = provider.requirements(&["a"]);
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);
assert_snapshot!(result, @r###"
a=1
big=50
"###);
}
#[test]
fn test_constrains_transitive() {
let mut provider = BundleBoxProvider::new();
provider.add_package("b", 1.into(), &["c 1..2"], &[]);
provider.add_package("b", 2.into(), &["c 2..3"], &[]);
provider.add_package("c", 1.into(), &[], &[]);
provider.add_package("c", 2.into(), &[], &[]);
provider.add_package("x", 1.into(), &["b"], &[]);
provider.add_package("a", 1.into(), &[], &["b 2..100"]);
let requirements = provider.requirements(&["x", "a"]);
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);
assert_snapshot!(result, @r###"
a=1
b=2
c=2
x=1
"###);
}
#[test]
fn test_constrains_shared_encoding_clause_count() {
let solve_and_count = |parents: usize| {
let mut provider = BundleBoxProvider::new();
for v in 1..=10u32 {
provider.add_package("pkg", v.into(), &[], &[]);
}
let parent_names = (0..parents).map(|i| format!("p{i}")).collect::<Vec<_>>();
for name in &parent_names {
provider.add_package(name, 1.into(), &[], &["pkg 11..100"]);
}
let requirements =
provider.requirements(&parent_names.iter().map(String::as_str).collect::<Vec<_>>());
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
solver
.solve(problem)
.expect("nothing requires pkg, so the constraints are never violated");
solver.clause_count()
};
let single_parent = solve_and_count(1);
let many_parents = solve_and_count(5);
assert_eq!(many_parents - single_parent, 4 * 2);
}
#[test]
fn test_constrains_pairwise_encoding_clause_count() {
let solve_and_count = |parents: usize| {
let mut provider = BundleBoxProvider::new();
provider.add_package("pkg", 1.into(), &[], &[]);
provider.add_package("pkg", 2.into(), &[], &[]);
let parent_names = (0..parents).map(|i| format!("p{i}")).collect::<Vec<_>>();
for name in &parent_names {
provider.add_package(name, 1.into(), &[], &["pkg 11..100"]);
}
let requirements =
provider.requirements(&parent_names.iter().map(String::as_str).collect::<Vec<_>>());
let mut solver = Solver::new(provider);
let problem = Problem::new().requirements(requirements);
solver
.solve(problem)
.expect("nothing requires pkg, so the constraints are never violated");
solver.clause_count()
};
let single_parent = solve_and_count(1);
let many_parents = solve_and_count(5);
assert_eq!(many_parents - single_parent, 4 * 3);
}
#[test]
fn test_unsat_constrains_shared_encoding() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 10, vec!["b 50..100"]),
("b", 55, vec![]),
("b", 54, vec![]),
("b", 53, vec![]),
("b", 52, vec![]),
("b", 51, vec![]),
("b", 50, vec![]),
("b", 42, vec![]),
]);
provider.add_package("c", 10.into(), &[], &["b 0..50"]);
let error = solve_unsat(provider, &["a", "c"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_constrains_shared_encoding_reverse() {
let mut provider = BundleBoxProvider::from_packages(&[
("x", 1, vec!["b 1..2"]),
("b", 1, vec![]),
("b", 2, vec![]),
("b", 3, vec![]),
("b", 4, vec![]),
]);
provider.add_package("a", 1.into(), &[], &["b 5..100"]);
let error = solve_unsat(provider, &["x", "a"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_unsat_constrains_shared_encoding_multiple_parents() {
let mut provider = BundleBoxProvider::from_packages(&[
("a", 10, vec!["b 50..100"]),
("b", 55, vec![]),
("b", 54, vec![]),
("b", 53, vec![]),
("b", 52, vec![]),
("b", 51, vec![]),
("b", 50, vec![]),
("b", 42, vec![]),
]);
provider.add_package("c", 10.into(), &[], &["b 0..50"]);
provider.add_package("d", 10.into(), &[], &["b 0..50"]);
let error = solve_unsat(provider, &["a", "c", "d"]);
insta::assert_snapshot!(error);
}
#[test]
fn test_constrains_multiple_parents() {
let mut provider = BundleBoxProvider::new();
for v in 1..=8u32 {
provider.add_package("pkg", v.into(), &[], &[]);
}
provider.add_package("x", 1.into(), &["pkg"], &[]);
provider.add_package("a", 1.into(), &[], &["pkg 3..100"]);
provider.add_package("b", 1.into(), &[], &["pkg 0..8"]);
let requirements = provider.requirements(&["x", "a", "b"]);
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);
assert_snapshot!(result, @r###"
a=1
b=1
pkg=7
x=1
"###);
}
#[test]
fn test_decide_queue_satisfaction_break() {
let mut provider = BundleBoxProvider::new();
provider.add_package("x", 1.into(), &[], &[]);
provider.add_package("x", 2.into(), &[], &[]);
provider.add_package("a", 1.into(), &["x 1..2"], &[]);
provider.add_package("a", 2.into(), &["x 2..3"], &[]);
provider.add_package("b", 1.into(), &["x 1..2"], &[]);
let requirements = provider.requirements(&["a", "b"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
a=1
b=1
x=1
"###);
}
#[test]
fn test_decide_queue_backjump_past_parent() {
let mut provider = BundleBoxProvider::new();
provider.add_package("leaf", 1.into(), &[], &[]);
provider.add_package("leaf", 2.into(), &[], &[]);
provider.add_package("mid", 1.into(), &["leaf 1..2"], &[]);
provider.add_package("mid", 2.into(), &["leaf 2..3"], &[]);
provider.add_package("top", 1.into(), &["mid"], &[]);
let requirements = provider.requirements(&["top", "leaf 1..2"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
leaf=1
mid=1
top=1
"###);
}
#[test]
fn test_decide_queue_condition_toggles() {
let mut provider = BundleBoxProvider::new();
provider.add_package("bar", 1.into(), &[], &[]);
provider.add_package("bar", 2.into(), &[], &[]);
provider.add_package("baz", 1.into(), &[], &[]);
provider.add_package("foo", 1.into(), &["baz; if bar 2..3"], &[]);
provider.add_package("qux", 1.into(), &["bar 1..2"], &[]);
let requirements = provider.requirements(&["foo", "bar", "qux"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
bar=1
foo=1
qux=1
"###);
}
#[test]
fn test_decide_queue_reset_on_late_conflict() {
let mut provider = BundleBoxProvider::new();
provider.add_package("a", 1.into(), &[], &[]);
provider.add_package("b", 1.into(), &[], &[]);
provider.add_package("b", 2.into(), &[], &["a 2..3"]);
let requirements = provider.requirements(&["a", "b"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
a=1
b=1
"###);
}
#[test]
fn test_decide_queue_union_duplicate_name() {
let mut provider = BundleBoxProvider::new();
provider.add_package("x", 1.into(), &[], &[]);
provider.add_package("x", 3.into(), &[], &[]);
provider.add_package("a", 1.into(), &["x 0..2 | x 3..4"], &[]);
provider.add_package("b", 1.into(), &["x 0..2"], &[]);
let requirements = provider.requirements(&["a", "b"]);
assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###"
a=1
b=1
x=1
"###);
}
mod decide_queue_prop;
type GateFuzzGraph = (Vec<(String, u32, Vec<String>)>, Vec<String>);
struct GateFuzzRng(u64);
impl GateFuzzRng {
fn new(seed: u64) -> Self {
Self(seed.wrapping_mul(0x9E3779B97F4A7C15) | 1)
}
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
}
fn gen_gate_fuzz_graph(seed: u64) -> GateFuzzGraph {
let mut r = GateFuzzRng::new(seed);
let n = 5 + (r.next() % 6) as usize;
let names: Vec<String> = (0..n).map(|i| format!("p{i}")).collect();
let mut pkgs = Vec::new();
for (i, name) in names.iter().enumerate() {
let versions = 1 + (r.next() % 5) as u32;
for v in 1..=versions {
let num_deps = (r.next() % 4) as usize;
let mut deps = Vec::new();
for _ in 0..num_deps {
let j = (r.next() as usize) % n;
if j == i {
continue;
}
if r.next() % 3 == 0 {
let lo = 1 + (r.next() % 3) as u32;
let hi = lo + 1 + (r.next() % 3) as u32;
deps.push(format!("{} {}..{}", names[j], lo, hi));
} else {
deps.push(names[j].clone());
}
}
pkgs.push((name.clone(), v, deps));
}
}
let num_roots = 1 + (r.next() % 3) as usize;
let roots = (0..num_roots)
.map(|_| names[(r.next() as usize) % n].clone())
.collect();
(pkgs, roots)
}
fn validate_complete(
pkgs: &[(String, u32, Vec<String>)],
roots: &[String],
sol: &[(String, u32)],
) -> Result<(), String> {
use std::collections::HashMap;
let chosen: HashMap<&str, u32> = sol.iter().map(|(n, v)| (n.as_str(), *v)).collect();
let satisfied = |spec: &str| -> bool {
let mut it = spec.split_whitespace();
let name = it.next().unwrap();
let Some(&cv) = chosen.get(name) else {
return false;
};
match it.next() {
None => true,
Some(range) => {
let mut p = range.split("..");
let lo: u32 = p.next().unwrap().parse().unwrap();
let hi: u32 = p.next().unwrap().parse().unwrap();
cv >= lo && cv < hi
}
}
};
for root in roots {
if !satisfied(root) {
return Err(format!("root `{root}` is unsatisfied"));
}
}
for (name, version) in sol {
let deps = &pkgs
.iter()
.find(|(n, v, _)| n == name && v == version)
.unwrap()
.2;
for dep in deps {
if !satisfied(dep) {
return Err(format!(
"{name}=={version} requires `{dep}` which is absent"
));
}
}
}
Ok(())
}
fn solve_gate_fuzz(
pkgs: &[(String, u32, Vec<String>)],
roots: &[String],
delay_seed: u64,
) -> Option<Vec<(String, u32)>> {
let pp: Vec<(&str, u32, Vec<&str>)> = pkgs
.iter()
.map(|(n, v, d)| (n.as_str(), *v, d.iter().map(String::as_str).collect()))
.collect();
let mut provider = BundleBoxProvider::from_packages(&pp);
provider.sleep_before_return = true;
provider.delay_seed = delay_seed;
let root_specs: Vec<&str> = roots.iter().map(String::as_str).collect();
let reqs = provider.requirements(&root_specs);
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let mut solver = Solver::new(provider).with_runtime(rt);
let solution = solver.solve(Problem::new().requirements(reqs)).ok()?;
Some(
solution
.iter()
.map(|s| {
let display = format!("{}", solver.provider().display_solvable(*s));
let mut it = display.split('=');
(
it.next().unwrap().to_string(),
it.next_back().unwrap().parse().unwrap(),
)
})
.collect(),
)
}
#[test]
fn shared_requires_gate_survives_backtrack() {
let (pkgs, roots) = gen_gate_fuzz_graph(542);
for delay_seed in 1..=12u64 {
if let Some(sol) = solve_gate_fuzz(&pkgs, &roots, 542_000 + delay_seed) {
validate_complete(&pkgs, &roots, &sol).unwrap_or_else(|e| {
panic!(
"delay seed {} produced an incomplete solution: {e}",
542_000 + delay_seed
)
});
}
}
}
mod allow_multiple_versions {
use super::*;
#[test]
fn test_non_overlapping_requirements_unsat_without_allow_multiple() {
let mut provider = BundleBoxProvider::new();
provider.add_package("kernel", 1.into(), &[], &[]);
provider.add_package("kernel", 2.into(), &[], &[]);
provider.add_package("kernel", 3.into(), &[], &[]);
provider.add_package("kmod-a", 1.into(), &["kernel 1..2"], &[]);
provider.add_package("kmod-b", 1.into(), &["kernel 3..4"], &[]);
let result = solve_unsat(provider, &["kmod-a", "kmod-b"]);
assert_snapshot!(result);
}
#[test]
fn test_non_overlapping_requirements() {
let mut provider = BundleBoxProvider::new();
provider.add_package("kernel", 1.into(), &[], &[]);
provider.add_package("kernel", 2.into(), &[], &[]);
provider.add_package("kernel", 3.into(), &[], &[]);
provider.set_allow_multiple("kernel");
provider.add_package("kmod-a", 1.into(), &["kernel 1..2"], &[]);
provider.add_package("kmod-b", 1.into(), &["kernel 3..4"], &[]);
let requirements = provider.requirements(&["kmod-a", "kmod-b"]);
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);
assert_snapshot!(result, @r"
kernel=1
kernel=3
kmod-a=1
kmod-b=1
");
}
#[test]
fn test_overlapping_requirements() {
let mut provider = BundleBoxProvider::new();
provider.add_package("kernel", 1.into(), &[], &[]);
provider.add_package("kernel", 2.into(), &[], &[]);
provider.add_package("kernel", 3.into(), &[], &[]);
provider.set_allow_multiple("kernel");
provider.add_package("kmod-a", 1.into(), &["kernel 1..3"], &[]);
provider.add_package("kmod-b", 1.into(), &["kernel 2..4"], &[]);
let requirements = provider.requirements(&["kmod-a", "kmod-b"]);
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);
assert_snapshot!(result, @r"
kernel=2
kernel=3
kmod-a=1
kmod-b=1
");
}
#[test]
fn test_allow_multiple_transitive() {
let mut provider = BundleBoxProvider::new();
provider.add_package("kernel", 1.into(), &[], &[]);
provider.add_package("kernel", 2.into(), &[], &[]);
provider.add_package("kernel", 3.into(), &[], &[]);
provider.set_allow_multiple("kernel");
provider.add_package("kmod", 1.into(), &["kernel 1..2"], &[]);
provider.add_package("kmod", 2.into(), &["kernel 2..3"], &[]);
provider.add_package("kmod", 3.into(), &["kernel 3..4"], &[]);
provider.set_allow_multiple("kmod");
provider.add_package("app-old", 1.into(), &["kmod 1..2"], &[]);
provider.add_package("app-new", 1.into(), &["kmod 3..4"], &[]);
let requirements = provider.requirements(&["app-old", "app-new"]);
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);
assert_snapshot!(result, @r"
app-new=1
app-old=1
kernel=1
kernel=3
kmod=1
kmod=3
");
}
#[test]
fn test_soft_requirements_with_allow_multiple() {
let mut provider = BundleBoxProvider::new();
provider.add_package("kernel", 1.into(), &[], &[]);
provider.add_package("kernel", 2.into(), &[], &[]);
provider.add_package("kernel", 3.into(), &[], &[]);
provider.set_allow_multiple("kernel");
provider.exclude("kernel", 1, "retracted due to security issue");
let k1 = provider.solvable_id("kernel", 1u32);
let k2 = provider.solvable_id("kernel", 2u32);
let k3 = provider.solvable_id("kernel", 3u32);
let requirements = provider.requirements(&["kernel"]);
let mut solver = Solver::new(provider);
let problem = Problem::new()
.requirements(requirements)
.soft_requirements(vec![k1, k2, k3]);
let solved = solver.solve(problem).unwrap();
let result = transaction_to_string(solver.provider(), &solved);
assert_snapshot!(result, @r"
kernel=2
kernel=3
");
}
}