diff --git a/pkg/solver/decoder.go b/pkg/solver/decoder.go index 096c250b..060d069d 100644 --- a/pkg/solver/decoder.go +++ b/pkg/solver/decoder.go @@ -19,8 +19,11 @@ import ( "fmt" pkg "github.com/mudler/luet/pkg/package" + toposort "github.com/philopon/go-toposort" ) +type PackagesAssertions []PackageAssert + // PackageAssert represent a package assertion. // It is composed of a Package and a Value which is indicating the absence or not // of the associated package state. @@ -30,8 +33,8 @@ type PackageAssert struct { } // DecodeModel decodes a model from the SAT solver to package assertions (PackageAssert) -func DecodeModel(model map[string]bool) ([]PackageAssert, error) { - ass := make([]PackageAssert, 0) +func DecodeModel(model map[string]bool) (PackagesAssertions, error) { + ass := make(PackagesAssertions, 0) for k, v := range model { a, err := pkg.DecodePackage(k) if err != nil { @@ -57,3 +60,45 @@ func (a *PackageAssert) ToString() string { } return fmt.Sprintf("%s/%s %s %s: %t", a.Package.GetCategory(), a.Package.GetName(), a.Package.GetVersion(), msg, a.Value) } + +func (assertions PackagesAssertions) Order() PackagesAssertions { + + orderedAssertions := PackagesAssertions{} + unorderedAssertions := PackagesAssertions{} + fingerprints := []string{} + + tmpMap := map[string]PackageAssert{} + + for _, a := range assertions { + if a.Package.Flagged() { + unorderedAssertions = append(unorderedAssertions, a) // Build a list of the ones that must be ordered + fingerprints = append(fingerprints, a.Package.GetFingerPrint()) + tmpMap[a.Package.GetFingerPrint()] = a + } else { + orderedAssertions = append(orderedAssertions, a) // Keep last the ones which are not meant to be installed + } + } + + // Build a topological graph + graph := toposort.NewGraph(len(unorderedAssertions)) + graph.AddNodes(fingerprints...) + for _, a := range unorderedAssertions { + for _, req := range a.Package.GetRequires() { + graph.AddEdge(a.Package.GetFingerPrint(), req.GetFingerPrint()) + } + } + result, ok := graph.Toposort() + if !ok { + panic("cycle detected") + } + + for _, res := range result { + a, ok := tmpMap[res] + if !ok { + panic("Sort order - this shouldn't happen") + } + orderedAssertions = append([]PackageAssert{a}, orderedAssertions...) // push upfront + } + + return orderedAssertions +} diff --git a/pkg/solver/decoder_test.go b/pkg/solver/decoder_test.go new file mode 100644 index 00000000..b63761cd --- /dev/null +++ b/pkg/solver/decoder_test.go @@ -0,0 +1,132 @@ +// Copyright © 2019 Ettore Di Giacinto +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package solver_test + +import ( + "strconv" + + pkg "github.com/mudler/luet/pkg/package" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + . "github.com/mudler/luet/pkg/solver" +) + +var _ = Describe("Decoder", func() { + Context("Assertion ordering", func() { + eq := 0 + for index := 0; index < 300; index++ { // Just to make sure we don't have false positives + It("Orders them correctly #"+strconv.Itoa(index), func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + G := pkg.NewPackage("G", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + H := pkg.NewPackage("H", "", []*pkg.DefaultPackage{G}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{H}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{D}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + + s := NewSolver([]pkg.Package{C}, []pkg.Package{A, B, C, D, E, F, G}) + + solution, err := s.Install([]pkg.Package{A}) + Expect(solution).To(ContainElement(PackageAssert{Package: A.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: B.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: D.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: C.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: H.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: G.IsFlagged(true), Value: true})) + + Expect(len(solution)).To(Equal(6)) + Expect(err).ToNot(HaveOccurred()) + solution = solution.Order() + Expect(len(solution)).To(Equal(6)) + Expect(solution[0].Package.GetName()).To(Equal("G")) + Expect(solution[1].Package.GetName()).To(Equal("H")) + Expect(solution[2].Package.GetName()).To(Equal("D")) + Expect(solution[3].Package.GetName()).To(Equal("B")) + eq += len(solution) + //Expect(solution[4].Package.GetName()).To(Equal("A")) + //Expect(solution[5].Package.GetName()).To(Equal("C")) As C doesn't have any dep it can be in both positions + }) + } + + It("Expects perfect equality when ordered", func() { + Expect(eq).To(Equal(300 * 6)) // assertions lenghts + }) + + disequality := 0 + equality := 0 + for index := 0; index < 300; index++ { // Just to make sure we don't have false positives + It("Doesn't order them correctly otherwise #"+strconv.Itoa(index), func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + G := pkg.NewPackage("G", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + H := pkg.NewPackage("H", "", []*pkg.DefaultPackage{G}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{H}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{D}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + + s := NewSolver([]pkg.Package{C}, []pkg.Package{A, B, C, D, E, F, G}) + + solution, err := s.Install([]pkg.Package{A}) + Expect(solution).To(ContainElement(PackageAssert{Package: A.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: B.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: D.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: C.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: H.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: G.IsFlagged(true), Value: true})) + + Expect(len(solution)).To(Equal(6)) + Expect(err).ToNot(HaveOccurred()) + if solution[0].Package.GetName() != "G" { + disequality++ + } else { + equality++ + } + if solution[1].Package.GetName() != "H" { + disequality++ + } else { + equality++ + } + if solution[2].Package.GetName() != "D" { + disequality++ + } else { + equality++ + } + if solution[3].Package.GetName() != "B" { + disequality++ + } else { + equality++ + } + if solution[4].Package.GetName() != "A" { + disequality++ + } else { + equality++ + } + if solution[5].Package.GetName() != "C" { + disequality++ + } else { + equality++ + } + }) + It("Expect disequality", func() { + Expect(disequality).ToNot(Equal(0)) + Expect(equality).ToNot(Equal(300 * 6)) + }) + } + }) +}) diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index a61872d4..5ae6ea3c 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -20,17 +20,15 @@ import ( "github.com/crillab/gophersat/bf" pkg "github.com/mudler/luet/pkg/package" - toposort "github.com/philopon/go-toposort" ) // PackageSolver is an interface to a generic package solving algorithm type PackageSolver interface { SetWorld(p []pkg.Package) - Install(p []pkg.Package) ([]PackageAssert, error) + Install(p []pkg.Package) (PackagesAssertions, error) Uninstall(candidate pkg.Package) ([]pkg.Package, error) ConflictsWithInstalled(p pkg.Package) (bool, error) ConflictsWith(p pkg.Package, ls []pkg.Package) (bool, error) - Order([]PackageAssert) []PackageAssert } // Solver is the default solver for luet @@ -253,7 +251,7 @@ func (s *Solver) solve(f bf.Formula) (map[string]bool, bf.Formula, error) { } // Solve builds the formula given the current state and returns package assertions -func (s *Solver) Solve() ([]PackageAssert, error) { +func (s *Solver) Solve() (PackagesAssertions, error) { f, err := s.BuildFormula() @@ -269,58 +267,16 @@ func (s *Solver) Solve() ([]PackageAssert, error) { return DecodeModel(model) } -func (s *Solver) Order(assertions []PackageAssert) []PackageAssert { - - orderedAssertions := []PackageAssert{} - unorderedAssertions := []PackageAssert{} - fingerprints := []string{} - - tmpMap := map[string]PackageAssert{} - - for _, a := range assertions { - if a.Package.Flagged() { - unorderedAssertions = append(unorderedAssertions, a) // Build a list of the ones that must be ordered - fingerprints = append(fingerprints, a.Package.GetFingerPrint()) - tmpMap[a.Package.GetFingerPrint()] = a - } else { - orderedAssertions = append(orderedAssertions, a) // Keep last the ones which are not meant to be installed - } - } - - // Build a topological graph - graph := toposort.NewGraph(len(unorderedAssertions)) - graph.AddNodes(fingerprints...) - for _, a := range unorderedAssertions { - for _, req := range a.Package.GetRequires() { - graph.AddEdge(a.Package.GetFingerPrint(), req.GetFingerPrint()) - } - } - result, ok := graph.Toposort() - if !ok { - panic("cycle detected") - } - - for _, res := range result { - a, ok := tmpMap[res] - if !ok { - panic("Sort order - this shouldn't happen") - } - orderedAssertions = append([]PackageAssert{a}, orderedAssertions...) // push upfront - } - - return orderedAssertions -} - // Install given a list of packages, returns package assertions to indicate the packages that must be installed in the system in order // to statisfy all the constraints -func (s *Solver) Install(coll []pkg.Package) ([]PackageAssert, error) { +func (s *Solver) Install(coll []pkg.Package) (PackagesAssertions, error) { for _, v := range coll { v.IsFlagged(false) } s.Wanted = coll if s.noRulesWorld() { - var ass []PackageAssert + var ass PackagesAssertions for _, p := range s.Installed { ass = append(ass, PackageAssert{Package: p.IsFlagged(true), Value: true}) diff --git a/pkg/solver/solver_test.go b/pkg/solver/solver_test.go index 7b1a4a75..837dfab1 100644 --- a/pkg/solver/solver_test.go +++ b/pkg/solver/solver_test.go @@ -16,8 +16,6 @@ package solver_test import ( - "strconv" - pkg "github.com/mudler/luet/pkg/package" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -335,107 +333,5 @@ var _ = Describe("Solver", func() { Expect(err).ToNot(HaveOccurred()) }) }) - Context("Assertion ordering", func() { - eq := 0 - for index := 0; index < 300; index++ { // Just to make sure we don't have false positives - It("Orders them correctly #"+strconv.Itoa(index), func() { - C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - G := pkg.NewPackage("G", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - H := pkg.NewPackage("H", "", []*pkg.DefaultPackage{G}, []*pkg.DefaultPackage{}) - D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{H}, []*pkg.DefaultPackage{}) - B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{D}, []*pkg.DefaultPackage{}) - A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) - s := NewSolver([]pkg.Package{C}, []pkg.Package{A, B, C, D, E, F, G}) - - solution, err := s.Install([]pkg.Package{A}) - Expect(solution).To(ContainElement(PackageAssert{Package: A.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: B.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: D.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: C.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: H.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: G.IsFlagged(true), Value: true})) - - Expect(len(solution)).To(Equal(6)) - Expect(err).ToNot(HaveOccurred()) - solution = s.Order(solution) - Expect(len(solution)).To(Equal(6)) - Expect(solution[0].Package.GetName()).To(Equal("G")) - Expect(solution[1].Package.GetName()).To(Equal("H")) - Expect(solution[2].Package.GetName()).To(Equal("D")) - Expect(solution[3].Package.GetName()).To(Equal("B")) - eq += len(solution) - //Expect(solution[4].Package.GetName()).To(Equal("A")) - //Expect(solution[5].Package.GetName()).To(Equal("C")) As C doesn't have any dep it can be in both positions - }) - } - - It("Expects perfect equality when ordered", func() { - Expect(eq).To(Equal(300 * 6)) // assertions lenghts - }) - - disequality := 0 - equality := 0 - for index := 0; index < 300; index++ { // Just to make sure we don't have false positives - It("Doesn't order them correctly otherwise #"+strconv.Itoa(index), func() { - C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - G := pkg.NewPackage("G", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - H := pkg.NewPackage("H", "", []*pkg.DefaultPackage{G}, []*pkg.DefaultPackage{}) - D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{H}, []*pkg.DefaultPackage{}) - B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{D}, []*pkg.DefaultPackage{}) - A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) - - s := NewSolver([]pkg.Package{C}, []pkg.Package{A, B, C, D, E, F, G}) - - solution, err := s.Install([]pkg.Package{A}) - Expect(solution).To(ContainElement(PackageAssert{Package: A.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: B.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: D.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: C.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: H.IsFlagged(true), Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: G.IsFlagged(true), Value: true})) - - Expect(len(solution)).To(Equal(6)) - Expect(err).ToNot(HaveOccurred()) - if solution[0].Package.GetName() != "G" { - disequality++ - } else { - equality++ - } - if solution[1].Package.GetName() != "H" { - disequality++ - } else { - equality++ - } - if solution[2].Package.GetName() != "D" { - disequality++ - } else { - equality++ - } - if solution[3].Package.GetName() != "B" { - disequality++ - } else { - equality++ - } - if solution[4].Package.GetName() != "A" { - disequality++ - } else { - equality++ - } - if solution[5].Package.GetName() != "C" { - disequality++ - } else { - equality++ - } - }) - It("Expect disequality", func() { - Expect(disequality).ToNot(Equal(0)) - Expect(equality).ToNot(Equal(300 * 6)) - }) - } - }) })