Use fs.FS
abstraction for filesystem (#550)
* compiles * mock fs in tests * fix parser tests * fix run.go * rename FeatureFS to FS * fix docs typos * remove debug log * add os.DirFS("./") to default options * reword docstring * add fs wrapper * updated readme and changelog * added note * fix changelog * remove ./ gating from defaults * add new storage.FS tests * increase coverage of parser.parsePath * increase coverage of TestSuite.RetrieveFeatures * remove another os.Stat --------- Co-authored-by: Tighearnán Carroll <tighearnan.carroll@gamil.com>
Этот коммит содержится в:
родитель
3bd9e9ca4f
коммит
6ce2b8696b
11 изменённых файлов: 331 добавлений и 59 удалений
|
@ -8,6 +8,7 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
### Added
|
### Added
|
||||||
|
- Support for reading feature files from an `fs.FS` ([550](https://github.com/cucumber/godog/pull/550) - [tigh-latte](https://github.com/tigh-latte))
|
||||||
- Added keyword functions. ([509](https://github.com/cucumber/godog/pull/509) - [otrava7](https://github.com/otrava7))
|
- Added keyword functions. ([509](https://github.com/cucumber/godog/pull/509) - [otrava7](https://github.com/otrava7))
|
||||||
- prefer go test to use of godog cli in README ([548](https://github.com/cucumber/godog/pull/548) - [danielhelfand](https://github.com/danielhelfand)
|
- prefer go test to use of godog cli in README ([548](https://github.com/cucumber/godog/pull/548) - [danielhelfand](https://github.com/danielhelfand)
|
||||||
|
|
||||||
|
|
26
README.md
26
README.md
|
@ -521,6 +521,32 @@ func (a *asserter) Errorf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Embeds
|
||||||
|
|
||||||
|
If you're looking to compile your test binary in advance of running, you can compile the feature files into the binary via `go:embed`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
//go:embed features/*
|
||||||
|
var features embed.FS
|
||||||
|
|
||||||
|
var opts = godog.Options{
|
||||||
|
Paths: []string{"features"},
|
||||||
|
FS: features,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, the test binary can be compiled with all feature files embedded, and can be ran independently from the feature files:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
> go test -c ./test/integration/integration_test.go
|
||||||
|
> mv integration.test /some/random/dir
|
||||||
|
> cd /some/random/dir
|
||||||
|
> ./integration.test
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE:** `godog.Options.FS` is as `fs.FS`, so custom filesystem loaders can be used.
|
||||||
|
|
||||||
## CLI Mode
|
## CLI Mode
|
||||||
|
|
||||||
**NOTE:** The [`godog` CLI has been deprecated](https://github.com/cucumber/godog/discussions/478). It is recommended to use `go test` instead.
|
**NOTE:** The [`godog` CLI has been deprecated](https://github.com/cucumber/godog/discussions/478). It is recommended to use `go test` instead.
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -46,7 +46,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
|
|
@ -3,6 +3,7 @@ package flags
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,6 +71,10 @@ type Options struct {
|
||||||
// in a map entry
|
// in a map entry
|
||||||
FeatureContents []Feature
|
FeatureContents []Feature
|
||||||
|
|
||||||
|
// FS allows passing in an `fs.FS` to read features from, such as an `embed.FS`
|
||||||
|
// or os.DirFS(string).
|
||||||
|
FS fs.FS
|
||||||
|
|
||||||
// ShowHelp enables suite to show CLI flags usage help and exit.
|
// ShowHelp enables suite to show CLI flags usage help and exit.
|
||||||
ShowHelp bool
|
ShowHelp bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,14 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cucumber/gherkin/go/v26"
|
gherkin "github.com/cucumber/gherkin/go/v26"
|
||||||
"github.com/cucumber/messages/go/v21"
|
messages "github.com/cucumber/messages/go/v21"
|
||||||
|
|
||||||
"github.com/cucumber/godog/internal/flags"
|
"github.com/cucumber/godog/internal/flags"
|
||||||
"github.com/cucumber/godog/internal/models"
|
"github.com/cucumber/godog/internal/models"
|
||||||
|
@ -33,8 +33,8 @@ func ExtractFeaturePathLine(p string) (string, int) {
|
||||||
return retPath, line
|
return retPath, line
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFeatureFile(path string, newIDFunc func() string) (*models.Feature, error) {
|
func parseFeatureFile(fsys fs.FS, path string, newIDFunc func() string) (*models.Feature, error) {
|
||||||
reader, err := os.Open(path)
|
reader, err := fsys.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -70,9 +70,9 @@ func parseBytes(path string, feature []byte, newIDFunc func() string) (*models.F
|
||||||
return &f, nil
|
return &f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFeatureDir(dir string, newIDFunc func() string) ([]*models.Feature, error) {
|
func parseFeatureDir(fsys fs.FS, dir string, newIDFunc func() string) ([]*models.Feature, error) {
|
||||||
var features []*models.Feature
|
var features []*models.Feature
|
||||||
return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error {
|
return features, fs.WalkDir(fsys, dir, func(p string, f fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ func parseFeatureDir(dir string, newIDFunc func() string) ([]*models.Feature, er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
feat, err := parseFeatureFile(p, newIDFunc)
|
feat, err := parseFeatureFile(fsys, p, newIDFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -95,21 +95,29 @@ func parseFeatureDir(dir string, newIDFunc func() string) ([]*models.Feature, er
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePath(path string, newIDFunc func() string) ([]*models.Feature, error) {
|
func parsePath(fsys fs.FS, path string, newIDFunc func() string) ([]*models.Feature, error) {
|
||||||
var features []*models.Feature
|
var features []*models.Feature
|
||||||
|
|
||||||
path, line := ExtractFeaturePathLine(path)
|
path, line := ExtractFeaturePathLine(path)
|
||||||
|
|
||||||
fi, err := os.Stat(path)
|
fi, err := func() (fs.FileInfo, error) {
|
||||||
|
file, err := fsys.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return file.Stat()
|
||||||
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return features, err
|
return features, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
return parseFeatureDir(path, newIDFunc)
|
return parseFeatureDir(fsys, path, newIDFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
ft, err := parseFeatureFile(path, newIDFunc)
|
ft, err := parseFeatureFile(fsys, path, newIDFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return features, err
|
return features, err
|
||||||
}
|
}
|
||||||
|
@ -138,14 +146,14 @@ func parsePath(path string, newIDFunc func() string) ([]*models.Feature, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFeatures ...
|
// ParseFeatures ...
|
||||||
func ParseFeatures(filter string, paths []string) ([]*models.Feature, error) {
|
func ParseFeatures(fsys fs.FS, filter string, paths []string) ([]*models.Feature, error) {
|
||||||
var order int
|
var order int
|
||||||
|
|
||||||
featureIdxs := make(map[string]int)
|
featureIdxs := make(map[string]int)
|
||||||
uniqueFeatureURI := make(map[string]*models.Feature)
|
uniqueFeatureURI := make(map[string]*models.Feature)
|
||||||
newIDFunc := (&messages.Incrementing{}).NewId
|
newIDFunc := (&messages.Incrementing{}).NewId
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
feats, err := parsePath(path, newIDFunc)
|
feats, err := parsePath(fsys, path, newIDFunc)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case os.IsNotExist(err):
|
case os.IsNotExist(err):
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package parser_test
|
package parser_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"errors"
|
||||||
"os"
|
"io/fs"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -71,16 +72,15 @@ Feature: eat godogs
|
||||||
When I eat 5
|
When I eat 5
|
||||||
Then there should be 7 remaining`
|
Then there should be 7 remaining`
|
||||||
|
|
||||||
baseDir := filepath.Join(os.TempDir(), t.Name(), "godogs")
|
baseDir := "base"
|
||||||
errA := os.MkdirAll(baseDir+"/a", 0755)
|
fsys := fstest.MapFS{
|
||||||
defer os.RemoveAll(baseDir)
|
filepath.Join(baseDir, featureFileName): {
|
||||||
|
Data: []byte(eatGodogContents),
|
||||||
|
Mode: fs.FileMode(0644),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
require.Nil(t, errA)
|
featureFromFile, err := parser.ParseFeatures(fsys, "", []string{baseDir})
|
||||||
|
|
||||||
err := ioutil.WriteFile(filepath.Join(baseDir, featureFileName), []byte(eatGodogContents), 0644)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
featureFromFile, err := parser.ParseFeatures("", []string{baseDir})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, featureFromFile, 1)
|
require.Len(t, featureFromFile, 1)
|
||||||
|
|
||||||
|
@ -96,8 +96,9 @@ Feature: eat godogs
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
|
func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
|
||||||
const featureFileName = "godogs.feature"
|
const (
|
||||||
const featureFileContents = `Feature: eat godogs
|
defaultFeatureFile = "godogs.feature"
|
||||||
|
defaultFeatureContents = `Feature: eat godogs
|
||||||
In order to be happy
|
In order to be happy
|
||||||
As a hungry gopher
|
As a hungry gopher
|
||||||
I need to be able to eat godogs
|
I need to be able to eat godogs
|
||||||
|
@ -106,23 +107,63 @@ func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
|
||||||
Given there are 12 godogs
|
Given there are 12 godogs
|
||||||
When I eat 5
|
When I eat 5
|
||||||
Then there should be 7 remaining`
|
Then there should be 7 remaining`
|
||||||
|
)
|
||||||
|
|
||||||
baseDir := filepath.Join(os.TempDir(), t.Name(), "godogs")
|
tests := map[string]struct {
|
||||||
errA := os.MkdirAll(baseDir+"/a", 0755)
|
fsys fs.FS
|
||||||
errB := os.MkdirAll(baseDir+"/b", 0755)
|
paths []string
|
||||||
defer os.RemoveAll(baseDir)
|
|
||||||
|
|
||||||
require.Nil(t, errA)
|
expFeatures int
|
||||||
require.Nil(t, errB)
|
expError error
|
||||||
|
}{
|
||||||
|
"feature directories can be parsed": {
|
||||||
|
paths: []string{"base/a", "base/b"},
|
||||||
|
fsys: fstest.MapFS{
|
||||||
|
filepath.Join("base/a", defaultFeatureFile): {
|
||||||
|
Data: []byte(defaultFeatureContents),
|
||||||
|
},
|
||||||
|
filepath.Join("base/b", defaultFeatureFile): {
|
||||||
|
Data: []byte(defaultFeatureContents),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expFeatures: 2,
|
||||||
|
},
|
||||||
|
"path not found errors": {
|
||||||
|
fsys: fstest.MapFS{},
|
||||||
|
paths: []string{"base/a", "base/b"},
|
||||||
|
expError: errors.New(`feature path "base/a" is not available`),
|
||||||
|
},
|
||||||
|
"feature files can be parsed": {
|
||||||
|
paths: []string{
|
||||||
|
filepath.Join("base/a/", defaultFeatureFile),
|
||||||
|
filepath.Join("base/b/", defaultFeatureFile),
|
||||||
|
},
|
||||||
|
fsys: fstest.MapFS{
|
||||||
|
filepath.Join("base/a", defaultFeatureFile): {
|
||||||
|
Data: []byte(defaultFeatureContents),
|
||||||
|
},
|
||||||
|
filepath.Join("base/b", defaultFeatureFile): {
|
||||||
|
Data: []byte(defaultFeatureContents),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expFeatures: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
err := ioutil.WriteFile(filepath.Join(baseDir+"/a", featureFileName), []byte(featureFileContents), 0644)
|
for name, test := range tests {
|
||||||
require.Nil(t, err)
|
test := test
|
||||||
err = ioutil.WriteFile(filepath.Join(baseDir+"/b", featureFileName), []byte(featureFileContents), 0644)
|
t.Run(name, func(t *testing.T) {
|
||||||
require.Nil(t, err)
|
t.Parallel()
|
||||||
|
|
||||||
|
features, err := parser.ParseFeatures(test.fsys, "", test.paths)
|
||||||
|
if test.expError != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.EqualError(t, err, test.expError.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
features, err := parser.ParseFeatures("", []string{baseDir + "/a", baseDir + "/b"})
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Len(t, features, 2)
|
assert.Len(t, features, test.expFeatures)
|
||||||
|
|
||||||
pickleIDs := map[string]bool{}
|
pickleIDs := map[string]bool{}
|
||||||
for _, f := range features {
|
for _, f := range features {
|
||||||
|
@ -134,4 +175,6 @@ func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
|
||||||
pickleIDs[p.Id] = true
|
pickleIDs[p.Id] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
21
internal/storage/fs.go
Обычный файл
21
internal/storage/fs.go
Обычный файл
|
@ -0,0 +1,21 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FS is a wrapper that falls back to `os`.
|
||||||
|
type FS struct {
|
||||||
|
FS fs.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a file in the provided `fs.FS`. If none provided,
|
||||||
|
// open via `os.Open`
|
||||||
|
func (f FS) Open(name string) (fs.File, error) {
|
||||||
|
if f.FS == nil {
|
||||||
|
return os.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.FS.Open(name)
|
||||||
|
}
|
109
internal/storage/fs_test.go
Обычный файл
109
internal/storage/fs_test.go
Обычный файл
|
@ -0,0 +1,109 @@
|
||||||
|
package storage_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
|
"github.com/cucumber/godog/internal/storage"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStorage_Open_FS(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
fs fs.FS
|
||||||
|
|
||||||
|
expData []byte
|
||||||
|
expError error
|
||||||
|
}{
|
||||||
|
"normal open": {
|
||||||
|
fs: fstest.MapFS{
|
||||||
|
"testfile": {
|
||||||
|
Data: []byte("hello worlds"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expData: []byte("hello worlds"),
|
||||||
|
},
|
||||||
|
"file not found": {
|
||||||
|
fs: fstest.MapFS{},
|
||||||
|
expError: errors.New("open testfile: file does not exist"),
|
||||||
|
},
|
||||||
|
"nil fs falls back on os": {
|
||||||
|
expError: errors.New("open testfile: no such file or directory"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
f, err := (storage.FS{FS: test.fs}).Open("testfile")
|
||||||
|
if test.expError != nil {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualError(t, err, test.expError.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
bb := make([]byte, len(test.expData))
|
||||||
|
_, _ = f.Read(bb)
|
||||||
|
assert.Equal(t, test.expData, bb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStorage_Open_OS(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
files map[string][]byte
|
||||||
|
expData []byte
|
||||||
|
expError error
|
||||||
|
}{
|
||||||
|
"normal open": {
|
||||||
|
files: map[string][]byte{
|
||||||
|
"testfile": []byte("hello worlds"),
|
||||||
|
},
|
||||||
|
expData: []byte("hello worlds"),
|
||||||
|
},
|
||||||
|
"nil fs falls back on os": {
|
||||||
|
expError: errors.New("open /tmp/TestStorage_Open_OS/nil_fs_falls_back_on_os/godogs/testfile: no such file or directory"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
baseDir := filepath.Join(os.TempDir(), t.Name(), "godogs")
|
||||||
|
err := os.MkdirAll(baseDir+"/a", 0755)
|
||||||
|
defer os.RemoveAll(baseDir)
|
||||||
|
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
for name, data := range test.files {
|
||||||
|
err := ioutil.WriteFile(filepath.Join(baseDir, name), data, 0644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := (storage.FS{}).Open(filepath.Join(baseDir, "testfile"))
|
||||||
|
if test.expError != nil {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualError(t, err, test.expError.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
bb := make([]byte, len(test.expData))
|
||||||
|
_, _ = f.Read(bb)
|
||||||
|
assert.Equal(t, test.expData, bb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
32
run.go
32
run.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/cucumber/messages/go/v21"
|
messages "github.com/cucumber/messages/go/v21"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
"github.com/cucumber/godog/formatters"
|
"github.com/cucumber/godog/formatters"
|
||||||
|
@ -215,7 +216,15 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opt.Paths) == 0 && len(opt.FeatureContents) == 0 {
|
if len(opt.Paths) == 0 && len(opt.FeatureContents) == 0 {
|
||||||
inf, err := os.Stat("features")
|
inf, err := func() (fs.FileInfo, error) {
|
||||||
|
file, err := opt.FS.Open("features")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return file.Stat()
|
||||||
|
}()
|
||||||
if err == nil && inf.IsDir() {
|
if err == nil && inf.IsDir() {
|
||||||
opt.Paths = []string{"features"}
|
opt.Paths = []string{"features"}
|
||||||
}
|
}
|
||||||
|
@ -226,6 +235,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
runner.fmt = multiFmt.FormatterFunc(suiteName, output)
|
runner.fmt = multiFmt.FormatterFunc(suiteName, output)
|
||||||
|
opt.FS = storage.FS{FS: opt.FS}
|
||||||
|
|
||||||
if len(opt.FeatureContents) > 0 {
|
if len(opt.FeatureContents) > 0 {
|
||||||
features, err := parser.ParseFromBytes(opt.Tags, opt.FeatureContents)
|
features, err := parser.ParseFromBytes(opt.Tags, opt.FeatureContents)
|
||||||
|
@ -237,7 +247,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opt.Paths) > 0 {
|
if len(opt.Paths) > 0 {
|
||||||
features, err := parser.ParseFeatures(opt.Tags, opt.Paths)
|
features, err := parser.ParseFeatures(opt.FS, opt.Tags, opt.Paths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
return exitOptionError
|
return exitOptionError
|
||||||
|
@ -325,6 +335,9 @@ func (ts TestSuite) Run() int {
|
||||||
return exitOptionError
|
return exitOptionError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ts.Options.FS == nil {
|
||||||
|
ts.Options.FS = storage.FS{}
|
||||||
|
}
|
||||||
if ts.Options.ShowHelp {
|
if ts.Options.ShowHelp {
|
||||||
flag.CommandLine.Usage()
|
flag.CommandLine.Usage()
|
||||||
|
|
||||||
|
@ -349,13 +362,21 @@ func (ts TestSuite) RetrieveFeatures() ([]*models.Feature, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opt.Paths) == 0 {
|
if len(opt.Paths) == 0 {
|
||||||
inf, err := os.Stat("features")
|
inf, err := func() (fs.FileInfo, error) {
|
||||||
|
file, err := opt.FS.Open("features")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return file.Stat()
|
||||||
|
}()
|
||||||
if err == nil && inf.IsDir() {
|
if err == nil && inf.IsDir() {
|
||||||
opt.Paths = []string{"features"}
|
opt.Paths = []string{"features"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parser.ParseFeatures(opt.Tags, opt.Paths)
|
return parser.ParseFeatures(opt.FS, opt.Tags, opt.Paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultOptions() (*Options, error) {
|
func getDefaultOptions() (*Options, error) {
|
||||||
|
@ -369,6 +390,7 @@ func getDefaultOptions() (*Options, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
opt.Paths = flagSet.Args()
|
opt.Paths = flagSet.Args()
|
||||||
|
opt.FS = storage.FS{}
|
||||||
|
|
||||||
return opt, nil
|
return opt, nil
|
||||||
}
|
}
|
||||||
|
|
42
run_test.go
42
run_test.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -12,9 +13,10 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
"github.com/cucumber/gherkin/go/v26"
|
gherkin "github.com/cucumber/gherkin/go/v26"
|
||||||
"github.com/cucumber/messages/go/v21"
|
messages "github.com/cucumber/messages/go/v21"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
@ -751,3 +753,39 @@ func parseSeed(str string) (seed int64) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_TestSuite_RetreiveFeatures(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
fsys fs.FS
|
||||||
|
|
||||||
|
expFeatures int
|
||||||
|
}{
|
||||||
|
"standard features": {
|
||||||
|
fsys: fstest.MapFS{
|
||||||
|
"features/test.feature": {
|
||||||
|
Data: []byte(`Feature: test retrieve features
|
||||||
|
To test the feature
|
||||||
|
I must use this feature
|
||||||
|
|
||||||
|
Scenario: Test function RetrieveFeatures
|
||||||
|
Given I create a TestSuite
|
||||||
|
When I call TestSuite.RetrieveFeatures
|
||||||
|
Then I should have one feature`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expFeatures: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
features, err := TestSuite{
|
||||||
|
Name: "succeed",
|
||||||
|
Options: &Options{FS: test.fsys},
|
||||||
|
}.RetrieveFeatures()
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expFeatures, len(features))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cucumber/gherkin/go/v26"
|
gherkin "github.com/cucumber/gherkin/go/v26"
|
||||||
"github.com/cucumber/messages/go/v21"
|
messages "github.com/cucumber/messages/go/v21"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
|
@ -558,7 +558,7 @@ func (tc *godogFeaturesScenario) featurePath(path string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *godogFeaturesScenario) parseFeatures() error {
|
func (tc *godogFeaturesScenario) parseFeatures() error {
|
||||||
fts, err := parser.ParseFeatures("", tc.paths)
|
fts, err := parser.ParseFeatures(storage.FS{}, "", tc.paths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче