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 }