Skip to content

Commit ee00829

Browse files
author
florian.cazals
committed
feat: add test suite and test case parallelization
1 parent 33e0b29 commit ee00829

6 files changed

Lines changed: 366 additions & 67 deletions

File tree

cmd/venom/run/cmd.go

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,26 @@ var (
2525
path []string
2626
v *venom.Venom
2727

28-
variables []string
29-
secrets []string
30-
format string = "xml" // Set the default value for formatFlag
31-
varFiles []string
32-
outputDir string
33-
libDir string
34-
htmlReport bool
35-
stopOnFailure bool
36-
verbose int = 0 // Set the default value for verboseFlag
37-
38-
variablesFlag *[]string
39-
formatFlag *string
40-
varFilesFlag *[]string
41-
outputDirFlag *string
42-
libDirFlag *string
43-
stopOnFailureFlag *bool
44-
htmlReportFlag *bool
45-
verboseFlag *int
28+
variables []string
29+
secrets []string
30+
format string = "xml" // Set the default value for formatFlag
31+
varFiles []string
32+
outputDir string
33+
libDir string
34+
htmlReport bool
35+
stopOnFailure bool
36+
verbose int = 0 // Set the default value for verboseFlag
37+
parallelSuites int = 0 // Number of testsuites to run in parallel (0 = sequential)
38+
39+
variablesFlag *[]string
40+
formatFlag *string
41+
varFilesFlag *[]string
42+
outputDirFlag *string
43+
libDirFlag *string
44+
stopOnFailureFlag *bool
45+
htmlReportFlag *bool
46+
verboseFlag *int
47+
parallelSuitesFlag *int
4648
)
4749

4850
func init() {
@@ -54,6 +56,7 @@ func init() {
5456
variablesFlag = Cmd.Flags().StringArray("var", nil, "--var cds='cds -f config.json' --var cds2='cds -f config.json'")
5557
outputDirFlag = Cmd.PersistentFlags().String("output-dir", "", "Output Directory: create tests results file inside this directory")
5658
libDirFlag = Cmd.PersistentFlags().String("lib-dir", "", "Lib Directory: can contain user executors. example:/etc/venom/lib:$HOME/venom.d/lib")
59+
parallelSuitesFlag = Cmd.Flags().Int("parallel-suites", 0, "Number of testsuites to run in parallel (0 or 1 = sequential)")
5760
}
5861

5962
func initArgs(cmd *cobra.Command) {
@@ -115,6 +118,10 @@ func initFromCommandArguments(f *pflag.Flag) {
115118
variables = mergeVariables(varFlag, variables)
116119
}
117120
}
121+
case "parallel-suites":
122+
if parallelSuitesFlag != nil {
123+
parallelSuites = *parallelSuitesFlag
124+
}
118125
}
119126
}
120127

@@ -161,6 +168,7 @@ type ConfigFileData struct {
161168
Secrets *[]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
162169
VariablesFiles *[]string `json:"variables_files,omitempty" yaml:"variables_files,omitempty"`
163170
Verbosity *int `json:"verbosity,omitempty" yaml:"verbosity,omitempty"`
171+
ParallelSuites *int `json:"parallel_suites,omitempty" yaml:"parallel_suites,omitempty"`
164172
}
165173

166174
// Configuration file overrides the environment variables.
@@ -209,6 +217,9 @@ func initFromReaderConfigFile(reader io.Reader) error {
209217
if configFileData.Verbosity != nil {
210218
verbose = *configFileData.Verbosity
211219
}
220+
if configFileData.ParallelSuites != nil {
221+
parallelSuites = *configFileData.ParallelSuites
222+
}
212223

213224
return nil
214225
}
@@ -279,6 +290,13 @@ func initFromEnv(environ []string) ([]string, error) {
279290
v2 := int(v)
280291
verbose = v2
281292
}
293+
if os.Getenv("VENOM_PARALLEL_SUITES") != "" {
294+
v, err := strconv.ParseInt(os.Getenv("VENOM_PARALLEL_SUITES"), 10, 64)
295+
if err != nil {
296+
return nil, fmt.Errorf("invalid value for VENOM_PARALLEL_SUITES, must be a positive integer")
297+
}
298+
parallelSuites = int(v)
299+
}
282300

283301
for _, env := range environ {
284302
if strings.HasPrefix(env, "VENOM_VAR_") {
@@ -299,6 +317,7 @@ func displayArg(ctx context.Context) {
299317
venom.Debug(ctx, "option htmlReport=%v", htmlReport)
300318
venom.Debug(ctx, "option varFiles=%v", strings.Join(varFiles, " "))
301319
venom.Debug(ctx, "option verbose=%v", verbose)
320+
venom.Debug(ctx, "option parallelSuites=%v", parallelSuites)
302321
}
303322

304323
// Cmd run
@@ -338,6 +357,7 @@ var Cmd = &cobra.Command{
338357
v.StopOnFailure = stopOnFailure
339358
v.HtmlReport = htmlReport
340359
v.Verbose = verbose
360+
v.ParallelSuites = parallelSuites
341361

342362
if err := v.InitLogger(); err != nil {
343363
fmt.Fprintf(os.Stderr, "%v\n", err)

process.go

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package venom
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"os"
78
"path/filepath"
89
"strings"
10+
"sync"
911
"time"
1012

1113
nested "github.com/antonfisher/nested-logrus-formatter"
@@ -183,17 +185,25 @@ func (v *Venom) Process(ctx context.Context, path []string) error {
183185
v.Tests.Status = StatusRun
184186
v.Tests.Start = time.Now()
185187
Debug(ctx, "nb testsuites: %d", len(v.Tests.TestSuites))
186-
for i := range v.Tests.TestSuites {
187188

188-
v.Tests.TestSuites[i].Start = time.Now()
189-
// ##### RUN Test Suite Here
190-
if err := v.runTestSuite(ctx, &v.Tests.TestSuites[i]); err != nil {
189+
parallelSuites := v.ParallelSuites
190+
if parallelSuites <= 1 {
191+
// Sequential execution (default)
192+
for i := range v.Tests.TestSuites {
193+
v.Tests.TestSuites[i].Start = time.Now()
194+
if err := v.runTestSuite(ctx, &v.Tests.TestSuites[i]); err != nil {
195+
return err
196+
}
197+
v.Tests.TestSuites[i].End = time.Now()
198+
v.Tests.TestSuites[i].Duration = v.Tests.TestSuites[i].End.Sub(v.Tests.TestSuites[i].Start).Seconds()
199+
}
200+
} else {
201+
// Parallel execution of testsuites
202+
if err := v.processTestSuitesParallel(ctx, parallelSuites); err != nil {
191203
return err
192204
}
193-
194-
v.Tests.TestSuites[i].End = time.Now()
195-
v.Tests.TestSuites[i].Duration = v.Tests.TestSuites[i].End.Sub(v.Tests.TestSuites[i].Start).Seconds()
196205
}
206+
197207
v.Tests.End = time.Now()
198208
v.Tests.Duration = v.Tests.End.Sub(v.Tests.Start).Seconds()
199209

@@ -219,3 +229,66 @@ func (v *Venom) Process(ctx context.Context, path []string) error {
219229

220230
return nil
221231
}
232+
233+
// processTestSuitesParallel runs testsuites concurrently with a bounded worker pool.
234+
func (v *Venom) processTestSuitesParallel(ctx context.Context, maxWorkers int) error {
235+
type suiteResult struct {
236+
idx int
237+
err error
238+
output string
239+
}
240+
241+
jobs := make(chan int, len(v.Tests.TestSuites))
242+
results := make(chan suiteResult, len(v.Tests.TestSuites))
243+
244+
workerCount := maxWorkers
245+
if workerCount > len(v.Tests.TestSuites) {
246+
workerCount = len(v.Tests.TestSuites)
247+
}
248+
249+
var wg sync.WaitGroup
250+
for w := 0; w < workerCount; w++ {
251+
wg.Add(1)
252+
go func() {
253+
defer wg.Done()
254+
for idx := range jobs {
255+
ts := &v.Tests.TestSuites[idx]
256+
257+
// Buffer output to avoid interleaving between suites
258+
var buf bytes.Buffer
259+
vCopy := *v
260+
vCopy.PrintFunc = func(format string, a ...interface{}) (n int, err error) {
261+
return fmt.Fprintf(&buf, format, a...)
262+
}
263+
264+
ts.Start = time.Now()
265+
err := vCopy.runTestSuite(ctx, ts)
266+
ts.End = time.Now()
267+
ts.Duration = ts.End.Sub(ts.Start).Seconds()
268+
269+
results <- suiteResult{idx: idx, err: err, output: buf.String()}
270+
}
271+
}()
272+
}
273+
274+
// Feed jobs
275+
for i := range v.Tests.TestSuites {
276+
jobs <- i
277+
}
278+
close(jobs)
279+
280+
// Collect results in completion order, print output serially
281+
var firstErr error
282+
for range v.Tests.TestSuites {
283+
res := <-results
284+
if res.output != "" {
285+
v.Print("%s", res.output)
286+
}
287+
if res.err != nil && firstErr == nil {
288+
firstErr = res.err
289+
}
290+
}
291+
292+
wg.Wait()
293+
return firstErr
294+
}

process_files.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ func (v *Venom) readFiles(ctx context.Context, filesPath []string) (err error) {
124124
TestCases: make([]TestCase, len(testSuiteInput.TestCases)),
125125
Vars: testSuiteInput.Vars,
126126
Secrets: testSuiteInput.Secrets,
127+
Parallel: testSuiteInput.Parallel,
127128
}
128129
for i := range testSuiteInput.TestCases {
129130
ts.TestCases[i] = TestCase{

0 commit comments

Comments
 (0)