| // 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 common | |
| import ( | |
| "flag" | |
| "fmt" | |
| "io" | |
| "os/exec" | |
| "path/filepath" | |
| "runtime" | |
| "dawn.googlesource.com/dawn/tools/src/fileutils" | |
| "dawn.googlesource.com/dawn/tools/src/oswrapper" | |
| "dawn.googlesource.com/dawn/tools/src/subcmd" | |
| "dawn.googlesource.com/dawn/tools/src/term" | |
| ) | |
| // Flags holds the common flags for all CTS runners | |
| type Flags struct { | |
| Verbose bool | |
| Colors bool | |
| FailuresOnly bool | |
| NumRunners int | |
| Log string | |
| ResultsPath string | |
| ExpectationsPath string | |
| CTS string // Path to the CTS directory | |
| Node string // Path to the Node executable | |
| Npx string // Path to the npx executable | |
| } | |
| func (f *Flags) Register(fsReader oswrapper.FilesystemReader) { | |
| flag.BoolVar(&f.Verbose, "verbose", false, "print extra information while testing") | |
| flag.BoolVar(&f.Colors, "colors", term.CanUseAnsiEscapeSequences(), "enable / disable colors") | |
| flag.BoolVar(&f.FailuresOnly, "failures-only", false, "output only failures to log file") | |
| flag.IntVar(&f.NumRunners, "j", runtime.NumCPU()/2, "number of concurrent runners. 0 runs serially") | |
| flag.StringVar(&f.Log, "log", "", "path to log file of tests run and result") | |
| flag.StringVar(&f.ResultsPath, "output", "", "path to write test results file") | |
| flag.StringVar(&f.ExpectationsPath, "expect", "", "path to expectations file") | |
| flag.StringVar(&f.CTS, "cts", defaultCtsPath(fsReader), "root directory of WebGPU CTS") | |
| flag.StringVar(&f.Node, "node", fileutils.NodePath(fsReader), "path to node executable") | |
| flag.StringVar(&f.Npx, "npx", defaultNpxPath(), "path to npx executable") | |
| } | |
| // Process processes the flags, returning a State. | |
| // Note: Ensure you call Close() on the returned State | |
| func (f *Flags) Process(fsReaderWriter oswrapper.FilesystemReaderWriter) (*State, error) { | |
| s := &State{ | |
| resultsPath: f.ResultsPath, | |
| failuresOnly: f.FailuresOnly, | |
| } | |
| // Create the stdout writer | |
| s.Stdout = term.NewAnsiWriter(f.Colors) | |
| // Check CTS path | |
| if f.CTS == "" { | |
| return nil, subcmd.InvalidCLA() | |
| } | |
| if !fileutils.IsDir(f.CTS, fsReaderWriter) { | |
| return nil, fmt.Errorf("'%v' is not a directory", f.CTS) | |
| } | |
| absCTS, err := filepath.Abs(f.CTS) | |
| if err != nil { | |
| return nil, fmt.Errorf("unable to get absolute path for '%v'", f.CTS) | |
| } | |
| f.CTS = absCTS | |
| // Build the logger, if needed | |
| if f.Log != "" { | |
| writer, err := fsReaderWriter.Create(f.Log) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to open log '%v': %w", f.Log, err) | |
| } | |
| s.Log = NewLogger(writer) | |
| s.logWriter = writer | |
| } | |
| // If an expectations file was specified, load it. | |
| if f.ExpectationsPath != "" { | |
| if err := s.Expectations.Load(f.ExpectationsPath); err != nil { | |
| return nil, err | |
| } | |
| } | |
| // Find node | |
| if f.Node == "" { | |
| return nil, fmt.Errorf("cannot find path to node. Specify with --node") | |
| } | |
| s.CTS = NewCTS(f.CTS, f.Npx, f.Node) | |
| return s, nil | |
| } | |
| // State holds the common state for commands | |
| type State struct { | |
| Stdout io.WriteCloser | |
| Log Logger | |
| Expectations Expectations | |
| CTS CTS | |
| resultsPath string | |
| failuresOnly bool | |
| logWriter io.WriteCloser | |
| } | |
| // Close closes s.Stdout and saves the results to the results path (if set) | |
| func (s *State) Close(results Results) error { | |
| if err := s.Stdout.Close(); err != nil { | |
| return err | |
| } | |
| // If an result file was specified, save results to it. | |
| if s.resultsPath != "" { | |
| expectations := Expectations{} | |
| for testCase, result := range results { | |
| if !s.failuresOnly || result.Status == Fail { | |
| expectations[testCase] = result.Status | |
| } | |
| } | |
| if err := expectations.Save(s.resultsPath); err != nil { | |
| return err | |
| } | |
| } | |
| if s.logWriter != nil { | |
| if err := s.logWriter.Close(); err != nil { | |
| return fmt.Errorf("failed to close log file: %w", err) | |
| } | |
| } | |
| return nil | |
| } | |
| // defaultCtsPath looks for the webgpu-cts directory in dawn's third_party | |
| // directory. This is used as the default for the --cts command line flag. | |
| func defaultCtsPath(fsReader oswrapper.FilesystemReader) string { | |
| if dawnRoot := fileutils.DawnRoot(fsReader); dawnRoot != "" { | |
| cts := filepath.Join(dawnRoot, "third_party", "webgpu-cts") | |
| if info, err := fsReader.Stat(cts); err == nil && info.IsDir() { | |
| return cts | |
| } | |
| } | |
| return "" | |
| } | |
| // defaultNpxPath looks for the npx executable on PATH | |
| func defaultNpxPath() string { | |
| if p, err := exec.LookPath("npx"); err == nil { | |
| return p | |
| } | |
| return "" | |
| } |