[test] Record trace to an in-memory meta-surface

Requires hooking into test-meta-surface currently. Export meta-surface!

The idea is that on detection of an error, we can reconstruct a minimal
trace from the meta-surface. The first step is to simply dump the trace
for the failing meta-surface. Later, we should automatically minimise
this further.
This commit is contained in:
Chris Wilson 2009-07-03 11:24:42 +01:00
parent 633efe8187
commit fe73a9dd14
4 changed files with 397 additions and 82 deletions

View file

@ -63,49 +63,17 @@ typedef struct _test_meta_surface {
cairo_bool_t image_reflects_meta;
} test_meta_surface_t;
static const cairo_surface_backend_t test_meta_surface_backend;
static cairo_int_status_t
_test_meta_surface_show_page (void *abstract_surface);
cairo_surface_t *
_cairo_test_meta_surface_create (cairo_content_t content,
int width,
int height)
slim_hidden_proto (_cairo_test_meta_surface_create);
static cairo_surface_t *
_test_meta_surface_create_similar (void *abstract_surface,
cairo_content_t content,
int width, int height)
{
test_meta_surface_t *surface;
cairo_status_t status;
surface = malloc (sizeof (test_meta_surface_t));
if (unlikely (surface == NULL)) {
status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
goto FAIL;
}
_cairo_surface_init (&surface->base, &test_meta_surface_backend,
content);
surface->meta = _cairo_meta_surface_create (content, width, height);
status = cairo_surface_status (surface->meta);
if (status)
goto FAIL_CLEANUP_SURFACE;
surface->image = _cairo_image_surface_create_with_content (content,
width, height);
status = cairo_surface_status (surface->image);
if (status)
goto FAIL_CLEANUP_META;
surface->image_reflects_meta = FALSE;
return &surface->base;
FAIL_CLEANUP_META:
cairo_surface_destroy (surface->meta);
FAIL_CLEANUP_SURFACE:
free (surface);
FAIL:
return _cairo_surface_create_in_error (status);
return _cairo_test_meta_surface_create (content, width, height);
}
static cairo_status_t
@ -303,7 +271,7 @@ _test_meta_surface_snapshot (void *abstract_other)
static const cairo_surface_backend_t test_meta_surface_backend = {
CAIRO_INTERNAL_SURFACE_TYPE_TEST_META,
NULL, /* create_similar */
_test_meta_surface_create_similar,
_test_meta_surface_finish,
_test_meta_surface_acquire_source_image,
_test_meta_surface_release_source_image,
@ -340,3 +308,56 @@ static const cairo_surface_backend_t test_meta_surface_backend = {
_test_meta_surface_has_show_text_glyphs,
_test_meta_surface_show_text_glyphs
};
cairo_surface_t *
_cairo_test_meta_surface_create (cairo_content_t content,
int width,
int height)
{
test_meta_surface_t *surface;
cairo_status_t status;
surface = malloc (sizeof (test_meta_surface_t));
if (unlikely (surface == NULL)) {
status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
goto FAIL;
}
_cairo_surface_init (&surface->base, &test_meta_surface_backend,
content);
surface->meta = _cairo_meta_surface_create (content, width, height);
status = cairo_surface_status (surface->meta);
if (status)
goto FAIL_CLEANUP_SURFACE;
surface->image = _cairo_image_surface_create_with_content (content,
width, height);
status = cairo_surface_status (surface->image);
if (status)
goto FAIL_CLEANUP_META;
surface->image_reflects_meta = FALSE;
return &surface->base;
FAIL_CLEANUP_META:
cairo_surface_destroy (surface->meta);
FAIL_CLEANUP_SURFACE:
free (surface);
FAIL:
return _cairo_surface_create_in_error (status);
}
slim_hidden_def (_cairo_test_meta_surface_create);
cairo_status_t
_cairo_test_meta_surface_replay (cairo_surface_t *abstract_surface,
cairo_surface_t *target)
{
test_meta_surface_t *surface = (test_meta_surface_t *) abstract_surface;
if (abstract_surface->backend != &test_meta_surface_backend)
return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
return _cairo_meta_surface_replay (surface->meta, target);
}

View file

@ -45,6 +45,10 @@ _cairo_test_meta_surface_create (cairo_content_t content,
int width,
int height);
cairo_status_t
_cairo_test_meta_surface_replay (cairo_surface_t *abstract_surface,
cairo_surface_t *target);
CAIRO_END_DECLS
#endif /* TEST_META_SURFACE_H */

View file

@ -110,6 +110,9 @@ cairo_test_trace_LDADD = \
$(top_builddir)/src/libcairo.la \
$(CAIRO_LDADD) \
$(SHM_LIBS)
if HAVE_PTHREAD
cairo_test_trace_LDADD += -lpthread
endif
cairo_test_trace_DEPENDENCIES = \
$(top_builddir)/test/pdiff/libpdiff.la \
$(top_builddir)/util/cairo-script/libcairo-script-interpreter.la \

View file

@ -60,6 +60,13 @@
#include "cairo-boilerplate-getopt.h"
#include <cairo-script-interpreter.h>
#if CAIRO_HAS_SCRIPT_SURFACE
#include <cairo-script.h>
#endif
/* XXX give me a real meta surface! */
#include <test-meta-surface.h>
/* For basename */
#ifdef HAVE_LIBGEN_H
#include <libgen.h>
@ -78,11 +85,16 @@
#include <sys/un.h>
#include <errno.h>
#include <assert.h>
#if HAVE_PTHREAD_H
#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"
@ -104,8 +116,10 @@ typedef struct _test_runner_thread {
cairo_surface_t *surface;
void *closure;
uint8_t *base;
const char *trace;
pid_t pid;
int sk;
cairo_bool_t is_meta;
cairo_script_interpreter_t *csi;
struct context_closure {
@ -128,10 +142,12 @@ struct slave {
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_meta;
};
struct request_image {
@ -139,16 +155,22 @@ struct request_image {
unsigned long start_line;
unsigned long end_line;
cairo_format_t format;
int width;
int height;
int stride;
long width;
long height;
long stride;
};
struct surface_tag {
int width, height;
long width, height;
};
static const cairo_user_data_key_t surface_tag;
#if HAVE_PTHREAD_H
#define thread_die(t) t->is_meta ? pthread_exit(NULL) : exit(1)
#else
#define thread_die(t) exit(1)
#endif
static cairo_bool_t
writen (int fd, const void *ptr, int len)
{
@ -221,6 +243,48 @@ format_for_content (cairo_content_t content)
}
}
static void
send_meta_surface (test_runner_thread_t *thread,
int width, int height,
struct context_closure *closure)
{
#if HAVE_PTHREAD_H
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-meta-surface: %lu [%lu, %lu]\n",
closure->id,
closure->start_line,
closure->end_line);
}
writen (thread->sk, &rq, sizeof (rq));
readn (thread->sk, &offset, sizeof (offset));
/* signal completion */
writen (thread->sk, &closure->id, sizeof (closure->id));
/* wait for image check */
serial = 0;
readn (thread->sk, &serial, sizeof (serial));
if (DEBUG > 1) {
printf ("send-meta-surface: serial: %lu\n", serial);
}
if (serial != closure->id)
pthread_exit (NULL);
#else
exit (1);
#endif
}
static void *
request_image (test_runner_thread_t *thread,
struct context_closure *closure,
@ -233,18 +297,20 @@ request_image (test_runner_thread_t *thread,
closure->end_line,
format, width, height, stride
};
size_t offset = -1;
unsigned long offset = -1;
assert (format != (cairo_format_t) -1);
writen (thread->sk, &rq, sizeof (rq));
readn (thread->sk, &offset, sizeof (offset));
if (offset == (size_t) -1)
if (offset == (unsigned long) -1)
return NULL;
return thread->base + offset;
}
static void
push_surface (test_runner_thread_t *thread,
send_surface (test_runner_thread_t *thread,
struct context_closure *closure)
{
cairo_surface_t *source = closure->surface;
@ -255,6 +321,10 @@ push_surface (test_runner_thread_t *thread,
void *data;
unsigned long serial;
if (DEBUG > 1) {
printf ("send-surface: '%s'\n", thread->target->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);
@ -279,9 +349,15 @@ push_surface (test_runner_thread_t *thread,
height = tag->height = y1 - y0;
if (cairo_surface_set_user_data (source, &surface_tag, tag, free))
exit (-1);
thread_die (thread);
}
}
if (thread->is_meta) {
send_meta_surface (thread, width, height, closure);
return;
}
if (format == (cairo_format_t) -1)
format = format_for_content (cairo_surface_get_content (source));
@ -289,7 +365,7 @@ push_surface (test_runner_thread_t *thread,
data = request_image (thread, closure, format, width, height, stride);
if (data == NULL)
exit (-1);
thread_die (thread);
image = cairo_image_surface_create_for_data (data,
format,
@ -310,7 +386,7 @@ push_surface (test_runner_thread_t *thread,
serial = 0;
readn (thread->sk, &serial, sizeof (serial));
if (serial != closure->id)
exit (-1);
thread_die (thread);
}
static cairo_surface_t *
@ -330,7 +406,7 @@ _surface_create (void *closure,
tag->width = width;
tag->height = height;
if (cairo_surface_set_user_data (surface, &surface_tag, tag, free))
exit (-1);
thread_die (thread);
}
return surface;
@ -367,13 +443,13 @@ _context_destroy (void *closure, void *ptr)
l->end_line =
cairo_script_interpreter_get_line_number (thread->csi);
if (cairo_surface_status (l->surface) == CAIRO_STATUS_SUCCESS) {
push_surface (thread, l);
send_surface (thread, l);
} else {
fprintf (stderr, "%s: error during replay, line %lu: %s!\n",
thread->target->name,
l->end_line,
cairo_status_to_string (cairo_surface_status (l->surface)));
exit (1);
thread_die (thread);
}
cairo_surface_destroy (l->surface);
@ -386,8 +462,7 @@ _context_destroy (void *closure, void *ptr)
}
static void
execute (test_runner_thread_t *thread,
const char *trace)
execute (test_runner_thread_t *thread)
{
const cairo_script_interpreter_hooks_t hooks = {
.closure = thread,
@ -395,15 +470,21 @@ execute (test_runner_thread_t *thread,
.context_create = _context_create,
.context_destroy = _context_destroy,
};
pid_t ack;
thread->csi = cairo_script_interpreter_create ();
cairo_script_interpreter_install_hooks (thread->csi, &hooks);
cairo_script_interpreter_run (thread->csi, trace);
ack = -1;
readn (thread->sk, &ack, sizeof (ack));
if (ack != thread->pid)
thread_die (thread);
cairo_script_interpreter_run (thread->csi, thread->trace);
cairo_script_interpreter_finish (thread->csi);
if (cairo_script_interpreter_destroy (thread->csi))
exit (1);
thread_die (thread);
}
static int
@ -457,10 +538,14 @@ spawn_target (const char *socket_path,
test_runner_thread_t thread;
pid_t pid;
if (DEBUG)
printf ("Spawning slave '%s' for %s\n", target->name, trace);
pid = fork ();
if (pid != 0)
return pid;
thread.is_meta = FALSE;
thread.pid = getpid ();
thread.sk = spawn_socket (socket_path, thread.pid);
@ -480,6 +565,7 @@ spawn_target (const char *socket_path,
thread.target = target;
thread.contexts = NULL;
thread.context_id = 0;
thread.trace = trace;
thread.surface = target->create_surface (NULL,
target->content,
@ -495,7 +581,7 @@ spawn_target (const char *socket_path,
exit (-1);
}
execute (&thread, trace);
execute (&thread);
cairo_surface_destroy (thread.surface);
@ -508,6 +594,95 @@ spawn_target (const char *socket_path,
exit (0);
}
#if HAVE_PTHREAD_H
static void
cleanup_recorder (void *arg)
{
test_runner_thread_t *thread = arg;
cairo_surface_finish (thread->surface);
cairo_surface_destroy (thread->surface);
if (thread->target->cleanup)
thread->target->cleanup (thread->closure);
close (thread->sk);
free (thread);
}
static void *
record (void *arg)
{
test_runner_thread_t *thread = arg;
pthread_cleanup_push (cleanup_recorder, thread);
execute (thread);
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 cairo_boilerplate_target_t *target,
const char *trace)
{
test_runner_thread_t *thread;
pthread_t id;
pthread_attr_t attr;
pid_t pid = getpid ();
if (DEBUG)
printf ("Spawning recorder for %s\n", trace);
thread = malloc (sizeof (*thread));
if (thread == NULL)
return -1;
thread->is_meta = TRUE;
thread->pid = pid;
thread->sk = spawn_socket (socket_path, thread->pid);
if (thread->sk == -1) {
free (thread);
return -1;
}
thread->base = NULL;
thread->target = target;
thread->contexts = NULL;
thread->context_id = 0;
thread->trace = trace;
thread->surface = target->create_surface (NULL,
target->content,
1, 1,
1, 1,
CAIRO_BOILERPLATE_MODE_TEST,
0,
&thread->closure);
if (thread->surface == NULL) {
cleanup_recorder (thread);
return -1;
}
pthread_attr_init (&attr);
pthread_attr_setdetachstate (&attr, TRUE);
if (pthread_create (&id, &attr, record, thread) < 0) {
pthread_attr_destroy (&attr);
cleanup_recorder (thread);
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)
@ -681,7 +856,7 @@ write_images (const char *trace, struct slave *slave, int num_slaves)
if (slave->image != NULL) {
char *filename;
xasprintf (&filename, "%s-%s-out.png",
xasprintf (&filename, "%s-%s-fail.png",
trace, slave->target->name);
cairo_surface_write_to_png (slave->image, filename);
free (filename);
@ -698,28 +873,69 @@ write_images (const char *trace, struct slave *slave, int num_slaves)
}
}
static size_t
allocate_image_for_slave (uint8_t *base, size_t offset, struct slave *slave)
static void
write_trace (const char *trace, struct slave *slave)
{
#if CAIRO_HAS_SCRIPT_SURFACE
cairo_surface_t *script;
char *filename;
cairo_status_t status;
xasprintf (&filename, "%s-fail.trace", trace);
script = cairo_script_surface_create (filename,
slave->width,
slave->height);
free (filename);
status = _cairo_test_meta_surface_replay (slave->image, script);
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;
size = rq.height * rq.stride;
size = (size + 127) & -128;
data = base + offset;
offset += size;
assert (offset <= DATA_SIZE);
slave->width = rq.width;
slave->height = rq.height;
assert (slave->image == NULL);
slave->image = cairo_image_surface_create_for_data (data, rq.format,
rq.width, rq.height,
rq.stride);
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_meta) {
/* special communication with meta-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;
}
@ -742,9 +958,14 @@ test_run (void *base,
int npfd, cnt, n, i;
int completion;
cairo_bool_t ret = FALSE;
size_t image;
unsigned long image;
pfd = xcalloc (num_slaves+2, sizeof (*pfd));
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;
@ -761,13 +982,23 @@ test_run (void *base,
readn (fd, &pid, sizeof (pid));
for (n = 0; n < num_slaves; n++) {
if (slaves[n].pid == pid)
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--;
}
@ -798,7 +1029,7 @@ test_run (void *base,
* if satisfied, send an acknowledgement to all slaves.
*/
if (slaves[i].image_serial == 0) {
size_t offset;
unsigned long offset;
image =
allocate_image_for_slave (base,
@ -810,11 +1041,18 @@ test_run (void *base,
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'? */
cairo_surface_mark_dirty (slaves[i].image);
if (! slaves[i].is_meta)
cairo_surface_mark_dirty (slaves[i].image);
completion++;
}
@ -826,12 +1064,26 @@ test_run (void *base,
}
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_meta)
write_trace (trace, &slaves[0]);
goto out;
}
@ -840,6 +1092,10 @@ test_run (void *base,
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)))
@ -859,6 +1115,10 @@ 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);
@ -983,7 +1243,7 @@ _test_trace (test_runner_t *test,
struct error_info *error)
{
const char *shm_path = SHM_PATH_XXX;
const cairo_boilerplate_target_t *target, *image;
const cairo_boilerplate_target_t *target, *image, *meta;
struct slave *slaves, *s;
char socket_dir[] = "/tmp/cairo-test-trace.XXXXXX";
char *socket_path;
@ -1016,8 +1276,31 @@ _test_trace (test_runner_t *test,
image = cairo_boilerplate_get_image_target (CAIRO_CONTENT_COLOR_ALPHA);
assert (image != NULL);
/* spawn slave processes to run the trace */
s = slaves = xcalloc (2*test->num_targets + 1, sizeof (struct slave));
#if HAVE_PTHREAD_H
/* set-up a meta-surface to reconstruct errors */
meta = cairo_boilerplate_get_target_by_name ("test-meta",
CAIRO_CONTENT_COLOR_ALPHA);
if (meta != NULL) {
pid_t slave;
slave = spawn_recorder (socket_path, meta, trace);
if (slave < 0) {
fprintf (stderr, "Unable to create meta surface\n");
goto cleanup_sk;
}
s->pid = slave;
s->is_meta = TRUE;
s->target = meta;
s->fd = -1;
s->reference = NULL;
s++;
}
#endif
/* spawn slave processes to run the trace */
for (i = 0; i < test->num_targets; i++) {
pid_t slave;
const cairo_boilerplate_target_t *reference;
@ -1082,12 +1365,15 @@ cleanup:
close (fd);
while (s-- > slaves) {
int status;
if (s->is_meta) /* in-process */
continue;
kill (s->pid, SIGKILL);
waitpid (s->pid, &status, 0);
ret &= WIFEXITED (status) && WEXITSTATUS (status) == 0;
if (WIFSIGNALED (status) && WTERMSIG(status) != SIGKILL) {
if (WIFSIGNALED (status) && WTERMSIG(status) != SIGKILL)
fprintf (stderr, "%s crashed\n", s->target->name);
}
}
free (slaves);
shm_unlink (shm_path);
@ -1293,10 +1579,13 @@ parse_options (test_runner_t *test, int argc, char *argv[])
static void
test_reset (test_runner_t *test)
{
/* XXX leaking fonts again via meta-surface? */
#if 0
cairo_debug_reset_static_data ();
#if HAVE_FCFINI
FcFini ();
#endif
#endif
}
static void
@ -1406,9 +1695,7 @@ main (int argc, char *argv[])
if (test_has_filenames (&test)) {
for (n = 0; n < test.num_names; n++) {
if (test_can_run (&test, test.names[n]) &&
access (test.names[n], R_OK) == 0)
{
if (access (test.names[n], R_OK) == 0) {
test_trace (&test, test.names[n]);
test_reset (&test);
}