Merge pull request #40 from DATA-DOG/build-tools
Revisit godog suite compilation and build tools to support vendoring
Этот коммит содержится в:
коммит
5a17900dba
19 изменённых файлов: 311 добавлений и 474 удалений
|
@ -1,6 +1,5 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.4
|
|
||||||
- 1.5
|
- 1.5
|
||||||
- 1.6
|
- 1.6
|
||||||
- tip
|
- tip
|
||||||
|
|
23
README.md
23
README.md
|
@ -24,13 +24,9 @@ behavior. You can leverage both frameworks to functionally test your
|
||||||
application while maintaining all test related source code in **_test.go**
|
application while maintaining all test related source code in **_test.go**
|
||||||
files.
|
files.
|
||||||
|
|
||||||
**Godog** acts similar compared to **go test** command. It uses
|
**Godog** acts similar compared to **go test** command. It uses go
|
||||||
a **TestMain** hook introduced in `go1.4` and clones the package sources
|
compiler and linker tool in order to produce test executable. Godog
|
||||||
to a temporary build directory. The only change it does is adding a runner
|
contexts needs to be exported same as Test functions for go test.
|
||||||
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.
|
|
||||||
|
|
||||||
**Godog** ships gherkin parser dependency as a subpackage. This will
|
**Godog** ships gherkin parser dependency as a subpackage. This will
|
||||||
ensure that it is always compatible with the installed version of godog.
|
ensure that it is always compatible with the installed version of godog.
|
||||||
|
@ -111,7 +107,7 @@ Since we need a working implementation, we may start by implementing only what i
|
||||||
|
|
||||||
#### Step 3
|
#### Step 3
|
||||||
|
|
||||||
We only need a number of **godogs** for now. Let's define steps.
|
We only need a number of **godogs** for now. Lets keep it simple.
|
||||||
|
|
||||||
``` go
|
``` go
|
||||||
/* file: examples/godogs/godog.go */
|
/* file: examples/godogs/godog.go */
|
||||||
|
@ -125,7 +121,8 @@ func main() { /* usual main func */ }
|
||||||
|
|
||||||
#### Step 4
|
#### Step 4
|
||||||
|
|
||||||
Now let's finish our step implementations in order to test our feature requirements:
|
Now lets implement our step definitions, which we can copy from generated
|
||||||
|
console output snippets in order to test our feature requirements:
|
||||||
|
|
||||||
``` go
|
``` go
|
||||||
/* file: examples/godogs/godog_test.go */
|
/* file: examples/godogs/godog_test.go */
|
||||||
|
@ -157,7 +154,7 @@ func thereShouldBeRemaining(remaining int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func featureContext(s *godog.Suite) {
|
func FeatureContext(s *godog.Suite) {
|
||||||
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
|
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
|
||||||
s.Step(`^I eat (\d+)$`, iEat)
|
s.Step(`^I eat (\d+)$`, iEat)
|
||||||
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
||||||
|
@ -193,6 +190,12 @@ See implementation examples:
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
**2016-06-14**
|
||||||
|
- godog now uses **go tool compile** and **go tool link** to support
|
||||||
|
vendor directory dependencies. It also compiles test executable the same
|
||||||
|
way as standard **go test** utility. With this change, only go
|
||||||
|
versions from **1.5** are now supported.
|
||||||
|
|
||||||
**2016-06-01**
|
**2016-06-01**
|
||||||
- parse flags in main command, to show version and help without needing
|
- parse flags in main command, to show version and help without needing
|
||||||
to compile test package and buildable go sources.
|
to compile test package and buildable go sources.
|
||||||
|
|
108
ast.go
108
ast.go
|
@ -1,12 +1,6 @@
|
||||||
package godog
|
package godog
|
||||||
|
|
||||||
import (
|
import "go/ast"
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/token"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func astContexts(f *ast.File) []string {
|
func astContexts(f *ast.File) []string {
|
||||||
var contexts []string
|
var contexts []string
|
||||||
|
@ -35,103 +29,3 @@ func astContexts(f *ast.File) []string {
|
||||||
}
|
}
|
||||||
return contexts
|
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
160
ast_test.go
160
ast_test.go
|
@ -1,162 +1,76 @@
|
||||||
package godog
|
package godog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"go/format"
|
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var astMainFile = `package main
|
var astContextSrc = `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 (
|
import (
|
||||||
"fmt"
|
"github.com/DATA-DOG/godog"
|
||||||
"testing"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func MyContext(s *godog.Suite) {
|
||||||
fmt.Println("hello")
|
|
||||||
os.Exit(0)
|
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var astPackAliases = `package main
|
var astTwoContextSrc = `package lib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"github.com/DATA-DOG/godog"
|
||||||
a "fmt"
|
|
||||||
b "fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func ApiContext(s *godog.Suite) {
|
||||||
a.Println("a")
|
}
|
||||||
b.Println("b")
|
|
||||||
|
func DBContext(s *godog.Suite) {
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var astAnonymousImport = `package main
|
func astContextParse(src string, t *testing.T) []string {
|
||||||
|
|
||||||
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()
|
fset := token.NewFileSet()
|
||||||
f, err := parser.ParseFile(fset, "", []byte(src), 0)
|
f, err := parser.ParseFile(fset, "", []byte(src), 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error while parsing ast: %v", err)
|
t.Fatalf("unexpected error while parsing ast: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
astDeleteTestMainFunc(f)
|
return astContexts(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) {
|
func TestShouldGetSingleContextFromSource(t *testing.T) {
|
||||||
actual := strings.TrimSpace(astProcess(astTestMainFile, t))
|
actual := astContextParse(astContextSrc, t)
|
||||||
expect := `package main`
|
expect := []string{"MyContext"}
|
||||||
|
|
||||||
if actual != expect {
|
if len(actual) != len(expect) {
|
||||||
t.Fatalf("expected output does not match: %s", actual)
|
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 TestShouldCleanTestMainFromFileWithPackageAliases(t *testing.T) {
|
func TestShouldGetTwoContextsFromSource(t *testing.T) {
|
||||||
actual := strings.TrimSpace(astProcess(astPackAliases, t))
|
actual := astContextParse(astTwoContextSrc, t)
|
||||||
expect := `package main`
|
expect := []string{"ApiContext", "DBContext"}
|
||||||
|
|
||||||
if actual != expect {
|
if len(actual) != len(expect) {
|
||||||
t.Fatalf("expected output does not match: %s", actual)
|
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 TestShouldNotModifyNormalFile(t *testing.T) {
|
func TestShouldNotFindAnyContextsInEmptyFile(t *testing.T) {
|
||||||
actual := strings.TrimSpace(astProcess(astNormalFile, t))
|
actual := astContextParse(`package main`, t)
|
||||||
expect := astNormalFile
|
|
||||||
|
|
||||||
if actual != expect {
|
if len(actual) != 0 {
|
||||||
t.Fatalf("expected output does not match: %s", actual)
|
t.Fatalf("expected no contexts to be found, but there was some: %v", 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
353
builder.go
353
builder.go
|
@ -1,118 +1,268 @@
|
||||||
package godog
|
package godog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/format"
|
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var runnerTemplate = template.Must(template.New("main").Parse(`package {{ .Name }}
|
var compiler = filepath.Join(build.ToolDir, "compile")
|
||||||
|
var linker = filepath.Join(build.ToolDir, "link")
|
||||||
|
var gopaths = filepath.SplitList(build.Default.GOPATH)
|
||||||
|
var goarch = build.Default.GOARCH
|
||||||
|
var goos = build.Default.GOOS
|
||||||
|
|
||||||
|
var godogImportPath = "github.com/DATA-DOG/godog"
|
||||||
|
var runnerTemplate = template.Must(template.New("testmain").Parse(`package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
{{ if ne .Name "godog" }}"github.com/DATA-DOG/godog"{{ end }}
|
"github.com/DATA-DOG/godog"
|
||||||
|
{{if .Contexts}}_test "{{.ImportPath}}"{{end}}
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const GodogSuiteName = "{{ .Name }}"
|
func main() {
|
||||||
|
status := godog.Run(func (suite *godog.Suite) {
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
status := {{ if ne .Name "godog" }}godog.{{ end }}Run(func (suite *{{ if ne .Name "godog" }}godog.{{ end }}Suite) {
|
|
||||||
{{range .Contexts}}
|
{{range .Contexts}}
|
||||||
{{ . }}(suite)
|
_test.{{ . }}(suite)
|
||||||
{{end}}
|
{{end}}
|
||||||
})
|
})
|
||||||
os.Exit(status)
|
os.Exit(status)
|
||||||
}`))
|
}`))
|
||||||
|
|
||||||
// Build scans clones current package into a temporary
|
// Build creates a test package like go test command.
|
||||||
// godog suite test package.
|
// If there are no go files in tested directory, then
|
||||||
|
// it simply builds a godog executable to scan features.
|
||||||
//
|
//
|
||||||
// If there is a TestMain func in any of test.go files
|
// If there are go test files, it first builds a test
|
||||||
// it removes it and all necessary unused imports related
|
// package with standard go test command.
|
||||||
// to this function.
|
|
||||||
//
|
//
|
||||||
// It also looks for any godog suite contexts and registers
|
// Finally it generates godog suite executable which
|
||||||
// them in order to call them on execution.
|
// registers exported godog contexts from the test files
|
||||||
|
// of tested package.
|
||||||
//
|
//
|
||||||
// The test entry point which uses go1.4 TestMain func
|
// Returns the path to generated executable
|
||||||
// is generated from the template above.
|
func Build() (string, error) {
|
||||||
func Build(dir string) error {
|
abs, err := filepath.Abs(".")
|
||||||
pkg, err := build.ImportDir(".", 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildTestPackage(pkg, dir)
|
bin := filepath.Join(abs, "godog.test")
|
||||||
}
|
// suffix with .exe for windows
|
||||||
|
if goos == "windows" {
|
||||||
// buildTestPackage clones a package and adds a godog
|
bin += ".exe"
|
||||||
// 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(
|
// we allow package to be nil, if godog is run only when
|
||||||
dir,
|
// there is a feature file in empty directory
|
||||||
pkg.TestGoFiles,
|
pkg, _ := build.ImportDir(abs, 0)
|
||||||
pkg.XTestGoFiles,
|
src, anyContexts, err := buildTestMain(pkg)
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return bin, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// build godog runner test file
|
workdir := fmt.Sprintf(filepath.Join("%s", "godog-%d"), os.TempDir(), time.Now().UnixNano())
|
||||||
out, err := os.Create(filepath.Join(dir, "godog_runner_test.go"))
|
testdir := workdir
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
data := struct {
|
// if none of test files exist, or there are no contexts found
|
||||||
Name string
|
// we will skip test package compilation, since it is useless
|
||||||
Contexts []string
|
if anyContexts {
|
||||||
}{pkg.Name, contexts}
|
// first of all compile test package dependencies
|
||||||
|
// that will save was many compilations for dependencies
|
||||||
|
// go does it better
|
||||||
|
out, err := exec.Command("go", "test", "-i").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return bin, fmt.Errorf("failed to compile package %s:\n%s", pkg.Name, string(out))
|
||||||
|
}
|
||||||
|
|
||||||
return runnerTemplate.Execute(out, data)
|
// let go do the dirty work and compile test
|
||||||
}
|
// package with it's dependencies. Older go
|
||||||
|
// versions does not accept existing file output
|
||||||
|
// so we create a temporary executable which will
|
||||||
|
// removed.
|
||||||
|
temp := fmt.Sprintf(filepath.Join("%s", "temp-%d.test"), os.TempDir(), time.Now().UnixNano())
|
||||||
|
|
||||||
// copyNonTestPackageFiles simply copies all given file packs
|
// builds and compile the tested package.
|
||||||
// to the destDir.
|
// generated test executable will be removed
|
||||||
func copyNonTestPackageFiles(destDir string, packs ...[]string) error {
|
// since we do not need it for godog suite.
|
||||||
for _, pack := range packs {
|
// we also print back the temp WORK directory
|
||||||
for _, file := range pack {
|
// go has built. We will reuse it for our suite workdir.
|
||||||
if err := copyPackageFile(file, filepath.Join(destDir, file)); err != nil {
|
out, err = exec.Command("go", "test", "-c", "-work", "-o", temp).CombinedOutput()
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return bin, fmt.Errorf("failed to compile tested package %s:\n%s", pkg.Name, string(out))
|
||||||
|
}
|
||||||
|
defer os.Remove(temp)
|
||||||
|
|
||||||
|
// extract go-build temporary directory as our workdir
|
||||||
|
workdir = strings.TrimSpace(string(out))
|
||||||
|
if !strings.HasPrefix(workdir, "WORK=") {
|
||||||
|
return bin, fmt.Errorf("expected WORK dir path, but got: %s", workdir)
|
||||||
|
}
|
||||||
|
workdir = strings.Replace(workdir, "WORK=", "", 1)
|
||||||
|
testdir = filepath.Join(workdir, pkg.ImportPath, "_test")
|
||||||
|
} else {
|
||||||
|
// still need to create temporary workdir
|
||||||
|
if err = os.MkdirAll(testdir, 0755); err != nil {
|
||||||
|
return bin, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
defer os.RemoveAll(workdir)
|
||||||
|
|
||||||
|
// replace _testmain.go file with our own
|
||||||
|
testmain := filepath.Join(testdir, "_testmain.go")
|
||||||
|
err = ioutil.WriteFile(testmain, src, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return bin, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// godog library may not be imported in tested package
|
||||||
|
// but we need it for our testmain package.
|
||||||
|
// So we look it up in available source paths
|
||||||
|
// including vendor directory, supported since 1.5.
|
||||||
|
try := []string{filepath.Join(abs, "vendor", godogImportPath)}
|
||||||
|
for _, d := range build.Default.SrcDirs() {
|
||||||
|
try = append(try, filepath.Join(d, godogImportPath))
|
||||||
|
}
|
||||||
|
godogPkg, err := locatePackage(try)
|
||||||
|
if err != nil {
|
||||||
|
return bin, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure godog package archive is installed, gherkin
|
||||||
|
// will be installed as dependency of godog
|
||||||
|
cmd := exec.Command("go", "install", godogPkg.ImportPath)
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return bin, fmt.Errorf("failed to install godog package:\n%s", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect all possible package dirs, will be
|
||||||
|
// used for includes and linker
|
||||||
|
pkgDirs := []string{workdir, testdir}
|
||||||
|
for _, gopath := range gopaths {
|
||||||
|
pkgDirs = append(pkgDirs, filepath.Join(gopath, "pkg", goos+"_"+goarch))
|
||||||
|
}
|
||||||
|
pkgDirs = uniqStringList(pkgDirs)
|
||||||
|
|
||||||
|
// compile godog testmain package archive
|
||||||
|
// we do not depend on CGO so a lot of checks are not necessary
|
||||||
|
testMainPkgOut := filepath.Join(testdir, "main.a")
|
||||||
|
args := []string{
|
||||||
|
"-o", testMainPkgOut,
|
||||||
|
// "-trimpath", workdir,
|
||||||
|
"-p", "main",
|
||||||
|
"-complete",
|
||||||
|
}
|
||||||
|
// if godog library is in vendor directory
|
||||||
|
// link it with import map
|
||||||
|
if i := strings.LastIndex(godogPkg.ImportPath, "vendor/"); i != -1 {
|
||||||
|
args = append(args, "-importmap", godogImportPath+"="+godogPkg.ImportPath)
|
||||||
|
}
|
||||||
|
for _, inc := range pkgDirs {
|
||||||
|
args = append(args, "-I", inc)
|
||||||
|
}
|
||||||
|
args = append(args, "-pack", testmain)
|
||||||
|
cmd = exec.Command(compiler, args...)
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return bin, fmt.Errorf("failed to compile testmain package:\n%s", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// link test suite executable
|
||||||
|
args = []string{
|
||||||
|
"-o", bin,
|
||||||
|
"-extld", build.Default.Compiler,
|
||||||
|
"-buildmode=exe",
|
||||||
|
}
|
||||||
|
for _, link := range pkgDirs {
|
||||||
|
args = append(args, "-L", link)
|
||||||
|
}
|
||||||
|
args = append(args, testMainPkgOut)
|
||||||
|
cmd = exec.Command(linker, args...)
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return bin, fmt.Errorf("failed to link test executable:\n%s", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
return bin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func locatePackage(try []string) (*build.Package, error) {
|
||||||
|
for _, path := range try {
|
||||||
|
abs, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkg, err := build.ImportDir(abs, 0)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return pkg, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to find godog package in any of:\n%s", strings.Join(try, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqStringList(strs []string) (unique []string) {
|
||||||
|
uniq := make(map[string]void, len(strs))
|
||||||
|
for _, s := range strs {
|
||||||
|
if _, ok := uniq[s]; !ok {
|
||||||
|
uniq[s] = void{}
|
||||||
|
unique = append(unique, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildTestMain if given package is valid
|
||||||
|
// it scans test files for contexts
|
||||||
|
// and produces a testmain source code.
|
||||||
|
func buildTestMain(pkg *build.Package) ([]byte, bool, error) {
|
||||||
|
var contexts []string
|
||||||
|
var importPath string
|
||||||
|
if nil != pkg {
|
||||||
|
ctxs, err := processPackageTestFiles(
|
||||||
|
pkg.TestGoFiles,
|
||||||
|
pkg.XTestGoFiles,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
contexts = ctxs
|
||||||
|
importPath = pkg.ImportPath
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Name string
|
||||||
|
Contexts []string
|
||||||
|
ImportPath string
|
||||||
|
}{pkg.Name, contexts, importPath}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := runnerTemplate.Execute(&buf, data); err != nil {
|
||||||
|
return nil, len(contexts) > 0, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), len(contexts) > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processPackageTestFiles runs through ast of each test
|
// processPackageTestFiles runs through ast of each test
|
||||||
// file pack and removes TestMain func if found. it also
|
// file pack and looks for godog suite contexts to register
|
||||||
// looks for godog suite contexts to register on run
|
// on run
|
||||||
func processPackageTestFiles(destDir string, packs ...[]string) ([]string, error) {
|
func processPackageTestFiles(packs ...[]string) ([]string, error) {
|
||||||
var ctxs []string
|
var ctxs []string
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
for _, pack := range packs {
|
for _, pack := range packs {
|
||||||
|
@ -122,52 +272,19 @@ func processPackageTestFiles(destDir string, packs ...[]string) ([]string, error
|
||||||
return ctxs, err
|
return ctxs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
astDeleteTestMainFunc(node)
|
|
||||||
ctxs = append(ctxs, astContexts(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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var failed []string
|
||||||
|
for _, ctx := range ctxs {
|
||||||
|
runes := []rune(ctx)
|
||||||
|
if unicode.IsLower(runes[0]) {
|
||||||
|
expected := append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...)
|
||||||
|
failed = append(failed, fmt.Sprintf("%s - should be: %s", ctx, string(expected)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(failed) > 0 {
|
||||||
|
return ctxs, fmt.Errorf("godog contexts must be exported:\n\t%s", strings.Join(failed, "\n\t"))
|
||||||
|
}
|
||||||
return ctxs, nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,11 +5,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/DATA-DOG/godog"
|
"github.com/DATA-DOG/godog"
|
||||||
)
|
)
|
||||||
|
@ -23,27 +21,10 @@ var stderr = statusOutputFilter(os.Stderr)
|
||||||
func buildAndRun() (int, error) {
|
func buildAndRun() (int, error) {
|
||||||
var status int
|
var status int
|
||||||
|
|
||||||
dir := fmt.Sprintf(filepath.Join("%s", "godog-%d"), os.TempDir(), time.Now().UnixNano())
|
bin, err := godog.Build()
|
||||||
err := godog.Build(dir)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1, err
|
return 1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return 1, err
|
|
||||||
}
|
|
||||||
bin := filepath.Join(wd, "godog.test")
|
|
||||||
|
|
||||||
cmdb := exec.Command("go", "test", "-c", "-o", bin)
|
|
||||||
cmdb.Dir = dir
|
|
||||||
cmdb.Env = os.Environ()
|
|
||||||
if details, err := cmdb.CombinedOutput(); err != nil {
|
|
||||||
fmt.Fprintln(stderr, string(details))
|
|
||||||
return 1, err
|
|
||||||
}
|
|
||||||
defer os.Remove(bin)
|
defer os.Remove(bin)
|
||||||
|
|
||||||
cmd := exec.Command(bin, os.Args[1:]...)
|
cmd := exec.Command(bin, os.Args[1:]...)
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (err er
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func featureContext(s *godog.Suite) {
|
func FeatureContext(s *godog.Suite) {
|
||||||
api := &apiFeature{}
|
api := &apiFeature{}
|
||||||
|
|
||||||
s.BeforeScenario(api.resetResponse)
|
s.BeforeScenario(api.resetResponse)
|
||||||
|
|
|
@ -20,6 +20,6 @@ Feature: get version
|
||||||
And the response should match json:
|
And the response should match json:
|
||||||
"""
|
"""
|
||||||
{
|
{
|
||||||
"version": "v0.4.3"
|
"version": "v0.5.0"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-txdb"
|
|
||||||
"github.com/DATA-DOG/godog"
|
"github.com/DATA-DOG/godog"
|
||||||
"github.com/DATA-DOG/godog/gherkin"
|
"github.com/DATA-DOG/godog/gherkin"
|
||||||
)
|
)
|
||||||
|
@ -118,7 +117,7 @@ func (a *apiFeature) thereAreUsers(users *gherkin.DataTable) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func featureContext(s *godog.Suite) {
|
func FeatureContext(s *godog.Suite) {
|
||||||
api := &apiFeature{}
|
api := &apiFeature{}
|
||||||
|
|
||||||
s.BeforeScenario(api.resetResponse)
|
s.BeforeScenario(api.resetResponse)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* file: examples/godogs/godog.go */
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// Godogs to eat
|
// Godogs to eat
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* file: examples/godogs/godog_test.go */
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -26,7 +27,7 @@ func thereShouldBeRemaining(remaining int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func featureContext(s *godog.Suite) {
|
func FeatureContext(s *godog.Suite) {
|
||||||
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
|
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
|
||||||
s.Step(`^I eat (\d+)$`, iEat)
|
s.Step(`^I eat (\d+)$`, iEat)
|
||||||
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
||||||
|
|
|
@ -28,7 +28,7 @@ Feature: undefined step snippets
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
|
|
||||||
func featureContext(s *godog.Suite) {
|
func FeatureContext(s *godog.Suite) {
|
||||||
s.Step(`^I send "([^"]*)" request to "([^"]*)"$`, iSendRequestTo)
|
s.Step(`^I send "([^"]*)" request to "([^"]*)"$`, iSendRequestTo)
|
||||||
s.Step(`^the response code should be (\d+)$`, theResponseCodeShouldBe)
|
s.Step(`^the response code should be (\d+)$`, theResponseCodeShouldBe)
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ Feature: undefined step snippets
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
|
|
||||||
func featureContext(s *godog.Suite) {
|
func FeatureContext(s *godog.Suite) {
|
||||||
s.Step(`^I send "([^"]*)" request to "([^"]*)" with:$`, iSendRequestToWith)
|
s.Step(`^I send "([^"]*)" request to "([^"]*)" with:$`, iSendRequestToWith)
|
||||||
s.Step(`^the response code should be (\d+) and header "([^"]*)" should be "([^"]*)"$`, theResponseCodeShouldBeAndHeaderShouldBe)
|
s.Step(`^the response code should be (\d+) and header "([^"]*)" should be "([^"]*)"$`, theResponseCodeShouldBeAndHeaderShouldBe)
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ Feature: undefined step snippets
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
|
|
||||||
func featureContext(s *godog.Suite) {
|
func FeatureContext(s *godog.Suite) {
|
||||||
s.Step(`^I pull from github\.com$`, iPullFromGithubcom)
|
s.Step(`^I pull from github\.com$`, iPullFromGithubcom)
|
||||||
s.Step(`^the project should be there$`, theProjectShouldBeThere)
|
s.Step(`^the project should be there$`, theProjectShouldBeThere)
|
||||||
}
|
}
|
||||||
|
|
2
fmt.go
2
fmt.go
|
@ -30,7 +30,7 @@ var undefinedSnippetsTpl = template.Must(template.New("snippets").Funcs(snippetH
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
|
|
||||||
{{end}}func featureContext(s *godog.Suite) { {{ range . }}
|
{{end}}func FeatureContext(s *godog.Suite) { {{ range . }}
|
||||||
s.Step({{ backticked .Expr }}, {{ .Method }}){{end}}
|
s.Step({{ backticked .Expr }}, {{ .Method }}){{end}}
|
||||||
}
|
}
|
||||||
`))
|
`))
|
||||||
|
|
12
godog.go
12
godog.go
|
@ -6,13 +6,9 @@ Godog does not intervene with the standard "go test" command and it's behavior.
|
||||||
You can leverage both frameworks to functionally test your application while
|
You can leverage both frameworks to functionally test your application while
|
||||||
maintaining all test related source code in *_test.go files.
|
maintaining all test related source code in *_test.go files.
|
||||||
|
|
||||||
Godog acts similar compared to go test command. It leverages
|
Godog acts similar compared to go test command. It uses go
|
||||||
a TestMain function introduced in go1.4 and clones the package sources
|
compiler and linker tool in order to produce test executable. Godog
|
||||||
to a temporary build directory. The only change it does is adding a runner
|
contexts needs to be exported same as Test functions for go test.
|
||||||
test.go file and replaces TestMain func if it was used in tests.
|
|
||||||
Godog uses standard go ast and build utils to generate test suite package,
|
|
||||||
compiles it with go test -c command. It accepts all your environment exported
|
|
||||||
build related vars.
|
|
||||||
|
|
||||||
For example, imagine you’re about to create the famous UNIX ls command.
|
For example, imagine you’re about to create the famous UNIX ls command.
|
||||||
Before you begin, you describe how the feature should work, see the example below..
|
Before you begin, you describe how the feature should work, see the example below..
|
||||||
|
@ -46,4 +42,4 @@ Godog was inspired by Behat and Cucumber the above description is taken from it'
|
||||||
package godog
|
package godog
|
||||||
|
|
||||||
// Version of package - based on Semantic Versioning 2.0.0 http://semver.org/
|
// Version of package - based on Semantic Versioning 2.0.0 http://semver.org/
|
||||||
const Version = "v0.4.3"
|
const Version = "v0.5.0"
|
||||||
|
|
Двоичные данные
screenshots/passed.png
Двоичные данные
screenshots/passed.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 54 КиБ После Ширина: | Высота: | Размер: 80 КиБ |
Двоичные данные
screenshots/undefined.png
Двоичные данные
screenshots/undefined.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 109 КиБ После Ширина: | Высота: | Размер: 105 КиБ |
|
@ -2,6 +2,7 @@ package godog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -215,8 +216,12 @@ func (s *suiteContext) iShouldHaveNumFeatureFiles(num int, files *gherkin.DocStr
|
||||||
return fmt.Errorf("expected %d feature paths to be parsed, but have %d", len(expected), len(actual))
|
return fmt.Errorf("expected %d feature paths to be parsed, but have %d", len(expected), len(actual))
|
||||||
}
|
}
|
||||||
for i := 0; i < len(expected); i++ {
|
for i := 0; i < len(expected); i++ {
|
||||||
if expected[i] != actual[i] {
|
split := strings.Split(expected[i], "/")
|
||||||
return fmt.Errorf(`expected feature path "%s" at position: %d, does not match actual "%s"`, expected[i], i, actual[i])
|
exp := filepath.Join(split...)
|
||||||
|
split = strings.Split(actual[i], "/")
|
||||||
|
act := filepath.Join(split...)
|
||||||
|
if exp != act {
|
||||||
|
return fmt.Errorf(`expected feature path "%s" at position: %d, does not match actual "%s"`, exp, i, act)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
3
utils.go
3
utils.go
|
@ -6,6 +6,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// empty struct value takes no space allocation
|
||||||
|
type void struct{}
|
||||||
|
|
||||||
// a color code type
|
// a color code type
|
||||||
type color int
|
type color int
|
||||||
|
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче