reorganize test package builder
Этот коммит содержится в:
родитель
4c779c8fdf
коммит
8cd540d3af
4 изменённых файлов: 286 добавлений и 208 удалений
109
ast.go
Обычный файл
109
ast.go
Обычный файл
|
@ -0,0 +1,109 @@
|
|||
package godog
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func removeUnusedImports(f *ast.File) {
|
||||
used := usedPackages(f)
|
||||
isUsed := func(p string) bool {
|
||||
for _, ref := range used {
|
||||
if p == ref {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return p == "_"
|
||||
}
|
||||
var decls []ast.Decl
|
||||
for _, d := range f.Decls {
|
||||
gen, ok := d.(*ast.GenDecl)
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
var specs []ast.Spec
|
||||
for _, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
ipath := strings.Trim(impspec.Path.Value, `\"`)
|
||||
check := importPathToName(ipath)
|
||||
if impspec.Name != nil {
|
||||
check = impspec.Name.Name
|
||||
}
|
||||
|
||||
if isUsed(check) {
|
||||
specs = append(specs, spec)
|
||||
}
|
||||
}
|
||||
|
||||
if len(specs) == 0 {
|
||||
continue
|
||||
}
|
||||
gen.Specs = specs
|
||||
}
|
||||
decls = append(decls, d)
|
||||
}
|
||||
f.Decls = decls
|
||||
}
|
||||
|
||||
func deleteTestMainFunc(f *ast.File) {
|
||||
var decls []ast.Decl
|
||||
var hadMain bool
|
||||
for _, d := range f.Decls {
|
||||
fun, ok := d.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
decls = append(decls, d)
|
||||
continue
|
||||
}
|
||||
if fun.Name.Name != "TestMain" {
|
||||
decls = append(decls, fun)
|
||||
} else {
|
||||
hadMain = true
|
||||
}
|
||||
}
|
||||
f.Decls = decls
|
||||
|
||||
if hadMain {
|
||||
removeUnusedImports(f)
|
||||
}
|
||||
}
|
||||
|
||||
type visitFn func(node ast.Node) ast.Visitor
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
return fn(node)
|
||||
}
|
||||
|
||||
func usedPackages(f *ast.File) []string {
|
||||
var refs []string
|
||||
var visitor visitFn
|
||||
visitor = visitFn(func(node ast.Node) ast.Visitor {
|
||||
if node == nil {
|
||||
return visitor
|
||||
}
|
||||
switch v := node.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
xident, ok := v.X.(*ast.Ident)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if xident.Obj != nil {
|
||||
// if the parser can resolve it, it's not a package ref
|
||||
break
|
||||
}
|
||||
refs = append(refs, xident.Name)
|
||||
}
|
||||
return visitor
|
||||
})
|
||||
ast.Walk(visitor, f)
|
||||
return refs
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if buildPkg, err := build.Import(importPath, "", 0); err == nil {
|
||||
return buildPkg.Name
|
||||
}
|
||||
return path.Base(importPath)
|
||||
}
|
117
ast_test.go
Обычный файл
117
ast_test.go
Обычный файл
|
@ -0,0 +1,117 @@
|
|||
package godog
|
||||
|
||||
var builderMainFile = `
|
||||
package main
|
||||
import "fmt"
|
||||
func main() {
|
||||
fmt.Println("hello")
|
||||
}`
|
||||
|
||||
var builderTestMainFile = `
|
||||
package main
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"os"
|
||||
)
|
||||
func TestMain(m *testing.M) {
|
||||
fmt.Println("hello")
|
||||
os.Exit(0)
|
||||
}`
|
||||
|
||||
var builderPackAliases = `
|
||||
package main
|
||||
import (
|
||||
"testing"
|
||||
a "fmt"
|
||||
b "fmt"
|
||||
)
|
||||
func TestMain(m *testing.M) {
|
||||
a.Println("a")
|
||||
b.Println("b")
|
||||
}`
|
||||
|
||||
var builderAnonymousImport = `
|
||||
package main
|
||||
import (
|
||||
"testing"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
func TestMain(m *testing.M) {
|
||||
}`
|
||||
|
||||
var builderContextSrc = `
|
||||
package main
|
||||
import (
|
||||
"github.com/DATA-DOG/godog"
|
||||
)
|
||||
func myContext(s *godog.Suite) {
|
||||
}
|
||||
`
|
||||
|
||||
var builderLibrarySrc = `
|
||||
package lib
|
||||
import "fmt"
|
||||
func test() {
|
||||
fmt.Println("hello")
|
||||
}
|
||||
`
|
||||
|
||||
var builderInternalPackageSrc = `
|
||||
package godog
|
||||
import "fmt"
|
||||
func test() {
|
||||
fmt.Println("hello")
|
||||
}
|
||||
`
|
||||
|
||||
// func builderProcess(src string, t *testing.T) string {
|
||||
// fset := token.NewFileSet()
|
||||
// f, err := parser.ParseFile(fset, "", []byte(builderTestMainFile), 0)
|
||||
// if err != nil {
|
||||
// t.Fatalf("unexpected error while parsing ast: %v", err)
|
||||
// }
|
||||
|
||||
// deleteTestMainFunc(f)
|
||||
|
||||
// var buf strings.Buffer
|
||||
// if err := format.Node(&buf, fset, node); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestShouldCleanTestMainFromSimpleTestFile(t *testing.T) {
|
||||
|
||||
// b := newBuilderSkel()
|
||||
// err := b.registerMulti([]string{
|
||||
// builderMainFile, builderPackAliases, builderAnonymousImport,
|
||||
// })
|
||||
// if err != nil {
|
||||
// t.Fatalf("unexpected error: %s", err)
|
||||
// }
|
||||
|
||||
// data, err := b.merge()
|
||||
// if err != nil {
|
||||
// t.Fatalf("unexpected error: %s", err)
|
||||
// }
|
||||
// expected := `package main
|
||||
// import (
|
||||
// a "fmt"
|
||||
// b "fmt"
|
||||
// "github.com/DATA-DOG/godog"
|
||||
// _ "github.com/go-sql-driver/mysql"
|
||||
// )
|
||||
// func main() {
|
||||
// godog.Run(func(suite *godog.Suite) {
|
||||
// })
|
||||
// }
|
||||
// func Tester() {
|
||||
// a.Println("a")
|
||||
// b.Println("b")
|
||||
// }`
|
||||
|
||||
// actual := string(data)
|
||||
// if b.cleanSpacing(expected) != b.cleanSpacing(actual) {
|
||||
// t.Fatalf("expected output does not match: %s", actual)
|
||||
// }
|
||||
// }
|
267
builder.go
267
builder.go
|
@ -3,49 +3,16 @@ package godog
|
|||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type builder struct {
|
||||
files map[string]*ast.File
|
||||
fset *token.FileSet
|
||||
Contexts []string
|
||||
Internal bool
|
||||
SuiteName string
|
||||
tpl *template.Template
|
||||
|
||||
imports []*ast.ImportSpec
|
||||
}
|
||||
|
||||
func (b *builder) hasImport(a *ast.ImportSpec) bool {
|
||||
for _, b := range b.imports {
|
||||
var aname, bname string
|
||||
if a.Name != nil {
|
||||
aname = a.Name.Name
|
||||
}
|
||||
if b.Name != nil {
|
||||
bname = b.Name.Name
|
||||
}
|
||||
if bname == aname && a.Path.Value == b.Path.Value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newBuilderSkel() *builder {
|
||||
return &builder{
|
||||
files: make(map[string]*ast.File),
|
||||
fset: token.NewFileSet(),
|
||||
tpl: template.Must(template.New("main").Parse(`package main
|
||||
var runnerTemplate = template.Must(template.New("main").Parse(`package main
|
||||
import (
|
||||
{{ if not .Internal }} "github.com/DATA-DOG/godog"{{ end }}
|
||||
"os"
|
||||
|
@ -61,54 +28,13 @@ func TestMain(m *testing.M) {
|
|||
{{end}}
|
||||
})
|
||||
os.Exit(status)
|
||||
}`)),
|
||||
}
|
||||
}
|
||||
}`))
|
||||
|
||||
func doBuild(buildPath, dir string) error {
|
||||
b := newBuilderSkel()
|
||||
err := filepath.Walk(buildPath, func(path string, file os.FileInfo, err error) error {
|
||||
if file.IsDir() && file.Name() != "." {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if err == nil && strings.HasSuffix(path, ".go") {
|
||||
f, err := parser.ParseFile(b.fset, path, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.register(f, file.Name())
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := b.tpl.Execute(&buf, b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := parser.ParseFile(b.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, b.fset, node); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
type builder struct {
|
||||
files map[string]*ast.File
|
||||
Contexts []string
|
||||
Internal bool
|
||||
SuiteName string
|
||||
}
|
||||
|
||||
func (b *builder) register(f *ast.File, name string) {
|
||||
|
@ -117,90 +43,12 @@ func (b *builder) register(f *ast.File, name string) {
|
|||
b.Internal = true
|
||||
}
|
||||
b.SuiteName = f.Name.Name
|
||||
b.deleteMainFunc(f)
|
||||
deleteTestMainFunc(f)
|
||||
f.Name.Name = "main"
|
||||
b.registerContexts(f)
|
||||
b.files[name] = f
|
||||
}
|
||||
|
||||
func (b *builder) removeUnusedImports(f *ast.File) {
|
||||
used := b.usedPackages(f)
|
||||
isUsed := func(p string) bool {
|
||||
for _, ref := range used {
|
||||
if p == ref {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return p == "_"
|
||||
}
|
||||
var decls []ast.Decl
|
||||
for _, d := range f.Decls {
|
||||
gen, ok := d.(*ast.GenDecl)
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
var specs []ast.Spec
|
||||
for _, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
ipath := strings.Trim(impspec.Path.Value, `\"`)
|
||||
check := importPathToName(ipath)
|
||||
if impspec.Name != nil {
|
||||
check = impspec.Name.Name
|
||||
}
|
||||
|
||||
if isUsed(check) {
|
||||
specs = append(specs, spec)
|
||||
}
|
||||
}
|
||||
|
||||
if len(specs) == 0 {
|
||||
continue
|
||||
}
|
||||
gen.Specs = specs
|
||||
}
|
||||
decls = append(decls, d)
|
||||
}
|
||||
f.Decls = decls
|
||||
}
|
||||
|
||||
func (b *builder) deleteImports(f *ast.File) {
|
||||
var decls []ast.Decl
|
||||
for _, d := range f.Decls {
|
||||
gen, ok := d.(*ast.GenDecl)
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
for _, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
if !b.hasImport(impspec) {
|
||||
b.imports = append(b.imports, impspec)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
decls = append(decls, d)
|
||||
}
|
||||
f.Decls = decls
|
||||
}
|
||||
|
||||
func (b *builder) deleteMainFunc(f *ast.File) {
|
||||
var decls []ast.Decl
|
||||
var hadMain bool
|
||||
for _, d := range f.Decls {
|
||||
fun, ok := d.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
decls = append(decls, d)
|
||||
continue
|
||||
}
|
||||
if fun.Name.Name != "TestMain" {
|
||||
decls = append(decls, fun)
|
||||
} else {
|
||||
hadMain = true
|
||||
}
|
||||
}
|
||||
f.Decls = decls
|
||||
|
||||
if hadMain {
|
||||
b.removeUnusedImports(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) registerContexts(f *ast.File) {
|
||||
for _, d := range f.Decls {
|
||||
switch fun := d.(type) {
|
||||
|
@ -227,56 +75,61 @@ func (b *builder) registerContexts(f *ast.File) {
|
|||
}
|
||||
}
|
||||
|
||||
type visitFn func(node ast.Node) ast.Visitor
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
return fn(node)
|
||||
}
|
||||
|
||||
func (b *builder) usedPackages(f *ast.File) []string {
|
||||
var refs []string
|
||||
var visitor visitFn
|
||||
visitor = visitFn(func(node ast.Node) ast.Visitor {
|
||||
if node == nil {
|
||||
return visitor
|
||||
}
|
||||
switch v := node.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
xident, ok := v.X.(*ast.Ident)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if xident.Obj != nil {
|
||||
// if the parser can resolve it, it's not a package ref
|
||||
break
|
||||
}
|
||||
refs = append(refs, xident.Name)
|
||||
}
|
||||
return visitor
|
||||
})
|
||||
ast.Walk(visitor, f)
|
||||
return refs
|
||||
}
|
||||
|
||||
// Build creates a runnable Godog executable file
|
||||
// from current package source and test source files.
|
||||
// Build scans all go files in current directory,
|
||||
// copies them to temporary build directory.
|
||||
// If there is a TestMain func in any of test.go files
|
||||
// it removes it and all necessary unused imports related
|
||||
// to this function.
|
||||
//
|
||||
// The package files are merged with the help of go/ast into
|
||||
// a single main package file which has a custom
|
||||
// main function to run test suite features.
|
||||
// It also looks for any godog suite contexts and registers
|
||||
// them in order to call them on execution.
|
||||
//
|
||||
// Currently, to manage imports we use "golang.org/x/tools/imports"
|
||||
// package, but that may be replaced in order to have
|
||||
// no external dependencies
|
||||
// The test entry point which uses go1.4 TestMain func
|
||||
// is generated from the template above.
|
||||
func Build(dir string) error {
|
||||
return doBuild(".", dir)
|
||||
}
|
||||
fset := token.NewFileSet()
|
||||
b := &builder{files: make(map[string]*ast.File)}
|
||||
|
||||
// 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) {
|
||||
if buildPkg, err := build.Import(importPath, "", 0); err == nil {
|
||||
return buildPkg.Name
|
||||
err := filepath.Walk(".", func(path string, file os.FileInfo, err error) error {
|
||||
if file.IsDir() && file.Name() != "." {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if err == nil && strings.HasSuffix(path, ".go") {
|
||||
f, err := parser.ParseFile(fset, path, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.register(f, file.Name())
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return path.Base(importPath)
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
package godog
|
Загрузка…
Создание таблицы
Сослаться в новой задаче