cmd/screentest,internal/screentest: add flag to run subset of tests

Add a -run flag to screentest, similar to the one for `go test`,
so the user can run only one or a few tests.

I found this useful when converting release notes to markdown.

Change-Id: I13efe9c5e95b4c939ce8d1b9442f723d51c04e05
Reviewed-on: https://go-review.googlesource.com/c/website/+/539497
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/cmd/screentest/main.go b/cmd/screentest/main.go
index da831db..94a3da5 100644
--- a/cmd/screentest/main.go
+++ b/cmd/screentest/main.go
@@ -9,14 +9,16 @@
 
 The flags are:
 
-	  -u
-	    update cached screenshots
-	  -v
-	    variables provided to script templates as comma separated KEY:VALUE pairs
-	  -c
-	    number of testcases to run concurrently
-		-d
-			chrome debugger url
+	-u
+	  update cached screenshots
+	-v
+	  variables provided to script templates as comma separated KEY:VALUE pairs
+	-c
+	  number of testcases to run concurrently
+	-d
+	  chrome debugger url
+	-run
+	  run only tests matching regexp
 */
 package main
 
@@ -26,6 +28,7 @@
 	"log"
 	"os"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"strings"
 
@@ -37,6 +40,7 @@
 	vars        = flag.String("v", "", "variables provided to script templates as comma separated KEY:VALUE pairs")
 	concurrency = flag.Int("c", (runtime.NumCPU()+1)/2, "number of testcases to run concurrently")
 	debuggerURL = flag.String("d", "", "chrome debugger url")
+	run         = flag.String("run", "", "regexp to match test")
 )
 
 func main() {
@@ -71,6 +75,13 @@
 		Vars:           parsedVars,
 		DebuggerURL:    *debuggerURL,
 	}
+	if *run != "" {
+		re, err := regexp.Compile(*run)
+		if err != nil {
+			log.Fatal(err)
+		}
+		opts.Filter = re.MatchString
+	}
 	if err := screentest.CheckHandler(glob, opts); err != nil {
 		log.Fatal(err)
 	}
diff --git a/internal/screentest/screentest.go b/internal/screentest/screentest.go
index ecbb4e8..29df1df 100644
--- a/internal/screentest/screentest.go
+++ b/internal/screentest/screentest.go
@@ -159,6 +159,10 @@
 	// screentest tries to find the Chrome executable on the system and starts
 	// a new instance.
 	DebuggerURL string
+
+	// If set, only tests for which Filter returns true are included.
+	// Filter is called on the test name.
+	Filter func(string) bool
 }
 
 // CheckHandler runs the test scripts matched by glob. If any errors are
@@ -188,11 +192,11 @@
 	defer cancel()
 	var buf bytes.Buffer
 	for _, file := range files {
-		tests, err := readTests(file, opts.Vars)
+		tests, err := readTests(file, opts.Vars, opts.Filter)
 		if err != nil {
 			return fmt.Errorf("readTestdata(%q): %w", file, err)
 		}
-		if len(tests) == 0 {
+		if len(tests) == 0 && opts.Filter == nil {
 			return fmt.Errorf("no tests found in %q", file)
 		}
 		if err := cleanOutput(ctx, tests); err != nil {
@@ -248,7 +252,7 @@
 	)...)
 	defer cancel()
 	for _, file := range files {
-		tests, err := readTests(file, opts.Vars)
+		tests, err := readTests(file, opts.Vars, nil)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -415,7 +419,7 @@
 }
 
 // readTests parses the testcases from a text file.
-func readTests(file string, vars map[string]string) ([]*testcase, error) {
+func readTests(file string, vars map[string]string, filter func(string) bool) ([]*testcase, error) {
 	tmpl := template.New(filepath.Base(file)).Funcs(template.FuncMap{
 		"ints": func(start, end int) []int {
 			var out []int
@@ -561,6 +565,9 @@
 			if err != nil {
 				return nil, fmt.Errorf("url.Parse(%q): %w", originB+pathname, err)
 			}
+			if filter != nil && !filter(testName) {
+				continue
+			}
 			test := &testcase{
 				name:        testName,
 				tasks:       tasks,
diff --git a/internal/screentest/screentest_test.go b/internal/screentest/screentest_test.go
index 99d7b8e..694cc75 100644
--- a/internal/screentest/screentest_test.go
+++ b/internal/screentest/screentest_test.go
@@ -173,7 +173,7 @@
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := readTests(tt.args.filename, map[string]string{"Authorization": "Bearer token"})
+			got, err := readTests(tt.args.filename, map[string]string{"Authorization": "Bearer token"}, nil)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("readTests() error = %v, wantErr %v", err, tt.wantErr)
 				return