aboutsummaryrefslogtreecommitdiffstats
path: root/spec/bundler/resolver/basic_spec.rb
blob: 9e93847ab5f9becc41022b36dc6be2196f076a60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# frozen_string_literal: true
require "spec_helper"

RSpec.describe "Resolving" do
  before :each do
    @index = an_awesome_index
  end

  it "resolves a single gem" do
    dep "rack"

    should_resolve_as %w(rack-1.1)
  end

  it "resolves a gem with dependencies" do
    dep "actionpack"

    should_resolve_as %w(actionpack-2.3.5 activesupport-2.3.5 rack-1.0)
  end

  it "resolves a conflicting index" do
    @index = a_conflict_index
    dep "my_app"
    should_resolve_as %w(activemodel-3.2.11 builder-3.0.4 grape-0.2.6 my_app-1.0.0)
  end

  it "resolves a complex conflicting index" do
    @index = a_complex_conflict_index
    dep "my_app"
    should_resolve_as %w(a-1.4.0 b-0.3.5 c-3.2 d-0.9.8 my_app-1.1.0)
  end

  it "resolves a index with conflict on child" do
    @index = index_with_conflict_on_child
    dep "chef_app"
    should_resolve_as %w(berkshelf-2.0.7 chef-10.26 chef_app-1.0.0 json-1.7.7)
  end

  it "resolves a index with root level conflict on child" do
    @index = a_index_with_root_conflict_on_child
    dep "i18n", "~> 0.4"
    dep "activesupport", "~> 3.0"
    dep "activerecord", "~> 3.0"
    dep "builder", "~> 2.1.2"
    should_resolve_as %w(activesupport-3.0.5 i18n-0.4.2 builder-2.1.2 activerecord-3.0.5 activemodel-3.0.5)
  end

  it "raises an exception if a child dependency is not resolved" do
    @index = a_unresovable_child_index
    dep "chef_app_error"
    expect do
      resolve
    end.to raise_error(Bundler::VersionConflict)
  end

  it "raises an exception with the minimal set of conflicting dependencies" do
    @index = build_index do
      %w(0.9 1.0 2.0).each {|v| gem("a", v) }
      gem("b", "1.0") { dep "a", ">= 2" }
      gem("c", "1.0") { dep "a", "< 1" }
    end
    dep "a"
    dep "b"
    dep "c"
    expect do
      resolve
    end.to raise_error(Bundler::VersionConflict, <<-E.strip)
Bundler could not find compatible versions for gem "a":
  In Gemfile:
    b was resolved to 1.0, which depends on
      a (>= 2)

    c was resolved to 1.0, which depends on
      a (< 1)
    E
  end

  it "should throw error in case of circular dependencies" do
    @index = a_circular_index
    dep "circular_app"

    expect do
      resolve
    end.to raise_error(Bundler::CyclicDependencyError, /please remove either gem 'bar' or gem 'foo'/i)
  end

  # Issue #3459
  it "should install the latest possible version of a direct requirement with no constraints given" do
    @index = a_complicated_index
    dep "foo"
    should_resolve_and_include %w(foo-3.0.5)
  end

  # Issue #3459
  it "should install the latest possible version of a direct requirement with constraints given" do
    @index = a_complicated_index
    dep "foo", ">= 3.0.0"
    should_resolve_and_include %w(foo-3.0.5)
  end

  it "takes into account required_ruby_version" do
    @index = build_index do
      gem "foo", "1.0.0" do
        dep "bar", ">= 0"
      end

      gem "foo", "2.0.0" do |s|
        dep "bar", ">= 0"
        s.required_ruby_version = "~> 2.0.0"
      end

      gem "bar", "1.0.0"

      gem "bar", "2.0.0" do |s|
        s.required_ruby_version = "~> 2.0.0"
      end

      gem "ruby\0", "1.8.7"
    end
    dep "foo"
    dep "ruby\0", "1.8.7"

    deps = []
    @deps.each do |d|
      deps << Bundler::DepProxy.new(d, "ruby")
    end

    should_resolve_and_include %w(foo-1.0.0 bar-1.0.0), [{}, []]
  end

  context "conservative" do
    before :each do
      @index = build_index do
        gem("foo", "1.3.7") { dep "bar", "~> 2.0" }
        gem("foo", "1.3.8") { dep "bar", "~> 2.0" }
        gem("foo", "1.4.3") { dep "bar", "~> 2.0" }
        gem("foo", "1.4.4") { dep "bar", "~> 2.0" }
        gem("foo", "1.4.5") { dep "bar", "~> 2.1" }
        gem("foo", "1.5.0") { dep "bar", "~> 2.1" }
        gem("foo", "1.5.1") { dep "bar", "~> 3.0" }
        gem("foo", "2.0.0") { dep "bar", "~> 3.0" }
        gem "bar", %w(2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0)
      end
      dep "foo"

      # base represents declared dependencies in the Gemfile that are still satisfied by the lockfile
      @base = Bundler::SpecSet.new([])

      # locked represents versions in lockfile
      @locked = locked(%w(foo 1.4.3), %w(bar 2.0.3))
    end

    it "resolves all gems to latest patch" do
      # strict is not set, so bar goes up a minor version due to dependency from foo 1.4.5
      should_conservative_resolve_and_include :patch, [], %w(foo-1.4.5 bar-2.1.1)
    end

    it "resolves all gems to latest patch strict" do
      # strict is set, so foo can only go up to 1.4.4 to avoid bar going up a minor version, and bar can go up to 2.0.5
      should_conservative_resolve_and_include [:patch, :strict], [], %w(foo-1.4.4 bar-2.0.5)
    end

    it "resolves foo only to latest patch - same dependency case" do
      @locked = locked(%w(foo 1.3.7), %w(bar 2.0.3))
      # bar is locked, and the lock holds here because the dependency on bar doesn't change on the matching foo version.
      should_conservative_resolve_and_include :patch, ["foo"], %w(foo-1.3.8 bar-2.0.3)
    end

    it "resolves foo only to latest patch - changing dependency not declared case" do
      # foo is the only gem being requested for update, therefore bar is locked, but bar is NOT
      # declared as a dependency in the Gemfile. In this case, locks don't apply to _changing_
      # dependencies and since the dependency of the selected foo gem changes, the latest matching
      # dependency of "bar", "~> 2.1" -- bar-2.1.1 -- is selected. This is not a bug and follows
      # the long-standing documented Conservative Updating behavior of bundle install.
      # http://bundler.io/v1.12/man/bundle-install.1.html#CONSERVATIVE-UPDATING
      should_conservative_resolve_and_include :patch, ["foo"], %w(foo-1.4.5 bar-2.1.1)
    end

    it "resolves foo only to latest patch - changing dependency declared case" do
      # bar is locked AND a declared dependency in the Gemfile, so it will not move, and therefore
      # foo can only move up to 1.4.4.
      @base << build_spec("bar", "2.0.3").first
      should_conservative_resolve_and_include :patch, ["foo"], %w(foo-1.4.4 bar-2.0.3)
    end

    it "resolves foo only to latest patch strict" do
      # adding strict helps solve the possibly unexpected behavior of bar changing in the prior test case,
      # because no versions will be returned for bar ~> 2.1, so the engine falls back to ~> 2.0 (turn on
      # debugging to see this happen).
      should_conservative_resolve_and_include [:patch, :strict], ["foo"], %w(foo-1.4.4 bar-2.0.3)
    end

    it "resolves bar only to latest patch" do
      # bar is locked, so foo can only go up to 1.4.4
      should_conservative_resolve_and_include :patch, ["bar"], %w(foo-1.4.3 bar-2.0.5)
    end

    it "resolves all gems to latest minor" do
      # strict is not set, so bar goes up a major version due to dependency from foo 1.4.5
      should_conservative_resolve_and_include :minor, [], %w(foo-1.5.1 bar-3.0.0)
    end

    it "resolves all gems to latest minor strict" do
      # strict is set, so foo can only go up to 1.5.0 to avoid bar going up a major version
      should_conservative_resolve_and_include [:minor, :strict], [], %w(foo-1.5.0 bar-2.1.1)
    end

    it "resolves all gems to latest major" do
      should_conservative_resolve_and_include :major, [], %w(foo-2.0.0 bar-3.0.0)
    end

    it "resolves all gems to latest major strict" do
      should_conservative_resolve_and_include [:major, :strict], [], %w(foo-2.0.0 bar-3.0.0)
    end

    # Why would this happen in real life? If bar 2.2 has a bug that the author of foo wants to bypass
    # by reverting the dependency, the author of foo could release a new gem with an older requirement.
    context "revert to previous" do
      before :each do
        @index = build_index do
          gem("foo", "1.4.3") { dep "bar", "~> 2.2" }
          gem("foo", "1.4.4") { dep "bar", "~> 2.1.0" }
          gem("foo", "1.5.0") { dep "bar", "~> 2.0.0" }
          gem "bar", %w(2.0.5 2.1.1 2.2.3)
        end
        dep "foo"

        # base represents declared dependencies in the Gemfile that are still satisfied by the lockfile
        @base = Bundler::SpecSet.new([])

        # locked represents versions in lockfile
        @locked = locked(%w(foo 1.4.3), %w(bar 2.2.3))
      end

      it "could revert to a previous version level patch" do
        should_conservative_resolve_and_include :patch, [], %w(foo-1.4.4 bar-2.1.1)
      end

      it "cannot revert to a previous version in strict mode level patch" do
        # the strict option removes the version required to match, so a version conflict results
        expect do
          should_conservative_resolve_and_include [:patch, :strict], [], %w(foo-1.4.3 bar-2.1.1)
        end.to raise_error Bundler::VersionConflict, /#{Regexp.escape("Could not find gem 'bar (~> 2.1.0)'")}/
      end

      it "could revert to a previous version level minor" do
        should_conservative_resolve_and_include :minor, [], %w(foo-1.5.0 bar-2.0.5)
      end

      it "cannot revert to a previous version in strict mode level minor" do
        # the strict option removes the version required to match, so a version conflict results
        expect do
          should_conservative_resolve_and_include [:minor, :strict], [], %w(foo-1.4.3 bar-2.1.1)
        end.to raise_error Bundler::VersionConflict, /#{Regexp.escape("Could not find gem 'bar (~> 2.0.0)'")}/
      end
    end
  end
end