mirror of
https://git.sr.ht/~eliasnaur/gio-cmd
synced 2026-07-01 07:35:37 +00:00
6826ef0b64
Signed-off-by: Elias Naur <mail@eliasnaur.com>
153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package main_test
|
|
|
|
import (
|
|
"context"
|
|
"image"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"golang.org/x/image/draw"
|
|
)
|
|
|
|
// Wine is tightly coupled with X11 at the moment, and we can reuse the same
|
|
// methods to automate screenshots and clicks. The main difference is how we
|
|
// build and run the app.
|
|
|
|
// The only quirk is that it seems impossible for the Wine window to take the
|
|
// entirety of the X server's dimensions, even if we try to resize it to take
|
|
// the entire display. It seems to want to leave some vertical space empty,
|
|
// presumably for window decorations or the "start" bar on Windows. To work
|
|
// around that, make the X server 50x50px bigger, and crop the screenshots back
|
|
// to the original size.
|
|
|
|
type WineTestDriver struct {
|
|
X11TestDriver
|
|
}
|
|
|
|
func (d *WineTestDriver) Start(path string) {
|
|
d.needPrograms("wine")
|
|
|
|
// First, build the app.
|
|
bin := filepath.Join(d.tempDir("gio-endtoend-windows"), "red.exe")
|
|
flags := []string{"build", "-o=" + bin}
|
|
if raceEnabled {
|
|
if runtime.GOOS != "windows" {
|
|
// cross-compilation disables CGo, which breaks -race.
|
|
d.Skipf("can't cross-compile -race for Windows; skipping")
|
|
}
|
|
flags = append(flags, "-race")
|
|
}
|
|
flags = append(flags, path)
|
|
cmd := exec.Command("go", flags...)
|
|
cmd.Env = os.Environ()
|
|
cmd.Env = append(cmd.Env, "GOOS=windows")
|
|
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)
|
|
|
|
// Add 50x50px to the display dimensions, as discussed earlier.
|
|
d.startServer(&wg, d.width+50, d.height+50)
|
|
|
|
// Then, start our program via Wine on the X server above.
|
|
{
|
|
cacheDir, err := os.UserCacheDir()
|
|
if err != nil {
|
|
d.Fatal(err)
|
|
}
|
|
// Use a wine directory separate from the default ~/.wine, so
|
|
// that the user's winecfg doesn't affect our test. This will
|
|
// default to ~/.cache/gio-e2e-wine. We use the user's cache,
|
|
// to reuse a previously set up wineprefix.
|
|
wineprefix := filepath.Join(cacheDir, "gio-e2e-wine")
|
|
|
|
// First, ensure that wineprefix is up to date with wineboot.
|
|
// Wait for this separately from the first frame, as setting up
|
|
// a new prefix might take 5s on its own.
|
|
env := []string{
|
|
"DISPLAY=" + d.display,
|
|
"WINEDEBUG=fixme-all", // hide "fixme" noise
|
|
"WINEPREFIX=" + wineprefix,
|
|
|
|
// Disable wine-gecko (Explorer) and wine-mono (.NET).
|
|
// Otherwise, if not installed, wineboot will get stuck
|
|
// with a prompt to install them on the virtual X
|
|
// display. Moreover, Gio doesn't need either, and wine
|
|
// is faster without them.
|
|
"WINEDLLOVERRIDES=mscoree,mshtml=",
|
|
}
|
|
{
|
|
start := time.Now()
|
|
cmd := exec.Command("wine", "wineboot", "-i")
|
|
cmd.Env = env
|
|
// Use a combined output pipe instead of CombinedOutput,
|
|
// so that we only wait for the child process to exit,
|
|
// and we don't need to wait for all of wine's
|
|
// grandchildren to exit and stop writing. This is
|
|
// relevant as wine leaves "wineserver" lingering for
|
|
// three seconds by default, to be reused later.
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
d.Fatal(err)
|
|
}
|
|
cmd.Stderr = cmd.Stdout
|
|
if err := cmd.Run(); err != nil {
|
|
io.Copy(os.Stderr, stdout)
|
|
d.Fatal(err)
|
|
}
|
|
d.Logf("set up WINEPREFIX in %s", time.Since(start))
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cmd := exec.CommandContext(ctx, "wine", bin)
|
|
cmd.Env = env
|
|
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()
|
|
|
|
// xdotool seems to fail at actually moving the window if we use it
|
|
// immediately after Gio is ready. Why?
|
|
// We can't tell if the windowmove operation worked until we take a
|
|
// screenshot, because the getwindowgeometry op reports the 0x0
|
|
// coordinates even if the window wasn't moved properly.
|
|
// A sleep of ~20ms seems to be enough on an idle laptop. Use 20x that.
|
|
// TODO(mvdan): revisit this, when you have a spare three hours.
|
|
time.Sleep(400 * time.Millisecond)
|
|
id := d.xdotool("search", "--sync", "--onlyvisible", "--name", "Gio")
|
|
d.xdotool("windowmove", "--sync", id, 0, 0)
|
|
}
|
|
|
|
func (d *WineTestDriver) Screenshot() image.Image {
|
|
img := d.X11TestDriver.Screenshot()
|
|
// Crop the screenshot back to the original dimensions.
|
|
cropped := image.NewRGBA(image.Rect(0, 0, d.width, d.height))
|
|
draw.Draw(cropped, cropped.Bounds(), img, image.Point{}, draw.Src)
|
|
return cropped
|
|
}
|