molinillo 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: