solve 0.2.1 → 0.3.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.
@@ -0,0 +1,17 @@
1
+ module Solve
2
+ class Solver
3
+ # @author Andrew Garson <andrew.garson@gmail.com>
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ class ConstraintRow
6
+ attr_reader :package
7
+ attr_reader :constraint
8
+ attr_reader :source
9
+
10
+ def initialize(package, constraint, source)
11
+ @package = package
12
+ @constraint = constraint
13
+ @source = source
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ module Solve
2
+ class Solver
3
+ # @author Andrew Garson <andrew.garson@gmail.com>
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ class ConstraintTable
6
+ attr_reader :rows
7
+
8
+ def initialize
9
+ @rows = Array.new
10
+ end
11
+
12
+ def add(package, constraint, source)
13
+ @rows << ConstraintRow.new(package, constraint, source)
14
+ end
15
+
16
+ def constraints_on_package(package)
17
+ @rows.select do |row|
18
+ row.package == package
19
+ end.map { |row| row.constraint }
20
+ end
21
+
22
+ def remove_constraints_from_source!(source)
23
+ @rows.reject! { |row| row.source == source }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ module Solve
2
+ class Solver
3
+ # @author Andrew Garson <andrew.garson@gmail.com>
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ class Variable
6
+ attr_reader :package
7
+ attr_reader :value
8
+ attr_reader :sources
9
+
10
+ def initialize(package, source)
11
+ @package = package
12
+ @value = nil
13
+ @sources = Array(source)
14
+ end
15
+
16
+ def add_source(source)
17
+ @sources << source
18
+ end
19
+
20
+ def last_source
21
+ @sources.last
22
+ end
23
+
24
+ def bind(value)
25
+ @value = value
26
+ end
27
+
28
+ def unbind
29
+ @value = nil
30
+ end
31
+
32
+ def bound?
33
+ !@value.nil?
34
+ end
35
+
36
+ def remove_source(source)
37
+ @sources.delete(source)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ module Solve
2
+ class Solver
3
+ # @author Andrew Garson <andrew.garson@gmail.com>
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ class VariableTable
6
+ attr_reader :rows
7
+
8
+ def initialize
9
+ @rows = Array.new
10
+ end
11
+
12
+ def add(package, source)
13
+ row = rows.detect { |row| row.package == package }
14
+ if row.nil?
15
+ @rows << Variable.new(package, source)
16
+ else
17
+ row.add_source(source)
18
+ end
19
+ end
20
+
21
+ def first_unbound
22
+ @rows.detect { |row| row.bound? == false }
23
+ end
24
+
25
+ def find_package(package)
26
+ @rows.detect { |row| row.package == package }
27
+ end
28
+
29
+ def remove_all_with_only_this_source!(source)
30
+ with_only_this_source, others = @rows.partition { |row| row.sources == [source] }
31
+ @rows = others
32
+ with_only_this_source
33
+ end
34
+
35
+ def all_from_source(source)
36
+ @rows.select { |row| row.sources.include?(source) }
37
+ end
38
+
39
+ def before(package)
40
+ package_index = @rows.index { |row| row.package == package }
41
+ (package_index == 0) ? nil : @rows[package_index - 1]
42
+ end
43
+
44
+ def all_after(package)
45
+ package_index = @rows.index { |row| row.package == package }
46
+ @rows[(package_index+1)..-1]
47
+ end
48
+ end
49
+ end
50
+ end
data/lib/solve/version.rb CHANGED
@@ -1,16 +1,63 @@
1
1
  module Solve
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
2
3
  class Version
4
+ class << self
5
+ # @param [#to_s] version_string
6
+ #
7
+ # @raise [InvalidVersionFormat]
8
+ #
9
+ # @return [Array]
10
+ def split(version_string)
11
+ major, minor, patch = case version_string.to_s
12
+ when /^(\d+)\.(\d+)\.(\d+)$/
13
+ [ $1.to_i, $2.to_i, $3.to_i ]
14
+ when /^(\d+)\.(\d+)$/
15
+ [ $1.to_i, $2.to_i, nil ]
16
+ else
17
+ raise Errors::InvalidVersionFormat.new(version_string)
18
+ end
19
+ end
20
+ end
21
+
3
22
  include Comparable
4
23
 
5
24
  attr_reader :major
6
25
  attr_reader :minor
7
26
  attr_reader :patch
8
27
 
9
- # @param [#to_s] ver_str
10
- def initialize(ver_str = String.new)
11
- @major, @minor, @patch = parse(ver_str)
28
+ # @overload initialize(version_array)
29
+ # @param [Array] version_array
30
+ #
31
+ # @example
32
+ # Version.new([1, 2, 3]) => #<Version: @major=1, @minor=2, @patch=3>
33
+ #
34
+ # @overload initialize(version_string)
35
+ # @param [#to_s] version_string
36
+ #
37
+ # @example
38
+ # Version.new("1.2.3") => #<Version: @major=1, @minor=2, @patch=3>
39
+ #
40
+ # @overload initialize(version)
41
+ # @param [Solve::Version] version
42
+ #
43
+ # @example
44
+ # Version.new(Version.new("1.2.3")) => #<Version: @major=1, @minor=2, @patch=3>
45
+ #
46
+ def initialize(*args)
47
+ if args.first.is_a?(Array)
48
+ @major, @minor, @patch = args.first
49
+ else
50
+ @major, @minor, @patch = self.class.split(args.first.to_s)
51
+ end
52
+
53
+ @major ||= 0
54
+ @minor ||= 0
55
+ @patch ||= 0
12
56
  end
13
57
 
58
+ # @param [Solve::Version] other
59
+ #
60
+ # @return [Integer]
14
61
  def <=>(other)
15
62
  [:major, :minor, :patch].each do |method|
16
63
  ans = (self.send(method) <=> other.send(method))
@@ -19,6 +66,9 @@ module Solve
19
66
  0
20
67
  end
21
68
 
69
+ # @param [Solve::Version] other
70
+ #
71
+ # @return [Boolean]
22
72
  def eql?(other)
23
73
  other.is_a?(Version) && self == other
24
74
  end
@@ -30,18 +80,5 @@ module Solve
30
80
  def to_s
31
81
  "#{major}.#{minor}.#{patch}"
32
82
  end
33
-
34
- private
35
-
36
- def parse(ver_str = String.new)
37
- @major, @minor, @patch = case ver_str.to_s
38
- when /^(\d+)\.(\d+)\.(\d+)$/
39
- [ $1.to_i, $2.to_i, $3.to_i ]
40
- when /^(\d+)\.(\d+)$/
41
- [ $1.to_i, $2.to_i, 0 ]
42
- else
43
- raise InvalidVersionFormat.new(ver_str)
44
- end
45
- end
46
83
  end
47
84
  end
data/solve.gemspec CHANGED
@@ -16,9 +16,8 @@ Gem::Specification.new do |s|
16
16
  s.version = Solve::VERSION
17
17
  s.required_ruby_version = ">= 1.9.1"
18
18
 
19
- s.add_runtime_dependency 'dep_selector', '~> 0.0.8'
20
-
21
- s.add_development_dependency 'thor'
19
+ s.add_development_dependency 'thor', '>= 0.16.0'
20
+ s.add_development_dependency 'rake', '>= 0.9.2.2'
22
21
  s.add_development_dependency 'rspec'
23
22
  s.add_development_dependency 'fuubar'
24
23
  s.add_development_dependency 'spork'
@@ -1,25 +1,98 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "Solutions" do
4
- it "chooses the best artifact for the demands" do
4
+
5
+ it "chooses the correct artifact for the demands" do
5
6
  graph = Solve::Graph.new
6
7
  graph.artifacts("mysql", "2.0.0")
7
8
  graph.artifacts("mysql", "1.2.0")
8
9
  graph.artifacts("nginx", "1.0.0").depends("mysql", "= 1.2.0")
9
- graph.demands('nginx')
10
- graph.demands('mysql')
11
10
 
12
- Solve.it(graph).should eql("nginx" => "1.0.0", "mysql" => "1.2.0")
11
+ result = Solve.it!(graph, [['nginx', '= 1.0.0'], ['mysql']])
12
+
13
+ result.should eql("nginx" => "1.0.0", "mysql" => "1.2.0")
13
14
  end
14
15
 
15
- it "raises NoSolutionError when a solution cannot be found" do
16
+ it "chooses the best artifact for the demands" do
16
17
  graph = Solve::Graph.new
18
+ graph.artifacts("mysql", "2.0.0")
17
19
  graph.artifacts("mysql", "1.2.0")
20
+ graph.artifacts("nginx", "1.0.0").depends("mysql", ">= 1.2.0")
21
+
22
+ result = Solve.it!(graph, [['nginx', '= 1.0.0'], ['mysql']])
18
23
 
19
- graph.demands("mysql", ">= 2.0.0")
24
+ result.should eql("nginx" => "1.0.0", "mysql" => "2.0.0")
25
+ end
26
+
27
+ it "raises NoSolutionError when a solution cannot be found" do
28
+ graph = Solve::Graph.new
29
+ graph.artifacts("mysql", "1.2.0")
20
30
 
21
31
  lambda {
22
- Solve.it!(graph)
23
- }.should raise_error(Solve::NoSolutionError)
32
+ Solve.it!(graph, ['mysql', '>= 2.0.0'])
33
+ }.should raise_error(Solve::Errors::NoSolutionError)
24
34
  end
35
+
36
+ it "find the correct solution when backtracking in variables introduced via demands" do
37
+ graph = Solve::Graph.new
38
+
39
+ graph.artifacts("D", "1.2.0")
40
+ graph.artifacts("D", "1.3.0")
41
+ graph.artifacts("D", "1.4.0")
42
+ graph.artifacts("D", "2.0.0")
43
+ graph.artifacts("D", "2.1.0")
44
+
45
+ graph.artifacts("C", "2.0.0").depends("D", "= 1.2.0")
46
+ graph.artifacts("C", "2.1.0").depends("D", ">= 2.1.0")
47
+ graph.artifacts("C", "2.2.0").depends("D", "> 2.0.0")
48
+
49
+ graph.artifacts("B", "1.0.0").depends("D", "= 1.0.0")
50
+ graph.artifacts("B", "1.1.0").depends("D", "= 1.0.0")
51
+ graph.artifacts("B", "2.0.0").depends("D", ">= 1.3.0")
52
+ graph.artifacts("B", "2.1.0").depends("D", ">= 2.0.0")
53
+
54
+ graph.artifacts("A", "1.0.0").depends("B", "> 1.0.0")
55
+ graph.artifacts("A", "1.0.0").depends("C", "= 2.0.0")
56
+ graph.artifacts("A", "1.0.1").depends("B", "> 1.0.0")
57
+ graph.artifacts("A", "1.0.1").depends("C", "= 2.1.0")
58
+ graph.artifacts("A", "1.0.2").depends("B", "> 2.0.0")
59
+ graph.artifacts("A", "1.0.2").depends("C", "= 2.1.0")
60
+
61
+ result = Solve.it!(graph, [['A', '~> 1.0.0'], ['D', ">= 2.0.0"]])
62
+
63
+
64
+ result.should eql("A" => "1.0.2",
65
+ "B" => "2.1.0",
66
+ "C" => "2.1.0",
67
+ "D" => "2.1.0")
68
+ end
69
+
70
+ it "finds the correct solution when there is a circular dependency" do
71
+ graph = Solve::Graph.new
72
+
73
+ graph.artifacts("A", "1.0.0").depends("B", "1.0.0")
74
+ graph.artifacts("B", "1.0.0").depends("C", "1.0.0")
75
+ graph.artifacts("C", "1.0.0").depends("A", "1.0.0")
76
+
77
+ result = Solve.it!(graph, [["A", "1.0.0"]])
78
+
79
+ result.should eql("A" => "1.0.0",
80
+ "B" => "1.0.0",
81
+ "C" => "1.0.0")
82
+ end
83
+
84
+ it "finds the correct solution when there is a p shaped depenency chain" do
85
+ graph = Solve::Graph.new
86
+
87
+ graph.artifacts("A", "1.0.0").depends("B", "1.0.0")
88
+ graph.artifacts("B", "1.0.0").depends("C", "1.0.0")
89
+ graph.artifacts("C", "1.0.0").depends("B", "1.0.0")
90
+
91
+ result = Solve.it!(graph, [["A", "1.0.0"]])
92
+
93
+ result.should eql("A" => "1.0.0",
94
+ "B" => "1.0.0",
95
+ "C" => "1.0.0")
96
+ end
97
+
25
98
  end
@@ -13,44 +13,58 @@ describe Solve::Artifact do
13
13
  let(:version) { "1.0.0" }
14
14
  subject { Solve::Artifact.new(graph, name, version) }
15
15
 
16
- describe "#dependencies" do
17
- context "given a name and constraint argument" do
18
- let(:name) { "nginx" }
19
- let(:constraint) { "~> 0.101.5" }
16
+ describe "equality" do
17
+ context "given an artifact with the same name and version" do
18
+ let(:one) { Solve::Artifact.new(graph, "riot", "1.0.0") }
19
+ let(:two) { Solve::Artifact.new(graph, "riot", "1.0.0") }
20
20
 
21
- context "given the dependency of the given name and constraint does not exist" do
22
- it "returns a Solve::Artifact" do
23
- subject.dependencies(name, constraint).should be_a(Solve::Dependency)
24
- end
25
-
26
- it "the dependency has the given name" do
27
- subject.dependencies(name, constraint).name.should eql(name)
28
- end
21
+ it "is equal" do
22
+ one.should be_eql(two)
23
+ end
24
+ end
29
25
 
30
- it "the dependency has the given constraint" do
31
- subject.dependencies(name, constraint).constraint.to_s.should eql(constraint)
32
- end
26
+ context "given an artifact with the same name but different version" do
27
+ let(:one) { Solve::Artifact.new(graph, "riot", "1.0.0") }
28
+ let(:two) { Solve::Artifact.new(graph, "riot", "2.0.0") }
33
29
 
34
- it "adds an dependency to the dependency collection" do
35
- subject.dependencies(name, constraint)
30
+ it "is not equal" do
31
+ one.should_not be_eql(two)
32
+ end
33
+ end
36
34
 
37
- subject.dependencies.should have(1).item
38
- end
35
+ context "given an artifact with the same version but different name" do
36
+ let(:one) { Solve::Artifact.new(graph, "riot", "1.0.0") }
37
+ let(:two) { Solve::Artifact.new(graph, "league", "1.0.0") }
39
38
 
40
- it "the dependency added matches the given name" do
41
- subject.dependencies(name, constraint)
39
+ it "is not equal" do
40
+ one.should_not be_eql(two)
41
+ end
42
+ end
43
+ end
42
44
 
43
- subject.dependencies[0].name.should eql(name)
44
- end
45
+ describe "sorting" do
46
+ let(:one) { Solve::Artifact.new(graph, "riot", "1.0.0") }
47
+ let(:two) { Solve::Artifact.new(graph, "riot", "2.0.0") }
48
+ let(:three) { Solve::Artifact.new(graph, "riot", "3.0.0") }
49
+
50
+ let(:artifacts) do
51
+ [
52
+ one,
53
+ two,
54
+ three
55
+ ].shuffle
56
+ end
45
57
 
46
- it "the dependency added matches the given constraint" do
47
- subject.dependencies(name, constraint)
58
+ it "orders artifacts by their version number" do
59
+ sorted = artifacts.sort
48
60
 
49
- subject.dependencies[0].constraint.to_s.should eql(constraint)
50
- end
51
- end
61
+ sorted[0].should eql(one)
62
+ sorted[1].should eql(two)
63
+ sorted[2].should eql(three)
52
64
  end
65
+ end
53
66
 
67
+ describe "#dependencies" do
54
68
  context "given no arguments" do
55
69
  it "returns an array" do
56
70
  subject.dependencies.should be_a(Array)
@@ -59,38 +73,48 @@ describe Solve::Artifact do
59
73
  it "returns an empty array if no dependencies have been accessed" do
60
74
  subject.dependencies.should have(0).items
61
75
  end
76
+ end
77
+ end
62
78
 
63
- it "returns an array containing an dependency if one was accessed" do
64
- subject.dependencies("nginx", "~> 0.101.5")
79
+ describe "#depends" do
80
+ context "given a name and constraint argument" do
81
+ let(:name) { "nginx" }
82
+ let(:constraint) { "~> 0.101.5" }
65
83
 
66
- subject.dependencies.should have(1).item
84
+ context "given the dependency of the given name and constraint does not exist" do
85
+ it "returns a Solve::Artifact" do
86
+ subject.depends(name, constraint).should be_a(Solve::Artifact)
87
+ end
88
+
89
+ it "adds a dependency with the given name and constraint to the list of dependencies" do
90
+ subject.depends(name, constraint)
91
+
92
+ subject.dependencies.should have(1).item
93
+ subject.dependencies.first.name.should eql(name)
94
+ subject.dependencies.first.constraint.to_s.should eql(constraint)
95
+ end
67
96
  end
68
97
  end
69
98
 
70
99
  context "given only a name argument" do
71
- it "returns an array containing a match all constraint (>= 0.0.0)" do
72
- subject.dependencies("nginx").constraint.to_s.should eql(">= 0.0.0")
100
+ it "adds a dependency with a all constraint (>= 0.0.0)" do
101
+ subject.depends("nginx")
102
+
103
+ subject.dependencies.should have(1).item
104
+ subject.dependencies.first.constraint.to_s.should eql(">= 0.0.0")
73
105
  end
74
106
  end
107
+ end
75
108
 
76
- context "given an unexpected number of arguments" do
77
- it "raises an ArgumentError if more than two are provided" do
78
- lambda {
79
- subject.dependencies(1, 2, 3)
80
- }.should raise_error(ArgumentError, "Unexpected number of arguments. You gave: 3. Expected: 2 or less.")
81
- end
109
+ describe "::get_dependency" do
110
+ before(:each) { subject.depends("nginx", "~> 1.2.3") }
82
111
 
83
- it "raises an ArgumentError if a name argument is provided but it is nil" do
84
- lambda {
85
- subject.dependencies(nil)
86
- }.should raise_error(ArgumentError, "A name must be specified. You gave: [nil].")
87
- end
112
+ it "returns an instance of Solve::Dependency matching the given name and constraint" do
113
+ dependency = subject.get_dependency("nginx", "~> 1.2.3")
88
114
 
89
- it "raises an ArgumentError if a name and constraint argument are provided but the name is nil" do
90
- lambda {
91
- subject.dependencies(nil, "= 1.0.0")
92
- }.should raise_error(ArgumentError, 'A name must be specified. You gave: [nil, "= 1.0.0"].')
93
- end
115
+ dependency.should be_a(Solve::Dependency)
116
+ dependency.name.should eql("nginx")
117
+ dependency.constraint.to_s.should eql("~> 1.2.3")
94
118
  end
95
119
  end
96
120