cmd/gogio: add the first end-to-end X11 test

Right now it's very similar to the JS test on Chrome. Like it, this one
just runs the "red.go" gio app, takes a screenshot, and expects to see
red.

It also supports the -headless flag; when true, Xvfb is used and it's
entirely headless and hidden. Otherwise, Xephyr is used and once can see
the test in action. If the tool isn't installed, the test is skipped.

We need to add xgb as a dependency, so that we can connect to the X
server and interact with it, like taking screenshots.

Finally, this is an initial version, and a number of TODOs are left for
a later time. They'll get fixed in follow-up patches.

While at it, start making all tests parallel, since the end-to-end tests
take about a second each and neither are very cpu-intensive.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
This commit is contained in:
Daniel Martí
2019-10-29 17:28:06 +00:00
committed by Elias Naur
parent b8edf2ee04
commit cea8dc374b
5 changed files with 178 additions and 10 deletions
+4
View File
@@ -4,6 +4,10 @@ go 1.13
require (
gioui.org v0.0.0-20191029130630-4641607cd6a4
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046
github.com/chromedp/cdproto v0.0.0-20191009033829-c22f49c9ff0a
github.com/chromedp/chromedp v0.5.1
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
+7
View File
@@ -1,7 +1,14 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gioui.org v0.0.0-20191029130630-4641607cd6a4 h1:WKYMON5AGU5/Xs5ezSWq9i/hiobSXBsQlsPSVXybScM=
gioui.org v0.0.0-20191029130630-4641607cd6a4/go.mod h1:KqFFi2Dq5gYA3FJ0sDOt8OBXoMsuxMtE8v2f0JExXAY=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA=
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=
github.com/chromedp/cdproto v0.0.0-20191009033829-c22f49c9ff0a h1:AuIGvB6IuWpMEdfKQ+t77D6dzLpNftzxAsktehYyWn8=
github.com/chromedp/cdproto v0.0.0-20191009033829-c22f49c9ff0a/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/chromedp v0.5.1 h1:PAqhoCWCHzRphYnmmxLSiYk7EEwDplCm4woTCCaV2cQ=
+14 -10
View File
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"flag"
"image"
"image/png"
"io/ioutil"
"net/http"
@@ -25,6 +26,8 @@ import (
var headless = flag.Bool("headless", true, "run end-to-end tests in headless mode")
func TestJSOnChrome(t *testing.T) {
t.Parallel()
// First, build the app.
dir, err := ioutil.TempDir("", "gio-endtoend-js")
if err != nil {
@@ -124,14 +127,15 @@ func TestJSOnChrome(t *testing.T) {
t.Fatalf("expected dimensions to be %d*%d, got %d*%d",
wantSize, wantSize, size.X, size.Y)
}
wantColor := func(x, y int, r, g, b, a uint32) {
color := img.At(x, y)
r_, g_, b_, a_ := color.RGBA()
if r_ != r || g_ != g || b_ != b || a_ != a {
t.Errorf("got 0x%04x%04x%04x%04x at (%d,%d), want 0x%04x%04x%04x%04x",
r_, g_, b_, a_, x, y, r, g, b, a)
}
}
wantColor(5, 5, 0xffff, 0x0, 0x0, 0xffff)
wantColor(595, 595, 0xffff, 0x0, 0x0, 0xffff)
wantColor(t, img, 5, 5, 0xffff, 0x0, 0x0, 0xffff)
wantColor(t, img, 595, 595, 0xffff, 0x0, 0x0, 0xffff)
}
func wantColor(t *testing.T, img image.Image, x, y int, r, g, b, a uint32) {
color := img.At(x, y)
r_, g_, b_, a_ := color.RGBA()
if r_ != r || g_ != g || b_ != b || a_ != a {
t.Errorf("got 0x%04x%04x%04x%04x at (%d,%d), want 0x%04x%04x%04x%04x",
r_, g_, b_, a_, x, y, r, g, b, a)
}
}
+2
View File
@@ -9,6 +9,8 @@ type expval struct {
}
func TestAppID(t *testing.T) {
t.Parallel()
tests := []expval{
{"example", "localhost.example"},
{"example.com", "com.example"},
+151
View File
@@ -0,0 +1,151 @@
// SPDX-License-Identifier: Unlicense OR MIT
// TODO(mvdan): come up with an end-to-end platform interface, including methods
// like "take screenshot" or "close app", so that we can run the same tests on
// all supported platforms without writing them many times.
package main_test
import (
"bytes"
"context"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sync"
"testing"
"time"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
"github.com/BurntSushi/xgbutil/xgraphics"
)
func TestX11(t *testing.T) {
t.Parallel()
// TODO(mvdan): pick a random one between a large pool, and retry if
// it's already taken.
const display = ":15"
var xprog string
xflags := []string{"-wr"}
if *headless {
xprog = "Xvfb" // virtual X server
xflags = append(xflags, "-screen", "0", "600x600x24")
} else {
xprog = "Xephyr" // nested X server as a window
xflags = append(xflags, "-screen", "600x600")
}
xflags = append(xflags, display)
if _, err := exec.LookPath(xprog); err != nil {
t.Skipf("%s needed to run with -headless=%t", xprog, *headless)
}
// First, build the app.
dir, err := ioutil.TempDir("", "gio-endtoend-x11")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
bin := filepath.Join(dir, "red")
cmd := exec.Command("go", "build", "-o="+bin, "testdata/red.go")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("could not build app: %s:\n%s", err, out)
}
var wg sync.WaitGroup
defer wg.Wait()
// First, start the X server.
{
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, xprog, xflags...)
out := &bytes.Buffer{}
cmd.Stdout = out
cmd.Stderr = out
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
defer cancel()
// TODO(mvdan): properly wait for the display to be ready instead.
time.Sleep(200 * time.Millisecond)
wg.Add(1)
go func() {
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
// Print all output and error.
io.Copy(os.Stdout, out)
t.Error(err)
}
wg.Done()
}()
}
// Then, start our program on the X server above.
{
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, bin)
out := &bytes.Buffer{}
cmd.Env = append(os.Environ(), "DISPLAY="+display)
cmd.Stdout = out
cmd.Stderr = out
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
defer cancel()
wg.Add(1)
go func() {
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
// Print all output and error.
io.Copy(os.Stdout, out)
t.Error(err)
}
wg.Done()
}()
}
// Finally, run our tests. A connection to the X server is used to
// interact with it.
{
if !testing.Verbose() {
xgb.Logger.SetOutput(ioutil.Discard)
xgbutil.Logger.SetOutput(ioutil.Discard)
}
xu, err := xgbutil.NewConnDisplay(display)
if err != nil {
t.Fatal(err)
}
defer func() {
xu.Conn().Close()
// TODO(mvdan): Figure out a way to remove this sleep
// without introducing a panic. The xgb code will
// encounter a panic if the Xorg server exits before xgb
// has shut down fully.
// See: https://github.com/BurntSushi/xgb/pull/44
time.Sleep(20 * time.Millisecond)
}()
// Wait for the gio app to render.
// TODO(mvdan): do this properly, e.g. via waiting for log lines
// from the gio program.
time.Sleep(200 * time.Millisecond)
img, err := xgraphics.NewDrawable(xu, xproto.Drawable(xu.RootWin()))
if err != nil {
t.Fatal(err)
}
size := img.Bounds().Size()
wantSize := 600 // 300px at 2.0 scaling factor
if size.X != wantSize || size.Y != wantSize {
t.Fatalf("expected dimensions to be %d*%d, got %d*%d",
wantSize, wantSize, size.X, size.Y)
}
wantColor(t, img, 5, 5, 0xffff, 0x0, 0x0, 0xffff)
wantColor(t, img, 595, 595, 0xffff, 0x0, 0x0, 0xffff)
}
}