зеркало из https://github.com/golang/dep.git
1605 строки
40 KiB
Go
1605 строки
40 KiB
Go
package gps
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/Masterminds/semver"
|
|
)
|
|
|
|
var regfrom = regexp.MustCompile(`^(\w*) from (\w*) ([0-9\.]*)`)
|
|
|
|
// nvSplit splits an "info" string on " " into the pair of name and
|
|
// version/constraint, and returns each individually.
|
|
//
|
|
// This is for narrow use - panics if there are less than two resulting items in
|
|
// the slice.
|
|
func nvSplit(info string) (id ProjectIdentifier, version string) {
|
|
if strings.Contains(info, " from ") {
|
|
parts := regfrom.FindStringSubmatch(info)
|
|
info = parts[1] + " " + parts[3]
|
|
id.NetworkName = parts[2]
|
|
}
|
|
|
|
s := strings.SplitN(info, " ", 2)
|
|
if len(s) < 2 {
|
|
panic(fmt.Sprintf("Malformed name/version info string '%s'", info))
|
|
}
|
|
|
|
id.ProjectRoot, version = ProjectRoot(s[0]), s[1]
|
|
if id.NetworkName == "" {
|
|
id.NetworkName = string(id.ProjectRoot)
|
|
}
|
|
return
|
|
}
|
|
|
|
// nvrSplit splits an "info" string on " " into the triplet of name,
|
|
// version/constraint, and revision, and returns each individually.
|
|
//
|
|
// It will work fine if only name and version/constraint are provided.
|
|
//
|
|
// This is for narrow use - panics if there are less than two resulting items in
|
|
// the slice.
|
|
func nvrSplit(info string) (id ProjectIdentifier, version string, revision Revision) {
|
|
if strings.Contains(info, " from ") {
|
|
parts := regfrom.FindStringSubmatch(info)
|
|
info = parts[1] + " " + parts[3]
|
|
id.NetworkName = parts[2]
|
|
}
|
|
|
|
s := strings.SplitN(info, " ", 3)
|
|
if len(s) < 2 {
|
|
panic(fmt.Sprintf("Malformed name/version info string '%s'", info))
|
|
}
|
|
|
|
id.ProjectRoot, version = ProjectRoot(s[0]), s[1]
|
|
if id.NetworkName == "" {
|
|
id.NetworkName = string(id.ProjectRoot)
|
|
}
|
|
|
|
if len(s) == 3 {
|
|
revision = Revision(s[2])
|
|
}
|
|
return
|
|
}
|
|
|
|
// mkAtom splits the input string on a space, and uses the first two elements as
|
|
// the project identifier and version, respectively.
|
|
//
|
|
// The version segment may have a leading character indicating the type of
|
|
// version to create:
|
|
//
|
|
// p: create a "plain" (non-semver) version.
|
|
// b: create a branch version.
|
|
// r: create a revision.
|
|
//
|
|
// No prefix is assumed to indicate a semver version.
|
|
//
|
|
// If a third space-delimited element is provided, it will be interepreted as a
|
|
// revision, and used as the underlying version in a PairedVersion. No prefix
|
|
// should be provided in this case. It is an error (and will panic) to try to
|
|
// pass a revision with an underlying revision.
|
|
func mkAtom(info string) atom {
|
|
// if info is "root", special case it to use the root "version"
|
|
if info == "root" {
|
|
return atom{
|
|
id: ProjectIdentifier{
|
|
ProjectRoot: ProjectRoot("root"),
|
|
},
|
|
v: rootRev,
|
|
}
|
|
}
|
|
|
|
id, ver, rev := nvrSplit(info)
|
|
|
|
var v Version
|
|
switch ver[0] {
|
|
case 'r':
|
|
if rev != "" {
|
|
panic("Cannot pair a revision with a revision")
|
|
}
|
|
v = Revision(ver[1:])
|
|
case 'p':
|
|
v = NewVersion(ver[1:])
|
|
case 'b':
|
|
v = NewBranch(ver[1:])
|
|
default:
|
|
_, err := semver.NewVersion(ver)
|
|
if err != nil {
|
|
// don't want to allow bad test data at this level, so just panic
|
|
panic(fmt.Sprintf("Error when converting '%s' into semver: %s", ver, err))
|
|
}
|
|
v = NewVersion(ver)
|
|
}
|
|
|
|
if rev != "" {
|
|
v = v.(UnpairedVersion).Is(rev)
|
|
}
|
|
|
|
return atom{
|
|
id: id,
|
|
v: v,
|
|
}
|
|
}
|
|
|
|
// mkPCstrnt splits the input string on a space, and uses the first two elements
|
|
// as the project identifier and constraint body, respectively.
|
|
//
|
|
// The constraint body may have a leading character indicating the type of
|
|
// version to create:
|
|
//
|
|
// p: create a "plain" (non-semver) version.
|
|
// b: create a branch version.
|
|
// r: create a revision.
|
|
//
|
|
// If no leading character is used, a semver constraint is assumed.
|
|
func mkPCstrnt(info string) ProjectConstraint {
|
|
id, ver, rev := nvrSplit(info)
|
|
|
|
var c Constraint
|
|
switch ver[0] {
|
|
case 'r':
|
|
c = Revision(ver[1:])
|
|
case 'p':
|
|
c = NewVersion(ver[1:])
|
|
case 'b':
|
|
c = NewBranch(ver[1:])
|
|
default:
|
|
// Without one of those leading characters, we know it's a proper semver
|
|
// expression, so use the other parser that doesn't look for a rev
|
|
rev = ""
|
|
id, ver = nvSplit(info)
|
|
var err error
|
|
c, err = NewSemverConstraint(ver)
|
|
if err != nil {
|
|
// don't want bad test data at this level, so just panic
|
|
panic(fmt.Sprintf("Error when converting '%s' into semver constraint: %s (full info: %s)", ver, err, info))
|
|
}
|
|
}
|
|
|
|
// There's no practical reason that a real tool would need to produce a
|
|
// constraint that's a PairedVersion, but it is a possibility admitted by the
|
|
// system, so we at least allow for it in our testing harness.
|
|
if rev != "" {
|
|
// Of course, this *will* panic if the predicate is a revision or a
|
|
// semver constraint, neither of which implement UnpairedVersion. This
|
|
// is as intended, to prevent bad data from entering the system.
|
|
c = c.(UnpairedVersion).Is(rev)
|
|
}
|
|
|
|
return ProjectConstraint{
|
|
Ident: id,
|
|
Constraint: c,
|
|
}
|
|
}
|
|
|
|
// mkCDep composes a completeDep struct from the inputs.
|
|
//
|
|
// The only real work here is passing the initial string to mkPDep. All the
|
|
// other args are taken as package names.
|
|
func mkCDep(pdep string, pl ...string) completeDep {
|
|
pc := mkPCstrnt(pdep)
|
|
return completeDep{
|
|
workingConstraint: workingConstraint{
|
|
Ident: pc.Ident,
|
|
Constraint: pc.Constraint,
|
|
},
|
|
pl: pl,
|
|
}
|
|
}
|
|
|
|
// A depspec is a fixture representing all the information a SourceManager would
|
|
// ordinarily glean directly from interrogating a repository.
|
|
type depspec struct {
|
|
n ProjectRoot
|
|
v Version
|
|
deps []ProjectConstraint
|
|
devdeps []ProjectConstraint
|
|
pkgs []tpkg
|
|
}
|
|
|
|
// mkDepspec creates a depspec by processing a series of strings, each of which
|
|
// contains an identiifer and version information.
|
|
//
|
|
// The first string is broken out into the name and version of the package being
|
|
// described - see the docs on mkAtom for details. subsequent strings are
|
|
// interpreted as dep constraints of that dep at that version. See the docs on
|
|
// mkPDep for details.
|
|
//
|
|
// If a string other than the first includes a "(dev) " prefix, it will be
|
|
// treated as a test-only dependency.
|
|
func mkDepspec(pi string, deps ...string) depspec {
|
|
pa := mkAtom(pi)
|
|
if string(pa.id.ProjectRoot) != pa.id.NetworkName {
|
|
panic("alternate source on self makes no sense")
|
|
}
|
|
|
|
ds := depspec{
|
|
n: pa.id.ProjectRoot,
|
|
v: pa.v,
|
|
}
|
|
|
|
for _, dep := range deps {
|
|
var sl *[]ProjectConstraint
|
|
if strings.HasPrefix(dep, "(dev) ") {
|
|
dep = strings.TrimPrefix(dep, "(dev) ")
|
|
sl = &ds.devdeps
|
|
} else {
|
|
sl = &ds.deps
|
|
}
|
|
|
|
*sl = append(*sl, mkPCstrnt(dep))
|
|
}
|
|
|
|
return ds
|
|
}
|
|
|
|
func mkDep(atom, pdep string, pl ...string) dependency {
|
|
return dependency{
|
|
depender: mkAtom(atom),
|
|
dep: mkCDep(pdep, pl...),
|
|
}
|
|
}
|
|
|
|
func mkADep(atom, pdep string, c Constraint, pl ...string) dependency {
|
|
return dependency{
|
|
depender: mkAtom(atom),
|
|
dep: completeDep{
|
|
workingConstraint: workingConstraint{
|
|
Ident: ProjectIdentifier{
|
|
ProjectRoot: ProjectRoot(pdep),
|
|
NetworkName: pdep,
|
|
},
|
|
Constraint: c,
|
|
},
|
|
pl: pl,
|
|
},
|
|
}
|
|
}
|
|
|
|
// mkPI creates a ProjectIdentifier with the ProjectRoot as the provided
|
|
// string, and with the NetworkName normalized to be the same.
|
|
func mkPI(root string) ProjectIdentifier {
|
|
return ProjectIdentifier{
|
|
ProjectRoot: ProjectRoot(root),
|
|
NetworkName: root,
|
|
}
|
|
}
|
|
|
|
// mkSVC creates a new semver constraint, panicking if an error is returned.
|
|
func mkSVC(body string) Constraint {
|
|
c, err := NewSemverConstraint(body)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Error while trying to create semver constraint from %s: %s", body, err.Error()))
|
|
}
|
|
return c
|
|
}
|
|
|
|
// mklock makes a fixLock, suitable to act as a lock file
|
|
func mklock(pairs ...string) fixLock {
|
|
l := make(fixLock, 0)
|
|
for _, s := range pairs {
|
|
pa := mkAtom(s)
|
|
l = append(l, NewLockedProject(pa.id.ProjectRoot, pa.v, pa.id.netName(), nil))
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// mkrevlock makes a fixLock, suitable to act as a lock file, with only a name
|
|
// and a rev
|
|
func mkrevlock(pairs ...string) fixLock {
|
|
l := make(fixLock, 0)
|
|
for _, s := range pairs {
|
|
pa := mkAtom(s)
|
|
l = append(l, NewLockedProject(pa.id.ProjectRoot, pa.v.(PairedVersion).Underlying(), pa.id.netName(), nil))
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// mksolution makes a result set
|
|
func mksolution(pairs ...string) map[string]Version {
|
|
m := make(map[string]Version)
|
|
for _, pair := range pairs {
|
|
a := mkAtom(pair)
|
|
// TODO(sdboyer) identifierify
|
|
m[string(a.id.ProjectRoot)] = a.v
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// computeBasicReachMap takes a depspec and computes a reach map which is
|
|
// identical to the explicit depgraph.
|
|
//
|
|
// Using a reachMap here is overkill for what the basic fixtures actually need,
|
|
// but we use it anyway for congruence with the more general cases.
|
|
func computeBasicReachMap(ds []depspec) reachMap {
|
|
rm := make(reachMap)
|
|
|
|
for k, d := range ds {
|
|
n := string(d.n)
|
|
lm := map[string][]string{
|
|
n: nil,
|
|
}
|
|
v := d.v
|
|
if k == 0 {
|
|
// Put the root in with a nil rev, to accommodate the solver
|
|
v = nil
|
|
}
|
|
rm[pident{n: d.n, v: v}] = lm
|
|
|
|
for _, dep := range d.deps {
|
|
lm[n] = append(lm[n], string(dep.Ident.ProjectRoot))
|
|
}
|
|
|
|
// first is root
|
|
if k == 0 {
|
|
for _, dep := range d.devdeps {
|
|
lm[n] = append(lm[n], string(dep.Ident.ProjectRoot))
|
|
}
|
|
}
|
|
}
|
|
|
|
return rm
|
|
}
|
|
|
|
type pident struct {
|
|
n ProjectRoot
|
|
v Version
|
|
}
|
|
|
|
type specfix interface {
|
|
name() string
|
|
rootmanifest() RootManifest
|
|
specs() []depspec
|
|
maxTries() int
|
|
solution() map[string]Version
|
|
failure() error
|
|
}
|
|
|
|
// A basicFixture is a declarative test fixture that can cover a wide variety of
|
|
// solver cases. All cases, however, maintain one invariant: package == project.
|
|
// There are no subpackages, and so it is impossible for them to trigger or
|
|
// require bimodal solving.
|
|
//
|
|
// This type is separate from bimodalFixture in part for legacy reasons - many
|
|
// of these were adapted from similar tests in dart's pub lib, where there is no
|
|
// such thing as "bimodal solving".
|
|
//
|
|
// But it's also useful to keep them separate because bimodal solving involves
|
|
// considerably more complexity than simple solving, both in terms of fixture
|
|
// declaration and actual solving mechanics. Thus, we gain a lot of value for
|
|
// contributors and maintainers by keeping comprehension costs relatively low
|
|
// while still covering important cases.
|
|
type basicFixture struct {
|
|
// name of this fixture datum
|
|
n string
|
|
// depspecs. always treat first as root
|
|
ds []depspec
|
|
// results; map of name/version pairs
|
|
r map[string]Version
|
|
// max attempts the solver should need to find solution. 0 means no limit
|
|
maxAttempts int
|
|
// Use downgrade instead of default upgrade sorter
|
|
downgrade bool
|
|
// lock file simulator, if one's to be used at all
|
|
l fixLock
|
|
// solve failure expected, if any
|
|
fail error
|
|
// overrides, if any
|
|
ovr ProjectConstraints
|
|
// request up/downgrade to all projects
|
|
changeall bool
|
|
}
|
|
|
|
func (f basicFixture) name() string {
|
|
return f.n
|
|
}
|
|
|
|
func (f basicFixture) specs() []depspec {
|
|
return f.ds
|
|
}
|
|
|
|
func (f basicFixture) maxTries() int {
|
|
return f.maxAttempts
|
|
}
|
|
|
|
func (f basicFixture) solution() map[string]Version {
|
|
return f.r
|
|
}
|
|
|
|
func (f basicFixture) rootmanifest() RootManifest {
|
|
return simpleRootManifest{
|
|
c: f.ds[0].deps,
|
|
tc: f.ds[0].devdeps,
|
|
ovr: f.ovr,
|
|
}
|
|
}
|
|
|
|
func (f basicFixture) failure() error {
|
|
return f.fail
|
|
}
|
|
|
|
// A table of basicFixtures, used in the basic solving test set.
|
|
var basicFixtures = map[string]basicFixture{
|
|
// basic fixtures
|
|
"no dependencies": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0"),
|
|
},
|
|
r: mksolution(),
|
|
},
|
|
"simple dependency tree": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"),
|
|
mkDepspec("a 1.0.0", "aa 1.0.0", "ab 1.0.0"),
|
|
mkDepspec("aa 1.0.0"),
|
|
mkDepspec("ab 1.0.0"),
|
|
mkDepspec("b 1.0.0", "ba 1.0.0", "bb 1.0.0"),
|
|
mkDepspec("ba 1.0.0"),
|
|
mkDepspec("bb 1.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"a 1.0.0",
|
|
"aa 1.0.0",
|
|
"ab 1.0.0",
|
|
"b 1.0.0",
|
|
"ba 1.0.0",
|
|
"bb 1.0.0",
|
|
),
|
|
},
|
|
"shared dependency with overlapping constraints": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"),
|
|
mkDepspec("a 1.0.0", "shared >=2.0.0, <4.0.0"),
|
|
mkDepspec("b 1.0.0", "shared >=3.0.0, <5.0.0"),
|
|
mkDepspec("shared 2.0.0"),
|
|
mkDepspec("shared 3.0.0"),
|
|
mkDepspec("shared 3.6.9"),
|
|
mkDepspec("shared 4.0.0"),
|
|
mkDepspec("shared 5.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"a 1.0.0",
|
|
"b 1.0.0",
|
|
"shared 3.6.9",
|
|
),
|
|
},
|
|
"downgrade on overlapping constraints": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"),
|
|
mkDepspec("a 1.0.0", "shared >=2.0.0, <=4.0.0"),
|
|
mkDepspec("b 1.0.0", "shared >=3.0.0, <5.0.0"),
|
|
mkDepspec("shared 2.0.0"),
|
|
mkDepspec("shared 3.0.0"),
|
|
mkDepspec("shared 3.6.9"),
|
|
mkDepspec("shared 4.0.0"),
|
|
mkDepspec("shared 5.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"a 1.0.0",
|
|
"b 1.0.0",
|
|
"shared 3.0.0",
|
|
),
|
|
downgrade: true,
|
|
},
|
|
"shared dependency where dependent version in turn affects other dependencies": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo <=1.0.2", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.0"),
|
|
mkDepspec("foo 1.0.1", "bang 1.0.0"),
|
|
mkDepspec("foo 1.0.2", "whoop 1.0.0"),
|
|
mkDepspec("foo 1.0.3", "zoop 1.0.0"),
|
|
mkDepspec("bar 1.0.0", "foo <=1.0.1"),
|
|
mkDepspec("bang 1.0.0"),
|
|
mkDepspec("whoop 1.0.0"),
|
|
mkDepspec("zoop 1.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"foo 1.0.1",
|
|
"bar 1.0.0",
|
|
"bang 1.0.0",
|
|
),
|
|
},
|
|
"removed dependency": {
|
|
ds: []depspec{
|
|
mkDepspec("root 1.0.0", "foo 1.0.0", "bar *"),
|
|
mkDepspec("foo 1.0.0"),
|
|
mkDepspec("foo 2.0.0"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 2.0.0", "baz 1.0.0"),
|
|
mkDepspec("baz 1.0.0", "foo 2.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"foo 1.0.0",
|
|
"bar 1.0.0",
|
|
),
|
|
maxAttempts: 2,
|
|
},
|
|
"with mismatched net addrs": {
|
|
ds: []depspec{
|
|
mkDepspec("root 1.0.0", "foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.0", "bar from baz 1.0.0"),
|
|
mkDepspec("bar 1.0.0"),
|
|
},
|
|
fail: &noVersionError{
|
|
pn: mkPI("foo"),
|
|
fails: []failedVersion{
|
|
{
|
|
v: NewVersion("1.0.0"),
|
|
f: &sourceMismatchFailure{
|
|
shared: ProjectRoot("bar"),
|
|
current: "bar",
|
|
mismatch: "baz",
|
|
prob: mkAtom("foo 1.0.0"),
|
|
sel: []dependency{mkDep("root", "foo 1.0.0", "foo")},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// fixtures with locks
|
|
"with compatible locked dependency": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo *"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.1", "bar 1.0.1"),
|
|
mkDepspec("foo 1.0.2", "bar 1.0.2"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 1.0.1"),
|
|
mkDepspec("bar 1.0.2"),
|
|
},
|
|
l: mklock(
|
|
"foo 1.0.1",
|
|
),
|
|
r: mksolution(
|
|
"foo 1.0.1",
|
|
"bar 1.0.1",
|
|
),
|
|
},
|
|
"upgrade through lock": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo *"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.1", "bar 1.0.1"),
|
|
mkDepspec("foo 1.0.2", "bar 1.0.2"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 1.0.1"),
|
|
mkDepspec("bar 1.0.2"),
|
|
},
|
|
l: mklock(
|
|
"foo 1.0.1",
|
|
),
|
|
r: mksolution(
|
|
"foo 1.0.2",
|
|
"bar 1.0.2",
|
|
),
|
|
changeall: true,
|
|
},
|
|
"downgrade through lock": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo *"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.1", "bar 1.0.1"),
|
|
mkDepspec("foo 1.0.2", "bar 1.0.2"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 1.0.1"),
|
|
mkDepspec("bar 1.0.2"),
|
|
},
|
|
l: mklock(
|
|
"foo 1.0.1",
|
|
),
|
|
r: mksolution(
|
|
"foo 1.0.0",
|
|
"bar 1.0.0",
|
|
),
|
|
changeall: true,
|
|
downgrade: true,
|
|
},
|
|
"with incompatible locked dependency": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo >1.0.1"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.1", "bar 1.0.1"),
|
|
mkDepspec("foo 1.0.2", "bar 1.0.2"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 1.0.1"),
|
|
mkDepspec("bar 1.0.2"),
|
|
},
|
|
l: mklock(
|
|
"foo 1.0.1",
|
|
),
|
|
r: mksolution(
|
|
"foo 1.0.2",
|
|
"bar 1.0.2",
|
|
),
|
|
},
|
|
"with unrelated locked dependency": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo *"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.1", "bar 1.0.1"),
|
|
mkDepspec("foo 1.0.2", "bar 1.0.2"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 1.0.1"),
|
|
mkDepspec("bar 1.0.2"),
|
|
mkDepspec("baz 1.0.0 bazrev"),
|
|
},
|
|
l: mklock(
|
|
"baz 1.0.0 bazrev",
|
|
),
|
|
r: mksolution(
|
|
"foo 1.0.2",
|
|
"bar 1.0.2",
|
|
),
|
|
},
|
|
"unlocks dependencies if necessary to ensure that a new dependency is satisfied": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo *", "newdep *"),
|
|
mkDepspec("foo 1.0.0 foorev", "bar <2.0.0"),
|
|
mkDepspec("bar 1.0.0 barrev", "baz <2.0.0"),
|
|
mkDepspec("baz 1.0.0 bazrev", "qux <2.0.0"),
|
|
mkDepspec("qux 1.0.0 quxrev"),
|
|
mkDepspec("foo 2.0.0", "bar <3.0.0"),
|
|
mkDepspec("bar 2.0.0", "baz <3.0.0"),
|
|
mkDepspec("baz 2.0.0", "qux <3.0.0"),
|
|
mkDepspec("qux 2.0.0"),
|
|
mkDepspec("newdep 2.0.0", "baz >=1.5.0"),
|
|
},
|
|
l: mklock(
|
|
"foo 1.0.0 foorev",
|
|
"bar 1.0.0 barrev",
|
|
"baz 1.0.0 bazrev",
|
|
"qux 1.0.0 quxrev",
|
|
),
|
|
r: mksolution(
|
|
"foo 2.0.0",
|
|
"bar 2.0.0",
|
|
"baz 2.0.0",
|
|
"qux 1.0.0 quxrev",
|
|
"newdep 2.0.0",
|
|
),
|
|
maxAttempts: 4,
|
|
},
|
|
"locked atoms are matched on both local and net name": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo *"),
|
|
mkDepspec("foo 1.0.0 foorev"),
|
|
mkDepspec("foo 2.0.0 foorev2"),
|
|
},
|
|
l: mklock(
|
|
"foo from baz 1.0.0 foorev",
|
|
),
|
|
r: mksolution(
|
|
"foo 2.0.0 foorev2",
|
|
),
|
|
},
|
|
"pairs bare revs in lock with versions": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo ~1.0.1"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.1 foorev", "bar 1.0.1"),
|
|
mkDepspec("foo 1.0.2", "bar 1.0.2"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 1.0.1"),
|
|
mkDepspec("bar 1.0.2"),
|
|
},
|
|
l: mkrevlock(
|
|
"foo 1.0.1 foorev", // mkrevlock drops the 1.0.1
|
|
),
|
|
r: mksolution(
|
|
"foo 1.0.1 foorev",
|
|
"bar 1.0.1",
|
|
),
|
|
},
|
|
"pairs bare revs in lock with all versions": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo ~1.0.1"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.1 foorev", "bar 1.0.1"),
|
|
mkDepspec("foo 1.0.2 foorev", "bar 1.0.2"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 1.0.1"),
|
|
mkDepspec("bar 1.0.2"),
|
|
},
|
|
l: mkrevlock(
|
|
"foo 1.0.1 foorev", // mkrevlock drops the 1.0.1
|
|
),
|
|
r: mksolution(
|
|
"foo 1.0.2 foorev",
|
|
"bar 1.0.1",
|
|
),
|
|
},
|
|
"does not pair bare revs in manifest with unpaired lock version": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo ~1.0.1"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.1 foorev", "bar 1.0.1"),
|
|
mkDepspec("foo 1.0.2", "bar 1.0.2"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 1.0.1"),
|
|
mkDepspec("bar 1.0.2"),
|
|
},
|
|
l: mkrevlock(
|
|
"foo 1.0.1 foorev", // mkrevlock drops the 1.0.1
|
|
),
|
|
r: mksolution(
|
|
"foo 1.0.1 foorev",
|
|
"bar 1.0.1",
|
|
),
|
|
},
|
|
"includes root package's dev dependencies": {
|
|
ds: []depspec{
|
|
mkDepspec("root 1.0.0", "(dev) foo 1.0.0", "(dev) bar 1.0.0"),
|
|
mkDepspec("foo 1.0.0"),
|
|
mkDepspec("bar 1.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"foo 1.0.0",
|
|
"bar 1.0.0",
|
|
),
|
|
},
|
|
"includes dev dependency's transitive dependencies": {
|
|
ds: []depspec{
|
|
mkDepspec("root 1.0.0", "(dev) foo 1.0.0"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("bar 1.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"foo 1.0.0",
|
|
"bar 1.0.0",
|
|
),
|
|
},
|
|
"ignores transitive dependency's dev dependencies": {
|
|
ds: []depspec{
|
|
mkDepspec("root 1.0.0", "(dev) foo 1.0.0"),
|
|
mkDepspec("foo 1.0.0", "(dev) bar 1.0.0"),
|
|
mkDepspec("bar 1.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"foo 1.0.0",
|
|
),
|
|
},
|
|
"no version that matches requirement": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo >=1.0.0, <2.0.0"),
|
|
mkDepspec("foo 2.0.0"),
|
|
mkDepspec("foo 2.1.3"),
|
|
},
|
|
fail: &noVersionError{
|
|
pn: mkPI("foo"),
|
|
fails: []failedVersion{
|
|
{
|
|
v: NewVersion("2.1.3"),
|
|
f: &versionNotAllowedFailure{
|
|
goal: mkAtom("foo 2.1.3"),
|
|
failparent: []dependency{mkDep("root", "foo ^1.0.0", "foo")},
|
|
c: mkSVC("^1.0.0"),
|
|
},
|
|
},
|
|
{
|
|
v: NewVersion("2.0.0"),
|
|
f: &versionNotAllowedFailure{
|
|
goal: mkAtom("foo 2.0.0"),
|
|
failparent: []dependency{mkDep("root", "foo ^1.0.0", "foo")},
|
|
c: mkSVC("^1.0.0"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"no version that matches combined constraint": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.0", "shared >=2.0.0, <3.0.0"),
|
|
mkDepspec("bar 1.0.0", "shared >=2.9.0, <4.0.0"),
|
|
mkDepspec("shared 2.5.0"),
|
|
mkDepspec("shared 3.5.0"),
|
|
},
|
|
fail: &noVersionError{
|
|
pn: mkPI("shared"),
|
|
fails: []failedVersion{
|
|
{
|
|
v: NewVersion("3.5.0"),
|
|
f: &versionNotAllowedFailure{
|
|
goal: mkAtom("shared 3.5.0"),
|
|
failparent: []dependency{mkDep("foo 1.0.0", "shared >=2.0.0, <3.0.0", "shared")},
|
|
c: mkSVC(">=2.9.0, <3.0.0"),
|
|
},
|
|
},
|
|
{
|
|
v: NewVersion("2.5.0"),
|
|
f: &versionNotAllowedFailure{
|
|
goal: mkAtom("shared 2.5.0"),
|
|
failparent: []dependency{mkDep("bar 1.0.0", "shared >=2.9.0, <4.0.0", "shared")},
|
|
c: mkSVC(">=2.9.0, <3.0.0"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"disjoint constraints": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.0", "shared <=2.0.0"),
|
|
mkDepspec("bar 1.0.0", "shared >3.0.0"),
|
|
mkDepspec("shared 2.0.0"),
|
|
mkDepspec("shared 4.0.0"),
|
|
},
|
|
fail: &noVersionError{
|
|
pn: mkPI("foo"),
|
|
fails: []failedVersion{
|
|
{
|
|
v: NewVersion("1.0.0"),
|
|
f: &disjointConstraintFailure{
|
|
goal: mkDep("foo 1.0.0", "shared <=2.0.0", "shared"),
|
|
failsib: []dependency{mkDep("bar 1.0.0", "shared >3.0.0", "shared")},
|
|
nofailsib: nil,
|
|
c: mkSVC(">3.0.0"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"no valid solution": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a *", "b *"),
|
|
mkDepspec("a 1.0.0", "b 1.0.0"),
|
|
mkDepspec("a 2.0.0", "b 2.0.0"),
|
|
mkDepspec("b 1.0.0", "a 2.0.0"),
|
|
mkDepspec("b 2.0.0", "a 1.0.0"),
|
|
},
|
|
fail: &noVersionError{
|
|
pn: mkPI("b"),
|
|
fails: []failedVersion{
|
|
{
|
|
v: NewVersion("2.0.0"),
|
|
f: &versionNotAllowedFailure{
|
|
goal: mkAtom("b 2.0.0"),
|
|
failparent: []dependency{mkDep("a 1.0.0", "b 1.0.0", "b")},
|
|
c: mkSVC("1.0.0"),
|
|
},
|
|
},
|
|
{
|
|
v: NewVersion("1.0.0"),
|
|
f: &constraintNotAllowedFailure{
|
|
goal: mkDep("b 1.0.0", "a 2.0.0", "a"),
|
|
v: NewVersion("1.0.0"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"no version that matches while backtracking": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a *", "b >1.0.0"),
|
|
mkDepspec("a 1.0.0"),
|
|
mkDepspec("b 1.0.0"),
|
|
},
|
|
fail: &noVersionError{
|
|
pn: mkPI("b"),
|
|
fails: []failedVersion{
|
|
{
|
|
v: NewVersion("1.0.0"),
|
|
f: &versionNotAllowedFailure{
|
|
goal: mkAtom("b 1.0.0"),
|
|
failparent: []dependency{mkDep("root", "b >1.0.0", "b")},
|
|
c: mkSVC(">1.0.0"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// The latest versions of a and b disagree on c. An older version of either
|
|
// will resolve the problem. This test validates that b, which is farther
|
|
// in the dependency graph from myapp is downgraded first.
|
|
"rolls back leaf versions first": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a *"),
|
|
mkDepspec("a 1.0.0", "b *"),
|
|
mkDepspec("a 2.0.0", "b *", "c 2.0.0"),
|
|
mkDepspec("b 1.0.0"),
|
|
mkDepspec("b 2.0.0", "c 1.0.0"),
|
|
mkDepspec("c 1.0.0"),
|
|
mkDepspec("c 2.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"a 2.0.0",
|
|
"b 1.0.0",
|
|
"c 2.0.0",
|
|
),
|
|
maxAttempts: 2,
|
|
},
|
|
// Only one version of baz, so foo and bar will have to downgrade until they
|
|
// reach it.
|
|
"mutual downgrading": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo *"),
|
|
mkDepspec("foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 2.0.0", "bar 2.0.0"),
|
|
mkDepspec("foo 3.0.0", "bar 3.0.0"),
|
|
mkDepspec("bar 1.0.0", "baz *"),
|
|
mkDepspec("bar 2.0.0", "baz 2.0.0"),
|
|
mkDepspec("bar 3.0.0", "baz 3.0.0"),
|
|
mkDepspec("baz 1.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"foo 1.0.0",
|
|
"bar 1.0.0",
|
|
"baz 1.0.0",
|
|
),
|
|
maxAttempts: 3,
|
|
},
|
|
// Ensures the solver doesn't exhaustively search all versions of b when
|
|
// it's a-2.0.0 whose dependency on c-2.0.0-nonexistent led to the
|
|
// problem. We make sure b has more versions than a so that the solver
|
|
// tries a first since it sorts sibling dependencies by number of
|
|
// versions.
|
|
"search real failer": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a *", "b *"),
|
|
mkDepspec("a 1.0.0", "c 1.0.0"),
|
|
mkDepspec("a 2.0.0", "c 2.0.0"),
|
|
mkDepspec("b 1.0.0"),
|
|
mkDepspec("b 2.0.0"),
|
|
mkDepspec("b 3.0.0"),
|
|
mkDepspec("c 1.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"a 1.0.0",
|
|
"b 3.0.0",
|
|
"c 1.0.0",
|
|
),
|
|
maxAttempts: 2,
|
|
},
|
|
// Dependencies are ordered so that packages with fewer versions are tried
|
|
// first. Here, there are two valid solutions (either a or b must be
|
|
// downgraded once). The chosen one depends on which dep is traversed first.
|
|
// Since b has fewer versions, it will be traversed first, which means a
|
|
// will come later. Since later selections are revised first, a gets
|
|
// downgraded.
|
|
"traverse into package with fewer versions first": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a *", "b *"),
|
|
mkDepspec("a 1.0.0", "c *"),
|
|
mkDepspec("a 2.0.0", "c *"),
|
|
mkDepspec("a 3.0.0", "c *"),
|
|
mkDepspec("a 4.0.0", "c *"),
|
|
mkDepspec("a 5.0.0", "c 1.0.0"),
|
|
mkDepspec("b 1.0.0", "c *"),
|
|
mkDepspec("b 2.0.0", "c *"),
|
|
mkDepspec("b 3.0.0", "c *"),
|
|
mkDepspec("b 4.0.0", "c 2.0.0"),
|
|
mkDepspec("c 1.0.0"),
|
|
mkDepspec("c 2.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"a 4.0.0",
|
|
"b 4.0.0",
|
|
"c 2.0.0",
|
|
),
|
|
maxAttempts: 2,
|
|
},
|
|
// This is similar to the preceding fixture. When getting the number of
|
|
// versions of a package to determine which to traverse first, versions that
|
|
// are disallowed by the root package's constraints should not be
|
|
// considered. Here, foo has more versions than bar in total (4), but fewer
|
|
// that meet myapp"s constraints (only 2). There is no solution, but we will
|
|
// do less backtracking if foo is tested first.
|
|
"root constraints pre-eliminate versions": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo *", "bar *"),
|
|
mkDepspec("foo 1.0.0", "none 2.0.0"),
|
|
mkDepspec("foo 2.0.0", "none 2.0.0"),
|
|
mkDepspec("foo 3.0.0", "none 2.0.0"),
|
|
mkDepspec("foo 4.0.0", "none 2.0.0"),
|
|
mkDepspec("bar 1.0.0"),
|
|
mkDepspec("bar 2.0.0"),
|
|
mkDepspec("bar 3.0.0"),
|
|
mkDepspec("none 1.0.0"),
|
|
},
|
|
fail: &noVersionError{
|
|
pn: mkPI("none"),
|
|
fails: []failedVersion{
|
|
{
|
|
v: NewVersion("1.0.0"),
|
|
f: &versionNotAllowedFailure{
|
|
goal: mkAtom("none 1.0.0"),
|
|
failparent: []dependency{mkDep("foo 1.0.0", "none 2.0.0", "none")},
|
|
c: mkSVC("2.0.0"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// If there"s a disjoint constraint on a package, then selecting other
|
|
// versions of it is a waste of time: no possible versions can match. We
|
|
// need to jump past it to the most recent package that affected the
|
|
// constraint.
|
|
"backjump past failed package on disjoint constraint": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a *", "foo *"),
|
|
mkDepspec("a 1.0.0", "foo *"),
|
|
mkDepspec("a 2.0.0", "foo <1.0.0"),
|
|
mkDepspec("foo 2.0.0"),
|
|
mkDepspec("foo 2.0.1"),
|
|
mkDepspec("foo 2.0.2"),
|
|
mkDepspec("foo 2.0.3"),
|
|
mkDepspec("foo 2.0.4"),
|
|
mkDepspec("none 1.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"a 1.0.0",
|
|
"foo 2.0.4",
|
|
),
|
|
maxAttempts: 2,
|
|
},
|
|
// Revision enters vqueue if a dep has a constraint on that revision
|
|
"revision injected into vqueue": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo r123abc"),
|
|
mkDepspec("foo r123abc"),
|
|
mkDepspec("foo 1.0.0 foorev"),
|
|
mkDepspec("foo 2.0.0 foorev2"),
|
|
},
|
|
r: mksolution(
|
|
"foo r123abc",
|
|
),
|
|
},
|
|
// Some basic override checks
|
|
"override root's own constraint": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a *", "b *"),
|
|
mkDepspec("a 1.0.0", "b 1.0.0"),
|
|
mkDepspec("a 2.0.0", "b 1.0.0"),
|
|
mkDepspec("b 1.0.0"),
|
|
},
|
|
ovr: ProjectConstraints{
|
|
ProjectRoot("a"): ProjectProperties{
|
|
Constraint: NewVersion("1.0.0"),
|
|
},
|
|
},
|
|
r: mksolution(
|
|
"a 1.0.0",
|
|
"b 1.0.0",
|
|
),
|
|
},
|
|
"override dep's constraint": {
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "a *"),
|
|
mkDepspec("a 1.0.0", "b 1.0.0"),
|
|
mkDepspec("a 2.0.0", "b 1.0.0"),
|
|
mkDepspec("b 1.0.0"),
|
|
mkDepspec("b 2.0.0"),
|
|
},
|
|
ovr: ProjectConstraints{
|
|
ProjectRoot("b"): ProjectProperties{
|
|
Constraint: NewVersion("2.0.0"),
|
|
},
|
|
},
|
|
r: mksolution(
|
|
"a 2.0.0",
|
|
"b 2.0.0",
|
|
),
|
|
},
|
|
"overridden mismatched net addrs, alt in dep, back to default": {
|
|
ds: []depspec{
|
|
mkDepspec("root 1.0.0", "foo 1.0.0", "bar 1.0.0"),
|
|
mkDepspec("foo 1.0.0", "bar from baz 1.0.0"),
|
|
mkDepspec("bar 1.0.0"),
|
|
},
|
|
ovr: ProjectConstraints{
|
|
ProjectRoot("bar"): ProjectProperties{
|
|
NetworkName: "bar",
|
|
},
|
|
},
|
|
r: mksolution(
|
|
"foo 1.0.0",
|
|
"bar 1.0.0",
|
|
),
|
|
},
|
|
|
|
// TODO(sdboyer) decide how to refactor the solver in order to re-enable these.
|
|
// Checking for revision existence is important...but kinda obnoxious.
|
|
//{
|
|
//// Solve fails if revision constraint calls for a nonexistent revision
|
|
//n: "fail on missing revision",
|
|
//ds: []depspec{
|
|
//mkDepspec("root 0.0.0", "bar *"),
|
|
//mkDepspec("bar 1.0.0", "foo r123abc"),
|
|
//mkDepspec("foo r123nomatch"),
|
|
//mkDepspec("foo 1.0.0"),
|
|
//mkDepspec("foo 2.0.0"),
|
|
//},
|
|
//errp: []string{"bar", "foo", "bar"},
|
|
//},
|
|
//{
|
|
//// Solve fails if revision constraint calls for a nonexistent revision,
|
|
//// even if rev constraint is specified by root
|
|
//n: "fail on missing revision from root",
|
|
//ds: []depspec{
|
|
//mkDepspec("root 0.0.0", "foo r123nomatch"),
|
|
//mkDepspec("foo r123abc"),
|
|
//mkDepspec("foo 1.0.0"),
|
|
//mkDepspec("foo 2.0.0"),
|
|
//},
|
|
//errp: []string{"foo", "root", "foo"},
|
|
//},
|
|
|
|
// TODO(sdboyer) add fixture that tests proper handling of loops via aliases (where
|
|
// a project that wouldn't be a loop is aliased to a project that is a loop)
|
|
}
|
|
|
|
func init() {
|
|
// This sets up a hundred versions of foo and bar, 0.0.0 through 9.9.0. Each
|
|
// version of foo depends on a baz with the same major version. Each version
|
|
// of bar depends on a baz with the same minor version. There is only one
|
|
// version of baz, 0.0.0, so only older versions of foo and bar will
|
|
// satisfy it.
|
|
fix := basicFixture{
|
|
ds: []depspec{
|
|
mkDepspec("root 0.0.0", "foo *", "bar *"),
|
|
mkDepspec("baz 0.0.0"),
|
|
},
|
|
r: mksolution(
|
|
"foo 0.9.0",
|
|
"bar 9.0.0",
|
|
"baz 0.0.0",
|
|
),
|
|
maxAttempts: 10,
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
for j := 0; j < 10; j++ {
|
|
fix.ds = append(fix.ds, mkDepspec(fmt.Sprintf("foo %v.%v.0", i, j), fmt.Sprintf("baz %v.0.0", i)))
|
|
fix.ds = append(fix.ds, mkDepspec(fmt.Sprintf("bar %v.%v.0", i, j), fmt.Sprintf("baz 0.%v.0", j)))
|
|
}
|
|
}
|
|
|
|
basicFixtures["complex backtrack"] = fix
|
|
|
|
for k, fix := range basicFixtures {
|
|
// Assign the name into the fixture itself
|
|
fix.n = k
|
|
basicFixtures[k] = fix
|
|
}
|
|
}
|
|
|
|
// reachMaps contain externalReach()-type data for a given depspec fixture's
|
|
// universe of proejcts, packages, and versions.
|
|
type reachMap map[pident]map[string][]string
|
|
|
|
type depspecSourceManager struct {
|
|
specs []depspec
|
|
rm reachMap
|
|
ig map[string]bool
|
|
}
|
|
|
|
type fixSM interface {
|
|
SourceManager
|
|
rootSpec() depspec
|
|
allSpecs() []depspec
|
|
ignore() map[string]bool
|
|
}
|
|
|
|
var _ fixSM = &depspecSourceManager{}
|
|
|
|
func newdepspecSM(ds []depspec, ignore []string) *depspecSourceManager {
|
|
ig := make(map[string]bool)
|
|
if len(ignore) > 0 {
|
|
for _, pkg := range ignore {
|
|
ig[pkg] = true
|
|
}
|
|
}
|
|
|
|
return &depspecSourceManager{
|
|
specs: ds,
|
|
rm: computeBasicReachMap(ds),
|
|
ig: ig,
|
|
}
|
|
}
|
|
|
|
func (sm *depspecSourceManager) GetManifestAndLock(id ProjectIdentifier, v Version) (Manifest, Lock, error) {
|
|
for _, ds := range sm.specs {
|
|
if id.ProjectRoot == ds.n && v.Matches(ds.v) {
|
|
return ds, dummyLock{}, nil
|
|
}
|
|
}
|
|
|
|
// TODO(sdboyer) proper solver-type errors
|
|
return nil, nil, fmt.Errorf("Project %s at version %s could not be found", id.errString(), v)
|
|
}
|
|
|
|
func (sm *depspecSourceManager) AnalyzerInfo() (string, *semver.Version) {
|
|
return "depspec-sm-builtin", sv("v1.0.0")
|
|
}
|
|
|
|
func (sm *depspecSourceManager) ExternalReach(id ProjectIdentifier, v Version) (map[string][]string, error) {
|
|
pid := pident{n: id.ProjectRoot, v: v}
|
|
if m, exists := sm.rm[pid]; exists {
|
|
return m, nil
|
|
}
|
|
return nil, fmt.Errorf("No reach data for %s at version %s", id.errString(), v)
|
|
}
|
|
|
|
func (sm *depspecSourceManager) ListExternal(id ProjectIdentifier, v Version) ([]string, error) {
|
|
// This should only be called for the root
|
|
pid := pident{n: id.ProjectRoot, v: v}
|
|
if r, exists := sm.rm[pid]; exists {
|
|
return r[string(id.ProjectRoot)], nil
|
|
}
|
|
return nil, fmt.Errorf("No reach data for %s at version %s", id.errString(), v)
|
|
}
|
|
|
|
func (sm *depspecSourceManager) ListPackages(id ProjectIdentifier, v Version) (PackageTree, error) {
|
|
pid := pident{n: id.ProjectRoot, v: v}
|
|
n := id.ProjectRoot
|
|
|
|
if r, exists := sm.rm[pid]; exists {
|
|
ptree := PackageTree{
|
|
ImportRoot: string(n),
|
|
Packages: map[string]PackageOrErr{
|
|
string(n): {
|
|
P: Package{
|
|
ImportPath: string(n),
|
|
Name: string(n),
|
|
Imports: r[string(n)],
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return ptree, nil
|
|
}
|
|
|
|
return PackageTree{}, fmt.Errorf("Project %s at version %s could not be found", n, v)
|
|
}
|
|
|
|
func (sm *depspecSourceManager) ListVersions(id ProjectIdentifier) (pi []Version, err error) {
|
|
for _, ds := range sm.specs {
|
|
// To simulate the behavior of the real SourceManager, we do not return
|
|
// revisions from ListVersions().
|
|
if _, isrev := ds.v.(Revision); !isrev && id.ProjectRoot == ds.n {
|
|
pi = append(pi, ds.v)
|
|
}
|
|
}
|
|
|
|
if len(pi) == 0 {
|
|
err = fmt.Errorf("Project %s could not be found", id.errString())
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (sm *depspecSourceManager) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) {
|
|
for _, ds := range sm.specs {
|
|
if id.ProjectRoot == ds.n && r == ds.v {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, fmt.Errorf("Project %s has no revision %s", id.errString(), r)
|
|
}
|
|
|
|
func (sm *depspecSourceManager) RepoExists(id ProjectIdentifier) (bool, error) {
|
|
for _, ds := range sm.specs {
|
|
if id.ProjectRoot == ds.n {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (sm *depspecSourceManager) VendorCodeExists(id ProjectIdentifier) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
func (sm *depspecSourceManager) Release() {}
|
|
|
|
func (sm *depspecSourceManager) ExportProject(id ProjectIdentifier, v Version, to string) error {
|
|
return fmt.Errorf("dummy sm doesn't support exporting")
|
|
}
|
|
|
|
func (sm *depspecSourceManager) rootSpec() depspec {
|
|
return sm.specs[0]
|
|
}
|
|
|
|
func (sm *depspecSourceManager) allSpecs() []depspec {
|
|
return sm.specs
|
|
}
|
|
|
|
func (sm *depspecSourceManager) ignore() map[string]bool {
|
|
return sm.ig
|
|
}
|
|
|
|
type depspecBridge struct {
|
|
*bridge
|
|
}
|
|
|
|
// override computeRootReach() on bridge to read directly out of the depspecs
|
|
func (b *depspecBridge) computeRootReach() ([]string, error) {
|
|
// This only gets called for the root project, so grab that one off the test
|
|
// source manager
|
|
dsm := b.sm.(fixSM)
|
|
root := dsm.rootSpec()
|
|
|
|
ptree, err := dsm.ListPackages(mkPI(string(root.n)), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ptree.ListExternalImports(true, true, dsm.ignore()), nil
|
|
}
|
|
|
|
// override verifyRoot() on bridge to prevent any filesystem interaction
|
|
func (b *depspecBridge) verifyRootDir(path string) error {
|
|
root := b.sm.(fixSM).rootSpec()
|
|
if string(root.n) != path {
|
|
return fmt.Errorf("Expected only root project %q to computeRootReach(), got %q", root.n, path)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *depspecBridge) ListPackages(id ProjectIdentifier, v Version) (PackageTree, error) {
|
|
return b.sm.(fixSM).ListPackages(id, v)
|
|
}
|
|
|
|
// override deduceRemoteRepo on bridge to make all our pkg/project mappings work
|
|
// as expected
|
|
func (b *depspecBridge) deduceRemoteRepo(path string) (*remoteRepo, error) {
|
|
for _, ds := range b.sm.(fixSM).allSpecs() {
|
|
n := string(ds.n)
|
|
if path == n || strings.HasPrefix(path, n+"/") {
|
|
return &remoteRepo{
|
|
Base: n,
|
|
RelPkg: strings.TrimPrefix(path, n+"/"),
|
|
}, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("Could not find %s, or any parent, in list of known fixtures", path)
|
|
}
|
|
|
|
// enforce interfaces
|
|
var _ Manifest = depspec{}
|
|
var _ Lock = dummyLock{}
|
|
var _ Lock = fixLock{}
|
|
|
|
// impl Spec interface
|
|
func (ds depspec) DependencyConstraints() []ProjectConstraint {
|
|
return ds.deps
|
|
}
|
|
|
|
// impl Spec interface
|
|
func (ds depspec) TestDependencyConstraints() []ProjectConstraint {
|
|
return ds.devdeps
|
|
}
|
|
|
|
type fixLock []LockedProject
|
|
|
|
func (fixLock) SolverVersion() string {
|
|
return "-1"
|
|
}
|
|
|
|
// impl Lock interface
|
|
func (fixLock) InputHash() []byte {
|
|
return []byte("fooooorooooofooorooofoo")
|
|
}
|
|
|
|
// impl Lock interface
|
|
func (l fixLock) Projects() []LockedProject {
|
|
return l
|
|
}
|
|
|
|
type dummyLock struct{}
|
|
|
|
// impl Lock interface
|
|
func (dummyLock) SolverVersion() string {
|
|
return "-1"
|
|
}
|
|
|
|
// impl Lock interface
|
|
func (dummyLock) InputHash() []byte {
|
|
return []byte("fooooorooooofooorooofoo")
|
|
}
|
|
|
|
// impl Lock interface
|
|
func (dummyLock) Projects() []LockedProject {
|
|
return nil
|
|
}
|
|
|
|
// We've borrowed this bestiary from pub's tests:
|
|
// https://github.com/dart-lang/pub/blob/master/test/version_solver_test.dart
|
|
|
|
// TODO(sdboyer) finish converting all of these
|
|
|
|
/*
|
|
func basicGraph() {
|
|
testResolve("circular dependency", {
|
|
"myapp 1.0.0": {
|
|
"foo": "1.0.0"
|
|
},
|
|
"foo 1.0.0": {
|
|
"bar": "1.0.0"
|
|
},
|
|
"bar 1.0.0": {
|
|
"foo": "1.0.0"
|
|
}
|
|
}, result: {
|
|
"myapp from root": "1.0.0",
|
|
"foo": "1.0.0",
|
|
"bar": "1.0.0"
|
|
});
|
|
|
|
}
|
|
|
|
func withLockFile() {
|
|
|
|
}
|
|
|
|
func rootDependency() {
|
|
testResolve("with root source", {
|
|
"myapp 1.0.0": {
|
|
"foo": "1.0.0"
|
|
},
|
|
"foo 1.0.0": {
|
|
"myapp from root": ">=1.0.0"
|
|
}
|
|
}, result: {
|
|
"myapp from root": "1.0.0",
|
|
"foo": "1.0.0"
|
|
});
|
|
|
|
testResolve("with different source", {
|
|
"myapp 1.0.0": {
|
|
"foo": "1.0.0"
|
|
},
|
|
"foo 1.0.0": {
|
|
"myapp": ">=1.0.0"
|
|
}
|
|
}, result: {
|
|
"myapp from root": "1.0.0",
|
|
"foo": "1.0.0"
|
|
});
|
|
|
|
testResolve("with wrong version", {
|
|
"myapp 1.0.0": {
|
|
"foo": "1.0.0"
|
|
},
|
|
"foo 1.0.0": {
|
|
"myapp": "<1.0.0"
|
|
}
|
|
}, error: couldNotSolve);
|
|
}
|
|
|
|
func unsolvable() {
|
|
|
|
testResolve("mismatched descriptions", {
|
|
"myapp 0.0.0": {
|
|
"foo": "1.0.0",
|
|
"bar": "1.0.0"
|
|
},
|
|
"foo 1.0.0": {
|
|
"shared-x": "1.0.0"
|
|
},
|
|
"bar 1.0.0": {
|
|
"shared-y": "1.0.0"
|
|
},
|
|
"shared-x 1.0.0": {},
|
|
"shared-y 1.0.0": {}
|
|
}, error: descriptionMismatch("shared", "foo", "bar"));
|
|
|
|
testResolve("mismatched sources", {
|
|
"myapp 0.0.0": {
|
|
"foo": "1.0.0",
|
|
"bar": "1.0.0"
|
|
},
|
|
"foo 1.0.0": {
|
|
"shared": "1.0.0"
|
|
},
|
|
"bar 1.0.0": {
|
|
"shared from mock2": "1.0.0"
|
|
},
|
|
"shared 1.0.0": {},
|
|
"shared 1.0.0 from mock2": {}
|
|
}, error: sourceMismatch("shared", "foo", "bar"));
|
|
|
|
|
|
|
|
// This is a regression test for #18300.
|
|
testResolve("...", {
|
|
"myapp 0.0.0": {
|
|
"angular": "any",
|
|
"collection": "any"
|
|
},
|
|
"analyzer 0.12.2": {},
|
|
"angular 0.10.0": {
|
|
"di": ">=0.0.32 <0.1.0",
|
|
"collection": ">=0.9.1 <1.0.0"
|
|
},
|
|
"angular 0.9.11": {
|
|
"di": ">=0.0.32 <0.1.0",
|
|
"collection": ">=0.9.1 <1.0.0"
|
|
},
|
|
"angular 0.9.10": {
|
|
"di": ">=0.0.32 <0.1.0",
|
|
"collection": ">=0.9.1 <1.0.0"
|
|
},
|
|
"collection 0.9.0": {},
|
|
"collection 0.9.1": {},
|
|
"di 0.0.37": {"analyzer": ">=0.13.0 <0.14.0"},
|
|
"di 0.0.36": {"analyzer": ">=0.13.0 <0.14.0"}
|
|
}, error: noVersion(["analyzer", "di"]), maxTries: 2);
|
|
}
|
|
|
|
func badSource() {
|
|
testResolve("fail if the root package has a bad source in dep", {
|
|
"myapp 0.0.0": {
|
|
"foo from bad": "any"
|
|
},
|
|
}, error: unknownSource("myapp", "foo", "bad"));
|
|
|
|
testResolve("fail if the root package has a bad source in dev dep", {
|
|
"myapp 0.0.0": {
|
|
"(dev) foo from bad": "any"
|
|
},
|
|
}, error: unknownSource("myapp", "foo", "bad"));
|
|
|
|
testResolve("fail if all versions have bad source in dep", {
|
|
"myapp 0.0.0": {
|
|
"foo": "any"
|
|
},
|
|
"foo 1.0.0": {
|
|
"bar from bad": "any"
|
|
},
|
|
"foo 1.0.1": {
|
|
"baz from bad": "any"
|
|
},
|
|
"foo 1.0.3": {
|
|
"bang from bad": "any"
|
|
},
|
|
}, error: unknownSource("foo", "bar", "bad"), maxTries: 3);
|
|
|
|
testResolve("ignore versions with bad source in dep", {
|
|
"myapp 1.0.0": {
|
|
"foo": "any"
|
|
},
|
|
"foo 1.0.0": {
|
|
"bar": "any"
|
|
},
|
|
"foo 1.0.1": {
|
|
"bar from bad": "any"
|
|
},
|
|
"foo 1.0.3": {
|
|
"bar from bad": "any"
|
|
},
|
|
"bar 1.0.0": {}
|
|
}, result: {
|
|
"myapp from root": "1.0.0",
|
|
"foo": "1.0.0",
|
|
"bar": "1.0.0"
|
|
}, maxTries: 3);
|
|
}
|
|
|
|
func backtracking() {
|
|
testResolve("circular dependency on older version", {
|
|
"myapp 0.0.0": {
|
|
"a": ">=1.0.0"
|
|
},
|
|
"a 1.0.0": {},
|
|
"a 2.0.0": {
|
|
"b": "1.0.0"
|
|
},
|
|
"b 1.0.0": {
|
|
"a": "1.0.0"
|
|
}
|
|
}, result: {
|
|
"myapp from root": "0.0.0",
|
|
"a": "1.0.0"
|
|
}, maxTries: 2);
|
|
}
|
|
*/
|