Этот коммит содержится в:
gedi 2015-06-23 13:51:45 +03:00
родитель 6e65757f89
коммит e824fde3f9
15 изменённых файлов: 244 добавлений и 100 удалений

1
.gitignore предоставленный
Просмотреть файл

@ -1 +1,2 @@
/cmd/godog/godog
/example/example

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

@ -9,6 +9,10 @@ describe a feature of your application and how it should work, and only then imp
The project is inspired by [behat][behat] and [cucumber][cucumber] and is based on cucumber [gherkin specification][gherkin].
### Install
go install github.com/DATA-DOG/godog/cmd/godog
### Be aware that
The work is still in progress and is not functional yet, neither it is intended for production usage.
@ -21,7 +25,9 @@ See **.travis.yml** for supported **go** versions.
### License
Licensed under the [three clause BSD license][license]
All package dependencies are **MIT** or **BSD** licensed.
**Godog** is licensed under the [three clause BSD license][license]
[godoc]: http://godoc.org/github.com/DATA-DOG/godog "Documentation on godoc"
[godoc_gherkin]: http://godoc.org/github.com/DATA-DOG/godog/gherkin "Documentation on godoc for gherkin"

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

@ -8,14 +8,18 @@ import (
)
// Arg is an argument for StepHandler parsed from
// the regexp submatch to handle the step
// the regexp submatch to handle the step.
//
// In future versions, it may be replaced with
// an argument injection toolkit using reflect
// package.
type Arg struct {
value interface{}
}
// StepArgument func creates a step argument.
// used in cases when calling another step from
// within a StepHandlerFunc
// within a StepHandler function.
func StepArgument(value interface{}) *Arg {
return &Arg{value: value}
}

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

@ -22,8 +22,8 @@ type builder struct {
tpl *template.Template
}
func newBuilder() *builder {
return &builder{
func newBuilder(buildPath string) (*builder, error) {
b := &builder{
files: make(map[string]*ast.File),
fset: token.NewFileSet(),
tpl: template.Must(template.New("main").Parse(`package main
@ -39,6 +39,18 @@ func main() {
suite.Run()
}`)),
}
return b, filepath.Walk(buildPath, func(path string, file os.FileInfo, err error) error {
if file.IsDir() && file.Name() != "." {
return filepath.SkipDir
}
if err == nil && strings.HasSuffix(path, ".go") {
if err := b.parseFile(path); err != nil {
return err
}
}
return err
})
}
func (b *builder) parseFile(path string) error {
@ -129,24 +141,18 @@ func (b *builder) merge() (*ast.File, error) {
return ast.MergePackageFiles(pkg, ast.FilterImportDuplicates), nil
}
// Build creates a runnable godog executable file
// from current package source and test files
// it merges the files with the help of go/ast into
// 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 features
// 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 := newBuilder()
err := filepath.Walk(".", func(path string, file os.FileInfo, err error) error {
if file.IsDir() && file.Name() != "." {
return filepath.SkipDir
}
if err == nil && strings.HasSuffix(path, ".go") {
if err := b.parseFile(path); err != nil {
return err
}
}
return err
})
b, err := newBuilder(".")
if err != nil {
return nil, err
}

7
example/README.md Обычный файл
Просмотреть файл

@ -0,0 +1,7 @@
# ls feature
In order to test our **ls** feature with **Godog**, run:
go install github.com/DATA-DOG/godog/cmd/godog
$GOPATH/bin/godog ls.feature

27
example/ls.feature Обычный файл
Просмотреть файл

@ -0,0 +1,27 @@
Feature: ls
In order to see the directory structure
As a UNIX user
I need to be able to list directory contents
Background:
Given I am in a directory "test"
Scenario: lists files in directory
Given I have a file named "foo"
And I have a file named "bar"
When I run ls
Then I should get output:
"""
bar
foo
"""
Scenario: lists files and directories
Given I have a file named "foo"
And I have a directory named "dir"
When I run ls
Then I should get output:
"""
dir
foo
"""

34
example/ls.go Обычный файл
Просмотреть файл

@ -0,0 +1,34 @@
package main
import (
"io"
"log"
"os"
"path/filepath"
)
func main() {
var location string
switch {
case os.Args[1] != "":
location = os.Args[1]
default:
location = "."
}
if err := ls(location, os.Stdout); err != nil {
log.Fatal(err)
}
}
func ls(path string, w io.Writer) error {
return filepath.Walk(path, func(p string, f os.FileInfo, err error) error {
switch {
case f.IsDir() && f.Name() != "." && f.Name() != ".." && filepath.Base(path) != f.Name():
w.Write([]byte(f.Name() + "\n"))
return filepath.SkipDir
case !f.IsDir():
w.Write([]byte(f.Name() + "\n"))
}
return err
})
}

65
example/ls_test.go Обычный файл
Просмотреть файл

@ -0,0 +1,65 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/DATA-DOG/godog"
)
type lsFeature struct {
dir string
buf *bytes.Buffer
}
func lsFeatureContext(s godog.Suite) {
c := &lsFeature{buf: bytes.NewBuffer(make([]byte, 1024))}
s.Step(`^I am in a directory "([^"]*)"$`, c.iAmInDirectory)
s.Step(`^I have a (file|directory) named "([^"]*)"$`, c.iHaveFileOrDirectoryNamed)
s.Step(`^I run ls$`, c.iRunLs)
s.Step(`^I should get output:$`, c.iShouldGetOutput)
}
func (f *lsFeature) iAmInDirectory(args ...*godog.Arg) error {
f.dir = os.TempDir() + "/" + args[0].String()
if err := os.RemoveAll(f.dir); err != nil && !os.IsNotExist(err) {
return err
}
return os.Mkdir(f.dir, 0775)
}
func (f *lsFeature) iHaveFileOrDirectoryNamed(args ...*godog.Arg) (err error) {
if len(f.dir) == 0 {
return fmt.Errorf("the directory was not chosen yet")
}
switch args[0].String() {
case "file":
err = ioutil.WriteFile(f.dir+"/"+args[1].String(), []byte{}, 0664)
case "directory":
err = os.Mkdir(f.dir+"/"+args[1].String(), 0775)
}
return err
}
func (f *lsFeature) iShouldGetOutput(args ...*godog.Arg) error {
expected := args[0].PyString().Lines
actual := strings.Split(strings.TrimSpace(f.buf.String()), "\n")
if len(expected) != len(actual) {
return fmt.Errorf("number of expected output lines %d, does not match actual: %d", len(expected), len(actual))
}
for i, line := range actual {
if line != expected[i] {
return fmt.Errorf(`expected line "%s" at position: %d to match "%s", but it did not`, expected[i], i, line)
}
}
return nil
}
func (f *lsFeature) iRunLs(args ...*godog.Arg) error {
f.buf.Reset()
return ls(f.dir, f.buf)
}

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

@ -7,7 +7,12 @@ import (
)
// Formatter is an interface for feature runner
// output summary presentation
// output summary presentation.
//
// New formatters may be created to represent
// suite results in different ways. These new
// formatters needs to be registered with a
// RegisterFormatter function call
type Formatter interface {
Node(interface{})
Failed(*gherkin.Step, *StepDef, error)

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

@ -22,11 +22,9 @@ var outlinePlaceholderRegexp *regexp.Regexp = regexp.MustCompile("<[^>]+>")
// a built in default pretty formatter
type pretty struct {
feature *gherkin.Feature
commentPos int
doneBackground bool
background *gherkin.Background
scenario *gherkin.Scenario
feature *gherkin.Feature
commentPos int
backgroundSteps int
// outline
outlineExamples int
@ -56,21 +54,17 @@ func (f *pretty) Node(node interface{}) {
fmt.Println("")
}
f.feature = t
f.scenario = nil
f.background = nil
f.features = append(f.features, t)
// print feature header
fmt.Println(bcl(t.Token.Keyword+": ", white) + t.Title)
fmt.Println(t.Description)
case *gherkin.Background:
// do not repeat background for the same feature
if f.background == nil && f.scenario == nil {
f.background = t
f.commentPos = longestStep(t.Steps, t.Token.Length())
// print background node
fmt.Println("\n" + s(t.Token.Indent) + bcl(t.Token.Keyword+":", white))
// print background header
if t.Background != nil {
f.commentPos = longestStep(t.Background.Steps, t.Background.Token.Length())
f.backgroundSteps = len(t.Background.Steps)
fmt.Println("\n" + s(t.Background.Token.Indent) + bcl(t.Background.Token.Keyword+":", white))
}
case *gherkin.Scenario:
f.scenario = t
f.commentPos = longestStep(t.Steps, t.Token.Length())
if t.Outline != nil {
f.outlineSteps = []interface{}{} // reset steps list
@ -162,10 +156,10 @@ func (f *pretty) Summary() {
fmt.Println(elapsed)
}
func (f *pretty) printOutlineExample() {
func (f *pretty) printOutlineExample(scenario *gherkin.Scenario) {
var failed error
clr := green
tbl := f.scenario.Outline.Examples
tbl := scenario.Outline.Examples
firstExample := f.outlineExamples == len(tbl.Rows)-1
for i, act := range f.outlineSteps {
@ -187,7 +181,7 @@ func (f *pretty) printOutlineExample() {
if firstExample {
// in first example, we need to print steps
var text string
ostep := f.scenario.Outline.Steps[i]
ostep := scenario.Outline.Steps[i]
if def != nil {
if m := outlinePlaceholderRegexp.FindAllStringIndex(ostep.Text, -1); len(m) > 0 {
var pos int
@ -216,7 +210,7 @@ func (f *pretty) printOutlineExample() {
max := longest(tbl)
// an example table header
if firstExample {
out := f.scenario.Outline
out := scenario.Outline
fmt.Println("")
fmt.Println(s(out.Token.Indent) + bcl(out.Token.Keyword+":", white))
row := tbl.Rows[0]
@ -308,15 +302,18 @@ func (f *pretty) printStepKind(stepAction interface{}) {
step, def, c, err = f.stepDetails(stepAction)
// do not print background more than once
if f.scenario == nil && step.Background != f.background {
switch {
case step.Background != nil && f.backgroundSteps == 0:
return
case step.Background != nil && f.backgroundSteps > 0:
f.backgroundSteps -= 1
}
if f.outlineExamples != 0 {
f.outlineSteps = append(f.outlineSteps, stepAction)
if len(f.outlineSteps) == f.outlineNumSteps {
// an outline example steps has went through
f.printOutlineExample()
f.printOutlineExample(step.Scenario)
f.outlineExamples -= 1
}
return // wait till example steps
@ -385,7 +382,7 @@ func longestStep(steps []*gherkin.Step, base int) int {
ret := base
for _, step := range steps {
length := step.Token.Length()
if length > base {
if length > ret {
ret = length
}
}

Двоичные данные
gherkin/example/example

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

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

@ -1,15 +0,0 @@
Feature: ls
In order to see the directory structure
As a UNIX user
I need to be able to list the current directory's contents
Scenario:
Given I am in a directory "test"
And I have a file named "foo"
And I have a file named "bar"
When I run "ls"
Then I should get:
"""
bar
foo
"""

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

@ -1,21 +0,0 @@
package main
import (
"log"
"os"
"github.com/DATA-DOG/godog/gherkin"
)
func main() {
feature, err := gherkin.ParseFile("ls.feature")
switch {
case err == gherkin.ErrEmpty:
log.Println("the feature file is empty and does not describe any feature")
return
case err != nil:
log.Println("the feature file is incorrect or could not be read:", err)
os.Exit(1)
}
log.Println("have parsed a feature:", feature.Title, "with", len(feature.Scenarios), "scenarios")
}

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

@ -16,26 +16,30 @@ import (
// it can be either a string or a *regexp.Regexp
type Regexp interface{}
// StepHandler is a function contract for
// step handler
// StepHandler is a func to handle the step
//
// It receives all arguments which
// will be matched according to the regular expression
// The handler receives all arguments which
// will be matched according to the Regexp
// which is passed with a step registration.
// The error in return - represents a reason of failure.
//
// Returning signals that the step has finished
// and that the feature runner can move on to the next
// step.
// The error in return - represents a reason of failure.
// All consequent scenario steps are skipped.
//
// Returning signals that the step has finished and that
// the feature runner can move on to the next step.
type StepHandler func(...*Arg) error
// ErrUndefined is returned in case if step definition was not found
var ErrUndefined = fmt.Errorf("step is undefined")
// StepDef is a registered step definition
// contains a StepHandler, a regexp which
// is used to match a step and Args which
// were matched by last step
// contains a StepHandler and regexp which
// is used to match a step. Args which
// were matched by last executed step
//
// This structure is passed to the formatter
// when step is matched and is either failed
// or successful
type StepDef struct {
Args []*Arg
Handler StepHandler
@ -43,7 +47,16 @@ type StepDef struct {
}
// Suite is an interface which allows various contexts
// to register step definitions and event handlers
// to register steps and event handlers.
//
// When running a test suite, this interface is passed
// to all functions (contexts), which have it as a
// first and only argument.
//
// Note that all event hooks does not catch panic errors
// in order to have a trace information. Only step
// executions are catching panic error since it may
// be a context specific error.
type Suite interface {
Step(expr Regexp, h StepHandler)
// suite events
@ -80,17 +93,16 @@ func New() *suite {
// Step allows to register a StepHandler in Godog
// feature suite, the handler will be applied to all
// steps matching the given regexp expr
// steps matching the given Regexp expr
//
// It will panic if expr is not a valid regular expression
// or handler does not satisfy StepHandler interface
//
// Note that if there are two handlers which may match
// the same step, then the only first matched handler
// will be applied
// will be applied.
//
// If none of the StepHandlers are matched, then a pending
// step error will be raised.
// If none of the StepHandlers are matched, then
// ErrUndefined error will be returned.
func (s *suite) Step(expr Regexp, h StepHandler) {
var regex *regexp.Regexp
@ -112,13 +124,20 @@ func (s *suite) Step(expr Regexp, h StepHandler) {
}
// BeforeSuite registers a function or method
// to be run once before suite runner
// to be run once before suite runner.
//
// Use it to prepare the test suite for a spin.
// Connect and prepare database for instance...
func (s *suite) BeforeSuite(f func()) {
s.beforeSuiteHandlers = append(s.beforeSuiteHandlers, f)
}
// BeforeScenario registers a function or method
// to be run before every scenario
// to be run before every scenario.
//
// It is a good practice to restore the default state
// before every scenario so it would be isolated from
// any kind of state.
func (s *suite) BeforeScenario(f func(*gherkin.Scenario)) {
s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, f)
}
@ -131,6 +150,13 @@ func (s *suite) BeforeStep(f func(*gherkin.Step)) {
// AfterStep registers an function or method
// to be run after every scenario
//
// It may be convenient to return a different kind of error
// in order to print more state details which may help
// in case of step failure
//
// In some cases, for example when running a headless
// browser, to take a screenshot after failure.
func (s *suite) AfterStep(f func(*gherkin.Step, error)) {
s.afterStepHandlers = append(s.afterStepHandlers, f)
}
@ -147,7 +173,7 @@ func (s *suite) AfterSuite(f func()) {
s.afterSuiteHandlers = append(s.afterSuiteHandlers, f)
}
// Run - runs a godog feature suite
// Run starts the Godog feature suite
func (s *suite) Run() {
var err error
if !flag.Parsed() {

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

@ -6,10 +6,12 @@ import (
"strings"
)
// a color code type
type color int
const ansiEscape = "\x1b"
// some ansi colors
const (
black color = iota + 30
red