diff --git a/README b/README index 51f37ef..e392f45 100644 --- a/README +++ b/README @@ -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, diff --git a/configure.ac b/configure.ac index 566effa..91879b1 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 104f584..40211bf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ MAINTAINERCLEANFILES = Makefile.in -SUBDIRS = lib scripts +SUBDIRS = lib bin diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am new file mode 100644 index 0000000..200ccd7 --- /dev/null +++ b/src/bin/Makefile.am @@ -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)\" diff --git a/src/bin/exactness.c b/src/bin/exactness.c new file mode 100644 index 0000000..15ab1fc --- /dev/null +++ b/src/bin/exactness.c @@ -0,0 +1,185 @@ +#include +#include + +#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> ", + 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; +} diff --git a/src/bin/exactness_config.c b/src/bin/exactness_config.c new file mode 100644 index 0000000..a70f39a --- /dev/null +++ b/src/bin/exactness_config.c @@ -0,0 +1,4 @@ +#include "exactness_config.h" + +Exactness_Config exactness_config; +Exactness_Ctx exactness_ctx; diff --git a/src/bin/exactness_config.h b/src/bin/exactness_config.h new file mode 100644 index 0000000..dd31c8e --- /dev/null +++ b/src/bin/exactness_config.h @@ -0,0 +1,34 @@ +#ifndef EXACTNESS_CONFIG_H +#define EXACTNESS_CONFIG_H + +#include + +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 diff --git a/src/bin/list_file.c b/src/bin/list_file.c new file mode 100644 index 0000000..e0f5f23 --- /dev/null +++ b/src/bin/list_file.c @@ -0,0 +1,72 @@ +#include + +#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. */ + } +} + diff --git a/src/bin/list_file.h b/src/bin/list_file.h new file mode 100644 index 0000000..f4f35ae --- /dev/null +++ b/src/bin/list_file.h @@ -0,0 +1,17 @@ +#ifndef LIST_FILE_H +#define LIST_FILE_H + +#include + +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 diff --git a/src/bin/md5/md5.c b/src/bin/md5/md5.c new file mode 100644 index 0000000..7d43a60 --- /dev/null +++ b/src/bin/md5/md5.c @@ -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 + * + * 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 + +#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 diff --git a/src/bin/md5/md5.h b/src/bin/md5/md5.h new file mode 100644 index 0000000..f1a6857 --- /dev/null +++ b/src/bin/md5/md5.h @@ -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 + * + * 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 +#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 diff --git a/src/bin/run_test.c b/src/bin/run_test.c new file mode 100644 index 0000000..fefc37f --- /dev/null +++ b/src/bin/run_test.c @@ -0,0 +1,146 @@ +#include +#include + +#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); +} diff --git a/src/bin/run_test.h b/src/bin/run_test.h new file mode 100644 index 0000000..07ad7e2 --- /dev/null +++ b/src/bin/run_test.h @@ -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 diff --git a/src/bin/scheduler.c b/src/bin/scheduler.c new file mode 100644 index 0000000..c18d595 --- /dev/null +++ b/src/bin/scheduler.c @@ -0,0 +1,94 @@ +#include + +#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; + } +} diff --git a/src/bin/scheduler.h b/src/bin/scheduler.h new file mode 100644 index 0000000..91d9b3a --- /dev/null +++ b/src/bin/scheduler.h @@ -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 diff --git a/src/scripts/Makefile.am b/src/scripts/Makefile.am deleted file mode 100644 index 1a74df3..0000000 --- a/src/scripts/Makefile.am +++ /dev/null @@ -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 diff --git a/src/scripts/exactness.in b/src/scripts/exactness.in deleted file mode 100755 index 30ade97..0000000 --- a/src/scripts/exactness.in +++ /dev/null @@ -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