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>
95 #define MAP_NORESERVE 0
100 #define ignore_image_differences 0 /* XXX make me a cmdline option! */
101 #define write_results 1
102 #define write_traces 1
104 #define DATA_SIZE (256 << 20)
105 #define SHM_PATH_XXX "/.shmem-cairo-trace"
107 typedef struct _test_trace {
108 /* Options from command-line */
109 cairo_bool_t list_only;
111 unsigned int num_names;
112 char **exclude_names;
113 unsigned int num_exclude_names;
115 /* Stuff used internally */
116 const cairo_boilerplate_target_t **targets;
120 typedef struct _test_runner {
122 cairo_surface_t *surface;
128 cairo_bool_t is_recording;
130 cairo_script_interpreter_t *csi;
131 struct context_closure {
132 struct context_closure *next;
134 unsigned long start_line;
135 unsigned long end_line;
137 cairo_surface_t *surface;
140 unsigned long context_id;
146 unsigned long image_serial;
147 unsigned long image_ready;
148 unsigned long start_line;
149 unsigned long end_line;
150 cairo_surface_t *image;
152 cairo_surface_t *difference;
153 buffer_diff_result_t result;
154 const cairo_boilerplate_target_t *target;
155 const struct slave *reference;
156 cairo_bool_t is_recording;
159 struct request_image {
161 unsigned long start_line;
162 unsigned long end_line;
163 cairo_format_t format;
172 static const cairo_user_data_key_t surface_tag;
174 #define TARGET_NAME(T) ((T) ? (T)->name : "recording")
176 #if CAIRO_HAS_REAL_PTHREAD
177 #define tr_die(t) t->is_recording ? pthread_exit(NULL) : exit(1)
179 #define tr_die(t) exit(1)
183 writen (int fd, const void *ptr, int len)
186 const uint8_t *data = ptr;
188 int ret = write (fd, data, len);
197 } else if (ret == 0) {
206 int ret = send (fd, ptr, len, 0);
212 readn (int fd, void *ptr, int len)
217 int ret = read (fd, data, len);
226 } else if (ret == 0) {
235 int ret = recv (fd, ptr, len, MSG_WAITALL);
240 static cairo_format_t
241 format_for_content (cairo_content_t content)
244 case CAIRO_CONTENT_ALPHA:
245 return CAIRO_FORMAT_A8;
246 case CAIRO_CONTENT_COLOR:
247 return CAIRO_FORMAT_RGB24;
249 case CAIRO_CONTENT_COLOR_ALPHA:
250 return CAIRO_FORMAT_ARGB32;
255 send_recording_surface (test_runner_t *tr,
256 int width, int height,
257 struct context_closure *closure)
259 #if CAIRO_HAS_REAL_PTHREAD
260 const struct request_image rq = {
266 (long) closure->surface,
268 unsigned long offset;
269 unsigned long serial;
272 printf ("send-recording-surface: %lu [%lu, %lu]\n",
277 writen (tr->sk, &rq, sizeof (rq));
278 readn (tr->sk, &offset, sizeof (offset));
280 /* signal completion */
281 writen (tr->sk, &closure->id, sizeof (closure->id));
283 /* wait for image check */
285 readn (tr->sk, &serial, sizeof (serial));
287 printf ("send-recording-surface: serial: %lu\n", serial);
289 if (serial != closure->id)
297 request_image (test_runner_t *tr,
298 struct context_closure *closure,
299 cairo_format_t format,
300 int width, int height, int stride)
302 const struct request_image rq = {
306 format, width, height, stride
308 unsigned long offset = -1;
310 assert (format != (cairo_format_t) -1);
312 writen (tr->sk, &rq, sizeof (rq));
313 readn (tr->sk, &offset, sizeof (offset));
314 if (offset == (unsigned long) -1)
317 return tr->base + offset;
321 send_surface (test_runner_t *tr,
322 struct context_closure *closure)
324 cairo_surface_t *source = closure->surface;
325 cairo_surface_t *image;
326 cairo_format_t format = (cairo_format_t) -1;
328 int width, height, stride;
330 unsigned long serial;
333 printf ("send-surface: '%s', is-recording? %d\n",
334 tr->name, tr->is_recording);
337 if (cairo_surface_get_type (source) == CAIRO_SURFACE_TYPE_IMAGE) {
338 width = cairo_image_surface_get_width (source);
339 height = cairo_image_surface_get_height (source);
340 format = cairo_image_surface_get_format (source);
342 struct surface_tag *tag;
344 tag = cairo_surface_get_user_data (source, &surface_tag);
347 height = tag->height;
349 double x0, x1, y0, y1;
351 /* presumably created using cairo_surface_create_similar() */
352 cr = cairo_create (source);
353 cairo_clip_extents (cr, &x0, &y0, &x1, &y1);
356 tag = xmalloc (sizeof (*tag));
357 width = tag->width = x1 - x0;
358 height = tag->height = y1 - y0;
360 if (cairo_surface_set_user_data (source, &surface_tag, tag, free))
365 if (tr->is_recording) {
366 send_recording_surface (tr, width, height, closure);
370 if (format == (cairo_format_t) -1)
371 format = format_for_content (cairo_surface_get_content (source));
373 stride = cairo_format_stride_for_width (format, width);
375 data = request_image (tr, closure, format, width, height, stride);
379 image = cairo_image_surface_create_for_data (data,
383 cr = cairo_create (image);
384 cairo_surface_destroy (image);
386 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
387 cairo_set_source_surface (cr, source, 0, 0);
391 /* signal completion */
392 writen (tr->sk, &closure->id, sizeof (closure->id));
394 /* wait for image check */
396 readn (tr->sk, &serial, sizeof (serial));
397 if (serial != closure->id)
401 static cairo_surface_t *
402 _surface_create (void *closure,
403 cairo_content_t content,
404 double width, double height,
407 test_runner_t *tr = closure;
408 cairo_surface_t *surface;
410 surface = cairo_surface_create_similar (tr->surface,
411 content, width, height);
412 if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE) {
413 struct surface_tag *tag;
415 tag = xmalloc (sizeof (*tag));
417 tag->height = height;
418 if (cairo_surface_set_user_data (surface, &surface_tag, tag, free))
426 _context_create (void *closure, cairo_surface_t *surface)
428 test_runner_t *tr = closure;
429 struct context_closure *l;
432 fprintf (stderr, "%s: starting context %lu on line %d\n",
433 tr->name ? tr->name : "recording" ,
435 cairo_script_interpreter_get_line_number (tr->csi));
438 l = xmalloc (sizeof (*l));
439 l->next = tr->contexts;
440 l->start_line = cairo_script_interpreter_get_line_number (tr->csi);
441 l->end_line = l->start_line;
442 l->context = cairo_create (surface);
443 l->surface = cairo_surface_reference (surface);
444 l->id = ++tr->context_id;
446 l->id = ++tr->context_id;
453 _context_destroy (void *closure, void *ptr)
455 test_runner_t *tr = closure;
456 struct context_closure *l, **prev = &tr->contexts;
458 while ((l = *prev) != NULL) {
459 if (l->context == ptr) {
461 fprintf (stderr, "%s: context %lu complete on line %d\n",
462 tr->name ? tr->name : "recording" ,
464 cairo_script_interpreter_get_line_number (tr->csi));
467 cairo_script_interpreter_get_line_number (tr->csi);
468 if (cairo_surface_status (l->surface) == CAIRO_STATUS_SUCCESS) {
469 send_surface (tr, l);
471 fprintf (stderr, "%s: error during replay, line %lu: %s!\n",
474 cairo_status_to_string (cairo_surface_status (l->surface)));
478 cairo_surface_destroy (l->surface);
488 execute (test_runner_t *tr)
490 const cairo_script_interpreter_hooks_t hooks = {
492 .surface_create = _surface_create,
493 .context_create = _context_create,
494 .context_destroy = _context_destroy,
498 tr->csi = cairo_script_interpreter_create ();
499 cairo_script_interpreter_install_hooks (tr->csi, &hooks);
502 readn (tr->sk, &ack, sizeof (ack));
506 cairo_script_interpreter_run (tr->csi, tr->trace);
508 cairo_script_interpreter_finish (tr->csi);
509 if (cairo_script_interpreter_destroy (tr->csi))
514 spawn_socket (const char *socket_path, pid_t pid)
516 struct sockaddr_un addr;
519 sk = socket (PF_UNIX, SOCK_STREAM, 0);
523 memset (&addr, 0, sizeof (addr));
524 addr.sun_family = AF_UNIX;
525 strcpy (addr.sun_path, socket_path);
527 if (connect (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1)
530 if (! writen (sk, &pid, sizeof (pid)))
537 spawn_shm (const char *shm_path)
542 fd = shm_open (shm_path, O_RDWR, 0);
546 base = mmap (NULL, DATA_SIZE,
547 PROT_READ | PROT_WRITE,
548 MAP_SHARED | MAP_NORESERVE,
556 spawn_target (const char *socket_path,
557 const char *shm_path,
558 const cairo_boilerplate_target_t *target,
565 printf ("Spawning slave '%s' for %s\n", target->name, trace);
571 tr.is_recording = FALSE;
574 tr.sk = spawn_socket (socket_path, tr.pid);
576 fprintf (stderr, "%s: Failed to open socket.\n",
581 tr.base = spawn_shm (shm_path);
582 if (tr.base == MAP_FAILED) {
583 fprintf (stderr, "%s: Failed to map shared memory segment.\n",
588 tr.name = target->name;
593 tr.surface = target->create_surface (NULL,
597 CAIRO_BOILERPLATE_MODE_TEST,
599 if (tr.surface == NULL) {
601 "%s: Failed to create target surface.\n",
608 cairo_surface_destroy (tr.surface);
611 target->cleanup (tr.closure);
614 munmap (tr.base, DATA_SIZE);
619 #if CAIRO_HAS_REAL_PTHREAD
621 cleanup_recorder (void *arg)
623 test_runner_t *tr = arg;
625 cairo_surface_finish (tr->surface);
626 cairo_surface_destroy (tr->surface);
635 test_runner_t *tr = arg;
637 pthread_cleanup_push (cleanup_recorder, tr);
639 pthread_cleanup_pop (TRUE);
644 /* The recorder is special:
645 * 1. It doesn't generate an image, but keeps an in-memory trace to
646 * reconstruct any surface.
647 * 2. Runs in the same process, but separate thread.
650 spawn_recorder (const char *socket_path, const char *trace, test_runner_t **out)
655 pid_t pid = getpid ();
658 printf ("Spawning recorder for %s\n", trace);
660 tr = malloc (sizeof (*tr));
664 tr->is_recording = TRUE;
666 tr->sk = spawn_socket (socket_path, tr->pid);
678 tr->surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
680 if (tr->surface == NULL) {
681 cleanup_recorder (tr);
685 pthread_attr_init (&attr);
686 pthread_attr_setdetachstate (&attr, TRUE);
687 if (pthread_create (&id, &attr, record, tr) < 0) {
688 pthread_attr_destroy (&attr);
689 cleanup_recorder (tr);
692 pthread_attr_destroy (&attr);
700 /* XXX imagediff - is the extra expense worth it? */
702 matches_reference (struct slave *slave)
704 cairo_surface_t *a, *b;
707 b = slave->reference->image;
712 if (a == NULL || b == NULL)
715 if (cairo_surface_status (a) || cairo_surface_status (b))
718 if (cairo_surface_get_type (a) != cairo_surface_get_type (b))
721 if (cairo_image_surface_get_format (a) != cairo_image_surface_get_format (b))
724 if (cairo_image_surface_get_width (a) != cairo_image_surface_get_width (b))
727 if (cairo_image_surface_get_height (a) != cairo_image_surface_get_height (b))
730 if (cairo_image_surface_get_stride (a) != cairo_image_surface_get_stride (b))
733 if (FALSE && cairo_surface_get_content (a) & CAIRO_CONTENT_COLOR) {
734 cairo_surface_t *diff;
735 int width, height, stride, size;
737 cairo_status_t status;
739 width = cairo_image_surface_get_width (a);
740 height = cairo_image_surface_get_height (a);
741 stride = cairo_image_surface_get_stride (a);
742 size = height * stride * 4;
743 data = malloc (size);
747 diff = cairo_image_surface_create_for_data (data,
748 cairo_image_surface_get_format (a),
749 width, height, stride);
750 cairo_surface_set_user_data (diff, (cairo_user_data_key_t *) diff,
753 status = image_diff (NULL, a, b, diff, &slave->result);
755 cairo_surface_destroy (diff);
759 if (image_diff_is_failure (&slave->result, slave->target->error_tolerance)) {
760 slave->difference = diff;
763 cairo_surface_destroy (diff);
767 int width, height, stride;
768 const uint8_t *aa, *bb;
771 width = cairo_image_surface_get_width (a);
772 height = cairo_image_surface_get_height (a);
773 stride = cairo_image_surface_get_stride (a);
775 aa = cairo_image_surface_get_data (a);
776 bb = cairo_image_surface_get_data (b);
777 switch (cairo_image_surface_get_format (a)) {
778 case CAIRO_FORMAT_ARGB32:
779 for (y = 0; y < height; y++) {
780 const uint32_t *ua = (uint32_t *) aa;
781 const uint32_t *ub = (uint32_t *) bb;
782 for (x = 0; x < width; x++) {
783 if (ua[x] != ub[x]) {
786 for (channel = 0; channel < 4; channel++) {
787 unsigned va, vb, diff;
789 va = (ua[x] >> (channel*8)) & 0xff;
790 vb = (ub[x] >> (channel*8)) & 0xff;
791 diff = abs (va - vb);
792 if (diff > slave->target->error_tolerance)
802 case CAIRO_FORMAT_RGB24:
803 for (y = 0; y < height; y++) {
804 const uint32_t *ua = (uint32_t *) aa;
805 const uint32_t *ub = (uint32_t *) bb;
806 for (x = 0; x < width; x++) {
807 if ((ua[x] & 0x00ffffff) != (ub[x] & 0x00ffffff)) {
810 for (channel = 0; channel < 3; channel++) {
811 unsigned va, vb, diff;
813 va = (ua[x] >> (channel*8)) & 0xff;
814 vb = (ub[x] >> (channel*8)) & 0xff;
815 diff = abs (va - vb);
816 if (diff > slave->target->error_tolerance)
826 case CAIRO_FORMAT_A8:
827 for (y = 0; y < height; y++) {
828 for (x = 0; x < width; x++) {
829 if (aa[x] != bb[x]) {
830 unsigned diff = abs (aa[x] - bb[x]);
831 if (diff > slave->target->error_tolerance)
840 case CAIRO_FORMAT_A1:
842 for (y = 0; y < height; y++) {
843 if (memcmp (aa, bb, width))
850 case CAIRO_FORMAT_RGB30:
851 case CAIRO_FORMAT_RGB16_565:
852 case CAIRO_FORMAT_INVALID:
861 check_images (struct slave *slaves, int num_slaves)
865 if (ignore_image_differences)
868 for (n = 0; n < num_slaves; n++) {
869 if (slaves[n].reference == NULL)
872 if (! matches_reference (&slaves[n]))
880 write_images (const char *trace, struct slave *slave, int num_slaves)
882 while (num_slaves--) {
883 if (slave->image != NULL && ! slave->is_recording) {
886 xasprintf (&filename, "%s-%s-fail.png",
887 trace, slave->target->name);
888 cairo_surface_write_to_png (slave->image, filename);
891 if (slave->difference) {
892 xasprintf (&filename, "%s-%s-diff.png",
893 trace, slave->target->name);
894 cairo_surface_write_to_png (slave->difference, filename);
904 write_result (const char *trace, struct slave *slave)
909 xasprintf (&filename, "%s-%s-pass-%d-%d-%d.png",
910 trace, slave->target->name, ++index,
911 slave->start_line, slave->end_line);
912 cairo_surface_write_to_png (slave->image, filename);
917 write_trace (const char *trace, const char *id, struct slave *slave)
919 #if CAIRO_HAS_SCRIPT_SURFACE
920 cairo_device_t *script;
923 assert (slave->is_recording);
925 xasprintf (&filename, "%s-%s.trace", trace, id);
927 script = cairo_script_create (filename);
928 cairo_script_from_recording_surface (script, slave->image);
929 cairo_device_destroy (script);
936 dump_traces (test_runner_t *tr,
941 #if CAIRO_HAS_SCRIPT_SURFACE
942 struct context_closure *c;
944 for (c = tr->contexts; c; c = c->next) {
945 cairo_device_t *script;
948 xasprintf (&filename, "%s-%s-%s.%lu.trace",
949 trace, target, fail, c->start_line);
951 script = cairo_script_create (filename);
952 cairo_script_from_recording_surface (script, c->surface);
953 cairo_device_destroy (script);
961 allocate_image_for_slave (uint8_t *base,
962 unsigned long offset,
965 struct request_image rq;
969 assert (slave->image == NULL);
971 readn (slave->fd, &rq, sizeof (rq));
972 slave->image_serial = rq.id;
973 slave->start_line = rq.start_line;
974 slave->end_line = rq.end_line;
976 slave->width = rq.width;
977 slave->height = rq.height;
980 printf ("allocate-image-for-slave: %s %lu [%lu, %lu] %ldx%ld stride=%lu => %lu, is-recording? %d\n",
981 TARGET_NAME (slave->target),
989 slave->is_recording);
992 if (slave->is_recording) {
993 /* special communication with recording-surface thread */
994 slave->image = cairo_surface_reference ((cairo_surface_t *) rq.stride);
996 size = rq.height * rq.stride;
997 size = (size + 4095) & -4096;
998 data = base + offset;
1000 assert (offset <= DATA_SIZE);
1002 slave->image = cairo_image_surface_create_for_data (data, rq.format,
1003 rq.width, rq.height,
1011 unsigned long context_id;
1012 unsigned long start_line;
1013 unsigned long end_line;
1017 test_run (void *base,
1020 struct slave *slaves,
1022 struct error_info *error)
1025 int npfd, cnt, n, i;
1026 int completion, err = 0;
1027 cairo_bool_t ret = FALSE;
1028 unsigned long image;
1031 printf ("Running trace '%s' over %d slaves\n",
1035 pfd = xcalloc (num_slaves+1, sizeof (*pfd));
1038 pfd[0].events = POLLIN;
1043 while ((cnt = poll (pfd, npfd, -1)) > 0) {
1044 if (pfd[0].revents) {
1047 while ((fd = accept (sk, NULL, NULL)) != -1) {
1050 readn (fd, &pid, sizeof (pid));
1051 for (n = 0; n < num_slaves; n++) {
1052 if (slaves[n].pid == pid) {
1057 if (n == num_slaves) {
1059 printf ("unknown slave pid\n");
1064 pfd[npfd].events = POLLIN;
1067 if (! writen (fd, &pid, sizeof (pid)))
1073 for (n = 1; n < npfd && cnt; n++) {
1074 if (! pfd[n].revents)
1077 if (pfd[n].revents & POLLHUP) {
1078 pfd[n].events = pfd[n].revents = 0;
1083 for (i = 0; i < num_slaves; i++) {
1084 if (slaves[i].fd == pfd[n].fd) {
1085 /* Communication with the slave is done in three phases,
1086 * and we do each pass synchronously.
1088 * 1. The slave requests an image buffer, which we
1089 * allocate and then return to the slave the offset into
1090 * the shared memory segment.
1092 * 2. The slave indicates that it has finished writing
1093 * into the shared image buffer. The slave now waits
1094 * for the server to collate all the image data - thereby
1095 * throttling the slaves.
1097 * 3. After all slaves have finished writing their images,
1098 * we compare them all against the reference image and,
1099 * if satisfied, send an acknowledgement to all slaves.
1101 if (slaves[i].image_serial == 0) {
1102 unsigned long offset;
1105 allocate_image_for_slave (base,
1108 if (! writen (pfd[n].fd, &offset, sizeof (offset))) {
1109 pfd[n].events = pfd[n].revents = 0;
1116 &slaves[i].image_ready,
1117 sizeof (slaves[i].image_ready));
1119 printf ("slave '%s' reports completion on %lu (expecting %lu)\n",
1120 TARGET_NAME (slaves[i].target),
1121 slaves[i].image_ready,
1122 slaves[i].image_serial);
1124 if (slaves[i].image_ready != slaves[i].image_serial) {
1125 pfd[n].events = pfd[n].revents = 0;
1131 /* Can anyone spell 'P·E·D·A·N·T'? */
1132 if (! slaves[i].is_recording)
1133 cairo_surface_mark_dirty (slaves[i].image);
1144 if (completion >= num_slaves) {
1147 printf ("error detected\n");
1152 printf ("all saves report completion\n");
1154 if (slaves[0].end_line >= slaves[0].start_line &&
1155 ! check_images (slaves, num_slaves)) {
1156 error->context_id = slaves[0].image_serial;
1157 error->start_line = slaves[0].start_line;
1158 error->end_line = slaves[0].end_line;
1161 printf ("check_images failed: %lu, [%lu, %lu]\n",
1162 slaves[0].image_serial,
1163 slaves[0].start_line,
1164 slaves[0].end_line);
1167 write_images (trace, slaves, num_slaves);
1169 if (slaves[0].is_recording)
1170 write_trace (trace, "fail", &slaves[0]);
1175 if (write_results) write_result (trace, &slaves[1]);
1176 if (write_traces && slaves[0].is_recording) {
1178 snprintf (buf, sizeof (buf), "%d", slaves[0].image_serial);
1179 write_trace (trace, buf, &slaves[0]);
1183 for (i = 0; i < num_slaves; i++) {
1184 cairo_surface_destroy (slaves[i].image);
1185 slaves[i].image = NULL;
1188 printf ("sending continuation to '%s'\n",
1189 TARGET_NAME (slaves[i].target));
1191 if (! writen (slaves[i].fd,
1192 &slaves[i].image_serial,
1193 sizeof (slaves[i].image_serial)))
1198 slaves[i].image_serial = 0;
1199 slaves[i].image_ready = 0;
1211 printf ("run complete: %d\n", ret);
1214 for (n = 0; n < num_slaves; n++) {
1215 if (slaves[n].fd != -1)
1216 close (slaves[n].fd);
1218 if (slaves[n].image == NULL)
1221 cairo_surface_destroy (slaves[n].image);
1222 slaves[n].image = NULL;
1224 cairo_surface_destroy (slaves[n].difference);
1225 slaves[n].difference = NULL;
1227 slaves[n].image_serial = 0;
1228 slaves[n].image_ready = 0;
1237 server_socket (const char *socket_path)
1240 struct sockaddr_un addr;
1243 sk = socket (PF_UNIX, SOCK_STREAM, 0);
1247 memset (&addr, 0, sizeof (addr));
1248 addr.sun_family = AF_UNIX;
1249 strcpy (addr.sun_path, socket_path);
1250 if (bind (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) {
1255 flags = fcntl (sk, F_GETFL);
1256 if (flags == -1 || fcntl (sk, F_SETFL, flags | O_NONBLOCK) == -1) {
1261 if (listen (sk, 5) == -1) {
1270 server_shm (const char *shm_path)
1274 fd = shm_open (shm_path, O_RDWR | O_EXCL | O_CREAT, 0777);
1278 if (ftruncate (fd, DATA_SIZE) == -1) {
1287 _test_trace (test_trace_t *test,
1290 struct error_info *error)
1292 const char *shm_path = SHM_PATH_XXX;
1293 const cairo_boilerplate_target_t *target, *image;
1294 struct slave *slaves, *s;
1295 test_runner_t *recorder = NULL;
1297 char socket_dir[] = "/tmp/cairo-test-trace.XXXXXX";
1302 cairo_bool_t ret = FALSE;
1305 printf ("setting up trace '%s'\n", trace);
1307 /* create a socket to control the test runners */
1308 if (mkdtemp (socket_dir) == NULL) {
1309 fprintf (stderr, "Unable to create temporary name for socket\n");
1313 xasprintf (&socket_path, "%s/socket", socket_dir);
1314 sk = server_socket (socket_path);
1316 fprintf (stderr, "Unable to create socket for server\n");
1320 /* allocate some shared memory */
1321 fd = server_shm (shm_path);
1323 fprintf (stderr, "Unable to create shared memory '%s': %s\n",
1324 shm_path, strerror (errno));
1328 image = cairo_boilerplate_get_image_target (CAIRO_CONTENT_COLOR_ALPHA);
1329 assert (image != NULL);
1331 s = slaves = xcalloc (2*test->num_targets + 1, sizeof (struct slave));
1333 #if CAIRO_HAS_REAL_PTHREAD
1334 /* set-up a recording-surface to reconstruct errors */
1335 slave = spawn_recorder (socket_path, trace, &recorder);
1337 fprintf (stderr, "Unable to create recording surface\n");
1342 s->is_recording = TRUE;
1345 s->reference = NULL;
1349 /* spawn slave processes to run the trace */
1350 for (i = 0; i < test->num_targets; i++) {
1351 const cairo_boilerplate_target_t *reference;
1352 struct slave *master;
1354 target = test->targets[i];
1357 printf ("setting up target[%d]? '%s' (image? %d, measurable? %d)\n",
1358 i, target->name, target == image, target->is_measurable);
1360 if (target == image || ! target->is_measurable)
1363 /* find a matching slave to use as a reference for this target */
1364 if (target->reference_target != NULL) {
1366 cairo_boilerplate_get_target_by_name (target->reference_target,
1368 assert (reference != NULL);
1372 for (master = slaves; master < s; master++) {
1373 if (master->target == reference)
1378 /* no match found, spawn a slave to render the reference image */
1379 slave = spawn_target (socket_path, shm_path, reference, trace);
1384 s->target = reference;
1386 s->reference = NULL;
1390 slave = spawn_target (socket_path, shm_path, target, trace);
1397 s->reference = master;
1400 num_slaves = s - slaves;
1401 if (num_slaves == 1) {
1402 fprintf (stderr, "No targets to test\n");
1406 base = mmap (NULL, DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1407 if (base == MAP_FAILED) {
1408 fprintf (stderr, "Unable to mmap shared memory\n");
1411 ret = test_run (base, sk, name, slaves, num_slaves, error);
1412 munmap (base, DATA_SIZE);
1416 while (s-- > slaves) {
1422 cairo_surface_destroy (s->image);
1423 cairo_surface_destroy (s->difference);
1425 if (s->is_recording) /* in-process */
1428 kill (s->pid, SIGKILL);
1429 waitpid (s->pid, &status, 0);
1430 if (WIFSIGNALED (status) && WTERMSIG(status) != SIGKILL) {
1431 fprintf (stderr, "%s crashed\n", s->target->name);
1433 dump_traces (recorder, trace, s->target->name, "crash");
1437 shm_unlink (shm_path);
1442 remove (socket_path);
1443 remove (socket_dir);
1450 test_trace (test_trace_t *test, const char *trace)
1452 char *trace_cpy, *name, *dot;
1454 trace_cpy = xstrdup (trace);
1455 name = basename (trace_cpy);
1456 dot = strchr (name, '.');
1460 if (test->list_only) {
1461 printf ("%s\n", name);
1463 struct error_info error = {0};
1466 printf ("%s: ", name);
1469 ret = _test_trace (test, trace, name, &error);
1473 if (error.context_id) {
1474 printf ("FAIL (context %lu, lines [%lu, %lu])\n",
1488 read_excludes (test_trace_t *test, const char *filename)
1492 size_t line_size = 0;
1495 file = fopen (filename, "r");
1499 while (getline (&line, &line_size, file) != -1) {
1500 /* terminate the line at a comment marker '#' */
1501 s = strchr (line, '#');
1505 /* whitespace delimits */
1507 while (*s != '\0' && isspace (*s))
1511 while (*t != '\0' && ! isspace (*t))
1515 int i = test->num_exclude_names;
1516 test->exclude_names = xrealloc (test->exclude_names,
1517 sizeof (char *) * (i+1));
1518 test->exclude_names[i] = strndup (s, t-s);
1519 test->num_exclude_names++;
1530 usage (const char *argv0)
1533 "Usage: %s [-l] [-x exclude-file] [test-names ... | traces ...]\n"
1535 "Run the cairo test suite over the given traces (all by default).\n"
1536 "The command-line arguments are interpreted as follows:\n"
1538 " -l list only; just list selected test case names without executing\n"
1539 " -x exclude; specify a file to read a list of traces to exclude\n"
1541 "If test names are given they are used as sub-string matches so a command\n"
1542 "such as \"%s firefox\" can be used to run all firefox traces.\n"
1543 "Alternatively, you can specify a list of filenames to execute.\n",
1548 parse_options (test_trace_t *test, int argc, char *argv[])
1552 test->list_only = FALSE;
1554 test->num_names = 0;
1555 test->exclude_names = NULL;
1556 test->num_exclude_names = 0;
1559 c = _cairo_getopt (argc, argv, "lx:");
1565 test->list_only = TRUE;
1568 if (! read_excludes (test, optarg)) {
1569 fprintf (stderr, "Invalid argument for -x (not readable file): %s\n",
1575 fprintf (stderr, "Internal error: unhandled option: %c\n", c);
1583 if (optind < argc) {
1584 test->names = &argv[optind];
1585 test->num_names = argc - optind;
1590 test_reset (test_trace_t *test)
1592 /* XXX leaking fonts again via recording-surface? */
1594 cairo_debug_reset_static_data ();
1602 test_fini (test_trace_t *test)
1606 cairo_boilerplate_free_targets (test->targets);
1607 free (test->exclude_names);
1611 test_has_filenames (test_trace_t *test)
1615 if (test->num_names == 0)
1618 for (i = 0; i < test->num_names; i++)
1619 if (access (test->names[i], R_OK) == 0)
1626 test_can_run (test_trace_t *test, const char *name)
1632 if (test->num_names == 0 && test->num_exclude_names == 0)
1635 copy = xstrdup (name);
1636 dot = strrchr (copy, '.');
1640 if (test->num_names) {
1642 for (i = 0; i < test->num_names; i++)
1643 if (strstr (copy, test->names[i]))
1651 if (test->num_exclude_names) {
1653 for (i = 0; i < test->num_exclude_names; i++)
1654 if (strstr (copy, test->exclude_names[i]))
1668 warn_no_traces (const char *message, const char *trace_dir)
1672 "Have you cloned the cairo-traces repository and uncompressed the traces?\n"
1673 " git clone git://anongit.freedesktop.org/cairo-traces\n"
1674 " cd cairo-traces && make\n"
1675 "Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n",
1676 message, trace_dir);
1682 shm_unlink (SHM_PATH_XXX);
1684 signal (sig, SIG_DFL);
1689 main (int argc, char *argv[])
1692 const char *trace_dir = "cairo-traces";
1695 signal (SIGPIPE, SIG_IGN);
1696 signal (SIGINT, interrupt);
1698 parse_options (&test, argc, argv);
1700 shm_unlink (SHM_PATH_XXX);
1702 if (getenv ("CAIRO_TRACE_DIR") != NULL)
1703 trace_dir = getenv ("CAIRO_TRACE_DIR");
1705 test.targets = cairo_boilerplate_get_targets (&test.num_targets, NULL);
1707 if (test_has_filenames (&test)) {
1708 for (n = 0; n < test.num_names; n++) {
1709 if (access (test.names[n], R_OK) == 0) {
1710 test_trace (&test, test.names[n]);
1719 dir = opendir (trace_dir);
1721 warn_no_traces ("Failed to open directory", trace_dir);
1726 while ((de = readdir (dir)) != NULL) {
1730 dot = strrchr (de->d_name, '.');
1733 if (strcmp (dot, ".trace"))
1737 if (! test_can_run (&test, de->d_name))
1740 xasprintf (&trace, "%s/%s", trace_dir, de->d_name);
1741 test_trace (&test, trace);
1749 if (num_traces == 0) {
1750 warn_no_traces ("Found no traces in", trace_dir);
1762 cairo_test_logv (const cairo_test_context_t *ctx,
1763 const char *fmt, va_list va)
1766 vfprintf (stderr, fmt, va);
1771 cairo_test_log (const cairo_test_context_t *ctx, const char *fmt, ...)
1777 vfprintf (stderr, fmt, va);