forked from cucumber/godog
/
builder.go
172 lines (153 loc) · 3.51 KB
/
builder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package godog
import (
"bytes"
"go/ast"
"go/format"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"text/template"
"golang.org/x/tools/imports"
)
type builder struct {
files map[string]*ast.File
fset *token.FileSet
Contexts []string
Internal bool
tpl *template.Template
}
func newBuilder(buildPath string) (*builder, error) {
b := &builder{
files: make(map[string]*ast.File),
fset: token.NewFileSet(),
tpl: template.Must(template.New("main").Parse(`package main
{{ if not .Internal }}import (
"github.com/DATA-DOG/godog"
){{ end }}
func main() {
suite := {{ if not .Internal }}godog.{{ end }}New()
{{range .Contexts}}
{{ . }}(suite)
{{end}}
suite.Run()
}`)),
}
return b, 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") {
if err := b.parseFile(path); err != nil {
return err
}
}
return err
})
}
func (b *builder) parseFile(path string) error {
f, err := parser.ParseFile(b.fset, path, nil, 0)
if err != nil {
return err
}
// mark godog package as internal
if f.Name.Name == "godog" && !b.Internal {
b.Internal = true
}
b.deleteMainFunc(f)
b.registerSteps(f)
b.deleteImports(f)
b.files[path] = f
return nil
}
func (b *builder) deleteImports(f *ast.File) {
var decls []ast.Decl
for _, d := range f.Decls {
fun, ok := d.(*ast.GenDecl)
if !ok {
decls = append(decls, d)
continue
}
if fun.Tok != token.IMPORT {
decls = append(decls, fun)
}
}
f.Decls = decls
}
func (b *builder) deleteMainFunc(f *ast.File) {
var decls []ast.Decl
for _, d := range f.Decls {
fun, ok := d.(*ast.FuncDecl)
if !ok {
decls = append(decls, d)
continue
}
if fun.Name.Name != "main" {
decls = append(decls, fun)
}
}
f.Decls = decls
}
func (b *builder) registerSteps(f *ast.File) {
for _, d := range f.Decls {
switch fun := d.(type) {
case *ast.FuncDecl:
for _, param := range fun.Type.Params.List {
switch expr := param.Type.(type) {
case *ast.SelectorExpr:
switch x := expr.X.(type) {
case *ast.Ident:
if x.Name == "godog" && expr.Sel.Name == "Suite" {
b.Contexts = append(b.Contexts, fun.Name.Name)
}
}
case *ast.Ident:
if expr.Name == "Suite" {
b.Contexts = append(b.Contexts, fun.Name.Name)
}
}
}
}
}
}
func (b *builder) merge() (*ast.File, error) {
var buf bytes.Buffer
if err := b.tpl.Execute(&buf, b); err != nil {
return nil, err
}
f, err := parser.ParseFile(b.fset, "", &buf, 0)
if err != nil {
return nil, err
}
b.deleteImports(f)
b.files["main.go"] = f
pkg, _ := ast.NewPackage(b.fset, b.files, nil, nil)
pkg.Name = "main"
return ast.MergePackageFiles(pkg, ast.FilterImportDuplicates), nil
}
// Build creates a runnable Godog executable file
// from current package source and test source files.
//
// 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.
//
// Currently, to manage imports we use "golang.org/x/tools/imports"
// package, but that may be replaced in order to have
// no external dependencies
func Build() ([]byte, error) {
b, err := newBuilder(".")
if err != nil {
return nil, err
}
merged, err := b.merge()
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := format.Node(&buf, b.fset, merged); err != nil {
return nil, err
}
return imports.Process("", buf.Bytes(), nil)
}