/* 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;
}
void __attribute__ ((noreturn))
test_complete (void)
{
test_log ("test ran successfully");
exit (0);
}