mirror of
https://gitlab.freedesktop.org/cairo/cairo.git
synced 2025-12-20 14:00:10 +01:00
1803 lines
47 KiB
C
1803 lines
47 KiB
C
/*
|
|
* Copyright © 2004 Red Hat, Inc.
|
|
* Copyright © 2008 Chris Wilson
|
|
*
|
|
* Permission to use, copy, modify, distribute, and sell this software
|
|
* and its documentation for any purpose is hereby granted without
|
|
* fee, provided that the above copyright notice appear in all copies
|
|
* and that both that copyright notice and this permission notice
|
|
* appear in supporting documentation, and that the name of
|
|
* Red Hat, Inc. not be used in advertising or publicity pertaining to
|
|
* distribution of the software without specific, written prior
|
|
* permission. Red Hat, Inc. makes no representations about the
|
|
* suitability of this software for any purpose. It is provided "as
|
|
* is" without express or implied warranty.
|
|
*
|
|
* RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
|
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
* FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
|
|
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
|
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
|
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* Author: Carl D. Worth <cworth@cworth.org>
|
|
* Chris Wilson <chris@chris-wilson.co.uk>
|
|
*/
|
|
|
|
#define _GNU_SOURCE 1 /* for feenableexcept() et al */
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#if HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#if HAVE_FCFINI
|
|
#include <fontconfig/fontconfig.h>
|
|
#endif
|
|
#if CAIRO_HAS_REAL_PTHREAD
|
|
#include <pthread.h>
|
|
#endif
|
|
#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-private.h"
|
|
|
|
#include "buffer-diff.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#include <crtdbg.h>
|
|
#include <direct.h>
|
|
#define F_OK 0
|
|
#define HAVE_MKDIR 1
|
|
#define mkdir _mkdir
|
|
#endif
|
|
|
|
#ifndef FALSE
|
|
#define FALSE 0
|
|
#endif
|
|
#ifndef TRUE
|
|
#define TRUE !FALSE
|
|
#endif
|
|
|
|
#if ! HAVE_ALARM || ! defined(SIGALRM)
|
|
#define alarm(X);
|
|
#endif
|
|
|
|
static const cairo_user_data_key_t _cairo_test_context_key;
|
|
|
|
static void
|
|
_xunlink (const cairo_test_context_t *ctx, const char *pathname);
|
|
|
|
static const char *fail_face = "", *xfail_face="", *normal_face = "";
|
|
static cairo_bool_t print_fail_on_stdout;
|
|
static int cairo_test_timeout = 60;
|
|
|
|
#define NUM_DEVICE_OFFSETS 2
|
|
#define NUM_DEVICE_SCALE 2
|
|
|
|
cairo_bool_t
|
|
cairo_test_mkdir (const char *path)
|
|
{
|
|
#if ! HAVE_MKDIR
|
|
return FALSE;
|
|
#elif HAVE_MKDIR == 1
|
|
if (mkdir (path) == 0)
|
|
return TRUE;
|
|
#elif HAVE_MKDIR == 2
|
|
if (mkdir (path, 0770) == 0)
|
|
return TRUE;
|
|
#else
|
|
#error Bad value for HAVE_MKDIR
|
|
#endif
|
|
|
|
return errno == EEXIST;
|
|
}
|
|
|
|
static char *
|
|
_cairo_test_fixup_name (const char *original)
|
|
{
|
|
char *name, *s;
|
|
|
|
s = name = xstrdup (original);
|
|
while ((s = strchr (s, '_')) != NULL)
|
|
*s++ = '-';
|
|
|
|
return name;
|
|
}
|
|
|
|
char *
|
|
cairo_test_get_name (const cairo_test_t *test)
|
|
{
|
|
return _cairo_test_fixup_name (test->name);
|
|
}
|
|
|
|
static void
|
|
_cairo_test_init (cairo_test_context_t *ctx,
|
|
const cairo_test_context_t *parent,
|
|
const cairo_test_t *test,
|
|
const char *test_name,
|
|
const char *output)
|
|
{
|
|
char *log_name;
|
|
|
|
MF (MEMFAULT_DISABLE_FAULTS ());
|
|
|
|
#if HAVE_FEENABLEEXCEPT
|
|
feenableexcept (FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
|
|
#endif
|
|
|
|
ctx->test = test;
|
|
ctx->test_name = _cairo_test_fixup_name (test_name);
|
|
ctx->output = output;
|
|
|
|
cairo_test_mkdir (ctx->output);
|
|
|
|
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
|
|
|
|
ctx->timeout = cairo_test_timeout;
|
|
if (getenv ("CAIRO_TEST_TIMEOUT"))
|
|
ctx->timeout = atoi (getenv ("CAIRO_TEST_TIMEOUT"));
|
|
|
|
xasprintf (&log_name, "%s/%s%s", ctx->output, ctx->test_name, CAIRO_TEST_LOG_SUFFIX);
|
|
_xunlink (NULL, log_name);
|
|
|
|
ctx->log_file = fopen (log_name, "a");
|
|
if (ctx->log_file == NULL) {
|
|
fprintf (stderr, "Error opening log file: %s\n", log_name);
|
|
ctx->log_file = stderr;
|
|
}
|
|
free (log_name);
|
|
|
|
ctx->ref_name = NULL;
|
|
ctx->ref_image = NULL;
|
|
ctx->ref_image_flattened = NULL;
|
|
|
|
if (parent != NULL) {
|
|
ctx->targets_to_test = parent->targets_to_test;
|
|
ctx->num_targets = parent->num_targets;
|
|
ctx->limited_targets = parent->limited_targets;
|
|
ctx->own_targets = FALSE;
|
|
|
|
ctx->srcdir = parent->srcdir;
|
|
ctx->refdir = parent->refdir;
|
|
} else {
|
|
int tmp_num_targets;
|
|
cairo_bool_t tmp_limited_targets;
|
|
|
|
ctx->targets_to_test = cairo_boilerplate_get_targets (&tmp_num_targets, &tmp_limited_targets);
|
|
ctx->num_targets = tmp_num_targets;
|
|
ctx->limited_targets = tmp_limited_targets;
|
|
ctx->own_targets = TRUE;
|
|
|
|
ctx->srcdir = getenv ("srcdir");
|
|
if (ctx->srcdir == NULL)
|
|
ctx->srcdir = ".";
|
|
|
|
ctx->refdir = getenv ("CAIRO_REF_DIR");
|
|
}
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
if (*fail_face == '\0' && isatty (2)) {
|
|
fail_face = "\033[41;37;1m";
|
|
xfail_face = "\033[43;37;1m";
|
|
normal_face = "\033[m";
|
|
if (isatty (1))
|
|
print_fail_on_stdout = FALSE;
|
|
}
|
|
#endif
|
|
|
|
printf ("\nTESTING %s\n", ctx->test_name);
|
|
}
|
|
|
|
void
|
|
_cairo_test_context_init_for_test (cairo_test_context_t *ctx,
|
|
const cairo_test_context_t *parent,
|
|
const cairo_test_t *test)
|
|
{
|
|
_cairo_test_init (ctx, parent, test, test->name, CAIRO_TEST_OUTPUT_DIR);
|
|
}
|
|
|
|
void
|
|
cairo_test_init (cairo_test_context_t *ctx,
|
|
const char *test_name,
|
|
const char *output)
|
|
{
|
|
_cairo_test_init (ctx, NULL, NULL, test_name, output);
|
|
}
|
|
|
|
void
|
|
cairo_test_fini (cairo_test_context_t *ctx)
|
|
{
|
|
if (ctx->log_file == NULL)
|
|
return;
|
|
|
|
if (ctx->log_file != stderr)
|
|
fclose (ctx->log_file);
|
|
ctx->log_file = NULL;
|
|
|
|
free (ctx->ref_name);
|
|
cairo_surface_destroy (ctx->ref_image);
|
|
cairo_surface_destroy (ctx->ref_image_flattened);
|
|
|
|
if (ctx->test_name != NULL)
|
|
free ((char *) ctx->test_name);
|
|
|
|
if (ctx->own_targets)
|
|
cairo_boilerplate_free_targets (ctx->targets_to_test);
|
|
|
|
cairo_boilerplate_fini ();
|
|
|
|
cairo_debug_reset_static_data ();
|
|
#if HAVE_FCFINI
|
|
FcFini ();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
cairo_test_logv (const cairo_test_context_t *ctx,
|
|
const char *fmt, va_list va)
|
|
{
|
|
FILE *file = ctx && ctx->log_file ? ctx->log_file : stderr;
|
|
vfprintf (file, fmt, va);
|
|
}
|
|
|
|
void
|
|
cairo_test_log (const cairo_test_context_t *ctx, const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
|
|
va_start (va, fmt);
|
|
cairo_test_logv (ctx, fmt, va);
|
|
va_end (va);
|
|
}
|
|
|
|
static void
|
|
_xunlink (const cairo_test_context_t *ctx, const char *pathname)
|
|
{
|
|
if (unlink (pathname) < 0 && errno != ENOENT) {
|
|
cairo_test_log (ctx, "Error: Cannot remove %s: %s\n",
|
|
pathname, strerror (errno));
|
|
exit (1);
|
|
}
|
|
}
|
|
|
|
char *
|
|
cairo_test_reference_filename (const cairo_test_context_t *ctx,
|
|
const char *base_name,
|
|
const char *test_name,
|
|
const char *target_name,
|
|
const char *base_target_name,
|
|
const char *format,
|
|
const char *suffix,
|
|
const char *extension)
|
|
{
|
|
char *ref_name = NULL;
|
|
|
|
/* First look for a previous build for comparison. */
|
|
if (ctx->refdir != NULL && strcmp(suffix, CAIRO_TEST_REF_SUFFIX) == 0) {
|
|
xasprintf (&ref_name, "%s/%s" CAIRO_TEST_OUT_SUFFIX "%s",
|
|
ctx->refdir,
|
|
base_name,
|
|
extension);
|
|
if (access (ref_name, F_OK) != 0)
|
|
free (ref_name);
|
|
else
|
|
goto done;
|
|
}
|
|
|
|
if (target_name != NULL) {
|
|
/* Next look for a target/format-specific reference image. */
|
|
xasprintf (&ref_name, "%s/reference/%s.%s.%s%s%s",
|
|
ctx->srcdir,
|
|
test_name,
|
|
target_name,
|
|
format,
|
|
suffix,
|
|
extension);
|
|
if (access (ref_name, F_OK) != 0)
|
|
free (ref_name);
|
|
else
|
|
goto done;
|
|
|
|
/* Next, look for target-specific reference image. */
|
|
xasprintf (&ref_name, "%s/reference/%s.%s%s%s",
|
|
ctx->srcdir,
|
|
test_name,
|
|
target_name,
|
|
suffix,
|
|
extension);
|
|
if (access (ref_name, F_OK) != 0)
|
|
free (ref_name);
|
|
else
|
|
goto done;
|
|
}
|
|
|
|
if (base_target_name != NULL) {
|
|
/* Next look for a base/format-specific reference image. */
|
|
xasprintf (&ref_name, "%s/reference/%s.%s.%s%s%s",
|
|
ctx->srcdir,
|
|
test_name,
|
|
base_target_name,
|
|
format,
|
|
suffix,
|
|
extension);
|
|
if (access (ref_name, F_OK) != 0)
|
|
free (ref_name);
|
|
else
|
|
goto done;
|
|
|
|
/* Next, look for base-specific reference image. */
|
|
xasprintf (&ref_name, "%s/reference/%s.%s%s%s",
|
|
ctx->srcdir,
|
|
test_name,
|
|
base_target_name,
|
|
suffix,
|
|
extension);
|
|
if (access (ref_name, F_OK) != 0)
|
|
free (ref_name);
|
|
else
|
|
goto done;
|
|
}
|
|
|
|
/* Next, look for format-specific reference image. */
|
|
xasprintf (&ref_name, "%s/reference/%s.%s%s%s",
|
|
ctx->srcdir,
|
|
test_name,
|
|
format,
|
|
suffix,
|
|
extension);
|
|
if (access (ref_name, F_OK) != 0)
|
|
free (ref_name);
|
|
else
|
|
goto done;
|
|
|
|
/* Finally, look for the standard reference image. */
|
|
xasprintf (&ref_name, "%s/reference/%s%s%s", ctx->srcdir,
|
|
test_name,
|
|
suffix,
|
|
extension);
|
|
if (access (ref_name, F_OK) != 0)
|
|
free (ref_name);
|
|
else
|
|
goto done;
|
|
|
|
ref_name = NULL;
|
|
|
|
done:
|
|
return ref_name;
|
|
}
|
|
|
|
cairo_test_similar_t
|
|
cairo_test_target_has_similar (const cairo_test_context_t *ctx,
|
|
const cairo_boilerplate_target_t *target)
|
|
{
|
|
cairo_surface_t *surface;
|
|
cairo_test_similar_t has_similar;
|
|
cairo_t * cr;
|
|
cairo_surface_t *similar;
|
|
cairo_status_t status;
|
|
void *closure;
|
|
char *path;
|
|
|
|
/* ignore image intermediate targets */
|
|
if (target->expected_type == CAIRO_SURFACE_TYPE_IMAGE)
|
|
return DIRECT;
|
|
|
|
if (getenv ("CAIRO_TEST_IGNORE_SIMILAR"))
|
|
return DIRECT;
|
|
|
|
xasprintf (&path, "%s/%s",
|
|
cairo_test_mkdir (ctx->output) ? ctx->output : ".",
|
|
ctx->test_name);
|
|
|
|
has_similar = DIRECT;
|
|
do {
|
|
do {
|
|
surface = (target->create_surface) (path,
|
|
target->content,
|
|
ctx->test->width,
|
|
ctx->test->height,
|
|
ctx->test->width* NUM_DEVICE_SCALE + 25 * NUM_DEVICE_OFFSETS,
|
|
ctx->test->height* NUM_DEVICE_SCALE + 25 * NUM_DEVICE_OFFSETS,
|
|
CAIRO_BOILERPLATE_MODE_TEST,
|
|
&closure);
|
|
if (surface == NULL)
|
|
goto out;
|
|
} while (cairo_test_malloc_failure (ctx, cairo_surface_status (surface)));
|
|
|
|
if (cairo_surface_status (surface))
|
|
goto out;
|
|
|
|
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 (cairo_surface_get_type (similar) == cairo_surface_get_type (surface))
|
|
has_similar = SIMILAR;
|
|
else
|
|
has_similar = DIRECT;
|
|
|
|
cairo_destroy (cr);
|
|
cairo_surface_destroy (surface);
|
|
|
|
if (target->cleanup)
|
|
target->cleanup (closure);
|
|
} while (! has_similar && cairo_test_malloc_failure (ctx, status));
|
|
out:
|
|
free (path);
|
|
|
|
return has_similar;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
_cairo_test_flatten_reference_image (cairo_test_context_t *ctx,
|
|
cairo_bool_t flatten)
|
|
{
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
|
|
if (! flatten)
|
|
return ctx->ref_image;
|
|
|
|
if (ctx->ref_image_flattened != NULL)
|
|
return ctx->ref_image_flattened;
|
|
|
|
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
cairo_image_surface_get_width (ctx->ref_image),
|
|
cairo_image_surface_get_height (ctx->ref_image));
|
|
cr = cairo_create (surface);
|
|
cairo_surface_destroy (surface);
|
|
|
|
cairo_set_source_rgb (cr, 1, 1, 1);
|
|
cairo_paint (cr);
|
|
|
|
cairo_set_source_surface (cr, ctx->ref_image, 0, 0);
|
|
cairo_paint (cr);
|
|
|
|
surface = cairo_surface_reference (cairo_get_target (cr));
|
|
cairo_destroy (cr);
|
|
|
|
if (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS)
|
|
ctx->ref_image_flattened = surface;
|
|
return surface;
|
|
}
|
|
|
|
cairo_surface_t *
|
|
cairo_test_get_reference_image (cairo_test_context_t *ctx,
|
|
const char *filename,
|
|
cairo_bool_t flatten)
|
|
{
|
|
cairo_surface_t *surface;
|
|
|
|
if (ctx->ref_name != NULL) {
|
|
if (strcmp (ctx->ref_name, filename) == 0)
|
|
return _cairo_test_flatten_reference_image (ctx, flatten);
|
|
|
|
cairo_surface_destroy (ctx->ref_image);
|
|
ctx->ref_image = NULL;
|
|
|
|
cairo_surface_destroy (ctx->ref_image_flattened);
|
|
ctx->ref_image_flattened = NULL;
|
|
|
|
free (ctx->ref_name);
|
|
ctx->ref_name = NULL;
|
|
}
|
|
|
|
surface = cairo_image_surface_create_from_png (filename);
|
|
if (cairo_surface_status (surface))
|
|
return surface;
|
|
|
|
ctx->ref_name = xstrdup (filename);
|
|
ctx->ref_image = surface;
|
|
return _cairo_test_flatten_reference_image (ctx, flatten);
|
|
}
|
|
|
|
static cairo_bool_t
|
|
cairo_test_file_is_older (const char *filename,
|
|
char **ref_filenames,
|
|
int num_ref_filenames)
|
|
{
|
|
#if HAVE_SYS_STAT_H
|
|
struct stat st;
|
|
|
|
if (stat (filename, &st) < 0)
|
|
return FALSE;
|
|
|
|
while (num_ref_filenames--) {
|
|
struct stat ref;
|
|
char *ref_filename = *ref_filenames++;
|
|
|
|
if (ref_filename == NULL)
|
|
continue;
|
|
|
|
if (stat (ref_filename++, &ref) < 0)
|
|
continue;
|
|
|
|
if (st.st_mtime <= ref.st_mtime)
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static cairo_bool_t
|
|
cairo_test_files_equal (const char *test_filename,
|
|
const char *pass_filename)
|
|
{
|
|
FILE *test, *pass;
|
|
int t, p;
|
|
|
|
if (test_filename == NULL || pass_filename == NULL)
|
|
return FALSE;
|
|
|
|
test = fopen (test_filename, "rb");
|
|
if (test == NULL)
|
|
return FALSE;
|
|
|
|
pass = fopen (pass_filename, "rb");
|
|
if (pass == NULL) {
|
|
fclose (test);
|
|
return FALSE;
|
|
}
|
|
|
|
/* as simple as it gets */
|
|
do {
|
|
t = getc (test);
|
|
p = getc (pass);
|
|
if (t != p)
|
|
break;
|
|
} while (t != EOF && p != EOF);
|
|
|
|
fclose (pass);
|
|
fclose (test);
|
|
|
|
return t == p; /* both EOF */
|
|
}
|
|
|
|
static cairo_bool_t
|
|
cairo_test_copy_file (const char *src_filename,
|
|
const char *dst_filename)
|
|
{
|
|
FILE *src, *dst;
|
|
int c;
|
|
|
|
#if HAVE_LINK
|
|
if (link (src_filename, dst_filename) == 0)
|
|
return TRUE;
|
|
|
|
unlink (dst_filename);
|
|
#endif
|
|
|
|
src = fopen (src_filename, "rb");
|
|
if (src == NULL)
|
|
return FALSE;
|
|
|
|
dst = fopen (dst_filename, "wb");
|
|
if (dst == NULL) {
|
|
fclose (src);
|
|
return FALSE;
|
|
}
|
|
|
|
/* as simple as it gets */
|
|
while ((c = getc (src)) != EOF)
|
|
putc (c, dst);
|
|
|
|
fclose (src);
|
|
fclose (dst);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static cairo_test_status_t
|
|
cairo_test_for_target (cairo_test_context_t *ctx,
|
|
const cairo_boilerplate_target_t *target,
|
|
int dev_offset,
|
|
int dev_scale,
|
|
cairo_bool_t similar)
|
|
{
|
|
cairo_test_status_t status;
|
|
cairo_surface_t *surface = NULL;
|
|
cairo_t *cr;
|
|
const char *empty_str = "";
|
|
char *offset_str;
|
|
char *scale_str;
|
|
char *base_name, *base_path;
|
|
char *out_png_path;
|
|
char *ref_path = NULL, *ref_png_path, *cmp_png_path = NULL;
|
|
char *new_path = NULL, *new_png_path;
|
|
char *xfail_path = NULL, *xfail_png_path;
|
|
char *base_ref_png_path;
|
|
char *base_new_png_path;
|
|
char *base_xfail_png_path;
|
|
char *diff_png_path;
|
|
char *test_filename = NULL, *pass_filename = NULL, *fail_filename = NULL;
|
|
cairo_test_status_t ret;
|
|
cairo_content_t expected_content;
|
|
cairo_font_options_t *font_options;
|
|
const char *format;
|
|
cairo_bool_t have_output = FALSE;
|
|
cairo_bool_t have_result = FALSE;
|
|
void *closure;
|
|
double width, height;
|
|
cairo_bool_t have_output_dir;
|
|
#if HAVE_MEMFAULT
|
|
int malloc_failure_iterations = ctx->malloc_failure;
|
|
int last_fault_count = 0;
|
|
#endif
|
|
|
|
/* Get the strings ready that we'll need. */
|
|
format = cairo_boilerplate_content_name (target->content);
|
|
if (dev_offset)
|
|
xasprintf (&offset_str, ".%d", dev_offset);
|
|
else
|
|
offset_str = (char *) empty_str;
|
|
|
|
if (dev_scale != 1)
|
|
xasprintf (&scale_str, ".x%d", dev_scale);
|
|
else
|
|
scale_str = (char *) empty_str;
|
|
|
|
xasprintf (&base_name, "%s.%s.%s%s%s%s",
|
|
ctx->test_name,
|
|
target->name,
|
|
format,
|
|
similar ? ".similar" : "",
|
|
offset_str,
|
|
scale_str);
|
|
|
|
if (offset_str != empty_str)
|
|
free (offset_str);
|
|
if (scale_str != empty_str)
|
|
free (scale_str);
|
|
|
|
ref_png_path = cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
target->name,
|
|
target->basename,
|
|
format,
|
|
CAIRO_TEST_REF_SUFFIX,
|
|
CAIRO_TEST_PNG_EXTENSION);
|
|
new_png_path = cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
target->name,
|
|
target->basename,
|
|
format,
|
|
CAIRO_TEST_NEW_SUFFIX,
|
|
CAIRO_TEST_PNG_EXTENSION);
|
|
xfail_png_path = cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
target->name,
|
|
target->basename,
|
|
format,
|
|
CAIRO_TEST_XFAIL_SUFFIX,
|
|
CAIRO_TEST_PNG_EXTENSION);
|
|
|
|
base_ref_png_path = cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
NULL, NULL,
|
|
format,
|
|
CAIRO_TEST_REF_SUFFIX,
|
|
CAIRO_TEST_PNG_EXTENSION);
|
|
base_new_png_path = cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
NULL, NULL,
|
|
format,
|
|
CAIRO_TEST_NEW_SUFFIX,
|
|
CAIRO_TEST_PNG_EXTENSION);
|
|
base_xfail_png_path = cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
NULL, NULL,
|
|
format,
|
|
CAIRO_TEST_XFAIL_SUFFIX,
|
|
CAIRO_TEST_PNG_EXTENSION);
|
|
|
|
if (target->file_extension != NULL) {
|
|
ref_path = cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
target->name,
|
|
target->basename,
|
|
format,
|
|
CAIRO_TEST_REF_SUFFIX,
|
|
target->file_extension);
|
|
new_path = cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
target->name,
|
|
target->basename,
|
|
format,
|
|
CAIRO_TEST_NEW_SUFFIX,
|
|
target->file_extension);
|
|
xfail_path = cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
target->name,
|
|
target->basename,
|
|
format,
|
|
CAIRO_TEST_XFAIL_SUFFIX,
|
|
target->file_extension);
|
|
}
|
|
|
|
have_output_dir = cairo_test_mkdir (ctx->output);
|
|
xasprintf (&base_path, "%s/%s",
|
|
have_output_dir ? ctx->output : ".",
|
|
base_name);
|
|
xasprintf (&out_png_path, "%s" CAIRO_TEST_OUT_PNG, base_path);
|
|
xasprintf (&diff_png_path, "%s" CAIRO_TEST_DIFF_PNG, base_path);
|
|
|
|
if (ctx->test->requirements != NULL) {
|
|
const char *required;
|
|
|
|
required = target->is_vector ? "target=raster" : "target=vector";
|
|
if (strstr (ctx->test->requirements, required) != NULL) {
|
|
cairo_test_log (ctx, "Error: Skipping for %s target %s\n",
|
|
target->is_vector ? "vector" : "raster",
|
|
target->name);
|
|
ret = CAIRO_TEST_UNTESTED;
|
|
goto UNWIND_STRINGS;
|
|
}
|
|
|
|
required = target->is_recording ? "target=!recording" : "target=recording";
|
|
if (strstr (ctx->test->requirements, required) != NULL) {
|
|
cairo_test_log (ctx, "Error: Skipping for %s target %s\n",
|
|
target->is_recording ? "recording" : "non-recording",
|
|
target->name);
|
|
ret = CAIRO_TEST_UNTESTED;
|
|
goto UNWIND_STRINGS;
|
|
}
|
|
}
|
|
|
|
width = ctx->test->width;
|
|
height = ctx->test->height;
|
|
if (width && height) {
|
|
width *= dev_scale;
|
|
height *= dev_scale;
|
|
width += dev_offset;
|
|
height += dev_offset;
|
|
}
|
|
|
|
#if HAVE_MEMFAULT
|
|
REPEAT:
|
|
MEMFAULT_CLEAR_FAULTS ();
|
|
MEMFAULT_RESET_LEAKS ();
|
|
ctx->last_fault_count = 0;
|
|
last_fault_count = MEMFAULT_COUNT_FAULTS ();
|
|
|
|
/* Pre-initialise fontconfig so that the configuration is loaded without
|
|
* malloc failures (our primary goal is to test cairo fault tolerance).
|
|
*/
|
|
#if HAVE_FCINIT
|
|
FcInit ();
|
|
#endif
|
|
|
|
MEMFAULT_ENABLE_FAULTS ();
|
|
#endif
|
|
have_output = FALSE;
|
|
have_result = FALSE;
|
|
|
|
/* Run the actual drawing code. */
|
|
ret = CAIRO_TEST_SUCCESS;
|
|
surface = (target->create_surface) (base_path,
|
|
target->content,
|
|
width, height,
|
|
ctx->test->width * NUM_DEVICE_SCALE + 25 * NUM_DEVICE_OFFSETS,
|
|
ctx->test->height * NUM_DEVICE_SCALE + 25 * NUM_DEVICE_OFFSETS,
|
|
CAIRO_BOILERPLATE_MODE_TEST,
|
|
&closure);
|
|
if (surface == NULL) {
|
|
cairo_test_log (ctx, "Error: Failed to set %s target\n", target->name);
|
|
ret = CAIRO_TEST_UNTESTED;
|
|
goto UNWIND_STRINGS;
|
|
}
|
|
|
|
#if HAVE_MEMFAULT
|
|
if (ctx->malloc_failure &&
|
|
MEMFAULT_COUNT_FAULTS () - last_fault_count > 0 &&
|
|
cairo_surface_status (surface) == CAIRO_STATUS_NO_MEMORY)
|
|
{
|
|
goto REPEAT;
|
|
}
|
|
#endif
|
|
|
|
if (cairo_surface_status (surface)) {
|
|
MF (MEMFAULT_PRINT_FAULTS ());
|
|
cairo_test_log (ctx, "Error: Created an error surface: %s\n",
|
|
cairo_status_to_string (cairo_surface_status (surface)));
|
|
ret = CAIRO_TEST_FAILURE;
|
|
goto UNWIND_STRINGS;
|
|
}
|
|
|
|
/* Check that we created a surface of the expected type. */
|
|
if (cairo_surface_get_type (surface) != target->expected_type) {
|
|
MF (MEMFAULT_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_UNTESTED;
|
|
goto UNWIND_SURFACE;
|
|
}
|
|
|
|
/* Check that we created a surface of the expected content,
|
|
* (ignore the artificial CAIRO_TEST_CONTENT_COLOR_ALPHA_FLATTENED value).
|
|
*/
|
|
expected_content = cairo_boilerplate_content (target->content);
|
|
|
|
if (cairo_surface_get_content (surface) != expected_content) {
|
|
MF (MEMFAULT_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;
|
|
goto UNWIND_SURFACE;
|
|
}
|
|
|
|
if (cairo_surface_set_user_data (surface,
|
|
&cairo_boilerplate_output_basename_key,
|
|
base_path,
|
|
NULL))
|
|
{
|
|
#if HAVE_MEMFAULT
|
|
cairo_surface_destroy (surface);
|
|
|
|
if (target->cleanup)
|
|
target->cleanup (closure);
|
|
|
|
goto REPEAT;
|
|
#else
|
|
ret = CAIRO_TEST_FAILURE;
|
|
goto UNWIND_SURFACE;
|
|
#endif
|
|
}
|
|
|
|
cairo_surface_set_device_offset (surface, dev_offset, dev_offset);
|
|
cairo_surface_set_device_scale (surface, dev_scale, dev_scale);
|
|
|
|
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)
|
|
cairo_push_group_with_content (cr, expected_content);
|
|
|
|
/* Clear to transparent (or black) depending on whether the target
|
|
* surface supports alpha. */
|
|
cairo_save (cr);
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
|
|
cairo_paint (cr);
|
|
cairo_restore (cr);
|
|
|
|
/* Set all components of font_options to avoid backend differences
|
|
* and reduce number of needed reference images. */
|
|
font_options = cairo_font_options_create ();
|
|
cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
|
|
cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_ON);
|
|
cairo_font_options_set_antialias (font_options, CAIRO_ANTIALIAS_GRAY);
|
|
cairo_set_font_options (cr, font_options);
|
|
cairo_font_options_destroy (font_options);
|
|
|
|
cairo_save (cr);
|
|
alarm (ctx->timeout);
|
|
status = (ctx->test->draw) (cr, ctx->test->width, ctx->test->height);
|
|
alarm (0);
|
|
cairo_restore (cr);
|
|
|
|
if (similar) {
|
|
cairo_pop_group_to_source (cr);
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
|
cairo_paint (cr);
|
|
}
|
|
|
|
#if HAVE_MEMFAULT
|
|
MEMFAULT_DISABLE_FAULTS ();
|
|
|
|
/* repeat test after malloc failure injection */
|
|
if (ctx->malloc_failure &&
|
|
MEMFAULT_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);
|
|
cairo_debug_reset_static_data ();
|
|
#if HAVE_FCFINI
|
|
FcFini ();
|
|
#endif
|
|
if (MEMFAULT_COUNT_LEAKS () > 0) {
|
|
MEMFAULT_PRINT_FAULTS ();
|
|
MEMFAULT_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");
|
|
ret = status;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
|
|
#if HAVE_MEMFAULT
|
|
if (MEMFAULT_COUNT_FAULTS () - last_fault_count > 0 &&
|
|
MEMFAULT_HAS_FAULTS ())
|
|
{
|
|
VALGRIND_PRINTF ("Unreported memfaults...");
|
|
MEMFAULT_PRINT_FAULTS ();
|
|
}
|
|
#endif
|
|
|
|
if (target->finish_surface != NULL) {
|
|
#if HAVE_MEMFAULT
|
|
/* We need to re-enable faults as most recording-surface processing
|
|
* is done during cairo_surface_finish().
|
|
*/
|
|
MEMFAULT_CLEAR_FAULTS ();
|
|
last_fault_count = MEMFAULT_COUNT_FAULTS ();
|
|
MEMFAULT_ENABLE_FAULTS ();
|
|
#endif
|
|
|
|
/* also check for infinite loops whilst replaying */
|
|
alarm (ctx->timeout);
|
|
status = target->finish_surface (surface);
|
|
alarm (0);
|
|
|
|
#if HAVE_MEMFAULT
|
|
MEMFAULT_DISABLE_FAULTS ();
|
|
|
|
if (ctx->malloc_failure &&
|
|
MEMFAULT_COUNT_FAULTS () - last_fault_count > 0 &&
|
|
status == CAIRO_STATUS_NO_MEMORY)
|
|
{
|
|
cairo_destroy (cr);
|
|
cairo_surface_destroy (surface);
|
|
if (target->cleanup)
|
|
target->cleanup (closure);
|
|
cairo_debug_reset_static_data ();
|
|
#if HAVE_FCFINI
|
|
FcFini ();
|
|
#endif
|
|
if (MEMFAULT_COUNT_LEAKS () > 0) {
|
|
MEMFAULT_PRINT_FAULTS ();
|
|
MEMFAULT_PRINT_LEAKS ();
|
|
}
|
|
|
|
goto REPEAT;
|
|
}
|
|
#endif
|
|
if (status) {
|
|
cairo_test_log (ctx, "Error: Failed to finish surface: %s\n",
|
|
cairo_status_to_string (status));
|
|
ret = CAIRO_TEST_FAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
cairo_surface_t *test_image;
|
|
cairo_surface_t *diff_image;
|
|
buffer_diff_result_t result;
|
|
cairo_status_t diff_status;
|
|
|
|
if (ref_png_path == NULL) {
|
|
cairo_test_log (ctx, "Error: Cannot find reference image for %s\n",
|
|
base_name);
|
|
|
|
/* we may be running this test to generate reference images */
|
|
_xunlink (ctx, out_png_path);
|
|
/* be more generous as we may need to use external renderers */
|
|
alarm (4 * ctx->timeout);
|
|
test_image = target->get_image_surface (surface, 0,
|
|
ctx->test->width,
|
|
ctx->test->height);
|
|
alarm (0);
|
|
diff_status = cairo_surface_write_to_png (test_image, out_png_path);
|
|
cairo_surface_destroy (test_image);
|
|
if (diff_status) {
|
|
if (cairo_surface_status (test_image) == CAIRO_STATUS_INVALID_STATUS)
|
|
ret = CAIRO_TEST_CRASHED;
|
|
else
|
|
ret = CAIRO_TEST_FAILURE;
|
|
cairo_test_log (ctx,
|
|
"Error: Failed to write output image: %s\n",
|
|
cairo_status_to_string (diff_status));
|
|
}
|
|
have_output = TRUE;
|
|
|
|
ret = CAIRO_TEST_XFAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
|
|
if (target->file_extension != NULL) { /* compare vector surfaces */
|
|
char *filenames[] = {
|
|
ref_png_path,
|
|
ref_path,
|
|
new_png_path,
|
|
new_path,
|
|
xfail_png_path,
|
|
xfail_path,
|
|
base_ref_png_path,
|
|
base_new_png_path,
|
|
base_xfail_png_path,
|
|
};
|
|
|
|
xasprintf (&test_filename, "%s.out%s",
|
|
base_path, target->file_extension);
|
|
xasprintf (&pass_filename, "%s.pass%s",
|
|
base_path, target->file_extension);
|
|
xasprintf (&fail_filename, "%s.fail%s",
|
|
base_path, target->file_extension);
|
|
|
|
if (cairo_test_file_is_older (pass_filename,
|
|
filenames,
|
|
ARRAY_LENGTH (filenames)))
|
|
{
|
|
_xunlink (ctx, pass_filename);
|
|
}
|
|
if (cairo_test_file_is_older (fail_filename,
|
|
filenames,
|
|
ARRAY_LENGTH (filenames)))
|
|
{
|
|
_xunlink (ctx, fail_filename);
|
|
}
|
|
|
|
if (cairo_test_files_equal (out_png_path, ref_path)) {
|
|
cairo_test_log (ctx, "Vector surface matches reference.\n");
|
|
have_output = FALSE;
|
|
ret = CAIRO_TEST_SUCCESS;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (out_png_path, new_path)) {
|
|
cairo_test_log (ctx, "Vector surface matches current failure.\n");
|
|
have_output = FALSE;
|
|
ret = CAIRO_TEST_NEW;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (out_png_path, xfail_path)) {
|
|
cairo_test_log (ctx, "Vector surface matches known failure.\n");
|
|
have_output = FALSE;
|
|
ret = CAIRO_TEST_XFAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
|
|
if (cairo_test_files_equal (test_filename, pass_filename)) {
|
|
/* identical output as last known PASS */
|
|
cairo_test_log (ctx, "Vector surface matches last pass.\n");
|
|
have_output = TRUE;
|
|
ret = CAIRO_TEST_SUCCESS;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (test_filename, fail_filename)) {
|
|
/* identical output as last known FAIL, fail */
|
|
cairo_test_log (ctx, "Vector surface matches last fail.\n");
|
|
have_result = TRUE; /* presume these were kept around as well */
|
|
have_output = TRUE;
|
|
ret = CAIRO_TEST_FAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
}
|
|
|
|
/* be more generous as we may need to use external renderers */
|
|
alarm (4 * ctx->timeout);
|
|
test_image = target->get_image_surface (surface, 0,
|
|
ctx->test->width,
|
|
ctx->test->height);
|
|
alarm (0);
|
|
if (cairo_surface_status (test_image)) {
|
|
cairo_test_log (ctx, "Error: Failed to extract image: %s\n",
|
|
cairo_status_to_string (cairo_surface_status (test_image)));
|
|
if (cairo_surface_status (test_image) == CAIRO_STATUS_INVALID_STATUS)
|
|
ret = CAIRO_TEST_CRASHED;
|
|
else
|
|
ret = CAIRO_TEST_FAILURE;
|
|
cairo_surface_destroy (test_image);
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
|
|
_xunlink (ctx, out_png_path);
|
|
diff_status = cairo_surface_write_to_png (test_image, out_png_path);
|
|
if (diff_status) {
|
|
cairo_test_log (ctx, "Error: Failed to write output image: %s\n",
|
|
cairo_status_to_string (diff_status));
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_FAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
have_output = TRUE;
|
|
|
|
/* binary compare png files (no decompression) */
|
|
if (target->file_extension == NULL) {
|
|
char *filenames[] = {
|
|
ref_png_path,
|
|
new_png_path,
|
|
xfail_png_path,
|
|
base_ref_png_path,
|
|
base_new_png_path,
|
|
base_xfail_png_path,
|
|
};
|
|
|
|
xasprintf (&test_filename, "%s", out_png_path);
|
|
xasprintf (&pass_filename, "%s.pass.png", base_path);
|
|
xasprintf (&fail_filename, "%s.fail.png", base_path);
|
|
|
|
if (cairo_test_file_is_older (pass_filename,
|
|
filenames,
|
|
ARRAY_LENGTH (filenames)))
|
|
{
|
|
_xunlink (ctx, pass_filename);
|
|
}
|
|
if (cairo_test_file_is_older (fail_filename,
|
|
filenames,
|
|
ARRAY_LENGTH (filenames)))
|
|
{
|
|
_xunlink (ctx, fail_filename);
|
|
}
|
|
|
|
if (cairo_test_files_equal (test_filename, pass_filename)) {
|
|
cairo_test_log (ctx, "PNG file exactly matches last pass.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_SUCCESS;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (out_png_path, ref_png_path)) {
|
|
cairo_test_log (ctx, "PNG file exactly matches reference image.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_SUCCESS;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (out_png_path, new_png_path)) {
|
|
cairo_test_log (ctx, "PNG file exactly matches current failure image.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_NEW;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (out_png_path, xfail_png_path)) {
|
|
cairo_test_log (ctx, "PNG file exactly matches known failure image.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_XFAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (test_filename, fail_filename)) {
|
|
cairo_test_log (ctx, "PNG file exactly matches last fail.\n");
|
|
have_result = TRUE; /* presume these were kept around as well */
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_FAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
} else {
|
|
if (cairo_test_files_equal (out_png_path, ref_png_path)) {
|
|
cairo_test_log (ctx, "PNG file exactly matches reference image.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_SUCCESS;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (out_png_path, new_png_path)) {
|
|
cairo_test_log (ctx, "PNG file exactly matches current failure image.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_NEW;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (out_png_path, xfail_png_path)) {
|
|
cairo_test_log (ctx, "PNG file exactly matches known failure image.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_XFAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
}
|
|
|
|
if (cairo_test_files_equal (out_png_path, base_ref_png_path)) {
|
|
cairo_test_log (ctx, "PNG file exactly reference image.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_SUCCESS;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (out_png_path, base_new_png_path)) {
|
|
cairo_test_log (ctx, "PNG file exactly current failure image.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_NEW;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
if (cairo_test_files_equal (out_png_path, base_xfail_png_path)) {
|
|
cairo_test_log (ctx, "PNG file exactly known failure image.\n");
|
|
have_result = TRUE;
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_XFAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
|
|
/* first compare against the ideal reference */
|
|
ref_image = cairo_test_get_reference_image (ctx, base_ref_png_path,
|
|
target->content == CAIRO_TEST_CONTENT_COLOR_ALPHA_FLATTENED);
|
|
if (cairo_surface_status (ref_image)) {
|
|
cairo_test_log (ctx, "Error: Cannot open reference image for %s: %s\n",
|
|
base_ref_png_path,
|
|
cairo_status_to_string (cairo_surface_status (ref_image)));
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_FAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
|
|
diff_image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
ctx->test->width,
|
|
ctx->test->height);
|
|
|
|
cmp_png_path = base_ref_png_path;
|
|
diff_status = image_diff (ctx,
|
|
test_image, ref_image, diff_image,
|
|
&result);
|
|
_xunlink (ctx, diff_png_path);
|
|
if (diff_status ||
|
|
image_diff_is_failure (&result, target->error_tolerance))
|
|
{
|
|
/* that failed, so check against the specific backend */
|
|
ref_image = cairo_test_get_reference_image (ctx, ref_png_path,
|
|
target->content == CAIRO_TEST_CONTENT_COLOR_ALPHA_FLATTENED);
|
|
if (cairo_surface_status (ref_image)) {
|
|
cairo_test_log (ctx, "Error: Cannot open reference image for %s: %s\n",
|
|
ref_png_path,
|
|
cairo_status_to_string (cairo_surface_status (ref_image)));
|
|
cairo_surface_destroy (test_image);
|
|
ret = CAIRO_TEST_FAILURE;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
|
|
cmp_png_path = ref_png_path;
|
|
diff_status = image_diff (ctx,
|
|
test_image, ref_image,
|
|
diff_image,
|
|
&result);
|
|
if (diff_status)
|
|
{
|
|
cairo_test_log (ctx, "Error: Failed to compare images: %s\n",
|
|
cairo_status_to_string (diff_status));
|
|
ret = CAIRO_TEST_FAILURE;
|
|
}
|
|
else if (image_diff_is_failure (&result, target->error_tolerance))
|
|
{
|
|
ret = CAIRO_TEST_FAILURE;
|
|
|
|
diff_status = cairo_surface_write_to_png (diff_image,
|
|
diff_png_path);
|
|
if (diff_status) {
|
|
cairo_test_log (ctx, "Error: Failed to write differences image: %s\n",
|
|
cairo_status_to_string (diff_status));
|
|
} else {
|
|
have_result = TRUE;
|
|
}
|
|
|
|
cairo_test_copy_file (test_filename, fail_filename);
|
|
}
|
|
else
|
|
{ /* success */
|
|
cairo_test_copy_file (test_filename, pass_filename);
|
|
}
|
|
}
|
|
else
|
|
{ /* success */
|
|
cairo_test_copy_file (test_filename, pass_filename);
|
|
}
|
|
|
|
/* If failed, compare against the current image output,
|
|
* and attempt to detect systematic failures.
|
|
*/
|
|
if (ret == CAIRO_TEST_FAILURE) {
|
|
char *image_out_path;
|
|
|
|
image_out_path =
|
|
cairo_test_reference_filename (ctx,
|
|
base_name,
|
|
ctx->test_name,
|
|
"image",
|
|
"image",
|
|
format,
|
|
CAIRO_TEST_OUT_SUFFIX,
|
|
CAIRO_TEST_PNG_EXTENSION);
|
|
if (image_out_path != NULL) {
|
|
if (cairo_test_files_equal (out_png_path,
|
|
image_out_path))
|
|
{
|
|
ret = CAIRO_TEST_XFAILURE;
|
|
}
|
|
else
|
|
{
|
|
ref_image =
|
|
cairo_image_surface_create_from_png (image_out_path);
|
|
if (cairo_surface_status (ref_image) == CAIRO_STATUS_SUCCESS)
|
|
{
|
|
diff_status = image_diff (ctx,
|
|
test_image, ref_image,
|
|
diff_image,
|
|
&result);
|
|
if (diff_status == CAIRO_STATUS_SUCCESS &&
|
|
!image_diff_is_failure (&result, target->error_tolerance))
|
|
{
|
|
ret = CAIRO_TEST_XFAILURE;
|
|
}
|
|
|
|
cairo_surface_destroy (ref_image);
|
|
}
|
|
}
|
|
|
|
free (image_out_path);
|
|
}
|
|
}
|
|
|
|
cairo_surface_destroy (test_image);
|
|
cairo_surface_destroy (diff_image);
|
|
}
|
|
|
|
if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
|
|
cairo_test_log (ctx, "Error: Function under test left cairo status in an error state: %s\n",
|
|
cairo_status_to_string (cairo_status (cr)));
|
|
ret = CAIRO_TEST_ERROR;
|
|
goto UNWIND_CAIRO;
|
|
}
|
|
|
|
UNWIND_CAIRO:
|
|
free (test_filename);
|
|
free (fail_filename);
|
|
free (pass_filename);
|
|
|
|
test_filename = fail_filename = pass_filename = NULL;
|
|
|
|
#if HAVE_MEMFAULT
|
|
if (ret == CAIRO_TEST_FAILURE)
|
|
MEMFAULT_PRINT_FAULTS ();
|
|
#endif
|
|
cairo_destroy (cr);
|
|
UNWIND_SURFACE:
|
|
cairo_surface_destroy (surface);
|
|
|
|
if (target->cleanup)
|
|
target->cleanup (closure);
|
|
|
|
#if HAVE_MEMFAULT
|
|
cairo_debug_reset_static_data ();
|
|
|
|
#if HAVE_FCFINI
|
|
FcFini ();
|
|
#endif
|
|
|
|
if (MEMFAULT_COUNT_LEAKS () > 0) {
|
|
if (ret != CAIRO_TEST_FAILURE)
|
|
MEMFAULT_PRINT_FAULTS ();
|
|
MEMFAULT_PRINT_LEAKS ();
|
|
}
|
|
|
|
if (ret == CAIRO_TEST_SUCCESS && --malloc_failure_iterations > 0)
|
|
goto REPEAT;
|
|
#endif
|
|
|
|
if (have_output)
|
|
cairo_test_log (ctx, "OUTPUT: %s\n", out_png_path);
|
|
|
|
if (have_result) {
|
|
if (cmp_png_path == NULL) {
|
|
/* XXX presume we matched the normal ref last time */
|
|
cmp_png_path = ref_png_path;
|
|
}
|
|
cairo_test_log (ctx,
|
|
"REFERENCE: %s\nDIFFERENCE: %s\n",
|
|
cmp_png_path, diff_png_path);
|
|
}
|
|
|
|
UNWIND_STRINGS:
|
|
free (out_png_path);
|
|
free (ref_png_path);
|
|
free (base_ref_png_path);
|
|
free (ref_path);
|
|
free (new_png_path);
|
|
free (base_new_png_path);
|
|
free (new_path);
|
|
free (xfail_png_path);
|
|
free (base_xfail_png_path);
|
|
free (xfail_path);
|
|
free (diff_png_path);
|
|
free (base_path);
|
|
free (base_name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(HAVE_SIGNAL_H) && defined(HAVE_SETJMP_H)
|
|
#include <signal.h>
|
|
#include <setjmp.h>
|
|
/* Used to catch crashes in a test, so that we report it as such and
|
|
* continue testing, although one crasher may already have corrupted memory in
|
|
* an nonrecoverable fashion. */
|
|
static jmp_buf jmpbuf;
|
|
|
|
static void
|
|
segfault_handler (int signal)
|
|
{
|
|
longjmp (jmpbuf, signal);
|
|
}
|
|
#endif
|
|
|
|
cairo_test_status_t
|
|
_cairo_test_context_run_for_target (cairo_test_context_t *ctx,
|
|
const cairo_boilerplate_target_t *target,
|
|
cairo_bool_t similar,
|
|
int dev_offset, int dev_scale)
|
|
{
|
|
cairo_test_status_t status;
|
|
|
|
if (target->get_image_surface == NULL)
|
|
return CAIRO_TEST_UNTESTED;
|
|
|
|
if (similar && ! cairo_test_target_has_similar (ctx, target))
|
|
return CAIRO_TEST_UNTESTED;
|
|
|
|
cairo_test_log (ctx,
|
|
"Testing %s with %s%s target (dev offset %d scale: %d)\n",
|
|
ctx->test_name,
|
|
similar ? " (similar) " : "",
|
|
target->name,
|
|
dev_offset, dev_scale);
|
|
|
|
printf ("%s.%s.%s [%dx%d]%s:\t", ctx->test_name, target->name,
|
|
cairo_boilerplate_content_name (target->content),
|
|
dev_offset, dev_scale,
|
|
similar ? " (similar)": "");
|
|
fflush (stdout);
|
|
|
|
#if defined(HAVE_SIGNAL_H) && defined(HAVE_SETJMP_H)
|
|
if (! RUNNING_ON_VALGRIND) {
|
|
void (* volatile old_segfault_handler)(int);
|
|
void (* volatile old_segfpe_handler)(int);
|
|
void (* volatile old_sigpipe_handler)(int);
|
|
void (* volatile old_sigabrt_handler)(int);
|
|
void (* volatile old_sigalrm_handler)(int);
|
|
|
|
/* Set up a checkpoint to get back to in case of segfaults. */
|
|
#ifdef SIGSEGV
|
|
old_segfault_handler = signal (SIGSEGV, segfault_handler);
|
|
#endif
|
|
#ifdef SIGFPE
|
|
old_segfpe_handler = signal (SIGFPE, segfault_handler);
|
|
#endif
|
|
#ifdef SIGPIPE
|
|
old_sigpipe_handler = signal (SIGPIPE, segfault_handler);
|
|
#endif
|
|
#ifdef SIGABRT
|
|
old_sigabrt_handler = signal (SIGABRT, segfault_handler);
|
|
#endif
|
|
#ifdef SIGALRM
|
|
old_sigalrm_handler = signal (SIGALRM, segfault_handler);
|
|
#endif
|
|
if (0 == setjmp (jmpbuf))
|
|
status = cairo_test_for_target (ctx, target, dev_offset, dev_scale, similar);
|
|
else
|
|
status = CAIRO_TEST_CRASHED;
|
|
#ifdef SIGSEGV
|
|
signal (SIGSEGV, old_segfault_handler);
|
|
#endif
|
|
#ifdef SIGFPE
|
|
signal (SIGFPE, old_segfpe_handler);
|
|
#endif
|
|
#ifdef SIGPIPE
|
|
signal (SIGPIPE, old_sigpipe_handler);
|
|
#endif
|
|
#ifdef SIGABRT
|
|
signal (SIGABRT, old_sigabrt_handler);
|
|
#endif
|
|
#ifdef SIGALRM
|
|
signal (SIGALRM, old_sigalrm_handler);
|
|
#endif
|
|
} else {
|
|
status = cairo_test_for_target (ctx, target, dev_offset, dev_scale, similar);
|
|
}
|
|
#else
|
|
status = cairo_test_for_target (ctx, target, dev_offset, dev_scale, similar);
|
|
#endif
|
|
|
|
cairo_test_log (ctx,
|
|
"TEST: %s TARGET: %s FORMAT: %s OFFSET: %d SCALE: %d SIMILAR: %d RESULT: ",
|
|
ctx->test_name, target->name,
|
|
cairo_boilerplate_content_name (target->content),
|
|
dev_offset, dev_scale, similar);
|
|
switch (status) {
|
|
case CAIRO_TEST_SUCCESS:
|
|
printf ("PASS\n");
|
|
cairo_test_log (ctx, "PASS\n");
|
|
break;
|
|
|
|
case CAIRO_TEST_UNTESTED:
|
|
printf ("UNTESTED\n");
|
|
cairo_test_log (ctx, "UNTESTED\n");
|
|
break;
|
|
|
|
default:
|
|
case CAIRO_TEST_CRASHED:
|
|
if (print_fail_on_stdout) {
|
|
printf ("!!!CRASHED!!!\n");
|
|
} else {
|
|
/* eat the test name */
|
|
printf ("\r");
|
|
fflush (stdout);
|
|
}
|
|
cairo_test_log (ctx, "CRASHED\n");
|
|
fprintf (stderr, "%s.%s.%s [%dx%d]%s:\t%s!!!CRASHED!!!%s\n",
|
|
ctx->test_name, target->name,
|
|
cairo_boilerplate_content_name (target->content), dev_offset, dev_scale, similar ? " (similar)" : "",
|
|
fail_face, normal_face);
|
|
break;
|
|
|
|
case CAIRO_TEST_ERROR:
|
|
if (print_fail_on_stdout) {
|
|
printf ("!!!ERROR!!!\n");
|
|
} else {
|
|
/* eat the test name */
|
|
printf ("\r");
|
|
fflush (stdout);
|
|
}
|
|
cairo_test_log (ctx, "ERROR\n");
|
|
fprintf (stderr, "%s.%s.%s [%dx%d]%s:\t%s!!!ERROR!!!%s\n",
|
|
ctx->test_name, target->name,
|
|
cairo_boilerplate_content_name (target->content), dev_offset, dev_scale, similar ? " (similar)" : "",
|
|
fail_face, normal_face);
|
|
break;
|
|
|
|
case CAIRO_TEST_XFAILURE:
|
|
if (print_fail_on_stdout) {
|
|
printf ("XFAIL\n");
|
|
} else {
|
|
/* eat the test name */
|
|
printf ("\r");
|
|
fflush (stdout);
|
|
}
|
|
fprintf (stderr, "%s.%s.%s [%dx%d]%s:\t%sXFAIL%s\n",
|
|
ctx->test_name, target->name,
|
|
cairo_boilerplate_content_name (target->content), dev_offset, dev_scale, similar ? " (similar)" : "",
|
|
xfail_face, normal_face);
|
|
cairo_test_log (ctx, "XFAIL\n");
|
|
break;
|
|
|
|
case CAIRO_TEST_NEW:
|
|
if (print_fail_on_stdout) {
|
|
printf ("NEW\n");
|
|
} else {
|
|
/* eat the test name */
|
|
printf ("\r");
|
|
fflush (stdout);
|
|
}
|
|
fprintf (stderr, "%s.%s.%s [%dx%d]%s:\t%sNEW%s\n",
|
|
ctx->test_name, target->name,
|
|
cairo_boilerplate_content_name (target->content), dev_offset, dev_scale, similar ? " (similar)" : "",
|
|
fail_face, normal_face);
|
|
cairo_test_log (ctx, "NEW\n");
|
|
break;
|
|
|
|
case CAIRO_TEST_NO_MEMORY:
|
|
case CAIRO_TEST_FAILURE:
|
|
if (print_fail_on_stdout) {
|
|
printf ("FAIL\n");
|
|
} else {
|
|
/* eat the test name */
|
|
printf ("\r");
|
|
fflush (stdout);
|
|
}
|
|
fprintf (stderr, "%s.%s.%s [%dx%d]%s:\t%sFAIL%s\n",
|
|
ctx->test_name, target->name,
|
|
cairo_boilerplate_content_name (target->content), dev_offset, dev_scale, similar ? " (similar)" : "",
|
|
fail_face, normal_face);
|
|
cairo_test_log (ctx, "FAIL\n");
|
|
break;
|
|
}
|
|
fflush (stdout);
|
|
|
|
return status;
|
|
}
|
|
|
|
const cairo_test_context_t *
|
|
cairo_test_get_context (cairo_t *cr)
|
|
{
|
|
return cairo_get_user_data (cr, &_cairo_test_context_key);
|
|
}
|
|
|
|
cairo_surface_t *
|
|
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);
|
|
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
|
|
*/
|
|
if (ctx->srcdir) {
|
|
char *srcdir_filename;
|
|
xasprintf (&srcdir_filename, "%s/%s", ctx->srcdir, filename);
|
|
cairo_surface_destroy (image);
|
|
image = cairo_image_surface_create_from_png (srcdir_filename);
|
|
free (srcdir_filename);
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
cairo_pattern_t *
|
|
cairo_test_create_pattern_from_png (const cairo_test_context_t *ctx,
|
|
const char *filename)
|
|
{
|
|
cairo_surface_t *image;
|
|
cairo_pattern_t *pattern;
|
|
|
|
image = cairo_test_create_surface_from_png (ctx, filename);
|
|
|
|
pattern = cairo_pattern_create_for_surface (image);
|
|
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
|
|
|
|
cairo_surface_destroy (image);
|
|
|
|
return pattern;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
_draw_check (int width, int height)
|
|
{
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
|
|
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);
|
|
|
|
cairo_set_source_rgb (cr, 0.25, 0.25, 0.25); /* dark gray */
|
|
cairo_rectangle (cr, width / 2, 0, width / 2, height / 2);
|
|
cairo_rectangle (cr, 0, height / 2, width / 2, height / 2);
|
|
cairo_fill (cr);
|
|
|
|
surface = cairo_surface_reference (cairo_get_target (cr));
|
|
cairo_destroy (cr);
|
|
|
|
return surface;
|
|
}
|
|
|
|
void
|
|
cairo_test_paint_checkered (cairo_t *cr)
|
|
{
|
|
cairo_surface_t *check;
|
|
|
|
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);
|
|
}
|
|
|
|
cairo_bool_t
|
|
cairo_test_is_target_enabled (const cairo_test_context_t *ctx,
|
|
const char *target)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < ctx->num_targets; i++) {
|
|
const cairo_boilerplate_target_t *t = ctx->targets_to_test[i];
|
|
if (strcmp (t->name, target) == 0) {
|
|
/* XXX ask the target whether is it possible to run?
|
|
* e.g. the xlib backend could check whether it is able to connect
|
|
* to the Display.
|
|
*/
|
|
return t->get_image_surface != NULL;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
cairo_bool_t
|
|
cairo_test_malloc_failure (const cairo_test_context_t *ctx,
|
|
cairo_status_t status)
|
|
{
|
|
if (! ctx->malloc_failure)
|
|
return FALSE;
|
|
|
|
if (status != CAIRO_STATUS_NO_MEMORY)
|
|
return FALSE;
|
|
|
|
#if HAVE_MEMFAULT
|
|
{
|
|
int n_faults;
|
|
|
|
/* prevent infinite loops... */
|
|
n_faults = MEMFAULT_COUNT_FAULTS ();
|
|
if (n_faults == ctx->last_fault_count)
|
|
return FALSE;
|
|
|
|
((cairo_test_context_t *) ctx)->last_fault_count = n_faults;
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
cairo_test_status_t
|
|
cairo_test_status_from_status (const cairo_test_context_t *ctx,
|
|
cairo_status_t status)
|
|
{
|
|
if (status == CAIRO_STATUS_SUCCESS)
|
|
return CAIRO_TEST_SUCCESS;
|
|
|
|
if (cairo_test_malloc_failure (ctx, status))
|
|
return CAIRO_TEST_NO_MEMORY;
|
|
|
|
return CAIRO_TEST_FAILURE;
|
|
}
|