mirror of
https://gitlab.freedesktop.org/cairo/cairo.git
synced 2026-01-11 13:20:17 +01:00
Use two levels of pthread support: a minimal level used to build cairo itself, and a full level to build threaded apps which want to use cairo. The minimal level tries to use pthread stubs from libc if possible, but falls back to the full level if that's not possible. We use CFLAGS=-D_REENTRANT LIBS=-lpthread to find a real pthread library since that seems to work on every unix-like test box we can get our hands on.
1758 lines
39 KiB
C
1758 lines
39 KiB
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"
|
|
|
|
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;
|
|
|
|
#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;
|
|
}
|
|
|
|
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
|
|
}
|