4 Commits 05281845c0 ... 2e73e51e42

Author SHA1 Message Date
  Kajetan Johannes Hammerle 2e73e51e42 More adaptions for release build 2 months ago
  Kajetan Johannes Hammerle 93bb1907eb Better build support for profiles 2 months ago
  Kajetan Johannes Hammerle 55f682838d Size check for list 2 months ago
  Kajetan Johannes Hammerle 9ab253e168 Fix release build again 2 months ago

+ 2 - 1
.gitignore

@@ -1,4 +1,5 @@
 .vscode
-build
+build_debug
+build_release
 install
 profile

+ 17 - 5
CMakeLists.txt

@@ -67,9 +67,21 @@ set(SRC_PERFORMANCE
     "test/Test.cpp"
 )
 
+if("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
+    set(DEBUG_COMPILE "")
+    set(DEBUG_LINK "")
+    set(LOG_LEVEL 2)
+    set(ERROR_SIMULATOR "")
+else()
+    set(DEBUG_COMPILE --coverage)
+    set(DEBUG_LINK gcov)
+    set(LOG_LEVEL 4)
+    set(ERROR_SIMULATOR "ERROR_SIMULATOR")
+endif()
+
 add_library(core STATIC ${SRC})
 target_compile_options(core PUBLIC
-    --coverage
+    ${DEBUG_COMPILE}
     -fdiagnostics-color=always
     -fno-exceptions
     -fno-rtti
@@ -167,12 +179,12 @@ target_compile_options(core PUBLIC
     -Wzero-as-null-pointer-constant
 )
 target_compile_definitions(core 
-    PUBLIC CORE_LOG_LEVEL=4
-    PRIVATE ERROR_SIMULATOR=true
+    PUBLIC CORE_LOG_LEVEL=${LOG_LEVEL}
+    PRIVATE ${ERROR_SIMULATOR}
 )
 target_link_libraries(core 
     PUBLIC -nodefaultlibs c m
-    PRIVATE gcov
+    PRIVATE ${DEBUG_LINK}
 )
 target_sources(core PUBLIC 
     FILE_SET HEADERS
@@ -221,7 +233,7 @@ install(TARGETS core FILE_SET HEADERS)
 
 add_executable(test ${SRC_TESTS})
 target_link_libraries(test PRIVATE core)
-target_compile_definitions(test PRIVATE ERROR_SIMULATOR=true)
+target_compile_definitions(test PRIVATE ${ERROR_SIMULATOR})
 
 add_executable(performance ${SRC_PERFORMANCE})
 target_link_libraries(performance PRIVATE core)

+ 23 - 19
include/core/data/List.hpp

@@ -65,28 +65,14 @@ namespace Core {
             if(n <= capacity) {
                 return Error::NONE;
             }
-            List copy;
-            CORE_RETURN_ERROR(allocate(copy.data, n));
-            copy.capacity = n;
-            for(T& t : *this) {
-                copy.unsafeAdd(Core::move(t));
-            }
-            swap(copy);
-            return Error::NONE;
+            return setSize(n);
         }
 
         check_return Error shrink() {
             if(length == capacity) {
                 return Error::NONE;
             }
-            List copy;
-            CORE_RETURN_ERROR(allocate(copy.data, length));
-            copy.capacity = length;
-            for(int i = 0; i < length; i++) {
-                copy.unsafeAdd(Core::move(data[i]));
-            }
-            swap(copy);
-            return Error::NONE;
+            return setSize(length);
         }
 
         check_return Error resize(int n, const T& t) {
@@ -209,9 +195,16 @@ namespace Core {
         }
 
         check_return Error ensureCapacity() {
-            return length >= capacity
-                       ? reserve(capacity + Core::Math::max(4, capacity / 4))
-                       : Error::NONE;
+            if(length == CORE_INT_MAX) {
+                return Error::CAPACITY_REACHED;
+            } else if(length < capacity) {
+                return Error::NONE;
+            }
+            int add = Core::Math::max(4, capacity / 4);
+            if(capacity > CORE_INT_MAX - add) {
+                return reserve(CORE_INT_MAX);
+            }
+            return reserve(capacity + add);
         }
 
         // does not check for capacity
@@ -219,6 +212,17 @@ namespace Core {
         T* unsafeAdd(Args&&... args) {
             return new(data + length++) T(Core::forward<Args>(args)...);
         }
+
+        check_return Error setSize(int n) {
+            List copy;
+            CORE_RETURN_ERROR(allocate(copy.data, n));
+            copy.capacity = n;
+            for(int i = 0; i < length; i++) {
+                copy.unsafeAdd(Core::move(data[i]));
+            }
+            swap(copy);
+            return Error::NONE;
+        }
     };
 }
 

+ 16 - 13
include/core/data/ProbingHashMap.hpp

@@ -164,7 +164,8 @@ namespace Core {
                 return Error::NONE;
             }
             ProbingHashMap<K, V> map;
-            int l = Core::Math::max(1 << Math::roundUpLog2(minCapacity), 8);
+            int l = static_cast<int>(Core::Math::max(
+                1u << static_cast<u32>(Math::roundUpLog2(minCapacity)), 8u));
             CORE_RETURN_ERROR(map.keys.resize(l, emptyValue<K>()));
             map.values = reinterpret_cast<V*>(new AlignedType<V>[l]);
             if(map.values == nullptr) {
@@ -281,11 +282,11 @@ namespace Core {
             int rehashFactor = 2;
             while(true) {
                 CORE_RETURN_ERROR(rehash(entries * rehashFactor + 1));
-                int baseHash = static_cast<int>(hashCode(key) * 514685581u);
-                int end = keys.getLength() - 1;
+                u32 baseHash = hashCode(key) * 514685581u;
+                u32 end = static_cast<u32>(keys.getLength()) - 1;
                 // rehash on bad clustering
-                for(int i = 0; i <= 5; i++) {
-                    int hash = (baseHash + i) & end;
+                for(u32 i = 0; i <= 5; i++) {
+                    int hash = static_cast<int>((baseHash + i) & end);
                     if(keys[hash] == emptyValue<K>() || keys[hash] == key) {
                         slot = hash;
                         return Core::Error::NONE;
@@ -297,14 +298,16 @@ namespace Core {
 
         template<typename Value>
         Value* searchValue(const K& key) const {
-            int baseHash = static_cast<int>(hashCode(key) * 514685581u);
-            int end = keys.getLength() - 1;
-            for(int i = 0; i <= end; i++) [[unlikely]] {
-                int hash = (baseHash + i) & end;
-                if(keys[hash] == key) [[likely]] {
-                    return values + hash;
-                } else if(keys[hash] == emptyValue<K>()) {
-                    return nullptr;
+            if(keys.getLength() != 0) {
+                u32 baseHash = hashCode(key) * 514685581u;
+                u32 end = static_cast<u32>(keys.getLength()) - 1;
+                for(u32 i = 0; i <= end; i++) [[unlikely]] {
+                    int hash = static_cast<int>((baseHash + i) & end);
+                    if(keys[hash] == key) [[likely]] {
+                        return values + hash;
+                    } else if(keys[hash] == emptyValue<K>()) {
+                        return nullptr;
+                    }
                 }
             }
             return nullptr;

+ 30 - 9
include/core/utils/Logger.hpp

@@ -7,6 +7,12 @@ namespace Core::Logger {
     enum class Level { ERROR, WARNING, INFO, DEBUG };
     extern Level level;
 
+    [[maybe_unused]] static constexpr const char* COLOR_RED = "\33[1;31m";
+    [[maybe_unused]] static constexpr const char* COLOR_YELLOW = "\33[1;33m";
+    [[maybe_unused]] static constexpr const char* COLOR_GRAY = "\33[1;37m";
+    [[maybe_unused]] static constexpr const char* COLOR_GREEN = "\33[1;32m";
+    [[maybe_unused]] static constexpr const char* COLOR_RESET = "\33[0m";
+
     const char* getFileName(const char* path);
 
     inline bool filterError(Error e) {
@@ -15,25 +21,37 @@ namespace Core::Logger {
 
     // aborts on critical logging failure
     template<typename... Args>
-    void log(Level l, const char* file, int line, const char* start,
-             const char* format, Args&&... args) {
+    void log(Level l, const char* file, int line, const char* prefix,
+             const char* tag, const char* format, Args&&... args) {
         if(Core::Logger::level < l) {
             return;
         }
         file = getFileName(file);
         Core::String32<2048> s;
-        if(filterError(s.append(start)) || filterError(s.append("#:# | ")) ||
+        if(filterError(s.append(prefix)) || filterError(s.append(tag)) ||
+           filterError(s.append("#:# | ")) ||
            filterError(s.format(file, line)) || filterError(s.append(format)) ||
            filterError(s.format(Core::forward<Args>(args)...)) ||
-           filterError(s.append("\33[0m\n")) || filterError(s.print())) {
-            CORE_EXIT(1);
+           filterError(s.append(COLOR_RESET)) || filterError(s.printLine())) {
+            CORE_EXIT(1); // CoverageIgnore
+        }
+    }
+
+    template<typename... Args>
+    void log(const char* prefix, const char* format, Args&&... args) {
+        Core::String32<2048> s;
+        if(filterError(s.append(prefix)) || filterError(s.append(format)) ||
+           filterError(s.format(Core::forward<Args>(args)...)) ||
+           filterError(s.append(COLOR_RESET)) || filterError(s.printLine())) {
+            CORE_EXIT(1); // CoverageIgnore
         }
     }
 }
 
 #if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 1
 #define CORE_LOG_ERROR(format, ...)                                            \
-    log(Core::Logger::Level::ERROR, __FILE__, __LINE__, "\33[1;31m[ERROR] ",   \
+    log(Core::Logger::Level::ERROR, __FILE__, __LINE__,                        \
+        Core::Logger::COLOR_RED, "[ERROR] ",                                   \
         format __VA_OPT__(, ) __VA_ARGS__);
 #else
 #define CORE_LOG_ERROR(format, ...)
@@ -42,14 +60,16 @@ namespace Core::Logger {
 #if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 2
 #define CORE_LOG_WARNING(format, ...)                                          \
     log(Core::Logger::Level::WARNING, __FILE__, __LINE__,                      \
-        "\33[1;33m[WARNING] ", format __VA_OPT__(, ) __VA_ARGS__);
+        Core::Logger::COLOR_YELLOW, "[WARNING] ",                              \
+        format __VA_OPT__(, ) __VA_ARGS__);
 #else
 #define CORE_LOG_WARNING(format, ...)
 #endif
 
 #if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 3
 #define CORE_LOG_INFO(format, ...)                                             \
-    log(Core::Logger::Level::INFO, __FILE__, __LINE__, "\33[1;37m[INFO] ",     \
+    log(Core::Logger::Level::INFO, __FILE__, __LINE__,                         \
+        Core::Logger::COLOR_GRAY, "[INFO] ",                                   \
         format __VA_OPT__(, ) __VA_ARGS__);
 #else
 #define CORE_LOG_INFO(format, ...)
@@ -57,7 +77,8 @@ namespace Core::Logger {
 
 #if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 4
 #define CORE_LOG_DEBUG(format, ...)                                            \
-    log(Core::Logger::Level::DEBUG, __FILE__, __LINE__, "\33[1;32m[DEBUG] ",   \
+    log(Core::Logger::Level::DEBUG, __FILE__, __LINE__,                        \
+        Core::Logger::COLOR_GREEN, "[DEBUG] ",                                 \
         format __VA_OPT__(, ) __VA_ARGS__);
 #else
 #define CORE_LOG_DEBUG(format, ...)

+ 7 - 0
include/core/utils/Types.hpp

@@ -1,6 +1,7 @@
 #ifndef CORE_TYPES_HPP
 #define CORE_TYPES_HPP
 
+#include <limits.h>
 #include <stdint.h>
 
 using i64 = int64_t;
@@ -13,4 +14,10 @@ using u16 = uint16_t;
 using u8 = uint8_t;
 using c32 = char32_t;
 
+// user customizable max container size
+#ifndef CORE_INT_MAX
+#define CORE_INT_MAX 65535
+#endif
+static_assert(CORE_INT_MAX < INT_MAX, "custom int max too large");
+
 #endif

+ 28 - 24
performance/Main.cpp

@@ -4,24 +4,25 @@
 #include "core/utils/Clock.hpp"
 #include "core/utils/Random.hpp"
 
-using Millis = Core::Clock::Nanos;
+using Nanos = Core::Clock::Nanos;
+namespace Logger = Core::Logger;
 
 struct Timer {
-    Core::Clock::Nanos nanos;
+    Nanos nanos;
 
     Timer() : nanos(0) {
         CORE_TEST_ERROR(Core::Clock::getNanos(nanos));
     }
 
-    Core::Clock::Nanos get() const {
-        Core::Clock::Nanos nanos2 = 0;
+    Nanos get() const {
+        Nanos nanos2 = 0;
         CORE_TEST_ERROR(Core::Clock::getNanos(nanos2));
         return nanos2 - nanos;
     }
 };
 
 template<typename Map>
-static Core::Clock::Nanos testSearch(const Map& m) {
+static Nanos testSearch(const Map& m) {
     Timer t;
     volatile int sum = 0;
     for(int i = 0; i < 10000; i++) {
@@ -36,7 +37,7 @@ static Core::Clock::Nanos testSearch(const Map& m) {
 }
 
 template<typename Map>
-static Core::Clock::Nanos testEmptySearch(const Map& m) {
+static Nanos testEmptySearch(const Map& m) {
     Timer t;
     volatile int sum = 0;
     for(int i = 0; i < 100'000'000; i++) {
@@ -65,12 +66,12 @@ static void fillChaos(Map& m) {
 }
 
 template<typename Map>
-static Millis average(Map& m, Core::Clock::Nanos (*f)(const Map&), int n) {
-    Core::Clock::Nanos sum = 0;
+static int average(Map& m, Nanos (*f)(const Map&), int n) {
+    Nanos sum = 0;
     for(int i = 0; i < n; i++) {
         sum += f(m);
     }
-    return sum / (n * 1'000'000);
+    return static_cast<int>(sum / (n * 1'000'000));
 }
 
 static void order(int n) {
@@ -78,11 +79,12 @@ static void order(int n) {
     Core::ProbingHashMap<int, int> m2;
     fillOrder(m);
     fillOrder(m2);
-    CORE_LOG_INFO("Order Chaining | Order Probing");
-    CORE_LOG_INFO("Search | # ms | # ms", average(m, testSearch, n),
-                  average(m2, testSearch, n));
-    CORE_LOG_INFO("EmptySearch | # ms | # ms", average(m, testEmptySearch, n),
-                  average(m2, testEmptySearch, n));
+    Logger::log(Logger::COLOR_GRAY, "Order Chaining | Order Probing");
+    Logger::log(Logger::COLOR_GRAY, "Search | # ms | # ms",
+                average(m, testSearch, n), average(m2, testSearch, n));
+    Logger::log(Logger::COLOR_GRAY, "EmptySearch | # ms | # ms",
+                average(m, testEmptySearch, n),
+                average(m2, testEmptySearch, n));
 }
 
 static void chaos(int n) {
@@ -90,11 +92,12 @@ static void chaos(int n) {
     Core::ProbingHashMap<int, int> m2;
     fillChaos(m);
     fillChaos(m2);
-    CORE_LOG_INFO("Chaos Chaining | Chaos Probing");
-    CORE_LOG_INFO("Search | # ms | # ms", average(m, testSearch, n),
-                  average(m2, testSearch, n));
-    CORE_LOG_INFO("EmptySearch | # ms | # ms", average(m, testEmptySearch, n),
-                  average(m2, testEmptySearch, n));
+    Logger::log(Logger::COLOR_GRAY, "Chaos Chaining | Chaos Probing");
+    Logger::log(Logger::COLOR_GRAY, "Search | # ms | # ms",
+                average(m, testSearch, n), average(m2, testSearch, n));
+    Logger::log(Logger::COLOR_GRAY, "EmptySearch | # ms | # ms",
+                average(m, testEmptySearch, n),
+                average(m2, testEmptySearch, n));
 }
 
 static void testProbing(int n) {
@@ -102,11 +105,12 @@ static void testProbing(int n) {
     Core::ProbingHashMap<int, int> m2;
     fillOrder(m);
     fillChaos(m2);
-    CORE_LOG_INFO("Order | Chaos");
-    CORE_LOG_INFO("Search | # ms | # ms", average(m, testSearch, n),
-                  average(m2, testSearch, n));
-    CORE_LOG_INFO("EmptySearch | # ms | # ms", average(m, testEmptySearch, n),
-                  average(m2, testEmptySearch, n));
+    Logger::log(Logger::COLOR_GRAY, "Order | Chaos");
+    Logger::log(Logger::COLOR_GRAY, "Search | # ms | # ms",
+                average(m, testSearch, n), average(m2, testSearch, n));
+    Logger::log(Logger::COLOR_GRAY, "EmptySearch | # ms | # ms",
+                average(m, testEmptySearch, n),
+                average(m2, testEmptySearch, n));
 }
 
 int main() {

+ 7 - 0
src/ErrorSimulator.cpp

@@ -2,9 +2,16 @@
 
 #ifdef ERROR_SIMULATOR
 
+#include "core/utils/Utility.hpp"
+
 bool Core::Fail::realloc = false;
 bool Core::Fail::fileClose = false;
 bool Core::Fail::timeGet = false;
 int Core::Fail::leftAllocations = -1;
 
+bool Core::Fail::freeAndReturn(void* p) {
+    Core::free(p);
+    return true;
+}
+
 #endif

+ 2 - 7
src/ErrorSimulator.hpp

@@ -1,8 +1,6 @@
 #ifndef CORE_ERROR_SIMULATOR_HPP
 #define CORE_ERROR_SIMULATOR_HPP
 
-#include "core/utils/Utility.hpp"
-
 #ifdef ERROR_SIMULATOR
 namespace Core::Fail {
     extern bool realloc;
@@ -10,17 +8,14 @@ namespace Core::Fail {
     extern bool timeGet;
     extern int leftAllocations;
 
-    inline bool freeAndReturn(void* p) {
-        Core::free(p);
-        return true;
-    }
+    bool freeAndReturn(void* p);
 }
 #define CORE_REALLOC_FAIL(pointer)                                             \
     (Core::Fail::realloc && Core::Fail::freeAndReturn(pointer))
 #define CORE_FILE_CLOSE_FAIL Core::Fail::fileClose
 #define CORE_TIME_GET_FAIL Core::Fail::timeGet
 #else
-#define CORE_REALLOC_FAIL false
+#define CORE_REALLOC_FAIL(...) false
 #define CORE_FILE_CLOSE_FAIL false
 #define CORE_TIME_GET_FAIL false
 #endif

+ 4 - 2
src/Utility.cpp

@@ -5,14 +5,16 @@
 #include <string.h>
 
 #include "ErrorSimulator.hpp"
+#include "core/utils/Logger.hpp"
 
 static Core::ExitHandler exitHandler = nullptr;
 static void* exitData = nullptr;
 
 void Core::exitWithHandler(const char* file, int line, int value) {
     if(value != 0) {
-        printf("\33[1;31mExit from %s:%d with value %d\33[39;49m\n", file, line,
-               value);
+        printf("%sExit from %s:%d with value %d%s\n", Core::Logger::COLOR_RED,
+               Core::Logger::getFileName(file), line, value,
+               Core::Logger::COLOR_RESET);
     }
     if(exitHandler != nullptr) {
         exitHandler(value, exitData);

+ 95 - 45
tasks

@@ -4,61 +4,101 @@ clear
 cd $(dirname $0)
 
 printHelpExit() {
-    echo "$0 clean       | remove build results"
-    echo "$0 build       | build everything"
-    echo "$0 install     | move build results into the install folder"
-    echo "$0 test        | run the tests"
-    echo "$0 valgrind    | run the tests with valgrind"
-    echo "$0 coverage    | generate code coverage"
-    echo "$0 performance | run the performance tests"
-    echo "$0 final       | find classes / structs which are not final"
-    echo "$0 macro       | find macros without CORE" 
-    echo "$0 include     | find system includes" 
-    echo "$0 time        | check build time"
+    echo "$0 clean           | remove build results"
+    echo "$0 build <type>    | build everything"
+    echo "$0 install         | move build results into the install folder"
+    echo "$0 test <type>     | run the tests"
+    echo "$0 valgrind <type> | run the tests with valgrind"
+    echo "$0 coverage        | generate code coverage"
+    echo "$0 performance     | run the performance tests"
+    echo "$0 final           | find classes / structs which are not final"
+    echo "$0 macro           | find macros without CORE" 
+    echo "$0 include         | find system includes" 
+    echo "$0 time            | check build time"
     exit 0
 }
 
-# TODO gcovr https://gcovr.com/en/5.0/guide.html
-
 task=$1
 if [ -z "$task" ]; then
     printHelpExit
 fi
 
-# tasks
-build=false
-test=false
-valgrind=false
+# task vars
+build_debug=false
+build_release=false
+
+test_debug=false
+test_release=false
+
+valgrind=""
 performance=false
 time=false
 install=false
 coverage=false
-profile=$(cat profile)
 
 # parsing
 if [ "$task" = "clean" ]; then
-    rm -rf build install
-    exit 0
+    rm -rf build_debug build_release install
 elif [ "$task" = "build" ]; then
-    build=true
+    type=$2
+    if [ "$type" = "debug" ]; then
+        build_debug=true
+    elif [ "$type" = "release" ]; then
+        build_release=true
+    elif [ "$type" = "all" ]; then
+        build_debug=true
+        build_release=true
+    else
+        echo "Valid build types are: debug, release, all"
+        printHelpExit
+    fi
 elif [ "$task" = "install" ]; then
-    build=true
+    build_release=true
     install=true
 elif [ "$task" = "coverage" ]; then
-    build=true
+    build_debug=true
+    test_debug=true
     coverage=true
-    test=true
 elif [ "$task" = "test" ]; then
-    build=true
-    test=true
+    type=$2
+    if [ "$type" = "debug" ]; then
+        build_debug=true
+        test_debug=true
+    elif [ "$type" = "release" ]; then
+        build_release=true
+        test_release=true
+    elif [ "$type" = "all" ]; then
+        build_debug=true
+        test_debug=true
+        build_release=true
+        test_release=true
+    else
+        echo "Valid test types are: debug, release, all"
+        printHelpExit
+    fi
 elif [ "$task" = "valgrind" ]; then
-    build=true
-    valgrind=true
+    type=$2
+    if [ "$type" = "debug" ]; then
+        build_debug=true
+        test_debug=true
+    elif [ "$type" = "release" ]; then
+        build_release=true
+        test_release=true
+    elif [ "$type" = "all" ]; then
+        build_debug=true
+        test_debug=true
+        build_release=true
+        test_release=true
+    else
+        echo "Valid valgrind types are: debug, release, all"
+        printHelpExit
+    fi
+    valgrind="valgrind"
 elif [ "$task" = "performance" ]; then
-    build=true
+    build_release=true
     performance=true
 elif [ "$task" = "time" ]; then
-    build=true
+    build_release=true
     time=true
 elif [ "$task" = "final" ]; then
     grep -r " class" src include | grep -v -E 'final|enum|.git' || true
@@ -80,32 +120,41 @@ else
 fi
 
 # task execution
-if $build; then
-    if [ ! -e build ]; then 
-        cmake -B build -S . -G Ninja -DCMAKE_INSTALL_PREFIX=./install \
-            -DCMAKE_BUILD_TYPE=$profile
+buildProfile() {
+    folder=$1
+    shift 1
+    if [ ! -e "$folder" ]; then 
+        cmake -B "$folder" -S . -G Ninja -DCMAKE_INSTALL_PREFIX=./install $@
     fi
-    ninja -C build
+    ninja -C "$folder"
+}
+
+if $build_debug; then
+    buildProfile build_debug -DCMAKE_BUILD_TYPE=Debug
+fi
+if $build_release; then
+    buildProfile build_release -DCMAKE_BUILD_TYPE=Release
 fi
 if $install; then
-    ninja -C build install
+    ninja -C build_release install
 fi
-if $test; then
-    cd build
-    ./test light || true
+if $test_debug; then
+    cd build_debug
+    $valgrind ./test light $valgrind || true
     cd ..
 fi
-if $valgrind; then
-    cd build
-    valgrind ./test light valgrind || true
+if $test_release; then
+    cd build_release
+    $valgrind ./test light $valgrind || true
     cd ..
 fi
 if $performance; then
-    cd build
+    cd build_release
     ./performance
+    cd ..
 fi
 if $time; then
-    lines=$(cat build/.ninja_log | grep "^[0-9]")
+    lines=$(cat build_release/.ninja_log | grep "^[0-9]")
 
     startMillis=0
     endMillis=0
@@ -127,5 +176,6 @@ if $time; then
     printf "$output" | sort -n
 fi
 if $coverage; then
-    gcovr -r . build -e test -e performance
+    gcovr -r . build_debug -e test -e performance \
+        --exclude-lines-by-pattern ".*CoverageIgnore.*"
 fi

+ 1 - 1
test/Main.cpp

@@ -65,7 +65,7 @@ int main(int argAmount, const char** args) {
     Core::setExitHandler(onExit, &data);
 
     char buffer[] = {-100, 0};
-    CORE_LOG_DEBUG(buffer);
+    CORE_LOG_ERROR(buffer);
 
     return 0;
 }

+ 15 - 16
test/Test.cpp

@@ -1,29 +1,28 @@
 #include "Test.hpp"
 
-Core::HashMap<Core::Test::Internal::FileName, Core::Test::Internal::Result>
-    Core::Test::Internal::results;
+namespace Internal = Core::Test::Internal;
+namespace Logger = Core::Logger;
 
-void Core::Test::Internal::warn(const char* file, int line, Error e) {
-    CORE_LOG_WARNING("#:# | #", Core::Logger::getFileName(file), line, e);
+Core::HashMap<Internal::FileName, Internal::Result> Internal::results;
+
+void Internal::warn(const char* file, int line, Error e) {
+    Logger::log("#:# | #", Logger::getFileName(file), line, e);
 }
 
-bool Core::Test::Internal::checkFloat(const char* file, int line, float wanted,
-                                      float actual, float error) {
+bool Internal::checkFloat(const char* file, int line, float wanted,
+                          float actual, float error) {
     float diff = wanted - actual;
     diff = diff < 0.0f ? -diff : diff;
     return check(file, line, wanted, actual, diff <= error);
 }
 
 void Core::Test::finalize() {
-    using namespace Internal;
-    for(const auto& e : results) {
-        if(e.value.successTests == e.value.tests) {
-            CORE_LOG_DEBUG("# - # / # tests succeeded", e.getKey(),
-                           e.value.successTests, e.value.tests);
-        } else {
-            CORE_LOG_ERROR("# - # / # tests succeeded", e.getKey(),
-                           e.value.successTests, e.value.tests);
-        }
+    for(const auto& e : Internal::results) {
+        const char* color = e.value.successTests == e.value.tests
+                                ? Logger::COLOR_GREEN
+                                : Logger::COLOR_RED;
+        Logger::log(color, "# - # / # tests succeeded", e.getKey(),
+                    e.value.successTests, e.value.tests);
     }
-    results.clear();
+    Internal::results.clear();
 }

+ 6 - 3
test/Test.hpp

@@ -37,8 +37,9 @@ namespace Core::Test {
                 result->successTests++;
                 return true;
             }
-            CORE_LOG_ERROR("#:# - expected '#' got '#'", fileName, line, wanted,
-                           actual);
+            Core::Logger::log(Core::Logger::COLOR_RED,
+                              "#:# - expected '#' got '#'", fileName, line,
+                              wanted, actual);
             return false;
         }
 
@@ -92,7 +93,9 @@ namespace Core::Test {
 #define CORE_TEST_TRUE(actual) CORE_TEST_EQUAL(true, actual)
 #define CORE_TEST_ERROR(actual) CORE_TEST_EQUAL(Core::Error::NONE, actual)
 #define CORE_TEST_NULL(actual) CORE_TEST_EQUAL(true, actual == nullptr)
-#define CORE_TEST_NOT_NULL(actual) CORE_TEST_EQUAL(true, actual != nullptr)
+// double check to make the null check clear for the compiler
+#define CORE_TEST_NOT_NULL(actual)                                             \
+    (CORE_TEST_EQUAL(true, (actual) != nullptr) && (actual) != nullptr)
 #define CORE_TEST_FLOAT(wanted, actual, error)                                 \
     Core::Test::Internal::checkFloat(__FILE__, __LINE__, wanted, actual, error)
 #define CORE_TEST_VECTOR(wanted, actual)                                       \

+ 2 - 0
test/modules/BitArrayTests.cpp

@@ -143,10 +143,12 @@ static void testNegativeArgument() {
 }
 
 static void testOutOfMemory() {
+#ifdef ERROR_SIMULATOR
     Core::BitArray bits;
     Core::Fail::leftAllocations = 0;
     CORE_TEST_EQUAL(Core::Error::OUT_OF_MEMORY, bits.resize(8, 4));
     Core::Fail::leftAllocations = -1;
+#endif
 }
 
 void Core::testBitArray(bool outOfMemoryTest) {

+ 2 - 0
test/modules/ClockTests.cpp

@@ -38,10 +38,12 @@ static void testWait(Core::Clock::Nanos wait) {
 }
 
 static void testFail() {
+#ifdef ERROR_SIMULATOR
     Core::Clock::Nanos n = 0;
     Core::Fail::timeGet = true;
     CORE_TEST_EQUAL(Core::Error::TIME_NOT_AVAILABLE, Core::Clock::getNanos(n));
     Core::Fail::timeGet = false;
+#endif
 }
 
 void Core::testClock(bool light) {

+ 2 - 0
test/modules/ComponentsTests.cpp

@@ -187,6 +187,7 @@ static void testRemove() {
 }
 
 static void testOutOfMemory() {
+#ifdef ERROR_SIMULATOR
     IntComponent c;
     int* i1 = nullptr;
     int memFails = 0;
@@ -199,6 +200,7 @@ static void testOutOfMemory() {
     }
     Core::Fail::leftAllocations = -1;
     CORE_TEST_TRUE(memFails != 0);
+#endif
 }
 
 static void testConstSearch() {

+ 2 - 0
test/modules/FileReaderTests.cpp

@@ -97,12 +97,14 @@ static void testInvalidAccessPath() {
 }
 
 static void testCloseFail() {
+#ifdef ERROR_SIMULATOR
     Core::Fail::fileClose = true;
     {
         Core::FileReader r;
         CORE_TEST_ERROR(r.open(TEST_FILE));
     }
     Core::Fail::fileClose = false;
+#endif
 }
 
 void Core::testFileReader() {

+ 2 - 0
test/modules/HashMapTests.cpp

@@ -312,6 +312,7 @@ static void testTypes() {
 }
 
 static void testOutOfMemory() {
+#ifdef ERROR_SIMULATOR
     IntMap map;
     int memFails = 0;
     for(int i = 0; i < 40; i++) {
@@ -328,6 +329,7 @@ static void testOutOfMemory() {
     }
     Core::Fail::leftAllocations = -1;
     CORE_TEST_TRUE(memFails != 0);
+#endif
 }
 
 void Core::testHashMap(bool light, bool outOfMemoryTest) {

+ 2 - 0
test/modules/LinkedListTests.cpp

@@ -275,6 +275,7 @@ static void testRemoveLast() {
 }
 
 static void testOutOfMemory() {
+#ifdef ERROR_SIMULATOR
     IntList list;
     int memFails = 0;
     for(int i = 0; i < 40; i++) {
@@ -289,6 +290,7 @@ static void testOutOfMemory() {
     }
     Core::Fail::leftAllocations = -1;
     CORE_TEST_TRUE(memFails != 0);
+#endif
 }
 
 void Core::testLinkedList(bool light, bool outOfMemoryTest) {

+ 13 - 1
test/modules/ListTests.cpp

@@ -1,6 +1,7 @@
 #include "../../src/ErrorSimulator.hpp"
 #include "../Tests.hpp"
 #include "core/data/List.hpp"
+#include "core/math/Math.hpp"
 
 template class Core::List<int>;
 using IntList = Core::List<int>;
@@ -75,7 +76,8 @@ static void testCopy() {
     IntList copy;
     CORE_TEST_ERROR(copy.copyFrom(list));
     CORE_TEST_EQUAL(list.getLength(), copy.getLength());
-    for(int i = 0; i < copy.getLength() && i < list.getLength(); i++) {
+    int limit = Core::Math::min(copy.getLength(), list.getLength());
+    for(int i = 0; i < limit; i++) {
         CORE_TEST_EQUAL(list[i], copy[i]);
     }
 }
@@ -210,6 +212,15 @@ static void testCopyEmpty() {
     CORE_TEST_ERROR(copy.copyFrom(list));
 }
 
+static void testOverflow() {
+    IntList list;
+    for(int i = 0; i < CORE_INT_MAX; i++) {
+        CORE_TEST_ERROR(list.add(i));
+    }
+    CORE_TEST_EQUAL(Core::Error::CAPACITY_REACHED, list.add(1));
+    CORE_TEST_EQUAL(CORE_INT_MAX, list.getCapacity());
+}
+
 void Core::testList(bool light) {
     testAdd();
     testMultipleAdd();
@@ -231,4 +242,5 @@ void Core::testList(bool light) {
     testShrinkExact();
     testShrinkResize();
     testCopyEmpty();
+    testOverflow();
 }

+ 2 - 0
test/modules/ProbingHashMapTests.cpp

@@ -313,6 +313,7 @@ static void testTypes() {
 }
 
 static void testOutOfMemory() {
+#ifdef ERROR_SIMULATOR
     IntMap map;
     int memFails = 0;
     for(int i = 0; i < 40; i++) {
@@ -329,6 +330,7 @@ static void testOutOfMemory() {
     }
     Core::Fail::leftAllocations = -1;
     CORE_TEST_TRUE(memFails != 0);
+#endif
 }
 
 static void testInsertInvalid() {

+ 2 - 0
test/modules/UtilityTests.cpp

@@ -38,11 +38,13 @@ static void testNegativeRellocate() {
 }
 
 static void testReallocateFail() {
+#ifdef ERROR_SIMULATOR
     char* buffer = nullptr;
     Core::Fail::realloc = true;
     CORE_TEST_EQUAL(Core::Error::OUT_OF_MEMORY, Core::reallocate(buffer, 16));
     CORE_TEST_TRUE(buffer == nullptr);
     Core::Fail::realloc = false;
+#endif
 }
 
 void Core::testUtility() {