libgd/tests/gdtest/gdtest.c

726 lines
15 KiB
C

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include <time.h>
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)
#include "readdir.h"
#include <errno.h>
#endif
#if defined(__MINGW32__) || defined(__MINGW64__)
# define lstat stat
#endif
#include "gd_intern.h"
/* GDTEST_TOP_DIR is defined in other compile ways except msys
* test_config.h is created by windows/msys/run_test.sh*/
#ifndef GDTEST_TOP_DIR
#include <test_config.h>
#endif
#include "gd.h"
#include "gdtest.h"
/* max is already defined in windows/msvc */
#ifndef max
static inline int max(int a, int b) {return a > b ? a : b;}
#endif
void gdSilence(int priority, const char *format, va_list args)
{
(void)priority;
(void)format;
(void)args;
}
gdImagePtr gdTestImageFromPng(const char *filename)
{
gdImagePtr image;
FILE *fp;
/* If the path is relative, then assume it's in the tests/ dir. */
if (filename[0] == '/' || filename[0] == '.'
#ifdef _WIN32
|| filename[1] == ':'
#endif
) {
fp = fopen(filename, "rb");
} else {
fp = gdTestFileOpen(filename);
}
if (fp == NULL) {
return NULL;
}
image = gdImageCreateFromPng(fp);
fclose(fp);
return image;
}
static char *tmpdir_base;
int gdTestIsDir(char *path) {
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)
WIN32_FILE_ATTRIBUTE_DATA data;
if (!GetFileAttributesEx(path, GetFileExInfoStandard, &data)) {
return 0;
}
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
return 1;
} else {
return 0;
}
#else
struct stat st;
if (lstat(path, &st) == 0)
if (S_ISDIR(st.st_mode))
return 1;
return 0;
#endif
}
/* This is kind of hacky, but it's meant to be simple. */
static void _clean_dir(const char *dir)
{
DIR *d;
struct dirent *de;
d = opendir(dir);
if (d == NULL)
return;
if (chdir(dir) != 0)
goto done;
while ((de = readdir(d)) != NULL) {
struct stat st;
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
continue;
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)
{
WIN32_FILE_ATTRIBUTE_DATA data;
if (!GetFileAttributesEx(de->d_name, GetFileExInfoStandard, &data)) {
continue;
}
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
_clean_dir(de->d_name);
} else {
unlink(de->d_name);
}
}
#else
if (lstat(de->d_name, &st) != 0)
continue;
if (S_ISDIR(st.st_mode))
_clean_dir(de->d_name);
else
unlink(de->d_name);
#endif
}
if (chdir("..")) {
/* Ignore. */;
}
done:
closedir(d);
rmdir(dir);
}
static void tmpdir_cleanup(void)
{
_clean_dir(tmpdir_base);
free(tmpdir_base);
}
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)
typedef VOID (WINAPI *MyGetSystemTimeAsFileTime)(LPFILETIME lpSystemTimeAsFileTime);
static MyGetSystemTimeAsFileTime get_time_func(void)
{
MyGetSystemTimeAsFileTime timefunc = NULL;
HMODULE hMod = GetModuleHandle("kernel32.dll");
if (hMod) {
/* Max possible resolution <1us, win8/server2012 */
timefunc = (MyGetSystemTimeAsFileTime)GetProcAddress(hMod, "GetSystemTimePreciseAsFileTime");
if(!timefunc) {
/* 100ns blocks since 01-Jan-1641 */
timefunc = (MyGetSystemTimeAsFileTime)GetProcAddress(hMod, "GetSystemTimeAsFileTime");
}
}
return timefunc;
}
static MyGetSystemTimeAsFileTime timefunc = NULL;
static int getfilesystemtime(struct timeval *tv)
{
FILETIME ft;
unsigned __int64 ff = 0;
ULARGE_INTEGER fft;
if (timefunc == NULL) {
timefunc = get_time_func();
}
timefunc(&ft);
/*
* Do not cast a pointer to a FILETIME structure to either a
* ULARGE_INTEGER* or __int64* value because it can cause alignment faults on 64-bit Windows.
* via http://technet.microsoft.com/en-us/library/ms724284(v=vs.85).aspx
*/
fft.HighPart = ft.dwHighDateTime;
fft.LowPart = ft.dwLowDateTime;
ff = fft.QuadPart;
ff /= 10ULL; /* convert to microseconds */
ff -= 11644473600000000ULL; /* convert to unix epoch */
tv->tv_sec = (long)(ff / 1000000ULL);
tv->tv_usec = (long)(ff % 1000000ULL);
return 0;
}
#endif
#if defined(_WIN32)
static void randtemplate(char *template, size_t l) {
// just to avoid calls within the same second
srand(time (NULL) + (unsigned int)template);
for (size_t i = l - 6; i < l; i++) {
int r = rand();
if ((r / (RAND_MAX + 1)) > ((RAND_MAX + 1) / 2))
template[i] = 'A' + (double) rand () / (RAND_MAX + 1) * ('Z' - 'A');
else
template[i] = 'a' + (double) rand () / (RAND_MAX + 1) * ('z' - 'a');
}
}
char* strrstr (char* haystack, char* needle)
{
int needle_length = strlen(needle);
char * haystack_end = haystack + strlen(haystack) - needle_length;
char * p;
int i;
for(p = haystack_end; p >= haystack; --p)
{
for(i = 0; i < needle_length; ++i) {
if(p[i] != needle[i])
goto next;
}
return p;
next:;
}
return 0;
}
static char *
mkdtemp (char *tmpl)
{
size_t l;
char attempts = 8;
int res = 0;
if (tmpl == NULL) {
errno = EINVAL;
return NULL;
}
l = strlen (tmpl);
if (l < 6 || strcmp (&tmpl[l - 6], "XXXXXX") != 0) {
errno = EINVAL;
return NULL;
}
do {
randtemplate (tmpl, l);
res = mkdir(tmpl);
attempts--;
} while (attempts > 0 && res != 0 );
if (res == 0) {
return tmpl;
}
if (errno != EEXIST) {
printf("Failed to create tmp dir, last attempt %s.", tmpl);
return NULL;
}
/* We got out of the loop because we ran out of combinations to try. */
errno = EEXIST;
return NULL;
}
#endif
const char *gdTestTempDir(void)
{
if (tmpdir_base == NULL) {
char *tmpdir;
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)
char tmpdir_root[MAXPATHLEN];
size_t tmpdir_root_len = GetTempPath(MAX_PATH, tmpdir_root);
if ((tmpdir_root_len + 30 > MAX_PATH) || (tmpdir_root_len == 0)) {
printf("Tmp dir path too long or 0 length <%s>\n", tmpdir_root);
return NULL;
}
#else
char *tmpdir_root;
tmpdir_root = getenv("TMPDIR");
if (tmpdir_root == NULL) {
// Mingw defines it
tmpdir_root = getenv("TMP");
if (tmpdir_root == NULL) {
// Fall back here.
tmpdir_root = "/tmp";
if (!gdTestIsDir(tmpdir_root)) {
printf("tmpdir failed to be used or initialized (%s).", tmpdir_root);
exit(2);
}
}
}
#endif
/* The constant here is a lazy over-estimate. */
tmpdir = malloc(strlen(tmpdir_root) + 30);
if (tmpdir == NULL) {
printf("cannot alloc tmpdir path.");
return NULL;
}
#if defined(_WIN32)
sprintf(tmpdir, "%sgdtest.XXXXXX", tmpdir_root);
#else
sprintf(tmpdir, "%s/gdtest.XXXXXX", tmpdir_root);
#endif
tmpdir_base = mkdtemp(tmpdir);
if (tmpdir_base == NULL) {
printf("failed to generate the tmp dir path (%s).", tmpdir);
return NULL;
}
atexit(tmpdir_cleanup);
}
return tmpdir_base;
}
char *gdTestTempFile(const char *template)
{
const char *tempdir = gdTestTempDir();
char *ret;
if (tempdir == NULL) {
return NULL;
}
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)
{
char *tmpfilename;
UINT error;
ret = malloc(MAX_PATH);
if (ret == NULL) {
printf("Failed to alloc tmp path");
return NULL;
}
if (template == NULL) {
error = GetTempFileName(tempdir,
"gdtest",
0,
ret);
if (error = 0) {
printf("GetTempFileName failed.");
gdFree(ret);
return NULL;
}
} else {
sprintf(ret, "%s\\%s", tempdir, template);
}
}
#else
if (template == NULL) {
template = "gdtemp.XXXXXX";
}
ret = malloc(strlen(tempdir) + 10 + strlen(template));
if (ret == NULL) {
printf("Failed to alloc tmp path");
return NULL;
}
sprintf(ret, "%s/%s", tempdir, template);
if (strstr(template, "XXXXXX") != NULL) {
int fd = mkstemp(ret);
if (fd == -1) {
printf("mkstemp failed");
gdFree(ret);
return NULL;
}
close(fd);
}
#endif
return ret;
}
FILE *gdTestTempFp(void)
{
char *file = gdTestTempFile(NULL);
FILE *fp = fopen(file, "wb");
if (fp == NULL) {
printf("fail to open tmp file");
return NULL;
}
free(file);
return fp;
}
char *gdTestFilePathV(const char *path, va_list args)
{
size_t len;
const char *p;
char *file;
va_list args_len;
/* Figure out how much space we need. */
va_copy(args_len, args);
len = strlen(GDTEST_TOP_DIR) + 1;
p = path;
do {
len += strlen(p) + 1;
} while ((p = va_arg(args_len, const char *)) != NULL);
va_end(args_len);
/* Now build the path. */
file = malloc(len);
if (file == NULL) {
printf("failed to alloc path.");
return NULL;
}
strcpy(file, GDTEST_TOP_DIR);
p = path;
do {
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)
strcat(file, "\\");
#else
strcat(file, "/");
#endif
strcat(file, p);
} while ((p = va_arg(args, const char *)) != NULL);
va_end(args);
return file;
}
char *gdTestFilePathX(const char *path, ...)
{
va_list args;
va_start(args, path);
return gdTestFilePathV(path, args);
}
FILE *gdTestFileOpenX(const char *path, ...)
{
va_list args;
FILE *fp;
char *file;
va_start(args, path);
file = gdTestFilePathV(path, args);
fp = fopen(file, "rb");
if (fp == NULL) {
printf("failed to open path (rb).");
return NULL;
}
free(file);
return fp;
}
/* Compare two buffers, returning the number of pixels that are
* different and the maximum difference of any single color channel in
* result_ret.
*
* This function should be rewritten to compare all formats supported by
* cairo_format_t instead of taking a mask as a parameter.
*/
void gdTestImageDiff(gdImagePtr buf_a, gdImagePtr buf_b,
gdImagePtr buf_diff, CuTestImageResult *result_ret)
{
int x, y;
int c1, c2;
# define UP_DIFF(var) result_ret->max_diff = max((var), result_ret->max_diff)
for (y = 0; y < gdImageSY(buf_a); y++) {
for (x = 0; x < gdImageSX(buf_a); x++) {
c1 = gdImageGetTrueColorPixel(buf_a, x, y);
c2 = gdImageGetTrueColorPixel(buf_b, x, y);
/* check if the pixels are the same */
if (c1 != c2) {
int r1,b1,g1,a1,r2,b2,g2,a2;
unsigned int diff_a,diff_r,diff_g,diff_b;
a1 = gdTrueColorGetAlpha(c1);
a2 = gdTrueColorGetAlpha(c2);
diff_a = abs (a1 - a2);
diff_a *= 4; /* emphasize */
if (diff_a) {
diff_a += 128; /* make sure it's visible */
}
if (diff_a > gdAlphaMax) {
diff_a = gdAlphaMax/2;
}
r1 = gdTrueColorGetRed(c1);
r2 = gdTrueColorGetRed(c2);
diff_r = abs (r1 - r2);
// diff_r *= 4; /* emphasize */
if (diff_r) {
diff_r += gdRedMax/2; /* make sure it's visible */
}
if (diff_r > 255) {
diff_r = 255;
}
UP_DIFF(diff_r);
g1 = gdTrueColorGetGreen(c1);
g2 = gdTrueColorGetGreen(c2);
diff_g = abs (g1 - g2);
diff_g *= 4; /* emphasize */
if (diff_g) {
diff_g += gdGreenMax/2; /* make sure it's visible */
}
if (diff_g > 255) {
diff_g = 255;
}
UP_DIFF(diff_g);
b1 = gdTrueColorGetBlue(c1);
b2 = gdTrueColorGetBlue(c2);
diff_b = abs (b1 - b2);
diff_b *= 4; /* emphasize */
if (diff_b) {
diff_b += gdBlueMax/2; /* make sure it's visible */
}
if (diff_b > 255) {
diff_b = 255;
}
UP_DIFF(diff_b);
result_ret->pixels_changed++;
if (buf_diff) gdImageSetPixel(buf_diff, x,y, gdTrueColorAlpha(diff_r, diff_g, diff_b, diff_a));
} else {
if (buf_diff) gdImageSetPixel(buf_diff, x,y, gdTrueColorAlpha(255,255,255,0));
}
}
}
# undef UP_DIFF
}
/* Return the largest difference between two corresponding pixels and
* channels. */
unsigned int gdMaxPixelDiff(gdImagePtr a, gdImagePtr b)
{
int diff = 0;
int x, y;
if (a == NULL || b == NULL || a->sx != b->sx || a->sy != b->sy)
return UINT_MAX;
for (x = 0; x < a->sx; x++) {
for (y = 0; y < a->sy; y++) {
int c1, c2;
c1 = gdImageGetTrueColorPixel(a, x, y);
c2 = gdImageGetTrueColorPixel(b, x, y);
if (c1 == c2) continue;
diff = max(diff, abs(gdTrueColorGetAlpha(c1) - gdTrueColorGetAlpha(c2)));
diff = max(diff, abs(gdTrueColorGetRed(c1) - gdTrueColorGetRed(c2)));
diff = max(diff, abs(gdTrueColorGetGreen(c1) - gdTrueColorGetGreen(c2)));
diff = max(diff, abs(gdTrueColorGetBlue(c1) - gdTrueColorGetBlue(c2)));
}/* for */
}/* for */
return diff;
}
int gdTestImageCompareToImage(const char* file, unsigned int line, const char* message,
gdImagePtr expected, gdImagePtr actual)
{
unsigned int width_a, height_a;
unsigned int width_b, height_b;
gdImagePtr surface_diff = NULL;
CuTestImageResult result = {0, 0};
(void)message;
if (!actual) {
_gdTestErrorMsg(file, line, "Image is NULL\n");
goto fail;
}
width_a = gdImageSX(expected);
height_a = gdImageSY(expected);
width_b = gdImageSX(actual);
height_b = gdImageSY(actual);
if (width_a != width_b || height_a != height_b) {
_gdTestErrorMsg(file, line,
"Image size mismatch: (%ux%u) vs. (%ux%u)\n for %s vs. buffer\n",
width_a, height_a,
width_b, height_b,
file);
goto fail;
}
surface_diff = gdImageCreateTrueColor(width_a, height_a);
gdTestImageDiff(expected, actual, surface_diff, &result);
if (result.pixels_changed>0) {
char file_diff[255];
char file_out[1024];
FILE *fp;
int len, p;
_gdTestErrorMsg(file, line,
"Total pixels changed: %d with a maximum channel difference of %d.\n",
result.pixels_changed,
result.max_diff
);
p = len = strlen(file);
p--;
/* Use only the filename (and store it in the bld dir not the src dir
*/
while(p > 0 && (file[p] != '/' && file[p] != '\\')) {
p--;
}
sprintf(file_diff, "%s_%u_diff.png", file + p + 1, line);
sprintf(file_out, "%s_%u_out.png", file + p + 1, line);
fp = fopen(file_diff, "wb");
if (!fp) goto fail;
gdImagePng(surface_diff,fp);
fclose(fp);
gdImageDestroy(surface_diff);
fp = fopen(file_out, "wb");
if (!fp) goto fail;
gdImagePng(actual, fp);
fclose(fp);
return 0;
} else {
if (surface_diff) {
gdImageDestroy(surface_diff);
}
return 1;
}
fail:
if (surface_diff) {
gdImageDestroy(surface_diff);
}
return 1;
}
int gdTestImageCompareToFile(const char* file, unsigned int line, const char* message,
const char *expected_file, gdImagePtr actual)
{
gdImagePtr expected = 0;
int res = 1;
expected = gdTestImageFromPng(expected_file);
if (!expected) {
_gdTestErrorMsg(file, line, "Cannot open PNG <%s>\n", expected_file);
res = 0;
} else {
res = gdTestImageCompareToImage(file, line, message, expected, actual);
gdImageDestroy(expected);
}
return res;
}
static int failureCount = 0;
int gdNumFailures() {
return failureCount;
}
int _gdTestAssert(const char* file, unsigned int line, int condition)
{
if (condition) return 1;
_gdTestErrorMsg(file, line, "Assert failed in <%s:%i>\n", file, line);
++failureCount;
return 0;
}
int _gdTestAssertMsg(const char* file, unsigned int line, int condition, const char* message, ...)
{
va_list args;
if (condition) return 1;
fprintf(stderr, "%s:%u: ", file, line);
va_start(args, message);
vfprintf(stderr, message, args);
va_end(args);
fflush(stderr);
++failureCount;
return 0;
}
int _gdTestErrorMsg(const char* file, unsigned int line, const char* format, ...) /* {{{ */
{
va_list args;
fprintf(stderr, "%s:%u: ", file, line);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fflush(stderr);
++failureCount;
return 0;
}
/* }}} */