Merge pull request #29 from DATA-DOG/try-testmain
build test binary instead of single file to execute, use TestMain
Этот коммит содержится в:
коммит
c25dedf000
13 изменённых файлов: 571 добавлений и 638 удалений
|
@ -1,8 +1,5 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
|
@ -14,6 +11,8 @@ script:
|
|||
- go get github.com/shiena/ansicolor
|
||||
|
||||
# run standard go tests
|
||||
- go vet ./...
|
||||
- go fmt ./...
|
||||
- go test -v
|
||||
- go test -race
|
||||
|
||||
|
|
34
README.md
34
README.md
|
@ -7,18 +7,27 @@
|
|||
|
||||
**The API is likely to change a few times before we reach 1.0.0**
|
||||
|
||||
**Godog** is an open source behavior-driven development framework for [go][golang] programming language.
|
||||
What is behavior-driven development, you ask? It’s the idea that you start by writing human-readable sentences that
|
||||
describe a feature of your application and how it should work, and only then implement this behavior in software.
|
||||
**Godog** is an open source behavior-driven development framework for
|
||||
[go][golang] programming language. What is behavior-driven development,
|
||||
you ask? It’s the idea that you start by writing human-readable sentences
|
||||
that describe a feature of your application and how it should work, and
|
||||
only then implement this behavior in software.
|
||||
|
||||
The project is inspired by [behat][behat] and [cucumber][cucumber] and is based on cucumber [gherkin3 parser][gherkin].
|
||||
The project is inspired by [behat][behat] and [cucumber][cucumber] and is
|
||||
based on cucumber [gherkin3 parser][gherkin].
|
||||
|
||||
**Godog** does not intervene with the standard **go test** command and its behavior. You can leverage both frameworks
|
||||
to functionally test your application while maintaining all test related source code in **_test.go** files.
|
||||
**Godog** does not intervene with the standard **go test** command and its
|
||||
behavior. You can leverage both frameworks to functionally test your
|
||||
application while maintaining all test related source code in **_test.go**
|
||||
files.
|
||||
|
||||
**Godog** acts similar compared to **go test** command. It builds all package sources to a single main package file
|
||||
and replaces **main** func with its own and runs the build to test described application behavior in feature files.
|
||||
Production builds remain clean without any test related source code.
|
||||
**Godog** acts similar compared to **go test** command. It uses
|
||||
a **TestMain** hook introduced in `go1.4` and clones the package sources
|
||||
to a temporary build directory. The only change it does is adding a runner
|
||||
test.go file additionally and ensures to cleanup TestMain func if it was
|
||||
used in tests. **Godog** uses standard **go** ast and build utils to
|
||||
generate test suite package and even builds it with **go test -c**
|
||||
command. It even passes all your environment exported vars.
|
||||
|
||||
### Install
|
||||
|
||||
|
@ -144,6 +153,13 @@ See implementation examples:
|
|||
|
||||
### Changes
|
||||
|
||||
**2016-05-25**
|
||||
- refactored test suite build tooling in order to use standard **go test**
|
||||
tool. Which allows to compile package with godog runner script in **go**
|
||||
idiomatic way. It also supports all build environment options as usual.
|
||||
- **godog.Run** now returns an **int** exit status. It was not returning
|
||||
anything before, so there is no compatibility breaks.
|
||||
|
||||
**2016-03-04**
|
||||
- added **junit** compatible output formatter, which prints **xml**
|
||||
results to **os.Stdout**
|
||||
|
|
137
ast.go
Обычный файл
137
ast.go
Обычный файл
|
@ -0,0 +1,137 @@
|
|||
package godog
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func astContexts(f *ast.File) []string {
|
||||
var contexts []string
|
||||
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.StarExpr:
|
||||
switch x := expr.X.(type) {
|
||||
case *ast.Ident:
|
||||
if x.Name == "Suite" {
|
||||
contexts = append(contexts, fun.Name.Name)
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
switch t := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
if t.Name == "godog" && x.Sel.Name == "Suite" {
|
||||
contexts = append(contexts, fun.Name.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return contexts
|
||||
}
|
||||
|
||||
func astRemoveUnusedImports(f *ast.File) {
|
||||
used := astUsedPackages(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 := astImportPathToName(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 astDeleteTestMainFunc(f *ast.File) {
|
||||
var decls []ast.Decl
|
||||
var hadTestMain 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 {
|
||||
hadTestMain = true
|
||||
}
|
||||
}
|
||||
f.Decls = decls
|
||||
|
||||
if hadTestMain {
|
||||
astRemoveUnusedImports(f)
|
||||
}
|
||||
}
|
||||
|
||||
type visitFn func(node ast.Node) ast.Visitor
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
return fn(node)
|
||||
}
|
||||
|
||||
func astUsedPackages(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 astImportPathToName(importPath string) (packageName string) {
|
||||
if buildPkg, err := build.Import(importPath, "", 0); err == nil {
|
||||
return buildPkg.Name
|
||||
}
|
||||
return path.Base(importPath)
|
||||
}
|
76
ast_context_test.go
Обычный файл
76
ast_context_test.go
Обычный файл
|
@ -0,0 +1,76 @@
|
|||
package godog
|
||||
|
||||
import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var astContextSrc = `package main
|
||||
|
||||
import (
|
||||
"github.com/DATA-DOG/godog"
|
||||
)
|
||||
|
||||
func myContext(s *godog.Suite) {
|
||||
}`
|
||||
|
||||
var astTwoContextSrc = `package lib
|
||||
|
||||
import (
|
||||
"github.com/DATA-DOG/godog"
|
||||
)
|
||||
|
||||
func apiContext(s *godog.Suite) {
|
||||
}
|
||||
|
||||
func dbContext(s *godog.Suite) {
|
||||
}`
|
||||
|
||||
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 astContexts(f)
|
||||
}
|
||||
|
||||
func TestShouldGetSingleContextFromSource(t *testing.T) {
|
||||
actual := astContextParse(astContextSrc, t)
|
||||
expect := []string{"myContext"}
|
||||
|
||||
if len(actual) != len(expect) {
|
||||
t.Fatalf("number of found contexts do not match, expected %d, but got %d", len(expect), len(actual))
|
||||
}
|
||||
|
||||
for i, c := range expect {
|
||||
if c != actual[i] {
|
||||
t.Fatalf("expected context '%s' at pos %d, but got: '%s'", c, i, actual[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldGetTwoContextsFromSource(t *testing.T) {
|
||||
actual := astContextParse(astTwoContextSrc, t)
|
||||
expect := []string{"apiContext", "dbContext"}
|
||||
|
||||
if len(actual) != len(expect) {
|
||||
t.Fatalf("number of found contexts do not match, expected %d, but got %d", len(expect), len(actual))
|
||||
}
|
||||
|
||||
for i, c := range expect {
|
||||
if c != actual[i] {
|
||||
t.Fatalf("expected context '%s' at pos %d, but got: '%s'", c, i, actual[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotFindAnyContextsInEmptyFile(t *testing.T) {
|
||||
actual := astContextParse(`package main`, t)
|
||||
|
||||
if len(actual) != 0 {
|
||||
t.Fatalf("expected no contexts to be found, but there was some: %v", actual)
|
||||
}
|
||||
}
|
162
ast_test.go
Обычный файл
162
ast_test.go
Обычный файл
|
@ -0,0 +1,162 @@
|
|||
package godog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var astMainFile = `package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("hello")
|
||||
}`
|
||||
|
||||
var astNormalFile = `package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func hello() {
|
||||
fmt.Println("hello")
|
||||
}`
|
||||
|
||||
var astTestMainFile = `package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"os"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
fmt.Println("hello")
|
||||
os.Exit(0)
|
||||
}`
|
||||
|
||||
var astPackAliases = `package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
a "fmt"
|
||||
b "fmt"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
a.Println("a")
|
||||
b.Println("b")
|
||||
}`
|
||||
|
||||
var astAnonymousImport = `package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
}`
|
||||
|
||||
var astLibrarySrc = `package lib
|
||||
|
||||
import "fmt"
|
||||
|
||||
func test() {
|
||||
fmt.Println("hello")
|
||||
}`
|
||||
|
||||
var astInternalPackageSrc = `package godog
|
||||
|
||||
import "fmt"
|
||||
|
||||
func test() {
|
||||
fmt.Println("hello")
|
||||
}`
|
||||
|
||||
func astProcess(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)
|
||||
}
|
||||
|
||||
astDeleteTestMainFunc(f)
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, fset, f); err != nil {
|
||||
t.Fatalf("failed to build source file: %v", err)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func TestShouldCleanTestMainFromSimpleTestFile(t *testing.T) {
|
||||
actual := strings.TrimSpace(astProcess(astTestMainFile, t))
|
||||
expect := `package main`
|
||||
|
||||
if actual != expect {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldCleanTestMainFromFileWithPackageAliases(t *testing.T) {
|
||||
actual := strings.TrimSpace(astProcess(astPackAliases, t))
|
||||
expect := `package main`
|
||||
|
||||
if actual != expect {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotModifyNormalFile(t *testing.T) {
|
||||
actual := strings.TrimSpace(astProcess(astNormalFile, t))
|
||||
expect := astNormalFile
|
||||
|
||||
if actual != expect {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotModifyMainFile(t *testing.T) {
|
||||
actual := strings.TrimSpace(astProcess(astMainFile, t))
|
||||
expect := astMainFile
|
||||
|
||||
if actual != expect {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldMaintainAnonymousImport(t *testing.T) {
|
||||
actual := strings.TrimSpace(astProcess(astAnonymousImport, t))
|
||||
expect := `package main
|
||||
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)`
|
||||
|
||||
if actual != expect {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotModifyLibraryPackageSource(t *testing.T) {
|
||||
actual := strings.TrimSpace(astProcess(astLibrarySrc, t))
|
||||
expect := astLibrarySrc
|
||||
|
||||
if actual != expect {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotModifyGodogPackageSource(t *testing.T) {
|
||||
actual := strings.TrimSpace(astProcess(astInternalPackageSrc, t))
|
||||
expect := astInternalPackageSrc
|
||||
|
||||
if actual != expect {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
508
builder.go
508
builder.go
|
@ -1,417 +1,171 @@
|
|||
package godog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type builder struct {
|
||||
files map[string]*ast.File
|
||||
fset *token.FileSet
|
||||
Contexts []string
|
||||
Internal bool
|
||||
tpl *template.Template
|
||||
var runnerTemplate = template.Must(template.New("main").Parse(`package {{ .Name }}
|
||||
|
||||
imports []*ast.ImportSpec
|
||||
}
|
||||
import (
|
||||
{{ if ne .Name "godog" }}"github.com/DATA-DOG/godog"{{ end }}
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
const GodogSuiteName = "{{ .Name }}"
|
||||
|
||||
func newBuilderSkel() *builder {
|
||||
return &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() {
|
||||
|
||||
{{ if not .Internal }}godog.{{ end }}Run(func (suite *{{ if not .Internal }}godog.{{ end }}Suite) {
|
||||
{{range .Contexts}}
|
||||
{{ . }}(suite)
|
||||
{{end}}
|
||||
func TestMain(m *testing.M) {
|
||||
status := {{ if ne .Name "godog" }}godog.{{ end }}Run(func (suite *{{ if ne .Name "godog" }}godog.{{ end }}Suite) {
|
||||
{{range .Contexts}}{{ . }}(suite){{end}}
|
||||
})
|
||||
}`)),
|
||||
os.Exit(status)
|
||||
}`))
|
||||
|
||||
// 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.
|
||||
//
|
||||
// It also looks for any godog suite contexts and registers
|
||||
// them in order to call them on execution.
|
||||
//
|
||||
// The test entry point which uses go1.4 TestMain func
|
||||
// is generated from the template above.
|
||||
func Build(dir string) error {
|
||||
pkg, err := build.ImportDir(".", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return buildTestPackage(pkg, dir)
|
||||
}
|
||||
|
||||
func newBuilder(buildPath string) (*builder, 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 {
|
||||
// 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, path)
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (b *builder) register(f *ast.File, path string) {
|
||||
// mark godog package as internal
|
||||
if f.Name.Name == "godog" && !b.Internal {
|
||||
b.Internal = true
|
||||
}
|
||||
b.deleteMainFunc(f)
|
||||
b.registerContexts(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 {
|
||||
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)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
continue
|
||||
}
|
||||
decls = append(decls, d)
|
||||
}
|
||||
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
|
||||
}
|
||||
astDeleteTestMainFunc(node)
|
||||
ctxs = append(ctxs, astContexts(node)...)
|
||||
|
||||
func (b *builder) registerContexts(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.StarExpr:
|
||||
switch x := expr.X.(type) {
|
||||
case *ast.Ident:
|
||||
if x.Name == "Suite" {
|
||||
b.Contexts = append(b.Contexts, fun.Name.Name)
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
switch t := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
if t.Name == "godog" && x.Sel.Name == "Suite" {
|
||||
b.Contexts = append(b.Contexts, fun.Name.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (b *builder) merge() ([]byte, 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)
|
||||
// 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 nil, err
|
||||
return
|
||||
}
|
||||
b.deleteImports(f)
|
||||
b.files["main.go"] = f
|
||||
|
||||
pkg, _ := ast.NewPackage(b.fset, b.files, nil, nil)
|
||||
pkg.Name = "main"
|
||||
|
||||
ret, err := ast.MergePackageFiles(pkg, 0), nil
|
||||
defer in.Close()
|
||||
if err = os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
return
|
||||
}
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
|
||||
// @TODO: we reread the file, probably something goes wrong with position
|
||||
buf.Reset()
|
||||
if err = format.Node(&buf, b.fset, ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret, err = parser.ParseFile(b.fset, "", buf.Bytes(), 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
used := b.usedPackages(ret)
|
||||
isUsed := func(p string) bool {
|
||||
for _, ref := range used {
|
||||
if p == ref {
|
||||
return true
|
||||
}
|
||||
defer func() {
|
||||
cerr := out.Close()
|
||||
if err == nil {
|
||||
err = cerr
|
||||
}
|
||||
return p == "_"
|
||||
}()
|
||||
if _, err = io.Copy(out, in); err != nil {
|
||||
return
|
||||
}
|
||||
for _, spec := range b.imports {
|
||||
var name string
|
||||
ipath := strings.Trim(spec.Path.Value, `\"`)
|
||||
check := importPathToName(ipath)
|
||||
if spec.Name != nil {
|
||||
name = spec.Name.Name
|
||||
check = spec.Name.Name
|
||||
}
|
||||
if isUsed(check) {
|
||||
addImport(b.fset, ret, name, ipath)
|
||||
}
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if err := format.Node(&buf, b.fset, ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), 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
|
||||
}
|
||||
|
||||
return b.merge()
|
||||
}
|
||||
|
||||
// taken from https://github.com/golang/tools/blob/master/go/ast/astutil/imports.go#L17
|
||||
func addImport(fset *token.FileSet, f *ast.File, name, ipath string) {
|
||||
newImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: strconv.Quote(ipath),
|
||||
},
|
||||
}
|
||||
if name != "" {
|
||||
newImport.Name = &ast.Ident{Name: name}
|
||||
}
|
||||
|
||||
// Find an import decl to add to.
|
||||
// The goal is to find an existing import
|
||||
// whose import path has the longest shared
|
||||
// prefix with ipath.
|
||||
var (
|
||||
bestMatch = -1 // length of longest shared prefix
|
||||
lastImport = -1 // index in f.Decls of the file's final import decl
|
||||
impDecl *ast.GenDecl // import decl containing the best match
|
||||
impIndex = -1 // spec index in impDecl containing the best match
|
||||
)
|
||||
for i, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
lastImport = i
|
||||
// Do not add to import "C", to avoid disrupting the
|
||||
// association with its doc comment, breaking cgo.
|
||||
if declImports(gen, "C") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Match an empty import decl if that's all that is available.
|
||||
if len(gen.Specs) == 0 && bestMatch == -1 {
|
||||
impDecl = gen
|
||||
}
|
||||
|
||||
// Compute longest shared prefix with imports in this group.
|
||||
for j, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
n := matchLen(importPath(impspec), ipath)
|
||||
if n > bestMatch {
|
||||
bestMatch = n
|
||||
impDecl = gen
|
||||
impIndex = j
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no import decl found, add one after the last import.
|
||||
if impDecl == nil {
|
||||
impDecl = &ast.GenDecl{
|
||||
Tok: token.IMPORT,
|
||||
}
|
||||
if lastImport >= 0 {
|
||||
impDecl.TokPos = f.Decls[lastImport].End()
|
||||
} else {
|
||||
// There are no existing imports.
|
||||
// Our new import goes after the package declaration and after
|
||||
// the comment, if any, that starts on the same line as the
|
||||
// package declaration.
|
||||
impDecl.TokPos = f.Package
|
||||
|
||||
file := fset.File(f.Package)
|
||||
pkgLine := file.Line(f.Package)
|
||||
for _, c := range f.Comments {
|
||||
if file.Line(c.Pos()) > pkgLine {
|
||||
break
|
||||
}
|
||||
impDecl.TokPos = c.End()
|
||||
}
|
||||
}
|
||||
f.Decls = append(f.Decls, nil)
|
||||
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
|
||||
f.Decls[lastImport+1] = impDecl
|
||||
}
|
||||
|
||||
// Insert new import at insertAt.
|
||||
insertAt := 0
|
||||
if impIndex >= 0 {
|
||||
// insert after the found import
|
||||
insertAt = impIndex + 1
|
||||
}
|
||||
impDecl.Specs = append(impDecl.Specs, nil)
|
||||
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
|
||||
impDecl.Specs[insertAt] = newImport
|
||||
pos := impDecl.Pos()
|
||||
if insertAt > 0 {
|
||||
// Assign same position as the previous import,
|
||||
// so that the sorter sees it as being in the same block.
|
||||
pos = impDecl.Specs[insertAt-1].Pos()
|
||||
}
|
||||
if newImport.Name != nil {
|
||||
newImport.Name.NamePos = pos
|
||||
}
|
||||
newImport.Path.ValuePos = pos
|
||||
newImport.EndPos = pos
|
||||
|
||||
// Clean up parens. impDecl contains at least one spec.
|
||||
if len(impDecl.Specs) == 1 {
|
||||
// Remove unneeded parens.
|
||||
impDecl.Lparen = token.NoPos
|
||||
} else if !impDecl.Lparen.IsValid() {
|
||||
// impDecl needs parens added.
|
||||
impDecl.Lparen = impDecl.Specs[0].Pos()
|
||||
}
|
||||
|
||||
f.Imports = append(f.Imports, newImport)
|
||||
}
|
||||
|
||||
func declImports(gen *ast.GenDecl, path string) bool {
|
||||
if gen.Tok != token.IMPORT {
|
||||
return false
|
||||
}
|
||||
for _, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
if importPath(impspec) == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchLen(x, y string) int {
|
||||
n := 0
|
||||
for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ {
|
||||
if x[i] == '/' {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func importPath(s *ast.ImportSpec) string {
|
||||
return strings.Trim(s.Path.Value, `\"`)
|
||||
}
|
||||
|
||||
var importPathToName = importPathToNameGoPath
|
||||
|
||||
// importPathToNameBasic assumes the package name is the base of import path.
|
||||
func importPathToNameBasic(importPath string) (packageName string) {
|
||||
return path.Base(importPath)
|
||||
}
|
||||
|
||||
// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
|
||||
// If there's a problem, it falls back to using importPathToNameBasic.
|
||||
func importPathToNameGoPath(importPath string) (packageName string) {
|
||||
if buildPkg, err := build.Import(importPath, "", 0); err == nil {
|
||||
return buildPkg.Name
|
||||
}
|
||||
return importPathToNameBasic(importPath)
|
||||
err = out.Sync()
|
||||
return
|
||||
}
|
||||
|
|
228
builder_test.go
228
builder_test.go
|
@ -1,228 +0,0 @@
|
|||
package godog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var builderMainFile = `
|
||||
package main
|
||||
import "fmt"
|
||||
func main() {
|
||||
fmt.Println("hello")
|
||||
}`
|
||||
|
||||
var builderPackAliases = `
|
||||
package main
|
||||
import (
|
||||
a "fmt"
|
||||
b "fmt"
|
||||
)
|
||||
func Tester() {
|
||||
a.Println("a")
|
||||
b.Println("b")
|
||||
}`
|
||||
|
||||
var builderAnonymousImport = `
|
||||
package main
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
`
|
||||
|
||||
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 (b *builder) registerMulti(contents []string) error {
|
||||
for i, c := range contents {
|
||||
f, err := parser.ParseFile(token.NewFileSet(), "", []byte(c), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.register(f, fmt.Sprintf("path%d", i))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builder) cleanSpacing(src string) string {
|
||||
var lines []string
|
||||
for _, ln := range strings.Split(src, "\n") {
|
||||
if ln == "" {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, strings.TrimSpace(ln))
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func TestUsualSourceFileMerge(t *testing.T) {
|
||||
if strings.HasPrefix(runtime.Version(), "go1.1") {
|
||||
t.Skip("skipping this test for go1.1")
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldCallContextOnMerged(t *testing.T) {
|
||||
b := newBuilderSkel()
|
||||
err := b.registerMulti([]string{
|
||||
builderMainFile, builderContextSrc,
|
||||
})
|
||||
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 "github.com/DATA-DOG/godog"
|
||||
|
||||
func main() {
|
||||
godog.Run(func(suite *godog.Suite) {
|
||||
myContext(suite)
|
||||
})
|
||||
}
|
||||
|
||||
func myContext(s *godog.Suite) {
|
||||
}`
|
||||
|
||||
actual := string(data)
|
||||
// log.Println("actual:", actual)
|
||||
// log.Println("expected:", expected)
|
||||
if b.cleanSpacing(expected) != b.cleanSpacing(actual) {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLibraryPackage(t *testing.T) {
|
||||
b := newBuilderSkel()
|
||||
err := b.registerMulti([]string{
|
||||
builderLibrarySrc,
|
||||
})
|
||||
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 (
|
||||
"fmt"
|
||||
"github.com/DATA-DOG/godog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
godog.Run(func(suite *godog.Suite) {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func test() {
|
||||
fmt.Println(
|
||||
"hello",
|
||||
)
|
||||
}`
|
||||
|
||||
actual := string(data)
|
||||
if b.cleanSpacing(expected) != b.cleanSpacing(actual) {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInternalPackage(t *testing.T) {
|
||||
b := newBuilderSkel()
|
||||
err := b.registerMulti([]string{
|
||||
builderInternalPackageSrc,
|
||||
})
|
||||
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 "fmt"
|
||||
|
||||
func main() {
|
||||
Run(func(suite *Suite) {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func test() {
|
||||
fmt.Println("hello")
|
||||
}`
|
||||
|
||||
actual := string(data)
|
||||
if b.cleanSpacing(expected) != b.cleanSpacing(actual) {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
@ -23,28 +24,33 @@ func buildAndRun() (int, error) {
|
|||
stdout := ansicolor.NewAnsiColorWriter(os.Stdout)
|
||||
stderr := ansicolor.NewAnsiColorWriter(statusOutputFilter(os.Stderr))
|
||||
|
||||
builtFile := fmt.Sprintf("%s/%dgodog.go", os.TempDir(), time.Now().UnixNano())
|
||||
|
||||
buf, err := godog.Build()
|
||||
dir := fmt.Sprintf(filepath.Join("%s", "godog-%d"), os.TempDir(), time.Now().UnixNano())
|
||||
err := godog.Build(dir)
|
||||
if err != nil {
|
||||
return status, err
|
||||
return 1, err
|
||||
}
|
||||
|
||||
w, err := os.Create(builtFile)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return status, err
|
||||
return 1, err
|
||||
}
|
||||
defer os.Remove(builtFile)
|
||||
bin := filepath.Join(wd, "godog.test")
|
||||
|
||||
if _, err = w.Write(buf); err != nil {
|
||||
w.Close()
|
||||
return status, err
|
||||
cmdb := exec.Command("go", "test", "-c", "-o", bin)
|
||||
cmdb.Dir = dir
|
||||
cmdb.Env = os.Environ()
|
||||
if details, err := cmdb.CombinedOutput(); err != nil {
|
||||
fmt.Println(string(details))
|
||||
return 1, err
|
||||
}
|
||||
w.Close()
|
||||
defer os.Remove(bin)
|
||||
|
||||
cmd := exec.Command("go", append([]string{"run", builtFile}, os.Args[1:]...)...)
|
||||
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
|
||||
|
@ -72,7 +78,8 @@ func buildAndRun() (int, error) {
|
|||
func main() {
|
||||
status, err := buildAndRun()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// it might be a case, that status might not be resolved
|
||||
// in some OSes. this is attempt to parse it from stderr
|
||||
|
|
6
flags.go
6
flags.go
|
@ -32,16 +32,16 @@ func usage() {
|
|||
|
||||
// --- GENERAL ---
|
||||
fmt.Println(cl("Usage:", yellow))
|
||||
fmt.Println(s(2) + "godog [options] [<paths>]\n")
|
||||
fmt.Println(s(2) + "godog [options] [<features>]\n")
|
||||
|
||||
// --- ARGUMENTS ---
|
||||
fmt.Println(cl("Arguments:", yellow))
|
||||
// --> paths
|
||||
fmt.Println(opt("paths", "Optional path(s) to execute. Can be:"))
|
||||
fmt.Println(opt("features", "Optional feature(s) to run. Can be:"))
|
||||
fmt.Println(opt("", s(4)+"- dir "+cl("(features/)", yellow)))
|
||||
fmt.Println(opt("", s(4)+"- feature "+cl("(*.feature)", yellow)))
|
||||
fmt.Println(opt("", s(4)+"- scenario at specific line "+cl("(*.feature:10)", yellow)))
|
||||
fmt.Println(opt("", "If no paths are listed, suite tries "+cl("features", yellow)+" path by default."))
|
||||
fmt.Println(opt("", "If no feature paths are listed, suite tries "+cl("features", yellow)+" path by default."))
|
||||
fmt.Println("")
|
||||
|
||||
// --- OPTIONS ---
|
||||
|
|
|
@ -4,6 +4,7 @@ import "gopkg.in/cucumber/gherkin-go.v3"
|
|||
|
||||
// examples is a helper func to cast gherkin.Examples
|
||||
// or gherkin.BaseExamples if its empty
|
||||
// @TODO: this should go away with gherkin update
|
||||
func examples(ex interface{}) (*gherkin.Examples, bool) {
|
||||
t, ok := ex.(*gherkin.Examples)
|
||||
return t, ok
|
||||
|
|
2
godog.go
2
godog.go
|
@ -44,4 +44,4 @@ Godog was inspired by Behat and the above description is taken from it's documen
|
|||
package godog
|
||||
|
||||
// Version of package - based on Semantic Versioning 2.0.0 http://semver.org/
|
||||
const Version = "v0.2.0"
|
||||
const Version = "v0.4.0"
|
||||
|
|
9
run.go
9
run.go
|
@ -52,7 +52,7 @@ func (r *runner) run() (failed bool) {
|
|||
//
|
||||
// contextInitializer must be able to register
|
||||
// the step definitions and event handlers.
|
||||
func Run(contextInitializer func(suite *Suite)) {
|
||||
func Run(contextInitializer func(suite *Suite)) int {
|
||||
var vers, defs, sof bool
|
||||
var tags, format string
|
||||
var concurrency int
|
||||
|
@ -63,12 +63,12 @@ func Run(contextInitializer func(suite *Suite)) {
|
|||
switch {
|
||||
case vers:
|
||||
fmt.Println(cl("Godog", green) + " version is " + cl(Version, yellow))
|
||||
return
|
||||
return 0
|
||||
case defs:
|
||||
s := &Suite{}
|
||||
contextInitializer(s)
|
||||
s.printStepDefinitions()
|
||||
return
|
||||
return 0
|
||||
}
|
||||
|
||||
paths := flagSet.Args()
|
||||
|
@ -99,6 +99,7 @@ func Run(contextInitializer func(suite *Suite)) {
|
|||
}
|
||||
|
||||
if failed := r.run(); failed {
|
||||
os.Exit(1)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
package godog
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/cucumber/gherkin-go.v3"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func SuiteContext(s *Suite) {
|
||||
c := &suiteContext{}
|
||||
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче