Store and compare objects information at every shot

This feature is aimed to provide a new way to debug applications during
scenarios playing.
When a difference happens between two shots of an application, the
investigation can be tough as the cause may be hidden into a tiny
change, such as an update of the theme.
This feature tries to respond to this problem by storing objects of
the application every time a shot is taken. Then during shots comparison,
objects information are compared and differences are displayed on the
screen.

The feature can be used with the -S option.

For the moment, only hierarchy, order and geometry are checked.
This commit is contained in:
Daniel Zaoui 2016-10-13 07:46:39 +03:00
parent 8e0a638b19
commit dd4c1caade
8 changed files with 383 additions and 26 deletions

View File

@ -12,7 +12,7 @@ exactness_SOURCES = \
exactness_helper_SOURCES = exactness_helper.c
exactness_LDADD = \
@EFL_LIBS@
@EFL_LIBS@ ../lib/libexactness_player.la
exactness_helper_LDADD = \
@EFL_LIBS@ ../lib/libexactness_player.la

View File

@ -40,6 +40,7 @@ static const Ecore_Getopt optdesc = {
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('S', "store-objects", "Store information about objects at every screen shot time."),
ECORE_GETOPT_STORE_TRUE('v', "verbose", "Turn verbose messages on."),
ECORE_GETOPT_LICENSE('L', "license"),
@ -69,6 +70,7 @@ main(int argc, char *argv[])
ECORE_GETOPT_VALUE_BOOL(mode_play),
ECORE_GETOPT_VALUE_BOOL(mode_init),
ECORE_GETOPT_VALUE_BOOL(mode_simulation),
ECORE_GETOPT_VALUE_BOOL(exactness_config.store_objects),
ECORE_GETOPT_VALUE_BOOL(exactness_config.verbose),
ECORE_GETOPT_VALUE_BOOL(want_quit),

View File

@ -12,6 +12,7 @@ struct _Exactness_Config
char *dest_dir;
char *wrap_command;
Eina_Bool verbose;
Eina_Bool store_objects;
};
extern Exactness_Config exactness_config;

View File

@ -116,7 +116,7 @@ _event_specific_info_get(const Variant_st *v, char output[1024])
}
static const Ecore_Getopt optdesc = {
"exactness_helper",
"%prog [options] <rec file>",
"%prog [options] [<rec file> | <file1 file2>]",
NULL,
"(C) 2016 Enlightenment",
"BSD",
@ -125,7 +125,8 @@ static const Ecore_Getopt optdesc = {
{
ECORE_GETOPT_STORE_USHORT('d', "delay", "Delay the given recording by a given time (in seconds)."),
ECORE_GETOPT_STORE_TRUE('c', "clean", "Clean the given recording from wrong events."),
ECORE_GETOPT_STORE_TRUE('l', "list", "List the events of the given recording"),
ECORE_GETOPT_STORE_TRUE('l', "list", "List the events of the given recording."),
ECORE_GETOPT_STORE_TRUE('C', "compare", "Compare two given files (images files or objects eet files)."),
ECORE_GETOPT_LICENSE('L', "license"),
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
@ -149,14 +150,15 @@ _is_hook_duplicate(const Variant_st *cur_v, const Variant_st *prev_v)
int
main(int argc, char *argv[])
{
const char *rec_file = NULL;
const char *rec_file = NULL, *comp1 = NULL, *comp2 = NULL;
int ret = 0, args = 0;
unsigned short delay = 0;
Eina_Bool want_quit, clean = EINA_FALSE, list_get = EINA_FALSE;
Eina_Bool want_quit, clean = EINA_FALSE, list_get = EINA_FALSE, compare_files = EINA_FALSE;
Ecore_Getopt_Value values[] = {
ECORE_GETOPT_VALUE_USHORT(delay),
ECORE_GETOPT_VALUE_BOOL(clean),
ECORE_GETOPT_VALUE_BOOL(list_get),
ECORE_GETOPT_VALUE_BOOL(compare_files),
ECORE_GETOPT_VALUE_BOOL(want_quit),
ECORE_GETOPT_VALUE_BOOL(want_quit),
@ -180,17 +182,33 @@ main(int argc, char *argv[])
ret = 1;
goto end;
}
else if (args == argc)
else if ((clean || delay || list_get) && args == argc)
{
fprintf(stderr, "Expected rec file as the last argument..\n");
ecore_getopt_help(stderr, &optdesc);
ret = 1;
goto end;
}
else if (compare_files && argc - args != 2)
{
fprintf(stderr, "Expected two files to compare as last arguments..\n");
ecore_getopt_help(stderr, &optdesc);
ret = 1;
goto end;
}
rec_file = argv[args];
Timer_Data td;
Lists_st *list = read_events(rec_file, &td);
Lists_st *list = NULL;
if (clean || delay || list_get)
{
rec_file = argv[args];
Timer_Data td;
list = read_events(rec_file, &td);
}
if (compare_files)
{
comp1 = argv[args];
comp2 = argv[args+1];
}
if (clean)
{
@ -243,7 +261,37 @@ main(int argc, char *argv[])
}
}
write_events(rec_file, list);
if (compare_files)
{
const char *ext = strrchr(comp1, '.');
if (!ext)
{
fprintf(stderr, "Extension required\n");
goto end;
}
if (!strcmp(ext, ".eet"))
{
if (!objects_files_compare(comp1, comp2, EINA_TRUE))
{
fprintf(stderr, "Failed objects comparing\n");
}
}
else
{
char buf[1024];
/* FIXME: Clean up. */
snprintf(buf, sizeof(buf),
"compare '%s' '%s' 'comp_file%s'",
comp1, comp2, ext);
if (system(buf))
{
fprintf(stderr, "Failed image comparing '%s' and '%s'\n", comp1, comp2);
}
}
}
if (rec_file) write_events(rec_file, list);
end:
ecore_shutdown();

View File

@ -9,6 +9,8 @@
#include "exactness_config.h"
#include "exactness_private.h"
#include "tsuite_file_data.h"
#define CONFIG "ELM_SCALE=1 ELM_FINGER_SIZE=10"
typedef enum
@ -38,6 +40,8 @@ _run_command_prepare(const List_Entry *ent, Run_Mode mode, char *buf)
eina_strbuf_append(sbuf, "ELM_ENGINE='buffer' ");
eina_strbuf_append_printf(sbuf, "TSUITE_DEST_DIR='%s/%s' ",
exactness_config.dest_dir, CURRENT_SUBDIR);
if (exactness_config.store_objects)
eina_strbuf_append(sbuf, "TSUITE_STORE_OBJECTS=1 ");
break;
}
case RUN_INIT:
@ -45,6 +49,8 @@ _run_command_prepare(const List_Entry *ent, Run_Mode mode, char *buf)
eina_strbuf_append(sbuf, "ELM_ENGINE='buffer' ");
eina_strbuf_append_printf(sbuf, "TSUITE_DEST_DIR='%s/%s' ",
exactness_config.dest_dir, ORIG_SUBDIR);
if (exactness_config.store_objects)
eina_strbuf_append(sbuf, "TSUITE_STORE_OBJECTS=1 ");
break;
}
case RUN_RECORD:
@ -159,24 +165,35 @@ _compare_list_cb(const char *name, const char *path EINA_UNUSED, void *data)
if (_check_prefix(prefix, name))
{
char filename1[EXACTNESS_PATH_MAX], filename2[EXACTNESS_PATH_MAX];
snprintf(filename1, EXACTNESS_PATH_MAX, "%s/%s/%s", exactness_config.dest_dir, CURRENT_SUBDIR, name);
snprintf(filename2, EXACTNESS_PATH_MAX, "%s/%s/%s", exactness_config.dest_dir, ORIG_SUBDIR, name);
snprintf(filename1, EXACTNESS_PATH_MAX, "%s/%s/%s", exactness_config.dest_dir, ORIG_SUBDIR, name);
snprintf(filename2, EXACTNESS_PATH_MAX, "%s/%s/%s", exactness_config.dest_dir, CURRENT_SUBDIR, name);
if (!_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/comp_%s'",
filename1, filename2,
exactness_config.dest_dir,
CURRENT_SUBDIR, name);
if (system(buf))
const char *ext = strrchr(name, '.');
if (!strcmp(ext, ".eet"))
{
fprintf(stderr, "Failed image comparing '%s'\n", name);
if (!objects_files_compare(filename1, filename2, EINA_FALSE))
{
fprintf(stderr, "Failed objects comparing '%s'\n", name);
}
}
else
{
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/comp_%s'",
filename1, filename2,
exactness_config.dest_dir,
CURRENT_SUBDIR, name);
if (system(buf))
{
fprintf(stderr, "Failed image comparing '%s'\n", name);
}
}
}
}

View File

@ -728,6 +728,114 @@ multi_move_desc_make(void)
return _d;
}
Eet_Data_Descriptor *
object_info_desc_make(void)
{
Eet_Data_Descriptor_Class eddc;
static Eet_Data_Descriptor *info_d = NULL;
if (!info_d)
{
EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Object_Info);
info_d = eet_data_descriptor_stream_new(&eddc);
EET_DATA_DESCRIPTOR_ADD_BASIC(info_d, Object_Info, "kl_name", kl_name, EET_T_STRING);
EET_DATA_DESCRIPTOR_ADD_BASIC(info_d, Object_Info, "id", id, EET_T_UINT);
EET_DATA_DESCRIPTOR_ADD_LIST(info_d, Object_Info, "children", children, info_d);
/* Evas stuff */
EET_DATA_DESCRIPTOR_ADD_BASIC(info_d, Object_Info, "x", x, EET_T_INT);
EET_DATA_DESCRIPTOR_ADD_BASIC(info_d, Object_Info, "y", y, EET_T_INT);
EET_DATA_DESCRIPTOR_ADD_BASIC(info_d, Object_Info, "w", w, EET_T_INT);
EET_DATA_DESCRIPTOR_ADD_BASIC(info_d, Object_Info, "h", h, EET_T_INT);
}
return info_d;
}
#define INFO_CHECK(i1, i2, obj_path, var) \
({ \
Eina_Bool _ret = EINA_TRUE; \
if (i1->var != i2->var) \
{ \
if (verbose) fprintf(stderr, "%s value is different for %s: %d-%d\n", #var, obj_path, i1->var, i2->var); \
_ret = EINA_FALSE; \
} \
_ret; \
})
static Eina_Bool
_object_info_compare(Object_Info *info1, Object_Info *info2, Eina_Bool verbose, const char *path)
{
/* The caller has to give 2 infos whose kl_name and id are respectively the same */
Eina_List *itr1, *itr2;
Object_Info *c1, *c2;
int cnt1, cnt2;
Eina_Bool ret = EINA_TRUE;
char fpath[512];
if (!info1 || !info2) return EINA_FALSE;
if (info1->kl_name)
sprintf(fpath, "%s/%s_%d", path, info1->kl_name, info1->id);
else
*fpath = '\0';
ret &= INFO_CHECK(info1, info2, fpath, x);
ret &= INFO_CHECK(info1, info2, fpath, y);
ret &= INFO_CHECK(info1, info2, fpath, w);
ret &= INFO_CHECK(info1, info2, fpath, h);
cnt1 = eina_list_count(info1->children);
cnt2 = eina_list_count(info2->children);
if (cnt1 != cnt2 && verbose)
fprintf(stderr, "Object %s - number of children differs (%d - %d)\n", fpath, cnt1, cnt2);
EINA_LIST_FOREACH(info1->children, itr1, c1)
{
Eina_Bool found = EINA_FALSE;
if (!verbose && !ret) goto end;
EINA_LIST_FOREACH(info2->children, itr2, c2)
{
if (!found && c1->id == c2->id && c1->kl_name == c2->kl_name)
{
found = EINA_TRUE;
ret &= _object_info_compare(c1, c2, verbose, fpath);
}
}
}
end:
return ret;
}
EAPI Eina_Bool
objects_files_compare(const char *filename1, const char *filename2, Eina_Bool verbose)
{
Eina_Bool ret = EINA_FALSE;
Eet_File *f1, *f2;
Eet_Data_Descriptor *desc = NULL;
Object_Info *lst1 = NULL, *lst2 = NULL;
f1 = eet_open(filename1, EET_FILE_MODE_READ);
f2 = eet_open(filename2, EET_FILE_MODE_READ);
desc = object_info_desc_make();
if (!f1 || !f2)
{
if (verbose) fprintf(stderr, "Can't open %s\n", !f1?filename1:filename2);
goto end;
}
lst1 = eet_data_read(f1, desc, "entry");
lst2 = eet_data_read(f2, desc, "entry");
if (!lst1 || !lst2)
{
if (verbose) fprintf(stderr, "Can't decode %s data\n", !lst1?filename1:filename2);
goto end;
}
ret = _object_info_compare(lst1, lst2, verbose, NULL);
end:
if (desc) eet_data_descriptor_free(desc);
if (f1) eet_close(f1);
if (f2) eet_close(f2);
return ret;
}
/* declaring types */
data_desc *_data_descriptors_init(void)
{

View File

@ -245,6 +245,28 @@ data_desc *_data_descriptors_init(void);
void _data_descriptors_shutdown(void);
/* END Event struct descriptors */
/* START Objects */
typedef struct
{
Eo *object;
Eo *parent;
const char *kl_name;
Eina_List *children;
int id;
/* Evas stuff */
int x;
int y;
int w;
int h;
} Object_Info;
Eet_Data_Descriptor *object_info_desc_make(void);
EAPI Eina_Bool objects_files_compare(const char *filename1, const char *filename2, Eina_Bool verbose);
/* END Objects */
Tsuite_Event_Type tsuite_event_mapping_type_get(const char *name);
const char * tsuite_event_mapping_type_str_get(Tsuite_Event_Type t);
const char * _variant_type_get(const void *data, Eina_Bool *unknow);

View File

@ -18,6 +18,7 @@
#define TSUITE_MAX_PATH 1024
#define IMAGE_FILENAME_EXT ".png"
#define OBJECTS_FILENAME_EXT ".eet"
struct _evas_hook_setting
{
@ -25,6 +26,7 @@ struct _evas_hook_setting
char *test_name;
char *file_name;
Eina_Bool verbose;
Eina_Bool store_objects;
};
typedef struct _evas_hook_setting evas_hook_setting;
@ -34,6 +36,15 @@ static Tsuite_Data ts;
static Eina_List *evas_list = NULL; /* List of Evas pointers */
static int ignore_evas_new = 0; /* Counter to know if we should ignore evas new or not. */
typedef struct
{
const char *kl_name;
int last;
} Main_Widget_Id;
static Eina_List *_main_widget_ids = NULL;
static Object_Info _widgets_list = {0};
static void _objects_snapshot_do();
static void
_tsuite_verbosef(const char *fmt, ...)
{
@ -85,7 +96,6 @@ _shot_do(char *name, Evas *e)
filename = malloc(strlen(_hook_setting->test_name) + strlen(IMAGE_FILENAME_EXT) +
dir_name_len + 8); /* also space for serial */
ts.serial++;
if (_hook_setting->dest_dir)
sprintf(filename, "%s/", _hook_setting->dest_dir);
@ -142,6 +152,7 @@ ecore_init(void)
_hook_setting->dest_dir = getenv("TSUITE_DEST_DIR");
_hook_setting->test_name = getenv("TSUITE_TEST_NAME");
_hook_setting->file_name = getenv("TSUITE_FILE_NAME");
_hook_setting->store_objects = !!getenv("TSUITE_STORE_OBJECTS");
tmp = getenv("TSUITE_VERBOSE");
if (tmp)
_hook_setting->verbose = atoi(tmp);
@ -459,10 +470,12 @@ tsuite_feed_event(void *data)
printf("%s take shot timestamp=<%u> t->n_evas=<%d>\n", __func__, t->timestamp, t->n_evas);
#endif
if (rect) evas_object_color_set(rect, 0, 0, 255, 255);
ts.serial++;
if (_hook_setting->dest_dir)
{
_shot_do(NULL,
eina_list_nth(evas_list, t->n_evas)); /* Serial name based on test-name */
if (_hook_setting->store_objects) _objects_snapshot_do();
}
break;
}
@ -547,3 +560,149 @@ ecore_main_loop_begin(void)
return _ecore_main_loop_begin();
}
static void
_obj_del(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *parent = efl_parent_get(event->object);
Object_Info *info = efl_key_data_get(event->object, "exactness_info");
if (parent)
{
Object_Info *parent_info = parent ? efl_key_data_get(parent, "exactness_info") : NULL;
if (parent_info) parent_info->children = eina_list_remove(parent_info->children, info);
}
else
{
_widgets_list.children = eina_list_remove(_widgets_list.children, info);
}
efl_key_data_set(event->object, "exactness_info", NULL);
eina_stringshare_del(info->kl_name);
free(info);
}
EAPI Eo *
_efl_add_internal_start(const char *file, int line, const Efl_Class *klass_id, Eo *parent_id, Eina_Bool ref, Eina_Bool is_fallback)
{
Eo *(*foo)(const char *, int, const Efl_Class *, Eo *, Eina_Bool, Eina_Bool) =
dlsym(RTLD_NEXT, __FUNCTION__);
Eo *ret = foo(file, line, klass_id, parent_id, ref, is_fallback);
if (!efl_isa(ret, EFL_CANVAS_INTERFACE) && !efl_isa(ret, EFL_CANVAS_OBJECT_CLASS)) goto end;
efl_event_callback_add(ret, EFL_EVENT_DEL, _obj_del, NULL);
end:
return ret;
}
EOAPI void
efl_parent_set(Eo *obj, Efl_Object *new_parent)
{
void (*foo)(Eo *, Efl_Object *) = dlsym(RTLD_NEXT, __FUNCTION__);
Object_Info *info = NULL;
if (!efl_isa(obj, EFL_CANVAS_INTERFACE) && !efl_isa(obj, EFL_CANVAS_OBJECT_CLASS)) goto end;
info = efl_key_data_get(obj, "exactness_info");
if (!info)
{
info = calloc(1, sizeof(*info));
info->object = obj;
info->kl_name = eina_stringshare_add(efl_class_name_get(obj));
efl_key_data_set(obj, "exactness_info", info);
}
Eo *old_parent = efl_parent_get(obj);
if (info->id && old_parent == new_parent) goto end;
if (old_parent)
{
Object_Info *old_parent_info = efl_key_data_get(old_parent, "exactness_info");
if (old_parent_info)
old_parent_info->children = eina_list_remove(old_parent_info->children, info);
int last_parent_id = (intptr_t)efl_key_data_get(old_parent, info->kl_name);
if (info->id && last_parent_id == info->id)
efl_key_data_set(old_parent, info->kl_name, (void *)(intptr_t)(last_parent_id - 1));
}
else
{
Eina_List *itr;
Main_Widget_Id *wid;
EINA_LIST_FOREACH(_main_widget_ids, itr, wid)
{
if (info->kl_name == wid->kl_name) goto found_old_parent;
}
wid = NULL;
found_old_parent:
if (wid && wid->last == info->id) wid->last--;
_widgets_list.children = eina_list_remove(_widgets_list.children, info);
}
info->id = 0;
info->parent = new_parent;
if (new_parent)
{
int last_parent_id = (intptr_t)efl_key_data_get(new_parent, info->kl_name);
info->id = ++last_parent_id;
efl_key_data_set(new_parent, info->kl_name, (void *)(intptr_t)last_parent_id);
Object_Info *new_parent_info = efl_key_data_get(new_parent, "exactness_info");
if (new_parent_info)
new_parent_info->children = eina_list_append(new_parent_info->children, info);
}
else
{
Eina_List *itr;
Main_Widget_Id *wid;
EINA_LIST_FOREACH(_main_widget_ids, itr, wid)
{
if (info->kl_name == wid->kl_name) goto found_new_parent;
}
wid = calloc(1, sizeof(*wid));
wid->kl_name = info->kl_name;
_main_widget_ids = eina_list_append(_main_widget_ids, wid);
found_new_parent:
info->id = ++wid->last;
_widgets_list.children = eina_list_append(_widgets_list.children, info);
}
end:
foo(obj, new_parent);
}
static void
_info_fill(Object_Info *info)
{
Eina_List *itr;
if (efl_isa(info->object, EFL_CANVAS_OBJECT_CLASS))
evas_object_geometry_get(info->object, &info->x, &info->y, &info->w, &info->h);
EINA_LIST_FOREACH(info->children, itr, info)
{
_info_fill(info);
}
}
static void
_objects_snapshot_do()
{
Eina_List *itr;
Object_Info *info;
int dir_name_len = _hook_setting->dest_dir ? strlen(_hook_setting->dest_dir) + 1 : 0; /* includes space of a '/' */
char *filename = malloc(strlen(_hook_setting->test_name) + strlen(OBJECTS_FILENAME_EXT) +
dir_name_len + 8); /* also space for serial */
if (_hook_setting->dest_dir)
sprintf(filename, "%s/", _hook_setting->dest_dir);
sprintf(filename + dir_name_len, "%s%c%03d%s", _hook_setting->test_name,
SHOT_DELIMITER, ts.serial, OBJECTS_FILENAME_EXT);
printf("%d objects saved into %s\n", eina_list_count(_widgets_list.children), filename);
EINA_LIST_FOREACH(_widgets_list.children, itr, info)
{
_info_fill(info);
}
Eet_File *file = eet_open(filename, EET_FILE_MODE_WRITE);
eet_data_write(file, object_info_desc_make(), "entry", &_widgets_list, EINA_TRUE);
eet_close(file);
free(filename);
}