goose: read tests from testdata
Since tests are all written in terms of Go source, it makes tests easier to write. They still need to be arranged in a GOPATH for go build, but tests that just call Generate can operate in-place because I've faked the filesystem. Reviewed-by: Tuo Shan <shantuo@google.com> Reviewed-by: Herbie Ong <herbie@google.com>
This commit is contained in:
@@ -2,351 +2,387 @@ package goose
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(light): pull this out into a testdata directory
|
func TestGoose(t *testing.T) {
|
||||||
|
wd, err := os.Getwd()
|
||||||
var tests = []struct {
|
if err != nil {
|
||||||
name string
|
t.Fatal(err)
|
||||||
files map[string]string
|
|
||||||
pkg string
|
|
||||||
wantOutput string
|
|
||||||
wantError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "No-op build",
|
|
||||||
files: map[string]string{
|
|
||||||
"foo/foo.go": `package main; import "fmt"; func main() { fmt.Println("Hello, World!"); }`,
|
|
||||||
},
|
|
||||||
pkg: "foo",
|
|
||||||
wantOutput: "Hello, World!\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Niladic identity provider",
|
|
||||||
files: map[string]string{
|
|
||||||
"foo/foo.go": `package main
|
|
||||||
import "fmt"
|
|
||||||
func main() { fmt.Println(injectedMessage()); }
|
|
||||||
|
|
||||||
//goose:provide
|
|
||||||
|
|
||||||
// provideMessage provides a friendly user greeting.
|
|
||||||
func provideMessage() string { return "Hello, World!"; }
|
|
||||||
`,
|
|
||||||
"foo/foo_goose.go": `//+build gooseinject
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
//goose:use provideMessage
|
|
||||||
|
|
||||||
func injectedMessage() string
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
pkg: "foo",
|
|
||||||
wantOutput: "Hello, World!\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Missing use",
|
|
||||||
files: map[string]string{
|
|
||||||
"foo/foo.go": `package main
|
|
||||||
import "fmt"
|
|
||||||
func main() { fmt.Println(injectedMessage()); }
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
|
|
||||||
// provideMessage provides a friendly user greeting.
|
|
||||||
func provideMessage() string { return "Hello, World!"; }
|
|
||||||
`,
|
|
||||||
"foo/foo_goose.go": `//+build gooseinject
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
func injectedMessage() string
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
pkg: "foo",
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Chain",
|
|
||||||
files: map[string]string{
|
|
||||||
"foo/foo.go": `package main
|
|
||||||
import "fmt"
|
|
||||||
func main() {
|
|
||||||
fmt.Println(injectFooBar())
|
|
||||||
}
|
|
||||||
|
|
||||||
type Foo int
|
|
||||||
type FooBar int
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
func provideFoo() Foo { return 41 }
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
func provideFooBar(foo Foo) FooBar { return FooBar(foo) + 1 }
|
|
||||||
`,
|
|
||||||
"foo/foo_goose.go": `//+build gooseinject
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
//goose:use Set
|
|
||||||
|
|
||||||
func injectFooBar() FooBar
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
pkg: "foo",
|
|
||||||
wantOutput: "42\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Two deps",
|
|
||||||
files: map[string]string{
|
|
||||||
"foo/foo.go": `package main
|
|
||||||
import "fmt"
|
|
||||||
func main() {
|
|
||||||
fmt.Println(injectFooBar())
|
|
||||||
}
|
|
||||||
|
|
||||||
type Foo int
|
|
||||||
type Bar int
|
|
||||||
type FooBar int
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
func provideFoo() Foo { return 40 }
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
func provideBar() Bar { return 2 }
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
func provideFooBar(foo Foo, bar Bar) FooBar { return FooBar(foo) + FooBar(bar) }
|
|
||||||
`,
|
|
||||||
"foo/foo_goose.go": `//+build gooseinject
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
//goose:use Set
|
|
||||||
|
|
||||||
func injectFooBar() FooBar
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
pkg: "foo",
|
|
||||||
wantOutput: "42\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Inject input",
|
|
||||||
files: map[string]string{
|
|
||||||
"foo/foo.go": `package main
|
|
||||||
import "fmt"
|
|
||||||
func main() {
|
|
||||||
fmt.Println(injectFooBar(40))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Foo int
|
|
||||||
type Bar int
|
|
||||||
type FooBar int
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
func provideBar() Bar { return 2 }
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
func provideFooBar(foo Foo, bar Bar) FooBar { return FooBar(foo) + FooBar(bar) }
|
|
||||||
`,
|
|
||||||
"foo/foo_goose.go": `//+build gooseinject
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
//goose:use Set
|
|
||||||
|
|
||||||
func injectFooBar(foo Foo) FooBar
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
pkg: "foo",
|
|
||||||
wantOutput: "42\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Inject input conflict",
|
|
||||||
files: map[string]string{
|
|
||||||
"foo/foo.go": `package main
|
|
||||||
import "fmt"
|
|
||||||
func main() {
|
|
||||||
fmt.Println(injectBar(40))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Foo int
|
|
||||||
type Bar int
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
func provideFoo() Foo { return -888 }
|
|
||||||
|
|
||||||
//goose:provide Set
|
|
||||||
func provideBar(foo Foo) Bar { return 2 }
|
|
||||||
`,
|
|
||||||
"foo/foo_goose.go": `//+build gooseinject
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
//goose:use Set
|
|
||||||
|
|
||||||
func injectBar(foo Foo) Bar
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
pkg: "foo",
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Return error",
|
|
||||||
files: map[string]string{
|
|
||||||
"foo/foo.go": `package main
|
|
||||||
import "errors"
|
|
||||||
import "fmt"
|
|
||||||
import "strings"
|
|
||||||
func main() {
|
|
||||||
foo, err := injectFoo()
|
|
||||||
fmt.Println(foo)
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("<nil>")
|
|
||||||
} else {
|
|
||||||
fmt.Println(strings.Contains(err.Error(), "there is no Foo"))
|
|
||||||
}
|
}
|
||||||
}
|
const testRoot = "testdata"
|
||||||
|
testdataEnts, err := ioutil.ReadDir(testRoot) // ReadDir sorts by name
|
||||||
type Foo int
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
//goose:provide Set
|
|
||||||
func provideFoo() (Foo, error) { return 42, errors.New("there is no Foo") }
|
|
||||||
`,
|
|
||||||
"foo/foo_goose.go": `//+build gooseinject
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
//goose:use Set
|
|
||||||
|
|
||||||
func injectFoo() (Foo, error)
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
pkg: "foo",
|
|
||||||
wantOutput: "0\ntrue\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGeneratedCode(t *testing.T) {
|
|
||||||
if _, err := os.Stat(filepath.Join(build.Default.GOROOT, "bin", "go")); err != nil {
|
|
||||||
t.Fatalf("go toolchain not available: %v", err)
|
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
tests := make([]*testCase, 0, len(testdataEnts))
|
||||||
t.Run(test.name, func(t *testing.T) {
|
for _, ent := range testdataEnts {
|
||||||
gopath, err := ioutil.TempDir("", "goose_test")
|
name := ent.Name()
|
||||||
if err != nil {
|
if !ent.IsDir() || strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") {
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(gopath)
|
|
||||||
bctx := &build.Context{
|
|
||||||
GOARCH: build.Default.GOARCH,
|
|
||||||
GOOS: build.Default.GOOS,
|
|
||||||
GOROOT: build.Default.GOROOT,
|
|
||||||
GOPATH: gopath,
|
|
||||||
CgoEnabled: build.Default.CgoEnabled,
|
|
||||||
Compiler: build.Default.Compiler,
|
|
||||||
ReleaseTags: build.Default.ReleaseTags,
|
|
||||||
}
|
|
||||||
for name, content := range test.files {
|
|
||||||
p := filepath.Join(gopath, "src", filepath.FromSlash(name))
|
|
||||||
if err := os.MkdirAll(filepath.Dir(p), 0777); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(p, []byte(content), 0666); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gen, err := Generate(bctx, gopath, test.pkg)
|
|
||||||
if len(gen) > 0 {
|
|
||||||
defer t.Logf("goose_gen.go:\n%s", gen)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if !test.wantError {
|
|
||||||
t.Fatalf("goose: %v", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err == nil && test.wantError {
|
|
||||||
t.Fatal("goose succeeded; want error")
|
|
||||||
}
|
|
||||||
if len(gen) > 0 {
|
|
||||||
genPath := filepath.Join(gopath, "src", filepath.FromSlash(test.pkg), "goose_gen.go")
|
|
||||||
if err := ioutil.WriteFile(genPath, gen, 0666); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
testExePath := filepath.Join(gopath, "bin", "testprog")
|
|
||||||
if err := runGo(bctx, "build", "-o", testExePath, test.pkg); err != nil {
|
|
||||||
t.Fatal("build:", err)
|
|
||||||
}
|
|
||||||
out, err := exec.Command(testExePath).Output()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("run compiled program:", err)
|
|
||||||
}
|
|
||||||
if string(out) != test.wantOutput {
|
|
||||||
t.Errorf("compiled program output = %q; want %q", out, test.wantOutput)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeterminism(t *testing.T) {
|
|
||||||
runs := 10
|
|
||||||
if testing.Short() {
|
|
||||||
runs = 3
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
if test.wantError {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.Run(test.name, func(t *testing.T) {
|
test, err := loadTestCase(filepath.Join(testRoot, name))
|
||||||
gopath, err := ioutil.TempDir("", "goose_test")
|
if err != nil {
|
||||||
if err != nil {
|
t.Error(err)
|
||||||
t.Fatal(err)
|
}
|
||||||
}
|
tests = append(tests, test)
|
||||||
defer os.RemoveAll(gopath)
|
|
||||||
bctx := &build.Context{
|
|
||||||
GOARCH: build.Default.GOARCH,
|
|
||||||
GOOS: build.Default.GOOS,
|
|
||||||
GOROOT: build.Default.GOROOT,
|
|
||||||
GOPATH: gopath,
|
|
||||||
CgoEnabled: build.Default.CgoEnabled,
|
|
||||||
Compiler: build.Default.Compiler,
|
|
||||||
ReleaseTags: build.Default.ReleaseTags,
|
|
||||||
}
|
|
||||||
for name, content := range test.files {
|
|
||||||
p := filepath.Join(gopath, "src", filepath.FromSlash(name))
|
|
||||||
if err := os.MkdirAll(filepath.Dir(p), 0777); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(p, []byte(content), 0666); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gold, err := Generate(bctx, gopath, test.pkg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("goose:", err)
|
|
||||||
}
|
|
||||||
goldstr := string(gold)
|
|
||||||
for i := 0; i < runs-1; i++ {
|
|
||||||
out, err := Generate(bctx, gopath, test.pkg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("goose (on subsequent run):", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(gold, out) {
|
|
||||||
t.Fatalf("goose output differs when run repeatedly on same input:\n%s", diff(goldstr, string(out)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("Generate", func(t *testing.T) {
|
||||||
|
if _, err := os.Stat(filepath.Join(build.Default.GOROOT, "bin", "go")); err != nil {
|
||||||
|
t.Skip("go toolchain not available:", err)
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
bctx := test.buildContext()
|
||||||
|
gen, err := Generate(bctx, wd, test.pkg)
|
||||||
|
if len(gen) > 0 {
|
||||||
|
defer t.Logf("goose_gen.go:\n%s", gen)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if !test.wantError {
|
||||||
|
t.Fatalf("goose: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil && test.wantError {
|
||||||
|
t.Fatal("goose succeeded; want error")
|
||||||
|
}
|
||||||
|
|
||||||
|
gopath, err := ioutil.TempDir("", "goose_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(gopath)
|
||||||
|
if err := test.materialize(gopath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(gen) > 0 {
|
||||||
|
genPath := filepath.Join(gopath, "src", filepath.FromSlash(test.pkg), "goose_gen.go")
|
||||||
|
if err := ioutil.WriteFile(genPath, gen, 0666); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testExePath := filepath.Join(gopath, "bin", "testprog")
|
||||||
|
realBuildCtx := &build.Context{
|
||||||
|
GOARCH: bctx.GOARCH,
|
||||||
|
GOOS: bctx.GOOS,
|
||||||
|
GOROOT: bctx.GOROOT,
|
||||||
|
GOPATH: gopath,
|
||||||
|
CgoEnabled: bctx.CgoEnabled,
|
||||||
|
Compiler: bctx.Compiler,
|
||||||
|
BuildTags: bctx.BuildTags,
|
||||||
|
ReleaseTags: bctx.ReleaseTags,
|
||||||
|
}
|
||||||
|
if err := runGo(realBuildCtx, "build", "-o", testExePath, test.pkg); err != nil {
|
||||||
|
t.Fatal("build:", err)
|
||||||
|
}
|
||||||
|
out, err := exec.Command(testExePath).Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("run compiled program:", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(out, test.wantOutput) {
|
||||||
|
t.Errorf("compiled program output = %q; want %q", out, test.wantOutput)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Determinism", func(t *testing.T) {
|
||||||
|
runs := 10
|
||||||
|
if testing.Short() {
|
||||||
|
runs = 3
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
if test.wantError {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
bctx := test.buildContext()
|
||||||
|
gold, err := Generate(bctx, wd, test.pkg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("goose:", err)
|
||||||
|
}
|
||||||
|
goldstr := string(gold)
|
||||||
|
for i := 0; i < runs-1; i++ {
|
||||||
|
out, err := Generate(bctx, wd, test.pkg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("goose (on subsequent run):", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(gold, out) {
|
||||||
|
t.Fatalf("goose output differs when run repeatedly on same input:\n%s", diff(goldstr, string(out)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
pkg string
|
||||||
|
goFiles map[string][]byte
|
||||||
|
wantOutput []byte
|
||||||
|
wantError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTestCase reads a test case from a directory.
|
||||||
|
//
|
||||||
|
// The directory structure is:
|
||||||
|
//
|
||||||
|
// root/
|
||||||
|
// pkg file containing the package name containing the inject function
|
||||||
|
// (must also be package main)
|
||||||
|
// out.txt file containing the expected output, or the magic string "ERROR"
|
||||||
|
// if this test should cause generation to fail
|
||||||
|
// ... any Go files found recursively placed under GOPATH/src/...
|
||||||
|
func loadTestCase(root string) (*testCase, error) {
|
||||||
|
name := filepath.Base(root)
|
||||||
|
pkg, err := ioutil.ReadFile(filepath.Join(root, "pkg"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load test case %s: %v", name, err)
|
||||||
|
}
|
||||||
|
out, err := ioutil.ReadFile(filepath.Join(root, "out.txt"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load test case %s: %v", name, err)
|
||||||
|
}
|
||||||
|
wantError := false
|
||||||
|
if bytes.Equal(bytes.TrimSpace(out), []byte("ERROR")) {
|
||||||
|
wantError = true
|
||||||
|
out = nil
|
||||||
|
}
|
||||||
|
goFiles := make(map[string][]byte)
|
||||||
|
err = filepath.Walk(root, func(src string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() || filepath.Ext(src) != ".go" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rel, err := filepath.Rel(root, src)
|
||||||
|
if err != nil {
|
||||||
|
return err // unlikely
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadFile(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
goFiles[rel] = data
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load test case %s: %v", name, err)
|
||||||
|
}
|
||||||
|
return &testCase{
|
||||||
|
name: name,
|
||||||
|
pkg: string(bytes.TrimSpace(pkg)),
|
||||||
|
goFiles: goFiles,
|
||||||
|
wantOutput: out,
|
||||||
|
wantError: wantError,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *testCase) buildContext() *build.Context {
|
||||||
|
return &build.Context{
|
||||||
|
GOARCH: build.Default.GOARCH,
|
||||||
|
GOOS: build.Default.GOOS,
|
||||||
|
GOROOT: build.Default.GOROOT,
|
||||||
|
GOPATH: magicGOPATH(),
|
||||||
|
CgoEnabled: build.Default.CgoEnabled,
|
||||||
|
Compiler: build.Default.Compiler,
|
||||||
|
ReleaseTags: build.Default.ReleaseTags,
|
||||||
|
HasSubdir: test.hasSubdir,
|
||||||
|
ReadDir: test.readDir,
|
||||||
|
OpenFile: test.openFile,
|
||||||
|
IsDir: test.isDir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
magicGOPATHUnix = "/goose_gopath"
|
||||||
|
magicGOPATHWindows = `C:\goose_gopath`
|
||||||
|
)
|
||||||
|
|
||||||
|
func magicGOPATH() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return magicGOPATHWindows
|
||||||
|
} else {
|
||||||
|
return magicGOPATHUnix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *testCase) hasSubdir(root, dir string) (rel string, ok bool) {
|
||||||
|
// Don't consult filesystem, just lexical.
|
||||||
|
|
||||||
|
if dir == root {
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
prefix := root
|
||||||
|
if !strings.HasSuffix(prefix, string(filepath.Separator)) {
|
||||||
|
prefix += string(filepath.Separator)
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(dir, prefix) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return filepath.ToSlash(dir[len(prefix):]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *testCase) resolve(path string) (resolved string, pathType int) {
|
||||||
|
subpath, isMagic := test.hasSubdir(magicGOPATH(), path)
|
||||||
|
if !isMagic {
|
||||||
|
return path, systemPath
|
||||||
|
}
|
||||||
|
if subpath == "src" {
|
||||||
|
return "", gopathRoot
|
||||||
|
}
|
||||||
|
const srcPrefix = "src/"
|
||||||
|
if !strings.HasPrefix(subpath, srcPrefix) {
|
||||||
|
return subpath, gopathRoot
|
||||||
|
}
|
||||||
|
return subpath[len(srcPrefix):], gopathSrc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path types
|
||||||
|
const (
|
||||||
|
systemPath = iota
|
||||||
|
gopathRoot
|
||||||
|
gopathSrc
|
||||||
|
)
|
||||||
|
|
||||||
|
func (test *testCase) readDir(dir string) ([]os.FileInfo, error) {
|
||||||
|
rpath, pathType := test.resolve(dir)
|
||||||
|
switch {
|
||||||
|
case pathType == systemPath:
|
||||||
|
return ioutil.ReadDir(rpath)
|
||||||
|
case pathType == gopathRoot && rpath == "":
|
||||||
|
return []os.FileInfo{dirInfo{name: "src"}}, nil
|
||||||
|
case pathType == gopathSrc:
|
||||||
|
names := make([]string, 0, len(test.goFiles))
|
||||||
|
prefix := rpath + string(filepath.Separator)
|
||||||
|
for name := range test.goFiles {
|
||||||
|
if strings.HasPrefix(name, prefix) {
|
||||||
|
names = append(names, name[len(prefix):])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
ents := make([]os.FileInfo, 0, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
if i := strings.IndexRune(name, filepath.Separator); i != -1 {
|
||||||
|
// Directory
|
||||||
|
dirName := name[:i]
|
||||||
|
if len(ents) == 0 || ents[len(ents)-1].Name() != dirName {
|
||||||
|
ents = append(ents, dirInfo{name: dirName})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ents = append(ents, fileInfo{
|
||||||
|
name: name,
|
||||||
|
size: int64(len(test.goFiles[name])),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ents, nil
|
||||||
|
default:
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: dir,
|
||||||
|
Err: os.ErrNotExist,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *testCase) isDir(path string) bool {
|
||||||
|
rpath, pathType := test.resolve(path)
|
||||||
|
switch {
|
||||||
|
case pathType == systemPath:
|
||||||
|
info, err := os.Stat(rpath)
|
||||||
|
return err == nil && info.IsDir()
|
||||||
|
case pathType == gopathRoot && rpath == "":
|
||||||
|
return true
|
||||||
|
case pathType == gopathSrc:
|
||||||
|
prefix := rpath + string(filepath.Separator)
|
||||||
|
for name := range test.goFiles {
|
||||||
|
if strings.HasPrefix(name, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dirInfo struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dirInfo) Name() string { return d.name }
|
||||||
|
func (d dirInfo) Size() int64 { return 0 }
|
||||||
|
func (d dirInfo) Mode() os.FileMode { return os.ModeDir | 0777 }
|
||||||
|
func (d dirInfo) ModTime() time.Time { return time.Unix(0, 0) }
|
||||||
|
func (d dirInfo) IsDir() bool { return true }
|
||||||
|
func (d dirInfo) Sys() interface{} { return nil }
|
||||||
|
|
||||||
|
type fileInfo struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileInfo) Name() string { return f.name }
|
||||||
|
func (f fileInfo) Size() int64 { return f.size }
|
||||||
|
func (f fileInfo) Mode() os.FileMode { return os.ModeDir | 0666 }
|
||||||
|
func (f fileInfo) ModTime() time.Time { return time.Unix(0, 0) }
|
||||||
|
func (f fileInfo) IsDir() bool { return false }
|
||||||
|
func (f fileInfo) Sys() interface{} { return nil }
|
||||||
|
|
||||||
|
func (test *testCase) openFile(path string) (io.ReadCloser, error) {
|
||||||
|
rpath, pathType := test.resolve(path)
|
||||||
|
switch {
|
||||||
|
case pathType == systemPath:
|
||||||
|
return os.Open(path)
|
||||||
|
case pathType == gopathSrc:
|
||||||
|
content, ok := test.goFiles[rpath]
|
||||||
|
if !ok {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: path,
|
||||||
|
Err: errors.New("does not exist or is not a file"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ioutil.NopCloser(bytes.NewReader(content)), nil
|
||||||
|
default:
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: path,
|
||||||
|
Err: errors.New("does not exist or is not a file"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// materialize creates a new GOPATH at the given directory, which may or
|
||||||
|
// may not exist.
|
||||||
|
func (test *testCase) materialize(gopath string) error {
|
||||||
|
for name, content := range test.goFiles {
|
||||||
|
dst := filepath.Join(gopath, "src", name)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
|
||||||
|
return fmt.Errorf("materialize GOPATH: %v", err)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(dst, content, 0666); err != nil {
|
||||||
|
return fmt.Errorf("materialize GOPATH: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGo(bctx *build.Context, args ...string) error {
|
func runGo(bctx *build.Context, args ...string) error {
|
||||||
|
|||||||
20
internal/goose/testdata/Chain/foo/foo.go
vendored
Normal file
20
internal/goose/testdata/Chain/foo/foo.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(injectFooBar())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo int
|
||||||
|
type FooBar int
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideFoo() Foo {
|
||||||
|
return 41
|
||||||
|
}
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideFooBar(foo Foo) FooBar {
|
||||||
|
return FooBar(foo) + 1
|
||||||
|
}
|
||||||
7
internal/goose/testdata/Chain/foo/foo_goose.go
vendored
Normal file
7
internal/goose/testdata/Chain/foo/foo_goose.go
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//+build gooseinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
//goose:use Set
|
||||||
|
|
||||||
|
func injectFooBar() FooBar
|
||||||
1
internal/goose/testdata/Chain/out.txt
vendored
Normal file
1
internal/goose/testdata/Chain/out.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
42
|
||||||
1
internal/goose/testdata/Chain/pkg
vendored
Normal file
1
internal/goose/testdata/Chain/pkg
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
21
internal/goose/testdata/InjectInput/foo/foo.go
vendored
Normal file
21
internal/goose/testdata/InjectInput/foo/foo.go
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(injectFooBar(40))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo int
|
||||||
|
type Bar int
|
||||||
|
type FooBar int
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideBar() Bar {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideFooBar(foo Foo, bar Bar) FooBar {
|
||||||
|
return FooBar(foo) + FooBar(bar)
|
||||||
|
}
|
||||||
7
internal/goose/testdata/InjectInput/foo/foo_goose.go
vendored
Normal file
7
internal/goose/testdata/InjectInput/foo/foo_goose.go
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//+build gooseinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
//goose:use Set
|
||||||
|
|
||||||
|
func injectFooBar(foo Foo) FooBar
|
||||||
1
internal/goose/testdata/InjectInput/out.txt
vendored
Normal file
1
internal/goose/testdata/InjectInput/out.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
42
|
||||||
1
internal/goose/testdata/InjectInput/pkg
vendored
Normal file
1
internal/goose/testdata/InjectInput/pkg
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
23
internal/goose/testdata/InjectInputConflict/foo/foo.go
vendored
Normal file
23
internal/goose/testdata/InjectInputConflict/foo/foo.go
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// I'm on the fence as to whether this should be an error (versus an
|
||||||
|
// override). For now, I will make it an error that can be relaxed
|
||||||
|
// later.
|
||||||
|
fmt.Println(injectBar(40))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo int
|
||||||
|
type Bar int
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideFoo() Foo {
|
||||||
|
return -888
|
||||||
|
}
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideBar(foo Foo) Bar {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
7
internal/goose/testdata/InjectInputConflict/foo/foo_goose.go
vendored
Normal file
7
internal/goose/testdata/InjectInputConflict/foo/foo_goose.go
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//+build gooseinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
//goose:use Set
|
||||||
|
|
||||||
|
func injectBar(foo Foo) Bar
|
||||||
1
internal/goose/testdata/InjectInputConflict/out.txt
vendored
Normal file
1
internal/goose/testdata/InjectInputConflict/out.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ERROR
|
||||||
1
internal/goose/testdata/InjectInputConflict/pkg
vendored
Normal file
1
internal/goose/testdata/InjectInputConflict/pkg
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
14
internal/goose/testdata/MissingUse/foo/foo.go
vendored
Normal file
14
internal/goose/testdata/MissingUse/foo/foo.go
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(injectedMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
|
||||||
|
// provideMessage provides a friendly user greeting.
|
||||||
|
func provideMessage() string {
|
||||||
|
return "Hello, World!"
|
||||||
|
}
|
||||||
5
internal/goose/testdata/MissingUse/foo/foo_goose.go
vendored
Normal file
5
internal/goose/testdata/MissingUse/foo/foo_goose.go
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//+build gooseinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func injectedMessage() string
|
||||||
1
internal/goose/testdata/MissingUse/out.txt
vendored
Normal file
1
internal/goose/testdata/MissingUse/out.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ERROR
|
||||||
1
internal/goose/testdata/MissingUse/pkg
vendored
Normal file
1
internal/goose/testdata/MissingUse/pkg
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
14
internal/goose/testdata/NiladicIdentity/foo/foo.go
vendored
Normal file
14
internal/goose/testdata/NiladicIdentity/foo/foo.go
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(injectedMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
//goose:provide
|
||||||
|
|
||||||
|
// provideMessage provides a friendly user greeting.
|
||||||
|
func provideMessage() string {
|
||||||
|
return "Hello, World!"
|
||||||
|
}
|
||||||
7
internal/goose/testdata/NiladicIdentity/foo/foo_goose.go
vendored
Normal file
7
internal/goose/testdata/NiladicIdentity/foo/foo_goose.go
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//+build gooseinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
//goose:use provideMessage
|
||||||
|
|
||||||
|
func injectedMessage() string
|
||||||
1
internal/goose/testdata/NiladicIdentity/out.txt
vendored
Normal file
1
internal/goose/testdata/NiladicIdentity/out.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello, World!
|
||||||
1
internal/goose/testdata/NiladicIdentity/pkg
vendored
Normal file
1
internal/goose/testdata/NiladicIdentity/pkg
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
7
internal/goose/testdata/NoopBuild/foo/foo.go
vendored
Normal file
7
internal/goose/testdata/NoopBuild/foo/foo.go
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Hello, World!")
|
||||||
|
}
|
||||||
1
internal/goose/testdata/NoopBuild/out.txt
vendored
Normal file
1
internal/goose/testdata/NoopBuild/out.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello, World!
|
||||||
1
internal/goose/testdata/NoopBuild/pkg
vendored
Normal file
1
internal/goose/testdata/NoopBuild/pkg
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
22
internal/goose/testdata/ReturnError/foo/foo.go
vendored
Normal file
22
internal/goose/testdata/ReturnError/foo/foo.go
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
import "fmt"
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
foo, err := injectFoo()
|
||||||
|
fmt.Println(foo) // should be zero, the injector should ignore provideFoo's return value.
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("<nil>")
|
||||||
|
} else {
|
||||||
|
fmt.Println(strings.Contains(err.Error(), "there is no Foo"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo int
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideFoo() (Foo, error) {
|
||||||
|
return 42, errors.New("there is no Foo")
|
||||||
|
}
|
||||||
7
internal/goose/testdata/ReturnError/foo/foo_goose.go
vendored
Normal file
7
internal/goose/testdata/ReturnError/foo/foo_goose.go
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//+build gooseinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
//goose:use Set
|
||||||
|
|
||||||
|
func injectFoo() (Foo, error)
|
||||||
2
internal/goose/testdata/ReturnError/out.txt
vendored
Normal file
2
internal/goose/testdata/ReturnError/out.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
0
|
||||||
|
true
|
||||||
1
internal/goose/testdata/ReturnError/pkg
vendored
Normal file
1
internal/goose/testdata/ReturnError/pkg
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
26
internal/goose/testdata/TwoDeps/foo/foo.go
vendored
Normal file
26
internal/goose/testdata/TwoDeps/foo/foo.go
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(injectFooBar())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo int
|
||||||
|
type Bar int
|
||||||
|
type FooBar int
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideFoo() Foo {
|
||||||
|
return 40
|
||||||
|
}
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideBar() Bar {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
//goose:provide Set
|
||||||
|
func provideFooBar(foo Foo, bar Bar) FooBar {
|
||||||
|
return FooBar(foo) + FooBar(bar)
|
||||||
|
}
|
||||||
7
internal/goose/testdata/TwoDeps/foo/foo_goose.go
vendored
Normal file
7
internal/goose/testdata/TwoDeps/foo/foo_goose.go
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//+build gooseinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
//goose:use Set
|
||||||
|
|
||||||
|
func injectFooBar() FooBar
|
||||||
1
internal/goose/testdata/TwoDeps/out.txt
vendored
Normal file
1
internal/goose/testdata/TwoDeps/out.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
42
|
||||||
1
internal/goose/testdata/TwoDeps/pkg
vendored
Normal file
1
internal/goose/testdata/TwoDeps/pkg
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
Reference in New Issue
Block a user