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  }