121 lines
3.2 KiB
Go
121 lines
3.2 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"log"
|
||
|
"os"
|
||
|
"path"
|
||
|
|
||
|
"github.com/codeskyblue/go-sh"
|
||
|
)
|
||
|
|
||
|
type jobDefinition struct {
|
||
|
runNumber int
|
||
|
compositionPath string
|
||
|
outputDir string
|
||
|
skipStdout bool
|
||
|
}
|
||
|
|
||
|
type jobResult struct {
|
||
|
job jobDefinition
|
||
|
runError error
|
||
|
}
|
||
|
|
||
|
func runComposition(job jobDefinition) jobResult {
|
||
|
outputArchive := path.Join(job.outputDir, "test-outputs.tgz")
|
||
|
cmd := sh.Command("testground", "run", "composition", "-f", job.compositionPath, "--collect", "-o", outputArchive)
|
||
|
if err := os.MkdirAll(job.outputDir, os.ModePerm); err != nil {
|
||
|
return jobResult{runError: fmt.Errorf("unable to make output directory: %w", err)}
|
||
|
}
|
||
|
|
||
|
outPath := path.Join(job.outputDir, "run.out")
|
||
|
outFile, err := os.Create(outPath)
|
||
|
if err != nil {
|
||
|
return jobResult{runError: fmt.Errorf("unable to create output file %s: %w", outPath, err)}
|
||
|
}
|
||
|
if job.skipStdout {
|
||
|
cmd.Stdout = outFile
|
||
|
} else {
|
||
|
cmd.Stdout = io.MultiWriter(os.Stdout, outFile)
|
||
|
}
|
||
|
log.Printf("starting test run %d. writing testground client output to %s\n", job.runNumber, outPath)
|
||
|
if err = cmd.Run(); err != nil {
|
||
|
return jobResult{job: job, runError: err}
|
||
|
}
|
||
|
return jobResult{job: job}
|
||
|
}
|
||
|
|
||
|
func worker(id int, jobs <-chan jobDefinition, results chan<- jobResult) {
|
||
|
log.Printf("started worker %d\n", id)
|
||
|
for j := range jobs {
|
||
|
log.Printf("worker %d started test run %d\n", id, j.runNumber)
|
||
|
results <- runComposition(j)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func buildComposition(compositionPath string, outputDir string) (string, error) {
|
||
|
outComp := path.Join(outputDir, "composition.toml")
|
||
|
err := sh.Command("cp", compositionPath, outComp).Run()
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
return outComp, sh.Command("testground", "build", "composition", "-w", "-f", outComp).Run()
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
runs := flag.Int("runs", 1, "number of times to run composition")
|
||
|
parallelism := flag.Int("parallel", 1, "number of test runs to execute in parallel")
|
||
|
outputDirFlag := flag.String("output", "", "path to output directory (will use temp dir if unset)")
|
||
|
flag.Parse()
|
||
|
|
||
|
if len(flag.Args()) != 1 {
|
||
|
log.Fatal("must provide a single composition file path argument")
|
||
|
}
|
||
|
|
||
|
outdir := *outputDirFlag
|
||
|
if outdir == "" {
|
||
|
var err error
|
||
|
outdir, err = ioutil.TempDir(os.TempDir(), "oni-batch-run-")
|
||
|
if err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
if err := os.MkdirAll(outdir, os.ModePerm); err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
|
||
|
compositionPath := flag.Args()[0]
|
||
|
|
||
|
// first build the composition and write out the artifacts.
|
||
|
// we copy to a temp file first to avoid modifying the original
|
||
|
log.Printf("building composition %s\n", compositionPath)
|
||
|
compositionPath, err := buildComposition(compositionPath, outdir)
|
||
|
if err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
|
||
|
jobs := make(chan jobDefinition, *runs)
|
||
|
results := make(chan jobResult, *runs)
|
||
|
for w := 1; w <= *parallelism; w++ {
|
||
|
go worker(w, jobs, results)
|
||
|
}
|
||
|
|
||
|
for j := 1; j <= *runs; j++ {
|
||
|
dir := path.Join(outdir, fmt.Sprintf("run-%d", j))
|
||
|
skipStdout := *parallelism != 1
|
||
|
jobs <- jobDefinition{runNumber: j, compositionPath: compositionPath, outputDir: dir, skipStdout: skipStdout}
|
||
|
}
|
||
|
close(jobs)
|
||
|
|
||
|
for i := 0; i < *runs; i++ {
|
||
|
r := <-results
|
||
|
if r.runError != nil {
|
||
|
log.Printf("error running job %d: %s\n", r.job.runNumber, r.runError)
|
||
|
}
|
||
|
}
|
||
|
}
|