solve 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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