rizin/test/unit/test_task.c
Riccardo Schirone 53bf5c497f SPDX Copyright text for all files based on history
- Add LICENSES directory
- Download additional licenses used
- Add .reuse directory
- Add doc about SPDX/reuse

Co-authored-by: Florian Märkl <info@florianmaerkl.de>
2021-03-05 19:39:15 +08:00

247 lines
No EOL
6.7 KiB
C

// SPDX-FileCopyrightText: 2020 Florian Märkl <info@florianmaerkl.de>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_core.h>
#include "minunit.h"
typedef struct shared_ctx_t {
RzStrBuf log;
RzThreadLock *free_lock;
} SharedCtx;
typedef struct local_ctx_t {
SharedCtx *shared;
int id;
int freed;
unsigned int want_to_consume;
bool breaked;
} LocalCtx;
static void ctx_switch(RzCoreTask *next, void *user) {
SharedCtx *ctx = user;
LocalCtx *local_ctx = next->runner_user;
if (local_ctx) {
rz_strbuf_appendf(&ctx->log, "Context switch to %d\n", local_ctx->id);
} else {
rz_strbuf_appendf(&ctx->log, "Context switch to main\n");
}
}
static void task_break(RzCoreTask *task, void *user) {
LocalCtx *ctx = task->runner_user;
ctx->breaked = true;
}
static void runner(RzCoreTaskScheduler *sched, void *user) {
LocalCtx *ctx = user;
rz_strbuf_appendf(&ctx->shared->log, "Runner %d start\n", ctx->id);
while (ctx->want_to_consume) {
if (ctx->breaked) {
rz_strbuf_appendf(&ctx->shared->log, "Runner %d was breaked!\n", ctx->id);
break;
}
rz_strbuf_appendf(&ctx->shared->log, "Runner %d consume %u\n", ctx->id, ctx->want_to_consume);
ctx->want_to_consume--;
rz_core_task_yield(sched);
}
rz_strbuf_appendf(&ctx->shared->log, "Runner %d done\n", ctx->id);
}
static void local_ctx_free(void *user) {
LocalCtx *ctx = user;
SharedCtx *shared_ctx = ctx->shared;
rz_th_lock_enter(shared_ctx->free_lock);
ctx->freed++;
rz_th_lock_leave(shared_ctx->free_lock);
}
// Busy-wait for task threads to be started and in the queue
static void wait_for_tasks_enqueued(RzCoreTaskScheduler *sched, size_t count) {
while (true) {
rz_th_lock_enter(sched->lock);
size_t queued = rz_list_length(sched->tasks_queue);
rz_th_lock_leave(sched->lock);
if (queued == count) {
break;
}
rz_sys_usleep(1000);
}
}
static bool test_task(void) {
SharedCtx shared_ctx = { 0 };
rz_strbuf_init(&shared_ctx.log);
shared_ctx.free_lock = rz_th_lock_new(false);
RzCoreTaskScheduler sched;
rz_core_task_scheduler_init(&sched, ctx_switch, &shared_ctx, task_break, &shared_ctx);
rz_core_task_sync_begin(&sched);
LocalCtx a_ctx = {
.shared = &shared_ctx,
.id = 1,
.want_to_consume = 3
};
RzCoreTask *a = rz_core_task_new(&sched, runner, local_ctx_free, &a_ctx);
int a_id = a->id;
rz_core_task_enqueue(&sched, a);
a = NULL; // ownership moved to the scheduler, don't touch anymore!
wait_for_tasks_enqueued(&sched, 1);
LocalCtx b_ctx = {
.shared = &shared_ctx,
.id = 2,
.want_to_consume = 3
};
RzCoreTask *b = rz_core_task_new(&sched, runner, local_ctx_free, &b_ctx);
b->transient = true;
int b_id = b->id;
rz_core_task_enqueue(&sched, b);
b = NULL; // ownership moved to the scheduler, don't touch anymore!
wait_for_tasks_enqueued(&sched, 2);
mu_assert_streq(rz_strbuf_get(&shared_ctx.log), "Context switch to main\n", "log");
// The scheduler uses very primitive round-robin scheduling, which is why it is completely deterministic
// after the queue has been filled and we can test it well.
// In case the scheduling scheme changes in the future, these tests are allowed to be changed for it.
rz_core_task_yield(&sched);
mu_assert_streq(rz_strbuf_get(&shared_ctx.log),
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 start\n"
"Runner 1 consume 3\n"
"Context switch to 2\n"
"Runner 2 start\n"
"Runner 2 consume 3\n"
"Context switch to main\n",
"log");
rz_core_task_break(&sched, b_id);
rz_th_lock_enter(shared_ctx.free_lock);
mu_assert_eq(a_ctx.freed, 0, "freed");
mu_assert_eq(b_ctx.freed, 0, "freed");
rz_th_lock_leave(shared_ctx.free_lock);
rz_core_task_yield(&sched);
mu_assert_streq(rz_strbuf_get(&shared_ctx.log),
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 start\n"
"Runner 1 consume 3\n"
"Context switch to 2\n"
"Runner 2 start\n"
"Runner 2 consume 3\n"
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 consume 2\n"
"Context switch to 2\n"
"Runner 2 was breaked!\n"
"Runner 2 done\n"
"Context switch to main\n",
"log");
rz_th_lock_enter(shared_ctx.free_lock);
mu_assert_eq(a_ctx.freed, 0, "freed");
mu_assert_eq(b_ctx.freed, 1, "freed"); // this one is transient and should now have been freed automatically
rz_th_lock_leave(shared_ctx.free_lock);
rz_core_task_yield(&sched);
mu_assert_streq(rz_strbuf_get(&shared_ctx.log),
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 start\n"
"Runner 1 consume 3\n"
"Context switch to 2\n"
"Runner 2 start\n"
"Runner 2 consume 3\n"
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 consume 2\n"
"Context switch to 2\n"
"Runner 2 was breaked!\n"
"Runner 2 done\n"
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 consume 1\n"
"Context switch to main\n",
"log");
rz_core_task_yield(&sched);
mu_assert_streq(rz_strbuf_get(&shared_ctx.log),
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 start\n"
"Runner 1 consume 3\n"
"Context switch to 2\n"
"Runner 2 start\n"
"Runner 2 consume 3\n"
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 consume 2\n"
"Context switch to 2\n"
"Runner 2 was breaked!\n"
"Runner 2 done\n"
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 consume 1\n"
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 done\n"
"Context switch to main\n",
"log");
// everything done now and no new logs should happen
rz_core_task_yield(&sched);
mu_assert_streq(rz_strbuf_get(&shared_ctx.log),
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 start\n"
"Runner 1 consume 3\n"
"Context switch to 2\n"
"Runner 2 start\n"
"Runner 2 consume 3\n"
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 consume 2\n"
"Context switch to 2\n"
"Runner 2 was breaked!\n"
"Runner 2 done\n"
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 consume 1\n"
"Context switch to main\n"
"Context switch to 1\n"
"Runner 1 done\n"
"Context switch to main\n",
"log");
rz_th_lock_enter(shared_ctx.free_lock);
mu_assert_eq(a_ctx.freed, 0, "freed"); // this one isn't transient so need to free it manually
mu_assert_eq(b_ctx.freed, 1, "freed");
rz_th_lock_leave(shared_ctx.free_lock);
rz_core_task_del(&sched, a_id);
rz_th_lock_enter(shared_ctx.free_lock);
mu_assert_eq(a_ctx.freed, 1, "freed");
mu_assert_eq(b_ctx.freed, 1, "freed");
rz_th_lock_leave(shared_ctx.free_lock);
rz_core_task_sync_end(&sched);
rz_core_task_join(&sched, NULL, -1);
rz_core_task_scheduler_fini(&sched);
rz_th_lock_free(shared_ctx.free_lock);
rz_strbuf_fini(&shared_ctx.log);
mu_end;
}
// This test is best served with helgrind
static int all_tests(void) {
mu_run_test(test_task);
return tests_passed != tests_run;
}
mu_main(all_tests)