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 *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/messages-go/v16"
|
||||
|
||||
"github.com/cucumber/godog/internal/flags"
|
||||
"github.com/cucumber/godog/internal/models"
|
||||
"github.com/cucumber/godog/internal/tags"
|
||||
)
|
||||
|
@ -53,6 +54,22 @@ func parseFeatureFile(path string, newIDFunc func() string) (*models.Feature, er
|
|||
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) {
|
||||
var features []*models.Feature
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
for _, ft := range features {
|
||||
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) {
|
||||
const featureFileName = "godogs.feature"
|
||||
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
|
||||
}
|
||||
|
||||
if len(opt.Paths) == 0 {
|
||||
if len(opt.Paths) == 0 && len(opt.FeatureContents) == 0 {
|
||||
inf, err := os.Stat("features")
|
||||
if err == nil && inf.IsDir() {
|
||||
opt.Paths = []string{"features"}
|
||||
|
@ -226,10 +226,22 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
|
|||
|
||||
runner.fmt = multiFmt.FormatterFunc(suiteName, output)
|
||||
|
||||
var err error
|
||||
if runner.features, err = parser.ParseFeatures(opt.Tags, opt.Paths); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return exitOptionError
|
||||
if len(opt.FeatureContents) > 0 {
|
||||
features, err := parser.ParseFromBytes(opt.Tags, opt.FeatureContents)
|
||||
if err != nil {
|
||||
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()
|
||||
|
|
86
run_test.go
86
run_test.go
|
@ -263,6 +263,92 @@ func Test_ByDefaultRunsFeaturesPath(t *testing.T) {
|
|||
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()) {
|
||||
stderr := os.Stderr
|
||||
r, w, err := os.Pipe()
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/cucumber/godog/formatters"
|
||||
"github.com/cucumber/godog/internal/builder"
|
||||
"github.com/cucumber/godog/internal/flags"
|
||||
"github.com/cucumber/godog/internal/models"
|
||||
"github.com/cucumber/messages-go/v16"
|
||||
)
|
||||
|
@ -316,3 +317,5 @@ func (ctx *ScenarioContext) Step(expr, stepFunc interface{}) {
|
|||
func Build(bin string) error {
|
||||
return builder.Build(bin)
|
||||
}
|
||||
|
||||
type Feature = flags.Feature
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче