tools: Support adding tint flags to the test cases

Starting a test case with `// flags: <flags>` will
append the <flags> to the tint executable for that test case.

Let's you specify things like `// flags: --transform XXX`, which lets us
end-to-end test a particular set of transforms.

Change-Id: I181e9f7e7c1fba5e3a47cf58aee462b51e4b6e3b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/60921
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
diff --git a/tools/src/cmd/test-runner/main.go b/tools/src/cmd/test-runner/main.go
index ce87b2e..d480db0 100644
--- a/tools/src/cmd/test-runner/main.go
+++ b/tools/src/cmd/test-runner/main.go
@@ -23,6 +23,7 @@
 	"os"
 	"os/exec"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"sort"
 	"strings"
@@ -230,9 +231,11 @@
 	go func() {
 		for i, file := range files { // For each test file...
 			file := filepath.Join(dir, file)
+			flags := parseFlags(file)
 			for _, format := range formats { // For each output format...
 				pendingJobs <- job{
 					file:   file,
+					flags:  flags,
 					format: format,
 					result: results[i][format],
 				}
@@ -472,6 +475,7 @@
 
 type job struct {
 	file   string
+	flags  []string
 	format outputFormat
 	result chan status
 }
@@ -524,6 +528,8 @@
 			}
 		}
 
+		args = append(args, j.flags...)
+
 		// Invoke the compiler...
 		start := time.Now()
 		ok, out := invoke(wd, exe, args...)
@@ -703,6 +709,24 @@
 	return true, str
 }
 
+var reFlags = regexp.MustCompile(` *\/\/ *flags:(.*)\n`)
+
+// parseFlags looks for a `// flags:` header at the start of the file with the
+// given path, returning each of the space delimited tokens that follow for the
+// line
+func parseFlags(path string) []string {
+	content, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil
+	}
+	header := strings.SplitN(string(content), "\n", 1)[0]
+	m := reFlags.FindStringSubmatch(header)
+	if len(m) != 2 {
+		return nil
+	}
+	return strings.Split(m[1], " ")
+}
+
 func printDuration(d time.Duration) string {
 	sec := int(d.Seconds())
 	min := int(sec) / 60