Regres: support GCOV_PREFIX for collecting coverage
If you compile SwiftShader with gcc and the --coverage flag, the build
will contain coverage instrumentation. However, the coverage
instrumentation races when multiple instances of SwiftShader run in
parallel. This change makes Regres replace the string "PROC_ID" inside
the GCOV_PREFIX environment variable with an ID that is unique between
parallel dEQP processes. This allows the user to prevent races using
e.g. GCOV_PREFIX=/tmp/cov/PROC_ID
Change-Id: I227896053c5a0ea34dc8acc282014970a626785d
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38928
Reviewed-by: Ben Clayton <bclayton@google.com>
Tested-by: Paul Thomson <paulthomson@google.com>
diff --git a/tests/regres/deqp/deqp.go b/tests/regres/deqp/deqp.go
index 7d7acd4..cc5066d 100644
--- a/tests/regres/deqp/deqp.go
+++ b/tests/regres/deqp/deqp.go
@@ -25,6 +25,7 @@
"os/exec"
"path/filepath"
"regexp"
+ "strconv"
"strings"
"sync"
"time"
@@ -139,6 +140,8 @@
numTests := 0
+ goroutineIndex := 0
+
// For each API that we are testing
for _, list := range c.TestLists {
// Resolve the test runner
@@ -165,10 +168,11 @@
// Start a number of go routines to run the tests.
wg.Add(c.NumParallelTests)
for i := 0; i < c.NumParallelTests; i++ {
- go func() {
- c.TestRoutine(exe, tests, results)
+ go func(index int) {
+ c.TestRoutine(exe, tests, results, index)
wg.Done()
- }()
+ }(goroutineIndex)
+ goroutineIndex++
}
// Shuffle the test list.
@@ -229,13 +233,42 @@
// is written to results.
// TestRoutine only returns once the tests chan has been closed.
// TestRoutine does not close the results chan.
-func (c *Config) TestRoutine(exe string, tests <-chan string, results chan<- TestResult) {
+func (c *Config) TestRoutine(exe string, tests <-chan string, results chan<- TestResult, goroutineIndex int) {
+ // Context for the GCOV_PREFIX environment variable:
+ // If you compile SwiftShader with gcc and the --coverage flag, the build will contain coverage instrumentation.
+ // We can use this to get the code coverage of SwiftShader from running dEQP.
+ // The coverage instrumentation reads the existing coverage files on start-up (at a hardcoded path alongside the
+ // SwiftShader build), updates coverage info as the programs runs, then (over)writes the coverage files on exit.
+ // Thus, multiple parallel processes will race when updating coverage information. The GCOV_PREFIX environment
+ // variable adds a prefix to the hardcoded paths.
+ // E.g. Given GCOV_PREFIX=/tmp/coverage, the hardcoded path /ss/build/a.gcno becomes /tmp/coverage/ss/build/a.gcno.
+ // This is mainly intended for running the target program on a different machine where the hardcoded paths don't
+ // make sense. It can also be used to avoid races. It would be trivial to avoid races if the GCOV_PREFIX variable
+ // supported macro variables like the Clang code coverage "%p" variable that expands to the process ID; in this
+ // case, we could use GCOV_PREFIX=/tmp/coverage/%p to avoid races. Unfortunately, gcc does not support this.
+ // Furthermore, processing coverage information from many directories can be slow; we start a lot of dEQP child
+ // processes, each of which will likely get a unique process ID. In practice, we only need one directory per go
+ // routine.
+
+ // If GCOV_PREFIX is in Env, replace occurrences of "PROC_ID" in GCOV_PREFIX with goroutineIndex.
+ // This avoids races between parallel child processes reading and writing coverage output files.
+ // For example, GCOV_PREFIX="/tmp/gcov_output/PROC_ID" becomes GCOV_PREFIX="/tmp/gcov_output/1" in the first go routine.
+ // You might expect PROC_ID to be the process ID of some process, but the only real requirement is that
+ // it is a unique ID between the *parallel* child processes.
+ env := make([]string, len(c.Env))
+ for _, v := range c.Env {
+ if strings.HasPrefix(v, "GCOV_PREFIX=") {
+ v = strings.ReplaceAll(v, "PROC_ID", strconv.Itoa(goroutineIndex))
+ }
+ env = append(env, v)
+ }
+
nextTest:
for name := range tests {
// log.Printf("Running test '%s'\n", name)
start := time.Now()
- outRaw, err := shell.Exec(c.TestTimeout, exe, filepath.Dir(exe), c.Env,
+ outRaw, err := shell.Exec(c.TestTimeout, exe, filepath.Dir(exe), env,
"--deqp-surface-type=pbuffer",
"--deqp-shadercache=disable",
"--deqp-log-images=disable",