github.com/golang/dep@v0.5.4/gps/verify/lockdiff_test.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package verify
     6  
     7  import (
     8  	"fmt"
     9  	"math/bits"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/golang/dep/gps"
    14  )
    15  
    16  func contains(haystack []string, needle string) bool {
    17  	for _, str := range haystack {
    18  		if str == needle {
    19  			return true
    20  		}
    21  	}
    22  	return false
    23  }
    24  
    25  func (dd DeltaDimension) String() string {
    26  	var parts []string
    27  
    28  	for dd != 0 {
    29  		index := bits.TrailingZeros32(uint32(dd))
    30  		dd &= ^(1 << uint(index))
    31  
    32  		switch DeltaDimension(1 << uint(index)) {
    33  		case InputImportsChanged:
    34  			parts = append(parts, "input imports")
    35  		case ProjectAdded:
    36  			parts = append(parts, "project added")
    37  		case ProjectRemoved:
    38  			parts = append(parts, "project removed")
    39  		case SourceChanged:
    40  			parts = append(parts, "source changed")
    41  		case VersionChanged:
    42  			parts = append(parts, "version changed")
    43  		case RevisionChanged:
    44  			parts = append(parts, "revision changed")
    45  		case PackagesChanged:
    46  			parts = append(parts, "packages changed")
    47  		case PruneOptsChanged:
    48  			parts = append(parts, "pruneopts changed")
    49  		case HashVersionChanged:
    50  			parts = append(parts, "hash version changed")
    51  		case HashChanged:
    52  			parts = append(parts, "hash digest changed")
    53  		}
    54  	}
    55  
    56  	return strings.Join(parts, ", ")
    57  }
    58  
    59  func TestLockDelta(t *testing.T) {
    60  	fooversion := gps.NewVersion("v1.0.0").Pair("foorev1")
    61  	bazversion := gps.NewVersion("v2.0.0").Pair("bazrev1")
    62  	transver := gps.NewVersion("v0.5.0").Pair("transrev1")
    63  	l := safeLock{
    64  		i: []string{"foo.com/bar", "baz.com/qux"},
    65  		p: []gps.LockedProject{
    66  			newVerifiableProject(mkPI("foo.com/bar"), fooversion, []string{".", "subpkg"}),
    67  			newVerifiableProject(mkPI("baz.com/qux"), bazversion, []string{".", "other"}),
    68  			newVerifiableProject(mkPI("transitive.com/dependency"), transver, []string{"."}),
    69  		},
    70  	}
    71  
    72  	var dup lockTransformer = func(l safeLock) safeLock {
    73  		return l.dup()
    74  	}
    75  
    76  	tt := map[string]struct {
    77  		lt      lockTransformer
    78  		delta   DeltaDimension
    79  		checkfn func(*testing.T, LockDelta)
    80  	}{
    81  		"ident": {
    82  			lt: dup,
    83  		},
    84  		"added import": {
    85  			lt:    dup.addII("other.org"),
    86  			delta: InputImportsChanged,
    87  		},
    88  		"added import 2x": {
    89  			lt:    dup.addII("other.org").addII("andsomethingelse.com/wowie"),
    90  			delta: InputImportsChanged,
    91  			checkfn: func(t *testing.T, ld LockDelta) {
    92  				if !contains(ld.AddedImportInputs, "other.org") {
    93  					t.Error("first added input import missing")
    94  				}
    95  				if !contains(ld.AddedImportInputs, "andsomethingelse.com/wowie") {
    96  					t.Error("first added input import missing")
    97  				}
    98  			},
    99  		},
   100  		"removed import": {
   101  			lt:    dup.rmII("baz.com/qux"),
   102  			delta: InputImportsChanged,
   103  			checkfn: func(t *testing.T, ld LockDelta) {
   104  				if !contains(ld.RemovedImportInputs, "baz.com/qux") {
   105  					t.Error("removed input import missing")
   106  				}
   107  			},
   108  		},
   109  		"add project": {
   110  			lt:    dup.addDumbProject("madeup.org"),
   111  			delta: ProjectAdded,
   112  		},
   113  		"remove project": {
   114  			lt:    dup.rmProject("foo.com/bar"),
   115  			delta: ProjectRemoved,
   116  		},
   117  		"remove last project": {
   118  			lt:    dup.rmProject("transitive.com/dependency"),
   119  			delta: ProjectRemoved,
   120  		},
   121  		"all": {
   122  			lt:    dup.addII("other.org").rmII("baz.com/qux").addDumbProject("zebrafun.org").rmProject("foo.com/bar"),
   123  			delta: InputImportsChanged | ProjectRemoved | ProjectAdded,
   124  		},
   125  		"remove all projects and imports": {
   126  			lt:    dup.rmII("baz.com/qux").rmII("foo.com/bar").rmProject("baz.com/qux").rmProject("foo.com/bar").rmProject("transitive.com/dependency"),
   127  			delta: InputImportsChanged | ProjectRemoved,
   128  		},
   129  	}
   130  
   131  	for name, fix := range tt {
   132  		fix := fix
   133  		t.Run(name, func(t *testing.T) {
   134  			fixl := fix.lt(l)
   135  			ld := DiffLocks(l, fixl)
   136  
   137  			if !ld.Changed(AnyChanged) && fix.delta != 0 {
   138  				t.Errorf("Changed() reported false when expecting some dimensions to be changed: %s", fix.delta)
   139  			} else if ld.Changed(AnyChanged) && fix.delta == 0 {
   140  				t.Error("Changed() reported true when expecting no changes")
   141  			}
   142  			if ld.Changed(AnyChanged & ^fix.delta) {
   143  				t.Errorf("Changed() reported true when checking along not-expected dimensions: %s", ld.Changes() & ^fix.delta)
   144  			}
   145  
   146  			gotdelta := ld.Changes()
   147  			if fix.delta & ^gotdelta != 0 {
   148  				t.Errorf("wanted change in some dimensions that were unchanged: %s", fix.delta & ^gotdelta)
   149  			}
   150  			if gotdelta & ^fix.delta != 0 {
   151  				t.Errorf("did not want change in some dimensions that were changed: %s", gotdelta & ^fix.delta)
   152  			}
   153  
   154  			if fix.checkfn != nil {
   155  				fix.checkfn(t, ld)
   156  			}
   157  		})
   158  	}
   159  }
   160  
   161  func TestLockedProjectPropertiesDelta(t *testing.T) {
   162  	fooversion, foorev := gps.NewVersion("v1.0.0"), gps.Revision("foorev1")
   163  	foopair := fooversion.Pair(foorev)
   164  	foovp := VerifiableProject{
   165  		LockedProject: gps.NewLockedProject(mkPI("foo.com/project"), foopair, []string{".", "subpkg"}),
   166  		PruneOpts:     gps.PruneNestedVendorDirs,
   167  		Digest: VersionedDigest{
   168  			HashVersion: HashVersion,
   169  			Digest:      []byte("foobytes"),
   170  		},
   171  	}
   172  	var dup lockedProjectTransformer = func(lp gps.LockedProject) gps.LockedProject {
   173  		return lp.(VerifiableProject).dup()
   174  	}
   175  
   176  	tt := map[string]struct {
   177  		lt1, lt2 lockedProjectTransformer
   178  		delta    DeltaDimension
   179  		checkfn  func(*testing.T, LockedProjectPropertiesDelta)
   180  	}{
   181  		"ident": {
   182  			lt1: dup,
   183  		},
   184  		"add pkg": {
   185  			lt1:   dup.addPkg("whatev"),
   186  			delta: PackagesChanged,
   187  		},
   188  		"rm pkg": {
   189  			lt1:   dup.rmPkg("subpkg"),
   190  			delta: PackagesChanged,
   191  		},
   192  		"add and rm pkg": {
   193  			lt1:   dup.rmPkg("subpkg").addPkg("whatev"),
   194  			delta: PackagesChanged,
   195  			checkfn: func(t *testing.T, ld LockedProjectPropertiesDelta) {
   196  				if !contains(ld.PackagesAdded, "whatev") {
   197  					t.Error("added pkg missing from list")
   198  				}
   199  				if !contains(ld.PackagesRemoved, "subpkg") {
   200  					t.Error("removed pkg missing from list")
   201  				}
   202  			},
   203  		},
   204  		"add source": {
   205  			lt1:   dup.setSource("somethingelse"),
   206  			delta: SourceChanged,
   207  		},
   208  		"remove source": {
   209  			lt1:   dup.setSource("somethingelse"),
   210  			lt2:   dup,
   211  			delta: SourceChanged,
   212  		},
   213  		"to rev only": {
   214  			lt1:   dup.setVersion(foorev),
   215  			delta: VersionChanged,
   216  		},
   217  		"from rev only": {
   218  			lt1:   dup.setVersion(foorev),
   219  			lt2:   dup,
   220  			delta: VersionChanged,
   221  		},
   222  		"to new rev only": {
   223  			lt1:   dup.setVersion(gps.Revision("newrev")),
   224  			delta: VersionChanged | RevisionChanged,
   225  		},
   226  		"from new rev only": {
   227  			lt1:   dup.setVersion(gps.Revision("newrev")),
   228  			lt2:   dup,
   229  			delta: VersionChanged | RevisionChanged,
   230  		},
   231  		"version change": {
   232  			lt1:   dup.setVersion(gps.NewVersion("v0.5.0").Pair(foorev)),
   233  			delta: VersionChanged,
   234  		},
   235  		"version change to norev": {
   236  			lt1:   dup.setVersion(gps.NewVersion("v0.5.0")),
   237  			delta: VersionChanged | RevisionChanged,
   238  		},
   239  		"version change from norev": {
   240  			lt1:   dup.setVersion(gps.NewVersion("v0.5.0")),
   241  			lt2:   dup.setVersion(gps.NewVersion("v0.5.0").Pair(foorev)),
   242  			delta: RevisionChanged,
   243  		},
   244  		"to branch": {
   245  			lt1:   dup.setVersion(gps.NewBranch("master").Pair(foorev)),
   246  			delta: VersionChanged,
   247  		},
   248  		"to branch new rev": {
   249  			lt1:   dup.setVersion(gps.NewBranch("master").Pair(gps.Revision("newrev"))),
   250  			delta: VersionChanged | RevisionChanged,
   251  		},
   252  		"to empty prune opts": {
   253  			lt1:   dup.setPruneOpts(0),
   254  			delta: PruneOptsChanged,
   255  		},
   256  		"from empty prune opts": {
   257  			lt1:   dup.setPruneOpts(0),
   258  			lt2:   dup,
   259  			delta: PruneOptsChanged,
   260  		},
   261  		"prune opts change": {
   262  			lt1:   dup.setPruneOpts(gps.PruneNestedVendorDirs | gps.PruneNonGoFiles),
   263  			delta: PruneOptsChanged,
   264  		},
   265  		"empty digest": {
   266  			lt1:   dup.setDigest(VersionedDigest{}),
   267  			delta: HashVersionChanged | HashChanged,
   268  		},
   269  		"to empty digest": {
   270  			lt1:   dup.setDigest(VersionedDigest{}),
   271  			lt2:   dup,
   272  			delta: HashVersionChanged | HashChanged,
   273  		},
   274  		"hash version changed": {
   275  			lt1:   dup.setDigest(VersionedDigest{HashVersion: HashVersion + 1, Digest: []byte("foobytes")}),
   276  			delta: HashVersionChanged,
   277  		},
   278  		"hash contents changed": {
   279  			lt1:   dup.setDigest(VersionedDigest{HashVersion: HashVersion, Digest: []byte("barbytes")}),
   280  			delta: HashChanged,
   281  		},
   282  		"to plain locked project": {
   283  			lt1:   dup.toPlainLP(),
   284  			delta: PruneOptsChanged | HashChanged | HashVersionChanged,
   285  		},
   286  		"from plain locked project": {
   287  			lt1:   dup.toPlainLP(),
   288  			lt2:   dup,
   289  			delta: PruneOptsChanged | HashChanged | HashVersionChanged,
   290  		},
   291  		"all": {
   292  			lt1:   dup.setDigest(VersionedDigest{}).setVersion(gps.NewBranch("master").Pair(gps.Revision("newrev"))).setPruneOpts(gps.PruneNestedVendorDirs | gps.PruneNonGoFiles).setSource("whatever"),
   293  			delta: SourceChanged | VersionChanged | RevisionChanged | PruneOptsChanged | HashChanged | HashVersionChanged,
   294  		},
   295  	}
   296  
   297  	for name, fix := range tt {
   298  		fix := fix
   299  		t.Run(name, func(t *testing.T) {
   300  			// Use two patterns for constructing locks to compare: if only lt1
   301  			// is set, use foovp as the first lp and compare with the lt1
   302  			// transforms applied. If lt2 is set, transform foovp with lt1 for
   303  			// the first lp, then transform foovp with lt2 for the second lp.
   304  			var lp1, lp2 gps.LockedProject
   305  			if fix.lt2 == nil {
   306  				lp1 = foovp
   307  				lp2 = fix.lt1(foovp)
   308  			} else {
   309  				lp1 = fix.lt1(foovp)
   310  				lp2 = fix.lt2(foovp)
   311  			}
   312  
   313  			lppd := DiffLockedProjectProperties(lp1, lp2)
   314  			if !lppd.Changed(AnyChanged) && fix.delta != 0 {
   315  				t.Errorf("Changed() reporting false when expecting some dimensions to be changed: %s", fix.delta)
   316  			} else if lppd.Changed(AnyChanged) && fix.delta == 0 {
   317  				t.Error("Changed() reporting true when expecting no changes")
   318  			}
   319  			if lppd.Changed(AnyChanged & ^fix.delta) {
   320  				t.Errorf("Changed() reported true when checking along not-expected dimensions: %s", lppd.Changes() & ^fix.delta)
   321  			}
   322  
   323  			gotdelta := lppd.Changes()
   324  			if fix.delta & ^gotdelta != 0 {
   325  				t.Errorf("wanted change in some dimensions that were unchanged: %s", fix.delta & ^gotdelta)
   326  			}
   327  			if gotdelta & ^fix.delta != 0 {
   328  				t.Errorf("did not want change in some dimensions that were changed: %s", gotdelta & ^fix.delta)
   329  			}
   330  
   331  			if fix.checkfn != nil {
   332  				fix.checkfn(t, lppd)
   333  			}
   334  		})
   335  	}
   336  }
   337  
   338  type lockTransformer func(safeLock) safeLock
   339  
   340  func (lt lockTransformer) compose(lt2 lockTransformer) lockTransformer {
   341  	if lt == nil {
   342  		return lt2
   343  	}
   344  	return func(l safeLock) safeLock {
   345  		return lt2(lt(l))
   346  	}
   347  }
   348  
   349  func (lt lockTransformer) addDumbProject(root string) lockTransformer {
   350  	vp := newVerifiableProject(mkPI(root), gps.NewVersion("whatever").Pair("addedrev"), []string{"."})
   351  	return lt.compose(func(l safeLock) safeLock {
   352  		for _, lp := range l.p {
   353  			if lp.Ident().ProjectRoot == vp.Ident().ProjectRoot {
   354  				panic(fmt.Sprintf("%q already in lock", vp.Ident().ProjectRoot))
   355  			}
   356  		}
   357  		l.p = append(l.p, vp)
   358  		return l
   359  	})
   360  }
   361  
   362  func (lt lockTransformer) rmProject(pr string) lockTransformer {
   363  	return lt.compose(func(l safeLock) safeLock {
   364  		for k, lp := range l.p {
   365  			if lp.Ident().ProjectRoot == gps.ProjectRoot(pr) {
   366  				l.p = l.p[:k+copy(l.p[k:], l.p[k+1:])]
   367  				return l
   368  			}
   369  		}
   370  		panic(fmt.Sprintf("%q not in lock", pr))
   371  	})
   372  }
   373  
   374  func (lt lockTransformer) addII(path string) lockTransformer {
   375  	return lt.compose(func(l safeLock) safeLock {
   376  		for _, impath := range l.i {
   377  			if path == impath {
   378  				panic(fmt.Sprintf("%q already in input imports", impath))
   379  			}
   380  		}
   381  		l.i = append(l.i, path)
   382  		return l
   383  	})
   384  }
   385  
   386  func (lt lockTransformer) rmII(path string) lockTransformer {
   387  	return lt.compose(func(l safeLock) safeLock {
   388  		for k, impath := range l.i {
   389  			if path == impath {
   390  				l.i = l.i[:k+copy(l.i[k:], l.i[k+1:])]
   391  				return l
   392  			}
   393  		}
   394  		panic(fmt.Sprintf("%q not in input imports", path))
   395  	})
   396  }
   397  
   398  type lockedProjectTransformer func(gps.LockedProject) gps.LockedProject
   399  
   400  func (lpt lockedProjectTransformer) compose(lpt2 lockedProjectTransformer) lockedProjectTransformer {
   401  	if lpt == nil {
   402  		return lpt2
   403  	}
   404  	return func(lp gps.LockedProject) gps.LockedProject {
   405  		return lpt2(lpt(lp))
   406  	}
   407  }
   408  
   409  func (lpt lockedProjectTransformer) addPkg(path string) lockedProjectTransformer {
   410  	return lpt.compose(func(lp gps.LockedProject) gps.LockedProject {
   411  		for _, pkg := range lp.Packages() {
   412  			if path == pkg {
   413  				panic(fmt.Sprintf("%q already in pkg list", path))
   414  			}
   415  		}
   416  
   417  		nlp := gps.NewLockedProject(lp.Ident(), lp.Version(), append(lp.Packages(), path))
   418  		if vp, ok := lp.(VerifiableProject); ok {
   419  			vp.LockedProject = nlp
   420  			return vp
   421  		}
   422  		return nlp
   423  	})
   424  }
   425  
   426  func (lpt lockedProjectTransformer) rmPkg(path string) lockedProjectTransformer {
   427  	return lpt.compose(func(lp gps.LockedProject) gps.LockedProject {
   428  		pkglist := lp.Packages()
   429  		for k, pkg := range pkglist {
   430  			if path == pkg {
   431  				pkglist = pkglist[:k+copy(pkglist[k:], pkglist[k+1:])]
   432  				nlp := gps.NewLockedProject(lp.Ident(), lp.Version(), pkglist)
   433  				if vp, ok := lp.(VerifiableProject); ok {
   434  					vp.LockedProject = nlp
   435  					return vp
   436  				}
   437  				return nlp
   438  			}
   439  		}
   440  		panic(fmt.Sprintf("%q not in pkg list", path))
   441  	})
   442  }
   443  
   444  func (lpt lockedProjectTransformer) setSource(source string) lockedProjectTransformer {
   445  	return lpt.compose(func(lp gps.LockedProject) gps.LockedProject {
   446  		ident := lp.Ident()
   447  		ident.Source = source
   448  		nlp := gps.NewLockedProject(ident, lp.Version(), lp.Packages())
   449  		if vp, ok := lp.(VerifiableProject); ok {
   450  			vp.LockedProject = nlp
   451  			return vp
   452  		}
   453  		return nlp
   454  	})
   455  }
   456  
   457  func (lpt lockedProjectTransformer) setVersion(v gps.Version) lockedProjectTransformer {
   458  	return lpt.compose(func(lp gps.LockedProject) gps.LockedProject {
   459  		nlp := gps.NewLockedProject(lp.Ident(), v, lp.Packages())
   460  		if vp, ok := lp.(VerifiableProject); ok {
   461  			vp.LockedProject = nlp
   462  			return vp
   463  		}
   464  		return nlp
   465  	})
   466  }
   467  
   468  func (lpt lockedProjectTransformer) setPruneOpts(po gps.PruneOptions) lockedProjectTransformer {
   469  	return lpt.compose(func(lp gps.LockedProject) gps.LockedProject {
   470  		vp := lp.(VerifiableProject)
   471  		vp.PruneOpts = po
   472  		return vp
   473  	})
   474  }
   475  
   476  func (lpt lockedProjectTransformer) setDigest(vd VersionedDigest) lockedProjectTransformer {
   477  	return lpt.compose(func(lp gps.LockedProject) gps.LockedProject {
   478  		vp := lp.(VerifiableProject)
   479  		vp.Digest = vd
   480  		return vp
   481  	})
   482  }
   483  
   484  func (lpt lockedProjectTransformer) toPlainLP() lockedProjectTransformer {
   485  	return lpt.compose(func(lp gps.LockedProject) gps.LockedProject {
   486  		if vp, ok := lp.(VerifiableProject); ok {
   487  			return vp.LockedProject
   488  		}
   489  		return lp
   490  	})
   491  }