mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-08 13:14:56 +00:00
Move tools to a tools/src directory
Allows bash scripts to share the same name as the directory. Add fix-tests bash script. Change-Id: Iaf1943d50ec1fd3f382a2c7823fb7cdd13b1d9a2 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47766 Commit-Queue: Ben Clayton <bclayton@google.com> Reviewed-by: Antonio Maiorano <amaiorano@google.com>
This commit is contained in:
committed by
Commit Bot service account
parent
648b05e4e2
commit
434cd396fe
329
tools/src/fix-tests/fix-tests.go
Normal file
329
tools/src/fix-tests/fix-tests.go
Normal file
@@ -0,0 +1,329 @@
|
||||
// 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.
|
||||
|
||||
// fix-tests is a tool to update tests with new expected output.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"dawn.googlesource.com/tint/tools/src/fix-tests/substr"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func showUsage() {
|
||||
fmt.Println(`
|
||||
fix-tests is a tool to update tests with new expected output.
|
||||
|
||||
fix-tests performs string matching and heuristics to fix up expected results of
|
||||
tests that use EXPECT_EQ(a, b) and EXPECT_THAT(a, HasSubstr(b))
|
||||
|
||||
WARNING: Always thoroughly check the generated output for mistakes.
|
||||
This may produce incorrect output
|
||||
|
||||
Usage:
|
||||
fix-tests <executable>
|
||||
|
||||
executable - the path to the test executable to run.`)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func run() error {
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
if len(args) < 1 {
|
||||
showUsage()
|
||||
}
|
||||
|
||||
exe := args[0] // The path to the test executable
|
||||
wd := filepath.Dir(exe) // The directory holding the test exe
|
||||
|
||||
// Create a temporary directory to hold the 'test-results.json' file
|
||||
tmpDir, err := ioutil.TempDir("", "fix-tests")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(tmpDir, 0666); err != nil {
|
||||
return fmt.Errorf("Failed to create temporary directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Full path to the 'test-results.json' in the temporary directory
|
||||
testResultsPath := filepath.Join(tmpDir, "test-results.json")
|
||||
|
||||
// Run the tests
|
||||
switch err := exec.Command(exe, "--gtest_output=json:"+testResultsPath).Run().(type) {
|
||||
default:
|
||||
return err
|
||||
case nil:
|
||||
fmt.Println("All tests passed")
|
||||
case *exec.ExitError:
|
||||
}
|
||||
|
||||
// Read the 'test-results.json' file
|
||||
testResultsFile, err := os.Open(testResultsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var testResults Results
|
||||
if err := json.NewDecoder(testResultsFile).Decode(&testResults); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For each failing test...
|
||||
seen := map[string]bool{}
|
||||
numFixed, numFailed := 0, 0
|
||||
for _, group := range testResults.Groups {
|
||||
for _, suite := range group.Testsuites {
|
||||
for _, failure := range suite.Failures {
|
||||
// .. attempt to fix the problem
|
||||
test := testName(group, suite)
|
||||
if seen[test] {
|
||||
continue
|
||||
}
|
||||
seen[test] = true
|
||||
|
||||
if err := processFailure(test, wd, failure.Failure); err != nil {
|
||||
fmt.Println(fmt.Errorf("%v: %w", test, err))
|
||||
numFailed++
|
||||
} else {
|
||||
numFixed++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
if numFailed > 0 {
|
||||
fmt.Println(numFailed, "tests could not be fixed")
|
||||
}
|
||||
if numFixed > 0 {
|
||||
fmt.Println(numFixed, "tests fixed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testName(group TestsuiteGroup, suite Testsuite) string {
|
||||
groupParts := strings.Split(group.Name, "/")
|
||||
suiteParts := strings.Split(suite.Name, "/")
|
||||
return groupParts[len(groupParts)-1] + "." + suiteParts[0]
|
||||
}
|
||||
|
||||
var (
|
||||
// Regular expression to match a test declaration
|
||||
reTests = regexp.MustCompile(`TEST(?:_[FP])?\([ \n]*(\w+),[ \n]*(\w+)\)`)
|
||||
// Regular expression to match a `EXPECT_EQ(a, b)` failure for strings
|
||||
reExpectEq = regexp.MustCompile(`([./\\a-z_-]*):(\d+).*\nExpected equality of these values:\n(?:.|\n)*?(?:Which is: | )"((?:.|\n)*?[^\\])"\n(?:.|\n)*?(?:Which is: | )"((?:.|\n)*?[^\\])"`)
|
||||
// Regular expression to match a `EXPECT_THAT(a, HasSubstr(b))` failure for strings
|
||||
reExpectHasSubstr = regexp.MustCompile(`([./\\a-z_-]*):(\d+).*\nValue of: .*\nExpected: has substring "((?:.|\n)*?[^\\])"\n Actual: "((?:.|\n)*?[^\\])"`)
|
||||
)
|
||||
|
||||
func processFailure(test, wd, failure string) error {
|
||||
// Start by un-escaping newlines in the failure message
|
||||
failure = strings.ReplaceAll(failure, "\\n", "\n")
|
||||
// Matched regex strings will also need to be un-escaped, but do this after
|
||||
// the match, as unescaped quotes may upset the regex patterns
|
||||
unescape := func(s string) string {
|
||||
return strings.ReplaceAll(s, `\"`, `"`)
|
||||
}
|
||||
escape := func(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\"", `\"`)
|
||||
return s
|
||||
}
|
||||
|
||||
// Look for a EXPECT_EQ failure pattern
|
||||
var file string
|
||||
var fix func(testSource string) (string, error)
|
||||
if parts := reExpectEq.FindStringSubmatch(failure); len(parts) == 5 {
|
||||
// EXPECT_EQ(a, b)
|
||||
a, b := unescape(parts[3]), unescape(parts[4])
|
||||
file = parts[1]
|
||||
fix = func(testSource string) (string, error) {
|
||||
// We don't know if a or b is the expected, so just try flipping the string
|
||||
// to the other form.
|
||||
switch {
|
||||
case strings.Contains(testSource, a):
|
||||
testSource = strings.Replace(testSource, a, b, -1)
|
||||
case strings.Contains(testSource, b):
|
||||
testSource = strings.Replace(testSource, b, a, -1)
|
||||
default:
|
||||
// Try escaping for R"(...)" strings
|
||||
a, b = escape(a), escape(b)
|
||||
switch {
|
||||
case strings.Contains(testSource, a):
|
||||
testSource = strings.Replace(testSource, a, b, -1)
|
||||
case strings.Contains(testSource, b):
|
||||
testSource = strings.Replace(testSource, b, a, -1)
|
||||
default:
|
||||
return "", fmt.Errorf("Could not fix 'EXPECT_EQ' pattern in '%v'", file)
|
||||
}
|
||||
}
|
||||
return testSource, nil
|
||||
}
|
||||
} else if parts := reExpectHasSubstr.FindStringSubmatch(failure); len(parts) == 5 {
|
||||
// EXPECT_THAT(a, HasSubstr(b))
|
||||
a, b := unescape(parts[4]), unescape(parts[3])
|
||||
file = parts[1]
|
||||
fix = func(testSource string) (string, error) {
|
||||
if fix := substr.Fix(a, b); fix != "" {
|
||||
if !strings.Contains(testSource, b) {
|
||||
// Try escaping for R"(...)" strings
|
||||
b, fix = escape(b), escape(fix)
|
||||
}
|
||||
if strings.Contains(testSource, b) {
|
||||
testSource = strings.Replace(testSource, b, fix, -1)
|
||||
return testSource, nil
|
||||
}
|
||||
return "", fmt.Errorf("Could apply fix for 'HasSubstr' pattern in '%v'", file)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Could find fix for 'HasSubstr' pattern in '%v'", file)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Cannot fix this type of failure")
|
||||
}
|
||||
|
||||
// Get the path to the source file containing the test failure
|
||||
sourcePath := filepath.Join(wd, file)
|
||||
|
||||
// Parse the source file, split into tests
|
||||
sourceFile, err := parseSourceFile(sourcePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't parse tests from file '%v': %w", file, err)
|
||||
}
|
||||
|
||||
// Find the test
|
||||
testIdx, ok := sourceFile.tests[test]
|
||||
if !ok {
|
||||
return fmt.Errorf("Test not found in '%v'", file)
|
||||
}
|
||||
|
||||
// Grab the source for the particular test
|
||||
testSource := sourceFile.parts[testIdx]
|
||||
|
||||
if testSource, err = fix(testSource); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Replace the part of the source file
|
||||
sourceFile.parts[testIdx] = testSource
|
||||
|
||||
// Write out the source file
|
||||
return writeSourceFile(sourcePath, sourceFile)
|
||||
}
|
||||
|
||||
// parseSourceFile() reads the file at path, splitting the content into chunks
|
||||
// for each TEST.
|
||||
func parseSourceFile(path string) (sourceFile, error) {
|
||||
fileBytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return sourceFile{}, err
|
||||
}
|
||||
fileContent := string(fileBytes)
|
||||
|
||||
out := sourceFile{
|
||||
tests: map[string]int{},
|
||||
}
|
||||
|
||||
pos := 0
|
||||
for _, span := range reTests.FindAllStringIndex(fileContent, -1) {
|
||||
out.parts = append(out.parts, fileContent[pos:span[0]])
|
||||
pos = span[0]
|
||||
|
||||
match := reTests.FindStringSubmatch(fileContent[span[0]:span[1]])
|
||||
group := match[1]
|
||||
suite := match[2]
|
||||
out.tests[group+"."+suite] = len(out.parts)
|
||||
}
|
||||
out.parts = append(out.parts, fileContent[pos:])
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// writeSourceFile() joins the chunks of the file, and writes the content out to
|
||||
// path.
|
||||
func writeSourceFile(path string, file sourceFile) error {
|
||||
body := strings.Join(file.parts, "")
|
||||
return ioutil.WriteFile(path, []byte(body), 0666)
|
||||
}
|
||||
|
||||
type sourceFile struct {
|
||||
parts []string
|
||||
tests map[string]int // "X.Y" -> part index
|
||||
}
|
||||
|
||||
// Results is the root JSON structure of the JSON --gtest_output file .
|
||||
type Results struct {
|
||||
Tests int `json:"tests"`
|
||||
Failures int `json:"failures"`
|
||||
Disabled int `json:"disabled"`
|
||||
Errors int `json:"errors"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Time string `json:"time"`
|
||||
Name string `json:"name"`
|
||||
Groups []TestsuiteGroup `json:"testsuites"`
|
||||
}
|
||||
|
||||
// TestsuiteGroup is a group of test suites in the JSON --gtest_output file .
|
||||
type TestsuiteGroup struct {
|
||||
Name string `json:"name"`
|
||||
Tests int `json:"tests"`
|
||||
Failures int `json:"failures"`
|
||||
Disabled int `json:"disabled"`
|
||||
Errors int `json:"errors"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Time string `json:"time"`
|
||||
Testsuites []Testsuite `json:"testsuite"`
|
||||
}
|
||||
|
||||
// Testsuite is a suite of tests in the JSON --gtest_output file.
|
||||
type Testsuite struct {
|
||||
Name string `json:"name"`
|
||||
ValueParam string `json:"value_param,omitempty"`
|
||||
Status Status `json:"status"`
|
||||
Result Result `json:"result"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Time string `json:"time"`
|
||||
Classname string `json:"classname"`
|
||||
Failures []Failure `json:"failures,omitempty"`
|
||||
}
|
||||
|
||||
// Failure is a reported test failure in the JSON --gtest_output file.
|
||||
type Failure struct {
|
||||
Failure string `json:"failure"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Status is a status code in the JSON --gtest_output file.
|
||||
type Status string
|
||||
|
||||
// Result is a result code in the JSON --gtest_output file.
|
||||
type Result string
|
||||
5
tools/src/fix-tests/go.mod
Normal file
5
tools/src/fix-tests/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module dawn.googlesource.com/tint/tools/src/fix-tests
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/sergi/go-diff v1.2.0
|
||||
18
tools/src/fix-tests/go.sum
Normal file
18
tools/src/fix-tests/go.sum
Normal file
@@ -0,0 +1,18 @@
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
52
tools/src/fix-tests/substr/substr.go
Normal file
52
tools/src/fix-tests/substr/substr.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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 substr
|
||||
|
||||
import (
|
||||
diff "github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
// Fix attempts to reconstruct substr by comparing it to body.
|
||||
// substr is a fuzzy substring of body.
|
||||
// Fix returns a new exact substring of body, by calculating a diff of the text.
|
||||
// If no match could be made, Fix() returns an empty string.
|
||||
func Fix(body, substr string) string {
|
||||
dmp := diff.New()
|
||||
|
||||
diffs := dmp.DiffMain(body, substr, false)
|
||||
if len(diffs) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
front := func() diff.Diff { return diffs[0] }
|
||||
back := func() diff.Diff { return diffs[len(diffs)-1] }
|
||||
|
||||
start, end := 0, len(body)
|
||||
|
||||
// Trim edits that remove text from body start
|
||||
for len(diffs) > 0 && front().Type == diff.DiffDelete {
|
||||
start += len(front().Text)
|
||||
diffs = diffs[1:]
|
||||
}
|
||||
|
||||
// Trim edits that remove text from body end
|
||||
for len(diffs) > 0 && back().Type == diff.DiffDelete {
|
||||
end -= len(back().Text)
|
||||
diffs = diffs[:len(diffs)-1]
|
||||
}
|
||||
|
||||
// New substring is the span for the remainder of the edits
|
||||
return body[start:end]
|
||||
}
|
||||
275
tools/src/fix-tests/substr/substr_test.go
Normal file
275
tools/src/fix-tests/substr/substr_test.go
Normal file
@@ -0,0 +1,275 @@
|
||||
// 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 substr
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFixSubstr(t *testing.T) {
|
||||
type test struct {
|
||||
body string
|
||||
substr string
|
||||
expect string
|
||||
}
|
||||
|
||||
for _, test := range []test{
|
||||
{
|
||||
body: "abc_def_ghi_jkl_mno",
|
||||
substr: "def_XXX_jkl",
|
||||
expect: "def_ghi_jkl",
|
||||
},
|
||||
{
|
||||
body: "abc\ndef\nghi\njkl\nmno",
|
||||
substr: "def\nXXX\njkl",
|
||||
expect: "def\nghi\njkl",
|
||||
},
|
||||
{
|
||||
body: "aaaaa12345ccccc",
|
||||
substr: "1x345",
|
||||
expect: "12345",
|
||||
},
|
||||
{
|
||||
body: "aaaaa12345ccccc",
|
||||
substr: "12x45",
|
||||
expect: "12345",
|
||||
},
|
||||
{
|
||||
body: "aaaaa12345ccccc",
|
||||
substr: "123x5",
|
||||
expect: "12345",
|
||||
},
|
||||
{
|
||||
body: "aaaaaaaaaaaaa",
|
||||
substr: "bbbbbbbbbbbbb",
|
||||
expect: "", // cannot produce a sensible diff
|
||||
}, { ///////////////////////////////////////////////////////////////////
|
||||
body: `Return{
|
||||
{
|
||||
ScalarConstructor[not set]{42u}
|
||||
}
|
||||
}
|
||||
`,
|
||||
substr: `Return{
|
||||
{
|
||||
ScalarConstructor[not set]{42}
|
||||
}
|
||||
}`,
|
||||
expect: `Return{
|
||||
{
|
||||
ScalarConstructor[not set]{42u}
|
||||
}
|
||||
}`,
|
||||
}, { ///////////////////////////////////////////////////////////////////
|
||||
body: `VariableDeclStatement{
|
||||
Variable{
|
||||
x_1
|
||||
function
|
||||
__u32
|
||||
}
|
||||
}
|
||||
Assignment{
|
||||
Identifier[not set]{x_1}
|
||||
ScalarConstructor[not set]{42u}
|
||||
}
|
||||
Assignment{
|
||||
Identifier[not set]{x_1}
|
||||
ScalarConstructor[not set]{0u}
|
||||
}
|
||||
Return{}
|
||||
`,
|
||||
substr: `Assignment{
|
||||
Identifier[not set]{x_1}
|
||||
ScalarConstructor[not set]{42}
|
||||
}
|
||||
Assignment{
|
||||
Identifier[not set]{x_1}
|
||||
ScalarConstructor[not set]{0}
|
||||
}`,
|
||||
expect: `Assignment{
|
||||
Identifier[not set]{x_1}
|
||||
ScalarConstructor[not set]{42u}
|
||||
}
|
||||
Assignment{
|
||||
Identifier[not set]{x_1}
|
||||
ScalarConstructor[not set]{0u}
|
||||
}`,
|
||||
}, { ///////////////////////////////////////////////////////////////////
|
||||
body: `VariableDeclStatement{
|
||||
Variable{
|
||||
a
|
||||
function
|
||||
__bool
|
||||
{
|
||||
ScalarConstructor[not set]{true}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
b
|
||||
function
|
||||
__bool
|
||||
{
|
||||
ScalarConstructor[not set]{false}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
c
|
||||
function
|
||||
__i32
|
||||
{
|
||||
ScalarConstructor[not set]{-1}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
d
|
||||
function
|
||||
__u32
|
||||
{
|
||||
ScalarConstructor[not set]{1u}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
e
|
||||
function
|
||||
__f32
|
||||
{
|
||||
ScalarConstructor[not set]{1.500000}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
substr: `VariableDeclStatement{
|
||||
Variable{
|
||||
a
|
||||
function
|
||||
__bool
|
||||
{
|
||||
ScalarConstructor[not set]{true}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
b
|
||||
function
|
||||
__bool
|
||||
{
|
||||
ScalarConstructor[not set]{false}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
c
|
||||
function
|
||||
__i32
|
||||
{
|
||||
ScalarConstructor[not set]{-1}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
d
|
||||
function
|
||||
__u32
|
||||
{
|
||||
ScalarConstructor[not set]{1}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
e
|
||||
function
|
||||
__f32
|
||||
{
|
||||
ScalarConstructor[not set]{1.500000}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
expect: `VariableDeclStatement{
|
||||
Variable{
|
||||
a
|
||||
function
|
||||
__bool
|
||||
{
|
||||
ScalarConstructor[not set]{true}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
b
|
||||
function
|
||||
__bool
|
||||
{
|
||||
ScalarConstructor[not set]{false}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
c
|
||||
function
|
||||
__i32
|
||||
{
|
||||
ScalarConstructor[not set]{-1}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
d
|
||||
function
|
||||
__u32
|
||||
{
|
||||
ScalarConstructor[not set]{1u}
|
||||
}
|
||||
}
|
||||
}
|
||||
VariableDeclStatement{
|
||||
Variable{
|
||||
e
|
||||
function
|
||||
__f32
|
||||
{
|
||||
ScalarConstructor[not set]{1.500000}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
} {
|
||||
body := strings.ReplaceAll(test.body, "\n", "")
|
||||
substr := strings.ReplaceAll(test.substr, "\n", "")
|
||||
expect := strings.ReplaceAll(test.expect, "\n", ``)
|
||||
got := strings.ReplaceAll(Fix(test.body, test.substr), "\n", "")
|
||||
if got != expect {
|
||||
t.Errorf("Test failure:\nbody: '%v'\nsubstr: '%v'\nexpect: '%v'\ngot: '%v'\n\n", body, substr, expect, got)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
133
tools/src/run-parallel/main.go
Normal file
133
tools/src/run-parallel/main.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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.
|
||||
|
||||
// run-parallel is a tool to run an executable with the provided templated
|
||||
// arguments across all the hardware threads.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func showUsage() {
|
||||
fmt.Println(`
|
||||
run-parallel is a tool to run an executable with the provided templated
|
||||
arguments across all the hardware threads.
|
||||
|
||||
Usage:
|
||||
run-parallel <executable> [arguments...] -- [per-instance-value...]
|
||||
|
||||
executable - the path to the executable to run.
|
||||
arguments - a list of arguments to pass to the executable.
|
||||
Any occurrance of $ will be substituted with the
|
||||
per-instance-value for the given invocation.
|
||||
per-instance-value - a list of values. The executable will be invoked for each
|
||||
value in this list.`)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func run() error {
|
||||
onlyPrintFailures := flag.Bool("only-print-failures", false, "Omit output for processes that did not fail")
|
||||
flag.Parse()
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) < 2 {
|
||||
showUsage()
|
||||
}
|
||||
exe := args[0]
|
||||
args = args[1:]
|
||||
|
||||
var perInstanceValues []string
|
||||
for i, arg := range args {
|
||||
if arg == "--" {
|
||||
perInstanceValues = args[i+1:]
|
||||
args = args[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if perInstanceValues == nil {
|
||||
showUsage()
|
||||
}
|
||||
|
||||
taskIndices := make(chan int, 64)
|
||||
type result struct {
|
||||
msg string
|
||||
failed bool
|
||||
}
|
||||
results := make([]result, len(perInstanceValues))
|
||||
|
||||
numCPU := runtime.NumCPU()
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(numCPU)
|
||||
for i := 0; i < numCPU; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for idx := range taskIndices {
|
||||
taskArgs := make([]string, len(args))
|
||||
for i, arg := range args {
|
||||
taskArgs[i] = strings.ReplaceAll(arg, "$", perInstanceValues[idx])
|
||||
}
|
||||
success, out := invoke(exe, taskArgs)
|
||||
if !success || !*onlyPrintFailures {
|
||||
results[idx] = result{out, !success}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := range perInstanceValues {
|
||||
taskIndices <- i
|
||||
}
|
||||
close(taskIndices)
|
||||
|
||||
wg.Wait()
|
||||
|
||||
failed := false
|
||||
for _, result := range results {
|
||||
if result.msg != "" {
|
||||
fmt.Println(result.msg)
|
||||
}
|
||||
failed = failed || result.failed
|
||||
}
|
||||
if failed {
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
11
tools/src/trim-includes/build.sh
Executable file
11
tools/src/trim-includes/build.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e # Fail on any error.
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
|
||||
ROOT_DIR="$( cd "${SCRIPT_DIR}/../.." >/dev/null 2>&1 && pwd )"
|
||||
|
||||
cd $ROOT_DIR
|
||||
autoninja -C out/Debug
|
||||
cd $ROOT_DIR/build
|
||||
ninja
|
||||
6
tools/src/trim-includes/config.cfg
Normal file
6
tools/src/trim-includes/config.cfg
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"paths": [
|
||||
{ "include": [ "src/**.h", "src/**.cc" ] },
|
||||
{ "exclude": [ "src/**_windows.*", "src/**_other.*" ] }
|
||||
]
|
||||
}
|
||||
185
tools/src/trim-includes/glob/glob.go
Normal file
185
tools/src/trim-includes/glob/glob.go
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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 glob provides file globbing utilities
|
||||
package glob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"dawn.googlesource.com/tint/tools/src/trim-includes/match"
|
||||
)
|
||||
|
||||
// Scan walks all files and subdirectories from root, returning those
|
||||
// that Config.shouldExamine() returns true for.
|
||||
func Scan(root string, cfg Config) ([]string, error) {
|
||||
files := []string{}
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
rel, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
rel = path
|
||||
}
|
||||
|
||||
if rel == ".git" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if !cfg.shouldExamine(root, path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
files = append(files, rel)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Configs is a slice of Config.
|
||||
type Configs []Config
|
||||
|
||||
// Config is used to parse the JSON configuration file.
|
||||
type Config struct {
|
||||
// Paths holds a number of JSON objects that contain either a "includes" or
|
||||
// "excludes" key to an array of path patterns.
|
||||
// Each path pattern is considered in turn to either include or exclude the
|
||||
// file path for license scanning. Pattern use forward-slashes '/' for
|
||||
// directory separators, and may use the following wildcards:
|
||||
// ? - matches any single non-separator character
|
||||
// * - matches any sequence of non-separator characters
|
||||
// ** - matches any sequence of characters including separators
|
||||
//
|
||||
// Rules are processed in the order in which they are declared, with later
|
||||
// rules taking precedence over earlier rules.
|
||||
//
|
||||
// All files are excluded before the first rule is evaluated.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// {
|
||||
// "paths": [
|
||||
// { "exclude": [ "out/*", "build/*" ] },
|
||||
// { "include": [ "out/foo.txt" ] }
|
||||
// ],
|
||||
// }
|
||||
Paths searchRules
|
||||
}
|
||||
|
||||
// LoadConfig loads a config file at path.
|
||||
func LoadConfig(path string) (Config, error) {
|
||||
cfgBody, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
d := json.NewDecoder(bytes.NewReader(cfgBody))
|
||||
cfg := Config{}
|
||||
if err := d.Decode(&cfg); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// rule is a search path predicate.
|
||||
// root is the project relative path.
|
||||
// cond is the value to return if the rule doesn't either include or exclude.
|
||||
type rule func(path string, cond bool) bool
|
||||
|
||||
// searchRules is a ordered list of search rules.
|
||||
// searchRules is its own type as it has to perform custom JSON unmarshalling.
|
||||
type searchRules []rule
|
||||
|
||||
// UnmarshalJSON unmarshals the array of rules in the form:
|
||||
// { "include": [ ... ] } or { "exclude": [ ... ] }
|
||||
func (l *searchRules) UnmarshalJSON(body []byte) error {
|
||||
type parsed struct {
|
||||
Include []string
|
||||
Exclude []string
|
||||
}
|
||||
|
||||
p := []parsed{}
|
||||
if err := json.NewDecoder(bytes.NewReader(body)).Decode(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*l = searchRules{}
|
||||
for _, rule := range p {
|
||||
rule := rule
|
||||
switch {
|
||||
case len(rule.Include) > 0 && len(rule.Exclude) > 0:
|
||||
return fmt.Errorf("Rule cannot contain both include and exclude")
|
||||
case len(rule.Include) > 0:
|
||||
tests := make([]match.Test, len(rule.Include))
|
||||
for i, pattern := range rule.Include {
|
||||
test, err := match.New(pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tests[i] = test
|
||||
}
|
||||
*l = append(*l, func(path string, cond bool) bool {
|
||||
for _, test := range tests {
|
||||
if test(path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return cond
|
||||
})
|
||||
case len(rule.Exclude) > 0:
|
||||
tests := make([]match.Test, len(rule.Exclude))
|
||||
for i, pattern := range rule.Exclude {
|
||||
test, err := match.New(pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tests[i] = test
|
||||
}
|
||||
*l = append(*l, func(path string, cond bool) bool {
|
||||
for _, test := range tests {
|
||||
if test(path) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return cond
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldExamine returns true if the file at absPath should be scanned.
|
||||
func (c Config) shouldExamine(root, absPath string) bool {
|
||||
root = filepath.ToSlash(root) // Canonicalize
|
||||
absPath = filepath.ToSlash(absPath) // Canonicalize
|
||||
relPath, err := filepath.Rel(root, absPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
res := false
|
||||
for _, rule := range c.Paths {
|
||||
res = rule(relPath, res)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
3
tools/src/trim-includes/go.mod
Normal file
3
tools/src/trim-includes/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module dawn.googlesource.com/tint/tools/src/trim-includes
|
||||
|
||||
go 1.16
|
||||
295
tools/src/trim-includes/main.go
Normal file
295
tools/src/trim-includes/main.go
Normal file
@@ -0,0 +1,295 @@
|
||||
// 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.
|
||||
|
||||
// trim-includes is a tool to try removing unnecessary include statements from
|
||||
// all .cc and .h files in the tint project.
|
||||
//
|
||||
// trim-includes removes each #include from each file, then runs the provided
|
||||
// build script to determine whether the line was necessary. If the include is
|
||||
// required, it is restored, otherwise it is left deleted.
|
||||
// After all the #include statements have been tested, the file is
|
||||
// clang-formatted and git staged.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"dawn.googlesource.com/tint/tools/src/trim-includes/glob"
|
||||
)
|
||||
|
||||
var (
|
||||
// Directory to this .go file
|
||||
toolRoot = getToolRoot()
|
||||
// Root directory of the Tint project
|
||||
projectRoot = getProjectRoot(toolRoot)
|
||||
|
||||
// Path to the build script to run after each attempting to remove each
|
||||
// #include
|
||||
buildScript = ""
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func showUsage() {
|
||||
fmt.Println(`
|
||||
trim-includes is a tool to try removing unnecessary include statements from all
|
||||
.cc and .h files in the tint project.
|
||||
|
||||
trim-includes removes each #include from each file, then runs the provided build
|
||||
script to determine whether the line was necessary. If the include is required,
|
||||
it is restored, otherwise it is left deleted.
|
||||
After all the #include statements have been tested, the file is clang-formatted
|
||||
and git staged.
|
||||
|
||||
Usage:
|
||||
trim-includes <path-to-build-script>`)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func run() error {
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
if len(args) < 1 {
|
||||
showUsage()
|
||||
}
|
||||
|
||||
var err error
|
||||
buildScript, err = exec.LookPath(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildScript, err = filepath.Abs(buildScript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := glob.LoadConfig("config.cfg")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Checking the project builds with no changes...")
|
||||
ok, err := tryBuild()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Project does not build without edits")
|
||||
}
|
||||
|
||||
fmt.Println("Scanning for files...")
|
||||
paths, err := glob.Scan(projectRoot, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Loading %v source files...\n", len(paths))
|
||||
files, err := loadFiles(paths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for fileIdx, file := range files {
|
||||
fmt.Printf("[%d/%d]: %v\n", fileIdx+1, len(files), file.path)
|
||||
includeLines := file.includesLineNumbers()
|
||||
enabled := make(map[int]bool, len(file.lines))
|
||||
for i := range file.lines {
|
||||
enabled[i] = true
|
||||
}
|
||||
for includeIdx, line := range includeLines {
|
||||
fmt.Printf(" [%d/%d]: %v", includeIdx+1, len(includeLines), file.lines[line])
|
||||
enabled[line] = false
|
||||
if err := file.save(enabled); err != nil {
|
||||
return err
|
||||
}
|
||||
ok, err := tryBuild()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
fmt.Printf(" removed\n")
|
||||
// Wait a bit so file timestamps get an opportunity to change.
|
||||
// Attempting to save too soon after a successful build may
|
||||
// result in a false-positive build.
|
||||
time.Sleep(time.Second)
|
||||
} else {
|
||||
fmt.Printf(" required\n")
|
||||
enabled[line] = true
|
||||
}
|
||||
}
|
||||
if err := file.save(enabled); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.format(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.stage(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Println("Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Attempt to build the project. Returns true on successful build, false if
|
||||
// there was a build failure.
|
||||
func tryBuild() (bool, error) {
|
||||
cmd := exec.Command("sh", "-c", buildScript)
|
||||
out, err := cmd.CombinedOutput()
|
||||
switch err := err.(type) {
|
||||
case nil:
|
||||
return cmd.ProcessState.Success(), nil
|
||||
case *exec.ExitError:
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf("Test failed with error: %v\n%v", err, string(out))
|
||||
}
|
||||
}
|
||||
|
||||
type file struct {
|
||||
path string
|
||||
lines []string
|
||||
}
|
||||
|
||||
var includeRE = regexp.MustCompile(`^\s*#include (?:\"([^"]*)\"|:?\<([^"]*)\>)`)
|
||||
|
||||
// Returns the file path with the extension stripped
|
||||
func stripExtension(path string) string {
|
||||
if dot := strings.IndexRune(path, '.'); dot > 0 {
|
||||
return path[:dot]
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// Returns the zero-based line numbers of all #include statements in the file
|
||||
func (f *file) includesLineNumbers() []int {
|
||||
out := []int{}
|
||||
for i, l := range f.lines {
|
||||
matches := includeRE.FindStringSubmatch(l)
|
||||
if len(matches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
include := matches[1]
|
||||
if include == "" {
|
||||
include = matches[2]
|
||||
}
|
||||
|
||||
if strings.HasSuffix(stripExtension(f.path), stripExtension(include)) {
|
||||
// Don't remove #include for header of cc
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Saves the file, omitting the lines with the zero-based line number that are
|
||||
// either not in `lines` or have a `false` value.
|
||||
func (f *file) save(lines map[int]bool) error {
|
||||
content := []string{}
|
||||
for i, l := range f.lines {
|
||||
if lines[i] {
|
||||
content = append(content, l)
|
||||
}
|
||||
}
|
||||
data := []byte(strings.Join(content, "\n"))
|
||||
return ioutil.WriteFile(f.path, data, 0666)
|
||||
}
|
||||
|
||||
// Runs clang-format on the file
|
||||
func (f *file) format() error {
|
||||
err := exec.Command("clang-format", "-i", f.path).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't format file '%v': %w", f.path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Runs git add on the file
|
||||
func (f *file) stage() error {
|
||||
err := exec.Command("git", "-C", projectRoot, "add", f.path).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't stage file '%v': %w", f.path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loads all the files with the given file paths, splitting their content into
|
||||
// into lines.
|
||||
func loadFiles(paths []string) ([]file, error) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(paths))
|
||||
files := make([]file, len(paths))
|
||||
errs := make([]error, len(paths))
|
||||
for i, path := range paths {
|
||||
i, path := i, filepath.Join(projectRoot, path)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
body, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
errs[i] = fmt.Errorf("Failed to open %v: %w", path, err)
|
||||
} else {
|
||||
content := string(body)
|
||||
lines := strings.Split(content, "\n")
|
||||
files[i] = file{path: path, lines: lines}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Returns the path to the directory holding this .go file
|
||||
func getToolRoot() string {
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
panic("No caller information")
|
||||
}
|
||||
mainPath, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return filepath.Dir(mainPath)
|
||||
}
|
||||
|
||||
// Returns the path to the project root
|
||||
func getProjectRoot(toolRoot string) string {
|
||||
root, err := filepath.Abs(filepath.Join(toolRoot, "../.."))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return root
|
||||
}
|
||||
76
tools/src/trim-includes/match/match.go
Normal file
76
tools/src/trim-includes/match/match.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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 match provides functions for performing filepath [?,*,**] wildcard
|
||||
// matching.
|
||||
package match
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Test is the match predicate returned by New.
|
||||
type Test func(path string) bool
|
||||
|
||||
// New returns a Test function that returns true iff the path matches the
|
||||
// provided pattern.
|
||||
//
|
||||
// pattern uses forward-slashes for directory separators '/', and may use the
|
||||
// following wildcards:
|
||||
// ? - matches any single non-separator character
|
||||
// * - matches any sequence of non-separator characters
|
||||
// ** - matches any sequence of characters including separators
|
||||
func New(pattern string) (Test, error) {
|
||||
// Transform pattern into a regex by replacing the uses of `?`, `*`, `**`
|
||||
// with corresponding regex patterns.
|
||||
// As the pattern may contain other regex sequences, the string has to be
|
||||
// escaped. So:
|
||||
// a) Replace the patterns of `?`, `*`, `**` with unique placeholder tokens.
|
||||
// b) Escape the expression so that other sequences don't confuse the regex
|
||||
// parser.
|
||||
// c) Replace the placeholder tokens with the corresponding regex tokens.
|
||||
|
||||
// Temporary placeholder tokens
|
||||
const (
|
||||
starstar = "••"
|
||||
star = "•"
|
||||
questionmark = "¿"
|
||||
)
|
||||
// Check pattern doesn't contain any of our placeholder tokens
|
||||
for _, r := range []rune{'•', '¿'} {
|
||||
if strings.ContainsRune(pattern, r) {
|
||||
return nil, fmt.Errorf("Pattern must not contain '%c'", r)
|
||||
}
|
||||
}
|
||||
// Replace **, * and ? with placeholder tokens
|
||||
subbed := pattern
|
||||
subbed = strings.ReplaceAll(subbed, "**", starstar)
|
||||
subbed = strings.ReplaceAll(subbed, "*", star)
|
||||
subbed = strings.ReplaceAll(subbed, "?", questionmark)
|
||||
// Escape any remaining regex characters
|
||||
escaped := regexp.QuoteMeta(subbed)
|
||||
// Insert regex matchers for the substituted tokens
|
||||
regex := "^" + escaped + "$"
|
||||
regex = strings.ReplaceAll(regex, starstar, ".*")
|
||||
regex = strings.ReplaceAll(regex, star, "[^/]*")
|
||||
regex = strings.ReplaceAll(regex, questionmark, "[^/]")
|
||||
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`Failed to compile regex "%v" for pattern "%v": %w`, regex, pattern, err)
|
||||
}
|
||||
return re.MatchString, nil
|
||||
}
|
||||
106
tools/src/trim-includes/match/match_test.go
Normal file
106
tools/src/trim-includes/match/match_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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 match_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
match "."
|
||||
)
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
pattern string
|
||||
path string
|
||||
expect bool
|
||||
}{
|
||||
{"a", "a", true},
|
||||
{"b", "a", false},
|
||||
|
||||
{"?", "a", true},
|
||||
{"a/?/c", "a/x/c", true},
|
||||
{"a/??/c", "a/x/c", false},
|
||||
{"a/??/c", "a/xx/c", true},
|
||||
{"a/???/c", "a/x z/c", true},
|
||||
{"a/?/c", "a/xx/c", false},
|
||||
{"a/?/?/c", "a/x/y/c", true},
|
||||
{"a/?/?/?/c", "a/x/y/z/c", true},
|
||||
{"a/???/c", "a/x/y/c", false},
|
||||
{"a/?????", "a/x/y/c", false},
|
||||
|
||||
{"*", "a", true},
|
||||
{"*", "abc", true},
|
||||
{"*", "abc 123", true},
|
||||
{"*", "xxx/yyy", false},
|
||||
{"*/*", "xxx/yyy", true},
|
||||
{"*/*", "xxx/yyy/zzz", false},
|
||||
{"*/*/c", "xxx/yyy/c", true},
|
||||
{"a/*/*", "a/xxx/yyy", true},
|
||||
{"a/*/c", "a/xxx/c", true},
|
||||
{"a/*/c", "a/xxx/c", true},
|
||||
{"a/*/*/c", "a/b/c", false},
|
||||
|
||||
{"**", "a", true},
|
||||
{"**", "abc", true},
|
||||
{"**", "abc 123", true},
|
||||
{"**", "xxx/yyy", true},
|
||||
{"**", "xxx/yyy/zzz", true},
|
||||
{"**/**", "xxx", false},
|
||||
{"**/**", "xxx/yyy", true},
|
||||
{"**/**", "xxx/yyy/zzz", true},
|
||||
{"**/**/**", "xxx/yyy/zzz", true},
|
||||
{"**/**/c", "xxx/yyy/c", true},
|
||||
{"**/**/c", "xxx/yyy/c/d", false},
|
||||
{"a/**/**", "a/xxx/yyy", true},
|
||||
{"a/**/c", "a/xxx/c", true},
|
||||
{"a/**/c", "a/xxx/yyy/c", true},
|
||||
{"a/**/c", "a/xxx/y y/zzz/c", true},
|
||||
|
||||
{"a/**/c", "a/c", false},
|
||||
{"a/**c", "a/c", true},
|
||||
|
||||
{"xxx/**.foo", "xxx/aaa.foo", true},
|
||||
{"xxx/**.foo", "xxx/yyy/zzz/.foo", true},
|
||||
{"xxx/**.foo", "xxx/yyy/zzz/bar.foo", true},
|
||||
} {
|
||||
f, err := match.New(test.pattern)
|
||||
if err != nil {
|
||||
t.Errorf(`match.New("%v")`, test.pattern)
|
||||
continue
|
||||
}
|
||||
matched := f(test.path)
|
||||
switch {
|
||||
case matched && !test.expect:
|
||||
t.Errorf(`Path "%v" matched against pattern "%v"`, test.path, test.pattern)
|
||||
case !matched && test.expect:
|
||||
t.Errorf(`Path "%v" did not match against pattern "%v"`, test.path, test.pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrOnPlaceholder(t *testing.T) {
|
||||
for _, pattern := range []string{"a/b••c", "a/b•c", "a/b/¿c"} {
|
||||
_, err := match.New(pattern)
|
||||
if err == nil {
|
||||
t.Errorf(`match.New("%v") did not return an expected error`, pattern)
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Pattern must not contain") {
|
||||
t.Errorf(`match.New("%v") returned unrecognised error: %v`, pattern, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user