diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9d50bb2f6b..dcc39bc1fa 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,9 +5,9 @@ /cmd/dep/init* @carolynvs /cmd/dep/gopath_scanner* @carolynvs /cmd/dep/root_analyzer* @carolynvs -/cmd/dep/*_importer* @carolynvs /cmd/dep/testdata/init @carolynvs /cmd/dep/testdata/harness_tests/init @carolynvs +/internal/importers @carolynvs /analyzer* @carolynvs /testdata/analyzer @carolynvs /internal/feedback @carolynvs diff --git a/cmd/dep/base_importer_test.go b/cmd/dep/base_importer_test.go deleted file mode 100644 index 628e349ce7..0000000000 --- a/cmd/dep/base_importer_test.go +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "bytes" - "log" - "sort" - "strings" - "testing" - - "github.com/golang/dep" - "github.com/golang/dep/internal/gps" - "github.com/golang/dep/internal/test" - "github.com/pkg/errors" -) - -const ( - importerTestProject = "github.com/carolynvs/deptest-importers" - importerTestProjectSrc = "https://github.com/carolynvs/deptest-importers.git" - importerTestUntaggedRev = "9b670d143bfb4a00f7461451d5c4a62f80e9d11d" - importerTestUntaggedRevAbbrv = "v1.0.0-1-g9b670d1" - importerTestBeta1Tag = "beta1" - importerTestBeta1Rev = "7913ab26988c6fb1e16225f845a178e8849dd254" - importerTestV2Branch = "v2" - importerTestV2Rev = "45dcf5a09c64b48b6e836028a3bc672b19b9d11d" - importerTestV2PatchTag = "v2.0.0-alpha1" - importerTestV2PatchRev = "347760b50204948ea63e531dd6560e56a9adde8f" - importerTestV1Tag = "v1.0.0" - importerTestV1Rev = "d0c29640b17f77426b111f4c1640d716591aa70e" - importerTestV1PatchTag = "v1.0.2" - importerTestV1PatchRev = "788963efe22e3e6e24c776a11a57468bb2fcd780" - importerTestV1Constraint = "^1.0.0" - importerTestMultiTaggedRev = "34cf993cc346f65601fe4356dd68bd54d20a1bfe" - importerTestMultiTaggedSemverTag = "v1.0.4" - importerTestMultiTaggedPlainTag = "stable" -) - -func TestBaseImporter_IsTag(t *testing.T) { - testcases := map[string]struct { - input string - wantIsTag bool - wantTag gps.Version - }{ - "non-semver tag": { - input: importerTestBeta1Tag, - wantIsTag: true, - wantTag: gps.NewVersion(importerTestBeta1Tag).Pair(importerTestBeta1Rev), - }, - "semver-tag": { - input: importerTestV1PatchTag, - wantIsTag: true, - wantTag: gps.NewVersion(importerTestV1PatchTag).Pair(importerTestV1PatchRev)}, - "untagged revision": { - input: importerTestUntaggedRev, - wantIsTag: false, - }, - "branch name": { - input: importerTestV2Branch, - wantIsTag: false, - }, - "empty": { - input: "", - wantIsTag: false, - }, - } - - pi := gps.ProjectIdentifier{ProjectRoot: importerTestProject} - - for name, tc := range testcases { - name := name - tc := tc - t.Run(name, func(t *testing.T) { - h := test.NewHelper(t) - defer h.Cleanup() - h.Parallel() - - ctx := newTestContext(h) - sm, err := ctx.SourceManager() - h.Must(err) - defer sm.Release() - - i := newBaseImporter(discardLogger, false, sm) - gotIsTag, gotTag, err := i.isTag(pi, tc.input) - h.Must(err) - - if tc.wantIsTag != gotIsTag { - t.Fatalf("unexpected isTag result for %v: \n\t(GOT) %v \n\t(WNT) %v", - tc.input, gotIsTag, tc.wantIsTag) - } - - if tc.wantTag != gotTag { - t.Fatalf("unexpected tag for %v: \n\t(GOT) %v \n\t(WNT) %v", - tc.input, gotTag, tc.wantTag) - } - }) - } -} - -func TestBaseImporter_LookupVersionForLockedProject(t *testing.T) { - testcases := map[string]struct { - revision gps.Revision - constraint gps.Constraint - wantVersion string - }{ - "match revision to tag": { - revision: importerTestV1PatchRev, - wantVersion: importerTestV1PatchTag, - }, - "match revision with multiple tags using constraint": { - revision: importerTestMultiTaggedRev, - constraint: gps.NewVersion(importerTestMultiTaggedPlainTag), - wantVersion: importerTestMultiTaggedPlainTag, - }, - "revision with multiple tags with no constraint defaults to best match": { - revision: importerTestMultiTaggedRev, - wantVersion: importerTestMultiTaggedSemverTag, - }, - "revision with multiple tags with nonmatching constraint defaults to best match": { - revision: importerTestMultiTaggedRev, - constraint: gps.NewVersion("thismatchesnothing"), - wantVersion: importerTestMultiTaggedSemverTag, - }, - "untagged revision fallback to branch constraint": { - revision: importerTestUntaggedRev, - constraint: gps.NewBranch("master"), - wantVersion: "master", - }, - "fallback to revision": { - revision: importerTestUntaggedRev, - wantVersion: importerTestUntaggedRev, - }, - } - - pi := gps.ProjectIdentifier{ProjectRoot: importerTestProject} - - for name, tc := range testcases { - name := name - tc := tc - t.Run(name, func(t *testing.T) { - h := test.NewHelper(t) - defer h.Cleanup() - // Disable parallel tests until we can resolve this error on the Windows builds: - // "remote repository at https://github.com/carolynvs/deptest-importers does not exist, or is inaccessible" - //h.Parallel() - - ctx := newTestContext(h) - sm, err := ctx.SourceManager() - h.Must(err) - defer sm.Release() - - i := newBaseImporter(discardLogger, false, sm) - v, err := i.lookupVersionForLockedProject(pi, tc.constraint, tc.revision) - h.Must(err) - - gotVersion := v.String() - if gotVersion != tc.wantVersion { - t.Fatalf("unexpected locked version: \n\t(GOT) %v\n\t(WNT) %v", gotVersion, tc.wantVersion) - } - }) - } -} - -func TestBaseImporter_ImportProjects(t *testing.T) { - testcases := map[string]struct { - convertTestCase - projects []importedPackage - }{ - "tag constraints are ignored": { - convertTestCase{ - wantConstraint: "*", - wantVersion: importerTestBeta1Tag, - wantRevision: importerTestBeta1Rev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestBeta1Rev, - ConstraintHint: importerTestBeta1Tag, - }, - }, - }, - "tag lock hints lock to tagged revision": { - convertTestCase{ - wantConstraint: "*", - wantVersion: importerTestBeta1Tag, - wantRevision: importerTestBeta1Rev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestBeta1Tag, - }, - }, - }, - "untagged revision ignores range constraint": { - convertTestCase{ - wantConstraint: "*", - wantRevision: importerTestUntaggedRev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestUntaggedRev, - ConstraintHint: importerTestV1Constraint, - }, - }, - }, - "untagged revision keeps branch constraint": { - convertTestCase{ - wantConstraint: "master", - wantVersion: "master", - wantRevision: importerTestUntaggedRev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestUntaggedRev, - ConstraintHint: "master", - }, - }, - }, - "HEAD revisions default constraint to the matching branch": { - convertTestCase{ - defaultConstraintFromLock: true, - wantConstraint: importerTestV2Branch, - wantVersion: importerTestV2Branch, - wantRevision: importerTestV2Rev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestV2Rev, - }, - }, - }, - "Semver tagged revisions default to ^VERSION": { - convertTestCase{ - defaultConstraintFromLock: true, - wantConstraint: importerTestV1Constraint, - wantVersion: importerTestV1Tag, - wantRevision: importerTestV1Rev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestV1Rev, - }, - }, - }, - "Semver lock hint defaults constraint to ^VERSION": { - convertTestCase{ - defaultConstraintFromLock: true, - wantConstraint: importerTestV1Constraint, - wantVersion: importerTestV1Tag, - wantRevision: importerTestV1Rev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestV1Tag, - }, - }, - }, - "Semver constraint hint": { - convertTestCase{ - wantConstraint: importerTestV1Constraint, - wantVersion: importerTestV1PatchTag, - wantRevision: importerTestV1PatchRev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestV1PatchRev, - ConstraintHint: importerTestV1Constraint, - }, - }, - }, - "Semver prerelease lock hint": { - convertTestCase{ - wantConstraint: importerTestV2Branch, - wantVersion: importerTestV2PatchTag, - wantRevision: importerTestV2PatchRev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestV2PatchRev, - ConstraintHint: importerTestV2Branch, - }, - }, - }, - "Revision constraints are ignored": { - convertTestCase{ - wantConstraint: "*", - wantVersion: importerTestV1Tag, - wantRevision: importerTestV1Rev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestV1Rev, - ConstraintHint: importerTestV1Rev, - }, - }, - }, - "Branch constraint hint": { - convertTestCase{ - wantConstraint: "master", - wantVersion: importerTestV1Tag, - wantRevision: importerTestV1Rev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestV1Rev, - ConstraintHint: "master", - }, - }, - }, - "Non-matching semver constraint is ignored": { - convertTestCase{ - wantConstraint: "*", - wantVersion: importerTestV1Tag, - wantRevision: importerTestV1Rev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestV1Rev, - ConstraintHint: "^2.0.0", - }, - }, - }, - "git describe constraint is ignored": { - convertTestCase{ - wantConstraint: "*", - wantRevision: importerTestUntaggedRev, - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: importerTestUntaggedRev, - ConstraintHint: importerTestUntaggedRevAbbrv, - }, - }, - }, - "consolidate subpackages under root": { - convertTestCase{ - wantConstraint: "master", - wantVersion: "master", - wantRevision: importerTestUntaggedRev, - }, - []importedPackage{ - { - Name: importerTestProject + "/subpkA", - ConstraintHint: "master", - }, - { - Name: importerTestProject, - LockHint: importerTestUntaggedRev, - }, - }, - }, - "ignore duplicate packages": { - convertTestCase{ - wantConstraint: "*", - wantRevision: importerTestUntaggedRev, - }, - []importedPackage{ - { - Name: importerTestProject + "/subpkgA", - LockHint: importerTestUntaggedRev, // first wins - }, - { - Name: importerTestProject + "/subpkgB", - LockHint: importerTestV1Rev, - }, - }, - }, - "skip empty lock hints": { - convertTestCase{ - wantConstraint: "*", - wantRevision: "", - }, - []importedPackage{ - { - Name: importerTestProject, - LockHint: "", - }, - }, - }, - } - - for name, tc := range testcases { - name := name - tc := tc - t.Run(name, func(t *testing.T) { - err := tc.Exec(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { - i := newBaseImporter(logger, true, sm) - convertErr := i.importPackages(tc.projects, tc.defaultConstraintFromLock) - return i.manifest, i.lock, convertErr - }) - if err != nil { - t.Fatalf("%#v", err) - } - }) - } -} - -// convertTestCase is a common set of validations applied to the result -// of an importer converting from an external config format to dep's. -type convertTestCase struct { - defaultConstraintFromLock bool - wantConvertErr bool - wantSourceRepo string - wantConstraint string - wantRevision gps.Revision - wantVersion string - wantIgnored []string - wantWarning string -} - -func (tc convertTestCase) Exec(t *testing.T, convert func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error)) error { - h := test.NewHelper(t) - defer h.Cleanup() - // Disable parallel tests until we can resolve this error on the Windows builds: - // "remote repository at https://github.com/carolynvs/deptest-importers does not exist, or is inaccessible" - //h.Parallel() - - ctx := newTestContext(h) - sm, err := ctx.SourceManager() - h.Must(err) - defer sm.Release() - - // Capture stderr so we can verify warnings - output := &bytes.Buffer{} - ctx.Err = log.New(output, "", 0) - - manifest, lock, convertErr := convert(ctx.Err, sm) - return tc.validate(manifest, lock, convertErr, output) -} - -// validate returns an error if any of the testcase validations failed. -func (tc convertTestCase) validate(manifest *dep.Manifest, lock *dep.Lock, convertErr error, output *bytes.Buffer) error { - if tc.wantConvertErr { - if convertErr == nil { - return errors.New("Expected the conversion to fail, but it did not return an error") - } - return nil - } - - if convertErr != nil { - return errors.Wrap(convertErr, "Expected the conversion to pass, but it returned an error") - } - - if !equalSlice(manifest.Ignored, tc.wantIgnored) { - return errors.Errorf("unexpected set of ignored projects: \n\t(GOT) %v \n\t(WNT) %v", - manifest.Ignored, tc.wantIgnored) - } - - wantConstraintCount := 0 - if tc.wantConstraint != "" { - wantConstraintCount = 1 - } - gotConstraintCount := len(manifest.Constraints) - if gotConstraintCount != wantConstraintCount { - return errors.Errorf("unexpected number of constraints: \n\t(GOT) %v \n\t(WNT) %v", - gotConstraintCount, wantConstraintCount) - } - - if tc.wantConstraint != "" { - d, ok := manifest.Constraints[importerTestProject] - if !ok { - return errors.Errorf("Expected the manifest to have a dependency for '%v'", - importerTestProject) - } - - gotConstraint := d.Constraint.String() - if gotConstraint != tc.wantConstraint { - return errors.Errorf("unexpected constraint: \n\t(GOT) %v \n\t(WNT) %v", - gotConstraint, tc.wantConstraint) - } - - } - - // Lock checks. - wantLockCount := 0 - if tc.wantRevision != "" { - wantLockCount = 1 - } - gotLockCount := 0 - if lock != nil { - gotLockCount = len(lock.P) - } - if gotLockCount != wantLockCount { - return errors.Errorf("unexpected number of locked projects: \n\t(GOT) %v \n\t(WNT) %v", - gotLockCount, wantLockCount) - } - - if tc.wantRevision != "" { - lp := lock.P[0] - - gotProjectRoot := lp.Ident().ProjectRoot - if gotProjectRoot != importerTestProject { - return errors.Errorf("unexpected root project in lock: \n\t(GOT) %v \n\t(WNT) %v", - gotProjectRoot, importerTestProject) - } - - gotSource := lp.Ident().Source - if gotSource != tc.wantSourceRepo { - return errors.Errorf("unexpected source repository: \n\t(GOT) %v \n\t(WNT) %v", - gotSource, tc.wantSourceRepo) - } - - // Break down the locked "version" into a version (optional) and revision - var gotVersion string - var gotRevision gps.Revision - if lpv, ok := lp.Version().(gps.PairedVersion); ok { - gotVersion = lpv.String() - gotRevision = lpv.Revision() - } else if lr, ok := lp.Version().(gps.Revision); ok { - gotRevision = lr - } else { - return errors.New("could not determine the type of the locked version") - } - - if gotRevision != tc.wantRevision { - return errors.Errorf("unexpected locked revision: \n\t(GOT) %v \n\t(WNT) %v", - gotRevision, - tc.wantRevision) - } - if gotVersion != tc.wantVersion { - return errors.Errorf("unexpected locked version: \n\t(GOT) %v \n\t(WNT) %v", - gotVersion, - tc.wantVersion) - } - } - - if tc.wantWarning != "" { - if !strings.Contains(output.String(), tc.wantWarning) { - return errors.Errorf("Expected the output to include the warning '%s'", tc.wantWarning) - } - } - - return nil -} - -// equalSlice is comparing two string slices for equality. -func equalSlice(a, b []string) bool { - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - if len(a) != len(b) { - return false - } - - sort.Strings(a) - sort.Strings(b) - for i := range a { - if a[i] != b[i] { - return false - } - } - - return true -} diff --git a/cmd/dep/gopath_scanner.go b/cmd/dep/gopath_scanner.go index 48d3189ac9..77d14a0cd2 100644 --- a/cmd/dep/gopath_scanner.go +++ b/cmd/dep/gopath_scanner.go @@ -150,8 +150,8 @@ func contains(a []string, b string) bool { return false } -// getProjectPropertiesFromVersion takes a gps.Version and returns a proper -// gps.ProjectProperties with Constraint value based on the provided version. +// getProjectPropertiesFromVersion takes a Version and returns a proper +// ProjectProperties with Constraint value based on the provided version. func getProjectPropertiesFromVersion(v gps.Version) gps.ProjectProperties { pp := gps.ProjectProperties{} diff --git a/cmd/dep/gopath_scanner_test.go b/cmd/dep/gopath_scanner_test.go index fffa39eb14..142504ab4a 100644 --- a/cmd/dep/gopath_scanner_test.go +++ b/cmd/dep/gopath_scanner_test.go @@ -5,6 +5,8 @@ package main import ( + "io/ioutil" + "log" "reflect" "testing" @@ -16,12 +18,25 @@ import ( const testProject1 string = "github.com/sdboyer/deptest" const testProject2 string = "github.com/sdboyer/deptestdos" +// NewTestContext creates a unique context with its own GOPATH for a single test. +func NewTestContext(h *test.Helper) *dep.Ctx { + h.TempDir("src") + pwd := h.Path(".") + discardLogger := log.New(ioutil.Discard, "", 0) + + return &dep.Ctx{ + GOPATH: pwd, + Out: discardLogger, + Err: discardLogger, + } +} + func TestGopathScanner_OverlayManifestConstraints(t *testing.T) { h := test.NewHelper(t) h.Parallel() defer h.Cleanup() - ctx := newTestContext(h) + ctx := NewTestContext(h) pi1 := gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot(testProject1)} pi2 := gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot(testProject2)} @@ -74,7 +89,7 @@ func TestGopathScanner_OverlayLockProjects(t *testing.T) { h.Parallel() defer h.Cleanup() - ctx := newTestContext(h) + ctx := NewTestContext(h) rootM := dep.NewManifest() pi1 := gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot(testProject1)} diff --git a/cmd/dep/init_test.go b/cmd/dep/init_test.go index f853fe0193..18c2fb540f 100644 --- a/cmd/dep/init_test.go +++ b/cmd/dep/init_test.go @@ -5,9 +5,8 @@ package main import ( - "testing" - "path/filepath" + "testing" "github.com/golang/dep" "github.com/golang/dep/internal/gps" @@ -18,7 +17,7 @@ func TestGetDirectDependencies_ConsolidatesRootProjects(t *testing.T) { h := test.NewHelper(t) defer h.Cleanup() - ctx := newTestContext(h) + ctx := NewTestContext(h) sm, err := ctx.SourceManager() h.Must(err) defer sm.Release() diff --git a/cmd/dep/prune_test.go b/cmd/dep/prune_test.go index a1bc2b7ffe..8a9c1d1d96 100644 --- a/cmd/dep/prune_test.go +++ b/cmd/dep/prune_test.go @@ -5,6 +5,8 @@ package main import ( + "io/ioutil" + "log" "path/filepath" "reflect" "sort" @@ -27,6 +29,8 @@ func TestCalculatePrune(t *testing.T) { filepath.FromSlash("github.com/keep/pkg/sub"), } + discardLogger := log.New(ioutil.Discard, "", 0) + got, err := calculatePrune(h.Path(vendorDir), toKeep, discardLogger) if err != nil { t.Fatal(err) diff --git a/cmd/dep/root_analyzer.go b/cmd/dep/root_analyzer.go index 471b673b33..c300eadad8 100644 --- a/cmd/dep/root_analyzer.go +++ b/cmd/dep/root_analyzer.go @@ -11,16 +11,9 @@ import ( "github.com/golang/dep" fb "github.com/golang/dep/internal/feedback" "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers" ) -// importer handles importing configuration from other dependency managers into -// the dep configuration format. -type importer interface { - Name() string - Import(path string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) - HasDepMetadata(dir string) bool -} - // rootAnalyzer supplies manifest/lock data from both dep and external tool's // configuration files. // * When used on the root project, it imports only from external tools. @@ -66,14 +59,7 @@ func (a *rootAnalyzer) importManifestAndLock(dir string, pr gps.ProjectRoot, sup logger = log.New(ioutil.Discard, "", 0) } - importers := []importer{ - newGlideImporter(logger, a.ctx.Verbose, a.sm), - newGodepImporter(logger, a.ctx.Verbose, a.sm), - newVndrImporter(logger, a.ctx.Verbose, a.sm), - newGovendImporter(logger, a.ctx.Verbose, a.sm), - } - - for _, i := range importers { + for _, i := range importers.BuildAll(logger, a.ctx.Verbose, a.sm) { if i.HasDepMetadata(dir) { a.ctx.Err.Printf("Importing configuration from %s. These are only initial constraints, and are further refined during the solve process.", i.Name()) m, l, err := i.Import(dir, pr) diff --git a/cmd/dep/base_importer.go b/internal/importers/base/importer.go similarity index 76% rename from cmd/dep/base_importer.go rename to internal/importers/base/importer.go index 8e81e8bf42..0a572fea6d 100644 --- a/cmd/dep/base_importer.go +++ b/internal/importers/base/importer.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package base import ( "log" @@ -13,29 +13,30 @@ import ( "github.com/pkg/errors" ) -// baseImporter provides a common implementation for importing from other +// Importer provides a common implementation for importing from other // dependency managers. -type baseImporter struct { - logger *log.Logger - verbose bool - sm gps.SourceManager - manifest *dep.Manifest - lock *dep.Lock +type Importer struct { + sm gps.SourceManager + + Logger *log.Logger + Verbose bool + Manifest *dep.Manifest + Lock *dep.Lock } -// newBaseImporter creates a new baseImporter for embedding in an importer. -func newBaseImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *baseImporter { - return &baseImporter{ - logger: logger, - verbose: verbose, - manifest: dep.NewManifest(), - lock: &dep.Lock{}, +// NewImporter creates a new Importer for embedding in an importer. +func NewImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *Importer { + return &Importer{ + Logger: logger, + Verbose: verbose, + Manifest: dep.NewManifest(), + Lock: &dep.Lock{}, sm: sm, } } // isTag determines if the specified value is a tag (plain or semver). -func (i *baseImporter) isTag(pi gps.ProjectIdentifier, value string) (bool, gps.Version, error) { +func (i *Importer) isTag(pi gps.ProjectIdentifier, value string) (bool, gps.Version, error) { versions, err := i.sm.ListVersions(pi) if err != nil { return false, nil, errors.Wrapf(err, "unable to list versions for %s(%s)", pi.ProjectRoot, pi.Source) @@ -58,7 +59,7 @@ func (i *baseImporter) isTag(pi gps.ProjectIdentifier, value string) (bool, gps. // project based on the locked revision and the constraint from the manifest. // First try matching the revision to a version, then try the constraint from the // manifest, then finally the revision. -func (i *baseImporter) lookupVersionForLockedProject(pi gps.ProjectIdentifier, c gps.Constraint, rev gps.Revision) (gps.Version, error) { +func (i *Importer) lookupVersionForLockedProject(pi gps.ProjectIdentifier, c gps.Constraint, rev gps.Revision) (gps.Version, error) { // Find the version that goes with this revision, if any versions, err := i.sm.ListVersions(pi) if err != nil { @@ -98,9 +99,9 @@ func (i *baseImporter) lookupVersionForLockedProject(pi gps.ProjectIdentifier, c return rev, nil } -// importedPackage is a common intermediate representation of a package imported +// ImportedPackage is a common intermediate representation of a package imported // from an external tool's configuration. -type importedPackage struct { +type ImportedPackage struct { // Required. The package path, not necessarily the project root. Name string @@ -118,11 +119,11 @@ type importedPackage struct { // for the same project root. type importedProject struct { Root gps.ProjectRoot - importedPackage + ImportedPackage } // loadPackages consolidates all package references into a set of project roots. -func (i *baseImporter) loadPackages(packages []importedPackage) ([]importedProject, error) { +func (i *Importer) loadPackages(packages []ImportedPackage) ([]importedProject, error) { // preserve the original order of the packages so that messages that // are printed as they are processed are in a consistent order. orderedProjects := make([]importedProject, 0, len(packages)) @@ -161,7 +162,7 @@ func (i *baseImporter) loadPackages(packages []importedPackage) ([]importedProje return orderedProjects, nil } -// importPackages loads imported packages into the manifest and lock. +// ImportPackages loads imported packages into the manifest and lock. // - defaultConstraintFromLock specifies if a constraint should be defaulted // based on the locked version when there wasn't a constraint hint. // @@ -172,7 +173,7 @@ func (i *baseImporter) loadPackages(packages []importedPackage) ([]importedProje // * Revision constraints are ignored. // * Versions that don't satisfy the constraint, drop the constraint. // * Untagged revisions ignore non-branch constraint hints. -func (i *baseImporter) importPackages(packages []importedPackage, defaultConstraintFromLock bool) (err error) { +func (i *Importer) ImportPackages(packages []ImportedPackage, defaultConstraintFromLock bool) (err error) { projects, err := i.loadPackages(packages) if err != nil { return err @@ -206,23 +207,23 @@ func (i *baseImporter) importPackages(packages []importedPackage, defaultConstra version, err = i.lookupVersionForLockedProject(pc.Ident, pc.Constraint, revision) if err != nil { version = nil - i.logger.Println(err) + i.Logger.Println(err) } } // Default the constraint based on the locked version if defaultConstraintFromLock && prj.ConstraintHint == "" && version != nil { - props := getProjectPropertiesFromVersion(version) - if props.Constraint != nil { - pc.Constraint = props.Constraint + c := i.convertToConstraint(version) + if c != nil { + pc.Constraint = c } } } // Ignore pinned constraints if i.isConstraintPinned(pc.Constraint) { - if i.verbose { - i.logger.Printf(" Ignoring pinned constraint %v for %v.\n", pc.Constraint, pc.Ident) + if i.Verbose { + i.Logger.Printf(" Ignoring pinned constraint %v for %v.\n", pc.Constraint, pc.Ident) } pc.Constraint = gps.Any() } @@ -230,22 +231,22 @@ func (i *baseImporter) importPackages(packages []importedPackage, defaultConstra // Ignore constraints which conflict with the locked revision, so that // solve doesn't later change the revision to satisfy the constraint. if !i.testConstraint(pc.Constraint, version) { - if i.verbose { - i.logger.Printf(" Ignoring constraint %v for %v because it would invalidate the locked version %v.\n", pc.Constraint, pc.Ident, version) + if i.Verbose { + i.Logger.Printf(" Ignoring constraint %v for %v because it would invalidate the locked version %v.\n", pc.Constraint, pc.Ident, version) } pc.Constraint = gps.Any() } - i.manifest.Constraints[pc.Ident.ProjectRoot] = gps.ProjectProperties{ + i.Manifest.Constraints[pc.Ident.ProjectRoot] = gps.ProjectProperties{ Source: pc.Ident.Source, Constraint: pc.Constraint, } - fb.NewConstraintFeedback(pc, fb.DepTypeImported).LogFeedback(i.logger) + fb.NewConstraintFeedback(pc, fb.DepTypeImported).LogFeedback(i.Logger) if version != nil { lp := gps.NewLockedProject(pc.Ident, version, nil) - i.lock.P = append(i.lock.P, lp) - fb.NewLockedProjectFeedback(lp, fb.DepTypeImported).LogFeedback(i.logger) + i.Lock.P = append(i.Lock.P, lp) + fb.NewLockedProjectFeedback(lp, fb.DepTypeImported).LogFeedback(i.Logger) } } @@ -253,7 +254,7 @@ func (i *baseImporter) importPackages(packages []importedPackage, defaultConstra } // isConstraintPinned returns if a constraint is pinned to a specific revision. -func (i *baseImporter) isConstraintPinned(c gps.Constraint) bool { +func (i *Importer) isConstraintPinned(c gps.Constraint) bool { if version, isVersion := c.(gps.Version); isVersion { switch version.Type() { case gps.IsRevision, gps.IsVersion: @@ -264,7 +265,7 @@ func (i *baseImporter) isConstraintPinned(c gps.Constraint) bool { } // testConstraint verifies that the constraint won't invalidate the locked version. -func (i *baseImporter) testConstraint(c gps.Constraint, v gps.Version) bool { +func (i *Importer) testConstraint(c gps.Constraint, v gps.Version) bool { // Assume branch constraints are satisfied if version, isVersion := c.(gps.Version); isVersion { if version.Type() == gps.IsBranch { @@ -275,3 +276,18 @@ func (i *baseImporter) testConstraint(c gps.Constraint, v gps.Version) bool { return c.Matches(v) } + +// convertToConstraint turns a version into a constraint. +// Semver tags are converted to a range with the caret operator. +func (i *Importer) convertToConstraint(v gps.Version) gps.Constraint { + if v.Type() == gps.IsSemver { + c, err := gps.NewSemverConstraintIC(v.String()) + if err != nil { + // This should never fail, because the type is semver. + // If it does fail somehow, don't let that impact the import. + return nil + } + return c + } + return v +} diff --git a/internal/importers/base/importer_test.go b/internal/importers/base/importer_test.go new file mode 100644 index 0000000000..a70dd8fb03 --- /dev/null +++ b/internal/importers/base/importer_test.go @@ -0,0 +1,389 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package base + +import ( + "log" + "testing" + + "github.com/golang/dep" + "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/importertest" + "github.com/golang/dep/internal/test" +) + +func TestBaseImporter_IsTag(t *testing.T) { + testcases := map[string]struct { + input string + wantIsTag bool + wantTag gps.Version + }{ + "non-semver tag": { + input: importertest.Beta1Tag, + wantIsTag: true, + wantTag: gps.NewVersion(importertest.Beta1Tag).Pair(importertest.Beta1Rev), + }, + "semver-tag": { + input: importertest.V1PatchTag, + wantIsTag: true, + wantTag: gps.NewVersion(importertest.V1PatchTag).Pair(importertest.V1PatchRev)}, + "untagged revision": { + input: importertest.UntaggedRev, + wantIsTag: false, + }, + "branch name": { + input: importertest.V2Branch, + wantIsTag: false, + }, + "empty": { + input: "", + wantIsTag: false, + }, + } + + pi := gps.ProjectIdentifier{ProjectRoot: importertest.Project} + + for name, tc := range testcases { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + h := test.NewHelper(t) + defer h.Cleanup() + // Disable parallel tests until we can resolve this error on the Windows builds: + // "remote repository at https://github.com/carolynvs/deptest-importers does not exist, or is inaccessible" + //h.Parallel() + + ctx := importertest.NewTestContext(h) + sm, err := ctx.SourceManager() + h.Must(err) + defer sm.Release() + + i := NewImporter(ctx.Err, ctx.Verbose, sm) + gotIsTag, gotTag, err := i.isTag(pi, tc.input) + h.Must(err) + + if tc.wantIsTag != gotIsTag { + t.Fatalf("unexpected isTag result for %v: \n\t(GOT) %v \n\t(WNT) %v", + tc.input, gotIsTag, tc.wantIsTag) + } + + if tc.wantTag != gotTag { + t.Fatalf("unexpected tag for %v: \n\t(GOT) %v \n\t(WNT) %v", + tc.input, gotTag, tc.wantTag) + } + }) + } +} + +func TestBaseImporter_LookupVersionForLockedProject(t *testing.T) { + testcases := map[string]struct { + revision gps.Revision + constraint gps.Constraint + wantVersion string + }{ + "match revision to tag": { + revision: importertest.V1PatchRev, + wantVersion: importertest.V1PatchTag, + }, + "match revision with multiple tags using constraint": { + revision: importertest.MultiTaggedRev, + constraint: gps.NewVersion(importertest.MultiTaggedPlainTag), + wantVersion: importertest.MultiTaggedPlainTag, + }, + "revision with multiple tags with no constraint defaults to best match": { + revision: importertest.MultiTaggedRev, + wantVersion: importertest.MultiTaggedSemverTag, + }, + "revision with multiple tags with nonmatching constraint defaults to best match": { + revision: importertest.MultiTaggedRev, + constraint: gps.NewVersion("thismatchesnothing"), + wantVersion: importertest.MultiTaggedSemverTag, + }, + "untagged revision fallback to branch constraint": { + revision: importertest.UntaggedRev, + constraint: gps.NewBranch("master"), + wantVersion: "master", + }, + "fallback to revision": { + revision: importertest.UntaggedRev, + wantVersion: importertest.UntaggedRev, + }, + } + + pi := gps.ProjectIdentifier{ProjectRoot: importertest.Project} + + for name, tc := range testcases { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + h := test.NewHelper(t) + defer h.Cleanup() + // Disable parallel tests until we can resolve this error on the Windows builds: + // "remote repository at https://github.com/carolynvs/deptest-importers does not exist, or is inaccessible" + //h.Parallel() + + ctx := importertest.NewTestContext(h) + sm, err := ctx.SourceManager() + h.Must(err) + defer sm.Release() + + i := NewImporter(ctx.Err, ctx.Verbose, sm) + v, err := i.lookupVersionForLockedProject(pi, tc.constraint, tc.revision) + h.Must(err) + + gotVersion := v.String() + if gotVersion != tc.wantVersion { + t.Fatalf("unexpected locked version: \n\t(GOT) %v\n\t(WNT) %v", gotVersion, tc.wantVersion) + } + }) + } +} + +func TestBaseImporter_ImportProjects(t *testing.T) { + testcases := map[string]struct { + importertest.TestCase + projects []ImportedPackage + }{ + "tag constraints are ignored": { + importertest.TestCase{ + WantConstraint: "*", + WantVersion: importertest.Beta1Tag, + WantRevision: importertest.Beta1Rev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.Beta1Rev, + ConstraintHint: importertest.Beta1Tag, + }, + }, + }, + "tag lock hints Lock to tagged revision": { + importertest.TestCase{ + WantConstraint: "*", + WantVersion: importertest.Beta1Tag, + WantRevision: importertest.Beta1Rev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.Beta1Tag, + }, + }, + }, + "untagged revision ignores range constraint": { + importertest.TestCase{ + WantConstraint: "*", + WantRevision: importertest.UntaggedRev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.UntaggedRev, + ConstraintHint: importertest.V1Constraint, + }, + }, + }, + "untagged revision keeps branch constraint": { + importertest.TestCase{ + WantConstraint: "master", + WantVersion: "master", + WantRevision: importertest.UntaggedRev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.UntaggedRev, + ConstraintHint: "master", + }, + }, + }, + "HEAD revisions default constraint to the matching branch": { + importertest.TestCase{ + DefaultConstraintFromLock: true, + WantConstraint: importertest.V2Branch, + WantVersion: importertest.V2Branch, + WantRevision: importertest.V2Rev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.V2Rev, + }, + }, + }, + "Semver tagged revisions default to ^VERSION": { + importertest.TestCase{ + DefaultConstraintFromLock: true, + WantConstraint: importertest.V1Constraint, + WantVersion: importertest.V1Tag, + WantRevision: importertest.V1Rev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.V1Rev, + }, + }, + }, + "Semver lock hint defaults constraint to ^VERSION": { + importertest.TestCase{ + DefaultConstraintFromLock: true, + WantConstraint: importertest.V1Constraint, + WantVersion: importertest.V1Tag, + WantRevision: importertest.V1Rev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.V1Tag, + }, + }, + }, + "Semver constraint hint": { + importertest.TestCase{ + WantConstraint: importertest.V1Constraint, + WantVersion: importertest.V1PatchTag, + WantRevision: importertest.V1PatchRev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.V1PatchRev, + ConstraintHint: importertest.V1Constraint, + }, + }, + }, + "Semver prerelease lock hint": { + importertest.TestCase{ + WantConstraint: importertest.V2Branch, + WantVersion: importertest.V2PatchTag, + WantRevision: importertest.V2PatchRev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.V2PatchRev, + ConstraintHint: importertest.V2Branch, + }, + }, + }, + "Revision constraints are ignored": { + importertest.TestCase{ + WantConstraint: "*", + WantVersion: importertest.V1Tag, + WantRevision: importertest.V1Rev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.V1Rev, + ConstraintHint: importertest.V1Rev, + }, + }, + }, + "Branch constraint hint": { + importertest.TestCase{ + WantConstraint: "master", + WantVersion: importertest.V1Tag, + WantRevision: importertest.V1Rev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.V1Rev, + ConstraintHint: "master", + }, + }, + }, + "Non-matching semver constraint is ignored": { + importertest.TestCase{ + WantConstraint: "*", + WantVersion: importertest.V1Tag, + WantRevision: importertest.V1Rev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.V1Rev, + ConstraintHint: "^2.0.0", + }, + }, + }, + "git describe constraint is ignored": { + importertest.TestCase{ + WantConstraint: "*", + WantRevision: importertest.UntaggedRev, + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: importertest.UntaggedRev, + ConstraintHint: importertest.UntaggedRevAbbrv, + }, + }, + }, + "consolidate subpackages under root": { + importertest.TestCase{ + WantConstraint: "master", + WantVersion: "master", + WantRevision: importertest.UntaggedRev, + }, + []ImportedPackage{ + { + Name: importertest.Project + "/subpkA", + ConstraintHint: "master", + }, + { + Name: importertest.Project, + LockHint: importertest.UntaggedRev, + }, + }, + }, + "ignore duplicate packages": { + importertest.TestCase{ + WantConstraint: "*", + WantRevision: importertest.UntaggedRev, + }, + []ImportedPackage{ + { + Name: importertest.Project + "/subpkgA", + LockHint: importertest.UntaggedRev, // first wins + }, + { + Name: importertest.Project + "/subpkgB", + LockHint: importertest.V1Rev, + }, + }, + }, + "skip empty lock hints": { + importertest.TestCase{ + WantConstraint: "*", + WantRevision: "", + }, + []ImportedPackage{ + { + Name: importertest.Project, + LockHint: "", + }, + }, + }, + } + + for name, tc := range testcases { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + err := tc.Execute(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { + i := NewImporter(logger, true, sm) + convertErr := i.ImportPackages(tc.projects, tc.DefaultConstraintFromLock) + return i.Manifest, i.Lock, convertErr + }) + if err != nil { + t.Fatalf("%#v", err) + } + }) + } +} diff --git a/cmd/dep/glide_importer.go b/internal/importers/glide/importer.go similarity index 72% rename from cmd/dep/glide_importer.go rename to internal/importers/glide/importer.go index e5dae70bff..1745b1375c 100644 --- a/cmd/dep/glide_importer.go +++ b/internal/importers/glide/importer.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package glide import ( "bytes" @@ -16,22 +16,24 @@ import ( "github.com/golang/dep" "github.com/golang/dep/internal/fs" "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/base" "github.com/pkg/errors" ) const glideYamlName = "glide.yaml" const glideLockName = "glide.lock" -// glideImporter imports glide configuration into the dep configuration format. -type glideImporter struct { - *baseImporter +// Importer imports glide configuration into the dep configuration format. +type Importer struct { + *base.Importer glideConfig glideYaml glideLock glideLock lockFound bool } -func newGlideImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *glideImporter { - return &glideImporter{baseImporter: newBaseImporter(logger, verbose, sm)} +// NewImporter for glide. +func NewImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *Importer { + return &Importer{Importer: base.NewImporter(logger, verbose, sm)} } type glideYaml struct { @@ -64,11 +66,13 @@ type glideLockedPackage struct { Repository string `yaml:"repo"` } -func (g *glideImporter) Name() string { +// Name of the importer. +func (g *Importer) Name() string { return "glide" } -func (g *glideImporter) HasDepMetadata(dir string) bool { +// HasDepMetadata checks if a directory contains config that the importer can handle. +func (g *Importer) HasDepMetadata(dir string) bool { // Only require glide.yaml, the lock is optional y := filepath.Join(dir, glideYamlName) if _, err := os.Stat(y); err != nil { @@ -78,7 +82,8 @@ func (g *glideImporter) HasDepMetadata(dir string) bool { return true } -func (g *glideImporter) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { +// Import the config found in the directory. +func (g *Importer) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { err := g.load(dir) if err != nil { return nil, nil, err @@ -88,11 +93,11 @@ func (g *glideImporter) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, * } // load the glide configuration files. -func (g *glideImporter) load(projectDir string) error { - g.logger.Println("Detected glide configuration files...") +func (g *Importer) load(projectDir string) error { + g.Logger.Println("Detected glide configuration files...") y := filepath.Join(projectDir, glideYamlName) - if g.verbose { - g.logger.Printf(" Loading %s", y) + if g.Verbose { + g.Logger.Printf(" Loading %s", y) } yb, err := ioutil.ReadFile(y) if err != nil { @@ -105,8 +110,8 @@ func (g *glideImporter) load(projectDir string) error { l := filepath.Join(projectDir, glideLockName) if exists, _ := fs.IsRegular(l); exists { - if g.verbose { - g.logger.Printf(" Loading %s", l) + if g.Verbose { + g.Logger.Printf(" Loading %s", l) } g.lockFound = true lb, err := ioutil.ReadFile(l) @@ -125,7 +130,7 @@ func (g *glideImporter) load(projectDir string) error { } // convert the glide configuration files into dep configuration files. -func (g *glideImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { +func (g *Importer) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { projectName := string(pr) task := bytes.NewBufferString("Converting from glide.yaml") @@ -133,10 +138,10 @@ func (g *glideImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, e task.WriteString(" and glide.lock") } task.WriteString("...") - g.logger.Println(task) + g.Logger.Println(task) numPkgs := len(g.glideConfig.Imports) + len(g.glideConfig.TestImports) + len(g.glideLock.Imports) + len(g.glideLock.TestImports) - packages := make([]importedPackage, 0, numPkgs) + packages := make([]base.ImportedPackage, 0, numPkgs) // Constraints for _, pkg := range append(g.glideConfig.Imports, g.glideConfig.TestImports...) { @@ -146,16 +151,16 @@ func (g *glideImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, e } // Warn - if g.verbose { + if g.Verbose { if pkg.OS != "" { - g.logger.Printf(" The %s package specified an os, but that isn't supported by dep yet, and will be ignored. See https://github.com/golang/dep/issues/291.\n", pkg.Name) + g.Logger.Printf(" The %s package specified an os, but that isn't supported by dep yet, and will be ignored. See https://github.com/golang/dep/issues/291.\n", pkg.Name) } if pkg.Arch != "" { - g.logger.Printf(" The %s package specified an arch, but that isn't supported by dep yet, and will be ignored. See https://github.com/golang/dep/issues/291.\n", pkg.Name) + g.Logger.Printf(" The %s package specified an arch, but that isn't supported by dep yet, and will be ignored. See https://github.com/golang/dep/issues/291.\n", pkg.Name) } } - ip := importedPackage{ + ip := base.ImportedPackage{ Name: pkg.Name, Source: pkg.Repository, ConstraintHint: pkg.Reference, @@ -170,7 +175,7 @@ func (g *glideImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, e return nil, nil, errors.New("invalid glide lock: Name is required") } - ip := importedPackage{ + ip := base.ImportedPackage{ Name: pkg.Name, Source: pkg.Repository, LockHint: pkg.Revision, @@ -178,23 +183,23 @@ func (g *glideImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, e packages = append(packages, ip) } - err := g.importPackages(packages, false) + err := g.ImportPackages(packages, false) if err != nil { return nil, nil, errors.Wrap(err, "invalid glide configuration") } // Ignores - g.manifest.Ignored = append(g.manifest.Ignored, g.glideConfig.Ignores...) + g.Manifest.Ignored = append(g.Manifest.Ignored, g.glideConfig.Ignores...) if len(g.glideConfig.ExcludeDirs) > 0 { if g.glideConfig.Name != "" && g.glideConfig.Name != projectName { - g.logger.Printf(" Glide thinks the package is '%s' but dep thinks it is '%s', using dep's value.\n", g.glideConfig.Name, projectName) + g.Logger.Printf(" Glide thinks the package is '%s' but dep thinks it is '%s', using dep's value.\n", g.glideConfig.Name, projectName) } for _, dir := range g.glideConfig.ExcludeDirs { pkg := path.Join(projectName, dir) - g.manifest.Ignored = append(g.manifest.Ignored, pkg) + g.Manifest.Ignored = append(g.Manifest.Ignored, pkg) } } - return g.manifest, g.lock, nil + return g.Manifest, g.Lock, nil } diff --git a/cmd/dep/glide_importer_test.go b/internal/importers/glide/importer_test.go similarity index 52% rename from cmd/dep/glide_importer_test.go rename to internal/importers/glide/importer_test.go index 2440b6fa8c..eb2099a458 100644 --- a/cmd/dep/glide_importer_test.go +++ b/internal/importers/glide/importer_test.go @@ -2,116 +2,102 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package glide import ( "bytes" - "io/ioutil" "log" "path/filepath" "testing" "github.com/golang/dep" "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/importertest" "github.com/golang/dep/internal/test" "github.com/pkg/errors" ) -var ( - discardLogger = log.New(ioutil.Discard, "", 0) -) - -func newTestContext(h *test.Helper) *dep.Ctx { - h.TempDir("src") - pwd := h.Path(".") - return &dep.Ctx{ - GOPATH: pwd, - Out: discardLogger, - Err: discardLogger, - } -} - func TestGlideConfig_Convert(t *testing.T) { testCases := map[string]struct { yaml glideYaml lock glideLock - convertTestCase + importertest.TestCase }{ "project": { glideYaml{ Imports: []glidePackage{ { - Name: importerTestProject, - Repository: importerTestProjectSrc, - Reference: importerTestV2Branch, + Name: importertest.Project, + Repository: importertest.ProjectSrc, + Reference: importertest.V2Branch, }, }, }, glideLock{ Imports: []glideLockedPackage{ { - Name: importerTestProject, - Repository: importerTestProjectSrc, - Revision: importerTestV2PatchRev, + Name: importertest.Project, + Repository: importertest.ProjectSrc, + Revision: importertest.V2PatchRev, }, }, }, - convertTestCase{ - wantSourceRepo: importerTestProjectSrc, - wantConstraint: importerTestV2Branch, - wantRevision: importerTestV2PatchRev, - wantVersion: importerTestV2PatchTag, + importertest.TestCase{ + WantSourceRepo: importertest.ProjectSrc, + WantConstraint: importertest.V2Branch, + WantRevision: importertest.V2PatchRev, + WantVersion: importertest.V2PatchTag, }, }, "test project": { glideYaml{ Imports: []glidePackage{ { - Name: importerTestProject, - Repository: importerTestProjectSrc, - Reference: importerTestV2Branch, + Name: importertest.Project, + Repository: importertest.ProjectSrc, + Reference: importertest.V2Branch, }, }, }, glideLock{ Imports: []glideLockedPackage{ { - Name: importerTestProject, - Repository: importerTestProjectSrc, - Revision: importerTestV2PatchRev, + Name: importertest.Project, + Repository: importertest.ProjectSrc, + Revision: importertest.V2PatchRev, }, }, }, - convertTestCase{ - wantSourceRepo: importerTestProjectSrc, - wantConstraint: importerTestV2Branch, - wantRevision: importerTestV2PatchRev, - wantVersion: importerTestV2PatchTag, + importertest.TestCase{ + WantSourceRepo: importertest.ProjectSrc, + WantConstraint: importertest.V2Branch, + WantRevision: importertest.V2PatchRev, + WantVersion: importertest.V2PatchTag, }, }, "yaml only": { glideYaml{ Imports: []glidePackage{ { - Name: importerTestProject, - Repository: importerTestProjectSrc, - Reference: importerTestV2Branch, + Name: importertest.Project, + Repository: importertest.ProjectSrc, + Reference: importertest.V2Branch, }, }, }, glideLock{}, - convertTestCase{ - wantSourceRepo: importerTestProjectSrc, - wantConstraint: importerTestV2Branch, + importertest.TestCase{ + WantSourceRepo: importertest.ProjectSrc, + WantConstraint: importertest.V2Branch, }, }, "ignored package": { glideYaml{ - Ignores: []string{importerTestProject}, + Ignores: []string{importertest.Project}, }, glideLock{}, - convertTestCase{ - wantIgnored: []string{importerTestProject}, + importertest.TestCase{ + WantIgnored: []string{importertest.Project}, }, }, "exclude dir": { @@ -119,8 +105,8 @@ func TestGlideConfig_Convert(t *testing.T) { ExcludeDirs: []string{"samples"}, }, glideLock{}, - convertTestCase{ - wantIgnored: []string{testProjectRoot + "/samples"}, + importertest.TestCase{ + WantIgnored: []string{importertest.RootProject + "/samples"}, }, }, "exclude dir ignores mismatched package name": { @@ -129,8 +115,8 @@ func TestGlideConfig_Convert(t *testing.T) { ExcludeDirs: []string{"samples"}, }, glideLock{}, - convertTestCase{ - wantIgnored: []string{testProjectRoot + "/samples"}, + importertest.TestCase{ + WantIgnored: []string{importertest.RootProject + "/samples"}, }, }, "missing package name": { @@ -138,36 +124,36 @@ func TestGlideConfig_Convert(t *testing.T) { Imports: []glidePackage{{Name: ""}}, }, glideLock{}, - convertTestCase{ - wantConvertErr: true, + importertest.TestCase{ + WantConvertErr: true, }, }, "warn unused os field": { glideYaml{ Imports: []glidePackage{ { - Name: importerTestProject, + Name: importertest.Project, OS: "windows", }, }}, glideLock{}, - convertTestCase{ - wantConstraint: "*", - wantWarning: "specified an os", + importertest.TestCase{ + WantConstraint: "*", + WantWarning: "specified an os", }, }, "warn unused arch field": { glideYaml{ Imports: []glidePackage{ { - Name: importerTestProject, + Name: importertest.Project, Arch: "i686", }, }}, glideLock{}, - convertTestCase{ - wantConstraint: "*", - wantWarning: "specified an arch", + importertest.TestCase{ + WantConstraint: "*", + WantWarning: "specified an arch", }, }, } @@ -176,11 +162,11 @@ func TestGlideConfig_Convert(t *testing.T) { name := name testCase := testCase t.Run(name, func(t *testing.T) { - err := testCase.Exec(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { - g := newGlideImporter(logger, true, sm) + err := testCase.Execute(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { + g := NewImporter(logger, true, sm) g.glideConfig = testCase.yaml g.glideLock = testCase.lock - return g.convert(testProjectRoot) + return g.convert(importertest.RootProject) }) if err != nil { t.Fatalf("%#v", err) @@ -193,26 +179,26 @@ func TestGlideConfig_Import(t *testing.T) { h := test.NewHelper(t) defer h.Cleanup() - ctx := newTestContext(h) + ctx := importertest.NewTestContext(h) sm, err := ctx.SourceManager() h.Must(err) defer sm.Release() - h.TempDir(filepath.Join("src", testProjectRoot)) - h.TempCopy(filepath.Join(testProjectRoot, glideYamlName), "init/glide/glide.yaml") - h.TempCopy(filepath.Join(testProjectRoot, glideLockName), "init/glide/glide.lock") - projectRoot := h.Path(testProjectRoot) + h.TempDir(filepath.Join("src", importertest.RootProject)) + h.TempCopy(filepath.Join(importertest.RootProject, glideYamlName), "glide.yaml") + h.TempCopy(filepath.Join(importertest.RootProject, glideLockName), "glide.lock") + projectRoot := h.Path(importertest.RootProject) // Capture stderr so we can verify output verboseOutput := &bytes.Buffer{} ctx.Err = log.New(verboseOutput, "", 0) - g := newGlideImporter(ctx.Err, false, sm) // Disable verbose so that we don't print values that change each test run + g := NewImporter(ctx.Err, false, sm) // Disable verbose so that we don't print values that change each test run if !g.HasDepMetadata(projectRoot) { t.Fatal("Expected the importer to detect the glide configuration files") } - m, l, err := g.Import(projectRoot, testProjectRoot) + m, l, err := g.Import(projectRoot, importertest.RootProject) h.Must(err) if m == nil { @@ -223,7 +209,7 @@ func TestGlideConfig_Import(t *testing.T) { t.Fatal("Expected the lock to be generated") } - goldenFile := "init/glide/golden.txt" + goldenFile := "golden.txt" got := verboseOutput.String() want := h.GetTestFileString(goldenFile) if want != got { diff --git a/cmd/dep/testdata/init/glide/glide.lock b/internal/importers/glide/testdata/glide.lock similarity index 100% rename from cmd/dep/testdata/init/glide/glide.lock rename to internal/importers/glide/testdata/glide.lock diff --git a/cmd/dep/testdata/init/glide/glide.yaml b/internal/importers/glide/testdata/glide.yaml similarity index 100% rename from cmd/dep/testdata/init/glide/glide.yaml rename to internal/importers/glide/testdata/glide.yaml diff --git a/cmd/dep/testdata/init/glide/golden.txt b/internal/importers/glide/testdata/golden.txt similarity index 100% rename from cmd/dep/testdata/init/glide/golden.txt rename to internal/importers/glide/testdata/golden.txt diff --git a/cmd/dep/godep_importer.go b/internal/importers/godep/importer.go similarity index 57% rename from cmd/dep/godep_importer.go rename to internal/importers/godep/importer.go index 9d84e3e56d..e35636ec51 100644 --- a/cmd/dep/godep_importer.go +++ b/internal/importers/godep/importer.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package godep import ( "encoding/json" @@ -13,18 +13,21 @@ import ( "github.com/golang/dep" "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/base" "github.com/pkg/errors" ) const godepPath = "Godeps" + string(os.PathSeparator) + "Godeps.json" -type godepImporter struct { - *baseImporter +// Importer imports godep configuration into the dep configuration format. +type Importer struct { + *base.Importer json godepJSON } -func newGodepImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *godepImporter { - return &godepImporter{baseImporter: newBaseImporter(logger, verbose, sm)} +// NewImporter for godep. +func NewImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *Importer { + return &Importer{Importer: base.NewImporter(logger, verbose, sm)} } type godepJSON struct { @@ -37,11 +40,13 @@ type godepPackage struct { Comment string `json:"Comment"` } -func (g *godepImporter) Name() string { +// Name of the importer. +func (g *Importer) Name() string { return "godep" } -func (g *godepImporter) HasDepMetadata(dir string) bool { +// HasDepMetadata checks if a directory contains config that the importer can handle. +func (g *Importer) HasDepMetadata(dir string) bool { y := filepath.Join(dir, godepPath) if _, err := os.Stat(y); err != nil { return false @@ -50,7 +55,8 @@ func (g *godepImporter) HasDepMetadata(dir string) bool { return true } -func (g *godepImporter) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { +// Import the config found in the directory. +func (g *Importer) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { err := g.load(dir) if err != nil { return nil, nil, err @@ -59,11 +65,11 @@ func (g *godepImporter) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, * return g.convert(pr) } -func (g *godepImporter) load(projectDir string) error { - g.logger.Println("Detected godep configuration files...") +func (g *Importer) load(projectDir string) error { + g.Logger.Println("Detected godep configuration files...") j := filepath.Join(projectDir, godepPath) - if g.verbose { - g.logger.Printf(" Loading %s", j) + if g.Verbose { + g.Logger.Printf(" Loading %s", j) } jb, err := ioutil.ReadFile(j) if err != nil { @@ -77,10 +83,10 @@ func (g *godepImporter) load(projectDir string) error { return nil } -func (g *godepImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { - g.logger.Println("Converting from Godeps.json ...") +func (g *Importer) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { + g.Logger.Println("Converting from Godeps.json ...") - packages := make([]importedPackage, 0, len(g.json.Imports)) + packages := make([]base.ImportedPackage, 0, len(g.json.Imports)) for _, pkg := range g.json.Imports { // Validate if pkg.ImportPath == "" { @@ -93,7 +99,7 @@ func (g *godepImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, e return nil, nil, err } - ip := importedPackage{ + ip := base.ImportedPackage{ Name: pkg.ImportPath, LockHint: pkg.Rev, ConstraintHint: pkg.Comment, @@ -101,10 +107,10 @@ func (g *godepImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, e packages = append(packages, ip) } - err := g.importPackages(packages, true) + err := g.ImportPackages(packages, true) if err != nil { return nil, nil, err } - return g.manifest, g.lock, nil + return g.Manifest, g.Lock, nil } diff --git a/cmd/dep/godep_importer_test.go b/internal/importers/godep/importer_test.go similarity index 67% rename from cmd/dep/godep_importer_test.go rename to internal/importers/godep/importer_test.go index 992cb6f380..3aa27159a8 100644 --- a/cmd/dep/godep_importer_test.go +++ b/internal/importers/godep/importer_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package godep import ( "bytes" @@ -12,64 +12,63 @@ import ( "github.com/golang/dep" "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/importertest" "github.com/golang/dep/internal/test" "github.com/pkg/errors" ) -const testProjectRoot = "github.com/golang/notexist" - func TestGodepConfig_Convert(t *testing.T) { testCases := map[string]struct { - convertTestCase + importertest.TestCase json godepJSON }{ "package without comment": { - convertTestCase{ - wantConstraint: importerTestV1Constraint, - wantRevision: importerTestV1Rev, - wantVersion: importerTestV1Tag, + importertest.TestCase{ + WantConstraint: importertest.V1Constraint, + WantRevision: importertest.V1Rev, + WantVersion: importertest.V1Tag, }, godepJSON{ Imports: []godepPackage{ { - ImportPath: importerTestProject, - Rev: importerTestV1Rev, + ImportPath: importertest.Project, + Rev: importertest.V1Rev, }, }, }, }, "package with comment": { - convertTestCase{ - wantConstraint: importerTestV2Branch, - wantRevision: importerTestV2PatchRev, - wantVersion: importerTestV2PatchTag, + importertest.TestCase{ + WantConstraint: importertest.V2Branch, + WantRevision: importertest.V2PatchRev, + WantVersion: importertest.V2PatchTag, }, godepJSON{ Imports: []godepPackage{ { - ImportPath: importerTestProject, - Rev: importerTestV2PatchRev, - Comment: importerTestV2Branch, + ImportPath: importertest.Project, + Rev: importertest.V2PatchRev, + Comment: importertest.V2Branch, }, }, }, }, "missing package name": { - convertTestCase{ - wantConvertErr: true, + importertest.TestCase{ + WantConvertErr: true, }, godepJSON{ Imports: []godepPackage{{ImportPath: ""}}, }, }, "missing revision": { - convertTestCase{ - wantConvertErr: true, + importertest.TestCase{ + WantConvertErr: true, }, godepJSON{ Imports: []godepPackage{ { - ImportPath: importerTestProject, + ImportPath: importertest.Project, }, }, }, @@ -80,10 +79,10 @@ func TestGodepConfig_Convert(t *testing.T) { name := name testCase := testCase t.Run(name, func(t *testing.T) { - err := testCase.Exec(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { - g := newGodepImporter(logger, true, sm) + err := testCase.Execute(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { + g := NewImporter(logger, true, sm) g.json = testCase.json - return g.convert(testProjectRoot) + return g.convert(importertest.RootProject) }) if err != nil { t.Fatalf("%#v", err) @@ -99,10 +98,10 @@ func TestGodepConfig_Import(t *testing.T) { cacheDir := "gps-repocache" h.TempDir(cacheDir) h.TempDir("src") - h.TempDir(filepath.Join("src", testProjectRoot)) - h.TempCopy(filepath.Join(testProjectRoot, godepPath), "init/godep/Godeps.json") + h.TempDir(filepath.Join("src", importertest.RootProject)) + h.TempCopy(filepath.Join(importertest.RootProject, godepPath), "Godeps.json") - projectRoot := h.Path(testProjectRoot) + projectRoot := h.Path(importertest.RootProject) sm, err := gps.NewSourceManager(gps.SourceManagerConfig{ Cachedir: h.Path(cacheDir), Logger: log.New(test.Writer{TB: t}, "", 0), @@ -114,12 +113,12 @@ func TestGodepConfig_Import(t *testing.T) { verboseOutput := &bytes.Buffer{} logger := log.New(verboseOutput, "", 0) - g := newGodepImporter(logger, false, sm) // Disable verbose so that we don't print values that change each test run + g := NewImporter(logger, false, sm) // Disable Verbose so that we don't print values that change each test run if !g.HasDepMetadata(projectRoot) { t.Fatal("Expected the importer to detect godep configuration file") } - m, l, err := g.Import(projectRoot, testProjectRoot) + m, l, err := g.Import(projectRoot, importertest.RootProject) h.Must(err) if m == nil { @@ -130,7 +129,7 @@ func TestGodepConfig_Import(t *testing.T) { t.Fatal("Expected the lock to be generated") } - goldenFile := "init/godep/golden.txt" + goldenFile := "golden.txt" got := verboseOutput.String() want := h.GetTestFileString(goldenFile) if want != got { @@ -163,13 +162,13 @@ func TestGodepConfig_JsonLoad(t *testing.T) { h := test.NewHelper(t) defer h.Cleanup() - ctx := newTestContext(h) + ctx := importertest.NewTestContext(h) - h.TempCopy(filepath.Join(testProjectRoot, godepPath), "init/godep/Godeps.json") + h.TempCopy(filepath.Join(importertest.RootProject, godepPath), "Godeps.json") - projectRoot := h.Path(testProjectRoot) + projectRoot := h.Path(importertest.RootProject) - g := newGodepImporter(ctx.Err, true, nil) + g := NewImporter(ctx.Err, true, nil) err := g.load(projectRoot) if err != nil { t.Fatalf("Error while loading... %v", err) diff --git a/cmd/dep/testdata/init/godep/Godeps.json b/internal/importers/godep/testdata/Godeps.json similarity index 100% rename from cmd/dep/testdata/init/godep/Godeps.json rename to internal/importers/godep/testdata/Godeps.json diff --git a/cmd/dep/testdata/init/godep/golden.txt b/internal/importers/godep/testdata/golden.txt similarity index 100% rename from cmd/dep/testdata/init/godep/golden.txt rename to internal/importers/godep/testdata/golden.txt diff --git a/cmd/dep/govend_importer.go b/internal/importers/govend/importer.go similarity index 58% rename from cmd/dep/govend_importer.go rename to internal/importers/govend/importer.go index 3dc0799c65..2a49cce39f 100644 --- a/cmd/dep/govend_importer.go +++ b/internal/importers/govend/importer.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package govend import ( "io/ioutil" @@ -13,6 +13,7 @@ import ( "github.com/go-yaml/yaml" "github.com/golang/dep" "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/base" "github.com/pkg/errors" ) @@ -20,14 +21,15 @@ import ( // govend don't have a separate lock file. const govendYAMLName = "vendor.yml" -// govendImporter imports govend configuration in to the dep configuration format. -type govendImporter struct { - *baseImporter +// Importer imports govend configuration in to the dep configuration format. +type Importer struct { + *base.Importer yaml govendYAML } -func newGovendImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *govendImporter { - return &govendImporter{baseImporter: newBaseImporter(logger, verbose, sm)} +// NewImporter for govend. +func NewImporter(logger *log.Logger, verbose bool, sm gps.SourceManager) *Importer { + return &Importer{Importer: base.NewImporter(logger, verbose, sm)} } type govendYAML struct { @@ -39,11 +41,13 @@ type govendPackage struct { Revision string `yaml:"rev"` } -func (g *govendImporter) Name() string { +// Name of the importer. +func (g *Importer) Name() string { return "govend" } -func (g *govendImporter) HasDepMetadata(dir string) bool { +// HasDepMetadata checks if a directory contains config that the importer can handle. +func (g *Importer) HasDepMetadata(dir string) bool { y := filepath.Join(dir, govendYAMLName) if _, err := os.Stat(y); err != nil { return false @@ -52,7 +56,8 @@ func (g *govendImporter) HasDepMetadata(dir string) bool { return true } -func (g *govendImporter) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { +// Import the config found in the directory. +func (g *Importer) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { err := g.load(dir) if err != nil { return nil, nil, err @@ -62,11 +67,11 @@ func (g *govendImporter) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, } // load the govend configuration files. -func (g *govendImporter) load(projectDir string) error { - g.logger.Println("Detected govend configuration files...") +func (g *Importer) load(projectDir string) error { + g.Logger.Println("Detected govend configuration files...") y := filepath.Join(projectDir, govendYAMLName) - if g.verbose { - g.logger.Printf(" Loading %s", y) + if g.Verbose { + g.Logger.Printf(" Loading %s", y) } yb, err := ioutil.ReadFile(y) if err != nil { @@ -80,27 +85,27 @@ func (g *govendImporter) load(projectDir string) error { } // convert the govend configuration files into dep configuration files. -func (g *govendImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { - g.logger.Println("Converting from vendor.yaml...") +func (g *Importer) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { + g.Logger.Println("Converting from vendor.yaml...") - packages := make([]importedPackage, 0, len(g.yaml.Imports)) + packages := make([]base.ImportedPackage, 0, len(g.yaml.Imports)) for _, pkg := range g.yaml.Imports { // Path must not be empty if pkg.Path == "" || pkg.Revision == "" { return nil, nil, errors.New("invalid govend configuration, path and rev are required") } - ip := importedPackage{ + ip := base.ImportedPackage{ Name: pkg.Path, LockHint: pkg.Revision, } packages = append(packages, ip) } - err := g.importPackages(packages, true) + err := g.ImportPackages(packages, true) if err != nil { return nil, nil, err } - return g.manifest, g.lock, nil + return g.Manifest, g.Lock, nil } diff --git a/cmd/dep/govend_importer_test.go b/internal/importers/govend/importer_test.go similarity index 67% rename from cmd/dep/govend_importer_test.go rename to internal/importers/govend/importer_test.go index 8f757be304..04c9ea5d8c 100644 --- a/cmd/dep/govend_importer_test.go +++ b/internal/importers/govend/importer_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package govend import ( "bytes" @@ -12,6 +12,7 @@ import ( "github.com/golang/dep" "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/importertest" "github.com/golang/dep/internal/test" "github.com/pkg/errors" ) @@ -19,21 +20,21 @@ import ( func TestGovendConfig_Convert(t *testing.T) { testCases := map[string]struct { yaml govendYAML - convertTestCase + importertest.TestCase }{ "package": { govendYAML{ Imports: []govendPackage{ { - Path: importerTestProject, - Revision: importerTestV1Rev, + Path: importertest.Project, + Revision: importertest.V1Rev, }, }, }, - convertTestCase{ - wantConstraint: importerTestV1Constraint, - wantRevision: importerTestV1Rev, - wantVersion: importerTestV1Tag, + importertest.TestCase{ + WantConstraint: importertest.V1Constraint, + WantRevision: importertest.V1Rev, + WantVersion: importertest.V1Tag, }, }, "missing package name": { @@ -44,8 +45,8 @@ func TestGovendConfig_Convert(t *testing.T) { }, }, }, - convertTestCase{ - wantConvertErr: true, + importertest.TestCase{ + WantConvertErr: true, }, }, @@ -53,12 +54,12 @@ func TestGovendConfig_Convert(t *testing.T) { govendYAML{ Imports: []govendPackage{ { - Path: importerTestProject, + Path: importertest.Project, }, }, }, - convertTestCase{ - wantConvertErr: true, + importertest.TestCase{ + WantConvertErr: true, }, }, } @@ -67,10 +68,10 @@ func TestGovendConfig_Convert(t *testing.T) { name := name testCase := testCase t.Run(name, func(t *testing.T) { - err := testCase.Exec(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { - g := newGovendImporter(logger, true, sm) + err := testCase.Execute(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { + g := NewImporter(logger, true, sm) g.yaml = testCase.yaml - return g.convert(testProjectRoot) + return g.convert(importertest.RootProject) }) if err != nil { t.Fatalf("%#v", err) @@ -86,10 +87,10 @@ func TestGovendConfig_Import(t *testing.T) { cacheDir := "gps-repocache" h.TempDir(cacheDir) h.TempDir("src") - h.TempDir(filepath.Join("src", testProjectRoot)) - h.TempCopy(filepath.Join(testProjectRoot, govendYAMLName), "init/govend/vendor.yml") + h.TempDir(filepath.Join("src", importertest.RootProject)) + h.TempCopy(filepath.Join(importertest.RootProject, govendYAMLName), "vendor.yml") - projectRoot := h.Path(testProjectRoot) + projectRoot := h.Path(importertest.RootProject) sm, err := gps.NewSourceManager(gps.SourceManagerConfig{Cachedir: h.Path(cacheDir)}) h.Must(err) defer sm.Release() @@ -98,13 +99,13 @@ func TestGovendConfig_Import(t *testing.T) { verboseOutput := &bytes.Buffer{} logger := log.New(verboseOutput, "", 0) - // Disable verbose so that we don't print values that change each test run - g := newGovendImporter(logger, false, sm) + // Disable Verbose so that we don't print values that change each test run + g := NewImporter(logger, false, sm) if !g.HasDepMetadata(projectRoot) { t.Fatal("Expected the importer to detect govend configuration file") } - m, l, err := g.Import(projectRoot, testProjectRoot) + m, l, err := g.Import(projectRoot, importertest.RootProject) h.Must(err) if m == nil { @@ -115,7 +116,7 @@ func TestGovendConfig_Import(t *testing.T) { t.Fatal("Expected the lock to be generated") } - govendImportOutputFile := "init/govend/golden.txt" + govendImportOutputFile := "golden.txt" got := verboseOutput.String() want := h.GetTestFileString(govendImportOutputFile) if want != got { @@ -132,7 +133,7 @@ func TestGovendConfig_Import(t *testing.T) { func TestGovendConfig_YAMLLoad(t *testing.T) { // This is same as cmd/testdata/init/govend/vendor.yml - wantYAML := govendYAML{ + wantYaml := govendYAML{ Imports: []govendPackage{ { Path: "github.com/sdboyer/deptest", @@ -147,19 +148,19 @@ func TestGovendConfig_YAMLLoad(t *testing.T) { h := test.NewHelper(t) defer h.Cleanup() - ctx := newTestContext(h) - h.TempCopy(filepath.Join(testProjectRoot, govendYAMLName), "init/govend/vendor.yml") + ctx := importertest.NewTestContext(h) + h.TempCopy(filepath.Join(importertest.RootProject, govendYAMLName), "vendor.yml") - projectRoot := h.Path(testProjectRoot) + projectRoot := h.Path(importertest.RootProject) - g := newGovendImporter(ctx.Err, true, nil) + g := NewImporter(ctx.Err, true, nil) err := g.load(projectRoot) if err != nil { t.Fatalf("Error while loading %v", err) } - if !equalGovendImports(g.yaml.Imports, wantYAML.Imports) { - t.Fatalf("Expected import to be equal. \n\t(GOT): %v\n\t(WNT): %v", g.yaml.Imports, wantYAML.Imports) + if !equalGovendImports(g.yaml.Imports, wantYaml.Imports) { + t.Fatalf("Expected import to be equal. \n\t(GOT): %v\n\t(WNT): %v", g.yaml.Imports, wantYaml.Imports) } } diff --git a/cmd/dep/testdata/init/govend/golden.txt b/internal/importers/govend/testdata/golden.txt similarity index 100% rename from cmd/dep/testdata/init/govend/golden.txt rename to internal/importers/govend/testdata/golden.txt diff --git a/cmd/dep/testdata/init/govend/vendor.yml b/internal/importers/govend/testdata/vendor.yml similarity index 100% rename from cmd/dep/testdata/init/govend/vendor.yml rename to internal/importers/govend/testdata/vendor.yml diff --git a/internal/importers/importers.go b/internal/importers/importers.go new file mode 100644 index 0000000000..7cc7cfac78 --- /dev/null +++ b/internal/importers/importers.go @@ -0,0 +1,39 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package importers + +import ( + "log" + + "github.com/golang/dep" + "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/glide" + "github.com/golang/dep/internal/importers/godep" + "github.com/golang/dep/internal/importers/govend" + "github.com/golang/dep/internal/importers/vndr" +) + +// Importer handles importing configuration from other dependency managers into +// the dep configuration format. +type Importer interface { + // Name of the importer. + Name() string + + // Import the config found in the directory. + Import(path string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) + + // HasDepMetadata checks if a directory contains config that the importer can handle. + HasDepMetadata(dir string) bool +} + +// BuildAll returns a slice of all the importers. +func BuildAll(logger *log.Logger, verbose bool, sm gps.SourceManager) []Importer { + return []Importer{ + glide.NewImporter(logger, verbose, sm), + godep.NewImporter(logger, verbose, sm), + vndr.NewImporter(logger, verbose, sm), + govend.NewImporter(logger, verbose, sm), + } +} diff --git a/internal/importers/importertest/testcase.go b/internal/importers/importertest/testcase.go new file mode 100644 index 0000000000..54d041548e --- /dev/null +++ b/internal/importers/importertest/testcase.go @@ -0,0 +1,196 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package importertest + +import ( + "bytes" + "io/ioutil" + "log" + "sort" + "strings" + "testing" + + "github.com/golang/dep" + "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/test" + "github.com/pkg/errors" +) + +// TestCase is a common set of validations applied to the result +// of an importer converting from an external config format to dep's. +type TestCase struct { + DefaultConstraintFromLock bool + WantConvertErr bool + WantSourceRepo string + WantConstraint string + WantRevision gps.Revision + WantVersion string + WantIgnored []string + WantWarning string +} + +// NewTestContext creates a unique context with its own GOPATH for a single test. +func NewTestContext(h *test.Helper) *dep.Ctx { + h.TempDir("src") + pwd := h.Path(".") + discardLogger := log.New(ioutil.Discard, "", 0) + + return &dep.Ctx{ + GOPATH: pwd, + Out: discardLogger, + Err: discardLogger, + } +} + +// Execute and validate the test case. +func (tc TestCase) Execute(t *testing.T, convert func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error)) error { + h := test.NewHelper(t) + defer h.Cleanup() + // Disable parallel tests until we can resolve this error on the Windows builds: + // "remote repository at https://github.com/carolynvs/deptest-importers does not exist, or is inaccessible" + //h.Parallel() + + ctx := NewTestContext(h) + sm, err := ctx.SourceManager() + h.Must(err) + defer sm.Release() + + // Capture stderr so we can verify warnings + output := &bytes.Buffer{} + ctx.Err = log.New(output, "", 0) + + manifest, lock, convertErr := convert(ctx.Err, sm) + return tc.validate(manifest, lock, convertErr, output) +} + +// validate returns an error if any of the testcase validations failed. +func (tc TestCase) validate(manifest *dep.Manifest, lock *dep.Lock, convertErr error, output *bytes.Buffer) error { + if tc.WantConvertErr { + if convertErr == nil { + return errors.New("Expected the conversion to fail, but it did not return an error") + } + return nil + } + + if convertErr != nil { + return errors.Wrap(convertErr, "Expected the conversion to pass, but it returned an error") + } + + if !equalSlice(manifest.Ignored, tc.WantIgnored) { + return errors.Errorf("unexpected set of ignored projects: \n\t(GOT) %v \n\t(WNT) %v", + manifest.Ignored, tc.WantIgnored) + } + + wantConstraintCount := 0 + if tc.WantConstraint != "" { + wantConstraintCount = 1 + } + gotConstraintCount := len(manifest.Constraints) + if gotConstraintCount != wantConstraintCount { + return errors.Errorf("unexpected number of constraints: \n\t(GOT) %v \n\t(WNT) %v", + gotConstraintCount, wantConstraintCount) + } + + if tc.WantConstraint != "" { + d, ok := manifest.Constraints[Project] + if !ok { + return errors.Errorf("Expected the manifest to have a dependency for '%v'", + Project) + } + + gotConstraint := d.Constraint.String() + if gotConstraint != tc.WantConstraint { + return errors.Errorf("unexpected constraint: \n\t(GOT) %v \n\t(WNT) %v", + gotConstraint, tc.WantConstraint) + } + + } + + // Lock checks. + wantLockCount := 0 + if tc.WantRevision != "" { + wantLockCount = 1 + } + gotLockCount := 0 + if lock != nil { + gotLockCount = len(lock.P) + } + if gotLockCount != wantLockCount { + return errors.Errorf("unexpected number of locked projects: \n\t(GOT) %v \n\t(WNT) %v", + gotLockCount, wantLockCount) + } + + if tc.WantRevision != "" { + lp := lock.P[0] + + gotProjectRoot := lp.Ident().ProjectRoot + if gotProjectRoot != Project { + return errors.Errorf("unexpected root project in lock: \n\t(GOT) %v \n\t(WNT) %v", + gotProjectRoot, Project) + } + + gotSource := lp.Ident().Source + if gotSource != tc.WantSourceRepo { + return errors.Errorf("unexpected source repository: \n\t(GOT) %v \n\t(WNT) %v", + gotSource, tc.WantSourceRepo) + } + + // Break down the locked "version" into a version (optional) and revision + var gotVersion string + var gotRevision gps.Revision + if lpv, ok := lp.Version().(gps.PairedVersion); ok { + gotVersion = lpv.String() + gotRevision = lpv.Revision() + } else if lr, ok := lp.Version().(gps.Revision); ok { + gotRevision = lr + } else { + return errors.New("could not determine the type of the locked version") + } + + if gotRevision != tc.WantRevision { + return errors.Errorf("unexpected locked revision: \n\t(GOT) %v \n\t(WNT) %v", + gotRevision, + tc.WantRevision) + } + if gotVersion != tc.WantVersion { + return errors.Errorf("unexpected locked version: \n\t(GOT) %v \n\t(WNT) %v", + gotVersion, + tc.WantVersion) + } + } + + if tc.WantWarning != "" { + if !strings.Contains(output.String(), tc.WantWarning) { + return errors.Errorf("Expected the output to include the warning '%s'", tc.WantWarning) + } + } + + return nil +} + +// equalSlice is comparing two string slices for equality. +func equalSlice(a, b []string) bool { + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + if len(a) != len(b) { + return false + } + + sort.Strings(a) + sort.Strings(b) + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} diff --git a/internal/importers/importertest/testdata.go b/internal/importers/importertest/testdata.go new file mode 100644 index 0000000000..cd7f2c0879 --- /dev/null +++ b/internal/importers/importertest/testdata.go @@ -0,0 +1,64 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package importertest + +const ( + // RootProject is the containing project performing the import. + RootProject = "github.com/golang/notexist" + + // Project being imported. + Project = "github.com/carolynvs/deptest-importers" + + // ProjectSrc is an alternate source for the imported project. + ProjectSrc = "https://github.com/carolynvs/deptest-importers.git" + + // UntaggedRev is a revision without any tags. + UntaggedRev = "9b670d143bfb4a00f7461451d5c4a62f80e9d11d" + + // UntaggedRevAbbrv is the result of running `git describe` on UntaggedRev + UntaggedRevAbbrv = "v1.0.0-1-g9b670d1" + + // Beta1Tag is a non-semver tag. + Beta1Tag = "beta1" + + // Beta1Rev is the revision of Beta1Tag + Beta1Rev = "7913ab26988c6fb1e16225f845a178e8849dd254" + + // V2Branch is a branch that could be interpreted as a semver tag (but shouldn't). + V2Branch = "v2" + + // V2Rev is the HEAD revision of V2Branch. + V2Rev = "45dcf5a09c64b48b6e836028a3bc672b19b9d11d" + + // V2PatchTag is a prerelease semver tag on the non-default branch. + V2PatchTag = "v2.0.0-alpha1" + + // V2PatchRev is the revision of V2PatchTag. + V2PatchRev = "347760b50204948ea63e531dd6560e56a9adde8f" + + // V1Tag is a semver tag that matches V1Constraint. + V1Tag = "v1.0.0" + + // V1Rev is the revision of V1Tag. + V1Rev = "d0c29640b17f77426b111f4c1640d716591aa70e" + + // V1PatchTag is a semver tag that matches V1Constraint. + V1PatchTag = "v1.0.2" + + // V1PatchRev is the revision of V1PatchTag + V1PatchRev = "788963efe22e3e6e24c776a11a57468bb2fcd780" + + // V1Constraint is a constraint that matches multiple semver tags. + V1Constraint = "^1.0.0" + + // MultiTaggedRev is a revision with multiple tags. + MultiTaggedRev = "34cf993cc346f65601fe4356dd68bd54d20a1bfe" + + // MultiTaggedSemverTag is a semver tag on MultiTaggedRev. + MultiTaggedSemverTag = "v1.0.4" + + // MultiTaggedPlainTag is a non-semver tag on MultiTaggedRev. + MultiTaggedPlainTag = "stable" +) diff --git a/cmd/dep/vndr_importer.go b/internal/importers/vndr/importer.go similarity index 67% rename from cmd/dep/vndr_importer.go rename to internal/importers/vndr/importer.go index 911700a260..f24723ee0e 100644 --- a/cmd/dep/vndr_importer.go +++ b/internal/importers/vndr/importer.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package vndr import ( "bufio" @@ -13,6 +13,7 @@ import ( "github.com/golang/dep" "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/base" "github.com/pkg/errors" ) @@ -20,24 +21,29 @@ func vndrFile(dir string) string { return filepath.Join(dir, "vendor.conf") } -type vndrImporter struct { - *baseImporter +// Importer imports vndr configuration into the dep configuration format. +type Importer struct { + *base.Importer packages []vndrPackage } -func newVndrImporter(log *log.Logger, verbose bool, sm gps.SourceManager) *vndrImporter { - return &vndrImporter{baseImporter: newBaseImporter(log, verbose, sm)} +// NewImporter for vndr. +func NewImporter(log *log.Logger, verbose bool, sm gps.SourceManager) *Importer { + return &Importer{Importer: base.NewImporter(log, verbose, sm)} } -func (v *vndrImporter) Name() string { return "vndr" } +// Name of the importer. +func (v *Importer) Name() string { return "vndr" } -func (v *vndrImporter) HasDepMetadata(dir string) bool { +// HasDepMetadata checks if a directory contains config that the importer can handle. +func (v *Importer) HasDepMetadata(dir string) bool { _, err := os.Stat(vndrFile(dir)) return err == nil } -func (v *vndrImporter) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { - v.logger.Println("Detected vndr configuration file...") +// Import the config found in the directory. +func (v *Importer) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { + v.Logger.Println("Detected vndr configuration file...") err := v.loadVndrFile(dir) if err != nil { @@ -47,8 +53,8 @@ func (v *vndrImporter) Import(dir string, pr gps.ProjectRoot) (*dep.Manifest, *d return v.convert(pr) } -func (v *vndrImporter) loadVndrFile(dir string) error { - v.logger.Printf("Converting from vendor.conf...") +func (v *Importer) loadVndrFile(dir string) error { + v.Logger.Printf("Converting from vendor.conf...") f, err := os.Open(vndrFile(dir)) if err != nil { @@ -76,8 +82,8 @@ func (v *vndrImporter) loadVndrFile(dir string) error { return nil } -func (v *vndrImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { - packages := make([]importedPackage, 0, len(v.packages)) +func (v *Importer) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, error) { + packages := make([]base.ImportedPackage, 0, len(v.packages)) for _, pkg := range v.packages { // Validate if pkg.importPath == "" { @@ -90,19 +96,19 @@ func (v *vndrImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, er return nil, nil, err } - ip := importedPackage{ + ip := base.ImportedPackage{ Name: pkg.importPath, Source: pkg.repository, LockHint: pkg.reference, } packages = append(packages, ip) } - err := v.importPackages(packages, true) + err := v.ImportPackages(packages, true) if err != nil { return nil, nil, err } - return v.manifest, v.lock, nil + return v.Manifest, v.Lock, nil } type vndrPackage struct { diff --git a/cmd/dep/vndr_importer_test.go b/internal/importers/vndr/importer_test.go similarity index 80% rename from cmd/dep/vndr_importer_test.go rename to internal/importers/vndr/importer_test.go index 7fac02394f..c417c1188e 100644 --- a/cmd/dep/vndr_importer_test.go +++ b/internal/importers/vndr/importer_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package vndr import ( "bytes" @@ -13,6 +13,7 @@ import ( "github.com/golang/dep" "github.com/golang/dep/internal/gps" + "github.com/golang/dep/internal/importers/importertest" "github.com/golang/dep/internal/test" "github.com/pkg/errors" ) @@ -20,35 +21,35 @@ import ( func TestVndrConfig_Convert(t *testing.T) { testCases := map[string]struct { packages []vndrPackage - convertTestCase + importertest.TestCase }{ "package": { []vndrPackage{{ - importPath: importerTestProject, - reference: importerTestV1Rev, - repository: importerTestProjectSrc, + importPath: importertest.Project, + reference: importertest.V1Rev, + repository: importertest.ProjectSrc, }}, - convertTestCase{ - wantSourceRepo: importerTestProjectSrc, - wantConstraint: importerTestV1Constraint, - wantRevision: importerTestV1Rev, - wantVersion: importerTestV1Tag, + importertest.TestCase{ + WantSourceRepo: importertest.ProjectSrc, + WantConstraint: importertest.V1Constraint, + WantRevision: importertest.V1Rev, + WantVersion: importertest.V1Tag, }, }, "missing importPath": { []vndrPackage{{ - reference: importerTestV1Tag, + reference: importertest.V1Tag, }}, - convertTestCase{ - wantConvertErr: true, + importertest.TestCase{ + WantConvertErr: true, }, }, "missing reference": { []vndrPackage{{ - importPath: importerTestProject, + importPath: importertest.Project, }}, - convertTestCase{ - wantConvertErr: true, + importertest.TestCase{ + WantConvertErr: true, }, }, } @@ -57,10 +58,10 @@ func TestVndrConfig_Convert(t *testing.T) { name := name testCase := testCase t.Run(name, func(t *testing.T) { - err := testCase.Exec(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { - g := newVndrImporter(logger, true, sm) + err := testCase.Execute(t, func(logger *log.Logger, sm gps.SourceManager) (*dep.Manifest, *dep.Lock, error) { + g := NewImporter(logger, true, sm) g.packages = testCase.packages - return g.convert(testProjectRoot) + return g.convert(importertest.RootProject) }) if err != nil { t.Fatalf("%#v", err) @@ -73,24 +74,24 @@ func TestVndrConfig_Import(t *testing.T) { h := test.NewHelper(t) defer h.Cleanup() - ctx := newTestContext(h) + ctx := importertest.NewTestContext(h) sm, err := ctx.SourceManager() h.Must(err) defer sm.Release() - h.TempDir(filepath.Join("src", testProjectRoot)) - h.TempCopy(vndrFile(testProjectRoot), "init/vndr/vendor.conf") - projectRoot := h.Path(testProjectRoot) + h.TempDir(filepath.Join("src", importertest.RootProject)) + h.TempCopy(vndrFile(importertest.RootProject), "vendor.conf") + projectRoot := h.Path(importertest.RootProject) logOutput := bytes.NewBuffer(nil) ctx.Err = log.New(logOutput, "", 0) - v := newVndrImporter(ctx.Err, false, sm) + v := NewImporter(ctx.Err, false, sm) if !v.HasDepMetadata(projectRoot) { t.Fatal("Expected the importer to detect vndr configuration file") } - m, l, err := v.Import(projectRoot, testProjectRoot) + m, l, err := v.Import(projectRoot, importertest.RootProject) h.Must(err) wantM := dep.NewManifest() @@ -130,7 +131,7 @@ func TestVndrConfig_Import(t *testing.T) { t.Errorf("unexpected lock\nhave=%+v\nwant=%+v", l, wantL) } - goldenFile := "init/vndr/golden.txt" + goldenFile := "golden.txt" got := logOutput.String() want := h.GetTestFileString(goldenFile) if want != got { diff --git a/cmd/dep/testdata/init/vndr/golden.txt b/internal/importers/vndr/testdata/golden.txt similarity index 100% rename from cmd/dep/testdata/init/vndr/golden.txt rename to internal/importers/vndr/testdata/golden.txt diff --git a/cmd/dep/testdata/init/vndr/vendor.conf b/internal/importers/vndr/testdata/vendor.conf similarity index 100% rename from cmd/dep/testdata/init/vndr/vendor.conf rename to internal/importers/vndr/testdata/vendor.conf