molinillo 0.5.7 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/ARCHITECTURE.md +102 -0
  3. data/CHANGELOG.md +352 -0
  4. data/lib/molinillo.rb +2 -0
  5. data/lib/molinillo/compatibility.rb +26 -0
  6. data/lib/molinillo/delegates/resolution_state.rb +7 -0
  7. data/lib/molinillo/delegates/specification_provider.rb +1 -0
  8. data/lib/molinillo/dependency_graph.rb +3 -2
  9. data/lib/molinillo/dependency_graph/action.rb +1 -0
  10. data/lib/molinillo/dependency_graph/add_edge_no_circular.rb +1 -0
  11. data/lib/molinillo/dependency_graph/add_vertex.rb +1 -0
  12. data/lib/molinillo/dependency_graph/delete_edge.rb +1 -0
  13. data/lib/molinillo/dependency_graph/detach_vertex_named.rb +1 -0
  14. data/lib/molinillo/dependency_graph/log.rb +1 -0
  15. data/lib/molinillo/dependency_graph/set_payload.rb +1 -0
  16. data/lib/molinillo/dependency_graph/tag.rb +1 -0
  17. data/lib/molinillo/dependency_graph/vertex.rb +3 -2
  18. data/lib/molinillo/errors.rb +69 -6
  19. data/lib/molinillo/gem_metadata.rb +2 -1
  20. data/lib/molinillo/modules/specification_provider.rb +1 -0
  21. data/lib/molinillo/modules/ui.rb +3 -1
  22. data/lib/molinillo/resolution.rb +495 -145
  23. data/lib/molinillo/resolver.rb +1 -0
  24. data/lib/molinillo/state.rb +8 -4
  25. metadata +8 -30
  26. data/spec/dependency_graph/log_spec.rb +0 -29
  27. data/spec/dependency_graph_spec.rb +0 -74
  28. data/spec/errors_spec.rb +0 -26
  29. data/spec/fuzz_spec.rb +0 -94
  30. data/spec/resolver_integration_specs/index_from_rubygems.rb +0 -76
  31. data/spec/resolver_spec.rb +0 -235
  32. data/spec/spec_helper.rb +0 -44
  33. data/spec/spec_helper/equal_dependency_graph.rb +0 -16
  34. data/spec/spec_helper/index.rb +0 -186
  35. data/spec/spec_helper/naive_resolver.rb +0 -48
  36. data/spec/spec_helper/specification.rb +0 -28
  37. data/spec/spec_helper/ui.rb +0 -14
  38. data/spec/state_spec.rb +0 -30
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'molinillo/dependency_graph'
3
4
 
4
5
  module Molinillo
@@ -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 [Set<Object>] conflicts unresolved conflicts
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, Set.new)
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.5.7
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-03-03 00:00:00.000000000 Z
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.10
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
- - spec/dependency_graph/log_spec.rb
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
@@ -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
@@ -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 }
@@ -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