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.
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