From 4197d7af619e8e024d12c7d4fe39db49dbf91c2a Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Fri, 22 May 2020 20:45:28 +0200 Subject: [PATCH] Add upgrade by using only the SAT core - Adds upgrade --universe and upgrade --universe --clean. It will attempt to bring the system as much close as the content available in the repositories. It differs from a standard upgrade which checks directly that what is pulled in doesn't conflict with the system. In this new way, we just query the SAT solver to decide that on our behalf. - Add uninstall --full-clean. It uses only the SAT solver to uninstall the package and it will drop as many packages as required (including revdeps of packages too. --- cmd/uninstall.go | 15 +- cmd/upgrade.go | 16 +- pkg/installer/installer.go | 49 ++- pkg/solver/solver.go | 170 ++++++++ pkg/solver/solver_test.go | 529 ++++++++++++++++------- tests/integration/05_upgrade.sh | 1 + tests/integration/11_upgrade_universe.sh | 127 ++++++ 7 files changed, 722 insertions(+), 185 deletions(-) create mode 100755 tests/integration/11_upgrade_universe.sh diff --git a/cmd/uninstall.go b/cmd/uninstall.go index 4234dff8..60c8404c 100644 --- a/cmd/uninstall.go +++ b/cmd/uninstall.go @@ -60,6 +60,7 @@ var uninstallCmd = &cobra.Command{ nodeps, _ := cmd.Flags().GetBool("nodeps") full, _ := cmd.Flags().GetBool("full") checkconflicts, _ := cmd.Flags().GetBool("conflictscheck") + fullClean, _ := cmd.Flags().GetBool("full-clean") LuetCfg.GetSolverOptions().Type = stype LuetCfg.GetSolverOptions().LearnRate = float32(rate) @@ -69,12 +70,13 @@ var uninstallCmd = &cobra.Command{ Debug("Solver", LuetCfg.GetSolverOptions().CompactString()) inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{ - Concurrency: LuetCfg.GetGeneral().Concurrency, - SolverOptions: *LuetCfg.GetSolverOptions(), - NoDeps: nodeps, - Force: force, - FullUninstall: full, - CheckConflicts: checkconflicts, + Concurrency: LuetCfg.GetGeneral().Concurrency, + SolverOptions: *LuetCfg.GetSolverOptions(), + NoDeps: nodeps, + Force: force, + FullUninstall: full, + FullCleanUninstall: fullClean, + CheckConflicts: checkconflicts, }) if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { @@ -107,6 +109,7 @@ func init() { uninstallCmd.Flags().Bool("force", false, "Force uninstall") uninstallCmd.Flags().Bool("full", false, "Attempts to remove as much packages as possible which aren't required (slow)") uninstallCmd.Flags().Bool("conflictscheck", true, "Check if the package marked for deletion is required by other packages") + uninstallCmd.Flags().Bool("full-clean", false, "(experimental) Uninstall packages and all the other deps/revdeps of it.") RootCmd.AddCommand(uninstallCmd) } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 922df5c0..e142168a 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -60,6 +60,8 @@ var upgradeCmd = &cobra.Command{ force := LuetCfg.Viper.GetBool("force") nodeps, _ := cmd.Flags().GetBool("nodeps") full, _ := cmd.Flags().GetBool("full") + universe, _ := cmd.Flags().GetBool("universe") + clean, _ := cmd.Flags().GetBool("clean") LuetCfg.GetSolverOptions().Type = stype LuetCfg.GetSolverOptions().LearnRate = float32(rate) @@ -69,11 +71,13 @@ var upgradeCmd = &cobra.Command{ Debug("Solver", LuetCfg.GetSolverOptions().String()) inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{ - Concurrency: LuetCfg.GetGeneral().Concurrency, - SolverOptions: *LuetCfg.GetSolverOptions(), - Force: force, - FullUninstall: full, - NoDeps: nodeps, + Concurrency: LuetCfg.GetGeneral().Concurrency, + SolverOptions: *LuetCfg.GetSolverOptions(), + Force: force, + FullUninstall: full, + NoDeps: nodeps, + SolverUpgrade: universe, + RemoveUnavailableOnUpgrade: clean, }) inst.Repositories(repos) _, err := inst.SyncRepositories(false) @@ -109,6 +113,8 @@ func init() { upgradeCmd.Flags().Bool("force", false, "Force upgrade by ignoring errors") upgradeCmd.Flags().Bool("nodeps", false, "Don't consider package dependencies (harmful! overrides checkconflicts and full!)") upgradeCmd.Flags().Bool("full", true, "Attempts to remove as much packages as possible which aren't required (slow)") + upgradeCmd.Flags().Bool("universe", false, "Use ONLY the SAT solver to compute upgrades (experimental)") + upgradeCmd.Flags().Bool("clean", false, "Try to drop removed packages (experimental, only when --universe is enabled)") RootCmd.AddCommand(upgradeCmd) } diff --git a/pkg/installer/installer.go b/pkg/installer/installer.go index 4a40d18c..ecc9bce5 100644 --- a/pkg/installer/installer.go +++ b/pkg/installer/installer.go @@ -36,14 +36,15 @@ import ( ) type LuetInstallerOptions struct { - SolverOptions config.LuetSolverOptions - Concurrency int - NoDeps bool - OnlyDeps bool - Force bool - PreserveSystemEssentialData bool - FullUninstall bool - CheckConflicts bool + SolverOptions config.LuetSolverOptions + Concurrency int + NoDeps bool + OnlyDeps bool + Force bool + PreserveSystemEssentialData bool + FullUninstall, FullCleanUninstall bool + CheckConflicts bool + SolverUpgrade, RemoveUnavailableOnUpgrade bool } type LuetInstaller struct { @@ -74,9 +75,19 @@ func (l *LuetInstaller) Upgrade(s *System) error { syncedRepos.SyncDatabase(allRepos) // compute a "big" world solv := solver.NewResolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver()) - uninstall, solution, err := solv.Upgrade(!l.Options.FullUninstall, l.Options.NoDeps) - if err != nil { - return errors.Wrap(err, "Failed solving solution for upgrade") + var uninstall pkg.Packages + var solution solver.PackagesAssertions + + if l.Options.SolverUpgrade { + uninstall, solution, err = solv.UpgradeUniverse(l.Options.RemoveUnavailableOnUpgrade) + if err != nil { + return errors.Wrap(err, "Failed solving solution for upgrade") + } + } else { + uninstall, solution, err = solv.Upgrade(!l.Options.FullUninstall, l.Options.NoDeps) + if err != nil { + return errors.Wrap(err, "Failed solving solution for upgrade") + } } Info("Marked for uninstall") @@ -608,10 +619,20 @@ func (l *LuetInstaller) Uninstall(p pkg.Package, s *System) error { if !l.Options.NoDeps { Info("Finding :package:", p.HumanReadableString(), "dependency graph :deciduous_tree:") solv := solver.NewResolver(installedtmp, installedtmp, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver()) - solution, err := solv.Uninstall(p, checkConflicts, full) - if err != nil && !l.Options.Force { - return errors.Wrap(err, "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps") + var solution pkg.Packages + var err error + if l.Options.FullCleanUninstall { + solution, err = solv.UninstallUniverse(pkg.Packages{p}) + if err != nil { + return errors.Wrap(err, "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps") + } + } else { + solution, err = solv.Uninstall(p, checkConflicts, full) + if err != nil && !l.Options.Force { + return errors.Wrap(err, "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps") + } } + for _, p := range solution { Info("Uninstalling", p.HumanReadableString()) err := l.uninstall(p, s) diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index 73dd86d4..f7f6b48e 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -38,6 +38,9 @@ type PackageSolver interface { World() pkg.Packages Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) + UpgradeUniverse(dropremoved bool) (pkg.Packages, PackagesAssertions, error) + UninstallUniverse(toremove pkg.Packages) (pkg.Packages, error) + SetResolver(PackageResolver) Solve() (PackagesAssertions, error) @@ -95,6 +98,16 @@ func (s *Solver) noRulesWorld() bool { return true } +func (s *Solver) noRulesInstalled() bool { + for _, p := range s.Installed() { + if len(p.GetConflicts()) != 0 || len(p.GetRequires()) != 0 { + return false + } + } + + return true +} + func (s *Solver) BuildInstalled() (bf.Formula, error) { var formulas []bf.Formula for _, p := range s.Installed() { @@ -251,6 +264,163 @@ func (s *Solver) ConflictsWithInstalled(p pkg.Package) (bool, error) { return s.ConflictsWith(p, s.Installed()) } +// UninstallUniverse takes a list of candidate package and return a list of packages that would be removed +// in order to purge the candidate. Uses the solver to check constraints and nothing else +// +// It can be compared to the counterpart Uninstall as this method acts like a uninstall --full +// it removes all the packages and its deps. taking also in consideration other packages that might have +// revdeps +func (s *Solver) UninstallUniverse(toremove pkg.Packages) (pkg.Packages, error) { + + if s.noRulesInstalled() { + return s.getList(s.InstalledDatabase, toremove) + } + + // resolve to packages from the db + toRemove, err := s.getList(s.InstalledDatabase, toremove) + if err != nil { + return nil, errors.Wrap(err, "Package not found in definition db") + } + + var formulas []bf.Formula + r, err := s.BuildInstalled() + if err != nil { + return nil, errors.Wrap(err, "Package not found in definition db") + } + + // SAT encode the clauses against the world + for _, p := range toRemove.Unique() { + encodedP, err := p.Encode(s.InstalledDatabase) + if err != nil { + return nil, errors.Wrap(err, "Package not found in definition db") + } + P := bf.Var(encodedP) + formulas = append(formulas, bf.And(bf.Not(P), r)) + } + + markedForRemoval := pkg.Packages{} + model := bf.Solve(bf.And(formulas...)) + if model == nil { + return nil, errors.New("Failed finding a solution") + } + assertion, err := DecodeModel(model, s.InstalledDatabase) + if err != nil { + return nil, errors.Wrap(err, "while decoding model from solution") + } + for _, a := range assertion { + if !a.Value { + if p, err := s.InstalledDatabase.FindPackage(a.Package); err == nil { + markedForRemoval = append(markedForRemoval, p) + } + + } + } + return markedForRemoval, nil +} + +// UpgradeUniverse mark packages for removal and returns a solution. It considers +// the Universe db as authoritative +// See also on the subject: https://arxiv.org/pdf/1007.1021.pdf +func (s *Solver) UpgradeUniverse(dropremoved bool) (pkg.Packages, PackagesAssertions, error) { + // we first figure out which aren't up-to-date + // which has to be removed + // and which needs to be upgraded + notUptodate := pkg.Packages{} + removed := pkg.Packages{} + toUpgrade := pkg.Packages{} + + // TODO: this is memory expensive, we need to optimize this + universe := pkg.NewInMemoryDatabase(false) + for _, p := range s.DefinitionDatabase.World() { + universe.CreatePackage(p) + } + for _, p := range s.Installed() { + universe.CreatePackage(p) + } + + // Grab all the installed ones, see if they are eligible for update + for _, p := range s.Installed() { + available, err := universe.FindPackageVersions(p) + if err != nil { + removed = append(removed, p) + } + if len(available) == 0 { + continue + } + + bestmatch := available.Best(nil) + // Found a better version available + if !bestmatch.Matches(p) { + notUptodate = append(notUptodate, p) + toUpgrade = append(toUpgrade, bestmatch) + } + } + + // resolve to packages from the db to be able to encode correctly + oldPackages, err := s.getList(universe, notUptodate) + if err != nil { + return nil, nil, errors.Wrap(err, "couldn't get package marked for removal from universe") + } + + updates, err := s.getList(universe, toUpgrade) + if err != nil { + return nil, nil, errors.Wrap(err, "couldn't get package marked for update from universe") + } + + var formulas []bf.Formula + + // Build constraints for the whole defdb + r, err := s.BuildWorld(true) + if err != nil { + return nil, nil, errors.Wrap(err, "couldn't build world constraints") + } + + // Treat removed packages from universe as marked for deletion + if dropremoved { + oldPackages = append(oldPackages, removed...) + } + + // SAT encode the clauses against the world + for _, p := range oldPackages.Unique() { + encodedP, err := p.Encode(universe) + if err != nil { + return nil, nil, errors.Wrap(err, "couldn't encode package") + } + P := bf.Var(encodedP) + formulas = append(formulas, bf.And(bf.Not(P), r)) + } + + for _, p := range updates { + encodedP, err := p.Encode(universe) + if err != nil { + return nil, nil, errors.Wrap(err, "couldn't encode package") + } + P := bf.Var(encodedP) + formulas = append(formulas, bf.And(P, r)) + } + + markedForRemoval := pkg.Packages{} + model := bf.Solve(bf.And(formulas...)) + if model == nil { + return nil, nil, errors.New("Failed finding a solution") + } + + assertion, err := DecodeModel(model, universe) + if err != nil { + return nil, nil, errors.Wrap(err, "while decoding model from solution") + } + for _, a := range assertion { + if !a.Value { + if p, err := s.InstalledDatabase.FindPackage(a.Package); err == nil { + markedForRemoval = append(markedForRemoval, p) + } + + } + + } + return markedForRemoval, assertion, nil +} + func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) { // First get candidates that needs to be upgraded.. diff --git a/pkg/solver/solver_test.go b/pkg/solver/solver_test.go index 80b01fd1..fe2c0ae0 100644 --- a/pkg/solver/solver_test.go +++ b/pkg/solver/solver_test.go @@ -574,57 +574,366 @@ var _ = Describe("Solver", func() { Expect(err).ToNot(HaveOccurred()) }) - It("Uninstalls simple package correctly", func() { + Context("Uninstall", func() { + It("Uninstalls simple package correctly", func() { - C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbDefinitions.CreatePackage(p) + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + s = NewSolver(dbInstalled, dbDefinitions, db) + + solution, err := s.Uninstall(A, true, true) Expect(err).ToNot(HaveOccurred()) - } - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbInstalled.CreatePackage(p) + Expect(solution).To(ContainElement(A.IsFlagged(false))) + + // Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(len(solution)).To(Equal(1)) + }) + It("Uninstalls simple package expanded correctly", func() { + + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "1.2", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + s = NewSolver(dbInstalled, dbDefinitions, db) + + solution, err := s.Uninstall(&pkg.DefaultPackage{Name: "A", Version: ">1.0"}, true, true) Expect(err).ToNot(HaveOccurred()) - } - s = NewSolver(dbInstalled, dbDefinitions, db) - solution, err := s.Uninstall(A, true, true) - Expect(err).ToNot(HaveOccurred()) + Expect(solution).To(ContainElement(A.IsFlagged(false))) - Expect(solution).To(ContainElement(A.IsFlagged(false))) + // Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(len(solution)).To(Equal(1)) + }) + It("Uninstalls simple packages not in world correctly", func() { - // Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) - Expect(len(solution)).To(Equal(1)) - }) - It("Uninstalls simple package expanded correctly", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - A := pkg.NewPackage("A", "1.2", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + for _, p := range []pkg.Package{B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbDefinitions.CreatePackage(p) + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + solution, err := s.Uninstall(A, true, true) Expect(err).ToNot(HaveOccurred()) - } - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbInstalled.CreatePackage(p) + Expect(solution).To(ContainElement(A.IsFlagged(false))) + + // Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(len(solution)).To(Equal(1)) + }) + + It("Uninstalls complex packages not in world correctly", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + solution, err := s.Uninstall(A, true, true) Expect(err).ToNot(HaveOccurred()) - } - s = NewSolver(dbInstalled, dbDefinitions, db) - solution, err := s.Uninstall(&pkg.DefaultPackage{Name: "A", Version: ">1.0"}, true, true) - Expect(err).ToNot(HaveOccurred()) + Expect(solution).To(ContainElement(A.IsFlagged(false))) - Expect(solution).To(ContainElement(A.IsFlagged(false))) + Expect(len(solution)).To(Equal(1)) + }) + + It("Uninstalls complex packages correctly, even if shared deps are required by system packages", func() { + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + solution, err := s.Uninstall(A, true, true) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + Expect(solution).ToNot(ContainElement(B.IsFlagged(false))) + + Expect(len(solution)).To(Equal(1)) + }) + + It("Uninstalls complex packages in world correctly", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{C}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + solution, err := s.Uninstall(A, true, true) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + Expect(solution).To(ContainElement(C.IsFlagged(false))) + + Expect(len(solution)).To(Equal(2)) + }) + + It("Uninstalls complex package correctly", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{D}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + // C // installed + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + solution, err := s.Uninstall(A, true, true) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + Expect(solution).To(ContainElement(B.IsFlagged(false))) + Expect(solution).To(ContainElement(D.IsFlagged(false))) + + Expect(len(solution)).To(Equal(3)) + + }) + + It("UninstallUniverse simple package correctly", func() { + + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + s = NewSolver(dbInstalled, dbDefinitions, db) + + solution, err := s.UninstallUniverse(pkg.Packages{A}) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + + // Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(len(solution)).To(Equal(1)) + }) + It("UninstallUniverse simple package expanded correctly", func() { + + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "1.2", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + s = NewSolver(dbInstalled, dbDefinitions, db) + + solution, err := s.UninstallUniverse(pkg.Packages{ + &pkg.DefaultPackage{Name: "A", Version: ">1.0"}}) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + + // Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(len(solution)).To(Equal(1)) + }) + It("UninstallUniverse simple packages not in world correctly", func() { + + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + solution, err := s.UninstallUniverse(pkg.Packages{A}) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + + // Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(len(solution)).To(Equal(1)) + }) + + It("UninstallUniverse complex packages not in world correctly", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + solution, err := s.UninstallUniverse(pkg.Packages{A}) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + Expect(solution).To(ContainElement(B.IsFlagged(false))) + + Expect(len(solution)).To(Equal(2)) + }) + + It("UninstallUniverse complex packages correctly, even if shared deps are required by system packages", func() { + // Here we diff a lot from standard Uninstall: + // all the packages that has reverse deps will be removed (aka --full) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + solution, err := s.UninstallUniverse(pkg.Packages{A}) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + Expect(solution).To(ContainElement(B.IsFlagged(false))) + Expect(solution).To(ContainElement(C.IsFlagged(false))) + + Expect(len(solution)).To(Equal(3)) + }) + + It("UninstallUniverse complex packages in world correctly", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{C}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + solution, err := s.UninstallUniverse(pkg.Packages{A}) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + Expect(solution).To(ContainElement(C.IsFlagged(false))) + + Expect(len(solution)).To(Equal(2)) + }) + + It("UninstallUniverse complex package correctly", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{D}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + // C // installed + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + solution, err := s.UninstallUniverse(pkg.Packages{A}) + Expect(err).ToNot(HaveOccurred()) + + Expect(solution).To(ContainElement(A.IsFlagged(false))) + Expect(solution).To(ContainElement(B.IsFlagged(false))) + Expect(solution).To(ContainElement(D.IsFlagged(false))) + + Expect(len(solution)).To(Equal(3)) + + }) - // Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) - Expect(len(solution)).To(Equal(1)) }) It("Find conflicts", func() { @@ -810,131 +1119,6 @@ var _ = Describe("Solver", func() { Expect(val).ToNot(BeTrue()) }) - It("Uninstalls simple packages not in world correctly", func() { - - C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - - for _, p := range []pkg.Package{B, C, D} { - _, err := dbDefinitions.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbInstalled.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - solution, err := s.Uninstall(A, true, true) - Expect(err).ToNot(HaveOccurred()) - - Expect(solution).To(ContainElement(A.IsFlagged(false))) - - // Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) - Expect(len(solution)).To(Equal(1)) - }) - - It("Uninstalls complex packages not in world correctly", func() { - C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) - - for _, p := range []pkg.Package{B, C, D} { - _, err := dbDefinitions.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbInstalled.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - solution, err := s.Uninstall(A, true, true) - Expect(err).ToNot(HaveOccurred()) - - Expect(solution).To(ContainElement(A.IsFlagged(false))) - - Expect(len(solution)).To(Equal(1)) - }) - - It("Uninstalls complex packages correctly, even if shared deps are required by system packages", func() { - D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) - C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) - - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbDefinitions.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbInstalled.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - solution, err := s.Uninstall(A, true, true) - Expect(err).ToNot(HaveOccurred()) - - Expect(solution).To(ContainElement(A.IsFlagged(false))) - Expect(solution).ToNot(ContainElement(B.IsFlagged(false))) - - Expect(len(solution)).To(Equal(1)) - }) - - It("Uninstalls complex packages in world correctly", func() { - C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{C}, []*pkg.DefaultPackage{}) - - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbDefinitions.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - - for _, p := range []pkg.Package{A, C, D} { - _, err := dbInstalled.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - - solution, err := s.Uninstall(A, true, true) - Expect(err).ToNot(HaveOccurred()) - - Expect(solution).To(ContainElement(A.IsFlagged(false))) - Expect(solution).To(ContainElement(C.IsFlagged(false))) - - Expect(len(solution)).To(Equal(2)) - }) - - It("Uninstalls complex package correctly", func() { - C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) - B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{D}, []*pkg.DefaultPackage{}) - A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) - // C // installed - - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbDefinitions.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - - for _, p := range []pkg.Package{A, B, C, D} { - _, err := dbInstalled.CreatePackage(p) - Expect(err).ToNot(HaveOccurred()) - } - - solution, err := s.Uninstall(A, true, true) - Expect(err).ToNot(HaveOccurred()) - - Expect(solution).To(ContainElement(A.IsFlagged(false))) - Expect(solution).To(ContainElement(B.IsFlagged(false))) - Expect(solution).To(ContainElement(D.IsFlagged(false))) - - Expect(len(solution)).To(Equal(3)) - - }) - }) Context("Conflict set", func() { @@ -1058,5 +1242,30 @@ var _ = Describe("Solver", func() { Expect(len(solution)).To(Equal(3)) }) + + It("UpgradeUniverse upgrades correctly", func() { + for _, p := range []pkg.Package{A1, B, C} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + uninstall, solution, err := s.UpgradeUniverse(true) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(uninstall)).To(Equal(1)) + Expect(uninstall[0].GetName()).To(Equal("a")) + Expect(uninstall[0].GetVersion()).To(Equal("1.1")) + + Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: false})) + Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false})) + + Expect(len(solution)).To(Equal(4)) + }) }) }) diff --git a/tests/integration/05_upgrade.sh b/tests/integration/05_upgrade.sh index ddbe88cf..253cc02f 100755 --- a/tests/integration/05_upgrade.sh +++ b/tests/integration/05_upgrade.sh @@ -110,6 +110,7 @@ testInstall() { testUpgrade() { upgrade=$(luet --config $tmpdir/luet.yaml upgrade) installst=$? + echo "$upgrade" assertEquals 'install test successfully' "$installst" "0" assertTrue 'package uninstalled B' "[ ! -e '$tmpdir/testrootfs/test5' ]" assertTrue 'package installed B' "[ -e '$tmpdir/testrootfs/newc' ]" diff --git a/tests/integration/11_upgrade_universe.sh b/tests/integration/11_upgrade_universe.sh new file mode 100755 index 00000000..4b6c868c --- /dev/null +++ b/tests/integration/11_upgrade_universe.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +export LUET_NOLOCK=true + +oneTimeSetUp() { +export tmpdir="$(mktemp -d)" +} + +oneTimeTearDown() { + rm -rf "$tmpdir" +} + +testBuild() { + mkdir $tmpdir/testbuild + luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/b-1.0 + buildst=$? + assertTrue 'create package B 1.0' "[ -e '$tmpdir/testbuild/b-test-1.0.package.tar.gz' ]" + assertEquals 'builds successfully' "$buildst" "0" + + luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/b-1.1 + buildst=$? + assertEquals 'builds successfully' "$buildst" "0" + assertTrue 'create package B 1.1' "[ -e '$tmpdir/testbuild/b-test-1.1.package.tar.gz' ]" + + luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/a-1.0 + buildst=$? + assertEquals 'builds successfully' "$buildst" "0" + assertTrue 'create package A 1.0' "[ -e '$tmpdir/testbuild/a-test-1.0.package.tar.gz' ]" + + luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/a-1.1 + buildst=$? + assertEquals 'builds successfully' "$buildst" "0" + + assertTrue 'create package A 1.1' "[ -e '$tmpdir/testbuild/a-test-1.1.package.tar.gz' ]" + + luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/a-1.2 + buildst=$? + assertEquals 'builds successfully' "$buildst" "0" + + assertTrue 'create package A 1.2' "[ -e '$tmpdir/testbuild/a-test-1.2.package.tar.gz' ]" + + + luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/c-1.0 + buildst=$? + assertEquals 'builds successfully' "$buildst" "0" + assertTrue 'create package C 1.0' "[ -e '$tmpdir/testbuild/c-test-1.0.package.tar.gz' ]" + +} + +testRepo() { + assertTrue 'no repository' "[ ! -e '$tmpdir/testbuild/repository.yaml' ]" + luet create-repo --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" \ + --output $tmpdir/testbuild \ + --packages $tmpdir/testbuild \ + --name "test" \ + --descr "Test Repo" \ + --urls $tmpdir/testrootfs \ + --type http + + createst=$? + assertEquals 'create repo successfully' "$createst" "0" + assertTrue 'create repository' "[ -e '$tmpdir/testbuild/repository.yaml' ]" +} + +testConfig() { + mkdir $tmpdir/testrootfs + cat < $tmpdir/luet.yaml +general: + debug: true +system: + rootfs: $tmpdir/testrootfs + database_path: "/" + database_engine: "boltdb" +repositories: + - name: "main" + type: "disk" + enable: true + urls: + - "$tmpdir/testbuild" +EOF + luet config --config $tmpdir/luet.yaml + res=$? + assertEquals 'config test successfully' "$res" "0" +} + +testInstall() { + luet install --config $tmpdir/luet.yaml test/b-1.0 + installst=$? + assertEquals 'install test successfully' "$installst" "0" + assertTrue 'package installed B' "[ -e '$tmpdir/testrootfs/test5' ]" + + luet install --config $tmpdir/luet.yaml test/a-1.0 + assertTrue 'package installed A' "[ -e '$tmpdir/testrootfs/testaa' ]" + installst=$? + assertEquals 'install test successfully' "$installst" "0" + + luet install --config $tmpdir/luet.yaml test/a-1.1 + assertTrue 'package installed A' "[ -e '$tmpdir/testrootfs/testaa' ]" + installst=$? + assertEquals 'install test successfully' "$installst" "0" + assertTrue 'package keeps old A' "[ -e '$tmpdir/testrootfs/testaa' ]" + assertTrue 'package new A was not installed' "[ ! -e '$tmpdir/testrootfs/testlatest' ]" + + luet install --config $tmpdir/luet.yaml test/c-1.0 + installst=$? + assertEquals 'install test successfully' "$installst" "0" + assertTrue 'package installed C' "[ -e '$tmpdir/testrootfs/c' ]" +} + +testUpgrade() { + upgrade=$(luet --config $tmpdir/luet.yaml upgrade --universe --clean) + installst=$? + assertEquals 'install test successfully' "$installst" "0" + echo "$upgrade" + assertTrue 'package uninstalled B' "[ ! -e '$tmpdir/testrootfs/test5' ]" + assertTrue 'package installed B' "[ -e '$tmpdir/testrootfs/newc' ]" + assertTrue 'package uninstalled A' "[ ! -e '$tmpdir/testrootfs/testaa' ]" + assertTrue 'package installed new A' "[ -e '$tmpdir/testrootfs/testlatest' ]" + + # It does remove C as well, no other package depends on it. + assertContains 'does contain test/c-1.0' "$upgrade" 'test/c-1.0' + assertNotContains 'does not attempt to download test/c-1.0' "$upgrade" 'test/c-1.0 downloaded' +} + +# Load shUnit2. +. "$ROOT_DIR/tests/integration/shunit2"/shunit2 +