github.com/golang/dep@v0.5.4/gps/solve_basic_test.go (about)

     1  // Copyright 2017 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 gps
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"net/url"
    11  	"regexp"
    12  	"strings"
    13  
    14  	"github.com/Masterminds/semver"
    15  	"github.com/golang/dep/gps/pkgtree"
    16  )
    17  
    18  var regfrom = regexp.MustCompile(`^(\w*) from (\w*) ([0-9\.\*]*)`)
    19  
    20  // nvSplit splits an "info" string on " " into the pair of name and
    21  // version/constraint, and returns each individually.
    22  //
    23  // This is for narrow use - panics if there are less than two resulting items in
    24  // the slice.
    25  func nvSplit(info string) (id ProjectIdentifier, version string) {
    26  	if strings.Contains(info, " from ") {
    27  		parts := regfrom.FindStringSubmatch(info)
    28  		info = parts[1] + " " + parts[3]
    29  		id.Source = parts[2]
    30  	}
    31  
    32  	s := strings.SplitN(info, " ", 2)
    33  	if len(s) < 2 {
    34  		panic(fmt.Sprintf("Malformed name/version info string '%s'", info))
    35  	}
    36  
    37  	id.ProjectRoot, version = ProjectRoot(s[0]), s[1]
    38  	return
    39  }
    40  
    41  // nvrSplit splits an "info" string on " " into the triplet of name,
    42  // version/constraint, and revision, and returns each individually.
    43  //
    44  // It will work fine if only name and version/constraint are provided.
    45  //
    46  // This is for narrow use - panics if there are less than two resulting items in
    47  // the slice.
    48  func nvrSplit(info string) (id ProjectIdentifier, version string, revision Revision) {
    49  	if strings.Contains(info, " from ") {
    50  		parts := regfrom.FindStringSubmatch(info)
    51  		info = fmt.Sprintf("%s %s", parts[1], parts[3])
    52  		id.Source = parts[2]
    53  	}
    54  
    55  	s := strings.SplitN(info, " ", 3)
    56  	if len(s) < 2 {
    57  		panic(fmt.Sprintf("Malformed name/version info string '%s'", info))
    58  	}
    59  
    60  	id.ProjectRoot, version = ProjectRoot(s[0]), s[1]
    61  
    62  	if len(s) == 3 {
    63  		revision = Revision(s[2])
    64  	}
    65  	return
    66  }
    67  
    68  // mkAtom splits the input string on a space, and uses the first two elements as
    69  // the project identifier and version, respectively.
    70  //
    71  // The version segment may have a leading character indicating the type of
    72  // version to create:
    73  //
    74  //  p: create a "plain" (non-semver) version.
    75  //  b: create a branch version.
    76  //  r: create a revision.
    77  //
    78  // No prefix is assumed to indicate a semver version.
    79  //
    80  // If a third space-delimited element is provided, it will be interepreted as a
    81  // revision, and used as the underlying version in a PairedVersion. No prefix
    82  // should be provided in this case. It is an error (and will panic) to try to
    83  // pass a revision with an underlying revision.
    84  func mkAtom(info string) atom {
    85  	// if info is "root", special case it to use the root "version"
    86  	if info == "root" {
    87  		return atom{
    88  			id: ProjectIdentifier{
    89  				ProjectRoot: ProjectRoot("root"),
    90  			},
    91  			v: rootRev,
    92  		}
    93  	}
    94  
    95  	id, ver, rev := nvrSplit(info)
    96  
    97  	var v Version
    98  	switch ver[0] {
    99  	case 'r':
   100  		if rev != "" {
   101  			panic("Cannot pair a revision with a revision")
   102  		}
   103  		v = Revision(ver[1:])
   104  	case 'p':
   105  		v = NewVersion(ver[1:])
   106  	case 'b':
   107  		v = NewBranch(ver[1:])
   108  	default:
   109  		_, err := semver.NewVersion(ver)
   110  		if err != nil {
   111  			// don't want to allow bad test data at this level, so just panic
   112  			panic(fmt.Sprintf("Error when converting '%s' into semver: %s", ver, err))
   113  		}
   114  		v = NewVersion(ver)
   115  	}
   116  
   117  	if rev != "" {
   118  		v = v.(UnpairedVersion).Pair(rev)
   119  	}
   120  
   121  	return atom{
   122  		id: id,
   123  		v:  v,
   124  	}
   125  }
   126  
   127  // mkPCstrnt splits the input string on a space, and uses the first two elements
   128  // as the project identifier and constraint body, respectively.
   129  //
   130  // The constraint body may have a leading character indicating the type of
   131  // version to create:
   132  //
   133  //  p: create a "plain" (non-semver) version.
   134  //  b: create a branch version.
   135  //  r: create a revision.
   136  //
   137  // If no leading character is used, a semver constraint is assumed.
   138  func mkPCstrnt(info string) ProjectConstraint {
   139  	id, ver, rev := nvrSplit(info)
   140  
   141  	var c Constraint
   142  	switch ver[0] {
   143  	case 'r':
   144  		c = Revision(ver[1:])
   145  	case 'p':
   146  		c = NewVersion(ver[1:])
   147  	case 'b':
   148  		c = NewBranch(ver[1:])
   149  	default:
   150  		// Without one of those leading characters, we know it's a proper semver
   151  		// expression, so use the other parser that doesn't look for a rev
   152  		rev = ""
   153  		id, ver = nvSplit(info)
   154  		var err error
   155  		c, err = NewSemverConstraint(ver)
   156  		if err != nil {
   157  			// don't want bad test data at this level, so just panic
   158  			panic(fmt.Sprintf("Error when converting '%s' into semver constraint: %s (full info: %s)", ver, err, info))
   159  		}
   160  	}
   161  
   162  	// There's no practical reason that a real tool would need to produce a
   163  	// constraint that's a PairedVersion, but it is a possibility admitted by the
   164  	// system, so we at least allow for it in our testing harness.
   165  	if rev != "" {
   166  		// Of course, this *will* panic if the predicate is a revision or a
   167  		// semver constraint, neither of which implement UnpairedVersion. This
   168  		// is as intended, to prevent bad data from entering the system.
   169  		c = c.(UnpairedVersion).Pair(rev)
   170  	}
   171  
   172  	return ProjectConstraint{
   173  		Ident:      id,
   174  		Constraint: c,
   175  	}
   176  }
   177  
   178  // mkCDep composes a completeDep struct from the inputs.
   179  //
   180  // The only real work here is passing the initial string to mkPDep. All the
   181  // other args are taken as package names.
   182  func mkCDep(pdep string, pl ...string) completeDep {
   183  	pc := mkPCstrnt(pdep)
   184  	return completeDep{
   185  		workingConstraint: workingConstraint{
   186  			Ident:      pc.Ident,
   187  			Constraint: pc.Constraint,
   188  		},
   189  		pl: pl,
   190  	}
   191  }
   192  
   193  // A depspec is a fixture representing all the information a SourceManager would
   194  // ordinarily glean directly from interrogating a repository.
   195  type depspec struct {
   196  	n    ProjectRoot
   197  	v    Version
   198  	deps []ProjectConstraint
   199  	pkgs []tpkg
   200  }
   201  
   202  // mkDepspec creates a depspec by processing a series of strings, each of which
   203  // contains an identiifer and version information.
   204  //
   205  // The first string is broken out into the name and version of the package being
   206  // described - see the docs on mkAtom for details. subsequent strings are
   207  // interpreted as dep constraints of that dep at that version. See the docs on
   208  // mkPDep for details.
   209  func mkDepspec(pi string, deps ...string) depspec {
   210  	pa := mkAtom(pi)
   211  	if string(pa.id.ProjectRoot) != pa.id.Source && pa.id.Source != "" {
   212  		panic("alternate source on self makes no sense")
   213  	}
   214  
   215  	ds := depspec{
   216  		n: pa.id.ProjectRoot,
   217  		v: pa.v,
   218  	}
   219  
   220  	for _, dep := range deps {
   221  		ds.deps = append(ds.deps, mkPCstrnt(dep))
   222  	}
   223  
   224  	return ds
   225  }
   226  
   227  func mkDep(atom, pdep string, pl ...string) dependency {
   228  	return dependency{
   229  		depender: mkAtom(atom),
   230  		dep:      mkCDep(pdep, pl...),
   231  	}
   232  }
   233  
   234  func mkADep(atom, pdep string, c Constraint, pl ...string) dependency {
   235  	return dependency{
   236  		depender: mkAtom(atom),
   237  		dep: completeDep{
   238  			workingConstraint: workingConstraint{
   239  				Ident: ProjectIdentifier{
   240  					ProjectRoot: ProjectRoot(pdep),
   241  				},
   242  				Constraint: c,
   243  			},
   244  			pl: pl,
   245  		},
   246  	}
   247  }
   248  
   249  // mkPI creates a ProjectIdentifier with the ProjectRoot as the provided
   250  // string, and the Source unset.
   251  //
   252  // Call normalize() on the returned value if you need the Source to be be
   253  // equal to the ProjectRoot.
   254  func mkPI(root string) ProjectIdentifier {
   255  	return ProjectIdentifier{
   256  		ProjectRoot: ProjectRoot(root),
   257  	}
   258  }
   259  
   260  // mkSVC creates a new semver constraint, panicking if an error is returned.
   261  func mkSVC(body string) Constraint {
   262  	c, err := NewSemverConstraint(body)
   263  	if err != nil {
   264  		panic(fmt.Sprintf("Error while trying to create semver constraint from %s: %s", body, err.Error()))
   265  	}
   266  	return c
   267  }
   268  
   269  // mklock makes a fixLock, suitable to act as a lock file
   270  func mklock(pairs ...string) fixLock {
   271  	l := make(fixLock, 0)
   272  	for _, s := range pairs {
   273  		pa := mkAtom(s)
   274  		l = append(l, NewLockedProject(pa.id, pa.v, nil))
   275  	}
   276  
   277  	return l
   278  }
   279  
   280  // mkrevlock makes a fixLock, suitable to act as a lock file, with only a name
   281  // and a rev
   282  func mkrevlock(pairs ...string) fixLock {
   283  	l := make(fixLock, 0)
   284  	for _, s := range pairs {
   285  		pa := mkAtom(s)
   286  		l = append(l, NewLockedProject(pa.id, pa.v.(PairedVersion).Revision(), nil))
   287  	}
   288  
   289  	return l
   290  }
   291  
   292  // mksolution creates a map of project identifiers to their LockedProject
   293  // result, which is sufficient to act as a solution fixture for the purposes of
   294  // most tests.
   295  //
   296  // Either strings or LockedProjects can be provided. If a string is provided, it
   297  // is assumed that we're in the default, "basic" case where there is exactly one
   298  // package in a project, and it is the root of the project - meaning that only
   299  // the "." package should be listed. If a LockedProject is provided (e.g. as
   300  // returned from mklp()), then it's incorporated directly.
   301  //
   302  // If any other type is provided, the func will panic.
   303  func mksolution(inputs ...interface{}) map[ProjectIdentifier]LockedProject {
   304  	m := make(map[ProjectIdentifier]LockedProject)
   305  	for _, in := range inputs {
   306  		switch t := in.(type) {
   307  		case string:
   308  			a := mkAtom(t)
   309  			m[a.id] = NewLockedProject(a.id, a.v, []string{"."})
   310  		case LockedProject:
   311  			m[t.Ident()] = t
   312  		default:
   313  			panic(fmt.Sprintf("unexpected input to mksolution: %T %s", in, in))
   314  		}
   315  	}
   316  
   317  	return m
   318  }
   319  
   320  // mklp creates a LockedProject from string inputs
   321  func mklp(pair string, pkgs ...string) LockedProject {
   322  	a := mkAtom(pair)
   323  	return NewLockedProject(a.id, a.v, pkgs)
   324  }
   325  
   326  // computeBasicReachMap takes a depspec and computes a reach map which is
   327  // identical to the explicit depgraph.
   328  //
   329  // Using a reachMap here is overkill for what the basic fixtures actually need,
   330  // but we use it anyway for congruence with the more general cases.
   331  func computeBasicReachMap(ds []depspec) reachMap {
   332  	rm := make(reachMap)
   333  
   334  	for k, d := range ds {
   335  		n := string(d.n)
   336  		lm := map[string][]string{
   337  			n: nil,
   338  		}
   339  		v := d.v
   340  		if k == 0 {
   341  			// Put the root in with a nil rev, to accommodate the solver
   342  			v = nil
   343  		}
   344  		rm[pident{n: d.n, v: v}] = lm
   345  
   346  		for _, dep := range d.deps {
   347  			lm[n] = append(lm[n], string(dep.Ident.ProjectRoot))
   348  		}
   349  	}
   350  
   351  	return rm
   352  }
   353  
   354  type pident struct {
   355  	n ProjectRoot
   356  	v Version
   357  }
   358  
   359  type specfix interface {
   360  	name() string
   361  	rootmanifest() RootManifest
   362  	rootTree() pkgtree.PackageTree
   363  	specs() []depspec
   364  	maxTries() int
   365  	solution() map[ProjectIdentifier]LockedProject
   366  	failure() error
   367  }
   368  
   369  // A basicFixture is a declarative test fixture that can cover a wide variety of
   370  // solver cases. All cases, however, maintain one invariant: package == project.
   371  // There are no subpackages, and so it is impossible for them to trigger or
   372  // require bimodal solving.
   373  //
   374  // This type is separate from bimodalFixture in part for legacy reasons - many
   375  // of these were adapted from similar tests in dart's pub lib, where there is no
   376  // such thing as "bimodal solving".
   377  //
   378  // But it's also useful to keep them separate because bimodal solving involves
   379  // considerably more complexity than simple solving, both in terms of fixture
   380  // declaration and actual solving mechanics. Thus, we gain a lot of value for
   381  // contributors and maintainers by keeping comprehension costs relatively low
   382  // while still covering important cases.
   383  type basicFixture struct {
   384  	// name of this fixture datum
   385  	n string
   386  	// depspecs. always treat first as root
   387  	ds []depspec
   388  	// results; map of name/atom pairs
   389  	r map[ProjectIdentifier]LockedProject
   390  	// max attempts the solver should need to find solution. 0 means no limit
   391  	maxAttempts int
   392  	// Use downgrade instead of default upgrade sorter
   393  	downgrade bool
   394  	// lock file simulator, if one's to be used at all
   395  	l fixLock
   396  	// solve failure expected, if any
   397  	fail error
   398  	// overrides, if any
   399  	ovr ProjectConstraints
   400  	// request up/downgrade to all projects
   401  	changeall bool
   402  	// individual projects to change
   403  	changelist []ProjectRoot
   404  	// if the fixture is currently broken/expected to fail, this has a message
   405  	// recording why
   406  	broken string
   407  }
   408  
   409  func (f basicFixture) name() string {
   410  	return f.n
   411  }
   412  
   413  func (f basicFixture) specs() []depspec {
   414  	return f.ds
   415  }
   416  
   417  func (f basicFixture) maxTries() int {
   418  	return f.maxAttempts
   419  }
   420  
   421  func (f basicFixture) solution() map[ProjectIdentifier]LockedProject {
   422  	return f.r
   423  }
   424  
   425  func (f basicFixture) rootmanifest() RootManifest {
   426  	return simpleRootManifest{
   427  		c:   pcSliceToMap(f.ds[0].deps),
   428  		ovr: f.ovr,
   429  	}
   430  }
   431  
   432  func (f basicFixture) rootTree() pkgtree.PackageTree {
   433  	var imp []string
   434  	for _, dep := range f.ds[0].deps {
   435  		imp = append(imp, string(dep.Ident.ProjectRoot))
   436  	}
   437  
   438  	n := string(f.ds[0].n)
   439  	pt := pkgtree.PackageTree{
   440  		ImportRoot: n,
   441  		Packages: map[string]pkgtree.PackageOrErr{
   442  			string(n): {
   443  				P: pkgtree.Package{
   444  					ImportPath: n,
   445  					Name:       n,
   446  					Imports:    imp,
   447  				},
   448  			},
   449  		},
   450  	}
   451  
   452  	return pt
   453  }
   454  
   455  func (f basicFixture) failure() error {
   456  	return f.fail
   457  }
   458  
   459  // A table of basicFixtures, used in the basic solving test set.
   460  var basicFixtures = map[string]basicFixture{
   461  	// basic fixtures
   462  	"no dependencies": {
   463  		ds: []depspec{
   464  			mkDepspec("root 0.0.0"),
   465  		},
   466  		r: mksolution(),
   467  	},
   468  	"simple dependency tree": {
   469  		ds: []depspec{
   470  			mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"),
   471  			mkDepspec("a 1.0.0", "aa 1.0.0", "ab 1.0.0"),
   472  			mkDepspec("aa 1.0.0"),
   473  			mkDepspec("ab 1.0.0"),
   474  			mkDepspec("b 1.0.0", "ba 1.0.0", "bb 1.0.0"),
   475  			mkDepspec("ba 1.0.0"),
   476  			mkDepspec("bb 1.0.0"),
   477  		},
   478  		r: mksolution(
   479  			"a 1.0.0",
   480  			"aa 1.0.0",
   481  			"ab 1.0.0",
   482  			"b 1.0.0",
   483  			"ba 1.0.0",
   484  			"bb 1.0.0",
   485  		),
   486  	},
   487  	"shared dependency with overlapping constraints": {
   488  		ds: []depspec{
   489  			mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"),
   490  			mkDepspec("a 1.0.0", "shared >=2.0.0, <4.0.0"),
   491  			mkDepspec("b 1.0.0", "shared >=3.0.0, <5.0.0"),
   492  			mkDepspec("shared 2.0.0"),
   493  			mkDepspec("shared 3.0.0"),
   494  			mkDepspec("shared 3.6.9"),
   495  			mkDepspec("shared 4.0.0"),
   496  			mkDepspec("shared 5.0.0"),
   497  		},
   498  		r: mksolution(
   499  			"a 1.0.0",
   500  			"b 1.0.0",
   501  			"shared 3.6.9",
   502  		),
   503  	},
   504  	"downgrade on overlapping constraints": {
   505  		ds: []depspec{
   506  			mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"),
   507  			mkDepspec("a 1.0.0", "shared >=2.0.0, <=4.0.0"),
   508  			mkDepspec("b 1.0.0", "shared >=3.0.0, <5.0.0"),
   509  			mkDepspec("shared 2.0.0"),
   510  			mkDepspec("shared 3.0.0"),
   511  			mkDepspec("shared 3.6.9"),
   512  			mkDepspec("shared 4.0.0"),
   513  			mkDepspec("shared 5.0.0"),
   514  		},
   515  		r: mksolution(
   516  			"a 1.0.0",
   517  			"b 1.0.0",
   518  			"shared 3.0.0",
   519  		),
   520  		downgrade: true,
   521  	},
   522  	"shared dependency where dependent version in turn affects other dependencies": {
   523  		ds: []depspec{
   524  			mkDepspec("root 0.0.0", "foo <=1.0.2", "bar 1.0.0"),
   525  			mkDepspec("foo 1.0.0"),
   526  			mkDepspec("foo 1.0.1", "bang 1.0.0"),
   527  			mkDepspec("foo 1.0.2", "whoop 1.0.0"),
   528  			mkDepspec("foo 1.0.3", "zoop 1.0.0"),
   529  			mkDepspec("bar 1.0.0", "foo <=1.0.1"),
   530  			mkDepspec("bang 1.0.0"),
   531  			mkDepspec("whoop 1.0.0"),
   532  			mkDepspec("zoop 1.0.0"),
   533  		},
   534  		r: mksolution(
   535  			"foo 1.0.1",
   536  			"bar 1.0.0",
   537  			"bang 1.0.0",
   538  		),
   539  	},
   540  	"removed dependency": {
   541  		ds: []depspec{
   542  			mkDepspec("root 1.0.0", "foo 1.0.0", "bar *"),
   543  			mkDepspec("foo 1.0.0"),
   544  			mkDepspec("foo 2.0.0"),
   545  			mkDepspec("bar 1.0.0"),
   546  			mkDepspec("bar 2.0.0", "baz 1.0.0"),
   547  			mkDepspec("baz 1.0.0", "foo 2.0.0"),
   548  		},
   549  		r: mksolution(
   550  			"foo 1.0.0",
   551  			"bar 1.0.0",
   552  		),
   553  		maxAttempts: 2,
   554  	},
   555  	// fixtures with locks
   556  	"with compatible locked dependency": {
   557  		ds: []depspec{
   558  			mkDepspec("root 0.0.0", "foo *"),
   559  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
   560  			mkDepspec("foo 1.0.1", "bar 1.0.1"),
   561  			mkDepspec("foo 1.0.2", "bar 1.0.2"),
   562  			mkDepspec("bar 1.0.0"),
   563  			mkDepspec("bar 1.0.1"),
   564  			mkDepspec("bar 1.0.2"),
   565  		},
   566  		l: mklock(
   567  			"foo 1.0.1",
   568  		),
   569  		r: mksolution(
   570  			"foo 1.0.1",
   571  			"bar 1.0.1",
   572  		),
   573  	},
   574  	"upgrade through lock": {
   575  		ds: []depspec{
   576  			mkDepspec("root 0.0.0", "foo *"),
   577  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
   578  			mkDepspec("foo 1.0.1", "bar 1.0.1"),
   579  			mkDepspec("foo 1.0.2", "bar 1.0.2"),
   580  			mkDepspec("bar 1.0.0"),
   581  			mkDepspec("bar 1.0.1"),
   582  			mkDepspec("bar 1.0.2"),
   583  		},
   584  		l: mklock(
   585  			"foo 1.0.1",
   586  		),
   587  		r: mksolution(
   588  			"foo 1.0.2",
   589  			"bar 1.0.2",
   590  		),
   591  		changeall: true,
   592  	},
   593  	"downgrade through lock": {
   594  		ds: []depspec{
   595  			mkDepspec("root 0.0.0", "foo *"),
   596  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
   597  			mkDepspec("foo 1.0.1", "bar 1.0.1"),
   598  			mkDepspec("foo 1.0.2", "bar 1.0.2"),
   599  			mkDepspec("bar 1.0.0"),
   600  			mkDepspec("bar 1.0.1"),
   601  			mkDepspec("bar 1.0.2"),
   602  		},
   603  		l: mklock(
   604  			"foo 1.0.1",
   605  		),
   606  		r: mksolution(
   607  			"foo 1.0.0",
   608  			"bar 1.0.0",
   609  		),
   610  		changeall: true,
   611  		downgrade: true,
   612  	},
   613  	"update one with only one": {
   614  		ds: []depspec{
   615  			mkDepspec("root 0.0.0", "foo *"),
   616  			mkDepspec("foo 1.0.0"),
   617  			mkDepspec("foo 1.0.1"),
   618  			mkDepspec("foo 1.0.2"),
   619  		},
   620  		l: mklock(
   621  			"foo 1.0.1",
   622  		),
   623  		r: mksolution(
   624  			"foo 1.0.2",
   625  		),
   626  		changelist: []ProjectRoot{"foo"},
   627  	},
   628  	"update one of multi": {
   629  		ds: []depspec{
   630  			mkDepspec("root 0.0.0", "foo *", "bar *"),
   631  			mkDepspec("foo 1.0.0"),
   632  			mkDepspec("foo 1.0.1"),
   633  			mkDepspec("foo 1.0.2"),
   634  			mkDepspec("bar 1.0.0"),
   635  			mkDepspec("bar 1.0.1"),
   636  			mkDepspec("bar 1.0.2"),
   637  		},
   638  		l: mklock(
   639  			"foo 1.0.1",
   640  			"bar 1.0.1",
   641  		),
   642  		r: mksolution(
   643  			"foo 1.0.2",
   644  			"bar 1.0.1",
   645  		),
   646  		changelist: []ProjectRoot{"foo"},
   647  	},
   648  	"update both of multi": {
   649  		ds: []depspec{
   650  			mkDepspec("root 0.0.0", "foo *", "bar *"),
   651  			mkDepspec("foo 1.0.0"),
   652  			mkDepspec("foo 1.0.1"),
   653  			mkDepspec("foo 1.0.2"),
   654  			mkDepspec("bar 1.0.0"),
   655  			mkDepspec("bar 1.0.1"),
   656  			mkDepspec("bar 1.0.2"),
   657  		},
   658  		l: mklock(
   659  			"foo 1.0.1",
   660  			"bar 1.0.1",
   661  		),
   662  		r: mksolution(
   663  			"foo 1.0.2",
   664  			"bar 1.0.2",
   665  		),
   666  		changelist: []ProjectRoot{"foo", "bar"},
   667  	},
   668  	"update two of more": {
   669  		ds: []depspec{
   670  			mkDepspec("root 0.0.0", "foo *", "bar *", "baz *"),
   671  			mkDepspec("foo 1.0.0"),
   672  			mkDepspec("foo 1.0.1"),
   673  			mkDepspec("foo 1.0.2"),
   674  			mkDepspec("bar 1.0.0"),
   675  			mkDepspec("bar 1.0.1"),
   676  			mkDepspec("bar 1.0.2"),
   677  			mkDepspec("baz 1.0.0"),
   678  			mkDepspec("baz 1.0.1"),
   679  			mkDepspec("baz 1.0.2"),
   680  		},
   681  		l: mklock(
   682  			"foo 1.0.1",
   683  			"bar 1.0.1",
   684  			"baz 1.0.1",
   685  		),
   686  		r: mksolution(
   687  			"foo 1.0.2",
   688  			"bar 1.0.2",
   689  			"baz 1.0.1",
   690  		),
   691  		changelist: []ProjectRoot{"foo", "bar"},
   692  	},
   693  	"break other lock with targeted update": {
   694  		ds: []depspec{
   695  			mkDepspec("root 0.0.0", "foo *", "baz *"),
   696  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
   697  			mkDepspec("foo 1.0.1", "bar 1.0.1"),
   698  			mkDepspec("foo 1.0.2", "bar 1.0.2"),
   699  			mkDepspec("bar 1.0.0"),
   700  			mkDepspec("bar 1.0.1"),
   701  			mkDepspec("bar 1.0.2"),
   702  			mkDepspec("baz 1.0.0"),
   703  			mkDepspec("baz 1.0.1"),
   704  			mkDepspec("baz 1.0.2"),
   705  		},
   706  		l: mklock(
   707  			"foo 1.0.1",
   708  			"bar 1.0.1",
   709  			"baz 1.0.1",
   710  		),
   711  		r: mksolution(
   712  			"foo 1.0.2",
   713  			"bar 1.0.2",
   714  			"baz 1.0.1",
   715  		),
   716  		changelist: []ProjectRoot{"foo", "bar"},
   717  	},
   718  	"with incompatible locked dependency": {
   719  		ds: []depspec{
   720  			mkDepspec("root 0.0.0", "foo >1.0.1"),
   721  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
   722  			mkDepspec("foo 1.0.1", "bar 1.0.1"),
   723  			mkDepspec("foo 1.0.2", "bar 1.0.2"),
   724  			mkDepspec("bar 1.0.0"),
   725  			mkDepspec("bar 1.0.1"),
   726  			mkDepspec("bar 1.0.2"),
   727  		},
   728  		l: mklock(
   729  			"foo 1.0.1",
   730  		),
   731  		r: mksolution(
   732  			"foo 1.0.2",
   733  			"bar 1.0.2",
   734  		),
   735  	},
   736  	"with unrelated locked dependency": {
   737  		ds: []depspec{
   738  			mkDepspec("root 0.0.0", "foo *"),
   739  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
   740  			mkDepspec("foo 1.0.1", "bar 1.0.1"),
   741  			mkDepspec("foo 1.0.2", "bar 1.0.2"),
   742  			mkDepspec("bar 1.0.0"),
   743  			mkDepspec("bar 1.0.1"),
   744  			mkDepspec("bar 1.0.2"),
   745  			mkDepspec("baz 1.0.0 bazrev"),
   746  		},
   747  		l: mklock(
   748  			"baz 1.0.0 bazrev",
   749  		),
   750  		r: mksolution(
   751  			"foo 1.0.2",
   752  			"bar 1.0.2",
   753  		),
   754  	},
   755  	"unlocks dependencies if necessary to ensure that a new dependency is satisfied": {
   756  		ds: []depspec{
   757  			mkDepspec("root 0.0.0", "foo *", "newdep *"),
   758  			mkDepspec("foo 1.0.0 foorev", "bar <2.0.0"),
   759  			mkDepspec("bar 1.0.0 barrev", "baz <2.0.0"),
   760  			mkDepspec("baz 1.0.0 bazrev", "qux <2.0.0"),
   761  			mkDepspec("qux 1.0.0 quxrev"),
   762  			mkDepspec("foo 2.0.0", "bar <3.0.0"),
   763  			mkDepspec("bar 2.0.0", "baz <3.0.0"),
   764  			mkDepspec("baz 2.0.0", "qux <3.0.0"),
   765  			mkDepspec("qux 2.0.0"),
   766  			mkDepspec("newdep 2.0.0", "baz >=1.5.0"),
   767  		},
   768  		l: mklock(
   769  			"foo 1.0.0 foorev",
   770  			"bar 1.0.0 barrev",
   771  			"baz 1.0.0 bazrev",
   772  			"qux 1.0.0 quxrev",
   773  		),
   774  		r: mksolution(
   775  			"foo 2.0.0",
   776  			"bar 2.0.0",
   777  			"baz 2.0.0",
   778  			"qux 1.0.0 quxrev",
   779  			"newdep 2.0.0",
   780  		),
   781  		maxAttempts: 4,
   782  	},
   783  	"break lock when only the deps necessitate it": {
   784  		ds: []depspec{
   785  			mkDepspec("root 0.0.0", "foo *", "bar *"),
   786  			mkDepspec("foo 1.0.0 foorev", "bar <2.0.0"),
   787  			mkDepspec("foo 2.0.0", "bar <3.0.0"),
   788  			mkDepspec("bar 2.0.0", "baz <3.0.0"),
   789  			mkDepspec("baz 2.0.0", "foo >1.0.0"),
   790  		},
   791  		l: mklock(
   792  			"foo 1.0.0 foorev",
   793  		),
   794  		r: mksolution(
   795  			"foo 2.0.0",
   796  			"bar 2.0.0",
   797  			"baz 2.0.0",
   798  		),
   799  		maxAttempts: 4,
   800  	},
   801  	"locked atoms are matched on both local and net name": {
   802  		ds: []depspec{
   803  			mkDepspec("root 0.0.0", "foo *"),
   804  			mkDepspec("foo 1.0.0 foorev"),
   805  			mkDepspec("foo 2.0.0 foorev2"),
   806  		},
   807  		l: mklock(
   808  			"foo from baz 1.0.0 foorev",
   809  		),
   810  		r: mksolution(
   811  			"foo 2.0.0 foorev2",
   812  		),
   813  	},
   814  	// This fixture describes a situation that should be impossible with a
   815  	// real-world VCS (contents of dep at same rev are different, as indicated
   816  	// by different constraints on bar). But, that's not the SUT here, so it's
   817  	// OK.
   818  	"pairs bare revs in lock with all versions": {
   819  		ds: []depspec{
   820  			mkDepspec("root 0.0.0", "foo ~1.0.1"),
   821  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
   822  			mkDepspec("foo 1.0.1 foorev", "bar 1.0.1"),
   823  			mkDepspec("foo 1.0.2 foorev", "bar 1.0.2"),
   824  			mkDepspec("bar 1.0.0"),
   825  			mkDepspec("bar 1.0.1"),
   826  			mkDepspec("bar 1.0.2"),
   827  		},
   828  		l: mkrevlock(
   829  			"foo 1.0.1 foorev", // mkrevlock drops the 1.0.1
   830  		),
   831  		r: mksolution(
   832  			"foo 1.0.2 foorev",
   833  			"bar 1.0.2",
   834  		),
   835  	},
   836  	"does not pair bare revs in manifest with unpaired lock version": {
   837  		ds: []depspec{
   838  			mkDepspec("root 0.0.0", "foo ~1.0.1"),
   839  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
   840  			mkDepspec("foo 1.0.1 foorev", "bar 1.0.1"),
   841  			mkDepspec("foo 1.0.2", "bar 1.0.2"),
   842  			mkDepspec("bar 1.0.0"),
   843  			mkDepspec("bar 1.0.1"),
   844  			mkDepspec("bar 1.0.2"),
   845  		},
   846  		l: mkrevlock(
   847  			"foo 1.0.1 foorev", // mkrevlock drops the 1.0.1
   848  		),
   849  		r: mksolution(
   850  			"foo 1.0.2",
   851  			"bar 1.0.2",
   852  		),
   853  	},
   854  	"lock to branch on old rev keeps old rev": {
   855  		ds: []depspec{
   856  			mkDepspec("root 0.0.0", "foo bmaster"),
   857  			mkDepspec("foo bmaster newrev"),
   858  		},
   859  		l: mklock(
   860  			"foo bmaster oldrev",
   861  		),
   862  		r: mksolution(
   863  			"foo bmaster oldrev",
   864  		),
   865  	},
   866  	// Whereas this is a normal situation for a branch, when it occurs for a
   867  	// tag, it means someone's been naughty upstream. Still, though, the outcome
   868  	// is the same.
   869  	//
   870  	// TODO(sdboyer) this needs to generate a warning, once we start doing that
   871  	"lock to now-moved tag on old rev keeps old rev": {
   872  		ds: []depspec{
   873  			mkDepspec("root 0.0.0", "foo ptaggerino"),
   874  			mkDepspec("foo ptaggerino newrev"),
   875  		},
   876  		l: mklock(
   877  			"foo ptaggerino oldrev",
   878  		),
   879  		r: mksolution(
   880  			"foo ptaggerino oldrev",
   881  		),
   882  	},
   883  	"no version that matches requirement": {
   884  		ds: []depspec{
   885  			mkDepspec("root 0.0.0", "foo ^1.0.0"),
   886  			mkDepspec("foo 2.0.0"),
   887  			mkDepspec("foo 2.1.3"),
   888  		},
   889  		fail: &noVersionError{
   890  			pn: mkPI("foo"),
   891  			fails: []failedVersion{
   892  				{
   893  					v: NewVersion("2.1.3"),
   894  					f: &versionNotAllowedFailure{
   895  						goal:       mkAtom("foo 2.1.3"),
   896  						failparent: []dependency{mkDep("root", "foo ^1.0.0", "foo")},
   897  						c:          mkSVC("^1.0.0"),
   898  					},
   899  				},
   900  				{
   901  					v: NewVersion("2.0.0"),
   902  					f: &versionNotAllowedFailure{
   903  						goal:       mkAtom("foo 2.0.0"),
   904  						failparent: []dependency{mkDep("root", "foo ^1.0.0", "foo")},
   905  						c:          mkSVC("^1.0.0"),
   906  					},
   907  				},
   908  			},
   909  		},
   910  	},
   911  	"no version that matches combined constraint": {
   912  		ds: []depspec{
   913  			mkDepspec("root 0.0.0", "foo 1.0.0", "bar 1.0.0"),
   914  			mkDepspec("foo 1.0.0", "shared >=2.0.0, <3.0.0"),
   915  			mkDepspec("bar 1.0.0", "shared >=2.9.0, <4.0.0"),
   916  			mkDepspec("shared 2.5.0"),
   917  			mkDepspec("shared 3.5.0"),
   918  		},
   919  		fail: &noVersionError{
   920  			pn: mkPI("shared"),
   921  			fails: []failedVersion{
   922  				{
   923  					v: NewVersion("3.5.0"),
   924  					f: &versionNotAllowedFailure{
   925  						goal:       mkAtom("shared 3.5.0"),
   926  						failparent: []dependency{mkDep("foo 1.0.0", "shared >=2.0.0, <3.0.0", "shared")},
   927  						c:          mkSVC(">=2.9.0, <3.0.0"),
   928  					},
   929  				},
   930  				{
   931  					v: NewVersion("2.5.0"),
   932  					f: &versionNotAllowedFailure{
   933  						goal:       mkAtom("shared 2.5.0"),
   934  						failparent: []dependency{mkDep("bar 1.0.0", "shared >=2.9.0, <4.0.0", "shared")},
   935  						c:          mkSVC(">=2.9.0, <3.0.0"),
   936  					},
   937  				},
   938  			},
   939  		},
   940  	},
   941  	"disjoint constraints": {
   942  		ds: []depspec{
   943  			mkDepspec("root 0.0.0", "foo 1.0.0", "bar 1.0.0"),
   944  			mkDepspec("foo 1.0.0", "shared <=2.0.0"),
   945  			mkDepspec("bar 1.0.0", "shared >3.0.0"),
   946  			mkDepspec("shared 2.0.0"),
   947  			mkDepspec("shared 4.0.0"),
   948  		},
   949  		fail: &noVersionError{
   950  			pn: mkPI("foo"),
   951  			fails: []failedVersion{
   952  				{
   953  					v: NewVersion("1.0.0"),
   954  					f: &disjointConstraintFailure{
   955  						goal:      mkDep("foo 1.0.0", "shared <=2.0.0", "shared"),
   956  						failsib:   []dependency{mkDep("bar 1.0.0", "shared >3.0.0", "shared")},
   957  						nofailsib: nil,
   958  						c:         mkSVC(">3.0.0"),
   959  					},
   960  				},
   961  			},
   962  		},
   963  	},
   964  	"no valid solution": {
   965  		ds: []depspec{
   966  			mkDepspec("root 0.0.0", "a *", "b *"),
   967  			mkDepspec("a 1.0.0", "b 1.0.0"),
   968  			mkDepspec("a 2.0.0", "b 2.0.0"),
   969  			mkDepspec("b 1.0.0", "a 2.0.0"),
   970  			mkDepspec("b 2.0.0", "a 1.0.0"),
   971  		},
   972  		fail: &noVersionError{
   973  			pn: mkPI("b"),
   974  			fails: []failedVersion{
   975  				{
   976  					v: NewVersion("2.0.0"),
   977  					f: &versionNotAllowedFailure{
   978  						goal:       mkAtom("b 2.0.0"),
   979  						failparent: []dependency{mkDep("a 1.0.0", "b 1.0.0", "b")},
   980  						c:          mkSVC("1.0.0"),
   981  					},
   982  				},
   983  				{
   984  					v: NewVersion("1.0.0"),
   985  					f: &constraintNotAllowedFailure{
   986  						goal: mkDep("b 1.0.0", "a 2.0.0", "a"),
   987  						v:    NewVersion("1.0.0"),
   988  					},
   989  				},
   990  			},
   991  		},
   992  	},
   993  	"no version that matches while backtracking": {
   994  		ds: []depspec{
   995  			mkDepspec("root 0.0.0", "a *", "b >1.0.0"),
   996  			mkDepspec("a 1.0.0"),
   997  			mkDepspec("b 1.0.0"),
   998  		},
   999  		fail: &noVersionError{
  1000  			pn: mkPI("b"),
  1001  			fails: []failedVersion{
  1002  				{
  1003  					v: NewVersion("1.0.0"),
  1004  					f: &versionNotAllowedFailure{
  1005  						goal:       mkAtom("b 1.0.0"),
  1006  						failparent: []dependency{mkDep("root", "b >1.0.0", "b")},
  1007  						c:          mkSVC(">1.0.0"),
  1008  					},
  1009  				},
  1010  			},
  1011  		},
  1012  	},
  1013  	// The latest versions of a and b disagree on c. An older version of either
  1014  	// will resolve the problem. This test validates that b, which is farther
  1015  	// in the dependency graph from myapp is downgraded first.
  1016  	"rolls back leaf versions first": {
  1017  		ds: []depspec{
  1018  			mkDepspec("root 0.0.0", "a *"),
  1019  			mkDepspec("a 1.0.0", "b *"),
  1020  			mkDepspec("a 2.0.0", "b *", "c 2.0.0"),
  1021  			mkDepspec("b 1.0.0"),
  1022  			mkDepspec("b 2.0.0", "c 1.0.0"),
  1023  			mkDepspec("c 1.0.0"),
  1024  			mkDepspec("c 2.0.0"),
  1025  		},
  1026  		r: mksolution(
  1027  			"a 2.0.0",
  1028  			"b 1.0.0",
  1029  			"c 2.0.0",
  1030  		),
  1031  		maxAttempts: 2,
  1032  	},
  1033  	// Only one version of baz, so foo and bar will have to downgrade until they
  1034  	// reach it.
  1035  	"mutual downgrading": {
  1036  		ds: []depspec{
  1037  			mkDepspec("root 0.0.0", "foo *"),
  1038  			mkDepspec("foo 1.0.0", "bar 1.0.0"),
  1039  			mkDepspec("foo 2.0.0", "bar 2.0.0"),
  1040  			mkDepspec("foo 3.0.0", "bar 3.0.0"),
  1041  			mkDepspec("bar 1.0.0", "baz *"),
  1042  			mkDepspec("bar 2.0.0", "baz 2.0.0"),
  1043  			mkDepspec("bar 3.0.0", "baz 3.0.0"),
  1044  			mkDepspec("baz 1.0.0"),
  1045  		},
  1046  		r: mksolution(
  1047  			"foo 1.0.0",
  1048  			"bar 1.0.0",
  1049  			"baz 1.0.0",
  1050  		),
  1051  		maxAttempts: 3,
  1052  	},
  1053  	// Ensures the solver doesn't exhaustively search all versions of b when
  1054  	// it's a-2.0.0 whose dependency on c-2.0.0-nonexistent led to the
  1055  	// problem. We make sure b has more versions than a so that the solver
  1056  	// tries a first since it sorts sibling dependencies by number of
  1057  	// versions.
  1058  	"search real failer": {
  1059  		ds: []depspec{
  1060  			mkDepspec("root 0.0.0", "a *", "b *"),
  1061  			mkDepspec("a 1.0.0", "c 1.0.0"),
  1062  			mkDepspec("a 2.0.0", "c 2.0.0"),
  1063  			mkDepspec("b 1.0.0"),
  1064  			mkDepspec("b 2.0.0"),
  1065  			mkDepspec("b 3.0.0"),
  1066  			mkDepspec("c 1.0.0"),
  1067  		},
  1068  		r: mksolution(
  1069  			"a 1.0.0",
  1070  			"b 3.0.0",
  1071  			"c 1.0.0",
  1072  		),
  1073  		maxAttempts: 2,
  1074  	},
  1075  	// Dependencies are ordered so that packages with fewer versions are tried
  1076  	// first. Here, there are two valid solutions (either a or b must be
  1077  	// downgraded once). The chosen one depends on which dep is traversed first.
  1078  	// Since b has fewer versions, it will be traversed first, which means a
  1079  	// will come later. Since later selections are revised first, a gets
  1080  	// downgraded.
  1081  	"traverse into package with fewer versions first": {
  1082  		ds: []depspec{
  1083  			mkDepspec("root 0.0.0", "a *", "b *"),
  1084  			mkDepspec("a 1.0.0", "c *"),
  1085  			mkDepspec("a 2.0.0", "c *"),
  1086  			mkDepspec("a 3.0.0", "c *"),
  1087  			mkDepspec("a 4.0.0", "c *"),
  1088  			mkDepspec("a 5.0.0", "c 1.0.0"),
  1089  			mkDepspec("b 1.0.0", "c *"),
  1090  			mkDepspec("b 2.0.0", "c *"),
  1091  			mkDepspec("b 3.0.0", "c *"),
  1092  			mkDepspec("b 4.0.0", "c 2.0.0"),
  1093  			mkDepspec("c 1.0.0"),
  1094  			mkDepspec("c 2.0.0"),
  1095  		},
  1096  		r: mksolution(
  1097  			"a 4.0.0",
  1098  			"b 4.0.0",
  1099  			"c 2.0.0",
  1100  		),
  1101  		maxAttempts: 2,
  1102  	},
  1103  	// This is similar to the preceding fixture. When getting the number of
  1104  	// versions of a package to determine which to traverse first, versions that
  1105  	// are disallowed by the root package's constraints should not be
  1106  	// considered. Here, foo has more versions than bar in total (4), but fewer
  1107  	// that meet myapp"s constraints (only 2). There is no solution, but we will
  1108  	// do less backtracking if foo is tested first.
  1109  	"root constraints pre-eliminate versions": {
  1110  		ds: []depspec{
  1111  			mkDepspec("root 0.0.0", "foo *", "bar *"),
  1112  			mkDepspec("foo 1.0.0", "none 2.0.0"),
  1113  			mkDepspec("foo 2.0.0", "none 2.0.0"),
  1114  			mkDepspec("foo 3.0.0", "none 2.0.0"),
  1115  			mkDepspec("foo 4.0.0", "none 2.0.0"),
  1116  			mkDepspec("bar 1.0.0"),
  1117  			mkDepspec("bar 2.0.0"),
  1118  			mkDepspec("bar 3.0.0"),
  1119  			mkDepspec("none 1.0.0"),
  1120  		},
  1121  		fail: &noVersionError{
  1122  			pn: mkPI("none"),
  1123  			fails: []failedVersion{
  1124  				{
  1125  					v: NewVersion("1.0.0"),
  1126  					f: &versionNotAllowedFailure{
  1127  						goal:       mkAtom("none 1.0.0"),
  1128  						failparent: []dependency{mkDep("foo 1.0.0", "none 2.0.0", "none")},
  1129  						c:          mkSVC("2.0.0"),
  1130  					},
  1131  				},
  1132  			},
  1133  		},
  1134  	},
  1135  	// If there"s a disjoint constraint on a package, then selecting other
  1136  	// versions of it is a waste of time: no possible versions can match. We
  1137  	// need to jump past it to the most recent package that affected the
  1138  	// constraint.
  1139  	"backjump past failed package on disjoint constraint": {
  1140  		ds: []depspec{
  1141  			mkDepspec("root 0.0.0", "a *", "foo *"),
  1142  			mkDepspec("a 1.0.0", "foo *"),
  1143  			mkDepspec("a 2.0.0", "foo <1.0.0"),
  1144  			mkDepspec("foo 2.0.0"),
  1145  			mkDepspec("foo 2.0.1"),
  1146  			mkDepspec("foo 2.0.2"),
  1147  			mkDepspec("foo 2.0.3"),
  1148  			mkDepspec("foo 2.0.4"),
  1149  			mkDepspec("none 1.0.0"),
  1150  		},
  1151  		r: mksolution(
  1152  			"a 1.0.0",
  1153  			"foo 2.0.4",
  1154  		),
  1155  		maxAttempts: 2,
  1156  	},
  1157  	// Revision enters vqueue if a dep has a constraint on that revision
  1158  	"revision injected into vqueue": {
  1159  		ds: []depspec{
  1160  			mkDepspec("root 0.0.0", "foo r123abc"),
  1161  			mkDepspec("foo r123abc"),
  1162  			mkDepspec("foo 1.0.0 foorev"),
  1163  			mkDepspec("foo 2.0.0 foorev2"),
  1164  		},
  1165  		r: mksolution(
  1166  			"foo r123abc",
  1167  		),
  1168  	},
  1169  	// Some basic override checks
  1170  	"override root's own constraint": {
  1171  		ds: []depspec{
  1172  			mkDepspec("root 0.0.0", "a *", "b *"),
  1173  			mkDepspec("a 1.0.0", "b 1.0.0"),
  1174  			mkDepspec("a 2.0.0", "b 1.0.0"),
  1175  			mkDepspec("b 1.0.0"),
  1176  		},
  1177  		ovr: ProjectConstraints{
  1178  			ProjectRoot("a"): ProjectProperties{
  1179  				Constraint: NewVersion("1.0.0"),
  1180  			},
  1181  		},
  1182  		r: mksolution(
  1183  			"a 1.0.0",
  1184  			"b 1.0.0",
  1185  		),
  1186  	},
  1187  	"override dep's constraint": {
  1188  		ds: []depspec{
  1189  			mkDepspec("root 0.0.0", "a *"),
  1190  			mkDepspec("a 1.0.0", "b 1.0.0"),
  1191  			mkDepspec("a 2.0.0", "b 1.0.0"),
  1192  			mkDepspec("b 1.0.0"),
  1193  			mkDepspec("b 2.0.0"),
  1194  		},
  1195  		ovr: ProjectConstraints{
  1196  			ProjectRoot("b"): ProjectProperties{
  1197  				Constraint: NewVersion("2.0.0"),
  1198  			},
  1199  		},
  1200  		r: mksolution(
  1201  			"a 2.0.0",
  1202  			"b 2.0.0",
  1203  		),
  1204  	},
  1205  	"overridden mismatched net addrs, alt in dep, back to default": {
  1206  		ds: []depspec{
  1207  			mkDepspec("root 1.0.0", "foo 1.0.0", "bar 1.0.0"),
  1208  			mkDepspec("foo 1.0.0", "bar from baz 1.0.0"),
  1209  			mkDepspec("bar 1.0.0"),
  1210  		},
  1211  		ovr: ProjectConstraints{
  1212  			ProjectRoot("bar"): ProjectProperties{
  1213  				Source: "bar",
  1214  			},
  1215  		},
  1216  		r: mksolution(
  1217  			"foo 1.0.0",
  1218  			"bar from bar 1.0.0",
  1219  		),
  1220  	},
  1221  
  1222  	// TODO(sdboyer) decide how to refactor the solver in order to re-enable these.
  1223  	// Checking for revision existence is important...but kinda obnoxious.
  1224  	//{
  1225  	//// Solve fails if revision constraint calls for a nonexistent revision
  1226  	//n: "fail on missing revision",
  1227  	//ds: []depspec{
  1228  	//mkDepspec("root 0.0.0", "bar *"),
  1229  	//mkDepspec("bar 1.0.0", "foo r123abc"),
  1230  	//mkDepspec("foo r123nomatch"),
  1231  	//mkDepspec("foo 1.0.0"),
  1232  	//mkDepspec("foo 2.0.0"),
  1233  	//},
  1234  	//errp: []string{"bar", "foo", "bar"},
  1235  	//},
  1236  	//{
  1237  	//// Solve fails if revision constraint calls for a nonexistent revision,
  1238  	//// even if rev constraint is specified by root
  1239  	//n: "fail on missing revision from root",
  1240  	//ds: []depspec{
  1241  	//mkDepspec("root 0.0.0", "foo r123nomatch"),
  1242  	//mkDepspec("foo r123abc"),
  1243  	//mkDepspec("foo 1.0.0"),
  1244  	//mkDepspec("foo 2.0.0"),
  1245  	//},
  1246  	//errp: []string{"foo", "root", "foo"},
  1247  	//},
  1248  
  1249  	// TODO(sdboyer) add fixture that tests proper handling of loops via aliases (where
  1250  	// a project that wouldn't be a loop is aliased to a project that is a loop)
  1251  }
  1252  
  1253  func init() {
  1254  	// This sets up a hundred versions of foo and bar, 0.0.0 through 9.9.0. Each
  1255  	// version of foo depends on a baz with the same major version. Each version
  1256  	// of bar depends on a baz with the same minor version. There is only one
  1257  	// version of baz, 0.0.0, so only older versions of foo and bar will
  1258  	// satisfy it.
  1259  	fix := basicFixture{
  1260  		ds: []depspec{
  1261  			mkDepspec("root 0.0.0", "foo *", "bar *"),
  1262  			mkDepspec("baz 0.0.0"),
  1263  		},
  1264  		r: mksolution(
  1265  			"foo 0.9.0",
  1266  			"bar 9.0.0",
  1267  			"baz 0.0.0",
  1268  		),
  1269  		maxAttempts: 10,
  1270  	}
  1271  
  1272  	for i := 0; i < 10; i++ {
  1273  		for j := 0; j < 10; j++ {
  1274  			fix.ds = append(fix.ds, mkDepspec(fmt.Sprintf("foo %v.%v.0", i, j), fmt.Sprintf("baz %v.0.0", i)))
  1275  			fix.ds = append(fix.ds, mkDepspec(fmt.Sprintf("bar %v.%v.0", i, j), fmt.Sprintf("baz 0.%v.0", j)))
  1276  		}
  1277  	}
  1278  
  1279  	basicFixtures["complex backtrack"] = fix
  1280  
  1281  	for k, fix := range basicFixtures {
  1282  		// Assign the name into the fixture itself
  1283  		fix.n = k
  1284  		basicFixtures[k] = fix
  1285  	}
  1286  }
  1287  
  1288  // reachMaps contain externalReach()-type data for a given depspec fixture's
  1289  // universe of projects, packages, and versions.
  1290  type reachMap map[pident]map[string][]string
  1291  
  1292  type depspecSourceManager struct {
  1293  	specs []depspec
  1294  	rm    reachMap
  1295  	ig    map[string]bool
  1296  }
  1297  
  1298  type fixSM interface {
  1299  	SourceManager
  1300  	rootSpec() depspec
  1301  	allSpecs() []depspec
  1302  	ignore() map[string]bool
  1303  }
  1304  
  1305  var _ fixSM = &depspecSourceManager{}
  1306  
  1307  func newdepspecSM(ds []depspec, ignore []string) *depspecSourceManager {
  1308  	ig := make(map[string]bool)
  1309  	if len(ignore) > 0 {
  1310  		for _, pkg := range ignore {
  1311  			ig[pkg] = true
  1312  		}
  1313  	}
  1314  
  1315  	return &depspecSourceManager{
  1316  		specs: ds,
  1317  		rm:    computeBasicReachMap(ds),
  1318  		ig:    ig,
  1319  	}
  1320  }
  1321  
  1322  func (sm *depspecSourceManager) GetManifestAndLock(id ProjectIdentifier, v Version, an ProjectAnalyzer) (Manifest, Lock, error) {
  1323  	// If the input version is a PairedVersion, look only at its top version,
  1324  	// not the underlying. This is generally consistent with the idea that, for
  1325  	// this class of lookup, the rev probably DOES exist, but upstream changed
  1326  	// it (typically a branch). For the purposes of tests, then, that's an OK
  1327  	// scenario, because otherwise we'd have to enumerate all the revs in the
  1328  	// fixture declarations, which would screw up other things.
  1329  	if pv, ok := v.(PairedVersion); ok {
  1330  		v = pv.Unpair()
  1331  	}
  1332  
  1333  	src := toFold(id.normalizedSource())
  1334  	for _, ds := range sm.specs {
  1335  		if src == string(ds.n) && v.Matches(ds.v) {
  1336  			return ds, dummyLock{}, nil
  1337  		}
  1338  	}
  1339  
  1340  	return nil, nil, fmt.Errorf("project %s at version %s could not be found", id, v)
  1341  }
  1342  
  1343  func (sm *depspecSourceManager) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) {
  1344  	pid := pident{n: ProjectRoot(toFold(id.normalizedSource())), v: v}
  1345  	if pv, ok := v.(PairedVersion); ok && pv.Revision() == "FAKEREV" {
  1346  		// An empty rev may come in here because that's what we produce in
  1347  		// ListVersions(). If that's what we see, then just pretend like we have
  1348  		// an unpaired.
  1349  		pid.v = pv.Unpair()
  1350  	}
  1351  
  1352  	if r, exists := sm.rm[pid]; exists {
  1353  		return pkgtree.PackageTree{
  1354  			ImportRoot: id.normalizedSource(),
  1355  			Packages: map[string]pkgtree.PackageOrErr{
  1356  				string(pid.n): {
  1357  					P: pkgtree.Package{
  1358  						ImportPath: string(pid.n),
  1359  						Name:       string(pid.n),
  1360  						Imports:    r[string(pid.n)],
  1361  					},
  1362  				},
  1363  			},
  1364  		}, nil
  1365  	}
  1366  
  1367  	// if incoming version was paired, walk the map and search for a match on
  1368  	// top-only version
  1369  	if pv, ok := v.(PairedVersion); ok {
  1370  		uv := pv.Unpair()
  1371  		for pid, r := range sm.rm {
  1372  			if uv.Matches(pid.v) {
  1373  				return pkgtree.PackageTree{
  1374  					ImportRoot: id.normalizedSource(),
  1375  					Packages: map[string]pkgtree.PackageOrErr{
  1376  						string(pid.n): {
  1377  							P: pkgtree.Package{
  1378  								ImportPath: string(pid.n),
  1379  								Name:       string(pid.n),
  1380  								Imports:    r[string(pid.n)],
  1381  							},
  1382  						},
  1383  					},
  1384  				}, nil
  1385  			}
  1386  		}
  1387  	}
  1388  
  1389  	return pkgtree.PackageTree{}, fmt.Errorf("project %s at version %s could not be found", pid.n, v)
  1390  }
  1391  
  1392  func (sm *depspecSourceManager) ListVersions(id ProjectIdentifier) ([]PairedVersion, error) {
  1393  	var pvl []PairedVersion
  1394  	src := toFold(id.normalizedSource())
  1395  	for _, ds := range sm.specs {
  1396  		if src != string(ds.n) {
  1397  			continue
  1398  		}
  1399  
  1400  		switch tv := ds.v.(type) {
  1401  		case Revision:
  1402  			// To simulate the behavior of the real SourceManager, we do not return
  1403  			// raw revisions from listVersions().
  1404  		case PairedVersion:
  1405  			pvl = append(pvl, tv)
  1406  		case UnpairedVersion:
  1407  			// Dummy revision; if the fixture doesn't provide it, we know
  1408  			// the test doesn't need revision info, anyway.
  1409  			pvl = append(pvl, tv.Pair(Revision("FAKEREV")))
  1410  		default:
  1411  			panic(fmt.Sprintf("unreachable: type of version was %#v for spec %s", ds.v, id))
  1412  		}
  1413  	}
  1414  
  1415  	if len(pvl) == 0 {
  1416  		return nil, fmt.Errorf("project %s could not be found", id)
  1417  	}
  1418  	return pvl, nil
  1419  }
  1420  
  1421  func (sm *depspecSourceManager) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) {
  1422  	src := toFold(id.normalizedSource())
  1423  	for _, ds := range sm.specs {
  1424  		if src == string(ds.n) && r == ds.v {
  1425  			return true, nil
  1426  		}
  1427  	}
  1428  
  1429  	return false, fmt.Errorf("project %s has no revision %s", id, r)
  1430  }
  1431  
  1432  func (sm *depspecSourceManager) SourceExists(id ProjectIdentifier) (bool, error) {
  1433  	src := toFold(id.normalizedSource())
  1434  	for _, ds := range sm.specs {
  1435  		if src == string(ds.n) {
  1436  			return true, nil
  1437  		}
  1438  	}
  1439  
  1440  	return false, nil
  1441  }
  1442  
  1443  func (sm *depspecSourceManager) SyncSourceFor(id ProjectIdentifier) error {
  1444  	// Ignore err because it can't happen
  1445  	if exist, _ := sm.SourceExists(id); !exist {
  1446  		return fmt.Errorf("source %s does not exist", id)
  1447  	}
  1448  	return nil
  1449  }
  1450  
  1451  func (sm *depspecSourceManager) Release() {}
  1452  
  1453  func (sm *depspecSourceManager) ExportProject(context.Context, ProjectIdentifier, Version, string) error {
  1454  	return fmt.Errorf("dummy sm doesn't support exporting")
  1455  }
  1456  
  1457  func (sm *depspecSourceManager) ExportPrunedProject(context.Context, LockedProject, PruneOptions, string) error {
  1458  	return fmt.Errorf("dummy sm doesn't support exporting")
  1459  }
  1460  
  1461  func (sm *depspecSourceManager) DeduceProjectRoot(ip string) (ProjectRoot, error) {
  1462  	fip := toFold(ip)
  1463  	for _, ds := range sm.allSpecs() {
  1464  		n := string(ds.n)
  1465  		if fip == n || strings.HasPrefix(fip, n+"/") {
  1466  			return ProjectRoot(ip[:len(n)]), nil
  1467  		}
  1468  	}
  1469  	return "", fmt.Errorf("could not find %s, or any parent, in list of known fixtures", ip)
  1470  }
  1471  
  1472  func (sm *depspecSourceManager) SourceURLsForPath(ip string) ([]*url.URL, error) {
  1473  	return nil, fmt.Errorf("dummy sm doesn't implement SourceURLsForPath")
  1474  }
  1475  
  1476  func (sm *depspecSourceManager) rootSpec() depspec {
  1477  	return sm.specs[0]
  1478  }
  1479  
  1480  func (sm *depspecSourceManager) allSpecs() []depspec {
  1481  	return sm.specs
  1482  }
  1483  
  1484  func (sm *depspecSourceManager) ignore() map[string]bool {
  1485  	return sm.ig
  1486  }
  1487  
  1488  // InferConstraint tries to puzzle out what kind of version is given in a string -
  1489  // semver, a revision, or as a fallback, a plain tag. This current implementation
  1490  // is a panic because there's no current circumstance under which the depspecSourceManager
  1491  // is useful outside of the gps solving tests, and it shouldn't be used anywhere else without a conscious and intentional
  1492  // expansion of its semantics.
  1493  func (sm *depspecSourceManager) InferConstraint(s string, pi ProjectIdentifier) (Constraint, error) {
  1494  	panic("depsecSourceManager is only for gps solving tests")
  1495  }
  1496  
  1497  type depspecBridge struct {
  1498  	*bridge
  1499  }
  1500  
  1501  func (b *depspecBridge) listVersions(id ProjectIdentifier) ([]Version, error) {
  1502  	if vl, exists := b.vlists[id]; exists {
  1503  		return vl, nil
  1504  	}
  1505  
  1506  	pvl, err := b.sm.ListVersions(id)
  1507  	if err != nil {
  1508  		return nil, err
  1509  	}
  1510  
  1511  	// Construct a []Version slice. If any paired versions use the fake rev,
  1512  	// remove the underlying component.
  1513  	vl := make([]Version, 0, len(pvl))
  1514  	for _, v := range pvl {
  1515  		if v.Revision() == "FAKEREV" {
  1516  			vl = append(vl, v.Unpair())
  1517  		} else {
  1518  			vl = append(vl, v)
  1519  		}
  1520  	}
  1521  
  1522  	if b.down {
  1523  		SortForDowngrade(vl)
  1524  	} else {
  1525  		SortForUpgrade(vl)
  1526  	}
  1527  
  1528  	b.vlists[id] = vl
  1529  	return vl, nil
  1530  }
  1531  
  1532  // override verifyRoot() on bridge to prevent any filesystem interaction
  1533  func (b *depspecBridge) verifyRootDir(path string) error {
  1534  	root := b.sm.(fixSM).rootSpec()
  1535  	if string(root.n) != path {
  1536  		return fmt.Errorf("expected only root project %q to verifyRootDir(), got %q", root.n, path)
  1537  	}
  1538  
  1539  	return nil
  1540  }
  1541  
  1542  func (b *depspecBridge) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) {
  1543  	return b.sm.(fixSM).ListPackages(id, v)
  1544  }
  1545  
  1546  func (b *depspecBridge) vendorCodeExists(id ProjectIdentifier) (bool, error) {
  1547  	return false, nil
  1548  }
  1549  
  1550  // enforce interfaces
  1551  var _ Manifest = depspec{}
  1552  var _ Lock = dummyLock{}
  1553  var _ Lock = fixLock{}
  1554  
  1555  // impl Spec interface
  1556  func (ds depspec) DependencyConstraints() ProjectConstraints {
  1557  	return pcSliceToMap(ds.deps)
  1558  }
  1559  
  1560  type fixLock []LockedProject
  1561  
  1562  // impl Lock interface
  1563  func (l fixLock) Projects() []LockedProject {
  1564  	return l
  1565  }
  1566  
  1567  // impl Lock interface
  1568  func (fixLock) InputImports() []string {
  1569  	return nil
  1570  }
  1571  
  1572  type dummyLock struct{}
  1573  
  1574  // impl Lock interface
  1575  func (dummyLock) Projects() []LockedProject {
  1576  	return nil
  1577  }
  1578  
  1579  // impl Lock interface
  1580  func (dummyLock) InputImports() []string {
  1581  	return nil
  1582  }