From 7eb0fc5581a6b6bcc8f318a5d9fbe38d562ed6fc Mon Sep 17 00:00:00 2001 From: Max Wash Date: Sat, 3 Aug 2024 07:54:28 +0100 Subject: [PATCH] initial commit --- .clang-format | 58 ++ .gitignore | 169 ++++++ CMakeLists.txt | 22 + LICENSE | 32 + README | 0 cmake/Templates.cmake | 30 + core-test/core-test.c | 183 ++++++ core/CMakeLists.txt | 3 + core/btree.c | 821 ++++++++++++++++++++++++++ core/include/blue/core.h | 0 core/include/blue/core/btree.h | 357 +++++++++++ core/include/blue/core/exception.h | 0 core/include/blue/core/iterator.h | 25 + core/include/blue/core/misc.h | 13 + core/include/blue/core/queue.h | 88 +++ core/include/blue/core/status.h | 23 + core/include/blue/core/stringstream.h | 30 + core/iterator.c | 37 ++ core/queue.c | 196 ++++++ core/status.c | 15 + core/stringstream.c | 118 ++++ include/blue.h | 0 misc/AllTests.c | 24 + misc/CuTest.c | 345 +++++++++++ misc/CuTest.h | 117 ++++ misc/CuTestTest.c | 712 ++++++++++++++++++++++ 26 files changed, 3418 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README create mode 100644 cmake/Templates.cmake create mode 100644 core-test/core-test.c create mode 100644 core/CMakeLists.txt create mode 100644 core/btree.c create mode 100644 core/include/blue/core.h create mode 100644 core/include/blue/core/btree.h create mode 100644 core/include/blue/core/exception.h create mode 100644 core/include/blue/core/iterator.h create mode 100644 core/include/blue/core/misc.h create mode 100644 core/include/blue/core/queue.h create mode 100644 core/include/blue/core/status.h create mode 100644 core/include/blue/core/stringstream.h create mode 100644 core/iterator.c create mode 100644 core/queue.c create mode 100644 core/status.c create mode 100644 core/stringstream.c create mode 100644 include/blue.h create mode 100644 misc/AllTests.c create mode 100644 misc/CuTest.c create mode 100644 misc/CuTest.h create mode 100644 misc/CuTestTest.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..be15bcc --- /dev/null +++ b/.clang-format @@ -0,0 +1,58 @@ +BasedOnStyle: WebKit +IndentWidth: 8 +--- +Language: Cpp +DerivePointerAlignment: false +PointerAlignment: Right +ColumnLimit: 80 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: AcrossEmptyLinesAndComments +AlignEscapedNewlines: Right +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +ExperimentalAutoDetectBinPacking: false +BitFieldColonSpacing: Both +BreakBeforeBraces: Linux +BreakBeforeBinaryOperators: All +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: true +IncludeBlocks: Regroup +SortIncludes: true +IndentRequires: true +NamespaceIndentation: Inner +ReflowComments: true +SpacesBeforeTrailingComments: 3 +TabWidth: 8 +UseTab: AlignWithSpaces +PenaltyReturnTypeOnItsOwnLine: 1000000 +PenaltyExcessCharacter: 5 +PenaltyBreakOpenParenthesis: 5 +PenaltyBreakBeforeFirstCallParameter: 5 +PenaltyIndentedWhitespace: 0 +AttributeMacros: + - BLUELIB_API +ForEachMacros: + - b_btree_foreach + - b_queue_foreach diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bb9708 --- /dev/null +++ b/.gitignore @@ -0,0 +1,169 @@ +# Created by https://www.toptal.com/developers/gitignore/api/macos,c,vim,linux,windows,git +# Edit at https://www.toptal.com/developers/gitignore?templates=macos,c,vim,linux,windows,git + +### C ### +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/macos,c,vim,linux,windows,git + +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6320208 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.25) +project(bluelib C) + +set(b_modules core) + +set(b_system_name ${CMAKE_SYSTEM_NAME}) +string(TOLOWER ${b_system_name} b_system_name) + +foreach (module ${b_modules}) + add_subdirectory(${module}) + if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${module}-test) + message(STATUS "Building unit tests for module ${module}") + + add_executable(blue-${module}-test + ${module}-test/${module}-test.c + misc/AllTests.c + misc/CuTest.c + misc/CuTest.h) + target_link_libraries(blue-${module}-test blue-${module}) + target_include_directories(blue-${module}-test PRIVATE misc/) + endif () +endforeach (module) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d654e09 --- /dev/null +++ b/LICENSE @@ -0,0 +1,32 @@ +The Clear BSD License + +Copyright (c) 2023 Max Wash +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted (subject to the limitations in the disclaimer +below) provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY +THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/cmake/Templates.cmake b/cmake/Templates.cmake new file mode 100644 index 0000000..9d25c17 --- /dev/null +++ b/cmake/Templates.cmake @@ -0,0 +1,30 @@ +macro(add_bluelib_module module_name) + file(GLOB sources *.c *.h) + file(GLOB sys_sources sys/${b_system_name}/*.c sys/${b_system_name}/*.h) + set(root_header include/blue/${module_name}.h) + file(GLOB headers include/blue/${module_name}/*.h) + + add_library(blue-${module_name}-obj OBJECT ${sources} ${root_header} ${headers}) + + set_target_properties(blue-${module_name}-obj + PROPERTIES POSITION_INDEPENDENT_CODE ON) + + string(REPLACE "-" "_" module_preproc_token ${module_name}) + string(TOUPPER ${module_preproc_token} module_preproc_token) + set(module_preproc_token BLUELIB_${module_preproc_token}) + + target_include_directories(blue-${module_name}-obj PUBLIC include/) + target_compile_definitions(blue-${module_name}-obj PUBLIC ${module_preproc_token}) + + message(STATUS "Building module ${module_name} (shared)") + add_library(blue-${module_name} SHARED $) + message(STATUS "Building module ${module_name} (static)") + add_library(blue-${module_name}-s STATIC $) + + target_include_directories(blue-${module_name} PUBLIC include/) + target_include_directories(blue-${module_name}-s PUBLIC include/) + + install(TARGETS blue-${module_name} blue-${module_name}-s) + install(FILES ${root_header} DESTINATION include/blue) + install(FILES ${headers} DESTINATION include/blue/${module_name}) +endmacro(add_bluelib_module) diff --git a/core-test/core-test.c b/core-test/core-test.c new file mode 100644 index 0000000..e5b70ec --- /dev/null +++ b/core-test/core-test.c @@ -0,0 +1,183 @@ +#include "blue/core/misc.h" + +#include +#include +#include +#include +#include +#include + +struct test_tree_node { + int value; + b_btree_node node; +}; + +struct test_queue_entry { + int value; + b_queue_entry entry; +}; + +B_BTREE_DEFINE_SIMPLE_INSERT(struct test_tree_node, node, value, test_tree_insert); + +void test_btree_insert(CuTest *tc) +{ + b_btree tree = {0}; + struct test_tree_node nodes[3] = {0}; + + for (int i = 0; i < sizeof nodes / sizeof *nodes; i++) { + nodes[i].value = i; + } + + test_tree_insert(&tree, &nodes[0]); + + CuAssertPtrEquals(tc, NULL, nodes[0].node.b_left); + CuAssertPtrEquals(tc, NULL, nodes[0].node.b_right); + CuAssertIntEquals(tc, 1, nodes[0].node.b_height); + + test_tree_insert(&tree, &nodes[1]); + + CuAssertPtrEquals(tc, NULL, nodes[0].node.b_left); + CuAssertPtrEquals(tc, &nodes[1].node, nodes[0].node.b_right); + CuAssertIntEquals(tc, 2, nodes[0].node.b_height); + + CuAssertPtrEquals(tc, NULL, nodes[1].node.b_left); + CuAssertPtrEquals(tc, NULL, nodes[1].node.b_right); + CuAssertIntEquals(tc, 1, nodes[1].node.b_height); + + test_tree_insert(&tree, &nodes[2]); + + CuAssertPtrEquals(tc, &nodes[0].node, nodes[1].node.b_left); + CuAssertPtrEquals(tc, &nodes[2].node, nodes[1].node.b_right); + CuAssertIntEquals(tc, 2, nodes[1].node.b_height); + + CuAssertPtrEquals(tc, NULL, nodes[0].node.b_left); + CuAssertPtrEquals(tc, NULL, nodes[0].node.b_right); + CuAssertIntEquals(tc, 1, nodes[0].node.b_height); + + CuAssertPtrEquals(tc, NULL, nodes[2].node.b_left); + CuAssertPtrEquals(tc, NULL, nodes[2].node.b_right); + CuAssertIntEquals(tc, 1, nodes[2].node.b_height); +} + +void test_btree_iterate(CuTest *tc) +{ + static const size_t nr_nodes = 256; + srand(time(NULL)); + + b_btree tree = {0}; + struct test_tree_node *nodes = calloc(nr_nodes, sizeof *nodes); + + for (int i = 0; i < nr_nodes; i++) { + nodes[i].value = rand(); + test_tree_insert(&tree, &nodes[i]); + } + + int prev = -1; + b_btree_iterator it; + b_btree_foreach (&it, &tree) { + struct test_tree_node *node + = b_unbox(struct test_tree_node, it.node, node); + CuAssertPtrNotNull(tc, node); + + if (prev == -1) { + prev = node->value; + continue; + } + + CuAssertTrue(tc, prev < node->value); + prev = node->value; + } + + free(nodes); +} + +void test_queue_insert(CuTest *tc) +{ + struct test_queue_entry entries[5] = {0}; + for (int i = 0; i < sizeof entries / sizeof *entries; i++) { + entries[i].value = i; + } + + b_queue q = B_QUEUE_INIT; + + b_queue_push_back(&q, &entries[0].entry); + b_queue_push_back(&q, &entries[2].entry); + b_queue_push_back(&q, &entries[4].entry); + b_queue_insert_after(&q, &entries[3].entry, &entries[2].entry); + b_queue_insert_before(&q, &entries[1].entry, &entries[2].entry); + + CuAssertPtrEquals(tc, NULL, entries[0].entry.qe_prev); + CuAssertPtrEquals(tc, &entries[1].entry, entries[0].entry.qe_next); + + CuAssertPtrEquals(tc, &entries[0].entry, entries[1].entry.qe_prev); + CuAssertPtrEquals(tc, &entries[2].entry, entries[1].entry.qe_next); + + CuAssertPtrEquals(tc, &entries[1].entry, entries[2].entry.qe_prev); + CuAssertPtrEquals(tc, &entries[3].entry, entries[2].entry.qe_next); + + CuAssertPtrEquals(tc, &entries[2].entry, entries[3].entry.qe_prev); + CuAssertPtrEquals(tc, &entries[4].entry, entries[3].entry.qe_next); + + CuAssertPtrEquals(tc, &entries[3].entry, entries[4].entry.qe_prev); + CuAssertPtrEquals(tc, NULL, entries[4].entry.qe_next); +} + +void test_queue_iterate(CuTest *tc) +{ + b_queue q = B_QUEUE_INIT; + struct test_queue_entry entries[32] = {0}; + + for (int i = 0; i < sizeof entries / sizeof *entries; i++) { + entries[i].value = i; + b_queue_push_back(&q, &entries[i].entry); + } + + int prev = -1; + b_queue_iterator it; + b_queue_foreach (&it, &q) { + struct test_queue_entry *e + = b_unbox(struct test_queue_entry, it.entry, entry); + CuAssertPtrNotNull(tc, e); + + if (prev == -1) { + prev = e->value; + continue; + } + + CuAssertTrue(tc, prev < e->value); + prev = e->value; + } +} + +void test_stringstream_1(CuTest *tc) +{ + char buf[1024]; + b_stringstream s; + b_stringstream_begin(&s, buf, sizeof buf); + + b_stringstream_add(&s, "hello"); + b_stringstream_addf(&s, "(%d + %.1f)", 32, 2.3); + + const char *x[] = {"ABC", "DEF", NULL}; + + b_stringstream_addv(&s, x); + b_stringstream_addvl(&s, x, 2); + + b_stringstream_add_many(&s, "more", "more", "more", NULL); + const char *end = b_stringstream_end(&s); + + CuAssertStrEquals(tc, "hello(32 + 2.3)ABCDEFABCDEFmoremoremore", end); +} + +CuSuite *get_all_tests(void) +{ + CuSuite *suite = CuSuiteNew(); + + SUITE_ADD_TEST(suite, test_btree_insert); + SUITE_ADD_TEST(suite, test_btree_iterate); + SUITE_ADD_TEST(suite, test_queue_insert); + SUITE_ADD_TEST(suite, test_queue_iterate); + SUITE_ADD_TEST(suite, test_stringstream_1); + + return suite; +} diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 0000000..e28413d --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,3 @@ +include(../cmake/Templates.cmake) + +add_bluelib_module(core) diff --git a/core/btree.c b/core/btree.c new file mode 100644 index 0000000..2664ed3 --- /dev/null +++ b/core/btree.c @@ -0,0 +1,821 @@ +/* + The Clear BSD License + + Copyright (c) 2023 Max Wash + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted (subject to the limitations in the disclaimer + below) provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + */ + +/* templated AVL binary tree implementation + + this file implements an extensible AVL binary tree data structure. + + the primary rule of an AVL binary tree is that for a given node N, + the heights of N's left and right subtrees can differ by at most 1. + + the height of a subtree is the length of the longest path between + the root of the subtree and a leaf node, including the root node itself. + + the height of a leaf node is 1. + + when a node is inserted into or deleted from the tree, this rule may + be broken, in which the tree must be rotated to restore the balance. + + no more than one rotation is required for any insert operations, + while multiple rotations may be required for a delete operation. + + there are four types of rotations that can be applied to a tree: + - left rotation + - right rotation + - double left rotations + - double right rotations + + by enforcing the balance rule, for a tree with n nodes, the worst-case + performance for insert, delete, and search operations is guaranteed + to be O(log n). + + this file intentionally excludes any kind of search function implementation. + it is up to the programmer to implement their own tree node type + using b_btree_node, and their own search function using b_btree. + this allows the programmer to define their own node types with complex + non-integer key types. btree.h contains a number of macros to help + define these functions. the macros do all the work, you just have to + provide a comparator function. +*/ + +#include +#include + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define IS_LEFT_CHILD(p, c) ((p) && (c) && ((p)->b_left == (c))) +#define IS_RIGHT_CHILD(p, c) ((p) && (c) && ((p)->b_right == (c))) + +#define HAS_LEFT_CHILD(x) ((x) && ((x)->b_left)) +#define HAS_RIGHT_CHILD(x) ((x) && ((x)->b_right)) + +#define HAS_NO_CHILDREN(x) ((x) && (!(x)->b_left) && (!(x)->b_right)) +#define HAS_ONE_CHILD(x) \ + ((HAS_LEFT_CHILD(x) && !HAS_RIGHT_CHILD(x)) \ + || (!HAS_LEFT_CHILD(x) && HAS_RIGHT_CHILD(x))) +#define HAS_TWO_CHILDREN(x) (HAS_LEFT_CHILD(x) && HAS_RIGHT_CHILD(x)) + +#define HEIGHT(x) ((x) ? (x)->b_height : 0) + +static inline void update_height(struct b_btree_node *x) +{ + x->b_height = MAX(HEIGHT(x->b_left), HEIGHT((x->b_right))) + 1; +} + +static inline int bf(struct b_btree_node *x) +{ + int bf = 0; + + if (!x) { + return bf; + } + + if (x->b_right) { + bf += x->b_right->b_height; + } + + if (x->b_left) { + bf -= x->b_left->b_height; + } + + return bf; +} + +/* perform a left rotation on a subtree + + if you have a tree like this: + + Z + / \ + X . + / \ + . Y + / \ + . . + + and you perform a left rotation on node X, + you will get the following tree: + + Z + / \ + Y . + / \ + X . + / \ + . . + + note that this function does NOT update b_height for the rotated + nodes. it is up to you to call update_height_to_root(). +*/ +static void rotate_left(struct b_btree *tree, struct b_btree_node *x) +{ + struct b_btree_node *y = x->b_right; + struct b_btree_node *p = x->b_parent; + + if (y->b_left) { + y->b_left->b_parent = x; + } + + x->b_right = y->b_left; + + if (!p) { + tree->b_root = y; + } else if (x == p->b_left) { + p->b_left = y; + } else { + p->b_right = y; + } + + x->b_parent = y; + y->b_left = x; + y->b_parent = p; +} + +static void update_height_to_root(struct b_btree_node *x) +{ + while (x) { + update_height(x); + x = x->b_parent; + } +} + +/* perform a right rotation on a subtree + + if you have a tree like this: + + Z + / \ + . X + / \ + Y . + / \ + . . + + and you perform a right rotation on node X, + you will get the following tree: + + Z + / \ + . Y + / \ + . X + / \ + . . + + note that this function does NOT update b_height for the rotated + nodes. it is up to you to call update_height_to_root(). +*/ +static void rotate_right(struct b_btree *tree, struct b_btree_node *y) +{ + struct b_btree_node *x = y->b_left; + struct b_btree_node *p = y->b_parent; + + if (x->b_right) { + x->b_right->b_parent = y; + } + + y->b_left = x->b_right; + + if (!p) { + tree->b_root = x; + } else if (y == p->b_left) { + p->b_left = x; + } else { + p->b_right = x; + } + + y->b_parent = x; + x->b_right = y; + x->b_parent = p; +} + +/* for a given node Z, perform a right rotation on Z's right child, + followed by a left rotation on Z itself. + + if you have a tree like this: + + Z + / \ + . X + / \ + Y . + / \ + . . + + and you perform a double-left rotation on node Z, + you will get the following tree: + + Y + / \ + / \ + Z X + / \ / \ + . . . . + + note that, unlike rotate_left and rotate_right, this function + DOES update b_height for the rotated nodes (since it needs to be + done in a certain order). +*/ +static void rotate_double_left(struct b_btree *tree, struct b_btree_node *z) +{ + struct b_btree_node *x = z->b_right; + struct b_btree_node *y = x->b_left; + + rotate_right(tree, x); + rotate_left(tree, z); + + update_height(z); + update_height(x); + + while (y) { + update_height(y); + y = y->b_parent; + } +} + +/* for a given node Z, perform a left rotation on Z's left child, + followed by a right rotation on Z itself. + + if you have a tree like this: + + Z + / \ + X . + / \ + . Y + / \ + . . + + and you perform a double-right rotation on node Z, + you will get the following tree: + + Y + / \ + / \ + X Z + / \ / \ + . . . . + + note that, unlike rotate_left and rotate_right, this function + DOES update b_height for the rotated nodes (since it needs to be + done in a certain order). +*/ +static void rotate_double_right(struct b_btree *tree, struct b_btree_node *z) +{ + struct b_btree_node *x = z->b_left; + struct b_btree_node *y = x->b_right; + + rotate_left(tree, x); + rotate_right(tree, z); + + update_height(z); + update_height(x); + + while (y) { + update_height(y); + y = y->b_parent; + } +} + +/* run after an insert operation. checks that the balance factor + of the local subtree is within the range -1 <= BF <= 1. if it + is not, rotate the subtree to restore balance. + + note that at most one rotation should be required after a node + is inserted into the tree. + + this function depends on all nodes in the tree having + correct b_height values. + + @param w the node that was just inserted into the tree +*/ +static void insert_fixup(struct b_btree *tree, struct b_btree_node *w) +{ + struct b_btree_node *z = NULL, *y = NULL, *x = NULL; + + z = w; + while (z) { + if (bf(z) >= -1 && bf(z) <= 1) { + goto next_ancestor; + } + + if (IS_LEFT_CHILD(z, y)) { + if (IS_LEFT_CHILD(y, x)) { + rotate_right(tree, z); + update_height_to_root(z); + } else { + rotate_double_right(tree, z); + } + } else { + if (IS_LEFT_CHILD(y, x)) { + rotate_double_left(tree, z); + } else { + rotate_left(tree, z); + update_height_to_root(z); + } + } + + next_ancestor: + x = y; + y = z; + z = z->b_parent; + } +} + +/* run after a delete operation. checks that the balance factor + of the local subtree is within the range -1 <= BF <= 1. if it + is not, rotate the subtree to restore balance. + + note that, unlike insert_fixup, multiple rotations may be required + to restore balance after a node is deleted. + + this function depends on all nodes in the tree having + correct b_height values. + + @param w one of the following: + - the parent of the node that was deleted if the node + had no children. + - the parent of the node that replaced the deleted node + if the deleted node had two children. + - the node that replaced the node that was deleted, if + the node that was deleted had one child. +*/ +static void delete_fixup(struct b_btree *tree, struct b_btree_node *w) +{ + struct b_btree_node *z = w; + + while (z) { + if (bf(z) > 1) { + if (bf(z->b_right) >= 0) { + rotate_left(tree, z); + update_height_to_root(z); + } else { + rotate_double_left(tree, z); + } + } else if (bf(z) < -1) { + if (bf(z->b_left) <= 0) { + rotate_right(tree, z); + update_height_to_root(z); + } else { + rotate_double_right(tree, z); + } + } + + z = z->b_parent; + } +} + +/* updates b_height for all nodes between the inserted node and the root + of the tree, and calls insert_fixup. + + @param node the node that was just inserted into the tree. +*/ +void b_btree_insert_fixup(struct b_btree *tree, struct b_btree_node *node) +{ + node->b_height = 0; + + struct b_btree_node *cur = node; + while (cur) { + update_height(cur); + cur = cur->b_parent; + } + + insert_fixup(tree, node); +} + +/* remove a node from a tree. + + this function assumes that `node` has no children, and therefore + doesn't need to be replaced. + + updates b_height for all nodes between `node` and the tree root. + + @param node the node to delete. +*/ +static struct b_btree_node *remove_node_with_no_children( + struct b_btree *tree, + struct b_btree_node *node) +{ + struct b_btree_node *w = node->b_parent; + struct b_btree_node *p = node->b_parent; + node->b_parent = NULL; + + if (!p) { + tree->b_root = NULL; + } else if (IS_LEFT_CHILD(p, node)) { + p->b_left = NULL; + } else { + p->b_right = NULL; + } + + while (p) { + update_height(p); + p = p->b_parent; + } + + return w; +} + +/* remove a node from a tree. + + this function assumes that `node` has one child. + the child of `node` is inherited by `node`'s parent, and `node` is removed. + + updates b_height for all nodes between the node that replaced + `node` and the tree root. + + @param node the node to delete. +*/ +static struct b_btree_node *replace_node_with_one_subtree( + struct b_btree *tree, + struct b_btree_node *node) +{ + struct b_btree_node *p = node->b_parent; + struct b_btree_node *z = NULL; + + if (HAS_LEFT_CHILD(node)) { + z = node->b_left; + } else { + z = node->b_right; + } + + struct b_btree_node *w = z; + if (!p) { + tree->b_root = z; + } else if (IS_LEFT_CHILD(p, node)) { + p->b_left = z; + } else if (IS_RIGHT_CHILD(p, node)) { + p->b_right = z; + } + + z->b_parent = p; + + node->b_parent = NULL; + node->b_left = node->b_right = NULL; + + while (z) { + update_height(z); + z = z->b_parent; + } + + return w; +} + +/* remove a node from a tree. + + this function assumes that `node` has two children. + find the in-order successor Y of `node` (the largest node in `node`'s left + sub-tree), removes `node` from the tree and moves Y to where `node` used to + be. + + if Y has a child (it will never have more than one), have Y's parent inherit + Y's child. + + updates b_height for all nodes between the deepest node that was modified + and the tree root. + + @param z the node to delete. +*/ +static struct b_btree_node *replace_node_with_two_subtrees( + struct b_btree *tree, + struct b_btree_node *z) +{ + /* x will replace z */ + struct b_btree_node *x = z->b_left; + + while (x->b_right) { + x = x->b_right; + } + + /* y is the node that will replace x (if x has a left child) */ + struct b_btree_node *y = x->b_left; + + /* w is the starting point for the height update and fixup */ + struct b_btree_node *w = x; + if (w->b_parent != z) { + w = w->b_parent; + } + + if (y) { + w = y; + } + + if (IS_LEFT_CHILD(x->b_parent, x)) { + x->b_parent->b_left = y; + } else if (IS_RIGHT_CHILD(x->b_parent, x)) { + x->b_parent->b_right = y; + } + + if (y) { + y->b_parent = x->b_parent; + } + + if (IS_LEFT_CHILD(z->b_parent, z)) { + z->b_parent->b_left = x; + } else if (IS_RIGHT_CHILD(z->b_parent, z)) { + z->b_parent->b_right = x; + } + + x->b_parent = z->b_parent; + x->b_left = z->b_left; + x->b_right = z->b_right; + + if (x->b_left) { + x->b_left->b_parent = x; + } + + if (x->b_right) { + x->b_right->b_parent = x; + } + + if (!x->b_parent) { + tree->b_root = x; + } + + struct b_btree_node *cur = w; + while (cur) { + update_height(cur); + cur = cur->b_parent; + } + + return w; +} + +/* delete a node from the tree and re-balance it afterwards */ +void b_btree_delete(struct b_btree *tree, struct b_btree_node *node) +{ + struct b_btree_node *w = NULL; + + if (HAS_NO_CHILDREN(node)) { + w = remove_node_with_no_children(tree, node); + } else if (HAS_ONE_CHILD(node)) { + w = replace_node_with_one_subtree(tree, node); + } else if (HAS_TWO_CHILDREN(node)) { + w = replace_node_with_two_subtrees(tree, node); + } + + if (w) { + delete_fixup(tree, w); + } + + node->b_left = node->b_right = node->b_parent = NULL; +} + +static struct b_btree_node *first_node(const struct b_btree *tree, int *depth) +{ + /* the first node in the tree is the node with the smallest key. + we keep moving left until we can't go any further */ + struct b_btree_node *cur = tree->b_root; + int d = 0; + + if (!cur) { + *depth = 0; + return NULL; + } + + while (cur->b_left) { + d++; + cur = cur->b_left; + } + + *depth = d; + return cur; +} + +struct b_btree_node *b_btree_first(const struct b_btree *tree) +{ + int d; + return first_node(tree, &d); +} + +static struct b_btree_node *last_node(const struct b_btree *tree, int *depth) +{ + /* the first node in the tree is the node with the largest key. + we keep moving right until we can't go any further */ + struct b_btree_node *cur = tree->b_root; + int d = 0; + if (!cur) { + return NULL; + } + + while (cur->b_right) { + d++; + cur = cur->b_right; + } + + *depth = d; + return cur; +} + +b_btree_node *b_btree_last(const struct b_btree *tree) +{ + int d; + return last_node(tree, &d); +} + +static b_btree_node *next_node(const struct b_btree_node *node, int *depth_diff) +{ + if (!node) { + return NULL; + } + + int depth = 0; + + /* there are two possibilities for the next node: + + 1. if `node` has a right sub-tree, every node in this sub-tree is + bigger than node. the in-order successor of `node` is the smallest + node in this subtree. + 2. if `node` has no right sub-tree, we've reached the largest node in + the sub-tree rooted at `node`. we need to go back to our parent + and continue the search elsewhere. + */ + if (node->b_right) { + /* case 1: step into `node`'s right sub-tree and keep going + left to find the smallest node */ + struct b_btree_node *cur = node->b_right; + depth++; + while (cur->b_left) { + cur = cur->b_left; + depth++; + } + + *depth_diff = depth; + return cur; + } + + /* case 2: keep stepping back up towards the root of the tree. + if we encounter a step where we are our parent's left child, + we've found a parent with a value larger than us. this parent + is the in-order successor of `node` */ + while (node->b_parent && node->b_parent->b_left != node) { + node = node->b_parent; + depth--; + } + + *depth_diff = depth - 1; + return node->b_parent; +} + +static b_btree_node *prev_node(const struct b_btree_node *node, int *depth_diff) +{ + if (!node) { + return NULL; + } + + int depth = 0; + + /* there are two possibilities for the previous node: + + 1. if `node` has a left sub-tree, every node in this sub-tree is + smaller than `node`. the in-order predecessor of `node` is the + largest node in this subtree. + 2. if `node` has no left sub-tree, we've reached the smallest node in + the sub-tree rooted at `node`. we need to go back to our parent + and continue the search elsewhere. + */ + if (node->b_left) { + /* case 1: step into `node`'s left sub-tree and keep going + right to find the largest node */ + b_btree_node *cur = node->b_left; + depth++; + while (cur->b_right) { + cur = cur->b_right; + depth++; + } + + *depth_diff = depth; + return cur; + } + + /* case 2: keep stepping back up towards the root of the tree. + if we encounter a step where we are our parent's right child, + we've found a parent with a value smaller than us. this parent + is the in-order predecessor of `node`. */ + while (node->b_parent && node->b_parent->b_right != node) { + node = node->b_parent; + depth--; + } + + *depth_diff = depth - 1; + return node->b_parent; +} + +b_btree_node *b_btree_next(const struct b_btree_node *node) +{ + int d; + return next_node(node, &d); +} + +b_btree_node *b_btree_prev(const struct b_btree_node *node) +{ + int d; + return prev_node(node, &d); +} + +static bool btree_iterator_next(struct b_iterator *it) +{ + return b_btree_iterator_next((struct b_btree_iterator *)it); +} + +static b_status btree_iterator_erase(struct b_iterator *it) +{ + return b_btree_iterator_erase((struct b_btree_iterator *)it); +} + +static bool btree_iterator_is_valid(const struct b_iterator *it) +{ + return b_btree_iterator_is_valid((struct b_btree_iterator *)it); +} + +static const b_iterator_ops btree_iterator_ops = { + .it_next = btree_iterator_next, + .it_erase = btree_iterator_erase, + .it_close = NULL, + .it_is_valid = btree_iterator_is_valid, +}; + +int b_btree_iterator_begin( + const struct b_btree *tree, + struct b_btree_iterator *it) +{ + int depth = 0; + + it->_b = (struct b_btree *)tree; + it->i = 0; + it->node = first_node(tree, &depth); + it->depth = depth; + it->_base.it_ops = &btree_iterator_ops; + + return 0; +} + +bool b_btree_iterator_next(struct b_btree_iterator *it) +{ + int depth_diff = 0; + struct b_btree_node *next = next_node(it->node, &depth_diff); + + if (!next) { + it->node = NULL; + it->depth = 0; + it->i++; + return false; + } + + it->node = next; + it->i++; + it->depth += depth_diff; + return true; +} + +b_status b_btree_iterator_erase(struct b_btree_iterator *it) +{ + if (!it->node) { + return B_ERR_OUT_OF_BOUNDS; + } + + int depth_diff = 0; + struct b_btree_node *next = next_node(it->node, &depth_diff); + + b_btree_delete(it->_b, it->node); + if (!next) { + it->node = NULL; + it->depth = 0; + } else { + it->node = next; + it->depth = 0; + + struct b_btree_node *cur = next->b_parent; + while (cur) { + it->depth++; + cur = cur->b_parent; + } + } + + return B_SUCCESS; +} + +bool b_btree_iterator_is_valid(const struct b_btree_iterator *it) +{ + return it->node != NULL; +} diff --git a/core/include/blue/core.h b/core/include/blue/core.h new file mode 100644 index 0000000..e69de29 diff --git a/core/include/blue/core/btree.h b/core/include/blue/core/btree.h new file mode 100644 index 0000000..07eca4d --- /dev/null +++ b/core/include/blue/core/btree.h @@ -0,0 +1,357 @@ +#ifndef BLUELIB_CORE_BTREE_H_ +#define BLUELIB_CORE_BTREE_H_ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* defines a simple node insertion function. + this function assumes that your nodes have simple integer keys that can be + compared with the usual operators. + + EXAMPLE: + if you have a tree node type like this: + + struct my_tree_node { + int key; + b_btree_node base; + } + + You would use the following call to generate an insert function for a tree + with this node type: + + BTREE_DEFINE_SIMPLE_INSERT( + struct my_tree_node, + base, + key, + my_tree_node_insert); + + Which would emit a function defined like: + + static void my_tree_node_insert(b_btree *tree, struct my_tree_node *node); + + @param node_type your custom tree node type. usually a structure that + contains a b_btree_node member. + @param container_node_member the name of the b_btree_node member variable + within your custom type. + @param container_key_member the name of the key member variable within your + custom type. + @param function_name the name of the function to generate. +*/ +#define B_BTREE_DEFINE_SIMPLE_INSERT( \ + node_type, container_node_member, container_key_member, function_name) \ + void function_name(b_btree *tree, node_type *node) \ + { \ + if (!tree->b_root) { \ + tree->b_root = &node->container_node_member; \ + b_btree_insert_fixup(tree, &node->container_node_member); \ + return; \ + } \ + \ + b_btree_node *cur = tree->b_root; \ + while (1) { \ + node_type *cur_node = b_unbox( \ + node_type, cur, container_node_member); \ + b_btree_node *next = NULL; \ + \ + if (node->container_key_member \ + >= cur_node->container_key_member) { \ + next = b_btree_right(cur); \ + \ + if (!next) { \ + b_btree_put_right( \ + cur, \ + &node->container_node_member); \ + break; \ + } \ + } else if ( \ + node->container_key_member \ + < cur_node->container_key_member) { \ + next = b_btree_left(cur); \ + \ + if (!next) { \ + b_btree_put_left( \ + cur, \ + &node->container_node_member); \ + break; \ + } \ + } \ + \ + cur = next; \ + } \ + \ + b_btree_insert_fixup(tree, &node->container_node_member); \ + } + +/* defines a node insertion function. + this function should be used for trees with complex node keys that cannot be + directly compared. a comparator for your keys must be supplied. + + EXAMPLE: + if you have a tree node type like this: + + struct my_tree_node { + complex_key_t key; + b_btree_node base; + } + + You would need to define a comparator function or macro with the following + signature: + + int my_comparator(struct my_tree_node *a, struct my_tree_node *b); + + Which implements the following: + + return -1 if a < b + return 0 if a == b + return 1 if a > b + + You would use the following call to generate an insert function for a tree + with this node type: + + BTREE_DEFINE_INSERT(struct my_tree_node, base, key, my_tree_node_insert, + my_comparator); + + Which would emit a function defined like: + + static void my_tree_node_insert(b_btree *tree, struct my_tree_node *node); + + @param node_type your custom tree node type. usually a structure that + contains a b_btree_node member. + @param container_node_member the name of the b_btree_node member variable + within your custom type. + @param container_key_member the name of the key member variable within your + custom type. + @param function_name the name of the function to generate. + @param comparator the name of a comparator function or functional-macro that + conforms to the requirements listed above. +*/ +#define B_BTREE_DEFINE_INSERT( \ + node_type, container_node_member, container_key_member, function_name, \ + comparator) \ + void function_name(b_btree *tree, node_type *node) \ + { \ + if (!tree->b_root) { \ + tree->b_root = &node->container_node_member; \ + b_btree_insert_fixup(tree, &node->container_node_member); \ + return; \ + } \ + \ + b_btree_node *cur = tree->b_root; \ + while (1) { \ + node_type *cur_node = b_unbox( \ + node_type, cur, container_node_member); \ + b_btree_node *next = NULL; \ + int cmp = comparator(node, cur_node); \ + \ + if (cmp >= 0) { \ + next = b_btree_right(cur); \ + \ + if (!next) { \ + b_btree_put_right( \ + cur, \ + &node->container_node_member); \ + break; \ + } \ + } else if (cmp == -1) { \ + next = b_btree_left(cur); \ + \ + if (!next) { \ + b_btree_put_left( \ + cur, \ + &node->container_node_member); \ + break; \ + } \ + } else { \ + return; \ + } \ + \ + cur = next; \ + } \ + \ + b_btree_insert_fixup(tree, &node->container_node_member); \ + } + +/* defines a simple tree search function. + this function assumes that your nodes have simple integer keys that can be + compared with the usual operators. + + EXAMPLE: + if you have a tree node type like this: + + struct my_tree_node { + int key; + b_btree_node base; + } + + You would use the following call to generate a search function for a tree + with this node type: + + BTREE_DEFINE_SIMPLE_GET(struct my_tree_node, int, base, key, + my_tree_node_get); + + Which would emit a function defined like: + + static struct my_tree_node *my_tree_node_get(b_btree *tree, int key); + + @param node_type your custom tree node type. usually a structure that + contains a b_btree_node member. + @param key_type the type name of the key embedded in your custom tree node + type. this type must be compatible with the builtin comparison operators. + @param container_node_member the name of the b_btree_node member variable + within your custom type. + @param container_key_member the name of the key member variable within your + custom type. + @param function_name the name of the function to generate. +*/ +#define B_BTREE_DEFINE_SIMPLE_GET( \ + node_type, key_type, container_node_member, container_key_member, \ + function_name) \ + node_type *function_name(const b_btree *tree, key_type key) \ + { \ + b_btree_node *cur = tree->b_root; \ + while (cur) { \ + node_type *cur_node = b_unbox( \ + node_type, cur, container_node_member); \ + if (key > cur_node->container_key_member) { \ + cur = b_btree_right(cur); \ + } else if (key < cur_node->container_key_member) { \ + cur = b_btree_left(cur); \ + } else { \ + return cur_node; \ + } \ + } \ + \ + return NULL; \ + } + +#define b_btree_foreach(it, btree) \ + for (int z__b_unique_name() = b_btree_iterator_begin(btree, it); \ + (it)->node != NULL; b_btree_iterator_next(it)) + +/* binary tree nodes. this *cannot* be used directly. you need to define a + custom node type that contains a member variable of type b_btree_node. + + you would then use the supplied macros to define functions to manipulate your + custom binary tree. +*/ +typedef struct b_btree_node { + struct b_btree_node *b_parent, *b_left, *b_right; + unsigned short b_height; +} b_btree_node; + +/* binary tree. unlike b_btree_node, you can define variables of type b_btree. + */ +typedef struct b_btree { + b_btree_node *b_root; +} b_btree; + +typedef struct b_btree_iterator { + b_iterator _base; + size_t i, depth; + b_btree_node *node; + b_btree *_b; +} b_btree_iterator; + +/* re-balance a binary tree after an insertion operation. + + NOTE that, if you define an insertion function using BTREE_DEFINE_INSERT or + similar, this function will automatically called for you. + + @param tree the tree to re-balance. + @param node the node that was just inserted into the tree. +*/ +extern void b_btree_insert_fixup(b_btree *tree, b_btree_node *node); + +/* delete a node from a binary tree and re-balance the tree afterwards. + + @param tree the tree to delete from + @param node the node to delete. +*/ +extern void b_btree_delete(b_btree *tree, b_btree_node *node); + +/* get the first node in a binary tree. + + this will be the node with the smallest key (i.e. the node that is + furthest-left from the root) +*/ +extern b_btree_node *b_btree_first(const b_btree *tree); + +/* get the last node in a binary tree. + + this will be the node with the largest key (i.e. the node that is + furthest-right from the root) +*/ +extern b_btree_node *b_btree_last(const b_btree *tree); +/* for any binary tree node, this function returns the node with the + * next-largest key value */ +extern b_btree_node *b_btree_next(const b_btree_node *node); +/* for any binary tree node, this function returns the node with the + * next-smallest key value */ +extern b_btree_node *b_btree_prev(const b_btree_node *node); +/* return true if the btree is empty, false otherwise */ +static inline bool b_btree_empty(const b_btree *tree) +{ + return tree->b_root == NULL; +} + +/* sets `child` as the immediate left-child of `parent` */ +static inline void b_btree_put_left(b_btree_node *parent, b_btree_node *child) +{ + parent->b_left = child; + child->b_parent = parent; +} + +/* sets `child` as the immediate right-child of `parent` */ +static inline void b_btree_put_right(b_btree_node *parent, b_btree_node *child) +{ + parent->b_right = child; + child->b_parent = parent; +} + +/* get the immediate left-child of `node` */ +static inline b_btree_node *b_btree_left(b_btree_node *node) +{ + return node->b_left; +} + +/* get the immediate right-child of `node` */ +static inline b_btree_node *b_btree_right(b_btree_node *node) +{ + return node->b_right; +} + +/* get the immediate parent of `node` */ +static inline b_btree_node *b_btree_parent(b_btree_node *node) +{ + return node->b_parent; +} + +/* get the height of `node`. + + the height of a node is defined as the length of the longest path + between the node and a leaf node. + + this count includes the node itself, so the height of a leaf node will be 1. +*/ +static inline unsigned short b_btree_height(b_btree_node *node) +{ + return node->b_height; +} + +extern int b_btree_iterator_begin(const b_btree *tree, b_btree_iterator *it); +extern bool b_btree_iterator_next(b_btree_iterator *it); +extern b_status b_btree_iterator_erase(b_btree_iterator *it); +extern bool b_btree_iterator_is_valid(const b_btree_iterator *it); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/include/blue/core/exception.h b/core/include/blue/core/exception.h new file mode 100644 index 0000000..e69de29 diff --git a/core/include/blue/core/iterator.h b/core/include/blue/core/iterator.h new file mode 100644 index 0000000..349b66a --- /dev/null +++ b/core/include/blue/core/iterator.h @@ -0,0 +1,25 @@ +#ifndef BLUELIB_CORE_ITERATOR_H_ +#define BLUELIB_CORE_ITERATOR_H_ + +#include +#include + +struct b_iterator; + +typedef struct b_iterator_ops { + b_status (*it_close)(struct b_iterator *); + bool (*it_next)(struct b_iterator *); + b_status (*it_erase)(struct b_iterator *); + bool (*it_is_valid)(const struct b_iterator *); +} b_iterator_ops; + +typedef struct b_iterator { + const b_iterator_ops *it_ops; +} b_iterator; + +extern b_status b_iterator_close(b_iterator *it); +extern bool b_iterator_next(b_iterator *it); +extern b_status b_iterator_erase(b_iterator *it); +extern bool b_iterator_is_valid(const b_iterator *it); + +#endif diff --git a/core/include/blue/core/misc.h b/core/include/blue/core/misc.h new file mode 100644 index 0000000..31938a2 --- /dev/null +++ b/core/include/blue/core/misc.h @@ -0,0 +1,13 @@ +#ifndef BLUELIB_MISC_H_ +#define BLUELIB_MISC_H_ + +#define b_unbox(type, box, member) \ + ((type *_Nonnull)((box) ? (uintptr_t)(box) - (offsetof(type, member)) : 0)) + +#define z__b_merge_(a, b) a##b +#define z__b_label_(a) z__b_merge_(__unique_name_, a) +#define z__b_unique_name() z__b_label_(__LINE__) +#define z__b_numargs(arg_type, ...) \ + (sizeof((arg_type[]) {__VA_ARGS__}) / sizeof(arg_type)) + +#endif // C_MISC_H_ diff --git a/core/include/blue/core/queue.h b/core/include/blue/core/queue.h new file mode 100644 index 0000000..2e6d999 --- /dev/null +++ b/core/include/blue/core/queue.h @@ -0,0 +1,88 @@ +#ifndef BLUELIB_CORE_QUEUE_H_ +#define BLUELIB_CORE_QUEUE_H_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define B_QUEUE_INIT ((b_queue) {.q_first = NULL, .q_last = NULL}) +#define B_QUEUE_ENTRY_INIT ((b_queue_entry) {.qe_next = NULL, .qe_prev = NULL}) + +#define b_queue_foreach(it, q) \ + for (int z__b_unique_name() = b_queue_iterator_begin(q, it); \ + (it)->entry != NULL; b_queue_iterator_next(it)) + +typedef struct b_queue_entry { + struct b_queue_entry *qe_next; + struct b_queue_entry *qe_prev; +} b_queue_entry; + +typedef struct b_queue { + b_queue_entry *q_first; + b_queue_entry *q_last; +} b_queue; + +typedef struct b_queue_iterator { + b_iterator _base; + size_t i; + b_queue_entry *entry; + b_queue *_q; +} b_queue_iterator; + +static inline void b_queue_init(b_queue *q) +{ + memset(q, 0x00, sizeof *q); +} +static inline bool b_queue_empty(b_queue *q) +{ + return q->q_first == NULL; +} + +static inline b_queue_entry *b_queue_first(const b_queue *q) +{ + return q->q_first; +} +static inline b_queue_entry *b_queue_last(const b_queue *q) +{ + return q->q_last; +} +static inline b_queue_entry *b_queue_next(const b_queue_entry *entry) +{ + return entry->qe_next; +} +static inline b_queue_entry *b_queue_prev(const b_queue_entry *entry) +{ + return entry->qe_prev; +} + +extern size_t b_queue_length(b_queue *q); + +extern void b_queue_insert_before( + b_queue *q, b_queue_entry *entry, b_queue_entry *before); +extern void b_queue_insert_after( + b_queue *q, b_queue_entry *entry, b_queue_entry *after); + +extern void b_queue_push_front(b_queue *q, b_queue_entry *entry); +extern void b_queue_push_back(b_queue *q, b_queue_entry *entry); + +extern b_queue_entry *b_queue_pop_front(b_queue *q); +extern b_queue_entry *b_queue_pop_back(b_queue *q); + +extern void b_queue_delete(b_queue *q, b_queue_entry *entry); +extern void b_queue_delete_all(b_queue *q); + +extern int b_queue_iterator_begin(const b_queue *q, b_queue_iterator *it); +extern bool b_queue_iterator_next(b_queue_iterator *it); +extern b_status b_queue_iterator_erase(b_queue_iterator *it); +extern bool b_queue_iterator_is_valid(const b_queue_iterator *it); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/include/blue/core/status.h b/core/include/blue/core/status.h new file mode 100644 index 0000000..e983754 --- /dev/null +++ b/core/include/blue/core/status.h @@ -0,0 +1,23 @@ +#ifndef BLUELIB_CORE_STATUS_H_ +#define BLUELIB_CORE_STATUS_H_ + +#define B_OK(status) ((status) == B_SUCCESS) +#define B_ERR(status) ((status) != B_SUCCESS) + +typedef enum { + B_SUCCESS = 0x00u, + B_ERR_NO_MEMORY, + B_ERR_OUT_OF_BOUNDS, + B_ERR_INVALID_ARGUMENT, + B_ERR_NAME_EXISTS, + B_ERR_NOT_SUPPORTED, + B_ERR_BAD_STATE, + B_ERR_NO_ENTRY, + B_ERR_NO_DATA, + B_ERR_UNKNOWN_FUNCTION, + B_ERR_BAD_FORMAT, +} b_status; + +extern const char *b_status_to_string(b_status status); + +#endif diff --git a/core/include/blue/core/stringstream.h b/core/include/blue/core/stringstream.h new file mode 100644 index 0000000..6141598 --- /dev/null +++ b/core/include/blue/core/stringstream.h @@ -0,0 +1,30 @@ +#ifndef BLUELIB_CORE_STRINGSTREAM_H_ +#define BLUELIB_CORE_STRINGSTREAM_H_ + +#include +#include + +typedef struct b_stringstream { + char *ss_buf; + size_t ss_len; + size_t ss_max; + unsigned char ss_alloc; +} b_stringstream; + +extern void b_stringstream_begin(b_stringstream *strv, char *buf, size_t max); +extern void b_stringstream_begin_dynamic(b_stringstream *strv); + +extern b_status b_stringstream_add(b_stringstream *strv, const char *str); +extern b_status b_stringstream_addf( + b_stringstream *strv, + const char *format, + ...); +extern b_status b_stringstream_addv(b_stringstream *strv, const char **strs); +extern b_status b_stringstream_addvl( + b_stringstream *strv, + const char **strs, + size_t count); +extern b_status b_stringstream_add_many(b_stringstream *strv, ...); +extern char *b_stringstream_end(b_stringstream *strv); + +#endif diff --git a/core/iterator.c b/core/iterator.c new file mode 100644 index 0000000..c1f9b5e --- /dev/null +++ b/core/iterator.c @@ -0,0 +1,37 @@ +#include + +b_status b_iterator_cleanup(struct b_iterator *it) +{ + if (it->it_ops && it->it_ops->it_close) { + return it->it_ops->it_close(it); + } + + return B_SUCCESS; +} + +bool b_iterator_next(struct b_iterator *it) +{ + if (it->it_ops && it->it_ops->it_next) { + return it->it_ops->it_next(it); + } + + return false; +} + +b_status b_iterator_erase(struct b_iterator *it) +{ + if (it->it_ops && it->it_ops->it_erase) { + return it->it_ops->it_erase(it); + } + + return B_ERR_NOT_SUPPORTED; +} + +bool b_iterator_is_valid(const struct b_iterator *it) +{ + if (it->it_ops && it->it_ops->it_is_valid) { + return it->it_ops->it_is_valid(it); + } + + return false; +} diff --git a/core/queue.c b/core/queue.c new file mode 100644 index 0000000..52bd190 --- /dev/null +++ b/core/queue.c @@ -0,0 +1,196 @@ +#include + +size_t b_queue_length(struct b_queue *q) +{ + size_t i = 0; + struct b_queue_entry *x = q->q_first; + while (x) { + i++; + x = x->qe_next; + } + + return i; +} + +void b_queue_insert_before( + struct b_queue *q, struct b_queue_entry *entry, struct b_queue_entry *before) +{ + struct b_queue_entry *x = before->qe_prev; + if (x) { + x->qe_next = entry; + } else { + q->q_first = entry; + } + + entry->qe_prev = x; + + before->qe_prev = entry; + entry->qe_next = before; +} + +void b_queue_insert_after( + struct b_queue *q, struct b_queue_entry *entry, struct b_queue_entry *after) +{ + struct b_queue_entry *x = after->qe_next; + if (x) { + x->qe_prev = entry; + } else { + q->q_last = entry; + } + + entry->qe_next = x; + + after->qe_next = entry; + entry->qe_prev = after; +} + +void b_queue_push_front(struct b_queue *q, struct b_queue_entry *entry) +{ + if (q->q_first) { + q->q_first->qe_prev = entry; + } + + entry->qe_next = q->q_first; + entry->qe_prev = NULL; + + q->q_first = entry; + + if (!q->q_last) { + q->q_last = entry; + } +} + +void b_queue_push_back(struct b_queue *q, struct b_queue_entry *entry) +{ + if (q->q_last) { + q->q_last->qe_next = entry; + } + + entry->qe_prev = q->q_last; + entry->qe_next = NULL; + + q->q_last = entry; + + if (!q->q_first) { + q->q_first = entry; + } +} + +struct b_queue_entry *b_queue_pop_front(struct b_queue *q) +{ + struct b_queue_entry *x = q->q_first; + if (x) { + b_queue_delete(q, x); + } + + return x; +} + +struct b_queue_entry *b_queue_pop_back(struct b_queue *q) +{ + struct b_queue_entry *x = q->q_last; + if (x) { + b_queue_delete(q, x); + } + + return x; +} + +void b_queue_delete(struct b_queue *q, struct b_queue_entry *entry) +{ + if (!entry) { + return; + } + + if (entry == q->q_first) { + q->q_first = q->q_first->qe_next; + } + + if (entry == q->q_last) { + q->q_last = q->q_last->qe_prev; + } + + if (entry->qe_next) { + entry->qe_next->qe_prev = entry->qe_prev; + } + + if (entry->qe_prev) { + entry->qe_prev->qe_next = entry->qe_next; + } + + entry->qe_next = entry->qe_prev = NULL; +} + +void b_queue_delete_all(struct b_queue *q) +{ + struct b_queue_entry *x = q->q_first; + while (x) { + struct b_queue_entry *next = x->qe_next; + x->qe_next = x->qe_prev = NULL; + x = next; + } + + q->q_first = q->q_last = NULL; +} + +static bool queue_iterator_next(struct b_iterator *it) +{ + return b_queue_iterator_next((struct b_queue_iterator *)it); +} + +static b_status queue_iterator_erase(struct b_iterator *it) +{ + return b_queue_iterator_erase((struct b_queue_iterator *)it); +} + +static bool queue_iterator_is_valid(const struct b_iterator *it) +{ + return b_queue_iterator_is_valid((const struct b_queue_iterator *)it); +} + +static const b_iterator_ops queue_iterator_ops = { + .it_next = queue_iterator_next, + .it_erase = queue_iterator_erase, + .it_close = NULL, + .it_is_valid = queue_iterator_is_valid, +}; + +int b_queue_iterator_begin(const struct b_queue *q, struct b_queue_iterator *it) +{ + it->_q = (struct b_queue *)q; + it->_base.it_ops = &queue_iterator_ops; + it->entry = q->q_first; + it->i = 0; + + return 0; +} + +bool b_queue_iterator_next(struct b_queue_iterator *it) +{ + if (!it->entry) { + return false; + } + + it->entry = it->entry->qe_next; + it->i++; + + return it->entry != NULL; +} + +b_status b_queue_iterator_erase(struct b_queue_iterator *it) +{ + if (!it->entry) { + return B_ERR_OUT_OF_BOUNDS; + } + + struct b_queue_entry *next = it->entry->qe_next; + b_queue_delete(it->_q, it->entry); + it->entry = next; + + return B_SUCCESS; +} + +bool b_queue_iterator_is_valid(const struct b_queue_iterator *it) +{ + return it->entry != NULL; +} diff --git a/core/status.c b/core/status.c new file mode 100644 index 0000000..24c5304 --- /dev/null +++ b/core/status.c @@ -0,0 +1,15 @@ +#include +#include + +#define ENUM_STR(s) \ + case s: \ + return #s; + +const char *b_status_to_string(b_status status) +{ + switch (status) { + ENUM_STR(B_SUCCESS); + default: + return NULL; + } +} diff --git a/core/stringstream.c b/core/stringstream.c new file mode 100644 index 0000000..a368e59 --- /dev/null +++ b/core/stringstream.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include + +void b_stringstream_begin(b_stringstream *ss, char *buf, size_t max) +{ + ss->ss_buf = buf; + ss->ss_max = max; + ss->ss_len = 0; + ss->ss_alloc = 0; +} + +void b_stringstream_begin_dynamic(b_stringstream *ss) +{ + ss->ss_buf = NULL; + ss->ss_max = 0; + ss->ss_len = 0; + ss->ss_alloc = 1; +} + +static b_status ss_builder_push_string(b_stringstream *ss, const char *s, size_t len) +{ + if (ss->ss_len + len >= ss->ss_max && ss->ss_alloc == 1) { + char *new_buf = realloc(ss->ss_buf, ss->ss_len + len + 1); + if (!new_buf) { + return B_ERR_NO_MEMORY; + } + + ss->ss_buf = new_buf; + ss->ss_max = ss->ss_len + len + 1; + } + + for (size_t i = 0; i < len; i++) { + if (ss->ss_len < ss->ss_max) { + ss->ss_buf[ss->ss_len++] = s[i]; + ss->ss_buf[ss->ss_len] = 0; + } + } + + return B_SUCCESS; +} + +b_status b_stringstream_add(struct b_stringstream *ss, const char *str) +{ + return ss_builder_push_string(ss, str, strlen(str)); +} + +b_status b_stringstream_addf(struct b_stringstream *ss, const char *format, ...) +{ + char str[1024]; + va_list arg; + va_start(arg, format); + size_t len = vsnprintf(str, sizeof str, format, arg); + va_end(arg); + + return ss_builder_push_string(ss, str, len); +} + +b_status b_stringstream_addv(b_stringstream *ss, const char **strs) +{ + for (size_t i = 0; strs[i]; i++) { + size_t len = strlen(strs[i]); + b_status status = ss_builder_push_string(ss, strs[i], len); + if (B_ERR(status)) { + return status; + } + } + + return B_SUCCESS; +} + +b_status b_stringstream_addvl(b_stringstream *ss, const char **strs, size_t count) +{ + for (size_t i = 0; i < count; i++) { + if (!strs[i]) { + continue; + } + + size_t len = strlen(strs[i]); + b_status status = ss_builder_push_string(ss, strs[i], len); + if (B_ERR(status)) { + return status; + } + } + + return B_SUCCESS; +} + +b_status b_stringstream_add_many(b_stringstream *ss, ...) +{ + va_list arg; + va_start(arg, ss); + const char *s = NULL; + + while ((s = va_arg(arg, const char *))) { + size_t len = strlen(s); + b_status status = ss_builder_push_string(ss, s, len); + if (B_ERR(status)) { + return status; + } + } + + return B_SUCCESS; +} + +char *b_stringstream_end(b_stringstream *ss) +{ + char *out = ss->ss_buf; + + ss->ss_alloc = 0; + ss->ss_len = 0; + ss->ss_max = 0; + ss->ss_buf = NULL; + + return out; +} diff --git a/include/blue.h b/include/blue.h new file mode 100644 index 0000000..e69de29 diff --git a/misc/AllTests.c b/misc/AllTests.c new file mode 100644 index 0000000..50b135b --- /dev/null +++ b/misc/AllTests.c @@ -0,0 +1,24 @@ +#include "CuTest.h" + +#include + +CuSuite *get_all_tests(void); + +int RunAllTests(void) +{ + CuString *output = CuStringNew(); + CuSuite *suite = CuSuiteNew(); + + CuSuiteAddSuite(suite, get_all_tests()); + + CuSuiteRun(suite); + CuSuiteSummary(suite, output); + CuSuiteDetails(suite, output); + printf("%s\n", output->buffer); + return suite->failCount; +} + +int main(void) +{ + return RunAllTests(); +} diff --git a/misc/CuTest.c b/misc/CuTest.c new file mode 100644 index 0000000..4b14ce9 --- /dev/null +++ b/misc/CuTest.c @@ -0,0 +1,345 @@ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif +#include +#include +#include +#include +#include +#include + +#include "CuTest.h" + +/*-------------------------------------------------------------------------* + * CuStr + *-------------------------------------------------------------------------*/ + +char* CuStrAlloc(size_t size) +{ + char* newStr = (char*) malloc( sizeof(char) * (size) ); + return newStr; +} + +char* CuStrCopy(const char* old) +{ + size_t len = strlen(old); + char* newStr = CuStrAlloc(len + 1); + strcpy(newStr, old); + return newStr; +} + +/*-------------------------------------------------------------------------* + * CuString + *-------------------------------------------------------------------------*/ + +void CuStringInit(CuString* str) +{ + str->length = 0; + str->size = STRING_MAX; + str->buffer = (char*) malloc(sizeof(char) * str->size); + str->buffer[0] = '\0'; +} + +CuString* CuStringNew(void) +{ + CuString* str = (CuString*) malloc(sizeof(CuString)); + str->length = 0; + str->size = STRING_MAX; + str->buffer = (char*) malloc(sizeof(char) * str->size); + str->buffer[0] = '\0'; + return str; +} + +void CuStringDelete(CuString *str) +{ + if (!str) return; + free(str->buffer); + free(str); +} + +void CuStringResize(CuString* str, size_t newSize) +{ + str->buffer = (char*) realloc(str->buffer, sizeof(char) * newSize); + str->size = newSize; +} + +void CuStringAppend(CuString* str, const char* text) +{ + size_t length; + + if (text == NULL) { + text = "NULL"; + } + + length = strlen(text); + if (str->length + length + 1 >= str->size) + CuStringResize(str, str->length + length + 1 + STRING_INC); + str->length += length; + strcat(str->buffer, text); +} + +void CuStringAppendChar(CuString* str, char ch) +{ + char text[2]; + text[0] = ch; + text[1] = '\0'; + CuStringAppend(str, text); +} + +void CuStringAppendFormat(CuString* str, const char* format, ...) +{ + va_list argp; + char buf[HUGE_STRING_LEN]; + va_start(argp, format); + vsprintf(buf, format, argp); + va_end(argp); + CuStringAppend(str, buf); +} + +void CuStringInsert(CuString* str, const char* text, size_t pos) +{ + size_t length = strlen(text); + if (pos > str->length) + pos = str->length; + if (str->length + length + 1 >= str->size) + CuStringResize(str, str->length + length + 1 + STRING_INC); + memmove(str->buffer + pos + length, str->buffer + pos, (str->length - pos) + 1); + str->length += length; + memcpy(str->buffer + pos, text, length); +} + +/*-------------------------------------------------------------------------* + * CuTest + *-------------------------------------------------------------------------*/ + +void CuTestInit(CuTest* t, const char* name, TestFunction function) +{ + t->name = CuStrCopy(name); + t->failed = 0; + t->ran = 0; + t->message = NULL; + t->function = function; + t->jumpBuf = NULL; +} + +CuTest* CuTestNew(const char* name, TestFunction function) +{ + CuTest* tc = CU_ALLOC(CuTest); + CuTestInit(tc, name, function); + return tc; +} + +void CuTestDelete(CuTest *t) +{ + if (!t) return; + CuStringDelete(t->message); + free(t->name); + free(t); +} + +void CuTestRun(CuTest* tc) +{ + jmp_buf buf; + tc->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + tc->ran = 1; + (tc->function)(tc); + } + tc->jumpBuf = 0; +} + +static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string) +{ + char buf[HUGE_STRING_LEN]; + + sprintf(buf, "%s:%d: ", file, line); + CuStringInsert(string, buf, 0); + + tc->failed = 1; + free(tc->message); + tc->message = CuStringNew(); + CuStringAppend(tc->message, string->buffer); + if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0); +} + +void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message) +{ + CuString string; + + CuStringInit(&string); + if (message2 != NULL) + { + CuStringAppend(&string, message2); + CuStringAppend(&string, ": "); + } + CuStringAppend(&string, message); + CuFailInternal(tc, file, line, &string); +} + +void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition) +{ + if (condition) return; + CuFail_Line(tc, file, line, NULL, message); +} + +void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, + const char* expected, const char* actual) +{ + CuString string; + if ((expected == NULL && actual == NULL) || + (expected != NULL && actual != NULL && + strcmp(expected, actual) == 0)) + { + return; + } + + CuStringInit(&string); + if (message != NULL) + { + CuStringAppend(&string, message); + CuStringAppend(&string, ": "); + } + CuStringAppend(&string, "expected <"); + CuStringAppend(&string, expected); + CuStringAppend(&string, "> but was <"); + CuStringAppend(&string, actual); + CuStringAppend(&string, ">"); + CuFailInternal(tc, file, line, &string); +} + +void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, + int expected, int actual) +{ + char buf[STRING_MAX]; + if (expected == actual) return; + sprintf(buf, "expected <%d> but was <%d>", expected, actual); + CuFail_Line(tc, file, line, message, buf); +} + +void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, + double expected, double actual, double delta) +{ + char buf[STRING_MAX]; + if (fabs(expected - actual) <= delta) return; + sprintf(buf, "expected <%f> but was <%f>", expected, actual); + + CuFail_Line(tc, file, line, message, buf); +} + +void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, + void* expected, void* actual) +{ + char buf[STRING_MAX]; + if (expected == actual) return; + sprintf(buf, "expected pointer <0x%p> but was <0x%p>", expected, actual); + CuFail_Line(tc, file, line, message, buf); +} + + +/*-------------------------------------------------------------------------* + * CuSuite + *-------------------------------------------------------------------------*/ + +void CuSuiteInit(CuSuite* testSuite) +{ + testSuite->count = 0; + testSuite->failCount = 0; + memset(testSuite->list, 0, sizeof(testSuite->list)); +} + +CuSuite* CuSuiteNew(void) +{ + CuSuite* testSuite = CU_ALLOC(CuSuite); + CuSuiteInit(testSuite); + return testSuite; +} + +void CuSuiteDelete(CuSuite *testSuite) +{ + unsigned int n; + for (n=0; n < MAX_TEST_CASES; n++) + { + if (testSuite->list[n]) + { + CuTestDelete(testSuite->list[n]); + } + } + free(testSuite); + +} + +void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase) +{ + assert(testSuite->count < MAX_TEST_CASES); + testSuite->list[testSuite->count] = testCase; + testSuite->count++; +} + +void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2) +{ + int i; + for (i = 0 ; i < testSuite2->count ; ++i) + { + CuTest* testCase = testSuite2->list[i]; + CuSuiteAdd(testSuite, testCase); + } +} + +void CuSuiteRun(CuSuite* testSuite) +{ + int i; + for (i = 0 ; i < testSuite->count ; ++i) + { + CuTest* testCase = testSuite->list[i]; + CuTestRun(testCase); + if (testCase->failed) { testSuite->failCount += 1; } + } +} + +void CuSuiteSummary(CuSuite* testSuite, CuString* summary) +{ + int i; + for (i = 0 ; i < testSuite->count ; ++i) + { + CuTest* testCase = testSuite->list[i]; + CuStringAppend(summary, testCase->failed ? "F" : "."); + } + CuStringAppend(summary, "\n\n"); +} + +void CuSuiteDetails(CuSuite* testSuite, CuString* details) +{ + int i; + int failCount = 0; + + if (testSuite->failCount == 0) + { + int passCount = testSuite->count - testSuite->failCount; + const char* testWord = passCount == 1 ? "test" : "tests"; + CuStringAppendFormat(details, "OK (%d %s)\n", passCount, testWord); + } + else + { + if (testSuite->failCount == 1) + CuStringAppend(details, "There was 1 failure:\n"); + else + CuStringAppendFormat(details, "There were %d failures:\n", testSuite->failCount); + + for (i = 0 ; i < testSuite->count ; ++i) + { + CuTest* testCase = testSuite->list[i]; + if (testCase->failed) + { + failCount++; + CuStringAppendFormat(details, "%d) %s: %s\n", + failCount, testCase->name, testCase->message->buffer); + } + } + CuStringAppend(details, "\n!!!FAILURES!!!\n"); + + CuStringAppendFormat(details, "Runs: %d ", testSuite->count); + CuStringAppendFormat(details, "Passes: %d ", testSuite->count - testSuite->failCount); + CuStringAppendFormat(details, "Fails: %d\n", testSuite->failCount); + } +} diff --git a/misc/CuTest.h b/misc/CuTest.h new file mode 100644 index 0000000..1ffb0f8 --- /dev/null +++ b/misc/CuTest.h @@ -0,0 +1,117 @@ +#ifndef CU_TEST_H +#define CU_TEST_H + +#include +#include +#include + +#define CUTEST_VERSION "CuTest 1.5c" + +/* CuString */ + +char* CuStrAlloc(size_t size); +char* CuStrCopy(const char* old); + +#define CU_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE))) + +#define HUGE_STRING_LEN 8192 +#define STRING_MAX 256 +#define STRING_INC 256 + +typedef struct +{ + size_t length; + size_t size; + char* buffer; +} CuString; + +void CuStringInit(CuString* str); +CuString* CuStringNew(void); +void CuStringRead(CuString* str, const char* path); +void CuStringAppend(CuString* str, const char* text); +void CuStringAppendChar(CuString* str, char ch); +void CuStringAppendFormat(CuString* str, const char* format, ...); +void CuStringInsert(CuString* str, const char* text, size_t pos); +void CuStringResize(CuString* str, size_t newSize); +void CuStringDelete(CuString* str); + +/* CuTest */ + +typedef struct CuTest CuTest; + +typedef void (*TestFunction)(CuTest *); + +struct CuTest +{ + char* name; + TestFunction function; + int failed; + int ran; + CuString *message; + jmp_buf *jumpBuf; +}; + +void CuTestInit(CuTest* t, const char* name, TestFunction function); +CuTest* CuTestNew(const char* name, TestFunction function); +void CuTestRun(CuTest* tc); +void CuTestDelete(CuTest *t); + +/* Internal versions of assert functions -- use the public versions */ +void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message); +void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition); +void CuAssertStrEquals_LineMsg(CuTest* tc, + const char* file, int line, const char* message, + const char* expected, const char* actual); +void CuAssertIntEquals_LineMsg(CuTest* tc, + const char* file, int line, const char* message, + int expected, int actual); +void CuAssertDblEquals_LineMsg(CuTest* tc, + const char* file, int line, const char* message, + double expected, double actual, double delta); +void CuAssertPtrEquals_LineMsg(CuTest* tc, + const char* file, int line, const char* message, + void* expected, void* actual); + +/* public assert functions */ + +#define CuFail(tc, ms) CuFail_Line( (tc), __FILE__, __LINE__, NULL, (ms)) +#define CuAssert(tc, ms, cond) CuAssert_Line((tc), __FILE__, __LINE__, (ms), (cond)) +#define CuAssertTrue(tc, cond) CuAssert_Line((tc), __FILE__, __LINE__, "assert failed", (cond)) + +#define CuAssertStrEquals(tc,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) +#define CuAssertStrEquals_Msg(tc,ms,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) +#define CuAssertIntEquals(tc,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) +#define CuAssertIntEquals_Msg(tc,ms,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) +#define CuAssertDblEquals(tc,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac),(dl)) +#define CuAssertDblEquals_Msg(tc,ms,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac),(dl)) +#define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) +#define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) + +#define CuAssertPtrNotNull(tc,p) CuAssert_Line((tc),__FILE__,__LINE__,"null pointer unexpected",((p) != NULL)) +#define CuAssertPtrNotNullMsg(tc,msg,p) CuAssert_Line((tc),__FILE__,__LINE__,(msg),((p) != NULL)) + +/* CuSuite */ + +#define MAX_TEST_CASES 1024 + +#define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST)) + +typedef struct +{ + int count; + CuTest* list[MAX_TEST_CASES]; + int failCount; + +} CuSuite; + + +void CuSuiteInit(CuSuite* testSuite); +CuSuite* CuSuiteNew(void); +void CuSuiteDelete(CuSuite *testSuite); +void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase); +void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2); +void CuSuiteRun(CuSuite* testSuite); +void CuSuiteSummary(CuSuite* testSuite, CuString* summary); +void CuSuiteDetails(CuSuite* testSuite, CuString* details); + +#endif /* CU_TEST_H */ diff --git a/misc/CuTestTest.c b/misc/CuTestTest.c new file mode 100644 index 0000000..110e22b --- /dev/null +++ b/misc/CuTestTest.c @@ -0,0 +1,712 @@ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif +#include +#include +#include +#include +#include + +#include "CuTest.h" + +/*-------------------------------------------------------------------------* + * Helper functions + *-------------------------------------------------------------------------*/ + +#define CompareAsserts(tc, message, expected, actual) X_CompareAsserts((tc), __FILE__, __LINE__, (message), (expected), (actual)) + +static void X_CompareAsserts(CuTest* tc, const char *file, int line, const char* message, const char* expected, CuString *actual) +{ + int mismatch; + if (expected == NULL || actual == NULL || actual==NULL) { + mismatch = (expected != NULL || actual != NULL); + } else { + const char *front = __FILE__ ":"; + const size_t frontLen = strlen(front); + const size_t expectedLen = strlen(expected); + + const char *matchStr = actual->buffer; + + mismatch = (strncmp(matchStr, front, frontLen) != 0); + if (!mismatch) { + matchStr = strchr(matchStr + frontLen, ':'); + mismatch |= (matchStr == NULL || strncmp(matchStr, ": ", 2)); + if (!mismatch) { + matchStr += 2; + mismatch |= (strncmp(matchStr, expected, expectedLen) != 0); + } + } + } + + CuAssert_Line(tc, file, line, message, !mismatch); +} + +/*-------------------------------------------------------------------------* + * CuString Test + *-------------------------------------------------------------------------*/ + +void TestCuStringNew(CuTest* tc) +{ + CuString* str = CuStringNew(); + CuAssertTrue(tc, 0 == str->length); + CuAssertTrue(tc, 0 != str->size); + CuAssertStrEquals(tc, "", str->buffer); +} + + +void TestCuStringAppend(CuTest* tc) +{ + CuString* str = CuStringNew(); + CuStringAppend(str, "hello"); + CuAssertIntEquals(tc, 5, (int)str->length); + CuAssertStrEquals(tc, "hello", str->buffer); + CuStringAppend(str, " world"); + CuAssertIntEquals(tc, 11, (int)str->length); + CuAssertStrEquals(tc, "hello world", str->buffer); +} + + +void TestCuStringAppendNULL(CuTest* tc) +{ + CuString* str = CuStringNew(); + CuStringAppend(str, NULL); + CuAssertIntEquals(tc, 4, (int)str->length); + CuAssertStrEquals(tc, "NULL", str->buffer); +} + + +void TestCuStringAppendChar(CuTest* tc) +{ + CuString* str = CuStringNew(); + CuStringAppendChar(str, 'a'); + CuStringAppendChar(str, 'b'); + CuStringAppendChar(str, 'c'); + CuStringAppendChar(str, 'd'); + CuAssertIntEquals(tc, 4, (int)str->length); + CuAssertStrEquals(tc, "abcd", str->buffer); +} + + +void TestCuStringInserts(CuTest* tc) +{ + CuString* str = CuStringNew(); + CuStringAppend(str, "world"); + CuAssertIntEquals(tc, 5, (int)str->length); + CuAssertStrEquals(tc, "world", str->buffer); + CuStringInsert(str, "hell", 0); + CuAssertIntEquals(tc, 9, (int)str->length); + CuAssertStrEquals(tc, "hellworld", str->buffer); + CuStringInsert(str, "o ", 4); + CuAssertIntEquals(tc, 11, (int)str->length); + CuAssertStrEquals(tc, "hello world", str->buffer); + CuStringInsert(str, "!", 11); + CuAssertIntEquals(tc, 12, (int)str->length); + CuAssertStrEquals(tc, "hello world!", str->buffer); +} + + +void TestCuStringResizes(CuTest* tc) +{ + CuString* str = CuStringNew(); + int i; + for(i = 0 ; i < STRING_MAX ; ++i) + { + CuStringAppend(str, "aa"); + } + CuAssertTrue(tc, STRING_MAX * 2 == str->length); + CuAssertTrue(tc, STRING_MAX * 2 <= str->size); +} + +CuSuite* CuStringGetSuite(void) +{ + CuSuite* suite = CuSuiteNew(); + + SUITE_ADD_TEST(suite, TestCuStringNew); + SUITE_ADD_TEST(suite, TestCuStringAppend); + SUITE_ADD_TEST(suite, TestCuStringAppendNULL); + SUITE_ADD_TEST(suite, TestCuStringAppendChar); + SUITE_ADD_TEST(suite, TestCuStringInserts); + SUITE_ADD_TEST(suite, TestCuStringResizes); + + return suite; +} + +/*-------------------------------------------------------------------------* + * CuTest Test + *-------------------------------------------------------------------------*/ + +void TestPasses(CuTest* tc) +{ + CuAssert(tc, "test should pass", 1 == 0 + 1); +} + +void zTestFails(CuTest* tc) +{ + CuAssert(tc, "test should fail", 1 == 1 + 1); +} + + +void TestCuTestNew(CuTest* tc) +{ + CuTest* tc2 = CuTestNew("MyTest", TestPasses); + CuAssertStrEquals(tc, "MyTest", tc2->name); + CuAssertTrue(tc, !tc2->failed); + CuAssertTrue(tc, tc2->message == NULL); + CuAssertTrue(tc, tc2->function == TestPasses); + CuAssertTrue(tc, tc2->ran == 0); + CuAssertTrue(tc, tc2->jumpBuf == NULL); +} + + +void TestCuTestInit(CuTest *tc) +{ + CuTest tc2; + CuTestInit(&tc2, "MyTest", TestPasses); + CuAssertStrEquals(tc, "MyTest", tc2.name); + CuAssertTrue(tc, !tc2.failed); + CuAssertTrue(tc, tc2.message == NULL); + CuAssertTrue(tc, tc2.function == TestPasses); + CuAssertTrue(tc, tc2.ran == 0); + CuAssertTrue(tc, tc2.jumpBuf == NULL); +} + +void TestCuAssert(CuTest* tc) +{ + CuTest tc2; + CuTestInit(&tc2, "MyTest", TestPasses); + + CuAssert(&tc2, "test 1", 5 == 4 + 1); + CuAssertTrue(tc, !tc2.failed); + CuAssertTrue(tc, tc2.message == NULL); + + CuAssert(&tc2, "test 2", 0); + CuAssertTrue(tc, tc2.failed); + CompareAsserts(tc, "CuAssert didn't fail", "test 2", tc2.message); + + CuAssert(&tc2, "test 3", 1); + CuAssertTrue(tc, tc2.failed); + CompareAsserts(tc, "CuAssert didn't fail", "test 2", tc2.message); + + CuAssert(&tc2, "test 4", 0); + CuAssertTrue(tc, tc2.failed); + CompareAsserts(tc, "CuAssert didn't fail", "test 4", tc2.message); + +} + +void TestCuAssertPtrEquals_Success(CuTest* tc) +{ + CuTest tc2; + int x; + + CuTestInit(&tc2, "MyTest", TestPasses); + + /* test success case */ + CuAssertPtrEquals(&tc2, &x, &x); + CuAssertTrue(tc, ! tc2.failed); + CuAssertTrue(tc, tc2.message == NULL); +} + +void TestCuAssertPtrEquals_Failure(CuTest* tc) +{ + CuTest tc2; + int x; + int* nullPtr = NULL; + char expected_message[STRING_MAX]; + + CuTestInit(&tc2, "MyTest", TestPasses); + + /* test failing case */ + sprintf(expected_message, "expected pointer <0x%p> but was <0x%p>", (void*)nullPtr, (void*)&x); + CuAssertPtrEquals(&tc2, NULL, &x); + CuAssertTrue(tc, tc2.failed); + CompareAsserts(tc, "CuAssertPtrEquals failed", expected_message, tc2.message); +} + +void TestCuAssertPtrNotNull_Success(CuTest* tc) +{ + CuTest tc2; + int x; + + CuTestInit(&tc2, "MyTest", TestPasses); + + /* test success case */ + CuAssertPtrNotNull(&tc2, &x); + CuAssertTrue(tc, ! tc2.failed); + CuAssertTrue(tc, tc2.message == NULL); +} + +void TestCuAssertPtrNotNull_Failure(CuTest* tc) +{ + CuTest tc2; + + CuTestInit(&tc2, "MyTest", TestPasses); + + /* test failing case */ + CuAssertPtrNotNull(&tc2, NULL); + CuAssertTrue(tc, tc2.failed); + CompareAsserts(tc, "CuAssertPtrNotNull failed", "null pointer unexpected", tc2.message); +} + +void TestCuTestRun(CuTest* tc) +{ + CuTest tc2; + CuTestInit(&tc2, "MyTest", zTestFails); + CuTestRun(&tc2); + + CuAssertStrEquals(tc, "MyTest", tc2.name); + CuAssertTrue(tc, tc2.failed); + CuAssertTrue(tc, tc2.ran); + CompareAsserts(tc, "TestRun failed", "test should fail", tc2.message); +} + +/*-------------------------------------------------------------------------* + * CuSuite Test + *-------------------------------------------------------------------------*/ + +void TestCuSuiteInit(CuTest* tc) +{ + CuSuite ts; + CuSuiteInit(&ts); + CuAssertTrue(tc, ts.count == 0); + CuAssertTrue(tc, ts.failCount == 0); +} + +void TestCuSuiteNew(CuTest* tc) +{ + CuSuite* ts = CuSuiteNew(); + CuAssertTrue(tc, ts->count == 0); + CuAssertTrue(tc, ts->failCount == 0); +} + +void TestCuSuiteAddTest(CuTest* tc) +{ + CuSuite ts; + CuTest tc2; + + CuSuiteInit(&ts); + CuTestInit(&tc2, "MyTest", zTestFails); + + CuSuiteAdd(&ts, &tc2); + CuAssertTrue(tc, ts.count == 1); + + CuAssertStrEquals(tc, "MyTest", ts.list[0]->name); +} + +void TestCuSuiteAddSuite(CuTest* tc) +{ + CuSuite* ts1 = CuSuiteNew(); + CuSuite* ts2 = CuSuiteNew(); + + CuSuiteAdd(ts1, CuTestNew("TestFails1", zTestFails)); + CuSuiteAdd(ts1, CuTestNew("TestFails2", zTestFails)); + + CuSuiteAdd(ts2, CuTestNew("TestFails3", zTestFails)); + CuSuiteAdd(ts2, CuTestNew("TestFails4", zTestFails)); + + CuSuiteAddSuite(ts1, ts2); + CuAssertIntEquals(tc, 4, ts1->count); + + CuAssertStrEquals(tc, "TestFails1", ts1->list[0]->name); + CuAssertStrEquals(tc, "TestFails2", ts1->list[1]->name); + CuAssertStrEquals(tc, "TestFails3", ts1->list[2]->name); + CuAssertStrEquals(tc, "TestFails4", ts1->list[3]->name); +} + +void TestCuSuiteRun(CuTest* tc) +{ + CuSuite ts; + CuTest tc1, tc2, tc3, tc4; + + CuSuiteInit(&ts); + CuTestInit(&tc1, "TestPasses", TestPasses); + CuTestInit(&tc2, "TestPasses", TestPasses); + CuTestInit(&tc3, "TestFails", zTestFails); + CuTestInit(&tc4, "TestFails", zTestFails); + + CuSuiteAdd(&ts, &tc1); + CuSuiteAdd(&ts, &tc2); + CuSuiteAdd(&ts, &tc3); + CuSuiteAdd(&ts, &tc4); + CuAssertTrue(tc, ts.count == 4); + + CuSuiteRun(&ts); + CuAssertTrue(tc, ts.count - ts.failCount == 2); + CuAssertTrue(tc, ts.failCount == 2); +} + +void TestCuSuiteSummary(CuTest* tc) +{ + CuSuite ts; + CuTest tc1, tc2; + CuString summary; + + CuSuiteInit(&ts); + CuTestInit(&tc1, "TestPasses", TestPasses); + CuTestInit(&tc2, "TestFails", zTestFails); + CuStringInit(&summary); + + CuSuiteAdd(&ts, &tc1); + CuSuiteAdd(&ts, &tc2); + CuSuiteRun(&ts); + + CuSuiteSummary(&ts, &summary); + + CuAssertTrue(tc, ts.count == 2); + CuAssertTrue(tc, ts.failCount == 1); + CuAssertStrEquals(tc, ".F\n\n", summary.buffer); +} + + +void TestCuSuiteDetails_SingleFail(CuTest* tc) +{ + CuSuite ts; + CuTest tc1, tc2; + CuString details; + const char* front; + const char* back; + + CuSuiteInit(&ts); + CuTestInit(&tc1, "TestPasses", TestPasses); + CuTestInit(&tc2, "TestFails", zTestFails); + CuStringInit(&details); + + CuSuiteAdd(&ts, &tc1); + CuSuiteAdd(&ts, &tc2); + CuSuiteRun(&ts); + + CuSuiteDetails(&ts, &details); + + CuAssertTrue(tc, ts.count == 2); + CuAssertTrue(tc, ts.failCount == 1); + + front = "There was 1 failure:\n" + "1) TestFails: "; + back = "test should fail\n" + "\n!!!FAILURES!!!\n" + "Runs: 2 Passes: 1 Fails: 1\n"; + + CuAssertStrEquals(tc, back, details.buffer + strlen(details.buffer) - strlen(back)); + details.buffer[strlen(front)] = 0; + CuAssertStrEquals(tc, front, details.buffer); +} + + +void TestCuSuiteDetails_SinglePass(CuTest* tc) +{ + CuSuite ts; + CuTest tc1; + CuString details; + const char* expected; + + CuSuiteInit(&ts); + CuTestInit(&tc1, "TestPasses", TestPasses); + CuStringInit(&details); + + CuSuiteAdd(&ts, &tc1); + CuSuiteRun(&ts); + + CuSuiteDetails(&ts, &details); + + CuAssertTrue(tc, ts.count == 1); + CuAssertTrue(tc, ts.failCount == 0); + + expected = + "OK (1 test)\n"; + + CuAssertStrEquals(tc, expected, details.buffer); +} + +void TestCuSuiteDetails_MultiplePasses(CuTest* tc) +{ + CuSuite ts; + CuTest tc1, tc2; + CuString details; + const char* expected; + + CuSuiteInit(&ts); + CuTestInit(&tc1, "TestPasses", TestPasses); + CuTestInit(&tc2, "TestPasses", TestPasses); + CuStringInit(&details); + + CuSuiteAdd(&ts, &tc1); + CuSuiteAdd(&ts, &tc2); + CuSuiteRun(&ts); + + CuSuiteDetails(&ts, &details); + + CuAssertTrue(tc, ts.count == 2); + CuAssertTrue(tc, ts.failCount == 0); + + expected = + "OK (2 tests)\n"; + + CuAssertStrEquals(tc, expected, details.buffer); +} + +void TestCuSuiteDetails_MultipleFails(CuTest* tc) +{ + CuSuite ts; + CuTest tc1, tc2; + CuString details; + const char* front; + const char* mid; + const char* back; + + CuSuiteInit(&ts); + CuTestInit(&tc1, "TestFails1", zTestFails); + CuTestInit(&tc2, "TestFails2", zTestFails); + CuStringInit(&details); + + CuSuiteAdd(&ts, &tc1); + CuSuiteAdd(&ts, &tc2); + CuSuiteRun(&ts); + + CuSuiteDetails(&ts, &details); + + CuAssertTrue(tc, ts.count == 2); + CuAssertTrue(tc, ts.failCount == 2); + + front = + "There were 2 failures:\n" + "1) TestFails1: "; + mid = "test should fail\n" + "2) TestFails2: "; + back = "test should fail\n" + "\n!!!FAILURES!!!\n" + "Runs: 2 Passes: 0 Fails: 2\n"; + + CuAssertStrEquals(tc, back, details.buffer + strlen(details.buffer) - strlen(back)); + CuAssert(tc, "Couldn't find middle", strstr(details.buffer, mid) != NULL); + details.buffer[strlen(front)] = 0; + CuAssertStrEquals(tc, front, details.buffer); +} + + +/*-------------------------------------------------------------------------* + * Misc Test + *-------------------------------------------------------------------------*/ + +void TestCuStrCopy(CuTest* tc) +{ + const char* old = "hello world"; + const char* newStr = CuStrCopy(old); + CuAssert(tc, "old is new", strcmp(old, newStr) == 0); +} + + +void TestCuStringAppendFormat(CuTest* tc) +{ + int i; + char* text = CuStrAlloc(301); /* long string */ + CuString* str = CuStringNew(); + for (i = 0 ; i < 300 ; ++i) + text[i] = 'a'; + text[300] = '\0'; + CuStringAppendFormat(str, "%s", text); + + /* buffer limit raised to HUGE_STRING_LEN so no overflow */ + + CuAssert(tc, "length of str->buffer is 300", 300 == strlen(str->buffer)); +} + +void TestFail(CuTest* tc) +{ + jmp_buf buf; + int pointReached = 0; + CuTest* tc2 = CuTestNew("TestFails", zTestFails); + tc2->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + CuFail(tc2, "hello world"); + pointReached = 1; + } + CuAssert(tc, "point was not reached", pointReached == 0); +} + +void TestAssertStrEquals(CuTest* tc) +{ + jmp_buf buf; + CuTest *tc2 = CuTestNew("TestAssertStrEquals", zTestFails); + + const char* expected = "expected but was "; + const char *expectedMsg = "some text: expected but was "; + + tc2->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + CuAssertStrEquals(tc2, "hello", "world"); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertStrEquals failed", expected, tc2->message); + if (setjmp(buf) == 0) + { + CuAssertStrEquals_Msg(tc2, "some text", "hello", "world"); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertStrEquals failed", expectedMsg, tc2->message); +} + +void TestAssertStrEquals_NULL(CuTest* tc) +{ + jmp_buf buf; + CuTest *tc2 = CuTestNew("TestAssertStrEquals_NULL", zTestFails); + + tc2->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + CuAssertStrEquals(tc2, NULL, NULL); + } + CuAssertTrue(tc, !tc2->failed); + CompareAsserts(tc, "CuAssertStrEquals_NULL failed", NULL, tc2->message); + if (setjmp(buf) == 0) + { + CuAssertStrEquals_Msg(tc2, "some text", NULL, NULL); + } + CuAssertTrue(tc, !tc2->failed); + CompareAsserts(tc, "CuAssertStrEquals_NULL failed", NULL, tc2->message); +} + +void TestAssertStrEquals_FailNULLStr(CuTest* tc) +{ + jmp_buf buf; + CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailNULLStr", zTestFails); + + const char* expected = "expected but was "; + const char *expectedMsg = "some text: expected but was "; + + tc2->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + CuAssertStrEquals(tc2, "hello", NULL); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertStrEquals_FailNULLStr failed", expected, tc2->message); + if (setjmp(buf) == 0) + { + CuAssertStrEquals_Msg(tc2, "some text", "hello", NULL); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertStrEquals_FailNULLStr failed", expectedMsg, tc2->message); +} + +void TestAssertStrEquals_FailStrNULL(CuTest* tc) +{ + jmp_buf buf; + CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailStrNULL", zTestFails); + + const char* expected = "expected but was "; + const char *expectedMsg = "some text: expected but was "; + + tc2->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + CuAssertStrEquals(tc2, NULL, "hello"); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertStrEquals_FailStrNULL failed", expected, tc2->message); + if (setjmp(buf) == 0) + { + CuAssertStrEquals_Msg(tc2, "some text", NULL, "hello"); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertStrEquals_FailStrNULL failed", expectedMsg, tc2->message); +} + +void TestAssertIntEquals(CuTest* tc) +{ + jmp_buf buf; + CuTest *tc2 = CuTestNew("TestAssertIntEquals", zTestFails); + const char* expected = "expected <42> but was <32>"; + const char* expectedMsg = "some text: expected <42> but was <32>"; + tc2->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + CuAssertIntEquals(tc2, 42, 32); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertIntEquals failed", expected, tc2->message); + if (setjmp(buf) == 0) + { + CuAssertIntEquals_Msg(tc2, "some text", 42, 32); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertStrEquals failed", expectedMsg, tc2->message); +} + +void TestAssertDblEquals(CuTest* tc) +{ + jmp_buf buf; + double x = 3.33; + double y = 10.0 / 3.0; + CuTest *tc2 = CuTestNew("TestAssertDblEquals", zTestFails); + char expected[STRING_MAX]; + char expectedMsg[STRING_MAX]; + sprintf(expected, "expected <%lf> but was <%lf>", x, y); + sprintf(expectedMsg, "some text: expected <%lf> but was <%lf>", x, y); + + CuTestInit(tc2, "TestAssertDblEquals", TestPasses); + + CuAssertDblEquals(tc2, x, x, 0.0); + CuAssertTrue(tc, ! tc2->failed); + CuAssertTrue(tc, tc2->message == NULL); + + CuAssertDblEquals(tc2, x, y, 0.01); + CuAssertTrue(tc, ! tc2->failed); + CuAssertTrue(tc, tc2->message == NULL); + + tc2->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + CuAssertDblEquals(tc2, x, y, 0.001); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertDblEquals failed", expected, tc2->message); + tc2->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + CuAssertDblEquals_Msg(tc2, "some text", x, y, 0.001); + } + CuAssertTrue(tc, tc2->failed); + CompareAsserts(tc, "CuAssertDblEquals failed", expectedMsg, tc2->message); +} + +/*-------------------------------------------------------------------------* + * main + *-------------------------------------------------------------------------*/ + +CuSuite* CuGetSuite(void) +{ + CuSuite* suite = CuSuiteNew(); + + SUITE_ADD_TEST(suite, TestCuStringAppendFormat); + SUITE_ADD_TEST(suite, TestCuStrCopy); + SUITE_ADD_TEST(suite, TestFail); + SUITE_ADD_TEST(suite, TestAssertStrEquals); + SUITE_ADD_TEST(suite, TestAssertStrEquals_NULL); + SUITE_ADD_TEST(suite, TestAssertStrEquals_FailStrNULL); + SUITE_ADD_TEST(suite, TestAssertStrEquals_FailNULLStr); + SUITE_ADD_TEST(suite, TestAssertIntEquals); + SUITE_ADD_TEST(suite, TestAssertDblEquals); + + SUITE_ADD_TEST(suite, TestCuTestNew); + SUITE_ADD_TEST(suite, TestCuTestInit); + SUITE_ADD_TEST(suite, TestCuAssert); + SUITE_ADD_TEST(suite, TestCuAssertPtrEquals_Success); + SUITE_ADD_TEST(suite, TestCuAssertPtrEquals_Failure); + SUITE_ADD_TEST(suite, TestCuAssertPtrNotNull_Success); + SUITE_ADD_TEST(suite, TestCuAssertPtrNotNull_Failure); + SUITE_ADD_TEST(suite, TestCuTestRun); + + SUITE_ADD_TEST(suite, TestCuSuiteInit); + SUITE_ADD_TEST(suite, TestCuSuiteNew); + SUITE_ADD_TEST(suite, TestCuSuiteAddTest); + SUITE_ADD_TEST(suite, TestCuSuiteAddSuite); + SUITE_ADD_TEST(suite, TestCuSuiteRun); + SUITE_ADD_TEST(suite, TestCuSuiteSummary); + SUITE_ADD_TEST(suite, TestCuSuiteDetails_SingleFail); + SUITE_ADD_TEST(suite, TestCuSuiteDetails_SinglePass); + SUITE_ADD_TEST(suite, TestCuSuiteDetails_MultiplePasses); + SUITE_ADD_TEST(suite, TestCuSuiteDetails_MultipleFails); + + return suite; +}