diff options
Diffstat (limited to 'test/cairo-test-trace.c')
-rw-r--r-- | test/cairo-test-trace.c | 1780 |
1 files changed, 0 insertions, 1780 deletions
diff --git a/test/cairo-test-trace.c b/test/cairo-test-trace.c deleted file mode 100644 index 5badc4377..000000000 --- a/test/cairo-test-trace.c +++ /dev/null @@ -1,1780 +0,0 @@ -/* - * Copyright © 2009 Chris Wilson - * - * Permission to use, copy, modify, distribute, and sell this software - * and its documentation for any purpose is hereby granted without - * fee, provided that the above copyright notice appear in all copies - * and that both that copyright notice and this permission notice - * appear in supporting documentation, and that the name of - * the authors not be used in advertising or publicity pertaining to - * distribution of the software without specific, written prior - * permission. The authors make no representations about the - * suitability of this software for any purpose. It is provided "as - * is" without express or implied warranty. - * - * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, - * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER - * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR - * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * Authors: Chris Wilson <chris@chris-wilson.co.uk> - */ - -/* - * The basic idea is that we feed the trace to multiple backends in parallel - * and compare the output at the end of each context (based on the premise - * that contexts demarcate expose events, or their logical equivalents) with - * that of the image[1] backend. Each backend is executed in a separate - * process, for robustness and to isolate the global cairo state, with the - * image data residing in shared memory and synchronising over a socket. - * - * [1] Should be reference implementation, currently the image backend is - * considered to be the reference for all other backends. - */ - -/* XXX Can't directly compare fills using spans versus trapezoidation, - * i.e. xlib vs image. Gah, kinda renders this whole scheme moot. - * How about reference platforms? - * E.g. accelerated xlib driver vs Xvfb? - * - * boilerplate->create_reference_surface()? - * boilerplate->reference->create_surface()? - * So for each backend spawn two processes, a reference and xlib - * (obviously minimising the number of reference processes when possible) - */ - -/* - * XXX Handle show-page as well as cairo_destroy()? Though arguably that is - * only relevant for paginated backends which is currently outside the - * scope of this test. - */ - -#define _GNU_SOURCE 1 /* getline() */ - -#include "cairo-test.h" -#include "buffer-diff.h" - -#include "cairo-boilerplate-getopt.h" -#include <cairo-script-interpreter.h> -#include "cairo-missing.h" - -#if CAIRO_HAS_SCRIPT_SURFACE -#include <cairo-script.h> -#endif - -/* For basename */ -#ifdef HAVE_LIBGEN_H -#include <libgen.h> -#endif -#include <ctype.h> /* isspace() */ - -#include <sys/types.h> -#include <dirent.h> -#include <fcntl.h> -#include <signal.h> -#include <sys/wait.h> -#include <sys/stat.h> -#include <sys/socket.h> -#include <sys/mman.h> -#include <sys/poll.h> -#include <sys/un.h> -#include <errno.h> -#include <assert.h> -#if CAIRO_HAS_REAL_PTHREAD -#include <pthread.h> -#endif - -#if HAVE_FCFINI -#include <fontconfig/fontconfig.h> -#endif - -#ifndef MAP_NORESERVE -#define MAP_NORESERVE 0 -#endif - -#define DEBUG 0 - -#define ignore_image_differences 0 /* XXX make me a cmdline option! */ -#define write_results 1 -#define write_traces 1 - -#define DATA_SIZE (256 << 20) -#define SHM_PATH_XXX "/.shmem-cairo-trace" - -typedef struct _test_trace { - /* Options from command-line */ - cairo_bool_t list_only; - char **names; - unsigned int num_names; - char **exclude_names; - unsigned int num_exclude_names; - - /* Stuff used internally */ - const cairo_boilerplate_target_t **targets; - int num_targets; -} test_trace_t; - -typedef struct _test_runner { - const char *name; - cairo_surface_t *surface; - void *closure; - uint8_t *base; - const char *trace; - pid_t pid; - int sk; - cairo_bool_t is_recording; - - cairo_script_interpreter_t *csi; - struct context_closure { - struct context_closure *next; - unsigned long id; - unsigned long start_line; - unsigned long end_line; - cairo_t *context; - cairo_surface_t *surface; - } *contexts; - - unsigned long context_id; -} test_runner_t; - -struct slave { - pid_t pid; - int fd; - unsigned long image_serial; - unsigned long image_ready; - unsigned long start_line; - unsigned long end_line; - cairo_surface_t *image; - long width, height; - cairo_surface_t *difference; - buffer_diff_result_t result; - const cairo_boilerplate_target_t *target; - const struct slave *reference; - cairo_bool_t is_recording; -}; - -struct request_image { - unsigned long id; - unsigned long start_line; - unsigned long end_line; - cairo_format_t format; - long width; - long height; - long stride; -}; - -struct surface_tag { - long width, height; -}; -static const cairo_user_data_key_t surface_tag; - -#define TARGET_NAME(T) ((T) ? (T)->name : "recording") - -#if CAIRO_HAS_REAL_PTHREAD -#define tr_die(t) t->is_recording ? pthread_exit(NULL) : exit(1) -#else -#define tr_die(t) exit(1) -#endif - -static cairo_bool_t -writen (int fd, const void *ptr, int len) -{ -#if 0 - const uint8_t *data = ptr; - while (len) { - int ret = write (fd, data, len); - if (ret < 0) { - switch (errno) { - case EAGAIN: - case EINTR: - continue; - default: - return FALSE; - } - } else if (ret == 0) { - return FALSE; - } else { - data += ret; - len -= ret; - } - } - return TRUE; -#else - int ret = send (fd, ptr, len, 0); - return ret == len; -#endif -} - -static cairo_bool_t -readn (int fd, void *ptr, int len) -{ -#if 0 - uint8_t *data = ptr; - while (len) { - int ret = read (fd, data, len); - if (ret < 0) { - switch (errno) { - case EAGAIN: - case EINTR: - continue; - default: - return FALSE; - } - } else if (ret == 0) { - return FALSE; - } else { - data += ret; - len -= ret; - } - } - return TRUE; -#else - int ret = recv (fd, ptr, len, MSG_WAITALL); - return ret == len; -#endif -} - -static cairo_format_t -format_for_content (cairo_content_t content) -{ - switch (content) { - case CAIRO_CONTENT_ALPHA: - return CAIRO_FORMAT_A8; - case CAIRO_CONTENT_COLOR: - return CAIRO_FORMAT_RGB24; - default: - case CAIRO_CONTENT_COLOR_ALPHA: - return CAIRO_FORMAT_ARGB32; - } -} - -static void -send_recording_surface (test_runner_t *tr, - int width, int height, - struct context_closure *closure) -{ -#if CAIRO_HAS_REAL_PTHREAD - const struct request_image rq = { - closure->id, - closure->start_line, - closure->end_line, - -1, - width, height, - (long) closure->surface, - }; - unsigned long offset; - unsigned long serial; - - if (DEBUG > 1) { - printf ("send-recording-surface: %lu [%lu, %lu]\n", - closure->id, - closure->start_line, - closure->end_line); - } - writen (tr->sk, &rq, sizeof (rq)); - readn (tr->sk, &offset, sizeof (offset)); - - /* signal completion */ - writen (tr->sk, &closure->id, sizeof (closure->id)); - - /* wait for image check */ - serial = 0; - readn (tr->sk, &serial, sizeof (serial)); - if (DEBUG > 1) { - printf ("send-recording-surface: serial: %lu\n", serial); - } - if (serial != closure->id) - pthread_exit (NULL); -#else - exit (1); -#endif -} - -static void * -request_image (test_runner_t *tr, - struct context_closure *closure, - cairo_format_t format, - int width, int height, int stride) -{ - const struct request_image rq = { - closure->id, - closure->start_line, - closure->end_line, - format, width, height, stride - }; - unsigned long offset = -1; - - assert (format != (cairo_format_t) -1); - - writen (tr->sk, &rq, sizeof (rq)); - readn (tr->sk, &offset, sizeof (offset)); - if (offset == (unsigned long) -1) - return NULL; - - return tr->base + offset; -} - -static void -send_surface (test_runner_t *tr, - struct context_closure *closure) -{ - cairo_surface_t *source = closure->surface; - cairo_surface_t *image; - cairo_format_t format = (cairo_format_t) -1; - cairo_t *cr; - int width, height, stride; - void *data; - unsigned long serial; - - if (DEBUG > 1) { - printf ("send-surface: '%s', is-recording? %d\n", - tr->name, tr->is_recording); - } - - if (cairo_surface_get_type (source) == CAIRO_SURFACE_TYPE_IMAGE) { - width = cairo_image_surface_get_width (source); - height = cairo_image_surface_get_height (source); - format = cairo_image_surface_get_format (source); - } else { - struct surface_tag *tag; - - tag = cairo_surface_get_user_data (source, &surface_tag); - if (tag != NULL) { - width = tag->width; - height = tag->height; - } else { - double x0, x1, y0, y1; - - /* presumably created using cairo_surface_create_similar() */ - cr = cairo_create (source); - cairo_clip_extents (cr, &x0, &y0, &x1, &y1); - cairo_destroy (cr); - - tag = xmalloc (sizeof (*tag)); - width = tag->width = x1 - x0; - height = tag->height = y1 - y0; - - if (cairo_surface_set_user_data (source, &surface_tag, tag, free)) - tr_die (tr); - } - } - - if (tr->is_recording) { - send_recording_surface (tr, width, height, closure); - return; - } - - if (format == (cairo_format_t) -1) - format = format_for_content (cairo_surface_get_content (source)); - - stride = cairo_format_stride_for_width (format, width); - - data = request_image (tr, closure, format, width, height, stride); - if (data == NULL) - tr_die (tr); - - image = cairo_image_surface_create_for_data (data, - format, - width, height, - stride); - cr = cairo_create (image); - cairo_surface_destroy (image); - - cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_surface (cr, source, 0, 0); - cairo_paint (cr); - cairo_destroy (cr); - - /* signal completion */ - writen (tr->sk, &closure->id, sizeof (closure->id)); - - /* wait for image check */ - serial = 0; - readn (tr->sk, &serial, sizeof (serial)); - if (serial != closure->id) - tr_die (tr); -} - -static cairo_surface_t * -_surface_create (void *closure, - cairo_content_t content, - double width, double height, - long uid) -{ - test_runner_t *tr = closure; - cairo_surface_t *surface; - - surface = cairo_surface_create_similar (tr->surface, - content, width, height); - if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE) { - struct surface_tag *tag; - - tag = xmalloc (sizeof (*tag)); - tag->width = width; - tag->height = height; - if (cairo_surface_set_user_data (surface, &surface_tag, tag, free)) - tr_die (tr); - } - - return surface; -} - -static cairo_t * -_context_create (void *closure, cairo_surface_t *surface) -{ - test_runner_t *tr = closure; - struct context_closure *l; - - if (DEBUG) { - fprintf (stderr, "%s: starting context %lu on line %d\n", - tr->name ? tr->name : "recording" , - tr->context_id + 1, - cairo_script_interpreter_get_line_number (tr->csi)); - } - - l = xmalloc (sizeof (*l)); - l->next = tr->contexts; - l->start_line = cairo_script_interpreter_get_line_number (tr->csi); - l->end_line = l->start_line; - l->context = cairo_create (surface); - l->surface = cairo_surface_reference (surface); - l->id = ++tr->context_id; - if (l->id == 0) - l->id = ++tr->context_id; - tr->contexts = l; - - return l->context; -} - -static void -_context_destroy (void *closure, void *ptr) -{ - test_runner_t *tr = closure; - struct context_closure *l, **prev = &tr->contexts; - - while ((l = *prev) != NULL) { - if (l->context == ptr) { - if (DEBUG) { - fprintf (stderr, "%s: context %lu complete on line %d\n", - tr->name ? tr->name : "recording" , - tr->context_id, - cairo_script_interpreter_get_line_number (tr->csi)); - } - l->end_line = - cairo_script_interpreter_get_line_number (tr->csi); - if (cairo_surface_status (l->surface) == CAIRO_STATUS_SUCCESS) { - send_surface (tr, l); - } else { - fprintf (stderr, "%s: error during replay, line %lu: %s!\n", - tr->name, - l->end_line, - cairo_status_to_string (cairo_surface_status (l->surface))); - tr_die (tr); - } - - cairo_surface_destroy (l->surface); - *prev = l->next; - free (l); - return; - } - prev = &l->next; - } -} - -static void -execute (test_runner_t *tr) -{ - const cairo_script_interpreter_hooks_t hooks = { - .closure = tr, - .surface_create = _surface_create, - .context_create = _context_create, - .context_destroy = _context_destroy, - }; - pid_t ack; - - tr->csi = cairo_script_interpreter_create (); - cairo_script_interpreter_install_hooks (tr->csi, &hooks); - - ack = -1; - readn (tr->sk, &ack, sizeof (ack)); - if (ack != tr->pid) - tr_die (tr); - - cairo_script_interpreter_run (tr->csi, tr->trace); - - cairo_script_interpreter_finish (tr->csi); - if (cairo_script_interpreter_destroy (tr->csi)) - tr_die (tr); -} - -static int -spawn_socket (const char *socket_path, pid_t pid) -{ - struct sockaddr_un addr; - int sk; - - sk = socket (PF_UNIX, SOCK_STREAM, 0); - if (sk == -1) - return -1; - - memset (&addr, 0, sizeof (addr)); - addr.sun_family = AF_UNIX; - strcpy (addr.sun_path, socket_path); - - if (connect (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) - return -1; - - if (! writen (sk, &pid, sizeof (pid))) - return -1; - - return sk; -} - -static void * -spawn_shm (const char *shm_path) -{ - void *base; - int fd; - - fd = shm_open (shm_path, O_RDWR, 0); - if (fd == -1) - return MAP_FAILED; - - base = mmap (NULL, DATA_SIZE, - PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_NORESERVE, - fd, 0); - close (fd); - - return base; -} - -static int -spawn_target (const char *socket_path, - const char *shm_path, - const cairo_boilerplate_target_t *target, - const char *trace) -{ - test_runner_t tr; - pid_t pid; - - if (DEBUG) - printf ("Spawning slave '%s' for %s\n", target->name, trace); - - pid = fork (); - if (pid != 0) - return pid; - - tr.is_recording = FALSE; - tr.pid = getpid (); - - tr.sk = spawn_socket (socket_path, tr.pid); - if (tr.sk == -1) { - fprintf (stderr, "%s: Failed to open socket.\n", - target->name); - exit (-1); - } - - tr.base = spawn_shm (shm_path); - if (tr.base == MAP_FAILED) { - fprintf (stderr, "%s: Failed to map shared memory segment.\n", - target->name); - exit (-1); - } - - tr.name = target->name; - tr.contexts = NULL; - tr.context_id = 0; - tr.trace = trace; - - tr.surface = target->create_surface (NULL, - target->content, - 1, 1, - 1, 1, - CAIRO_BOILERPLATE_MODE_TEST, - &tr.closure); - if (tr.surface == NULL) { - fprintf (stderr, - "%s: Failed to create target surface.\n", - target->name); - exit (-1); - } - - execute (&tr); - - cairo_surface_destroy (tr.surface); - - if (target->cleanup) - target->cleanup (tr.closure); - - close (tr.sk); - munmap (tr.base, DATA_SIZE); - - exit (0); -} - -#if CAIRO_HAS_REAL_PTHREAD -static void -cleanup_recorder (void *arg) -{ - test_runner_t *tr = arg; - - cairo_surface_finish (tr->surface); - cairo_surface_destroy (tr->surface); - - close (tr->sk); - free (tr); -} - -static void * -record (void *arg) -{ - test_runner_t *tr = arg; - - pthread_cleanup_push (cleanup_recorder, tr); - execute (tr); - pthread_cleanup_pop (TRUE); - - return NULL; -} - -/* The recorder is special: - * 1. It doesn't generate an image, but keeps an in-memory trace to - * reconstruct any surface. - * 2. Runs in the same process, but separate thread. - */ -static pid_t -spawn_recorder (const char *socket_path, const char *trace, test_runner_t **out) -{ - test_runner_t *tr; - pthread_t id; - pthread_attr_t attr; - pid_t pid = getpid (); - - if (DEBUG) - printf ("Spawning recorder for %s\n", trace); - - tr = malloc (sizeof (*tr)); - if (tr == NULL) - return -1; - - tr->is_recording = TRUE; - tr->pid = pid; - tr->sk = spawn_socket (socket_path, tr->pid); - if (tr->sk == -1) { - free (tr); - return -1; - } - - tr->base = NULL; - tr->name = NULL; - tr->contexts = NULL; - tr->context_id = 0; - tr->trace = trace; - - tr->surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, - NULL); - if (tr->surface == NULL) { - cleanup_recorder (tr); - return -1; - } - - pthread_attr_init (&attr); - pthread_attr_setdetachstate (&attr, TRUE); - if (pthread_create (&id, &attr, record, tr) < 0) { - pthread_attr_destroy (&attr); - cleanup_recorder (tr); - return -1; - } - pthread_attr_destroy (&attr); - - - *out = tr; - return pid; -} -#endif - -/* XXX imagediff - is the extra expense worth it? */ -static cairo_bool_t -matches_reference (struct slave *slave) -{ - cairo_surface_t *a, *b; - - a = slave->image; - b = slave->reference->image; - - if (a == b) - return TRUE; - - if (a == NULL || b == NULL) - return FALSE; - - if (cairo_surface_status (a) || cairo_surface_status (b)) - return FALSE; - - if (cairo_surface_get_type (a) != cairo_surface_get_type (b)) - return FALSE; - - if (cairo_image_surface_get_format (a) != cairo_image_surface_get_format (b)) - return FALSE; - - if (cairo_image_surface_get_width (a) != cairo_image_surface_get_width (b)) - return FALSE; - - if (cairo_image_surface_get_height (a) != cairo_image_surface_get_height (b)) - return FALSE; - - if (cairo_image_surface_get_stride (a) != cairo_image_surface_get_stride (b)) - return FALSE; - - if (FALSE && cairo_surface_get_content (a) & CAIRO_CONTENT_COLOR) { - cairo_surface_t *diff; - int width, height, stride, size; - unsigned char *data; - cairo_status_t status; - - width = cairo_image_surface_get_width (a); - height = cairo_image_surface_get_height (a); - stride = cairo_image_surface_get_stride (a); - size = height * stride * 4; - data = malloc (size); - if (data == NULL) - return FALSE; - - diff = cairo_image_surface_create_for_data (data, - cairo_image_surface_get_format (a), - width, height, stride); - cairo_surface_set_user_data (diff, (cairo_user_data_key_t *) diff, - data, free); - - status = image_diff (NULL, a, b, diff, &slave->result); - if (status) { - cairo_surface_destroy (diff); - return FALSE; - } - - if (image_diff_is_failure (&slave->result, slave->target->error_tolerance)) { - slave->difference = diff; - return FALSE; - } else { - cairo_surface_destroy (diff); - return TRUE; - } - } else { - int width, height, stride; - const uint8_t *aa, *bb; - int x, y; - - width = cairo_image_surface_get_width (a); - height = cairo_image_surface_get_height (a); - stride = cairo_image_surface_get_stride (a); - - aa = cairo_image_surface_get_data (a); - bb = cairo_image_surface_get_data (b); - switch (cairo_image_surface_get_format (a)) { - case CAIRO_FORMAT_ARGB32: - for (y = 0; y < height; y++) { - const uint32_t *ua = (uint32_t *) aa; - const uint32_t *ub = (uint32_t *) bb; - for (x = 0; x < width; x++) { - if (ua[x] != ub[x]) { - int channel; - - for (channel = 0; channel < 4; channel++) { - unsigned va, vb, diff; - - va = (ua[x] >> (channel*8)) & 0xff; - vb = (ub[x] >> (channel*8)) & 0xff; - diff = abs (va - vb); - if (diff > slave->target->error_tolerance) - return FALSE; - } - } - } - aa += stride; - bb += stride; - } - break; - - case CAIRO_FORMAT_RGB24: - for (y = 0; y < height; y++) { - const uint32_t *ua = (uint32_t *) aa; - const uint32_t *ub = (uint32_t *) bb; - for (x = 0; x < width; x++) { - if ((ua[x] & 0x00ffffff) != (ub[x] & 0x00ffffff)) { - int channel; - - for (channel = 0; channel < 3; channel++) { - unsigned va, vb, diff; - - va = (ua[x] >> (channel*8)) & 0xff; - vb = (ub[x] >> (channel*8)) & 0xff; - diff = abs (va - vb); - if (diff > slave->target->error_tolerance) - return FALSE; - } - } - } - aa += stride; - bb += stride; - } - break; - - case CAIRO_FORMAT_A8: - for (y = 0; y < height; y++) { - for (x = 0; x < width; x++) { - if (aa[x] != bb[x]) { - unsigned diff = abs (aa[x] - bb[x]); - if (diff > slave->target->error_tolerance) - return FALSE; - } - } - aa += stride; - bb += stride; - } - break; - - case CAIRO_FORMAT_A1: - width /= 8; - for (y = 0; y < height; y++) { - if (memcmp (aa, bb, width)) - return FALSE; - aa += stride; - bb += stride; - } - break; - - case CAIRO_FORMAT_RGB30: - case CAIRO_FORMAT_RGB16_565: - case CAIRO_FORMAT_INVALID: - assert (0); - } - - return TRUE; - } -} - -static cairo_bool_t -check_images (struct slave *slaves, int num_slaves) -{ - int n; - - if (ignore_image_differences) - return TRUE; - - for (n = 0; n < num_slaves; n++) { - if (slaves[n].reference == NULL) - continue; - - if (! matches_reference (&slaves[n])) - return FALSE; - } - - return TRUE; -} - -static void -write_images (const char *trace, struct slave *slave, int num_slaves) -{ - while (num_slaves--) { - if (slave->image != NULL && ! slave->is_recording) { - char *filename; - - xasprintf (&filename, "%s-%s-fail.png", - trace, slave->target->name); - cairo_surface_write_to_png (slave->image, filename); - free (filename); - - if (slave->difference) { - xasprintf (&filename, "%s-%s-diff.png", - trace, slave->target->name); - cairo_surface_write_to_png (slave->difference, filename); - free (filename); - } - } - - slave++; - } -} - -static void -write_result (const char *trace, struct slave *slave) -{ - static int index; - char *filename; - - xasprintf (&filename, "%s-%s-pass-%d-%d-%d.png", - trace, slave->target->name, ++index, - slave->start_line, slave->end_line); - cairo_surface_write_to_png (slave->image, filename); - free (filename); -} - -static void -write_trace (const char *trace, const char *id, struct slave *slave) -{ -#if CAIRO_HAS_SCRIPT_SURFACE - cairo_device_t *script; - char *filename; - - assert (slave->is_recording); - - xasprintf (&filename, "%s-%s.trace", trace, id); - - script = cairo_script_create (filename); - cairo_script_from_recording_surface (script, slave->image); - cairo_device_destroy (script); - - free (filename); -#endif -} - -static void -dump_traces (test_runner_t *tr, - const char *trace, - const char *target, - const char *fail) -{ -#if CAIRO_HAS_SCRIPT_SURFACE - struct context_closure *c; - - for (c = tr->contexts; c; c = c->next) { - cairo_device_t *script; - char *filename; - - xasprintf (&filename, "%s-%s-%s.%lu.trace", - trace, target, fail, c->start_line); - - script = cairo_script_create (filename); - cairo_script_from_recording_surface (script, c->surface); - cairo_device_destroy (script); - - free (filename); - } -#endif -} - -static unsigned long -allocate_image_for_slave (uint8_t *base, - unsigned long offset, - struct slave *slave) -{ - struct request_image rq; - int size; - uint8_t *data; - - assert (slave->image == NULL); - - readn (slave->fd, &rq, sizeof (rq)); - slave->image_serial = rq.id; - slave->start_line = rq.start_line; - slave->end_line = rq.end_line; - - slave->width = rq.width; - slave->height = rq.height; - - if (DEBUG > 1) { - printf ("allocate-image-for-slave: %s %lu [%lu, %lu] %ldx%ld stride=%lu => %lu, is-recording? %d\n", - TARGET_NAME (slave->target), - slave->image_serial, - slave->start_line, - slave->end_line, - slave->width, - slave->height, - rq.stride, - offset, - slave->is_recording); - } - - if (slave->is_recording) { - /* special communication with recording-surface thread */ - slave->image = cairo_surface_reference ((cairo_surface_t *) rq.stride); - } else { - size = rq.height * rq.stride; - size = (size + 4095) & -4096; - data = base + offset; - offset += size; - assert (offset <= DATA_SIZE); - - slave->image = cairo_image_surface_create_for_data (data, rq.format, - rq.width, rq.height, - rq.stride); - } - - return offset; -} - -struct error_info { - unsigned long context_id; - unsigned long start_line; - unsigned long end_line; -}; - -static cairo_bool_t -test_run (void *base, - int sk, - const char *trace, - struct slave *slaves, - int num_slaves, - struct error_info *error) -{ - struct pollfd *pfd; - int npfd, cnt, n, i; - int completion, err = 0; - cairo_bool_t ret = FALSE; - unsigned long image; - - if (DEBUG) { - printf ("Running trace '%s' over %d slaves\n", - trace, num_slaves); - } - - pfd = xcalloc (num_slaves+1, sizeof (*pfd)); - - pfd[0].fd = sk; - pfd[0].events = POLLIN; - npfd = 1; - - completion = 0; - image = 0; - while ((cnt = poll (pfd, npfd, -1)) > 0) { - if (pfd[0].revents) { - int fd; - - while ((fd = accept (sk, NULL, NULL)) != -1) { - pid_t pid; - - readn (fd, &pid, sizeof (pid)); - for (n = 0; n < num_slaves; n++) { - if (slaves[n].pid == pid) { - slaves[n].fd = fd; - break; - } - } - if (n == num_slaves) { - if (DEBUG) - printf ("unknown slave pid\n"); - goto out; - } - - pfd[npfd].fd = fd; - pfd[npfd].events = POLLIN; - npfd++; - - if (! writen (fd, &pid, sizeof (pid))) - goto out; - } - cnt--; - } - - for (n = 1; n < npfd && cnt; n++) { - if (! pfd[n].revents) - continue; - - if (pfd[n].revents & POLLHUP) { - pfd[n].events = pfd[n].revents = 0; - completion++; - continue; - } - - for (i = 0; i < num_slaves; i++) { - if (slaves[i].fd == pfd[n].fd) { - /* Communication with the slave is done in three phases, - * and we do each pass synchronously. - * - * 1. The slave requests an image buffer, which we - * allocate and then return to the slave the offset into - * the shared memory segment. - * - * 2. The slave indicates that it has finished writing - * into the shared image buffer. The slave now waits - * for the server to collate all the image data - thereby - * throttling the slaves. - * - * 3. After all slaves have finished writing their images, - * we compare them all against the reference image and, - * if satisfied, send an acknowledgement to all slaves. - */ - if (slaves[i].image_serial == 0) { - unsigned long offset; - - image = - allocate_image_for_slave (base, - offset = image, - &slaves[i]); - if (! writen (pfd[n].fd, &offset, sizeof (offset))) { - pfd[n].events = pfd[n].revents = 0; - err = 1; - completion++; - continue; - } - } else { - readn (pfd[n].fd, - &slaves[i].image_ready, - sizeof (slaves[i].image_ready)); - if (DEBUG) { - printf ("slave '%s' reports completion on %lu (expecting %lu)\n", - TARGET_NAME (slaves[i].target), - slaves[i].image_ready, - slaves[i].image_serial); - } - if (slaves[i].image_ready != slaves[i].image_serial) { - pfd[n].events = pfd[n].revents = 0; - err = 1; - completion++; - continue; - } - - /* Can anyone spell 'P·E·D·A·N·T'? */ - if (! slaves[i].is_recording) - cairo_surface_mark_dirty (slaves[i].image); - completion++; - } - - break; - } - } - - cnt--; - } - - if (completion >= num_slaves) { - if (err) { - if (DEBUG > 1) - printf ("error detected\n"); - goto out; - } - - if (DEBUG > 1) { - printf ("all saves report completion\n"); - } - if (slaves[0].end_line >= slaves[0].start_line && - ! check_images (slaves, num_slaves)) { - error->context_id = slaves[0].image_serial; - error->start_line = slaves[0].start_line; - error->end_line = slaves[0].end_line; - - if (DEBUG) { - printf ("check_images failed: %lu, [%lu, %lu]\n", - slaves[0].image_serial, - slaves[0].start_line, - slaves[0].end_line); - } - - write_images (trace, slaves, num_slaves); - - if (slaves[0].is_recording) - write_trace (trace, "fail", &slaves[0]); - - goto out; - } - - if (write_results) write_result (trace, &slaves[1]); - if (write_traces && slaves[0].is_recording) { - char buf[80]; - snprintf (buf, sizeof (buf), "%d", slaves[0].image_serial); - write_trace (trace, buf, &slaves[0]); - } - - /* ack */ - for (i = 0; i < num_slaves; i++) { - cairo_surface_destroy (slaves[i].image); - slaves[i].image = NULL; - - if (DEBUG > 1) { - printf ("sending continuation to '%s'\n", - TARGET_NAME (slaves[i].target)); - } - if (! writen (slaves[i].fd, - &slaves[i].image_serial, - sizeof (slaves[i].image_serial))) - { - goto out; - } - - slaves[i].image_serial = 0; - slaves[i].image_ready = 0; - } - - completion = 0; - image = 0; - } - } -done: - ret = TRUE; - -out: - if (DEBUG) { - printf ("run complete: %d\n", ret); - } - - for (n = 0; n < num_slaves; n++) { - if (slaves[n].fd != -1) - close (slaves[n].fd); - - if (slaves[n].image == NULL) - continue; - - cairo_surface_destroy (slaves[n].image); - slaves[n].image = NULL; - - cairo_surface_destroy (slaves[n].difference); - slaves[n].difference = NULL; - - slaves[n].image_serial = 0; - slaves[n].image_ready = 0; - } - - free (pfd); - - return ret; -} - -static int -server_socket (const char *socket_path) -{ - long flags; - struct sockaddr_un addr; - int sk; - - sk = socket (PF_UNIX, SOCK_STREAM, 0); - if (sk == -1) - return -1; - - memset (&addr, 0, sizeof (addr)); - addr.sun_family = AF_UNIX; - strcpy (addr.sun_path, socket_path); - if (bind (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) { - close (sk); - return -1; - } - - flags = fcntl (sk, F_GETFL); - if (flags == -1 || fcntl (sk, F_SETFL, flags | O_NONBLOCK) == -1) { - close (sk); - return -1; - } - - if (listen (sk, 5) == -1) { - close (sk); - return -1; - } - - return sk; -} - -static int -server_shm (const char *shm_path) -{ - int fd; - - fd = shm_open (shm_path, O_RDWR | O_EXCL | O_CREAT, 0777); - if (fd == -1) - return -1; - - if (ftruncate (fd, DATA_SIZE) == -1) { - close (fd); - return -1; - } - - return fd; -} - -static cairo_bool_t -_test_trace (test_trace_t *test, - const char *trace, - const char *name, - struct error_info *error) -{ - const char *shm_path = SHM_PATH_XXX; - const cairo_boilerplate_target_t *target, *image; - struct slave *slaves, *s; - test_runner_t *recorder = NULL; - pid_t slave; - char socket_dir[] = "/tmp/cairo-test-trace.XXXXXX"; - char *socket_path; - int sk, fd; - int i, num_slaves; - void *base; - cairo_bool_t ret = FALSE; - - if (DEBUG) - printf ("setting up trace '%s'\n", trace); - - /* create a socket to control the test runners */ - if (mkdtemp (socket_dir) == NULL) { - fprintf (stderr, "Unable to create temporary name for socket\n"); - return FALSE; - } - - xasprintf (&socket_path, "%s/socket", socket_dir); - sk = server_socket (socket_path); - if (sk == -1) { - fprintf (stderr, "Unable to create socket for server\n"); - goto cleanup_paths; - } - - /* allocate some shared memory */ - fd = server_shm (shm_path); - if (fd == -1) { - fprintf (stderr, "Unable to create shared memory '%s': %s\n", - shm_path, strerror (errno)); - goto cleanup_sk; - } - - image = cairo_boilerplate_get_image_target (CAIRO_CONTENT_COLOR_ALPHA); - assert (image != NULL); - - s = slaves = xcalloc (2*test->num_targets + 1, sizeof (struct slave)); - -#if CAIRO_HAS_REAL_PTHREAD - /* set-up a recording-surface to reconstruct errors */ - slave = spawn_recorder (socket_path, trace, &recorder); - if (slave < 0) { - fprintf (stderr, "Unable to create recording surface\n"); - goto cleanup_sk; - } - - s->pid = slave; - s->is_recording = TRUE; - s->target = NULL; - s->fd = -1; - s->reference = NULL; - s++; -#endif - - /* spawn slave processes to run the trace */ - for (i = 0; i < test->num_targets; i++) { - const cairo_boilerplate_target_t *reference; - struct slave *master; - - target = test->targets[i]; - - if (DEBUG) - printf ("setting up target[%d]? '%s' (image? %d, measurable? %d)\n", - i, target->name, target == image, target->is_measurable); - - if (target == image || ! target->is_measurable) - continue; - - /* find a matching slave to use as a reference for this target */ - if (target->reference_target != NULL) { - reference = - cairo_boilerplate_get_target_by_name (target->reference_target, - target->content); - assert (reference != NULL); - } else { - reference = image; - } - for (master = slaves; master < s; master++) { - if (master->target == reference) - break; - } - - if (master == s) { - /* no match found, spawn a slave to render the reference image */ - slave = spawn_target (socket_path, shm_path, reference, trace); - if (slave < 0) - continue; - - s->pid = slave; - s->target = reference; - s->fd = -1; - s->reference = NULL; - s++; - } - - slave = spawn_target (socket_path, shm_path, target, trace); - if (slave < 0) - continue; - - s->pid = slave; - s->target = target; - s->fd = -1; - s->reference = master; - s++; - } - num_slaves = s - slaves; - if (num_slaves == 1) { - fprintf (stderr, "No targets to test\n"); - goto cleanup; - } - - base = mmap (NULL, DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (base == MAP_FAILED) { - fprintf (stderr, "Unable to mmap shared memory\n"); - goto cleanup; - } - ret = test_run (base, sk, name, slaves, num_slaves, error); - munmap (base, DATA_SIZE); - -cleanup: - close (fd); - while (s-- > slaves) { - int status; - - if (s->fd != -1) - close (s->fd); - - cairo_surface_destroy (s->image); - cairo_surface_destroy (s->difference); - - if (s->is_recording) /* in-process */ - continue; - - kill (s->pid, SIGKILL); - waitpid (s->pid, &status, 0); - if (WIFSIGNALED (status) && WTERMSIG(status) != SIGKILL) { - fprintf (stderr, "%s crashed\n", s->target->name); - if (recorder) - dump_traces (recorder, trace, s->target->name, "crash"); - } - } - free (slaves); - shm_unlink (shm_path); -cleanup_sk: - close (sk); - -cleanup_paths: - remove (socket_path); - remove (socket_dir); - - free (socket_path); - return ret; -} - -static void -test_trace (test_trace_t *test, const char *trace) -{ - char *trace_cpy, *name, *dot; - - trace_cpy = xstrdup (trace); - name = basename (trace_cpy); - dot = strchr (name, '.'); - if (dot) - *dot = '\0'; - - if (test->list_only) { - printf ("%s\n", name); - } else { - struct error_info error = {0}; - cairo_bool_t ret; - - printf ("%s: ", name); - fflush (stdout); - - ret = _test_trace (test, trace, name, &error); - if (ret) { - printf ("PASS\n"); - } else { - if (error.context_id) { - printf ("FAIL (context %lu, lines [%lu, %lu])\n", - error.context_id, - error.start_line, - error.end_line); - } else { - printf ("FAIL\n"); - } - } - } - - free (trace_cpy); -} - -static cairo_bool_t -read_excludes (test_trace_t *test, const char *filename) -{ - FILE *file; - char *line = NULL; - size_t line_size = 0; - char *s, *t; - - file = fopen (filename, "r"); - if (file == NULL) - return FALSE; - - while (getline (&line, &line_size, file) != -1) { - /* terminate the line at a comment marker '#' */ - s = strchr (line, '#'); - if (s) - *s = '\0'; - - /* whitespace delimits */ - s = line; - while (*s != '\0' && isspace (*s)) - s++; - - t = s; - while (*t != '\0' && ! isspace (*t)) - t++; - - if (s != t) { - int i = test->num_exclude_names; - test->exclude_names = xrealloc (test->exclude_names, - sizeof (char *) * (i+1)); - test->exclude_names[i] = strndup (s, t-s); - test->num_exclude_names++; - } - } - free (line); - - fclose (file); - - return TRUE; -} - -static void -usage (const char *argv0) -{ - fprintf (stderr, -"Usage: %s [-l] [-x exclude-file] [test-names ... | traces ...]\n" -"\n" -"Run the cairo test suite over the given traces (all by default).\n" -"The command-line arguments are interpreted as follows:\n" -"\n" -" -l list only; just list selected test case names without executing\n" -" -x exclude; specify a file to read a list of traces to exclude\n" -"\n" -"If test names are given they are used as sub-string matches so a command\n" -"such as \"%s firefox\" can be used to run all firefox traces.\n" -"Alternatively, you can specify a list of filenames to execute.\n", - argv0, argv0); -} - -static void -parse_options (test_trace_t *test, int argc, char *argv[]) -{ - int c; - - test->list_only = FALSE; - test->names = NULL; - test->num_names = 0; - test->exclude_names = NULL; - test->num_exclude_names = 0; - - while (1) { - c = _cairo_getopt (argc, argv, "lx:"); - if (c == -1) - break; - - switch (c) { - case 'l': - test->list_only = TRUE; - break; - case 'x': - if (! read_excludes (test, optarg)) { - fprintf (stderr, "Invalid argument for -x (not readable file): %s\n", - optarg); - exit (1); - } - break; - default: - fprintf (stderr, "Internal error: unhandled option: %c\n", c); - /* fall-through */ - case '?': - usage (argv[0]); - exit (1); - } - } - - if (optind < argc) { - test->names = &argv[optind]; - test->num_names = argc - optind; - } -} - -static void -test_reset (test_trace_t *test) -{ - /* XXX leaking fonts again via recording-surface? */ -#if 0 - cairo_debug_reset_static_data (); -#if HAVE_FCFINI - FcFini (); -#endif -#endif -} - -static void -test_fini (test_trace_t *test) -{ - test_reset (test); - - cairo_boilerplate_free_targets (test->targets); - free (test->exclude_names); -} - -static cairo_bool_t -test_has_filenames (test_trace_t *test) -{ - unsigned int i; - - if (test->num_names == 0) - return FALSE; - - for (i = 0; i < test->num_names; i++) - if (access (test->names[i], R_OK) == 0) - return TRUE; - - return FALSE; -} - -static cairo_bool_t -test_can_run (test_trace_t *test, const char *name) -{ - unsigned int i; - char *copy, *dot; - cairo_bool_t ret; - - if (test->num_names == 0 && test->num_exclude_names == 0) - return TRUE; - - copy = xstrdup (name); - dot = strrchr (copy, '.'); - if (dot != NULL) - *dot = '\0'; - - if (test->num_names) { - ret = TRUE; - for (i = 0; i < test->num_names; i++) - if (strstr (copy, test->names[i])) - goto check_exclude; - - ret = FALSE; - goto done; - } - -check_exclude: - if (test->num_exclude_names) { - ret = FALSE; - for (i = 0; i < test->num_exclude_names; i++) - if (strstr (copy, test->exclude_names[i])) - goto done; - - ret = TRUE; - goto done; - } - -done: - free (copy); - - return ret; -} - -static void -warn_no_traces (const char *message, const char *trace_dir) -{ - fprintf (stderr, -"Error: %s '%s'.\n" -"Have you cloned the cairo-traces repository and uncompressed the traces?\n" -" git clone git://anongit.freedesktop.org/cairo-traces\n" -" cd cairo-traces && make\n" -"Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n", - message, trace_dir); -} - -static void -interrupt (int sig) -{ - shm_unlink (SHM_PATH_XXX); - - signal (sig, SIG_DFL); - raise (sig); -} - -int -main (int argc, char *argv[]) -{ - test_trace_t test; - const char *trace_dir = "cairo-traces"; - unsigned int n; - - signal (SIGPIPE, SIG_IGN); - signal (SIGINT, interrupt); - - parse_options (&test, argc, argv); - - shm_unlink (SHM_PATH_XXX); - - if (getenv ("CAIRO_TRACE_DIR") != NULL) - trace_dir = getenv ("CAIRO_TRACE_DIR"); - - test.targets = cairo_boilerplate_get_targets (&test.num_targets, NULL); - - if (test_has_filenames (&test)) { - for (n = 0; n < test.num_names; n++) { - if (access (test.names[n], R_OK) == 0) { - test_trace (&test, test.names[n]); - test_reset (&test); - } - } - } else { - DIR *dir; - struct dirent *de; - int num_traces = 0; - - dir = opendir (trace_dir); - if (dir == NULL) { - warn_no_traces ("Failed to open directory", trace_dir); - test_fini (&test); - return 1; - } - - while ((de = readdir (dir)) != NULL) { - char *trace; - const char *dot; - - dot = strrchr (de->d_name, '.'); - if (dot == NULL) - continue; - if (strcmp (dot, ".trace")) - continue; - - num_traces++; - if (! test_can_run (&test, de->d_name)) - continue; - - xasprintf (&trace, "%s/%s", trace_dir, de->d_name); - test_trace (&test, trace); - test_reset (&test); - - free (trace); - - } - closedir (dir); - - if (num_traces == 0) { - warn_no_traces ("Found no traces in", trace_dir); - test_fini (&test); - return 1; - } - } - - test_fini (&test); - - return 0; -} - -void -cairo_test_logv (const cairo_test_context_t *ctx, - const char *fmt, va_list va) -{ -#if 0 - vfprintf (stderr, fmt, va); -#endif -} - -void -cairo_test_log (const cairo_test_context_t *ctx, const char *fmt, ...) -{ -#if 0 - va_list va; - - va_start (va, fmt); - vfprintf (stderr, fmt, va); - va_end (va); -#endif -} |