# frozen_string_literal: true require_relative "helper" class TestGemResolver < Gem::TestCase def make_dep(name, *req) Gem::Dependency.new(name, *req) end def set(*specs) source = Gem::Source.new Gem::URI @gem_repo specs = specs.map do |spec| Gem::Resolver::SpecSpecification.new nil, spec, source end StaticSet.new(specs) end def assert_resolves_to(expected, resolver) actual = resolver.resolve exp = expected.sort_by(&:full_name) act = actual.map {|a| a.spec.spec }.sort_by(&:full_name) msg = "Set of gems was not the same: #{exp.map(&:full_name).inspect} != #{act.map(&:full_name).inspect}" assert_equal exp, act, msg rescue Gem::DependencyResolutionError => e flunk e.message end def test_self_compose_sets_best_set best_set = Gem::Resolver::BestSet.new composed = Gem::Resolver.compose_sets best_set assert_equal best_set, composed end def test_self_compose_sets_multiple index_set = Gem::Resolver::IndexSet.new vendor_set = Gem::Resolver::VendorSet.new composed = Gem::Resolver.compose_sets index_set, vendor_set assert_kind_of Gem::Resolver::ComposedSet, composed assert_equal [index_set, vendor_set], composed.sets end def test_self_compose_sets_nest index_set = Gem::Resolver::IndexSet.new vendor_set = Gem::Resolver::VendorSet.new inner = Gem::Resolver.compose_sets index_set, vendor_set current_set = Gem::Resolver::CurrentSet.new composed = Gem::Resolver.compose_sets inner, current_set assert_kind_of Gem::Resolver::ComposedSet, composed assert_equal [index_set, vendor_set, current_set], composed.sets end def test_self_compose_sets_nil index_set = Gem::Resolver::IndexSet.new composed = Gem::Resolver.compose_sets index_set, nil assert_same index_set, composed e = assert_raise ArgumentError do Gem::Resolver.compose_sets nil end assert_equal "one set in the composition must be non-nil", e.message end def test_self_compose_sets_single index_set = Gem::Resolver::IndexSet.new composed = Gem::Resolver.compose_sets index_set assert_same index_set, composed end def test_requests a1 = util_spec "a", 1, "b" => 2 r1 = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil act = Gem::Resolver::ActivationRequest.new a1, r1 res = Gem::Resolver.new [a1] reqs = [] res.requests a1, act, reqs assert_equal ["b (= 2)"], reqs.map(&:to_s) end def test_requests_development a1 = util_spec "a", 1, "b" => 2 spec = Gem::Resolver::SpecSpecification.new nil, a1 def spec.fetch_development_dependencies @called = true end r1 = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil act = Gem::Resolver::ActivationRequest.new spec, r1 res = Gem::Resolver.new [act] res.development = true reqs = [] res.requests spec, act, reqs assert_equal ["b (= 2)"], reqs.map(&:to_s) assert spec.instance_variable_defined? :@called end def test_requests_ignore_dependencies a1 = util_spec "a", 1, "b" => 2 r1 = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil act = Gem::Resolver::ActivationRequest.new a1, r1 res = Gem::Resolver.new [a1] res.ignore_dependencies = true reqs = [] res.requests a1, act, reqs assert_empty reqs end def test_resolve_conservative a1_spec = util_spec "a", 1 a2_spec = util_spec "a", 2 do |s| s.add_dependency "b", 2 s.add_dependency "c" end b1_spec = util_spec "b", 1 b2_spec = util_spec "b", 2 c1_spec = util_spec "c", 1 do |s| s.add_dependency "d", 2 end c2_spec = util_spec "c", 2 do |s| s.add_dependency "d", 2 end d1_spec = util_spec "d", 1 do |s| s.add_dependency "e" end d2_spec = util_spec "d", 2 do |s| s.add_dependency "e" end e1_spec = util_spec "e", 1 e2_spec = util_spec "e", 2 a_dep = make_dep "a", "= 2" e_dep = make_dep "e" # When requesting to install: # a-2, e deps = [a_dep, e_dep] s = set a1_spec, a2_spec, b1_spec, b2_spec, c1_spec, c2_spec, d1_spec, d2_spec, e1_spec, e2_spec res = Gem::Resolver.new deps, s # With the following gems already installed: # a-1, b-1, c-1, e-1 res.skip_gems = { "a" => [a1_spec], "b" => [b1_spec], "c" => [c1_spec], "e" => [e1_spec] } # Make sure the following gems end up getting used/installed/upgraded: # a-2 (upgraded) # b-2 (upgraded), specific dependency from a-2 # c-1 (used, not upgraded), open dependency from a-2 # d-2 (installed), specific dependency from c-2 # e-1 (used, not upgraded), open dependency from request assert_resolves_to [a2_spec, b2_spec, c1_spec, d2_spec, e1_spec], res end def test_resolve_development a_spec = util_spec "a", 1 do |s| s.add_development_dependency "b" end b_spec = util_spec "b", 1 do |s| s.add_development_dependency "c" end c_spec = util_spec "c", 1 a_dep = make_dep "a", "= 1" deps = [a_dep] s = set a_spec, b_spec, c_spec res = Gem::Resolver.new deps, s res.development = true assert_resolves_to [a_spec, b_spec, c_spec], res end def test_resolve_development_shallow a_spec = util_spec "a", 1 do |s| s.add_development_dependency "b" s.add_runtime_dependency "d" end b_spec = util_spec "b", 1 do |s| s.add_development_dependency "c" end c_spec = util_spec "c", 1 d_spec = util_spec "d", 1 do |s| s.add_development_dependency "e" end e_spec = util_spec "e", 1 a_dep = make_dep "a", "= 1" deps = [a_dep] s = set a_spec, b_spec, c_spec, d_spec, e_spec res = Gem::Resolver.new deps, s res.development = true res.development_shallow = true assert_resolves_to [a_spec, b_spec, d_spec], res end def test_resolve_remote_missing_dependency @fetcher = Gem::FakeFetcher.new Gem::RemoteFetcher.fetcher = @fetcher a_dep = make_dep "a", "= 1" res = Gem::Resolver.new [a_dep], Gem::Resolver::IndexSet.new e = assert_raise Gem::UnsatisfiableDependencyError do res.resolve end refute_empty e.errors end def test_no_overlap_specifically a = util_spec "a", "1" b = util_spec "b", "1" ad = make_dep "a", "= 1" bd = make_dep "b", "= 1" deps = [ad, bd] s = set(a, b) res = Gem::Resolver.new(deps, s) assert_resolves_to [a, b], res end def test_pulls_in_dependencies a = util_spec "a", "1" b = util_spec "b", "1", "c" => "= 1" c = util_spec "c", "1" ad = make_dep "a", "= 1" bd = make_dep "b", "= 1" deps = [ad, bd] s = set(a, b, c) res = Gem::Resolver.new(deps, s) assert_resolves_to [a, b, c], res end def test_picks_highest_version a1 = util_spec "a", "1" a2 = util_spec "a", "2" s = set(a1, a2) ad = make_dep "a" res = Gem::Resolver.new([ad], s) assert_resolves_to [a2], res end def test_picks_best_platform is = Gem::Resolver::IndexSpecification unknown = Gem::Platform.new "unknown" spec_fetcher do |fetcher| fetcher.spec "a", 2 fetcher.spec "a", 2 do |s| s.platform = Gem::Platform.local end fetcher.spec "a", 3 do |s| s.platform = unknown end end v2 = v(2) v3 = v(3) source = Gem::Source.new @gem_repo s = set a2 = is.new s, "a", v2, source, Gem::Platform::RUBY a2_p1 = is.new s, "a", v2, source, Gem::Platform.local.to_s a3_p2 = is.new s, "a", v3, source, unknown s.add a3_p2 s.add a2_p1 s.add a2 ad = make_dep "a" res = Gem::Resolver.new([ad], s) assert_resolves_to [a2_p1.spec], res end def test_does_not_pick_musl_variants_on_non_musl_linux util_set_arch "aarch64-linux" do is = Gem::Resolver::IndexSpecification linux_musl = Gem::Platform.new("aarch64-linux-musl") spec_fetcher do |fetcher| fetcher.spec "libv8-node", "15.14.0.1" do |s| s.platform = Gem::Platform.local end fetcher.spec "libv8-node", "15.14.0.1" do |s| s.platform = linux_musl end end v15 = v("15.14.0.1") source = Gem::Source.new @gem_repo s = set v15_linux = is.new s, "libv8-node", v15, source, Gem::Platform.local.to_s v15_linux_musl = is.new s, "libv8-node", v15, source, linux_musl.to_s s.add v15_linux s.add v15_linux_musl ad = make_dep "libv8-node", "= 15.14.0.1" res = Gem::Resolver.new([ad], s) assert_resolves_to [v15_linux.spec], res end end def test_pick_generic_linux_variants_on_musl_linux util_set_arch "aarch64-linux-musl" do is = Gem::Resolver::IndexSpecification linux = Gem::Platform.new("aarch64-linux") spec_fetcher do |fetcher| fetcher.spec "libv8-node", "15.14.0.1" do |s| s.platform = linux end fetcher.spec "libv8-node", "15.14.0.1" end v15 = v("15.14.0.1") source = Gem::Source.new @gem_repo s = set v15_ruby = is.new s, "libv8-node", v15, source, Gem::Platform::RUBY v15_linux = is.new s, "libv8-node", v15, source, linux.to_s s.add v15_linux s.add v15_ruby ad = make_dep "libv8-node", "= 15.14.0.1" res = Gem::Resolver.new([ad], s) assert_resolves_to [v15_linux.spec], res end end def test_only_returns_spec_once a1 = util_spec "a", "1", "c" => "= 1" b1 = util_spec "b", "1", "c" => "= 1" c1 = util_spec "c", "1" ad = make_dep "a" bd = make_dep "b" s = set(a1, b1, c1) res = Gem::Resolver.new([ad, bd], s) assert_resolves_to [a1, b1, c1], res end def test_picks_lower_version_when_needed a1 = util_spec "a", "1", "c" => ">= 1" b1 = util_spec "b", "1", "c" => "= 1" c1 = util_spec "c", "1" c2 = util_spec "c", "2" ad = make_dep "a" bd = make_dep "b" s = set(a1, b1, c1, c2) res = Gem::Resolver.new([ad, bd], s) assert_resolves_to [a1, b1, c1], res end def test_conflict_resolution_only_effects_correct_spec a1 = util_spec "a", "1", "c" => ">= 1" b1 = util_spec "b", "1", "d" => ">= 1" d3 = util_spec "d", "3", "c" => "= 1" d4 = util_spec "d", "4", "c" => "= 1" c1 = util_spec "c", "1" c2 = util_spec "c", "2" ad = make_dep "a" bd = make_dep "b" s = set(a1, b1, d3, d4, c1, c2) res = Gem::Resolver.new([ad, bd], s) assert_resolves_to [a1, b1, c1, d4], res end def test_backoff_higher_version_to_satisfy_dep t3 = util_spec "railties", "3.2" t4 = util_spec "railties", "4.0" r3 = util_spec "rails", "3.2", "railties" => "= 3.2" r4 = util_spec "rails", "4.0", "railties" => "= 4.0" rd = make_dep "rails", "3.2" c3 = util_spec "coffee", "3.0", "railties" => "~> 3.0" c4 = util_spec "coffee", "4.0", "railties" => "~> 4.0" cd = make_dep "coffee" s = set(t3, t4, r3, r4, c3, c4) res = Gem::Resolver.new([rd, cd], s) assert_resolves_to [r3, t3, c3], res end def test_raises_dependency_error a1 = util_spec "a", "1", "c" => "= 1" b1 = util_spec "b", "1", "c" => "= 2" c1 = util_spec "c", "1" c2 = util_spec "c", "2" ad = make_dep "a" bd = make_dep "b" s = set(a1, b1, c1, c2) r = Gem::Resolver.new([ad, bd], s) e = assert_raise Gem::DependencyResolutionError do r.resolve end deps = [make_dep("c", "= 2"), make_dep("c", "= 1")] assert_equal deps, e.conflicting_dependencies con = e.conflict act = con.activated assert_equal "c-1", act.spec.full_name parent = act.parent assert_equal "a-1", parent.spec.full_name act = con.requester assert_equal "b-1", act.spec.full_name end def test_raises_when_a_gem_is_missing ad = make_dep "a" r = Gem::Resolver.new([ad], set) e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end assert_equal "Unable to resolve dependency: user requested 'a (>= 0)'", e.message assert_equal "a (>= 0)", e.dependency.to_s end def test_raises_when_a_gem_version_is_missing a1 = util_spec "a", "1" ad = make_dep "a", "= 3" r = Gem::Resolver.new([ad], set(a1)) e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end assert_equal "a (= 3)", e.dependency.to_s end def test_raises_and_reports_a_toplevel_request_properly a1 = util_spec "a", "1" ad = make_dep "a", "= 3" r = Gem::Resolver.new([ad], set(a1)) e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end assert_equal "Unable to resolve dependency: user requested 'a (= 3)'", e.message end def test_raises_and_reports_an_implicit_request_properly a1 = util_spec "a", "1" do |s| s.add_runtime_dependency "b", "= 2" end ad = make_dep "a", "= 1" r = Gem::Resolver.new([ad], set(a1)) e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end assert_equal "Unable to resolve dependency: 'a (= 1)' requires 'b (= 2)'", e.message end def test_raises_when_possibles_are_exhausted a1 = util_spec "a", "1", "c" => ">= 2" b1 = util_spec "b", "1", "c" => "= 1" c1 = util_spec "c", "1" c2 = util_spec "c", "2" c3 = util_spec "c", "3" s = set(a1, b1, c1, c2, c3) ad = make_dep "a" bd = make_dep "b" r = Gem::Resolver.new([ad, bd], s) e = assert_raise Gem::DependencyResolutionError do r.resolve end dependency = e.conflict.dependency assert_includes %w[a b], dependency.name assert_equal req(">= 0"), dependency.requirement activated = e.conflict.activated assert_equal "c-1", activated.full_name assert_equal dep("c", "= 1"), activated.request.dependency assert_equal [dep("c", ">= 2"), dep("c", "= 1")], e.conflict.conflicting_dependencies end def test_keeps_resolving_after_seeing_satisfied_dep a1 = util_spec "a", "1", "b" => "= 1", "c" => "= 1" b1 = util_spec "b", "1" c1 = util_spec "c", "1" ad = make_dep "a" bd = make_dep "b" s = set(a1, b1, c1) r = Gem::Resolver.new([ad, bd], s) assert_resolves_to [a1, b1, c1], r end def test_common_rack_activation_scenario rack100 = util_spec "rack", "1.0.0" rack101 = util_spec "rack", "1.0.1" lib1 = util_spec "lib", "1", "rack" => ">= 1.0.1" rails = util_spec "rails", "3", "actionpack" => "= 3" ap = util_spec "actionpack", "3", "rack" => ">= 1.0.0" d1 = make_dep "rails" d2 = make_dep "lib" s = set(lib1, rails, ap, rack100, rack101) r = Gem::Resolver.new([d1, d2], s) assert_resolves_to [rails, ap, rack101, lib1], r # check it with the deps reverse too r = Gem::Resolver.new([d2, d1], s) assert_resolves_to [lib1, rack101, rails, ap], r end def test_backtracks_to_the_first_conflict a1 = util_spec "a", "1" a2 = util_spec "a", "2" a3 = util_spec "a", "3" a4 = util_spec "a", "4" d1 = make_dep "a" d2 = make_dep "a", ">= 2" d3 = make_dep "a", "= 1" s = set(a1, a2, a3, a4) r = Gem::Resolver.new([d1, d2, d3], s) assert_raise Gem::DependencyResolutionError do r.resolve end end def test_resolve_conflict a1 = util_spec "a", 1 a2 = util_spec "a", 2 b2 = util_spec "b", 2, "a" => "~> 2.0" s = set a1, a2, b2 a_dep = dep "a", "~> 1.0" b_dep = dep "b" r = Gem::Resolver.new [a_dep, b_dep], s assert_raise Gem::DependencyResolutionError do r.resolve end end def test_resolve_bug_699 a1 = util_spec "a", "1", "b" => "= 2", "c" => "~> 1.0.3" b1 = util_spec "b", "2", "c" => "~> 1.0" c1 = util_spec "c", "1.0.9" c2 = util_spec "c", "1.1.0" c3 = util_spec "c", "1.2.0" s = set a1, b1, c1, c2, c3 a_dep = dep "a", "= 1" r = Gem::Resolver.new [a_dep], s assert_resolves_to [a1, b1, c1], r end def test_resolve_rollback a1 = util_spec "a", 1 a2 = util_spec "a", 2 b1 = util_spec "b", 1, "a" => "~> 1.0" b2 = util_spec "b", 2, "a" => "~> 2.0" s = set a1, a2, b1, b2 a_dep = dep "a", "~> 1.0" b_dep = dep "b" r = Gem::Resolver.new [a_dep, b_dep], s assert_resolves_to [a1, b1], r end # actionmailer 2.3.4 # activemerchant 1.5.0 # activesupport 2.3.5, 2.3.4 # Activemerchant needs activesupport >= 2.3.2. When you require activemerchant, it will activate the latest version that meets that requirement which is 2.3.5. Actionmailer on the other hand needs activesupport = 2.3.4. When rubygems tries to activate activesupport 2.3.4, it will raise an error. def test_simple_activesupport_problem sup1 = util_spec "activesupport", "2.3.4" sup2 = util_spec "activesupport", "2.3.5" merch = util_spec "activemerchant", "1.5.0", "activesupport" => ">= 2.3.2" mail = util_spec "actionmailer", "2.3.4", "activesupport" => "= 2.3.4" s = set(mail, merch, sup1, sup2) d1 = make_dep "activemerchant" d2 = make_dep "actionmailer" r = Gem::Resolver.new([d1, d2], s) assert_resolves_to [merch, mail, sup1], r end def test_second_level_backout b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/b.rb" b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/b.rb" c1 = util_spec "c", "1" c2 = util_spec "c", "2" d1 = util_spec "d", "1", { "c" => "< 2" }, "lib/d.rb" d2 = util_spec "d", "2", { "c" => "< 2" }, "lib/d.rb" s = set(b1, b2, c1, c2, d1, d2) p1 = make_dep "b", "> 0" p2 = make_dep "d", "> 0" r = Gem::Resolver.new([p1, p2], s) assert_resolves_to [b1, c1, d2], r end def test_sorts_by_source_then_version source_a = Gem::Source.new "http://example.com/a" source_b = Gem::Source.new "http://example.com/b" source_c = Gem::Source.new "http://example.com/c" spec_a_1 = util_spec "some-dep", "0.0.1" spec_a_2 = util_spec "some-dep", "1.0.0" spec_b_1 = util_spec "some-dep", "0.0.1" spec_b_2 = util_spec "some-dep", "0.0.2" spec_c_1 = util_spec "some-dep", "0.1.0" set = StaticSet.new [ Gem::Resolver::SpecSpecification.new(nil, spec_b_1, source_b), Gem::Resolver::SpecSpecification.new(nil, spec_b_2, source_b), Gem::Resolver::SpecSpecification.new(nil, spec_c_1, source_c), Gem::Resolver::SpecSpecification.new(nil, spec_a_2, source_a), Gem::Resolver::SpecSpecification.new(nil, spec_a_1, source_a), ] dependency = make_dep "some-dep", "> 0" resolver = Gem::Resolver.new [dependency], set assert_resolves_to [spec_b_2], resolver end def test_select_local_platforms r = Gem::Resolver.new nil, nil a1 = util_spec "a", 1 a1_p1 = util_spec "a", 1 do |s| s.platform = Gem::Platform.local end a1_p2 = util_spec "a", 1 do |s| s.platform = "unknown" end selected = r.select_local_platforms [a1, a1_p1, a1_p2] assert_equal [a1, a1_p1], selected end def test_search_for_local_platform_partial_string_match a1 = util_spec "a", 1 a1_p1 = util_spec "a", 1 do |s| s.platform = Gem::Platform.local.os end a1_p2 = util_spec "a", 1 do |s| s.platform = "unknown" end s = set(a1_p1, a1_p2, a1) d = [make_dep("a")] r = Gem::Resolver.new(d, s) assert_resolves_to [a1_p1], r end def test_raises_and_explains_when_platform_prevents_install a1 = util_spec "a", "1" do |s| s.platform = Gem::Platform.new %w[c p 1] end ad = make_dep "a", "= 1" r = Gem::Resolver.new([ad], set(a1)) e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end assert_match "No match for 'a (= 1)' on this platform. Found: c-p-1", e.message end end