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 +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:
|