use standard go build package to create a godog test suite

* 1d6c8ac finish refactoring to support standard way of building test package
Этот коммит содержится в:
gedi 2016-05-24 17:42:28 +03:00
родитель 468711f03c
коммит 198755476d
5 изменённых файлов: 145 добавлений и 82 удалений

16
ast.go
Просмотреть файл

@ -8,7 +8,7 @@ import (
"strings"
)
func contexts(f *ast.File) []string {
func astContexts(f *ast.File) []string {
var contexts []string
for _, d := range f.Decls {
switch fun := d.(type) {
@ -36,8 +36,8 @@ func contexts(f *ast.File) []string {
return contexts
}
func removeUnusedImports(f *ast.File) {
used := usedPackages(f)
func astRemoveUnusedImports(f *ast.File) {
used := astUsedPackages(f)
isUsed := func(p string) bool {
for _, ref := range used {
if p == ref {
@ -54,7 +54,7 @@ func removeUnusedImports(f *ast.File) {
for _, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec)
ipath := strings.Trim(impspec.Path.Value, `\"`)
check := importPathToName(ipath)
check := astImportPathToName(ipath)
if impspec.Name != nil {
check = impspec.Name.Name
}
@ -74,7 +74,7 @@ func removeUnusedImports(f *ast.File) {
f.Decls = decls
}
func deleteTestMainFunc(f *ast.File) {
func astDeleteTestMainFunc(f *ast.File) {
var decls []ast.Decl
var hadTestMain bool
for _, d := range f.Decls {
@ -92,7 +92,7 @@ func deleteTestMainFunc(f *ast.File) {
f.Decls = decls
if hadTestMain {
removeUnusedImports(f)
astRemoveUnusedImports(f)
}
}
@ -102,7 +102,7 @@ func (fn visitFn) Visit(node ast.Node) ast.Visitor {
return fn(node)
}
func usedPackages(f *ast.File) []string {
func astUsedPackages(f *ast.File) []string {
var refs []string
var visitor visitFn
visitor = visitFn(func(node ast.Node) ast.Visitor {
@ -129,7 +129,7 @@ func usedPackages(f *ast.File) []string {
// importPathToName finds out the actual package name, as declared in its .go files.
// If there's a problem, it falls back to using importPathToNameBasic.
func importPathToName(importPath string) (packageName string) {
func astImportPathToName(importPath string) (packageName string) {
if buildPkg, err := build.Import(importPath, "", 0); err == nil {
return buildPkg.Name
}

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

@ -27,18 +27,18 @@ func apiContext(s *godog.Suite) {
func dbContext(s *godog.Suite) {
}`
func astContexts(src string, t *testing.T) []string {
func astContextParse(src string, t *testing.T) []string {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", []byte(src), 0)
if err != nil {
t.Fatalf("unexpected error while parsing ast: %v", err)
}
return contexts(f)
return astContexts(f)
}
func TestShouldGetSingleContextFromSource(t *testing.T) {
actual := astContexts(astContextSrc, t)
actual := astContextParse(astContextSrc, t)
expect := []string{"myContext"}
if len(actual) != len(expect) {
@ -53,7 +53,7 @@ func TestShouldGetSingleContextFromSource(t *testing.T) {
}
func TestShouldGetTwoContextsFromSource(t *testing.T) {
actual := astContexts(astTwoContextSrc, t)
actual := astContextParse(astTwoContextSrc, t)
expect := []string{"apiContext", "dbContext"}
if len(actual) != len(expect) {
@ -68,7 +68,7 @@ func TestShouldGetTwoContextsFromSource(t *testing.T) {
}
func TestShouldNotFindAnyContextsInEmptyFile(t *testing.T) {
actual := astContexts(`package main`, t)
actual := astContextParse(`package main`, t)
if len(actual) != 0 {
t.Fatalf("expected no contexts to be found, but there was some: %v", actual)

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

@ -84,7 +84,7 @@ func astProcess(src string, t *testing.T) string {
t.Fatalf("unexpected error while parsing ast: %v", err)
}
deleteTestMainFunc(f)
astDeleteTestMainFunc(f)
var buf bytes.Buffer
if err := format.Node(&buf, fset, f); err != nil {

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

@ -1,28 +1,27 @@
package godog
import (
"bytes"
"go/ast"
"go/build"
"go/format"
"go/parser"
"go/token"
"io"
"os"
"path/filepath"
"strings"
"text/template"
)
var runnerTemplate = template.Must(template.New("main").Parse(`package {{ .PackageName }}
var runnerTemplate = template.Must(template.New("main").Parse(`package {{ .Name }}
import (
{{ if ne .PackageName "godog" }} "github.com/DATA-DOG/godog"{{ end }}
{{ if ne .Name "godog" }} "github.com/DATA-DOG/godog"{{ end }}
"os"
"testing"
)
const GodogSuiteName = "{{ .PackageName }}"
const GodogSuiteName = "{{ .Name }}"
func TestMain(m *testing.M) {
status := {{ if ne .PackageName "godog" }}godog.{{ end }}Run(func (suite *{{ if ne .PackageName "godog" }}godog.{{ end }}Suite) {
status := {{ if ne .Name "godog" }}godog.{{ end }}Run(func (suite *{{ if ne .Name "godog" }}godog.{{ end }}Suite) {
{{range .Contexts}}
{{ . }}(suite)
{{end}}
@ -30,22 +29,9 @@ func TestMain(m *testing.M) {
os.Exit(status)
}`))
type builder struct {
files map[string]*ast.File
Contexts []string
PackageName string
}
func (b *builder) register(f *ast.File, name string) {
b.PackageName = f.Name.Name
deleteTestMainFunc(f)
// f.Name.Name = "main"
b.Contexts = append(b.Contexts, contexts(f)...)
b.files[name] = f
}
// Build scans all go files in current directory,
// copies them to temporary build directory.
// Build scans clones current package into a temporary
// godog suite test package.
//
// If there is a TestMain func in any of test.go files
// it removes it and all necessary unused imports related
// to this function.
@ -56,51 +42,127 @@ func (b *builder) register(f *ast.File, name string) {
// The test entry point which uses go1.4 TestMain func
// is generated from the template above.
func Build(dir string) error {
fset := token.NewFileSet()
b := &builder{files: make(map[string]*ast.File)}
pkg, err := build.ImportDir(".", 0)
if err != nil {
return err
}
err := filepath.Walk(".", func(path string, file os.FileInfo, err error) error {
if file.IsDir() && file.Name() != "." {
return filepath.SkipDir
}
// @TODO: maybe should copy all files in root dir (may contain CGO)
// or use build.Import go tool, to manage package details
if err == nil && strings.HasSuffix(path, ".go") {
f, err := parser.ParseFile(fset, path, nil, 0)
if err != nil {
return buildTestPackage(pkg, dir)
}
// buildTestPackage clones a package and adds a godog
// entry point with TestMain func in order to
// run the test suite. If TestMain func is found in tested
// source, it will be removed so it can be replaced
func buildTestPackage(pkg *build.Package, dir string) error {
// these file packs may be adjusted in the future, if there are complaints
// in general, most important aspect is to replicate go test behavior
err := copyNonTestPackageFiles(
dir,
pkg.CFiles,
pkg.CgoFiles,
pkg.CXXFiles,
pkg.HFiles,
pkg.GoFiles,
pkg.IgnoredGoFiles,
)
if err != nil {
return err
}
contexts, err := processPackageTestFiles(dir, pkg.TestGoFiles, pkg.XTestGoFiles)
if err != nil {
return err
}
// build godog runner test file
out, err := os.Create(filepath.Join(dir, "godog_runner_test.go"))
if err != nil {
return err
}
defer out.Close()
data := struct {
Name string
Contexts []string
}{pkg.Name, contexts}
return runnerTemplate.Execute(out, data)
}
// copyNonTestPackageFiles simply copies all given file packs
// to the destDir.
func copyNonTestPackageFiles(destDir string, packs ...[]string) error {
for _, pack := range packs {
for _, file := range pack {
if err := copyPackageFile(file, filepath.Join(destDir, file)); err != nil {
return err
}
b.register(f, file.Name())
}
return err
})
if err != nil {
return err
}
var buf bytes.Buffer
if err := runnerTemplate.Execute(&buf, b); err != nil {
return err
}
f, err := parser.ParseFile(fset, "", &buf, 0)
if err != nil {
return err
}
b.files["godog_test.go"] = f
os.Mkdir(dir, 0755)
for name, node := range b.files {
f, err := os.Create(filepath.Join(dir, name))
if err != nil {
return err
}
if err := format.Node(f, fset, node); err != nil {
return err
}
}
return nil
}
// processPackageTestFiles runs through ast of each test
// file pack and removes TestMain func if found. it also
// looks for godog suite contexts to register on run
func processPackageTestFiles(destDir string, packs ...[]string) ([]string, error) {
var ctxs []string
fset := token.NewFileSet()
for _, pack := range packs {
for _, testFile := range pack {
node, err := parser.ParseFile(fset, testFile, nil, 0)
if err != nil {
return ctxs, err
}
astDeleteTestMainFunc(node)
ctxs = append(ctxs, astContexts(node)...)
destFile := filepath.Join(destDir, testFile)
if err = os.MkdirAll(filepath.Dir(destFile), 0755); err != nil {
return ctxs, err
}
out, err := os.Create(destFile)
if err != nil {
return ctxs, err
}
defer out.Close()
if err := format.Node(out, fset, node); err != nil {
return ctxs, err
}
}
}
return ctxs, nil
}
// copyPackageFile simply copies the file, if dest file dir does
// not exist it creates it
func copyPackageFile(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
if err = os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return
}
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}

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

@ -3,7 +3,6 @@ package main
import (
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
@ -25,7 +24,7 @@ func buildAndRun() (int, error) {
stdout := ansicolor.NewAnsiColorWriter(os.Stdout)
stderr := ansicolor.NewAnsiColorWriter(statusOutputFilter(os.Stderr))
dir := fmt.Sprintf(filepath.Join("%s", "%dgodogs"), os.TempDir(), time.Now().UnixNano())
dir := fmt.Sprintf(filepath.Join("%s", "godog-%d"), os.TempDir(), time.Now().UnixNano())
err := godog.Build(dir)
if err != nil {
return 1, err
@ -41,15 +40,17 @@ func buildAndRun() (int, error) {
cmdb := exec.Command("go", "test", "-c", "-o", bin)
cmdb.Dir = dir
cmdb.Env = os.Environ()
if dat, err := cmdb.CombinedOutput(); err != nil {
log.Println(string(dat))
return 1, err
fmt.Println(string(dat))
return 1, nil
}
defer os.Remove(bin)
cmd := exec.Command(bin, os.Args[1:]...)
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Env = os.Environ()
if err = cmd.Start(); err != nil {
return status, err