cuelang.org/go@v0.10.1/internal/mod/mvs/mvs_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 mvs 6 7 import ( 8 "fmt" 9 "reflect" 10 "strings" 11 "testing" 12 ) 13 14 var tests = ` 15 # Scenario from blog. 16 name: blog 17 A: B1 C2 18 B1: D3 19 C1: D2 20 C2: D4 21 C3: D5 22 C4: G1 23 D2: E1 24 D3: E2 25 D4: E2 F1 26 D5: E2 27 G1: C4 28 A2: B1 C4 D4 29 build A: A B1 C2 D4 E2 F1 30 upgrade* A: A B1 C4 D5 E2 F1 G1 31 upgrade A C4: A B1 C4 D4 E2 F1 G1 32 build A2: A2 B1 C4 D4 E2 F1 G1 33 downgrade A2 D2: A2 C4 D2 E2 F1 G1 34 35 name: trim 36 A: B1 C2 37 B1: D3 38 C2: B2 39 B2: 40 build A: A B2 C2 D3 41 42 # Cross-dependency between D and E. 43 # No matter how it arises, should get result of merging all build lists via max, 44 # which leads to including both D2 and E2. 45 46 name: cross1 47 A: B C 48 B: D1 49 C: D2 50 D1: E2 51 D2: E1 52 build A: A B C D2 E2 53 54 name: cross1V 55 A: B2 C D2 E1 56 B1: 57 B2: D1 58 C: D2 59 D1: E2 60 D2: E1 61 build A: A B2 C D2 E2 62 63 name: cross1U 64 A: B1 C 65 B1: 66 B2: D1 67 C: D2 68 D1: E2 69 D2: E1 70 build A: A B1 C D2 E1 71 upgrade A B2: A B2 C D2 E2 72 73 name: cross1R 74 A: B C 75 B: D2 76 C: D1 77 D1: E2 78 D2: E1 79 build A: A B C D2 E2 80 81 name: cross1X 82 A: B C 83 B: D1 E2 84 C: D2 85 D1: E2 86 D2: E1 87 build A: A B C D2 E2 88 89 name: cross2 90 A: B D2 91 B: D1 92 D1: E2 93 D2: E1 94 build A: A B D2 E2 95 96 name: cross2X 97 A: B D2 98 B: D1 E2 99 C: D2 100 D1: E2 101 D2: E1 102 build A: A B D2 E2 103 104 name: cross3 105 A: B D2 E1 106 B: D1 107 D1: E2 108 D2: E1 109 build A: A B D2 E2 110 111 name: cross3X 112 A: B D2 E1 113 B: D1 E2 114 D1: E2 115 D2: E1 116 build A: A B D2 E2 117 118 # Should not get E2 here, because B has been updated 119 # not to depend on D1 anymore. 120 name: cross4 121 A1: B1 D2 122 A2: B2 D2 123 B1: D1 124 B2: D2 125 D1: E2 126 D2: E1 127 build A1: A1 B1 D2 E2 128 build A2: A2 B2 D2 E1 129 130 # But the upgrade from A1 preserves the E2 dep explicitly. 131 upgrade A1 B2: A1 B2 D2 E2 132 upgradereq A1 B2: B2 E2 133 134 name: cross5 135 A: D1 136 D1: E2 137 D2: E1 138 build A: A D1 E2 139 upgrade* A: A D2 E2 140 upgrade A D2: A D2 E2 141 upgradereq A D2: D2 E2 142 143 name: cross6 144 A: D2 145 D1: E2 146 D2: E1 147 build A: A D2 E1 148 upgrade* A: A D2 E2 149 upgrade A E2: A D2 E2 150 151 name: cross7 152 A: B C 153 B: D1 154 C: E1 155 D1: E2 156 E1: D2 157 build A: A B C D2 E2 158 159 # golang.org/issue/31248: 160 # Even though we select X2, the requirement on I1 161 # via X1 should be preserved. 162 name: cross8 163 M: A1 B1 164 A1: X1 165 B1: X2 166 X1: I1 167 X2: 168 build M: M A1 B1 I1 X2 169 170 # Upgrade from B1 to B2 should not drop the transitive dep on D. 171 name: drop 172 A: B1 C1 173 B1: D1 174 B2: 175 C2: 176 D2: 177 build A: A B1 C1 D1 178 upgrade* A: A B2 C2 D2 179 180 name: simplify 181 A: B1 C1 182 B1: C2 183 C1: D1 184 C2: 185 build A: A B1 C2 D1 186 187 name: up1 188 A: B1 C1 189 B1: 190 B2: 191 B3: 192 B4: 193 B5.hidden: 194 C2: 195 C3: 196 build A: A B1 C1 197 upgrade* A: A B4 C3 198 199 name: up2 200 A: B5.hidden C1 201 B1: 202 B2: 203 B3: 204 B4: 205 B5.hidden: 206 C2: 207 C3: 208 build A: A B5.hidden C1 209 upgrade* A: A B5.hidden C3 210 211 name: down1 212 A: B2 213 B1: C1 214 B2: C2 215 build A: A B2 C2 216 downgrade A C1: A B1 C1 217 218 name: down2 219 A: B2 E2 220 B1: 221 B2: C2 F2 222 C1: 223 D1: 224 C2: D2 E2 225 D2: B2 226 E2: D2 227 E1: 228 F1: 229 build A: A B2 C2 D2 E2 F2 230 downgrade A F1: A B1 C1 D1 E1 F1 231 232 # https://research.swtch.com/vgo-mvs#algorithm_4: 233 # “[D]owngrades are constrained to only downgrade packages, not also upgrade 234 # them; if an upgrade before downgrade is needed, the user must ask for it 235 # explicitly.” 236 # 237 # Here, downgrading B2 to B1 upgrades C1 to C2, and C2 does not depend on D2. 238 # However, C2 would be an upgrade — not a downgrade — so B1 must also be 239 # rejected. 240 name: downcross1 241 A: B2 C1 242 B1: C2 243 B2: C1 244 C1: D2 245 C2: 246 D1: 247 D2: 248 build A: A B2 C1 D2 249 downgrade A D1: A D1 250 251 # https://research.swtch.com/vgo-mvs#algorithm_4: 252 # “Unlike upgrades, downgrades must work by removing requirements, not adding 253 # them.” 254 # 255 # However, downgrading a requirement may introduce a new requirement on a 256 # previously-unrequired module. If each dependency's requirements are complete 257 # (“tidy”), that can't change the behavior of any other package whose version is 258 # not also being downgraded, so we should allow it. 259 name: downcross2 260 A: B2 261 B1: C1 262 B2: D2 263 C1: 264 D1: 265 D2: 266 build A: A B2 D2 267 downgrade A D1: A B1 C1 D1 268 269 name: downcycle 270 A: A B2 271 B2: A 272 B1: 273 build A: A B2 274 downgrade A B1: A B1 275 276 # Both B3 and C2 require D2. 277 # If we downgrade D to D1, then in isolation B3 would downgrade to B1, 278 # because B2 is hidden — B1 is the next-highest version that is not hidden. 279 # However, if we downgrade D, we will also downgrade C to C1. 280 # And C1 requires B2.hidden, and B2.hidden also meets our requirements: 281 # it is compatible with D1 and a strict downgrade from B3. 282 # 283 # Since neither the initial nor the final build list includes B1, 284 # and the nothing in the final downgraded build list requires E at all, 285 # no dependency on E1 (required by only B1) should be introduced. 286 # 287 name: downhiddenartifact 288 A: B3 C2 289 A1: B3 290 B1: E1 291 B2.hidden: 292 B3: D2 293 C1: B2.hidden 294 C2: D2 295 D1: 296 D2: 297 build A1: A1 B3 D2 298 downgrade A1 D1: A1 B1 D1 E1 299 build A: A B3 C2 D2 300 downgrade A D1: A B2.hidden C1 D1 301 302 # Both B3 and C3 require D2. 303 # If we downgrade D to D1, then in isolation B3 would downgrade to B1, 304 # and C3 would downgrade to C1. 305 # But C1 requires B2.hidden, and B1 requires C2.hidden, so we can't 306 # downgrade to either of those without pulling the other back up a little. 307 # 308 # B2.hidden and C2.hidden are both compatible with D1, so that still 309 # meets our requirements — but then we're in an odd state in which 310 # B and C have both been downgraded to hidden versions, without any 311 # remaining requirements to explain how those hidden versions got there. 312 # 313 # TODO(bcmills): Would it be better to force downgrades to land on non-hidden 314 # versions? 315 # In this case, that would remove the dependencies on B and C entirely. 316 # 317 name: downhiddencross 318 A: B3 C3 319 B1: C2.hidden 320 B2.hidden: 321 B3: D2 322 C1: B2.hidden 323 C2.hidden: 324 C3: D2 325 D1: 326 D2: 327 build A: A B3 C3 D2 328 downgrade A D1: A B2.hidden C2.hidden D1 329 330 # golang.org/issue/25542. 331 name: noprev1 332 A: B4 C2 333 B2.hidden: 334 C2: 335 build A: A B4 C2 336 downgrade A B2.hidden: A B2.hidden C2 337 338 name: noprev2 339 A: B4 C2 340 B2.hidden: 341 B1: 342 C2: 343 build A: A B4 C2 344 downgrade A B2.hidden: A B2.hidden C2 345 346 name: noprev3 347 A: B4 C2 348 B3: 349 B2.hidden: 350 C2: 351 build A: A B4 C2 352 downgrade A B2.hidden: A B2.hidden C2 353 354 # Cycles involving the target. 355 356 # The target must be the newest version of itself. 357 name: cycle1 358 A: B1 359 B1: A1 360 B2: A2 361 B3: A3 362 build A: A B1 363 upgrade A B2: A B2 364 upgrade* A: A B3 365 366 # golang.org/issue/29773: 367 # Requirements of older versions of the target 368 # must be carried over. 369 name: cycle2 370 A: B1 371 A1: C1 372 A2: D1 373 B1: A1 374 B2: A2 375 C1: A2 376 C2: 377 D2: 378 build A: A B1 C1 D1 379 upgrade* A: A B2 C2 D2 380 381 # Cycles with multiple possible solutions. 382 # (golang.org/issue/34086) 383 name: cycle3 384 M: A1 C2 385 A1: B1 386 B1: C1 387 B2: C2 388 C1: 389 C2: B2 390 build M: M A1 B2 C2 391 req M: A1 B2 392 req M A: A1 B2 393 req M C: A1 C2 394 395 # Requirement minimization. 396 397 name: req1 398 A: B1 C1 D1 E1 F1 399 B1: C1 E1 F1 400 req A: B1 D1 401 req A C: B1 C1 D1 402 403 name: req2 404 A: G1 H1 405 G1: H1 406 H1: G1 407 req A: G1 408 req A G: G1 409 req A H: H1 410 411 name: req3 412 M: A1 B1 413 A1: X1 414 B1: X2 415 X1: I1 416 X2: 417 req M: A1 B1 418 419 name: reqnone 420 M: Anone B1 D1 E1 421 B1: Cnone D1 422 E1: Fnone 423 build M: M B1 D1 E1 424 req M: B1 E1 425 426 name: reqdup 427 M: A1 B1 428 A1: B1 429 B1: 430 req M A A: A1 431 432 name: reqcross 433 M: A1 B1 C1 434 A1: B1 C1 435 B1: C1 436 C1: 437 req M A B: A1 B1 438 ` 439 440 func Test(t *testing.T) { 441 var ( 442 name string 443 reqs reqsMap 444 fns []func(*testing.T) 445 ) 446 flush := func() { 447 if name != "" { 448 t.Run(name, func(t *testing.T) { 449 for _, fn := range fns { 450 fn(t) 451 } 452 if len(fns) == 0 { 453 t.Errorf("no functions tested") 454 } 455 }) 456 } 457 } 458 m := func(s string) version { 459 return version{s[:1], s[1:]} 460 } 461 ms := func(list []string) []version { 462 var mlist []version 463 for _, s := range list { 464 mlist = append(mlist, m(s)) 465 } 466 return mlist 467 } 468 checkList := func(t *testing.T, desc string, list []version, err error, val string) { 469 if err != nil { 470 t.Fatalf("%s: %v", desc, err) 471 } 472 vs := ms(strings.Fields(val)) 473 if !reflect.DeepEqual(list, vs) { 474 t.Errorf("%s = %v, want %v", desc, list, vs) 475 } 476 } 477 478 for _, line := range strings.Split(tests, "\n") { 479 line = strings.TrimSpace(line) 480 if strings.HasPrefix(line, "#") || line == "" { 481 continue 482 } 483 i := strings.Index(line, ":") 484 if i < 0 { 485 t.Fatalf("missing colon: %q", line) 486 } 487 key := strings.TrimSpace(line[:i]) 488 val := strings.TrimSpace(line[i+1:]) 489 if key == "" { 490 t.Fatalf("missing key: %q", line) 491 } 492 kf := strings.Fields(key) 493 switch kf[0] { 494 case "name": 495 if len(kf) != 1 { 496 t.Fatalf("name takes no arguments: %q", line) 497 } 498 flush() 499 reqs = make(reqsMap) 500 fns = nil 501 name = val 502 continue 503 case "build": 504 if len(kf) != 2 { 505 t.Fatalf("build takes one argument: %q", line) 506 } 507 fns = append(fns, func(t *testing.T) { 508 list, err := BuildList([]version{m(kf[1])}, reqs) 509 checkList(t, key, list, err, val) 510 }) 511 continue 512 case "upgrade*": 513 if len(kf) != 2 { 514 t.Fatalf("upgrade* takes one argument: %q", line) 515 } 516 fns = append(fns, func(t *testing.T) { 517 list, err := UpgradeAll(m(kf[1]), reqs) 518 checkList(t, key, list, err, val) 519 }) 520 continue 521 case "upgradereq": 522 if len(kf) < 2 { 523 t.Fatalf("upgrade takes at least one argument: %q", line) 524 } 525 fns = append(fns, func(t *testing.T) { 526 list, err := Upgrade(m(kf[1]), reqs, ms(kf[2:])...) 527 if err == nil { 528 // Copy the reqs map, but substitute the upgraded requirements in 529 // place of the target's original requirements. 530 upReqs := make(reqsMap, len(reqs)) 531 for m, r := range reqs { 532 upReqs[m] = r 533 } 534 upReqs[m(kf[1])] = list 535 536 list, err = Req(m(kf[1]), nil, upReqs) 537 } 538 checkList(t, key, list, err, val) 539 }) 540 continue 541 case "upgrade": 542 if len(kf) < 2 { 543 t.Fatalf("upgrade takes at least one argument: %q", line) 544 } 545 fns = append(fns, func(t *testing.T) { 546 list, err := Upgrade(m(kf[1]), reqs, ms(kf[2:])...) 547 checkList(t, key, list, err, val) 548 }) 549 continue 550 case "downgrade": 551 if len(kf) < 2 { 552 t.Fatalf("downgrade takes at least one argument: %q", line) 553 } 554 fns = append(fns, func(t *testing.T) { 555 list, err := Downgrade(m(kf[1]), reqs, ms(kf[1:])...) 556 checkList(t, key, list, err, val) 557 }) 558 continue 559 case "req": 560 if len(kf) < 2 { 561 t.Fatalf("req takes at least one argument: %q", line) 562 } 563 fns = append(fns, func(t *testing.T) { 564 list, err := Req(m(kf[1]), kf[2:], reqs) 565 checkList(t, key, list, err, val) 566 }) 567 continue 568 } 569 if len(kf) == 1 && 'A' <= key[0] && key[0] <= 'Z' { 570 var rs []version 571 for _, f := range strings.Fields(val) { 572 r := m(f) 573 if reqs[r] == nil { 574 reqs[r] = []version{} 575 } 576 rs = append(rs, r) 577 } 578 reqs[m(key)] = rs 579 continue 580 } 581 t.Fatalf("bad line: %q", line) 582 } 583 flush() 584 } 585 586 type reqsMap map[version][]version 587 588 func (r reqsMap) Path(v version) string { 589 return v.path 590 } 591 592 func (r reqsMap) Version(v version) string { 593 return v.version 594 } 595 596 func (r reqsMap) New(p, v string) (version, error) { 597 return version{p, v}, nil 598 } 599 600 func (r reqsMap) Max(v1, v2 string) string { 601 if v1 == "none" || v2 == "" { 602 return v2 603 } 604 if v2 == "none" || v1 == "" { 605 return v1 606 } 607 if v1 < v2 { 608 return v2 609 } 610 return v1 611 } 612 613 func (r reqsMap) Upgrade(m version) (version, error) { 614 u := version{"", "none"} 615 for k := range r { 616 if k.path == m.path && r.Max(u.version, k.version) == k.version && !strings.HasSuffix(k.version, ".hidden") { 617 u = k 618 } 619 } 620 if u.path == "" { 621 return version{}, fmt.Errorf("missing module: %v", m.path) 622 } 623 return u, nil 624 } 625 626 func (r reqsMap) Previous(m version) (version, error) { 627 var p version 628 for k := range r { 629 if k.path == m.path && p.version < k.version && k.version < m.version && !strings.HasSuffix(k.version, ".hidden") { 630 p = k 631 } 632 } 633 if p.path == "" { 634 return version{m.path, "none"}, nil 635 } 636 return p, nil 637 } 638 639 func (r reqsMap) Required(m version) ([]version, error) { 640 rr, ok := r[m] 641 if !ok { 642 return nil, fmt.Errorf("missing module: %v", m) 643 } 644 return rr, nil 645 } 646 647 type version struct { 648 path string 649 version string 650 }