spec/unit/solve/solver_spec.rb in solve-0.8.2 vs spec/unit/solve/solver_spec.rb in solve-1.0.0.rc1

- old
+ new

@@ -1,302 +1,158 @@ require 'spec_helper' describe Solve::Solver do - let(:graph) { double('graph') } + let(:graph) { double(Solve::Graph) } + let(:demands) { [["mysql"], ["nginx"]] } + subject(:solver) { described_class.new(graph, demands) } - describe "ClassMethods" do - subject { Solve::Solver } + it "has a list of demands as ruby literals" do + solver.demands_array.should == demands + end - describe "::new" do - let(:demand_array) { ["nginx", "ntp"] } + it "has a list of demands as model objects" do + expected = [ + Solve::Demand.new(solver, "mysql"), + Solve::Demand.new(solver, "nginx") + ] + solver.demands.should == expected + end - it "adds a demand for each element in the array" do - obj = subject.new(graph, demand_array) + it "has a graph" do + solver.graph.should == graph + end - obj.demands.should have(2).items - end + describe "when the constraints are solvable" do + let(:graph) do + graph = Solve::Graph.new + graph.artifact("A", "1.0.0") + graph.artifact("B", "1.0.0").depends("A") + graph + end - context "when demand_array is an array of array" do - let(:demand_array) { [["nginx", "= 1.2.3"], ["ntp", "= 1.0.0"]] } + let(:demands) { [["A"], ["B"]] } - it "creates a new demand with the name and constraint of each element in the array" do - obj = subject.new(graph, demand_array) + it "gives the solution as a Hash" do + solver.resolve.should == {"A"=>"1.0.0", "B"=>"1.0.0"} + end - obj.demands[0].name.should eql("nginx") - obj.demands[0].constraint.to_s.should eql("= 1.2.3") - obj.demands[1].name.should eql("ntp") - obj.demands[1].constraint.to_s.should eql("= 1.0.0") - end - end + it "gives the solution in sorted form" do + solver.resolve(sorted: true).should == [["A", "1.0.0"], ["B", "1.0.0"]] + end + end - context "when demand_array is an array of strings" do - let(:demand_array) { ["nginx", "ntp"] } - - it "creates a new demand with the name and a default constraint of each element in the array" do - obj = subject.new(graph, demand_array) - - obj.demands[0].name.should eql("nginx") - obj.demands[0].constraint.to_s.should eql(">= 0.0.0") - obj.demands[1].name.should eql("ntp") - obj.demands[1].constraint.to_s.should eql(">= 0.0.0") - end + describe "when the constraints are not solvable" do + let(:error) do + begin + solver.resolve + rescue => e + e + else + raise "Expected resolve to cause an error" end + end - context "when demand_array is a mix between an array of arrays and an array of strings" do - let(:demand_array) { [["nginx", "= 1.2.3"], "ntp"] } - - it "creates a new demand with the name and default constraint or constraint of each element in the array" do - obj = subject.new(graph, demand_array) - - obj.demands[0].name.should eql("nginx") - obj.demands[0].constraint.to_s.should eql("= 1.2.3") - obj.demands[1].name.should eql("ntp") - obj.demands[1].constraint.to_s.should eql(">= 0.0.0") - end + context "and dep-selector identifies missing artifacts" do + let(:graph) do + graph = Solve::Graph.new + graph.artifact("A", "1.0.0") + graph end - end - describe "::demand_key" do - let(:demand) { Solve::Demand.new(double('solver'), "nginx", "= 1.2.3") } + let(:demands) { [ ["Z"] ] } - it "returns a symbol containing the name and constraint of the demand" do - subject.demand_key(demand).should eql(:'nginx-= 1.2.3') + it "raises an error detailing the missing artifacts" do + error.to_s.should include("Missing artifacts: Z") end end - describe "::satisfy_all" do - let(:ver_one) { Solve::Version.new("3.1.1") } - let(:ver_two) { Solve::Version.new("3.1.2") } - - let(:constraints) do - [ - Solve::Constraint.new("> 3.0.0"), - Solve::Constraint.new("<= 3.1.2") - ] + context "and dep-selector identifies constraints that exclude all known versions" do + let(:graph) do + graph = Solve::Graph.new + graph.artifact("A", "1.0.0") + graph end - let(:versions) do - [ - Solve::Version.new("0.0.1"), - Solve::Version.new("0.1.0"), - Solve::Version.new("1.0.0"), - Solve::Version.new("2.0.0"), - Solve::Version.new("3.0.0"), - ver_one, - ver_two, - Solve::Version.new("4.1.0") - ].shuffle - end + let(:demands) { [ ["A", "> 1.0.0"] ] } - it "returns all of the versions which satisfy all of the given constraints" do - solution = subject.satisfy_all(constraints, versions) - - solution.should have(2).items - solution.should include(ver_one) - solution.should include(ver_two) + it "raises an error detailing the missing artifacts" do + error.to_s.should include("Required artifacts do not exist at the desired version") + error.to_s.should include("Constraints that match no available version: (A > 1.0.0)") end - - context "given multiple duplicate versions" do - let(:versions) do - [ - ver_one, - ver_one, - ver_one - ] - end - - it "does not return duplicate satisfied versions" do - solution = subject.satisfy_all(constraints, versions) - - solution.should have(1).item - end - end end - describe "::satisfy_best" do - let(:versions) do - [ - Solve::Version.new("0.0.1"), - Solve::Version.new("0.1.0"), - Solve::Version.new("1.0.0"), - Solve::Version.new("2.0.0"), - Solve::Version.new("3.0.0"), - Solve::Version.new("3.1.1"), - Solve::Version.new("3.1.2"), - Solve::Version.new("4.1.0") - ].shuffle + context "and dep-selector identifies dependency conflicts" do + let(:graph) do + graph = Solve::Graph.new + graph.artifact("A", "1.0.0").depends("B").depends("C") + graph.artifact("B", "1.0.0").depends("D", "= 1.0.0") + graph.artifact("C", "1.0.0").depends("D", "= 2.0.0") + graph.artifact("D", "1.0.0") + graph.artifact("D", "2.0.0") + graph end - it "returns the best possible match for the given constraints" do - subject.satisfy_best([">= 1.0.0", "< 4.1.0"], versions).to_s.should eql("3.1.2") - end + let(:demands) { [ [ "A" ] ] } - context "given no version matches a constraint" do - let(:versions) do - [ - Solve::Version.new("4.1.0") - ] - end - - it "raises a NoSolutionError error" do - lambda { - subject.satisfy_best(">= 5.0.0", versions) - }.should raise_error(Solve::Errors::NoSolutionError) - end + it "raises an error detailing the missing artifacts" do + error.to_s.should include("Demand that cannot be met: (A >= 0.0.0)") + error.to_s.should include("Artifacts for which there are conflicting dependencies: D = 1.0.0 -> []") end end - end - subject { Solve::Solver.new(graph) } + context "and dep-selector times out looking for a solution" do + let(:selector) { double(DepSelector::Selector) } - describe "#resolve" do - let(:graph) { Solve::Graph.new } - - subject { Solve::Solver.new(graph) } - - before(:each) do - graph.artifacts("nginx", "1.0.0") - subject.demands("nginx", "= 1.0.0") - end - - it "returns a solution in the form of a Hash" do - subject.resolve.should be_a(Hash) - end - end - - describe "#demands" do - context "given a name and constraint argument" do - let(:name) { "nginx" } - let(:constraint) { "~> 0.101.5" } - - context "given the artifact of the given name and constraint does not exist" do - it "returns a Solve::Demand" do - subject.demands(name, constraint).should be_a(Solve::Demand) - end - - it "the artifact has the given name" do - subject.demands(name, constraint).name.should eql(name) - end - - it "the artifact has the given constraint" do - subject.demands(name, constraint).constraint.to_s.should eql(constraint) - end - - it "adds an artifact to the demands collection" do - subject.demands(name, constraint) - - subject.demands.should have(1).item - end - - it "the artifact added matches the given name" do - subject.demands(name, constraint) - - subject.demands[0].name.should eql(name) - end - - it "the artifact added matches the given constraint" do - subject.demands(name, constraint) - - subject.demands[0].constraint.to_s.should eql(constraint) - end + before do + graph.stub(:artifacts).and_return([]) + DepSelector::Selector.stub(:new).and_return(selector) + selector.stub(:find_solution).and_raise(DepSelector::Exceptions::TimeBoundExceeded) end - end - context "given only a name argument" do - it "returns a demand with a match all version constraint (>= 0.0.0)" do - subject.demands("nginx").constraint.to_s.should eql(">= 0.0.0") + it "raises an error explaining no solution could be found" do + error.to_s.should include("The dependency constraints could not be solved in the time allotted.") end end - context "given no arguments" do - it "returns an array" do - subject.demands.should be_a(Array) - end + context "and dep-selector times out looking for dependency conflicts" do + let(:selector) { double(DepSelector::Selector) } - it "returns an empty array if no demands have been accessed" do - subject.demands.should have(0).items + before do + graph.stub(:artifacts).and_return([]) + DepSelector::Selector.stub(:new).and_return(selector) + selector.stub(:find_solution).and_raise(DepSelector::Exceptions::TimeBoundExceededNoSolution) end - it "returns an array containing a demand if one was accessed" do - subject.demands("nginx", "~> 0.101.5") - - subject.demands.should have(1).item + it "raises a NoSolutionCauseUnknown error to indicate that no debug info was generated" do + error.should be_a_kind_of(Solve::Errors::NoSolutionCauseUnknown) end - end - context "given an unexpected number of arguments" do - it "raises an ArgumentError if more than two are provided" do - lambda { - subject.demands(1, 2, 3) - }.should raise_error(ArgumentError, "Unexpected number of arguments. You gave: 3. Expected: 2 or less.") + it "raises an error explaining that no solution exists but the cause could not be determined" do + error.to_s.should include("There is a dependency conflict, but the solver could not determine the precise cause in the time allotted.") end - - it "raises an ArgumentError if a name argument of nil is provided" do - lambda { - subject.demands(nil) - }.should raise_error(ArgumentError, "A name must be specified. You gave: [nil].") - end - - it "raises an ArgumentError if a name and constraint argument are provided but name is nil" do - lambda { - subject.demands(nil, "= 1.0.0") - }.should raise_error(ArgumentError, 'A name must be specified. You gave: [nil, "= 1.0.0"].') - end end end - describe "#add_demand" do - let(:demand) { Solve::Demand.new(double('graph'), 'ntp') } - - it "adds a Solve::Artifact to the collection of artifacts" do - subject.add_demand(demand) - - subject.should have_demand(demand) - subject.demands.should have(1).item - end - - it "should not add the same demand twice to the collection" do - subject.add_demand(demand) - subject.add_demand(demand) - - subject.demands.should have(1).item - end + describe "finding unsatisfiable demands" do + it "partitions demands into satisfiable and not satisfiable" end - describe "#remove_demand" do - let(:demand) { Solve::Demand.new(double('graph'), 'ntp') } + describe "supporting Serializer interface" do + let(:serializer) { Solve::Solver::Serializer.new } - context "given the demand is a member of the collection" do - before(:each) { subject.add_demand(demand) } - - it "removes the Solve::Artifact from the collection of demands" do - subject.remove_demand(demand) - - subject.demands.should have(0).items - end - - it "returns the removed Solve::Artifact" do - subject.remove_demand(demand).should eql(demand) - end + before do + graph.stub(:artifacts).and_return([]) end - context "given the demand is not a member of the collection" do - it "should return nil" do - subject.remove_demand(demand).should be_nil - end - end - end + it "implements the required interface" do + json_string = serializer.serialize(solver) + problem_data = JSON.parse(json_string) + expected_demands = [ + {"name" => "mysql", "constraint" => ">= 0.0.0"}, + {"name" => "nginx", "constraint" => ">= 0.0.0"} + ] - describe "#has_demand?" do - let(:demand) { Solve::Demand.new(double('graph'), 'ntp') } - - it "returns true if the given Solve::Artifact is a member of the collection" do - subject.add_demand(demand) - - subject.has_demand?(demand).should be_true + problem_data["demands"].should =~ expected_demands end - - it "returns false if the given Solve::Artifact is not a member of the collection" do - subject.has_demand?(demand).should be_false - end end end +