Skip to content

Commit

Permalink
add new option for created features with parsing from byte slices (#476)
Browse files Browse the repository at this point in the history
  • Loading branch information
akaswenwilk committed Jul 26, 2022
1 parent b2672bb commit d45a9aa
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 5 deletions.
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

0 comments on commit d45a9aa

Please sign in to comment.