// Copyright 2019 The SwiftShader Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // run_testlist is a tool runs a dEQP test list, using multiple sand-boxed // processes. // // Unlike simply running deqp with its --deqp-caselist-file flag, run_testlist // uses multiple sand-boxed processes, which greatly reduces testing time, and // gracefully handles crashing processes. package main import ( "bytes" "encoding/json" "errors" "flag" "fmt" "io/ioutil" "log" "math/rand" "os" "path/filepath" "regexp" "runtime" "strings" "time" "../../cause" "../../cov" "../../deqp" "../../llvm" "../../shell" "../../testlist" "../../util" ) func min(a, b int) int { if a < b { return a } return b } var ( 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") 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") limit = flag.Int("limit", 0, "only run a maximum of this number of tests") shuffle = flag.Bool("shuffle", false, "shuffle tests") noResults = flag.Bool("no-results", false, "disable generation of results.json file") genCoverage = flag.Bool("coverage", false, "generate test coverage") enableValidation = flag.Bool("validation", false, "run deqp-vk with Vulkan validation layers") ) const testTimeout = time.Minute * 2 func run() error { group := testlist.Group{ Name: "", File: *testList, API: testlist.Vulkan, } if err := group.Load(); err != nil { return err } if *filter != "" { if strings.HasPrefix(*filter, "/") { re := regexp.MustCompile((*filter)[1:]) group = group.Filter(re.MatchString) } else { group = group.Filter(func(name string) bool { ok, _ := filepath.Match(*filter, name) return ok }) } } shell.MaxProcMemory = *maxProcMemory if *shuffle { rnd := rand.New(rand.NewSource(time.Now().UnixNano())) rnd.Shuffle(len(group.Tests), func(i, j int) { group.Tests[i], group.Tests[j] = group.Tests[j], group.Tests[i] }) } if *limit != 0 && len(group.Tests) > *limit { group.Tests = group.Tests[:*limit] } log.Printf("Running %d tests...\n", len(group.Tests)) config := deqp.Config{ ExeEgl: "", ExeGles2: "", ExeGles3: "", ExeVulkan: *deqpVkBinary, Env: os.Environ(), NumParallelTests: *numThreads, TestLists: testlist.Lists{group}, TestTimeout: testTimeout, ValidationLayer: *enableValidation, } if *genCoverage { icdPath := findSwiftshaderICD() t := findToolchain(icdPath) config.CoverageEnv = &cov.Env{ LLVM: t.llvm, TurboCov: t.turbocov, RootDir: projectRootDir(), ExePath: findSwiftshaderSO(icdPath), } } res, err := config.Run() if err != nil { return err } counts := map[testlist.Status]int{} for _, r := range res.Tests { counts[r.Status] = counts[r.Status] + 1 } for _, s := range testlist.Statuses { if count := counts[s]; count > 0 { log.Printf("%s: %d\n", string(s), count) } } if *genCoverage { f, err := os.Create("coverage.dat") if err != nil { return cause.Wrap(err, "Couldn't open coverage.dat file") } if err := res.Coverage.Encode("master", f); err != nil { return cause.Wrap(err, "Couldn't encode coverage data") } } if !*noResults { err = res.Save(*output) if err != nil { return err } } return nil } func findSwiftshaderICD() string { icdPaths := strings.Split(os.Getenv("VK_ICD_FILENAMES"), ";") for _, icdPath := range icdPaths { _, file := filepath.Split(icdPath) if file == "vk_swiftshader_icd.json" { return icdPath } } panic("Cannot find vk_swiftshader_icd.json in VK_ICD_FILENAMES") } func findSwiftshaderSO(vkSwiftshaderICD string) string { root := struct { ICD struct { Path string `json:"library_path"` } }{} icd, err := ioutil.ReadFile(vkSwiftshaderICD) if err != nil { panic(fmt.Errorf("Could not read '%v'. %v", vkSwiftshaderICD, err)) } if err := json.NewDecoder(bytes.NewReader(icd)).Decode(&root); err != nil { panic(fmt.Errorf("Could not parse '%v'. %v", vkSwiftshaderICD, err)) } if util.IsFile(root.ICD.Path) { return root.ICD.Path } dir := filepath.Dir(vkSwiftshaderICD) path, err := filepath.Abs(filepath.Join(dir, root.ICD.Path)) if err != nil { panic(fmt.Errorf("Could not locate ICD so at '%v'. %v", root.ICD.Path, err)) } return path } type toolchain struct { llvm llvm.Toolchain turbocov string } func findToolchain(vkSwiftshaderICD string) toolchain { minVersion := llvm.Version{Major: 7} // Try finding the llvm toolchain via the CMake generated // coverage-toolchain.txt file that sits next to vk_swiftshader_icd.json. dir := filepath.Dir(vkSwiftshaderICD) toolchainInfoPath := filepath.Join(dir, "coverage-toolchain.txt") if util.IsFile(toolchainInfoPath) { if file, err := os.Open(toolchainInfoPath); err == nil { defer file.Close() content := struct { LLVM string `json:"llvm"` TurboCov string `json:"turbo-cov"` }{} err := json.NewDecoder(file).Decode(&content) if err != nil { log.Fatalf("Couldn't read 'toolchainInfoPath': %v", err) } if t := llvm.Search(content.LLVM).FindAtLeast(minVersion); t != nil { return toolchain{*t, content.TurboCov} } } } // Fallback, try searching PATH. if t := llvm.Search().FindAtLeast(minVersion); t != nil { return toolchain{*t, ""} } log.Fatal("Could not find LLVM toolchain") return toolchain{} } func projectRootDir() string { _, thisFile, _, _ := runtime.Caller(1) thisDir := filepath.Dir(thisFile) root, err := filepath.Abs(filepath.Join(thisDir, "../../../..")) if err != nil { panic(err) } return root } func main() { flag.ErrHelp = errors.New("regres is a tool to detect regressions between versions of SwiftShader") flag.Parse() if err := run(); err != nil { _, _ = fmt.Fprintln(os.Stderr, err) os.Exit(-1) } }