~eliasnaur/gio-patches

cmd/gogio: extract endtoend driver base into a type v1 PROPOSED

Daniel Martí: 1
 cmd/gogio: extract endtoend driver base into a type

 5 files changed, 109 insertions(+), 119 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio-patches/patches/9734/mbox | git am -3
Learn more about email & git

[PATCH] cmd/gogio: extract endtoend driver base into a type Export this patch

This type contains all the common bits, such as *testing.T, as well as
the channel and method used to wait for blocking until a frame is ready.

It also allows us to initialise this base separately from Start, which
keeps the exported method simpler to understand.

The base type is embedded into the specific driver types, so that the
code remains simple. While at it, start embedding *testing.T too, so
that we can write d.Fatalf instead of d.t.Fatalf. The drivers will only
have a small number of exported methods as per the interface, so it's
easy to keep those from colliding with the method set on T.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
---
 cmd/gogio/android_test.go | 44 +++++++++++++-----------------
 cmd/gogio/e2e_test.go     | 32 +++++++++++++++-------
 cmd/gogio/js_test.go      | 44 +++++++++++++-----------------
 cmd/gogio/wayland_test.go | 56 +++++++++++++++++----------------------
 cmd/gogio/x11_test.go     | 52 ++++++++++++++++--------------------
 5 files changed, 109 insertions(+), 119 deletions(-)

diff --git a/cmd/gogio/android_test.go b/cmd/gogio/android_test.go
index 6571392..9fe87f8 100644
--- a/cmd/gogio/android_test.go
+++ b/cmd/gogio/android_test.go
@@ -15,14 +15,11 @@ import (
	"path/filepath"
	"regexp"
	"strings"
	"testing"
	"time"
)

type AndroidTestDriver struct {
	t *testing.T

	frameNotifs chan bool
	driverBase

	sdkDir  string
	adbPath string
@@ -30,13 +27,10 @@ type AndroidTestDriver struct {

var rxAdbDevice = regexp.MustCompile(`(.*)\s+device$`)

func (d *AndroidTestDriver) Start(t_ *testing.T, path string, width, height int) {
	d.frameNotifs = make(chan bool, 1)
	d.t = t_

func (d *AndroidTestDriver) Start(path string, width, height int) {
	d.sdkDir = os.Getenv("ANDROID_HOME")
	if d.sdkDir == "" {
		d.t.Skipf("Android SDK is required; set $ANDROID_HOME")
		d.Skipf("Android SDK is required; set $ANDROID_HOME")
	}
	d.adbPath = filepath.Join(d.sdkDir, "platform-tools", "adb")

@@ -44,10 +38,10 @@ func (d *AndroidTestDriver) Start(t_ *testing.T, path string, width, height int)
	devices := rxAdbDevice.FindAllSubmatch(devOut, -1)
	switch len(devices) {
	case 0:
		d.t.Skipf("no Android devices attached via adb; skipping")
		d.Skipf("no Android devices attached via adb; skipping")
	case 1:
	default:
		d.t.Skipf("multiple Android devices attached via adb; skipping")
		d.Skipf("multiple Android devices attached via adb; skipping")
	}

	// If the device is attached but asleep, it's probably just charging.
@@ -57,29 +51,29 @@ func (d *AndroidTestDriver) Start(t_ *testing.T, path string, width, height int)
		d.adb("shell", "dumpsys", "power"),
		[]byte(" mWakefulness=Awake"),
	) {
		d.t.Skipf("Android device isn't awake; skipping")
		d.Skipf("Android device isn't awake; skipping")
	}

	// First, build the app.
	dir, err := ioutil.TempDir("", "gio-endtoend-android")
	if err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	d.t.Cleanup(func() { os.RemoveAll(dir) })
	d.Cleanup(func() { os.RemoveAll(dir) })
	apk := filepath.Join(dir, "e2e.apk")

	// TODO(mvdan): This is inefficient, as we link the gogio tool every time.
	// Consider options in the future. On the plus side, this is simple.
	cmd := exec.Command("go", "run", ".", "-target=android", "-appid="+appid, "-o="+apk, path)
	if out, err := cmd.CombinedOutput(); err != nil {
		d.t.Fatalf("could not build app: %s:\n%s", err, out)
		d.Fatalf("could not build app: %s:\n%s", err, out)
	}

	// Make sure the app isn't installed already, and try to uninstall it
	// when we finish. Previous failed test runs might have left the app.
	d.tryUninstall()
	d.adb("install", apk)
	d.t.Cleanup(d.tryUninstall)
	d.Cleanup(d.tryUninstall)

	// Force our e2e app to be fullscreen, so that the android system bar at
	// the top doesn't mess with our screenshots.
@@ -101,13 +95,13 @@ func (d *AndroidTestDriver) Start(t_ *testing.T, path string, width, height int)
		)
		logcat, err := cmd.StdoutPipe()
		if err != nil {
			d.t.Fatal(err)
			d.Fatal(err)
		}
		cmd.Stderr = os.Stderr
		if err := cmd.Start(); err != nil {
			d.t.Fatal(err)
			d.Fatal(err)
		}
		d.t.Cleanup(cancel)
		d.Cleanup(cancel)
		go func() {
			scanner := bufio.NewScanner(logcat)
			for scanner.Scan() {
@@ -130,14 +124,14 @@ func (d *AndroidTestDriver) Start(t_ *testing.T, path string, width, height int)
	time.Sleep(500 * time.Millisecond)

	// Wait for the gio app to render.
	waitForFrame(d.t, d.frameNotifs)
	d.waitForFrame()
}

func (d *AndroidTestDriver) Screenshot() image.Image {
	out := d.adb("shell", "screencap", "-p")
	img, err := png.Decode(bytes.NewReader(out))
	if err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	return img
}
@@ -150,7 +144,7 @@ func (d *AndroidTestDriver) tryUninstall() {
			// The package is not installed. Don't log anything.
			return
		}
		d.t.Logf("could not uninstall: %v\n%s", err, out)
		d.Logf("could not uninstall: %v\n%s", err, out)
	}
}

@@ -162,8 +156,8 @@ func (d *AndroidTestDriver) adb(args ...interface{}) []byte {
	cmd := exec.Command(d.adbPath, strs...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		d.t.Errorf("%s", out)
		d.t.Fatal(err)
		d.Errorf("%s", out)
		d.Fatal(err)
	}
	return out
}
@@ -172,5 +166,5 @@ func (d *AndroidTestDriver) Click(x, y int) {
	d.adb("shell", "input", "tap", x, y)

	// Wait for the gio app to render after this click.
	waitForFrame(d.t, d.frameNotifs)
	d.waitForFrame()
}
diff --git a/cmd/gogio/e2e_test.go b/cmd/gogio/e2e_test.go
index b7f6a9e..5d29617 100644
--- a/cmd/gogio/e2e_test.go
+++ b/cmd/gogio/e2e_test.go
@@ -20,14 +20,15 @@ const appid = "localhost.gogio.endtoend"
// tests on. None of its methods return any errors, as the errors are directly
// reported to testing.T via methods like Fatal.
type TestDriver interface {
	// Start provides the test driver with a testing.T, as well as the path
	// to the Gio app to use for the test. The driver should attempt to run
	// the app with the given width and height, and the platform's
	initBase(*testing.T)

	// Start opens the Gio app found at path. The driver should attempt to
	// run the app with the given width and height, and the platform's
	// background should be white.
	//
	// When the function returns, the gio app must be ready to use on the
	// platform, with its initial frame fully drawn.
	Start(t *testing.T, path string, width, height int)
	Start(path string, width, height int)

	// Screenshot takes a screenshot of the Gio app on the platform.
	Screenshot() image.Image
@@ -38,6 +39,17 @@ type TestDriver interface {
	Click(x, y int)
}

type driverBase struct {
	*testing.T

	frameNotifs chan bool
}

func (d *driverBase) initBase(t *testing.T) {
	d.T = t
	d.frameNotifs = make(chan bool, 1)
}

func TestEndToEnd(t *testing.T) {
	if testing.Short() {
		t.Skipf("end-to-end tests tend to be slow")
@@ -66,9 +78,11 @@ func TestEndToEnd(t *testing.T) {
}

func runEndToEndTest(t *testing.T, driver TestDriver) {
	driver.initBase(t)

	size := image.Point{X: 800, Y: 600}
	t.Log("starting driver and gio app")
	driver.Start(t, "testdata/red.go", size.X, size.Y)
	driver.Start("testdata/red.go", size.X, size.Y)

	// The colors are split in four rectangular sections. Check the corners
	// of each of the sections. We check the corners left to right, top to
@@ -146,8 +160,8 @@ func wantColor(t *testing.T, img image.Image, x, y int, want color.Color) {
	}
}

func waitForFrame(t *testing.T, frameNotifs <-chan bool) {
	t.Helper()
func (d *driverBase) waitForFrame() {
	d.Helper()

	// Unfortunately, there isn't a way to select on a test failing, since
	// testing.T doesn't have anything like a context or a "done" channel.
@@ -158,8 +172,8 @@ func waitForFrame(t *testing.T, frameNotifs <-chan bool) {
	// For now, a static short timeout is better than nothing. 2s is plenty
	// for our simple test app to render on any device.
	select {
	case <-frameNotifs:
	case <-d.frameNotifs:
	case <-time.After(5 * time.Second):
		t.Fatalf("timed out waiting for a frame to be ready")
		d.Fatalf("timed out waiting for a frame to be ready")
	}
}
diff --git a/cmd/gogio/js_test.go b/cmd/gogio/js_test.go
index 3b7df63..b8fdcfc 100644
--- a/cmd/gogio/js_test.go
+++ b/cmd/gogio/js_test.go
@@ -14,7 +14,6 @@ import (
	"os"
	"os/exec"
	"strings"
	"testing"

	"github.com/chromedp/cdproto/runtime"
	"github.com/chromedp/chromedp"
@@ -23,34 +22,29 @@ import (
)

type JSTestDriver struct {
	t *testing.T

	frameNotifs chan bool
	driverBase

	// ctx is the chromedp context.
	ctx context.Context
}

func (d *JSTestDriver) Start(t_ *testing.T, path string, width, height int) {
	d.frameNotifs = make(chan bool, 1)
	d.t = t_

func (d *JSTestDriver) Start(path string, width, height int) {
	if raceEnabled {
		d.t.Skipf("js/wasm doesn't support -race; skipping")
		d.Skipf("js/wasm doesn't support -race; skipping")
	}

	// First, build the app.
	dir, err := ioutil.TempDir("", "gio-endtoend-js")
	if err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	d.t.Cleanup(func() { os.RemoveAll(dir) })
	d.Cleanup(func() { os.RemoveAll(dir) })

	// TODO(mvdan): This is inefficient, as we link the gogio tool every time.
	// Consider options in the future. On the plus side, this is simple.
	cmd := exec.Command("go", "run", ".", "-target=js", "-o="+dir, path)
	if out, err := cmd.CombinedOutput(); err != nil {
		d.t.Fatalf("could not build app: %s:\n%s", err, out)
		d.Fatalf("could not build app: %s:\n%s", err, out)
	}

	// Second, start Chrome.
@@ -75,21 +69,21 @@ func (d *JSTestDriver) Start(t_ *testing.T, path string, width, height int) {
	)

	actx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
	d.t.Cleanup(cancel)
	d.Cleanup(cancel)

	ctx, cancel := chromedp.NewContext(actx,
		// Send all logf/errf calls to t.Logf
		chromedp.WithLogf(d.t.Logf),
		chromedp.WithLogf(d.Logf),
	)
	d.t.Cleanup(cancel)
	d.Cleanup(cancel)
	d.ctx = ctx

	if err := chromedp.Run(ctx); err != nil {
		if errors.Is(err, exec.ErrNotFound) {
			d.t.Skipf("test requires Chrome to be installed: %v", err)
			d.Skipf("test requires Chrome to be installed: %v", err)
			return
		}
		d.t.Fatal(err)
		d.Fatal(err)
	}
	chromedp.ListenTarget(ctx, func(ev interface{}) {
		switch ev := ev.(type) {
@@ -111,7 +105,7 @@ func (d *JSTestDriver) Start(t_ *testing.T, path string, width, height int) {
					}
					args.Write(arg.Value)
				}
				d.t.Logf("console %s: %s", ev.Type, args.String())
				d.Logf("console %s: %s", ev.Type, args.String())
			}
		}
	})
@@ -119,17 +113,17 @@ func (d *JSTestDriver) Start(t_ *testing.T, path string, width, height int) {
	// Third, serve the app folder, set the browser tab dimensions, and
	// navigate to the folder.
	ts := httptest.NewServer(http.FileServer(http.Dir(dir)))
	d.t.Cleanup(ts.Close)
	d.Cleanup(ts.Close)

	if err := chromedp.Run(ctx,
		chromedp.EmulateViewport(int64(width), int64(height)),
		chromedp.Navigate(ts.URL),
	); err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}

	// Wait for the gio app to render.
	waitForFrame(d.t, d.frameNotifs)
	d.waitForFrame()
}

func (d *JSTestDriver) Screenshot() image.Image {
@@ -137,11 +131,11 @@ func (d *JSTestDriver) Screenshot() image.Image {
	if err := chromedp.Run(d.ctx,
		chromedp.CaptureScreenshot(&buf),
	); err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	img, err := png.Decode(bytes.NewReader(buf))
	if err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	return img
}
@@ -150,9 +144,9 @@ func (d *JSTestDriver) Click(x, y int) {
	if err := chromedp.Run(d.ctx,
		chromedp.MouseClickXY(float64(x), float64(y)),
	); err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}

	// Wait for the gio app to render after this click.
	waitForFrame(d.t, d.frameNotifs)
	d.waitForFrame()
}
diff --git a/cmd/gogio/wayland_test.go b/cmd/gogio/wayland_test.go
index a57f8a8..d10ea82 100644
--- a/cmd/gogio/wayland_test.go
+++ b/cmd/gogio/wayland_test.go
@@ -18,15 +18,12 @@ import (
	"regexp"
	"strings"
	"sync"
	"testing"
	"text/template"
	"time"
)

type WaylandTestDriver struct {
	t *testing.T

	frameNotifs chan bool
	driverBase

	runtimeDir string
	socket     string
@@ -42,10 +39,7 @@ default_border none

var rxSwayReady = regexp.MustCompile(`Running compositor on wayland display '(.*)'`)

func (d *WaylandTestDriver) Start(t_ *testing.T, path string, width, height int) {
	d.frameNotifs = make(chan bool, 1)
	d.t = t_

func (d *WaylandTestDriver) Start(path string, width, height int) {
	// We want os.Environ, so that it can e.g. find $DISPLAY to run within
	// X11. wlroots env vars are documented at:
	// https://github.com/swaywm/wlroots/blob/master/docs/env_vars.md
@@ -60,16 +54,16 @@ func (d *WaylandTestDriver) Start(t_ *testing.T, path string, width, height int)
		"swaymsg", // to send input
	} {
		if _, err := exec.LookPath(prog); err != nil {
			d.t.Skipf("%s needed to run", prog)
			d.Skipf("%s needed to run", prog)
		}
	}

	// First, build the app.
	dir, err := ioutil.TempDir("", "gio-endtoend-wayland")
	if err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	d.t.Cleanup(func() { os.RemoveAll(dir) })
	d.Cleanup(func() { os.RemoveAll(dir) })

	bin := filepath.Join(dir, "red")
	flags := []string{"build", "-tags", "nox11", "-o=" + bin}
@@ -79,19 +73,19 @@ func (d *WaylandTestDriver) Start(t_ *testing.T, path string, width, height int)
	flags = append(flags, path)
	cmd := exec.Command("go", flags...)
	if out, err := cmd.CombinedOutput(); err != nil {
		d.t.Fatalf("could not build app: %s:\n%s", err, out)
		d.Fatalf("could not build app: %s:\n%s", err, out)
	}

	conf := filepath.Join(dir, "config")
	f, err := os.Create(conf)
	if err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	defer f.Close()
	if err := tmplSwayConfig.Execute(f, struct{ Width, Height int }{
		width, height,
	}); err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}

	d.socket = filepath.Join(dir, "socket")
@@ -100,7 +94,7 @@ func (d *WaylandTestDriver) Start(t_ *testing.T, path string, width, height int)
	env = append(env, "XDG_RUNTIME_DIR="+d.runtimeDir)

	var wg sync.WaitGroup
	d.t.Cleanup(wg.Wait)
	d.Cleanup(wg.Wait)

	// First, start sway.
	{
@@ -112,10 +106,10 @@ func (d *WaylandTestDriver) Start(t_ *testing.T, path string, width, height int)
			log.Fatal(err)
		}
		if err := cmd.Start(); err != nil {
			d.t.Fatal(err)
			d.Fatal(err)
		}
		d.t.Cleanup(cancel)
		d.t.Cleanup(func() {
		d.Cleanup(cancel)
		d.Cleanup(func() {
			// Give it a chance to exit gracefully, cleaning up
			// after itself. After 10ms, the deferred cancel above
			// will signal an os.Kill.
@@ -129,7 +123,7 @@ func (d *WaylandTestDriver) Start(t_ *testing.T, path string, width, height int)
		for {
			line, err := br.ReadString('\n')
			if err != nil {
				d.t.Fatal(err)
				d.Fatal(err)
			}
			if m := rxSwayReady.FindStringSubmatch(line); m != nil {
				d.display = m[1]
@@ -143,7 +137,7 @@ func (d *WaylandTestDriver) Start(t_ *testing.T, path string, width, height int)
				// Don't print all stderr, since we use --verbose.
				// TODO(mvdan): if it's useful, probably filter
				// errors and show them.
				d.t.Error(err)
				d.Error(err)
			}
			wg.Done()
		}()
@@ -156,20 +150,20 @@ func (d *WaylandTestDriver) Start(t_ *testing.T, path string, width, height int)
		cmd.Env = []string{"XDG_RUNTIME_DIR=" + d.runtimeDir, "WAYLAND_DISPLAY=" + d.display}
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			d.t.Fatal(err)
			d.Fatal(err)
		}
		stderr := &bytes.Buffer{}
		cmd.Stderr = stderr
		if err := cmd.Start(); err != nil {
			d.t.Fatal(err)
			d.Fatal(err)
		}
		d.t.Cleanup(cancel)
		d.Cleanup(cancel)
		wg.Add(1)
		go func() {
			if err := cmd.Wait(); err != nil && ctx.Err() == nil {
				// Print stderr and error.
				io.Copy(os.Stdout, stderr)
				d.t.Error(err)
				d.Error(err)
			}
			wg.Done()
		}()
@@ -185,7 +179,7 @@ func (d *WaylandTestDriver) Start(t_ *testing.T, path string, width, height int)
	}

	// Wait for the gio app to render.
	waitForFrame(d.t, d.frameNotifs)
	d.waitForFrame()
}

func (d *WaylandTestDriver) Screenshot() image.Image {
@@ -193,12 +187,12 @@ func (d *WaylandTestDriver) Screenshot() image.Image {
	cmd.Env = []string{"XDG_RUNTIME_DIR=" + d.runtimeDir, "WAYLAND_DISPLAY=" + d.display}
	out, err := cmd.CombinedOutput()
	if err != nil {
		d.t.Errorf("%s", out)
		d.t.Fatal(err)
		d.Errorf("%s", out)
		d.Fatal(err)
	}
	img, err := png.Decode(bytes.NewReader(out))
	if err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	return img
}
@@ -210,8 +204,8 @@ func (d *WaylandTestDriver) swaymsg(args ...interface{}) {
	}
	cmd := exec.Command("swaymsg", strs...)
	if out, err := cmd.CombinedOutput(); err != nil {
		d.t.Errorf("%s", out)
		d.t.Fatal(err)
		d.Errorf("%s", out)
		d.Fatal(err)
	}
}

@@ -221,5 +215,5 @@ func (d *WaylandTestDriver) Click(x, y int) {
	d.swaymsg("seat", "-", "cursor", "release", "button1")

	// Wait for the gio app to render after this click.
	waitForFrame(d.t, d.frameNotifs)
	d.waitForFrame()
}
diff --git a/cmd/gogio/x11_test.go b/cmd/gogio/x11_test.go
index e23e655..5774e09 100644
--- a/cmd/gogio/x11_test.go
+++ b/cmd/gogio/x11_test.go
@@ -16,22 +16,16 @@ import (
	"os/exec"
	"path/filepath"
	"sync"
	"testing"
	"time"
)

type X11TestDriver struct {
	t *testing.T

	frameNotifs chan bool
	driverBase

	display string
}

func (d *X11TestDriver) Start(t_ *testing.T, path string, width, height int) {
	d.frameNotifs = make(chan bool, 1)
	d.t = t_

func (d *X11TestDriver) Start(path string, width, height int) {
	// Pick a random display number between 1 and 100,000. Most machines
	// will only be using :0, so there's only a 0.001% chance of two
	// concurrent test runs to run into a conflict.
@@ -57,16 +51,16 @@ func (d *X11TestDriver) Start(t_ *testing.T, path string, width, height int) {
		"xdotool", // to send input
	} {
		if _, err := exec.LookPath(prog); err != nil {
			d.t.Skipf("%s needed to run", prog)
			d.Skipf("%s needed to run", prog)
		}
	}

	// First, build the app.
	dir, err := ioutil.TempDir("", "gio-endtoend-x11")
	if err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	d.t.Cleanup(func() { os.RemoveAll(dir) })
	d.Cleanup(func() { os.RemoveAll(dir) })

	bin := filepath.Join(dir, "red")
	flags := []string{"build", "-tags", "nowayland", "-o=" + bin}
@@ -76,11 +70,11 @@ func (d *X11TestDriver) Start(t_ *testing.T, path string, width, height int) {
	flags = append(flags, path)
	cmd := exec.Command("go", flags...)
	if out, err := cmd.CombinedOutput(); err != nil {
		d.t.Fatalf("could not build app: %s:\n%s", err, out)
		d.Fatalf("could not build app: %s:\n%s", err, out)
	}

	var wg sync.WaitGroup
	d.t.Cleanup(wg.Wait)
	d.Cleanup(wg.Wait)

	// First, start the X server.
	{
@@ -90,10 +84,10 @@ func (d *X11TestDriver) Start(t_ *testing.T, path string, width, height int) {
		cmd.Stdout = combined
		cmd.Stderr = combined
		if err := cmd.Start(); err != nil {
			d.t.Fatal(err)
			d.Fatal(err)
		}
		d.t.Cleanup(cancel)
		d.t.Cleanup(func() {
		d.Cleanup(cancel)
		d.Cleanup(func() {
			// Give it a chance to exit gracefully, cleaning up
			// after itself. After 10ms, the deferred cancel above
			// will signal an os.Kill.
@@ -111,7 +105,7 @@ func (d *X11TestDriver) Start(t_ *testing.T, path string, width, height int) {
				break
			}
			if i >= 100 {
				d.t.Fatalf("timed out waiting for %s", socket)
				d.Fatalf("timed out waiting for %s", socket)
			}
		}

@@ -120,7 +114,7 @@ func (d *X11TestDriver) Start(t_ *testing.T, path string, width, height int) {
			if err := cmd.Wait(); err != nil && ctx.Err() == nil {
				// Print all output and error.
				io.Copy(os.Stdout, combined)
				d.t.Error(err)
				d.Error(err)
			}
			wg.Done()
		}()
@@ -133,21 +127,21 @@ func (d *X11TestDriver) Start(t_ *testing.T, path string, width, height int) {
		cmd.Env = []string{"DISPLAY=" + d.display}
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			d.t.Fatal(err)
			d.Fatal(err)
		}
		stderr := &bytes.Buffer{}
		cmd.Stderr = stderr

		if err := cmd.Start(); err != nil {
			d.t.Fatal(err)
			d.Fatal(err)
		}
		d.t.Cleanup(cancel)
		d.Cleanup(cancel)
		wg.Add(1)
		go func() {
			if err := cmd.Wait(); err != nil && ctx.Err() == nil {
				// Print stderr and error.
				io.Copy(os.Stdout, stderr)
				d.t.Error(err)
				d.Error(err)
			}
			wg.Done()
		}()
@@ -164,7 +158,7 @@ func (d *X11TestDriver) Start(t_ *testing.T, path string, width, height int) {
	}

	// Wait for the gio app to render.
	waitForFrame(d.t, d.frameNotifs)
	d.waitForFrame()
}

func (d *X11TestDriver) Screenshot() image.Image {
@@ -172,12 +166,12 @@ func (d *X11TestDriver) Screenshot() image.Image {
	cmd.Env = []string{"DISPLAY=" + d.display}
	out, err := cmd.CombinedOutput()
	if err != nil {
		d.t.Errorf("%s", out)
		d.t.Fatal(err)
		d.Errorf("%s", out)
		d.Fatal(err)
	}
	img, err := png.Decode(bytes.NewReader(out))
	if err != nil {
		d.t.Fatal(err)
		d.Fatal(err)
	}
	return img
}
@@ -190,8 +184,8 @@ func (d *X11TestDriver) xdotool(args ...interface{}) {
	cmd := exec.Command("xdotool", strs...)
	cmd.Env = []string{"DISPLAY=" + d.display}
	if out, err := cmd.CombinedOutput(); err != nil {
		d.t.Errorf("%s", out)
		d.t.Fatal(err)
		d.Errorf("%s", out)
		d.Fatal(err)
	}
}

@@ -200,5 +194,5 @@ func (d *X11TestDriver) Click(x, y int) {
	d.xdotool("click", "1")

	// Wait for the gio app to render after this click.
	waitForFrame(d.t, d.frameNotifs)
	d.waitForFrame()
}
-- 
2.25.0
Thanks, applied.
View this thread in the archives