blob: cfd5de52210fdaa9e6cc544fdd5b18b5183279c1 [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main_test
import (
"bytes"
"internal/testenv"
"io/ioutil"
"os"
"path/filepath"
"sort"
"testing"
"cmd/go/internal/cfg"
"cmd/go/internal/modconv"
"cmd/go/internal/modload"
"cmd/go/internal/txtar"
)
var cmdGoDir, _ = os.Getwd()
// testGoModules returns a testgoData set up for running
// tests of Go modules. It:
//
// - sets $GO111MODULE=on
// - sets $GOPROXY to the URL of a module proxy serving from ./testdata/mod
// - creates a new temp directory with subdirectories home, gopath, and work
// - sets $GOPATH to the new temp gopath directory
// - sets $HOME to the new temp home directory
// - writes work/go.mod containing "module m"
// - chdirs to the the new temp work directory
//
// The caller must defer tg.cleanup().
//
func testGoModules(t *testing.T) *testgoData {
tg := testgo(t)
tg.setenv("GO111MODULE", "on")
StartProxy()
tg.setenv("GOPROXY", proxyURL)
tg.makeTempdir()
tg.setenv(homeEnvName(), tg.path("home")) // for build cache
tg.setenv("GOPATH", tg.path("gopath")) // for download cache
tg.tempFile("work/go.mod", "module m")
tg.cd(tg.path("work"))
return tg
}
// extract clears the temp work directory and then
// extracts the txtar archive named by file into that directory.
// The file name is interpreted relative to the cmd/go directory,
// so it usually begins with "testdata/".
func (tg *testgoData) extract(file string) {
a, err := txtar.ParseFile(filepath.Join(cmdGoDir, file))
if err != nil {
tg.t.Fatal(err)
}
tg.cd(tg.path("."))
tg.must(removeAll(tg.path("work")))
tg.must(os.MkdirAll(tg.path("work"), 0777))
tg.cd(tg.path("work"))
for _, f := range a.Files {
tg.tempFile(filepath.Join("work", f.Name), string(f.Data))
}
}
func TestModFindModuleRoot(t *testing.T) {
tg := testGoModules(t)
defer tg.cleanup()
tg.must(os.MkdirAll(tg.path("x/Godeps"), 0777))
tg.must(os.MkdirAll(tg.path("x/vendor"), 0777))
tg.must(os.MkdirAll(tg.path("x/y/z"), 0777))
tg.must(os.MkdirAll(tg.path("x/.git"), 0777))
var files []string
for file := range modconv.Converters {
files = append(files, file)
}
files = append(files, "go.mod")
files = append(files, ".git/config")
sort.Strings(files)
for file := range modconv.Converters {
tg.must(ioutil.WriteFile(tg.path("x/"+file), []byte{}, 0666))
root, file1 := modload.FindModuleRoot(tg.path("x/y/z"), tg.path("."), true)
if root != tg.path("x") || file1 != file {
t.Errorf("%s: findModuleRoot = %q, %q, want %q, %q", file, root, file1, tg.path("x"), file)
}
tg.must(os.Remove(tg.path("x/" + file)))
}
}
func TestModFindModulePath(t *testing.T) {
tg := testGoModules(t)
defer tg.cleanup()
tg.must(os.MkdirAll(tg.path("x"), 0777))
tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte("package x // import \"x\"\n"), 0666))
path, err := modload.FindModulePath(tg.path("x"))
if err != nil {
t.Fatal(err)
}
if path != "x" {
t.Fatalf("FindModulePath = %q, want %q", path, "x")
}
// Windows line-ending.
tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte("package x // import \"x\"\r\n"), 0666))
path, err = modload.FindModulePath(tg.path("x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath = %q, %v, want %q, nil", path, err, "x")
}
// Explicit setting in Godeps.json takes priority over implicit setting from GOPATH location.
tg.tempFile("gp/src/example.com/x/y/z/z.go", "package z")
gopath := cfg.BuildContext.GOPATH
defer func() {
cfg.BuildContext.GOPATH = gopath
}()
cfg.BuildContext.GOPATH = tg.path("gp")
path, err = modload.FindModulePath(tg.path("gp/src/example.com/x/y/z"))
if path != "example.com/x/y/z" || err != nil {
t.Fatalf("FindModulePath = %q, %v, want %q, nil", path, err, "example.com/x/y/z")
}
tg.tempFile("gp/src/example.com/x/y/z/Godeps/Godeps.json", `
{"ImportPath": "unexpected.com/z"}
`)
path, err = modload.FindModulePath(tg.path("gp/src/example.com/x/y/z"))
if path != "unexpected.com/z" || err != nil {
t.Fatalf("FindModulePath = %q, %v, want %q, nil", path, err, "unexpected.com/z")
}
// Empty dir outside GOPATH
tg.must(os.MkdirAll(tg.path("gp1"), 0777))
tg.must(os.MkdirAll(tg.path("x1"), 0777))
cfg.BuildContext.GOPATH = tg.path("gp1")
path, err = modload.FindModulePath(tg.path("x1"))
if path != "" || err == nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, %q", path, err, "", "cannot determine module path for source directory")
}
// Empty dir inside GOPATH
tg.must(os.MkdirAll(tg.path("gp2/src/x"), 0777))
cfg.BuildContext.GOPATH = tg.path("gp2")
path, err = modload.FindModulePath(tg.path("gp2/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
if !testenv.HasSymlink() {
t.Logf("skipping symlink tests")
return
}
// Empty dir inside GOPATH, dir has symlink
// GOPATH = gp
// gplink -> gp
tg.must(os.MkdirAll(tg.path("gp3/src/x"), 0777))
tg.must(os.Symlink(tg.path("gp3"), tg.path("gplink3")))
cfg.BuildContext.GOPATH = tg.path("gp3")
path, err = modload.FindModulePath(tg.path("gplink3/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
path, err = modload.FindModulePath(tg.path("gp3/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
// Empty dir inside GOPATH, dir has symlink 2
// GOPATH = gp
// gp/src/x -> x/x
tg.must(os.MkdirAll(tg.path("gp4/src"), 0777))
tg.must(os.MkdirAll(tg.path("x4/x"), 0777))
tg.must(os.Symlink(tg.path("x4/x"), tg.path("gp4/src/x")))
cfg.BuildContext.GOPATH = tg.path("gp4")
path, err = modload.FindModulePath(tg.path("gp4/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
// Empty dir inside GOPATH, GOPATH has symlink
// GOPATH = gplink
// gplink -> gp
tg.must(os.MkdirAll(tg.path("gp5/src/x"), 0777))
tg.must(os.Symlink(tg.path("gp5"), tg.path("gplink5")))
cfg.BuildContext.GOPATH = tg.path("gplink5")
path, err = modload.FindModulePath(tg.path("gplink5/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
path, err = modload.FindModulePath(tg.path("gp5/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
// Empty dir inside GOPATH, GOPATH has symlink, dir has symlink 2
// GOPATH = gplink
// gplink -> gp
// gplink2 -> gp
tg.must(os.MkdirAll(tg.path("gp6/src/x"), 0777))
tg.must(os.Symlink(tg.path("gp6"), tg.path("gplink6")))
tg.must(os.Symlink(tg.path("gp6"), tg.path("gplink62")))
cfg.BuildContext.GOPATH = tg.path("gplink6")
path, err = modload.FindModulePath(tg.path("gplink62/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
path, err = modload.FindModulePath(tg.path("gplink6/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
path, err = modload.FindModulePath(tg.path("gp6/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
// Empty dir inside GOPATH, GOPATH has symlink, dir has symlink 3
// GOPATH = gplink
// gplink -> gp
// gplink2 -> gp
// gp/src/x -> x/x
tg.must(os.MkdirAll(tg.path("gp7/src"), 0777))
tg.must(os.MkdirAll(tg.path("x7/x"), 0777))
tg.must(os.Symlink(tg.path("gp7"), tg.path("gplink7")))
tg.must(os.Symlink(tg.path("gp7"), tg.path("gplink72")))
tg.must(os.Symlink(tg.path("x7/x"), tg.path("gp7/src/x")))
cfg.BuildContext.GOPATH = tg.path("gplink7")
path, err = modload.FindModulePath(tg.path("gplink7/src/x"))
if path != "x" || err != nil {
t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
}
// This test fails when /tmp -> /private/tmp.
// path, err = modload.FindModulePath(tg.path("gp7/src/x"))
// if path != "x" || err != nil {
// t.Fatalf("FindModulePath() = %q, %v, want %q, nil", path, err, "x")
// }
}
func TestModEdit(t *testing.T) {
// Test that local replacements work
// and that they can use a dummy name
// that isn't resolvable and need not even
// include a dot. See golang.org/issue/24100.
tg := testGoModules(t)
defer tg.cleanup()
tg.cd(tg.path("."))
tg.must(os.MkdirAll(tg.path("w"), 0777))
tg.must(ioutil.WriteFile(tg.path("x.go"), []byte("package x\n"), 0666))
tg.must(ioutil.WriteFile(tg.path("w/w.go"), []byte("package w\n"), 0666))
mustHaveGoMod := func(text string) {
t.Helper()
data, err := ioutil.ReadFile(tg.path("go.mod"))
tg.must(err)
if string(data) != text {
t.Fatalf("go.mod mismatch:\nhave:<<<\n%s>>>\nwant:<<<\n%s\n", string(data), text)
}
}
tg.runFail("mod", "-init")
tg.grepStderr(`cannot determine module path`, "")
_, err := os.Stat(tg.path("go.mod"))
if err == nil {
t.Fatalf("failed go mod -init created go.mod")
}
tg.run("mod", "-init", "-module", "x.x/y/z")
tg.grepStderr("creating new go.mod: module x.x/y/z", "")
mustHaveGoMod(`module x.x/y/z
`)
tg.runFail("mod", "-init")
mustHaveGoMod(`module x.x/y/z
`)
tg.run("mod",
"-droprequire=x.1",
"-require=x.1@v1.0.0",
"-require=x.2@v1.1.0",
"-droprequire=x.2",
"-exclude=x.1 @ v1.2.0",
"-exclude=x.1@v1.2.1",
"-replace=x.1@v1.3.0=y.1@v1.4.0",
"-replace=x.1@v1.4.0 = ../z",
)
mustHaveGoMod(`module x.x/y/z
require x.1 v1.0.0
exclude (
x.1 v1.2.0
x.1 v1.2.1
)
replace (
x.1 v1.3.0 => y.1 v1.4.0
x.1 v1.4.0 => ../z
)
`)
tg.run("mod",
"-droprequire=x.1",
"-dropexclude=x.1@v1.2.1",
"-dropreplace=x.1@v1.3.0",
"-require=x.3@v1.99.0",
)
mustHaveGoMod(`module x.x/y/z
exclude x.1 v1.2.0
replace x.1 v1.4.0 => ../z
require x.3 v1.99.0
`)
tg.run("mod", "-json")
want := `{
"Module": {
"Path": "x.x/y/z"
},
"Require": [
{
"Path": "x.3",
"Version": "v1.99.0"
}
],
"Exclude": [
{
"Path": "x.1",
"Version": "v1.2.0"
}
],
"Replace": [
{
"Old": {
"Path": "x.1",
"Version": "v1.4.0"
},
"New": {
"Path": "../z"
}
}
]
}
`
if have := tg.getStdout(); have != want {
t.Fatalf("go mod -json mismatch:\nhave:<<<\n%s>>>\nwant:<<<\n%s\n", have, want)
}
tg.run("mod",
"-replace=x.1@v1.3.0=y.1/v2@v2.3.5",
"-replace=x.1@v1.4.0=y.1/v2@v2.3.5",
)
mustHaveGoMod(`module x.x/y/z
exclude x.1 v1.2.0
replace (
x.1 v1.3.0 => y.1/v2 v2.3.5
x.1 v1.4.0 => y.1/v2 v2.3.5
)
require x.3 v1.99.0
`)
tg.run("mod",
"-replace=x.1=y.1/v2@v2.3.6",
)
mustHaveGoMod(`module x.x/y/z
exclude x.1 v1.2.0
replace x.1 => y.1/v2 v2.3.6
require x.3 v1.99.0
`)
tg.run("mod", "-packages")
want = `x.x/y/z
x.x/y/z/w
`
if have := tg.getStdout(); have != want {
t.Fatalf("go mod -packages mismatch:\nhave:<<<\n%s>>>\nwant:<<<\n%s\n", have, want)
}
data, err := ioutil.ReadFile(tg.path("go.mod"))
tg.must(err)
data = bytes.Replace(data, []byte("\n"), []byte("\r\n"), -1)
data = append(data, " \n"...)
tg.must(ioutil.WriteFile(tg.path("go.mod"), data, 0666))
tg.run("mod", "-fmt")
mustHaveGoMod(`module x.x/y/z
exclude x.1 v1.2.0
replace x.1 => y.1/v2 v2.3.6
require x.3 v1.99.0
`)
}
func TestModSync(t *testing.T) {
tg := testGoModules(t)
defer tg.cleanup()
write := func(name, text string) {
name = tg.path(name)
dir := filepath.Dir(name)
tg.must(os.MkdirAll(dir, 0777))
tg.must(ioutil.WriteFile(name, []byte(text), 0666))
}
write("m/go.mod", `
module m
require (
x.1 v1.0.0
y.1 v1.0.0
w.1 v1.2.0
)
replace x.1 v1.0.0 => ../x
replace y.1 v1.0.0 => ../y
replace z.1 v1.1.0 => ../z
replace z.1 v1.2.0 => ../z
replace w.1 => ../w
`)
write("m/m.go", `
package m
import _ "x.1"
import _ "z.1/sub"
`)
write("w/go.mod", `
module w
`)
write("w/w.go", `
package w
`)
write("x/go.mod", `
module x
require w.1 v1.1.0
require z.1 v1.1.0
`)
write("x/x.go", `
package x
import _ "w.1"
`)
write("y/go.mod", `
module y
require z.1 v1.2.0
`)
write("z/go.mod", `
module z
`)
write("z/sub/sub.go", `
package sub
`)
tg.cd(tg.path("m"))
tg.run("mod", "-sync", "-v")
tg.grepStderr(`^unused y.1`, "need y.1 unused")
tg.grepStderrNot(`^unused [^y]`, "only y.1 should be unused")
tg.run("list", "-m", "all")
tg.grepStdoutNot(`^y.1`, "y should be gone")
tg.grepStdout(`^w.1\s+v1.2.0`, "need w.1 to stay at v1.2.0")
tg.grepStdout(`^z.1\s+v1.2.0`, "need z.1 to stay at v1.2.0 even though y is gone")
}
func TestModVendor(t *testing.T) {
tg := testGoModules(t)
defer tg.cleanup()
tg.extract("testdata/vendormod.txt")
tg.run("list", "-m", "all")
tg.grepStdout(`^x`, "expected to see module x")
tg.grepStdout(`=> ./x`, "expected to see replacement for module x")
tg.grepStdout(`^w`, "expected to see module w")
if !testing.Short() {
tg.run("build")
tg.runFail("build", "-getmode=vendor")
}
tg.run("list", "-f={{.Dir}}", "x")
tg.grepStdout(`work[/\\]x$`, "expected x in work/x")
mustHaveVendor := func(name string) {
t.Helper()
tg.mustExist(filepath.Join(tg.path("work/vendor"), name))
}
mustNotHaveVendor := func(name string) {
t.Helper()
tg.mustNotExist(filepath.Join(tg.path("work/vendor"), name))
}
tg.run("mod", "-vendor", "-v")
tg.grepStderr(`^# x v1.0.0 => ./x`, "expected to see module x with replacement")
tg.grepStderr(`^x`, "expected to see package x")
tg.grepStderr(`^# y v1.0.0 => ./y`, "expected to see module y with replacement")
tg.grepStderr(`^y`, "expected to see package y")
tg.grepStderr(`^# z v1.0.0 => ./z`, "expected to see module z with replacement")
tg.grepStderr(`^z`, "expected to see package z")
tg.grepStderrNot(`w`, "expected NOT to see unused module w")
tg.run("list", "-f={{.Dir}}", "x")
tg.grepStdout(`work[/\\]x$`, "expected x in work/x")
tg.run("list", "-f={{.Dir}}", "-m", "x")
tg.grepStdout(`work[/\\]x$`, "expected x in work/x")
tg.run("list", "-getmode=vendor", "-f={{.Dir}}", "x")
tg.grepStdout(`work[/\\]vendor[/\\]x$`, "expected x in work/vendor/x in -get=vendor mode")
tg.run("list", "-getmode=vendor", "-f={{.Dir}}", "-m", "x")
tg.grepStdout(`work[/\\]vendor[/\\]x$`, "expected x in work/vendor/x in -get=vendor mode")
tg.run("list", "-f={{.Dir}}", "w")
tg.grepStdout(`work[/\\]w$`, "expected w in work/w")
tg.runFail("list", "-getmode=vendor", "-f={{.Dir}}", "w")
tg.grepStderr(`work[/\\]vendor[/\\]w`, "want error about work/vendor/w not existing")
tg.run("list", "-getmode=local", "-f={{.Dir}}", "w")
tg.grepStdout(`work[/\\]w`, "expected w in work/w")
tg.runFail("list", "-getmode=local", "-f={{.Dir}}", "newpkg")
tg.grepStderr(`disabled by -getmode=local`, "expected -getmode=local to avoid network")
mustNotHaveVendor("x/testdata")
mustNotHaveVendor("a/foo/bar/b/main_test.go")
mustHaveVendor("a/foo/AUTHORS.txt")
mustHaveVendor("a/foo/CONTRIBUTORS")
mustHaveVendor("a/foo/LICENSE")
mustHaveVendor("a/foo/PATENTS")
mustHaveVendor("a/foo/COPYING")
mustHaveVendor("a/foo/COPYLEFT")
mustHaveVendor("x/NOTICE!")
mustHaveVendor("mysite/myname/mypkg/LICENSE.txt")
mustNotHaveVendor("a/foo/licensed-to-kill")
mustNotHaveVendor("w")
mustNotHaveVendor("w/LICENSE") // w wasn't copied at all
mustNotHaveVendor("x/x2")
mustNotHaveVendor("x/x2/LICENSE") // x/x2 wasn't copied at all
if !testing.Short() {
tg.run("build")
tg.run("build", "-getmode=vendor")
tg.run("test", "-getmode=vendor", ".", "./subdir")
tg.run("test", "-getmode=vendor", "./...")
}
}