github.com/golang/dep@v0.5.4/gps/solve_bimodal_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  	"fmt"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/golang/dep/gps/pkgtree"
    13  )
    14  
    15  // dsp - "depspec with packages"
    16  //
    17  // Wraps a set of tpkgs onto a depspec, and returns it.
    18  func dsp(ds depspec, pkgs ...tpkg) depspec {
    19  	ds.pkgs = pkgs
    20  	return ds
    21  }
    22  
    23  // pkg makes a tpkg appropriate for use in bimodal testing
    24  func pkg(path string, imports ...string) tpkg {
    25  	return tpkg{
    26  		path:    path,
    27  		imports: imports,
    28  	}
    29  }
    30  
    31  func init() {
    32  	for k, fix := range bimodalFixtures {
    33  		// Assign the name into the fixture itself
    34  		fix.n = k
    35  		bimodalFixtures[k] = fix
    36  	}
    37  }
    38  
    39  // Fixtures that rely on simulated bimodal (project and package-level)
    40  // analysis for correct operation. The name given in the map gets assigned into
    41  // the fixture itself in init().
    42  var bimodalFixtures = map[string]bimodalFixture{
    43  	// Simple case, ensures that we do the very basics of picking up and
    44  	// including a single, simple import that is not expressed as a constraint
    45  	"simple bm-add": {
    46  		ds: []depspec{
    47  			dsp(mkDepspec("root 0.0.0"),
    48  				pkg("root", "a")),
    49  			dsp(mkDepspec("a 1.0.0"),
    50  				pkg("a")),
    51  		},
    52  		r: mksolution(
    53  			"a 1.0.0",
    54  		),
    55  	},
    56  	// Ensure it works when the import jump is not from the package with the
    57  	// same path as root, but from a subpkg
    58  	"subpkg bm-add": {
    59  		ds: []depspec{
    60  			dsp(mkDepspec("root 0.0.0"),
    61  				pkg("root", "root/foo"),
    62  				pkg("root/foo", "a"),
    63  			),
    64  			dsp(mkDepspec("a 1.0.0"),
    65  				pkg("a"),
    66  			),
    67  		},
    68  		r: mksolution(
    69  			"a 1.0.0",
    70  		),
    71  	},
    72  	// The same, but with a jump through two subpkgs
    73  	"double-subpkg bm-add": {
    74  		ds: []depspec{
    75  			dsp(mkDepspec("root 0.0.0"),
    76  				pkg("root", "root/foo"),
    77  				pkg("root/foo", "root/bar"),
    78  				pkg("root/bar", "a"),
    79  			),
    80  			dsp(mkDepspec("a 1.0.0"),
    81  				pkg("a"),
    82  			),
    83  		},
    84  		r: mksolution(
    85  			"a 1.0.0",
    86  		),
    87  	},
    88  	// Same again, but now nest the subpkgs
    89  	"double nested subpkg bm-add": {
    90  		ds: []depspec{
    91  			dsp(mkDepspec("root 0.0.0"),
    92  				pkg("root", "root/foo"),
    93  				pkg("root/foo", "root/foo/bar"),
    94  				pkg("root/foo/bar", "a"),
    95  			),
    96  			dsp(mkDepspec("a 1.0.0"),
    97  				pkg("a"),
    98  			),
    99  		},
   100  		r: mksolution(
   101  			"a 1.0.0",
   102  		),
   103  	},
   104  	// Importing package from project with no root package
   105  	"bm-add on project with no pkg in root dir": {
   106  		ds: []depspec{
   107  			dsp(mkDepspec("root 0.0.0"),
   108  				pkg("root", "a/foo")),
   109  			dsp(mkDepspec("a 1.0.0"),
   110  				pkg("a/foo")),
   111  		},
   112  		r: mksolution(
   113  			mklp("a 1.0.0", "foo"),
   114  		),
   115  	},
   116  	// Import jump is in a dep, and points to a transitive dep
   117  	"transitive bm-add": {
   118  		ds: []depspec{
   119  			dsp(mkDepspec("root 0.0.0"),
   120  				pkg("root", "root/foo"),
   121  				pkg("root/foo", "a"),
   122  			),
   123  			dsp(mkDepspec("a 1.0.0"),
   124  				pkg("a", "b"),
   125  			),
   126  			dsp(mkDepspec("b 1.0.0"),
   127  				pkg("b"),
   128  			),
   129  		},
   130  		r: mksolution(
   131  			"a 1.0.0",
   132  			"b 1.0.0",
   133  		),
   134  	},
   135  	// Constraints apply only if the project that declares them has a
   136  	// reachable import
   137  	"constraints activated by import": {
   138  		ds: []depspec{
   139  			dsp(mkDepspec("root 0.0.0", "b 1.0.0"),
   140  				pkg("root", "root/foo"),
   141  				pkg("root/foo", "a"),
   142  			),
   143  			dsp(mkDepspec("a 1.0.0"),
   144  				pkg("a", "b"),
   145  			),
   146  			dsp(mkDepspec("b 1.0.0"),
   147  				pkg("b"),
   148  			),
   149  			dsp(mkDepspec("b 1.1.0"),
   150  				pkg("b"),
   151  			),
   152  		},
   153  		r: mksolution(
   154  			"a 1.0.0",
   155  			"b 1.1.0",
   156  		),
   157  	},
   158  	// Constraints apply only if the project that declares them has a
   159  	// reachable import - non-root
   160  	"constraints activated by import, transitive": {
   161  		ds: []depspec{
   162  			dsp(mkDepspec("root 0.0.0"),
   163  				pkg("root", "root/foo", "b"),
   164  				pkg("root/foo", "a"),
   165  			),
   166  			dsp(mkDepspec("a 1.0.0", "b 1.0.0"),
   167  				pkg("a"),
   168  			),
   169  			dsp(mkDepspec("b 1.0.0"),
   170  				pkg("b"),
   171  			),
   172  			dsp(mkDepspec("b 1.1.0"),
   173  				pkg("b"),
   174  			),
   175  		},
   176  		r: mksolution(
   177  			"a 1.0.0",
   178  			"b 1.1.0",
   179  		),
   180  	},
   181  	// Import jump is in a dep, and points to a transitive dep - but only in not
   182  	// the first version we try
   183  	"transitive bm-add on older version": {
   184  		ds: []depspec{
   185  			dsp(mkDepspec("root 0.0.0", "a ~1.0.0"),
   186  				pkg("root", "root/foo"),
   187  				pkg("root/foo", "a"),
   188  			),
   189  			dsp(mkDepspec("a 1.0.0"),
   190  				pkg("a", "b"),
   191  			),
   192  			dsp(mkDepspec("a 1.1.0"),
   193  				pkg("a"),
   194  			),
   195  			dsp(mkDepspec("b 1.0.0"),
   196  				pkg("b"),
   197  			),
   198  		},
   199  		r: mksolution(
   200  			"a 1.0.0",
   201  			"b 1.0.0",
   202  		),
   203  	},
   204  	// Import jump is in a dep, and points to a transitive dep - but will only
   205  	// get there via backtracking
   206  	"backtrack to dep on bm-add": {
   207  		ds: []depspec{
   208  			dsp(mkDepspec("root 0.0.0"),
   209  				pkg("root", "root/foo"),
   210  				pkg("root/foo", "a", "b"),
   211  			),
   212  			dsp(mkDepspec("a 1.0.0"),
   213  				pkg("a", "c"),
   214  			),
   215  			dsp(mkDepspec("a 1.1.0"),
   216  				pkg("a"),
   217  			),
   218  			// Include two versions of b, otherwise it'll be selected first
   219  			dsp(mkDepspec("b 0.9.0"),
   220  				pkg("b", "c"),
   221  			),
   222  			dsp(mkDepspec("b 1.0.0"),
   223  				pkg("b", "c"),
   224  			),
   225  			dsp(mkDepspec("c 1.0.0", "a 1.0.0"),
   226  				pkg("c", "a"),
   227  			),
   228  		},
   229  		r: mksolution(
   230  			"a 1.0.0",
   231  			"b 1.0.0",
   232  			"c 1.0.0",
   233  		),
   234  	},
   235  	"backjump through pkg-only selection": {
   236  		ds: []depspec{
   237  			dsp(mkDepspec("root 0.0.0"),
   238  				pkg("root", "root/foo"),
   239  				pkg("root/foo", "a", "b"),
   240  			),
   241  			dsp(mkDepspec("a 1.0.0"),
   242  				pkg("a", "c"),
   243  			),
   244  			// Include two versions of b to ensure that a is visited first
   245  			dsp(mkDepspec("b 0.9.0", "d ^1.0.0"),
   246  				pkg("b", "c/other", "d"),
   247  			),
   248  			dsp(mkDepspec("b 1.0.0", "d ^1.2.0"),
   249  				pkg("b", "c/other", "d"),
   250  			),
   251  			// Three versions of c so it's last
   252  			dsp(mkDepspec("c 1.0.0", "d ^1.0.0"),
   253  				pkg("c", "d"),
   254  				pkg("c/other"),
   255  			),
   256  			dsp(mkDepspec("d 1.0.0"),
   257  				pkg("d"),
   258  			),
   259  			dsp(mkDepspec("d 1.1.0"),
   260  				pkg("d"),
   261  			),
   262  		},
   263  		r: mksolution(
   264  			"a 1.0.0",
   265  			"b 0.9.0",
   266  			mklp("c 1.0.0", ".", "other"),
   267  			"d 1.1.0",
   268  		),
   269  	},
   270  	// Import jump is in a dep subpkg, and points to a transitive dep
   271  	"transitive subpkg bm-add": {
   272  		ds: []depspec{
   273  			dsp(mkDepspec("root 0.0.0"),
   274  				pkg("root", "root/foo"),
   275  				pkg("root/foo", "a"),
   276  			),
   277  			dsp(mkDepspec("a 1.0.0"),
   278  				pkg("a", "a/bar"),
   279  				pkg("a/bar", "b"),
   280  			),
   281  			dsp(mkDepspec("b 1.0.0"),
   282  				pkg("b"),
   283  			),
   284  		},
   285  		r: mksolution(
   286  			mklp("a 1.0.0", ".", "bar"),
   287  			"b 1.0.0",
   288  		),
   289  	},
   290  	// Import jump is in a dep subpkg, pointing to a transitive dep, but only in
   291  	// not the first version we try
   292  	"transitive subpkg bm-add on older version": {
   293  		ds: []depspec{
   294  			dsp(mkDepspec("root 0.0.0", "a ~1.0.0"),
   295  				pkg("root", "root/foo"),
   296  				pkg("root/foo", "a"),
   297  			),
   298  			dsp(mkDepspec("a 1.0.0"),
   299  				pkg("a", "a/bar"),
   300  				pkg("a/bar", "b"),
   301  			),
   302  			dsp(mkDepspec("a 1.1.0"),
   303  				pkg("a", "a/bar"),
   304  				pkg("a/bar"),
   305  			),
   306  			dsp(mkDepspec("b 1.0.0"),
   307  				pkg("b"),
   308  			),
   309  		},
   310  		r: mksolution(
   311  			mklp("a 1.0.0", ".", "bar"),
   312  			"b 1.0.0",
   313  		),
   314  	},
   315  	"project cycle involving root": {
   316  		ds: []depspec{
   317  			dsp(mkDepspec("root 0.0.0", "a ~1.0.0"),
   318  				pkg("root", "a"),
   319  				pkg("root/foo"),
   320  			),
   321  			dsp(mkDepspec("a 1.0.0"),
   322  				pkg("a", "root/foo"),
   323  			),
   324  		},
   325  		r: mksolution(
   326  			"a 1.0.0",
   327  		),
   328  	},
   329  	"project cycle involving root with backtracking": {
   330  		ds: []depspec{
   331  			dsp(mkDepspec("root 0.0.0", "a ~1.0.0"),
   332  				pkg("root", "a", "b"),
   333  				pkg("root/foo"),
   334  			),
   335  			dsp(mkDepspec("a 1.0.0"),
   336  				pkg("a", "root/foo"),
   337  			),
   338  			dsp(mkDepspec("a 1.0.1"),
   339  				pkg("a", "root/foo"),
   340  			),
   341  			dsp(mkDepspec("b 1.0.0", "a 1.0.0"),
   342  				pkg("b", "a"),
   343  			),
   344  			dsp(mkDepspec("b 1.0.1", "a 1.0.0"),
   345  				pkg("b", "a"),
   346  			),
   347  			dsp(mkDepspec("b 1.0.2", "a 1.0.0"),
   348  				pkg("b", "a"),
   349  			),
   350  		},
   351  		r: mksolution(
   352  			"a 1.0.0",
   353  			"b 1.0.2",
   354  		),
   355  	},
   356  	"unify project on disjoint package imports + source switching": {
   357  		ds: []depspec{
   358  			dsp(mkDepspec("root 0.0.0", "b from baz 1.0.0"),
   359  				pkg("root", "a", "b"),
   360  			),
   361  			dsp(mkDepspec("a 1.0.0"),
   362  				pkg("a", "b/foo"),
   363  			),
   364  			dsp(mkDepspec("b 1.0.0"),
   365  				pkg("b"),
   366  				pkg("b/foo"),
   367  			),
   368  			dsp(mkDepspec("baz 1.0.0"),
   369  				pkg("b"),
   370  				pkg("b/foo"),
   371  			),
   372  		},
   373  		r: mksolution(
   374  			"a 1.0.0",
   375  			mklp("b from baz 1.0.0", ".", "foo"),
   376  		),
   377  	},
   378  	"project cycle not involving root": {
   379  		ds: []depspec{
   380  			dsp(mkDepspec("root 0.0.0", "a ~1.0.0"),
   381  				pkg("root", "a"),
   382  			),
   383  			dsp(mkDepspec("a 1.0.0"),
   384  				pkg("a", "b"),
   385  				pkg("a/foo"),
   386  			),
   387  			dsp(mkDepspec("b 1.0.0"),
   388  				pkg("b", "a/foo"),
   389  			),
   390  		},
   391  		r: mksolution(
   392  			mklp("a 1.0.0", ".", "foo"),
   393  			"b 1.0.0",
   394  		),
   395  	},
   396  	"project cycle not involving root with internal paths": {
   397  		ds: []depspec{
   398  			dsp(mkDepspec("root 0.0.0", "a ~1.0.0"),
   399  				pkg("root", "a"),
   400  			),
   401  			dsp(mkDepspec("a 1.0.0"),
   402  				pkg("a", "b/baz"),
   403  				pkg("a/foo", "a/quux", "a/quark"),
   404  				pkg("a/quux"),
   405  				pkg("a/quark"),
   406  			),
   407  			dsp(mkDepspec("b 1.0.0"),
   408  				pkg("b", "a/foo"),
   409  				pkg("b/baz", "b"),
   410  			),
   411  		},
   412  		r: mksolution(
   413  			mklp("a 1.0.0", ".", "foo", "quark", "quux"),
   414  			mklp("b 1.0.0", ".", "baz"),
   415  		),
   416  	},
   417  	// Ensure that if a constraint is expressed, but no actual import exists,
   418  	// then the constraint is disregarded - the project named in the constraint
   419  	// is not part of the solution.
   420  	"ignore constraint without import": {
   421  		ds: []depspec{
   422  			dsp(mkDepspec("root 0.0.0", "a 1.0.0"),
   423  				pkg("root", "root/foo"),
   424  				pkg("root/foo"),
   425  			),
   426  			dsp(mkDepspec("a 1.0.0"),
   427  				pkg("a"),
   428  			),
   429  		},
   430  		r: mksolution(),
   431  	},
   432  	// Transitive deps from one project (a) get incrementally included as other
   433  	// deps incorporate its various packages.
   434  	"multi-stage pkg incorporation": {
   435  		ds: []depspec{
   436  			dsp(mkDepspec("root 0.0.0"),
   437  				pkg("root", "a", "d"),
   438  			),
   439  			dsp(mkDepspec("a 1.0.0"),
   440  				pkg("a", "b"),
   441  				pkg("a/second", "c"),
   442  			),
   443  			dsp(mkDepspec("b 2.0.0"),
   444  				pkg("b"),
   445  			),
   446  			dsp(mkDepspec("c 1.2.0"),
   447  				pkg("c"),
   448  			),
   449  			dsp(mkDepspec("d 1.0.0"),
   450  				pkg("d", "a/second"),
   451  			),
   452  		},
   453  		r: mksolution(
   454  			mklp("a 1.0.0", ".", "second"),
   455  			"b 2.0.0",
   456  			"c 1.2.0",
   457  			"d 1.0.0",
   458  		),
   459  	},
   460  	// Regression - make sure that the the constraint/import intersector only
   461  	// accepts a project 'match' if exactly equal, or a separating slash is
   462  	// present.
   463  	"radix path separator post-check": {
   464  		ds: []depspec{
   465  			dsp(mkDepspec("root 0.0.0"),
   466  				pkg("root", "foo", "foobar"),
   467  			),
   468  			dsp(mkDepspec("foo 1.0.0"),
   469  				pkg("foo"),
   470  			),
   471  			dsp(mkDepspec("foobar 1.0.0"),
   472  				pkg("foobar"),
   473  			),
   474  		},
   475  		r: mksolution(
   476  			"foo 1.0.0",
   477  			"foobar 1.0.0",
   478  		),
   479  	},
   480  	// Well-formed failure when there's a dependency on a pkg that doesn't exist
   481  	"fail when imports nonexistent package": {
   482  		ds: []depspec{
   483  			dsp(mkDepspec("root 0.0.0", "a 1.0.0"),
   484  				pkg("root", "a/foo"),
   485  			),
   486  			dsp(mkDepspec("a 1.0.0"),
   487  				pkg("a"),
   488  			),
   489  		},
   490  		fail: &noVersionError{
   491  			pn: mkPI("a"),
   492  			fails: []failedVersion{
   493  				{
   494  					v: NewVersion("1.0.0"),
   495  					f: &checkeeHasProblemPackagesFailure{
   496  						goal: mkAtom("a 1.0.0"),
   497  						failpkg: map[string]errDeppers{
   498  							"a/foo": {
   499  								err: nil, // nil indicates package is missing
   500  								deppers: []atom{
   501  									mkAtom("root"),
   502  								},
   503  							},
   504  						},
   505  					},
   506  				},
   507  			},
   508  		},
   509  	},
   510  	// Transitive deps from one project (a) get incrementally included as other
   511  	// deps incorporate its various packages, and fail with proper error when we
   512  	// discover one incrementally that isn't present
   513  	"fail multi-stage missing pkg": {
   514  		ds: []depspec{
   515  			dsp(mkDepspec("root 0.0.0"),
   516  				pkg("root", "a", "d"),
   517  			),
   518  			dsp(mkDepspec("a 1.0.0"),
   519  				pkg("a", "b"),
   520  				pkg("a/second", "c"),
   521  			),
   522  			dsp(mkDepspec("b 2.0.0"),
   523  				pkg("b"),
   524  			),
   525  			dsp(mkDepspec("c 1.2.0"),
   526  				pkg("c"),
   527  			),
   528  			dsp(mkDepspec("d 1.0.0"),
   529  				pkg("d", "a/second"),
   530  				pkg("d", "a/nonexistent"),
   531  			),
   532  		},
   533  		fail: &noVersionError{
   534  			pn: mkPI("d"),
   535  			fails: []failedVersion{
   536  				{
   537  					v: NewVersion("1.0.0"),
   538  					f: &depHasProblemPackagesFailure{
   539  						goal: mkADep("d 1.0.0", "a", Any(), "a/nonexistent"),
   540  						v:    NewVersion("1.0.0"),
   541  						prob: map[string]error{
   542  							"a/nonexistent": nil,
   543  						},
   544  					},
   545  				},
   546  			},
   547  		},
   548  	},
   549  	// Check ignores on the root project
   550  	"ignore in double-subpkg": {
   551  		ds: []depspec{
   552  			dsp(mkDepspec("root 0.0.0"),
   553  				pkg("root", "root/foo"),
   554  				pkg("root/foo", "root/bar", "b"),
   555  				pkg("root/bar", "a"),
   556  			),
   557  			dsp(mkDepspec("a 1.0.0"),
   558  				pkg("a"),
   559  			),
   560  			dsp(mkDepspec("b 1.0.0"),
   561  				pkg("b"),
   562  			),
   563  		},
   564  		ignore: []string{"root/bar"},
   565  		r: mksolution(
   566  			"b 1.0.0",
   567  		),
   568  	},
   569  	// Ignores on a dep pkg
   570  	"ignore through dep pkg": {
   571  		ds: []depspec{
   572  			dsp(mkDepspec("root 0.0.0"),
   573  				pkg("root", "root/foo"),
   574  				pkg("root/foo", "a"),
   575  			),
   576  			dsp(mkDepspec("a 1.0.0"),
   577  				pkg("a", "a/bar"),
   578  				pkg("a/bar", "b"),
   579  			),
   580  			dsp(mkDepspec("b 1.0.0"),
   581  				pkg("b"),
   582  			),
   583  		},
   584  		ignore: []string{"a/bar"},
   585  		r: mksolution(
   586  			"a 1.0.0",
   587  		),
   588  	},
   589  	// Preferred version, as derived from a dep's lock, is attempted first
   590  	"respect prefv, simple case": {
   591  		ds: []depspec{
   592  			dsp(mkDepspec("root 0.0.0"),
   593  				pkg("root", "a")),
   594  			dsp(mkDepspec("a 1.0.0"),
   595  				pkg("a", "b")),
   596  			dsp(mkDepspec("b 1.0.0 foorev"),
   597  				pkg("b")),
   598  			dsp(mkDepspec("b 2.0.0 barrev"),
   599  				pkg("b")),
   600  		},
   601  		lm: map[string]fixLock{
   602  			"a 1.0.0": mklock(
   603  				"b 1.0.0 foorev",
   604  			),
   605  		},
   606  		r: mksolution(
   607  			"a 1.0.0",
   608  			"b 1.0.0 foorev",
   609  		),
   610  	},
   611  	// Preferred version, as derived from a dep's lock, is attempted first, even
   612  	// if the root also has a direct dep on it (root doesn't need to use
   613  	// preferreds, because it has direct control AND because the root lock
   614  	// already supersedes dep lock "preferences")
   615  	"respect dep prefv with root import": {
   616  		ds: []depspec{
   617  			dsp(mkDepspec("root 0.0.0"),
   618  				pkg("root", "a", "b")),
   619  			dsp(mkDepspec("a 1.0.0"),
   620  				pkg("a", "b")),
   621  			//dsp(newDepspec("a 1.0.1"),
   622  			//pkg("a", "b")),
   623  			//dsp(newDepspec("a 1.1.0"),
   624  			//pkg("a", "b")),
   625  			dsp(mkDepspec("b 1.0.0 foorev"),
   626  				pkg("b")),
   627  			dsp(mkDepspec("b 2.0.0 barrev"),
   628  				pkg("b")),
   629  		},
   630  		lm: map[string]fixLock{
   631  			"a 1.0.0": mklock(
   632  				"b 1.0.0 foorev",
   633  			),
   634  		},
   635  		r: mksolution(
   636  			"a 1.0.0",
   637  			"b 1.0.0 foorev",
   638  		),
   639  	},
   640  	// Preferred versions can only work if the thing offering it has been
   641  	// selected, or at least marked in the unselected queue
   642  	"prefv only works if depper is selected": {
   643  		ds: []depspec{
   644  			dsp(mkDepspec("root 0.0.0"),
   645  				pkg("root", "a", "b")),
   646  			// Three atoms for a, which will mean it gets visited after b
   647  			dsp(mkDepspec("a 1.0.0"),
   648  				pkg("a", "b")),
   649  			dsp(mkDepspec("a 1.0.1"),
   650  				pkg("a", "b")),
   651  			dsp(mkDepspec("a 1.1.0"),
   652  				pkg("a", "b")),
   653  			dsp(mkDepspec("b 1.0.0 foorev"),
   654  				pkg("b")),
   655  			dsp(mkDepspec("b 2.0.0 barrev"),
   656  				pkg("b")),
   657  		},
   658  		lm: map[string]fixLock{
   659  			"a 1.0.0": mklock(
   660  				"b 1.0.0 foorev",
   661  			),
   662  		},
   663  		r: mksolution(
   664  			"a 1.1.0",
   665  			"b 2.0.0 barrev",
   666  		),
   667  	},
   668  	"override unconstrained root import": {
   669  		ds: []depspec{
   670  			dsp(mkDepspec("root 0.0.0"),
   671  				pkg("root", "a")),
   672  			dsp(mkDepspec("a 1.0.0"),
   673  				pkg("a")),
   674  			dsp(mkDepspec("a 2.0.0"),
   675  				pkg("a")),
   676  		},
   677  		ovr: ProjectConstraints{
   678  			ProjectRoot("a"): ProjectProperties{
   679  				Constraint: NewVersion("1.0.0"),
   680  			},
   681  		},
   682  		r: mksolution(
   683  			"a 1.0.0",
   684  		),
   685  	},
   686  	"simple case-only differences": {
   687  		ds: []depspec{
   688  			dsp(mkDepspec("root 0.0.0"),
   689  				pkg("root", "foo", "bar")),
   690  			dsp(mkDepspec("foo 1.0.0"),
   691  				pkg("foo", "Bar")),
   692  			dsp(mkDepspec("bar 1.0.0"),
   693  				pkg("bar")),
   694  		},
   695  		fail: &noVersionError{
   696  			pn: mkPI("foo"),
   697  			fails: []failedVersion{
   698  				{
   699  					v: NewVersion("1.0.0"),
   700  					f: &caseMismatchFailure{
   701  						goal:    mkDep("foo 1.0.0", "Bar 1.0.0", "Bar"),
   702  						current: ProjectRoot("bar"),
   703  						failsib: []dependency{mkDep("root", "bar 1.0.0", "bar")},
   704  					},
   705  				},
   706  			},
   707  		},
   708  	},
   709  	"case variations acceptable with agreement": {
   710  		ds: []depspec{
   711  			dsp(mkDepspec("root 0.0.0"),
   712  				pkg("root", "foo")),
   713  			dsp(mkDepspec("foo 1.0.0"),
   714  				pkg("foo", "Bar", "baz")),
   715  			dsp(mkDepspec("baz 1.0.0"),
   716  				pkg("baz", "Bar")),
   717  			dsp(mkDepspec("bar 1.0.0"),
   718  				pkg("bar")),
   719  		},
   720  		r: mksolution(
   721  			"foo 1.0.0",
   722  			"Bar 1.0.0",
   723  			"baz 1.0.0",
   724  		),
   725  	},
   726  	"case variations within root": {
   727  		ds: []depspec{
   728  			dsp(mkDepspec("root 0.0.0"),
   729  				pkg("root", "foo", "bar", "Bar")),
   730  			dsp(mkDepspec("foo 1.0.0"),
   731  				pkg("foo")),
   732  			dsp(mkDepspec("bar 1.0.0"),
   733  				pkg("bar")),
   734  		},
   735  		fail: &noVersionError{
   736  			pn: mkPI("foo"),
   737  			fails: []failedVersion{
   738  				{
   739  					v: NewVersion("1.0.0"),
   740  					f: &caseMismatchFailure{
   741  						goal:    mkDep("foo 1.0.0", "Bar 1.0.0", "Bar"),
   742  						current: ProjectRoot("bar"),
   743  						failsib: []dependency{mkDep("root", "foo 1.0.0", "foo")},
   744  					},
   745  				},
   746  			},
   747  		},
   748  		broken: "need to implement checking for import case variations *within* the root",
   749  	},
   750  	"case variations within single dep": {
   751  		ds: []depspec{
   752  			dsp(mkDepspec("root 0.0.0"),
   753  				pkg("root", "foo")),
   754  			dsp(mkDepspec("foo 1.0.0"),
   755  				pkg("foo", "bar", "Bar")),
   756  			dsp(mkDepspec("bar 1.0.0"),
   757  				pkg("bar")),
   758  		},
   759  		fail: &noVersionError{
   760  			pn: mkPI("foo"),
   761  			fails: []failedVersion{
   762  				{
   763  					v: NewVersion("1.0.0"),
   764  					f: &caseMismatchFailure{
   765  						goal:    mkDep("foo 1.0.0", "Bar 1.0.0", "Bar"),
   766  						current: ProjectRoot("bar"),
   767  						failsib: []dependency{mkDep("root", "foo 1.0.0", "foo")},
   768  					},
   769  				},
   770  			},
   771  		},
   772  		broken: "need to implement checking for import case variations *within* a single project",
   773  	},
   774  	"case variations across multiple deps": {
   775  		ds: []depspec{
   776  			dsp(mkDepspec("root 0.0.0"),
   777  				pkg("root", "foo", "bar")),
   778  			dsp(mkDepspec("foo 1.0.0"),
   779  				pkg("foo", "bar", "baz")),
   780  			dsp(mkDepspec("baz 1.0.0"),
   781  				pkg("baz", "Bar")),
   782  			dsp(mkDepspec("bar 1.0.0"),
   783  				pkg("bar")),
   784  		},
   785  		fail: &noVersionError{
   786  			pn: mkPI("baz"),
   787  			fails: []failedVersion{
   788  				{
   789  					v: NewVersion("1.0.0"),
   790  					f: &caseMismatchFailure{
   791  						goal:    mkDep("baz 1.0.0", "Bar 1.0.0", "Bar"),
   792  						current: ProjectRoot("bar"),
   793  						failsib: []dependency{
   794  							mkDep("root", "bar 1.0.0", "bar"),
   795  							mkDep("foo 1.0.0", "bar 1.0.0", "bar"),
   796  						},
   797  					},
   798  				},
   799  			},
   800  		},
   801  	},
   802  	// This isn't actually as crazy as it might seem, as the root is defined by
   803  	// the addresser, not the addressee. It would occur (to provide a
   804  	// real-as-of-this-writing example) if something imports
   805  	// github.com/Sirupsen/logrus, as the contained subpackage at
   806  	// github.com/Sirupsen/logrus/hooks/syslog imports
   807  	// github.com/sirupsen/logrus. The only reason that doesn't blow up all the
   808  	// time is that most people only import the root package, not the syslog
   809  	// subpackage.
   810  	"canonical case is established by mutual self-imports": {
   811  		ds: []depspec{
   812  			dsp(mkDepspec("root 0.0.0"),
   813  				pkg("root", "foo")),
   814  			dsp(mkDepspec("foo 1.0.0"),
   815  				pkg("foo", "Bar")),
   816  			dsp(mkDepspec("bar 1.0.0"),
   817  				pkg("bar", "bar/subpkg"),
   818  				pkg("bar/subpkg")),
   819  		},
   820  		fail: &noVersionError{
   821  			pn: mkPI("Bar"),
   822  			fails: []failedVersion{
   823  				{
   824  					v: NewVersion("1.0.0"),
   825  					f: &wrongCaseFailure{
   826  						correct: ProjectRoot("bar"),
   827  						goal:    mkDep("Bar 1.0.0", "bar 1.0.0", "bar"),
   828  						badcase: []dependency{mkDep("foo 1.0.0", "Bar 1.0.0", "Bar/subpkg")},
   829  					},
   830  				},
   831  			},
   832  		},
   833  	},
   834  	"canonical case only applies if relevant imports are activated": {
   835  		ds: []depspec{
   836  			dsp(mkDepspec("root 0.0.0"),
   837  				pkg("root", "foo")),
   838  			dsp(mkDepspec("foo 1.0.0"),
   839  				pkg("foo", "Bar/subpkg")),
   840  			dsp(mkDepspec("bar 1.0.0"),
   841  				pkg("bar", "bar/subpkg"),
   842  				pkg("bar/subpkg")),
   843  		},
   844  		r: mksolution(
   845  			"foo 1.0.0",
   846  			mklp("Bar 1.0.0", "subpkg"),
   847  		),
   848  	},
   849  	"simple case-only variations plus source variance": {
   850  		ds: []depspec{
   851  			dsp(mkDepspec("root 0.0.0"),
   852  				pkg("root", "foo", "bar")),
   853  			dsp(mkDepspec("foo 1.0.0", "Bar from quux 1.0.0"),
   854  				pkg("foo", "Bar")),
   855  			dsp(mkDepspec("bar 1.0.0"),
   856  				pkg("bar")),
   857  			dsp(mkDepspec("quux 1.0.0"),
   858  				pkg("bar")),
   859  		},
   860  		fail: &noVersionError{
   861  			pn: mkPI("foo"),
   862  			fails: []failedVersion{
   863  				{
   864  					v: NewVersion("1.0.0"),
   865  					f: &caseMismatchFailure{
   866  						goal:    mkDep("foo 1.0.0", "Bar from quux 1.0.0", "Bar"),
   867  						current: ProjectRoot("bar"),
   868  						failsib: []dependency{mkDep("root", "bar 1.0.0", "bar")},
   869  					},
   870  				},
   871  			},
   872  		},
   873  	},
   874  	"case-only variations plus source variance with internal canonicality": {
   875  		ds: []depspec{
   876  			dsp(mkDepspec("root 0.0.0", "Bar from quux 1.0.0"),
   877  				pkg("root", "foo", "Bar")),
   878  			dsp(mkDepspec("foo 1.0.0", "Bar from quux 1.0.0"),
   879  				pkg("foo", "Bar")),
   880  			dsp(mkDepspec("bar 1.0.0"),
   881  				pkg("bar", "bar/subpkg"),
   882  				pkg("bar/subpkg")),
   883  			dsp(mkDepspec("quux 1.0.0"),
   884  				pkg("bar", "bar/subpkg"),
   885  				pkg("bar/subpkg")),
   886  		},
   887  		fail: &noVersionError{
   888  			pn: mkPI("Bar"),
   889  			fails: []failedVersion{
   890  				{
   891  					v: NewVersion("1.0.0"),
   892  					f: &wrongCaseFailure{
   893  						correct: ProjectRoot("bar"),
   894  						goal:    mkDep("Bar from quux 1.0.0", "bar 1.0.0", "bar"),
   895  						badcase: []dependency{mkDep("root", "Bar 1.0.0", "Bar/subpkg")},
   896  					},
   897  				},
   898  			},
   899  		},
   900  	},
   901  	"alternate net address": {
   902  		ds: []depspec{
   903  			dsp(mkDepspec("root 1.0.0", "foo from bar 2.0.0"),
   904  				pkg("root", "foo")),
   905  			dsp(mkDepspec("foo 1.0.0"),
   906  				pkg("foo")),
   907  			dsp(mkDepspec("foo 2.0.0"),
   908  				pkg("foo")),
   909  			dsp(mkDepspec("bar 1.0.0"),
   910  				pkg("foo")),
   911  			dsp(mkDepspec("bar 2.0.0"),
   912  				pkg("foo")),
   913  		},
   914  		r: mksolution(
   915  			"foo from bar 2.0.0",
   916  		),
   917  	},
   918  	"alternate net address, version only in alt": {
   919  		ds: []depspec{
   920  			dsp(mkDepspec("root 1.0.0", "foo from bar 2.0.0"),
   921  				pkg("root", "foo")),
   922  			dsp(mkDepspec("foo 1.0.0"),
   923  				pkg("foo")),
   924  			dsp(mkDepspec("bar 1.0.0"),
   925  				pkg("foo")),
   926  			dsp(mkDepspec("bar 2.0.0"),
   927  				pkg("foo")),
   928  		},
   929  		r: mksolution(
   930  			"foo from bar 2.0.0",
   931  		),
   932  	},
   933  	"alternate net address in dep": {
   934  		ds: []depspec{
   935  			dsp(mkDepspec("root 1.0.0", "foo 1.0.0"),
   936  				pkg("root", "foo")),
   937  			dsp(mkDepspec("foo 1.0.0", "bar from baz 2.0.0"),
   938  				pkg("foo", "bar")),
   939  			dsp(mkDepspec("bar 1.0.0"),
   940  				pkg("bar")),
   941  			dsp(mkDepspec("baz 1.0.0"),
   942  				pkg("bar")),
   943  			dsp(mkDepspec("baz 2.0.0"),
   944  				pkg("bar")),
   945  		},
   946  		r: mksolution(
   947  			"foo 1.0.0",
   948  			"bar from baz 2.0.0",
   949  		),
   950  	},
   951  	// Because NOT specifying an alternate net address for a given import path
   952  	// is taken as an "eh, whatever", if we see an empty net addr after
   953  	// something else has already set an alternate one, then the second should
   954  	// just "go along" with whatever's already been specified.
   955  	"alternate net address with second depper": {
   956  		ds: []depspec{
   957  			dsp(mkDepspec("root 1.0.0", "foo from bar 2.0.0"),
   958  				pkg("root", "foo", "baz")),
   959  			dsp(mkDepspec("foo 1.0.0"),
   960  				pkg("foo")),
   961  			dsp(mkDepspec("foo 2.0.0"),
   962  				pkg("foo")),
   963  			dsp(mkDepspec("bar 1.0.0"),
   964  				pkg("foo")),
   965  			dsp(mkDepspec("bar 2.0.0"),
   966  				pkg("foo")),
   967  			dsp(mkDepspec("baz 1.0.0"),
   968  				pkg("baz", "foo")),
   969  		},
   970  		r: mksolution(
   971  			"foo from bar 2.0.0",
   972  			"baz 1.0.0",
   973  		),
   974  	},
   975  	// Same as the previous, except the alternate declaration originates in a
   976  	// dep, not the root.
   977  	"alternate net addr from dep, with second default depper": {
   978  		ds: []depspec{
   979  			dsp(mkDepspec("root 1.0.0", "foo 1.0.0"),
   980  				pkg("root", "foo", "bar")),
   981  			dsp(mkDepspec("foo 1.0.0", "bar 2.0.0"),
   982  				pkg("foo", "baz")),
   983  			dsp(mkDepspec("foo 2.0.0", "bar 2.0.0"),
   984  				pkg("foo", "baz")),
   985  			dsp(mkDepspec("bar 2.0.0", "baz from quux 1.0.0"),
   986  				pkg("bar", "baz")),
   987  			dsp(mkDepspec("baz 1.0.0"),
   988  				pkg("baz")),
   989  			dsp(mkDepspec("baz 2.0.0"),
   990  				pkg("baz")),
   991  			dsp(mkDepspec("quux 1.0.0"),
   992  				pkg("baz")),
   993  		},
   994  		r: mksolution(
   995  			"foo 1.0.0",
   996  			"bar 2.0.0",
   997  			"baz from quux 1.0.0",
   998  		),
   999  	},
  1000  	// When a given project is initially brought in using the default (i.e.,
  1001  	// empty) ProjectIdentifier.Source, and a later, presumably
  1002  	// as-yet-undiscovered dependency specifies an alternate net addr for it, we
  1003  	// have to fail - even though, if the deps were visited in the opposite
  1004  	// order (deeper dep w/the alternate location first, default location
  1005  	// second), it would be fine.
  1006  	//
  1007  	// TODO A better solution here would involve restarting the solver w/a
  1008  	// marker to use that alternate, or (ugh) introducing a new failure
  1009  	// path/marker type that changes how backtracking works. (In fact, these
  1010  	// approaches are probably demonstrably equivalent.)
  1011  	"fails with net mismatch when deeper dep specs it": {
  1012  		ds: []depspec{
  1013  			dsp(mkDepspec("root 1.0.0", "foo 1.0.0"),
  1014  				pkg("root", "foo", "baz")),
  1015  			dsp(mkDepspec("foo 1.0.0", "bar 2.0.0"),
  1016  				pkg("foo", "bar")),
  1017  			dsp(mkDepspec("bar 2.0.0", "baz from quux 1.0.0"),
  1018  				pkg("bar", "baz")),
  1019  			dsp(mkDepspec("baz 1.0.0"),
  1020  				pkg("baz")),
  1021  			dsp(mkDepspec("quux 1.0.0"),
  1022  				pkg("baz")),
  1023  		},
  1024  		fail: &noVersionError{
  1025  			pn: mkPI("bar"),
  1026  			fails: []failedVersion{
  1027  				{
  1028  					v: NewVersion("2.0.0"),
  1029  					f: &sourceMismatchFailure{
  1030  						shared:   ProjectRoot("baz"),
  1031  						current:  "baz",
  1032  						mismatch: "quux",
  1033  						prob:     mkAtom("bar 2.0.0"),
  1034  						sel:      []dependency{mkDep("foo 1.0.0", "bar 2.0.0", "bar")},
  1035  					},
  1036  				},
  1037  			},
  1038  		},
  1039  	},
  1040  	"with mismatched net addrs": {
  1041  		ds: []depspec{
  1042  			dsp(mkDepspec("root 1.0.0", "foo 1.0.0", "bar 1.0.0"),
  1043  				pkg("root", "foo", "bar")),
  1044  			dsp(mkDepspec("foo 1.0.0", "bar from baz 1.0.0"),
  1045  				pkg("foo", "bar")),
  1046  			dsp(mkDepspec("bar 1.0.0"),
  1047  				pkg("bar")),
  1048  			dsp(mkDepspec("baz 1.0.0"),
  1049  				pkg("bar")),
  1050  		},
  1051  		fail: &noVersionError{
  1052  			pn: mkPI("foo"),
  1053  			fails: []failedVersion{
  1054  				{
  1055  					v: NewVersion("1.0.0"),
  1056  					f: &sourceMismatchFailure{
  1057  						shared:   ProjectRoot("bar"),
  1058  						current:  "bar",
  1059  						mismatch: "baz",
  1060  						prob:     mkAtom("foo 1.0.0"),
  1061  						sel:      []dependency{mkDep("root", "foo 1.0.0", "foo")},
  1062  					},
  1063  				},
  1064  			},
  1065  		},
  1066  	},
  1067  	"overridden mismatched net addrs, alt in dep": {
  1068  		ds: []depspec{
  1069  			dsp(mkDepspec("root 0.0.0"),
  1070  				pkg("root", "foo")),
  1071  			dsp(mkDepspec("foo 1.0.0", "bar from baz 1.0.0"),
  1072  				pkg("foo", "bar")),
  1073  			dsp(mkDepspec("bar 1.0.0"),
  1074  				pkg("bar")),
  1075  			dsp(mkDepspec("baz 1.0.0"),
  1076  				pkg("bar")),
  1077  		},
  1078  		ovr: ProjectConstraints{
  1079  			ProjectRoot("bar"): ProjectProperties{
  1080  				Source: "baz",
  1081  			},
  1082  		},
  1083  		r: mksolution(
  1084  			"foo 1.0.0",
  1085  			"bar from baz 1.0.0",
  1086  		),
  1087  	},
  1088  	"overridden mismatched net addrs, alt in root": {
  1089  		ds: []depspec{
  1090  			dsp(mkDepspec("root 0.0.0", "bar from baz 1.0.0"),
  1091  				pkg("root", "foo")),
  1092  			dsp(mkDepspec("foo 1.0.0"),
  1093  				pkg("foo", "bar")),
  1094  			dsp(mkDepspec("bar 1.0.0"),
  1095  				pkg("bar")),
  1096  			dsp(mkDepspec("baz 1.0.0"),
  1097  				pkg("bar")),
  1098  		},
  1099  		ovr: ProjectConstraints{
  1100  			ProjectRoot("bar"): ProjectProperties{
  1101  				Source: "baz",
  1102  			},
  1103  		},
  1104  		r: mksolution(
  1105  			"foo 1.0.0",
  1106  			"bar from baz 1.0.0",
  1107  		),
  1108  	},
  1109  	"require package": {
  1110  		ds: []depspec{
  1111  			dsp(mkDepspec("root 0.0.0", "bar 1.0.0"),
  1112  				pkg("root", "foo")),
  1113  			dsp(mkDepspec("foo 1.0.0"),
  1114  				pkg("foo", "bar")),
  1115  			dsp(mkDepspec("bar 1.0.0"),
  1116  				pkg("bar")),
  1117  			dsp(mkDepspec("baz 1.0.0"),
  1118  				pkg("baz")),
  1119  		},
  1120  		require: []string{"baz"},
  1121  		r: mksolution(
  1122  			"foo 1.0.0",
  1123  			"bar 1.0.0",
  1124  			"baz 1.0.0",
  1125  		),
  1126  	},
  1127  	"require activates constraints": {
  1128  		ds: []depspec{
  1129  			dsp(mkDepspec("root 0.0.0", "foo 1.0.0", "bar 1.0.0"),
  1130  				pkg("root", "foo")),
  1131  			dsp(mkDepspec("foo 1.0.0"),
  1132  				pkg("foo", "bar")),
  1133  			dsp(mkDepspec("bar 1.0.0"),
  1134  				pkg("bar")),
  1135  			dsp(mkDepspec("bar 1.1.0"),
  1136  				pkg("bar")),
  1137  		},
  1138  		require: []string{"bar"},
  1139  		r: mksolution(
  1140  			"foo 1.0.0",
  1141  			"bar 1.0.0",
  1142  		),
  1143  	},
  1144  	"require subpackage": {
  1145  		ds: []depspec{
  1146  			dsp(mkDepspec("root 0.0.0", "bar 1.0.0"),
  1147  				pkg("root", "foo")),
  1148  			dsp(mkDepspec("foo 1.0.0"),
  1149  				pkg("foo", "bar")),
  1150  			dsp(mkDepspec("bar 1.0.0"),
  1151  				pkg("bar")),
  1152  			dsp(mkDepspec("baz 1.0.0"),
  1153  				pkg("baz", "baz/qux"),
  1154  				pkg("baz/qux")),
  1155  		},
  1156  		require: []string{"baz/qux"},
  1157  		r: mksolution(
  1158  			"foo 1.0.0",
  1159  			"bar 1.0.0",
  1160  			mklp("baz 1.0.0", "qux"),
  1161  		),
  1162  	},
  1163  	"require impossible subpackage": {
  1164  		ds: []depspec{
  1165  			dsp(mkDepspec("root 0.0.0", "baz 1.0.0"),
  1166  				pkg("root", "foo")),
  1167  			dsp(mkDepspec("foo 1.0.0"),
  1168  				pkg("foo")),
  1169  			dsp(mkDepspec("baz 1.0.0"),
  1170  				pkg("baz")),
  1171  			dsp(mkDepspec("baz 2.0.0"),
  1172  				pkg("baz", "baz/qux"),
  1173  				pkg("baz/qux")),
  1174  		},
  1175  		require: []string{"baz/qux"},
  1176  		fail: &noVersionError{
  1177  			pn: mkPI("baz"),
  1178  			fails: []failedVersion{
  1179  				{
  1180  					v: NewVersion("2.0.0"),
  1181  					f: &versionNotAllowedFailure{
  1182  						goal:       mkAtom("baz 2.0.0"),
  1183  						failparent: []dependency{mkDep("root", "baz 1.0.0", "baz/qux")},
  1184  						c:          NewVersion("1.0.0"),
  1185  					},
  1186  				},
  1187  				{
  1188  					v: NewVersion("1.0.0"),
  1189  					f: &checkeeHasProblemPackagesFailure{
  1190  						goal: mkAtom("baz 1.0.0"),
  1191  						failpkg: map[string]errDeppers{
  1192  							"baz/qux": {
  1193  								err: nil, // nil indicates package is missing
  1194  								deppers: []atom{
  1195  									mkAtom("root"),
  1196  								},
  1197  							},
  1198  						},
  1199  					},
  1200  				},
  1201  			},
  1202  		},
  1203  	},
  1204  	"require subpkg conflicts with other dep constraint": {
  1205  		ds: []depspec{
  1206  			dsp(mkDepspec("root 0.0.0"),
  1207  				pkg("root", "foo")),
  1208  			dsp(mkDepspec("foo 1.0.0", "baz 1.0.0"),
  1209  				pkg("foo", "baz")),
  1210  			dsp(mkDepspec("baz 1.0.0"),
  1211  				pkg("baz")),
  1212  			dsp(mkDepspec("baz 2.0.0"),
  1213  				pkg("baz", "baz/qux"),
  1214  				pkg("baz/qux")),
  1215  		},
  1216  		require: []string{"baz/qux"},
  1217  		fail: &noVersionError{
  1218  			pn: mkPI("baz"),
  1219  			fails: []failedVersion{
  1220  				{
  1221  					v: NewVersion("2.0.0"),
  1222  					f: &versionNotAllowedFailure{
  1223  						goal:       mkAtom("baz 2.0.0"),
  1224  						failparent: []dependency{mkDep("foo 1.0.0", "baz 1.0.0", "baz")},
  1225  						c:          NewVersion("1.0.0"),
  1226  					},
  1227  				},
  1228  				{
  1229  					v: NewVersion("1.0.0"),
  1230  					f: &checkeeHasProblemPackagesFailure{
  1231  						goal: mkAtom("baz 1.0.0"),
  1232  						failpkg: map[string]errDeppers{
  1233  							"baz/qux": {
  1234  								err: nil, // nil indicates package is missing
  1235  								deppers: []atom{
  1236  									mkAtom("root"),
  1237  								},
  1238  							},
  1239  						},
  1240  					},
  1241  				},
  1242  			},
  1243  		},
  1244  	},
  1245  	"require independent subpkg conflicts with other dep constraint": {
  1246  		ds: []depspec{
  1247  			dsp(mkDepspec("root 0.0.0"),
  1248  				pkg("root", "foo")),
  1249  			dsp(mkDepspec("foo 1.0.0", "baz 1.0.0"),
  1250  				pkg("foo", "baz")),
  1251  			dsp(mkDepspec("baz 1.0.0"),
  1252  				pkg("baz")),
  1253  			dsp(mkDepspec("baz 2.0.0"),
  1254  				pkg("baz"),
  1255  				pkg("baz/qux")),
  1256  		},
  1257  		require: []string{"baz/qux"},
  1258  		fail: &noVersionError{
  1259  			pn: mkPI("baz"),
  1260  			fails: []failedVersion{
  1261  				{
  1262  					v: NewVersion("2.0.0"),
  1263  					f: &versionNotAllowedFailure{
  1264  						goal:       mkAtom("baz 2.0.0"),
  1265  						failparent: []dependency{mkDep("foo 1.0.0", "baz 1.0.0", "baz")},
  1266  						c:          NewVersion("1.0.0"),
  1267  					},
  1268  				},
  1269  				{
  1270  					v: NewVersion("1.0.0"),
  1271  					f: &checkeeHasProblemPackagesFailure{
  1272  						goal: mkAtom("baz 1.0.0"),
  1273  						failpkg: map[string]errDeppers{
  1274  							"baz/qux": {
  1275  								err: nil, // nil indicates package is missing
  1276  								deppers: []atom{
  1277  									mkAtom("root"),
  1278  								},
  1279  							},
  1280  						},
  1281  					},
  1282  				},
  1283  			},
  1284  		},
  1285  	},
  1286  }
  1287  
  1288  // tpkg is a representation of a single package. It has its own import path, as
  1289  // well as a list of paths it itself "imports".
  1290  type tpkg struct {
  1291  	// Full import path of this package
  1292  	path string
  1293  	// Slice of full paths to its virtual imports
  1294  	imports []string
  1295  }
  1296  
  1297  type bimodalFixture struct {
  1298  	// name of this fixture datum
  1299  	n string
  1300  	// bimodal project; first is always treated as root project
  1301  	ds []depspec
  1302  	// results; map of name/version pairs
  1303  	r map[ProjectIdentifier]LockedProject
  1304  	// max attempts the solver should need to find solution. 0 means no limit
  1305  	maxAttempts int
  1306  	// Use downgrade instead of default upgrade sorter
  1307  	downgrade bool
  1308  	// lock file simulator, if one's to be used at all
  1309  	l fixLock
  1310  	// map of locks for deps, if any. keys should be of the form:
  1311  	// "<project> <version>"
  1312  	lm map[string]fixLock
  1313  	// solve failure expected, if any
  1314  	fail error
  1315  	// overrides, if any
  1316  	ovr ProjectConstraints
  1317  	// request up/downgrade to all projects
  1318  	changeall bool
  1319  	// pkgs to ignore
  1320  	ignore []string
  1321  	// pkgs to require
  1322  	require []string
  1323  	// if the fixture is currently broken/expected to fail, this has a message
  1324  	// recording why
  1325  	broken string
  1326  }
  1327  
  1328  func (f bimodalFixture) name() string {
  1329  	return f.n
  1330  }
  1331  
  1332  func (f bimodalFixture) specs() []depspec {
  1333  	return f.ds
  1334  }
  1335  
  1336  func (f bimodalFixture) maxTries() int {
  1337  	return f.maxAttempts
  1338  }
  1339  
  1340  func (f bimodalFixture) solution() map[ProjectIdentifier]LockedProject {
  1341  	return f.r
  1342  }
  1343  
  1344  func (f bimodalFixture) rootmanifest() RootManifest {
  1345  	m := simpleRootManifest{
  1346  		c:   pcSliceToMap(f.ds[0].deps),
  1347  		ovr: f.ovr,
  1348  		ig:  pkgtree.NewIgnoredRuleset(f.ignore),
  1349  		req: make(map[string]bool),
  1350  	}
  1351  	for _, req := range f.require {
  1352  		m.req[req] = true
  1353  	}
  1354  
  1355  	return m
  1356  }
  1357  
  1358  func (f bimodalFixture) rootTree() pkgtree.PackageTree {
  1359  	pt := pkgtree.PackageTree{
  1360  		ImportRoot: string(f.ds[0].n),
  1361  		Packages:   map[string]pkgtree.PackageOrErr{},
  1362  	}
  1363  
  1364  	for _, pkg := range f.ds[0].pkgs {
  1365  		elems := strings.Split(pkg.path, "/")
  1366  		pt.Packages[pkg.path] = pkgtree.PackageOrErr{
  1367  			P: pkgtree.Package{
  1368  				ImportPath: pkg.path,
  1369  				Name:       elems[len(elems)-1],
  1370  				// TODO(sdboyer) ugh, tpkg type has no space for supporting test
  1371  				// imports...
  1372  				Imports: pkg.imports,
  1373  			},
  1374  		}
  1375  	}
  1376  
  1377  	return pt
  1378  }
  1379  
  1380  func (f bimodalFixture) failure() error {
  1381  	return f.fail
  1382  }
  1383  
  1384  // bmSourceManager is an SM specifically for the bimodal fixtures. It composes
  1385  // the general depspec SM, and differs from it in how it answers static analysis
  1386  // calls, and its support for package ignores and dep lock data.
  1387  type bmSourceManager struct {
  1388  	depspecSourceManager
  1389  	lm map[string]fixLock
  1390  }
  1391  
  1392  var _ SourceManager = &bmSourceManager{}
  1393  
  1394  func newbmSM(bmf bimodalFixture) *bmSourceManager {
  1395  	sm := &bmSourceManager{
  1396  		depspecSourceManager: *newdepspecSM(bmf.ds, bmf.ignore),
  1397  	}
  1398  	sm.rm = computeBimodalExternalMap(bmf.ds)
  1399  	sm.lm = bmf.lm
  1400  
  1401  	return sm
  1402  }
  1403  
  1404  func (sm *bmSourceManager) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) {
  1405  	// Deal with address-based root-switching with both case folding and
  1406  	// alternate sources.
  1407  	var src, fsrc, root, froot string
  1408  	src, fsrc = id.normalizedSource(), toFold(id.normalizedSource())
  1409  	if id.Source != "" {
  1410  		root = string(id.ProjectRoot)
  1411  		froot = toFold(root)
  1412  	} else {
  1413  		root, froot = src, fsrc
  1414  	}
  1415  
  1416  	for k, ds := range sm.specs {
  1417  		// Cheat for root, otherwise we blow up b/c version is empty
  1418  		if fsrc == string(ds.n) && (k == 0 || ds.v.Matches(v)) {
  1419  			var replace bool
  1420  			if root != string(ds.n) {
  1421  				// We're in a case-varying lookup; ensure we replace the actual
  1422  				// leading ProjectRoot portion of import paths with the literal
  1423  				// string from the input.
  1424  				replace = true
  1425  			}
  1426  
  1427  			ptree := pkgtree.PackageTree{
  1428  				ImportRoot: src,
  1429  				Packages:   make(map[string]pkgtree.PackageOrErr),
  1430  			}
  1431  			for _, pkg := range ds.pkgs {
  1432  				if replace {
  1433  					pkg.path = strings.Replace(pkg.path, froot, root, 1)
  1434  				}
  1435  				ptree.Packages[pkg.path] = pkgtree.PackageOrErr{
  1436  					P: pkgtree.Package{
  1437  						ImportPath: pkg.path,
  1438  						Name:       filepath.Base(pkg.path),
  1439  						Imports:    pkg.imports,
  1440  					},
  1441  				}
  1442  			}
  1443  
  1444  			return ptree, nil
  1445  		}
  1446  	}
  1447  
  1448  	return pkgtree.PackageTree{}, fmt.Errorf("project %s at version %s could not be found", id, v)
  1449  }
  1450  
  1451  func (sm *bmSourceManager) GetManifestAndLock(id ProjectIdentifier, v Version, an ProjectAnalyzer) (Manifest, Lock, error) {
  1452  	src := toFold(id.normalizedSource())
  1453  	for _, ds := range sm.specs {
  1454  		if src == string(ds.n) && v.Matches(ds.v) {
  1455  			if l, exists := sm.lm[src+" "+v.String()]; exists {
  1456  				return ds, l, nil
  1457  			}
  1458  			return ds, dummyLock{}, nil
  1459  		}
  1460  	}
  1461  
  1462  	// TODO(sdboyer) proper solver-type errors
  1463  	return nil, nil, fmt.Errorf("project %s at version %s could not be found", id, v)
  1464  }
  1465  
  1466  // computeBimodalExternalMap takes a set of depspecs and computes an
  1467  // internally-versioned ReachMap that is useful for quickly answering
  1468  // ReachMap.Flatten()-type calls.
  1469  //
  1470  // Note that it does not do things like stripping out stdlib packages - these
  1471  // maps are intended for use in SM fixtures, and that's a higher-level
  1472  // responsibility within the system.
  1473  func computeBimodalExternalMap(specs []depspec) map[pident]map[string][]string {
  1474  	// map of project name+version -> map of subpkg name -> external pkg list
  1475  	rm := make(map[pident]map[string][]string)
  1476  
  1477  	for _, ds := range specs {
  1478  		ptree := pkgtree.PackageTree{
  1479  			ImportRoot: string(ds.n),
  1480  			Packages:   make(map[string]pkgtree.PackageOrErr),
  1481  		}
  1482  		for _, pkg := range ds.pkgs {
  1483  			ptree.Packages[pkg.path] = pkgtree.PackageOrErr{
  1484  				P: pkgtree.Package{
  1485  					ImportPath: pkg.path,
  1486  					Name:       filepath.Base(pkg.path),
  1487  					Imports:    pkg.imports,
  1488  				},
  1489  			}
  1490  		}
  1491  		reachmap, em := ptree.ToReachMap(false, true, true, nil)
  1492  		if len(em) > 0 {
  1493  			panic(fmt.Sprintf("pkgs with errors in reachmap processing: %s", em))
  1494  		}
  1495  
  1496  		drm := make(map[string][]string)
  1497  		for ip, ie := range reachmap {
  1498  			drm[ip] = ie.External
  1499  		}
  1500  		rm[pident{n: ds.n, v: ds.v}] = drm
  1501  	}
  1502  
  1503  	return rm
  1504  }