[test] Add support for memfault.

Add the core support to cairo-test for running the test-suite under a
malloc fault injector. This commit contains the adjustments to
cairo_test_run() to repeat the test if it detects a failure due to fault
injection and complains if it detects unreported faults or memory leaks.
This commit is contained in:
Chris Wilson 2008-10-16 13:47:27 +01:00
parent 974fabfe05
commit bccfdf7d93
3 changed files with 235 additions and 55 deletions

View file

@ -76,3 +76,20 @@ dnl PHP_ADD_MAKEFILE_FRAGMENT($abs_srcdir/Makefile.gcov, $abs_srcdir)
fi
AM_CONDITIONAL(CAIRO_HAS_LCOV, test "x$cairo_has_lcov" = "xyes")
dnl ===========================================================================
dnl Check for some custom valgrind modules
PKG_CHECK_MODULES(VALGRIND, valgrind, [
safe_CFLAGS="$CFLAGS"
safe_CPPFLAGS="$CPPFLAGS"
CFLAGS="$CFLAGS $VALGRIND_CFLAGS"
CPPFLAGS="$CPPFLAGS $VALGRIND_CFLAGS"
AC_CHECK_HEADER([valgrind.h], [AC_DEFINE([HAVE_VALGRIND], [1],
[Define to 1 if you have Valgrind])])
AC_CHECK_HEADER([lockdep.h], [AC_DEFINE([HAVE_LOCKDEP], [1],
[Define to 1 if you have the Valgrind lockdep tool])])
AC_CHECK_HEADER([memfault.h], [AC_DEFINE([HAVE_MEMFAULT], [1],
[Define to 1 if you have the Valgrind memfault tool])])
CAIRO_CFLAGS="$VALGRIND_CFLAGS $CAIRO_CFLAGS"
CFLAGS="$safe_CFLAGS"
CPPFLAGS="$safe_CPPFLAGS"
], AC_MSG_RESULT(no))

View file

@ -38,7 +38,7 @@
#include <fenv.h>
#endif
#include <assert.h>
#ifdef HAVE_UNISTD_H
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
@ -46,13 +46,26 @@
#if HAVE_FCFINI
#include <fontconfig/fontconfig.h>
#endif
#ifdef HAVE_PTHREAD_H
#if HAVE_PTHREAD_H
#include <pthread.h>
#endif
#ifdef HAVE_SYS_STAT_H
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if HAVE_VALGRIND
#include <valgrind.h>
#else
#define RUNNING_ON_VALGRIND 0
#endif
#if HAVE_MEMFAULT
#include <memfault.h>
#define MF(x) x
#else
#define MF(x)
#endif
#include "cairo-test.h"
#include "buffer-diff.h"
@ -115,6 +128,8 @@ _cairo_test_init (cairo_test_context_t *ctx,
{
char *log_name;
MF (VALGRIND_DISABLE_FAULTS ());
#if HAVE_FEENABLEEXCEPT
feenableexcept (FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
#endif
@ -123,6 +138,14 @@ _cairo_test_init (cairo_test_context_t *ctx,
ctx->test_name = test_name;
ctx->expectation = expectation;
ctx->malloc_failure = 0;
#if HAVE_MEMFAULT
if (getenv ("CAIRO_TEST_MALLOC_FAILURE"))
ctx->malloc_failure = atoi (getenv ("CAIRO_TEST_MALLOC_FAILURE"));
if (ctx->malloc_failure && ! RUNNING_ON_MEMFAULT ())
ctx->malloc_failure = 0;
#endif
xasprintf (&log_name, "%s%s", test_name, CAIRO_TEST_LOG_SUFFIX);
_xunlink (NULL, log_name);
@ -164,8 +187,12 @@ cairo_test_init (cairo_test_context_t *ctx,
}
static void
cairo_test_init_thread (cairo_test_context_t *ctx, cairo_test_context_t *master, int thread)
cairo_test_init_thread (cairo_test_context_t *ctx,
cairo_test_context_t *master,
int thread)
{
MF (VALGRIND_DISABLE_FAULTS ());
*ctx = *master;
ctx->thread = thread;
}
@ -328,6 +355,7 @@ cairo_test_target_has_similar (const cairo_test_context_t *ctx,
cairo_bool_t has_similar;
cairo_t * cr;
cairo_surface_t *similar;
cairo_status_t status;
void *closure;
/* ignore image intermediate targets */
@ -337,30 +365,39 @@ cairo_test_target_has_similar (const cairo_test_context_t *ctx,
if (getenv ("CAIRO_TEST_IGNORE_SIMILAR"))
return FALSE;
surface = (target->create_surface) (ctx->test->name,
target->content,
ctx->test->width,
ctx->test->height,
ctx->test->width + 25 * NUM_DEVICE_OFFSETS,
ctx->test->height + 25 * NUM_DEVICE_OFFSETS,
CAIRO_BOILERPLATE_MODE_TEST,
0,
&closure);
if (surface == NULL)
return FALSE;
do {
do {
surface = (target->create_surface) (ctx->test->name,
target->content,
ctx->test->width,
ctx->test->height,
ctx->test->width + 25 * NUM_DEVICE_OFFSETS,
ctx->test->height + 25 * NUM_DEVICE_OFFSETS,
CAIRO_BOILERPLATE_MODE_TEST,
0,
&closure);
if (surface == NULL)
return FALSE;
} while (cairo_test_malloc_failure (ctx, cairo_surface_status (surface)));
cr = cairo_create (surface);
cairo_push_group_with_content (cr,
cairo_boilerplate_content (target->content));
similar = cairo_get_group_target (cr);
if (cairo_surface_status (surface))
return FALSE;
has_similar = cairo_surface_get_type (similar) == cairo_surface_get_type (surface);
cairo_destroy (cr);
cairo_surface_destroy (surface);
cr = cairo_create (surface);
cairo_push_group_with_content (cr,
cairo_boilerplate_content (target->content));
similar = cairo_get_group_target (cr);
status = cairo_surface_status (similar);
if (target->cleanup)
target->cleanup (closure);
has_similar = cairo_surface_get_type (similar) == cairo_surface_get_type (surface);
cairo_destroy (cr);
cairo_surface_destroy (surface);
if (target->cleanup)
target->cleanup (closure);
} while (cairo_test_malloc_failure (ctx, status));
return has_similar;
}
@ -436,7 +473,7 @@ static cairo_bool_t
cairo_test_file_is_older (const char *filename,
const char *ref_filename)
{
#ifdef HAVE_SYS_STAT_H
#if HAVE_SYS_STAT_H
struct stat st, ref;
if (stat (filename, &st) < 0)
@ -536,8 +573,10 @@ cairo_test_for_target (cairo_test_context_t *ctx,
const char *format;
cairo_bool_t have_output = FALSE;
cairo_bool_t have_result = FALSE;
int malloc_failure_iterations = ctx->malloc_failure;
void *closure;
int width, height;
int last_fault_count = 0;
/* Get the strings ready that we'll need. */
format = cairo_boilerplate_content_name (target->content);
@ -590,6 +629,14 @@ cairo_test_for_target (cairo_test_context_t *ctx,
height += dev_offset;
}
REPEAT:
#if HAVE_MEMFAULT
VALGRIND_CLEAR_FAULTS ();
VALGRIND_RESET_LEAKS ();
ctx->last_fault_count = 0;
last_fault_count = VALGRIND_COUNT_FAULTS ();
VALGRIND_ENABLE_FAULTS ();
#endif
have_output = FALSE;
have_result = FALSE;
@ -609,7 +656,11 @@ cairo_test_for_target (cairo_test_context_t *ctx,
goto UNWIND_STRINGS;
}
if (cairo_test_malloc_failure (ctx, cairo_surface_status (surface)))
goto REPEAT;
if (cairo_surface_status (surface)) {
MF (VALGRIND_PRINT_FAULTS ());
cairo_test_log (ctx, "Error: Created an error surface\n");
ret = CAIRO_TEST_FAILURE;
goto UNWIND_STRINGS;
@ -617,6 +668,7 @@ cairo_test_for_target (cairo_test_context_t *ctx,
/* Check that we created a surface of the expected type. */
if (cairo_surface_get_type (surface) != target->expected_type) {
MF (VALGRIND_PRINT_FAULTS ());
cairo_test_log (ctx, "Error: Created surface is of type %d (expected %d)\n",
cairo_surface_get_type (surface), target->expected_type);
ret = CAIRO_TEST_FAILURE;
@ -629,6 +681,7 @@ cairo_test_for_target (cairo_test_context_t *ctx,
expected_content = cairo_boilerplate_content (target->content);
if (cairo_surface_get_content (surface) != expected_content) {
MF (VALGRIND_PRINT_FAULTS ());
cairo_test_log (ctx, "Error: Created surface has content %d (expected %d)\n",
cairo_surface_get_content (surface), expected_content);
ret = CAIRO_TEST_FAILURE;
@ -639,8 +692,18 @@ cairo_test_for_target (cairo_test_context_t *ctx,
cr = cairo_create (surface);
if (cairo_set_user_data (cr, &_cairo_test_context_key, (void*) ctx, NULL)) {
#if HAVE_MEMFAULT
cairo_destroy (cr);
cairo_surface_destroy (surface);
if (target->cleanup)
target->cleanup (closure);
goto REPEAT;
#else
ret = CAIRO_TEST_FAILURE;
goto UNWIND_CAIRO;
#endif
}
if (similar)
@ -672,6 +735,35 @@ cairo_test_for_target (cairo_test_context_t *ctx,
cairo_paint (cr);
}
#if HAVE_MEMFAULT
VALGRIND_DISABLE_FAULTS ();
/* repeat test after malloc failure injection */
if (ctx->malloc_failure &&
VALGRIND_COUNT_FAULTS () - last_fault_count > 0 &&
(status == CAIRO_TEST_NO_MEMORY ||
cairo_status (cr) == CAIRO_STATUS_NO_MEMORY ||
cairo_surface_status (surface) == CAIRO_STATUS_NO_MEMORY))
{
cairo_destroy (cr);
cairo_surface_destroy (surface);
if (target->cleanup)
target->cleanup (closure);
if (ctx->thread == 0) {
cairo_debug_reset_static_data ();
#if HAVE_FCFINI
FcFini ();
#endif
if (VALGRIND_COUNT_LEAKS () > 0) {
VALGRIND_PRINT_FAULTS ();
VALGRIND_PRINT_LEAKS ();
}
}
goto REPEAT;
}
#endif
/* Then, check all the different ways it could fail. */
if (status) {
cairo_test_log (ctx, "Error: Function under test failed\n");
@ -686,6 +778,13 @@ cairo_test_for_target (cairo_test_context_t *ctx,
goto UNWIND_CAIRO;
}
#if HAVE_MEMFAULT
if (VALGRIND_COUNT_FAULTS () - last_fault_count > 0) {
VALGRIND_PRINTF ("Unreported memfaults...");
VALGRIND_PRINT_FAULTS ();
}
#endif
/* Skip image check for tests with no image (width,height == 0,0) */
if (ctx->test->width != 0 && ctx->test->height != 0) {
cairo_surface_t *ref_image;
@ -869,6 +968,23 @@ cairo_test_for_target (cairo_test_context_t *ctx,
}
UNWIND_CAIRO:
if (test_filename != NULL) {
free (test_filename);
test_filename = NULL;
}
if (fail_filename != NULL) {
free (fail_filename);
fail_filename = NULL;
}
if (pass_filename != NULL) {
free (pass_filename);
pass_filename = NULL;
}
#if HAVE_MEMFAULT
if (ret == CAIRO_TEST_FAILURE && ctx->expectation != CAIRO_TEST_FAILURE)
VALGRIND_PRINT_FAULTS ();
#endif
cairo_destroy (cr);
UNWIND_SURFACE:
cairo_surface_destroy (surface);
@ -876,6 +992,28 @@ UNWIND_SURFACE:
if (target->cleanup)
target->cleanup (closure);
#if HAVE_MEMFAULT
if (ctx->thread == 0) {
cairo_debug_reset_static_data ();
#if HAVE_FCFINI
FcFini ();
#endif
if (VALGRIND_COUNT_LEAKS () > 0) {
if (ret != CAIRO_TEST_FAILURE ||
ctx->expectation == CAIRO_TEST_FAILURE)
{
VALGRIND_PRINT_FAULTS ();
}
VALGRIND_PRINT_LEAKS ();
}
}
if (ret == CAIRO_TEST_SUCCESS && --malloc_failure_iterations > 0)
goto REPEAT;
#endif
if (ctx->thread == 0) {
if (have_output)
cairo_test_log (ctx, "OUTPUT: %s\n", png_name);
@ -888,12 +1026,6 @@ UNWIND_SURFACE:
}
UNWIND_STRINGS:
if (test_filename != NULL)
free (test_filename);
if (fail_filename != NULL)
free (fail_filename);
if (pass_filename != NULL)
free (pass_filename);
if (png_name)
free (png_name);
if (ref_name)
@ -930,7 +1062,7 @@ cairo_test_run (cairo_test_context_t *ctx)
volatile cairo_bool_t print_fail_on_stdout = ctx->thread == 0;
volatile cairo_test_status_t status, ret;
#ifdef HAVE_UNISTD_H
#if HAVE_UNISTD_H
if (ctx->thread == 0 && isatty (2)) {
fail_face = "\033[41m\033[37m\033[1m";
normal_face = "\033[m";
@ -976,7 +1108,7 @@ cairo_test_run (cairo_test_context_t *ctx)
}
#if defined(HAVE_SIGNAL_H) && defined(HAVE_SETJMP_H)
if (ctx->thread == 0) {
if (ctx->thread == 0 && ! RUNNING_ON_VALGRIND) {
void (* volatile old_segfault_handler)(int);
void (* volatile old_sigpipe_handler)(int);
@ -1037,6 +1169,7 @@ cairo_test_run (cairo_test_context_t *ctx)
ret = CAIRO_TEST_FAILURE;
break;
default:
case CAIRO_TEST_NO_MEMORY:
case CAIRO_TEST_FAILURE:
if (ctx->expectation == CAIRO_TEST_FAILURE) {
printf ("XFAIL\n");
@ -1080,6 +1213,7 @@ cairo_test_run (cairo_test_context_t *ctx)
ret = CAIRO_TEST_FAILURE;
break;
default:
case CAIRO_TEST_NO_MEMORY:
case CAIRO_TEST_FAILURE:
if (ctx->expectation == CAIRO_TEST_FAILURE) {
printf ("XFAIL\n");
@ -1102,7 +1236,7 @@ cairo_test_run (cairo_test_context_t *ctx)
return ret;
}
#ifdef HAVE_PTHREAD_H
#if HAVE_PTHREAD_H
typedef struct _cairo_test_thread {
pthread_t thread;
cairo_test_context_t *ctx;
@ -1141,7 +1275,7 @@ cairo_test_expecting (const cairo_test_t *test,
if (expectation == CAIRO_TEST_FAILURE)
printf ("Expecting failure\n");
#ifdef HAVE_PTHREAD_H
#if HAVE_PTHREAD_H
num_threads = 0;
if (getenv ("CAIRO_TEST_NUM_THREADS"))
num_threads = atoi (getenv ("CAIRO_TEST_NUM_THREADS"));
@ -1235,9 +1369,11 @@ cairo_test_create_surface_from_png (const cairo_test_context_t *ctx,
const char *filename)
{
cairo_surface_t *image;
cairo_status_t status;
image = cairo_image_surface_create_from_png (filename);
if (cairo_surface_status(image)) {
status = cairo_surface_status (image);
if (status == CAIRO_STATUS_FILE_NOT_FOUND) {
/* expect not found when running with srcdir != builddir
* such as when 'make distcheck' is run
*/
@ -1270,13 +1406,16 @@ cairo_test_create_pattern_from_png (const cairo_test_context_t *ctx,
return pattern;
}
static cairo_status_t
_draw_check (cairo_surface_t *surface, int width, int height)
static cairo_surface_t *
_draw_check (int width, int height)
{
cairo_surface_t *surface;
cairo_t *cr;
cairo_status_t status;
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 12, 12);
cr = cairo_create (surface);
cairo_surface_destroy (surface);
cairo_set_source_rgb (cr, 0.75, 0.75, 0.75); /* light gray */
cairo_paint (cr);
@ -1285,37 +1424,29 @@ _draw_check (cairo_surface_t *surface, int width, int height)
cairo_rectangle (cr, 0, height / 2, width / 2, height / 2);
cairo_fill (cr);
status = cairo_status (cr);
surface = cairo_surface_reference (cairo_get_target (cr));
cairo_destroy (cr);
return status;
return surface;
}
cairo_status_t
void
cairo_test_paint_checkered (cairo_t *cr)
{
cairo_status_t status;
cairo_surface_t *check;
check = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 12, 12);
status = _draw_check (check, 12, 12);
if (status) {
cairo_surface_destroy (check);
return status;
}
check = _draw_check (12, 12);
cairo_save (cr);
cairo_set_source_surface (cr, check, 0, 0);
cairo_surface_destroy (check);
cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_NEAREST);
cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
cairo_paint (cr);
cairo_restore (cr);
status = cairo_surface_status (check);
cairo_surface_destroy (check);
return status;
}
cairo_bool_t
@ -1336,3 +1467,27 @@ cairo_test_is_target_enabled (const cairo_test_context_t *ctx, const char *targe
return FALSE;
}
cairo_bool_t
cairo_test_malloc_failure (const cairo_test_context_t *ctx,
cairo_status_t status)
{
int n_faults;
if (! ctx->malloc_failure)
return FALSE;
if (status != CAIRO_STATUS_NO_MEMORY)
return FALSE;
#if HAVE_MEMFAULT
/* prevent infinite loops... */
n_faults = VALGRIND_COUNT_FAULTS ();
if (n_faults == ctx->last_fault_count)
return FALSE;
((cairo_test_context_t *) ctx)->last_fault_count = n_faults;
#endif
return TRUE;
}

View file

@ -61,6 +61,7 @@ typedef unsigned __int64 uint64_t;
typedef enum cairo_test_status {
CAIRO_TEST_SUCCESS = 0,
CAIRO_TEST_NO_MEMORY,
CAIRO_TEST_FAILURE,
CAIRO_TEST_CRASHED,
CAIRO_TEST_UNTESTED = 77 /* match automake's skipped exit status */
@ -128,6 +129,9 @@ struct _cairo_test_context {
cairo_bool_t limited_targets;
cairo_boilerplate_target_t **targets_to_test;
int malloc_failure;
int last_fault_count;
int thread;
};
@ -175,7 +179,7 @@ cairo_pattern_t *
cairo_test_create_pattern_from_png (const cairo_test_context_t *ctx,
const char *filename);
cairo_status_t
void
cairo_test_paint_checkered (cairo_t *cr);
#define CAIRO_TEST_DOUBLE_EQUALS(a,b) (fabs((a)-(b)) < 0.00001)
@ -184,6 +188,10 @@ cairo_bool_t
cairo_test_is_target_enabled (const cairo_test_context_t *ctx,
const char *target);
cairo_bool_t
cairo_test_malloc_failure (const cairo_test_context_t *ctx,
cairo_status_t status);
char *
cairo_test_reference_image_filename (const cairo_test_context_t *ctx,
const char *base_name,