mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-13 10:51:35 +00:00
tools/test-all.sh: Reimplement in golang
Makes future development easier. New features: * A more compact and cleaner results view * Concurrent testing, much quicker across multiple cores * Supports comparing output against an expected file, including a text diff of differences. Also has a flag for updating the expected outputs * Advanced file-globbing support, including scanning for files in subdirectories * Skip lists are now no longer hidden away in the tool, but defined as a SKIP header in the *.expected.* file Change-Id: I4fac80bb084a720ec9a307b4acf9f73792973a1d Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/50903 Commit-Queue: Ben Clayton <bclayton@google.com> Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
parent
72f6ba4efe
commit
57b2a06ba7
1
test/bug_tint_749.spvasm.expected.hlsl
Normal file
1
test/bug_tint_749.spvasm.expected.hlsl
Normal file
@ -0,0 +1 @@
|
|||||||
|
SKIP: TINT_UNIMPLEMENTED crbug.com/tint/726: module-scope private and workgroup variables not yet implemented
|
1
test/bug_tint_749.spvasm.expected.msl
Normal file
1
test/bug_tint_749.spvasm.expected.msl
Normal file
@ -0,0 +1 @@
|
|||||||
|
SKIP: TINT_UNIMPLEMENTED crbug.com/tint/726: module-scope private and workgroup variables not yet implemented
|
5
test/bug_tint_749.spvasm.expected.spvasm
Normal file
5
test/bug_tint_749.spvasm.expected.spvasm
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
SKIP:
|
||||||
|
|
||||||
|
Validation Failure:
|
||||||
|
1:1: OpLoad Pointer <id> '51[%51]' is not a logical pointer.
|
||||||
|
%52 = OpLoad %int %51
|
159
test/test-all.sh
159
test/test-all.sh
@ -16,33 +16,18 @@
|
|||||||
|
|
||||||
set -e # Fail on any error.
|
set -e # Fail on any error.
|
||||||
|
|
||||||
TEXT_YELLOW="\033[0;33m"
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
|
||||||
TEXT_GREEN="\033[0;32m"
|
|
||||||
TEXT_RED="\033[0;31m"
|
|
||||||
TEXT_DEFAULT="\033[0m"
|
|
||||||
|
|
||||||
CHECK_WGSL=1
|
|
||||||
CHECK_SPV=1
|
|
||||||
CHECK_MSL=1
|
|
||||||
CHECK_HLSL=1
|
|
||||||
|
|
||||||
TINT="$1"
|
|
||||||
ONLY_FORMAT="$2"
|
|
||||||
TARGETDIR="$3"
|
|
||||||
|
|
||||||
function usage() {
|
function usage() {
|
||||||
echo "test-all.sh uses tint to compile .wgsl and .spvasm files, reporting errors as test failures."
|
echo "test-all.sh is a simple wrapper around <tint>/tools/test-runner that"
|
||||||
|
echo "injects the <tint>/tools directory as the second command line argument"
|
||||||
echo
|
echo
|
||||||
echo "Usage: test-all.sh <path-to-tint-executable> [<only-format> [directory]]"
|
echo "Usage of <tint>/tools/test-runner:"
|
||||||
echo
|
SCRIPT_DIR/../tools/test-runner --help
|
||||||
echo "<only-format> specifies which output format is tested."
|
|
||||||
echo " Possible values are: all, wgsl, spv, msl, hlsl."
|
|
||||||
echo " The default is 'all'."
|
|
||||||
echo
|
|
||||||
echo "[directory] specifies which directory holds the source files"
|
|
||||||
echo " The default is to use the script directory."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TINT="$1"
|
||||||
|
|
||||||
if [ -z "$TINT" ]; then
|
if [ -z "$TINT" ]; then
|
||||||
echo "error: missing argument: location of the 'tint' executable"
|
echo "error: missing argument: location of the 'tint' executable"
|
||||||
echo
|
echo
|
||||||
@ -56,132 +41,4 @@ if [ ! -x "$TINT" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$ONLY_FORMAT" ]; then
|
"${SCRIPT_DIR}/../tools/test-runner" ${@:2} "${TINT}" "${SCRIPT_DIR}"
|
||||||
case "${ONLY_FORMAT}" in
|
|
||||||
all)
|
|
||||||
;;
|
|
||||||
wgsl)
|
|
||||||
CHECK_WGSL=1
|
|
||||||
CHECK_SPV=0
|
|
||||||
CHECK_MSL=0
|
|
||||||
CHECK_HLSL=0
|
|
||||||
;;
|
|
||||||
spv)
|
|
||||||
CHECK_WGSL=0
|
|
||||||
CHECK_SPV=1
|
|
||||||
CHECK_MSL=0
|
|
||||||
CHECK_HLSL=0
|
|
||||||
;;
|
|
||||||
msl)
|
|
||||||
CHECK_WGSL=0
|
|
||||||
CHECK_SPV=0
|
|
||||||
CHECK_MSL=1
|
|
||||||
CHECK_HLSL=0
|
|
||||||
;;
|
|
||||||
hlsl)
|
|
||||||
CHECK_WGSL=0
|
|
||||||
CHECK_SPV=0
|
|
||||||
CHECK_MSL=0
|
|
||||||
CHECK_HLSL=1
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "error: invalid format argument: $ONLY_FORMAT"
|
|
||||||
echo
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$4" ]; then
|
|
||||||
echo "error: Too many arguments"
|
|
||||||
echo
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
|
|
||||||
|
|
||||||
# If no subdirectory was specified, look in the script directory.
|
|
||||||
if [ -z "${TARGETDIR}" ]; then
|
|
||||||
TARGETDIR="${SCRIPT_DIR}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d "${TARGETDIR}" ]; then
|
|
||||||
echo "error: ${TARGETDIR} is not a directory"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
NUM_PASS=0
|
|
||||||
NUM_SKIP=0
|
|
||||||
NUM_FAIL=0
|
|
||||||
|
|
||||||
SKIPPED=""
|
|
||||||
SKIPPED+="msl:bug_tint_749.spvasm" # TINT_UNIMPLEMENTED crbug.com/tint/726: module-scope private and workgroup variables not yet implemented
|
|
||||||
SKIPPED+="hlsl:bug_tint_749.spvasm" # Failed to generate: error: pointers not supported in HLSL
|
|
||||||
|
|
||||||
# should_skip(TEST_FILE, FORMAT)
|
|
||||||
function should_skip() {
|
|
||||||
local TEST="$1-$2"
|
|
||||||
if [[ "$TEST" == "bug_tint_749.spvasm-msl" ]]; then
|
|
||||||
echo 1
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
echo 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
# check(TEST_FILE, FORMAT)
|
|
||||||
function check() {
|
|
||||||
local TEST_FILE="$1"
|
|
||||||
local FORMAT=$2
|
|
||||||
SKIP=
|
|
||||||
|
|
||||||
TEST_FILE_WITHOUT_DIR=$(basename ${TEST_FILE})
|
|
||||||
if [[ $SKIPPED == *"${FORMAT}:${TEST_FILE_WITHOUT_DIR}"* ]]; then
|
|
||||||
SKIP=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "%7s: " "${FORMAT}"
|
|
||||||
if [[ -n "$SKIP" ]]; then
|
|
||||||
echo -e "${TEXT_YELLOW}SKIPPED${TEXT_DEFAULT}"
|
|
||||||
NUM_SKIP=$((${NUM_SKIP}+1))
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
set +e
|
|
||||||
"${TINT}" ${TEST_FILE} --format ${FORMAT} -o /dev/null
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo -e "${TEXT_GREEN}PASS${TEXT_DEFAULT}"
|
|
||||||
NUM_PASS=$((${NUM_PASS}+1))
|
|
||||||
else
|
|
||||||
echo -e "${TEXT_RED}FAIL${TEXT_DEFAULT}"
|
|
||||||
NUM_FAIL=$((${NUM_FAIL}+1))
|
|
||||||
fi
|
|
||||||
set -e
|
|
||||||
}
|
|
||||||
|
|
||||||
# check_formats(TEST_FILE)
|
|
||||||
function check_formats() {
|
|
||||||
local TEST_FILE=$1
|
|
||||||
echo
|
|
||||||
echo "Testing ${TEST_FILE}..."
|
|
||||||
[ ${CHECK_WGSL} -eq 0 ] || check "${TEST_FILE}" wgsl
|
|
||||||
[ ${CHECK_SPV} -eq 0 ] || check "${TEST_FILE}" spirv
|
|
||||||
[ ${CHECK_MSL} -eq 0 ] || check "${TEST_FILE}" msl
|
|
||||||
[ ${CHECK_HLSL} -eq 0 ] || check "${TEST_FILE}" hlsl
|
|
||||||
}
|
|
||||||
|
|
||||||
for F in "${TARGETDIR}"/*.spvasm "${TARGETDIR}"/*.wgsl
|
|
||||||
do
|
|
||||||
check_formats "$F"
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ ${NUM_FAIL} -ne 0 ]; then
|
|
||||||
echo
|
|
||||||
echo -e "${TEXT_RED}${NUM_FAIL} tests failed. ${TEXT_DEFAULT}${NUM_SKIP} skipped. ${NUM_PASS} passed.${TEXT_DEFAULT}"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo
|
|
||||||
echo -e "${NUM_SKIP} tests skipped. ${TEXT_GREEN}${NUM_PASS} passed.${TEXT_DEFAULT}"
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
401
tools/src/cmd/test-runner/main.go
Normal file
401
tools/src/cmd/test-runner/main.go
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
// Copyright 2021 The Tint Authors.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// test-runner runs tint against a number of test shaders checking for expected behavior
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"dawn.googlesource.com/tint/tools/src/fileutils"
|
||||||
|
"dawn.googlesource.com/tint/tools/src/glob"
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
|
)
|
||||||
|
|
||||||
|
type outputFormat string
|
||||||
|
|
||||||
|
const (
|
||||||
|
wgsl = outputFormat("wgsl")
|
||||||
|
spvasm = outputFormat("spvasm")
|
||||||
|
msl = outputFormat("msl")
|
||||||
|
hlsl = outputFormat("hlsl")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := run(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showUsage() {
|
||||||
|
fmt.Println(`
|
||||||
|
test-runner runs tint against a number of test shaders checking for expected behavior
|
||||||
|
|
||||||
|
usage:
|
||||||
|
test-runner [flags...] <executable> [<directory>]
|
||||||
|
|
||||||
|
<executable> the path to the tint executable
|
||||||
|
<directory> the root directory of the test files
|
||||||
|
|
||||||
|
optional flags:`)
|
||||||
|
flag.PrintDefaults()
|
||||||
|
fmt.Println(``)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() error {
|
||||||
|
var formatList, filter string
|
||||||
|
generateExpected := false
|
||||||
|
flag.StringVar(&formatList, "format", "all", "comma separated list of formats to emit. Possible values are: all, wgsl, spvasm, msl, hlsl")
|
||||||
|
flag.StringVar(&filter, "filter", "**.wgsl, **.spvasm, **.spv", "comma separated list of glob patterns for test files")
|
||||||
|
flag.BoolVar(&generateExpected, "generate-expected", false, "create or update all expected outputs")
|
||||||
|
flag.Usage = showUsage
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
if len(args) == 0 {
|
||||||
|
showUsage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// executable path is the first argument
|
||||||
|
exe, args := args[0], args[1:]
|
||||||
|
|
||||||
|
// (optional) target directory is the second argument
|
||||||
|
dir := "."
|
||||||
|
if len(args) > 0 {
|
||||||
|
dir, args = args[0], args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the executable can be found and actually is executable
|
||||||
|
if !fileutils.IsExe(exe) {
|
||||||
|
return fmt.Errorf("'%s' not found or is not executable", exe)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the --filter flag up by ',', trimming any whitespace at the start and end
|
||||||
|
globIncludes := strings.Split(filter, ",")
|
||||||
|
for i, s := range globIncludes {
|
||||||
|
globIncludes[i] = `"` + strings.TrimSpace(s) + `"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glob the files to test
|
||||||
|
files, err := glob.Scan(dir, glob.MustParseConfig(`{
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"include": [ `+strings.Join(globIncludes, ",")+` ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exclude": [
|
||||||
|
"**.expected.wgsl",
|
||||||
|
"**.expected.spvasm",
|
||||||
|
"**.expected.msl",
|
||||||
|
"**.expected.hlsl"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to glob files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the files are sorted (globbing should do this, but why not)
|
||||||
|
sort.Strings(files)
|
||||||
|
|
||||||
|
// Parse --format into a list of outputFormat
|
||||||
|
formats := []outputFormat{}
|
||||||
|
if formatList == "all" {
|
||||||
|
formats = []outputFormat{wgsl, spvasm, msl, hlsl}
|
||||||
|
} else {
|
||||||
|
for _, f := range strings.Split(formatList, ",") {
|
||||||
|
switch strings.TrimSpace(f) {
|
||||||
|
case "wgsl":
|
||||||
|
formats = append(formats, wgsl)
|
||||||
|
case "spvasm":
|
||||||
|
formats = append(formats, spvasm)
|
||||||
|
case "msl":
|
||||||
|
formats = append(formats, msl)
|
||||||
|
case "hlsl":
|
||||||
|
formats = append(formats, hlsl)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown format '%s'", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structures to hold the results of the tests
|
||||||
|
type statusCode string
|
||||||
|
const (
|
||||||
|
fail statusCode = "FAIL"
|
||||||
|
pass statusCode = "PASS"
|
||||||
|
skip statusCode = "SKIP"
|
||||||
|
)
|
||||||
|
type status struct {
|
||||||
|
code statusCode
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
type result map[outputFormat]status
|
||||||
|
results := make([]result, len(files))
|
||||||
|
|
||||||
|
// In parallel...
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(files))
|
||||||
|
for i, file := range files { // For each test file...
|
||||||
|
i, file := i, filepath.Join(dir, file)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
r := result{}
|
||||||
|
for _, format := range formats { // For each output format...
|
||||||
|
|
||||||
|
// Is there an expected output?
|
||||||
|
expected := loadExpectedFile(file, format)
|
||||||
|
if strings.HasPrefix(expected, "SKIP") { // Special SKIP token
|
||||||
|
r[format] = status{code: skip}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke the compiler...
|
||||||
|
var err error
|
||||||
|
if ok, out := invoke(exe, file, "--format", string(format), "--dawn-validation"); ok {
|
||||||
|
if generateExpected {
|
||||||
|
// If --generate-expected was passed, write out the output
|
||||||
|
err = saveExpectedFile(file, format, out)
|
||||||
|
} else if expected != "" && expected != out {
|
||||||
|
// Expected output did not match
|
||||||
|
dmp := diffmatchpatch.New()
|
||||||
|
diff := dmp.DiffPrettyText(dmp.DiffMain(expected, out, true))
|
||||||
|
err = fmt.Errorf(`Output was not as expected
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Expected: --
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
%s
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Got: --
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
%s
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Diff: --
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
%s`,
|
||||||
|
expected, out, diff)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Compiler returned a non-zero exit code
|
||||||
|
err = fmt.Errorf("%s", out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r[format] = status{code: fail, err: err}
|
||||||
|
} else {
|
||||||
|
r[format] = status{code: pass}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results[i] = r
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// At this point all the tests have been run
|
||||||
|
// Time to print the outputs
|
||||||
|
|
||||||
|
// Start by printing the error message for any file x format combinations
|
||||||
|
// that failed...
|
||||||
|
for i, file := range files {
|
||||||
|
results := results[i]
|
||||||
|
for _, format := range formats {
|
||||||
|
if err := results[format].err; err != nil {
|
||||||
|
color.Set(color.FgBlue)
|
||||||
|
fmt.Printf("%s ", file)
|
||||||
|
color.Set(color.FgCyan)
|
||||||
|
fmt.Printf("%s ", format)
|
||||||
|
color.Set(color.FgRed)
|
||||||
|
fmt.Println("FAIL")
|
||||||
|
color.Unset()
|
||||||
|
fmt.Println(indent(err.Error(), 4))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now print the table of file x format
|
||||||
|
numTests, numPass, numSkip, numFail := 0, 0, 0, 0
|
||||||
|
filenameFmt := columnFormat(maxStringLen(files), false)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf(filenameFmt, "")
|
||||||
|
fmt.Printf(" ┃ ")
|
||||||
|
for _, format := range formats {
|
||||||
|
color.Set(color.FgCyan)
|
||||||
|
fmt.Printf(columnFormat(formatWidth(format), false), format)
|
||||||
|
color.Unset()
|
||||||
|
fmt.Printf(" │ ")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf(strings.Repeat("━", maxStringLen(files)))
|
||||||
|
fmt.Printf("━╋━")
|
||||||
|
for _, format := range formats {
|
||||||
|
fmt.Printf(strings.Repeat("━", formatWidth(format)))
|
||||||
|
fmt.Printf("━│━")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
for i, file := range files {
|
||||||
|
results := results[i]
|
||||||
|
|
||||||
|
color.Set(color.FgBlue)
|
||||||
|
fmt.Printf(filenameFmt, file)
|
||||||
|
color.Unset()
|
||||||
|
fmt.Printf(" ┃ ")
|
||||||
|
for _, format := range formats {
|
||||||
|
formatFmt := columnFormat(formatWidth(format), true)
|
||||||
|
result := results[format]
|
||||||
|
numTests++
|
||||||
|
switch result.code {
|
||||||
|
case pass:
|
||||||
|
color.Set(color.FgGreen)
|
||||||
|
fmt.Printf(formatFmt, "PASS")
|
||||||
|
numPass++
|
||||||
|
case fail:
|
||||||
|
color.Set(color.FgRed)
|
||||||
|
fmt.Printf(formatFmt, "FAIL")
|
||||||
|
numFail++
|
||||||
|
case skip:
|
||||||
|
color.Set(color.FgYellow)
|
||||||
|
fmt.Printf(formatFmt, "SKIP")
|
||||||
|
numSkip++
|
||||||
|
default:
|
||||||
|
fmt.Printf(formatFmt, result.code)
|
||||||
|
}
|
||||||
|
color.Unset()
|
||||||
|
fmt.Printf(" │ ")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Printf("%d tests run", numTests)
|
||||||
|
if numPass > 0 {
|
||||||
|
fmt.Printf(", ")
|
||||||
|
color.Set(color.FgGreen)
|
||||||
|
fmt.Printf("%d tests pass", numPass)
|
||||||
|
color.Unset()
|
||||||
|
} else {
|
||||||
|
fmt.Printf(", %d tests pass", numPass)
|
||||||
|
}
|
||||||
|
if numSkip > 0 {
|
||||||
|
fmt.Printf(", ")
|
||||||
|
color.Set(color.FgYellow)
|
||||||
|
fmt.Printf("%d tests skipped", numSkip)
|
||||||
|
color.Unset()
|
||||||
|
} else {
|
||||||
|
fmt.Printf(", %d tests skipped", numSkip)
|
||||||
|
}
|
||||||
|
if numFail > 0 {
|
||||||
|
fmt.Printf(", ")
|
||||||
|
color.Set(color.FgRed)
|
||||||
|
fmt.Printf("%d tests failed", numFail)
|
||||||
|
color.Unset()
|
||||||
|
} else {
|
||||||
|
fmt.Printf(", %d tests failed", numFail)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadExpectedFile loads the expected output file for the test file at 'path'
|
||||||
|
// and the output format 'format'. If the file does not exist, or cannot be
|
||||||
|
// read, then an empty string is returned.
|
||||||
|
func loadExpectedFile(path string, format outputFormat) string {
|
||||||
|
content, err := ioutil.ReadFile(expectedFilePath(path, format))
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveExpectedFile writes the expected output file for the test file at 'path'
|
||||||
|
// and the output format 'format', with the content 'content'.
|
||||||
|
func saveExpectedFile(path string, format outputFormat, content string) error {
|
||||||
|
return ioutil.WriteFile(expectedFilePath(path, format), []byte(content), 0666)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expectedFilePath returns the expected output file path for the test file at
|
||||||
|
// 'path' and the output format 'format'.
|
||||||
|
func expectedFilePath(path string, format outputFormat) string {
|
||||||
|
return path + ".expected." + string(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
// indent returns the string 's' indented with 'n' whitespace characters
|
||||||
|
func indent(s string, n int) string {
|
||||||
|
tab := strings.Repeat(" ", n)
|
||||||
|
return tab + strings.ReplaceAll(s, "\n", "\n"+tab)
|
||||||
|
}
|
||||||
|
|
||||||
|
// columnFormat returns the printf format string to sprint a string with the
|
||||||
|
// width of 'i' runes.
|
||||||
|
func columnFormat(i int, alignLeft bool) string {
|
||||||
|
if alignLeft {
|
||||||
|
return "%-" + strconv.Itoa(i) + "s"
|
||||||
|
}
|
||||||
|
return "%" + strconv.Itoa(i) + "s"
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxStringLen returns the maximum number of runes found in all the strings in
|
||||||
|
// 'l'
|
||||||
|
func maxStringLen(l []string) int {
|
||||||
|
max := 0
|
||||||
|
for _, s := range l {
|
||||||
|
if c := utf8.RuneCountInString(s); c > max {
|
||||||
|
max = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatWidth returns the width in runes for the outputFormat column 'b'
|
||||||
|
func formatWidth(b outputFormat) int {
|
||||||
|
c := utf8.RuneCountInString(string(b))
|
||||||
|
if c > 4 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// invoke runs the executable 'exe' with the provided arguments.
|
||||||
|
func invoke(exe string, args ...string) (ok bool, output string) {
|
||||||
|
cmd := exec.Command(exe, args...)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
str := string(out)
|
||||||
|
if err != nil {
|
||||||
|
if str != "" {
|
||||||
|
return false, str
|
||||||
|
}
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
return true, str
|
||||||
|
}
|
29
tools/src/fileutils/fileutils.go
Normal file
29
tools/src/fileutils/fileutils.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2021 The Tint Authors.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package fileutils contains utility functions for files
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsExe returns true if the file at path is an executable
|
||||||
|
func IsExe(path string) bool {
|
||||||
|
s, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.Mode()&0100 != 0
|
||||||
|
}
|
@ -22,6 +22,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"dawn.googlesource.com/tint/tools/src/match"
|
"dawn.googlesource.com/tint/tools/src/match"
|
||||||
)
|
)
|
||||||
@ -92,7 +93,12 @@ func LoadConfig(path string) (Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return Config{}, err
|
return Config{}, err
|
||||||
}
|
}
|
||||||
d := json.NewDecoder(bytes.NewReader(cfgBody))
|
return ParseConfig(string(cfgBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseConfig parses the config from a JSON string.
|
||||||
|
func ParseConfig(config string) (Config, error) {
|
||||||
|
d := json.NewDecoder(strings.NewReader(config))
|
||||||
cfg := Config{}
|
cfg := Config{}
|
||||||
if err := d.Decode(&cfg); err != nil {
|
if err := d.Decode(&cfg); err != nil {
|
||||||
return Config{}, err
|
return Config{}, err
|
||||||
@ -100,6 +106,17 @@ func LoadConfig(path string) (Config, error) {
|
|||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustParseConfig parses the config from a JSON string, panicing if the config
|
||||||
|
// does not parse
|
||||||
|
func MustParseConfig(config string) Config {
|
||||||
|
d := json.NewDecoder(strings.NewReader(config))
|
||||||
|
cfg := Config{}
|
||||||
|
if err := d.Decode(&cfg); err != nil {
|
||||||
|
panic(fmt.Errorf("Failed to parse config: %w\nConfig:\n%v", err, config))
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
// rule is a search path predicate.
|
// rule is a search path predicate.
|
||||||
// root is the project relative path.
|
// root is the project relative path.
|
||||||
// cond is the value to return if the rule doesn't either include or exclude.
|
// cond is the value to return if the rule doesn't either include or exclude.
|
||||||
|
@ -2,4 +2,7 @@ module dawn.googlesource.com/tint/tools/src
|
|||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require github.com/sergi/go-diff v1.2.0
|
require (
|
||||||
|
github.com/fatih/color v1.10.0
|
||||||
|
github.com/sergi/go-diff v1.2.0
|
||||||
|
)
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||||
|
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||||
@ -11,6 +17,9 @@ github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
33
tools/test-runner
Executable file
33
tools/test-runner
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright 2021 The Tint Authors
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
set -e # Fail on any error.
|
||||||
|
|
||||||
|
if [ ! -x "$(which go)" ] ; then
|
||||||
|
echo "error: go needs to be on \$PATH to use $0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
|
||||||
|
ROOT_DIR="$( cd "${SCRIPT_DIR}/.." >/dev/null 2>&1 && pwd )"
|
||||||
|
BINARY="${SCRIPT_DIR}/bin/test-runner"
|
||||||
|
|
||||||
|
# Rebuild the binary.
|
||||||
|
# Note, go caches build artifacts, so this is quick for repeat calls
|
||||||
|
pushd "${SCRIPT_DIR}/src/cmd/test-runner" > /dev/null
|
||||||
|
go build -o "${BINARY}" main.go
|
||||||
|
popd > /dev/null
|
||||||
|
|
||||||
|
"${BINARY}" "$@"
|
Loading…
x
Reference in New Issue
Block a user