summaryrefslogtreecommitdiff
path: root/test/cairo-test-trace.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/cairo-test-trace.c')
-rw-r--r--test/cairo-test-trace.c1780
1 files changed, 1780 insertions, 0 deletions
diff --git a/test/cairo-test-trace.c b/test/cairo-test-trace.c
new file mode 100644
index 000000000..5badc4377
--- /dev/null
+++ b/test/cairo-test-trace.c
@@ -0,0 +1,1780 @@
+/*
+ * 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
+}