solve 0.3.0 → 0.3.1

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