From 3cf281965b7e5aa774f45a1a210bddc59303ac1e Mon Sep 17 00:00:00 2001 From: Simon Aquino Date: Fri, 27 Jun 2014 01:40:41 +0100 Subject: [PATCH] Implementation of all-matches functionality + tests When performing an *aptly snapshot pull*, users might list dependency versions that can potentially match multiple packages in the source snapshot. However, the current implementation of the 'snapshot pull' command only allows one package to be pulled from a snapshot at a time for a given dependency. The newly implemented all-matches flag allows users to pull all the matching packages from a source snapshot, provided that they satisfy the version requirements indicated by the dependencies. The all-matches flag defaults to false and only produces the described behaviour when it is explicitly set to true. --- cmd/snapshot_pull.go | 61 ++++++++++++++---------- deb/list.go | 42 +++++++++++----- deb/list_test.go | 111 ++++++++++++++++++++++++++++++++++++------- deb/package.go | 10 +++- deb/package_test.go | 34 +++++++------ deb/version.go | 23 +++++++++ deb/version_test.go | 42 ++++++++++++++++ 7 files changed, 255 insertions(+), 68 deletions(-) diff --git a/cmd/snapshot_pull.go b/cmd/snapshot_pull.go index dcf3338f..956b29c2 100644 --- a/cmd/snapshot_pull.go +++ b/cmd/snapshot_pull.go @@ -18,6 +18,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { noDeps := context.flags.Lookup("no-deps").Value.Get().(bool) noRemove := context.flags.Lookup("no-remove").Value.Get().(bool) + allMatches := context.flags.Lookup("all-matches").Value.Get().(bool) // Load snapshot snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) @@ -97,24 +98,30 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { dep := dependencies[i] // Search for package that can satisfy dependencies - pkg := sourcePackageList.Search(dep) - if pkg == nil { + searchResults := sourcePackageList.Search(dep, allMatches) + if searchResults == nil { context.Progress().ColoredPrintf("@y[!]@| @!Dependency %s can't be satisfied with source %s@|", &dep, source) continue } if !noRemove { // Remove all packages with the same name and architecture - for p := packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}); p != nil; { - packageList.Remove(p) - context.Progress().ColoredPrintf("@r[-]@| %s removed", p) - p = packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}) + for _, pkg := range searchResults { + for pS := packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, allMatches); pS != nil; { + for _, p := range pS { + packageList.Remove(p) + context.Progress().ColoredPrintf("@r[-]@| %s removed", p) + } + pS = packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, allMatches) + } } } // Add new discovered package - packageList.Add(pkg) - context.Progress().ColoredPrintf("@g[+]@| %s added", pkg) + for _, pkg := range searchResults { + packageList.Add(pkg) + context.Progress().ColoredPrintf("@g[+]@| %s added", pkg) + } if noDeps { continue @@ -122,26 +129,28 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { // Find missing dependencies for single added package pL := deb.NewPackageList() - pL.Add(pkg) + for _, pkg := range searchResults { + pL.Add(pkg) - var missing []deb.Dependency - missing, err = pL.VerifyDependencies(context.DependencyOptions(), []string{arch}, packageList, nil) - if err != nil { - context.Progress().ColoredPrintf("@y[!]@| @!Error while verifying dependencies for pkg %s: %s@|", pkg, err) - } - - // Append missing dependencies to the list of dependencies to satisfy - for _, misDep := range missing { - found := false - for _, d := range dependencies { - if d == misDep { - found = true - break - } + var missing []deb.Dependency + missing, err = pL.VerifyDependencies(context.DependencyOptions(), []string{arch}, packageList, nil) + if err != nil { + context.Progress().ColoredPrintf("@y[!]@| @!Error while verifying dependencies for pkg %s: %s@|", pkg, err) } - if !found { - dependencies = append(dependencies, misDep) + // Append missing dependencies to the list of dependencies to satisfy + for _, misDep := range missing { + found := false + for _, d := range dependencies { + if d == misDep { + found = true + break + } + } + + if !found { + dependencies = append(dependencies, misDep) + } } } } @@ -186,6 +195,8 @@ Example: cmd.Flag.Bool("dry-run", false, "don't create destination snapshot, just show what would be pulled") cmd.Flag.Bool("no-deps", false, "don't process dependencies, just pull listed packages") cmd.Flag.Bool("no-remove", false, "don't remove other package versions when pulling package") + cmd.Flag.Bool("all-matches", false, "pull all the packages that satisfy the dependency version requirements") + return cmd } diff --git a/deb/list.go b/deb/list.go index b615b5d2..e63e859f 100644 --- a/deb/list.go +++ b/deb/list.go @@ -263,7 +263,7 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so continue } - if sources.Search(dep) == nil { + if sources.Search(dep, false) == nil { variantsMissing = append(variantsMissing, dep) missingCount++ } else { @@ -326,29 +326,47 @@ func (l *PackageList) PrepareIndex() { } // Search searches package index for specified package -func (l *PackageList) Search(dep Dependency) *Package { +func (l *PackageList) Search(dep Dependency, allMatches bool) []*Package { if !l.indexed { panic("list not indexed, can't search") } + searchResults := []*Package{} + if dep.Relation == VersionDontCare { for _, p := range l.providesIndex[dep.Pkg] { if p.MatchesArchitecture(dep.Architecture) { - return p + searchResults = append(searchResults, p) + + if !allMatches { + break + } } } + if len(searchResults) != 0 { + return searchResults + } } i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.packagesIndex[j].Name >= dep.Pkg }) for i < len(l.packagesIndex) && l.packagesIndex[i].Name == dep.Pkg { p := l.packagesIndex[i] - if p.MatchesDependency(dep) { - return p + if p.MatchesDependency(dep, allMatches) { + searchResults = append(searchResults, p) + + if !allMatches { + break + } } i++ } + + if len(searchResults) != 0 { + return searchResults + } + return nil } @@ -396,7 +414,7 @@ func (l *PackageList) Filter(queries []string, withDependencies bool, source *Pa for i < len(l.packagesIndex) && l.packagesIndex[i].Name == dep.Pkg { p := l.packagesIndex[i] - if p.MatchesDependency(dep) { + if p.MatchesDependency(dep, false) { result.Add(p) } i++ @@ -423,11 +441,13 @@ func (l *PackageList) Filter(queries []string, withDependencies bool, source *Pa // try to satisfy dependencies for _, dep := range missing { - p := l.Search(dep) - if p != nil { - result.Add(p) - dependencySource.Add(p) - added++ + searchResults := l.Search(dep, false) + if searchResults != nil { + for _, p := range searchResults { + result.Add(p) + dependencySource.Add(p) + added++ + } } } } diff --git a/deb/list_test.go b/deb/list_test.go index 953f5e85..4fb8fb0a 100644 --- a/deb/list_test.go +++ b/deb/list_test.go @@ -7,6 +7,43 @@ import ( "strings" ) +type containsChecker struct { + *CheckerInfo +} + +func (c *containsChecker) Check(params []interface{}, names []string) (result bool, error string) { + var ( + pkgSlice1 []*Package + pkgSlice2 []*Package + ok bool + ) + + pkgMap := make (map[*Package]bool) + + + pkgSlice1, ok = params[0].([]*Package) + if !ok { + return false, "The first parameter is not a Package slice" + } + pkgSlice2, ok = params[1].([]*Package) + if !ok { + return false, "The second parameter is not a Package slice" + } + + for _, pkg := range pkgSlice2 { + pkgMap[pkg] = true + } + + for _, pkg := range pkgSlice1 { + if _, ok := pkgMap[pkg]; !ok { + return false, "" + } + } + return true, "" +} + +var Contains Checker = &containsChecker{&CheckerInfo{Name: "Contains", Params: []string{"Container", "Expected to contain"}}} + type PackageListSuite struct { // Simple list with "real" packages from stanzas list *PackageList @@ -14,8 +51,10 @@ type PackageListSuite struct { // Mocked packages in list packages []*Package + packages2 []*Package sourcePackages []*Package il *PackageList + il2 *PackageList } var _ = Suite(&PackageListSuite{}) @@ -60,6 +99,20 @@ func (s *PackageListSuite) SetUpTest(c *C) { } s.il.PrepareIndex() + s.il2 = NewPackageList() + s.packages2 = []*Package{ + &Package{Name: "mailer", Version: "3.5.8", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, + &Package{Name: "sendmail", Version: "1.0", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, + &Package{Name: "app", Version: "1.1-bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, + &Package{Name: "app", Version: "1.1-bp2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, + &Package{Name: "app", Version: "1.2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}}, + &Package{Name: "app", Version: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, + } + for _, p := range s.packages2 { + s.il2.Add(p) + } + s.il2.PrepareIndex() + s.sourcePackages = []*Package{ &Package{Name: "postfix", Version: "1.3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, &Package{Name: "app", Version: "1.1~bp1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, @@ -196,28 +249,54 @@ func (s *PackageListSuite) TestAppend(c *C) { } func (s *PackageListSuite) TestSearch(c *C) { - c.Check(func() { s.list.Search(Dependency{Architecture: "i386", Pkg: "app"}) }, Panics, "list not indexed, can't search") + //allMatches = False + c.Check(func() { s.list.Search(Dependency{Architecture: "i386", Pkg: "app"}, false) }, Panics, "list not indexed, can't search") - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app"}), Equals, s.packages[3]) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "mail-agent"}), Equals, s.packages[4]) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "puppy"}), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app"}, false), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "mail-agent"}, false), DeepEquals, []*Package{s.packages[4]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "puppy"}, false), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp1"}), Equals, s.packages[3]) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp2"}), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp2"}, false), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1"}), Equals, s.packages[3]) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1~~"}), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1"}, false), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1~~"}, false), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1"}), Equals, s.packages[3]) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~bp1"}), Equals, s.packages[3]) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~~"}), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1"}, false), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~~"}, false), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.0"}), Equals, s.packages[3]) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.2"}), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.0"}, false), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.2"}, false), IsNil) + + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}, false), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, false), IsNil) + + // allMatches = True + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "mail-agent"}, true), Contains, []*Package{s.packages2[0], s.packages2[1]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "puppy"}, true), IsNil) + + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) + + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "3"}, true), Contains, []*Package{s.packages2[5]}) + + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.2"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.1~"}, true), IsNil) + + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1-bp1"}, true), Contains, []*Package{s.packages2[2]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.0"}, true), IsNil) + + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "5.0"}, true), IsNil) + + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "5.0"}, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}), Equals, s.packages[3]) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}), Equals, s.packages[3]) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}), IsNil) } func (s *PackageListSuite) TestFilter(c *C) { diff --git a/deb/package.go b/deb/package.go index 8569ad43..4933a22e 100644 --- a/deb/package.go +++ b/deb/package.go @@ -198,7 +198,7 @@ func (p *Package) MatchesArchitecture(arch string) bool { } // MatchesDependency checks whether package matches specified dependency -func (p *Package) MatchesDependency(dep Dependency) bool { +func (p *Package) MatchesDependency(dep Dependency, allMatches bool) bool { if dep.Pkg != p.Name { return false } @@ -212,9 +212,15 @@ func (p *Package) MatchesDependency(dep Dependency) bool { } r := CompareVersions(p.Version, dep.Version) + switch dep.Relation { case VersionEqual: - return r == 0 + if allMatches { + rn := CompareVersions(p.Version, dep.NextVersion()) + return r+rn == 0 || r == 0 + } else { + return r == 0 + } case VersionLess: return r < 0 case VersionGreater: diff --git a/deb/package_test.go b/deb/package_test.go index 815da84d..3d37bd8e 100644 --- a/deb/package_test.go +++ b/deb/package_test.go @@ -172,38 +172,44 @@ func (s *PackageSuite) TestMatchesDependency(c *C) { p := NewPackageFromControlFile(s.stanza) // exact match - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionEqual, Version: "7.40-2"}), Equals, true) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionEqual, Version: "7.40-2"}, false), Equals, true) + + // exact match, same version, no revision specified + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionEqual, Version: "7.40"}, false), Equals, false) + + // non-exact match, same version, no revision specified + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionEqual, Version: "7.40"}, true), Equals, true) // different name - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena", Architecture: "i386", Relation: VersionEqual, Version: "7.40-2"}), Equals, false) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena", Architecture: "i386", Relation: VersionEqual, Version: "7.40-2"}, false), Equals, false) // different version - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionEqual, Version: "7.40-3"}), Equals, false) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionEqual, Version: "7.40-3"}, false), Equals, false) // different arch - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "amd64", Relation: VersionEqual, Version: "7.40-2"}), Equals, false) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "amd64", Relation: VersionEqual, Version: "7.40-2"}, false), Equals, false) // empty arch - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "", Relation: VersionEqual, Version: "7.40-2"}), Equals, true) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "", Relation: VersionEqual, Version: "7.40-2"}, false), Equals, true) // version don't care - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionDontCare, Version: ""}), Equals, true) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionDontCare, Version: ""}, false), Equals, true) // > - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreater, Version: "7.40-2"}), Equals, false) - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreater, Version: "7.40-1"}), Equals, true) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreater, Version: "7.40-2"}, false), Equals, false) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreater, Version: "7.40-1"}, false), Equals, true) // < - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLess, Version: "7.40-2"}), Equals, false) - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLess, Version: "7.40-3"}), Equals, true) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLess, Version: "7.40-2"}, false), Equals, false) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLess, Version: "7.40-3"}, false), Equals, true) // >= - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreaterOrEqual, Version: "7.40-2"}), Equals, true) - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreaterOrEqual, Version: "7.40-3"}), Equals, false) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreaterOrEqual, Version: "7.40-2"}, false), Equals, true) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreaterOrEqual, Version: "7.40-3"}, false), Equals, false) // <= - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLessOrEqual, Version: "7.40-2"}), Equals, true) - c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLessOrEqual, Version: "7.40-1"}), Equals, false) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLessOrEqual, Version: "7.40-2"}, false), Equals, true) + c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLessOrEqual, Version: "7.40-1"}, false), Equals, false) } func (s *PackageSuite) TestGetDependencies(c *C) { diff --git a/deb/version.go b/deb/version.go index e7b11584..6341bd73 100644 --- a/deb/version.go +++ b/deb/version.go @@ -188,6 +188,29 @@ type Dependency struct { Architecture string } +// NextVersion returns the next version of a dependency (eg. if d.Version = 1.9, it returns 1.10) +func (d *Dependency) NextVersion() string { + l := len(d.Version) + + if l == 0 { + return "" + } + + i := l + for i > 0 { + _, err := strconv.ParseUint(d.Version[i-1:l],10,0) + if err != nil { break } + i-- + } + + v, err := strconv.ParseUint(d.Version[i:l],10,0) + if err != nil { + return d.Version + } + + return d.Version[0:i] + strconv.Itoa(int(v)+1) +} + // Hash calculates some predefined unique ID of Dependency func (d *Dependency) Hash() string { return fmt.Sprintf("%s:%s:%d:%s", d.Architecture, d.Pkg, d.Relation, d.Version) diff --git a/deb/version_test.go b/deb/version_test.go index 53a47b95..923dc5f5 100644 --- a/deb/version_test.go +++ b/deb/version_test.go @@ -217,3 +217,45 @@ func (s *VersionSuite) TestDependencyString(c *C) { d.Architecture = "i386" c.Check(d.String(), Equals, "dpkg [i386]") } + +func (s *VersionSuite) TestDependencyNextVersion(c *C){ + d, _ := ParseDependency("dpkg(=1.7)") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "1.8") + + d, _ = ParseDependency("dpkg(=1.9)") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "1.10") + + d, _ = ParseDependency("dpkg(=9)") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "10") + + d, _ = ParseDependency("dpkg(=9.909)") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "9.910") + + d, _ = ParseDependency("dpkg(=9.0.9)") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "9.0.10") + + d, _ = ParseDependency("dpkg(=9.0.100)") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "9.0.101") + + d, _ = ParseDependency("dpkg(=9.0.0-209)") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "9.0.0-210") + + d, _ = ParseDependency("dpkg(=9.0.0-209rel)") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "9.0.0-209rel") + + d, _ = ParseDependency("dpkg(=9.0.0-rel219)") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "9.0.0-rel220") + + d, _ = ParseDependency("dpkg") + d.Architecture = "i386" + c.Check(d.NextVersion(), Equals, "") +}