github.com/sdboyer/gps@v0.16.3/solve_basic_test.go (about)

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