molinillo 0.5.5 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a3f407e98934857813a1d4e17fbf807171bfbd02
4
- data.tar.gz: 5ca9cff1741fb50a1c8767a7fb6aadc6465d1b70
2
+ SHA256:
3
+ metadata.gz: e49dcc72411487d09ae920c58cafe7df5acb0786af570d085c753282c2d3a622
4
+ data.tar.gz: 27de8ebaffabf3a5eb757742593a934842ce46ae39d7db498dea9322e75b380b
5
5
  SHA512:
6
- metadata.gz: 0a683263d7bf6566b74e88d948c20a6b2ec18cbbf26ae68e335ec27ab61708c25b9565329b6295e90bad515322196792d94df6f1d651997d84f32066253b6d85
7
- data.tar.gz: d529667799bbb115b1fadd3fffa107fadca0ec456b0c00a97af9404713b45d1ec6a4ae54a8f2309acd68ea068018f7cf63c494b91effcbbf80fc112390340bbb
6
+ metadata.gz: ea0bfaa9a5e53a3ea8cc4719b3a777ac906c6e8b01343c27082059b37990965e44aa7dea190a843ab5b33a542e5da5fc95682092f9d7f2abc604f26f5c0f5cf0
7
+ data.tar.gz: e6603baac5f6bb5f093e5f0139f49ae5f9b298abd1f1c79e42b5f377d3d6ca4206fe3113f54fdd65ae2bb5b86a1f29e7a24dad48773c7c898875c13f4b21b2f4
@@ -98,18 +98,27 @@ module Molinillo
98
98
  "#{self.class}:#{vertices.values.inspect}"
99
99
  end
100
100
 
101
+ # @param [Hash] options options for dot output.
101
102
  # @return [String] Returns a dot format representation of the graph
102
- def to_dot
103
+ def to_dot(options = {})
104
+ edge_label = options.delete(:edge_label)
105
+ raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty?
106
+
103
107
  dot_vertices = []
104
108
  dot_edges = []
105
109
  vertices.each do |n, v|
106
110
  dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]"
107
111
  v.outgoing_edges.each do |e|
108
- dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=\"#{e.requirement}\"]"
112
+ label = edge_label ? edge_label.call(e) : e.requirement
113
+ dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]"
109
114
  end
110
115
  end
116
+
117
+ dot_vertices.uniq!
111
118
  dot_vertices.sort!
119
+ dot_edges.uniq!
112
120
  dot_edges.sort!
121
+
113
122
  dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}')
114
123
  dot.join("\n")
115
124
  end
@@ -23,8 +23,8 @@ module Molinillo
23
23
  # (see Action#down)
24
24
  def down(graph)
25
25
  edge = make_edge(graph)
26
- edge.origin.outgoing_edges.delete(edge)
27
- edge.destination.incoming_edges.delete(edge)
26
+ delete_first(edge.origin.outgoing_edges, edge)
27
+ delete_first(edge.destination.incoming_edges, edge)
28
28
  end
29
29
 
30
30
  # @!group AddEdgeNoCircular
@@ -53,6 +53,13 @@ module Molinillo
53
53
  @destination = destination
54
54
  @requirement = requirement
55
55
  end
56
+
57
+ private
58
+
59
+ def delete_first(array, item)
60
+ return unless index = array.index(item)
61
+ array.delete_at(index)
62
+ end
56
63
  end
57
64
  end
58
65
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module Molinillo
3
3
  # The version of Molinillo.
4
- VERSION = '0.5.5'.freeze
4
+ VERSION = '0.5.6'.freeze
5
5
  end
@@ -48,7 +48,7 @@ module Molinillo
48
48
  if debug?
49
49
  debug_info = yield
50
50
  debug_info = debug_info.inspect unless debug_info.is_a?(String)
51
- output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
51
+ output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
52
52
  end
53
53
  end
54
54
 
@@ -356,13 +356,20 @@ module Molinillo
356
356
  # Ensures there are no orphaned successors to the given {vertex}.
357
357
  # @param [DependencyGraph::Vertex] vertex the vertex to fix up.
358
358
  # @return [void]
359
- def fixup_swapped_children(vertex)
359
+ def fixup_swapped_children(vertex) # rubocop:disable Metrics/CyclomaticComplexity
360
360
  payload = vertex.payload
361
361
  deps = dependencies_for(payload).group_by(&method(:name_for))
362
362
  vertex.outgoing_edges.each do |outgoing_edge|
363
- @parent_of[outgoing_edge.requirement] = states.size - 1
363
+ requirement = outgoing_edge.requirement
364
+ parent_index = @parent_of[requirement]
364
365
  succ = outgoing_edge.destination
365
366
  matching_deps = Array(deps[succ.name])
367
+ dep_matched = matching_deps.include?(requirement)
368
+
369
+ # only reset the parent index when it was originally required by the
370
+ # same named spec
371
+ @parent_of[requirement] = states.size - 1 if parent_index && states[parent_index].name == name
372
+
366
373
  if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex]
367
374
  debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" }
368
375
  succ.requirements.each { |r| @parent_of.delete(r) }
@@ -373,9 +380,13 @@ module Molinillo
373
380
  # so it's safe to delete only based upon name here
374
381
  removed_names.include?(name_for(r))
375
382
  end
376
- elsif !matching_deps.include?(outgoing_edge.requirement)
383
+ elsif !dep_matched
384
+ # also reset if we're removing the edge, but only if its parent has
385
+ # already been fixed up
386
+ @parent_of[requirement] = states.size - 1 if @parent_of[requirement].nil?
387
+
377
388
  activated.delete_edge(outgoing_edge)
378
- requirements.delete(outgoing_edge.requirement)
389
+ requirements.delete(requirement)
379
390
  end
380
391
  end
381
392
  end
@@ -3,14 +3,14 @@ require File.expand_path('../spec_helper', __FILE__)
3
3
 
4
4
  module Molinillo
5
5
  describe NoSuchDependencyError do
6
- let(:dependency) { VersionKit::Dependency.new('foo', '>= 1.0') }
6
+ let(:dependency) { Gem::Dependency.new('foo', '>= 1.0') }
7
7
  let(:required_by) { [] }
8
8
 
9
9
  subject { described_class.new(dependency, required_by) }
10
10
 
11
11
  describe '#message' do
12
12
  it 'says it is unable to find the spec' do
13
- expect(subject.message).to eq('Unable to find a specification for `foo (>= 1.0.0)`')
13
+ expect(subject.message).to eq('Unable to find a specification for `foo (>= 1.0)`')
14
14
  end
15
15
 
16
16
  context 'when #required_by is not empty' do
@@ -18,7 +18,7 @@ module Molinillo
18
18
 
19
19
  it 'includes the source names' do
20
20
  expect(subject.message).to eq(
21
- 'Unable to find a specification for `foo (>= 1.0.0)` depended upon by `spec-1` and `spec-2`')
21
+ 'Unable to find a specification for `foo (>= 1.0)` depended upon by `spec-1` and `spec-2`')
22
22
  end
23
23
  end
24
24
  end
@@ -7,13 +7,14 @@ describe 'fuzzing' do
7
7
  let(:dependencies) do
8
8
  index.specs.keys.sample(Random.rand(5)).
9
9
  map do |d|
10
- VersionKit::Dependency.new(
10
+ Gem::Dependency.new(
11
11
  d,
12
12
  "#{CONSTRAINTS.sample} #{Random.rand(2)}.#{Random.rand(2)}"
13
13
  )
14
14
  end
15
15
  end
16
- let(:index) { Molinillo::TestIndex.from_fixture('fuzz') }
16
+ let(:index_class) { Molinillo::TestIndex }
17
+ let(:index) { index_class.from_fixture('fuzz') }
17
18
  let(:ui) { Molinillo::TestUI.new }
18
19
  let(:resolver) { Molinillo::Resolver.new(index, ui) }
19
20
 
@@ -22,7 +23,7 @@ describe 'fuzzing' do
22
23
  def validate_dependency_graph_from(graph, dependency)
23
24
  vertex = graph.vertex_named(dependency.name)
24
25
  spec = vertex.payload
25
- expect(dependency).to be_satisfied_by(spec.version)
26
+ expect(dependency.requirement).to be_satisfied_by(spec.version)
26
27
  expect(spec.dependencies).to match_array(vertex.outgoing_edges.map(&:requirement))
27
28
  spec.dependencies.each do |d|
28
29
  validate_dependency_graph_from(graph, d)
@@ -48,19 +49,28 @@ describe 'fuzzing' do
48
49
  end
49
50
 
50
51
  def self.fuzz!(seeds = [])
51
- seeds.each do |seed|
52
- it "fuzzes with seed #{seed}" do
53
- Random.srand seed
54
- graph, error = begin
55
- subject
56
- [subject, nil]
57
- rescue => e
58
- [nil, e]
52
+ Molinillo::INDICES.each do |ic|
53
+ context "with #{ic.to_s.split('::').last}" do
54
+ let(:index_class) { ic }
55
+ seeds.each do |seed|
56
+ it "fuzzes with seed #{seed}" do
57
+ Random.srand seed
58
+ graph, error = begin
59
+ subject
60
+ [subject, nil]
61
+ rescue => e
62
+ [nil, e]
63
+ end
64
+ validate_dependency_graph(graph) if graph
65
+ validate_unresolvable(error) if error
66
+
67
+ if naive
68
+ expect(graph).to equal_dependency_graph(naive)
69
+ else
70
+ expect(graph).to be_nil, "#{graph && graph.map(&:payload).map(&:to_s)} vs nil"
71
+ end
72
+ end
59
73
  end
60
- validate_dependency_graph(graph) if graph
61
- validate_unresolvable(error) if error
62
- expect(graph).to eq(naive),
63
- "#{graph && graph.map(&:payload).map(&:to_s)} vs #{naive && naive.map(&:payload).map(&:to_s)}"
64
74
  end
65
75
  end
66
76
  end
@@ -13,11 +13,11 @@ module Molinillo
13
13
  self.name = test_case['name']
14
14
  self.index = TestIndex.from_fixture(test_case['index'] || 'awesome')
15
15
  self.requested = test_case['requested'].map do |(name, reqs)|
16
- VersionKit::Dependency.new name, reqs.split(',').map(&:chomp)
16
+ Gem::Dependency.new name.delete("\x01"), reqs.split(',').map(&:chomp)
17
17
  end
18
18
  add_dependencies_to_graph = lambda do |graph, parent, hash|
19
19
  name = hash['name']
20
- version = VersionKit::Version.new(hash['version'])
20
+ version = Gem::Version.new(hash['version'])
21
21
  dependency = index.specs[name].find { |s| s.version == version }
22
22
  node = if parent
23
23
  graph.add_vertex(name, dependency).tap do |v|
@@ -46,12 +46,11 @@ module Molinillo
46
46
 
47
47
  self.resolver = Resolver.new(index, TestUI.new)
48
48
  end
49
- end
50
49
 
51
- describe Resolver do
52
- describe 'dependency resolution' do
53
- Dir.glob(FIXTURE_CASE_DIR + '**/*.json').map do |fixture|
54
- test_case = TestCase.new(fixture)
50
+ def run(_index_class, context)
51
+ test_case = self
52
+
53
+ context.instance_eval do
55
54
  it test_case.name do
56
55
  resolve = lambda { test_case.resolver.resolve(test_case.requested, test_case.base) }
57
56
 
@@ -74,35 +73,66 @@ module Molinillo
74
73
  end
75
74
  expect(pretty_dependencies.call(result)).to contain_exactly(*pretty_dependencies.call(test_case.result))
76
75
 
77
- expect(result).to eq(test_case.result)
76
+ expect(result).to equal_dependency_graph(test_case.result)
78
77
  end
79
78
  end
80
79
  end
81
80
  end
82
81
 
82
+ def self.save!(path, name, index, requirements, resolved)
83
+ resolved_to_h = proc do |v|
84
+ { :name => v.name, :version => v.payload.version, :dependencies => v.successors.map(&resolved_to_h) }
85
+ end
86
+ resolved = resolved.vertices.reduce([]) do |array, (_n, v)|
87
+ if v.root
88
+ array << resolved_to_h.call(v)
89
+ else
90
+ array
91
+ end
92
+ end
93
+
94
+ File.open(File.join(FIXTURE_CASE_DIR, "#{path}.json"), 'w') do |f|
95
+ f.write JSON.pretty_generate(
96
+ :name => name,
97
+ :index => index,
98
+ :requested => Hash[requirements.map { |r| [r.name, r.requirement.to_s] }],
99
+ :base => [],
100
+ :resolved => resolved.sort_by { |v| v[:name] },
101
+ :conflicts => []
102
+ )
103
+ end
104
+ end
105
+ end
106
+
107
+ describe Resolver do
108
+ describe 'dependency resolution' do
109
+ test_cases = Dir.glob(FIXTURE_CASE_DIR + '**/*.json').map { |fixture| TestCase.new(fixture) }
110
+ INDICES.each do |index_class|
111
+ context "with the #{index_class.to_s.split('::').last} index" do
112
+ test_cases.each { |tc| tc.run(index_class, self) }
113
+ end
114
+ end
115
+ end
116
+
83
117
  describe 'in general' do
84
118
  before do
85
119
  @resolver = described_class.new(TestIndex.from_fixture('awesome'), TestUI.new)
86
120
  end
87
121
 
88
- it 'can resolve a list of 0 requirements' do
89
- expect(@resolver.resolve([], DependencyGraph.new)).to eq(DependencyGraph.new)
90
- end
91
-
92
122
  it 'includes the source of a user-specified unsatisfied dependency' do
93
123
  expect do
94
- @resolver.resolve([VersionKit::Dependency.new('missing', '3.0')], DependencyGraph.new)
124
+ @resolver.resolve([Gem::Dependency.new('missing', '3.0')], DependencyGraph.new)
95
125
  end.to raise_error(VersionConflict, /required by `user-specified dependency`/)
96
126
  end
97
127
 
98
128
  it 'can handle when allow_missing? returns true for the only requirement' do
99
- dep = VersionKit::Dependency.new('missing', '3.0')
129
+ dep = Gem::Dependency.new('missing', '3.0')
100
130
  allow(@resolver.specification_provider).to receive(:allow_missing?).with(dep).and_return(true)
101
131
  expect(@resolver.resolve([dep], DependencyGraph.new).to_a).to be_empty
102
132
  end
103
133
 
104
134
  it 'can handle when allow_missing? returns true for a nested requirement' do
105
- dep = VersionKit::Dependency.new('actionpack', '1.2.3')
135
+ dep = Gem::Dependency.new('actionpack', '1.2.3')
106
136
  allow(@resolver.specification_provider).to receive(:allow_missing?).
107
137
  with(have_attributes(:name => 'activesupport')).and_return(true)
108
138
  allow(@resolver.specification_provider).to receive(:search_for).
@@ -113,227 +143,7 @@ module Molinillo
113
143
  expect(resolved.map(&:payload).map(&:to_s)).to eq(['actionpack (1.2.3)'])
114
144
  end
115
145
 
116
- it 'can resolve when two specs have the same dependencies' do
117
- index = BundlerIndex.from_fixture('rubygems-2016-09-11')
118
- @resolver = described_class.new(index, TestUI.new)
119
- demands = [
120
- VersionKit::Dependency.new('chef', '~> 12.1.2'),
121
- ]
122
-
123
- demands.each { |d| index.search_for(d) }
124
- resolved = @resolver.resolve(demands, DependencyGraph.new)
125
-
126
- expected = [
127
- 'rake (10.5.0)',
128
- 'builder (3.2.2)',
129
- 'ffi (1.9.14)',
130
- 'libyajl2 (1.2.0)',
131
- 'hashie (2.1.2)',
132
- 'mixlib-log (1.7.1)',
133
- 'rack (2.0.1)',
134
- 'uuidtools (2.1.5)',
135
- 'diff-lcs (1.2.5)',
136
- 'erubis (2.7.0)',
137
- 'highline (1.7.8)',
138
- 'mixlib-cli (1.7.0)',
139
- 'mixlib-config (2.2.4)',
140
- 'mixlib-shellout (2.2.7)',
141
- 'net-ssh (2.9.4)',
142
- 'ipaddress (0.8.3)',
143
- 'mime-types (2.99.3)',
144
- 'systemu (2.6.5)',
145
- 'wmi-lite (1.0.0)',
146
- 'plist (3.1.0)',
147
- 'coderay (1.1.1)',
148
- 'method_source (0.8.2)',
149
- 'slop (3.6.0)',
150
- 'rspec-support (3.5.0)',
151
- 'multi_json (1.12.1)',
152
- 'net-telnet (0.1.1)',
153
- 'sfl (2.2.0)',
154
- 'ffi-yajl (1.4.0)',
155
- 'mixlib-authentication (1.4.1)',
156
- 'net-ssh-gateway (1.2.0)',
157
- 'net-scp (1.2.1)',
158
- 'pry (0.10.4)',
159
- 'rspec-core (3.5.3)',
160
- 'rspec-expectations (3.5.0)',
161
- 'rspec-mocks (3.5.0)',
162
- 'chef-zero (4.2.3)',
163
- 'ohai (8.4.0)',
164
- 'net-ssh-multi (1.2.1)',
165
- 'specinfra (2.61.3)',
166
- 'rspec_junit_formatter (0.2.3)',
167
- 'rspec-its (1.2.0)',
168
- 'rspec (3.5.0)',
169
- 'serverspec (2.36.1)',
170
- 'chef (12.1.2)',
171
- ]
172
-
173
- expect(resolved.map(&:payload).map(&:to_s)).to match_array(expected)
174
- end
175
-
176
- it 'can resolve when two specs have the same dependencies and swapping happens' do
177
- index = BundlerIndex.from_fixture('rubygems-2016-10-06')
178
- @resolver = described_class.new(index, TestUI.new)
179
- demands = [
180
- VersionKit::Dependency.new('avro_turf', '0.6.2'),
181
- VersionKit::Dependency.new('fog', '1.38.0'),
182
- ]
183
- demands.each { |d| index.search_for(d) }
184
-
185
- resolved = @resolver.resolve(demands, DependencyGraph.new)
186
-
187
- expected = [
188
- 'pkg-config (1.1.7)',
189
- 'CFPropertyList (2.3.3)',
190
- 'multi_json (1.12.1)',
191
- 'excon (0.45.4)',
192
- 'builder (3.2.2)',
193
- 'formatador (0.2.5)',
194
- 'ipaddress (0.8.3)',
195
- 'xml-simple (1.1.5)',
196
- 'mini_portile2 (2.1.0)',
197
- 'inflecto (0.0.2)',
198
- 'trollop (2.1.2)',
199
- 'fission (0.5.0)',
200
- 'avro (1.8.1)',
201
- 'fog-core (1.37.0)',
202
- 'nokogiri (1.6.8)',
203
- 'avro_turf (0.6.2)',
204
- 'fog-json (1.0.2)',
205
- 'fog-local (0.3.0)',
206
- 'fog-vmfusion (0.1.0)',
207
- 'fog-xml (0.1.2)',
208
- 'rbvmomi (1.8.2)',
209
- 'fog-aliyun (0.1.0)',
210
- 'fog-brightbox (0.11.0)',
211
- 'fog-sakuracloud (1.7.5)',
212
- 'fog-serverlove (0.1.2)',
213
- 'fog-softlayer (1.1.4)',
214
- 'fog-storm_on_demand (0.1.1)',
215
- 'fog-atmos (0.1.0)',
216
- 'fog-aws (0.9.2)',
217
- 'fog-cloudatcost (0.1.2)',
218
- 'fog-dynect (0.0.3)',
219
- 'fog-ecloud (0.3.0)',
220
- 'fog-google (0.1.0)',
221
- 'fog-openstack (0.1.3)',
222
- 'fog-powerdns (0.1.1)',
223
- 'fog-profitbricks (0.0.5)',
224
- 'fog-rackspace (0.1.1)',
225
- 'fog-radosgw (0.0.5)',
226
- 'fog-riakcs (0.1.0)',
227
- 'fog-terremark (0.1.0)',
228
- 'fog-voxel (0.1.0)',
229
- 'fog-xenserver (0.2.3)',
230
- 'fog-vsphere (1.2.0)',
231
- 'fog (1.38.0)',
232
- ]
233
-
234
- expect(resolved.map(&:payload).map(&:to_s).sort).to eq(expected.sort)
235
- end
236
-
237
- it 'can unwind when the conflict has a common parent' do
238
- index = BundlerIndex.from_fixture('rubygems-2016-11-05')
239
- @resolver = described_class.new(index, TestUI.new)
240
- demands = [
241
- VersionKit::Dependency.new('github-pages', '>= 0'),
242
- ]
243
- demands.each { |d| index.search_for(d) }
244
-
245
- resolved = @resolver.resolve(demands, DependencyGraph.new)
246
-
247
- expect(resolved.map(&:payload).map(&:to_s).sort).to include('github-pages (104.0.0)')
248
- end
249
-
250
- it 'can resolve when swapping changes transitive dependencies' do
251
- index = TestIndex.from_fixture('restkit')
252
- def index.sort_dependencies(dependencies, activated, conflicts)
253
- dependencies.sort_by do |d|
254
- [
255
- activated.vertex_named(d.name).payload ? 0 : 1,
256
- dependency_pre_release?(d) ? 0 : 1,
257
- conflicts[d.name] ? 0 : 1,
258
- search_for(d).count,
259
- ]
260
- end
261
- end
262
-
263
- def index.requirement_satisfied_by?(requirement, activated, spec)
264
- existing_vertices = activated.vertices.values.select do |v|
265
- v.name.split('/').first == requirement.name.split('/').first
266
- end
267
- existing = existing_vertices.map(&:payload).compact.first
268
- if existing
269
- existing.version == spec.version && requirement.satisfied_by?(spec.version)
270
- else
271
- requirement.satisfied_by? spec.version
272
- end
273
- end
274
-
275
- @resolver = described_class.new(index, TestUI.new)
276
- demands = [
277
- VersionKit::Dependency.new('RestKit', '~> 0.23.0'),
278
- VersionKit::Dependency.new('RestKit', '<= 0.23.2'),
279
- ]
280
-
281
- resolved = @resolver.resolve(demands, DependencyGraph.new)
282
-
283
- expected = [
284
- 'RestKit (0.23.2)',
285
- 'RestKit/Core (0.23.2)',
286
- ]
287
-
288
- expect(resolved.map(&:payload).map(&:to_s)).to match_array(expected)
289
- end
290
-
291
- # Regression test. See: https://github.com/CocoaPods/Molinillo/pull/38
292
- it 'can resolve when swapping children with successors' do
293
- swap_child_with_successors_index = Class.new(TestIndex) do
294
- # The bug we want to write a regression test for only occurs when
295
- # Molinillo processes dependencies in a specific order for the given
296
- # index and demands. This sorting logic ensures we hit the repro case
297
- # when using the index file "swap_child_with_successors"
298
- def sort_dependencies(dependencies, activated, conflicts)
299
- dependencies.sort_by do |dependency|
300
- name = name_for(dependency)
301
- [
302
- activated.vertex_named(name).payload ? 0 : 1,
303
- conflicts[name] ? 0 : 1,
304
- activated.vertex_named(name).payload ? 0 : versions_of(name),
305
- ]
306
- end
307
- end
308
-
309
- def versions_of(dependency_name)
310
- Array(specs[dependency_name]).count
311
- end
312
- end
313
-
314
- index = swap_child_with_successors_index.from_fixture('swap_child_with_successors')
315
- @resolver = described_class.new(index, TestUI.new)
316
- demands = [
317
- VersionKit::Dependency.new('build-essential', '>= 0.0.0'),
318
- VersionKit::Dependency.new('nginx', '>= 0.0.0'),
319
- ]
320
-
321
- resolved = @resolver.resolve(demands, DependencyGraph.new)
322
-
323
- expected = [
324
- 'build-essential (2.4.0)',
325
- '7-zip (1.0.0)',
326
- 'windows (1.39.2)',
327
- 'chef-handler (1.3.0)',
328
- 'nginx (2.7.6)',
329
- 'yum-epel (0.6.6)',
330
- 'yum (3.10.0)',
331
- ]
332
-
333
- expect(resolved.map(&:payload).map(&:to_s)).to match_array(expected)
334
- end
335
-
336
- it 'can resolve ur face' do
146
+ it 'only cleans up orphaned nodes after swapping' do
337
147
  index = TestIndex.new(
338
148
  'a' => [
339
149
  TestSpecification.new('name' => 'a', 'version' => '1.0.0', 'dependencies' => { 'z' => '= 2.0.0' }),
@@ -362,9 +172,9 @@ module Molinillo
362
172
  end
363
173
  @resolver = described_class.new(index, TestUI.new)
364
174
  demands = [
365
- VersionKit::Dependency.new('c', '= 1.0.0'),
366
- VersionKit::Dependency.new('c', '>= 1.0.0'),
367
- VersionKit::Dependency.new('z', '>= 1.0.0'),
175
+ Gem::Dependency.new('c', '= 1.0.0'),
176
+ Gem::Dependency.new('c', '>= 1.0.0'),
177
+ Gem::Dependency.new('z', '>= 1.0.0'),
368
178
  ]
369
179
 
370
180
  resolved = @resolver.resolve(demands, DependencyGraph.new)
@@ -376,6 +186,32 @@ module Molinillo
376
186
 
377
187
  expect(resolved.map(&:payload).map(&:to_s)).to match_array(expected)
378
188
  end
189
+
190
+ it 'does not reset parent tracking after swapping when another requirement led to the child' do
191
+ demands = [
192
+ Gem::Dependency.new('autobuild'),
193
+ Gem::Dependency.new('pastel'),
194
+ Gem::Dependency.new('tty-prompt'),
195
+ Gem::Dependency.new('tty-table'),
196
+ ]
197
+
198
+ index = BundlerIndex.from_fixture('rubygems-2017-01-24')
199
+ index.specs['autobuild'] = [
200
+ TestSpecification.new('name' => 'autobuild',
201
+ 'version' => '0.1.0',
202
+ 'dependencies' => {
203
+ 'tty-prompt' => '>= 0.6.0, ~> 0.6.0',
204
+ 'pastel' => '>= 0.6.0, ~> 0.6.0',
205
+ }),
206
+ ]
207
+
208
+ @resolver = described_class.new(index, TestUI.new)
209
+ demands.each { |d| index.search_for(d) }
210
+
211
+ resolved = @resolver.resolve(demands, DependencyGraph.new)
212
+
213
+ expect(resolved.map(&:payload).map(&:to_s).sort).to include('pastel (0.6.1)', 'tty-table (0.6.0)')
214
+ end
379
215
  end
380
216
  end
381
217
  end
@@ -29,12 +29,12 @@ ROOT = Pathname.new(File.expand_path('../../', __FILE__))
29
29
  $LOAD_PATH.unshift((ROOT + 'lib').to_s)
30
30
  $LOAD_PATH.unshift((ROOT + 'spec').to_s)
31
31
 
32
- require 'version_kit'
33
32
  require 'molinillo'
34
33
 
35
34
  require 'spec_helper/index'
36
35
  require 'spec_helper/specification'
37
36
  require 'spec_helper/ui'
37
+ require 'spec_helper/equal_dependency_graph'
38
38
 
39
39
  RSpec.configure do |config|
40
40
  # Enable flags like --only-failures and --next-failure
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ RSpec::Matchers.define :equal_dependency_graph do |expected|
3
+ diffable
4
+ attr_reader :actual, :expected
5
+
6
+ match do |actual|
7
+ @expected = expected.to_dot(:edge_label => proc { |e| e.destination.payload.version })
8
+ @actual = actual.to_dot(:edge_label => proc { |e| e.destination.payload.version })
9
+ actual == expected
10
+ end
11
+
12
+ failure_message do
13
+ 'Expected the two dependency graphs to be equal'
14
+ end
15
+ end
@@ -25,19 +25,19 @@ module Molinillo
25
25
  def requirement_satisfied_by?(requirement, _activated, spec)
26
26
  case requirement
27
27
  when TestSpecification
28
- VersionKit::Dependency.new(requirement.name, requirement.version).satisfied_by?(spec.version)
29
- when VersionKit::Dependency
30
- requirement.satisfied_by?(spec.version)
28
+ requirement.version == spec.version
29
+ when Gem::Dependency
30
+ requirement.requirement.satisfied_by?(spec.version)
31
31
  end
32
32
  end
33
33
 
34
34
  def search_for(dependency)
35
35
  @search_for ||= {}
36
36
  @search_for[dependency] ||= begin
37
- pre_release = dependency_pre_release?(dependency)
37
+ prerelease = dependency_prerelease?(dependency)
38
38
  Array(specs[dependency.name]).select do |spec|
39
- (pre_release ? true : !spec.version.pre_release?) &&
40
- dependency.satisfied_by?(spec.version)
39
+ (prerelease ? true : !spec.version.prerelease?) &&
40
+ dependency.requirement.satisfied_by?(spec.version)
41
41
  end
42
42
  end
43
43
  @search_for[dependency].dup
@@ -55,7 +55,7 @@ module Molinillo
55
55
  dependencies.sort_by do |d|
56
56
  [
57
57
  activated.vertex_named(d.name).payload ? 0 : 1,
58
- dependency_pre_release?(d) ? 0 : 1,
58
+ dependency_prerelease?(d) ? 0 : 1,
59
59
  conflicts[d.name] ? 0 : 1,
60
60
  activated.vertex_named(d.name).payload ? 0 : search_for(d).count,
61
61
  ]
@@ -64,10 +64,8 @@ module Molinillo
64
64
 
65
65
  private
66
66
 
67
- def dependency_pre_release?(dependency)
68
- dependency.requirement_list.requirements.any? do |r|
69
- VersionKit::Version.new(r.reference_version).pre_release?
70
- end
67
+ def dependency_prerelease?(dependency)
68
+ dependency.prerelease?
71
69
  end
72
70
  end
73
71
 
@@ -80,10 +78,91 @@ module Molinillo
80
78
  name = name_for(dependency)
81
79
  [
82
80
  activated.vertex_named(name).payload ? 0 : 1,
81
+ amount_constrained(dependency),
83
82
  conflicts[name] ? 0 : 1,
84
83
  activated.vertex_named(name).payload ? 0 : search_for(dependency).count,
85
84
  ]
86
85
  end
87
86
  end
87
+
88
+ def amount_constrained(dependency)
89
+ @amount_constrained ||= {}
90
+ @amount_constrained[dependency.name] ||= begin
91
+ all = specs[dependency.name].size
92
+ if all <= 1
93
+ all #- 1_000_000
94
+ else
95
+ search = search_for(dependency).size
96
+ search - all
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ class ReverseBundlerIndex < BundlerIndex
103
+ def sort_dependencies(*)
104
+ super.reverse
105
+ end
106
+ end
107
+
108
+ class RandomSortIndex < TestIndex
109
+ def sort_dependencies(dependencies, _activated, _conflicts)
110
+ dependencies.shuffle
111
+ end
88
112
  end
113
+
114
+ class CocoaPodsIndex < TestIndex
115
+ def sort_dependencies(dependencies, activated, conflicts)
116
+ dependencies.sort_by do |d|
117
+ [
118
+ activated.vertex_named(d.name).payload ? 0 : 1,
119
+ dependency_prerelease?(d) ? 0 : 1,
120
+ conflicts[d.name] ? 0 : 1,
121
+ search_for(d).count,
122
+ ]
123
+ end
124
+ end
125
+
126
+ def requirement_satisfied_by?(requirement, activated, spec)
127
+ existing_vertices = activated.vertices.values.select do |v|
128
+ v.name.split('/').first == requirement.name.split('/').first
129
+ end
130
+ existing = existing_vertices.map(&:payload).compact.first
131
+ if existing
132
+ existing.version == spec.version && requirement.requirement.satisfied_by?(spec.version)
133
+ else
134
+ requirement.requirement.satisfied_by? spec.version
135
+ end
136
+ end
137
+ end
138
+
139
+ class BerkshelfIndex < TestIndex
140
+ # The bug we want to write a regression test for only occurs when
141
+ # Molinillo processes dependencies in a specific order for the given
142
+ # index and demands. This sorting logic ensures we hit the repro case
143
+ # when using the index file "swap_child_with_successors"
144
+ def sort_dependencies(dependencies, activated, conflicts)
145
+ dependencies.sort_by do |dependency|
146
+ name = name_for(dependency)
147
+ [
148
+ activated.vertex_named(name).payload ? 0 : 1,
149
+ conflicts[name] ? 0 : 1,
150
+ activated.vertex_named(name).payload ? 0 : versions_of(name),
151
+ ]
152
+ end
153
+ end
154
+
155
+ def versions_of(dependency_name)
156
+ Array(specs[dependency_name]).count
157
+ end
158
+ end
159
+
160
+ INDICES = [
161
+ TestIndex,
162
+ BundlerIndex,
163
+ ReverseBundlerIndex,
164
+ RandomSortIndex,
165
+ CocoaPodsIndex,
166
+ BerkshelfIndex,
167
+ ].freeze
89
168
  end
@@ -15,9 +15,9 @@ module Molinillo
15
15
  loop do
16
16
  vertex = activated.find { |a| !a.requirements.empty? && a.payload.nil? }
17
17
  break unless vertex
18
- possibilities = possibilities_by_level[level] ||= index.search_for(VersionKit::Dependency.new(vertex.name, '>= 0.0.0-a'))
18
+ possibilities = possibilities_by_level[level] ||= index.search_for(Gem::Dependency.new(vertex.name, '>= 0.0.0-a'))
19
19
  possibilities.select! do |spec|
20
- vertex.requirements.all? { |r| r.satisfied_by?(spec.version) && (!spec.version.pre_release? || index.send(:dependency_pre_release?, r)) } &&
20
+ vertex.requirements.all? { |r| r.requirement.satisfied_by?(spec.version) && (!spec.version.prerelease? || index.send(:dependency_prerelease?, r)) } &&
21
21
  spec.dependencies.all? { |d| v = activated.vertex_named(d.name); !v || !v.payload || d.satisfied_by?(v.payload.version) }
22
22
  end
23
23
  warn "level = #{level} possibilities = #{possibilities.map(&:to_s)} requirements = #{vertex.requirements.map(&:to_s)}"
@@ -4,10 +4,10 @@ module Molinillo
4
4
  attr_accessor :name, :version, :dependencies
5
5
  def initialize(hash)
6
6
  self.name = hash['name']
7
- self.version = VersionKit::Version.new(hash['version'])
7
+ self.version = Gem::Version.new(hash['version'])
8
8
  self.dependencies = hash.fetch('dependencies') { Hash.new }.map do |(name, requirement)|
9
9
  requirements = requirement.split(',').map(&:chomp)
10
- VersionKit::Dependency.new(name, requirements)
10
+ Gem::Dependency.new(name, requirements)
11
11
  end
12
12
  end
13
13
 
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: molinillo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel E. Giddins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-07 00:00:00.000000000 Z
11
+ date: 2017-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.5'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  description:
@@ -74,6 +74,7 @@ files:
74
74
  - spec/resolver_integration_specs/index_from_rubygems.rb
75
75
  - spec/resolver_spec.rb
76
76
  - spec/spec_helper.rb
77
+ - spec/spec_helper/equal_dependency_graph.rb
77
78
  - spec/spec_helper/index.rb
78
79
  - spec/spec_helper/naive_resolver.rb
79
80
  - spec/spec_helper/specification.rb
@@ -89,17 +90,17 @@ require_paths:
89
90
  - lib
90
91
  required_ruby_version: !ruby/object:Gem::Requirement
91
92
  requirements:
92
- - - '>='
93
+ - - ">="
93
94
  - !ruby/object:Gem::Version
94
95
  version: '0'
95
96
  required_rubygems_version: !ruby/object:Gem::Requirement
96
97
  requirements:
97
- - - '>='
98
+ - - ">="
98
99
  - !ruby/object:Gem::Version
99
100
  version: '0'
100
101
  requirements: []
101
102
  rubyforge_project:
102
- rubygems_version: 2.6.7
103
+ rubygems_version: 2.6.10
103
104
  signing_key:
104
105
  specification_version: 4
105
106
  summary: Provides support for dependency resolution
@@ -110,10 +111,10 @@ test_files:
110
111
  - spec/fuzz_spec.rb
111
112
  - spec/resolver_integration_specs/index_from_rubygems.rb
112
113
  - spec/resolver_spec.rb
114
+ - spec/spec_helper/equal_dependency_graph.rb
113
115
  - spec/spec_helper/index.rb
114
116
  - spec/spec_helper/naive_resolver.rb
115
117
  - spec/spec_helper/specification.rb
116
118
  - spec/spec_helper/ui.rb
117
119
  - spec/spec_helper.rb
118
120
  - spec/state_spec.rb
119
- has_rdoc: