gps/verify/locksat_test.go
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package verify
import (
"strings"
"testing"
"github.com/golang/dep/gps"
"github.com/golang/dep/gps/pkgtree"
)
type lockUnsatisfactionDimension uint8
const (
noLock lockUnsatisfactionDimension = 1 << iota
missingImports
excessImports
unmatchedOverrides
unmatchedConstraints
)
func (lsd lockUnsatisfactionDimension) String() string {
var parts []string
for i := uint(0); i < 5; i++ {
if lsd&(1<<i) != 0 {
switch lsd {
case noLock:
parts = append(parts, "no lock")
case missingImports:
parts = append(parts, "missing imports")
case excessImports:
parts = append(parts, "excess imports")
case unmatchedOverrides:
parts = append(parts, "unmatched overrides")
case unmatchedConstraints:
parts = append(parts, "unmatched constraints")
}
}
}
return strings.Join(parts, ", ")
}
func TestLockSatisfaction(t *testing.T) {
fooversion := gps.NewVersion("v1.0.0").Pair("foorev1")
bazversion := gps.NewVersion("v2.0.0").Pair("bazrev1")
transver := gps.NewVersion("v0.5.0").Pair("transrev1")
l := safeLock{
i: []string{"foo.com/bar", "baz.com/qux"},
p: []gps.LockedProject{
newVerifiableProject(mkPI("foo.com/bar"), fooversion, []string{".", "subpkg"}),
newVerifiableProject(mkPI("baz.com/qux"), bazversion, []string{".", "other"}),
newVerifiableProject(mkPI("transitive.com/dependency"), transver, []string{"."}),
},
}
ptree := pkgtree.PackageTree{
ImportRoot: "current",
Packages: map[string]pkgtree.PackageOrErr{
"current": {
P: pkgtree.Package{
Name: "current",
ImportPath: "current",
Imports: []string{"foo.com/bar"},
},
},
},
}
rm := simpleRootManifest{
req: map[string]bool{
"baz.com/qux": true,
},
}
var dup rootManifestTransformer = func(simpleRootManifest) simpleRootManifest {
return rm.dup()
}
tt := map[string]struct {
rmt rootManifestTransformer
sat lockUnsatisfactionDimension
checkfn func(*testing.T, LockSatisfaction)
}{
"ident": {
rmt: dup,
},
"added import": {
rmt: dup.addReq("fiz.com/wow"),
sat: missingImports,
},
"removed import": {
rmt: dup.rmReq("baz.com/qux"),
sat: excessImports,
},
"added and removed import": {
rmt: dup.rmReq("baz.com/qux").addReq("fiz.com/wow"),
sat: excessImports | missingImports,
checkfn: func(t *testing.T, lsat LockSatisfaction) {
if lsat.MissingImports[0] != "fiz.com/wow" {
t.Errorf("expected 'fiz.com/wow' as sole missing import, got %s", lsat.MissingImports)
}
if lsat.ExcessImports[0] != "baz.com/qux" {
t.Errorf("expected 'baz.com/qux' as sole excess import, got %s", lsat.ExcessImports)
}
},
},
"acceptable constraint": {
rmt: dup.setConstraint("baz.com/qux", bazversion.Unpair(), ""),
},
"unacceptable constraint": {
rmt: dup.setConstraint("baz.com/qux", fooversion.Unpair(), ""),
sat: unmatchedConstraints,
checkfn: func(t *testing.T, lsat LockSatisfaction) {
pr := gps.ProjectRoot("baz.com/qux")
unmet, has := lsat.UnmetConstraints[pr]
if !has {
t.Errorf("did not have constraint on expected project %q; map contents: %s", pr, lsat.UnmetConstraints)
}
if unmet.C != fooversion.Unpair() {
t.Errorf("wanted %s for unmet constraint, got %s", fooversion.Unpair(), unmet.C)
}
if unmet.V != bazversion {
t.Errorf("wanted %s for version that did not meet constraint, got %s", bazversion, unmet.V)
}
},
},
"acceptable override": {
rmt: dup.setOverride("baz.com/qux", bazversion.Unpair(), ""),
},
"unacceptable override": {
rmt: dup.setOverride("baz.com/qux", fooversion.Unpair(), ""),
sat: unmatchedOverrides,
},
"ineffectual constraint": {
rmt: dup.setConstraint("transitive.com/dependency", bazversion.Unpair(), ""),
},
"transitive override": {
rmt: dup.setOverride("transitive.com/dependency", bazversion.Unpair(), ""),
sat: unmatchedOverrides,
},
"ignores respected": {
rmt: dup.addIgnore("foo.com/bar"),
sat: excessImports,
},
}
for name, fix := range tt {
fix := fix
t.Run(name, func(t *testing.T) {
fixrm := fix.rmt(rm)
lsat := LockSatisfiesInputs(l, fixrm, ptree)
gotsat := lsat.unsatTypes()
if fix.sat & ^gotsat != 0 {
t.Errorf("wanted unsat in some dimensions that were satisfied: %s", fix.sat & ^gotsat)
}
if gotsat & ^fix.sat != 0 {
t.Errorf("wanted sat in some dimensions that were unsatisfied: %s", gotsat & ^fix.sat)
}
if lsat.Satisfied() && fix.sat != 0 {
t.Errorf("Satisfied() incorrectly reporting true when expecting some dimensions to be unsatisfied: %s", fix.sat)
} else if !lsat.Satisfied() && fix.sat == 0 {
t.Error("Satisfied() incorrectly reporting false when expecting all dimensions to be satisfied")
}
if fix.checkfn != nil {
fix.checkfn(t, lsat)
}
})
}
var lsat LockSatisfaction
if lsat.Satisfied() {
t.Error("zero value of LockSatisfaction should fail")
}
if LockSatisfiesInputs(nil, nil, ptree).Satisfied() {
t.Error("nil lock to LockSatisfiesInputs should produce failing result")
}
}
func (ls LockSatisfaction) unsatTypes() lockUnsatisfactionDimension {
var dims lockUnsatisfactionDimension
if !ls.LockExisted {
dims |= noLock
}
if len(ls.MissingImports) != 0 {
dims |= missingImports
}
if len(ls.ExcessImports) != 0 {
dims |= excessImports
}
if len(ls.UnmetOverrides) != 0 {
dims |= unmatchedOverrides
}
if len(ls.UnmetConstraints) != 0 {
dims |= unmatchedConstraints
}
return dims
}
type rootManifestTransformer func(simpleRootManifest) simpleRootManifest
func (rmt rootManifestTransformer) compose(rmt2 rootManifestTransformer) rootManifestTransformer {
if rmt == nil {
return rmt2
}
return func(rm simpleRootManifest) simpleRootManifest {
return rmt2(rmt(rm))
}
}
func (rmt rootManifestTransformer) addReq(path string) rootManifestTransformer {
return rmt.compose(func(rm simpleRootManifest) simpleRootManifest {
rm.req[path] = true
return rm
})
}
func (rmt rootManifestTransformer) rmReq(path string) rootManifestTransformer {
return rmt.compose(func(rm simpleRootManifest) simpleRootManifest {
delete(rm.req, path)
return rm
})
}
func (rmt rootManifestTransformer) setConstraint(pr string, c gps.Constraint, source string) rootManifestTransformer {
return rmt.compose(func(rm simpleRootManifest) simpleRootManifest {
rm.c[gps.ProjectRoot(pr)] = gps.ProjectProperties{
Constraint: c,
Source: source,
}
return rm
})
}
func (rmt rootManifestTransformer) setOverride(pr string, c gps.Constraint, source string) rootManifestTransformer {
return rmt.compose(func(rm simpleRootManifest) simpleRootManifest {
rm.ovr[gps.ProjectRoot(pr)] = gps.ProjectProperties{
Constraint: c,
Source: source,
}
return rm
})
}
func (rmt rootManifestTransformer) addIgnore(path string) rootManifestTransformer {
return rmt.compose(func(rm simpleRootManifest) simpleRootManifest {
rm.ig = pkgtree.NewIgnoredRuleset(append(rm.ig.ToSlice(), path))
return rm
})
}