Merge pull request #40 from DATA-DOG/build-tools

Revisit godog suite compilation and build tools to support vendoring
Этот коммит содержится в:
Gediminas Morkevicius 2016-06-15 22:15:33 +03:00 коммит произвёл GitHub
родитель 2162380725 58c203d2e7
коммит 5a17900dba
19 изменённых файлов: 311 добавлений и 474 удалений

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

@ -1,6 +1,5 @@
language: go
go:
- 1.4
- 1.5
- 1.6
- tip

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

@ -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**
files.
**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.
**Godog** acts similar compared to **go test** command. It uses go
compiler and linker tool in order to produce test executable. Godog
contexts needs to be exported same as Test functions for go test.
**Godog** ships gherkin parser dependency as a subpackage. This will
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
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
/* file: examples/godogs/godog.go */
@ -125,7 +121,8 @@ func main() { /* usual main func */ }
#### 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
/* file: examples/godogs/godog_test.go */
@ -157,7 +154,7 @@ func thereShouldBeRemaining(remaining int) error {
return nil
}
func featureContext(s *godog.Suite) {
func FeatureContext(s *godog.Suite) {
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
s.Step(`^I eat (\d+)$`, iEat)
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
@ -193,6 +190,12 @@ See implementation examples:
### 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**
- parse flags in main command, to show version and help without needing
to compile test package and buildable go sources.

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

@ -1,12 +1,6 @@
package godog
import (
"go/ast"
"go/build"
"go/token"
"path"
"strings"
)
import "go/ast"
func astContexts(f *ast.File) []string {
var contexts []string
@ -35,103 +29,3 @@ func astContexts(f *ast.File) []string {
}
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)
}
}

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

@ -1,162 +1,76 @@
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
var astContextSrc = `package main
import (
"fmt"
"testing"
"os"
"github.com/DATA-DOG/godog"
)
func TestMain(m *testing.M) {
fmt.Println("hello")
os.Exit(0)
func MyContext(s *godog.Suite) {
}`
var astPackAliases = `package main
var astTwoContextSrc = `package lib
import (
"testing"
a "fmt"
b "fmt"
"github.com/DATA-DOG/godog"
)
func TestMain(m *testing.M) {
a.Println("a")
b.Println("b")
func ApiContext(s *godog.Suite) {
}
func DBContext(s *godog.Suite) {
}`
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 {
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)
}
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()
return astContexts(f)
}
func TestShouldCleanTestMainFromSimpleTestFile(t *testing.T) {
actual := strings.TrimSpace(astProcess(astTestMainFile, t))
expect := `package main`
func TestShouldGetSingleContextFromSource(t *testing.T) {
actual := astContextParse(astContextSrc, t)
expect := []string{"MyContext"}
if actual != expect {
t.Fatalf("expected output does not match: %s", actual)
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 TestShouldCleanTestMainFromFileWithPackageAliases(t *testing.T) {
actual := strings.TrimSpace(astProcess(astPackAliases, t))
expect := `package main`
func TestShouldGetTwoContextsFromSource(t *testing.T) {
actual := astContextParse(astTwoContextSrc, t)
expect := []string{"ApiContext", "DBContext"}
if actual != expect {
t.Fatalf("expected output does not match: %s", actual)
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 TestShouldNotModifyNormalFile(t *testing.T) {
actual := strings.TrimSpace(astProcess(astNormalFile, t))
expect := astNormalFile
func TestShouldNotFindAnyContextsInEmptyFile(t *testing.T) {
actual := astContextParse(`package main`, t)
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)
if len(actual) != 0 {
t.Fatalf("expected no contexts to be found, but there was some: %v", actual)
}
}

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

@ -1,118 +1,268 @@
package godog
import (
"bytes"
"fmt"
"go/build"
"go/format"
"go/parser"
"go/token"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"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 (
{{ if ne .Name "godog" }}"github.com/DATA-DOG/godog"{{ end }}
"github.com/DATA-DOG/godog"
{{if .Contexts}}_test "{{.ImportPath}}"{{end}}
"os"
"testing"
)
const GodogSuiteName = "{{ .Name }}"
func TestMain(m *testing.M) {
status := {{ if ne .Name "godog" }}godog.{{ end }}Run(func (suite *{{ if ne .Name "godog" }}godog.{{ end }}Suite) {
func main() {
status := godog.Run(func (suite *godog.Suite) {
{{range .Contexts}}
{{ . }}(suite)
_test.{{ . }}(suite)
{{end}}
})
os.Exit(status)
}`))
// Build scans clones current package into a temporary
// godog suite test package.
// Build creates a test package like go test command.
// 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
// it removes it and all necessary unused imports related
// to this function.
// If there are go test files, it first builds a test
// package with standard go test command.
//
// It also looks for any godog suite contexts and registers
// them in order to call them on execution.
// Finally it generates godog suite executable which
// registers exported godog contexts from the test files
// of tested package.
//
// 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)
// Returns the path to generated executable
func Build() (string, error) {
abs, err := filepath.Abs(".")
if err != nil {
return err
return "", err
}
return buildTestPackage(pkg, dir)
}
// buildTestPackage clones a package and adds a godog
// entry point with TestMain func in order to
// run the test suite. If TestMain func is found in tested
// source, it will be removed so it can be replaced
func buildTestPackage(pkg *build.Package, dir string) error {
// these file packs may be adjusted in the future, if there are complaints
// in general, most important aspect is to replicate go test behavior
err := copyNonTestPackageFiles(
dir,
pkg.CFiles,
pkg.CgoFiles,
pkg.CXXFiles,
pkg.HFiles,
pkg.GoFiles,
pkg.IgnoredGoFiles,
)
if err != nil {
return err
bin := filepath.Join(abs, "godog.test")
// suffix with .exe for windows
if goos == "windows" {
bin += ".exe"
}
contexts, err := processPackageTestFiles(
dir,
pkg.TestGoFiles,
pkg.XTestGoFiles,
)
// we allow package to be nil, if godog is run only when
// there is a feature file in empty directory
pkg, _ := build.ImportDir(abs, 0)
src, anyContexts, err := buildTestMain(pkg)
if err != nil {
return err
return bin, 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()
workdir := fmt.Sprintf(filepath.Join("%s", "godog-%d"), os.TempDir(), time.Now().UnixNano())
testdir := workdir
data := struct {
Name string
Contexts []string
}{pkg.Name, contexts}
// if none of test files exist, or there are no contexts found
// we will skip test package compilation, since it is useless
if anyContexts {
// 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
// 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
}
// builds and compile the tested package.
// generated test executable will be removed
// since we do not need it for godog suite.
// we also print back the temp WORK directory
// go has built. We will reuse it for our suite workdir.
out, err = exec.Command("go", "test", "-c", "-work", "-o", temp).CombinedOutput()
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
// 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) {
// file pack and looks for godog suite contexts to register
// on run
func processPackageTestFiles(packs ...[]string) ([]string, error) {
var ctxs []string
fset := token.NewFileSet()
for _, pack := range packs {
@ -122,52 +272,19 @@ func processPackageTestFiles(destDir string, packs ...[]string) ([]string, error
return ctxs, err
}
astDeleteTestMainFunc(node)
ctxs = append(ctxs, astContexts(node)...)
destFile := filepath.Join(destDir, testFile)
if err = os.MkdirAll(filepath.Dir(destFile), 0755); err != nil {
return ctxs, err
}
out, err := os.Create(destFile)
if err != nil {
return ctxs, err
}
defer out.Close()
if err := format.Node(out, fset, node); err != nil {
return ctxs, err
}
}
}
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
}
// 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"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"syscall"
"time"
"github.com/DATA-DOG/godog"
)
@ -23,27 +21,10 @@ var stderr = statusOutputFilter(os.Stderr)
func buildAndRun() (int, error) {
var status int
dir := fmt.Sprintf(filepath.Join("%s", "godog-%d"), os.TempDir(), time.Now().UnixNano())
err := godog.Build(dir)
bin, err := godog.Build()
if err != nil {
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)
cmd := exec.Command(bin, os.Args[1:]...)

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

@ -67,7 +67,7 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (err er
return
}
func featureContext(s *godog.Suite) {
func FeatureContext(s *godog.Suite) {
api := &apiFeature{}
s.BeforeScenario(api.resetResponse)

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

@ -20,6 +20,6 @@ Feature: get version
And the response should match json:
"""
{
"version": "v0.4.3"
"version": "v0.5.0"
}
"""

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

@ -8,7 +8,6 @@ import (
"net/http/httptest"
"strings"
"github.com/DATA-DOG/go-txdb"
"github.com/DATA-DOG/godog"
"github.com/DATA-DOG/godog/gherkin"
)
@ -118,7 +117,7 @@ func (a *apiFeature) thereAreUsers(users *gherkin.DataTable) error {
return nil
}
func featureContext(s *godog.Suite) {
func FeatureContext(s *godog.Suite) {
api := &apiFeature{}
s.BeforeScenario(api.resetResponse)

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

@ -1,3 +1,4 @@
/* file: examples/godogs/godog.go */
package main
// Godogs to eat

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

@ -1,3 +1,4 @@
/* file: examples/godogs/godog_test.go */
package main
import (
@ -26,7 +27,7 @@ func thereShouldBeRemaining(remaining int) error {
return nil
}
func featureContext(s *godog.Suite) {
func FeatureContext(s *godog.Suite) {
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
s.Step(`^I eat (\d+)$`, iEat)
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)

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

@ -28,7 +28,7 @@ Feature: undefined step snippets
return godog.ErrPending
}
func featureContext(s *godog.Suite) {
func FeatureContext(s *godog.Suite) {
s.Step(`^I send "([^"]*)" request to "([^"]*)"$`, iSendRequestTo)
s.Step(`^the response code should be (\d+)$`, theResponseCodeShouldBe)
}
@ -56,7 +56,7 @@ Feature: undefined step snippets
return godog.ErrPending
}
func featureContext(s *godog.Suite) {
func FeatureContext(s *godog.Suite) {
s.Step(`^I send "([^"]*)" request to "([^"]*)" with:$`, iSendRequestToWith)
s.Step(`^the response code should be (\d+) and header "([^"]*)" should be "([^"]*)"$`, theResponseCodeShouldBeAndHeaderShouldBe)
}
@ -87,7 +87,7 @@ Feature: undefined step snippets
return godog.ErrPending
}
func featureContext(s *godog.Suite) {
func FeatureContext(s *godog.Suite) {
s.Step(`^I pull from github\.com$`, iPullFromGithubcom)
s.Step(`^the project should be there$`, theProjectShouldBeThere)
}

2
fmt.go
Просмотреть файл

@ -30,7 +30,7 @@ var undefinedSnippetsTpl = template.Must(template.New("snippets").Funcs(snippetH
return godog.ErrPending
}
{{end}}func featureContext(s *godog.Suite) { {{ range . }}
{{end}}func FeatureContext(s *godog.Suite) { {{ range . }}
s.Step({{ backticked .Expr }}, {{ .Method }}){{end}}
}
`))

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

@ -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
maintaining all test related source code in *_test.go files.
Godog acts similar compared to go test command. It leverages
a TestMain function 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 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.
Godog acts similar compared to go test command. It uses go
compiler and linker tool in order to produce test executable. Godog
contexts needs to be exported same as Test functions for go test.
For example, imagine youre about to create the famous UNIX ls command.
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
// 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

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 54 КиБ

После

Ширина:  |  Высота:  |  Размер: 80 КиБ

Двоичные данные
screenshots/undefined.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 109 КиБ

После

Ширина:  |  Высота:  |  Размер: 105 КиБ

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

@ -2,6 +2,7 @@ package godog
import (
"fmt"
"path/filepath"
"strconv"
"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))
}
for i := 0; i < len(expected); i++ {
if expected[i] != actual[i] {
return fmt.Errorf(`expected feature path "%s" at position: %d, does not match actual "%s"`, expected[i], i, actual[i])
split := strings.Split(expected[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

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

@ -6,6 +6,9 @@ import (
"strings"
)
// empty struct value takes no space allocation
type void struct{}
// a color code type
type color int