Logo Search packages:      
Sourcecode: cairo version File versions  Download package

cairo-test-trace.c

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

#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

#define DEBUG 0

#define DATA_SIZE (256 << 20)
#define SHM_PATH_XXX "/shmem-cairo-trace"

00098 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;

00111 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;
00122     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;

00134 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;
};

00150 struct request_image {
    unsigned long id;
    unsigned long start_line;
    unsigned long end_line;
    cairo_format_t format;
    long width;
    long height;
    long stride;
};

00160 struct surface_tag {
    long width, height;
};
static const cairo_user_data_key_t surface_tag;

#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,
      cairo_surface_get_type (closure->surface) == CAIRO_SURFACE_TYPE_IMAGE ? 0 : (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'\n", tr->name);
    }

    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)
{
    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;

    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) {
          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,
                               0,
                               &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 *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,
                                                 0, 0);
    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);

    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 (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;
      }

      return TRUE;
    }
}

static cairo_bool_t
check_images (struct slave *slaves, int num_slaves)
{
    int n;

    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_trace (const char *trace, struct slave *slave)
{
#if CAIRO_HAS_SCRIPT_SURFACE
    cairo_device_t *ctx,
    cairo_surface_t *script;
    char *filename;
    cairo_t *cr;

    xasprintf (&filename, "%s-fail.trace", trace);
    ctx = cairo_script_create (filename);
    script = cairo_script_surface_create (ctx,
                                cairo_surface_get_content (slave->image),
                                slave->width,
                                slave->height);
    cairo_device_destroy (ctx);
    free (filename);

    cr = cairo_create (slave->image);
    cairo_set_source_surface (cr, script, 0, 0);
    cairo_paint (cr);
    cairo_destroy (cr);

    cairo_surface_destroy (script);
#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=> %lu\n",
            slave->target->name,
            slave->image_serial,
            slave->start_line,
            slave->end_line,
            slave->width,
            slave->height,
            offset);
    }

    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;
}

00937 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;
    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)
            goto done;

          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)))
                      goto out;
                } 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",
                            slaves[i].target->name,
                            slaves[i].image_ready,
                            slaves[i].image_serial);
                  }
                  if (slaves[i].image_ready != slaves[i].image_serial)
                      goto out;

                  /* 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 (DEBUG > 1) {
            printf ("all saves report completion\n");
          }
          if (! 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, &slaves[0]);

            goto out;
          }

          /* 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",
                      slaves[i].target->name);
            }
            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;
      ret = FALSE;
    }

    free (pfd);

    return ret;
}

/* Paginated surfaces require finalization and external converters and so
 * are not suitable for this basic technique.
 */
static cairo_bool_t
target_is_measurable (const cairo_boilerplate_target_t *target)
{
    if (target->content != CAIRO_CONTENT_COLOR_ALPHA)
      return FALSE;

    switch (target->expected_type) {
    case CAIRO_SURFACE_TYPE_IMAGE:
      if (strcmp (target->name, "pdf") == 0 ||
          strcmp (target->name, "ps") == 0)
      {
          return FALSE;
      }
      else
      {
          return TRUE;
      }
    case CAIRO_SURFACE_TYPE_XLIB:
    case CAIRO_SURFACE_TYPE_XCB:
    case CAIRO_SURFACE_TYPE_GLITZ:
    case CAIRO_SURFACE_TYPE_QUARTZ:
    case CAIRO_SURFACE_TYPE_QUARTZ_IMAGE:
    case CAIRO_SURFACE_TYPE_WIN32:
    case CAIRO_SURFACE_TYPE_BEOS:
    case CAIRO_SURFACE_TYPE_DIRECTFB:
    case CAIRO_SURFACE_TYPE_OS2:
    case CAIRO_SURFACE_TYPE_QT:
      return TRUE;

    case CAIRO_SURFACE_TYPE_PDF:
    case CAIRO_SURFACE_TYPE_PS:
    case CAIRO_SURFACE_TYPE_SCRIPT:
    case CAIRO_SURFACE_TYPE_SVG:
    case CAIRO_SURFACE_TYPE_WIN32_PRINTING:
    case CAIRO_SURFACE_TYPE_RECORDING:
    default:
      return FALSE;
    }

    return TRUE;
}

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;
    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;

    /* 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'\n",
             shm_path);
      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);
    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 (target == image || ! target_is_measurable (target))
          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->is_recording) /* in-process */
          continue;

      kill (s->pid, SIGKILL);
      waitpid (s->pid, &status, 0);
      ret &= WIFEXITED (status) && WEXITSTATUS (status) == 0;
      if (WIFSIGNALED (status) && WTERMSIG(status) != SIGKILL)
          fprintf (stderr, "%s crashed\n", s->target->name);
    }
    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);
}

#ifndef __USE_GNU
#define POORMANS_GETLINE_BUFFER_SIZE (65536)
static ssize_t
getline (char **lineptr, size_t *n, FILE *stream)
{
    if (*lineptr == NULL) {
        *n = POORMANS_GETLINE_BUFFER_SIZE;
        *lineptr = (char *) malloc (*n);
    }

    if (! fgets (*lineptr, *n, stream))
        return -1;

    if (! feof (stream) && !strchr (*lineptr, '\n')) {
        fprintf (stderr, "The poor man's implementation of getline in "
                          __FILE__ " needs a bigger buffer. Perhaps it's "
                         "time for a complete implementation of getline.\n");
        exit (0);
    }

    return strlen (*lineptr);
}
#undef POORMANS_GETLINE_BUFFER_SIZE

static char *
strndup (const char *s, size_t n)
{
    size_t len;
    char *sdup;

    if (!s)
        return NULL;

    len = strlen (s);
    len = (n < len ? n : len);
    sdup = (char *) malloc (len + 1);
    if (sdup)
    {
        memcpy (sdup, s, len);
        sdup[len] = '\0';
    }

    return sdup;
}
#endif /* ifndef __USE_GNU */

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++;
      }
    }
    if (line != NULL)
      free (line);

    fclose (file);

    return TRUE;
}

static void
usage (const char *argv0)
{
    fprintf (stderr,
"Usage: %s [-x exclude-file] [test-names ... | traces ...]\n"
"       %s -l\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"
"  -x   exclude; specify a file to read a list of traces to exclude\n"
"  -l list only; just list selected test case names without executing\n"
"\n"
"If test names are given they are used as sub-string matches so a command\n"
"such as \"cairo-test-trace 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, "x:l");
      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);
    if (test->exclude_names)
      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);

    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
}

Generated by  Doxygen 1.6.0   Back to index