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

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