{cmd,internal}/screentest: testcases run concurrently
Testcases for screentest will run concurrently with a configurable max concurrency setting that defaults to half of number of CPUs on a system. Change-Id: I07e7ffd8d3867c47b709c160110a58ac60ee357c Reviewed-on: https://go-review.googlesource.com/c/website/+/377256 Run-TryBot: Jamal Carvalho <jamal@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Trust: Jamal Carvalho <jamalcarvalho@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
Родитель
180886a91e
Коммит
41ad36154e
|
@ -2,7 +2,18 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Command screentest runs the screentest check for a set of scripts.
|
||||
/*
|
||||
Command screentest runs the screentest check for a set of scripts.
|
||||
Usage: screentest [flags] [glob]
|
||||
|
||||
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
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -11,19 +22,21 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/website/internal/screentest"
|
||||
)
|
||||
|
||||
var (
|
||||
update = flag.Bool("update", false, "update cached screenshots")
|
||||
vars = flag.String("vars", "", "provide variables to the script template as comma separated KEY:VALUE pairs")
|
||||
update = flag.Bool("u", false, "update cached screenshots")
|
||||
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")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Printf("Usage: screentest [OPTIONS] glob\n")
|
||||
fmt.Printf("Usage: screentest [flags] [glob]\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
@ -47,7 +60,7 @@ func main() {
|
|||
parsedVars[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
if err := screentest.CheckHandler(glob, *update, parsedVars); err != nil {
|
||||
if err := screentest.CheckHandler(glob, *update, *concurrency, parsedVars); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ import (
|
|||
|
||||
// CheckHandler runs the test scripts matched by glob. If any errors are
|
||||
// encountered, CheckHandler returns an error listing the problems.
|
||||
func CheckHandler(glob string, update bool, vars map[string]string) error {
|
||||
func CheckHandler(glob string, update bool, maxConcurrency int, vars map[string]string) error {
|
||||
now := time.Now()
|
||||
ctx := context.Background()
|
||||
files, err := filepath.Glob(glob)
|
||||
|
@ -160,7 +160,8 @@ func CheckHandler(glob string, update bool, vars map[string]string) error {
|
|||
ctx, cancel = chromedp.NewContext(ctx, chromedp.WithLogf(log.Printf))
|
||||
defer cancel()
|
||||
var hdr bool
|
||||
for _, tc := range tests {
|
||||
runConcurrently(len(tests), maxConcurrency, func(i int) {
|
||||
tc := tests[i]
|
||||
if err := tc.run(ctx, update); err != nil {
|
||||
if !hdr {
|
||||
fmt.Fprintf(&buf, "%s\n\n", file)
|
||||
|
@ -170,7 +171,7 @@ func CheckHandler(glob string, update bool, vars map[string]string) error {
|
|||
fmt.Fprintf(&buf, "inspect diff at %s\n\n", tc.outDiff)
|
||||
}
|
||||
fmt.Println(tc.output.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
fmt.Printf("finished in %s\n\n", time.Since(now).Truncate(time.Millisecond))
|
||||
if buf.Len() > 0 {
|
||||
|
@ -180,7 +181,7 @@ func CheckHandler(glob string, update bool, vars map[string]string) error {
|
|||
}
|
||||
|
||||
// TestHandler runs the test script files matched by glob.
|
||||
func TestHandler(t *testing.T, glob string, update bool, vars map[string]string) {
|
||||
func TestHandler(t *testing.T, glob string, update, parallel bool, vars map[string]string) {
|
||||
ctx := context.Background()
|
||||
files, err := filepath.Glob(glob)
|
||||
if err != nil {
|
||||
|
@ -206,6 +207,9 @@ func TestHandler(t *testing.T, glob string, update bool, vars map[string]string)
|
|||
defer cancel()
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if parallel {
|
||||
t.Parallel()
|
||||
}
|
||||
if err := tc.run(ctx, update); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -788,3 +792,22 @@ func waitForEvent(eventName string) chromedp.ActionFunc {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runConcurrently calls f on each integer from 0 to n-1,
|
||||
// with at most max invocations active at once.
|
||||
// It waits for all invocations to complete.
|
||||
func runConcurrently(n, max int, f func(int)) {
|
||||
tokens := make(chan struct{}, max)
|
||||
for i := 0; i < n; i++ {
|
||||
i := i
|
||||
tokens <- struct{}{} // wait until the number of goroutines is below the limit
|
||||
go func() {
|
||||
f(i)
|
||||
<-tokens // let another goroutine run
|
||||
}()
|
||||
}
|
||||
// Wait for all goroutines to finish.
|
||||
for i := 0; i < cap(tokens); i++ {
|
||||
tokens <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,7 +239,7 @@ func TestCheckHandler(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := CheckHandler(tt.args.glob, false, nil); (err != nil) != tt.wantErr {
|
||||
if err := CheckHandler(tt.args.glob, false, 1, nil); (err != nil) != tt.wantErr {
|
||||
t.Fatalf("CheckHandler() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if len(tt.wantFiles) != 0 {
|
||||
|
@ -262,7 +262,7 @@ func TestTestHandler(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Skip()
|
||||
}
|
||||
TestHandler(t, "testdata/pass.txt", false, nil)
|
||||
TestHandler(t, "testdata/pass.txt", false, false, nil)
|
||||
}
|
||||
|
||||
func TestHeaders(t *testing.T) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче