solve 0.3.0 → 0.3.1

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.
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+ require 'json'
1
3
  require 'solve/errors'
2
4
 
3
5
  # @author Jamie Winsor <jamie@vialstudios.com>
@@ -15,14 +17,17 @@ module Solve
15
17
  # requirements (demands) which must be met. Return me the best solution of
16
18
  # artifacts and verisons that I should use.
17
19
  #
20
+ # If a ui object is passed in, the resolution will be traced
21
+ #
18
22
  # @param [Solve::Graph] graph
19
23
  # @param [Array<Solve::Demand>, Array<String, String>] demands
24
+ # @param [#say, nil] ui
20
25
  #
21
26
  # @raise [NoSolutionError]
22
27
  #
23
28
  # @return [Hash]
24
- def it!(graph, demands)
25
- Solver.new(graph, demands).resolve
29
+ def it!(graph, demands, ui=nil)
30
+ Solver.new(graph, demands, ui).resolve
26
31
  end
27
32
  end
28
33
  end
@@ -145,9 +145,7 @@ module Solve
145
145
  def ==(other)
146
146
  other.is_a?(self.class) &&
147
147
  self.operator == other.operator &&
148
- self.major == other.minor &&
149
- self.minor == other.minor &&
150
- self.patch == other.patch
148
+ self.version == other.version
151
149
  end
152
150
  alias_method :eql?, :==
153
151
 
@@ -43,5 +43,12 @@ module Solve
43
43
  def to_s
44
44
  "#{name} (#{constraint})"
45
45
  end
46
+
47
+ def ==(other)
48
+ other.is_a?(self.class) &&
49
+ self.name == other.name &&
50
+ self.constraint == other.constraint
51
+ end
52
+ alias_method :eql?, :==
46
53
  end
47
54
  end
@@ -1,3 +1,3 @@
1
1
  module Solve
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -134,11 +134,35 @@ module Solve
134
134
  !get_artifact(name, version).nil?
135
135
  end
136
136
 
137
- private
137
+ # @param [Object] other
138
+ #
139
+ # @return [Boolean]
140
+ def ==(other)
141
+ return false unless other.is_a?(self.class)
138
142
 
139
- # @return [Array<Solve::Artifact>]
140
- def artifact_collection
141
- @artifacts.collect { |name, artifact| artifact }
142
- end
143
+ self_artifacts = self.artifacts
144
+ other_artifacts = other.artifacts
145
+
146
+ self_dependencies = self_artifacts.inject([]) do |list, artifact|
147
+ list << artifact.dependencies
148
+ end.flatten
149
+
150
+ other_dependencies = other_artifacts.inject([]) do |list, artifact|
151
+ list << artifact.dependencies
152
+ end.flatten
153
+
154
+ self_artifacts.size == other_artifacts.size &&
155
+ self_dependencies.size == other_dependencies.size &&
156
+ self_artifacts.all? { |artifact| other_artifacts.include?(artifact) } &&
157
+ self_dependencies.all? { |dependency| other_dependencies.include?(dependency) }
158
+ end
159
+ alias_method :eql?, :==
160
+
161
+ private
162
+
163
+ # @return [Array<Solve::Artifact>]
164
+ def artifact_collection
165
+ @artifacts.collect { |name, artifact| artifact }
166
+ end
143
167
  end
144
168
  end
@@ -2,9 +2,10 @@ module Solve
2
2
  # @author Jamie Winsor <jamie@vialstudios.com>
3
3
  class Solver
4
4
  autoload :VariableTable, 'solve/solver/variable_table'
5
- autoload :Variable, 'solve/solver/variable'
5
+ autoload :VariableRow, 'solve/solver/variable_row'
6
6
  autoload :ConstraintTable, 'solve/solver/constraint_table'
7
7
  autoload :ConstraintRow, 'solve/solver/constraint_row'
8
+ autoload :Serializer, 'solve/solver/serializer'
8
9
 
9
10
  class << self
10
11
  # Create a key to identify a demand on a Solver.
@@ -61,6 +62,8 @@ module Solve
61
62
  #
62
63
  # @return [Solve::Graph]
63
64
  attr_reader :graph
65
+ attr_reader :demands
66
+ attr_reader :ui
64
67
 
65
68
  attr_reader :domain
66
69
  attr_reader :variable_table
@@ -69,10 +72,13 @@ module Solve
69
72
 
70
73
  # @param [Solve::Graph] graph
71
74
  # @param [Array<String>, Array<Array<String, String>>] demands
72
- def initialize(graph, demands = Array.new)
75
+ # @param [#say] ui
76
+ def initialize(graph, demands = Array.new, ui=nil)
73
77
  @graph = graph
74
- @domain = Hash.new
75
78
  @demands = Hash.new
79
+ @ui = ui.respond_to?(:say) ? ui : nil
80
+
81
+ @domain = Hash.new
76
82
  @possible_values = Hash.new
77
83
  @constraint_table = ConstraintTable.new
78
84
  @variable_table = VariableTable.new
@@ -84,16 +90,20 @@ module Solve
84
90
 
85
91
  # @return [Hash]
86
92
  def resolve
93
+ trace("Attempting to find a solution")
87
94
  seed_demand_dependencies
88
95
 
89
96
  while unbound_variable = variable_table.first_unbound
90
97
  possible_values_for_unbound = possible_values_for(unbound_variable)
98
+ trace("Searching for a value for #{unbound_variable.artifact}")
99
+ trace("Possible values are #{possible_values_for_unbound}")
91
100
 
92
101
  while possible_value = possible_values_for_unbound.shift
93
- possible_artifact = graph.get_artifact(unbound_variable.package, possible_value.version)
102
+ possible_artifact = graph.get_artifact(unbound_variable.artifact, possible_value.version)
94
103
  possible_dependencies = possible_artifact.dependencies
95
104
  all_ok = possible_dependencies.all? { |dependency| can_add_new_constraint?(dependency) }
96
105
  if all_ok
106
+ trace("Attempting to use #{possible_artifact}")
97
107
  add_dependencies(possible_dependencies, possible_artifact)
98
108
  unbound_variable.bind(possible_value)
99
109
  break
@@ -101,15 +111,21 @@ module Solve
101
111
  end
102
112
 
103
113
  unless unbound_variable.bound?
114
+ trace("Could not find an acceptable value for #{unbound_variable.artifact}")
104
115
  backtrack(unbound_variable)
105
116
  end
106
117
  end
107
118
 
108
- {}.tap do |solution|
119
+ solution = {}.tap do |solution|
109
120
  variable_table.rows.each do |variable|
110
- solution[variable.package] = variable.value.version.to_s
121
+ solution[variable.artifact] = variable.value.version.to_s
111
122
  end
112
123
  end
124
+
125
+ trace("Found Solution")
126
+ trace(solution)
127
+
128
+ solution
113
129
  end
114
130
 
115
131
  # @overload demands(name, constraint)
@@ -192,28 +208,29 @@ module Solve
192
208
  end
193
209
 
194
210
  def can_add_new_constraint?(dependency)
195
- current_binding = variable_table.find_package(dependency.name)
211
+ current_binding = variable_table.find_artifact(dependency.name)
196
212
  #haven't seen it before, haven't bound it yet or the binding is ok
197
213
  current_binding.nil? || current_binding.value.nil? || dependency.constraint.satisfies?(current_binding.value.version)
198
214
  end
199
215
 
200
216
  def possible_values_for(variable)
201
- possible_values_for_variable = possible_values[variable.package]
217
+ possible_values_for_variable = possible_values[variable.artifact]
202
218
  if possible_values_for_variable.nil?
203
- constraints_for_variable = constraint_table.constraints_on_package(variable.package)
204
- all_values_for_variable = domain[variable.package]
219
+ constraints_for_variable = constraint_table.constraints_on_artifact(variable.artifact)
220
+ all_values_for_variable = domain[variable.artifact]
205
221
  possible_values_for_variable = constraints_for_variable.inject(all_values_for_variable) do |remaining_values, constraint|
206
222
  remaining_values.reject { |value| !constraint.satisfies?(value.version) }
207
223
  end
208
- possible_values[variable.package] = possible_values_for_variable
224
+ possible_values[variable.artifact] = possible_values_for_variable
209
225
  end
210
226
  possible_values_for_variable
211
227
  end
212
228
 
213
229
  def add_dependencies(dependencies, source)
214
230
  dependencies.each do |dependency|
231
+ trace("Adding constraint #{dependency.name} #{dependency.constraint} from #{source}")
215
232
  variable_table.add(dependency.name, source)
216
- constraint_table.add(dependency.name, dependency.constraint, source)
233
+ constraint_table.add(dependency, source)
217
234
  dependency_domain = graph.versions(dependency.name, dependency.constraint)
218
235
  domain[dependency.name] = [(domain[dependency.name] || []), dependency_domain]
219
236
  .flatten
@@ -223,24 +240,38 @@ module Solve
223
240
  end
224
241
 
225
242
  def reset_possible_values_for(variable)
226
- possible_values[variable.package] = nil
243
+ possible_values[variable.artifact] = nil
227
244
  possible_values_for(variable)
228
245
  end
229
246
 
230
247
  def backtrack(unbound_variable)
231
- previous_variable = variable_table.before(unbound_variable.package)
248
+ previous_variable = variable_table.before(unbound_variable.artifact)
232
249
 
233
250
  if previous_variable.nil?
251
+ trace("Cannot backtrack any further")
234
252
  raise Errors::NoSolutionError
235
253
  end
236
254
 
255
+ trace("Unbinding #{previous_variable.artifact}")
256
+
237
257
  source = previous_variable.value
238
- variable_table.remove_all_with_only_this_source!(source)
239
- constraint_table.remove_constraints_from_source!(source)
258
+ removed_variables = variable_table.remove_all_with_only_this_source!(source)
259
+ removed_variables.each do |removed_variable|
260
+ possible_values[removed_variable.artifact] = nil
261
+ trace("Removed variable #{removed_variable.artifact}")
262
+ end
263
+ removed_constraints = constraint_table.remove_constraints_from_source!(source)
264
+ removed_constraints.each do |removed_constraint|
265
+ trace("Removed constraint #{removed_constraint.name} #{removed_constraint.constraint}")
266
+ end
240
267
  previous_variable.unbind
241
- variable_table.all_after(previous_variable.package).each do |variable|
268
+ variable_table.all_after(previous_variable.artifact).each do |variable|
242
269
  new_possibles = reset_possible_values_for(variable)
243
270
  end
244
271
  end
272
+
273
+ def trace(message)
274
+ ui.say(message) unless ui.nil?
275
+ end
245
276
  end
246
277
  end
@@ -3,15 +3,23 @@ module Solve
3
3
  # @author Andrew Garson <andrew.garson@gmail.com>
4
4
  # @author Jamie Winsor <jamie@vialstudios.com>
5
5
  class ConstraintRow
6
- attr_reader :package
7
- attr_reader :constraint
6
+ extend Forwardable
7
+
8
8
  attr_reader :source
9
9
 
10
- def initialize(package, constraint, source)
11
- @package = package
12
- @constraint = constraint
10
+ def_delegator :dependency, :name
11
+ def_delegator :dependency, :constraint
12
+
13
+ # @param [Solve::Dependency] dependency
14
+ # @param [String, Symbol] source
15
+ def initialize(dependency, source)
16
+ @dependency = dependency
13
17
  @source = source
14
18
  end
19
+
20
+ private
21
+
22
+ attr_reader :dependency
15
23
  end
16
24
  end
17
25
  end
@@ -9,18 +9,24 @@ module Solve
9
9
  @rows = Array.new
10
10
  end
11
11
 
12
- def add(package, constraint, source)
13
- @rows << ConstraintRow.new(package, constraint, source)
12
+ # @param [Solve::Dependency] dependency
13
+ # @param [String, Symbol] source
14
+ #
15
+ # @return [Array<ConstraintRow>]
16
+ def add(dependency, source)
17
+ @rows << ConstraintRow.new(dependency, source)
14
18
  end
15
19
 
16
- def constraints_on_package(package)
20
+ def constraints_on_artifact(name)
17
21
  @rows.select do |row|
18
- row.package == package
22
+ row.name == name
19
23
  end.map { |row| row.constraint }
20
24
  end
21
25
 
22
26
  def remove_constraints_from_source!(source)
23
- @rows.reject! { |row| row.source == source }
27
+ from_source, others = @rows.partition { |row| row.source == source }
28
+ @rows = others
29
+ from_source
24
30
  end
25
31
  end
26
32
  end
@@ -0,0 +1,104 @@
1
+ module Solve
2
+ class Solver
3
+ class Serializer
4
+ # @param [Solve::Solver] solver
5
+ #
6
+ # @return [String]
7
+ def serialize(solver)
8
+ graph = solver.graph
9
+ demands = solver.demands
10
+
11
+ graph_hash = format_graph(graph)
12
+ demands_hash = format_demands(demands)
13
+
14
+ problem = graph_hash.merge(demands_hash)
15
+ problem.to_json
16
+ end
17
+
18
+ # @param [Hash, #to_s] solver a json string or a hash representing a solver
19
+ #
20
+ # @return [Solve::Solver]
21
+ def deserialize(solver)
22
+ unless solver.is_a?(Hash)
23
+ solver = JSON.parse(solver.to_s)
24
+ end
25
+
26
+ graph_spec = solver["graph"]
27
+ demands_spec = solver["demands"]
28
+
29
+ graph = load_graph(graph_spec)
30
+ demands = load_demands(demands_spec)
31
+
32
+ Solve::Solver.new(graph, demands)
33
+ end
34
+
35
+ private
36
+
37
+ def format_graph(graph)
38
+ artifacts = graph.artifacts.inject([]) do |list, artifact|
39
+ list << format_artifact(artifact)
40
+ end
41
+ { "graph" => artifacts }
42
+ end
43
+
44
+ def format_artifact(artifact)
45
+ dependencies = artifact.dependencies.inject([]) do |list, dependency|
46
+ list << format_dependency(dependency)
47
+ end
48
+
49
+ {
50
+ "name" => artifact.name,
51
+ "version" => artifact.version.to_s,
52
+ "dependencies" => dependencies
53
+ }
54
+ end
55
+
56
+ def format_dependency(dependency)
57
+ {
58
+ "name" => dependency.name,
59
+ "constraint" => dependency.constraint.to_s
60
+ }
61
+ end
62
+
63
+ def format_demands(demands)
64
+ demands_list = demands.inject([]) do |list, demand|
65
+ list << format_demand(demand)
66
+ end
67
+ { "demands" => demands_list }
68
+ end
69
+
70
+ def format_demand(demand)
71
+ {
72
+ "name" => demand.name,
73
+ "constraint" => demand.constraint.to_s
74
+ }
75
+ end
76
+
77
+ def load_graph(artifacts_list)
78
+ graph = Solve::Graph.new
79
+ artifacts_list.each do |artifact_spec|
80
+ load_artifact(graph, artifact_spec)
81
+ end
82
+ graph
83
+ end
84
+
85
+ def load_artifact(graph, artifact_spec)
86
+ artifact = graph.artifacts(artifact_spec["name"], artifact_spec["version"])
87
+ artifact_spec["dependencies"].each do |dependency_spec|
88
+ load_dependency(artifact, dependency_spec)
89
+ end
90
+ artifact
91
+ end
92
+
93
+ def load_dependency(artifact, dependency_spec)
94
+ artifact.depends(dependency_spec["name"], dependency_spec["constraint"])
95
+ end
96
+
97
+ def load_demands(demand_specs)
98
+ demand_specs.inject([]) do |list, demand_spec|
99
+ list << [demand_spec["name"], demand_spec["constraint"]]
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -2,17 +2,20 @@ module Solve
2
2
  class Solver
3
3
  # @author Andrew Garson <andrew.garson@gmail.com>
4
4
  # @author Jamie Winsor <jamie@vialstudios.com>
5
- class Variable
6
- attr_reader :package
5
+ class VariableRow
6
+ attr_reader :artifact
7
7
  attr_reader :value
8
8
  attr_reader :sources
9
9
 
10
- def initialize(package, source)
11
- @package = package
10
+ # @param [String] artifact
11
+ # @param [String, Symbol] source
12
+ def initialize(artifact, source)
13
+ @artifact = artifact
12
14
  @value = nil
13
15
  @sources = Array(source)
14
16
  end
15
17
 
18
+ # @param [String, Symbol] source
16
19
  def add_source(source)
17
20
  @sources << source
18
21
  end
@@ -33,6 +36,7 @@ module Solve
33
36
  !@value.nil?
34
37
  end
35
38
 
39
+ # @param [String, Symbol] source
36
40
  def remove_source(source)
37
41
  @sources.delete(source)
38
42
  end
@@ -9,10 +9,12 @@ module Solve
9
9
  @rows = Array.new
10
10
  end
11
11
 
12
- def add(package, source)
13
- row = rows.detect { |row| row.package == package }
12
+ # @param [String] artifact
13
+ # @param [String] source
14
+ def add(artifact, source)
15
+ row = rows.detect { |row| row.artifact == artifact }
14
16
  if row.nil?
15
- @rows << Variable.new(package, source)
17
+ @rows << VariableRow.new(artifact, source)
16
18
  else
17
19
  row.add_source(source)
18
20
  end
@@ -22,28 +24,33 @@ module Solve
22
24
  @rows.detect { |row| row.bound? == false }
23
25
  end
24
26
 
25
- def find_package(package)
26
- @rows.detect { |row| row.package == package }
27
+ # @param [String] artifact
28
+ def find_artifact(artifact)
29
+ @rows.detect { |row| row.artifact == artifact }
27
30
  end
28
31
 
32
+ # @param [String] source
29
33
  def remove_all_with_only_this_source!(source)
30
34
  with_only_this_source, others = @rows.partition { |row| row.sources == [source] }
31
35
  @rows = others
32
36
  with_only_this_source
33
37
  end
34
38
 
39
+ # @param [String] source
35
40
  def all_from_source(source)
36
41
  @rows.select { |row| row.sources.include?(source) }
37
42
  end
38
43
 
39
- def before(package)
40
- package_index = @rows.index { |row| row.package == package }
41
- (package_index == 0) ? nil : @rows[package_index - 1]
44
+ # @param [String] artifact
45
+ def before(artifact)
46
+ artifact_index = @rows.index { |row| row.artifact == artifact }
47
+ (artifact_index == 0) ? nil : @rows[artifact_index - 1]
42
48
  end
43
49
 
44
- def all_after(package)
45
- package_index = @rows.index { |row| row.package == package }
46
- @rows[(package_index+1)..-1]
50
+ # @param [String] artifact
51
+ def all_after(artifact)
52
+ artifact_index = @rows.index { |row| row.artifact == artifact }
53
+ @rows[(artifact_index+1)..-1]
47
54
  end
48
55
  end
49
56
  end
@@ -2,8 +2,8 @@
2
2
  require File.expand_path('../lib/solve/gem_version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
- s.authors = ["Jamie Winsor"]
6
- s.email = ["jamie@vialstudios.com"]
5
+ s.authors = ["Jamie Winsor", "Andrew Garson"]
6
+ s.email = ["jamie@vialstudios.com", "andrew.garson@gmail.com"]
7
7
  s.description = %q{A Ruby constraint solver}
8
8
  s.summary = s.description
9
9
  s.homepage = "https://github.com/reset/solve"
@@ -16,8 +16,11 @@ 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_dependency 'json'
20
+
19
21
  s.add_development_dependency 'thor', '>= 0.16.0'
20
22
  s.add_development_dependency 'rake', '>= 0.9.2.2'
23
+ s.add_development_dependency 'pry'
21
24
  s.add_development_dependency 'rspec'
22
25
  s.add_development_dependency 'fuubar'
23
26
  s.add_development_dependency 'spork'
@@ -51,17 +51,17 @@ describe "Solutions" do
51
51
  graph.artifacts("B", "2.0.0").depends("D", ">= 1.3.0")
52
52
  graph.artifacts("B", "2.1.0").depends("D", ">= 2.0.0")
53
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")
54
+ graph.artifacts("A", "1.0.0").depends("B", "> 2.0.0")
55
+ graph.artifacts("A", "1.0.0").depends("C", "= 2.1.0")
56
56
  graph.artifacts("A", "1.0.1").depends("B", "> 1.0.0")
57
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")
58
+ graph.artifacts("A", "1.0.2").depends("B", "> 1.0.0")
59
+ graph.artifacts("A", "1.0.2").depends("C", "= 2.0.0")
60
60
 
61
61
  result = Solve.it!(graph, [['A', '~> 1.0.0'], ['D', ">= 2.0.0"]])
62
62
 
63
63
 
64
- result.should eql("A" => "1.0.2",
64
+ result.should eql("A" => "1.0.1",
65
65
  "B" => "2.1.0",
66
66
  "C" => "2.1.0",
67
67
  "D" => "2.1.0")
@@ -95,4 +95,67 @@ describe "Solutions" do
95
95
  "C" => "1.0.0")
96
96
  end
97
97
 
98
+ it "finds the correct solution when there is a diamond shaped dependency" do
99
+ graph = Solve::Graph.new
100
+
101
+ graph.artifacts("A", "1.0.0")
102
+ .depends("B", "1.0.0")
103
+ .depends("C", "1.0.0")
104
+ graph.artifacts("B", "1.0.0")
105
+ .depends("D", "1.0.0")
106
+ graph.artifacts("C", "1.0.0")
107
+ .depends("D", "1.0.0")
108
+ graph.artifacts("D", "1.0.0")
109
+
110
+ result = Solve.it!(graph, [["A", "1.0.0"]])
111
+
112
+ result.should eql("A" => "1.0.0",
113
+ "B" => "1.0.0",
114
+ "C" => "1.0.0",
115
+ "D" => "1.0.0")
116
+ end
117
+
118
+ it "gives an empty solution when there are no demands" do
119
+ graph = Solve::Graph.new
120
+ result = Solve.it!(graph, [])
121
+ result.should eql({})
122
+ end
123
+
124
+ it "tries all combinations until it finds a solution" do
125
+
126
+ graph = Solve::Graph.new
127
+
128
+ graph.artifacts("A", "1.0.0").depends("B", "~> 1.0.0")
129
+ graph.artifacts("A", "1.0.1").depends("B", "~> 1.0.0")
130
+ graph.artifacts("A", "1.0.2").depends("B", "~> 1.0.0")
131
+
132
+ graph.artifacts("B", "1.0.0").depends("C", "~> 1.0.0")
133
+ graph.artifacts("B", "1.0.1").depends("C", "~> 1.0.0")
134
+ graph.artifacts("B", "1.0.2").depends("C", "~> 1.0.0")
135
+
136
+ graph.artifacts("C", "1.0.0").depends("D", "1.0.0")
137
+ graph.artifacts("C", "1.0.1").depends("D", "1.0.0")
138
+ graph.artifacts("C", "1.0.2").depends("D", "1.0.0")
139
+
140
+ # ensure we can't find a solution in the above
141
+ graph.artifacts("D", "1.0.0").depends("A", "< 0.0.0")
142
+
143
+ # Add a solution to the graph that should be reached only after
144
+ # all of the others have been tried
145
+ # it must be circular to ensure that no other branch can find it
146
+ graph.artifacts("A", "0.0.0").depends("B", "0.0.0")
147
+ graph.artifacts("B", "0.0.0").depends("C", "0.0.0")
148
+ graph.artifacts("C", "0.0.0").depends("D", "0.0.0")
149
+ graph.artifacts("D", "0.0.0").depends("A", "0.0.0")
150
+
151
+ demands = [["A"]]
152
+
153
+ result = Solve.it!(graph, demands)
154
+
155
+ result.should eql({ "A" => "0.0.0",
156
+ "B" => "0.0.0",
157
+ "C" => "0.0.0",
158
+ "D" => "0.0.0"})
159
+
160
+ end
98
161
  end
@@ -204,4 +204,29 @@ describe Solve::Constraint do
204
204
  end
205
205
  end
206
206
  end
207
+
208
+ describe "#eql?" do
209
+
210
+ subject { Solve::Constraint.new("= 1.0.0") }
211
+
212
+ it "returns true if the other object is a Solve::Constraint with the same operator and version" do
213
+ other = Solve::Constraint.new("= 1.0.0")
214
+ subject.should eql(other)
215
+ end
216
+
217
+ it "returns false if the other object is a Solve::Constraint with the same operator and different version" do
218
+ other = Solve::Constraint.new("= 9.9.9")
219
+ subject.should_not eql(other)
220
+ end
221
+
222
+ it "returns false if the other object is a Solve::Constraint with the same version and different operator" do
223
+ other = Solve::Constraint.new("> 1.0.0")
224
+ subject.should_not eql(other)
225
+ end
226
+
227
+ it "returns false if the other object is not a Solve::Constraint" do
228
+ other = "chicken"
229
+ subject.should_not eql(other)
230
+ end
231
+ end
207
232
  end
@@ -63,4 +63,28 @@ describe Solve::Demand do
63
63
  end
64
64
  end
65
65
  end
66
+
67
+ describe "equality" do
68
+ it "returns true when other is a Solve::Demand with the same name and constriant" do
69
+ other = Solve::Demand.new(solver, name, constraint)
70
+
71
+ subject.should eql(other)
72
+ end
73
+
74
+ it "returns false when other isn't a Solve::Demand" do
75
+ subject.should_not eql("chicken")
76
+ end
77
+
78
+ it "returns false when other is a Solve::Demand with the same name but a different constraint" do
79
+ other = Solve::Demand.new(solver, name, "< 3.4.5")
80
+
81
+ subject.should_not eql(other)
82
+ end
83
+
84
+ it "returns false when other is a Solve::Demand with the same constraint but a different name" do
85
+ other = Solve::Demand.new(solver, "chicken", constraint)
86
+
87
+ subject.should_not eql(other)
88
+ end
89
+ end
66
90
  end
@@ -226,4 +226,42 @@ describe Solve::Graph do
226
226
  subject.has_artifact?(artifact.name, artifact.version).should be_false
227
227
  end
228
228
  end
229
+
230
+ describe "eql?" do
231
+ subject do
232
+ graph = Solve::Graph.new
233
+ graph.artifacts("A", "1.0.0").depends("B", "1.0.0")
234
+ graph.artifacts("A", "2.0.0").depends("C", "1.0.0")
235
+ graph
236
+ end
237
+
238
+ it "returns false if other isn't a Solve::Graph" do
239
+ subject.should_not be_eql("chicken")
240
+ end
241
+
242
+ it "returns true if other is a Solve::Graph with the same artifacts and dependencies" do
243
+ other = Solve::Graph.new
244
+ other.artifacts("A", "1.0.0").depends("B", "1.0.0")
245
+ other.artifacts("A", "2.0.0").depends("C", "1.0.0")
246
+
247
+ subject.should eql(other)
248
+ end
249
+
250
+ it "returns false if the other is a Solve::Graph with the same artifacts but different dependencies" do
251
+ other = Solve::Graph.new
252
+ other.artifacts("A", "1.0.0")
253
+ other.artifacts("A", "2.0.0")
254
+
255
+ subject.should_not eql(other)
256
+ end
257
+
258
+ it "returns false if the other is a Solve::Graph with the same dependencies but different artifacts" do
259
+ other = Solve::Graph.new
260
+ other.artifacts("A", "1.0.0").depends("B", "1.0.0")
261
+ other.artifacts("A", "2.0.0").depends("C", "1.0.0")
262
+ other.artifacts("B", "1.0.0")
263
+
264
+ subject.should_not eql(other)
265
+ end
266
+ end
229
267
  end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Solve::Solver::Serializer do
4
+ it "deserializes a serialized solver to an equivalent solver" do
5
+
6
+ graph = Solve::Graph.new
7
+
8
+ graph.artifacts("A", "1.0.0").depends("B", "1.0.0")
9
+ graph.artifacts("B", "1.0.0").depends("C", "1.0.0")
10
+ graph.artifacts("C", "1.0.0")
11
+
12
+ demands = [["A", "1.0.0"]]
13
+
14
+ solver = Solve::Solver.new(graph, demands)
15
+ serializer = Solve::Solver::Serializer.new
16
+ serialized = serializer.serialize(solver)
17
+ deserialized = serializer.deserialize(serialized)
18
+
19
+ solver.graph.should eql(deserialized.graph)
20
+ solver.demands.should eql(deserialized.demands)
21
+
22
+ result = solver.resolve
23
+ deserialized_result = deserialized.resolve
24
+ result.should eql(deserialized_result)
25
+ end
26
+ end
metadata CHANGED
@@ -1,19 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Jamie Winsor
9
+ - Andrew Garson
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2012-09-25 00:00:00.000000000Z
13
+ date: 2012-09-29 00:00:00.000000000 Z
13
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
14
31
  - !ruby/object:Gem::Dependency
15
32
  name: thor
16
- requirement: &70233888694760 !ruby/object:Gem::Requirement
33
+ requirement: !ruby/object:Gem::Requirement
17
34
  none: false
18
35
  requirements:
19
36
  - - ! '>='
@@ -21,10 +38,15 @@ dependencies:
21
38
  version: 0.16.0
22
39
  type: :development
23
40
  prerelease: false
24
- version_requirements: *70233888694760
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: 0.16.0
25
47
  - !ruby/object:Gem::Dependency
26
48
  name: rake
27
- requirement: &70233888694060 !ruby/object:Gem::Requirement
49
+ requirement: !ruby/object:Gem::Requirement
28
50
  none: false
29
51
  requirements:
30
52
  - - ! '>='
@@ -32,10 +54,31 @@ dependencies:
32
54
  version: 0.9.2.2
33
55
  type: :development
34
56
  prerelease: false
35
- version_requirements: *70233888694060
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: 0.9.2.2
63
+ - !ruby/object:Gem::Dependency
64
+ name: pry
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
36
79
  - !ruby/object:Gem::Dependency
37
80
  name: rspec
38
- requirement: &70233888692060 !ruby/object:Gem::Requirement
81
+ requirement: !ruby/object:Gem::Requirement
39
82
  none: false
40
83
  requirements:
41
84
  - - ! '>='
@@ -43,10 +86,15 @@ dependencies:
43
86
  version: '0'
44
87
  type: :development
45
88
  prerelease: false
46
- version_requirements: *70233888692060
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
47
95
  - !ruby/object:Gem::Dependency
48
96
  name: fuubar
49
- requirement: &70233888691380 !ruby/object:Gem::Requirement
97
+ requirement: !ruby/object:Gem::Requirement
50
98
  none: false
51
99
  requirements:
52
100
  - - ! '>='
@@ -54,10 +102,15 @@ dependencies:
54
102
  version: '0'
55
103
  type: :development
56
104
  prerelease: false
57
- version_requirements: *70233888691380
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
58
111
  - !ruby/object:Gem::Dependency
59
112
  name: spork
60
- requirement: &70233888690880 !ruby/object:Gem::Requirement
113
+ requirement: !ruby/object:Gem::Requirement
61
114
  none: false
62
115
  requirements:
63
116
  - - ! '>='
@@ -65,10 +118,15 @@ dependencies:
65
118
  version: '0'
66
119
  type: :development
67
120
  prerelease: false
68
- version_requirements: *70233888690880
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
69
127
  - !ruby/object:Gem::Dependency
70
128
  name: yard
71
- requirement: &70233888683680 !ruby/object:Gem::Requirement
129
+ requirement: !ruby/object:Gem::Requirement
72
130
  none: false
73
131
  requirements:
74
132
  - - ! '>='
@@ -76,10 +134,15 @@ dependencies:
76
134
  version: '0'
77
135
  type: :development
78
136
  prerelease: false
79
- version_requirements: *70233888683680
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
80
143
  - !ruby/object:Gem::Dependency
81
144
  name: redcarpet
82
- requirement: &70233888683020 !ruby/object:Gem::Requirement
145
+ requirement: !ruby/object:Gem::Requirement
83
146
  none: false
84
147
  requirements:
85
148
  - - ! '>='
@@ -87,10 +150,15 @@ dependencies:
87
150
  version: '0'
88
151
  type: :development
89
152
  prerelease: false
90
- version_requirements: *70233888683020
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ! '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
91
159
  - !ruby/object:Gem::Dependency
92
160
  name: guard
93
- requirement: &70233888682320 !ruby/object:Gem::Requirement
161
+ requirement: !ruby/object:Gem::Requirement
94
162
  none: false
95
163
  requirements:
96
164
  - - ! '>='
@@ -98,10 +166,15 @@ dependencies:
98
166
  version: '0'
99
167
  type: :development
100
168
  prerelease: false
101
- version_requirements: *70233888682320
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
102
175
  - !ruby/object:Gem::Dependency
103
176
  name: guard-rspec
104
- requirement: &70233888681480 !ruby/object:Gem::Requirement
177
+ requirement: !ruby/object:Gem::Requirement
105
178
  none: false
106
179
  requirements:
107
180
  - - ! '>='
@@ -109,10 +182,15 @@ dependencies:
109
182
  version: '0'
110
183
  type: :development
111
184
  prerelease: false
112
- version_requirements: *70233888681480
185
+ version_requirements: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ! '>='
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
113
191
  - !ruby/object:Gem::Dependency
114
192
  name: guard-spork
115
- requirement: &70233888681000 !ruby/object:Gem::Requirement
193
+ requirement: !ruby/object:Gem::Requirement
116
194
  none: false
117
195
  requirements:
118
196
  - - ! '>='
@@ -120,10 +198,15 @@ dependencies:
120
198
  version: '0'
121
199
  type: :development
122
200
  prerelease: false
123
- version_requirements: *70233888681000
201
+ version_requirements: !ruby/object:Gem::Requirement
202
+ none: false
203
+ requirements:
204
+ - - ! '>='
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
124
207
  - !ruby/object:Gem::Dependency
125
208
  name: guard-yard
126
- requirement: &70233888680540 !ruby/object:Gem::Requirement
209
+ requirement: !ruby/object:Gem::Requirement
127
210
  none: false
128
211
  requirements:
129
212
  - - ! '>='
@@ -131,10 +214,15 @@ dependencies:
131
214
  version: '0'
132
215
  type: :development
133
216
  prerelease: false
134
- version_requirements: *70233888680540
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ none: false
219
+ requirements:
220
+ - - ! '>='
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
135
223
  - !ruby/object:Gem::Dependency
136
224
  name: coolline
137
- requirement: &70233888680000 !ruby/object:Gem::Requirement
225
+ requirement: !ruby/object:Gem::Requirement
138
226
  none: false
139
227
  requirements:
140
228
  - - ! '>='
@@ -142,10 +230,16 @@ dependencies:
142
230
  version: '0'
143
231
  type: :development
144
232
  prerelease: false
145
- version_requirements: *70233888680000
233
+ version_requirements: !ruby/object:Gem::Requirement
234
+ none: false
235
+ requirements:
236
+ - - ! '>='
237
+ - !ruby/object:Gem::Version
238
+ version: '0'
146
239
  description: A Ruby constraint solver
147
240
  email:
148
241
  - jamie@vialstudios.com
242
+ - andrew.garson@gmail.com
149
243
  executables: []
150
244
  extensions: []
151
245
  extra_rdoc_files: []
@@ -169,7 +263,8 @@ files:
169
263
  - lib/solve/solver.rb
170
264
  - lib/solve/solver/constraint_row.rb
171
265
  - lib/solve/solver/constraint_table.rb
172
- - lib/solve/solver/variable.rb
266
+ - lib/solve/solver/serializer.rb
267
+ - lib/solve/solver/variable_row.rb
173
268
  - lib/solve/solver/variable_table.rb
174
269
  - lib/solve/version.rb
175
270
  - solve.gemspec
@@ -180,6 +275,7 @@ files:
180
275
  - spec/unit/solve/demand_spec.rb
181
276
  - spec/unit/solve/dependency_spec.rb
182
277
  - spec/unit/solve/graph_spec.rb
278
+ - spec/unit/solve/solver/serializer_spec.rb
183
279
  - spec/unit/solve/solver_spec.rb
184
280
  - spec/unit/solve/version_spec.rb
185
281
  - spec/unit/solve_spec.rb
@@ -203,10 +299,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
203
299
  version: '0'
204
300
  segments:
205
301
  - 0
206
- hash: -4081621273929848192
302
+ hash: -267791564304481347
207
303
  requirements: []
208
304
  rubyforge_project:
209
- rubygems_version: 1.8.10
305
+ rubygems_version: 1.8.23
210
306
  signing_key:
211
307
  specification_version: 3
212
308
  summary: A Ruby constraint solver
@@ -218,6 +314,7 @@ test_files:
218
314
  - spec/unit/solve/demand_spec.rb
219
315
  - spec/unit/solve/dependency_spec.rb
220
316
  - spec/unit/solve/graph_spec.rb
317
+ - spec/unit/solve/solver/serializer_spec.rb
221
318
  - spec/unit/solve/solver_spec.rb
222
319
  - spec/unit/solve/version_spec.rb
223
320
  - spec/unit/solve_spec.rb