initial commit
This commit is contained in:
58
.clang-format
Normal file
58
.clang-format
Normal file
@@ -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
|
||||||
169
.gitignore
vendored
Normal file
169
.gitignore
vendored
Normal file
@@ -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/
|
||||||
22
CMakeLists.txt
Normal file
22
CMakeLists.txt
Normal file
@@ -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)
|
||||||
32
LICENSE
Normal file
32
LICENSE
Normal file
@@ -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.
|
||||||
30
cmake/Templates.cmake
Normal file
30
cmake/Templates.cmake
Normal file
@@ -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 $<TARGET_OBJECTS:blue-${module_name}-obj>)
|
||||||
|
message(STATUS "Building module ${module_name} (static)")
|
||||||
|
add_library(blue-${module_name}-s STATIC $<TARGET_OBJECTS:blue-${module_name}-obj>)
|
||||||
|
|
||||||
|
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)
|
||||||
183
core-test/core-test.c
Normal file
183
core-test/core-test.c
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#include "blue/core/misc.h"
|
||||||
|
|
||||||
|
#include <CuTest.h>
|
||||||
|
#include <blue/core/btree.h>
|
||||||
|
#include <blue/core/queue.h>
|
||||||
|
#include <blue/core/stringstream.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
3
core/CMakeLists.txt
Normal file
3
core/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
include(../cmake/Templates.cmake)
|
||||||
|
|
||||||
|
add_bluelib_module(core)
|
||||||
821
core/btree.c
Normal file
821
core/btree.c
Normal file
@@ -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 <blue/core/btree.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
0
core/include/blue/core.h
Normal file
0
core/include/blue/core.h
Normal file
357
core/include/blue/core/btree.h
Normal file
357
core/include/blue/core/btree.h
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
#ifndef BLUELIB_CORE_BTREE_H_
|
||||||
|
#define BLUELIB_CORE_BTREE_H_
|
||||||
|
|
||||||
|
#include <blue/core/iterator.h>
|
||||||
|
#include <blue/core/misc.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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
|
||||||
0
core/include/blue/core/exception.h
Normal file
0
core/include/blue/core/exception.h
Normal file
25
core/include/blue/core/iterator.h
Normal file
25
core/include/blue/core/iterator.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef BLUELIB_CORE_ITERATOR_H_
|
||||||
|
#define BLUELIB_CORE_ITERATOR_H_
|
||||||
|
|
||||||
|
#include <blue/core/status.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
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
|
||||||
13
core/include/blue/core/misc.h
Normal file
13
core/include/blue/core/misc.h
Normal file
@@ -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_
|
||||||
88
core/include/blue/core/queue.h
Normal file
88
core/include/blue/core/queue.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#ifndef BLUELIB_CORE_QUEUE_H_
|
||||||
|
#define BLUELIB_CORE_QUEUE_H_
|
||||||
|
|
||||||
|
#include <blue/core/iterator.h>
|
||||||
|
#include <blue/core/status.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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
|
||||||
23
core/include/blue/core/status.h
Normal file
23
core/include/blue/core/status.h
Normal file
@@ -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
|
||||||
30
core/include/blue/core/stringstream.h
Normal file
30
core/include/blue/core/stringstream.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef BLUELIB_CORE_STRINGSTREAM_H_
|
||||||
|
#define BLUELIB_CORE_STRINGSTREAM_H_
|
||||||
|
|
||||||
|
#include <blue/core/status.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
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
|
||||||
37
core/iterator.c
Normal file
37
core/iterator.c
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include <blue/core/iterator.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
196
core/queue.c
Normal file
196
core/queue.c
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#include <blue/core/queue.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
15
core/status.c
Normal file
15
core/status.c
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#include <blue/core/status.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
118
core/stringstream.c
Normal file
118
core/stringstream.c
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#include <blue/core/stringstream.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
0
include/blue.h
Normal file
0
include/blue.h
Normal file
24
misc/AllTests.c
Normal file
24
misc/AllTests.c
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#include "CuTest.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
345
misc/CuTest.c
Normal file
345
misc/CuTest.c
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
#ifdef _MSC_VER
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
#include <assert.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
117
misc/CuTest.h
Normal file
117
misc/CuTest.h
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#ifndef CU_TEST_H
|
||||||
|
#define CU_TEST_H
|
||||||
|
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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 */
|
||||||
712
misc/CuTestTest.c
Normal file
712
misc/CuTestTest.c
Normal file
@@ -0,0 +1,712 @@
|
|||||||
|
#ifdef _MSC_VER
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
#include <assert.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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 <hello> but was <world>";
|
||||||
|
const char *expectedMsg = "some text: expected <hello> but was <world>";
|
||||||
|
|
||||||
|
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 <hello> but was <NULL>";
|
||||||
|
const char *expectedMsg = "some text: expected <hello> but was <NULL>";
|
||||||
|
|
||||||
|
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 <NULL> but was <hello>";
|
||||||
|
const char *expectedMsg = "some text: expected <NULL> but was <hello>";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user