github.com/gernest/nezuko@v0.1.2/internal/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 "reflect" 9 "strings" 10 "testing" 11 12 "github.com/gernest/nezuko/internal/module" 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 G1 32 upgrade A C4: A B1 C4 D4 E2 F1 G1 33 downgrade A2 D2: A2 C4 D2 34 35 name: trim 36 A: B1 C2 37 B1: D3 38 C2: B2 39 B2: 40 build A: A B2 C2 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 # Upgrade from B1 to B2 should drop the transitive dep on D. 160 name: drop 161 A: B1 C1 162 B1: D1 163 B2: 164 C2: 165 D2: 166 build A: A B1 C1 D1 167 upgrade* A: A B2 C2 168 169 name: simplify 170 A: B1 C1 171 B1: C2 172 C1: D1 173 C2: 174 build A: A B1 C2 175 176 name: up1 177 A: B1 C1 178 B1: 179 B2: 180 B3: 181 B4: 182 B5.hidden: 183 C2: 184 C3: 185 build A: A B1 C1 186 upgrade* A: A B4 C3 187 188 name: up2 189 A: B5.hidden C1 190 B1: 191 B2: 192 B3: 193 B4: 194 B5.hidden: 195 C2: 196 C3: 197 build A: A B5.hidden C1 198 upgrade* A: A B5.hidden C3 199 200 name: down1 201 A: B2 202 B1: C1 203 B2: C2 204 build A: A B2 C2 205 downgrade A C1: A B1 206 207 name: down2 208 A: B2 E2 209 B1: 210 B2: C2 F2 211 C1: 212 D1: 213 C2: D2 E2 214 D2: B2 215 E2: D2 216 E1: 217 F1: 218 downgrade A F1: A B1 E1 219 220 name: down3 221 A: 222 223 # golang.org/issue/25542. 224 name: noprev1 225 A: B4 C2 226 B2.hidden: 227 C2: 228 downgrade A B2.hidden: A B2.hidden C2 229 230 name: noprev2 231 A: B4 C2 232 B2.hidden: 233 B1: 234 C2: 235 downgrade A B2.hidden: A B2.hidden C2 236 237 name: noprev3 238 A: B4 C2 239 B3: 240 B2.hidden: 241 C2: 242 downgrade A B2.hidden: A B2.hidden C2 243 244 # Cycles involving the target. 245 246 # The target must be the newest version of itself. 247 name: cycle1 248 A: B1 249 B1: A1 250 B2: A2 251 B3: A3 252 build A: A B1 253 upgrade A B2: A B2 254 upgrade* A: A B3 255 256 # Requirements of older versions of the target 257 # must not be carried over. 258 name: cycle2 259 A: B1 260 A1: C1 261 A2: D1 262 B1: A1 263 B2: A2 264 C1: A2 265 C2: 266 D2: 267 build A: A B1 268 upgrade* A: A B2 269 270 # Requirement minimization. 271 272 name: req1 273 A: B1 C1 D1 E1 F1 274 B1: C1 E1 F1 275 req A: B1 D1 276 req A C: B1 C1 D1 277 278 name: req2 279 A: G1 H1 280 G1: H1 281 H1: G1 282 req A: G1 283 req A G: G1 284 req A H: H1 285 ` 286 287 func Test(t *testing.T) { 288 var ( 289 name string 290 reqs reqsMap 291 fns []func(*testing.T) 292 ) 293 flush := func() { 294 if name != "" { 295 t.Run(name, func(t *testing.T) { 296 for _, fn := range fns { 297 fn(t) 298 } 299 }) 300 } 301 } 302 m := func(s string) module.Version { 303 return module.Version{Path: s[:1], Version: s[1:]} 304 } 305 ms := func(list []string) []module.Version { 306 var mlist []module.Version 307 for _, s := range list { 308 mlist = append(mlist, m(s)) 309 } 310 return mlist 311 } 312 checkList := func(t *testing.T, desc string, list []module.Version, err error, val string) { 313 if err != nil { 314 t.Fatalf("%s: %v", desc, err) 315 } 316 vs := ms(strings.Fields(val)) 317 if !reflect.DeepEqual(list, vs) { 318 t.Errorf("%s = %v, want %v", desc, list, vs) 319 } 320 } 321 322 for _, line := range strings.Split(tests, "\n") { 323 line = strings.TrimSpace(line) 324 if strings.HasPrefix(line, "#") || line == "" { 325 continue 326 } 327 i := strings.Index(line, ":") 328 if i < 0 { 329 t.Fatalf("missing colon: %q", line) 330 } 331 key := strings.TrimSpace(line[:i]) 332 val := strings.TrimSpace(line[i+1:]) 333 if key == "" { 334 t.Fatalf("missing key: %q", line) 335 } 336 kf := strings.Fields(key) 337 switch kf[0] { 338 case "name": 339 if len(kf) != 1 { 340 t.Fatalf("name takes no arguments: %q", line) 341 } 342 flush() 343 reqs = make(reqsMap) 344 fns = nil 345 name = val 346 continue 347 case "build": 348 if len(kf) != 2 { 349 t.Fatalf("build takes one argument: %q", line) 350 } 351 fns = append(fns, func(t *testing.T) { 352 list, err := BuildList(m(kf[1]), reqs) 353 checkList(t, key, list, err, val) 354 }) 355 continue 356 case "upgrade*": 357 if len(kf) != 2 { 358 t.Fatalf("upgrade* takes one argument: %q", line) 359 } 360 fns = append(fns, func(t *testing.T) { 361 list, err := UpgradeAll(m(kf[1]), reqs) 362 checkList(t, key, list, err, val) 363 }) 364 continue 365 case "upgradereq": 366 if len(kf) < 2 { 367 t.Fatalf("upgrade takes at least one argument: %q", line) 368 } 369 fns = append(fns, func(t *testing.T) { 370 list, err := Upgrade(m(kf[1]), reqs, ms(kf[2:])...) 371 if err == nil { 372 list, err = Req(m(kf[1]), list, nil, reqs) 373 } 374 checkList(t, key, list, err, val) 375 }) 376 continue 377 case "upgrade": 378 if len(kf) < 2 { 379 t.Fatalf("upgrade takes at least one argument: %q", line) 380 } 381 fns = append(fns, func(t *testing.T) { 382 list, err := Upgrade(m(kf[1]), reqs, ms(kf[2:])...) 383 checkList(t, key, list, err, val) 384 }) 385 continue 386 case "downgrade": 387 if len(kf) < 2 { 388 t.Fatalf("downgrade takes at least one argument: %q", line) 389 } 390 fns = append(fns, func(t *testing.T) { 391 list, err := Downgrade(m(kf[1]), reqs, ms(kf[1:])...) 392 checkList(t, key, list, err, val) 393 }) 394 continue 395 case "req": 396 if len(kf) < 2 { 397 t.Fatalf("req takes at least one argument: %q", line) 398 } 399 fns = append(fns, func(t *testing.T) { 400 list, err := BuildList(m(kf[1]), reqs) 401 if err != nil { 402 t.Fatal(err) 403 } 404 list, err = Req(m(kf[1]), list, kf[2:], reqs) 405 checkList(t, key, list, err, val) 406 }) 407 continue 408 } 409 if len(kf) == 1 && 'A' <= key[0] && key[0] <= 'Z' { 410 var rs []module.Version 411 for _, f := range strings.Fields(val) { 412 r := m(f) 413 if reqs[r] == nil { 414 reqs[r] = []module.Version{} 415 } 416 rs = append(rs, r) 417 } 418 reqs[m(key)] = rs 419 continue 420 } 421 t.Fatalf("bad line: %q", line) 422 } 423 flush() 424 } 425 426 type reqsMap map[module.Version][]module.Version 427 428 func (r reqsMap) Max(v1, v2 string) string { 429 if v1 == "none" || v2 == "" { 430 return v2 431 } 432 if v2 == "none" || v1 == "" { 433 return v1 434 } 435 if v1 < v2 { 436 return v2 437 } 438 return v1 439 } 440 441 func (r reqsMap) Upgrade(m module.Version) (module.Version, error) { 442 var u module.Version 443 for k := range r { 444 if k.Path == m.Path && u.Version < k.Version && !strings.HasSuffix(k.Version, ".hidden") { 445 u = k 446 } 447 } 448 if u.Path == "" { 449 return module.Version{}, &MissingModuleError{module.Version{Path: m.Path, Version: ""}} 450 } 451 return u, nil 452 } 453 454 func (r reqsMap) Previous(m module.Version) (module.Version, error) { 455 var p module.Version 456 for k := range r { 457 if k.Path == m.Path && p.Version < k.Version && k.Version < m.Version && !strings.HasSuffix(k.Version, ".hidden") { 458 p = k 459 } 460 } 461 if p.Path == "" { 462 return module.Version{Path: m.Path, Version: "none"}, nil 463 } 464 return p, nil 465 } 466 467 func (r reqsMap) Required(m module.Version) ([]module.Version, error) { 468 rr, ok := r[m] 469 if !ok { 470 return nil, &MissingModuleError{m} 471 } 472 return rr, nil 473 }