summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitya Selivanov <0x7fffff@guattari.ru>2022-08-05 21:17:16 +0400
committerMitya Selivanov <0x7fffff@guattari.ru>2022-08-05 21:17:16 +0400
commit26dfa76faa883c35fe3e5ff4b2b33f4102059a81 (patch)
tree27b71a44b38fb4b64e991146e235ae327c86ef5d
downloadkit-26dfa76faa883c35fe3e5ff4b2b33f4102059a81.zip
Async function & unit-testing
-rw-r--r--.clang-format65
-rw-r--r--.gitattributes1
-rw-r--r--.github/workflows/build_and_test.yml120
-rw-r--r--.github/workflows/cleanup.yml21
-rw-r--r--.gitignore8
-rw-r--r--CMakeLists.txt129
-rw-r--r--CODE_OF_CONDUCT.md8
-rw-r--r--CONTRIBUTING.md8
-rw-r--r--LICENSE19
-rw-r--r--README.md3
-rw-r--r--codecov.yml6
-rw-r--r--config.cmake.in3
-rwxr-xr-xgen_cmake.py89
-rw-r--r--source/CMakeLists.txt6
-rw-r--r--source/kit/CMakeLists.txt6
-rw-r--r--source/kit/async_function.c1
-rw-r--r--source/kit/async_function.h234
-rw-r--r--source/kit_test/CMakeLists.txt6
-rw-r--r--source/kit_test/run_tests.c112
-rw-r--r--source/kit_test/test.h96
-rw-r--r--source/test/CMakeLists.txt1
-rw-r--r--source/test/integration/fetch_content/.gitignore1
-rw-r--r--source/test/integration/fetch_content/CMakeLists.txt19
-rw-r--r--source/test/integration/fetch_content/main.c5
-rw-r--r--source/test/integration/find_package/.gitignore2
-rw-r--r--source/test/integration/find_package/CMakeLists.txt34
-rw-r--r--source/test/integration/find_package/source/CMakeLists.txt10
-rw-r--r--source/test/integration/find_package/source/main.c5
-rw-r--r--source/test/unittests/CMakeLists.txt4
-rw-r--r--source/test/unittests/async_function.test.c200
-rw-r--r--source/test/unittests/main.test.c5
31 files changed, 1227 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..ed344fc
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,65 @@
+BasedOnStyle: LLVM
+UseTab: Never
+TabWidth: 2
+IndentWidth: 2
+
+Language: Cpp
+Standard: Auto
+
+ColumnLimit: 70
+MaxEmptyLinesToKeep: 1
+SpacesBeforeTrailingComments: 1
+AccessModifierOffset: -2
+PenaltyBreakAssignment: 100
+PenaltyReturnTypeOnItsOwnLine: 200
+
+IndentCaseLabels: true
+IndentExternBlock: NoIndent
+IndentGotoLabels: false
+IndentPPDirectives: AfterHash
+IndentRequires: true
+IndentWrappedFunctionNames: false
+NamespaceIndentation: All
+
+SpaceAfterCStyleCast: true
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: true
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyBlock: true
+SpaceInEmptyParentheses: false
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInConditionalStatement: false
+SpacesInSquareBrackets: false
+
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: Consecutive
+AlignConsecutiveBitFields: Consecutive
+AlignConsecutiveDeclarations: Consecutive
+AlignEscapedNewlines: Left
+AllowShortLambdasOnASingleLine: All
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortBlocksOnASingleLine: Always
+AllowShortCaseLabelsOnASingleLine: true
+AllowShortLoopsOnASingleLine: true
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: true
+BinPackParameters: true
+BreakBeforeConceptDeclarations: true
+BreakConstructorInitializers: AfterColon
+Cpp11BracedListStyle: false
+EmptyLineBeforeAccessModifier: LogicalBlock
+FixNamespaceComments: false
+PointerAlignment: Right
+ReflowComments: true
+SortIncludes: CaseInsensitive
+SortUsingDeclarations: true
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..176a458
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
new file mode 100644
index 0000000..50aad28
--- /dev/null
+++ b/.github/workflows/build_and_test.yml
@@ -0,0 +1,120 @@
+name: Build and test
+
+on:
+ push:
+ branches: [ dev, stable ]
+ pull_request:
+ branches: [ dev, stable ]
+ schedule:
+ - cron: '0 0 */16 * *'
+ workflow_dispatch:
+
+env:
+ BUILD_TYPE: Debug
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest, windows-2019 ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up CMake 3.18
+ uses: jwlawson/actions-setup-cmake@v1.8
+ with:
+ cmake-version: 3.18
+
+ - name: Install gcovr
+ if: runner.os != 'Windows'
+ run: |
+ python3 -m pip install gcovr==5.0
+
+ - name: Install OpenCppCoverage
+ if: runner.os == 'Windows'
+ shell: bash
+ run: |
+ choco install opencppcoverage
+ echo "C:/Program Files/OpenCppCoverage" >> $GITHUB_PATH
+
+ - name: Build
+ if: runner.os != 'Windows'
+ run: |
+ cmake -D CMAKE_BUILD_TYPE=$BUILD_TYPE -B build -S .
+ cmake --build build --config $BUILD_TYPE
+
+ - name: Build with Visual Studio
+ if: runner.os == 'Windows'
+ shell: bash
+ run: |
+ cmake -B build -S .
+ cmake --build build --config $BUILD_TYPE
+
+ - name: Run tests & generate coverage on Linux
+ if: runner.os == 'Linux'
+ working-directory: ${{ github.workspace }}/build
+ run: |
+ ctest -V -C $BUILD_TYPE
+ gcovr --gcov-executable gcov-11 -j $(nproc) --delete --root ../source/ --exclude '\.\./source/test/' --print-summary --xml-pretty --xml coverage.xml .
+
+ - name: Run tests & generate coverage on macOS
+ if: runner.os == 'macOS'
+ working-directory: ${{ github.workspace }}/build
+ run: |
+ ctest -V -C $BUILD_TYPE
+ gcovr --gcov-executable gcov-11 -j 2 --delete --root ../source/ --exclude '\.\./source/test/' --print-summary --xml-pretty --xml coverage.xml .
+
+ - name: Run tests & generate coverage on Windows
+ if: runner.os == 'Windows'
+ shell: bash
+ working-directory: ${{ github.workspace }}/build
+ run: |
+ OpenCppCoverage.exe --sources source\\* --excluded_sources source\\test\\* --export_type cobertura:coverage.xml --cover_children -- ctest -V -C $BUILD_TYPE
+
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v2
+ with:
+ files: ./build/coverage.xml
+ flags: ${{ runner.os }}
+ name: ${{ runner.os }} build
+ fail_ci_if_error: false
+
+ integration:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest, windows-2019 ]
+ name: [ fetch_content, find_package ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up CMake 3.18
+ uses: jwlawson/actions-setup-cmake@v1.8
+ with:
+ cmake-version: 3.18
+
+ - name: Build with GCC
+ if: runner.os != 'Windows'
+ run: |
+ cmake -D CMAKE_BUILD_TYPE=$BUILD_TYPE -B build -S .
+ cmake --build build --config $BUILD_TYPE
+ working-directory: ./source/test/integration/${{ matrix.name }}
+
+ - name: Build with Visual Studio
+ if: runner.os == 'Windows'
+ shell: bash
+ run: |
+ cmake -B build -S .
+ cmake --build build --config $BUILD_TYPE
+ working-directory: ./source/test/integration/${{ matrix.name }}
+
+ - name: Run
+ shell: bash
+ run: |
+ ctest -V -C $BUILD_TYPE
+ working-directory: ./source/test/integration/${{ matrix.name }}/build
diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml
new file mode 100644
index 0000000..74e8d4a
--- /dev/null
+++ b/.github/workflows/cleanup.yml
@@ -0,0 +1,21 @@
+name: Cleanup
+
+on:
+ schedule:
+ - cron: '0 0 */16 * *'
+ workflow_dispatch:
+
+jobs:
+ delete_runs:
+ permissions:
+ actions: write
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Delete workflow runs
+ uses: Mattraks/delete-workflow-runs@main
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ repository: ${{ github.repository }}
+ retain_days: 30
+ keep_minimum_runs: 4
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2f2be3e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+/*build*/
+
+/.vs/
+/.idea/
+*.sln
+*.vcxproj
+*.filters
+*.user
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..79e4216
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,129 @@
+cmake_minimum_required(VERSION 3.16)
+
+option(KIT_ENABLE_LIBRARY "Enable library" ON)
+option(KIT_ENABLE_TESTING "Enable testing" ON)
+
+set(KIT_PROJECT kit)
+set(KIT_LIBRARY kit)
+set(KIT_TEST_LIBRARY kit_test)
+set(KIT_TEST_SUITE kit_test_suite)
+set(KIT_TARGETS kit-targets)
+set(KIT_UNITTESTS kit-unittests)
+
+if(NOT DEFINED CMAKE_BUILD_PARALLEL_LEVEL)
+ set(CMAKE_BUILD_PARALLEL_LEVEL 4)
+endif()
+
+project(
+ ${KIT_PROJECT}
+ VERSION 0.1.1
+ DESCRIPTION "Kit"
+ LANGUAGES C)
+
+if(KIT_ENABLE_LIBRARY OR KIT_ENABLE_TESTING)
+ add_library(${KIT_LIBRARY} STATIC)
+ add_library(${KIT_PROJECT}::${KIT_LIBRARY} ALIAS ${KIT_LIBRARY})
+ target_include_directories(
+ ${KIT_LIBRARY} INTERFACE
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/source>)
+ target_compile_features(${KIT_LIBRARY} PUBLIC c_std_11)
+
+ add_library(${KIT_TEST_LIBRARY} STATIC)
+ add_library(${KIT_PROJECT}::${KIT_TEST_LIBRARY} ALIAS ${KIT_TEST_LIBRARY})
+ target_include_directories(
+ ${KIT_TEST_LIBRARY} INTERFACE
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/source>)
+ target_compile_features(${KIT_TEST_LIBRARY} PUBLIC c_std_11)
+endif()
+
+enable_testing()
+
+if(KIT_ENABLE_TESTING)
+ add_executable(${KIT_TEST_SUITE})
+ add_executable(${KIT_PROJECT}::${KIT_TEST_SUITE} ALIAS ${KIT_TEST_SUITE})
+ target_compile_features(${KIT_TEST_SUITE} PRIVATE c_std_11)
+ target_link_libraries(${KIT_TEST_SUITE} PRIVATE ${KIT_LIBRARY} ${KIT_TEST_LIBRARY})
+
+ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
+ target_compile_options(
+ ${KIT_LIBRARY} PUBLIC
+ -fsanitize=undefined,address
+ --coverage -O0 -g)
+ target_link_options(
+ ${KIT_LIBRARY} PUBLIC
+ -fsanitize=undefined,address
+ --coverage)
+ endif()
+
+ add_test(
+ NAME ${KIT_UNITTESTS}
+ COMMAND ${KIT_TEST_SUITE})
+
+ set_tests_properties(
+ ${KIT_UNITTESTS}
+ PROPERTIES
+ TIMEOUT "30")
+endif()
+
+add_subdirectory(source)
+
+if(KIT_ENABLE_LIBRARY)
+ include(GNUInstallDirs)
+
+ file(
+ GLOB_RECURSE headers_
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/source/kit
+ ${CMAKE_CURRENT_SOURCE_DIR}/source/kit/*.h)
+
+ foreach(path_ ${headers_})
+ get_filename_component(dir_ "${path_}" DIRECTORY)
+
+ install(
+ FILES ${CMAKE_CURRENT_SOURCE_DIR}/source/kit/${path_}
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/kit/${dir_})
+ endforeach()
+
+ unset(headers_)
+ unset(path_)
+ unset(dir_)
+
+ install(
+ TARGETS ${KIT_LIBRARY} ${KIT_TEST_LIBRARY}
+ EXPORT ${KIT_TARGETS}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+
+ install(
+ EXPORT ${KIT_TARGETS}
+ FILE ${KIT_TARGETS}.cmake
+ NAMESPACE ${PROJECT_NAME}::
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
+
+ export(
+ EXPORT ${KIT_TARGETS}
+ FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/${KIT_TARGETS}.cmake"
+ NAMESPACE ${PROJECT_NAME}::)
+
+ include(CMakePackageConfigHelpers)
+
+ string(TOLOWER ${PROJECT_NAME} project_lower_)
+
+ configure_package_config_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/config.cmake.in
+ "${CMAKE_CURRENT_BINARY_DIR}/${project_lower_}-config.cmake"
+ INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
+
+ write_basic_package_version_file(
+ "${CMAKE_CURRENT_BINARY_DIR}/${project_lower_}-config-version.cmake"
+ VERSION "${PROJECT_VERSION}"
+ COMPATIBILITY AnyNewerVersion)
+
+ install(
+ FILES
+ "${CMAKE_CURRENT_BINARY_DIR}/${project_lower_}-config.cmake"
+ "${CMAKE_CURRENT_BINARY_DIR}/${project_lower_}-config-version.cmake"
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
+
+ unset(project_lower_)
+endif()
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..deacc03
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,8 @@
+## Minimalist Code of Conduct
+We believe that an elaborate code of conduct invites a lot of fighting over intricate details, much like law, and we do not have the resources to build up the equivalent of a legal system. Therefore, we prefer keeping our rules as short as possible and filling the gaps with the mortar of human interaction: empathy.
+
+All we ask of members of this project is this:
+1. Please treat each other with respect and understanding.
+2. Please respect our wish to not serve as a stage for disputes about fairness or personal differences.
+
+If you can agree to these conditions, your contributions are welcome. If you can not, please don’t spoil it for the rest of us.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..a3c6315
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,8 @@
+## Contributing
+To contribute, follow these steps:
+1. Find an applicable issue or open a new one with feature proposal or bug spotting.
+2. Fork the repository.
+3. Write tests for the issue.
+4. Implement your solution for the issue.
+5. Make sure all tests pass and cover your code.
+6. Make a pull-request to the `dev` branch or to the corresponding feature branch if there is any.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..07dde7d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2022 Mitya Selivanov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c71b735
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# kit
+- Async function
+- Unit-testing
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..8478b39
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,6 @@
+coverage:
+ status:
+ project:
+ default:
+ threshold: 2%
+ patch: off
diff --git a/config.cmake.in b/config.cmake.in
new file mode 100644
index 0000000..cc1cb07
--- /dev/null
+++ b/config.cmake.in
@@ -0,0 +1,3 @@
+@PACKAGE_INIT@
+
+include("${CMAKE_CURRENT_LIST_DIR}/@AF_TARGETS@.cmake")
diff --git a/gen_cmake.py b/gen_cmake.py
new file mode 100755
index 0000000..3d1a520
--- /dev/null
+++ b/gen_cmake.py
@@ -0,0 +1,89 @@
+#!/usr/bin/python3
+
+import os, glob
+
+def get_subdirs(folder: str):
+ dirs = list()
+ for f in glob.glob(os.path.join(folder, '*', '')):
+ dirs.append(os.path.basename(os.path.normpath(f)))
+ return dirs
+
+def get_files(folder: str, ext: str):
+ files = list()
+ for f in glob.glob(os.path.join(folder, ext)):
+ files.append(os.path.basename(f))
+ return files
+
+def check_subdirs(folder):
+ for r, d, f in os.walk(folder):
+ for file in f:
+ return True
+ return False
+
+def print_list(s: list, offset: int):
+ buf = ''
+ char_count = offset
+ for i in range(char_count - 1):
+ buf += ' '
+ for f in s:
+ char_count += len(f) + 1
+ if char_count >= 64:
+ char_count = 0
+ if char_count == 0:
+ buf += '\n'
+ for i in range(offset - 1):
+ buf += ' '
+ char_count = offset - 1
+ buf += ' ' + f
+ return buf
+
+def print_sources(folder: str, target_name: str):
+ buf = ''
+ srcs = get_files(folder, '*.c')
+ hdrs = get_files(folder, '*.h')
+ if len(srcs) > 0 or len(hdrs) > 0:
+ buf += 'target_sources(\n ' + target_name
+ if len(srcs) > 0:
+ buf += '\n PRIVATE\n' + print_list(srcs, 6)
+ if len(hdrs) > 0:
+ buf += '\n PUBLIC'
+ for f in hdrs:
+ buf += '\n $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/' + f + '>'
+ buf += ')\n'
+ return buf
+
+def print_subdirs(folder: str):
+ buf = ''
+ dirs = get_subdirs(folder)
+ for f in dirs:
+ if check_subdirs(os.path.join(folder, f)):
+ buf += 'add_subdirectory(' + f + ')\n'
+ return buf
+
+def write_subdirs(folder: str, target_name: str):
+ if check_subdirs(folder):
+ out = open(os.path.join(folder, 'CMakeLists.txt'), 'w')
+ out.write(print_sources(folder, target_name))
+ out.write(print_subdirs(folder))
+ out.close()
+
+ for dir in get_subdirs(folder):
+ write_subdirs(os.path.join(folder, dir), target_name)
+
+def clean_subdirs(folder: str):
+ for r, d, f in os.walk(folder):
+ for file in f:
+ if file == 'CMakeLists.txt':
+ os.remove(os.path.join(r, file))
+
+def gen_cmake(folder: str, target_name: str):
+ clean_subdirs(folder)
+ write_subdirs(folder, target_name)
+
+def main():
+ gen_cmake(os.path.join('source', 'kit'), '${KIT_LIBRARY}')
+ gen_cmake(os.path.join('source', 'kit_test'), '${KIT_TEST_LIBRARY}')
+ gen_cmake(os.path.join('source', 'test', 'unittests'), '${KIT_TEST_SUITE}')
+
+if __name__ == '__main__':
+ main()
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
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/async_function.h>)
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
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/test.h>)
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 <stdio.h>
+#include <time.h>
+
+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 <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+#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 <kit/async_function.h>
+
+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 <kit/async_function.h>
+
+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);
+}