Skip to content

Commit

Permalink
feat: Support shard by scenario (#940)
Browse files Browse the repository at this point in the history
* feat: Support shard by scenario

* unique names

* refine JSON schema

* refine JSON schema

* Update internal/cucumber/config.go

Co-authored-by: Alex Plischke <alex.plischke@saucelabs.com>

* Update internal/cucumber/scenario/scenario.go

Co-authored-by: Alex Plischke <alex.plischke@saucelabs.com>

* read feature separately

---------

Co-authored-by: Alex Plischke <alex.plischke@saucelabs.com>
  • Loading branch information
tianfeng92 and alexplischke authored Aug 26, 2024
1 parent f00fcc8 commit ca4dd29
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 22 deletions.
5 changes: 3 additions & 2 deletions api/saucectl.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2468,11 +2468,12 @@
]
},
"shard": {
"description": "When sharding is configured, saucectl automatically splits the tests (e.g. by spec or concurrency) so that they can easily run in parallel.",
"description": "When sharding is configured, saucectl automatically splits the tests (e.g. by spec, concurrency or scenario) so that they can easily run in parallel.",
"enum": [
"",
"concurrency",
"spec"
"spec",
"scenario"
]
},
"shardTagsEnabled": {
Expand Down
5 changes: 3 additions & 2 deletions api/v1alpha/framework/playwright-cucumberjs.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,12 @@
]
},
"shard": {
"description": "When sharding is configured, saucectl automatically splits the tests (e.g. by spec or concurrency) so that they can easily run in parallel.",
"description": "When sharding is configured, saucectl automatically splits the tests (e.g. by spec, concurrency or scenario) so that they can easily run in parallel.",
"enum": [
"",
"concurrency",
"spec"
"spec",
"scenario"
]
},
"shardTagsEnabled": {
Expand Down
12 changes: 11 additions & 1 deletion internal/cucumber/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/saucelabs/saucectl/internal/concurrency"
"github.com/saucelabs/saucectl/internal/config"
"github.com/saucelabs/saucectl/internal/cucumber/scenario"
"github.com/saucelabs/saucectl/internal/cucumber/tag"
"github.com/saucelabs/saucectl/internal/fpath"
"github.com/saucelabs/saucectl/internal/insights"
Expand Down Expand Up @@ -231,7 +232,7 @@ func shardSuites(rootDir string, suites []Suite, ccy int) ([]Suite, error) {
var shardedSuites []Suite

for _, s := range suites {
if s.Shard != "spec" && s.Shard != "concurrency" {
if s.Shard == "" {
shardedSuites = append(shardedSuites, s)
continue
}
Expand Down Expand Up @@ -291,6 +292,15 @@ func shardSuites(rootDir string, suites []Suite, ccy int) ([]Suite, error) {
shardedSuites = append(shardedSuites, replica)
}
}
if s.Shard == "scenario" {
scenarios := scenario.List(os.DirFS(rootDir), testFiles)
for _, name := range scenario.GetUniqueNames(scenarios) {
replica := s
replica.Name = fmt.Sprintf("%s - %s", s.Name, name)
replica.Options.Name = fmt.Sprintf("^%s$", name)
shardedSuites = append(shardedSuites, replica)
}
}
}

return shardedSuites, nil
Expand Down
55 changes: 55 additions & 0 deletions internal/cucumber/scenario/scenario.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package scenario

import (
"io/fs"

gherkin "github.com/cucumber/gherkin/go/v28"
messages "github.com/cucumber/messages/go/v24"
"github.com/rs/zerolog/log"
)

// List parses the provided files and returns a list of scenarios.
func List(sys fs.FS, files []string) []*messages.Pickle {
uuid := &messages.UUID{}

var scenarios []*messages.Pickle
for _, filename := range files {
scenarios = append(scenarios, ReadFile(sys, filename, uuid)...)
}
return scenarios
}

// ReadFile reads a feature file and returns the parsed list of scenarios.
func ReadFile(sys fs.FS, filename string, uuid *messages.UUID) []*messages.Pickle {
f, err := sys.Open(filename)
if err != nil {
log.Warn().Str("filename", filename).Msgf("Failed to open the file: %v", err)
return nil
}
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.")
return nil
}
return gherkin.Pickles(*doc, filename, uuid.NewId)
}

// GetUniqueNames extracts and returns unique scenario names.
func GetUniqueNames(scenarios []*messages.Pickle) []string {
uniqueMap := make(map[string]bool)

for _, s := range scenarios {
uniqueMap[s.Name] = true
}

var names []string
for name := range uniqueMap {
names = append(names, name)
}

return names
}
19 changes: 2 additions & 17 deletions internal/cucumber/tag/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ 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"
"github.com/saucelabs/saucectl/internal/cucumber/scenario"
)

// MatchFiles finds feature files that include scenarios with tags that match the given tag expression.
Expand All @@ -23,21 +22,7 @@ func MatchFiles(sys fs.FS, files []string, tagExpression string) (matched []stri
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)

scenarios := scenario.ReadFile(sys, filename, uuid)
hasMatch := false
for _, s := range scenarios {
if match(s.Tags, tagMatcher) {
Expand Down

0 comments on commit ca4dd29

Please sign in to comment.