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  }