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
         |