Files
gio/cmd/gogio/x11_test.go
T
Daniel Martí d31a129bf9 cmd/gogio: fix a couple of x11 e2e TODOs
First, pick a random display number between 1 and 100,000. The pool is
large enough that we don't need to think about collisions for now.

Second, wait for the X server to expose its socket for up to 1s, instead
of doing a single static sleep of 200ms. The average time we actually
need to sleep on my laptop is around 5ms, so this gives a noticeable
speed-up.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
2019-10-30 00:35:47 +01:00

177 lines
4.6 KiB
Go

// 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"
"fmt"
"io"
"io/ioutil"
"math/rand"
"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()
// 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.
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
display := fmt.Sprintf(":%d", rnd.Intn(100000)+1)
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...)
combined := &bytes.Buffer{}
cmd.Stdout = combined
cmd.Stderr = combined
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
defer cancel()
defer func() {
// Give Xserver a chance to exit gracefully, cleaning up
// after itself in /tmp. After 10ms, the deferred cancel
// above will signal an os.Kill.
cmd.Process.Signal(os.Interrupt)
time.Sleep(10 * time.Millisecond)
}()
// Wait for up to 1s (100 * 10ms) for the X server to be ready.
for i := 0; ; i++ {
time.Sleep(10 * time.Millisecond)
// This socket path isn't terribly portable, but the xgb
// library we use does the same, and we only really care
// about Linux here.
socket := fmt.Sprintf("/tmp/.X11-unix/X%s", display[1:])
if _, err := os.Stat(socket); err == nil {
break
}
if i >= 100 {
t.Fatalf("timed out waiting for %s", socket)
}
}
wg.Add(1)
go func() {
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
// Print all output and error.
io.Copy(os.Stdout, combined)
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(10 * 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)
}
}