Made the exactness script a C program (with improvements).

It's now considerably faster thanks to concurrency.
It's cleaner (almost the same loc).
Better error output.
Easier to use.
This commit is contained in:
Tom Hacohen 2013-05-03 11:53:37 +01:00
parent c3340d7909
commit c2547c0199
17 changed files with 943 additions and 548 deletions

2
README
View File

@ -1,3 +1,5 @@
POSSIBLY OUTDATED
exactness is a software package aimed to automate Elementary testing
after updating elm code.
The testing process is composed of running widget test,

View File

@ -15,6 +15,8 @@ AC_PROG_CC
AC_HEADER_STDC
AC_C_CONST
AM_PROG_CC_C_O
PKG_PROG_PKG_CONFIG
AC_PROG_LIBTOOL
@ -41,6 +43,7 @@ PKG_CHECK_MODULES([EFL],
AC_OUTPUT([
Makefile
src/Makefile
src/bin/Makefile
src/lib/Makefile
src/scripts/Makefile
data/Makefile

View File

@ -1,4 +1,4 @@
MAINTAINERCLEANFILES = Makefile.in
SUBDIRS = lib scripts
SUBDIRS = lib bin

19
src/bin/Makefile.am Normal file
View File

@ -0,0 +1,19 @@
MAINTAINERCLEANFILES = Makefile.in
bin_PROGRAMS = exactness
exactness_SOURCES = \
exactness.c \
list_file.c \
exactness_config.c \
scheduler.c \
run_test.c \
md5/md5.c
exactness_LDADD = \
@EFL_LIBS@
exactness_CFLAGS = \
@EFL_CFLAGS@ \
-DPACKAGE_LIBDIR=\"$(libdir)\" \
-DPACKAGE_DATADIR=\"$(datadir)\"

185
src/bin/exactness.c Normal file
View File

@ -0,0 +1,185 @@
#include <Ecore.h>
#include <Ecore_Getopt.h>
#include "list_file.h"
#include "exactness_config.h"
#include "run_test.h"
#include "scheduler.h"
#include "config.h"
static const Ecore_Getopt optdesc = {
"exactness",
"%prog [options] <-r|-p|-i|-s> <list file>",
PACKAGE_VERSION,
"(C) 2013 Enlightenment",
"BSD",
"A pixel perfect test suite for EFL based applications.",
0,
{
ECORE_GETOPT_STORE_STR('b', "base-dir", "The location of the rec files."),
ECORE_GETOPT_STORE_STR('d', "dest-dir", "The location of the images."),
ECORE_GETOPT_STORE_USHORT('j', "jobs", "The number of jobs to run in parallel."),
ECORE_GETOPT_STORE_TRUE('r', "record", "Run in record mode."),
ECORE_GETOPT_STORE_TRUE('p', "play", "Run in play mode."),
ECORE_GETOPT_STORE_TRUE('i', "init", "Run in init mode."),
ECORE_GETOPT_STORE_TRUE('s', "simulation", "Run in simulation mode."),
ECORE_GETOPT_STORE_TRUE('v', "verbose", "Turn verbose messages on."),
ECORE_GETOPT_LICENSE('L', "license"),
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
ECORE_GETOPT_VERSION('V', "version"),
ECORE_GETOPT_HELP('h', "help"),
ECORE_GETOPT_SENTINEL
}
};
int
main(int argc, char *argv[])
{
int ret = 0;
List_Entry *test_list;
int args = 0;
const char *list_file = "";
Eina_Bool mode_record, mode_play, mode_init, mode_simulation;
Eina_Bool want_quit;
Ecore_Getopt_Value values[] = {
ECORE_GETOPT_VALUE_STR(exactness_config.base_dir),
ECORE_GETOPT_VALUE_STR(exactness_config.dest_dir),
ECORE_GETOPT_VALUE_USHORT(exactness_config.jobs),
ECORE_GETOPT_VALUE_BOOL(mode_record),
ECORE_GETOPT_VALUE_BOOL(mode_play),
ECORE_GETOPT_VALUE_BOOL(mode_init),
ECORE_GETOPT_VALUE_BOOL(mode_simulation),
ECORE_GETOPT_VALUE_BOOL(exactness_config.verbose),
ECORE_GETOPT_VALUE_BOOL(want_quit),
ECORE_GETOPT_VALUE_BOOL(want_quit),
ECORE_GETOPT_VALUE_BOOL(want_quit),
ECORE_GETOPT_VALUE_BOOL(want_quit),
ECORE_GETOPT_VALUE_NONE
};
ecore_init();
mode_record = mode_play = mode_init = mode_simulation = EINA_FALSE;
want_quit = EINA_FALSE;
exactness_config.base_dir = PACKAGE_DATADIR "/exactness/recordings";
exactness_config.dest_dir = "./";
exactness_config.jobs = 1;
exactness_config.verbose = EINA_FALSE;
args = ecore_getopt_parse(&optdesc, values, argc, argv);
if (args < 0)
{
fprintf(stderr, "Failed parsing arguments.\n");
ret = 1;
goto end;
}
else if (want_quit)
{
ret = 1;
goto end;
}
else if (args == argc)
{
fprintf(stderr, "Expected test list as the last argument..\n");
ecore_getopt_help(stderr, &optdesc);
ret = 1;
goto end;
}
else if (mode_record + mode_play + mode_init + mode_simulation != 1)
{
fprintf(stderr, "At least and only one of the running modes can be set.\n");
ecore_getopt_help(stderr, &optdesc);
ret = 1;
goto end;
}
list_file = argv[args];
/* Load the list file and start iterating over the records. */
test_list = list_file_load(list_file);
if (!test_list)
{
fprintf(stderr, "No matching tests found in '%s'\n", list_file);
ret = 1;
goto end;
}
/* Pre-run summary */
fprintf(stderr, "Running with settings:\n");
fprintf(stderr, "\tConcurrent jobs: %d\n", exactness_config.jobs);
fprintf(stderr, "\tTest list: %s\n", list_file);
fprintf(stderr, "\tBase dir: %s\n", exactness_config.base_dir);
fprintf(stderr, "\tDest dir: %s\n", exactness_config.dest_dir);
if (mode_record)
{
scheduler_run(run_test_record, test_list);
}
else if (mode_play)
{
mkdir(CURRENT_SUBDIR, 0744);
scheduler_run(run_test_play, test_list);
}
else if (mode_init)
{
mkdir(ORIG_SUBDIR, 0744);
scheduler_run(run_test_init, test_list);
}
else if (mode_simulation)
{
scheduler_run(run_test_simulation, test_list);
}
ecore_main_loop_begin();
/* Results */
printf("*******************************************************\n");
if (mode_play)
{
List_Entry *list_itr;
EINA_INLIST_FOREACH(test_list, list_itr)
{
run_test_compare(list_itr);
}
}
printf("Finished executing %u out of %u tests.\n",
exactness_ctx.tests_executed,
eina_inlist_count(EINA_INLIST_GET(test_list)));
if (exactness_ctx.errors)
{
Eina_List *itr;
List_Entry *ent;
printf("List of tests that failed execution:\n");
EINA_LIST_FOREACH(exactness_ctx.errors, itr, ent)
{
printf("\t* %s\n", ent->name);
}
ret = 1;
}
if (exactness_ctx.compare_errors)
{
char *test_name;
printf("List of images that failed comparison:\n");
EINA_LIST_FREE(exactness_ctx.compare_errors, test_name)
{
printf("\t* %s\n", test_name);
free(test_name);
}
ret = 1;
}
list_file_free(test_list);
end:
ecore_shutdown();
return ret;
}

View File

@ -0,0 +1,4 @@
#include "exactness_config.h"
Exactness_Config exactness_config;
Exactness_Ctx exactness_ctx;

View File

@ -0,0 +1,34 @@
#ifndef EXACTNESS_CONFIG_H
#define EXACTNESS_CONFIG_H
#include <Eina.h>
typedef struct _Exactness_Config Exactness_Config;
struct _Exactness_Config
{
unsigned short jobs;
char *base_dir;
char *dest_dir;
Eina_Bool verbose;
};
extern Exactness_Config exactness_config;
typedef struct _Exactness_Ctx Exactness_Ctx;
struct _Exactness_Ctx
{
unsigned int tests_executed;
Eina_List *errors;
Eina_List *compare_errors;
};
extern Exactness_Ctx exactness_ctx;
#define ORIG_SUBDIR "orig"
#define CURRENT_SUBDIR "current"
#define EXACTNESS_PATH_MAX 1024
#endif

72
src/bin/list_file.c Normal file
View File

@ -0,0 +1,72 @@
#include <stdio.h>
#include "list_file.h"
#define BUF_SIZE 1024
List_Entry *
list_file_load(const char *filename)
{
List_Entry *ret = NULL;
char buf[BUF_SIZE] = "";
FILE *file;
file = fopen(filename, "r");
if (!file)
{
perror("Failed opening list file");
return NULL;
}
while (fgets(buf, BUF_SIZE, file))
{
/* Skip comment/empty lines. */
if ((*buf == '#') || (*buf == '\n') || (!*buf))
continue;
char *tmp;
List_Entry *cur = calloc(1, sizeof(*cur));
cur->name = strdup(buf);
/* Set the command to the second half and put a \0 in between. */
tmp = strchr(cur->name, ' ');
if (tmp)
{
*tmp = '\0';
cur->command = tmp + 1;
}
else
{
/* FIXME: error. */
cur->command = "";
}
/* Replace the newline char with a \0. */
tmp = strchr(cur->command, '\n');
if (tmp)
{
*tmp = '\0';
}
ret = EINA_INLIST_CONTAINER_GET(
eina_inlist_append(EINA_INLIST_GET(ret), EINA_INLIST_GET(cur)),
List_Entry);
}
return ret;
}
void
list_file_free(List_Entry *list)
{
while (list)
{
List_Entry *ent = list;
list = EINA_INLIST_CONTAINER_GET(EINA_INLIST_GET(list)->next,
List_Entry);
free(ent->name);
free(ent);
/* we don't free ent->command because it's allocated together. */
}
}

17
src/bin/list_file.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef LIST_FILE_H
#define LIST_FILE_H
#include <Eina.h>
typedef struct _List_Entry List_Entry;
struct _List_Entry
{
EINA_INLIST;
char *name;
const char *command;
};
List_Entry *list_file_load(const char *filename);
void list_file_free(List_Entry *list);
#endif

295
src/bin/md5/md5.c Normal file
View File

@ -0,0 +1,295 @@
/*
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
* MD5 Message-Digest Algorithm (RFC 1321).
*
* Homepage:
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
*
* Author:
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
*
* This software was written by Alexander Peslyak in 2001. No copyright is
* claimed, and the software is hereby placed in the public domain.
* In case this attempt to disclaim copyright and place the software in the
* public domain is deemed null and void, then the software is
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* (This is a heavily cut-down "BSD license".)
*
* This differs from Colin Plumb's older public domain implementation in that
* no exactly 32-bit integer data type is required (any 32-bit or wider
* unsigned integer data type will do), there's no compile-time endianness
* configuration, and the function prototypes match OpenSSL's. No code from
* Colin Plumb's implementation has been reused; this comment merely compares
* the properties of the two independent implementations.
*
* The primary goals of this implementation are portability and ease of use.
* It is meant to be fast, but not as fast as possible. Some known
* optimizations are not included to reduce source code size and avoid
* compile-time configuration.
*/
#ifndef HAVE_OPENSSL
#include <string.h>
#include "md5.h"
/*
* The basic MD5 functions.
*
* F and G are optimized compared to their RFC 1321 definitions for
* architectures that lack an AND-NOT instruction, just like in Colin Plumb's
* implementation.
*/
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
/*
* The MD5 transformation for all four rounds.
*/
#define STEP(f, a, b, c, d, x, t, s) \
(a) += f((b), (c), (d)) + (x) + (t); \
(a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \
(a) += (b);
/*
* SET reads 4 input bytes in little-endian byte order and stores them
* in a properly aligned word in host byte order.
*
* The check for little-endian architectures that tolerate unaligned
* memory accesses is just an optimization. Nothing will break if it
* doesn't work.
*/
#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
#define SET(n) \
(*(MD5_u32plus *)&ptr[(n) * 4])
#define GET(n) \
SET(n)
#else
#define SET(n) \
(ctx->block[(n)] = \
(MD5_u32plus)ptr[(n) * 4] | \
((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \
((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \
((MD5_u32plus)ptr[(n) * 4 + 3] << 24))
#define GET(n) \
(ctx->block[(n)])
#endif
/*
* This processes one or more 64-byte data blocks, but does NOT update
* the bit counters. There are no alignment requirements.
*/
static void *body(MD5_CTX *ctx, void *data, unsigned long size)
{
unsigned char *ptr;
MD5_u32plus a, b, c, d;
MD5_u32plus saved_a, saved_b, saved_c, saved_d;
ptr = data;
a = ctx->a;
b = ctx->b;
c = ctx->c;
d = ctx->d;
do {
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
/* Round 1 */
STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)
/* Round 2 */
STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
/* Round 3 */
STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
STEP(H, d, a, b, c, GET(8), 0x8771f681, 11)
STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23)
STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11)
STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23)
STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11)
STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
STEP(H, b, c, d, a, GET(6), 0x04881d05, 23)
STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11)
STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23)
/* Round 4 */
STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx->a = a;
ctx->b = b;
ctx->c = c;
ctx->d = d;
return ptr;
}
void MD5_Init(MD5_CTX *ctx)
{
ctx->a = 0x67452301;
ctx->b = 0xefcdab89;
ctx->c = 0x98badcfe;
ctx->d = 0x10325476;
ctx->lo = 0;
ctx->hi = 0;
}
void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size)
{
MD5_u32plus saved_lo;
unsigned long used, free;
saved_lo = ctx->lo;
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
ctx->hi++;
ctx->hi += size >> 29;
used = saved_lo & 0x3f;
if (used) {
free = 64 - used;
if (size < free) {
memcpy(&ctx->buffer[used], data, size);
return;
}
memcpy(&ctx->buffer[used], data, free);
data = (unsigned char *)data + free;
size -= free;
body(ctx, ctx->buffer, 64);
}
if (size >= 64) {
data = body(ctx, data, size & ~(unsigned long)0x3f);
size &= 0x3f;
}
memcpy(ctx->buffer, data, size);
}
void MD5_Final(unsigned char *result, MD5_CTX *ctx)
{
unsigned long used, free;
used = ctx->lo & 0x3f;
ctx->buffer[used++] = 0x80;
free = 64 - used;
if (free < 8) {
memset(&ctx->buffer[used], 0, free);
body(ctx, ctx->buffer, 64);
used = 0;
free = 64;
}
memset(&ctx->buffer[used], 0, free - 8);
ctx->lo <<= 3;
ctx->buffer[56] = ctx->lo;
ctx->buffer[57] = ctx->lo >> 8;
ctx->buffer[58] = ctx->lo >> 16;
ctx->buffer[59] = ctx->lo >> 24;
ctx->buffer[60] = ctx->hi;
ctx->buffer[61] = ctx->hi >> 8;
ctx->buffer[62] = ctx->hi >> 16;
ctx->buffer[63] = ctx->hi >> 24;
body(ctx, ctx->buffer, 64);
result[0] = ctx->a;
result[1] = ctx->a >> 8;
result[2] = ctx->a >> 16;
result[3] = ctx->a >> 24;
result[4] = ctx->b;
result[5] = ctx->b >> 8;
result[6] = ctx->b >> 16;
result[7] = ctx->b >> 24;
result[8] = ctx->c;
result[9] = ctx->c >> 8;
result[10] = ctx->c >> 16;
result[11] = ctx->c >> 24;
result[12] = ctx->d;
result[13] = ctx->d >> 8;
result[14] = ctx->d >> 16;
result[15] = ctx->d >> 24;
memset(ctx, 0, sizeof(*ctx));
}
#endif

45
src/bin/md5/md5.h Normal file
View File

@ -0,0 +1,45 @@
/*
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
* MD5 Message-Digest Algorithm (RFC 1321).
*
* Homepage:
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
*
* Author:
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
*
* This software was written by Alexander Peslyak in 2001. No copyright is
* claimed, and the software is hereby placed in the public domain.
* In case this attempt to disclaim copyright and place the software in the
* public domain is deemed null and void, then the software is
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* See md5.c for more information.
*/
#ifdef HAVE_OPENSSL
#include <openssl/md5.h>
#elif !defined(_MD5_H)
#define _MD5_H
/* Any 32-bit or wider unsigned integer data type will do */
typedef unsigned int MD5_u32plus;
typedef struct {
MD5_u32plus lo, hi;
MD5_u32plus a, b, c, d;
unsigned char buffer[64];
MD5_u32plus block[16];
} MD5_CTX;
extern void MD5_Init(MD5_CTX *ctx);
extern void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size);
extern void MD5_Final(unsigned char *result, MD5_CTX *ctx);
#endif

146
src/bin/run_test.c Normal file
View File

@ -0,0 +1,146 @@
#include <stdio.h>
#include <unistd.h>
#include "config.h"
#include "scheduler.h"
#include "run_test.h"
#include "list_file.h"
#include "exactness_config.h"
#include "md5/md5.h"
#define LIBEXACTNESS_PATH PACKAGE_LIBDIR "/exactness/libexactness.so"
void
run_test_simulation(const List_Entry *ent, char *buf)
{
snprintf(buf, SCHEDULER_CMD_SIZE, "TSUITE_DEST_DIR='%s' TSUITE_FILE_NAME='%s/%s.rec' TSUITE_TEST_NAME='%s' LD_PRELOAD='%s' %s",
exactness_config.dest_dir,
exactness_config.base_dir, ent->name,
ent->name, LIBEXACTNESS_PATH,
ent->command);
}
void
run_test_play(const List_Entry *ent, char *buf)
{
snprintf(buf, SCHEDULER_CMD_SIZE, "ELM_ENGINE='buffer' TSUITE_DEST_DIR='%s/" CURRENT_SUBDIR "' TSUITE_FILE_NAME='%s/%s.rec' TSUITE_TEST_NAME='%s' LD_PRELOAD='%s' %s",
exactness_config.dest_dir,
exactness_config.base_dir, ent->name,
ent->name, LIBEXACTNESS_PATH,
ent->command);
run_test_prefix_rm(CURRENT_SUBDIR, ent->name);
}
void
run_test_record(const List_Entry *ent, char *buf)
{
snprintf(buf, SCHEDULER_CMD_SIZE, "TSUITE_RECORDING='rec' TSUITE_DEST_DIR='%s' TSUITE_FILE_NAME='%s/%s.rec' TSUITE_TEST_NAME='%s' LD_PRELOAD='%s' %s",
exactness_config.dest_dir,
exactness_config.base_dir, ent->name,
ent->name, LIBEXACTNESS_PATH,
ent->command);
}
void
run_test_init(const List_Entry *ent, char *buf)
{
snprintf(buf, SCHEDULER_CMD_SIZE, "ELM_ENGINE='buffer' TSUITE_DEST_DIR='%s/" ORIG_SUBDIR "' TSUITE_FILE_NAME='%s/%s.rec' TSUITE_TEST_NAME='%s' LD_PRELOAD='%s' %s",
exactness_config.dest_dir,
exactness_config.base_dir, ent->name,
ent->name, LIBEXACTNESS_PATH,
ent->command);
run_test_prefix_rm(ORIG_SUBDIR, ent->name);
}
static Eina_Bool
_file_md5_get(const char *filename, unsigned char *result)
{
MD5_CTX ctx;
Eina_File *file;
file = eina_file_open(filename, 0);
if (!file)
return EINA_FALSE;
MD5_Init(&ctx);
MD5_Update(&ctx, eina_file_map_all(file, EINA_FILE_SEQUENTIAL), eina_file_size_get(file));
MD5_Final(result, &ctx);
eina_file_close(file);
return EINA_TRUE;
}
#define _MD5_SIZE 16
static Eina_Bool
_md5_is_equal(const char *filename1, const char *filename2)
{
unsigned char res1[_MD5_SIZE], res2[_MD5_SIZE];
if (!_file_md5_get(filename1, res1))
return EINA_FALSE;
if (!_file_md5_get(filename2, res2))
return EINA_FALSE;
return !memcmp(res1, res2, _MD5_SIZE);
}
static void
_compare_list_cb(const char *name, const char *path EINA_UNUSED, void *data)
{
const char *prefix = data;
if (!strncmp(name, prefix, strlen(prefix)))
{
char filename1[EXACTNESS_PATH_MAX], filename2[EXACTNESS_PATH_MAX];
snprintf(filename1, EXACTNESS_PATH_MAX, "%s/%s", CURRENT_SUBDIR, name);
snprintf(filename2, EXACTNESS_PATH_MAX, "%s/%s", ORIG_SUBDIR, name);
if (!_md5_is_equal(filename1, filename2))
{
char buf[EXACTNESS_PATH_MAX];
exactness_ctx.compare_errors =
eina_list_append(exactness_ctx.compare_errors,
strdup(name));
/* FIXME: Clean up. */
snprintf(buf, EXACTNESS_PATH_MAX,
"compare '%s/%s' '%s/%s' '%s/comp_%s'",
ORIG_SUBDIR, name,
CURRENT_SUBDIR, name,
CURRENT_SUBDIR, name);
if (system(buf))
{
fprintf(stderr, "Failed image comparing '%s'\n", name);
}
}
}
}
void
run_test_compare(const List_Entry *ent)
{
eina_file_dir_list(ORIG_SUBDIR, 0, _compare_list_cb, ent->name);
}
static void
_prefix_rm_cb(const char *name, const char *path, void *data)
{
const char *prefix = data;
if (!strncmp(name, prefix, strlen(prefix)))
{
char buf[EXACTNESS_PATH_MAX];
snprintf(buf, EXACTNESS_PATH_MAX, "%s/%s", path, name);
if (unlink(buf))
{
printf("Failed deleting '%s/%s': ", path, name);
perror("");
}
}
}
void
run_test_prefix_rm(const char *dir, const char *prefix)
{
eina_file_dir_list(dir, 0, _prefix_rm_cb, (void *) prefix);
}

14
src/bin/run_test.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef RUN_TEST_H
#define RUN_TEST_H
#include "list_file.h"
void run_test_simulation(const List_Entry *ent, char *buf);
void run_test_play(const List_Entry *ent, char *buf);
void run_test_record(const List_Entry *ent, char *buf);
void run_test_init(const List_Entry *ent, char *buf);
void run_test_compare(const List_Entry *ent);
void run_test_prefix_rm(const char *dir, const char *prefix);
#endif

94
src/bin/scheduler.c Normal file
View File

@ -0,0 +1,94 @@
#include <Ecore.h>
#include "scheduler.h"
#include "exactness_config.h"
typedef struct
{
Scheduler_Cb prepare_func;
List_Entry *last;
unsigned short jobs;
} Scheduler_Ctx;
static Ecore_Event_Handler *_job_del_callback_handler = NULL;
static Eina_Bool _job_dispatch(List_Entry *ent, Scheduler_Ctx *ctx);
static Eina_Bool
_job_deleted_cb(void *data, int type EINA_UNUSED, void *event)
{
Ecore_Exe_Event_Del *msg = (Ecore_Exe_Event_Del *) event;
Scheduler_Ctx *ctx = data;
if (msg->exit_code != 0)
{
List_Entry *ent = ecore_exe_data_get(msg->exe);
exactness_ctx.errors = eina_list_append(exactness_ctx.errors, ent);
}
ctx->jobs++;
exactness_ctx.tests_executed++;
if (ctx->last && EINA_INLIST_GET(ctx->last)->next)
{
ctx->last = EINA_INLIST_CONTAINER_GET(
EINA_INLIST_GET(ctx->last)->next, List_Entry);
_job_dispatch(ctx->last, ctx);
}
/* If all jobs are done. */
if (ctx->jobs == exactness_config.jobs)
{
free(ctx);
ecore_main_loop_quit();
return ECORE_CALLBACK_DONE;
}
return ECORE_CALLBACK_RENEW;
}
static Eina_Bool
_job_dispatch(List_Entry *ent, Scheduler_Ctx *ctx)
{
char buf[SCHEDULER_CMD_SIZE];
Ecore_Exe *exe;
if (ctx->jobs == 0)
return EINA_FALSE;
ctx->jobs--;
ctx->prepare_func(ent, buf);
if (!_job_del_callback_handler)
{
_job_del_callback_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
_job_deleted_cb, ctx);
}
exe = ecore_exe_pipe_run(buf, ECORE_EXE_TERM_WITH_PARENT, ent);
if (!exe)
{
fprintf(stderr, "Failed executing test '%s'\n", ent->name);
}
return EINA_TRUE;
}
void
scheduler_run(Scheduler_Cb prepare_func, List_Entry *list)
{
Scheduler_Ctx *ctx = calloc(1, sizeof(*ctx));
List_Entry *list_itr;
ctx->jobs = exactness_config.jobs;
ctx->prepare_func = prepare_func;
EINA_INLIST_FOREACH(list, list_itr)
{
if (!_job_dispatch(list_itr, ctx))
break;
ctx->last = list_itr;
}
}

12
src/bin/scheduler.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef SCHEDULER_H
#define SCHEDULER_H
#include "list_file.h"
#define SCHEDULER_CMD_SIZE 1024
typedef void (*Scheduler_Cb)(const List_Entry *, char *);
void scheduler_run(Scheduler_Cb prepare_func, List_Entry *list);
#endif

View File

@ -1,10 +0,0 @@
MAINTAINERCLEANFILES = Makefile.in
exactness: exactness.in
$(AM_V_GEN) $(SED) -e "s|\@libdir\@|$(libdir)|" -e "s|\@datadir\@|$(datadir)|" $(srcdir)/exactness.in > $(builddir)/exactness
bin_SCRIPTS = exactness
CLEAN_FILES = exactness
EXTRA_DIST = exactness.in

View File

@ -1,537 +0,0 @@
#!/bin/bash
# tsuite_script.sh -i this makes new 'orig' folder
# tsuite_script.sh -i -b [BaseDir] TestName1 [TestName2 ...] rewrite files for selcted tests in 'orig' folder
# tsuite_script.sh -r -b [BaseDir] [TestName1 TestName2 ...] ; this means record [all tests or TestName additional arg]
# tsuite_script.sh -b [BaseDir] -d FolderName -p [TestName1 TestName2 ...] this means play a record and put screenshots FolderName
# tsuite_script.sh -d FolderName -c [TestName1 TestName2 ...] this means compare "orig" with screenshots in FolderName
# When omitting DestDir we will use 'current' as default DestDir
#_DEBUG="on"
function DEBUG()
{
[ "$_DEBUG" == "on" ] && $@
}
function VERBOSE()
{
if $_verbose ; then
$@
fi
}
do_help () {
echo "Use $0 to test application screen-layout."
echo "First, you need to compose a tests file as follows:"
echo "Each line begins with test name"
echo "second field is test-command and [optional] params."
echo "Any line starting with '#' is a comment (ignored):"
echo
echo "# This is a comment line"
echo "TestName TestCmd [param1] [param2]"
echo
echo "Later, you run $0 with the tests file as parameter."
echo
echo "By default, exactness runs through test file running all tests specified."
echo "You may run selected tests by adding test name as param to exactness."
echo "Usage:"
echo "$0 -s TestsFile TestName1 [TestName2] [...]"
echo "Use this option to run selected tests without modifying your test file."
echo "TestName param has to match test name given in tests file (1st field)"
echo
echo
echo "Two additional parameters that $0 accepts:"
echo "BaseDir - This is where '.rec' files reside."
echo "DestDir - Where $0 creates test screen shots."
echo " Gets 'current' under 'pwd' by default ('orig' on init)"
echo
echo
echo "Use the following options:"
echo "To record tests:"
echo "$0 -r [-b BaseDir] TestsFile"
echo "Use BaseDir arg to create record files in specific folder."
echo "Otherwise pwd is used."
echo
echo "Pressing F2 while recording, sets screen shot at this stage of test."
echo "You may define env-var 'TSUITE_SHOT_KEY' to alter shot-key."
echo "'.rec' file is produced for each test in your TestsFile."
echo "File name is defined as 'TestName.rec' for each test."
echo
echo "You may test your record files with simulate option:"
echo "$0 -s [-b BaseDir] TestsFile"
echo
echo "You need to run $0 with init option prior"
echo "to using play option."
echo "Later, when doing play, PNG files are compared with"
echo "PNG files reside in 'orig' folder create when init."
echo
echo "To use init option:"
echo "$0 -i [-b BaseDir] TestsFile"
echo "Do not use DestDir param with init, target always 'orig'."
echo
echo "Use Play tests option to produce PNG files of screen shot:"
echo "$0 -p [-b BaseDir] [-d DestDir] TestsFile"
echo "Play option produces PNG files in DestDir."
echo "These are compares with PNGs in 'orig'."
echo "(created in 'init' phase)"
echo
echo "Use -v option for detailed flow-report."
echo "Thus, when running many tests, the output format makes"
echo "it easy to match output to a running test."
echo "Example:"
echo "$0 -v -p [-b BaseDir] [-d DestDir] TestsFile"
echo
echo "-b = Set the base dir, the location of the rec files."
echo "-p = Run in compare mode. Generates the current images and compares with the old ones."
echo "-r = Run in record mode. Record the '.rec' files."
echo "-i = Run in init mode. Generate the reference images."
echo "-s = Run in simulation mode. Similar to -p, but runs in visible window to show you what is actually recorded and does not actually generate images."
echo "-v = Run in verbose mode."
echo "-h/? = This message."
}
get_test_params () {
# This function analyze input line and sets test-file-name, rec-file-name
# reset globals
_test_name=
_test_cmd=
local line="$1"
local c=${line:0:1}
if [ "$c" = "#" ]
then
return 1
fi
local p=`expr index "$line" \ `
if [ $p -ne 0 ]
then
(( p-- ))
fi
_test_name=${line:0:p}
(( p++ ))
_test_cmd=${line:p}
# Test that input is valid
if [ -z "$_test_name" ]
then
_test_name=
_test_cmd=
return 1
fi
if [ -z "$_test_cmd" ]
then
_test_name=
_test_cmd=
return 1
fi
DEBUG echo test name=\""$_test_name"\"
DEBUG echo test cmd=\""$_test_cmd"\"
return 0
}
do_record () {
DEBUG printf "do_record()\n"
# This will run record for all test if no specific test specified
# or run recording of specified tests (names passed as parameter).
# run ALL tests to record
DEBUG echo do_record "$*"
get_test_params "$1"
if [ $? -ne 0 ]
then
return 0
fi
VERBOSE echo "do_record: $_test_name"
TSUITE_RECORDING='rec' TSUITE_DEST_DIR=${_dest_dir} TSUITE_FILE_NAME=${_base_dir}/${_test_name}.rec TSUITE_TEST_NAME=${_test_name} LD_PRELOAD=${OUR_LIBPATH}/libexactness.so eval ${_test_cmd}
}
do_simulation () {
# This will play simulation
# this will NOT produce screenshots
DEBUG echo do_simulation "$*"
get_test_params "$1"
if [ $? -ne 0 ]
then
return 0
fi
local file_name=${_base_dir}/${_test_name}.rec
if [ ! -e "$file_name" ]
then
echo Rec file "$file_name" not found.
return 1
fi
VERBOSE echo "do_simulation: $_test_name"
TSUITE_DEST_DIR=${_dest_dir} TSUITE_FILE_NAME=${file_name} TSUITE_TEST_NAME=${_test_name} LD_PRELOAD=${OUR_LIBPATH}/libexactness.so eval ${_test_cmd}
}
do_play () {
# This will play record for all test if specified.
# or play record of tests specified as parameter.
# run ALL tests to record
DEBUG echo base dir: "$_base_dir"
DEBUG echo dest dir: "$_dest_dir"
DEBUG echo do_play "$_dest_dir" "$*"
# Play recorded tests and produce PNG files.
# this will produce screenshots in "_dest_dir" folder
get_test_params "$1"
if [ $? -ne 0 ]
then
return 0
fi
local file_name=${_base_dir}/${_test_name}.rec
if [ ! -e "$file_name" ]
then
echo Rec file "$file_name" not found.
return 1
fi
if [ -e "$_dest_dir" ]
then
# Remove PNG files according to tests played
rm "$_dest_dir"/${_test_name}_[0-9]*.png &> /dev/null
else
# Create dest dir
mkdir -p "$_dest_dir" &> /dev/null
fi
VERBOSE echo "do_play: $_test_name"
ELM_ENGINE="buffer" TSUITE_DEST_DIR=${_dest_dir} TSUITE_FILE_NAME=${file_name} TSUITE_TEST_NAME=${_test_name} LD_PRELOAD=${OUR_LIBPATH}/libexactness.so eval ${_test_cmd}
}
compare_files () {
VERBOSE echo "compare_files: <$1> and <$2>"
if [ -e "$1" ]
# First file exists
then
local md_file1=`md5sum $1`
if [ -e "$2" ]
then
# Second file exists
local md_file2=`md5sum $2`
# Get md5 of both files
local md1=`echo "$md_file1" | cut -d ' ' -f1`
local md2=`echo "$md_file2" | cut -d ' ' -f1`
# Increase counter of comparisons
(( ncomp++ ))
# Compare md5 of both files
if [ "x$md1" != "x$md2" ]
then
if [ $comp_unavail -eq 0 ]
then
# Create diff-file with 'comp_' prefix.
local name=`basename "$1"`
compare "$1" "$2" "$_dest_dir"/comp_"$name"
else
echo "$name does not match."
fi
# Increment counter of files not identical.
(( nfail++ ))
fi
else
# Failed to find second file
echo "Test file was not found $2"
(( nerr++ ))
fi
else
# Failed to find first file
echo "Test file was not found $1"
(( nerr++ ))
fi
}
process_compare () {
# Process all files listed in array (param)
local files_list=( "$@" )
for line in "${files_list[@]}"
do
local name=`basename "$line"`
DEBUG echo "comparing $name"
compare_files "$_orig_dir"/"$name" "$_dest_dir"/"$name"
done
}
do_compare () {
DEBUG printf "do_compare()\n"
DEBUG echo orig dir: "$_orig_dir"
DEBUG echo dest dir: "$_dest_dir"
get_test_params "$1"
if [ $? -ne 0 ]; then
return 0;
fi
# This will compare files in 'orig' folder with files in _dest_dir
if [ $comp_unavail -ne 0 ]
then
if [ $# -eq 1 ]
then
echo "Compare software missing."
echo "Install \"ImageMagick\" if you like to procduce \"comp\" files."
echo "Printing diffs to output"
fi
fi
if [ -z "$_dest_dir" ]
then
printf "For comparing, Usage: %s -p -d DirName\nor\n%s -c -d DirName TestName1, TestName2,...\n" $(basename $0) $(basename $0) >&2
fi
if [ "$_dest_dir" = "$_orig_dir" ]
then
printf "Dest-Dir is $_dest_dir, exiting.\n"
return 0
fi
local files_list=
for test_name in $_test_name
do
rm "$_dest_dir"/comp_"$test_name"_[0-9]*.png &> /dev/null
files_list=( `ls "$_dest_dir"/"$test_name"_[0-9]*.png` )
process_compare "${files_list[@]}"
done
if [ "$ncomp" -ne 0 ]
then
echo "Compared $ncomp images."
fi
if [ "$nfail" -ne 0 ]
then
echo "Tests with render regressions: $nfail."
fi
if [ "$nerr" -ne 0 ]
then
echo "$nerr PNG-files were not found"
fi
return 0
}
name_in_args () {
# This function gets curline as first arg
# Then list of args to find if test name is first field of curline
get_test_params "$1"
if [ $? -ne 0 ]
then
return 0
fi
if [ -z "$_test_name" ]
then
return 0
fi
shift
while (( "$#" ));
do
if [ "$_test_name" = "$1" ]
# found test name in list of args
then
return 1
fi
shift
done
# Not found
return 0
}
for_test_in_test_file_do () {
while read curline;
do
name_in_args "$curline" $*
_run_test=$(( $? + $_test_all ))
if [ $_run_test -ne 0 ]
then
$1 "$curline"
if [ $? -ne 0 ]
then
(( _n_exe_err++ ))
fi
fi
done < "$_test_file_name"
}
# Script Entry Point
OUR_LIBPATH="@libdir@/exactness"
_verbose=false
_record=
_play=
_compare=
_init=
_simulation=
_remove_fail=
_orig_dir="orig"
# Init dest_dir - should change on the fly
_dest_dir=
_test_all=1
_base_dir="@datadir@/exactness/recordings"
_test_name=
_test_cmd=
nerr=0
ncomp=0
nfail=0
_n_exe_err=0
# Test that compare is insatlled
which compare &> /dev/null
comp_unavail=$?
while getopts 'ab:d:hprisv?' OPTION
do
case $OPTION in
b) _base_dir="$OPTARG"
;;
d) _dest_dir="$OPTARG"
;;
p) _play=1
_compare=1
_remove_fail=1
;;
r) _record=1
_remove_fail=1
;;
i) _dest_dir="$_orig_dir"
_init=1
_play=1
_remove_fail=1
;;
s) _dest_dir="$_orig_dir"
_simulation=1
;;
h) do_help
exit 0
;;
v) _verbose=true
;;
?) do_help
exit 0
;;
esac
done
shift $(($OPTIND - 1))
_test_file_name="$1"
shift
# Test if user added test-names as arguments
if [ ! -z "$*" ]
then
_test_all=0
fi
# when using -o option, we can loop now on tests names
# given as arguments to this script
DEBUG echo _test_file_name="$_test_file_name"
DEBUG echo _base_dir="$_base_dir"
DEBUG echo _dest_dir="$_dest_dir"
if [ ! -e "$_base_dir" ]
then
echo "Base dir <$_base_dir> - not found."
exit 1
fi
# printf "Remaining arguments are: %s\n" "$*"
if [ -z "$_dest_dir" ]
then
if [ ! -z "$_play" ]
then
_dest_dir="current"
fi
if [ ! -z "$_compare" ]
then
_dest_dir="current"
fi
else
if [ ! -z "$_init" ]
then
if [ "$_dest_dir" != "$_orig_dir" ]
then
echo "Cannot use '-i' option with a DestDir that is not 'orig'"
echo "No need to specify DestDir when using '-i'"
echo "For help: $0 -h"
exit 2
fi
fi
fi
if [ ! -z "$_compare" ]
then
if [ "$_dest_dir" = "$_orig_dir" ]
then
echo "Cannot use 'orig' dir with compare '-c' option"
echo "Please select different DestDir"
echo "For help: $0 -h"
exit 3
fi
fi
if [ "$_simulation" ]
then
# When in simulation mode, we will just commit play (ignore other options)
_init=
_record=
_compare=
_remove_fail=
_play=
for_test_in_test_file_do do_simulation
fi
if [ "$_record" ]
then
for_test_in_test_file_do do_record
fi
if [ "$_play" ]
then
for_test_in_test_file_do do_play
fi
if [ "$_compare" ]
then
for_test_in_test_file_do do_compare
fi
_n_tests_failed=0
# Add up total-error and emit user message.
total_errors=$(( $nfail + $nerr + $_n_tests_failed + $_n_exe_err ))
echo "Tests that ended with non-zero exit code: $_n_exe_err."
echo "Total errors: $total_errors."
status=0
# Compute exit code
if [ "$nfail" -ne 0 ]
then
status=$(( $status | 1 ))
fi
if [ "$nerr" -ne 0 ]
then
status=$(( $status | 2 ))
fi
if [ "$_n_tests_failed" -ne 0 ]
then
status=$(( $status | 4 ))
fi
exit $status