From 8b708fab8fa5f35d8c034facfcaea15850cc1c6f Mon Sep 17 00:00:00 2001 From: Fabio Cicerchia Date: Thu, 30 Sep 2021 18:30:40 +0200 Subject: [PATCH] added support for parallel execution [issue #13] --- cmd/go-mutesting/main.go | 115 +++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 35 deletions(-) diff --git a/cmd/go-mutesting/main.go b/cmd/go-mutesting/main.go index ee786f5..5911b89 100644 --- a/cmd/go-mutesting/main.go +++ b/cmd/go-mutesting/main.go @@ -15,7 +15,9 @@ import ( "os/exec" "path/filepath" "regexp" + "runtime" "strings" + "sync" "syscall" "github.com/jessevdk/go-flags" @@ -64,6 +66,7 @@ type options struct { Exec string `long:"exec" description:"Execute this command for every mutation (by default the built-in exec command is used)"` NoExec bool `long:"no-exec" description:"Skip the built-in exec command and just generate the mutations"` Timeout uint `long:"exec-timeout" description:"Sets a timeout for the command execution (in seconds)" default:"10"` + Jobs uint `long:"jobs" description:"Allow N jobs at once" default:"1"` } `group:"Exec options"` Test struct { @@ -158,6 +161,18 @@ func (ms *mutationStats) Total() int { return ms.passed + ms.failed + ms.skipped } +func getJobs(opts *options) int { + jobs := int(opts.Exec.Jobs) + if jobs < 0 { + jobs = 1 + } + if jobs > runtime.NumCPU() { + jobs = runtime.NumCPU() + } + + return jobs +} + func mainCmd(args []string) int { var opts = &options{} var mutationBlackList = map[string]struct{}{} @@ -251,45 +266,33 @@ MUTATOR: stats := &mutationStats{} - for _, file := range files { - verbose(opts, "Mutate %q", file) - - src, fset, pkg, info, err := mutesting.ParseAndTypeCheckFile(file) - if err != nil { - return exitError(err.Error()) - } - - err = os.MkdirAll(tmpDir+"/"+filepath.Dir(file), 0755) - if err != nil { - panic(err) - } - - tmpFile := tmpDir + "/" + file - - originalFile := fmt.Sprintf("%s.original", tmpFile) - err = osutil.CopyFile(file, originalFile) - if err != nil { - panic(err) - } - debug(opts, "Save original into %q", originalFile) - - mutationID := 0 - - if opts.Filter.Match != "" { - m, err := regexp.Compile(opts.Filter.Match) - if err != nil { - return exitError("Match regex is not valid: %v", err) - } + jobs := getJobs(opts) + c := make(chan string) + var wg sync.WaitGroup + wg.Add(jobs) + for runningJobs := 0; runningJobs < jobs; runningJobs++ { + go func(c chan string) { + for { + file, more := <-c + if more == false { + wg.Done() + return + } - for _, f := range astutil.Functions(src) { - if m.MatchString(f.Name.Name) { - mutationID = mutate(opts, mutators, mutationBlackList, mutationID, pkg, info, file, fset, src, f, tmpFile, execs, stats) + err := processFile(opts, tmpDir, file, mutators, mutationBlackList, execs, stats) + if err != returnOk { + // Stop execution right away. + wg.Done() + return } } - } else { - _ = mutate(opts, mutators, mutationBlackList, mutationID, pkg, info, file, fset, src, src, tmpFile, execs, stats) - } + }(c) + } + for _, file := range files { + c <- file } + close(c) + wg.Wait() if !opts.General.DoNotRemoveTmpFolder { err = os.RemoveAll(tmpDir) @@ -308,6 +311,48 @@ MUTATOR: return returnOk } +func processFile(opts *options, tmpDir, file string, mutators []mutatorItem, mutationBlackList map[string]struct{}, execs []string, stats *mutationStats) int { + verbose(opts, "Mutate %q", file) + + src, fset, pkg, info, err := mutesting.ParseAndTypeCheckFile(file) + if err != nil { + return exitError(err.Error()) + } + + err = os.MkdirAll(tmpDir+"/"+filepath.Dir(file), 0755) + if err != nil { + panic(err) + } + + tmpFile := tmpDir + "/" + file + + originalFile := fmt.Sprintf("%s.original", tmpFile) + err = osutil.CopyFile(file, originalFile) + if err != nil { + panic(err) + } + debug(opts, "Save original into %q", originalFile) + + mutationID := 0 + + if opts.Filter.Match != "" { + m, err := regexp.Compile(opts.Filter.Match) + if err != nil { + return exitError("Match regex is not valid: %v", err) + } + + for _, f := range astutil.Functions(src) { + if m.MatchString(f.Name.Name) { + mutationID = mutate(opts, mutators, mutationBlackList, mutationID, pkg, info, file, fset, src, f, tmpFile, execs, stats) + } + } + } else { + _ = mutate(opts, mutators, mutationBlackList, mutationID, pkg, info, file, fset, src, src, tmpFile, execs, stats) + } + + return returnOk +} + func mutate(opts *options, mutators []mutatorItem, mutationBlackList map[string]struct{}, mutationID int, pkg *types.Package, info *types.Info, file string, fset *token.FileSet, src ast.Node, node ast.Node, tmpFile string, execs []string, stats *mutationStats) int { for _, m := range mutators { debug(opts, "Mutator %s", m.Name)