[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