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()); + } +}