-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Tag aware sharding for cucumber-playwright (#933)
* Add gherkin deps * Add tag expression parser dep * Add module for parsing cucumber feature files * Enable shardGrepEnabled feature for cucumber playwright * Use the tags already available in configuration * Add another negation test case * Operate on the pickle * Readability * Organization * docs * Formatting * lint * update schema * rename * Align property names * Formatting for readability * Use cmp.Diff instead of DeepEqual for testing * Add warning if feature file could not be parsed * Formatting * efficiency
- Loading branch information
Showing
7 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// Package tag defines functions to parse cucumber feature files and filter them by cucumber tag expressions | ||
package tag | ||
|
||
import ( | ||
"io/fs" | ||
|
||
gherkin "github.com/cucumber/gherkin/go/v28" | ||
messages "github.com/cucumber/messages/go/v24" | ||
tagexpressions "github.com/cucumber/tag-expressions/go/v6" | ||
"github.com/rs/zerolog/log" | ||
) | ||
|
||
// MatchFiles finds feature files that include scenarios with tags that match the given tag expression. | ||
// A tag expression is a simple boolean expression including the logical operators "and", "or", "not". | ||
func MatchFiles(sys fs.FS, files []string, tagExpression string) (matched []string, unmatched []string) { | ||
tagMatcher, err := tagexpressions.Parse(tagExpression) | ||
|
||
if err != nil { | ||
return matched, unmatched | ||
|
||
} | ||
|
||
uuid := &messages.UUID{} | ||
|
||
for _, filename := range files { | ||
f, err := sys.Open(filename) | ||
if err != nil { | ||
continue | ||
} | ||
defer f.Close() | ||
|
||
doc, err := gherkin.ParseGherkinDocument(f, uuid.NewId) | ||
if err != nil { | ||
log.Warn(). | ||
Str("filename", filename). | ||
Msg("Could not parse file. It will be excluded from sharded execution.") | ||
continue | ||
} | ||
scenarios := gherkin.Pickles(*doc, filename, uuid.NewId) | ||
|
||
hasMatch := false | ||
for _, s := range scenarios { | ||
if match(s.Tags, tagMatcher) { | ||
matched = append(matched, filename) | ||
hasMatch = true | ||
break | ||
} | ||
} | ||
|
||
if !hasMatch { | ||
unmatched = append(unmatched, filename) | ||
} | ||
} | ||
return matched, unmatched | ||
} | ||
|
||
func match(tags []*messages.PickleTag, matcher tagexpressions.Evaluatable) bool { | ||
tagNames := make([]string, len(tags)) | ||
for i, t := range tags { | ||
tagNames[i] = t.Name | ||
} | ||
|
||
return matcher.Evaluate(tagNames) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package tag | ||
|
||
import ( | ||
"testing" | ||
"testing/fstest" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
) | ||
|
||
func TestMatchFiles(t *testing.T) { | ||
mockFS := fstest.MapFS{ | ||
"scenario1.feature": { | ||
Data: []byte(` | ||
@act1 | ||
Feature: Scenario 1 | ||
@interior @nomatch | ||
Scenario: Dinner scene | ||
When Turkey is served | ||
Then I say "bon appetit!" | ||
`), | ||
}, | ||
"scenario2.feature": { | ||
Data: []byte(` | ||
@act3 | ||
Feature: Scenario 2 | ||
@exterior @nomatch | ||
Scenario: Exterior scene | ||
When The character exits the house | ||
Then The camera pans out to show the exterior | ||
@interior @nomatch | ||
Scenario: Interior scene | ||
When The character enters the house | ||
Then The character's leitmotif starts | ||
`), | ||
}, | ||
"scenario3.feature": { | ||
Data: []byte(` | ||
@act3 @credits | ||
Feature: Scenario 3 | ||
@nomatch | ||
Scenario: Epilogue | ||
When The credits reach mid point | ||
Then Start the first mid-credit scene | ||
@nomatch | ||
Scenario: Last Bonus Scene | ||
When The credits reach the end | ||
Then Start the end-credit scene | ||
`), | ||
}, | ||
} | ||
|
||
files := []string{ | ||
"scenario1.feature", | ||
"scenario2.feature", | ||
"scenario3.feature", | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
files []string | ||
tagExpression string | ||
wantMatched []string | ||
wantUnmatched []string | ||
}{ | ||
{ | ||
name: "matches a single tag", | ||
files: files, | ||
tagExpression: "@act1", | ||
wantMatched: []string{ | ||
"scenario1.feature", | ||
}, | ||
wantUnmatched: []string{ | ||
"scenario2.feature", | ||
"scenario3.feature", | ||
}, | ||
}, | ||
{ | ||
name: "matches scenario tag", | ||
files: files, | ||
tagExpression: "@interior", | ||
wantMatched: []string{ | ||
"scenario1.feature", | ||
"scenario2.feature", | ||
}, | ||
wantUnmatched: []string{ | ||
"scenario3.feature", | ||
}, | ||
}, | ||
{ | ||
name: "matches multiple tags", | ||
files: files, | ||
tagExpression: "@act3 and @credits", | ||
wantMatched: []string{ | ||
"scenario3.feature", | ||
}, | ||
wantUnmatched: []string{ | ||
"scenario1.feature", | ||
"scenario2.feature", | ||
}, | ||
}, | ||
{ | ||
name: "matches multiple tags with negation", | ||
files: files, | ||
tagExpression: "@act3 and not @credits", | ||
wantMatched: []string{ | ||
"scenario2.feature", | ||
}, | ||
wantUnmatched: []string{ | ||
"scenario1.feature", | ||
"scenario3.feature", | ||
}, | ||
}, | ||
{ | ||
name: "no matches with negation", | ||
files: files, | ||
tagExpression: "not @nomatch", | ||
wantMatched: []string(nil), | ||
wantUnmatched: []string{ | ||
"scenario1.feature", | ||
"scenario2.feature", | ||
"scenario3.feature", | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
matched, unmatched := MatchFiles(mockFS, tt.files, tt.tagExpression) | ||
if diff := cmp.Diff(tt.wantMatched, matched); diff != "" { | ||
t.Errorf("MatchFiles() returned unexpected matched files (-want +got):\n%s", diff) | ||
} | ||
if diff := cmp.Diff(tt.wantUnmatched, unmatched); diff != "" { | ||
t.Errorf("MatchFiles() returned unexpected unmatched files (-want +got):\n%s", diff) | ||
} | ||
}) | ||
} | ||
} |