[cairo] Multithreaded cairo-test
Chris Wilson
chris at chris-wilson.co.uk
Fri Mar 23 04:45:05 PDT 2007
Similary make cairo-test run the test over multiple threads. In order to
acheive this we have to delay any printf until the test is complete and
move the globals to a per-thread context - for portability we'll
probably have to pass the context to the actual test functions. Again
WIP.
Any feedback welcome...
--
Chris Wilson
-------------- next part --------------
diff --git a/test/cairo-test.c b/test/cairo-test.c
index f3cf9e9..ed4b424 100755
--- a/test/cairo-test.c
+++ b/test/cairo-test.c
@@ -39,6 +39,7 @@
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
+#include <pthread.h>
#include <errno.h>
#include <string.h>
#if HAVE_FCFINI
@@ -75,16 +76,18 @@ static const char *fail_face = "", *normal_face = "";
#define NUM_DEVICE_OFFSETS 2
-/* Static data is messy, but we're coding for tests here, not a
- * general-purpose library, and it keeps the tests cleaner to avoid a
- * context object there, (though not a whole lot). */
-FILE *cairo_test_log_file = NULL;
-const char *srcdir;
+static const char *srcdir;
+static cairo_bool_t print_fail_on_stdout = TRUE;
-/* Used to catch crashes in a test, such that we report it as such and
- * continue testing, although one crasher may already have corrupted memory in
- * an nonrecoverable fashion. */
-jmp_buf jmpbuf;
+struct _cairo_test_context {
+ FILE *log_file;
+
+ /* Used to catch crashes in a test, such that we report it as such and
+ * continue testing, although one crasher may already have corrupted
+ * memory in an nonrecoverable fashion. */
+ jmp_buf jmpbuf;
+};
+static __thread cairo_test_context_t *cairo_test_context;
void
cairo_test_init (const char *test_name)
@@ -94,31 +97,26 @@ cairo_test_init (const char *test_name)
xasprintf (&log_name, "%s%s", test_name, CAIRO_TEST_LOG_SUFFIX);
xunlink (log_name);
- cairo_test_log_file = fopen (log_name, "a");
- if (cairo_test_log_file == NULL) {
+ cairo_test_context->log_file = fopen (log_name, "a");
+ if (cairo_test_context->log_file == NULL) {
fprintf (stderr, "Error opening log file: %s\n", log_name);
- cairo_test_log_file = stderr;
+ cairo_test_context->log_file = stderr;
}
free (log_name);
-
- printf ("\nTESTING %s\n", test_name);
}
void
cairo_test_fini (void)
{
- fclose (cairo_test_log_file);
- cairo_debug_reset_static_data ();
-#if HAVE_FCFINI
- FcFini ();
-#endif
+ if (cairo_test_context->log_file != stderr)
+ fclose (cairo_test_context->log_file);
}
void
cairo_test_log (const char *fmt, ...)
{
va_list va;
- FILE *file = cairo_test_log_file ? cairo_test_log_file : stderr;
+ FILE *file = cairo_test_context->log_file ? cairo_test_context->log_file : stderr;
va_start (va, fmt);
vfprintf (file, fmt, va);
@@ -189,8 +187,8 @@ done:
}
static cairo_test_status_t
-cairo_test_for_target (cairo_test_t *test,
- cairo_boilerplate_target_t *target,
+cairo_test_for_target (const cairo_test_t *test,
+ const cairo_boilerplate_target_t *target,
int dev_offset)
{
cairo_test_status_t status;
@@ -201,6 +199,8 @@ cairo_test_for_target (cairo_test_t *test,
cairo_content_t expected_content;
cairo_font_options_t *font_options;
const char *format;
+ unsigned int width, height;
+ void *closure = NULL;
/* Get the strings ready that we'll need. */
format = _cairo_test_content_name (target->content);
@@ -222,22 +222,19 @@ cairo_test_for_target (cairo_test_t *test,
offset_str, CAIRO_TEST_DIFF_SUFFIX);
/* Run the actual drawing code. */
- if (test->width && test->height) {
- test->width += dev_offset;
- test->height += dev_offset;
+ width = test->width;
+ height = test->height;
+ if (width && height) {
+ width += dev_offset;
+ height += dev_offset;
}
surface = (target->create_surface) (test->name,
target->content,
- test->width,
- test->height,
+ width,
+ height,
CAIRO_BOILERPLATE_MODE_TEST,
- &target->closure);
-
- if (test->width && test->height) {
- test->width -= dev_offset;
- test->height -= dev_offset;;
- }
+ &closure);
if (surface == NULL) {
cairo_test_log ("Error: Failed to set %s target\n", target->name);
@@ -352,7 +349,7 @@ UNWIND_SURFACE:
cairo_debug_reset_static_data ();
if (target->cleanup)
- target->cleanup (target->closure);
+ target->cleanup (closure);
UNWIND_STRINGS:
if (png_name)
@@ -371,48 +368,29 @@ UNWIND_STRINGS:
static void
segfault_handler (int signal)
{
- longjmp (jmpbuf, signal);
+ longjmp (cairo_test_context->jmpbuf, signal);
}
#endif
static cairo_test_status_t
-cairo_test_expecting (cairo_test_t *test,
- cairo_test_status_t expectation)
+cairo_test_run (const cairo_test_t *test)
{
/* we use volatile here to make sure values are not clobbered
* by longjmp */
volatile size_t i, j, num_targets;
- volatile cairo_bool_t limited_targets = FALSE, print_fail_on_stdout = TRUE;
const char *tname;
#ifdef HAVE_SIGNAL_H
void (*old_segfault_handler)(int);
#endif
volatile cairo_test_status_t status, ret;
cairo_boilerplate_target_t ** volatile targets_to_test;
+ cairo_test_context_t context;
-#ifdef HAVE_UNISTD_H
- if (isatty (2)) {
- fail_face = "\033[41m\033[37m\033[1m";
- normal_face = "\033[m";
- if (isatty (1))
- print_fail_on_stdout = FALSE;
- }
-#endif
-
- srcdir = getenv ("srcdir");
- if (!srcdir)
- srcdir = ".";
-
+ cairo_test_context = &context;
cairo_test_init (test->name);
- printf ("%s\n", test->description);
-
- if (expectation == CAIRO_TEST_FAILURE)
- printf ("Expecting failure\n");
if ((tname = getenv ("CAIRO_TEST_TARGET")) != NULL && *tname) {
- limited_targets = TRUE;
-
num_targets = 0;
targets_to_test = NULL;
@@ -434,7 +412,7 @@ cairo_test_expecting (cairo_test_t *test,
if (!found) {
fprintf (stderr, "Cannot test target '%.*s'\n", (int)(end - tname), tname);
- exit(-1);
+ return CAIRO_TEST_UNTESTED;
}
if (*end)
@@ -469,18 +447,17 @@ cairo_test_expecting (cairo_test_t *test,
ret = CAIRO_TEST_UNTESTED;
for (i = 0; i < num_targets; i++) {
for (j = 0; j < NUM_DEVICE_OFFSETS; j++) {
- cairo_boilerplate_target_t * volatile target = targets_to_test[i];
+ const cairo_boilerplate_target_t * volatile target = targets_to_test[i];
volatile int dev_offset = j * 25;
+ const char *result;
+ cairo_bool_t do_printf;
cairo_test_log ("Testing %s with %s target (dev offset %d)\n", test->name, target->name, dev_offset);
- printf ("%s-%s-%s [%d]:\t", test->name, target->name,
- _cairo_test_content_name (target->content),
- dev_offset);
#ifdef HAVE_SIGNAL_H
/* Set up a checkpoint to get back to in case of segfaults. */
old_segfault_handler = signal (SIGSEGV, segfault_handler);
- if (0 == setjmp (jmpbuf))
+ if (0 == setjmp (cairo_test_context->jmpbuf))
#endif
status = cairo_test_for_target (test, target, dev_offset);
#ifdef HAVE_SIGNAL_H
@@ -489,31 +466,21 @@ cairo_test_expecting (cairo_test_t *test,
signal (SIGSEGV, old_segfault_handler);
#endif
- cairo_test_log ("TEST: %s TARGET: %s FORMAT: %s OFFSET: %d RESULT: ",
- test->name, target->name,
- _cairo_test_content_name (target->content),
- dev_offset);
-
+ do_printf = TRUE;
switch (status) {
case CAIRO_TEST_SUCCESS:
- printf ("PASS\n");
- cairo_test_log ("PASS\n");
+ result = "PASS";
+ do_printf = TRUE;
if (ret == CAIRO_TEST_UNTESTED)
ret = CAIRO_TEST_SUCCESS;
break;
case CAIRO_TEST_UNTESTED:
- printf ("UNTESTED\n");
- cairo_test_log ("UNTESTED\n");
+ result = "UNTESTED";
+ do_printf = TRUE;
break;
case CAIRO_TEST_CRASHED:
- if (print_fail_on_stdout) {
- printf ("!!!CRASHED!!!\n");
- } else {
- /* eat the test name */
- printf ("\r");
- fflush (stdout);
- }
- cairo_test_log ("CRASHED\n");
+ result = "!!!CRASHED!!!";
+ do_printf = print_fail_on_stdout;
fprintf (stderr, "%s-%s-%s [%d]:\t%s!!!CRASHED!!!%s\n",
test->name, target->name,
_cairo_test_content_name (target->content), dev_offset,
@@ -522,58 +489,31 @@ cairo_test_expecting (cairo_test_t *test,
break;
default:
case CAIRO_TEST_FAILURE:
- if (expectation == CAIRO_TEST_FAILURE) {
- printf ("XFAIL\n");
- cairo_test_log ("XFAIL\n");
+ if (test->expectation == CAIRO_TEST_FAILURE) {
+ result = "XFAIL";
+ do_printf = TRUE;
} else {
- if (print_fail_on_stdout) {
- printf ("FAIL\n");
- } else {
- /* eat the test name */
- printf ("\r");
- fflush (stdout);
- }
+ result = "FAIL";
+ do_printf = print_fail_on_stdout;
fprintf (stderr, "%s-%s-%s [%d]:\t%sFAIL%s\n",
test->name, target->name,
_cairo_test_content_name (target->content), dev_offset,
fail_face, normal_face);
- cairo_test_log ("FAIL\n");
}
ret = status;
break;
}
- }
- }
-
- if (ret != CAIRO_TEST_SUCCESS)
- printf ("Check %s%s out for more information.\n", test->name, CAIRO_TEST_LOG_SUFFIX);
-
- /* if the set of targets to test was limited using CAIRO_TEST_TARGET, we
- * behave slightly differently, to ensure that limiting the targets does
- * not increase the number of tests failing. */
- if (limited_targets) {
-
- /* if all untested, success */
- if (ret == CAIRO_TEST_UNTESTED) {
- printf ("None of the tested backends passed, but tested targets are manually limited.\n"
- "Passing the test, to not fail the suite.\n");
- ret = CAIRO_TEST_SUCCESS;
- }
+ cairo_test_log ("TEST: %s TARGET: %s FORMAT: %s OFFSET: %d RESULT: %s\n",
+ test->name, target->name,
+ _cairo_test_content_name (target->content),
+ dev_offset, result);
- /* if all passed, but expecting failure, return failure to not
- * trigger an XPASS failure */
- if (expectation == CAIRO_TEST_FAILURE && ret == CAIRO_TEST_SUCCESS) {
- printf ("All tested backends passed, but tested targets are manually limited\n"
- "and the test suite expects this test to fail for at least one target.\n"
- "Intentionally failing the test, to not fail the suite.\n");
- ret = CAIRO_TEST_FAILURE;
+ if (do_printf) {
+ printf ("%s-%s-%s [%d]:\t%s\n", test->name, target->name,
+ _cairo_test_content_name (target->content),
+ dev_offset, result);
+ }
}
-
- } else {
-
- if (ret == CAIRO_TEST_UNTESTED)
- ret = CAIRO_TEST_FAILURE;
-
}
cairo_test_fini ();
@@ -583,11 +523,35 @@ cairo_test_expecting (cairo_test_t *test,
return ret;
}
+static void *
+cairo_test_run_wrapper (void *arg)
+{
+ return (void *) cairo_test_run (arg);
+}
+
+
+static cairo_bool_t
+has_string (const char *str)
+{
+ return str != NULL && *str != '\0';
+}
+
cairo_test_status_t
cairo_test (cairo_test_t *test)
{
+ cairo_test_status_t status = CAIRO_TEST_SUCCESS;
cairo_test_status_t expectation = CAIRO_TEST_SUCCESS;
const char *xfails;
+ unsigned int nthreads;
+
+#ifdef HAVE_UNISTD_H
+ if (isatty (2)) {
+ fail_face = "\033[41m\033[37m\033[1m";
+ normal_face = "\033[m";
+ if (isatty (1))
+ print_fail_on_stdout = FALSE;
+ }
+#endif
#ifdef _MSC_VER
/* We don't want an assert dialog, we want stderr */
@@ -612,15 +576,87 @@ cairo_test (cairo_test_t *test)
xfails = end;
}
}
+ test->expectation = expectation;
- return cairo_test_expecting (test, expectation);
+ srcdir = getenv ("srcdir");
+ if (!srcdir)
+ srcdir = ".";
+
+ printf ("\nTESTING %s\n", test->name);
+ printf ("%s\n", test->description);
+ if (expectation == CAIRO_TEST_FAILURE)
+ printf ("Expecting failure\n");
+
+ nthreads = 0;
+ if (getenv("CAIRO_TEST_THREADS"))
+ nthreads = strtoul(getenv("CAIRO_TEST_THREADS"), NULL, 0);
+ if (nthreads) {
+ unsigned int i;
+ pthread_t *threads = xmalloc (sizeof (pthread_t) * nthreads);
+
+ for (i = 0; i < nthreads; i++) {
+ if (pthread_create (&threads[i], NULL,
+ cairo_test_run_wrapper, (void *) test) < 0) {
+ fprintf (stderr, "Unable to create thread: %s\n",
+ strerror (errno));
+ exit (1);
+ }
+ }
+ for (i = 0; i < nthreads; i++) {
+ void *result;
+ pthread_join (threads[i], &result);
+ if (status == CAIRO_TEST_SUCCESS)
+ status = (cairo_test_status_t ) result;
+ }
+ free (threads);
+
+ } else {
+ status = cairo_test_run (test);
+ }
+
+ cairo_debug_reset_static_data ();
+#if HAVE_FCFINI
+ FcFini ();
+#endif
+
+ if (status != CAIRO_TEST_SUCCESS)
+ printf ("Check %s%s out for more information.\n", test->name, CAIRO_TEST_LOG_SUFFIX);
+
+ /* if the set of targets to test was limited using CAIRO_TEST_TARGET, we
+ * behave slightly differently, to ensure that limiting the targets does
+ * not increase the number of tests failing. */
+ if (has_string (getenv ("CAIRO_TEST_TARGET"))) {
+
+ /* if all untested, success */
+ if (status == CAIRO_TEST_UNTESTED) {
+ printf ("None of the tested backends passed, but tested targets are manually limited.\n"
+ "Passing the test, to not fail the suite.\n");
+ status = CAIRO_TEST_SUCCESS;
+ }
+
+ /* if all passed, but expecting failure, return failure to not
+ * trigger an XPASS failure */
+ if (expectation == CAIRO_TEST_FAILURE && status == CAIRO_TEST_SUCCESS) {
+ printf ("All tested backends passed, but tested targets are manually limited\n"
+ "and the test suite expects this test to fail for at least one target.\n"
+ "Intentionally failing the test, to not fail the suite.\n");
+ status = CAIRO_TEST_FAILURE;
+ }
+
+ } else {
+
+ if (status == CAIRO_TEST_UNTESTED)
+ status = CAIRO_TEST_FAILURE;
+
+ }
+
+ return status;
}
cairo_surface_t *
cairo_test_create_surface_from_png (const char *filename)
{
cairo_surface_t *image;
- char *srcdir = getenv ("srcdir");
image = cairo_image_surface_create_from_png (filename);
if (cairo_surface_status(image)) {
diff --git a/test/cairo-test.h b/test/cairo-test.h
index d3612ba..9b9625a 100755
--- a/test/cairo-test.h
+++ b/test/cairo-test.h
@@ -66,6 +66,8 @@ typedef enum cairo_test_status {
CAIRO_TEST_CRASHED
} cairo_test_status_t;
+typedef struct _cairo_test_context cairo_test_context_t;
+
typedef cairo_test_status_t (cairo_test_draw_function_t) (cairo_t *cr, int width, int height);
typedef struct _cairo_test {
@@ -74,6 +76,7 @@ typedef struct _cairo_test {
int width;
int height;
cairo_test_draw_function_t *draw;
+ cairo_test_status_t expectation;
} cairo_test_t;
/* The standard test interface which works by examining result image.
More information about the cairo
mailing list