mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-02 16:06:19 +00:00
6fedfaf3af
Now that we use tip due to breaking changes in Go's JS APIs, let's replace our hacky cleanup list code with 1.14's upcoming testing.T.Cleanup. Adding more deliberate uses of tip would ususally be best avoided, but these will only affect developers working on gio's tests, not regular users of gio. While at it, remove some debug t.Logf calls I forgot to remove. Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
229 lines
5.2 KiB
Go
229 lines
5.2 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package main_test
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
type WaylandTestDriver struct {
|
|
t *testing.T
|
|
|
|
frameNotifs chan bool
|
|
|
|
runtimeDir string
|
|
socket string
|
|
display string
|
|
}
|
|
|
|
// No bars or anything fancy. Just a white background with our dimensions.
|
|
var tmplSwayConfig = template.Must(template.New("").Parse(`
|
|
output * bg #FFFFFF solid_color
|
|
output * mode {{.Width}}x{{.Height}}
|
|
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_
|
|
|
|
// 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
|
|
env := os.Environ()
|
|
if *headless {
|
|
env = append(env, "WLR_BACKENDS=headless")
|
|
}
|
|
|
|
for _, prog := range []string{
|
|
"sway", // to run a wayland compositor
|
|
"grim", // to take screenshots
|
|
"swaymsg", // to send input
|
|
} {
|
|
if _, err := exec.LookPath(prog); err != nil {
|
|
d.t.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.t.Cleanup(func() { os.RemoveAll(dir) })
|
|
|
|
bin := filepath.Join(dir, "red")
|
|
flags := []string{"build", "-tags", "nox11", "-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.t.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)
|
|
}
|
|
defer f.Close()
|
|
if err := tmplSwayConfig.Execute(f, struct{ Width, Height int }{
|
|
width, height,
|
|
}); err != nil {
|
|
d.t.Fatal(err)
|
|
}
|
|
|
|
d.socket = filepath.Join(dir, "socket")
|
|
env = append(env, "SWAYSOCK="+d.socket)
|
|
d.runtimeDir = dir
|
|
env = append(env, "XDG_RUNTIME_DIR="+d.runtimeDir)
|
|
|
|
var wg sync.WaitGroup
|
|
d.t.Cleanup(wg.Wait)
|
|
|
|
// First, start sway.
|
|
{
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cmd := exec.CommandContext(ctx, "sway", "--config", conf, "--verbose")
|
|
cmd.Env = env
|
|
stderr, err := cmd.StderrPipe()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
d.t.Fatal(err)
|
|
}
|
|
d.t.Cleanup(cancel)
|
|
d.t.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 sway to be ready. We probably don't need a deadline
|
|
// here.
|
|
br := bufio.NewReader(stderr)
|
|
for {
|
|
line, err := br.ReadString('\n')
|
|
if err != nil {
|
|
d.t.Fatal(err)
|
|
}
|
|
if m := rxSwayReady.FindStringSubmatch(line); m != nil {
|
|
d.display = m[1]
|
|
break
|
|
}
|
|
}
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
if err := cmd.Wait(); err != nil && ctx.Err() == nil && !strings.Contains(err.Error(), "interrupt") {
|
|
// 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)
|
|
}
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
// Then, start our program on the sway compositor above.
|
|
{
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cmd := exec.CommandContext(ctx, bin)
|
|
cmd.Env = []string{"XDG_RUNTIME_DIR=" + d.runtimeDir, "WAYLAND_DISPLAY=" + d.display}
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
d.t.Fatal(err)
|
|
}
|
|
stderr := &bytes.Buffer{}
|
|
cmd.Stderr = stderr
|
|
if err := cmd.Start(); err != nil {
|
|
d.t.Fatal(err)
|
|
}
|
|
d.t.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)
|
|
}
|
|
wg.Done()
|
|
}()
|
|
go func() {
|
|
scanner := bufio.NewScanner(stdout)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if line == "frame ready" {
|
|
d.frameNotifs <- true
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Wait for the gio app to render.
|
|
<-d.frameNotifs
|
|
}
|
|
|
|
func (d *WaylandTestDriver) Screenshot() image.Image {
|
|
cmd := exec.Command("grim", "/dev/stdout")
|
|
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)
|
|
}
|
|
img, err := png.Decode(bytes.NewReader(out))
|
|
if err != nil {
|
|
d.t.Fatal(err)
|
|
}
|
|
return img
|
|
}
|
|
|
|
func (d *WaylandTestDriver) swaymsg(args ...interface{}) {
|
|
strs := []string{
|
|
"--socket", d.socket,
|
|
}
|
|
for _, arg := range args {
|
|
strs = append(strs, fmt.Sprint(arg))
|
|
}
|
|
cmd := exec.Command("swaymsg", strs...)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
d.t.Errorf("%s", out)
|
|
d.t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (d *WaylandTestDriver) Click(x, y int) {
|
|
d.swaymsg("-t", "get_seats")
|
|
d.swaymsg("seat", "-", "cursor", "set", x, y)
|
|
d.swaymsg("seat", "-", "cursor", "press", "button1")
|
|
d.swaymsg("seat", "-", "cursor", "release", "button1")
|
|
|
|
// Wait for the gio app to render after this click.
|
|
<-d.frameNotifs
|
|
}
|