| // 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" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| |
| "dawn.googlesource.com/dawn/tools/src/fileutils" |
| "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 |
| 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() { |
| flag.BoolVar(&f.Verbose, "verbose", false, "print extra information while testing") |
| flag.BoolVar(&f.Colors, "colors", term.CanUseAnsiEscapeSequences(), "enable / disable colors") |
| 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(), "root directory of WebGPU CTS") |
| flag.StringVar(&f.Node, "node", fileutils.NodePath(), "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() (*State, error) { |
| s := &State{ |
| resultsPath: f.ResultsPath, |
| } |
| |
| // 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) { |
| 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 := os.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 Results |
| CTS CTS |
| resultsPath string |
| 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 != "" { |
| if err := results.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() string { |
| if dawnRoot := fileutils.DawnRoot(); dawnRoot != "" { |
| cts := filepath.Join(dawnRoot, "third_party/webgpu-cts") |
| if info, err := os.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 "" |
| } |