From ccb710077e6de82132c462537a9f57a1009e2ae1 Mon Sep 17 00:00:00 2001 From: J Robert Ray Date: Tue, 23 Sep 2025 17:06:58 -0700 Subject: [PATCH] Test solver behavior when using options It is expected that the solver will make an effort to resolve packages that fit the options, even though it is not required to. Create a test where multiple builds exist with different values for a var and add an option to the solver for a specific value. A build exists with that value so the solver should pick it. Signed-off-by: J Robert Ray --- crates/spk-solve/crates/macros/src/lib.rs | 6 +- crates/spk-solve/src/solvers/solver_test.rs | 61 +++++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/crates/spk-solve/crates/macros/src/lib.rs b/crates/spk-solve/crates/macros/src/lib.rs index c891b44220..c812a657f7 100644 --- a/crates/spk-solve/crates/macros/src/lib.rs +++ b/crates/spk-solve/crates/macros/src/lib.rs @@ -13,14 +13,14 @@ pub use {serde_json, spfs}; /// make_repo!({"pkg": "mypkg/1.0.0"}, options = {"debug" => "off"}); #[macro_export] macro_rules! make_repo { - ( [ $( $spec:tt ),+ $(,)? ] ) => {{ + ( [ $( $spec:tt ),* $(,)? ] ) => {{ make_repo!([ $( $spec ),* ], options={}) }}; - ( [ $( $spec:tt ),+ $(,)? ], options={ $($k:expr => $v:expr),* } ) => {{ + ( [ $( $spec:tt ),* $(,)? ], options={ $($k:expr => $v:expr),* } ) => {{ let options = spk_schema::foundation::option_map!{$($k => $v),*}; make_repo!([ $( $spec ),* ], options=options) }}; - ( [ $( $spec:tt ),+ $(,)? ], options=$options:expr ) => {{ + ( [ $( $spec:tt ),* $(,)? ], options=$options:expr ) => {{ tracing::debug!("creating in-memory repository"); let repo = spk_storage::RepositoryHandle::new_mem(); let _opts = $options; diff --git a/crates/spk-solve/src/solvers/solver_test.rs b/crates/spk-solve/src/solvers/solver_test.rs index 50f55a41ab..f22fe34684 100644 --- a/crates/spk-solve/src/solvers/solver_test.rs +++ b/crates/spk-solve/src/solvers/solver_test.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // https://github.com/spkenv/spk +use std::collections::HashMap; use std::sync::Arc; use rstest::{fixture, rstest}; @@ -3083,3 +3084,63 @@ async fn request_for_all_component_picks_correct_version( let solution = run_and_print_resolve_for_tests(&mut solver).await.unwrap(); assert_resolved!(solution, "mypkg", version = version); } + +/// Test that adding options to the solver make the solver pick the build that +/// satisfies those options, when possible. +#[rstest] +#[case::step(step_solver())] +#[case::resolvo(resolvo_solver())] +#[tokio::test] +async fn options_influence_build_choice( + #[case] mut solver: SolverImpl, + #[values(true, false)] scope_var: bool, +) { + // Create 10 builds of a package, varying the value of an option called + // "num". + let builds = (0..10) + .map(|num| { + ( + num.to_string(), + make_build!({ + "pkg": "mypkg/1.0.0", + "build": {"options": [{"var": format!("num/{num}")}]}, + }), + ) + }) + .inspect(|(num, spec)| { + eprintln!("Created build with num {num}: {}", spec.ident().build()); + }) + .collect::>(); + + let repo = make_repo!([]); + let _options = option_map! {}; + for spec in builds.values() { + let (s, cmpts) = make_package!(repo, spec, _options); + repo.publish_package(&s, &cmpts).await.unwrap(); + } + + let repo = Arc::new(repo); + + // For each option value from above, ask the solver to resolve mypkg using + // an option with that value for "num". It is expected that it will pick the + // build that was created with that value for "num". + // + // Since there are 10 builds, the odds of this succeeding by chance are 1 in + // 10 billion. + for (num, spec) in &builds { + solver.reset(); + + solver.add_repository(Arc::clone(&repo)); + solver.add_request(request!("mypkg")); + + let options = if scope_var { + option_map! { "mypkg.num" => num.to_string() } + } else { + option_map! { "num" => num.to_string() } + }; + solver.update_options(options); + + let solution = run_and_print_resolve_for_tests(&mut solver).await.unwrap(); + assert_resolved!(solution, "mypkg", build = *spec.ident().build()); + } +}