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 +5 -5
- data/lib/molinillo/dependency_graph.rb +11 -2
- data/lib/molinillo/dependency_graph/add_edge_no_circular.rb +9 -2
- data/lib/molinillo/gem_metadata.rb +1 -1
- data/lib/molinillo/modules/ui.rb +1 -1
- data/lib/molinillo/resolution.rb +15 -4
- data/spec/errors_spec.rb +3 -3
- data/spec/fuzz_spec.rb +25 -15
- data/spec/resolver_spec.rb +75 -239
- data/spec/spec_helper.rb +1 -1
- data/spec/spec_helper/equal_dependency_graph.rb +15 -0
- data/spec/spec_helper/index.rb +90 -11
- data/spec/spec_helper/naive_resolver.rb +2 -2
- data/spec/spec_helper/specification.rb +2 -2
- metadata +11 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e49dcc72411487d09ae920c58cafe7df5acb0786af570d085c753282c2d3a622
|
4
|
+
data.tar.gz: 27de8ebaffabf3a5eb757742593a934842ce46ae39d7db498dea9322e75b380b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
27
|
-
edge.destination.incoming_edges
|
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
|
data/lib/molinillo/modules/ui.rb
CHANGED
@@ -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| '
|
51
|
+
output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
data/lib/molinillo/resolution.rb
CHANGED
@@ -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
|
-
|
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 !
|
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(
|
389
|
+
requirements.delete(requirement)
|
379
390
|
end
|
380
391
|
end
|
381
392
|
end
|
data/spec/errors_spec.rb
CHANGED
@@ -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) {
|
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
|
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
|
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
|
data/spec/fuzz_spec.rb
CHANGED
@@ -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
|
-
|
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(:
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
data/spec/resolver_spec.rb
CHANGED
@@ -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
|
-
|
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 =
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
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([
|
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 =
|
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 =
|
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 '
|
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
|
-
|
366
|
-
|
367
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
data/spec/spec_helper/index.rb
CHANGED
@@ -25,19 +25,19 @@ module Molinillo
|
|
25
25
|
def requirement_satisfied_by?(requirement, _activated, spec)
|
26
26
|
case requirement
|
27
27
|
when TestSpecification
|
28
|
-
|
29
|
-
when
|
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
|
-
|
37
|
+
prerelease = dependency_prerelease?(dependency)
|
38
38
|
Array(specs[dependency.name]).select do |spec|
|
39
|
-
(
|
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
|
-
|
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
|
68
|
-
dependency.
|
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(
|
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.
|
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 =
|
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
|
-
|
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.
|
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-
|
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.
|
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:
|