| /* |
| * 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 |
| } |