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 +