add an ls feature example
Этот коммит содержится в:
родитель
6e65757f89
коммит
e824fde3f9
15 изменённых файлов: 244 добавлений и 100 удалений
1
.gitignore
предоставленный
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}
|
||||
}
|
||||
|
|
42
builder.go
42
builder.go
|
@ -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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
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
Двоичные данные
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")
|
||||
}
|
66
suite.go
66
suite.go
|
@ -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() {
|
||||
|
|
2
utils.go
2
utils.go
|
@ -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
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче