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