1
/*
2
 * Copyright © 2009 Chris Wilson
3
 *
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.
14
 *
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.
22
 *
23
 * Authors: Chris Wilson <chris@chris-wilson.co.uk>
24
 */
25

            
26
/*
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.
33
 *
34
 * [1] Should be reference implementation, currently the image backend is
35
 *     considered to be the reference for all other backends.
36
 */
37

            
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?
42
 *
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)
47
 */
48

            
49
/*
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
52
 *     scope of this test.
53
 */
54

            
55
#include "cairo-test.h"
56
#include "buffer-diff.h"
57

            
58
#include "cairo-boilerplate-getopt.h"
59
#include <cairo-script-interpreter.h>
60
#include "cairo-missing.h"
61

            
62
#if CAIRO_HAS_SCRIPT_SURFACE
63
#include <cairo-script.h>
64
#endif
65

            
66
/* For basename */
67
#ifdef HAVE_LIBGEN_H
68
#include <libgen.h>
69
#endif
70
#include <ctype.h> /* isspace() */
71

            
72
#include <sys/types.h>
73
#include <dirent.h>
74
#include <fcntl.h>
75
#include <signal.h>
76
#include <sys/wait.h>
77
#include <sys/stat.h>
78
#include <sys/socket.h>
79
#include <sys/mman.h>
80
#include <sys/un.h>
81
#include <errno.h>
82
#include <assert.h>
83
#include <unistd.h>
84

            
85
#if CAIRO_HAS_REAL_PTHREAD
86
#include <pthread.h>
87
#endif
88

            
89
#if defined(HAVE_POLL_H)
90
#include <poll.h>
91
#elif defined(HAVE_SYS_POLL_H)
92
#include <sys/poll.h>
93
#else
94
#error No poll.h equivalent found
95
#endif
96

            
97
#if HAVE_FCFINI
98
#include <fontconfig/fontconfig.h>
99
#endif
100

            
101
#ifndef MAP_NORESERVE
102
#define MAP_NORESERVE 0
103
#endif
104

            
105
#define DEBUG 0
106

            
107
#define ignore_image_differences 0 /* XXX make me a cmdline option! */
108
#define write_results 1
109
#define write_traces 1
110

            
111
#define DATA_SIZE (256 << 20)
112
#define SHM_PATH_XXX "/.shmem-cairo-trace"
113

            
114
typedef struct _test_trace {
115
    /* Options from command-line */
116
    cairo_bool_t list_only;
117
    char **names;
118
    unsigned int num_names;
119
    char **exclude_names;
120
    unsigned int num_exclude_names;
121

            
122
    /* Stuff used internally */
123
    const cairo_boilerplate_target_t **targets;
124
    int num_targets;
125
} test_trace_t;
126

            
127
typedef struct _test_runner {
128
    const char *name;
129
    cairo_surface_t *surface;
130
    void *closure;
131
    uint8_t *base;
132
    const char *trace;
133
    pid_t pid;
134
    int sk;
135
    cairo_bool_t is_recording;
136

            
137
    cairo_script_interpreter_t *csi;
138
    struct context_closure {
139
	struct context_closure *next;
140
	unsigned long id;
141
	unsigned long start_line;
142
	unsigned long end_line;
143
	cairo_t *context;
144
	cairo_surface_t *surface;
145
    } *contexts;
146

            
147
    unsigned long context_id;
148
} test_runner_t;
149

            
150
struct slave {
151
    pid_t pid;
152
    int fd;
153
    unsigned long image_serial;
154
    unsigned long image_ready;
155
    unsigned long start_line;
156
    unsigned long end_line;
157
    cairo_surface_t *image;
158
    long width, height;
159
    cairo_surface_t *difference;
160
    buffer_diff_result_t result;
161
    const cairo_boilerplate_target_t *target;
162
    const struct slave *reference;
163
    cairo_bool_t is_recording;
164
};
165

            
166
struct request_image {
167
    unsigned long id;
168
    unsigned long start_line;
169
    unsigned long end_line;
170
    cairo_format_t format;
171
    long width;
172
    long height;
173
    long stride;
174
};
175

            
176
struct surface_tag {
177
    long width, height;
178
};
179
static const cairo_user_data_key_t surface_tag;
180

            
181
#define TARGET_NAME(T)  ((T) ? (T)->name : "recording")
182

            
183
#if CAIRO_HAS_REAL_PTHREAD
184
#define tr_die(t) t->is_recording ? pthread_exit(NULL) : exit(1)
185
#else
186
#define tr_die(t) exit(1)
187
#endif
188

            
189
static cairo_bool_t
190
writen (int fd, const void *ptr, int len)
191
{
192
#if 0
193
    const uint8_t *data = ptr;
194
    while (len) {
195
	int ret = write (fd, data, len);
196
	if (ret < 0) {
197
	    switch (errno) {
198
	    case EAGAIN:
199
	    case EINTR:
200
		continue;
201
	    default:
202
		return FALSE;
203
	    }
204
	} else if (ret == 0) {
205
	    return FALSE;
206
	} else {
207
	    data += ret;
208
	    len -= ret;
209
	}
210
    }
211
    return TRUE;
212
#else
213
    int ret = send (fd, ptr, len, 0);
214
    return ret == len;
215
#endif
216
}
217

            
218
static cairo_bool_t
219
readn (int fd, void *ptr, int len)
220
{
221
#if 0
222
    uint8_t *data = ptr;
223
    while (len) {
224
	int ret = read (fd, data, len);
225
	if (ret < 0) {
226
	    switch (errno) {
227
	    case EAGAIN:
228
	    case EINTR:
229
		continue;
230
	    default:
231
		return FALSE;
232
	    }
233
	} else if (ret == 0) {
234
	    return FALSE;
235
	} else {
236
	    data += ret;
237
	    len -= ret;
238
	}
239
    }
240
    return TRUE;
241
#else
242
    int ret = recv (fd, ptr, len, MSG_WAITALL);
243
    return ret == len;
244
#endif
245
}
246

            
247
static cairo_format_t
248
format_for_content (cairo_content_t content)
249
{
250
    switch (content) {
251
    case CAIRO_CONTENT_ALPHA:
252
	return CAIRO_FORMAT_A8;
253
    case CAIRO_CONTENT_COLOR:
254
	return CAIRO_FORMAT_RGB24;
255
    default:
256
    case CAIRO_CONTENT_COLOR_ALPHA:
257
	return CAIRO_FORMAT_ARGB32;
258
    }
259
}
260

            
261
static void
262
send_recording_surface (test_runner_t *tr,
263
			int width, int height,
264
			struct context_closure *closure)
265
{
266
#if CAIRO_HAS_REAL_PTHREAD
267
    const struct request_image rq = {
268
	closure->id,
269
	closure->start_line,
270
	closure->end_line,
271
	-1,
272
	width, height,
273
	(long) closure->surface,
274
    };
275
    unsigned long offset;
276
    unsigned long serial;
277

            
278
    if (DEBUG > 1) {
279
	printf ("send-recording-surface: %lu [%lu, %lu]\n",
280
		closure->id,
281
		closure->start_line,
282
		closure->end_line);
283
    }
284
    writen (tr->sk, &rq, sizeof (rq));
285
    readn (tr->sk, &offset, sizeof (offset));
286

            
287
    /* signal completion */
288
    writen (tr->sk, &closure->id, sizeof (closure->id));
289

            
290
    /* wait for image check */
291
    serial = 0;
292
    readn (tr->sk, &serial, sizeof (serial));
293
    if (DEBUG > 1) {
294
	printf ("send-recording-surface: serial: %lu\n", serial);
295
    }
296
    if (serial != closure->id)
297
	pthread_exit (NULL);
298
#else
299
    exit (1);
300
#endif
301
}
302

            
303
static void *
304
request_image (test_runner_t *tr,
305
	       struct context_closure *closure,
306
	       cairo_format_t format,
307
	       int width, int height, int stride)
308
{
309
    const struct request_image rq = {
310
	closure->id,
311
	closure->start_line,
312
	closure->end_line,
313
	format, width, height, stride
314
    };
315
    unsigned long offset = -1;
316

            
317
    assert (format != (cairo_format_t) -1);
318

            
319
    writen (tr->sk, &rq, sizeof (rq));
320
    readn (tr->sk, &offset, sizeof (offset));
321
    if (offset == (unsigned long) -1)
322
	return NULL;
323

            
324
    return tr->base + offset;
325
}
326

            
327
static void
328
send_surface (test_runner_t *tr,
329
	      struct context_closure *closure)
330
{
331
    cairo_surface_t *source = closure->surface;
332
    cairo_surface_t *image;
333
    cairo_format_t format = (cairo_format_t) -1;
334
    cairo_t *cr;
335
    int width, height, stride;
336
    void *data;
337
    unsigned long serial;
338

            
339
    if (DEBUG > 1) {
340
	printf ("send-surface: '%s', is-recording? %d\n",
341
		tr->name, tr->is_recording);
342
    }
343

            
344
    if (cairo_surface_get_type (source) == CAIRO_SURFACE_TYPE_IMAGE) {
345
	width = cairo_image_surface_get_width (source);
346
	height = cairo_image_surface_get_height (source);
347
	format = cairo_image_surface_get_format (source);
348
    } else {
349
	struct surface_tag *tag;
350

            
351
	tag = cairo_surface_get_user_data (source, &surface_tag);
352
	if (tag != NULL) {
353
	    width = tag->width;
354
	    height = tag->height;
355
	} else {
356
	    double x0, x1, y0, y1;
357

            
358
	    /* presumably created using cairo_surface_create_similar() */
359
	    cr = cairo_create (source);
360
	    cairo_clip_extents (cr, &x0, &y0, &x1, &y1);
361
	    cairo_destroy (cr);
362

            
363
	    tag = xmalloc (sizeof (*tag));
364
	    width = tag->width = x1 - x0;
365
	    height = tag->height = y1 - y0;
366

            
367
	    if (cairo_surface_set_user_data (source, &surface_tag, tag, free))
368
		tr_die (tr);
369
	}
370
    }
371

            
372
    if (tr->is_recording) {
373
	send_recording_surface (tr, width, height, closure);
374
	return;
375
    }
376

            
377
    if (format == (cairo_format_t) -1)
378
	format = format_for_content (cairo_surface_get_content (source));
379

            
380
    stride = cairo_format_stride_for_width (format, width);
381

            
382
    data = request_image (tr, closure, format, width, height, stride);
383
    if (data == NULL)
384
	tr_die (tr);
385

            
386
    image = cairo_image_surface_create_for_data (data,
387
						 format,
388
						 width, height,
389
						 stride);
390
    cr = cairo_create (image);
391
    cairo_surface_destroy (image);
392

            
393
    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
394
    cairo_set_source_surface (cr, source, 0, 0);
395
    cairo_paint (cr);
396
    cairo_destroy (cr);
397

            
398
    /* signal completion */
399
    writen (tr->sk, &closure->id, sizeof (closure->id));
400

            
401
    /* wait for image check */
402
    serial = 0;
403
    readn (tr->sk, &serial, sizeof (serial));
404
    if (serial != closure->id)
405
	tr_die (tr);
406
}
407

            
408
static cairo_surface_t *
409
_surface_create (void *closure,
410
		 cairo_content_t content,
411
		 double width, double height,
412
		 long uid)
413
{
414
    test_runner_t *tr = closure;
415
    cairo_surface_t *surface;
416

            
417
    surface = cairo_surface_create_similar (tr->surface,
418
					    content, width, height);
419
    if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE) {
420
	struct surface_tag *tag;
421

            
422
	tag = xmalloc (sizeof (*tag));
423
	tag->width = width;
424
	tag->height = height;
425
	if (cairo_surface_set_user_data (surface, &surface_tag, tag, free))
426
	    tr_die (tr);
427
    }
428

            
429
    return surface;
430
}
431

            
432
static cairo_t *
433
_context_create (void *closure, cairo_surface_t *surface)
434
{
435
    test_runner_t *tr = closure;
436
    struct context_closure *l;
437

            
438
    if (DEBUG) {
439
	fprintf (stderr, "%s: starting context %lu on line %d\n",
440
		 tr->name ? tr->name : "recording" ,
441
		 tr->context_id + 1,
442
		 cairo_script_interpreter_get_line_number (tr->csi));
443
    }
444

            
445
    l = xmalloc (sizeof (*l));
446
    l->next = tr->contexts;
447
    l->start_line = cairo_script_interpreter_get_line_number (tr->csi);
448
    l->end_line = l->start_line;
449
    l->context = cairo_create (surface);
450
    l->surface = cairo_surface_reference (surface);
451
    l->id = ++tr->context_id;
452
    if (l->id == 0)
453
	l->id = ++tr->context_id;
454
    tr->contexts = l;
455

            
456
    return l->context;
457
}
458

            
459
static void
460
_context_destroy (void *closure, void *ptr)
461
{
462
    test_runner_t *tr = closure;
463
    struct context_closure *l, **prev = &tr->contexts;
464

            
465
    while ((l = *prev) != NULL) {
466
	if (l->context == ptr) {
467
	    if (DEBUG) {
468
		fprintf (stderr, "%s: context %lu complete on line %d\n",
469
			 tr->name ? tr->name : "recording" ,
470
			 tr->context_id,
471
			 cairo_script_interpreter_get_line_number (tr->csi));
472
	    }
473
	    l->end_line =
474
		cairo_script_interpreter_get_line_number (tr->csi);
475
	    if (cairo_surface_status (l->surface) == CAIRO_STATUS_SUCCESS) {
476
		send_surface (tr, l);
477
            } else {
478
		fprintf (stderr, "%s: error during replay, line %lu: %s!\n",
479
			 tr->name,
480
			 l->end_line,
481
			 cairo_status_to_string (cairo_surface_status (l->surface)));
482
		tr_die (tr);
483
	    }
484

            
485
            cairo_surface_destroy (l->surface);
486
            *prev = l->next;
487
            free (l);
488
            return;
489
        }
490
        prev = &l->next;
491
    }
492
}
493

            
494
static void
495
execute (test_runner_t *tr)
496
{
497
    const cairo_script_interpreter_hooks_t hooks = {
498
	.closure = tr,
499
	.surface_create = _surface_create,
500
	.context_create = _context_create,
501
	.context_destroy = _context_destroy,
502
    };
503
    pid_t ack;
504

            
505
    tr->csi = cairo_script_interpreter_create ();
506
    cairo_script_interpreter_install_hooks (tr->csi, &hooks);
507

            
508
    ack = -1;
509
    readn (tr->sk, &ack, sizeof (ack));
510
    if (ack != tr->pid)
511
	tr_die (tr);
512

            
513
    cairo_script_interpreter_run (tr->csi, tr->trace);
514

            
515
    cairo_script_interpreter_finish (tr->csi);
516
    if (cairo_script_interpreter_destroy (tr->csi))
517
	tr_die (tr);
518
}
519

            
520
static int
521
spawn_socket (const char *socket_path, pid_t pid)
522
{
523
    struct sockaddr_un addr;
524
    int sk;
525

            
526
    sk = socket (PF_UNIX, SOCK_STREAM, 0);
527
    if (sk == -1)
528
	return -1;
529

            
530
    memset (&addr, 0, sizeof (addr));
531
    addr.sun_family = AF_UNIX;
532
    strcpy (addr.sun_path, socket_path);
533

            
534
    if (connect (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1)
535
	return -1;
536

            
537
    if (! writen (sk, &pid, sizeof (pid)))
538
	return -1;
539

            
540
    return sk;
541
}
542

            
543
static void *
544
spawn_shm (const char *shm_path)
545
{
546
    void *base;
547
    int fd;
548

            
549
    fd = shm_open (shm_path, O_RDWR, 0);
550
    if (fd == -1)
551
	return MAP_FAILED;
552

            
553
    base = mmap (NULL, DATA_SIZE,
554
		 PROT_READ | PROT_WRITE,
555
#ifdef MAP_NORESERVE
556
		 MAP_SHARED | MAP_NORESERVE,
557
#else
558
		 MAP_SHARED,
559
#endif
560
		 fd, 0);
561
    close (fd);
562

            
563
    return base;
564
}
565

            
566
static int
567
spawn_target (const char *socket_path,
568
	      const char *shm_path,
569
	      const cairo_boilerplate_target_t *target,
570
	      const char *trace)
571
{
572
    test_runner_t tr;
573
    pid_t pid;
574

            
575
    if (DEBUG)
576
	printf ("Spawning slave '%s' for %s\n", target->name, trace);
577

            
578
    pid = fork ();
579
    if (pid != 0)
580
	return pid;
581

            
582
    tr.is_recording = FALSE;
583
    tr.pid = getpid ();
584

            
585
    tr.sk = spawn_socket (socket_path, tr.pid);
586
    if (tr.sk == -1) {
587
	fprintf (stderr, "%s: Failed to open socket.\n",
588
		 target->name);
589
	exit (-1);
590
    }
591

            
592
    tr.base = spawn_shm (shm_path);
593
    if (tr.base == MAP_FAILED) {
594
	fprintf (stderr, "%s: Failed to map shared memory segment.\n",
595
		 target->name);
596
	exit (-1);
597
    }
598

            
599
    tr.name = target->name;
600
    tr.contexts = NULL;
601
    tr.context_id = 0;
602
    tr.trace = trace;
603

            
604
    tr.surface = target->create_surface (NULL,
605
					 target->content,
606
					 1, 1,
607
					 1, 1,
608
					 CAIRO_BOILERPLATE_MODE_TEST,
609
					 &tr.closure);
610
    if (tr.surface == NULL) {
611
	fprintf (stderr,
612
		 "%s:  Failed to create target surface.\n",
613
		 target->name);
614
	exit (-1);
615
    }
616

            
617
    execute (&tr);
618

            
619
    cairo_surface_destroy (tr.surface);
620

            
621
    if (target->cleanup)
622
	target->cleanup (tr.closure);
623

            
624
    close (tr.sk);
625
    munmap (tr.base, DATA_SIZE);
626

            
627
    exit (0);
628
}
629

            
630
#if CAIRO_HAS_REAL_PTHREAD
631
static void
632
cleanup_recorder (void *arg)
633
{
634
    test_runner_t *tr = arg;
635

            
636
    cairo_surface_finish (tr->surface);
637
    cairo_surface_destroy (tr->surface);
638

            
639
    close (tr->sk);
640
    free (tr);
641
}
642

            
643
static void *
644
record (void *arg)
645
{
646
    test_runner_t *tr = arg;
647

            
648
    pthread_cleanup_push (cleanup_recorder, tr);
649
    execute (tr);
650
    pthread_cleanup_pop (TRUE);
651

            
652
    return NULL;
653
}
654

            
655
/* The recorder is special:
656
 * 1. It doesn't generate an image, but keeps an in-memory trace to
657
 *    reconstruct any surface.
658
 * 2. Runs in the same process, but separate thread.
659
 */
660
static pid_t
661
spawn_recorder (const char *socket_path, const char *trace, test_runner_t **out)
662
{
663
    test_runner_t *tr;
664
    pthread_t id;
665
    pthread_attr_t attr;
666
    pid_t pid = getpid ();
667

            
668
    if (DEBUG)
669
	printf ("Spawning recorder for %s\n", trace);
670

            
671
    tr = malloc (sizeof (*tr));
672
    if (tr == NULL)
673
	return -1;
674

            
675
    tr->is_recording = TRUE;
676
    tr->pid = pid;
677
    tr->sk = spawn_socket (socket_path, tr->pid);
678
    if (tr->sk == -1) {
679
	free (tr);
680
	return -1;
681
    }
682

            
683
    tr->base = NULL;
684
    tr->name = NULL;
685
    tr->contexts = NULL;
686
    tr->context_id = 0;
687
    tr->trace = trace;
688

            
689
    tr->surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
690
						  NULL);
691
    if (tr->surface == NULL) {
692
	cleanup_recorder (tr);
693
	return -1;
694
    }
695

            
696
    pthread_attr_init (&attr);
697
    pthread_attr_setdetachstate (&attr, TRUE);
698
    if (pthread_create (&id, &attr, record, tr) < 0) {
699
	pthread_attr_destroy (&attr);
700
	cleanup_recorder (tr);
701
	return -1;
702
    }
703
    pthread_attr_destroy (&attr);
704

            
705

            
706
    *out = tr;
707
    return pid;
708
}
709
#endif
710

            
711
/* XXX imagediff - is the extra expense worth it? */
712
static cairo_bool_t
713
matches_reference (struct slave *slave)
714
{
715
    cairo_surface_t *a, *b;
716

            
717
    a = slave->image;
718
    b = slave->reference->image;
719

            
720
    if (a == b)
721
	return TRUE;
722

            
723
    if (a == NULL || b == NULL)
724
	return FALSE;
725

            
726
    if (cairo_surface_status (a) || cairo_surface_status (b))
727
	return FALSE;
728

            
729
    if (cairo_surface_get_type (a) != cairo_surface_get_type (b))
730
	return FALSE;
731

            
732
    if (cairo_image_surface_get_format (a) != cairo_image_surface_get_format (b))
733
	return FALSE;
734

            
735
    if (cairo_image_surface_get_width (a) != cairo_image_surface_get_width (b))
736
	return FALSE;
737

            
738
    if (cairo_image_surface_get_height (a) != cairo_image_surface_get_height (b))
739
	return FALSE;
740

            
741
    if (cairo_image_surface_get_stride (a) != cairo_image_surface_get_stride (b))
742
	return FALSE;
743

            
744
    if (FALSE && cairo_surface_get_content (a) & CAIRO_CONTENT_COLOR) {
745
	cairo_surface_t *diff;
746
	int width, height, stride, size;
747
	unsigned char *data;
748
	cairo_status_t status;
749

            
750
	width = cairo_image_surface_get_width (a);
751
	height = cairo_image_surface_get_height (a);
752
	stride = cairo_image_surface_get_stride (a);
753
	size = height * stride * 4;
754
	data = malloc (size);
755
	if (data == NULL)
756
	    return FALSE;
757

            
758
	diff = cairo_image_surface_create_for_data (data,
759
						    cairo_image_surface_get_format (a),
760
						    width, height, stride);
761
	cairo_surface_set_user_data (diff, (cairo_user_data_key_t *) diff,
762
				     data, free);
763

            
764
	status = image_diff (NULL, a, b, diff, &slave->result);
765
	if (status) {
766
	    cairo_surface_destroy (diff);
767
	    return FALSE;
768
	}
769

            
770
	if (image_diff_is_failure (&slave->result, slave->target->error_tolerance)) {
771
	    slave->difference = diff;
772
	    return FALSE;
773
	} else {
774
	    cairo_surface_destroy (diff);
775
	    return TRUE;
776
	}
777
    } else {
778
	int width, height, stride;
779
	const uint8_t *aa, *bb;
780
	int x, y;
781

            
782
	width = cairo_image_surface_get_width (a);
783
	height = cairo_image_surface_get_height (a);
784
	stride = cairo_image_surface_get_stride (a);
785

            
786
	aa = cairo_image_surface_get_data (a);
787
	bb = cairo_image_surface_get_data (b);
788
	switch (cairo_image_surface_get_format (a)) {
789
	case CAIRO_FORMAT_ARGB32:
790
	    for (y = 0; y < height; y++) {
791
		const uint32_t *ua = (uint32_t *) aa;
792
		const uint32_t *ub = (uint32_t *) bb;
793
		for (x = 0; x < width; x++) {
794
		    if (ua[x] != ub[x]) {
795
			int channel;
796

            
797
			for (channel = 0; channel < 4; channel++) {
798
			    int va, vb;
799
			    unsigned diff;
800

            
801
			    va = (ua[x] >> (channel*8)) & 0xff;
802
			    vb = (ub[x] >> (channel*8)) & 0xff;
803
			    diff = abs (va - vb);
804
			    if (diff > slave->target->error_tolerance)
805
				return FALSE;
806
			}
807
		    }
808
		}
809
		aa += stride;
810
		bb += stride;
811
	    }
812
	    break;
813

            
814
	case CAIRO_FORMAT_RGB24:
815
	    for (y = 0; y < height; y++) {
816
		const uint32_t *ua = (uint32_t *) aa;
817
		const uint32_t *ub = (uint32_t *) bb;
818
		for (x = 0; x < width; x++) {
819
		    if ((ua[x] & 0x00ffffff) != (ub[x] & 0x00ffffff)) {
820
			int channel;
821

            
822
			for (channel = 0; channel < 3; channel++) {
823
			    int va, vb;
824
			    unsigned diff;
825

            
826
			    va = (ua[x] >> (channel*8)) & 0xff;
827
			    vb = (ub[x] >> (channel*8)) & 0xff;
828
			    diff = abs (va - vb);
829
			    if (diff > slave->target->error_tolerance)
830
				return FALSE;
831
			}
832
		    }
833
		}
834
		aa += stride;
835
		bb += stride;
836
	    }
837
	    break;
838

            
839
	case CAIRO_FORMAT_A8:
840
	    for (y = 0; y < height; y++) {
841
		for (x = 0; x < width; x++) {
842
		    if (aa[x] != bb[x]) {
843
			unsigned diff = abs (aa[x] - bb[x]);
844
			if (diff > slave->target->error_tolerance)
845
			    return FALSE;
846
		    }
847
		}
848
		aa += stride;
849
		bb += stride;
850
	    }
851
	    break;
852

            
853
	case CAIRO_FORMAT_A1:
854
	    width /= 8;
855
	    for (y = 0; y < height; y++) {
856
		if (memcmp (aa, bb, width))
857
		    return FALSE;
858
		aa += stride;
859
		bb += stride;
860
	    }
861
	    break;
862

            
863
	case CAIRO_FORMAT_RGB30:
864
	case CAIRO_FORMAT_RGB16_565:
865
        case CAIRO_FORMAT_RGB96F:
866
        case CAIRO_FORMAT_RGBA128F:
867
	case CAIRO_FORMAT_INVALID:
868
	    assert (0);
869
	}
870

            
871
	return TRUE;
872
    }
873
}
874

            
875
static cairo_bool_t
876
check_images (struct slave *slaves, int num_slaves)
877
{
878
    int n;
879

            
880
    if (ignore_image_differences)
881
	return TRUE;
882

            
883
    for (n = 0; n < num_slaves; n++) {
884
	if (slaves[n].reference == NULL)
885
	    continue;
886

            
887
	if (! matches_reference (&slaves[n]))
888
	    return FALSE;
889
    }
890

            
891
    return TRUE;
892
}
893

            
894
static void
895
write_images (const char *trace, struct slave *slave, int num_slaves)
896
{
897
    while (num_slaves--) {
898
	if (slave->image != NULL && ! slave->is_recording) {
899
	    char *filename;
900

            
901
	    xasprintf (&filename, "%s-%s-fail.png",
902
		       trace, slave->target->name);
903
	    cairo_surface_write_to_png (slave->image, filename);
904
	    free (filename);
905

            
906
	    if (slave->difference) {
907
		xasprintf (&filename, "%s-%s-diff.png",
908
			   trace, slave->target->name);
909
		cairo_surface_write_to_png (slave->difference, filename);
910
		free (filename);
911
	    }
912
	}
913

            
914
	slave++;
915
    }
916
}
917

            
918
static void
919
write_result (const char *trace, struct slave *slave)
920
{
921
    static int index;
922
    char *filename;
923

            
924
    xasprintf (&filename, "%s-%s-pass-%d-%ld-%ld.png",
925
	       trace, slave->target->name, ++index,
926
	       slave->start_line, slave->end_line);
927
    cairo_surface_write_to_png (slave->image, filename);
928
    free (filename);
929
}
930

            
931
static void
932
write_trace (const char *trace, const char *id, struct slave *slave)
933
{
934
#if CAIRO_HAS_SCRIPT_SURFACE
935
    cairo_device_t *script;
936
    char *filename;
937

            
938
    assert (slave->is_recording);
939

            
940
    xasprintf (&filename, "%s-%s.trace", trace, id);
941

            
942
    script = cairo_script_create (filename);
943
    cairo_script_from_recording_surface (script, slave->image);
944
    cairo_device_destroy (script);
945

            
946
    free (filename);
947
#endif
948
}
949

            
950
static void
951
dump_traces (test_runner_t *tr,
952
	     const char *trace,
953
	     const char *target,
954
	     const char *fail)
955
{
956
#if CAIRO_HAS_SCRIPT_SURFACE
957
    struct context_closure *c;
958

            
959
    for (c = tr->contexts; c; c = c->next) {
960
	cairo_device_t *script;
961
	char *filename;
962

            
963
	xasprintf (&filename, "%s-%s-%s.%lu.trace",
964
		   trace, target, fail, c->start_line);
965

            
966
	script = cairo_script_create (filename);
967
	cairo_script_from_recording_surface (script, c->surface);
968
	cairo_device_destroy (script);
969

            
970
	free (filename);
971
    }
972
#endif
973
}
974

            
975
static unsigned long
976
allocate_image_for_slave (uint8_t *base,
977
			  unsigned long offset,
978
			  struct slave *slave)
979
{
980
    struct request_image rq;
981
    int size;
982
    uint8_t *data;
983

            
984
    assert (slave->image == NULL);
985

            
986
    readn (slave->fd, &rq, sizeof (rq));
987
    slave->image_serial = rq.id;
988
    slave->start_line = rq.start_line;
989
    slave->end_line = rq.end_line;
990

            
991
    slave->width = rq.width;
992
    slave->height = rq.height;
993

            
994
    if (DEBUG > 1) {
995
	printf ("allocate-image-for-slave: %s %lu [%lu, %lu] %ldx%ld stride=%lu => %lu, is-recording? %d\n",
996
		TARGET_NAME (slave->target),
997
		slave->image_serial,
998
		slave->start_line,
999
		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), "%ld", 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;
	}
    }
    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 ((unsigned char)*s))
	    s++;
	t = s;
	while (*t != '\0' && ! isspace ((unsigned char)*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
}