Files
gio/cmd/gogio/js_test.go
T
Daniel Martí 9bbeb92b61 cmd/gogio: reuse the test binary to call gogio in the e2e tests
We were using 'go run . <args>' before, which works fine, but does mean
re-linking a new binary and throwing it away at each invocation. Given
that the end-to-end tests don't do all that much work besides building
the tiny red.go app, this amount of extra work was noticeable.

We can obtain statistics for the JS sub-test, which used 'go run', via
the perflock and benchcmd tools:

	$ go test -c
	$ perflock -governorp% benchcmd EndToEnd/JS ./gogio.test -test.run=EndToEnd/JS

After capturing those numbers before and after the change, we can then
compare them with benchstat. The CPU cost of the subtest is halved:

	name         old time/op         new time/op         delta
	EndToEnd/JS          1.42s ± 2%          1.07s ± 3%  -25.04%  (p=0.008 n=5+5)

	name         old user-time/op    new user-time/op    delta
	EndToEnd/JS          1.46s ± 3%          0.75s ± 5%  -48.34%  (p=0.008 n=5+5)

	name         old sys-time/op     new sys-time/op     delta
	EndToEnd/JS          366ms ±13%          224ms ± 7%  -38.79%  (p=0.008 n=5+5)

An alternative here would have been to refactor main.go to allow being
called directly. However, that would have required a non-trivial
refactor, since flag parsing is done via globals. Given that the
TestMain method is asy and keeps the main function simple, we've
decided to avoid a refactor.

While at it, remove the sleep in the Android driver to wait for the app
to come up on screen. Since we retry screenshots now, we no longer need
a static sleep. On average, we still need one retry for the initial
screenshot, but that's just 100ms versus the old 500ms. The maximum wait
time is also 2s here, which should scale better for slower devices.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
2020-03-02 17:34:59 +01:00

141 lines
3.4 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package main_test
import (
"bytes"
"context"
"errors"
"image"
"image/png"
"net/http"
"net/http/httptest"
"os/exec"
"strings"
"github.com/chromedp/cdproto/runtime"
"github.com/chromedp/chromedp"
_ "gioui.org/unit" // the build tool adds it to go.mod, so keep it there
)
type JSTestDriver struct {
driverBase
// ctx is the chromedp context.
ctx context.Context
}
func (d *JSTestDriver) Start(path string, width, height int) {
if raceEnabled {
d.Skipf("js/wasm doesn't support -race; skipping")
}
// First, build the app.
dir := d.tempDir("gio-endtoend-js")
d.gogio("-target=js", "-o="+dir, path)
// Second, start Chrome.
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.Flag("headless", *headless),
// The default would be use-gl=desktop when there's a GPU we can
// use, falling back to use-gl=swiftshader otherwise or when we
// are running in headless mode. Swiftshader allows full WebGL
// support with just a CPU.
//
// Unfortunately, many Linux distros like Arch and Alpine
// package Chromium without Swiftshader, so we can't rely on the
// defaults above. use-gl=egl works on any machine with a GPU,
// even if we run Chrome in headless mode, which is OK for now.
//
// TODO(mvdan): remove all of this once these issues are fixed:
//
// https://bugs.archlinux.org/task/64307
// https://gitlab.alpinelinux.org/alpine/aports/issues/10920
chromedp.Flag("use-gl", "egl"),
)
actx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
d.Cleanup(cancel)
ctx, cancel := chromedp.NewContext(actx,
// Send all logf/errf calls to t.Logf
chromedp.WithLogf(d.Logf),
)
d.Cleanup(cancel)
d.ctx = ctx
if err := chromedp.Run(ctx); err != nil {
if errors.Is(err, exec.ErrNotFound) {
d.Skipf("test requires Chrome to be installed: %v", err)
return
}
d.Fatal(err)
}
chromedp.ListenTarget(ctx, func(ev interface{}) {
switch ev := ev.(type) {
case *runtime.EventConsoleAPICalled:
if ev.Type == "log" && len(ev.Args) == 1 &&
// Note that the argument values are JSON.
string(ev.Args[0].Value) == `"frame ready"` {
d.frameNotifs <- true
// These logs are expected. Don't show them.
break
}
switch ev.Type {
case "log", "info", "warning", "error":
var args strings.Builder
for i, arg := range ev.Args {
if i > 0 {
args.WriteString(", ")
}
args.Write(arg.Value)
}
d.Logf("console %s: %s", ev.Type, args.String())
}
}
})
// Third, serve the app folder, set the browser tab dimensions, and
// navigate to the folder.
ts := httptest.NewServer(http.FileServer(http.Dir(dir)))
d.Cleanup(ts.Close)
if err := chromedp.Run(ctx,
chromedp.EmulateViewport(int64(width), int64(height)),
chromedp.Navigate(ts.URL),
); err != nil {
d.Fatal(err)
}
// Wait for the gio app to render.
d.waitForFrame()
}
func (d *JSTestDriver) Screenshot() image.Image {
var buf []byte
if err := chromedp.Run(d.ctx,
chromedp.CaptureScreenshot(&buf),
); err != nil {
d.Fatal(err)
}
img, err := png.Decode(bytes.NewReader(buf))
if err != nil {
d.Fatal(err)
}
return img
}
func (d *JSTestDriver) Click(x, y int) {
if err := chromedp.Run(d.ctx,
chromedp.MouseClickXY(float64(x), float64(y)),
); err != nil {
d.Fatal(err)
}
// Wait for the gio app to render after this click.
d.waitForFrame()
}