mirror of
https://gitlab.freedesktop.org/cairo/cairo.git
synced 2025-12-20 14:00:10 +01:00
1776 lines
39 KiB
C
1776 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>
|
|
#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
|
|
|
|
#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
|
|
}
|