blob: 4edea3483e1609ed630c35c37290669aed6e2184 [file] [log] [blame]
// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package node
import (
"context"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"dawn.googlesource.com/dawn/tools/src/cmd/run-cts/common"
"dawn.googlesource.com/dawn/tools/src/cov"
"dawn.googlesource.com/dawn/tools/src/dawn/node"
"dawn.googlesource.com/dawn/tools/src/fileutils"
"dawn.googlesource.com/dawn/tools/src/subcmd"
)
type flags struct {
common.Flags
bin string
backend string
adapterName string
coverageFile string
isolated bool
build bool
validate bool
dumpShaders bool
fxc bool
unrollConstEvalLoops bool
genCoverage bool
compatibilityMode bool
skipVSCodeInfo bool
dawn node.Flags
}
func init() {
common.Register(&cmd{})
}
type cmd struct {
state common.State
flags flags
coverage *common.Coverage
query string
}
func (cmd) IsDefaultCommand() {}
func (cmd) Name() string {
return "node"
}
func (cmd) Desc() string {
return "runs the CTS with dawn.node"
}
func (c *cmd) RegisterFlags(ctx context.Context, cfg common.Config) ([]string, error) {
unrollConstEvalLoopsDefault := runtime.GOOS != "windows"
backendDefault := "default"
if vkIcdFilenames := os.Getenv("VK_ICD_FILENAMES"); vkIcdFilenames != "" {
backendDefault = "vulkan"
}
c.flags.Flags.Register()
flag.StringVar(&c.flags.bin, "bin", fileutils.BuildPath(), "path to the directory holding cts.js and dawn.node")
flag.BoolVar(&c.flags.isolated, "isolate", false, "run each test in an isolated process")
flag.BoolVar(&c.flags.build, "build", true, "attempt to build the CTS before running")
flag.BoolVar(&c.flags.validate, "validate", false, "enable backend validation")
flag.Var(&c.flags.dawn, "flag", "flag to pass to dawn-node as flag=value. multiple flags must be passed in individually")
flag.StringVar(&c.flags.backend, "backend", backendDefault, "backend to use: default|null|webgpu|d3d11|d3d12|metal|vulkan|opengl|opengles."+
" set to 'vulkan' if VK_ICD_FILENAMES environment variable is set, 'default' otherwise")
flag.StringVar(&c.flags.adapterName, "adapter", "", "name (or substring) of the GPU adapter to use")
flag.BoolVar(&c.flags.dumpShaders, "dump-shaders", false, "dump WGSL shaders. Enables --verbose")
flag.BoolVar(&c.flags.fxc, "fxc", false, "Use FXC instead of DXC. Disables 'use_dxc' Dawn flag")
flag.BoolVar(&c.flags.unrollConstEvalLoops, "unroll-const-eval-loops", unrollConstEvalLoopsDefault, "unroll loops in const-eval tests")
flag.BoolVar(&c.flags.genCoverage, "coverage", false, "displays coverage data")
flag.StringVar(&c.flags.coverageFile, "export-coverage", "", "write coverage data to the given path")
flag.BoolVar(&c.flags.compatibilityMode, "compat", false, "run tests in compatibility mode")
flag.BoolVar(&c.flags.skipVSCodeInfo, "skip-vs-code-info", false, "skips emitting VS Code information")
return []string{"[query]"}, nil
}
func (c *cmd) Run(ctx context.Context, cfg common.Config) error {
// Process the command line flags
if err := c.processFlags(); err != nil {
return err
}
if err := c.maybeInitCoverage(); err != nil {
return err
}
if err := c.state.CTS.Node.BuildIfRequired(c.flags.Verbose); err != nil {
return err
}
// Find all the test cases that match r.query.
testCases, err := c.state.CTS.QueryTestCases(c.flags.Verbose, c.query)
if err != nil {
return fmt.Errorf("failed to gather tests: %w", err)
}
fmt.Printf("Testing %d test cases...\n", len(testCases))
var runner func(ctx context.Context, testCases []common.TestCase, results chan<- common.Result)
if c.flags.isolated {
fmt.Println("Running in parallel isolated...")
runner = c.runTestCasesWithCmdline
} else {
fmt.Println("Running in parallel with server...")
runner = c.runTestCasesWithServers
}
resultStream := make(chan common.Result, 256)
go func() {
runner(ctx, testCases, resultStream)
close(resultStream)
}()
results, err := common.StreamResults(ctx,
c.flags.Colors,
c.state,
c.flags.Verbose,
c.coverage,
len(testCases),
resultStream)
if err != nil {
return err
}
if err := c.state.Close(results); err != nil {
return err
}
return nil
}
func (c *cmd) processFlags() error {
// Check mandatory arguments
if c.flags.bin == "" {
return subcmd.InvalidCLA()
}
if !fileutils.IsDir(c.flags.bin) {
return fmt.Errorf("'%v' is not a directory", c.flags.bin)
}
for _, file := range []string{"cts.js", "dawn.node"} {
if !fileutils.IsFile(filepath.Join(c.flags.bin, file)) {
return fmt.Errorf("'%v' does not contain '%v'", c.flags.bin, file)
}
}
// Make paths absolute
absBin, err := filepath.Abs(c.flags.bin)
if err != nil {
return fmt.Errorf("unable to get absolute path for '%v'", c.flags.bin)
}
c.flags.bin = absBin
// The test query is the optional unnamed argument
c.query = "webgpu:*"
switch len(flag.Args()) {
case 0:
case 1:
c.query = flag.Args()[0]
default:
return fmt.Errorf("only a single query can be provided")
}
c.flags.dawn.SetOptions(node.Options{
BinDir: c.flags.bin,
Backend: c.flags.backend,
Adapter: c.flags.adapterName,
Validate: c.flags.validate,
AllowUnsafeAPIs: true,
DumpShaders: c.flags.dumpShaders,
UseFXC: c.flags.fxc,
})
if c.flags.dumpShaders {
c.flags.Verbose = true
}
state, err := c.flags.Process()
if err != nil {
return err
}
c.state = *state
return nil
}
func (c *cmd) maybeInitCoverage() error {
if !c.flags.genCoverage && c.flags.coverageFile == "" {
return nil
}
profdata, err := exec.LookPath("llvm-profdata")
if err != nil {
profdata = ""
if runtime.GOOS == "darwin" {
profdata = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/llvm-profdata"
if !fileutils.IsExe(profdata) {
profdata = ""
}
}
}
if profdata == "" {
return fmt.Errorf("failed to find llvm-profdata, required for --coverage")
}
llvmCov := ""
turboCov := filepath.Join(c.flags.bin, "turbo-cov"+fileutils.ExeExt)
if !fileutils.IsExe(turboCov) {
turboCov = ""
if path, err := exec.LookPath("llvm-cov"); err == nil {
llvmCov = path
} else {
return fmt.Errorf("failed to find turbo-cov or llvm-cov")
}
}
c.coverage = &common.Coverage{
OutputFile: c.flags.coverageFile,
Env: &cov.Env{
Profdata: profdata,
Binary: filepath.Join(c.flags.bin, "dawn.node"),
Cov: llvmCov,
TurboCov: turboCov,
},
}
return nil
}