Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new option for created features with parsing from byte slices #476

Merged
10 changes: 10 additions & 0 deletions internal/flags/options.go
Expand Up @@ -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
}
52 changes: 52 additions & 0 deletions internal/parser/parser.go
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
58 changes: 58 additions & 0 deletions internal/parser/parser_test.go
Expand Up @@ -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
Expand Down
22 changes: 17 additions & 5 deletions run.go
Expand Up @@ -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"}
Expand All @@ -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()
Expand Down
86 changes: 86 additions & 0 deletions run_test.go
Expand Up @@ -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()
Expand Down
3 changes: 3 additions & 0 deletions test_context.go
Expand Up @@ -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"
)
Expand Down Expand Up @@ -316,3 +317,5 @@ func (ctx *ScenarioContext) Step(expr, stepFunc interface{}) {
func Build(bin string) error {
return builder.Build(bin)
}

type Feature = flags.Feature