Merge branch 'master' into add-gps

This commit is contained in:
Kris Nova 2017-05-01 20:34:30 -06:00 коммит произвёл GitHub
Родитель 379102bd93 7948c8a848
Коммит 4d5c597e0f
48 изменённых файлов: 876 добавлений и 333 удалений

10
.codeclimate.yml Normal file
Просмотреть файл

@ -0,0 +1,10 @@
engines:
gofmt:
enabled: true
govet:
enabled: true
ratings:
paths:
- "**.go"
exclude_paths:
- vendor/

Просмотреть файл

@ -16,8 +16,13 @@ matrix:
install: install:
- echo "This is an override of the default install deps step in travis." - echo "This is an override of the default install deps step in travis."
before_script: 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/) - PKGS=$(go list ./... | grep -v /vendor/)
- go get -v honnef.co/go/tools/cmd/{gosimple,staticcheck} - go get -v honnef.co/go/tools/cmd/{gosimple,staticcheck}
- npm install -g codeclimate-test-reporter
script: script:
- go build -v ./cmd/dep - go build -v ./cmd/dep
- go vet $PKGS - go vet $PKGS
@ -26,5 +31,10 @@ script:
- ./hack/validate-gofmt.bash - ./hack/validate-gofmt.bash
- ./hack/validate-vendor.bash - ./hack/validate-vendor.bash
- gosimple $PKGS - gosimple $PKGS
- go test -race $PKGS #- go test -race $PKGS
- go build ./hack/licenseok - 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

14
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) * [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? ## 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. * 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? ## 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`? ## Why is it `dep ensure` instead of `dep install`?

2
Gopkg.lock сгенерированный
Просмотреть файл

@ -1,4 +1,4 @@
memo = "099c73630ad2c4f0894ed8646e2e4b5a9f635c85661a77fbf3b9f9dd78c77e87" memo = "71329a18f735441776be73d92d064f26a67fa30e616bbdfbb47e8dc68bda8c5c"
[[projects]] [[projects]]
branch = "2.x" branch = "2.x"

Просмотреть файл

@ -1,3 +1,5 @@
# Temporarily, until gps moves in and this becomes a direct dep again
required = ["github.com/Masterminds/semver"]
[[dependencies]] [[dependencies]]
branch = "2.x" branch = "2.x"

Просмотреть файл

@ -1,6 +1,6 @@
# Dep # 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. Dep is a prototype dependency management tool. It requires Go 1.7 or newer to compile.

Просмотреть файл

@ -23,7 +23,7 @@ func (a Analyzer) DeriveManifestAndLock(path string, n gps.ProjectRoot) (gps.Man
} }
f, err := os.Open(mf) f, err := os.Open(mf)
if err != nil { if err != nil {
return nil, nil, nil return nil, nil, err
} }
defer f.Close() defer f.Close()

Просмотреть файл

@ -5,15 +5,15 @@
package dep package dep
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"testing" "testing"
"github.com/golang/dep/test" "github.com/golang/dep/test"
) )
func TestDeriveManifestAndLock(t *testing.T) { func TestAnalyzerDeriveManifestAndLock(t *testing.T) {
h := test.NewHelper(t) h := test.NewHelper(t)
defer h.Cleanup() defer h.Cleanup()
@ -49,17 +49,75 @@ func TestDeriveManifestAndLock(t *testing.T) {
} }
} }
func TestDeriveManifestAndLockDoesNotExist(t *testing.T) { func TestAnalyzerDeriveManifestAndLockDoesNotExist(t *testing.T) {
dir, err := ioutil.TempDir("", "dep") h := test.NewHelper(t)
if err != nil { defer h.Cleanup()
t.Fatal(err)
} h.TempDir("dep")
defer os.RemoveAll(dir)
a := Analyzer{} 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 { 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) 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)
}
}

Просмотреть файл

@ -9,6 +9,7 @@ import (
"encoding/hex" "encoding/hex"
"flag" "flag"
"fmt" "fmt"
"go/build"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
@ -27,7 +28,7 @@ const ensureLongHelp = `
Ensure is used to fetch project dependencies into the vendor folder, as well as 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, to set version constraints for specific dependencies. It takes user input,
solves the updated dependency graph of the project, writes any changes to the 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: Package spec:
@ -130,6 +131,10 @@ func (cmd *ensureCommand) Run(ctx *dep.Ctx, args []string) error {
return errors.Wrap(err, "ensure ListPackage for project") return errors.Wrap(err, "ensure ListPackage for project")
} }
if err := checkErrors(params.RootPackageTree.Packages); err != nil {
return err
}
if cmd.update { if cmd.update {
applyUpdateArgs(args, &params) applyUpdateArgs(args, &params)
} else { } else {
@ -160,19 +165,16 @@ func (cmd *ensureCommand) Run(ctx *dep.Ctx, args []string) error {
writeV = dep.VendorAlways writeV = dep.VendorAlways
} }
var sw dep.SafeWriter
var manifest *dep.Manifest
if !cmd.update {
manifest = p.Manifest
}
newLock := dep.LockFromInterface(solution) 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 { if cmd.dryRun {
return sw.PrintPreparedActions() 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) { 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? // TODO: if there is amgibuity here, then prompt the user?
return gps.NewVersion(s) 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
}

Просмотреть файл

@ -21,16 +21,21 @@ import (
const initShortHelp = `Initialize a new project with manifest and lock files` const initShortHelp = `Initialize a new project with manifest and lock files`
const initLongHelp = ` const initLongHelp = `
Initialize the project at filepath root by parsing its dependencies and writing Initialize the project at filepath root by parsing its dependencies, writing
manifest and lock files. If root isn't specified, use the current directory. 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 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, a dependency doesn't exist in the GOPATH, a version will be selected from the
but it will be solved-for, and will appear in the lock. 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" } 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) LongHelp() string { return initLongHelp }
func (cmd *initCommand) Hidden() bool { return false } 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 { func trimPathPrefix(p1, p2 string) string {
if internal.HasFilepathPrefix(p1, p2) { if internal.HasFilepathPrefix(p1, p2) {
@ -74,7 +83,7 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error {
return err return err
} }
if mok { if mok {
return errors.Errorf("manifest file %q already exists", mf) return errors.Errorf("manifest already exists: %s", mf)
} }
// Manifest file does not exist. // Manifest file does not exist.
@ -135,38 +144,59 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error {
) )
} }
if len(pd.notondisk) > 0 { // Run solver with project versions found on disk
internal.Vlogf("Solving...") internal.Vlogf("Solving...")
params := gps.SolveParameters{ params := gps.SolveParameters{
RootDir: root, RootDir: root,
RootPackageTree: pkgT, RootPackageTree: pkgT,
Manifest: m, Manifest: m,
Lock: l, Lock: l,
ProjectAnalyzer: dep.Analyzer{}, 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)
} }
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.") internal.Vlogf("Writing manifest and lock files.")
var sw dep.SafeWriter sw, err := dep.NewSafeWriter(m, nil, l, dep.VendorAlways)
sw.Prepare(m, nil, l, dep.VendorAlways) if err != nil {
if err := sw.Write(root, sm); err != nil { return err
}
if err := sw.Write(root, sm, cmd.noExamples); err != nil {
return errors.Wrap(err, "safe write of manifest and lock") 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 // TODO solve failures can be really creative - we need to be similarly creative
// in handling them and informing the user appropriately // in handling them and informing the user appropriately
func handleAllTheFailuresOfTheWorld(err error) { func handleAllTheFailuresOfTheWorld(err error) {
fmt.Printf("solve error: %s\n", err)
} }
func hasImportPathPrefix(s, prefix string) bool { func hasImportPathPrefix(s, prefix string) bool {
@ -210,6 +239,34 @@ func hasImportPathPrefix(s, prefix string) bool {
return strings.HasPrefix(s, prefix+"/") 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 { type projectData struct {
constraints gps.ProjectConstraints // constraints that could be found constraints gps.ProjectConstraints // constraints that could be found
dependencies map[gps.ProjectRoot][]string // all dependencies (imports) found by project root 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 ondisk[pr] = v
pp := gps.ProjectProperties{} constraints[pr] = getProjectPropertiesFromVersion(v)
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
} }
internal.Vlogf("Analyzing transitive imports...") internal.Vlogf("Analyzing transitive imports...")

Просмотреть файл

@ -4,7 +4,12 @@
package main package main
import "testing" import (
"reflect"
"testing"
"github.com/sdboyer/gps"
)
func TestContains(t *testing.T) { func TestContains(t *testing.T) {
a := []string{"a", "b", "abcd"} 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)
}
}
}

Просмотреть файл

@ -19,6 +19,10 @@ func TestIntegration(t *testing.T) {
test.NeedsGit(t) test.NeedsGit(t)
filepath.Walk(filepath.Join("testdata", "harness_tests"), func(path string, info os.FileInfo, err error) error { 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() wd, err := os.Getwd()
if err != nil { if err != nil {
panic(err) panic(err)
@ -51,13 +55,18 @@ func TestIntegration(t *testing.T) {
// Run commands // Run commands
testProj.RecordImportPaths() testProj.RecordImportPaths()
for _, args := range testCase.Commands {
var err error
for i, args := range testCase.Commands {
err = testProj.DoRun(args) err = testProj.DoRun(args)
if err != nil { if err != nil && i < len(testCase.Commands)-1 {
t.Fatalf("%v", err) 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 // Check final manifest and lock
testCase.CompareFile(dep.ManifestName, testProj.ProjPath(dep.ManifestName)) testCase.CompareFile(dep.ManifestName, testProj.ProjPath(dep.ManifestName))
testCase.CompareFile(dep.LockName, testProj.ProjPath(dep.LockName)) testCase.CompareFile(dep.LockName, testProj.ProjPath(dep.LockName))

Просмотреть файл

@ -19,7 +19,7 @@ import (
const removeShortHelp = `Remove a dependency from the project` const removeShortHelp = `Remove a dependency from the project`
const removeLongHelp = ` 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 folder. If the project includes that dependency in its import graph, remove will
fail unless -force is specified. fail unless -force is specified.
` `
@ -180,10 +180,13 @@ func (cmd *removeCommand) Run(ctx *dep.Ctx, args []string) error {
return err return err
} }
var sw dep.SafeWriter
newLock := dep.LockFromInterface(soln) 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 errors.Wrap(err, "grouped write of manifest, lock and vendor")
} }
return nil return nil

14
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 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 `category1/subcategory1/case1`, and could be singled out with the `-run` option
of `go test` (i.e. 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 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 the `testdata` tree. There is no need for code modification - the new test
will be included automatically. will be included automatically.
@ -58,7 +58,8 @@ The `testcase.json` file has the following format:
"github.com/sdboyer/deptestdos", "github.com/sdboyer/deptestdos",
"github.com/sdboyer/deptesttres", "github.com/sdboyer/deptesttres",
"github.com/sdboyer/deptestquatro" "github.com/sdboyer/deptestquatro"
] ],
"error-expected": "something went wrong"
} }
All of the categories are optional - if the `imports` list for a test is empty, 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 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 5. Fetch the repos and versions in `vendor-initial` to the project's `vendor` directory
6. Run `commands` on the project, in declaration order 6. Run `commands` on the project, in declaration order
7. Check the resulting files against those in the `final` input directory 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 `vendor` directory for the projects listed under `vendor-final` 8. Check the resulting files against those in the `final` input directory
9. Check that there were no changes to `src` listings 9. Check the `vendor` directory for the projects listed under `vendor-final`
10. Clean up 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. Note that for the remote fetches, only git repos are currently supported.

2
cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.lock сгенерированный поставляемый
Просмотреть файл

@ -1,4 +1,4 @@
memo = "4b36ae008ef4be09dee7e2ae00606d44fd75f4310fd0d0ef6e744690290569de" memo = "14b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c"
[[projects]] [[projects]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"

Просмотреть файл

@ -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]] # [[dependencies]]
# source = "https://github.com/myfork/package.git" ## Required: the root import path of the project being constrained.
# branch = "master" # name = "github.com/user/project"
# name = "github.com/vendor/package" #
# Note: revision will depend on your repository type, i.e git, svc, bzr etc... ## Recommended: the version constraint to enforce for the project.
# revision = "abc123" ## Only one of "branch", "version" or "revision" can be specified.
# version = "1.0.0" # 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"

Просмотреть файл

@ -1,4 +1,3 @@
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"
version = "^0.8.0" version = "~0.8.0"

Просмотреть файл

@ -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" name = "github.com/sdboyer/deptest"
version = "1.0.0" version = "^1.0.0"

1
cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.lock сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
memo = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7"

Просмотреть файл

Просмотреть файл

@ -0,0 +1,7 @@
{
"commands": [
["init", "-no-examples"],
["ensure", "-update"]
],
"error-expected" : "all dirs lacked any go code"
}

2
cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.lock сгенерированный поставляемый
Просмотреть файл

@ -1,4 +1,4 @@
memo = "" memo = "88d2718cda70cce45158f953d2c6ead79c1db38e67e9704aff72be8fddb096e7"
[[projects]] [[projects]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"

Просмотреть файл

@ -5,4 +5,3 @@
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptestdos" name = "github.com/sdboyer/deptestdos"
revision = "a0196baa11ea047dd65037287451d36b861b00ea"

Просмотреть файл

@ -1,6 +1,6 @@
{ {
"commands": [ "commands": [
["init"] ["init", "-no-examples"]
], ],
"gopath-initial": { "gopath-initial": {
"github.com/sdboyer/deptest": "v0.8.0", "github.com/sdboyer/deptest": "v0.8.0",

2
cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.lock сгенерированный поставляемый
Просмотреть файл

@ -1,4 +1,4 @@
memo = "88d2718cda70cce45158f953d2c6ead79c1db38e67e9704aff72be8fddb096e7" memo = "b4fe6e8bceac924197838b6ea47989abbdd3a8d31035d20ee0a1dabc0994c368"
[[projects]] [[projects]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"

Просмотреть файл

@ -2,3 +2,7 @@
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"
version = ">=0.8.0, <1.0.0" version = ">=0.8.0, <1.0.0"
[[dependencies]]
name = "github.com/sdboyer/deptestdos"
version = "^2.0.0"

Просмотреть файл

@ -1,6 +1,6 @@
{ {
"commands": [ "commands": [
["init"] ["init", "-no-examples"]
], ],
"gopath-initial": { "gopath-initial": {
"github.com/sdboyer/deptest": "v0.8.0" "github.com/sdboyer/deptest": "v0.8.0"

2
cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.lock сгенерированный поставляемый
Просмотреть файл

@ -1,4 +1,4 @@
memo = "" memo = "af9a783a5430dabcaaf44683c09e2b729e1c0d61f13bfdf6677c4fd0b41387ca"
[[projects]] [[projects]]
branch = "master" branch = "master"

Просмотреть файл

@ -5,4 +5,3 @@
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptestdos" name = "github.com/sdboyer/deptestdos"
revision = "a0196baa11ea047dd65037287451d36b861b00ea"

Просмотреть файл

@ -1,6 +1,6 @@
{ {
"commands": [ "commands": [
["init"] ["init", "-no-examples"]
], ],
"gopath-initial": { "gopath-initial": {
"github.com/sdboyer/deptestdos": "a0196baa11ea047dd65037287451d36b861b00ea" "github.com/sdboyer/deptestdos": "a0196baa11ea047dd65037287451d36b861b00ea"

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -0,0 +1,6 @@
{
"commands": [
["init"]
],
"error-expected" : "manifest already exists:"
}

2
cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.lock сгенерированный поставляемый
Просмотреть файл

@ -1,4 +1,4 @@
memo = "4b36ae008ef4be09dee7e2ae00606d44fd75f4310fd0d0ef6e744690290569de" memo = "14b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c"
[[projects]] [[projects]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"

Просмотреть файл

@ -1,9 +1,4 @@
# Example: [[dependencies]]
# [[dependencies]] name = "github.com/sdboyer/deptest"
# source = "https://github.com/myfork/package.git" version = "^1.0.0"
# 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"

Просмотреть файл

@ -1,6 +1,6 @@
{ {
"commands": [ "commands": [
["init"] ["init", "-no-examples"]
], ],
"vendor-final": [ "vendor-final": [
"github.com/sdboyer/deptest" "github.com/sdboyer/deptest"

Просмотреть файл

@ -1,4 +1,11 @@
[[dependencies]]
name = "github.com/not/used"
version = "2.0.0"
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"
version = ">=0.8.0, <1.0.0" version = ">=0.8.0, <1.0.0"
[[dependencies]]
name = "github.com/sdboyer/deptestdos"
revision = "a0196baa11ea047dd65037287451d36b861b00ea"

Просмотреть файл

@ -1,8 +1,11 @@
[[dependencies]]
name = "github.com/not/used"
version = "2.0.0"
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"
version = ">=0.8.0, <1.0.0" version = ">=0.8.0, <1.0.0"
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptestdos" name = "github.com/sdboyer/deptestdos"
revision = "a0196baa11ea047dd65037287451d36b861b00ea" revision = "a0196baa11ea047dd65037287451d36b861b00ea"

Просмотреть файл

@ -1,3 +1,6 @@
[[dependencies]]
name = "github.com/not/used"
version = "2.0.0"
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"
@ -5,4 +8,4 @@
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptestdos" name = "github.com/sdboyer/deptestdos"
revision = "a0196baa11ea047dd65037287451d36b861b00ea" revision = "a0196baa11ea047dd65037287451d36b861b00ea"

Просмотреть файл

@ -1,3 +1,6 @@
[[dependencies]]
name = "github.com/not/used"
version = "2.0.0"
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"
@ -5,4 +8,4 @@
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptestdos" name = "github.com/sdboyer/deptestdos"
revision = "a0196baa11ea047dd65037287451d36b861b00ea" revision = "a0196baa11ea047dd65037287451d36b861b00ea"

Просмотреть файл

@ -1,4 +1,3 @@
[[dependencies]] [[dependencies]]
name = "github.com/sdboyer/deptest" name = "github.com/sdboyer/deptest"
version = ">=0.8.0, <1.0.0" version = "^0.8.0"

Просмотреть файл

@ -352,22 +352,15 @@ func TestResolveProjectRoot(t *testing.T) {
tg := test.NewHelper(t) tg := test.NewHelper(t)
defer tg.Cleanup() 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/real/path")
tg.TempDir("go/src/sym") tg.TempDir("go/src/sym")
tg.TempDir("gotwo") // Another directory used as a GOPATH // Another directory used as a GOPATH
tg.TempDir("gotwo/src")
tg.TempDir("gotwo/src/real")
tg.TempDir("gotwo/src/real/path") tg.TempDir("gotwo/src/real/path")
tg.TempDir("gotwo/src/sym") tg.TempDir("gotwo/src/sym")
tg.TempDir("sym") // Directory for symlinks tg.TempDir("sym") // Directory for symlinks
tg.Setenv("GOPATH", tg.Path(filepath.Join(".", "go")))
ctx := &Ctx{ ctx := &Ctx{
GOPATH: tg.Path(filepath.Join(".", "go")), GOPATH: tg.Path(filepath.Join(".", "go")),
GOPATHS: []string{ GOPATHS: []string{
@ -376,45 +369,65 @@ func TestResolveProjectRoot(t *testing.T) {
}, },
} }
realPath := filepath.Join(ctx.GOPATH, "src", "real", "path") testcases := []struct {
realPathTwo := filepath.Join(ctx.GOPATHS[1], "src", "real", "path") name string
symlinkedPath := filepath.Join(tg.Path("."), "sym", "symlink") path string
symlinkedInGoPath := filepath.Join(ctx.GOPATH, "src/sym/path") resolvedPath string
symlinkedInOtherGoPath := filepath.Join(tg.Path("."), "sym", "symtwo") symlink bool
os.Symlink(realPath, symlinkedPath) expectErr bool
os.Symlink(realPath, symlinkedInGoPath) }{
os.Symlink(realPathTwo, symlinkedInOtherGoPath) {
name: "no-symlinks",
// Real path should be returned, no symlinks to deal with path: filepath.Join(ctx.GOPATH, "src/real/path"),
p, err := ctx.resolveProjectRoot(realPath) resolvedPath: filepath.Join(ctx.GOPATH, "src/real/path"),
if err != nil { },
t.Fatalf("Error resolving project root: %s", err) {
} name: "symlink-outside-gopath",
if p != realPath { path: filepath.Join(tg.Path("."), "sym/symlink"),
t.Fatalf("Want path to be %s, got %s", realPath, p) 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 for _, tc := range testcases {
p, err = ctx.resolveProjectRoot(symlinkedPath) t.Run(tc.name, func(t *testing.T) {
if err != nil { if tc.symlink {
t.Fatalf("Error resolving project root: %s", err) if err := os.Symlink(tc.resolvedPath, tc.path); err != nil {
} if runtime.GOOS == "windows" {
if p != realPath { t.Skipf("Not testing Windows symlinks because: %s", err)
t.Fatalf("Want path to be %s, got %s", realPath, p) } else {
} t.Fatal(err)
}
}
}
// Real path should be returned, symlink is in another GOPATH p, err := ctx.resolveProjectRoot(tc.path)
p, err = ctx.resolveProjectRoot(symlinkedInOtherGoPath) if err != nil {
if err != nil { if !tc.expectErr {
t.Fatalf("Error resolving project root: %s", err) t.Fatalf("Error resolving project root: %s", err)
} }
if p != realPathTwo { return
t.Fatalf("Want path to be %s, got %s", realPathTwo, p) }
} if err == nil && tc.expectErr {
t.Fatal("Wanted an error")
}
// Symlinked path is inside GOPATH, should return error if p != tc.resolvedPath {
_, err = ctx.resolveProjectRoot(symlinkedInGoPath) t.Errorf("Want path to be %s, got %s", tc.resolvedPath, p)
if err == nil { }
t.Fatalf("Wanted an error") })
} }
} }

Просмотреть файл

@ -190,6 +190,8 @@ func TestIsEmpty(t *testing.T) {
} }
h := test.NewHelper(t) h := test.NewHelper(t)
defer h.Cleanup()
h.TempDir("empty") h.TempDir("empty")
tests := map[string]string{ tests := map[string]string{
wd: "true", wd: "true",

Просмотреть файл

@ -12,6 +12,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"testing" "testing"
) )
@ -27,6 +28,7 @@ type IntegrationTestCase struct {
initialPath string initialPath string
finalPath string finalPath string
Commands [][]string `json:"commands"` Commands [][]string `json:"commands"`
ErrorExpected string `json:"error-expected"`
GopathInitial map[string]string `json:"gopath-initial"` GopathInitial map[string]string `json:"gopath-initial"`
VendorInitial map[string]string `json:"vendor-initial"` VendorInitial map[string]string `json:"vendor-initial"`
VendorFinal []string `json:"vendor-final"` 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) { func (tc *IntegrationTestCase) CompareVendorPaths(gotVendorPaths []string) {
if *UpdateGolden { if *UpdateGolden {
tc.VendorFinal = gotVendorPaths tc.VendorFinal = gotVendorPaths

Просмотреть файл

@ -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) { func (p *IntegrationTestProject) GetVendorGit(ip string) {
parse := strings.Split(ip, "/") parse := strings.Split(ip, "/")
gitDir := strings.Join(parse[:len(parse)-1], string(filepath.Separator)) gitDir := strings.Join(parse[:len(parse)-1], string(filepath.Separator))

52
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]] [[dependencies]]
name = "github.com/sdboyer/dep-test" name = "github.com/sdboyer/dep-test"
version = "1.0.0" version = "1.0.0"

Просмотреть файл

@ -21,15 +21,58 @@ import (
// Example string to be written to the manifest file // Example string to be written to the manifest file
// if no dependencies are found in the project // if no dependencies are found in the project
// during `dep init` // during `dep init`
const exampleToml = ` const exampleTOML = `
# 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]] # [[dependencies]]
# source = "https://github.com/myfork/package.git" ## Required: the root import path of the project being constrained.
# branch = "master" # name = "github.com/user/project"
# name = "github.com/vendor/package" #
# Note: revision will depend on your repository type, i.e git, svc, bzr etc... ## Recommended: the version constraint to enforce for the project.
# revision = "abc123" ## Only one of "branch", "version" or "revision" can be specified.
# version = "1.0.0" # 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 // 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 // It is not impervious to errors (writing to disk is hard), but it should
// guard against non-arcane failure conditions. // guard against non-arcane failure conditions.
type SafeWriter struct { type SafeWriter struct {
Payload *SafeWriterPayload
}
// SafeWriterPayload represents the actions SafeWriter will execute when SafeWriter.Write is called.
type SafeWriterPayload struct {
Manifest *Manifest Manifest *Manifest
Lock *Lock Lock *Lock
LockDiff *gps.LockDiff LockDiff *gps.LockDiff
WriteVendor bool WriteVendor bool
} }
func (payload *SafeWriterPayload) HasLock() bool { // NewSafeWriter sets up a SafeWriter to write a set of config yaml, lock and vendor tree.
return payload.Lock != nil //
// - 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 { // HasLock checks if a Lock is present in the SafeWriter
return payload.Manifest != nil func (sw *SafeWriter) HasLock() bool {
return sw.Lock != nil
} }
func (payload *SafeWriterPayload) HasVendor() bool { // HasManifest checks if a Manifest is present in the SafeWriter
return payload.WriteVendor 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 { type rawStringDiff struct {
@ -178,47 +257,7 @@ const (
VendorNever VendorNever
) )
// Prepare to write a set of config yaml, lock and vendor tree. func (sw SafeWriter) validate(root string, sm gps.SourceManager) error {
//
// - 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 {
if root == "" { if root == "" {
return errors.New("root path must be non-empty") 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) 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") 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. // 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 // This mostly guarantees that dep cannot exit with a partial write that would
// leave an undefined state on disk. // leave an undefined state on disk.
func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error { func (sw *SafeWriter) Write(root string, sm gps.SourceManager, noExamples bool) error {
err := sw.validate(root, sm)
if sw.Payload == nil {
return errors.New("Cannot call SafeWriter.Write before SafeWriter.Prepare")
}
err := sw.Payload.validate(root, sm)
if err != nil { if err != nil {
return err return err
} }
if !sw.Payload.HasManifest() && !sw.Payload.HasLock() && !sw.Payload.HasVendor() { if !sw.HasManifest() && !sw.HasLock() && !sw.HasVendor() {
// nothing to do // nothing to do
return nil return nil
} }
@ -270,30 +304,48 @@ func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error {
} }
defer os.RemoveAll(td) defer os.RemoveAll(td)
if sw.Payload.HasManifest() { if sw.HasManifest() {
if sw.Payload.Manifest.IsEmpty() { // Always write the example text to the bottom of the TOML file.
err := modifyWithString(filepath.Join(td, ManifestName), exampleToml) tb, err := sw.Manifest.MarshalTOML()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to generate example text") return errors.Wrap(err, "failed to marshal manifest to TOML")
} }
} else if err := writeFile(filepath.Join(td, ManifestName), sw.Payload.Manifest); err != nil {
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") return errors.Wrap(err, "failed to write manifest file to temp dir")
} }
} }
if sw.Payload.HasLock() { if sw.HasLock() {
if err := writeFile(filepath.Join(td, LockName), sw.Payload.Lock); err != nil { if err := writeFile(filepath.Join(td, LockName), sw.Lock); err != nil {
return errors.Wrap(err, "failed to write lock file to temp dir") return errors.Wrap(err, "failed to write lock file to temp dir")
} }
} }
if sw.Payload.HasVendor() { if sw.HasVendor() {
err = gps.WriteDepTree(filepath.Join(td, "vendor"), sw.Payload.Lock, sm, true) err = gps.WriteDepTree(filepath.Join(td, "vendor"), sw.Lock, sm, true)
if err != nil { if err != nil {
return errors.Wrap(err, "error while writing out vendor tree") 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 // 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. // ones in, to provide insurance against errors for as long as possible.
type pathpair struct { type pathpair struct {
@ -303,7 +355,7 @@ func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error {
var failerr error var failerr error
var vendorbak string var vendorbak string
if sw.Payload.HasManifest() { if sw.HasManifest() {
if _, err := os.Stat(mpath); err == nil { if _, err := os.Stat(mpath); err == nil {
// Move out the old one. // Move out the old one.
tmploc := filepath.Join(td, ManifestName+".orig") 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 { if _, err := os.Stat(lpath); err == nil {
// Move out the old one. // Move out the old one.
tmploc := filepath.Join(td, LockName+".orig") 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 { if _, err := os.Stat(vpath); err == nil {
// Move out the old vendor dir. just do it into an adjacent dir, to // Move out the old vendor dir. just do it into an adjacent dir, to
// try to mitigate the possibility of a pointless cross-filesystem // 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 // 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 // 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 // Nothing we can really do about an error at this point, so ignore it
os.RemoveAll(vendorbak) os.RemoveAll(vendorbak)
} }
@ -385,26 +437,26 @@ fail:
} }
func (sw *SafeWriter) PrintPreparedActions() error { func (sw *SafeWriter) PrintPreparedActions() error {
if sw.Payload.HasManifest() { if sw.HasManifest() {
fmt.Printf("Would have written the following %s:\n", ManifestName) fmt.Printf("Would have written the following %s:\n", ManifestName)
m, err := sw.Payload.Manifest.MarshalTOML() m, err := sw.Manifest.MarshalTOML()
if err != nil { if err != nil {
return errors.Wrap(err, "ensure DryRun cannot serialize manifest") return errors.Wrap(err, "ensure DryRun cannot serialize manifest")
} }
fmt.Println(string(m)) fmt.Println(string(m))
} }
if sw.Payload.HasLock() { if sw.HasLock() {
if sw.Payload.LockDiff == nil { if sw.LockDiff == nil {
fmt.Printf("Would have written the following %s:\n", LockName) fmt.Printf("Would have written the following %s:\n", LockName)
l, err := sw.Payload.Lock.MarshalTOML() l, err := sw.Lock.MarshalTOML()
if err != nil { if err != nil {
return errors.Wrap(err, "ensure DryRun cannot serialize lock") return errors.Wrap(err, "ensure DryRun cannot serialize lock")
} }
fmt.Println(string(l)) fmt.Println(string(l))
} else { } else {
fmt.Printf("Would have written the following changes to %s:\n", LockName) 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 { if err != nil {
return errors.Wrap(err, "ensure DryRun cannot serialize the lock diff") 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:") 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() prj := project.Ident()
rev, _, _ := gps.VersionComponentStrings(project.Version()) rev, _, _ := gps.VersionComponentStrings(project.Version())
if prj.Source == "" { if prj.Source == "" {
@ -526,6 +578,13 @@ func deleteDirs(toDelete []string) error {
return nil 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 type byLen []string
func (a byLen) Len() int { return len(a) } func (a byLen) Len() int { return len(a) }

Просмотреть файл

@ -5,6 +5,8 @@
package dep package dep
import ( import (
"io/ioutil"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@ -23,10 +25,8 @@ func TestSafeWriter_BadInput_MissingRoot(t *testing.T) {
pc := NewTestProjectContext(h, safeWriterProject) pc := NewTestProjectContext(h, safeWriterProject)
defer pc.Release() defer pc.Release()
var sw SafeWriter sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged)
sw.Prepare(nil, nil, nil, VendorOnChanged) err := sw.Write("", pc.SourceManager, false)
err := sw.Write("", pc.SourceManager)
if err == nil { if err == nil {
t.Fatal("should have errored without a root path, but did not") 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.CopyFile(LockName, safeWriterGoldenLock)
pc.Load() pc.Load()
var sw SafeWriter sw, _ := NewSafeWriter(nil, nil, pc.Project.Lock, VendorAlways)
sw.Prepare(nil, nil, pc.Project.Lock, VendorAlways) err := sw.Write(pc.Project.AbsRoot, nil, false)
err := sw.Write(pc.Project.AbsRoot, nil)
if err == nil { if err == nil {
t.Fatal("should have errored without a source manager when forceVendor is true, but did not") 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) pc := NewTestProjectContext(h, safeWriterProject)
defer pc.Release() defer pc.Release()
var sw SafeWriter _, err := NewSafeWriter(nil, nil, nil, VendorAlways)
err := sw.Prepare(nil, nil, nil, VendorAlways)
if err == nil { if err == nil {
t.Fatal("should have errored without a lock when forceVendor is true, but did not") t.Fatal("should have errored without a lock when forceVendor is true, but did not")
} else if !strings.Contains(err.Error(), "newLock") { } else if !strings.Contains(err.Error(), "newLock") {
@ -79,9 +75,7 @@ func TestSafeWriter_BadInput_OldLockOnly(t *testing.T) {
pc.CopyFile(LockName, safeWriterGoldenLock) pc.CopyFile(LockName, safeWriterGoldenLock)
pc.Load() pc.Load()
var sw SafeWriter _, err := NewSafeWriter(nil, pc.Project.Lock, nil, VendorAlways)
err := sw.Prepare(nil, pc.Project.Lock, nil, VendorAlways)
if err == nil { if err == nil {
t.Fatal("should have errored with only an old lock, but did not") t.Fatal("should have errored with only an old lock, but did not")
} else if !strings.Contains(err.Error(), "oldLock") { } else if !strings.Contains(err.Error(), "oldLock") {
@ -95,11 +89,10 @@ func TestSafeWriter_BadInput_NonexistentRoot(t *testing.T) {
pc := NewTestProjectContext(h, safeWriterProject) pc := NewTestProjectContext(h, safeWriterProject)
defer pc.Release() defer pc.Release()
var sw SafeWriter sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged)
sw.Prepare(nil, nil, nil, VendorOnChanged)
missingroot := filepath.Join(pc.Project.AbsRoot, "nonexistent") missingroot := filepath.Join(pc.Project.AbsRoot, "nonexistent")
err := sw.Write(missingroot, pc.SourceManager) err := sw.Write(missingroot, pc.SourceManager, false)
if err == nil { if err == nil {
t.Fatal("should have errored with nonexistent dir for root path, but did not") 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) pc := NewTestProjectContext(h, safeWriterProject)
defer pc.Release() defer pc.Release()
var sw SafeWriter sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged)
sw.Prepare(nil, nil, nil, VendorOnChanged)
fileroot := pc.CopyFile("fileroot", "txn_writer/badinput_fileroot") fileroot := pc.CopyFile("fileroot", "txn_writer/badinput_fileroot")
err := sw.Write(fileroot, pc.SourceManager) err := sw.Write(fileroot, pc.SourceManager, false)
if err == nil { if err == nil {
t.Fatal("should have errored when root path is a file, but did not") 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.CopyFile(ManifestName, safeWriterGoldenManifest)
pc.Load() pc.Load()
var sw SafeWriter sw, _ := NewSafeWriter(pc.Project.Manifest, nil, nil, VendorOnChanged)
sw.Prepare(pc.Project.Manifest, nil, nil, VendorOnChanged)
// Verify prepared actions // Verify prepared actions
if !sw.Payload.HasManifest() { if !sw.HasManifest() {
t.Fatal("Expected the payload to contain the manifest") 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") 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") t.Fatal("Did not expect the payload to contain the vendor directory")
} }
// Write changes // 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")) h.Must(errors.Wrap(err, "SafeWriter.Write failed"))
// Verify file system changes // Verify file system changes
@ -182,22 +173,21 @@ func TestSafeWriter_ManifestAndUnmodifiedLock(t *testing.T) {
pc.CopyFile(LockName, safeWriterGoldenLock) pc.CopyFile(LockName, safeWriterGoldenLock)
pc.Load() pc.Load()
var sw SafeWriter sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorOnChanged)
sw.Prepare(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorOnChanged)
// Verify prepared actions // Verify prepared actions
if !sw.Payload.HasManifest() { if !sw.HasManifest() {
t.Fatal("Expected the payload to contain the manifest") 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.") 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") t.Fatal("Did not expect the payload to contain the vendor directory")
} }
// Write changes // 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")) h.Must(errors.Wrap(err, "SafeWriter.Write failed"))
// Verify file system changes // Verify file system changes
@ -225,22 +215,21 @@ func TestSafeWriter_ManifestAndUnmodifiedLockWithForceVendor(t *testing.T) {
pc.CopyFile(LockName, safeWriterGoldenLock) pc.CopyFile(LockName, safeWriterGoldenLock)
pc.Load() pc.Load()
var sw SafeWriter sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorAlways)
sw.Prepare(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorAlways)
// Verify prepared actions // Verify prepared actions
if !sw.Payload.HasManifest() { if !sw.HasManifest() {
t.Fatal("Expected the payload to contain the manifest") 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") 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") t.Fatal("Expected the payload to contain the vendor directory")
} }
// Write changes // 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")) h.Must(errors.Wrap(err, "SafeWriter.Write failed"))
// Verify file system changes // Verify file system changes
@ -270,25 +259,24 @@ func TestSafeWriter_ModifiedLock(t *testing.T) {
pc.CopyFile(LockName, safeWriterGoldenLock) pc.CopyFile(LockName, safeWriterGoldenLock)
pc.Load() pc.Load()
var sw SafeWriter
originalLock := new(Lock) originalLock := new(Lock)
*originalLock = *pc.Project.Lock *originalLock = *pc.Project.Lock
originalLock.Memo = []byte{} // zero out the input hash to ensure non-equivalency 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 // Verify prepared actions
if sw.Payload.HasManifest() { if sw.HasManifest() {
t.Fatal("Did not expect the payload to contain the manifest") 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") 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") t.Fatal("Expected the payload to contain the vendor directory")
} }
// Write changes // 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")) h.Must(errors.Wrap(err, "SafeWriter.Write failed"))
// Verify file system changes // Verify file system changes
@ -318,25 +306,24 @@ func TestSafeWriter_ModifiedLockSkipVendor(t *testing.T) {
pc.CopyFile(LockName, safeWriterGoldenLock) pc.CopyFile(LockName, safeWriterGoldenLock)
pc.Load() pc.Load()
var sw SafeWriter
originalLock := new(Lock) originalLock := new(Lock)
*originalLock = *pc.Project.Lock *originalLock = *pc.Project.Lock
originalLock.Memo = []byte{} // zero out the input hash to ensure non-equivalency 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 // Verify prepared actions
if sw.Payload.HasManifest() { if sw.HasManifest() {
t.Fatal("Did not expect the payload to contain the manifest") 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") 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") t.Fatal("Did not expect the payload to contain the vendor directory")
} }
// Write changes // 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")) h.Must(errors.Wrap(err, "SafeWriter.Write failed"))
// Verify file system changes // Verify file system changes
@ -363,25 +350,23 @@ func TestSafeWriter_ForceVendorWhenVendorAlreadyExists(t *testing.T) {
pc.CopyFile(LockName, safeWriterGoldenLock) pc.CopyFile(LockName, safeWriterGoldenLock)
pc.Load() pc.Load()
var sw SafeWriter sw, _ := NewSafeWriter(nil, pc.Project.Lock, pc.Project.Lock, VendorAlways)
// Populate vendor err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, false)
sw.Prepare(nil, pc.Project.Lock, pc.Project.Lock, VendorAlways)
err := sw.Write(pc.Project.AbsRoot, pc.SourceManager)
h.Must(errors.Wrap(err, "SafeWriter.Write failed")) h.Must(errors.Wrap(err, "SafeWriter.Write failed"))
// Verify prepared actions // Verify prepared actions
sw.Prepare(nil, nil, pc.Project.Lock, VendorAlways) sw, _ = NewSafeWriter(nil, nil, pc.Project.Lock, VendorAlways)
if sw.Payload.HasManifest() { if sw.HasManifest() {
t.Fatal("Did not expect the payload to contain the manifest") 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") 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 ") 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")) h.Must(errors.Wrap(err, "SafeWriter.Write failed"))
// Verify file system changes // Verify file system changes
@ -410,26 +395,25 @@ func TestSafeWriter_NewLock(t *testing.T) {
defer pc.Release() defer pc.Release()
pc.Load() pc.Load()
var sw SafeWriter
lf := h.GetTestFile(safeWriterGoldenLock) lf := h.GetTestFile(safeWriterGoldenLock)
defer lf.Close() defer lf.Close()
newLock, err := readLock(lf) newLock, err := readLock(lf)
h.Must(err) h.Must(err)
sw.Prepare(nil, nil, newLock, VendorOnChanged) sw, _ := NewSafeWriter(nil, nil, newLock, VendorOnChanged)
// Verify prepared actions // Verify prepared actions
if sw.Payload.HasManifest() { if sw.HasManifest() {
t.Fatal("Did not expect the payload to contain the manifest") 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") 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") t.Fatal("Expected the payload to contain the vendor directory")
} }
// Write changes // 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")) h.Must(errors.Wrap(err, "SafeWriter.Write failed"))
// Verify file system changes // Verify file system changes
@ -455,26 +439,25 @@ func TestSafeWriter_NewLockSkipVendor(t *testing.T) {
defer pc.Release() defer pc.Release()
pc.Load() pc.Load()
var sw SafeWriter
lf := h.GetTestFile(safeWriterGoldenLock) lf := h.GetTestFile(safeWriterGoldenLock)
defer lf.Close() defer lf.Close()
newLock, err := readLock(lf) newLock, err := readLock(lf)
h.Must(err) h.Must(err)
sw.Prepare(nil, nil, newLock, VendorNever) sw, _ := NewSafeWriter(nil, nil, newLock, VendorNever)
// Verify prepared actions // Verify prepared actions
if sw.Payload.HasManifest() { if sw.HasManifest() {
t.Fatal("Did not expect the payload to contain the manifest") 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") 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") t.Fatal("Did not expect the payload to contain the vendor directory")
} }
// Write changes // 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")) h.Must(errors.Wrap(err, "SafeWriter.Write failed"))
// Verify file system changes // Verify file system changes
@ -506,11 +489,10 @@ func TestSafeWriter_DiffLocks(t *testing.T) {
updatedLock, err := readLock(ulf) updatedLock, err := readLock(ulf)
h.Must(err) h.Must(err)
var sw SafeWriter sw, _ := NewSafeWriter(nil, pc.Project.Lock, updatedLock, VendorOnChanged)
sw.Prepare(nil, pc.Project.Lock, updatedLock, VendorOnChanged)
// Verify lock diff // Verify lock diff
diff := sw.Payload.LockDiff diff := sw.LockDiff
if diff == nil { if diff == nil {
t.Fatal("Expected the payload to contain a diff of the lock files") 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) 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)
}
}