diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 00000000..fdf176eb --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,10 @@ +engines: + gofmt: + enabled: true + govet: + enabled: true +ratings: + paths: + - "**.go" +exclude_paths: + - vendor/ diff --git a/.travis.yml b/.travis.yml index 1c7e3bc3..17a34ec2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,13 @@ matrix: install: - echo "This is an override of the default install deps step in travis." before_script: + # OSX as of El Capitan sets an exit trap that interacts poorly with our + # set -e below. So, unset the trap. + # Related: https://superuser.com/questions/1044130/why-am-i-having-how-can-i-fix-this-error-shell-session-update-command-not-f + - if [[ "$(go env GOHOSTOS)" == "darwin" ]]; then trap EXIT; fi - PKGS=$(go list ./... | grep -v /vendor/) - go get -v honnef.co/go/tools/cmd/{gosimple,staticcheck} + - npm install -g codeclimate-test-reporter script: - go build -v ./cmd/dep - go vet $PKGS @@ -26,5 +31,10 @@ script: - ./hack/validate-gofmt.bash - ./hack/validate-vendor.bash - gosimple $PKGS - - go test -race $PKGS + #- go test -race $PKGS - go build ./hack/licenseok + - set -e; for pkg in $PKGS; do go test -race -coverprofile=profile.out -covermode=atomic $pkg; if [[ -f profile.out ]]; then cat profile.out >> coverage.txt; rm profile.out; fi; done + - find . -path ./vendor -prune -o -type f -name "*.go" -printf '%P\n' | xargs ./licenseok + - ./hack/validate-vendor.bash +after_success: + - codeclimate-test-reporter < coverage.txt diff --git a/FAQ.md b/FAQ.md index d577e49d..525824b0 100644 --- a/FAQ.md +++ b/FAQ.md @@ -17,16 +17,22 @@ Summarize the question and quote the reply, linking back to the original comment * [Why did dep use a different revision for package X instead of the revision in the lock file?](#why-did-dep-use-a-different-revision-for-package-x-instead-of-the-revision-in-the-lock-file) ## What is a direct or transitive dependency? -* Direct dependencies appear in at least one import statement from your project - are dependencies that are imported by your project. +* Direct dependencies are dependencies that are imported directly by your project: they appear in at least one import statement from your project. * Transitive dependencies are the dependencies of your dependencies. Necessary to compile but are not directly used by your code. ## Should I commit my vendor directory? -Committing the vendor directory is totally up to you. There is no general advice that applies in all cases. +It's up to you: -**Pros**: it's the only way to get truly reproducible builds, as it guards against upstream renames and deletes; and you don't need an extra `dep ensure` step on fresh clones to build your repo. +**Pros** -**Cons**: your repo will be bigger, potentially a lot bigger; and PR diffs are more annoying. +- it's the only way to get truly reproducible builds, as it guards against upstream renames and deletes +- you don't need an extra `dep ensure` step (to fetch dependencies) on fresh clones to build your repo + +**Cons** + +- your repo will be bigger, potentially a lot bigger +- PR diffs are more annoying ## Why is it `dep ensure` instead of `dep install`? diff --git a/Gopkg.lock b/Gopkg.lock index 18a3adcd..5c94a43c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,4 +1,4 @@ -memo = "099c73630ad2c4f0894ed8646e2e4b5a9f635c85661a77fbf3b9f9dd78c77e87" +memo = "71329a18f735441776be73d92d064f26a67fa30e616bbdfbb47e8dc68bda8c5c" [[projects]] branch = "2.x" diff --git a/Gopkg.toml b/Gopkg.toml index d68b99c4..5224e94e 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -1,3 +1,5 @@ +# Temporarily, until gps moves in and this becomes a direct dep again +required = ["github.com/Masterminds/semver"] [[dependencies]] branch = "2.x" diff --git a/README.md b/README.md index 0a7b88f5..54ea3879 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Dep -Linux: [![Build Status](https://travis-ci.org/golang/dep.svg?branch=master)](https://travis-ci.org/golang/dep) | Windows: [![Build status](https://ci.appveyor.com/api/projects/status/4pu2xnnrikol2gsf/branch/master?svg=true)](https://ci.appveyor.com/project/golang/dep/branch/master) +Linux: [![Build Status](https://travis-ci.org/golang/dep.svg?branch=master)](https://travis-ci.org/golang/dep) | Windows: [![Build status](https://ci.appveyor.com/api/projects/status/4pu2xnnrikol2gsf/branch/master?svg=true)](https://ci.appveyor.com/project/golang/dep/branch/master) | [![Code Climate](https://codeclimate.com/github/golang/dep/badges/gpa.svg)](https://codeclimate.com/github/golang/dep) Dep is a prototype dependency management tool. It requires Go 1.7 or newer to compile. diff --git a/analyzer.go b/analyzer.go index 605f8208..fa339d4a 100644 --- a/analyzer.go +++ b/analyzer.go @@ -23,7 +23,7 @@ func (a Analyzer) DeriveManifestAndLock(path string, n gps.ProjectRoot) (gps.Man } f, err := os.Open(mf) if err != nil { - return nil, nil, nil + return nil, nil, err } defer f.Close() diff --git a/analyzer_test.go b/analyzer_test.go index ff53a1c6..bb0a8e11 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -5,15 +5,15 @@ package dep import ( - "io/ioutil" "os" "path/filepath" + "runtime" "testing" "github.com/golang/dep/test" ) -func TestDeriveManifestAndLock(t *testing.T) { +func TestAnalyzerDeriveManifestAndLock(t *testing.T) { h := test.NewHelper(t) defer h.Cleanup() @@ -49,17 +49,75 @@ func TestDeriveManifestAndLock(t *testing.T) { } } -func TestDeriveManifestAndLockDoesNotExist(t *testing.T) { - dir, err := ioutil.TempDir("", "dep") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) +func TestAnalyzerDeriveManifestAndLockDoesNotExist(t *testing.T) { + h := test.NewHelper(t) + defer h.Cleanup() + + h.TempDir("dep") a := Analyzer{} - m, l, err := a.DeriveManifestAndLock(dir, "my/fake/project") + m, l, err := a.DeriveManifestAndLock(h.Path("dep"), "my/fake/project") if m != nil || l != nil || err != nil { t.Fatalf("expected manifest & lock & err to be nil: m -> %#v l -> %#v err-> %#v", m, l, err) } } + +func TestAnalyzerDeriveManifestAndLockCannotOpen(t *testing.T) { + if runtime.GOOS == "windows" { + // TODO: find an implementation that works on Microsoft + // Windows. Setting permissions works differently there. + // os.Chmod(..., 0222) below is not enough for the file + // to be write-only (unreadable), and os.Chmod(..., + // 0000) returns an invalid argument error. + t.Skip("skipping on windows") + } + + h := test.NewHelper(t) + defer h.Cleanup() + + h.TempDir("dep") + + // Create an empty manifest file + h.TempFile(filepath.Join("dep", ManifestName), "") + + // Change its mode so that it cannot be read + err := os.Chmod(filepath.Join(h.Path("dep"), ManifestName), 0222) + if err != nil { + t.Fatal(err) + } + + a := Analyzer{} + + m, l, err := a.DeriveManifestAndLock(h.Path("dep"), "my/fake/project") + if m != nil || l != nil || err == nil { + t.Fatalf("expected manifest & lock to be nil, err to be not nil: m -> %#v l -> %#v err -> %#v", m, l, err) + } +} + +func TestAnalyzerDeriveManifestAndLockInvalidManifest(t *testing.T) { + h := test.NewHelper(t) + defer h.Cleanup() + + h.TempDir("dep") + + // Create a manifest with invalid contents + h.TempFile(filepath.Join("dep", ManifestName), "invalid manifest") + + a := Analyzer{} + + m, l, err := a.DeriveManifestAndLock(h.Path("dep"), "my/fake/project") + if m != nil || l != nil || err == nil { + t.Fatalf("expected manifest & lock & err to be nil: m -> %#v l -> %#v err-> %#v", m, l, err) + } +} + +func TestAnalyzerInfo(t *testing.T) { + a := Analyzer{} + + name, vers := a.Info() + + if name != "dep" || vers != 1 { + t.Fatalf("expected name to be 'dep' and version to be 1: name -> %q vers -> %d", name, vers) + } +} diff --git a/cmd/dep/ensure.go b/cmd/dep/ensure.go index 12f6cbf8..00ea17aa 100644 --- a/cmd/dep/ensure.go +++ b/cmd/dep/ensure.go @@ -9,6 +9,7 @@ import ( "encoding/hex" "flag" "fmt" + "go/build" "log" "os" "path/filepath" @@ -27,7 +28,7 @@ const ensureLongHelp = ` Ensure is used to fetch project dependencies into the vendor folder, as well as to set version constraints for specific dependencies. It takes user input, solves the updated dependency graph of the project, writes any changes to the -manifest and lock file, and places dependencies in the vendor folder. +lock file, and places dependencies in the vendor folder. Package spec: @@ -130,6 +131,10 @@ func (cmd *ensureCommand) Run(ctx *dep.Ctx, args []string) error { return errors.Wrap(err, "ensure ListPackage for project") } + if err := checkErrors(params.RootPackageTree.Packages); err != nil { + return err + } + if cmd.update { applyUpdateArgs(args, ¶ms) } else { @@ -160,19 +165,16 @@ func (cmd *ensureCommand) Run(ctx *dep.Ctx, args []string) error { writeV = dep.VendorAlways } - var sw dep.SafeWriter - var manifest *dep.Manifest - if !cmd.update { - manifest = p.Manifest - } - newLock := dep.LockFromInterface(solution) - sw.Prepare(manifest, p.Lock, newLock, writeV) + sw, err := dep.NewSafeWriter(nil, p.Lock, newLock, writeV) + if err != nil { + return err + } if cmd.dryRun { return sw.PrintPreparedActions() } - return errors.Wrap(sw.Write(p.AbsRoot, sm), "grouped write of manifest, lock and vendor") + return errors.Wrap(sw.Write(p.AbsRoot, sm, true), "grouped write of manifest, lock and vendor") } func applyUpdateArgs(args []string, params *gps.SolveParameters) { @@ -366,3 +368,30 @@ func deduceConstraint(s string) gps.Constraint { // TODO: if there is amgibuity here, then prompt the user? return gps.NewVersion(s) } + +func checkErrors(m map[string]pkgtree.PackageOrErr) error { + noGoErrors, pkgErrors := 0, 0 + for _, poe := range m { + if poe.Err != nil { + switch poe.Err.(type) { + case *build.NoGoError: + noGoErrors++ + default: + pkgErrors++ + } + } + } + if len(m) == 0 || len(m) == noGoErrors { + return errors.New("all dirs lacked any go code") + } + + if len(m) == pkgErrors { + return errors.New("all dirs had go code with errors") + } + + if len(m) == pkgErrors+noGoErrors { + return errors.Errorf("%d dirs had errors and %d had no go code", pkgErrors, noGoErrors) + } + + return nil +} diff --git a/cmd/dep/init.go b/cmd/dep/init.go index 53faa44a..f3526a95 100644 --- a/cmd/dep/init.go +++ b/cmd/dep/init.go @@ -21,16 +21,21 @@ import ( const initShortHelp = `Initialize a new project with manifest and lock files` const initLongHelp = ` -Initialize the project at filepath root by parsing its dependencies and writing -manifest and lock files. If root isn't specified, use the current directory. +Initialize the project at filepath root by parsing its dependencies, writing +manifest and lock files, and vendoring the dependencies. If root isn't +specified, use the current directory. The version of each dependency will reflect the current state of the GOPATH. If -a dependency doesn't exist in the GOPATH, it won't be written to the manifest, -but it will be solved-for, and will appear in the lock. +a dependency doesn't exist in the GOPATH, a version will be selected from the +versions available from the upstream source per the following algorithm: -Note: init may use the network to solve the dependency graph. + - Tags conforming to semver (sorted by semver rules) + - Default branch(es) (sorted lexicographically) + - Non-semver tags (sorted lexicographically) -Note: init does NOT vendor dependencies at the moment. See dep ensure. +A Gopkg.toml file will be written with inferred version constraints for all +direct dependencies. Gopkg.lock will be written with precise versions, and +vendor/ will be populated with the precise versions written to Gopkg.lock. ` func (cmd *initCommand) Name() string { return "init" } @@ -39,9 +44,13 @@ func (cmd *initCommand) ShortHelp() string { return initShortHelp } func (cmd *initCommand) LongHelp() string { return initLongHelp } func (cmd *initCommand) Hidden() bool { return false } -func (cmd *initCommand) Register(fs *flag.FlagSet) {} +func (cmd *initCommand) Register(fs *flag.FlagSet) { + fs.BoolVar(&cmd.noExamples, "no-examples", false, "don't include example in Gopkg.toml") +} -type initCommand struct{} +type initCommand struct { + noExamples bool +} func trimPathPrefix(p1, p2 string) string { if internal.HasFilepathPrefix(p1, p2) { @@ -74,7 +83,7 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error { return err } if mok { - return errors.Errorf("manifest file %q already exists", mf) + return errors.Errorf("manifest already exists: %s", mf) } // Manifest file does not exist. @@ -135,38 +144,59 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error { ) } - if len(pd.notondisk) > 0 { - internal.Vlogf("Solving...") - params := gps.SolveParameters{ - RootDir: root, - RootPackageTree: pkgT, - Manifest: m, - Lock: l, - ProjectAnalyzer: dep.Analyzer{}, - } - - if *verbose { - params.Trace = true - params.TraceLogger = log.New(os.Stderr, "", 0) - } - s, err := gps.Prepare(params, sm) - if err != nil { - return errors.Wrap(err, "prepare solver") - } - - soln, err := s.Solve() - if err != nil { - handleAllTheFailuresOfTheWorld(err) - return err - } - l = dep.LockFromInterface(soln) + // Run solver with project versions found on disk + internal.Vlogf("Solving...") + params := gps.SolveParameters{ + RootDir: root, + RootPackageTree: pkgT, + Manifest: m, + Lock: l, + ProjectAnalyzer: dep.Analyzer{}, } + if *verbose { + params.Trace = true + params.TraceLogger = log.New(os.Stderr, "", 0) + } + s, err := gps.Prepare(params, sm) + if err != nil { + return errors.Wrap(err, "prepare solver") + } + + soln, err := s.Solve() + if err != nil { + handleAllTheFailuresOfTheWorld(err) + return err + } + l = dep.LockFromInterface(soln) + + // Pick notondisk project constraints from solution and add to manifest + for k, _ := range pd.notondisk { + for _, x := range l.Projects() { + if k == x.Ident().ProjectRoot { + m.Dependencies[k] = getProjectPropertiesFromVersion(x.Version()) + break + } + } + } + + // Run gps.Prepare with appropriate constraint solutions from solve run + // to generate the final lock memo. + s, err = gps.Prepare(params, sm) + if err != nil { + return errors.Wrap(err, "prepare solver") + } + + l.Memo = s.HashInputs() + internal.Vlogf("Writing manifest and lock files.") - var sw dep.SafeWriter - sw.Prepare(m, nil, l, dep.VendorAlways) - if err := sw.Write(root, sm); err != nil { + sw, err := dep.NewSafeWriter(m, nil, l, dep.VendorAlways) + if err != nil { + return err + } + + if err := sw.Write(root, sm, cmd.noExamples); err != nil { return errors.Wrap(err, "safe write of manifest and lock") } @@ -200,7 +230,6 @@ func isStdLib(path string) bool { // TODO solve failures can be really creative - we need to be similarly creative // in handling them and informing the user appropriately func handleAllTheFailuresOfTheWorld(err error) { - fmt.Printf("solve error: %s\n", err) } func hasImportPathPrefix(s, prefix string) bool { @@ -210,6 +239,34 @@ func hasImportPathPrefix(s, prefix string) bool { return strings.HasPrefix(s, prefix+"/") } +// getProjectPropertiesFromVersion takes a gps.Version and returns a proper +// gps.ProjectProperties with Constraint value based on the provided version. +func getProjectPropertiesFromVersion(v gps.Version) gps.ProjectProperties { + pp := gps.ProjectProperties{} + + // extract version and ignore if it's revision only + switch tv := v.(type) { + case gps.PairedVersion: + v = tv.Unpair() + case gps.Revision: + return pp + } + + switch v.Type() { + case gps.IsBranch, gps.IsVersion: + pp.Constraint = v + case gps.IsSemver: + // TODO: remove "^" when https://github.com/golang/dep/issues/225 is ready. + c, err := gps.NewSemverConstraint("^" + v.String()) + if err != nil { + panic(err) + } + pp.Constraint = c + } + + return pp +} + type projectData struct { constraints gps.ProjectConstraints // constraints that could be found dependencies map[gps.ProjectRoot][]string // all dependencies (imports) found by project root @@ -264,16 +321,7 @@ func getProjectData(ctx *dep.Ctx, pkgT pkgtree.PackageTree, cpr string, sm *gps. } ondisk[pr] = v - pp := gps.ProjectProperties{} - switch v.Type() { - case gps.IsBranch, gps.IsVersion, gps.IsRevision: - pp.Constraint = v - case gps.IsSemver: - c, _ := gps.NewSemverConstraint("^" + v.String()) - pp.Constraint = c - } - - constraints[pr] = pp + constraints[pr] = getProjectPropertiesFromVersion(v) } internal.Vlogf("Analyzing transitive imports...") diff --git a/cmd/dep/init_test.go b/cmd/dep/init_test.go index 58a9a057..784387a3 100644 --- a/cmd/dep/init_test.go +++ b/cmd/dep/init_test.go @@ -4,7 +4,12 @@ package main -import "testing" +import ( + "reflect" + "testing" + + "github.com/sdboyer/gps" +) func TestContains(t *testing.T) { a := []string{"a", "b", "abcd"} @@ -33,3 +38,46 @@ func TestIsStdLib(t *testing.T) { } } } + +func TestGetProjectPropertiesFromVersion(t *testing.T) { + wantSemver, _ := gps.NewSemverConstraint("^v1.0.0") + cases := []struct { + version, want gps.Constraint + }{ + { + version: gps.NewBranch("foo-branch"), + want: gps.NewBranch("foo-branch"), + }, + { + version: gps.NewVersion("foo-version"), + want: gps.NewVersion("foo-version"), + }, + { + version: gps.NewVersion("v1.0.0"), + want: wantSemver, + }, + { + version: gps.NewBranch("foo-branch").Is("some-revision"), + want: gps.NewBranch("foo-branch"), + }, + { + version: gps.NewVersion("foo-version").Is("some-revision"), + want: gps.NewVersion("foo-version"), + }, + { + version: gps.Revision("some-revision"), + want: nil, + }, + { + version: gps.NewVersion("v1.0.0").Is("some-revision"), + want: wantSemver, + }, + } + + for _, c := range cases { + actualProp := getProjectPropertiesFromVersion(c.version.(gps.Version)) + if !reflect.DeepEqual(c.want, actualProp.Constraint) { + t.Fatalf("Constraints are not as expected: \n\t(GOT) %v\n\t(WNT) %v", actualProp.Constraint, c.want) + } + } +} diff --git a/cmd/dep/integration_test.go b/cmd/dep/integration_test.go index fcf84116..30d04693 100644 --- a/cmd/dep/integration_test.go +++ b/cmd/dep/integration_test.go @@ -19,6 +19,10 @@ func TestIntegration(t *testing.T) { test.NeedsGit(t) filepath.Walk(filepath.Join("testdata", "harness_tests"), func(path string, info os.FileInfo, err error) error { + if err != nil { + t.Fatal("error walking filepath") + } + wd, err := os.Getwd() if err != nil { panic(err) @@ -51,13 +55,18 @@ func TestIntegration(t *testing.T) { // Run commands testProj.RecordImportPaths() - for _, args := range testCase.Commands { + + var err error + for i, args := range testCase.Commands { err = testProj.DoRun(args) - if err != nil { - t.Fatalf("%v", err) + if err != nil && i < len(testCase.Commands)-1 { + t.Fatalf("cmd %s raised an unexpected error: %s", args[0], err.Error()) } } + // Check error raised in final command + testCase.CompareError(err, testProj.GetStderr()) + // Check final manifest and lock testCase.CompareFile(dep.ManifestName, testProj.ProjPath(dep.ManifestName)) testCase.CompareFile(dep.LockName, testProj.ProjPath(dep.LockName)) diff --git a/cmd/dep/remove.go b/cmd/dep/remove.go index a14e3550..26122f56 100644 --- a/cmd/dep/remove.go +++ b/cmd/dep/remove.go @@ -19,7 +19,7 @@ import ( const removeShortHelp = `Remove a dependency from the project` const removeLongHelp = ` -Remove a dependency from the project's manifest file, lock file, and vendor +Remove a dependency from the project's lock file, and vendor folder. If the project includes that dependency in its import graph, remove will fail unless -force is specified. ` @@ -180,10 +180,13 @@ func (cmd *removeCommand) Run(ctx *dep.Ctx, args []string) error { return err } - var sw dep.SafeWriter newLock := dep.LockFromInterface(soln) - sw.Prepare(p.Manifest, p.Lock, newLock, dep.VendorOnChanged) - if err := sw.Write(p.AbsRoot, sm); err != nil { + + sw, err := dep.NewSafeWriter(nil, p.Lock, newLock, dep.VendorOnChanged) + if err != nil { + return err + } + if err := sw.Write(p.AbsRoot, sm, true); err != nil { return errors.Wrap(err, "grouped write of manifest, lock and vendor") } return nil diff --git a/cmd/dep/testdata/harness_tests/README.md b/cmd/dep/testdata/harness_tests/README.md index b7713d3a..41004b02 100644 --- a/cmd/dep/testdata/harness_tests/README.md +++ b/cmd/dep/testdata/harness_tests/README.md @@ -28,7 +28,7 @@ desired. The test name will consist of the directory path from `testdata` to the test case directory itself. In the above example, the test name would be `category1/subcategory1/case1`, and could be singled out with the `-run` option of `go test` (i.e. -`go test github.com/golang/dep/cmp/dep -run Integration/category1/subcategory1/case1`). +`go test github.com/golang/dep/cmd/dep -run Integration/category1/subcategory1/case1`). New tests can be added simply by adding a new directory with the json file to the `testdata` tree. There is no need for code modification - the new test will be included automatically. @@ -58,7 +58,8 @@ The `testcase.json` file has the following format: "github.com/sdboyer/deptestdos", "github.com/sdboyer/deptesttres", "github.com/sdboyer/deptestquatro" - ] + ], + "error-expected": "something went wrong" } All of the categories are optional - if the `imports` list for a test is empty, @@ -72,9 +73,10 @@ The test procedure is as follows: 4. Fetch the repos and versions in `gopath-initial` into `$TMPDIR/src` directory 5. Fetch the repos and versions in `vendor-initial` to the project's `vendor` directory 6. Run `commands` on the project, in declaration order -7. Check the resulting files against those in the `final` input directory -8. Check the `vendor` directory for the projects listed under `vendor-final` -9. Check that there were no changes to `src` listings -10. Clean up +7. Ensure that, if any errors are raised, it is only by the final command and their string output matches `error-expected` +8. Check the resulting files against those in the `final` input directory +9. Check the `vendor` directory for the projects listed under `vendor-final` +10. Check that there were no changes to `src` listings +11. Clean up Note that for the remote fetches, only git repos are currently supported. diff --git a/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.lock index 3897b397..2b9c2097 100644 --- a/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.lock @@ -1,4 +1,4 @@ -memo = "4b36ae008ef4be09dee7e2ae00606d44fd75f4310fd0d0ef6e744690290569de" +memo = "14b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c" [[projects]] name = "github.com/sdboyer/deptest" diff --git a/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.toml index 77028df6..26987273 100644 --- a/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.toml @@ -1,9 +1,56 @@ -# Example: +## Gopkg.toml example (these lines may be deleted) + +## "required" lists a set of packages (not projects) that must be included in +## Gopkg.lock. This list is merged with the set of packages imported by the current +## project. Use it when your project needs a package it doesn't explicitly import - +## including "main" packages. +# required = ["github.com/user/thing/cmd/thing"] + +## "ignored" lists a set of packages (not projects) that are ignored when +## dep statically analyzes source code. Ignored packages can be in this project, +## or in a dependency. +# ignored = ["github.com/user/project/badpkg"] + +## Dependencies define constraints on dependent projects. They are respected by +## dep whether coming from the Gopkg.toml of the current project or a dependency. # [[dependencies]] -# source = "https://github.com/myfork/package.git" -# branch = "master" -# name = "github.com/vendor/package" -# Note: revision will depend on your repository type, i.e git, svc, bzr etc... -# revision = "abc123" +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Recommended: the version constraint to enforce for the project. +## Only one of "branch", "version" or "revision" can be specified. # version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: an alternate location (URL or import path) for the project's source. +# source = "https://github.com/myfork/package.git" + +## Overrides have the same structure as [[dependencies]], but supercede all +## [[dependencies]] declarations from all projects. Only the current project's +## [[overrides]] are applied. +## +## Overrides are a sledgehammer. Use them only as a last resort. +# [[overrides]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Optional: specifying a version constraint override will cause all other +## constraints on this project to be ignored; only the overriden constraint +## need be satisfied. +## Again, only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: specifying an alternate source location as an override will +## enforce that the alternate location is used for that project, regardless of +## what source location any dependent projects specify. +# source = "https://github.com/myfork/package.git" + + + +[[dependencies]] + name = "github.com/sdboyer/deptest" + version = "^1.0.0" diff --git a/cmd/dep/testdata/harness_tests/ensure/empty/case2/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/ensure/empty/case2/final/Gopkg.toml index 6deaa217..d327c51a 100644 --- a/cmd/dep/testdata/harness_tests/ensure/empty/case2/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/ensure/empty/case2/final/Gopkg.toml @@ -1,4 +1,3 @@ - [[dependencies]] name = "github.com/sdboyer/deptest" - version = "^0.8.0" + version = "~0.8.0" \ No newline at end of file diff --git a/cmd/dep/testdata/harness_tests/ensure/override/case1/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/ensure/override/case1/final/Gopkg.toml index 7f784a4c..26987273 100644 --- a/cmd/dep/testdata/harness_tests/ensure/override/case1/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/ensure/override/case1/final/Gopkg.toml @@ -1,4 +1,56 @@ -[[overrides]] +## Gopkg.toml example (these lines may be deleted) + +## "required" lists a set of packages (not projects) that must be included in +## Gopkg.lock. This list is merged with the set of packages imported by the current +## project. Use it when your project needs a package it doesn't explicitly import - +## including "main" packages. +# required = ["github.com/user/thing/cmd/thing"] + +## "ignored" lists a set of packages (not projects) that are ignored when +## dep statically analyzes source code. Ignored packages can be in this project, +## or in a dependency. +# ignored = ["github.com/user/project/badpkg"] + +## Dependencies define constraints on dependent projects. They are respected by +## dep whether coming from the Gopkg.toml of the current project or a dependency. +# [[dependencies]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Recommended: the version constraint to enforce for the project. +## Only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: an alternate location (URL or import path) for the project's source. +# source = "https://github.com/myfork/package.git" + +## Overrides have the same structure as [[dependencies]], but supercede all +## [[dependencies]] declarations from all projects. Only the current project's +## [[overrides]] are applied. +## +## Overrides are a sledgehammer. Use them only as a last resort. +# [[overrides]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Optional: specifying a version constraint override will cause all other +## constraints on this project to be ignored; only the overriden constraint +## need be satisfied. +## Again, only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: specifying an alternate source location as an override will +## enforce that the alternate location is used for that project, regardless of +## what source location any dependent projects specify. +# source = "https://github.com/myfork/package.git" + + + +[[dependencies]] name = "github.com/sdboyer/deptest" - version = "1.0.0" + version = "^1.0.0" diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.lock new file mode 100644 index 00000000..70280d1a --- /dev/null +++ b/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.lock @@ -0,0 +1 @@ +memo = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7" diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.toml new file mode 100644 index 00000000..e69de29b diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/testcase.json b/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/testcase.json new file mode 100644 index 00000000..d3635f8d --- /dev/null +++ b/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/testcase.json @@ -0,0 +1,7 @@ +{ + "commands": [ + ["init", "-no-examples"], + ["ensure", "-update"] + ], + "error-expected" : "all dirs lacked any go code" + } diff --git a/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.lock index 20091c76..d43aa9f9 100644 --- a/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.lock @@ -1,4 +1,4 @@ -memo = "" +memo = "88d2718cda70cce45158f953d2c6ead79c1db38e67e9704aff72be8fddb096e7" [[projects]] name = "github.com/sdboyer/deptest" diff --git a/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.toml index 05db85d1..0681d4cf 100644 --- a/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.toml @@ -5,4 +5,3 @@ [[dependencies]] name = "github.com/sdboyer/deptestdos" - revision = "a0196baa11ea047dd65037287451d36b861b00ea" diff --git a/cmd/dep/testdata/harness_tests/init/case1/testcase.json b/cmd/dep/testdata/harness_tests/init/case1/testcase.json index c449f869..ea0ff804 100644 --- a/cmd/dep/testdata/harness_tests/init/case1/testcase.json +++ b/cmd/dep/testdata/harness_tests/init/case1/testcase.json @@ -1,6 +1,6 @@ { "commands": [ - ["init"] + ["init", "-no-examples"] ], "gopath-initial": { "github.com/sdboyer/deptest": "v0.8.0", diff --git a/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.lock index 9b7e4cbf..50ed86f6 100644 --- a/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.lock @@ -1,4 +1,4 @@ -memo = "88d2718cda70cce45158f953d2c6ead79c1db38e67e9704aff72be8fddb096e7" +memo = "b4fe6e8bceac924197838b6ea47989abbdd3a8d31035d20ee0a1dabc0994c368" [[projects]] name = "github.com/sdboyer/deptest" diff --git a/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.toml index a5284b92..487298ca 100644 --- a/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.toml @@ -2,3 +2,7 @@ [[dependencies]] name = "github.com/sdboyer/deptest" version = ">=0.8.0, <1.0.0" + +[[dependencies]] + name = "github.com/sdboyer/deptestdos" + version = "^2.0.0" diff --git a/cmd/dep/testdata/harness_tests/init/case2/testcase.json b/cmd/dep/testdata/harness_tests/init/case2/testcase.json index a2099b17..3f3140bc 100644 --- a/cmd/dep/testdata/harness_tests/init/case2/testcase.json +++ b/cmd/dep/testdata/harness_tests/init/case2/testcase.json @@ -1,6 +1,6 @@ { "commands": [ - ["init"] + ["init", "-no-examples"] ], "gopath-initial": { "github.com/sdboyer/deptest": "v0.8.0" diff --git a/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.lock index 66d52b35..465f5950 100644 --- a/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.lock @@ -1,4 +1,4 @@ -memo = "" +memo = "af9a783a5430dabcaaf44683c09e2b729e1c0d61f13bfdf6677c4fd0b41387ca" [[projects]] branch = "master" diff --git a/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.toml index 74acf5e6..bccfadb8 100644 --- a/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.toml @@ -5,4 +5,3 @@ [[dependencies]] name = "github.com/sdboyer/deptestdos" - revision = "a0196baa11ea047dd65037287451d36b861b00ea" diff --git a/cmd/dep/testdata/harness_tests/init/case3/testcase.json b/cmd/dep/testdata/harness_tests/init/case3/testcase.json index 9974fa15..13cba35a 100644 --- a/cmd/dep/testdata/harness_tests/init/case3/testcase.json +++ b/cmd/dep/testdata/harness_tests/init/case3/testcase.json @@ -1,6 +1,6 @@ { "commands": [ - ["init"] + ["init", "-no-examples"] ], "gopath-initial": { "github.com/sdboyer/deptestdos": "a0196baa11ea047dd65037287451d36b861b00ea" diff --git a/cmd/dep/testdata/harness_tests/init/manifest-exists/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/init/manifest-exists/final/Gopkg.toml new file mode 100644 index 00000000..e69de29b diff --git a/cmd/dep/testdata/harness_tests/init/manifest-exists/initial/Gopkg.toml b/cmd/dep/testdata/harness_tests/init/manifest-exists/initial/Gopkg.toml new file mode 100644 index 00000000..e69de29b diff --git a/cmd/dep/testdata/harness_tests/init/manifest-exists/testcase.json b/cmd/dep/testdata/harness_tests/init/manifest-exists/testcase.json new file mode 100644 index 00000000..08322d2a --- /dev/null +++ b/cmd/dep/testdata/harness_tests/init/manifest-exists/testcase.json @@ -0,0 +1,6 @@ +{ + "commands": [ + ["init"] + ], + "error-expected" : "manifest already exists:" + } \ No newline at end of file diff --git a/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.lock index 3897b397..2b9c2097 100644 --- a/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.lock @@ -1,4 +1,4 @@ -memo = "4b36ae008ef4be09dee7e2ae00606d44fd75f4310fd0d0ef6e744690290569de" +memo = "14b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c" [[projects]] name = "github.com/sdboyer/deptest" diff --git a/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.toml index 77028df6..d5f3e3c9 100644 --- a/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.toml @@ -1,9 +1,4 @@ -# Example: -# [[dependencies]] -# source = "https://github.com/myfork/package.git" -# branch = "master" -# name = "github.com/vendor/package" -# Note: revision will depend on your repository type, i.e git, svc, bzr etc... -# revision = "abc123" -# version = "1.0.0" +[[dependencies]] + name = "github.com/sdboyer/deptest" + version = "^1.0.0" diff --git a/cmd/dep/testdata/harness_tests/init/skip-hidden/testcase.json b/cmd/dep/testdata/harness_tests/init/skip-hidden/testcase.json index 18dcb9a5..28360425 100644 --- a/cmd/dep/testdata/harness_tests/init/skip-hidden/testcase.json +++ b/cmd/dep/testdata/harness_tests/init/skip-hidden/testcase.json @@ -1,6 +1,6 @@ { "commands": [ - ["init"] + ["init", "-no-examples"] ], "vendor-final": [ "github.com/sdboyer/deptest" diff --git a/cmd/dep/testdata/harness_tests/remove/force/case1/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/remove/force/case1/final/Gopkg.toml index a5284b92..413feb4e 100644 --- a/cmd/dep/testdata/harness_tests/remove/force/case1/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/remove/force/case1/final/Gopkg.toml @@ -1,4 +1,11 @@ +[[dependencies]] + name = "github.com/not/used" + version = "2.0.0" [[dependencies]] name = "github.com/sdboyer/deptest" version = ">=0.8.0, <1.0.0" + +[[dependencies]] + name = "github.com/sdboyer/deptestdos" + revision = "a0196baa11ea047dd65037287451d36b861b00ea" diff --git a/cmd/dep/testdata/harness_tests/remove/specific/case1/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/remove/specific/case1/final/Gopkg.toml index 05db85d1..26653704 100644 --- a/cmd/dep/testdata/harness_tests/remove/specific/case1/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/remove/specific/case1/final/Gopkg.toml @@ -1,8 +1,11 @@ +[[dependencies]] + name = "github.com/not/used" + version = "2.0.0" [[dependencies]] - name = "github.com/sdboyer/deptest" +name = "github.com/sdboyer/deptest" version = ">=0.8.0, <1.0.0" [[dependencies]] - name = "github.com/sdboyer/deptestdos" - revision = "a0196baa11ea047dd65037287451d36b861b00ea" +name = "github.com/sdboyer/deptestdos" + revision = "a0196baa11ea047dd65037287451d36b861b00ea" \ No newline at end of file diff --git a/cmd/dep/testdata/harness_tests/remove/specific/case2/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/remove/specific/case2/final/Gopkg.toml index 05db85d1..0bb0ab1d 100644 --- a/cmd/dep/testdata/harness_tests/remove/specific/case2/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/remove/specific/case2/final/Gopkg.toml @@ -1,3 +1,6 @@ +[[dependencies]] + name = "github.com/not/used" + version = "2.0.0" [[dependencies]] name = "github.com/sdboyer/deptest" @@ -5,4 +8,4 @@ [[dependencies]] name = "github.com/sdboyer/deptestdos" - revision = "a0196baa11ea047dd65037287451d36b861b00ea" + revision = "a0196baa11ea047dd65037287451d36b861b00ea" \ No newline at end of file diff --git a/cmd/dep/testdata/harness_tests/remove/unused/case1/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/remove/unused/case1/final/Gopkg.toml index 05db85d1..0bb0ab1d 100644 --- a/cmd/dep/testdata/harness_tests/remove/unused/case1/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/remove/unused/case1/final/Gopkg.toml @@ -1,3 +1,6 @@ +[[dependencies]] + name = "github.com/not/used" + version = "2.0.0" [[dependencies]] name = "github.com/sdboyer/deptest" @@ -5,4 +8,4 @@ [[dependencies]] name = "github.com/sdboyer/deptestdos" - revision = "a0196baa11ea047dd65037287451d36b861b00ea" + revision = "a0196baa11ea047dd65037287451d36b861b00ea" \ No newline at end of file diff --git a/cmd/dep/testdata/harness_tests/status/case1/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/case1/final/Gopkg.toml index a5284b92..122d0340 100644 --- a/cmd/dep/testdata/harness_tests/status/case1/final/Gopkg.toml +++ b/cmd/dep/testdata/harness_tests/status/case1/final/Gopkg.toml @@ -1,4 +1,3 @@ - [[dependencies]] name = "github.com/sdboyer/deptest" - version = ">=0.8.0, <1.0.0" + version = "^0.8.0" diff --git a/context_test.go b/context_test.go index 58956928..d913fcb1 100644 --- a/context_test.go +++ b/context_test.go @@ -352,22 +352,15 @@ func TestResolveProjectRoot(t *testing.T) { tg := test.NewHelper(t) defer tg.Cleanup() - tg.TempDir("go") - tg.TempDir("go/src") - tg.TempDir("go/src/real") tg.TempDir("go/src/real/path") tg.TempDir("go/src/sym") - tg.TempDir("gotwo") // Another directory used as a GOPATH - tg.TempDir("gotwo/src") - tg.TempDir("gotwo/src/real") + // Another directory used as a GOPATH tg.TempDir("gotwo/src/real/path") tg.TempDir("gotwo/src/sym") tg.TempDir("sym") // Directory for symlinks - tg.Setenv("GOPATH", tg.Path(filepath.Join(".", "go"))) - ctx := &Ctx{ GOPATH: tg.Path(filepath.Join(".", "go")), GOPATHS: []string{ @@ -376,45 +369,65 @@ func TestResolveProjectRoot(t *testing.T) { }, } - realPath := filepath.Join(ctx.GOPATH, "src", "real", "path") - realPathTwo := filepath.Join(ctx.GOPATHS[1], "src", "real", "path") - symlinkedPath := filepath.Join(tg.Path("."), "sym", "symlink") - symlinkedInGoPath := filepath.Join(ctx.GOPATH, "src/sym/path") - symlinkedInOtherGoPath := filepath.Join(tg.Path("."), "sym", "symtwo") - os.Symlink(realPath, symlinkedPath) - os.Symlink(realPath, symlinkedInGoPath) - os.Symlink(realPathTwo, symlinkedInOtherGoPath) - - // Real path should be returned, no symlinks to deal with - p, err := ctx.resolveProjectRoot(realPath) - if err != nil { - t.Fatalf("Error resolving project root: %s", err) - } - if p != realPath { - t.Fatalf("Want path to be %s, got %s", realPath, p) + testcases := []struct { + name string + path string + resolvedPath string + symlink bool + expectErr bool + }{ + { + name: "no-symlinks", + path: filepath.Join(ctx.GOPATH, "src/real/path"), + resolvedPath: filepath.Join(ctx.GOPATH, "src/real/path"), + }, + { + name: "symlink-outside-gopath", + path: filepath.Join(tg.Path("."), "sym/symlink"), + resolvedPath: filepath.Join(ctx.GOPATH, "src/real/path"), + symlink: true, + }, + { + name: "symlink-in-another-gopath", + path: filepath.Join(tg.Path("."), "sym/symtwo"), + resolvedPath: filepath.Join(ctx.GOPATHS[1], "src/real/path"), + symlink: true, + }, + { + name: "symlink-in-gopath", + path: filepath.Join(ctx.GOPATH, "src/sym/path"), + resolvedPath: filepath.Join(ctx.GOPATH, "src/real/path"), + symlink: true, + expectErr: true, + }, } - // Real path should be returned, symlink is outside GOPATH - p, err = ctx.resolveProjectRoot(symlinkedPath) - if err != nil { - t.Fatalf("Error resolving project root: %s", err) - } - if p != realPath { - t.Fatalf("Want path to be %s, got %s", realPath, p) - } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + if tc.symlink { + if err := os.Symlink(tc.resolvedPath, tc.path); err != nil { + if runtime.GOOS == "windows" { + t.Skipf("Not testing Windows symlinks because: %s", err) + } else { + t.Fatal(err) + } + } + } - // Real path should be returned, symlink is in another GOPATH - p, err = ctx.resolveProjectRoot(symlinkedInOtherGoPath) - if err != nil { - t.Fatalf("Error resolving project root: %s", err) - } - if p != realPathTwo { - t.Fatalf("Want path to be %s, got %s", realPathTwo, p) - } + p, err := ctx.resolveProjectRoot(tc.path) + if err != nil { + if !tc.expectErr { + t.Fatalf("Error resolving project root: %s", err) + } + return + } + if err == nil && tc.expectErr { + t.Fatal("Wanted an error") + } - // Symlinked path is inside GOPATH, should return error - _, err = ctx.resolveProjectRoot(symlinkedInGoPath) - if err == nil { - t.Fatalf("Wanted an error") + if p != tc.resolvedPath { + t.Errorf("Want path to be %s, got %s", tc.resolvedPath, p) + } + }) } } diff --git a/fs_test.go b/fs_test.go index a5c13172..447adb3f 100644 --- a/fs_test.go +++ b/fs_test.go @@ -190,6 +190,8 @@ func TestIsEmpty(t *testing.T) { } h := test.NewHelper(t) + defer h.Cleanup() + h.TempDir("empty") tests := map[string]string{ wd: "true", diff --git a/test/integration_testcase.go b/test/integration_testcase.go index 6fbf0516..fb4fa298 100644 --- a/test/integration_testcase.go +++ b/test/integration_testcase.go @@ -12,6 +12,7 @@ import ( "os" "path/filepath" "regexp" + "strings" "testing" ) @@ -27,6 +28,7 @@ type IntegrationTestCase struct { initialPath string finalPath string Commands [][]string `json:"commands"` + ErrorExpected string `json:"error-expected"` GopathInitial map[string]string `json:"gopath-initial"` VendorInitial map[string]string `json:"vendor-initial"` VendorFinal []string `json:"vendor-final"` @@ -126,6 +128,22 @@ func (tc *IntegrationTestCase) CompareFile(goldenPath, working string) { } } +// CompareError compares exected and actual error +func (tc *IntegrationTestCase) CompareError(err error, stderr string) { + wantExists, want := tc.ErrorExpected != "", tc.ErrorExpected + gotExists, got := stderr != "" && err != nil, stderr + + if wantExists && gotExists { + if !strings.Contains(got, want) { + tc.t.Errorf("expected error containing %s, got error %s", want, got) + } + } else if !wantExists && gotExists { + tc.t.Fatal("error raised where none was expected") + } else if wantExists && !gotExists { + tc.t.Error("error not raised where one was expected") + } +} + func (tc *IntegrationTestCase) CompareVendorPaths(gotVendorPaths []string) { if *UpdateGolden { tc.VendorFinal = gotVendorPaths diff --git a/test/integration_testproj.go b/test/integration_testproj.go index 475e7002..a8c638c3 100644 --- a/test/integration_testproj.go +++ b/test/integration_testproj.go @@ -139,6 +139,11 @@ func (p *IntegrationTestProject) RunGit(dir string, args ...string) { } } +// GetStderr gets the Stderr output from test run +func (p *IntegrationTestProject) GetStderr() string { + return p.stderr.String() +} + func (p *IntegrationTestProject) GetVendorGit(ip string) { parse := strings.Split(ip, "/") gitDir := strings.Join(parse[:len(parse)-1], string(filepath.Separator)) diff --git a/testdata/txn_writer/expected_manifest.toml b/testdata/txn_writer/expected_manifest.toml index d1057a0f..e0e080df 100644 --- a/testdata/txn_writer/expected_manifest.toml +++ b/testdata/txn_writer/expected_manifest.toml @@ -1,4 +1,56 @@ +## Gopkg.toml example (these lines may be deleted) + +## "required" lists a set of packages (not projects) that must be included in +## Gopkg.lock. This list is merged with the set of packages imported by the current +## project. Use it when your project needs a package it doesn't explicitly import - +## including "main" packages. +# required = ["github.com/user/thing/cmd/thing"] + +## "ignored" lists a set of packages (not projects) that are ignored when +## dep statically analyzes source code. Ignored packages can be in this project, +## or in a dependency. +# ignored = ["github.com/user/project/badpkg"] + +## Dependencies define constraints on dependent projects. They are respected by +## dep whether coming from the Gopkg.toml of the current project or a dependency. +# [[dependencies]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Recommended: the version constraint to enforce for the project. +## Only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: an alternate location (URL or import path) for the project's source. +# source = "https://github.com/myfork/package.git" + +## Overrides have the same structure as [[dependencies]], but supercede all +## [[dependencies]] declarations from all projects. Only the current project's +## [[overrides]] are applied. +## +## Overrides are a sledgehammer. Use them only as a last resort. +# [[overrides]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Optional: specifying a version constraint override will cause all other +## constraints on this project to be ignored; only the overriden constraint +## need be satisfied. +## Again, only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: specifying an alternate source location as an override will +## enforce that the alternate location is used for that project, regardless of +## what source location any dependent projects specify. +# source = "https://github.com/myfork/package.git" + + + [[dependencies]] name = "github.com/sdboyer/dep-test" version = "1.0.0" diff --git a/txn_writer.go b/txn_writer.go index a92b6414..5f1dba4e 100644 --- a/txn_writer.go +++ b/txn_writer.go @@ -21,15 +21,58 @@ import ( // Example string to be written to the manifest file // if no dependencies are found in the project // during `dep init` -const exampleToml = ` -# Example: +const exampleTOML = ` +## Gopkg.toml example (these lines may be deleted) + +## "required" lists a set of packages (not projects) that must be included in +## Gopkg.lock. This list is merged with the set of packages imported by the current +## project. Use it when your project needs a package it doesn't explicitly import - +## including "main" packages. +# required = ["github.com/user/thing/cmd/thing"] + +## "ignored" lists a set of packages (not projects) that are ignored when +## dep statically analyzes source code. Ignored packages can be in this project, +## or in a dependency. +# ignored = ["github.com/user/project/badpkg"] + +## Dependencies define constraints on dependent projects. They are respected by +## dep whether coming from the Gopkg.toml of the current project or a dependency. # [[dependencies]] -# source = "https://github.com/myfork/package.git" -# branch = "master" -# name = "github.com/vendor/package" -# Note: revision will depend on your repository type, i.e git, svc, bzr etc... -# revision = "abc123" +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Recommended: the version constraint to enforce for the project. +## Only one of "branch", "version" or "revision" can be specified. # version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: an alternate location (URL or import path) for the project's source. +# source = "https://github.com/myfork/package.git" + +## Overrides have the same structure as [[dependencies]], but supercede all +## [[dependencies]] declarations from all projects. Only the current project's +## [[overrides]] are applied. +## +## Overrides are a sledgehammer. Use them only as a last resort. +# [[overrides]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Optional: specifying a version constraint override will cause all other +## constraints on this project to be ignored; only the overriden constraint +## need be satisfied. +## Again, only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: specifying an alternate source location as an override will +## enforce that the alternate location is used for that project, regardless of +## what source location any dependent projects specify. +# source = "https://github.com/myfork/package.git" + + ` // SafeWriter transactionalizes writes of manifest, lock, and vendor dir, both @@ -39,27 +82,63 @@ const exampleToml = ` // It is not impervious to errors (writing to disk is hard), but it should // guard against non-arcane failure conditions. type SafeWriter struct { - Payload *SafeWriterPayload -} - -// SafeWriterPayload represents the actions SafeWriter will execute when SafeWriter.Write is called. -type SafeWriterPayload struct { Manifest *Manifest Lock *Lock LockDiff *gps.LockDiff WriteVendor bool } -func (payload *SafeWriterPayload) HasLock() bool { - return payload.Lock != nil +// NewSafeWriter sets up a SafeWriter to write a set of config yaml, lock and vendor tree. +// +// - If manifest is provided, it will be written to the standard manifest file +// name beneath root. +// - If newLock is provided, it will be written to the standard lock file +// name beneath root. +// - If vendor is VendorAlways, or is VendorOnChanged and the locks are different, +// the vendor directory will be written beneath root based on newLock. +// - If oldLock is provided without newLock, error. +// - If vendor is VendorAlways without a newLock, error. +func NewSafeWriter(manifest *Manifest, oldLock, newLock *Lock, vendor VendorBehavior) (*SafeWriter, error) { + sw := &SafeWriter{ + Manifest: manifest, + Lock: newLock, + } + if oldLock != nil { + if newLock == nil { + return nil, errors.New("must provide newLock when oldLock is specified") + } + sw.LockDiff = gps.DiffLocks(oldLock, newLock) + } + + switch vendor { + case VendorAlways: + sw.WriteVendor = true + case VendorOnChanged: + if sw.LockDiff != nil || (newLock != nil && oldLock == nil) { + sw.WriteVendor = true + } + } + + if sw.WriteVendor && newLock == nil { + return nil, errors.New("must provide newLock in order to write out vendor") + } + + return sw, nil } -func (payload *SafeWriterPayload) HasManifest() bool { - return payload.Manifest != nil +// HasLock checks if a Lock is present in the SafeWriter +func (sw *SafeWriter) HasLock() bool { + return sw.Lock != nil } -func (payload *SafeWriterPayload) HasVendor() bool { - return payload.WriteVendor +// HasManifest checks if a Manifest is present in the SafeWriter +func (sw *SafeWriter) HasManifest() bool { + return sw.Manifest != nil +} + +// HasVendor returns the if SafeWriter should write to vendor +func (sw *SafeWriter) HasVendor() bool { + return sw.WriteVendor } type rawStringDiff struct { @@ -178,47 +257,7 @@ const ( VendorNever ) -// Prepare to write a set of config yaml, lock and vendor tree. -// -// - If manifest is provided, it will be written to the standard manifest file -// name beneath root. -// - If newLock is provided, it will be written to the standard lock file -// name beneath root. -// - If vendor is VendorAlways, or is VendorOnChanged and the locks are different, -// the vendor directory will be written beneath root based on newLock. -// - If oldLock is provided without newLock, error. -// - If vendor is VendorAlways without a newLock, error. -func (sw *SafeWriter) Prepare(manifest *Manifest, oldLock, newLock *Lock, vendor VendorBehavior) error { - - sw.Payload = &SafeWriterPayload{ - Manifest: manifest, - Lock: newLock, - } - - if oldLock != nil { - if newLock == nil { - return errors.New("must provide newLock when oldLock is specified") - } - sw.Payload.LockDiff = gps.DiffLocks(oldLock, newLock) - } - - switch vendor { - case VendorAlways: - sw.Payload.WriteVendor = true - case VendorOnChanged: - if sw.Payload.LockDiff != nil || (newLock != nil && oldLock == nil) { - sw.Payload.WriteVendor = true - } - } - - if sw.Payload.WriteVendor && newLock == nil { - return errors.New("must provide newLock in order to write out vendor") - } - - return nil -} - -func (payload SafeWriterPayload) validate(root string, sm gps.SourceManager) error { +func (sw SafeWriter) validate(root string, sm gps.SourceManager) error { if root == "" { return errors.New("root path must be non-empty") } @@ -229,7 +268,7 @@ func (payload SafeWriterPayload) validate(root string, sm gps.SourceManager) err return errors.Errorf("root path %q does not exist", root) } - if payload.HasVendor() && sm == nil { + if sw.HasVendor() && sm == nil { return errors.New("must provide a SourceManager if writing out a vendor dir") } @@ -244,18 +283,13 @@ func (payload SafeWriterPayload) validate(root string, sm gps.SourceManager) err // operations succeeded. It also does its best to roll back if any moves fail. // This mostly guarantees that dep cannot exit with a partial write that would // leave an undefined state on disk. -func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error { - - if sw.Payload == nil { - return errors.New("Cannot call SafeWriter.Write before SafeWriter.Prepare") - } - - err := sw.Payload.validate(root, sm) +func (sw *SafeWriter) Write(root string, sm gps.SourceManager, noExamples bool) error { + err := sw.validate(root, sm) if err != nil { return err } - if !sw.Payload.HasManifest() && !sw.Payload.HasLock() && !sw.Payload.HasVendor() { + if !sw.HasManifest() && !sw.HasLock() && !sw.HasVendor() { // nothing to do return nil } @@ -270,30 +304,48 @@ func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error { } defer os.RemoveAll(td) - if sw.Payload.HasManifest() { - if sw.Payload.Manifest.IsEmpty() { - err := modifyWithString(filepath.Join(td, ManifestName), exampleToml) - if err != nil { - return errors.Wrap(err, "failed to generate example text") - } - } else if err := writeFile(filepath.Join(td, ManifestName), sw.Payload.Manifest); err != nil { + if sw.HasManifest() { + // Always write the example text to the bottom of the TOML file. + tb, err := sw.Manifest.MarshalTOML() + if err != nil { + return errors.Wrap(err, "failed to marshal manifest to TOML") + } + + var initOutput string + + // If examples are NOT disabled, use the example text + if !noExamples { + initOutput = exampleTOML + } + + // 0666 is before umask; mirrors behavior of os.Create (used by + // writeFile()) + if err = ioutil.WriteFile(filepath.Join(td, ManifestName), append([]byte(initOutput), tb...), 0666); err != nil { return errors.Wrap(err, "failed to write manifest file to temp dir") } } - if sw.Payload.HasLock() { - if err := writeFile(filepath.Join(td, LockName), sw.Payload.Lock); err != nil { + if sw.HasLock() { + if err := writeFile(filepath.Join(td, LockName), sw.Lock); err != nil { return errors.Wrap(err, "failed to write lock file to temp dir") } } - if sw.Payload.HasVendor() { - err = gps.WriteDepTree(filepath.Join(td, "vendor"), sw.Payload.Lock, sm, true) + if sw.HasVendor() { + err = gps.WriteDepTree(filepath.Join(td, "vendor"), sw.Lock, sm, true) if err != nil { return errors.Wrap(err, "error while writing out vendor tree") } } + // Ensure vendor/.git is preserved if present + if hasDotGit(vpath) { + err = renameWithFallback(filepath.Join(vpath, ".git"), filepath.Join(td, "vendor/.git")) + if _, ok := err.(*os.LinkError); ok { + return errors.Wrap(err, "failed to preserve vendor/.git") + } + } + // Move the existing files and dirs to the temp dir while we put the new // ones in, to provide insurance against errors for as long as possible. type pathpair struct { @@ -303,7 +355,7 @@ func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error { var failerr error var vendorbak string - if sw.Payload.HasManifest() { + if sw.HasManifest() { if _, err := os.Stat(mpath); err == nil { // Move out the old one. tmploc := filepath.Join(td, ManifestName+".orig") @@ -321,7 +373,7 @@ func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error { } } - if sw.Payload.HasLock() { + if sw.HasLock() { if _, err := os.Stat(lpath); err == nil { // Move out the old one. tmploc := filepath.Join(td, LockName+".orig") @@ -340,7 +392,7 @@ func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error { } } - if sw.Payload.HasVendor() { + if sw.HasVendor() { if _, err := os.Stat(vpath); err == nil { // Move out the old vendor dir. just do it into an adjacent dir, to // try to mitigate the possibility of a pointless cross-filesystem @@ -368,7 +420,7 @@ func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error { // Renames all went smoothly. The deferred os.RemoveAll will get the temp // dir, but if we wrote vendor, we have to clean that up directly - if sw.Payload.HasVendor() { + if sw.HasVendor() { // Nothing we can really do about an error at this point, so ignore it os.RemoveAll(vendorbak) } @@ -385,26 +437,26 @@ fail: } func (sw *SafeWriter) PrintPreparedActions() error { - if sw.Payload.HasManifest() { + if sw.HasManifest() { fmt.Printf("Would have written the following %s:\n", ManifestName) - m, err := sw.Payload.Manifest.MarshalTOML() + m, err := sw.Manifest.MarshalTOML() if err != nil { return errors.Wrap(err, "ensure DryRun cannot serialize manifest") } fmt.Println(string(m)) } - if sw.Payload.HasLock() { - if sw.Payload.LockDiff == nil { + if sw.HasLock() { + if sw.LockDiff == nil { fmt.Printf("Would have written the following %s:\n", LockName) - l, err := sw.Payload.Lock.MarshalTOML() + l, err := sw.Lock.MarshalTOML() if err != nil { return errors.Wrap(err, "ensure DryRun cannot serialize lock") } fmt.Println(string(l)) } else { fmt.Printf("Would have written the following changes to %s:\n", LockName) - diff, err := formatLockDiff(*sw.Payload.LockDiff) + diff, err := formatLockDiff(*sw.LockDiff) if err != nil { return errors.Wrap(err, "ensure DryRun cannot serialize the lock diff") } @@ -412,9 +464,9 @@ func (sw *SafeWriter) PrintPreparedActions() error { } } - if sw.Payload.HasVendor() { + if sw.HasVendor() { fmt.Println("Would have written the following projects to the vendor directory:") - for _, project := range sw.Payload.Lock.Projects() { + for _, project := range sw.Lock.Projects() { prj := project.Ident() rev, _, _ := gps.VersionComponentStrings(project.Version()) if prj.Source == "" { @@ -526,6 +578,13 @@ func deleteDirs(toDelete []string) error { return nil } +// hasDotGit checks if a given path has .git file or directory in it. +func hasDotGit(path string) bool { + gitfilepath := filepath.Join(path, ".git") + _, err := os.Stat(gitfilepath) + return err == nil +} + type byLen []string func (a byLen) Len() int { return len(a) } diff --git a/txn_writer_test.go b/txn_writer_test.go index 3e3ea237..94ea5fe7 100644 --- a/txn_writer_test.go +++ b/txn_writer_test.go @@ -5,6 +5,8 @@ package dep import ( + "io/ioutil" + "os" "path/filepath" "strings" "testing" @@ -23,10 +25,8 @@ func TestSafeWriter_BadInput_MissingRoot(t *testing.T) { pc := NewTestProjectContext(h, safeWriterProject) defer pc.Release() - var sw SafeWriter - sw.Prepare(nil, nil, nil, VendorOnChanged) - - err := sw.Write("", pc.SourceManager) + sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged) + err := sw.Write("", pc.SourceManager, false) if err == nil { t.Fatal("should have errored without a root path, but did not") @@ -43,10 +43,8 @@ func TestSafeWriter_BadInput_MissingSourceManager(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - var sw SafeWriter - sw.Prepare(nil, nil, pc.Project.Lock, VendorAlways) - - err := sw.Write(pc.Project.AbsRoot, nil) + sw, _ := NewSafeWriter(nil, nil, pc.Project.Lock, VendorAlways) + err := sw.Write(pc.Project.AbsRoot, nil, false) if err == nil { t.Fatal("should have errored without a source manager when forceVendor is true, but did not") @@ -61,9 +59,7 @@ func TestSafeWriter_BadInput_ForceVendorMissingLock(t *testing.T) { pc := NewTestProjectContext(h, safeWriterProject) defer pc.Release() - var sw SafeWriter - err := sw.Prepare(nil, nil, nil, VendorAlways) - + _, err := NewSafeWriter(nil, nil, nil, VendorAlways) if err == nil { t.Fatal("should have errored without a lock when forceVendor is true, but did not") } else if !strings.Contains(err.Error(), "newLock") { @@ -79,9 +75,7 @@ func TestSafeWriter_BadInput_OldLockOnly(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - var sw SafeWriter - err := sw.Prepare(nil, pc.Project.Lock, nil, VendorAlways) - + _, err := NewSafeWriter(nil, pc.Project.Lock, nil, VendorAlways) if err == nil { t.Fatal("should have errored with only an old lock, but did not") } else if !strings.Contains(err.Error(), "oldLock") { @@ -95,11 +89,10 @@ func TestSafeWriter_BadInput_NonexistentRoot(t *testing.T) { pc := NewTestProjectContext(h, safeWriterProject) defer pc.Release() - var sw SafeWriter - sw.Prepare(nil, nil, nil, VendorOnChanged) + sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged) missingroot := filepath.Join(pc.Project.AbsRoot, "nonexistent") - err := sw.Write(missingroot, pc.SourceManager) + err := sw.Write(missingroot, pc.SourceManager, false) if err == nil { t.Fatal("should have errored with nonexistent dir for root path, but did not") @@ -114,11 +107,10 @@ func TestSafeWriter_BadInput_RootIsFile(t *testing.T) { pc := NewTestProjectContext(h, safeWriterProject) defer pc.Release() - var sw SafeWriter - sw.Prepare(nil, nil, nil, VendorOnChanged) + sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged) fileroot := pc.CopyFile("fileroot", "txn_writer/badinput_fileroot") - err := sw.Write(fileroot, pc.SourceManager) + err := sw.Write(fileroot, pc.SourceManager, false) if err == nil { t.Fatal("should have errored when root path is a file, but did not") @@ -139,22 +131,21 @@ func TestSafeWriter_Manifest(t *testing.T) { pc.CopyFile(ManifestName, safeWriterGoldenManifest) pc.Load() - var sw SafeWriter - sw.Prepare(pc.Project.Manifest, nil, nil, VendorOnChanged) + sw, _ := NewSafeWriter(pc.Project.Manifest, nil, nil, VendorOnChanged) // Verify prepared actions - if !sw.Payload.HasManifest() { + if !sw.HasManifest() { t.Fatal("Expected the payload to contain the manifest") } - if sw.Payload.HasLock() { + if sw.HasLock() { t.Fatal("Did not expect the payload to contain the lock") } - if sw.Payload.HasVendor() { + if sw.HasVendor() { t.Fatal("Did not expect the payload to contain the vendor directory") } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -182,22 +173,21 @@ func TestSafeWriter_ManifestAndUnmodifiedLock(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - var sw SafeWriter - sw.Prepare(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorOnChanged) + sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorOnChanged) // Verify prepared actions - if !sw.Payload.HasManifest() { + if !sw.HasManifest() { t.Fatal("Expected the payload to contain the manifest") } - if !sw.Payload.HasLock() { + if !sw.HasLock() { t.Fatal("Expected the payload to contain the lock.") } - if sw.Payload.HasVendor() { + if sw.HasVendor() { t.Fatal("Did not expect the payload to contain the vendor directory") } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -225,22 +215,21 @@ func TestSafeWriter_ManifestAndUnmodifiedLockWithForceVendor(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - var sw SafeWriter - sw.Prepare(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorAlways) + sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorAlways) // Verify prepared actions - if !sw.Payload.HasManifest() { + if !sw.HasManifest() { t.Fatal("Expected the payload to contain the manifest") } - if !sw.Payload.HasLock() { + if !sw.HasLock() { t.Fatal("Expected the payload to contain the lock") } - if !sw.Payload.HasVendor() { + if !sw.HasVendor() { t.Fatal("Expected the payload to contain the vendor directory") } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -270,25 +259,24 @@ func TestSafeWriter_ModifiedLock(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - var sw SafeWriter originalLock := new(Lock) *originalLock = *pc.Project.Lock originalLock.Memo = []byte{} // zero out the input hash to ensure non-equivalency - sw.Prepare(nil, originalLock, pc.Project.Lock, VendorOnChanged) + sw, _ := NewSafeWriter(nil, originalLock, pc.Project.Lock, VendorOnChanged) // Verify prepared actions - if sw.Payload.HasManifest() { + if sw.HasManifest() { t.Fatal("Did not expect the payload to contain the manifest") } - if !sw.Payload.HasLock() { + if !sw.HasLock() { t.Fatal("Expected the payload to contain the lock") } - if !sw.Payload.HasVendor() { + if !sw.HasVendor() { t.Fatal("Expected the payload to contain the vendor directory") } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -318,25 +306,24 @@ func TestSafeWriter_ModifiedLockSkipVendor(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - var sw SafeWriter originalLock := new(Lock) *originalLock = *pc.Project.Lock originalLock.Memo = []byte{} // zero out the input hash to ensure non-equivalency - sw.Prepare(nil, originalLock, pc.Project.Lock, VendorNever) + sw, _ := NewSafeWriter(nil, originalLock, pc.Project.Lock, VendorNever) // Verify prepared actions - if sw.Payload.HasManifest() { + if sw.HasManifest() { t.Fatal("Did not expect the payload to contain the manifest") } - if !sw.Payload.HasLock() { + if !sw.HasLock() { t.Fatal("Expected the payload to contain the lock") } - if sw.Payload.HasVendor() { + if sw.HasVendor() { t.Fatal("Did not expect the payload to contain the vendor directory") } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -363,25 +350,23 @@ func TestSafeWriter_ForceVendorWhenVendorAlreadyExists(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - var sw SafeWriter - // Populate vendor - sw.Prepare(nil, pc.Project.Lock, pc.Project.Lock, VendorAlways) - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager) + sw, _ := NewSafeWriter(nil, pc.Project.Lock, pc.Project.Lock, VendorAlways) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify prepared actions - sw.Prepare(nil, nil, pc.Project.Lock, VendorAlways) - if sw.Payload.HasManifest() { + sw, _ = NewSafeWriter(nil, nil, pc.Project.Lock, VendorAlways) + if sw.HasManifest() { t.Fatal("Did not expect the payload to contain the manifest") } - if !sw.Payload.HasLock() { + if !sw.HasLock() { t.Fatal("Expected the payload to contain the lock") } - if !sw.Payload.HasVendor() { + if !sw.HasVendor() { t.Fatal("Expected the payload to contain the vendor directory ") } - err = sw.Write(pc.Project.AbsRoot, pc.SourceManager) + err = sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -410,26 +395,25 @@ func TestSafeWriter_NewLock(t *testing.T) { defer pc.Release() pc.Load() - var sw SafeWriter lf := h.GetTestFile(safeWriterGoldenLock) defer lf.Close() newLock, err := readLock(lf) h.Must(err) - sw.Prepare(nil, nil, newLock, VendorOnChanged) + sw, _ := NewSafeWriter(nil, nil, newLock, VendorOnChanged) // Verify prepared actions - if sw.Payload.HasManifest() { + if sw.HasManifest() { t.Fatal("Did not expect the payload to contain the manifest") } - if !sw.Payload.HasLock() { + if !sw.HasLock() { t.Fatal("Expected the payload to contain the lock") } - if !sw.Payload.HasVendor() { + if !sw.HasVendor() { t.Fatal("Expected the payload to contain the vendor directory") } // Write changes - err = sw.Write(pc.Project.AbsRoot, pc.SourceManager) + err = sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -455,26 +439,25 @@ func TestSafeWriter_NewLockSkipVendor(t *testing.T) { defer pc.Release() pc.Load() - var sw SafeWriter lf := h.GetTestFile(safeWriterGoldenLock) defer lf.Close() newLock, err := readLock(lf) h.Must(err) - sw.Prepare(nil, nil, newLock, VendorNever) + sw, _ := NewSafeWriter(nil, nil, newLock, VendorNever) // Verify prepared actions - if sw.Payload.HasManifest() { + if sw.HasManifest() { t.Fatal("Did not expect the payload to contain the manifest") } - if !sw.Payload.HasLock() { + if !sw.HasLock() { t.Fatal("Expected the payload to contain the lock") } - if sw.Payload.HasVendor() { + if sw.HasVendor() { t.Fatal("Did not expect the payload to contain the vendor directory") } // Write changes - err = sw.Write(pc.Project.AbsRoot, pc.SourceManager) + err = sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -506,11 +489,10 @@ func TestSafeWriter_DiffLocks(t *testing.T) { updatedLock, err := readLock(ulf) h.Must(err) - var sw SafeWriter - sw.Prepare(nil, pc.Project.Lock, updatedLock, VendorOnChanged) + sw, _ := NewSafeWriter(nil, pc.Project.Lock, updatedLock, VendorOnChanged) // Verify lock diff - diff := sw.Payload.LockDiff + diff := sw.LockDiff if diff == nil { t.Fatal("Expected the payload to contain a diff of the lock files") } @@ -522,3 +504,66 @@ func TestSafeWriter_DiffLocks(t *testing.T) { t.Fatal(err) } } + +func TestHasDotGit(t *testing.T) { + // Create a tempdir with .git file + td, err := ioutil.TempDir(os.TempDir(), "dotGitFile") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(td) + + os.OpenFile(td+string(filepath.Separator)+".git", os.O_CREATE, 0777) + if !hasDotGit(td) { + t.Fatal("Expected hasDotGit to find .git") + } +} + +func TestSafeWriter_VendorDotGitPreservedWithForceVendor(t *testing.T) { + h := test.NewHelper(t) + defer h.Cleanup() + + pc := NewTestProjectContext(h, safeWriterProject) + defer pc.Release() + + gitDirPath := filepath.Join(pc.Project.AbsRoot, "vendor", ".git") + os.MkdirAll(gitDirPath, 0777) + dummyFile := filepath.Join("vendor", ".git", "badinput_fileroot") + pc.CopyFile(dummyFile, "txn_writer/badinput_fileroot") + pc.CopyFile(ManifestName, safeWriterGoldenManifest) + pc.CopyFile(LockName, safeWriterGoldenLock) + pc.Load() + + sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorAlways) + + // Verify prepared actions + if !sw.HasManifest() { + t.Fatal("Expected the payload to contain the manifest") + } + if !sw.HasLock() { + t.Fatal("Expected the payload to contain the lock") + } + if !sw.HasVendor() { + t.Fatal("Expected the payload to contain the vendor directory") + } + + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, false) + h.Must(errors.Wrap(err, "SafeWriter.Write failed")) + + // Verify file system changes + if err := pc.ManifestShouldMatchGolden(safeWriterGoldenManifest); err != nil { + t.Fatal(err) + } + if err := pc.LockShouldMatchGolden(safeWriterGoldenLock); err != nil { + t.Fatal(err) + } + if err := pc.VendorShouldExist(); err != nil { + t.Fatal(err) + } + if err := pc.VendorFileShouldExist("github.com/sdboyer/dep-test"); err != nil { + t.Fatal(err) + } + if err := pc.VendorFileShouldExist(".git/badinput_fileroot"); err != nil { + t.Fatal(err) + } +}