add new option for created features with parsing from byte slices (#476)
Этот коммит содержится в:
родитель
b2672bb933
коммит
d45a9aaaa3
6 изменённых файлов: 226 добавлений и 5 удалений
|
@ -64,4 +64,14 @@ type Options struct {
|
||||||
|
|
||||||
// TestingT runs scenarios as subtests.
|
// TestingT runs scenarios as subtests.
|
||||||
TestingT *testing.T
|
TestingT *testing.T
|
||||||
|
|
||||||
|
// FeatureContents allows passing in each feature manually
|
||||||
|
// where the contents of each feature is stored as a byte slice
|
||||||
|
// in a map entry
|
||||||
|
FeatureContents []Feature
|
||||||
|
}
|
||||||
|
|
||||||
|
type Feature struct {
|
||||||
|
Name string
|
||||||
|
Contents []byte
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/cucumber/gherkin-go/v19"
|
"github.com/cucumber/gherkin-go/v19"
|
||||||
"github.com/cucumber/messages-go/v16"
|
"github.com/cucumber/messages-go/v16"
|
||||||
|
|
||||||
|
"github.com/cucumber/godog/internal/flags"
|
||||||
"github.com/cucumber/godog/internal/models"
|
"github.com/cucumber/godog/internal/models"
|
||||||
"github.com/cucumber/godog/internal/tags"
|
"github.com/cucumber/godog/internal/tags"
|
||||||
)
|
)
|
||||||
|
@ -53,6 +54,22 @@ func parseFeatureFile(path string, newIDFunc func() string) (*models.Feature, er
|
||||||
return &f, nil
|
return &f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseBytes(path string, feature []byte, newIDFunc func() string) (*models.Feature, error) {
|
||||||
|
reader := bytes.NewReader(feature)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s - %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gherkinDocument.Uri = path
|
||||||
|
pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc)
|
||||||
|
|
||||||
|
f := models.Feature{GherkinDocument: gherkinDocument, Pickles: pickles, Content: buf.Bytes()}
|
||||||
|
return &f, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseFeatureDir(dir string, newIDFunc func() string) ([]*models.Feature, error) {
|
func parseFeatureDir(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, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error {
|
||||||
|
@ -162,6 +179,41 @@ func ParseFeatures(filter string, paths []string) ([]*models.Feature, error) {
|
||||||
return features, nil
|
return features, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FeatureContent = flags.Feature
|
||||||
|
|
||||||
|
func ParseFromBytes(filter string, featuresInputs []FeatureContent) ([]*models.Feature, error) {
|
||||||
|
var order int
|
||||||
|
|
||||||
|
featureIdxs := make(map[string]int)
|
||||||
|
uniqueFeatureURI := make(map[string]*models.Feature)
|
||||||
|
newIDFunc := (&messages.Incrementing{}).NewId
|
||||||
|
for _, f := range featuresInputs {
|
||||||
|
ft, err := parseBytes(f.Name, f.Contents, newIDFunc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, duplicate := uniqueFeatureURI[ft.Uri]; duplicate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueFeatureURI[ft.Uri] = ft
|
||||||
|
featureIdxs[ft.Uri] = order
|
||||||
|
|
||||||
|
order++
|
||||||
|
}
|
||||||
|
|
||||||
|
var features = make([]*models.Feature, len(uniqueFeatureURI))
|
||||||
|
for uri, feature := range uniqueFeatureURI {
|
||||||
|
idx := featureIdxs[uri]
|
||||||
|
features[idx] = feature
|
||||||
|
}
|
||||||
|
|
||||||
|
features = filterFeatures(filter, features)
|
||||||
|
|
||||||
|
return features, nil
|
||||||
|
}
|
||||||
|
|
||||||
func filterFeatures(filter string, features []*models.Feature) (result []*models.Feature) {
|
func filterFeatures(filter string, features []*models.Feature) (result []*models.Feature) {
|
||||||
for _, ft := range features {
|
for _, ft := range features {
|
||||||
ft.Pickles = tags.ApplyTagFilter(filter, ft.Pickles)
|
ft.Pickles = tags.ApplyTagFilter(filter, ft.Pickles)
|
||||||
|
|
|
@ -37,6 +37,64 @@ func Test_FeatureFilePathParser(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_ParseFromBytes_FromMultipleFeatures_DuplicateNames(t *testing.T) {
|
||||||
|
eatGodogContents := `
|
||||||
|
Feature: eat godogs
|
||||||
|
In order to be happy
|
||||||
|
As a hungry gopher
|
||||||
|
I need to be able to eat godogs
|
||||||
|
|
||||||
|
Scenario: Eat 5 out of 12
|
||||||
|
Given there are 12 godogs
|
||||||
|
When I eat 5
|
||||||
|
Then there should be 7 remaining`
|
||||||
|
input := []parser.FeatureContent{
|
||||||
|
{Name: "MyCoolDuplicatedFeature", Contents: []byte(eatGodogContents)},
|
||||||
|
{Name: "MyCoolDuplicatedFeature", Contents: []byte(eatGodogContents)},
|
||||||
|
}
|
||||||
|
|
||||||
|
featureFromBytes, err := parser.ParseFromBytes("", input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, featureFromBytes, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ParseFromBytes_FromMultipleFeatures(t *testing.T) {
|
||||||
|
featureFileName := "godogs.feature"
|
||||||
|
eatGodogContents := `
|
||||||
|
Feature: eat godogs
|
||||||
|
In order to be happy
|
||||||
|
As a hungry gopher
|
||||||
|
I need to be able to eat godogs
|
||||||
|
|
||||||
|
Scenario: Eat 5 out of 12
|
||||||
|
Given there are 12 godogs
|
||||||
|
When I eat 5
|
||||||
|
Then there should be 7 remaining`
|
||||||
|
|
||||||
|
baseDir := filepath.Join(os.TempDir(), t.Name(), "godogs")
|
||||||
|
errA := os.MkdirAll(baseDir+"/a", 0755)
|
||||||
|
defer os.RemoveAll(baseDir)
|
||||||
|
|
||||||
|
require.Nil(t, errA)
|
||||||
|
|
||||||
|
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.Len(t, featureFromFile, 1)
|
||||||
|
|
||||||
|
input := []parser.FeatureContent{
|
||||||
|
{Name: filepath.Join(baseDir, featureFileName), Contents: []byte(eatGodogContents)},
|
||||||
|
}
|
||||||
|
|
||||||
|
featureFromBytes, err := parser.ParseFromBytes("", input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, featureFromBytes, 1)
|
||||||
|
|
||||||
|
assert.Equal(t, featureFromFile, featureFromBytes)
|
||||||
|
}
|
||||||
|
|
||||||
func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
|
func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
|
||||||
const featureFileName = "godogs.feature"
|
const featureFileName = "godogs.feature"
|
||||||
const featureFileContents = `Feature: eat godogs
|
const featureFileContents = `Feature: eat godogs
|
||||||
|
|
22
run.go
22
run.go
|
@ -213,7 +213,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
|
||||||
return exitOptionError
|
return exitOptionError
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opt.Paths) == 0 {
|
if len(opt.Paths) == 0 && len(opt.FeatureContents) == 0 {
|
||||||
inf, err := os.Stat("features")
|
inf, err := os.Stat("features")
|
||||||
if err == nil && inf.IsDir() {
|
if err == nil && inf.IsDir() {
|
||||||
opt.Paths = []string{"features"}
|
opt.Paths = []string{"features"}
|
||||||
|
@ -226,10 +226,22 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
|
||||||
|
|
||||||
runner.fmt = multiFmt.FormatterFunc(suiteName, output)
|
runner.fmt = multiFmt.FormatterFunc(suiteName, output)
|
||||||
|
|
||||||
var err error
|
if len(opt.FeatureContents) > 0 {
|
||||||
if runner.features, err = parser.ParseFeatures(opt.Tags, opt.Paths); err != nil {
|
features, err := parser.ParseFromBytes(opt.Tags, opt.FeatureContents)
|
||||||
fmt.Fprintln(os.Stderr, err)
|
if err != nil {
|
||||||
return exitOptionError
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return exitOptionError
|
||||||
|
}
|
||||||
|
runner.features = append(runner.features, features...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opt.Paths) > 0 {
|
||||||
|
features, err := parser.ParseFeatures(opt.Tags, opt.Paths)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return exitOptionError
|
||||||
|
}
|
||||||
|
runner.features = append(runner.features, features...)
|
||||||
}
|
}
|
||||||
|
|
||||||
runner.storage = storage.NewStorage()
|
runner.storage = storage.NewStorage()
|
||||||
|
|
86
run_test.go
86
run_test.go
|
@ -263,6 +263,92 @@ func Test_ByDefaultRunsFeaturesPath(t *testing.T) {
|
||||||
assert.Equal(t, exitSuccess, status)
|
assert.Equal(t, exitSuccess, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_RunsWithFeatureContentsOption(t *testing.T) {
|
||||||
|
items, err := ioutil.ReadDir("./features")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var featureContents []Feature
|
||||||
|
for _, item := range items {
|
||||||
|
if !item.IsDir() && strings.Contains(item.Name(), ".feature") {
|
||||||
|
contents, err := os.ReadFile("./features/" + item.Name())
|
||||||
|
require.NoError(t, err)
|
||||||
|
featureContents = append(featureContents, Feature{
|
||||||
|
Name: item.Name(),
|
||||||
|
Contents: contents,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := Options{
|
||||||
|
Format: "progress",
|
||||||
|
Output: ioutil.Discard,
|
||||||
|
Strict: true,
|
||||||
|
FeatureContents: featureContents,
|
||||||
|
}
|
||||||
|
|
||||||
|
status := TestSuite{
|
||||||
|
Name: "fails",
|
||||||
|
ScenarioInitializer: func(_ *ScenarioContext) {},
|
||||||
|
Options: &opts,
|
||||||
|
}.Run()
|
||||||
|
|
||||||
|
// should fail in strict mode due to undefined steps
|
||||||
|
assert.Equal(t, exitFailure, status)
|
||||||
|
|
||||||
|
opts.Strict = false
|
||||||
|
status = TestSuite{
|
||||||
|
Name: "succeeds",
|
||||||
|
ScenarioInitializer: func(_ *ScenarioContext) {},
|
||||||
|
Options: &opts,
|
||||||
|
}.Run()
|
||||||
|
|
||||||
|
// should succeed in non strict mode due to undefined steps
|
||||||
|
assert.Equal(t, exitSuccess, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_RunsWithFeatureContentsAndPathsOptions(t *testing.T) {
|
||||||
|
featureContents := []Feature{
|
||||||
|
{
|
||||||
|
Name: "MySuperCoolFeature",
|
||||||
|
Contents: []byte(`
|
||||||
|
Feature: run features from bytes
|
||||||
|
Scenario: should run a normal feature
|
||||||
|
Given a feature "normal.feature" file:
|
||||||
|
"""
|
||||||
|
Feature: normal feature
|
||||||
|
|
||||||
|
Scenario: parse a scenario
|
||||||
|
Given a feature path "features/load.feature:6"
|
||||||
|
When I parse features
|
||||||
|
Then I should have 1 scenario registered
|
||||||
|
"""
|
||||||
|
When I run feature suite
|
||||||
|
Then the suite should have passed
|
||||||
|
And the following steps should be passed:
|
||||||
|
"""
|
||||||
|
a feature path "features/load.feature:6"
|
||||||
|
I parse features
|
||||||
|
I should have 1 scenario registered
|
||||||
|
"""`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := Options{
|
||||||
|
Format: "progress",
|
||||||
|
Output: ioutil.Discard,
|
||||||
|
Paths: []string{"./features"},
|
||||||
|
FeatureContents: featureContents,
|
||||||
|
}
|
||||||
|
|
||||||
|
status := TestSuite{
|
||||||
|
Name: "succeeds",
|
||||||
|
ScenarioInitializer: func(_ *ScenarioContext) {},
|
||||||
|
Options: &opts,
|
||||||
|
}.Run()
|
||||||
|
|
||||||
|
assert.Equal(t, exitSuccess, status)
|
||||||
|
}
|
||||||
|
|
||||||
func bufErrorPipe(t *testing.T) (io.ReadCloser, func()) {
|
func bufErrorPipe(t *testing.T) (io.ReadCloser, func()) {
|
||||||
stderr := os.Stderr
|
stderr := os.Stderr
|
||||||
r, w, err := os.Pipe()
|
r, w, err := os.Pipe()
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/cucumber/godog/formatters"
|
"github.com/cucumber/godog/formatters"
|
||||||
"github.com/cucumber/godog/internal/builder"
|
"github.com/cucumber/godog/internal/builder"
|
||||||
|
"github.com/cucumber/godog/internal/flags"
|
||||||
"github.com/cucumber/godog/internal/models"
|
"github.com/cucumber/godog/internal/models"
|
||||||
"github.com/cucumber/messages-go/v16"
|
"github.com/cucumber/messages-go/v16"
|
||||||
)
|
)
|
||||||
|
@ -316,3 +317,5 @@ func (ctx *ScenarioContext) Step(expr, stepFunc interface{}) {
|
||||||
func Build(bin string) error {
|
func Build(bin string) error {
|
||||||
return builder.Build(bin)
|
return builder.Build(bin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Feature = flags.Feature
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче