mirror of
https://github.com/mudler/luet.git
synced 2025-09-09 02:59:30 +00:00
Introduce install --relaxed
It introduces a relaxed way to install packages with loose deps. Default installation now will by default prefer up-to-date packages during selection. Also: - Upgrade now it's used in install so it have to return the full system view also when there is nothing to upgrade - Avoid checking upgrade upfront if relaxed is on
This commit is contained in:
@@ -415,6 +415,7 @@ var _ = Describe("Decoder", func() {
|
||||
|
||||
orderW, err := solution.Order(dbDefinitions, W.GetFingerPrint())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(orderW) > 0).To(BeTrue())
|
||||
Expect(orderW[0].Package.GetName()).To(Equal("X"))
|
||||
Expect(orderW[1].Package.GetName()).To(Equal("Y"))
|
||||
Expect(orderW[2].Package.GetName()).To(Equal("Z"))
|
||||
|
@@ -550,6 +550,13 @@ func (s *Parallel) UpgradeUniverse(dropremoved bool) (pkg.Packages, PackagesAsse
|
||||
// Upgrade compute upgrades of the package against the world definition.
|
||||
// It accepts two boolean indicating if it has to check for conflicts or try to attempt a full upgrade
|
||||
func (s *Parallel) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
|
||||
return s.upgrade(s.DefinitionDatabase, s.InstalledDatabase, checkconflicts, full)
|
||||
|
||||
}
|
||||
|
||||
// Upgrade compute upgrades of the package against the world definition.
|
||||
// It accepts two boolean indicating if it has to check for conflicts or try to attempt a full upgrade
|
||||
func (s *Parallel) upgrade(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase, checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
|
||||
|
||||
// First get candidates that needs to be upgraded..
|
||||
|
||||
@@ -557,7 +564,7 @@ func (s *Parallel) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAss
|
||||
toInstall := pkg.Packages{}
|
||||
|
||||
// we do this in memory so we take into account of provides
|
||||
universe, err := s.DefinitionDatabase.Copy()
|
||||
universe, err := defDB.Copy()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Could not copy def db")
|
||||
}
|
||||
@@ -595,7 +602,7 @@ func (s *Parallel) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAss
|
||||
}
|
||||
}()
|
||||
|
||||
for _, p := range s.InstalledDatabase.World() {
|
||||
for _, p := range installDB.World() {
|
||||
all <- p
|
||||
}
|
||||
|
||||
@@ -604,7 +611,7 @@ func (s *Parallel) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAss
|
||||
close(results)
|
||||
wg2.Wait()
|
||||
|
||||
s2 := &Parallel{Concurrency: s.Concurrency, InstalledDatabase: installedcopy, DefinitionDatabase: s.DefinitionDatabase, ParallelDatabase: pkg.NewInMemoryDatabase(false)}
|
||||
s2 := &Parallel{Concurrency: s.Concurrency, InstalledDatabase: installedcopy, DefinitionDatabase: defDB, ParallelDatabase: pkg.NewInMemoryDatabase(false)}
|
||||
s2.SetResolver(s.Resolver)
|
||||
if !full {
|
||||
ass := PackagesAssertions{}
|
||||
@@ -855,6 +862,32 @@ func (s *Parallel) Install(c pkg.Packages) (PackagesAssertions, error) {
|
||||
}
|
||||
return ass, nil
|
||||
}
|
||||
assertions, err := s.Solve()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.Solve()
|
||||
return s.upgradeAssertions(assertions)
|
||||
}
|
||||
|
||||
func (s *Parallel) upgradeAssertions(assertions PackagesAssertions) (PackagesAssertions, error) {
|
||||
|
||||
systemAfterInstall := pkg.NewInMemoryDatabase(false)
|
||||
|
||||
for _, p := range assertions {
|
||||
if p.Value {
|
||||
systemAfterInstall.CreatePackage(p.Package)
|
||||
}
|
||||
}
|
||||
|
||||
_, assertions, err := s.upgrade(s.DefinitionDatabase, systemAfterInstall, false, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// for _, u := range toUninstall {
|
||||
// systemAfterInstall.RemovePackage()
|
||||
|
||||
// }
|
||||
return assertions, nil
|
||||
}
|
||||
|
@@ -37,6 +37,8 @@ const (
|
||||
type PackageSolver interface {
|
||||
SetDefinitionDatabase(pkg.PackageDatabase)
|
||||
Install(p pkg.Packages) (PackagesAssertions, error)
|
||||
RelaxedInstall(p pkg.Packages) (PackagesAssertions, error)
|
||||
|
||||
Uninstall(checkconflicts, full bool, candidate ...pkg.Package) (pkg.Packages, error)
|
||||
ConflictsWithInstalled(p pkg.Package) (bool, error)
|
||||
ConflictsWith(p pkg.Package, ls pkg.Packages) (bool, error)
|
||||
@@ -51,6 +53,7 @@ type PackageSolver interface {
|
||||
SetResolver(PackageResolver)
|
||||
|
||||
Solve() (PackagesAssertions, error)
|
||||
// BestInstall(c pkg.Packages) (PackagesAssertions, error)
|
||||
}
|
||||
|
||||
// Solver is the default solver for luet
|
||||
@@ -493,34 +496,47 @@ func (s *Solver) UpgradeUniverse(dropremoved bool) (pkg.Packages, PackagesAssert
|
||||
return markedForRemoval, assertion, nil
|
||||
}
|
||||
|
||||
func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
|
||||
|
||||
// First get candidates that needs to be upgraded..
|
||||
|
||||
toUninstall := pkg.Packages{}
|
||||
toInstall := pkg.Packages{}
|
||||
|
||||
// we do this in memory so we take into account of provides, and its faster
|
||||
universe, err := s.DefinitionDatabase.Copy()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed creating db copy")
|
||||
}
|
||||
|
||||
installedcopy := pkg.NewInMemoryDatabase(false)
|
||||
|
||||
for _, p := range s.InstalledDatabase.World() {
|
||||
installedcopy.CreatePackage(p)
|
||||
packages, err := universe.FindPackageVersions(p)
|
||||
if err == nil && len(packages) != 0 {
|
||||
best := packages.Best(nil)
|
||||
if !best.Matches(p) {
|
||||
toUninstall = append(toUninstall, p)
|
||||
toInstall = append(toInstall, best)
|
||||
}
|
||||
func inPackage(list []pkg.Package, p pkg.Package) bool {
|
||||
for _, l := range list {
|
||||
if l.AtomMatches(p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
s2 := NewSolver(Options{Type: SingleCoreSimple}, installedcopy, s.DefinitionDatabase, pkg.NewInMemoryDatabase(false))
|
||||
// Compute upgrade between packages if specified, or all if none is specified
|
||||
func (s *Solver) computeUpgrade(pps ...pkg.Package) func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase) {
|
||||
return func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase) {
|
||||
toUninstall := pkg.Packages{}
|
||||
toInstall := pkg.Packages{}
|
||||
|
||||
// we do this in memory so we take into account of provides, and its faster
|
||||
universe, _ := defDB.Copy()
|
||||
|
||||
installedcopy := pkg.NewInMemoryDatabase(false)
|
||||
|
||||
for _, p := range installDB.World() {
|
||||
installedcopy.CreatePackage(p)
|
||||
packages, err := universe.FindPackageVersions(p)
|
||||
if err == nil && len(packages) != 0 {
|
||||
best := packages.Best(nil)
|
||||
if !best.Matches(p) && len(pps) == 0 ||
|
||||
len(pps) != 0 && inPackage(pps, p) {
|
||||
toUninstall = append(toUninstall, p)
|
||||
toInstall = append(toInstall, best)
|
||||
}
|
||||
}
|
||||
}
|
||||
return toUninstall, toInstall, installedcopy
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Solver) upgrade(fn func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase), defDB pkg.PackageDatabase, installDB pkg.PackageDatabase, checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
|
||||
|
||||
toUninstall, toInstall, installedcopy := fn(defDB, installDB)
|
||||
|
||||
s2 := NewSolver(Options{Type: SingleCoreSimple}, installedcopy, defDB, pkg.NewInMemoryDatabase(false))
|
||||
s2.SetResolver(s.Resolver)
|
||||
if !full {
|
||||
ass := PackagesAssertions{}
|
||||
@@ -541,15 +557,20 @@ func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAsser
|
||||
}
|
||||
|
||||
if len(toInstall) == 0 {
|
||||
return toUninstall, PackagesAssertions{}, nil
|
||||
ass := PackagesAssertions{}
|
||||
for _, i := range installDB.World() {
|
||||
ass = append(ass, PackageAssert{Package: i.(*pkg.DefaultPackage), Value: true})
|
||||
}
|
||||
return toUninstall, ass, nil
|
||||
}
|
||||
|
||||
assertions, err := s2.Install(toInstall.Unique())
|
||||
assertions, err := s2.RelaxedInstall(toInstall.Unique())
|
||||
|
||||
return toUninstall, assertions, err
|
||||
// To that tree, ask to install the versions that should be upgraded, and try to solve
|
||||
// Return the solution
|
||||
}
|
||||
|
||||
func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
|
||||
return s.upgrade(s.computeUpgrade(), s.DefinitionDatabase, s.InstalledDatabase, checkconflicts, full)
|
||||
}
|
||||
|
||||
// Uninstall takes a candidate package and return a list of packages that would be removed
|
||||
@@ -619,7 +640,7 @@ func (s *Solver) Uninstall(checkconflicts, full bool, packs ...pkg.Package) (pkg
|
||||
s2.SetResolver(s.Resolver)
|
||||
|
||||
// Get the requirements to install the candidate
|
||||
asserts, err := s2.Install(toRemove)
|
||||
asserts, err := s2.RelaxedInstall(toRemove)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -729,7 +750,7 @@ func (s *Solver) Solve() (PackagesAssertions, error) {
|
||||
|
||||
// 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(c pkg.Packages) (PackagesAssertions, error) {
|
||||
func (s *Solver) RelaxedInstall(c pkg.Packages) (PackagesAssertions, error) {
|
||||
|
||||
coll, err := s.getList(s.DefinitionDatabase, c)
|
||||
if err != nil {
|
||||
@@ -749,6 +770,59 @@ func (s *Solver) Install(c pkg.Packages) (PackagesAssertions, error) {
|
||||
}
|
||||
return ass, nil
|
||||
}
|
||||
assertions, err := s.Solve()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.Solve()
|
||||
return assertions, nil
|
||||
}
|
||||
|
||||
// Install returns the assertions necessary in order to install the packages in
|
||||
// a system.
|
||||
// It calculates the best result possible, trying to maximize new packages.
|
||||
func (s *Solver) Install(c pkg.Packages) (PackagesAssertions, error) {
|
||||
assertions, err := s.RelaxedInstall(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
systemAfterInstall := pkg.NewInMemoryDatabase(false)
|
||||
|
||||
toUpgrade := pkg.Packages{}
|
||||
|
||||
for _, p := range c {
|
||||
if p.GetVersion() == ">=0" || p.GetVersion() == ">0" {
|
||||
toUpgrade = append(toUpgrade, p)
|
||||
}
|
||||
}
|
||||
for _, p := range assertions {
|
||||
if p.Value {
|
||||
systemAfterInstall.CreatePackage(p.Package)
|
||||
if !inPackage(c, p.Package) {
|
||||
toUpgrade = append(toUpgrade, p.Package)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(toUpgrade) == 0 {
|
||||
return assertions, nil
|
||||
}
|
||||
// do partial upgrade based on input.
|
||||
// IF there is no version specified in the input, or >=0 is specified,
|
||||
// then compute upgrade for those
|
||||
_, newassertions, err := s.upgrade(s.computeUpgrade(toUpgrade...), s.DefinitionDatabase, systemAfterInstall, false, false)
|
||||
if err != nil {
|
||||
// TODO: Emit warning.
|
||||
// We were not able to compute upgrades (maybe for some pinned packages, or a conflict)
|
||||
// so we return the relaxed result
|
||||
return assertions, nil
|
||||
}
|
||||
|
||||
// Protect if we return no assertion at all
|
||||
if len(newassertions) == 0 && len(assertions) > 0 {
|
||||
return assertions, nil
|
||||
}
|
||||
|
||||
return newassertions, nil
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@
|
||||
package solver_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
pkg "github.com/mudler/luet/pkg/package"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -36,6 +38,143 @@ var _ = Describe("Solver", func() {
|
||||
dbDefinitions = pkg.NewInMemoryDatabase(false)
|
||||
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
|
||||
})
|
||||
|
||||
Context("Select of best available package", func() {
|
||||
|
||||
It("picks the best versions available for each package, excluding the ones manually specified while installing", func() {
|
||||
|
||||
B1 := pkg.NewPackage("B", "1.1", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
B2 := pkg.NewPackage("B", "1.2", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
B3 := pkg.NewPackage("B", "1.3", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
B4 := pkg.NewPackage("B", "1.4", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
|
||||
A1 := pkg.NewPackage("A", "1.1", []*pkg.DefaultPackage{
|
||||
pkg.NewPackage("B", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
|
||||
}, []*pkg.DefaultPackage{})
|
||||
A2 := pkg.NewPackage("A", "1.2", []*pkg.DefaultPackage{
|
||||
pkg.NewPackage("B", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
|
||||
}, []*pkg.DefaultPackage{})
|
||||
|
||||
D := pkg.NewPackage("D", "1.0", []*pkg.DefaultPackage{
|
||||
pkg.NewPackage("A", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
|
||||
}, []*pkg.DefaultPackage{})
|
||||
|
||||
C := pkg.NewPackage("C", "1", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
|
||||
for _, p := range []pkg.Package{A1, A2, B1, B2, B3, B4, C, D} {
|
||||
_, err := dbDefinitions.CreatePackage(p)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
for _, p := range []pkg.Package{C} {
|
||||
_, err := dbInstalled.CreatePackage(p)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
|
||||
|
||||
solution, err := s.(*Solver).Install([]pkg.Package{D})
|
||||
fmt.Println(solution)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(solution)).To(Equal(8))
|
||||
|
||||
// Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: A2, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B4, Value: true}))
|
||||
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B1, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B2, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false}))
|
||||
|
||||
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
|
||||
|
||||
solution, err = s.(*Solver).Install([]pkg.Package{D, B2})
|
||||
fmt.Println(solution)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(solution)).To(Equal(8))
|
||||
|
||||
// Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: A2, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B2, Value: true}))
|
||||
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B1, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B4, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false}))
|
||||
|
||||
})
|
||||
|
||||
It("picks the best available excluding those manually input. In this case we the input is a selector >=0", func() {
|
||||
|
||||
B1 := pkg.NewPackage("B", "1.1", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
B2 := pkg.NewPackage("B", "1.2", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
B3 := pkg.NewPackage("B", "1.3", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
B4 := pkg.NewPackage("B", "1.4", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
|
||||
A1 := pkg.NewPackage("A", "1.1", []*pkg.DefaultPackage{
|
||||
pkg.NewPackage("B", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
|
||||
}, []*pkg.DefaultPackage{})
|
||||
A2 := pkg.NewPackage("A", "1.2", []*pkg.DefaultPackage{
|
||||
pkg.NewPackage("B", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
|
||||
}, []*pkg.DefaultPackage{})
|
||||
|
||||
D := pkg.NewPackage("D", "1.0", []*pkg.DefaultPackage{
|
||||
pkg.NewPackage("A", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
|
||||
}, []*pkg.DefaultPackage{})
|
||||
|
||||
C := pkg.NewPackage("C", "1", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
|
||||
for _, p := range []pkg.Package{A1, A2, B1, B2, B3, B4, C, D} {
|
||||
_, err := dbDefinitions.CreatePackage(p)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
for _, p := range []pkg.Package{C} {
|
||||
_, err := dbInstalled.CreatePackage(p)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
|
||||
|
||||
solution, err := s.(*Solver).Install([]pkg.Package{pkg.NewPackage("D", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})})
|
||||
fmt.Println(solution)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(solution)).To(Equal(8))
|
||||
|
||||
// Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: A2, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B4, Value: true}))
|
||||
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B1, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B2, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false}))
|
||||
|
||||
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
|
||||
|
||||
solution, err = s.(*Solver).Install([]pkg.Package{pkg.NewPackage("D", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}), B2})
|
||||
fmt.Println(solution)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(solution)).To(Equal(8))
|
||||
|
||||
// Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: A2, Value: true}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B2, Value: true}))
|
||||
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B1, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B4, Value: false}))
|
||||
Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false}))
|
||||
|
||||
})
|
||||
})
|
||||
Context("Simple set", func() {
|
||||
It("Solves correctly if the selected package has no requirements or conflicts and we have nothing installed yet", func() {
|
||||
|
||||
|
Reference in New Issue
Block a user