require "spec_helper"

describe "Solutions when using the ruby solver" do

  before do
    Solve.engine = :ruby
  end

  it "chooses the correct artifact for the demands" do
    graph = Solve::Graph.new
    graph.artifact("mysql", "2.0.0")
    graph.artifact("mysql", "1.2.0")
    graph.artifact("nginx", "1.0.0").depends("mysql", "= 1.2.0")

    result = Solve.it!(graph, [["nginx", "= 1.0.0"], ["mysql"]])

    result.should eql("nginx" => "1.0.0", "mysql" => "1.2.0")
  end

  it "chooses the best artifact for the demands" do
    graph = Solve::Graph.new
    graph.artifact("mysql", "2.0.0")
    graph.artifact("mysql", "1.2.0")
    graph.artifact("nginx", "1.0.0").depends("mysql", ">= 1.2.0")

    result = Solve.it!(graph, [["nginx", "= 1.0.0"], ["mysql"]])

    result.should eql("nginx" => "1.0.0", "mysql" => "2.0.0")
  end

  it "raises NoSolutionError when a solution cannot be found" do
    graph = Solve::Graph.new
    graph.artifact("mysql", "1.2.0")

    lambda {
      Solve.it!(graph, ["mysql", ">= 2.0.0"])
    }.should raise_error(Solve::Errors::NoSolutionError)
  end

  it "find the correct solution when backtracking in variables introduced via demands" do
    graph = Solve::Graph.new

    graph.artifact("D", "1.2.0")
    graph.artifact("D", "1.3.0")
    graph.artifact("D", "1.4.0")
    graph.artifact("D", "2.0.0")
    graph.artifact("D", "2.1.0")

    graph.artifact("C", "2.0.0").depends("D", "= 1.2.0")
    graph.artifact("C", "2.1.0").depends("D", ">= 2.1.0")
    graph.artifact("C", "2.2.0").depends("D", "> 2.0.0")

    graph.artifact("B", "1.0.0").depends("D", "= 1.0.0")
    graph.artifact("B", "1.1.0").depends("D", "= 1.0.0")
    graph.artifact("B", "2.0.0").depends("D", ">= 1.3.0")
    graph.artifact("B", "2.1.0").depends("D", ">= 2.0.0")

    graph.artifact("A", "1.0.0").depends("B", "> 2.0.0")
    graph.artifact("A", "1.0.0").depends("C", "= 2.1.0")
    graph.artifact("A", "1.0.1").depends("B", "> 1.0.0")
    graph.artifact("A", "1.0.1").depends("C", "= 2.1.0")
    graph.artifact("A", "1.0.2").depends("B", "> 1.0.0")
    graph.artifact("A", "1.0.2").depends("C", "= 2.0.0")

    result = Solve.it!(graph, [["A", "~> 1.0.0"], ["D", ">= 2.0.0"]])

    result.should eql("A" => "1.0.1",
                      "B" => "2.1.0",
                      "C" => "2.1.0",
                      "D" => "2.1.0")
  end

  it "rejects a circular dependency with a circular dep error" do
    graph = Solve::Graph.new

    graph.artifact("A", "1.0.0").depends("B", "1.0.0")
    graph.artifact("B", "1.0.0").depends("C", "1.0.0")
    graph.artifact("C", "1.0.0").depends("A", "1.0.0")

    expect { Solve.it!(graph, [["A", "1.0.0"]]) }.to raise_error(Solve::Errors::NoSolutionError)
  end

  it "rejects a p shaped depenency chain with a circular dep error" do
    graph = Solve::Graph.new

    graph.artifact("A", "1.0.0").depends("B", "1.0.0")
    graph.artifact("B", "1.0.0").depends("C", "1.0.0")
    graph.artifact("C", "1.0.0").depends("B", "1.0.0")

    expect { Solve.it!(graph, [["A", "1.0.0"]]) }.to raise_error(Solve::Errors::NoSolutionError)
  end

  it "finds the correct solution when there is a diamond shaped dependency" do
    graph = Solve::Graph.new

    graph.artifact("A", "1.0.0")
      .depends("B", "1.0.0")
      .depends("C", "1.0.0")
    graph.artifact("B", "1.0.0")
      .depends("D", "1.0.0")
    graph.artifact("C", "1.0.0")
      .depends("D", "1.0.0")
    graph.artifact("D", "1.0.0")

    result = Solve.it!(graph, [["A", "1.0.0"]])

    result.should eql("A" => "1.0.0",
                      "B" => "1.0.0",
                      "C" => "1.0.0",
                      "D" => "1.0.0")
  end

  it "solves when packages and constraints have prerelease elements" do
    graph = Solve::Graph.new

    graph.artifact("A", "1.0.0")
      .depends("B", ">= 1.0.0-alpha")
    graph.artifact("B", "1.0.0-alpha")
      .depends("C", "1.0.0")
    graph.artifact("C", "1.0.0")

    result = Solve.it!(graph, [["A", "1.0.0"]])

    result.should eql("A" => "1.0.0",
                      "B" => "1.0.0-alpha",
                      "C" => "1.0.0")

  end

  it "solves when packages and constraints have build elements" do
    graph = Solve::Graph.new

    graph.artifact("A", "1.0.0")
      .depends("B", ">= 1.0.0+build")
    graph.artifact("B", "1.0.0+build")
      .depends("C", "1.0.0")
    graph.artifact("C", "1.0.0")

    result = Solve.it!(graph, [["A", "1.0.0"]])

    result.should eql("A" => "1.0.0",
                      "B" => "1.0.0+build",
                      "C" => "1.0.0")

  end

  it "fails with a self dependency" do
    graph = Solve::Graph.new

    graph.artifact("bottom", "1.0.0")
    graph.artifact("middle", "1.0.0").depends("top", "= 1.0.0").depends("middle")

    demands = [["bottom", "1.0.0"], ["middle", "1.0.0"]]

    expect { Solve.it!(graph, demands, { sorted: true } ) }.to raise_error do |error|
      error.should be_a(Solve::Errors::NoSolutionError)
    end
  end

  it "gives an empty solution when there are no demands" do
    graph = Solve::Graph.new
    result = Solve.it!(graph, [])
    result.should eql({})
  end

  it "tries all combinations until it finds a solution" do

    graph = Solve::Graph.new

    graph.artifact("A", "1.0.0").depends("B", "~> 1.0.0")
    graph.artifact("A", "1.0.1").depends("B", "~> 1.0.0")
    graph.artifact("A", "1.0.2").depends("B", "~> 1.0.0")

    graph.artifact("B", "1.0.0").depends("C", "~> 1.0.0")
    graph.artifact("B", "1.0.1").depends("C", "~> 1.0.0")
    graph.artifact("B", "1.0.2").depends("C", "~> 1.0.0")

    graph.artifact("C", "1.0.0").depends("D", "1.0.0")
    graph.artifact("C", "1.0.1").depends("D", "1.0.0")
    graph.artifact("C", "1.0.2").depends("D", "1.0.0")

    # Note:
    # This test previously used two circular dependencies:
    # (D 1.0.0) -> A < 0.0.0
    # (D 0.0.0) -> A = 0.0.0
    # But Molinillo doesn't support circular dependencies at all.

    # ensure we can't find a solution in the above
    graph.artifact("D", "1.0.0").depends("E", "< 0.0.0")

    # Add a solution to the graph that should be reached only after all of the
    # others have been tried
    graph.artifact("A", "0.0.0").depends("B", "0.0.0")
    graph.artifact("B", "0.0.0").depends("C", "0.0.0")
    graph.artifact("C", "0.0.0").depends("D", "0.0.0")
    graph.artifact("D", "0.0.0")

    demands = [["A"]]

    result = Solve.it!(graph, demands)

    result.should eql({ "A" => "0.0.0",
                        "B" => "0.0.0",
                        "C" => "0.0.0",
                        "D" => "0.0.0" })

  end

  it "correctly resolves when a resolution exists but it is not the latest" do
    graph = Solve::Graph.new

    graph.artifact("get-the-old-one", "1.0.0")
      .depends("locked-mid-1", ">= 0.0.0")
      .depends("locked-mid-2", ">= 0.0.0")
    graph.artifact("get-the-old-one", "0.5.0")

    graph.artifact("locked-mid-1", "2.0.0").depends("old-bottom", "= 2.0.0")
    graph.artifact("locked-mid-1", "1.3.0").depends("old-bottom", "= 0.5.0")
    graph.artifact("locked-mid-1", "1.0.0")

    graph.artifact("locked-mid-2", "2.0.0").depends("old-bottom", "= 2.1.0")
    graph.artifact("locked-mid-2", "1.4.0").depends("old-bottom", "= 0.5.0")
    graph.artifact("locked-mid-2", "1.0.0")

    graph.artifact("old-bottom", "2.1.0")
    graph.artifact("old-bottom", "2.0.0")
    graph.artifact("old-bottom", "1.0.0")
    graph.artifact("old-bottom", "0.5.0")

    demands = [["get-the-old-one"]]

    result = Solve.it!(graph, demands)

    # Note: Gecode solver is different. It picks:
    #
    # "get-the-old-one" => "1.0.0",
    # "locked-mid-1" => "1.0.0",
    # "locked-mid-2" => "2.0.0",
    # "old-bottom" => "2.1.0"

    result.should eql({
      "get-the-old-one" => "1.0.0",
      "locked-mid-1" => "2.0.0",
      "locked-mid-2" => "1.0.0",
      "old-bottom" => "2.0.0",
    })
  end

  describe "when options[:sorted] is true" do
    describe "with a simple list of dependencies" do
      it "returns a sorted list of dependencies" do
        graph = Solve::Graph.new

        graph.artifact("A", "1.0.0").depends("B", "= 1.0.0")
        graph.artifact("B", "1.0.0").depends("C", "= 1.0.0")
        graph.artifact("C", "1.0.0")

        demands = [["A"]]

        result = Solve.it!(graph, demands, { sorted: true })

        result.should eql([
          ["C", "1.0.0"],
          ["B", "1.0.0"],
          ["A", "1.0.0"],
        ])
      end
    end

    # The order that the demands come in determines the order of artifacts
    # in the solver's variable_table. This must not determine the sort order
    describe "with a constraint that depends upon an earlier constrained artifact" do
      it "returns a sorted list of dependencies" do
        graph = Solve::Graph.new

        graph.artifact("B", "1.0.0").depends("A", "= 1.0.0")
        graph.artifact("A", "1.0.0").depends("C", "= 1.0.0")
        graph.artifact("C", "1.0.0")

        demands = [["A"], ["B"]]

        result = Solve.it!(graph, demands, { sorted: true } )

        result.should eql([
          ["C", "1.0.0"],
          ["A", "1.0.0"],
          ["B", "1.0.0"],
        ])
      end
    end

    describe "when the solution is cyclic" do
      it "raises a Solve::Errors::NoSolutionError because Molinillo doesn't support circular deps" do
        graph = Solve::Graph.new

        graph.artifact("A", "1.0.0").depends("B", "= 1.0.0")
        graph.artifact("B", "1.0.0").depends("C", "= 1.0.0")
        graph.artifact("C", "1.0.0").depends("A", "= 1.0.0")

        demands = [["A"]]

        expect { Solve.it!(graph, demands, { sorted: true } ) }.to raise_error(Solve::Errors::NoSolutionError)
      end
    end
  end
end
