Allow running multiple dEQP tests per process
This CL adds a new command line argument, --max-tests-per-proc,
which can be set to a value greater than 1 to run, at most, that
many tests per process (before this CL, there would always be 1
test per process).
If there is anything unexpected in the output while running
multiple tests at once, Regres will try again and re-run each test
individually in order to get a proper per test output.
Bug: b/253530501
Change-Id: I70e7f1fa86e001c834ef5b98f766e5db5d20ebe7
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/69128
Commit-Queue: Alexis Hétu <sugoi@google.com>
Tested-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Presubmit-Ready: Alexis Hétu <sugoi@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
diff --git a/tests/regres/cmd/regres/main.go b/tests/regres/cmd/regres/main.go
index 5048199..e7ed94e 100644
--- a/tests/regres/cmd/regres/main.go
+++ b/tests/regres/cmd/regres/main.go
@@ -83,20 +83,21 @@
numParallelTests = runtime.NumCPU()
llvmVersion = llvm.Version{Major: 10}
- cacheDir = flag.String("cache", "cache", "path to the output cache directory")
- gerritEmail = flag.String("email", "$SS_REGRES_EMAIL", "gerrit email address for posting regres results")
- gerritUser = flag.String("user", "$SS_REGRES_USER", "gerrit username for posting regres results")
- gerritPass = flag.String("pass", "$SS_REGRES_PASS", "gerrit password for posting regres results")
- githubUser = flag.String("gh-user", "$SS_GITHUB_USER", "github user for posting coverage results")
- githubPass = flag.String("gh-pass", "$SS_GITHUB_PASS", "github password for posting coverage results")
- keepCheckouts = flag.Bool("keep", false, "don't delete checkout directories after use")
- dryRun = flag.Bool("dry", false, "don't post regres reports to gerrit")
- maxProcMemory = flag.Uint64("max-proc-mem", shell.MaxProcMemory, "maximum virtual memory per child process")
- dailyNow = flag.Bool("dailynow", false, "Start by running the daily pass")
- dailyOnly = flag.Bool("dailyonly", false, "Run only the daily pass")
- dailyChange = flag.String("dailychange", "", "Change hash to use for daily pass, HEAD if not provided")
- priority = flag.Int("priority", 0, "Prioritize a single change with the given number")
- limit = flag.Int("limit", 0, "only run a maximum of this number of tests")
+ cacheDir = flag.String("cache", "cache", "path to the output cache directory")
+ gerritEmail = flag.String("email", "$SS_REGRES_EMAIL", "gerrit email address for posting regres results")
+ gerritUser = flag.String("user", "$SS_REGRES_USER", "gerrit username for posting regres results")
+ gerritPass = flag.String("pass", "$SS_REGRES_PASS", "gerrit password for posting regres results")
+ githubUser = flag.String("gh-user", "$SS_GITHUB_USER", "github user for posting coverage results")
+ githubPass = flag.String("gh-pass", "$SS_GITHUB_PASS", "github password for posting coverage results")
+ keepCheckouts = flag.Bool("keep", false, "don't delete checkout directories after use")
+ dryRun = flag.Bool("dry", false, "don't post regres reports to gerrit")
+ maxTestsPerProc = flag.Int("max-tests-per-proc", 1, "maximum number of tests running in a single process")
+ maxProcMemory = flag.Uint64("max-proc-mem", shell.MaxProcMemory, "maximum virtual memory per child process")
+ dailyNow = flag.Bool("dailynow", false, "Start by running the daily pass")
+ dailyOnly = flag.Bool("dailyonly", false, "Run only the daily pass")
+ dailyChange = flag.String("dailychange", "", "Change hash to use for daily pass, HEAD if not provided")
+ priority = flag.Int("priority", 0, "Prioritize a single change with the given number")
+ limit = flag.Int("limit", 0, "only run a maximum of this number of tests")
)
func main() {
@@ -1362,6 +1363,7 @@
t.checkoutDir: "<SwiftShader>",
},
NumParallelTests: numParallelTests,
+ MaxTestsPerProc: *maxTestsPerProc,
TestTimeout: testTimeout,
CoverageEnv: t.coverageEnv,
}
diff --git a/tests/regres/cmd/run_testlist/main.go b/tests/regres/cmd/run_testlist/main.go
index 8674502..4150dfa 100644
--- a/tests/regres/cmd/run_testlist/main.go
+++ b/tests/regres/cmd/run_testlist/main.go
@@ -56,6 +56,7 @@
deqpVkBinary = flag.String("deqp-vk", "deqp-vk", "path to the deqp-vk binary")
testList = flag.String("test-list", "vk-master-PASS.txt", "path to a test list file")
numThreads = flag.Int("num-threads", min(runtime.NumCPU(), 100), "number of parallel test runner processes")
+ maxTestsPerProc = flag.Int("max-tests-per-proc", 1, "maximum number of tests running in a single process")
maxProcMemory = flag.Uint64("max-proc-mem", shell.MaxProcMemory, "maximum virtual memory per child process")
output = flag.String("output", "results.json", "path to an output JSON results file")
filter = flag.String("filter", "", "filter for test names. Start with a '/' to indicate regex")
@@ -110,6 +111,7 @@
ExeVulkan: *deqpVkBinary,
Env: os.Environ(),
NumParallelTests: *numThreads,
+ MaxTestsPerProc: *maxTestsPerProc,
TestLists: testlist.Lists{group},
TestTimeout: testTimeout,
ValidationLayer: *enableValidation,
diff --git a/tests/regres/deqp/deqp.go b/tests/regres/deqp/deqp.go
index 1e49a74..2946706 100644
--- a/tests/regres/deqp/deqp.go
+++ b/tests/regres/deqp/deqp.go
@@ -53,6 +53,8 @@
assertRE = regexp.MustCompile(`[^\n]*ASSERT\([^\)]*\)[^\n]*`)
// Regular expression to parse a test that failed due to ABORT()
abortRE = regexp.MustCompile(`[^\n]*ABORT:[^\n]*`)
+ // Regular expression to parse individual test names and output
+ caseOutputRE = regexp.MustCompile("Test case '([^']*)'..")
)
// Config contains the inputs required for running dEQP on a group of test lists.
@@ -66,6 +68,7 @@
Env []string
LogReplacements map[string]string
NumParallelTests int
+ MaxTestsPerProc int
CoverageEnv *cov.Env
TestTimeout time.Duration
ValidationLayer bool
@@ -312,13 +315,22 @@
logPath = filepath.Join(c.TempDir, fmt.Sprintf("%v.log", goroutineIndex))
}
+ testNames := []string{}
for name := range tests {
- results <- c.PerformTest(exe, env, coverageFile, logPath, name, supportsCoverage)
+ testNames = append(testNames, name)
+ if len(testNames) >= c.MaxTestsPerProc {
+ c.PerformTests(exe, env, coverageFile, logPath, testNames, supportsCoverage, results)
+ // Clear list of test names
+ testNames = testNames[:0]
+ }
+ }
+ if len(testNames) > 0 {
+ c.PerformTests(exe, env, coverageFile, logPath, testNames, supportsCoverage, results)
}
}
-func (c *Config) PerformTest(exe string, env []string, coverageFile string, logPath string, name string, supportsCoverage bool) TestResult {
- // log.Printf("Running test '%s'\n", name)
+func (c *Config) PerformTests(exe string, env []string, coverageFile string, logPath string, testNames []string, supportsCoverage bool, results chan<- TestResult) {
+ // log.Printf("Running test(s) '%s'\n", testNames)
start := time.Now()
// Set validation layer according to flag.
@@ -328,9 +340,11 @@
}
// The list of test names will be passed to stdin, since the deqp-stdin-caselist option is used
- testNames := name + "\n"
+ stdin := strings.Join(testNames, "\n") + "\n"
- outRaw, err := shell.Exec(c.TestTimeout, exe, filepath.Dir(exe), env, testNames,
+ numTests := len(testNames)
+ timeout := c.TestTimeout * time.Duration(numTests)
+ outRaw, err := shell.Exec(timeout, exe, filepath.Dir(exe), env, stdin,
"--deqp-validation="+validation,
"--deqp-surface-type=pbuffer",
"--deqp-shadercache=disable",
@@ -352,11 +366,41 @@
if c.CoverageEnv != nil && supportsCoverage {
coverage, err = c.CoverageEnv.Import(coverageFile)
if err != nil {
- log.Printf("Warning: Failed to process test coverage for test '%v'. %v", name, err)
+ log.Printf("Warning: Failed to process test coverage for test '%v'. %v", testNames, err)
}
os.Remove(coverageFile)
}
+ if numTests > 1 {
+ // Separate output per test case
+ caseOutputs := caseOutputRE.Split(out, -1)
+
+ // If the output isn't as expected, a crash may have happened, so re-run tests separately
+ if len(caseOutputs) != (numTests + 1) {
+ // Re-run tests one by one
+ for _, testName := range testNames {
+ singleTest := []string{testName}
+ c.PerformTests(exe, env, coverageFile, logPath, singleTest, supportsCoverage, results)
+ }
+ } else {
+ caseOutputs = caseOutputs[1:] // Ignore text up to first "Test case '...'"
+ caseNameMatches := caseOutputRE.FindAllStringSubmatch(out, -1)
+ caseNames := make([]string, len(caseNameMatches))
+ for i, m := range caseNameMatches {
+ caseNames[i] = m[1]
+ }
+
+ averageDuration := duration / time.Duration(numTests)
+ for i, caseOutput := range caseOutputs {
+ results <- c.AnalyzeOutput(caseNames[i], caseOutput, averageDuration, coverage)
+ }
+ }
+ } else {
+ results <- c.AnalyzeOutput(testNames[0], out, duration, coverage)
+ }
+}
+
+func (c *Config) AnalyzeOutput(name string, out string, duration time.Duration, coverage *cov.Coverage) TestResult {
for _, test := range []struct {
re *regexp.Regexp
s testlist.Status
@@ -379,6 +423,7 @@
}
// Don't treat non-zero error codes as crashes.
+ var err error
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
if exitErr.ExitCode() != 255 {