molinillo 0.5.7 → 0.6.0
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/ARCHITECTURE.md +102 -0
- data/CHANGELOG.md +352 -0
- data/lib/molinillo.rb +2 -0
- data/lib/molinillo/compatibility.rb +26 -0
- data/lib/molinillo/delegates/resolution_state.rb +7 -0
- data/lib/molinillo/delegates/specification_provider.rb +1 -0
- data/lib/molinillo/dependency_graph.rb +3 -2
- data/lib/molinillo/dependency_graph/action.rb +1 -0
- data/lib/molinillo/dependency_graph/add_edge_no_circular.rb +1 -0
- data/lib/molinillo/dependency_graph/add_vertex.rb +1 -0
- data/lib/molinillo/dependency_graph/delete_edge.rb +1 -0
- data/lib/molinillo/dependency_graph/detach_vertex_named.rb +1 -0
- data/lib/molinillo/dependency_graph/log.rb +1 -0
- data/lib/molinillo/dependency_graph/set_payload.rb +1 -0
- data/lib/molinillo/dependency_graph/tag.rb +1 -0
- data/lib/molinillo/dependency_graph/vertex.rb +3 -2
- data/lib/molinillo/errors.rb +69 -6
- data/lib/molinillo/gem_metadata.rb +2 -1
- data/lib/molinillo/modules/specification_provider.rb +1 -0
- data/lib/molinillo/modules/ui.rb +3 -1
- data/lib/molinillo/resolution.rb +495 -145
- data/lib/molinillo/resolver.rb +1 -0
- data/lib/molinillo/state.rb +8 -4
- metadata +8 -30
- data/spec/dependency_graph/log_spec.rb +0 -29
- data/spec/dependency_graph_spec.rb +0 -74
- data/spec/errors_spec.rb +0 -26
- data/spec/fuzz_spec.rb +0 -94
- data/spec/resolver_integration_specs/index_from_rubygems.rb +0 -76
- data/spec/resolver_spec.rb +0 -235
- data/spec/spec_helper.rb +0 -44
- data/spec/spec_helper/equal_dependency_graph.rb +0 -16
- data/spec/spec_helper/index.rb +0 -186
- data/spec/spec_helper/naive_resolver.rb +0 -48
- data/spec/spec_helper/specification.rb +0 -28
- data/spec/spec_helper/ui.rb +0 -14
- data/spec/state_spec.rb +0 -30
data/lib/molinillo/resolver.rb
CHANGED
data/lib/molinillo/state.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Molinillo
|
3
4
|
# A state that a {Resolution} can be in
|
4
5
|
# @attr [String] name the name of the current requirement
|
@@ -7,7 +8,8 @@ module Molinillo
|
|
7
8
|
# @attr [Object] requirement the current requirement
|
8
9
|
# @attr [Object] possibilities the possibilities to satisfy the current requirement
|
9
10
|
# @attr [Integer] depth the depth of the resolution
|
10
|
-
# @attr [
|
11
|
+
# @attr [Hash] conflicts unresolved conflicts, indexed by dependency name
|
12
|
+
# @attr [Array<UnwindDetails>] unused_unwind_options unwinds for previous conflicts that weren't explored
|
11
13
|
ResolutionState = Struct.new(
|
12
14
|
:name,
|
13
15
|
:requirements,
|
@@ -15,14 +17,15 @@ module Molinillo
|
|
15
17
|
:requirement,
|
16
18
|
:possibilities,
|
17
19
|
:depth,
|
18
|
-
:conflicts
|
20
|
+
:conflicts,
|
21
|
+
:unused_unwind_options
|
19
22
|
)
|
20
23
|
|
21
24
|
class ResolutionState
|
22
25
|
# Returns an empty resolution state
|
23
26
|
# @return [ResolutionState] an empty state
|
24
27
|
def self.empty
|
25
|
-
new(nil, [], DependencyGraph.new, nil, nil, 0,
|
28
|
+
new(nil, [], DependencyGraph.new, nil, nil, 0, {}, [])
|
26
29
|
end
|
27
30
|
end
|
28
31
|
|
@@ -40,7 +43,8 @@ module Molinillo
|
|
40
43
|
requirement,
|
41
44
|
[possibilities.pop],
|
42
45
|
depth + 1,
|
43
|
-
conflicts.dup
|
46
|
+
conflicts.dup,
|
47
|
+
unused_unwind_options.dup
|
44
48
|
).tap do |state|
|
45
49
|
state.activated.tag(state)
|
46
50
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: molinillo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
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-07-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -45,9 +45,12 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
+
- ARCHITECTURE.md
|
49
|
+
- CHANGELOG.md
|
48
50
|
- LICENSE
|
49
51
|
- README.md
|
50
52
|
- lib/molinillo.rb
|
53
|
+
- lib/molinillo/compatibility.rb
|
51
54
|
- lib/molinillo/delegates/resolution_state.rb
|
52
55
|
- lib/molinillo/delegates/specification_provider.rb
|
53
56
|
- lib/molinillo/dependency_graph.rb
|
@@ -67,19 +70,6 @@ files:
|
|
67
70
|
- lib/molinillo/resolution.rb
|
68
71
|
- lib/molinillo/resolver.rb
|
69
72
|
- lib/molinillo/state.rb
|
70
|
-
- spec/dependency_graph/log_spec.rb
|
71
|
-
- spec/dependency_graph_spec.rb
|
72
|
-
- spec/errors_spec.rb
|
73
|
-
- spec/fuzz_spec.rb
|
74
|
-
- spec/resolver_integration_specs/index_from_rubygems.rb
|
75
|
-
- spec/resolver_spec.rb
|
76
|
-
- spec/spec_helper.rb
|
77
|
-
- spec/spec_helper/equal_dependency_graph.rb
|
78
|
-
- spec/spec_helper/index.rb
|
79
|
-
- spec/spec_helper/naive_resolver.rb
|
80
|
-
- spec/spec_helper/specification.rb
|
81
|
-
- spec/spec_helper/ui.rb
|
82
|
-
- spec/state_spec.rb
|
83
73
|
homepage: https://github.com/CocoaPods/Molinillo
|
84
74
|
licenses:
|
85
75
|
- MIT
|
@@ -100,21 +90,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
90
|
version: '0'
|
101
91
|
requirements: []
|
102
92
|
rubyforge_project:
|
103
|
-
rubygems_version: 2.6.
|
93
|
+
rubygems_version: 2.6.12
|
104
94
|
signing_key:
|
105
95
|
specification_version: 4
|
106
96
|
summary: Provides support for dependency resolution
|
107
|
-
test_files:
|
108
|
-
|
109
|
-
- spec/dependency_graph_spec.rb
|
110
|
-
- spec/errors_spec.rb
|
111
|
-
- spec/fuzz_spec.rb
|
112
|
-
- spec/resolver_integration_specs/index_from_rubygems.rb
|
113
|
-
- spec/resolver_spec.rb
|
114
|
-
- spec/spec_helper/equal_dependency_graph.rb
|
115
|
-
- spec/spec_helper/index.rb
|
116
|
-
- spec/spec_helper/naive_resolver.rb
|
117
|
-
- spec/spec_helper/specification.rb
|
118
|
-
- spec/spec_helper/ui.rb
|
119
|
-
- spec/spec_helper.rb
|
120
|
-
- spec/state_spec.rb
|
97
|
+
test_files: []
|
98
|
+
has_rdoc:
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require 'spec_helper'
|
3
|
-
|
4
|
-
describe Molinillo::DependencyGraph::Log do
|
5
|
-
shared_examples_for 'replay' do |steps|
|
6
|
-
it 'replays the log' do
|
7
|
-
copy = Molinillo::DependencyGraph.new
|
8
|
-
graph = Molinillo::DependencyGraph.new.tap { |g| steps.each { |s| s.call(g) } }
|
9
|
-
graph.log.each { |a| a.up(copy) }
|
10
|
-
expect(copy).to eq(graph)
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'can undo to an empty graph' do
|
14
|
-
graph = Molinillo::DependencyGraph.new
|
15
|
-
graph.tag(self)
|
16
|
-
steps.each { |s| s.call(graph) }
|
17
|
-
graph.log.rewind_to(graph, self)
|
18
|
-
expect(graph).to eq(Molinillo::DependencyGraph.new)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
it_behaves_like 'replay', []
|
23
|
-
it_behaves_like 'replay', [
|
24
|
-
proc { |g| g.add_child_vertex('Foo', 1, [nil], 4) },
|
25
|
-
proc { |g| g.add_child_vertex('Bar', 2, ['Foo', nil], 3) },
|
26
|
-
proc { |g| g.add_child_vertex('Baz', 3, %w(Foo Bar), 2) },
|
27
|
-
proc { |g| g.add_child_vertex('Foo', 4, [], 1) },
|
28
|
-
]
|
29
|
-
end
|
@@ -1,74 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require File.expand_path('../spec_helper', __FILE__)
|
3
|
-
|
4
|
-
module Molinillo
|
5
|
-
describe DependencyGraph do
|
6
|
-
describe 'in general' do
|
7
|
-
before do
|
8
|
-
@graph = described_class.new
|
9
|
-
@root = @graph.add_vertex('Root', 'Root', true)
|
10
|
-
@root2 = @graph.add_vertex('Root2', 'Root2', true)
|
11
|
-
@child = @graph.add_child_vertex('Child', 'Child', %w(Root), 'Child')
|
12
|
-
end
|
13
|
-
|
14
|
-
it 'returns root vertices by name' do
|
15
|
-
expect(@graph.root_vertex_named('Root')).to eq(@root)
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'returns vertices by name' do
|
19
|
-
expect(@graph.vertex_named('Root')).to eq(@root)
|
20
|
-
expect(@graph.vertex_named('Child')).to eq(@child)
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'returns nil for non-existent root vertices' do
|
24
|
-
expect(@graph.root_vertex_named('missing')).to be_nil
|
25
|
-
end
|
26
|
-
|
27
|
-
it 'returns nil for non-existent vertices' do
|
28
|
-
expect(@graph.vertex_named('missing')).to be_nil
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
describe 'detaching a node' do
|
33
|
-
before do
|
34
|
-
@graph = described_class.new
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'detaches a root vertex without successors' do
|
38
|
-
root = @graph.add_vertex('root', 'root', true)
|
39
|
-
@graph.detach_vertex_named(root.name)
|
40
|
-
expect(@graph.vertex_named(root.name)).to be_nil
|
41
|
-
expect(@graph.vertices).to be_empty
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'detaches a root vertex with successors' do
|
45
|
-
root = @graph.add_vertex('root', 'root', true)
|
46
|
-
child = @graph.add_child_vertex('child', 'child', %w(root), 'child')
|
47
|
-
@graph.detach_vertex_named(root.name)
|
48
|
-
expect(@graph.vertex_named(root.name)).to be_nil
|
49
|
-
expect(@graph.vertex_named(child.name)).to be_nil
|
50
|
-
expect(@graph.vertices).to be_empty
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'detaches a root vertex with successors with other parents' do
|
54
|
-
root = @graph.add_vertex('root', 'root', true)
|
55
|
-
root2 = @graph.add_vertex('root2', 'root2', true)
|
56
|
-
child = @graph.add_child_vertex('child', 'child', %w(root root2), 'child')
|
57
|
-
@graph.detach_vertex_named(root.name)
|
58
|
-
expect(@graph.vertex_named(root.name)).to be_nil
|
59
|
-
expect(@graph.vertex_named(child.name)).to eq(child)
|
60
|
-
expect(child.predecessors).to contain_exactly(root2)
|
61
|
-
expect(@graph.vertices.count).to eq(2)
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'detaches a vertex with predecessors' do
|
65
|
-
parent = @graph.add_vertex('parent', 'parent', true)
|
66
|
-
child = @graph.add_child_vertex('child', 'child', %w(parent), 'child')
|
67
|
-
@graph.detach_vertex_named(child.name)
|
68
|
-
expect(@graph.vertex_named(child.name)).to be_nil
|
69
|
-
expect(@graph.vertices).to eq(parent.name => parent)
|
70
|
-
expect(parent.outgoing_edges).to be_empty
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
data/spec/errors_spec.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require File.expand_path('../spec_helper', __FILE__)
|
3
|
-
|
4
|
-
module Molinillo
|
5
|
-
describe NoSuchDependencyError do
|
6
|
-
let(:dependency) { Gem::Dependency.new('foo', '>= 1.0') }
|
7
|
-
let(:required_by) { [] }
|
8
|
-
|
9
|
-
subject { described_class.new(dependency, required_by) }
|
10
|
-
|
11
|
-
describe '#message' do
|
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)`')
|
14
|
-
end
|
15
|
-
|
16
|
-
context 'when #required_by is not empty' do
|
17
|
-
let(:required_by) { %w(spec-1 spec-2) }
|
18
|
-
|
19
|
-
it 'includes the source names' do
|
20
|
-
expect(subject.message).to eq(
|
21
|
-
'Unable to find a specification for `foo (>= 1.0)` depended upon by `spec-1` and `spec-2`')
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
data/spec/fuzz_spec.rb
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require 'spec_helper'
|
3
|
-
require 'spec_helper/naive_resolver'
|
4
|
-
|
5
|
-
describe 'fuzzing' do
|
6
|
-
CONSTRAINTS = %w(<= ~> > < >= =).freeze
|
7
|
-
let(:dependencies) do
|
8
|
-
index.specs.keys.sample(Random.rand(5)).
|
9
|
-
map do |d|
|
10
|
-
Gem::Dependency.new(
|
11
|
-
d,
|
12
|
-
"#{CONSTRAINTS.sample} #{Random.rand(2)}.#{Random.rand(2)}"
|
13
|
-
)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
let(:index_class) { Molinillo::TestIndex }
|
17
|
-
let(:index) { index_class.from_fixture('fuzz') }
|
18
|
-
let(:ui) { Molinillo::TestUI.new }
|
19
|
-
let(:resolver) { Molinillo::Resolver.new(index, ui) }
|
20
|
-
|
21
|
-
subject { resolver.resolve(dependencies) }
|
22
|
-
|
23
|
-
def validate_dependency_graph_from(graph, dependency)
|
24
|
-
vertex = graph.vertex_named(dependency.name)
|
25
|
-
spec = vertex.payload
|
26
|
-
expect(dependency.requirement).to be_satisfied_by(spec.version)
|
27
|
-
expect(spec.dependencies).to match_array(vertex.outgoing_edges.map(&:requirement))
|
28
|
-
spec.dependencies.each do |d|
|
29
|
-
validate_dependency_graph_from(graph, d)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def validate_dependency_graph(graph)
|
34
|
-
dependencies.each do |d|
|
35
|
-
validate_dependency_graph_from(graph, d)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def all_possible_graphs
|
40
|
-
dependencies.reduce([]) { |d| strings(graphs, d) }
|
41
|
-
end
|
42
|
-
|
43
|
-
let(:naive) { Molinillo::NaiveResolver.resolve(index, dependencies) }
|
44
|
-
|
45
|
-
def validate_unresolvable(error)
|
46
|
-
expect(naive).to be_nil,
|
47
|
-
'Got an error resolving but the naive resolver found ' \
|
48
|
-
"#{naive && naive.map(&:payload).map(&:to_s)}:\n#{error}"
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.fuzz!(seeds = [])
|
52
|
-
Molinillo::INDICES.each do |ic|
|
53
|
-
context "with #{ic.to_s.split('::').last}" do
|
54
|
-
around(:example) do |ex|
|
55
|
-
old_seed = Random::DEFAULT.seed
|
56
|
-
ex.run
|
57
|
-
Random.srand old_seed
|
58
|
-
end
|
59
|
-
|
60
|
-
let(:index_class) { ic }
|
61
|
-
seeds.each do |seed|
|
62
|
-
it "fuzzes with seed #{seed}" do
|
63
|
-
Random.srand seed
|
64
|
-
graph, error = begin
|
65
|
-
subject
|
66
|
-
[subject, nil]
|
67
|
-
rescue => e
|
68
|
-
[nil, e]
|
69
|
-
end
|
70
|
-
validate_dependency_graph(graph) if graph
|
71
|
-
validate_unresolvable(error) if error
|
72
|
-
|
73
|
-
if naive
|
74
|
-
expect(graph).to equal_dependency_graph(naive)
|
75
|
-
else
|
76
|
-
expect(graph).to be_nil, "#{graph && graph.map(&:payload).map(&:to_s)} vs nil"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
fuzz! [
|
85
|
-
8,
|
86
|
-
9,
|
87
|
-
125,
|
88
|
-
188,
|
89
|
-
666,
|
90
|
-
7_898_789,
|
91
|
-
0,
|
92
|
-
3,
|
93
|
-
].concat(Array.new(ENV.fetch('MOLINILLO_FUZZER', '0').to_i) { Random.rand })
|
94
|
-
end if RUBY_VERSION >= '1.9'
|
@@ -1,76 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
require 'open-uri'
|
5
|
-
require 'set'
|
6
|
-
|
7
|
-
GEMS = %w(chef).freeze
|
8
|
-
|
9
|
-
VERSION_PATTERN = /\A
|
10
|
-
[0-9]+\.[0-9]+\.[0-9]+ (?# Number component)
|
11
|
-
([-][0-9a-z-]+(\.[0-9a-z-]+)*)? (?# Pre-release component)
|
12
|
-
([+][0-9a-z-]+(\.[0-9a-z-]+)*)? (?# Build component)
|
13
|
-
\Z/xi
|
14
|
-
|
15
|
-
def coerce_to_semver(version)
|
16
|
-
return version if version.sub(/^(\S+\s+)/, '') =~ VERSION_PATTERN
|
17
|
-
return "#{Regexp.last_match[1]}#{Regexp.last_match[2]}" if version =~ /^(\S+\s+)? (\d+\.\d+\.\d+) (?: \.\d+)*$/ix
|
18
|
-
|
19
|
-
parts = version.split(/[\.-]/, 4)
|
20
|
-
4.times do |i|
|
21
|
-
if parts[i] =~ /-?([a-zA-Z])/
|
22
|
-
parts << '0' until parts.size >= 3
|
23
|
-
parts[i].sub!(/-?([a-zA-Z]+)/, '')
|
24
|
-
parts[i] = '0' if parts[i].empty?
|
25
|
-
parts[3] = Regexp.last_match[1] + parts[i..-1].join('')
|
26
|
-
end
|
27
|
-
end
|
28
|
-
semver = parts[0..2].join('.')
|
29
|
-
semver.sub!(/([a-zA-Z])/, '-\1')
|
30
|
-
semver += '-' + parts[-1] if parts.size > 3
|
31
|
-
semver.chomp(".")
|
32
|
-
end
|
33
|
-
|
34
|
-
def coerce_dependencies_to_semver(deps)
|
35
|
-
dependencies = {}
|
36
|
-
deps.sort_by(&:first).each do |name, req|
|
37
|
-
dependencies[name] = req.split(',').map { |r| coerce_to_semver(r) }.join(',')
|
38
|
-
end
|
39
|
-
dependencies
|
40
|
-
end
|
41
|
-
|
42
|
-
gems = Set.new(GEMS)
|
43
|
-
downloaded_gems = Set.new
|
44
|
-
specs = []
|
45
|
-
|
46
|
-
loop do
|
47
|
-
size = gems.size
|
48
|
-
(gems ^ downloaded_gems).each_slice(200) do |g|
|
49
|
-
specs += JSON.load open("http://bundler.rubygems.org/api/v1/dependencies.json?gems=#{g.join(',')}")
|
50
|
-
end
|
51
|
-
downloaded_gems.merge(gems)
|
52
|
-
|
53
|
-
gems.merge(specs.flat_map { |s| s['dependencies'].map(&:first) })
|
54
|
-
|
55
|
-
break if gems.size == size
|
56
|
-
end
|
57
|
-
|
58
|
-
specs.reject! { |s| s['platform'] != 'ruby' }
|
59
|
-
specs.uniq! { |s| [s['name'], s['number']] }
|
60
|
-
specs.sort_by! { |s| s['name'].downcase }
|
61
|
-
specs = specs.group_by { |s| s['name'] }.values.map do |spec|
|
62
|
-
[spec.first['name'], spec.flat_map do |s|
|
63
|
-
{
|
64
|
-
'name' => s['name'],
|
65
|
-
'version' => coerce_to_semver(s['number']),
|
66
|
-
'dependencies' => coerce_dependencies_to_semver(s['dependencies'])
|
67
|
-
}
|
68
|
-
end.uniq { |s| s['version'] }.sort_by { |s| Gem::Version.new(s['version']) }
|
69
|
-
]
|
70
|
-
end
|
71
|
-
|
72
|
-
specs = Hash[specs]
|
73
|
-
|
74
|
-
json = JSON.generate(specs)
|
75
|
-
|
76
|
-
File.open("index/rubygems-#{Date.today}.json", 'w') { |f| f.write json }
|
data/spec/resolver_spec.rb
DELETED
@@ -1,235 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require File.expand_path('../spec_helper', __FILE__)
|
3
|
-
|
4
|
-
module Molinillo
|
5
|
-
FIXTURE_CASE_DIR = FIXTURE_DIR + 'case'
|
6
|
-
|
7
|
-
class TestCase
|
8
|
-
attr_accessor :name, :requested, :base, :conflicts, :result, :index
|
9
|
-
|
10
|
-
def initialize(fixture_path)
|
11
|
-
File.open(fixture_path) do |fixture|
|
12
|
-
JSON.load(fixture).tap do |test_case|
|
13
|
-
self.name = test_case['name']
|
14
|
-
self.index = TestIndex.from_fixture(test_case['index'] || 'awesome')
|
15
|
-
self.requested = test_case['requested'].map do |(name, reqs)|
|
16
|
-
Gem::Dependency.new name.delete("\x01"), reqs.split(',').map(&:chomp)
|
17
|
-
end
|
18
|
-
add_dependencies_to_graph = lambda do |graph, parent, hash|
|
19
|
-
name = hash['name']
|
20
|
-
version = Gem::Version.new(hash['version'])
|
21
|
-
dependency = index.specs[name].find { |s| s.version == version }
|
22
|
-
node = if parent
|
23
|
-
graph.add_vertex(name, dependency).tap do |v|
|
24
|
-
graph.add_edge(parent, v, dependency)
|
25
|
-
end
|
26
|
-
else
|
27
|
-
graph.add_vertex(name, dependency, true)
|
28
|
-
end
|
29
|
-
hash['dependencies'].each do |dep|
|
30
|
-
add_dependencies_to_graph.call(graph, node, dep)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
self.result = test_case['resolved'].reduce(DependencyGraph.new) do |graph, r|
|
34
|
-
graph.tap do |g|
|
35
|
-
add_dependencies_to_graph.call(g, nil, r)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
self.base = test_case['base'].reduce(DependencyGraph.new) do |graph, r|
|
39
|
-
graph.tap do |g|
|
40
|
-
add_dependencies_to_graph.call(g, nil, r)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
self.conflicts = test_case['conflicts'].to_set
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def run(index_class, context)
|
49
|
-
return if ignore?(index_class)
|
50
|
-
|
51
|
-
test_case = self
|
52
|
-
|
53
|
-
context.instance_eval do
|
54
|
-
it test_case.name do
|
55
|
-
resolve = lambda do
|
56
|
-
index = index_class.new(test_case.index.specs)
|
57
|
-
resolver = Resolver.new(index, TestUI.new)
|
58
|
-
resolver.resolve(test_case.requested, test_case.base)
|
59
|
-
end
|
60
|
-
|
61
|
-
if test_case.conflicts.any?
|
62
|
-
expect { resolve.call }.to raise_error do |error|
|
63
|
-
expect(error).to be_a(ResolverError)
|
64
|
-
names = case error
|
65
|
-
when CircularDependencyError
|
66
|
-
error.dependencies.map(&:name)
|
67
|
-
when VersionConflict
|
68
|
-
error.conflicts.keys
|
69
|
-
end.to_set
|
70
|
-
expect(names).to eq(test_case.conflicts)
|
71
|
-
end
|
72
|
-
else
|
73
|
-
result = resolve.call
|
74
|
-
|
75
|
-
pretty_dependencies = lambda do |dg|
|
76
|
-
dg.vertices.values.map { |v| "#{v.name} (#{v.payload && v.payload.version})" }
|
77
|
-
end
|
78
|
-
expect(pretty_dependencies.call(result)).to contain_exactly(*pretty_dependencies.call(test_case.result))
|
79
|
-
|
80
|
-
expect(result).to equal_dependency_graph(test_case.result)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def ignore?(index_class)
|
87
|
-
if index_class == BerkshelfIndex &&
|
88
|
-
name == 'can resolve when two specs have the same dependencies and swapping happens' &&
|
89
|
-
Gem.ruby_version < Gem::Version.new('2.3')
|
90
|
-
|
91
|
-
# That index doesn't do a great job sorting, and segiddins has been
|
92
|
-
# unable to get the test passing with the bad sort (on Ruby < 2.3)
|
93
|
-
# without breaking other specs
|
94
|
-
return true
|
95
|
-
end
|
96
|
-
|
97
|
-
false
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.save!(path, name, index, requirements, resolved)
|
101
|
-
resolved_to_h = proc do |v|
|
102
|
-
{ :name => v.name, :version => v.payload.version, :dependencies => v.successors.map(&resolved_to_h) }
|
103
|
-
end
|
104
|
-
resolved = resolved.vertices.reduce([]) do |array, (_n, v)|
|
105
|
-
if v.root
|
106
|
-
array << resolved_to_h.call(v)
|
107
|
-
else
|
108
|
-
array
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
File.open(File.join(FIXTURE_CASE_DIR, "#{path}.json"), 'w') do |f|
|
113
|
-
f.write JSON.pretty_generate(
|
114
|
-
:name => name,
|
115
|
-
:index => index,
|
116
|
-
:requested => Hash[requirements.map { |r| [r.name, r.requirement.to_s] }],
|
117
|
-
:base => [],
|
118
|
-
:resolved => resolved.sort_by { |v| v[:name] },
|
119
|
-
:conflicts => []
|
120
|
-
)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
describe Resolver do
|
126
|
-
describe 'dependency resolution' do
|
127
|
-
test_cases = Dir.glob(FIXTURE_CASE_DIR + '**/*.json').map { |fixture| TestCase.new(fixture) }
|
128
|
-
INDICES.each do |index_class|
|
129
|
-
context "with the #{index_class.to_s.split('::').last} index" do
|
130
|
-
test_cases.each { |tc| tc.run(index_class, self) }
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
describe 'in general' do
|
136
|
-
before do
|
137
|
-
@resolver = described_class.new(TestIndex.from_fixture('awesome'), TestUI.new)
|
138
|
-
end
|
139
|
-
|
140
|
-
it 'includes the source of a user-specified unsatisfied dependency' do
|
141
|
-
expect do
|
142
|
-
@resolver.resolve([Gem::Dependency.new('missing', '3.0')], DependencyGraph.new)
|
143
|
-
end.to raise_error(VersionConflict, /required by `user-specified dependency`/)
|
144
|
-
end
|
145
|
-
|
146
|
-
it 'can handle when allow_missing? returns true for the only requirement' do
|
147
|
-
dep = Gem::Dependency.new('missing', '3.0')
|
148
|
-
allow(@resolver.specification_provider).to receive(:allow_missing?).with(dep).and_return(true)
|
149
|
-
expect(@resolver.resolve([dep], DependencyGraph.new).to_a).to be_empty
|
150
|
-
end
|
151
|
-
|
152
|
-
it 'can handle when allow_missing? returns true for a nested requirement' do
|
153
|
-
dep = Gem::Dependency.new('actionpack', '1.2.3')
|
154
|
-
allow(@resolver.specification_provider).to receive(:allow_missing?).
|
155
|
-
with(have_attributes(:name => 'activesupport')).and_return(true)
|
156
|
-
allow(@resolver.specification_provider).to receive(:search_for).
|
157
|
-
with(have_attributes(:name => 'activesupport')).and_return([])
|
158
|
-
allow(@resolver.specification_provider).to receive(:search_for).
|
159
|
-
with(have_attributes(:name => 'actionpack')).and_call_original
|
160
|
-
resolved = @resolver.resolve([dep], DependencyGraph.new)
|
161
|
-
expect(resolved.map(&:payload).map(&:to_s)).to eq(['actionpack (1.2.3)'])
|
162
|
-
end
|
163
|
-
|
164
|
-
it 'only cleans up orphaned nodes after swapping' do
|
165
|
-
index = TestIndex.new(
|
166
|
-
'a' => [
|
167
|
-
TestSpecification.new('name' => 'a', 'version' => '1.0.0', 'dependencies' => { 'z' => '= 2.0.0' }),
|
168
|
-
TestSpecification.new('name' => 'a', 'version' => '2.0.0', 'dependencies' => { 'z' => '= 1.0.0' }),
|
169
|
-
],
|
170
|
-
'b' => [
|
171
|
-
TestSpecification.new('name' => 'b', 'version' => '1.0.0', 'dependencies' => { 'a' => '< 2' }),
|
172
|
-
TestSpecification.new('name' => 'b', 'version' => '2.0.0', 'dependencies' => { 'a' => '< 2' }),
|
173
|
-
],
|
174
|
-
'c' => [
|
175
|
-
TestSpecification.new('name' => 'c', 'version' => '1.0.0'),
|
176
|
-
TestSpecification.new('name' => 'c', 'version' => '2.0.0', 'dependencies' => { 'b' => '< 2' }),
|
177
|
-
],
|
178
|
-
'z' => [
|
179
|
-
TestSpecification.new('name' => 'z', 'version' => '1.0.0'),
|
180
|
-
TestSpecification.new('name' => 'z', 'version' => '2.0.0'),
|
181
|
-
]
|
182
|
-
)
|
183
|
-
def index.sort_dependencies(dependencies, _activated, _conflicts)
|
184
|
-
index = ['c (>= 1.0.0)', 'b (< 2.0.0)', 'a (< 2.0.0)', 'c (= 1.0.0)']
|
185
|
-
dependencies.sort_by do |dep|
|
186
|
-
[
|
187
|
-
index.index(dep.to_s) || 999,
|
188
|
-
]
|
189
|
-
end
|
190
|
-
end
|
191
|
-
@resolver = described_class.new(index, TestUI.new)
|
192
|
-
demands = [
|
193
|
-
Gem::Dependency.new('c', '= 1.0.0'),
|
194
|
-
Gem::Dependency.new('c', '>= 1.0.0'),
|
195
|
-
Gem::Dependency.new('z', '>= 1.0.0'),
|
196
|
-
]
|
197
|
-
|
198
|
-
resolved = @resolver.resolve(demands, DependencyGraph.new)
|
199
|
-
|
200
|
-
expected = [
|
201
|
-
'c (1.0.0)',
|
202
|
-
'z (2.0.0)',
|
203
|
-
]
|
204
|
-
|
205
|
-
expect(resolved.map(&:payload).map(&:to_s)).to match_array(expected)
|
206
|
-
end
|
207
|
-
|
208
|
-
it 'does not reset parent tracking after swapping when another requirement led to the child' do
|
209
|
-
demands = [
|
210
|
-
Gem::Dependency.new('autobuild'),
|
211
|
-
Gem::Dependency.new('pastel'),
|
212
|
-
Gem::Dependency.new('tty-prompt'),
|
213
|
-
Gem::Dependency.new('tty-table'),
|
214
|
-
]
|
215
|
-
|
216
|
-
index = BundlerIndex.from_fixture('rubygems-2017-01-24')
|
217
|
-
index.specs['autobuild'] = [
|
218
|
-
TestSpecification.new('name' => 'autobuild',
|
219
|
-
'version' => '0.1.0',
|
220
|
-
'dependencies' => {
|
221
|
-
'tty-prompt' => '>= 0.6.0, ~> 0.6.0',
|
222
|
-
'pastel' => '>= 0.6.0, ~> 0.6.0',
|
223
|
-
}),
|
224
|
-
]
|
225
|
-
|
226
|
-
@resolver = described_class.new(index, TestUI.new)
|
227
|
-
demands.each { |d| index.search_for(d) }
|
228
|
-
|
229
|
-
resolved = @resolver.resolve(demands, DependencyGraph.new)
|
230
|
-
|
231
|
-
expect(resolved.map(&:payload).map(&:to_s).sort).to include('pastel (0.6.1)', 'tty-table (0.6.0)')
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|