mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ type expval struct {
|
||||
}
|
||||
|
||||
func TestAppID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []expval{
|
||||
{"example", "localhost.example"},
|
||||
{"example.com", "com.example"},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user