forked from enlightenment/efl
563 lines
13 KiB
C
563 lines
13 KiB
C
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <Eina.h>
|
|
|
|
#include "eina_suite.h"
|
|
|
|
struct ctx {
|
|
int a, b;
|
|
};
|
|
|
|
#define VAL_A 1234
|
|
#define VAL_B 4567
|
|
#define RETVAL (void*)8901
|
|
#define CANCELVAL (void*)0xCA
|
|
#define COUNT 100
|
|
|
|
#ifdef EINA_SAFETY_CHECKS
|
|
struct log_ctx {
|
|
const char *msg;
|
|
const char *fnc;
|
|
int level;
|
|
Eina_Bool did;
|
|
Eina_Bool just_fmt;
|
|
};
|
|
|
|
/* tests should not output on success, just uncomment this for debugging */
|
|
//#define SHOW_LOG 1
|
|
|
|
static void
|
|
_eina_test_safety_print_cb(const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line, const char *fmt, void *data, va_list args EINA_UNUSED)
|
|
{
|
|
struct log_ctx *ctx = data;
|
|
va_list cp_args;
|
|
const char *str;
|
|
|
|
va_copy(cp_args, args);
|
|
str = va_arg(cp_args, const char *);
|
|
va_end(cp_args);
|
|
|
|
ck_assert_int_eq(level, ctx->level);
|
|
if (ctx->just_fmt)
|
|
ck_assert_str_eq(fmt, ctx->msg);
|
|
else
|
|
{
|
|
ck_assert_str_eq(fmt, "%s");
|
|
ck_assert_str_eq(ctx->msg, str);
|
|
}
|
|
ck_assert_str_eq(ctx->fnc, fnc);
|
|
ctx->did = EINA_TRUE;
|
|
|
|
#ifdef SHOW_LOG
|
|
eina_log_print_cb_stderr(d, level, file, fnc, line, fmt, NULL, args);
|
|
#else
|
|
(void)d;
|
|
(void)file;
|
|
(void)line;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
static const void *
|
|
coro_func_noyield(void *data, Eina_Bool canceled, Eina_Coro *coro EINA_UNUSED)
|
|
{
|
|
struct ctx *ctx = data;
|
|
|
|
ck_assert_ptr_nonnull(ctx);
|
|
ck_assert_int_eq(ctx->a, VAL_A);
|
|
|
|
if (canceled) return CANCELVAL;
|
|
|
|
ctx->b = VAL_B;
|
|
|
|
return RETVAL;
|
|
}
|
|
|
|
START_TEST(coro_noyield)
|
|
{
|
|
Eina_Coro *coro;
|
|
struct ctx ctx = {
|
|
.a = VAL_A,
|
|
.b = 0,
|
|
};
|
|
void *result = NULL;
|
|
int i = 0;
|
|
|
|
eina_init();
|
|
|
|
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_nonnull(coro);
|
|
|
|
while (eina_coro_run(&coro, &result, NULL))
|
|
{
|
|
i++;
|
|
ck_assert_int_le(i, 1);
|
|
}
|
|
ck_assert_ptr_null(coro);
|
|
|
|
ck_assert_int_eq(ctx.b, VAL_B);
|
|
ck_assert_ptr_eq(result, RETVAL);
|
|
|
|
eina_shutdown();
|
|
}
|
|
END_TEST
|
|
|
|
static const void *
|
|
coro_func_yield(void *data, Eina_Bool canceled, Eina_Coro *coro)
|
|
{
|
|
struct ctx *ctx = data;
|
|
char buf[256] = "hi there";
|
|
int i;
|
|
|
|
ck_assert_ptr_nonnull(ctx);
|
|
ck_assert_int_eq(ctx->a, VAL_A);
|
|
|
|
if (canceled) return CANCELVAL;
|
|
|
|
ctx->b = 1;
|
|
eina_coro_yield_or_return(coro, CANCELVAL);
|
|
|
|
ctx->b = 2;
|
|
eina_coro_yield_or_return(coro, CANCELVAL);
|
|
|
|
for (i = 0; i < COUNT; i++) {
|
|
ctx->b = i * 10;
|
|
/* have some stuff on stack and write to it, so we validate
|
|
* non-thread based solutions are really saving their stack.
|
|
*/
|
|
snprintf(buf, sizeof(buf), "b=%d -----------------", ctx->b);
|
|
eina_coro_yield_or_return(coro, CANCELVAL);
|
|
}
|
|
|
|
ctx->b = VAL_B;
|
|
|
|
return RETVAL;
|
|
}
|
|
|
|
START_TEST(coro_yield)
|
|
{
|
|
Eina_Coro *coro;
|
|
struct ctx ctx = {
|
|
.a = VAL_A,
|
|
.b = 0,
|
|
};
|
|
Eina_Bool r;
|
|
void *result = NULL;
|
|
int i = 0;
|
|
|
|
eina_init();
|
|
|
|
coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_nonnull(coro);
|
|
|
|
r = eina_coro_run(&coro, NULL, NULL);
|
|
ck_assert_int_eq(r, EINA_TRUE);
|
|
ck_assert_int_eq(ctx.b, 1);
|
|
|
|
r = eina_coro_run(&coro, NULL, NULL);
|
|
ck_assert_int_eq(r, EINA_TRUE);
|
|
ck_assert_int_eq(ctx.b, 2);
|
|
|
|
while (eina_coro_run(&coro, &result, NULL))
|
|
{
|
|
ck_assert_int_eq(ctx.b, i * 10);
|
|
i++;
|
|
ck_assert_int_le(i, COUNT);
|
|
/* change caller's stack to guarantee coroutine stack is
|
|
* being properly persisted.
|
|
*/
|
|
memset(alloca(10), 0xff, 10);
|
|
}
|
|
ck_assert_ptr_null(coro);
|
|
ck_assert_int_eq(i, COUNT);
|
|
|
|
ck_assert_int_eq(ctx.b, VAL_B);
|
|
ck_assert_ptr_eq(result, RETVAL);
|
|
|
|
eina_shutdown();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(coro_cancel)
|
|
{
|
|
Eina_Coro *coro;
|
|
struct ctx ctx = {
|
|
.a = VAL_A,
|
|
.b = 0,
|
|
};
|
|
Eina_Bool r;
|
|
|
|
eina_init();
|
|
|
|
// cancel before it runs
|
|
coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_nonnull(coro);
|
|
|
|
ck_assert_ptr_eq(eina_coro_cancel(&coro), CANCELVAL);
|
|
ck_assert_ptr_null(coro);
|
|
|
|
// cancel after single run
|
|
coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_nonnull(coro);
|
|
|
|
r = eina_coro_run(&coro, NULL, NULL);
|
|
ck_assert_int_eq(r, EINA_TRUE);
|
|
ck_assert_int_eq(ctx.b, 1);
|
|
|
|
ck_assert_ptr_eq(eina_coro_cancel(&coro), CANCELVAL);
|
|
ck_assert_ptr_null(coro);
|
|
ck_assert_int_eq(ctx.b, 1); // it's yielding after setting b=1
|
|
|
|
eina_shutdown();
|
|
}
|
|
END_TEST
|
|
|
|
static Eina_Bool
|
|
_coro_hook_enter_success(void *data, const Eina_Coro *coro EINA_UNUSED)
|
|
{
|
|
int *i = data;
|
|
(*i) += 1;
|
|
return EINA_TRUE;
|
|
}
|
|
|
|
static void
|
|
_coro_hook_exit(void *data, const Eina_Coro *coro EINA_UNUSED)
|
|
{
|
|
int *i = data;
|
|
(*i) += 100;
|
|
}
|
|
|
|
static void
|
|
_main_hook_enter(void *data, const Eina_Coro *coro EINA_UNUSED)
|
|
{
|
|
int *i = data;
|
|
(*i) += 10;
|
|
}
|
|
|
|
static Eina_Bool
|
|
_main_hook_exit_success(void *data, const Eina_Coro *coro EINA_UNUSED)
|
|
{
|
|
int *i = data;
|
|
(*i) += 1000;
|
|
return EINA_TRUE;
|
|
}
|
|
|
|
START_TEST(coro_hook)
|
|
{
|
|
Eina_Coro *coro;
|
|
struct ctx ctx = {
|
|
.a = VAL_A,
|
|
.b = 0,
|
|
};
|
|
void *result = NULL;
|
|
int i = 0, hooks_result = 0;
|
|
|
|
eina_init();
|
|
|
|
fail_unless(eina_coro_hook_add(_coro_hook_enter_success,
|
|
_coro_hook_exit,
|
|
_main_hook_enter,
|
|
_main_hook_exit_success,
|
|
&hooks_result));
|
|
|
|
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_nonnull(coro);
|
|
|
|
while (eina_coro_run(&coro, &result, NULL))
|
|
{
|
|
i++;
|
|
ck_assert_int_le(i, 1);
|
|
}
|
|
ck_assert_ptr_null(coro);
|
|
|
|
ck_assert_int_eq(ctx.b, VAL_B);
|
|
ck_assert_ptr_eq(result, RETVAL);
|
|
|
|
fail_unless(eina_coro_hook_del(_coro_hook_enter_success,
|
|
_coro_hook_exit,
|
|
_main_hook_enter,
|
|
_main_hook_exit_success,
|
|
&hooks_result));
|
|
|
|
ck_assert_int_eq(hooks_result, 1111);
|
|
|
|
eina_shutdown();
|
|
}
|
|
END_TEST
|
|
|
|
#ifdef EINA_SAFETY_CHECKS
|
|
START_TEST(coro_new_null)
|
|
{
|
|
Eina_Coro *coro;
|
|
|
|
eina_init();
|
|
|
|
#ifdef SHOW_LOG
|
|
fprintf(stderr, "you should have a safety check failure below:\n");
|
|
#endif
|
|
struct log_ctx lctx;
|
|
|
|
#define TEST_MAGIC_SAFETY(fn, _msg) \
|
|
lctx.msg = _msg; \
|
|
lctx.fnc = fn; \
|
|
lctx.just_fmt = EINA_FALSE; \
|
|
lctx.level = EINA_LOG_LEVEL_ERR; \
|
|
lctx.did = EINA_FALSE
|
|
|
|
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
|
|
|
|
TEST_MAGIC_SAFETY("eina_coro_new", "safety check failed: func == NULL");
|
|
|
|
coro = eina_coro_new(NULL, NULL, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_null(coro);
|
|
|
|
fail_unless(lctx.did);
|
|
|
|
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
|
|
#undef TEST_MAGIC_SAFETY
|
|
|
|
eina_shutdown();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(coro_yield_incorrect)
|
|
{
|
|
Eina_Coro *coro;
|
|
struct ctx ctx = {
|
|
.a = VAL_A,
|
|
.b = 0,
|
|
};
|
|
void *result = NULL;
|
|
int i = 0;
|
|
|
|
eina_init();
|
|
|
|
#ifdef SHOW_LOG
|
|
fprintf(stderr, "you should have a safety check failure below:\n");
|
|
#endif
|
|
struct log_ctx lctx;
|
|
|
|
#define TEST_MAGIC_SAFETY(fn, _msg) \
|
|
lctx.msg = _msg; \
|
|
lctx.fnc = fn; \
|
|
lctx.just_fmt = EINA_TRUE; \
|
|
lctx.level = EINA_LOG_LEVEL_CRITICAL; \
|
|
lctx.did = EINA_FALSE
|
|
|
|
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
|
|
|
|
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_nonnull(coro);
|
|
|
|
TEST_MAGIC_SAFETY("eina_coro_yield", "must be called from coroutine! coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
|
|
fail_if(eina_coro_yield(coro));
|
|
fail_unless(lctx.did);
|
|
|
|
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
|
|
#undef TEST_MAGIC_SAFETY
|
|
|
|
while (eina_coro_run(&coro, &result, NULL))
|
|
{
|
|
i++;
|
|
ck_assert_int_le(i, 1);
|
|
}
|
|
ck_assert_ptr_null(coro);
|
|
|
|
ck_assert_int_eq(ctx.b, VAL_B);
|
|
ck_assert_ptr_eq(result, RETVAL);
|
|
|
|
eina_shutdown();
|
|
}
|
|
END_TEST
|
|
|
|
static const void *
|
|
coro_func_run_incorrect(void *data, Eina_Bool canceled, Eina_Coro *coro)
|
|
{
|
|
#ifdef SHOW_LOG
|
|
fprintf(stderr, "you should have a safety check failure below:\n");
|
|
#endif
|
|
struct log_ctx lctx;
|
|
|
|
#define TEST_MAGIC_SAFETY(fn, _msg) \
|
|
lctx.msg = _msg; \
|
|
lctx.fnc = fn; \
|
|
lctx.just_fmt = EINA_TRUE; \
|
|
lctx.level = EINA_LOG_LEVEL_CRITICAL; \
|
|
lctx.did = EINA_FALSE
|
|
|
|
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
|
|
|
|
TEST_MAGIC_SAFETY("eina_coro_run", "must be called from main thread! coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
|
|
fail_if(eina_coro_run(&coro, NULL, NULL));
|
|
fail_unless(lctx.did);
|
|
|
|
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
|
|
#undef TEST_MAGIC_SAFETY
|
|
|
|
return coro_func_noyield(data, canceled, coro);
|
|
}
|
|
|
|
START_TEST(coro_run_incorrect)
|
|
{
|
|
Eina_Coro *coro;
|
|
struct ctx ctx = {
|
|
.a = VAL_A,
|
|
.b = 0,
|
|
};
|
|
void *result = NULL;
|
|
int i = 0;
|
|
|
|
eina_init();
|
|
|
|
coro = eina_coro_new(coro_func_run_incorrect, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_nonnull(coro);
|
|
|
|
while (eina_coro_run(&coro, &result, NULL))
|
|
{
|
|
i++;
|
|
ck_assert_int_le(i, 1);
|
|
}
|
|
ck_assert_ptr_null(coro);
|
|
|
|
ck_assert_int_eq(ctx.b, VAL_B);
|
|
ck_assert_ptr_eq(result, RETVAL);
|
|
|
|
eina_shutdown();
|
|
}
|
|
END_TEST
|
|
|
|
static Eina_Bool
|
|
_coro_hook_enter_failed(void *data, const Eina_Coro *coro EINA_UNUSED)
|
|
{
|
|
int *i = data;
|
|
(*i) += 2;
|
|
return EINA_FALSE;
|
|
}
|
|
|
|
static Eina_Bool
|
|
_main_hook_exit_failed(void *data, const Eina_Coro *coro EINA_UNUSED)
|
|
{
|
|
int *i = data;
|
|
(*i) += 2000;
|
|
return EINA_FALSE;
|
|
}
|
|
|
|
START_TEST(coro_hook_failed)
|
|
{
|
|
Eina_Coro *coro;
|
|
struct ctx ctx = {
|
|
.a = VAL_A,
|
|
.b = 0,
|
|
};
|
|
void *result = NULL;
|
|
int i = 0, hooks_result = 0;
|
|
|
|
eina_init();
|
|
|
|
#ifdef SHOW_LOG
|
|
fprintf(stderr, "you should have a safety check failure below:\n");
|
|
#endif
|
|
struct log_ctx lctx;
|
|
|
|
#define TEST_MAGIC_SAFETY(fn, _msg) \
|
|
lctx.msg = _msg; \
|
|
lctx.fnc = fn; \
|
|
lctx.just_fmt = EINA_TRUE; \
|
|
lctx.level = EINA_LOG_LEVEL_ERR; \
|
|
lctx.did = EINA_FALSE
|
|
|
|
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
|
|
|
|
fail_unless(eina_coro_hook_add(_coro_hook_enter_failed,
|
|
_coro_hook_exit,
|
|
_main_hook_enter,
|
|
_main_hook_exit_success,
|
|
&hooks_result));
|
|
|
|
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_nonnull(coro);
|
|
|
|
TEST_MAGIC_SAFETY("_eina_coro_hooks_coro_enter", "failed hook enter=%p data=%p for coroutine coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
|
|
|
|
while (eina_coro_run(&coro, &result, NULL))
|
|
{
|
|
i++;
|
|
ck_assert_int_le(i, 1);
|
|
}
|
|
ck_assert_ptr_null(coro);
|
|
|
|
ck_assert_int_eq(ctx.b, 0);
|
|
ck_assert_ptr_eq(result, CANCELVAL);
|
|
|
|
fail_unless(lctx.did);
|
|
|
|
fail_unless(eina_coro_hook_del(_coro_hook_enter_failed,
|
|
_coro_hook_exit,
|
|
_main_hook_enter,
|
|
_main_hook_exit_success,
|
|
&hooks_result));
|
|
|
|
ck_assert_int_eq(hooks_result, 1112);
|
|
|
|
// now fail main exit
|
|
hooks_result = 0;
|
|
|
|
fail_unless(eina_coro_hook_add(_coro_hook_enter_success,
|
|
_coro_hook_exit,
|
|
_main_hook_enter,
|
|
_main_hook_exit_failed,
|
|
&hooks_result));
|
|
|
|
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
|
ck_assert_ptr_nonnull(coro);
|
|
|
|
TEST_MAGIC_SAFETY("_eina_coro_hooks_main_exit", "failed hook exit=%p data=%p for main routine coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
|
|
|
|
while (eina_coro_run(&coro, &result, NULL))
|
|
{
|
|
i++;
|
|
ck_assert_int_le(i, 1);
|
|
}
|
|
ck_assert_ptr_null(coro);
|
|
|
|
ck_assert_int_eq(ctx.b, 0);
|
|
ck_assert_ptr_eq(result, CANCELVAL);
|
|
|
|
fail_unless(lctx.did);
|
|
|
|
fail_unless(eina_coro_hook_del(_coro_hook_enter_success,
|
|
_coro_hook_exit,
|
|
_main_hook_enter,
|
|
_main_hook_exit_failed,
|
|
&hooks_result));
|
|
|
|
ck_assert_int_eq(hooks_result, 2111);
|
|
|
|
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
|
|
#undef TEST_MAGIC_SAFETY
|
|
|
|
eina_shutdown();
|
|
}
|
|
END_TEST
|
|
#endif
|
|
|
|
void
|
|
eina_test_coro(TCase *tc)
|
|
{
|
|
tcase_add_test(tc, coro_noyield);
|
|
tcase_add_test(tc, coro_yield);
|
|
// coro_await is tested in ecore_suite, so it hooks into the main loop
|
|
tcase_add_test(tc, coro_cancel);
|
|
tcase_add_test(tc, coro_hook);
|
|
|
|
#ifdef EINA_SAFETY_CHECKS
|
|
tcase_add_test(tc, coro_new_null);
|
|
tcase_add_test(tc, coro_yield_incorrect);
|
|
tcase_add_test(tc, coro_run_incorrect);
|
|
tcase_add_test(tc, coro_hook_failed);
|
|
#endif
|
|
}
|