diff --git a/tests/Imakefile b/tests/Imakefile
new file mode 100644
index 0000000..2fb7655
--- /dev/null
+++ b/tests/Imakefile
@@ -0,0 +1,37 @@
+#include "../12to11.conf"
+
+ 12TO11ROOT = ..
+ DEPLIBS = $(DEPXLIB)
+LOCAL_LIBRARIES = $(WAYLAND_CLIENT) $(XLIB) $(PNG)
+ COMMONOBJS = test_harness.o
+ COMMONSRCS = test_harness.c
+ HEADER = test_harness.h
+ EXTRA_DEFINES := -D_GNU_SOURCE -U_BSD_SOURCE -U_SVID_SOURCE
+
+#define ScannerTarget(name) @@\
+name.h: $(12TO11ROOT)/name.xml @@\
+ $(WAYLAND_SCANNER) client-header $< $@ @@\
+ @@\
+name.c: $(12TO11ROOT)/name.xml name.h @@\
+ $(WAYLAND_SCANNER) private-code $< $@ @@\
+ @@\
+ COMMONOBJS := $(COMMONOBJS) name.o @@\
+ COMMONSRCS := $(COMMONSRCS) name.c @@\
+ HEADER := $(HEADER) name.h @@\
+
+ScannerTarget(12to11-test)
+
+ SRCS1 = $(COMMONSRCS) simple_test.c
+ OBJS1 = $(COMMONOBJS) simple_test.o
+ PROGRAMS = simple_test
+
+/* Make all objects depend on HEADER. */
+$(OBJS1): $(HEADER)
+
+/* And depend on all sources and headers. */
+depend:: $(HEADER) $(SRCS1)
+
+NormalProgramTarget(simple_test,$(OBJS1),NullParameter,$(LOCAL_LIBRARIES),NullParameter)
+DependTarget3($(SRCS1),NullParameter,NullParameter)
+
+all:: $(PROGRAMS)
diff --git a/tests/README b/tests/README
new file mode 100644
index 0000000..b236882
--- /dev/null
+++ b/tests/README
@@ -0,0 +1,6 @@
+This directory holds some work-in-progress code for testing the
+protocol translator. At present, there is only a simple smoke test of
+limited utility.
+
+Each test must be individually run on a system with an a8r8g8b8
+visual, GLOBAL_SCALE and OUTPUT_SCALE set to 1.
diff --git a/tests/basic_test_card.png b/tests/basic_test_card.png
new file mode 100644
index 0000000..027ca85
Binary files /dev/null and b/tests/basic_test_card.png differ
diff --git a/tests/blue.png b/tests/blue.png
new file mode 100644
index 0000000..c650f7e
Binary files /dev/null and b/tests/blue.png differ
diff --git a/tests/simple_test.c b/tests/simple_test.c
new file mode 100644
index 0000000..b3a732f
--- /dev/null
+++ b/tests/simple_test.c
@@ -0,0 +1,171 @@
+/* Tests for the Wayland compositor running on the X server.
+
+Copyright (C) 2022 to various contributors.
+
+This file is part of 12to11.
+
+12to11 is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+12to11 is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with 12to11. If not, see . */
+
+#include "test_harness.h"
+
+enum test_kind
+ {
+ MAP_WINDOW_KIND,
+ BASIC_TEST_CARD_IMAGE_KIND,
+ };
+
+/* The display. */
+static struct test_display *display;
+
+/* Test interfaces. */
+static struct test_interface test_interfaces[] =
+ {
+ /* No interfaces yet. */
+ };
+
+/* The test surface window. */
+static Window test_surface_window;
+
+/* The test surface and Wayland surface. */
+static struct test_surface *test_surface;
+static struct wl_surface *wayland_surface;
+
+
+
+/* Forward declarations. */
+static void submit_frame_callback (struct wl_surface *, enum test_kind);
+
+
+
+static void
+verify_single_step (enum test_kind kind)
+{
+ verify_image_data (display, test_surface_window, "simple_test.dump");
+}
+
+static void
+test_single_step (enum test_kind kind)
+{
+ struct wl_buffer *buffer;
+
+ switch (kind)
+ {
+ case MAP_WINDOW_KIND:
+ buffer = load_png_image (display, "blue.png");
+
+ if (!buffer)
+ report_test_failure ("failed to load blue.png");
+
+ wl_surface_attach (wayland_surface, buffer, 0, 0);
+ wl_surface_commit (wayland_surface);
+ wl_buffer_destroy (buffer);
+ break;
+
+ case BASIC_TEST_CARD_IMAGE_KIND:
+ buffer = load_png_image (display, "basic_test_card.png");
+
+ if (!buffer)
+ report_test_failure ("failed to load basic_test_card.png");
+
+ wl_surface_attach (wayland_surface, buffer, 0, 0);
+ submit_frame_callback (wayland_surface, kind);
+ wl_surface_commit (wayland_surface);
+ wl_buffer_destroy (buffer);
+ break;
+ }
+}
+
+
+
+static void
+handle_test_surface_mapped (void *data, struct test_surface *surface,
+ uint32_t xid, const char *display_string)
+{
+ /* Sleep for 1 second to ensure that the window is exposed and
+ redirected. */
+ sleep (1);
+
+ /* Start the test. */
+ test_surface_window = xid;
+
+ /* Run the test again. */
+ test_single_step (BASIC_TEST_CARD_IMAGE_KIND);
+}
+
+static const struct test_surface_listener test_surface_listener =
+ {
+ handle_test_surface_mapped,
+ };
+
+
+
+static void
+handle_wl_callback_done (void *data, struct wl_callback *callback,
+ uint32_t callback_data)
+{
+ enum test_kind kind;
+
+ kind = (intptr_t) data;
+ wl_callback_destroy (callback);
+ verify_single_step (kind);
+}
+
+static const struct wl_callback_listener wl_callback_listener =
+ {
+ handle_wl_callback_done,
+ };
+
+
+
+static void
+submit_frame_callback (struct wl_surface *surface, enum test_kind kind)
+{
+ struct wl_callback *callback;
+
+ callback = wl_surface_frame (surface);
+ wl_callback_set_user_data (callback, (void *) (intptr_t) kind);
+ wl_callback_add_listener (callback, &wl_callback_listener,
+ NULL);
+}
+
+static void
+run_test (void)
+{
+ if (!make_test_surface (display, &wayland_surface,
+ &test_surface))
+ report_test_failure ("failed to create test surface");
+
+ test_surface_add_listener (test_surface, &test_surface_listener,
+ NULL);
+ test_single_step (MAP_WINDOW_KIND);
+
+ while (true)
+ {
+ if (wl_display_dispatch (display->display) == -1)
+ die ("wl_display_dispatch");
+ }
+}
+
+int
+main (void)
+{
+ test_init ();
+ display = open_test_display (test_interfaces,
+ ARRAYELTS (test_interfaces));
+
+ if (!display)
+ report_test_failure ("failed to open display");
+
+ run_test ();
+}
diff --git a/tests/simple_test.dump b/tests/simple_test.dump
new file mode 100644
index 0000000..17aa3d1
Binary files /dev/null and b/tests/simple_test.dump differ
diff --git a/tests/svnignore.txt b/tests/svnignore.txt
new file mode 100644
index 0000000..ccbcac7
--- /dev/null
+++ b/tests/svnignore.txt
@@ -0,0 +1,5 @@
+*-*.h
+*-*.c
+vgcore*
+simple_test
+Makefile
diff --git a/tests/test_harness.c b/tests/test_harness.c
new file mode 100644
index 0000000..826e62c
--- /dev/null
+++ b/tests/test_harness.c
@@ -0,0 +1,753 @@
+/* Tests for the Wayland compositor running on the X server.
+
+Copyright (C) 2022 to various contributors.
+
+This file is part of 12to11.
+
+12to11 is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+12to11 is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with 12to11. If not, see . */
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "test_harness.h"
+
+#define BIG_ENDIAN_BYTE_ORDER (1 << 8)
+
+struct image_data_header
+{
+ /* Currently 1. High bit is byte order. */
+ unsigned char version;
+
+ /* The data format. Currently always 0. */
+ unsigned char format;
+
+ /* The width and height. */
+ unsigned short width, height;
+
+ /* The stride. */
+ unsigned int stride;
+};
+
+enum image_data_format
+ {
+ /* Little-endian ARGB8888. */
+ IMAGE_DATA_ARGB8888_LE,
+ /* Little-endian XRGB8888. */
+ IMAGE_DATA_XRGB8888_LE,
+ };
+
+#if PNG_LIBPNG_VER < 10500
+#define PNG_JMPBUF(ptr) ((ptr)->jmpbuf)
+# else
+#define PNG_JMPBUF(ptr) \
+ (*png_set_longjmp_fn (ptr, longjmp, sizeof (jmp_buf)))
+#endif
+
+/* Whether or not to write image data instead of verifying it. */
+static bool write_image_data_instead;
+
+static void
+handle_test_manager_display_string (void *data, struct test_manager *manager,
+ const char *display_string)
+{
+ struct test_display *display;
+
+ display = data;
+ display->x_display = XOpenDisplay (display_string);
+ display->pixmap_formats
+ = XListPixmapFormats (display->x_display,
+ &display->num_pixmap_formats);
+}
+
+static const struct test_manager_listener test_manager_listener =
+ {
+ handle_test_manager_display_string,
+ };
+
+static bool
+test_manager_check (struct test_display *display)
+{
+ return (display->x_display != NULL
+ && display->num_pixmap_formats
+ && display->pixmap_formats);
+}
+
+
+
+static void
+handle_registry_global (void *data, struct wl_registry *registry,
+ uint32_t id, const char *interface,
+ uint32_t version)
+{
+ struct test_display *display;
+ int i;
+
+ display = data;
+
+ if (!strcmp (interface, "wl_compositor") && version >= 5)
+ display->compositor
+ = wl_registry_bind (registry, id, &wl_compositor_interface,
+ 5);
+ if (!strcmp (interface, "wl_shm") && version >= 1)
+ display->shm = wl_registry_bind (registry, id, &wl_shm_interface, 1);
+ else if (!strcmp (interface, "test_manager"))
+ display->test_manager
+ = wl_registry_bind (registry, id, &test_manager_interface, 1);
+ else
+ {
+ /* Look through the user specified list of interfaces. */
+ for (i = 0; i < display->num_test_interfaces; ++i)
+ {
+ if (!strcmp (interface, display->interfaces[i].interface)
+ && display->interfaces[i].version >= version)
+ /* Bind to it. */
+ *((void **) display->interfaces[i].data)
+ = wl_registry_bind (registry, id,
+ display->interfaces[i].c_interface,
+ display->interfaces[i].version);
+ }
+ }
+}
+
+static void
+handle_registry_global_remove (void *data, struct wl_registry *registry,
+ uint32_t name)
+{
+ return;
+}
+
+static const struct wl_registry_listener registry_listener =
+ {
+ handle_registry_global,
+ handle_registry_global_remove,
+ };
+
+/* Check whether or not all required globals were found and bound
+ to. */
+
+static bool
+registry_listener_check (struct test_display *display)
+{
+ int i;
+
+ if (!display->compositor)
+ return false;
+
+ if (!display->shm)
+ return false;
+
+ if (!display->test_manager)
+ return false;
+
+ for (i = 0; i < display->num_test_interfaces; ++i)
+ {
+ if (!*((void **) display->interfaces[i].interface))
+ return false;
+ }
+
+ return true;
+}
+
+void __attribute__ ((noreturn))
+die (const char *reason)
+{
+ perror (reason);
+ exit (1);
+}
+
+struct test_display *
+open_test_display (struct test_interface *interfaces, int num_interfaces)
+{
+ struct test_display *display;
+
+ display = malloc (sizeof *display);
+
+ if (!display)
+ return NULL;
+
+ display->display = wl_display_connect (NULL);
+
+ if (!display->display)
+ goto error_1;
+
+ display->registry = wl_display_get_registry (display->display);
+
+ if (!display->registry)
+ goto error_2;
+
+ display->interfaces = interfaces;
+ display->num_test_interfaces = num_interfaces;
+ wl_registry_add_listener (display->registry, ®istry_listener,
+ display);
+ wl_display_roundtrip (display->display);
+
+ if (!registry_listener_check (display))
+ goto error_2;
+
+ /* Now establish the connection to the X display. */
+ test_manager_add_listener (display->test_manager,
+ &test_manager_listener, display);
+ wl_display_roundtrip (display->display);
+
+ if (!test_manager_check (display))
+ goto error_3;
+
+ return display;
+
+ error_3:
+ if (display->x_display)
+ XCloseDisplay (display->x_display);
+ error_2:
+ wl_display_disconnect (display->display);
+ error_1:
+ free (display);
+ return NULL;
+}
+
+int
+get_shm_file_descriptor (void)
+{
+ char name[sizeof "test_driver_buffer_XXXXXXXX"];
+ int fd;
+ uint32_t i;
+
+ i = 0;
+
+ while (i <= 0xffffffff)
+ {
+ sprintf (name, "test_driver_buffer_%"PRIu32, i);
+ fd = shm_open (name, O_RDWR | O_CREAT | O_EXCL, 0600);
+
+ if (fd >= 0)
+ {
+ shm_unlink (name);
+ return fd;
+ }
+
+ if (errno == EEXIST)
+ ++i;
+ else
+ return -1;
+ }
+
+ return -1;
+}
+
+#define IMAGE_PAD(nbytes, pad) ((((nbytes) + ((pad) - 1)) / (pad)) * ((pad) >> 3))
+
+size_t
+get_image_stride (struct test_display *display, int depth, int width)
+{
+ int bpp, scanline_pad, i;
+ size_t stride;
+
+ for (i = 0; i < display->num_pixmap_formats; ++i)
+ {
+ if (display->pixmap_formats[i].depth == depth)
+ {
+ bpp = display->pixmap_formats[i].bits_per_pixel;
+ scanline_pad = display->pixmap_formats[i].bits_per_pixel;
+ stride = IMAGE_PAD (width * bpp, scanline_pad);
+
+ return stride;
+ }
+ }
+
+ return 0;
+}
+
+struct wl_buffer *
+upload_image_data (struct test_display *display, const char *data,
+ int width, int height, int depth)
+{
+ size_t stride;
+ int fd;
+ void *mapping;
+ struct wl_shm_pool *pool;
+ struct wl_buffer *buffer;
+
+ if (depth != 32 && depth != 24)
+ return NULL;
+
+ stride = get_image_stride (display, depth, width);
+
+ if (!stride)
+ return NULL;
+
+ fd = get_shm_file_descriptor ();
+
+ if (fd < 0)
+ return NULL;
+
+ if (ftruncate (fd, stride * height) < 0)
+ return NULL;
+
+ mapping = mmap (NULL, stride * height, PROT_WRITE, MAP_SHARED,
+ fd, 0);
+
+ if (mapping == (void *) -1)
+ {
+ perror ("mmap");
+ close (fd);
+ return NULL;
+ }
+
+ memcpy (mapping, data, stride * height);
+
+ if (munmap (mapping, stride * height) < 0)
+ die ("munmap");
+
+ pool = wl_shm_create_pool (display->shm, fd, stride * height);
+
+ if (!pool)
+ {
+ close (fd);
+ return NULL;
+ }
+
+ close (fd);
+ buffer = wl_shm_pool_create_buffer (pool, 0, width, height, stride,
+ (depth == 32
+ ? WL_SHM_FORMAT_ARGB8888
+ : WL_SHM_FORMAT_XRGB8888));
+ wl_shm_pool_destroy (pool);
+
+ return buffer;
+}
+
+void __attribute__ ((noreturn, format (gnu_printf, 1, 2)))
+report_test_failure (const char *format, ...)
+{
+ va_list ap;
+
+ va_start (ap, format);
+ fputs ("failure: ", stderr);
+ vfprintf (stderr, format, ap);
+ fputs ("\n", stderr);
+ va_end (ap);
+
+ exit (1);
+}
+
+void __attribute__ ((format (gnu_printf, 1, 2)))
+test_log (const char *format, ...)
+{
+ va_list ap;
+
+ va_start (ap, format);
+ fputs ("note: ", stderr);
+ vfprintf (stderr, format, ap);
+ fputs ("\n", stderr);
+ va_end (ap);
+}
+
+bool
+make_test_surface (struct test_display *display,
+ struct wl_surface **surface_return,
+ struct test_surface **test_surface_return)
+{
+ struct wl_surface *surface;
+ struct test_surface *test_surface;
+
+ surface = wl_compositor_create_surface (display->compositor);
+
+ if (!surface)
+ return false;
+
+ test_surface
+ = test_manager_get_test_surface (display->test_manager,
+ surface);
+
+ if (!test_surface)
+ {
+ wl_surface_destroy (surface);
+ return false;
+ }
+
+ *surface_return = surface;
+ *test_surface_return = test_surface;
+ return true;
+}
+
+/* Swizzle the big-endian data in ROW_DATA to the little-endian format
+ Wayland mandates. */
+
+static void
+swizzle_png_row (unsigned char *row_data, int width)
+{
+ int i;
+ unsigned char byte_1, byte_2, byte_3, byte_4;
+
+ for (i = 0; i < width; ++i)
+ {
+ byte_1 = row_data[i * 4 + 0];
+ byte_2 = row_data[i * 4 + 1];
+ byte_3 = row_data[i * 4 + 2];
+ byte_4 = row_data[i * 4 + 3];
+
+ row_data[i * 4 + 0] = byte_3;
+ row_data[i * 4 + 1] = byte_2;
+ row_data[i * 4 + 2] = byte_1;
+ row_data[i * 4 + 3] = byte_4;
+ }
+}
+
+/* Load a PNG image into a wl_buffer. The image must be either
+ PNG_COLOR_TYPE_RGB or PNG_COLOR_TYPE_RGB_ALPHA. The image
+ background is ignored. */
+
+struct wl_buffer *
+load_png_image (struct test_display *display, const char *filename)
+{
+ FILE *file;
+ unsigned char signature[8];
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_uint_32 width, height;
+ int bit_depth, color_type, depth;
+ png_uint_32 i, rowbytes;
+ png_bytep *row_pointers;
+ unsigned char *image_data;
+ struct wl_buffer *buffer;
+
+ image_data = NULL;
+ file = fopen (filename, "r");
+
+ if (!file)
+ return NULL;
+
+ if (fread (signature, 1, 8, file) != 8)
+ goto error_1;
+
+ if (!png_check_sig (signature, 8))
+ goto error_1;
+
+ png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL,
+ NULL);
+
+ if (!png_ptr)
+ goto error_1;
+
+ info_ptr = png_create_info_struct (png_ptr);
+
+ if (!info_ptr)
+ goto error_2;
+
+ if (setjmp (PNG_JMPBUF (png_ptr)))
+ goto error_3;
+
+ png_init_io (png_ptr, file);
+ png_set_sig_bytes (png_ptr, 8);
+ png_read_info (png_ptr, info_ptr);
+ png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth,
+ &color_type, NULL, NULL, NULL);
+
+ if (color_type != PNG_COLOR_TYPE_RGB
+ && color_type != PNG_COLOR_TYPE_RGB_ALPHA)
+ goto error_3;
+
+ /* Get data as ARGB. */
+ if (color_type == PNG_COLOR_TYPE_RGB)
+ png_set_filler (png_ptr, 0, PNG_FILLER_AFTER);
+
+ /* Start reading the PNG data. Get the stride and depth. */
+ depth = (color_type == PNG_COLOR_TYPE_RGB_ALPHA
+ ? 32 : 24);
+
+ png_read_update_info (png_ptr, info_ptr);
+ rowbytes = get_image_stride (display, depth, width);
+
+ /* Allocate a buffer for the image data. */
+ image_data = malloc (rowbytes * height);
+
+ if (!image_data)
+ goto error_3;
+
+ /* Allocate the array of row pointers. */
+ row_pointers = alloca (sizeof *row_pointers * height);
+
+ for (i = 0; i < height; ++i)
+ row_pointers[i] = image_data + i * rowbytes;
+
+ /* Read the data. */
+ png_read_image (png_ptr, row_pointers);
+ png_read_end (png_ptr, NULL);
+
+ for (i = 0; i < height; ++i)
+ /* Swizzle the big-endian data. */
+ swizzle_png_row (row_pointers[i], width);
+
+ /* Now, destroy the read struct and close the file. */
+ png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
+ fclose (file);
+
+ /* Upload the image data. */
+ buffer = upload_image_data (display, (const char *) image_data,
+ width, height, depth);
+
+ /* Free the image data. */
+ free (image_data);
+
+ return buffer;
+
+ error_3:
+ if (image_data)
+ free (image_data);
+
+ /* This looks silly... */
+ if (true)
+ png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
+ else
+ error_2:
+ png_destroy_read_struct (&png_ptr, NULL, NULL);
+ error_1:
+ fclose (file);
+ return NULL;
+}
+
+static unsigned short
+bytes_per_pixel_for_format (enum image_data_format format)
+{
+ switch (format)
+ {
+ case IMAGE_DATA_ARGB8888_LE:
+ case IMAGE_DATA_XRGB8888_LE:
+ return 4;
+
+ default:
+ return 0;
+ }
+}
+
+static int
+byte_order_for_format (enum image_data_format format)
+{
+ switch (format)
+ {
+ case IMAGE_DATA_ARGB8888_LE:
+ case IMAGE_DATA_XRGB8888_LE:
+ return LSBFirst;
+
+ default:
+ return 0;
+ }
+}
+
+/* Load image data from a file. */
+
+static unsigned char *
+load_image_data (const char *filename, struct image_data_header *header)
+{
+ int fd;
+ struct iovec iov;
+ unsigned char *buffer;
+ unsigned short bpp;
+
+ fd = open (filename, O_RDONLY);
+
+ if (fd < 0)
+ return NULL;
+
+ iov.iov_base = header;
+ iov.iov_len = sizeof *header;
+
+ if (readv (fd, &iov, 1) != iov.iov_len)
+ goto error_1;
+
+#ifdef __BIG_ENDIAN__
+ if (!(header->version & BIG_ENDIAN_BYTE_ORDER))
+ goto error_1;
+#endif
+
+ if ((header->version & ~BIG_ENDIAN_BYTE_ORDER) != 1)
+ goto error_1;
+
+ bpp = bytes_per_pixel_for_format (header->format);
+
+ if (!bpp || header->stride < header->height * bpp)
+ goto error_1;
+
+ buffer = malloc (header->stride * header->height);
+
+ if (!buffer)
+ goto error_1;
+
+ iov.iov_base = buffer;
+ iov.iov_len = header->stride * header->height;
+
+ if (readv (fd, &iov, 1) != iov.iov_len)
+ goto error_2;
+
+ close (fd);
+ return buffer;
+
+ error_2:
+ free (buffer);
+ error_1:
+ close (fd);
+ return NULL;
+}
+
+static void
+compare_single_row (unsigned char *data, int row_no,
+ struct image_data_header *header, XImage *image)
+{
+ char *xdata;
+ unsigned short bytes_per_pixel;
+
+ bytes_per_pixel = bytes_per_pixel_for_format (header->format);
+ data = data + header->stride * row_no;
+ xdata = image->data + image->bytes_per_line * row_no;
+
+ if (memcmp (data, xdata, bytes_per_pixel * header->width))
+ report_test_failure ("row %d differs", row_no);
+}
+
+static void
+write_image_data (struct test_display *display, Window window,
+ const char *filename)
+{
+ struct image_data_header header;
+ XImage *image;
+ struct iovec iovecs[2];
+ int fd;
+ XWindowAttributes attrs;
+
+ test_log ("writing contents of drawable to reference %s", filename);
+
+ XGetWindowAttributes (display->x_display, window, &attrs);
+ image = XGetImage (display->x_display, window, 0, 0, attrs.width,
+ attrs.height, ~0, ZPixmap);
+
+ if (!image)
+ report_test_failure ("failed to load from drawable 0x%lx", window);
+
+ memset (&header, 0, sizeof header);
+ header.version = 1;
+#ifdef __BIG_ENDIAN__
+ header.version |= BIG_ENDIAN_BYTE_ORDER;
+#endif
+
+ if ((image->depth != 24 && image->depth != 32)
+ || image->bits_per_pixel != 32)
+ report_test_failure ("don't know how to save image of depth %d (bpp %d)",
+ image->depth, image->bits_per_pixel);
+
+ if (image->byte_order != LSBFirst)
+ report_test_failure ("don't know how to save big-endian image");
+
+ /* TODO: determine the image format based on the visual of the
+ window. */
+ header.format = (image->depth == 24
+ ? IMAGE_DATA_XRGB8888_LE
+ : IMAGE_DATA_ARGB8888_LE);
+ header.width = image->width;
+ header.height = image->height;
+ header.stride = image->bytes_per_line;
+
+ /* Open the output file and write the data. */
+ fd = open (filename, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
+
+ if (fd < 0)
+ report_test_failure ("failed to open output file %s", filename);
+
+ iovecs[0].iov_base = &header;
+ iovecs[0].iov_len = sizeof header;
+ iovecs[1].iov_base = image->data;
+ iovecs[1].iov_len = image->bytes_per_line * image->height;
+
+ if (writev (fd, iovecs, 2) != iovecs[0].iov_len + iovecs[1].iov_len)
+ report_test_failure ("failed to write to output file %s", filename);
+
+ close (fd);
+ XDestroyImage (image);
+
+ test_log ("image data written to %s", filename);
+}
+
+/* Verify the image data against a file. */
+
+void
+verify_image_data (struct test_display *display, Window window,
+ const char *filename)
+{
+ XImage *image;
+ XWindowAttributes attrs;
+ unsigned char *data;
+ struct image_data_header header;
+ unsigned short data_bpp, i;
+ int byte_order;
+
+ if (write_image_data_instead)
+ write_image_data (display, window, filename);
+
+ data = load_image_data (filename, &header);
+
+ if (!data)
+ report_test_failure ("failed to load input file: %s", filename);
+
+ XGetWindowAttributes (display->x_display, window, &attrs);
+ image = XGetImage (display->x_display, window, 0, 0, attrs.width,
+ attrs.height, ~0, ZPixmap);
+
+ if (!image)
+ report_test_failure ("failed to load from drawable 0x%lx", window);
+
+ /* Check if the image data is compatible. */
+ data_bpp = bytes_per_pixel_for_format (header.format);
+ byte_order = byte_order_for_format (header.format);
+
+ if (byte_order != image->byte_order)
+ report_test_failure ("image data has wrong byte order");
+
+ if (data_bpp * 8 != image->bits_per_pixel)
+ report_test_failure ("image data has %d bits per pixel, but reference"
+ " data has %hd * 8", image->bits_per_pixel, data_bpp);
+
+ if (image->width != header.width
+ || image->height != header.height)
+ report_test_failure ("image data is %d by %d, but reference data is"
+ " %hd by %hd", image->width, image->height,
+ header.width, header.height);
+
+ /* Now compare the actual image data. Make sure this is done with
+ the same visual as the reference data was saved in! */
+
+ for (i = 0; i < header.height; ++i)
+ compare_single_row (data, i, &header, image);
+
+ /* Destroy the images. */
+ free (data);
+ XDestroyImage (image);
+
+ test_log ("verified image data");
+}
+
+void
+test_init (void)
+{
+ write_image_data_instead
+ = getenv ("TEST_WRITE_REFERENCE") != NULL;
+}
diff --git a/tests/test_harness.h b/tests/test_harness.h
new file mode 100644
index 0000000..0e97f40
--- /dev/null
+++ b/tests/test_harness.h
@@ -0,0 +1,91 @@
+/* Tests for the Wayland compositor running on the X server.
+
+Copyright (C) 2022 to various contributors.
+
+This file is part of 12to11.
+
+12to11 is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+12to11 is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with 12to11. If not, see . */
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+#include "12to11-test.h"
+
+struct test_display
+{
+ /* The wayland display. */
+ struct wl_display *display;
+
+ /* The X display. */
+ Display *x_display;
+
+ /* List of pixmap formats. */
+ XPixmapFormatValues *pixmap_formats;
+
+ /* Number of pixmap formats. */
+ int num_pixmap_formats;
+
+ /* The registry and various Wayland interfaces. */
+ struct wl_registry *registry;
+ struct wl_compositor *compositor;
+ struct wl_shm *shm;
+ struct test_manager *test_manager;
+
+ /* Test interfaces. */
+ struct test_interface *interfaces;
+
+ /* The number of such interfaces. */
+ int num_test_interfaces;
+};
+
+struct test_interface
+{
+ /* The name of the interface. */
+ const char *interface;
+
+ /* Pointer to the interface data. */
+ void *data;
+
+ /* Pointer to the interface. */
+ struct wl_interface *c_interface;
+
+ /* The wanted version. */
+ uint32_t version;
+};
+
+extern void die (const char *);
+extern struct test_display *open_test_display (struct test_interface *, int);
+extern int get_shm_file_descriptor (void);
+extern size_t get_image_stride (struct test_display *, int, int);
+extern struct wl_buffer *upload_image_data (struct test_display *,
+ const char *, int, int,
+ int);
+extern void report_test_failure (const char *, ...)
+ __attribute__ ((noreturn, format (gnu_printf, 1, 2)));
+extern void test_log (const char *, ...)
+ __attribute__ ((format (gnu_printf, 1, 2)));
+
+extern bool make_test_surface (struct test_display *, struct wl_surface **,
+ struct test_surface **);
+extern struct wl_buffer *load_png_image (struct test_display *, const char *);
+extern void verify_image_data (struct test_display *, Window, const char *);
+extern void test_init (void);
+
+#define ARRAYELTS(arr) (sizeof (arr) / sizeof (arr)[0])