// SPDX-License-Identifier: Unlicense OR MIT package main_test import ( "flag" "image" "image/color" "testing" ) var raceEnabled = false var headless = flag.Bool("headless", true, "run end-to-end tests in headless mode") // TestDriver is implemented by each of the platforms we can run end-to-end // tests on. None of its methods return any errors, as the errors are directly // reported to testing.T via methods like Fatal. type TestDriver interface { // Start provides the test driver with a testing.T, as well as the path // to the Gio app to use for the test. The app will be run with the // given width and height, and the platform's background should be // white. // // When the function returns, the gio app must be ready to use on the // platform, with its initial frame fully drawn. // // The returned cleanup funcs must be run in reverse order, to mimic // deferred funcs. // TODO(mvdan): replace with testing.T.Cleanup once Go 1.14 is out. Start(t *testing.T, path string, width, height int) (cleanups []func()) // Screenshot takes a screenshot of the Gio app on the platform. Screenshot() image.Image // Click performs a pointer click at the specified coordinates, // including both press and release. It returns when the next frame is // fully drawn. Click(x, y int) } func runEndToEndTest(t *testing.T, driver TestDriver) { size := image.Point{X: 800, Y: 600} cleanups := driver.Start(t, "testdata/red.go", size.X, size.Y) for _, cleanup := range cleanups { defer cleanup() } // The colors are split in four rectangular sections. Check the corners // of each of the sections. We check the corners left to right, top to // bottom, like when reading left-to-right text. wantColors := func(topLeft, topRight, botLeft, botRight color.RGBA) { t.Helper() img := driver.Screenshot() size_ := img.Bounds().Size() if size_ != size { if !*headless { // Some non-headless drivers, like Sway, may get // their window resized by the host window manager. // Run the rest of the test with the new size. size = size_ } else { t.Fatalf("expected dimensions to be %v, got %v", size, size_) } } { minX, minY := 5, 5 maxX, maxY := (size.X/2)-5, (size.Y/2)-5 wantColor(t, img, minX, minY, topLeft) wantColor(t, img, maxX, minY, topLeft) wantColor(t, img, minX, maxY, topLeft) wantColor(t, img, maxX, maxY, topLeft) } { minX, minY := (size.X/2)+5, 5 maxX, maxY := size.X-5, (size.Y/2)-5 wantColor(t, img, minX, minY, topRight) wantColor(t, img, maxX, minY, topRight) wantColor(t, img, minX, maxY, topRight) wantColor(t, img, maxX, maxY, topRight) } { minX, minY := 5, (size.Y/2)+5 maxX, maxY := (size.X/2)-5, size.Y-5 wantColor(t, img, minX, minY, botLeft) wantColor(t, img, maxX, minY, botLeft) wantColor(t, img, minX, maxY, botLeft) wantColor(t, img, maxX, maxY, botLeft) } { minX, minY := (size.X/2)+5, (size.Y/2)+5 maxX, maxY := size.X-5, size.Y-5 wantColor(t, img, minX, minY, botRight) wantColor(t, img, maxX, minY, botRight) wantColor(t, img, minX, maxY, botRight) wantColor(t, img, maxX, maxY, botRight) } } beef := color.RGBA{R: 0xde, G: 0xad, B: 0xbe} white := color.RGBA{R: 0xff, G: 0xff, B: 0xff} black := color.RGBA{R: 0x00, G: 0x00, B: 0x00} gray := color.RGBA{R: 0xbb, G: 0xbb, B: 0xbb} red := color.RGBA{R: 0xff, G: 0x00, B: 0x00} // These are the four colors at the beginning. wantColors(beef, white, black, gray) // Click the first and last sections to turn them red. // TODO(mvdan): implement this properly in the Wayland driver; swaymsg // almost works to automate clicks, but the button presses end up in the // wrong coordinates. if _, ok := driver.(*WaylandTestDriver); ok { return } driver.Click(1*(size.X/4), 1*(size.Y/4)) driver.Click(3*(size.X/4), 3*(size.Y/4)) wantColors(red, white, black, red) } func wantColor(t *testing.T, img image.Image, x, y int, want color.Color) { t.Helper() r, g, b, _ := want.RGBA() got := img.At(x, y) r_, g_, b_, _ := got.RGBA() if r_ != r || g_ != g || b_ != b { t.Errorf("got 0x%04x%04x%04x at (%d,%d), want 0x%04x%04x%04x", r_, g_, b_, x, y, r, g, b) } }