lotus/lotus-soup/runner/main.go
Yusef Napora 0d0fc2d433
randomized network params & batch runner (#142)
Co-authored-by: Anton Evangelatov <anton.evangelatov@gmail.com>
Co-authored-by: Raúl Kripalani <raul@protocol.ai>
2020-07-27 14:58:27 +01:00

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)
}
}
}