From 26dfa76faa883c35fe3e5ff4b2b33f4102059a81 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov <0x7fffff@guattari.ru> Date: Fri, 5 Aug 2022 21:17:16 +0400 Subject: Async function & unit-testing --- source/CMakeLists.txt | 6 + source/kit/CMakeLists.txt | 6 + source/kit/async_function.c | 1 + source/kit/async_function.h | 234 +++++++++++++++++++++ source/kit_test/CMakeLists.txt | 6 + source/kit_test/run_tests.c | 112 ++++++++++ source/kit_test/test.h | 96 +++++++++ source/test/CMakeLists.txt | 1 + source/test/integration/fetch_content/.gitignore | 1 + .../test/integration/fetch_content/CMakeLists.txt | 19 ++ source/test/integration/fetch_content/main.c | 5 + source/test/integration/find_package/.gitignore | 2 + .../test/integration/find_package/CMakeLists.txt | 34 +++ .../integration/find_package/source/CMakeLists.txt | 10 + source/test/integration/find_package/source/main.c | 5 + source/test/unittests/CMakeLists.txt | 4 + source/test/unittests/async_function.test.c | 200 ++++++++++++++++++ source/test/unittests/main.test.c | 5 + 18 files changed, 747 insertions(+) create mode 100644 source/CMakeLists.txt create mode 100644 source/kit/CMakeLists.txt create mode 100644 source/kit/async_function.c create mode 100644 source/kit/async_function.h create mode 100644 source/kit_test/CMakeLists.txt create mode 100644 source/kit_test/run_tests.c create mode 100644 source/kit_test/test.h create mode 100644 source/test/CMakeLists.txt create mode 100644 source/test/integration/fetch_content/.gitignore create mode 100644 source/test/integration/fetch_content/CMakeLists.txt create mode 100644 source/test/integration/fetch_content/main.c create mode 100644 source/test/integration/find_package/.gitignore create mode 100644 source/test/integration/find_package/CMakeLists.txt create mode 100644 source/test/integration/find_package/source/CMakeLists.txt create mode 100644 source/test/integration/find_package/source/main.c create mode 100644 source/test/unittests/CMakeLists.txt create mode 100644 source/test/unittests/async_function.test.c create mode 100644 source/test/unittests/main.test.c (limited to 'source') diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt new file mode 100644 index 0000000..1444b6a --- /dev/null +++ b/source/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(kit) +add_subdirectory(kit_test) + +if(KIT_ENABLE_TESTING) + add_subdirectory(test) +endif() diff --git a/source/kit/CMakeLists.txt b/source/kit/CMakeLists.txt new file mode 100644 index 0000000..24d1aed --- /dev/null +++ b/source/kit/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources( + ${KIT_LIBRARY} + PRIVATE + async_function.c + PUBLIC + $) diff --git a/source/kit/async_function.c b/source/kit/async_function.c new file mode 100644 index 0000000..a62114d --- /dev/null +++ b/source/kit/async_function.c @@ -0,0 +1 @@ +#include "async_function.h" diff --git a/source/kit/async_function.h b/source/kit/async_function.h new file mode 100644 index 0000000..77a88ef --- /dev/null +++ b/source/kit/async_function.h @@ -0,0 +1,234 @@ +#ifndef AF_AF_H +#define AF_AF_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum af_request { + af_request_resume, + af_request_join, + af_request_resume_and_join, + af_request_execute +}; + +typedef struct { + int _; +} af_void; + +typedef void (*af_state_machine)(void *self_void_, int request_); +typedef void (*af_execute)(void *state, void *coro_state, + int request); + +typedef struct { + long long _internal; + void *state; + af_execute execute; +} af_execution_context; + +typedef struct { + int _index; + af_state_machine _state_machine; + af_execution_context _context; +} af_type_void; + +#ifndef AF_DISABLE_SELF_SHORTCUT +# define af self-> +#endif + +#define AF_INTERNAL(coro_) (*((af_type_void *) (coro_))) + +#define AF_STATE(ret_type_, name_, ...) \ + struct name_##_coro_state_ { \ + int _index; \ + af_state_machine _state_machine; \ + af_execution_context _context; \ + ret_type_ return_value; \ + __VA_ARGS__ \ + } + +#define AF_DECL(name_) \ + void name_##_coro_(void *self_void_, int request_) + +#define CORO_IMPL(name_) \ + AF_DECL(name_) { \ + struct name_##_coro_state_ *self = \ + (struct name_##_coro_state_ *) self_void_; \ + if (request_ != af_request_execute) { \ + if (self->_context.execute != NULL) \ + self->_context.execute(self->_context.state, self_void_, \ + request_); \ + else if (request_ == af_request_join || \ + request_ == af_request_resume_and_join) \ + self->_state_machine(self_void_, af_request_execute); \ + return; \ + } \ + switch (self->_index) { \ + case 0:; + +#define AF_LINE() __LINE__ + +#define CORO_END \ + } \ + self->_index = -1; \ + } + +#define CORO_DECL(ret_type_, name_, ...) \ + AF_STATE(ret_type_, name_, __VA_ARGS__); \ + AF_DECL(name_) + +#define CORO(ret_type_, name_, ...) \ + AF_STATE(ret_type_, name_, __VA_ARGS__); \ + CORO_IMPL(name_) + +#define CORO_DECL_VOID(name_, ...) \ + CORO_DECL(af_void, name_, __VA_ARGS__) + +#define CORO_VOID(name_, ...) CORO(af_void, name_, __VA_ARGS__) + +#define AF_YIELD(...) \ + { \ + self->_index = AF_LINE(); \ + self->return_value = __VA_ARGS__; \ + return; \ + case AF_LINE():; \ + } + +#define AF_YIELD_VOID \ + { \ + self->_index = AF_LINE(); \ + return; \ + case AF_LINE():; \ + } + +#define AF_RETURN(...) \ + { \ + self->_index = -1; \ + self->return_value = __VA_ARGS__; \ + return; \ + } + +#define AF_RETURN_VOID \ + { \ + self->_index = -1; \ + return; \ + } + +#define AF_AWAIT(promise_) \ + { \ + case AF_LINE(): \ + if ((promise_)._index != -1) { \ + self->_index = AF_LINE(); \ + (promise_)._state_machine(&(promise_), \ + af_request_resume_and_join); \ + } \ + if ((promise_)._index != -1) \ + return; \ + } + +#define AF_YIELD_AWAIT(promise_) \ + { \ + case AF_LINE(): \ + if ((promise_)._index != -1) { \ + self->_index = AF_LINE(); \ + (promise_)._state_machine(&(promise_), \ + af_request_resume_and_join); \ + self->return_value = (promise_).return_value; \ + return; \ + } \ + } + +#define AF_TYPE(coro_) struct coro_##_coro_state_ + +#define AF_INITIAL(coro_) \ + ._index = 0, ._state_machine = coro_##_coro_, \ + ._context = { .state = NULL, .execute = NULL } + +#define AF_CREATE(promise_, coro_, ...) \ + AF_TYPE(coro_) \ + promise_ = { AF_INITIAL(coro_), __VA_ARGS__ } + +#define AF_INIT(promise_, coro_, ...) \ + { \ + AF_CREATE(af_temp_, coro_, __VA_ARGS__); \ + (promise_) = af_temp_; \ + } + +#define AF_EXECUTION_CONTEXT(promise_, ...) \ + { \ + af_execution_context af_temp_ = { ._internal = 0, __VA_ARGS__ }; \ + (promise_)._context = af_temp_; \ + } + +#define AF_RESUME(promise_) \ + (promise_)._state_machine(&(promise_), af_request_resume) + +#define AF_RESUME_N(promises_, size_) \ + for (int af_index_ = 0; af_index_ < (size_); af_index_++) \ + AF_RESUME((promises_)[af_index_]) + +#define AF_JOIN(promise_) \ + ((promise_)._state_machine(&(promise_), af_request_join), \ + (promise_).return_value) + +#define AF_JOIN_N(promises_, size_) \ + for (int af_index_ = 0; af_index_ < (size_); af_index_++) \ + AF_JOIN((promises_)[af_index_]) + +#define AF_RESUME_AND_JOIN(promise_) \ + ((promise_)._state_machine(&(promise_), \ + af_request_resume_and_join), \ + (promise_).return_value) + +#define AF_RESUME_AND_JOIN_N(promises_, size_) \ + AF_RESUME_N((promises_), (size_)); \ + AF_JOIN_N((promises_), (size_)) + +#define AF_RESUME_ALL(promises_) \ + AF_RESUME_N((promises_), sizeof(promises_) / sizeof((promises_)[0])) + +#define AF_JOIN_ALL(promises_) \ + AF_JOIN_N((promises_), sizeof(promises_) / sizeof((promises_)[0])) + +#define AF_RESUME_AND_JOIN_ALL(promises_) \ + AF_RESUME_AND_JOIN_N((promises_), \ + sizeof(promises_) / sizeof((promises_)[0])) + +#define AF_FINISHED(promise_) ((promise_)._index == -1) + +#define AF_FINISHED_N(return_, promises_, size_) \ + { \ + (return_) = true; \ + for (int af_index_ = 0; af_index_ < (size_); af_index_++) \ + if (!AF_FINISHED((promises_)[af_index_])) { \ + (return_) = false; \ + break; \ + } \ + } + +#define AF_FINISHED_ALL(return_, promises_, size_) \ + AF_FINISHED_N((return_), (promises_), \ + sizeof(promises_) / sizeof((promises_)[0])) + +#define AF_AWAIT_N(promises_, size_) \ + { \ + case AF_LINE(): \ + self->_index = AF_LINE(); \ + AF_RESUME_AND_JOIN_N((promises_), (size_)); \ + bool af_done_; \ + AF_FINISHED_N(af_done_, (promises_), (size_)); \ + if (!af_done_) \ + return; \ + } + +#define AF_AWAIT_ALL(promises_) \ + AF_AWAIT_N((promises_), sizeof(promises_) / sizeof((promises_)[0])) + +#define AF_EXECUTE(coro_state_) \ + AF_INTERNAL(coro_state_)._state_machine(coro, af_request_execute) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/kit_test/CMakeLists.txt b/source/kit_test/CMakeLists.txt new file mode 100644 index 0000000..f7315a1 --- /dev/null +++ b/source/kit_test/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources( + ${KIT_TEST_LIBRARY} + PRIVATE + run_tests.c + PUBLIC + $) diff --git a/source/kit_test/run_tests.c b/source/kit_test/run_tests.c new file mode 100644 index 0000000..191982a --- /dev/null +++ b/source/kit_test/run_tests.c @@ -0,0 +1,112 @@ +#include "test.h" + +#include +#include + +struct kit_tests_list kit_tests_list = { 0 }; + +static void report(int i, char const *file, int line, bool ok) { + int const n = kit_tests_list.tests[i].assertions++; + + kit_tests_list.tests[i].file[n] = file; + kit_tests_list.tests[i].line[n] = line; + kit_tests_list.tests[i].status[n] = ok; +} + +static long long ns_to_ms(long long ns) { + return (ns + 500000) / 1000000; +} + +static long long sec_to_ms(long long sec) { + return 1000 * sec; +} + +enum code_value { white, yellow, red, green }; + +static void color_code(bool term_color, int c) { + if (term_color) { + if (c == white) + printf("\x1b[37m"); + if (c == yellow) + printf("\x1b[33m"); + if (c == red) + printf("\x1b[31m"); + if (c == green) + printf("\x1b[32m"); + } +} + +int kit_run_tests(int argc, char **argv) { + int fail_test_count = 0; + int fail_assertion_count = 0; + int total_assertion_count = 0; + int status = 0; + bool term_color = true; + + for (int i = 0; i < argc; i++) + if (strcmp("--no-term-color", argv[i]) == 0) + term_color = false; + + for (int i = 0; i < kit_tests_list.size; i++) { + color_code(term_color, yellow); + printf("[ RUN... ] %s ", kit_tests_list.tests[i].test_name); + color_code(term_color, white); + + struct timespec begin, end; + timespec_get(&begin, TIME_UTC); + + kit_tests_list.tests[i].test_fn(i, report); + + timespec_get(&end, TIME_UTC); + int duration = (int) (ns_to_ms(end.tv_nsec - begin.tv_nsec) + + sec_to_ms(end.tv_sec - begin.tv_sec)); + + printf("\r"); + + bool test_status = true; + + for (int j = 0; j < kit_tests_list.tests[i].assertions; j++) + if (kit_tests_list.tests[i].status[j] == false) { + fail_assertion_count++; + test_status = false; + } + + total_assertion_count += kit_tests_list.tests[i].assertions; + + if (test_status == false) { + color_code(term_color, red); + printf("[ RUN ] %s\n", kit_tests_list.tests[i].test_name); + printf("[ FAILED ] %s - %d ms\n", + kit_tests_list.tests[i].test_name, duration); + color_code(term_color, white); + fail_test_count++; + status = 1; + } else { + color_code(term_color, green); + printf("[ RUN ] %s\n", kit_tests_list.tests[i].test_name); + printf("[ OK ] %s - %d ms\n", + kit_tests_list.tests[i].test_name, duration); + color_code(term_color, white); + } + } + + printf("\n%d of %d tests passed.\n", + kit_tests_list.size - fail_test_count, kit_tests_list.size); + + printf("%d of %d assertions passed.\n\n", + total_assertion_count - fail_assertion_count, + total_assertion_count); + + if (status != 0) { + for (int i = 0; i < kit_tests_list.size; i++) + for (int j = 0; j < kit_tests_list.tests[i].assertions; j++) + if (!kit_tests_list.tests[i].status[j]) + printf("Assertion on line %d in \"%s\" failed\n", + kit_tests_list.tests[i].line[j], + kit_tests_list.tests[i].file[j]); + + printf("\n"); + } + + return status; +} diff --git a/source/kit_test/test.h b/source/kit_test/test.h new file mode 100644 index 0000000..da6c59b --- /dev/null +++ b/source/kit_test/test.h @@ -0,0 +1,96 @@ +#ifndef KIT_TEST_TEST_H +#define KIT_TEST_TEST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#ifndef KIT_TEST_FILE +# define kit_test +#endif + +#ifndef KIT_TESTS_SIZE_LIMIT +# define KIT_TESTS_SIZE_LIMIT 0x1000 +#endif + +#ifndef KIT_TEST_ASSERTIONS_LIMIT +# define KIT_TEST_ASSERTIONS_LIMIT 0x50 +#endif + +#ifndef KIT_TEST_STRING_SIZE +# define KIT_TEST_STRING_SIZE 0x100 +#endif + +typedef void (*kit_test_report)(int, char const *file, int line, + bool); +typedef void (*kit_test_function)(int, kit_test_report); + +struct kit_test_case { + char test_name[KIT_TEST_STRING_SIZE]; + kit_test_function test_fn; + int assertions; + char const *file[KIT_TEST_ASSERTIONS_LIMIT]; + int line[KIT_TEST_ASSERTIONS_LIMIT]; + bool status[KIT_TEST_ASSERTIONS_LIMIT]; +}; + +struct kit_tests_list { + int size; + struct kit_test_case tests[KIT_TESTS_SIZE_LIMIT]; +}; + +extern struct kit_tests_list kit_tests_list; + +#ifdef _MSC_VER +# pragma section(".CRT$XCU", read) +# define KIT_TEST_ON_START_2(f, p) \ + static void f(void); \ + __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \ + __pragma(comment(linker, "/include:" p #f "_")) static void f( \ + void) +# ifdef _WIN64 +# define KIT_TEST_ON_START(f) KIT_TEST_ON_START_2(f, "") +# else +# define KIT_TEST_ON_START(f) KIT_TEST_ON_START_2(f, "_") +# endif +#else +# define KIT_TEST_ON_START(f) \ + static void f(void) __attribute__((constructor)); \ + static void f(void) +#endif + +#define KIT_TEST_CONCAT4(a, b, c, d) a##b##c##d +#define KIT_TEST_CONCAT3(a, b, c) KIT_TEST_CONCAT4(a, b, _, c) + +#define TEST(name) \ + static void KIT_TEST_CONCAT3(kit_test_run_, __LINE__, \ + KIT_TEST_FILE)(int, kit_test_report); \ + KIT_TEST_ON_START( \ + KIT_TEST_CONCAT3(kit_test_case_, __LINE__, KIT_TEST_FILE)) { \ + int n = kit_tests_list.size; \ + if (n < KIT_TESTS_SIZE_LIMIT) { \ + kit_tests_list.size++; \ + kit_tests_list.tests[n].test_fn = KIT_TEST_CONCAT3( \ + kit_test_run_, __LINE__, KIT_TEST_FILE); \ + strcpy(kit_tests_list.tests[n].test_name, name); \ + kit_tests_list.tests[n].assertions = 0; \ + } \ + } \ + static void KIT_TEST_CONCAT3(kit_test_run_, __LINE__, \ + KIT_TEST_FILE)( \ + int kit_test_index_, kit_test_report kit_test_report_) + +#define REQUIRE(ok) \ + kit_test_report_(kit_test_index_, __FILE__, __LINE__, (ok)) + +int kit_run_tests(int argc, char **argv); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/test/CMakeLists.txt b/source/test/CMakeLists.txt new file mode 100644 index 0000000..740981e --- /dev/null +++ b/source/test/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(unittests) diff --git a/source/test/integration/fetch_content/.gitignore b/source/test/integration/fetch_content/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/source/test/integration/fetch_content/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/source/test/integration/fetch_content/CMakeLists.txt b/source/test/integration/fetch_content/CMakeLists.txt new file mode 100644 index 0000000..efa83d0 --- /dev/null +++ b/source/test/integration/fetch_content/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.16) +set(NAME kit-integration-fetch-content) +project(${NAME} C) + +include(FetchContent) +FetchContent_Declare( + kit + GIT_REPOSITORY https://github.com/automainint/kit.git + GIT_TAG dev) +set(KIT_ENABLE_TESTING OFF) +FetchContent_MakeAvailable(kit) + +add_executable(${NAME}) +target_sources(${NAME} PRIVATE main.c) +target_link_libraries(${NAME} PRIVATE kit::kit) + +enable_testing() +add_test(NAME ${NAME}-run COMMAND ${NAME}) +set_tests_properties(${NAME}-run PROPERTIES TIMEOUT "15") diff --git a/source/test/integration/fetch_content/main.c b/source/test/integration/fetch_content/main.c new file mode 100644 index 0000000..c9dc882 --- /dev/null +++ b/source/test/integration/fetch_content/main.c @@ -0,0 +1,5 @@ +#include + +int main() { + return 0; +} diff --git a/source/test/integration/find_package/.gitignore b/source/test/integration/find_package/.gitignore new file mode 100644 index 0000000..0ba45dd --- /dev/null +++ b/source/test/integration/find_package/.gitignore @@ -0,0 +1,2 @@ +/build/ +/install/ diff --git a/source/test/integration/find_package/CMakeLists.txt b/source/test/integration/find_package/CMakeLists.txt new file mode 100644 index 0000000..f21cdae --- /dev/null +++ b/source/test/integration/find_package/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.16) +set(NAME kit-integration-find-package) +project(${NAME}-root) + +include(ExternalProject) + +ExternalProject_Add( + kit + GIT_REPOSITORY https://github.com/automainint/kit.git + GIT_TAG dev + CMAKE_ARGS + -D CMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/install + -D KIT_ENABLE_TESTING=OFF) + +ExternalProject_Add( + ${NAME} + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/source + CMAKE_ARGS + -D CMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/install) + +ExternalProject_Add_StepDependencies(${NAME} build kit) + +include(GNUInstallDirs) + +enable_testing() + +add_test( + NAME ${NAME}-run + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/install/${CMAKE_INSTALL_BINDIR}/${NAME}${CMAKE_EXECUTABLE_SUFFIX}) + +set_tests_properties( + ${NAME}-run + PROPERTIES + TIMEOUT "15") diff --git a/source/test/integration/find_package/source/CMakeLists.txt b/source/test/integration/find_package/source/CMakeLists.txt new file mode 100644 index 0000000..2dc34a1 --- /dev/null +++ b/source/test/integration/find_package/source/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.16) +set(NAME kit-integration-find-package) +project(${NAME} C) + +find_package(kit REQUIRED) + +add_executable(${NAME}) +target_sources(${NAME} PRIVATE main.c) +target_link_libraries(${NAME} PRIVATE kit::kit) +install(TARGETS ${NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/source/test/integration/find_package/source/main.c b/source/test/integration/find_package/source/main.c new file mode 100644 index 0000000..c9dc882 --- /dev/null +++ b/source/test/integration/find_package/source/main.c @@ -0,0 +1,5 @@ +#include + +int main() { + return 0; +} diff --git a/source/test/unittests/CMakeLists.txt b/source/test/unittests/CMakeLists.txt new file mode 100644 index 0000000..d5cc573 --- /dev/null +++ b/source/test/unittests/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources( + ${KIT_TEST_SUITE} + PRIVATE + async_function.test.c main.test.c) diff --git a/source/test/unittests/async_function.test.c b/source/test/unittests/async_function.test.c new file mode 100644 index 0000000..1f50445 --- /dev/null +++ b/source/test/unittests/async_function.test.c @@ -0,0 +1,200 @@ +#include "../../kit/async_function.h" + +#define KIT_TEST_FILE async_function_test +#include "../../kit_test/test.h" + +CORO(int, test_foo) { + AF_RETURN(42); +} +CORO_END + +CORO(int, test_bar) { + AF_YIELD_VOID; + AF_RETURN(42); +} +CORO_END + +CORO(int, test_gen, int i; int min; int max;) { + for (af i = af min; af i < af max; af i++) AF_YIELD(af i); + AF_RETURN(af max); +} +CORO_END + +CORO_VOID(test_task) { + AF_YIELD_VOID; + AF_YIELD_VOID; + AF_RETURN_VOID; +} +CORO_END + +CORO_VOID(test_nest_task, AF_TYPE(test_task) promise;) { + AF_INIT(af promise, test_task); + AF_AWAIT(af promise); + AF_AWAIT(af promise); + AF_AWAIT(af promise); +} +CORO_END + +CORO(int, test_nest_generator, AF_TYPE(test_gen) promise;) { + AF_INIT(af promise, test_gen, .min = 1, .max = 3); + AF_YIELD_AWAIT(af promise); +} +CORO_END + +CORO(int, test_join_multiple, AF_TYPE(test_bar) promises[3];) { + for (int i = 0; i < 3; i++) + AF_INIT(af promises[i], test_bar, .return_value = 0); + AF_RESUME_AND_JOIN_ALL(af promises); + AF_RETURN(af promises[0].return_value + + af promises[1].return_value + + af promises[2].return_value); +} +CORO_END + +CORO(int, test_await_multiple, AF_TYPE(test_bar) promises[3];) { + for (int i = 0; i < 3; i++) + AF_INIT(af promises[i], test_bar, .return_value = 0); + AF_AWAIT_ALL(af promises); + AF_RETURN(af promises[0].return_value + + af promises[1].return_value + + af promises[2].return_value); +} +CORO_END + +void test_execute_lazy(void *_, void *coro, int request) { + if (request == af_request_resume) + return; + AF_EXECUTE(coro); +} + +void test_execute_immediate(void *_, void *coro, int request) { + if (request == af_request_join) + return; + AF_EXECUTE(coro); +} + +TEST("coroutine create") { + AF_CREATE(promise, test_foo); + REQUIRE(!AF_FINISHED(promise)); +} + +TEST("coroutine init") { + AF_TYPE(test_foo) promise; + AF_INIT(promise, test_foo); + REQUIRE(!AF_FINISHED(promise)); +} + +TEST("coroutine init with value") { + AF_TYPE(test_foo) promise; + AF_INIT(promise, test_foo, .return_value = 42); + REQUIRE(promise.return_value == 42); + REQUIRE(!AF_FINISHED(promise)); +} + +TEST("coroutine resume") { + AF_CREATE(promise, test_foo, .return_value = -1); + AF_RESUME(promise); + REQUIRE(promise.return_value == -1); + REQUIRE(!AF_FINISHED(promise)); +} + +TEST("coroutine resume and join") { + AF_CREATE(promise, test_foo); + REQUIRE(AF_RESUME_AND_JOIN(promise) == 42); + REQUIRE(AF_FINISHED(promise)); +} + +TEST("coroutine resume and join manually") { + AF_CREATE(promise, test_foo); + AF_RESUME(promise); + REQUIRE(AF_JOIN(promise) == 42); + REQUIRE(AF_FINISHED(promise)); +} + +TEST("coroutine suspend") { + AF_CREATE(promise, test_bar, .return_value = 0); + REQUIRE(AF_RESUME_AND_JOIN(promise) == 0); + REQUIRE(AF_RESUME_AND_JOIN(promise) == 42); +} + +TEST("coroutine generator") { + AF_CREATE(promise, test_gen, .min = 10, .max = 15); + for (int i = 0; i <= 5; i++) + REQUIRE(AF_RESUME_AND_JOIN(promise) == 10 + i); +} + +TEST("coroutine status finished") { + AF_CREATE(promise, test_bar); + REQUIRE(!AF_FINISHED(promise)); + AF_RESUME_AND_JOIN(promise); + REQUIRE(!AF_FINISHED(promise)); + AF_RESUME_AND_JOIN(promise); + REQUIRE(AF_FINISHED(promise)); +} + +TEST("coroutine task") { + AF_CREATE(promise, test_task); + AF_RESUME_AND_JOIN(promise); + REQUIRE(!AF_FINISHED(promise)); + AF_RESUME_AND_JOIN(promise); + REQUIRE(!AF_FINISHED(promise)); + AF_RESUME_AND_JOIN(promise); + REQUIRE(AF_FINISHED(promise)); +} + +TEST("coroutine nested task") { + AF_CREATE(promise, test_nest_task); + AF_RESUME_AND_JOIN(promise); + REQUIRE(!AF_FINISHED(promise)); + AF_RESUME_AND_JOIN(promise); + REQUIRE(!AF_FINISHED(promise)); + AF_RESUME_AND_JOIN(promise); + REQUIRE(AF_FINISHED(promise)); +} + +TEST("coroutine nested generator") { + AF_CREATE(promise, test_nest_generator); + REQUIRE(AF_RESUME_AND_JOIN(promise) == 1); + REQUIRE(AF_RESUME_AND_JOIN(promise) == 2); + REQUIRE(AF_RESUME_AND_JOIN(promise) == 3); + REQUIRE(!AF_FINISHED(promise)); + AF_RESUME_AND_JOIN(promise); + REQUIRE(AF_FINISHED(promise)); +} + +TEST("coroutine join multiple") { + AF_CREATE(promise, test_join_multiple); + REQUIRE(AF_RESUME_AND_JOIN(promise) == 0); + REQUIRE(AF_FINISHED(promise)); +} + +TEST("coroutine await multiple") { + AF_CREATE(promise, test_await_multiple); + REQUIRE(AF_RESUME_AND_JOIN(promise) == 0); + REQUIRE(AF_RESUME_AND_JOIN(promise) == 42 * 3); + REQUIRE(AF_FINISHED(promise)); +} + +TEST("coroutine custom execution context lazy") { + AF_CREATE(promise, test_foo, .return_value = 0); + AF_EXECUTION_CONTEXT(promise, .state = NULL, + .execute = test_execute_lazy); + AF_RESUME(promise); + REQUIRE(promise.return_value == 0); + REQUIRE(!AF_FINISHED(promise)); + AF_JOIN(promise); + REQUIRE(promise.return_value == 42); + REQUIRE(AF_FINISHED(promise)); +} + +TEST("coroutine custom execution context immediate") { + AF_CREATE(promise, test_foo, .return_value = 0); + AF_EXECUTION_CONTEXT(promise, .state = NULL, + .execute = test_execute_immediate); + AF_RESUME(promise); + REQUIRE(promise.return_value == 42); + REQUIRE(AF_FINISHED(promise)); + AF_JOIN(promise); + REQUIRE(promise.return_value == 42); + REQUIRE(AF_FINISHED(promise)); +} diff --git a/source/test/unittests/main.test.c b/source/test/unittests/main.test.c new file mode 100644 index 0000000..b072c67 --- /dev/null +++ b/source/test/unittests/main.test.c @@ -0,0 +1,5 @@ +#include "../../kit_test/test.h" + +int main(int argc, char **argv) { + return kit_run_tests(argc, argv); +} -- cgit v1.2.3