2 * Copyright © 2009 Chris Wilson
4 * Permission to use, copy, modify, distribute, and sell this software
5 * and its documentation for any purpose is hereby granted without
6 * fee, provided that the above copyright notice appear in all copies
7 * and that both that copyright notice and this permission notice
8 * appear in supporting documentation, and that the name of
9 * the authors not be used in advertising or publicity pertaining to
10 * distribution of the software without specific, written prior
11 * permission. The authors make no representations about the
12 * suitability of this software for any purpose. It is provided "as
13 * is" without express or implied warranty.
15 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
16 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
17 * FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL,
18 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
19 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
21 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * Authors: Chris Wilson <chris@chris-wilson.co.uk>
27 * The basic idea is that we feed the trace to multiple backends in parallel
28 * and compare the output at the end of each context (based on the premise
29 * that contexts demarcate expose events, or their logical equivalents) with
30 * that of the image[1] backend. Each backend is executed in a separate
31 * process, for robustness and to isolate the global cairo state, with the
32 * image data residing in shared memory and synchronising over a socket.
34 * [1] Should be reference implementation, currently the image backend is
35 * considered to be the reference for all other backends.
38 /* XXX Can't directly compare fills using spans versus trapezoidation,
39 * i.e. xlib vs image. Gah, kinda renders this whole scheme moot.
40 * How about reference platforms?
41 * E.g. accelerated xlib driver vs Xvfb?
43 * boilerplate->create_reference_surface()?
44 * boilerplate->reference->create_surface()?
45 * So for each backend spawn two processes, a reference and xlib
46 * (obviously minimising the number of reference processes when possible)
50 * XXX Handle show-page as well as cairo_destroy()? Though arguably that is
51 * only relevant for paginated backends which is currently outside the
55 #define _GNU_SOURCE 1 /* getline() */
57 #include "cairo-test.h"
58 #include "buffer-diff.h"
60 #include "cairo-boilerplate-getopt.h"
61 #include <cairo-script-interpreter.h>
62 #include "cairo-missing.h"
64 #if CAIRO_HAS_SCRIPT_SURFACE
65 #include <cairo-script.h>
72 #include <ctype.h> /* isspace() */
74 #include <sys/types.h>
80 #include <sys/socket.h>
86 #if CAIRO_HAS_REAL_PTHREAD
91 #include <fontconfig/fontconfig.h>
96 #define ignore_image_differences 0 /* XXX make me a cmdline option! */
97 #define write_results 1
98 #define write_traces 1
100 #define DATA_SIZE (256 << 20)
101 #define SHM_PATH_XXX "/.shmem-cairo-trace"
103 typedef struct _test_trace {
104 /* Options from command-line */
105 cairo_bool_t list_only;
107 unsigned int num_names;
108 char **exclude_names;
109 unsigned int num_exclude_names;
111 /* Stuff used internally */
112 const cairo_boilerplate_target_t **targets;
116 typedef struct _test_runner {
118 cairo_surface_t *surface;
124 cairo_bool_t is_recording;
126 cairo_script_interpreter_t *csi;
127 struct context_closure {
128 struct context_closure *next;
130 unsigned long start_line;
131 unsigned long end_line;
133 cairo_surface_t *surface;
136 unsigned long context_id;
142 unsigned long image_serial;
143 unsigned long image_ready;
144 unsigned long start_line;
145 unsigned long end_line;
146 cairo_surface_t *image;
148 cairo_surface_t *difference;
149 buffer_diff_result_t result;
150 const cairo_boilerplate_target_t *target;
151 const struct slave *reference;
152 cairo_bool_t is_recording;
155 struct request_image {
157 unsigned long start_line;
158 unsigned long end_line;
159 cairo_format_t format;
168 static const cairo_user_data_key_t surface_tag;
170 #define TARGET_NAME(T) ((T) ? (T)->name : "recording")
172 #if CAIRO_HAS_REAL_PTHREAD
173 #define tr_die(t) t->is_recording ? pthread_exit(NULL) : exit(1)
175 #define tr_die(t) exit(1)
179 writen (int fd, const void *ptr, int len)
182 const uint8_t *data = ptr;
184 int ret = write (fd, data, len);
193 } else if (ret == 0) {
202 int ret = send (fd, ptr, len, 0);
208 readn (int fd, void *ptr, int len)
213 int ret = read (fd, data, len);
222 } else if (ret == 0) {
231 int ret = recv (fd, ptr, len, MSG_WAITALL);
236 static cairo_format_t
237 format_for_content (cairo_content_t content)
240 case CAIRO_CONTENT_ALPHA:
241 return CAIRO_FORMAT_A8;
242 case CAIRO_CONTENT_COLOR:
243 return CAIRO_FORMAT_RGB24;
245 case CAIRO_CONTENT_COLOR_ALPHA:
246 return CAIRO_FORMAT_ARGB32;
251 send_recording_surface (test_runner_t *tr,
252 int width, int height,
253 struct context_closure *closure)
255 #if CAIRO_HAS_REAL_PTHREAD
256 const struct request_image rq = {
262 (long) closure->surface,
264 unsigned long offset;
265 unsigned long serial;
268 printf ("send-recording-surface: %lu [%lu, %lu]\n",
273 writen (tr->sk, &rq, sizeof (rq));
274 readn (tr->sk, &offset, sizeof (offset));
276 /* signal completion */
277 writen (tr->sk, &closure->id, sizeof (closure->id));
279 /* wait for image check */
281 readn (tr->sk, &serial, sizeof (serial));
283 printf ("send-recording-surface: serial: %lu\n", serial);
285 if (serial != closure->id)
293 request_image (test_runner_t *tr,
294 struct context_closure *closure,
295 cairo_format_t format,
296 int width, int height, int stride)
298 const struct request_image rq = {
302 format, width, height, stride
304 unsigned long offset = -1;
306 assert (format != (cairo_format_t) -1);
308 writen (tr->sk, &rq, sizeof (rq));
309 readn (tr->sk, &offset, sizeof (offset));
310 if (offset == (unsigned long) -1)
313 return tr->base + offset;
317 send_surface (test_runner_t *tr,
318 struct context_closure *closure)
320 cairo_surface_t *source = closure->surface;
321 cairo_surface_t *image;
322 cairo_format_t format = (cairo_format_t) -1;
324 int width, height, stride;
326 unsigned long serial;
329 printf ("send-surface: '%s', is-recording? %d\n",
330 tr->name, tr->is_recording);
333 if (cairo_surface_get_type (source) == CAIRO_SURFACE_TYPE_IMAGE) {
334 width = cairo_image_surface_get_width (source);
335 height = cairo_image_surface_get_height (source);
336 format = cairo_image_surface_get_format (source);
338 struct surface_tag *tag;
340 tag = cairo_surface_get_user_data (source, &surface_tag);
343 height = tag->height;
345 double x0, x1, y0, y1;
347 /* presumably created using cairo_surface_create_similar() */
348 cr = cairo_create (source);
349 cairo_clip_extents (cr, &x0, &y0, &x1, &y1);
352 tag = xmalloc (sizeof (*tag));
353 width = tag->width = x1 - x0;
354 height = tag->height = y1 - y0;
356 if (cairo_surface_set_user_data (source, &surface_tag, tag, free))
361 if (tr->is_recording) {
362 send_recording_surface (tr, width, height, closure);
366 if (format == (cairo_format_t) -1)
367 format = format_for_content (cairo_surface_get_content (source));
369 stride = cairo_format_stride_for_width (format, width);
371 data = request_image (tr, closure, format, width, height, stride);
375 image = cairo_image_surface_create_for_data (data,
379 cr = cairo_create (image);
380 cairo_surface_destroy (image);
382 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
383 cairo_set_source_surface (cr, source, 0, 0);
387 /* signal completion */
388 writen (tr->sk, &closure->id, sizeof (closure->id));
390 /* wait for image check */
392 readn (tr->sk, &serial, sizeof (serial));
393 if (serial != closure->id)
397 static cairo_surface_t *
398 _surface_create (void *closure,
399 cairo_content_t content,
400 double width, double height,
403 test_runner_t *tr = closure;
404 cairo_surface_t *surface;
406 surface = cairo_surface_create_similar (tr->surface,
407 content, width, height);
408 if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE) {
409 struct surface_tag *tag;
411 tag = xmalloc (sizeof (*tag));
413 tag->height = height;
414 if (cairo_surface_set_user_data (surface, &surface_tag, tag, free))
422 _context_create (void *closure, cairo_surface_t *surface)
424 test_runner_t *tr = closure;
425 struct context_closure *l;
428 fprintf (stderr, "%s: starting context %lu on line %d\n",
429 tr->name ? tr->name : "recording" ,
431 cairo_script_interpreter_get_line_number (tr->csi));
434 l = xmalloc (sizeof (*l));
435 l->next = tr->contexts;
436 l->start_line = cairo_script_interpreter_get_line_number (tr->csi);
437 l->end_line = l->start_line;
438 l->context = cairo_create (surface);
439 l->surface = cairo_surface_reference (surface);
440 l->id = ++tr->context_id;
442 l->id = ++tr->context_id;
449 _context_destroy (void *closure, void *ptr)
451 test_runner_t *tr = closure;
452 struct context_closure *l, **prev = &tr->contexts;
454 while ((l = *prev) != NULL) {
455 if (l->context == ptr) {
457 fprintf (stderr, "%s: context %lu complete on line %d\n",
458 tr->name ? tr->name : "recording" ,
460 cairo_script_interpreter_get_line_number (tr->csi));
463 cairo_script_interpreter_get_line_number (tr->csi);
464 if (cairo_surface_status (l->surface) == CAIRO_STATUS_SUCCESS) {
465 send_surface (tr, l);
467 fprintf (stderr, "%s: error during replay, line %lu: %s!\n",
470 cairo_status_to_string (cairo_surface_status (l->surface)));
474 cairo_surface_destroy (l->surface);
484 execute (test_runner_t *tr)
486 const cairo_script_interpreter_hooks_t hooks = {
488 .surface_create = _surface_create,
489 .context_create = _context_create,
490 .context_destroy = _context_destroy,
494 tr->csi = cairo_script_interpreter_create ();
495 cairo_script_interpreter_install_hooks (tr->csi, &hooks);
498 readn (tr->sk, &ack, sizeof (ack));
502 cairo_script_interpreter_run (tr->csi, tr->trace);
504 cairo_script_interpreter_finish (tr->csi);
505 if (cairo_script_interpreter_destroy (tr->csi))
510 spawn_socket (const char *socket_path, pid_t pid)
512 struct sockaddr_un addr;
515 sk = socket (PF_UNIX, SOCK_STREAM, 0);
519 memset (&addr, 0, sizeof (addr));
520 addr.sun_family = AF_UNIX;
521 strcpy (addr.sun_path, socket_path);
523 if (connect (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1)
526 if (! writen (sk, &pid, sizeof (pid)))
533 spawn_shm (const char *shm_path)
538 fd = shm_open (shm_path, O_RDWR, 0);
542 base = mmap (NULL, DATA_SIZE,
543 PROT_READ | PROT_WRITE,
544 MAP_SHARED | MAP_NORESERVE,
552 spawn_target (const char *socket_path,
553 const char *shm_path,
554 const cairo_boilerplate_target_t *target,
561 printf ("Spawning slave '%s' for %s\n", target->name, trace);
567 tr.is_recording = FALSE;
570 tr.sk = spawn_socket (socket_path, tr.pid);
572 fprintf (stderr, "%s: Failed to open socket.\n",
577 tr.base = spawn_shm (shm_path);
578 if (tr.base == MAP_FAILED) {
579 fprintf (stderr, "%s: Failed to map shared memory segment.\n",
584 tr.name = target->name;
589 tr.surface = target->create_surface (NULL,
593 CAIRO_BOILERPLATE_MODE_TEST,
595 if (tr.surface == NULL) {
597 "%s: Failed to create target surface.\n",
604 cairo_surface_destroy (tr.surface);
607 target->cleanup (tr.closure);
610 munmap (tr.base, DATA_SIZE);
615 #if CAIRO_HAS_REAL_PTHREAD
617 cleanup_recorder (void *arg)
619 test_runner_t *tr = arg;
621 cairo_surface_finish (tr->surface);
622 cairo_surface_destroy (tr->surface);
631 test_runner_t *tr = arg;
633 pthread_cleanup_push (cleanup_recorder, tr);
635 pthread_cleanup_pop (TRUE);
640 /* The recorder is special:
641 * 1. It doesn't generate an image, but keeps an in-memory trace to
642 * reconstruct any surface.
643 * 2. Runs in the same process, but separate thread.
646 spawn_recorder (const char *socket_path, const char *trace, test_runner_t **out)
651 pid_t pid = getpid ();
654 printf ("Spawning recorder for %s\n", trace);
656 tr = malloc (sizeof (*tr));
660 tr->is_recording = TRUE;
662 tr->sk = spawn_socket (socket_path, tr->pid);
674 tr->surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
676 if (tr->surface == NULL) {
677 cleanup_recorder (tr);
681 pthread_attr_init (&attr);
682 pthread_attr_setdetachstate (&attr, TRUE);
683 if (pthread_create (&id, &attr, record, tr) < 0) {
684 pthread_attr_destroy (&attr);
685 cleanup_recorder (tr);
688 pthread_attr_destroy (&attr);
696 /* XXX imagediff - is the extra expense worth it? */
698 matches_reference (struct slave *slave)
700 cairo_surface_t *a, *b;
703 b = slave->reference->image;
708 if (a == NULL || b == NULL)
711 if (cairo_surface_status (a) || cairo_surface_status (b))
714 if (cairo_surface_get_type (a) != cairo_surface_get_type (b))
717 if (cairo_image_surface_get_format (a) != cairo_image_surface_get_format (b))
720 if (cairo_image_surface_get_width (a) != cairo_image_surface_get_width (b))
723 if (cairo_image_surface_get_height (a) != cairo_image_surface_get_height (b))
726 if (cairo_image_surface_get_stride (a) != cairo_image_surface_get_stride (b))
729 if (FALSE && cairo_surface_get_content (a) & CAIRO_CONTENT_COLOR) {
730 cairo_surface_t *diff;
731 int width, height, stride, size;
733 cairo_status_t status;
735 width = cairo_image_surface_get_width (a);
736 height = cairo_image_surface_get_height (a);
737 stride = cairo_image_surface_get_stride (a);
738 size = height * stride * 4;
739 data = malloc (size);
743 diff = cairo_image_surface_create_for_data (data,
744 cairo_image_surface_get_format (a),
745 width, height, stride);
746 cairo_surface_set_user_data (diff, (cairo_user_data_key_t *) diff,
749 status = image_diff (NULL, a, b, diff, &slave->result);
751 cairo_surface_destroy (diff);
755 if (image_diff_is_failure (&slave->result, slave->target->error_tolerance)) {
756 slave->difference = diff;
759 cairo_surface_destroy (diff);
763 int width, height, stride;
764 const uint8_t *aa, *bb;
767 width = cairo_image_surface_get_width (a);
768 height = cairo_image_surface_get_height (a);
769 stride = cairo_image_surface_get_stride (a);
771 aa = cairo_image_surface_get_data (a);
772 bb = cairo_image_surface_get_data (b);
773 switch (cairo_image_surface_get_format (a)) {
774 case CAIRO_FORMAT_ARGB32:
775 for (y = 0; y < height; y++) {
776 const uint32_t *ua = (uint32_t *) aa;
777 const uint32_t *ub = (uint32_t *) bb;
778 for (x = 0; x < width; x++) {
779 if (ua[x] != ub[x]) {
782 for (channel = 0; channel < 4; channel++) {
783 unsigned va, vb, diff;
785 va = (ua[x] >> (channel*8)) & 0xff;
786 vb = (ub[x] >> (channel*8)) & 0xff;
787 diff = abs (va - vb);
788 if (diff > slave->target->error_tolerance)
798 case CAIRO_FORMAT_RGB24:
799 for (y = 0; y < height; y++) {
800 const uint32_t *ua = (uint32_t *) aa;
801 const uint32_t *ub = (uint32_t *) bb;
802 for (x = 0; x < width; x++) {
803 if ((ua[x] & 0x00ffffff) != (ub[x] & 0x00ffffff)) {
806 for (channel = 0; channel < 3; channel++) {
807 unsigned va, vb, diff;
809 va = (ua[x] >> (channel*8)) & 0xff;
810 vb = (ub[x] >> (channel*8)) & 0xff;
811 diff = abs (va - vb);
812 if (diff > slave->target->error_tolerance)
822 case CAIRO_FORMAT_A8:
823 for (y = 0; y < height; y++) {
824 for (x = 0; x < width; x++) {
825 if (aa[x] != bb[x]) {
826 unsigned diff = abs (aa[x] - bb[x]);
827 if (diff > slave->target->error_tolerance)
836 case CAIRO_FORMAT_A1:
838 for (y = 0; y < height; y++) {
839 if (memcmp (aa, bb, width))
846 case CAIRO_FORMAT_RGB30:
847 case CAIRO_FORMAT_RGB16_565:
848 case CAIRO_FORMAT_INVALID:
857 check_images (struct slave *slaves, int num_slaves)
861 if (ignore_image_differences)
864 for (n = 0; n < num_slaves; n++) {
865 if (slaves[n].reference == NULL)
868 if (! matches_reference (&slaves[n]))
876 write_images (const char *trace, struct slave *slave, int num_slaves)
878 while (num_slaves--) {
879 if (slave->image != NULL && ! slave->is_recording) {
882 xasprintf (&filename, "%s-%s-fail.png",
883 trace, slave->target->name);
884 cairo_surface_write_to_png (slave->image, filename);
887 if (slave->difference) {
888 xasprintf (&filename, "%s-%s-diff.png",
889 trace, slave->target->name);
890 cairo_surface_write_to_png (slave->difference, filename);
900 write_result (const char *trace, struct slave *slave)
905 xasprintf (&filename, "%s-%s-pass-%d-%d-%d.png",
906 trace, slave->target->name, ++index,
907 slave->start_line, slave->end_line);
908 cairo_surface_write_to_png (slave->image, filename);
913 write_trace (const char *trace, const char *id, struct slave *slave)
915 #if CAIRO_HAS_SCRIPT_SURFACE
916 cairo_device_t *script;
919 assert (slave->is_recording);
921 xasprintf (&filename, "%s-%s.trace", trace, id);
923 script = cairo_script_create (filename);
924 cairo_script_from_recording_surface (script, slave->image);
925 cairo_device_destroy (script);
932 dump_traces (test_runner_t *tr,
937 #if CAIRO_HAS_SCRIPT_SURFACE
938 struct context_closure *c;
940 for (c = tr->contexts; c; c = c->next) {
941 cairo_device_t *script;
944 xasprintf (&filename, "%s-%s-%s.%lu.trace",
945 trace, target, fail, c->start_line);
947 script = cairo_script_create (filename);
948 cairo_script_from_recording_surface (script, c->surface);
949 cairo_device_destroy (script);
957 allocate_image_for_slave (uint8_t *base,
958 unsigned long offset,
961 struct request_image rq;
965 assert (slave->image == NULL);
967 readn (slave->fd, &rq, sizeof (rq));
968 slave->image_serial = rq.id;
969 slave->start_line = rq.start_line;
970 slave->end_line = rq.end_line;
972 slave->width = rq.width;
973 slave->height = rq.height;
976 printf ("allocate-image-for-slave: %s %lu [%lu, %lu] %ldx%ld stride=%lu => %lu, is-recording? %d\n",
977 TARGET_NAME (slave->target),
985 slave->is_recording);
988 if (slave->is_recording) {
989 /* special communication with recording-surface thread */
990 slave->image = cairo_surface_reference ((cairo_surface_t *) rq.stride);
992 size = rq.height * rq.stride;
993 size = (size + 4095) & -4096;
994 data = base + offset;
996 assert (offset <= DATA_SIZE);
998 slave->image = cairo_image_surface_create_for_data (data, rq.format,
1007 unsigned long context_id;
1008 unsigned long start_line;
1009 unsigned long end_line;
1013 test_run (void *base,
1016 struct slave *slaves,
1018 struct error_info *error)
1021 int npfd, cnt, n, i;
1022 int completion, err = 0;
1023 cairo_bool_t ret = FALSE;
1024 unsigned long image;
1027 printf ("Running trace '%s' over %d slaves\n",
1031 pfd = xcalloc (num_slaves+1, sizeof (*pfd));
1034 pfd[0].events = POLLIN;
1039 while ((cnt = poll (pfd, npfd, -1)) > 0) {
1040 if (pfd[0].revents) {
1043 while ((fd = accept (sk, NULL, NULL)) != -1) {
1046 readn (fd, &pid, sizeof (pid));
1047 for (n = 0; n < num_slaves; n++) {
1048 if (slaves[n].pid == pid) {
1053 if (n == num_slaves) {
1055 printf ("unknown slave pid\n");
1060 pfd[npfd].events = POLLIN;
1063 if (! writen (fd, &pid, sizeof (pid)))
1069 for (n = 1; n < npfd && cnt; n++) {
1070 if (! pfd[n].revents)
1073 if (pfd[n].revents & POLLHUP) {
1074 pfd[n].events = pfd[n].revents = 0;
1079 for (i = 0; i < num_slaves; i++) {
1080 if (slaves[i].fd == pfd[n].fd) {
1081 /* Communication with the slave is done in three phases,
1082 * and we do each pass synchronously.
1084 * 1. The slave requests an image buffer, which we
1085 * allocate and then return to the slave the offset into
1086 * the shared memory segment.
1088 * 2. The slave indicates that it has finished writing
1089 * into the shared image buffer. The slave now waits
1090 * for the server to collate all the image data - thereby
1091 * throttling the slaves.
1093 * 3. After all slaves have finished writing their images,
1094 * we compare them all against the reference image and,
1095 * if satisfied, send an acknowledgement to all slaves.
1097 if (slaves[i].image_serial == 0) {
1098 unsigned long offset;
1101 allocate_image_for_slave (base,
1104 if (! writen (pfd[n].fd, &offset, sizeof (offset))) {
1105 pfd[n].events = pfd[n].revents = 0;
1112 &slaves[i].image_ready,
1113 sizeof (slaves[i].image_ready));
1115 printf ("slave '%s' reports completion on %lu (expecting %lu)\n",
1116 TARGET_NAME (slaves[i].target),
1117 slaves[i].image_ready,
1118 slaves[i].image_serial);
1120 if (slaves[i].image_ready != slaves[i].image_serial) {
1121 pfd[n].events = pfd[n].revents = 0;
1127 /* Can anyone spell 'P·E·D·A·N·T'? */
1128 if (! slaves[i].is_recording)
1129 cairo_surface_mark_dirty (slaves[i].image);
1140 if (completion >= num_slaves) {
1143 printf ("error detected\n");
1148 printf ("all saves report completion\n");
1150 if (slaves[0].end_line >= slaves[0].start_line &&
1151 ! check_images (slaves, num_slaves)) {
1152 error->context_id = slaves[0].image_serial;
1153 error->start_line = slaves[0].start_line;
1154 error->end_line = slaves[0].end_line;
1157 printf ("check_images failed: %lu, [%lu, %lu]\n",
1158 slaves[0].image_serial,
1159 slaves[0].start_line,
1160 slaves[0].end_line);
1163 write_images (trace, slaves, num_slaves);
1165 if (slaves[0].is_recording)
1166 write_trace (trace, "fail", &slaves[0]);
1171 if (write_results) write_result (trace, &slaves[1]);
1172 if (write_traces && slaves[0].is_recording) {
1174 snprintf (buf, sizeof (buf), "%d", slaves[0].image_serial);
1175 write_trace (trace, buf, &slaves[0]);
1179 for (i = 0; i < num_slaves; i++) {
1180 cairo_surface_destroy (slaves[i].image);
1181 slaves[i].image = NULL;
1184 printf ("sending continuation to '%s'\n",
1185 TARGET_NAME (slaves[i].target));
1187 if (! writen (slaves[i].fd,
1188 &slaves[i].image_serial,
1189 sizeof (slaves[i].image_serial)))
1194 slaves[i].image_serial = 0;
1195 slaves[i].image_ready = 0;
1207 printf ("run complete: %d\n", ret);
1210 for (n = 0; n < num_slaves; n++) {
1211 if (slaves[n].fd != -1)
1212 close (slaves[n].fd);
1214 if (slaves[n].image == NULL)
1217 cairo_surface_destroy (slaves[n].image);
1218 slaves[n].image = NULL;
1220 cairo_surface_destroy (slaves[n].difference);
1221 slaves[n].difference = NULL;
1223 slaves[n].image_serial = 0;
1224 slaves[n].image_ready = 0;
1233 server_socket (const char *socket_path)
1236 struct sockaddr_un addr;
1239 sk = socket (PF_UNIX, SOCK_STREAM, 0);
1243 memset (&addr, 0, sizeof (addr));
1244 addr.sun_family = AF_UNIX;
1245 strcpy (addr.sun_path, socket_path);
1246 if (bind (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) {
1251 flags = fcntl (sk, F_GETFL);
1252 if (flags == -1 || fcntl (sk, F_SETFL, flags | O_NONBLOCK) == -1) {
1257 if (listen (sk, 5) == -1) {
1266 server_shm (const char *shm_path)
1270 fd = shm_open (shm_path, O_RDWR | O_EXCL | O_CREAT, 0777);
1274 if (ftruncate (fd, DATA_SIZE) == -1) {
1283 _test_trace (test_trace_t *test,
1286 struct error_info *error)
1288 const char *shm_path = SHM_PATH_XXX;
1289 const cairo_boilerplate_target_t *target, *image;
1290 struct slave *slaves, *s;
1291 test_runner_t *recorder = NULL;
1293 char socket_dir[] = "/tmp/cairo-test-trace.XXXXXX";
1298 cairo_bool_t ret = FALSE;
1301 printf ("setting up trace '%s'\n", trace);
1303 /* create a socket to control the test runners */
1304 if (mkdtemp (socket_dir) == NULL) {
1305 fprintf (stderr, "Unable to create temporary name for socket\n");
1309 xasprintf (&socket_path, "%s/socket", socket_dir);
1310 sk = server_socket (socket_path);
1312 fprintf (stderr, "Unable to create socket for server\n");
1316 /* allocate some shared memory */
1317 fd = server_shm (shm_path);
1319 fprintf (stderr, "Unable to create shared memory '%s': %s\n",
1320 shm_path, strerror (errno));
1324 image = cairo_boilerplate_get_image_target (CAIRO_CONTENT_COLOR_ALPHA);
1325 assert (image != NULL);
1327 s = slaves = xcalloc (2*test->num_targets + 1, sizeof (struct slave));
1329 #if CAIRO_HAS_REAL_PTHREAD
1330 /* set-up a recording-surface to reconstruct errors */
1331 slave = spawn_recorder (socket_path, trace, &recorder);
1333 fprintf (stderr, "Unable to create recording surface\n");
1338 s->is_recording = TRUE;
1341 s->reference = NULL;
1345 /* spawn slave processes to run the trace */
1346 for (i = 0; i < test->num_targets; i++) {
1347 const cairo_boilerplate_target_t *reference;
1348 struct slave *master;
1350 target = test->targets[i];
1353 printf ("setting up target[%d]? '%s' (image? %d, measurable? %d)\n",
1354 i, target->name, target == image, target->is_measurable);
1356 if (target == image || ! target->is_measurable)
1359 /* find a matching slave to use as a reference for this target */
1360 if (target->reference_target != NULL) {
1362 cairo_boilerplate_get_target_by_name (target->reference_target,
1364 assert (reference != NULL);
1368 for (master = slaves; master < s; master++) {
1369 if (master->target == reference)
1374 /* no match found, spawn a slave to render the reference image */
1375 slave = spawn_target (socket_path, shm_path, reference, trace);
1380 s->target = reference;
1382 s->reference = NULL;
1386 slave = spawn_target (socket_path, shm_path, target, trace);
1393 s->reference = master;
1396 num_slaves = s - slaves;
1397 if (num_slaves == 1) {
1398 fprintf (stderr, "No targets to test\n");
1402 base = mmap (NULL, DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1403 if (base == MAP_FAILED) {
1404 fprintf (stderr, "Unable to mmap shared memory\n");
1407 ret = test_run (base, sk, name, slaves, num_slaves, error);
1408 munmap (base, DATA_SIZE);
1412 while (s-- > slaves) {
1418 cairo_surface_destroy (s->image);
1419 cairo_surface_destroy (s->difference);
1421 if (s->is_recording) /* in-process */
1424 kill (s->pid, SIGKILL);
1425 waitpid (s->pid, &status, 0);
1426 if (WIFSIGNALED (status) && WTERMSIG(status) != SIGKILL) {
1427 fprintf (stderr, "%s crashed\n", s->target->name);
1429 dump_traces (recorder, trace, s->target->name, "crash");
1433 shm_unlink (shm_path);
1438 remove (socket_path);
1439 remove (socket_dir);
1446 test_trace (test_trace_t *test, const char *trace)
1448 char *trace_cpy, *name, *dot;
1450 trace_cpy = xstrdup (trace);
1451 name = basename (trace_cpy);
1452 dot = strchr (name, '.');
1456 if (test->list_only) {
1457 printf ("%s\n", name);
1459 struct error_info error = {0};
1462 printf ("%s: ", name);
1465 ret = _test_trace (test, trace, name, &error);
1469 if (error.context_id) {
1470 printf ("FAIL (context %lu, lines [%lu, %lu])\n",
1484 read_excludes (test_trace_t *test, const char *filename)
1488 size_t line_size = 0;
1491 file = fopen (filename, "r");
1495 while (getline (&line, &line_size, file) != -1) {
1496 /* terminate the line at a comment marker '#' */
1497 s = strchr (line, '#');
1501 /* whitespace delimits */
1503 while (*s != '\0' && isspace (*s))
1507 while (*t != '\0' && ! isspace (*t))
1511 int i = test->num_exclude_names;
1512 test->exclude_names = xrealloc (test->exclude_names,
1513 sizeof (char *) * (i+1));
1514 test->exclude_names[i] = strndup (s, t-s);
1515 test->num_exclude_names++;
1526 usage (const char *argv0)
1529 "Usage: %s [-l] [-x exclude-file] [test-names ... | traces ...]\n"
1531 "Run the cairo test suite over the given traces (all by default).\n"
1532 "The command-line arguments are interpreted as follows:\n"
1534 " -l list only; just list selected test case names without executing\n"
1535 " -x exclude; specify a file to read a list of traces to exclude\n"
1537 "If test names are given they are used as sub-string matches so a command\n"
1538 "such as \"%s firefox\" can be used to run all firefox traces.\n"
1539 "Alternatively, you can specify a list of filenames to execute.\n",
1544 parse_options (test_trace_t *test, int argc, char *argv[])
1548 test->list_only = FALSE;
1550 test->num_names = 0;
1551 test->exclude_names = NULL;
1552 test->num_exclude_names = 0;
1555 c = _cairo_getopt (argc, argv, "lx:");
1561 test->list_only = TRUE;
1564 if (! read_excludes (test, optarg)) {
1565 fprintf (stderr, "Invalid argument for -x (not readable file): %s\n",
1571 fprintf (stderr, "Internal error: unhandled option: %c\n", c);
1579 if (optind < argc) {
1580 test->names = &argv[optind];
1581 test->num_names = argc - optind;
1586 test_reset (test_trace_t *test)
1588 /* XXX leaking fonts again via recording-surface? */
1590 cairo_debug_reset_static_data ();
1598 test_fini (test_trace_t *test)
1602 cairo_boilerplate_free_targets (test->targets);
1603 free (test->exclude_names);
1607 test_has_filenames (test_trace_t *test)
1611 if (test->num_names == 0)
1614 for (i = 0; i < test->num_names; i++)
1615 if (access (test->names[i], R_OK) == 0)
1622 test_can_run (test_trace_t *test, const char *name)
1628 if (test->num_names == 0 && test->num_exclude_names == 0)
1631 copy = xstrdup (name);
1632 dot = strrchr (copy, '.');
1636 if (test->num_names) {
1638 for (i = 0; i < test->num_names; i++)
1639 if (strstr (copy, test->names[i]))
1647 if (test->num_exclude_names) {
1649 for (i = 0; i < test->num_exclude_names; i++)
1650 if (strstr (copy, test->exclude_names[i]))
1664 warn_no_traces (const char *message, const char *trace_dir)
1668 "Have you cloned the cairo-traces repository and uncompressed the traces?\n"
1669 " git clone git://anongit.freedesktop.org/cairo-traces\n"
1670 " cd cairo-traces && make\n"
1671 "Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n",
1672 message, trace_dir);
1678 shm_unlink (SHM_PATH_XXX);
1680 signal (sig, SIG_DFL);
1685 main (int argc, char *argv[])
1688 const char *trace_dir = "cairo-traces";
1691 signal (SIGPIPE, SIG_IGN);
1692 signal (SIGINT, interrupt);
1694 parse_options (&test, argc, argv);
1696 shm_unlink (SHM_PATH_XXX);
1698 if (getenv ("CAIRO_TRACE_DIR") != NULL)
1699 trace_dir = getenv ("CAIRO_TRACE_DIR");
1701 test.targets = cairo_boilerplate_get_targets (&test.num_targets, NULL);
1703 if (test_has_filenames (&test)) {
1704 for (n = 0; n < test.num_names; n++) {
1705 if (access (test.names[n], R_OK) == 0) {
1706 test_trace (&test, test.names[n]);
1715 dir = opendir (trace_dir);
1717 warn_no_traces ("Failed to open directory", trace_dir);
1722 while ((de = readdir (dir)) != NULL) {
1726 dot = strrchr (de->d_name, '.');
1729 if (strcmp (dot, ".trace"))
1733 if (! test_can_run (&test, de->d_name))
1736 xasprintf (&trace, "%s/%s", trace_dir, de->d_name);
1737 test_trace (&test, trace);
1745 if (num_traces == 0) {
1746 warn_no_traces ("Found no traces in", trace_dir);
1758 cairo_test_logv (const cairo_test_context_t *ctx,
1759 const char *fmt, va_list va)
1762 vfprintf (stderr, fmt, va);
1767 cairo_test_log (const cairo_test_context_t *ctx, const char *fmt, ...)
1773 vfprintf (stderr, fmt, va);