mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
023e022255
Fix a long-standing TODO: instead of each sub-test handling its own output separately, just make each expose its output via an io.Reader. Then, the shared driverBase code can tell if any of the lines contain the magic "gio frame ready" string. Reduces the amount of code a bit, but most importantly, it keeps the "is a frame ready?" logic in a single place. In the future, this also enables us to do more with all the e2e test app output consistently. For example, we might want to add a -debug flag to always log output lines as they happen. Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
171 lines
4.0 KiB
Go
171 lines
4.0 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package main_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"io"
|
|
"math/rand"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type X11TestDriver struct {
|
|
driverBase
|
|
|
|
display string
|
|
}
|
|
|
|
func (d *X11TestDriver) Start(path string) {
|
|
// First, build the app.
|
|
bin := filepath.Join(d.tempDir("gio-endtoend-x11"), "red")
|
|
flags := []string{"build", "-tags", "nowayland", "-o=" + bin}
|
|
if raceEnabled {
|
|
flags = append(flags, "-race")
|
|
}
|
|
flags = append(flags, path)
|
|
cmd := exec.Command("go", flags...)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
d.Fatalf("could not build app: %s:\n%s", err, out)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
d.Cleanup(wg.Wait)
|
|
|
|
d.startServer(wg, d.width, d.height)
|
|
|
|
// Then, start our program on the X server above.
|
|
{
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cmd := exec.CommandContext(ctx, bin)
|
|
cmd.Env = []string{"DISPLAY=" + d.display}
|
|
output, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
d.Fatal(err)
|
|
}
|
|
cmd.Stderr = cmd.Stdout
|
|
d.output = output
|
|
if err := cmd.Start(); err != nil {
|
|
d.Fatal(err)
|
|
}
|
|
d.Cleanup(cancel)
|
|
wg.Add(1)
|
|
go func() {
|
|
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
|
|
d.Error(err)
|
|
}
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
// Wait for the gio app to render.
|
|
d.waitForFrame()
|
|
}
|
|
|
|
func (d *X11TestDriver) startServer(wg sync.WaitGroup, 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.
|
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
d.display = fmt.Sprintf(":%d", rnd.Intn(100000)+1)
|
|
|
|
var xprog string
|
|
xflags := []string{
|
|
"-wr", // we want a white background; the default is black
|
|
}
|
|
if *headless {
|
|
xprog = "Xvfb" // virtual X server
|
|
xflags = append(xflags, "-screen", "0", fmt.Sprintf("%dx%dx24", width, height))
|
|
} else {
|
|
xprog = "Xephyr" // nested X server as a window
|
|
xflags = append(xflags, "-screen", fmt.Sprintf("%dx%d", width, height))
|
|
}
|
|
xflags = append(xflags, d.display)
|
|
|
|
d.needPrograms(
|
|
xprog, // to run the X server
|
|
"scrot", // to take screenshots
|
|
"xdotool", // to send input
|
|
)
|
|
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 {
|
|
d.Fatal(err)
|
|
}
|
|
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.
|
|
cmd.Process.Signal(os.Interrupt)
|
|
time.Sleep(10 * time.Millisecond)
|
|
})
|
|
|
|
// Wait for the X server to be ready. The socket path isn't
|
|
// terribly portable, but that's okay for now.
|
|
withRetries(d.T, time.Second, func() error {
|
|
socket := fmt.Sprintf("/tmp/.X11-unix/X%s", d.display[1:])
|
|
_, err := os.Stat(socket)
|
|
return err
|
|
})
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
|
|
// Print all output and error.
|
|
io.Copy(os.Stdout, combined)
|
|
d.Error(err)
|
|
}
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
func (d *X11TestDriver) Screenshot() image.Image {
|
|
cmd := exec.Command("scrot", "--silent", "--overwrite", "/dev/stdout")
|
|
cmd.Env = []string{"DISPLAY=" + d.display}
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
d.Errorf("%s", out)
|
|
d.Fatal(err)
|
|
}
|
|
img, err := png.Decode(bytes.NewReader(out))
|
|
if err != nil {
|
|
d.Fatal(err)
|
|
}
|
|
return img
|
|
}
|
|
|
|
func (d *X11TestDriver) xdotool(args ...interface{}) string {
|
|
d.Helper()
|
|
strs := make([]string, len(args))
|
|
for i, arg := range args {
|
|
strs[i] = fmt.Sprint(arg)
|
|
}
|
|
cmd := exec.Command("xdotool", strs...)
|
|
cmd.Env = []string{"DISPLAY=" + d.display}
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
d.Errorf("%s", out)
|
|
d.Fatal(err)
|
|
}
|
|
return string(bytes.TrimSpace(out))
|
|
}
|
|
|
|
func (d *X11TestDriver) Click(x, y int) {
|
|
d.xdotool("mousemove", "--sync", x, y)
|
|
d.xdotool("click", "1")
|
|
|
|
// Wait for the gio app to render after this click.
|
|
d.waitForFrame()
|
|
}
|