/* 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->width * 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); }