Browse Source

More stuff move from gaming core, new linked list with tests, hash map
without need for move assignment (and others)

Kajetan Johannes Hammerle 1 year ago
parent
commit
3f9c50b4e7

+ 1 - 1
data/ArrayList.h

@@ -96,7 +96,7 @@ namespace Core {
         }
 
         // returns true on error
-        check_return bool remove(int index) {
+        check_return bool removeBySwap(int index) {
             if(index < 0 || index >= length) {
                 return true;
             }

+ 3 - 3
data/BitArray.cpp

@@ -51,12 +51,12 @@ static int getArrayLength(int length, int bits) {
 Core::BitArray::BitArray() : length(0), bits(0), data(nullptr) {
 }
 
-check_return bool Core::BitArray::copyTo(BitArray& other) const {
-    if(other.resize(length, bits)) {
+check_return bool Core::BitArray::copyFrom(const BitArray& other) {
+    if(resize(other.length, other.bits)) {
         return true;
     }
     for(int i = 0; i < length; i++) {
-        other.set(i, get(i));
+        set(i, other.get(i));
     }
     return false;
 }

+ 1 - 1
data/BitArray.h

@@ -19,7 +19,7 @@ namespace Core {
         BitArray& operator=(BitArray&& other);
 
         // returns true on error
-        check_return bool copyTo(BitArray& other) const;
+        check_return bool copyFrom(const BitArray& other);
 
         BitArray& set(int index, int value);
         int get(int index) const;

+ 325 - 0
data/HashMap.h

@@ -0,0 +1,325 @@
+#ifndef CORE_HASHMAP_H
+#define CORE_HASHMAP_H
+
+#include <stdio.h>
+
+#include "data/LinkedList.h"
+#include "data/List.h"
+#include "math/Math.h"
+#include "utils/String.h"
+#include "utils/Types.h"
+
+namespace Core {
+    template<typename K, typename V>
+    struct HashMap final {
+        class Node final {
+            friend HashMap;
+            friend List<Node>;
+            friend LinkedList<Node>;
+            K key;
+
+        public:
+            V value;
+
+            const K& getKey() const {
+                return key;
+            }
+
+        private:
+            template<typename... Args>
+            Node(const K& key_, Args&&... args)
+                : key(key_), value(Core::forward<Args>(args)...) {
+            }
+        };
+        using NodePointer = LinkedList<Node>::Node*;
+        using NodePointerList = List<NodePointer>;
+        using NodeIterator = LinkedList<Node>::Iterator;
+        using ConstNodeIterator = LinkedList<Node>::ConstIterator;
+
+        template<typename N, typename I, typename R, R& (*A)(I&)>
+        class Iterator final {
+            N iterator;
+
+        public:
+            Iterator(N iterator_) : iterator(iterator_) {
+            }
+
+            Iterator& operator++() {
+                ++iterator;
+                return *this;
+            }
+
+            bool operator!=(const Iterator& other) const {
+                return iterator != other.iterator;
+            }
+
+            R& operator*() const {
+                return A(*iterator);
+            }
+        };
+
+        template<typename R>
+        static R& access(R& node) {
+            return node;
+        }
+
+        template<typename I, typename R>
+        static R& accessValue(I& node) {
+            return node.value;
+        }
+
+        static const K& accessKey(const Node& node) {
+            return node.getKey();
+        }
+
+        template<typename N, typename R>
+        using BaseEntryIterator = Iterator<N, R, R, access<R>>;
+        using EntryIterator = BaseEntryIterator<NodeIterator, Node>;
+        using ConstEntryIterator =
+            BaseEntryIterator<ConstNodeIterator, const Node>;
+
+        template<typename N, typename I, typename R>
+        using BaseValueIterator = Iterator<N, I, R, accessValue<I, R>>;
+        using ValueIterator = BaseValueIterator<NodeIterator, Node, V>;
+        using ConstValueIterator =
+            BaseValueIterator<ConstNodeIterator, const Node, const V>;
+
+        using ConstKeyIterator =
+            Iterator<ConstNodeIterator, const Node, const K, accessKey>;
+
+        template<typename M, typename I>
+        struct IteratorAdapter final {
+            M& map;
+
+            I begin() const {
+                return I(map.nodes.begin());
+            }
+
+            I end() const {
+                return I(map.nodes.end());
+            }
+        };
+
+        using EntryIteratorAdapter = IteratorAdapter<HashMap, EntryIterator>;
+        using ConstEntryIteratorAdapter =
+            IteratorAdapter<const HashMap, ConstEntryIterator>;
+
+        using ValueIteratorAdapter = IteratorAdapter<HashMap, ValueIterator>;
+        using ConstValueIteratorAdapter =
+            IteratorAdapter<const HashMap, ConstValueIterator>;
+
+        using ConstKeyIteratorAdapter =
+            IteratorAdapter<const HashMap, ConstKeyIterator>;
+
+    private:
+        LinkedList<Node> nodes;
+        List<NodePointerList> nodePointers;
+
+    public:
+        // returns true on error
+        check_return bool copyFrom(const HashMap& other) {
+            HashMap copy;
+            for(const auto& e : other) {
+                if(copy.add(e.getKey(), e.value)) {
+                    return true;
+                }
+            }
+            swap(copy.nodes, nodes);
+            swap(copy.nodePointers, nodePointers);
+            return false;
+        }
+
+        // returns true on error
+        check_return bool rehash(int minCapacity) {
+            if(minCapacity <= nodePointers.getLength()) {
+                return false;
+            }
+            HashMap<K, V> map;
+            int l = 1 << Math::roundUpLog2(Core::Math::max(minCapacity, 8));
+            if(map.nodePointers.resize(l)) {
+                return true;
+            }
+            for(NodePointerList& list : nodePointers) {
+                for(NodePointer& n : list) {
+                    int h = map.hashIndex(n->data.key);
+                    if(map.nodePointers[h].add(n)) {
+                        return true;
+                    }
+                }
+            }
+            Core::swap(map.nodePointers, nodePointers);
+            return false;
+        }
+
+        // returns true on error
+        template<typename... Args>
+        check_return bool tryEmplace(const K& key, Args&&... args) {
+            if(rehash(nodes.getLength() + 1)) {
+                return true;
+            }
+            int h = hashIndex(key);
+            V* v = searchList(key, h);
+            if(v != nullptr) {
+                return true;
+            }
+            NodePointer np = nodes.add(key, Core::forward<Args>(args)...);
+            if(np == nullptr) {
+                return true;
+            } else if(nodePointers[h].add(np)) {
+                nodes.remove(np);
+                return true;
+            }
+            return false;
+        }
+
+        // returns true on error
+        template<typename VA>
+        check_return bool add(const K& key, VA&& value) {
+            if(rehash(nodes.getLength() + 1)) {
+                return true;
+            }
+            int h = hashIndex(key);
+            V* v = searchList(key, h);
+            if(v == nullptr) {
+                NodePointer np = nodes.add(key, Core::forward<VA>(value));
+                if(np == nullptr) {
+                    return true;
+                } else if(nodePointers[h].add(np)) {
+                    nodes.remove(np);
+                    return true;
+                }
+            } else {
+                *v = Core::forward<VA>(value);
+            }
+            return false;
+        }
+
+        // returns true when a value was removed
+        check_return bool remove(const K& key) {
+            NodePointerList& list = nodePointers[hashIndex(key)];
+            for(int i = 0; i < list.getLength(); i++) {
+                if(list[i]->data.key == key) {
+                    NodePointer np = list[i];
+                    bool r = list.removeBySwap(i);
+                    nodes.remove(np);
+                    return !r;
+                }
+            }
+            return false;
+        }
+
+        const V* search(const K& key) const {
+            return searchList(key, hashIndex(key));
+        }
+
+        V* search(const K& key) {
+            return searchList(key, hashIndex(key));
+        }
+
+        bool contains(const K& key) const {
+            return search(key) != nullptr;
+        }
+
+        HashMap& clear() {
+            nodes.clear();
+            for(NodePointerList& n : nodePointers) {
+                n.clear();
+            }
+            return *this;
+        }
+
+        EntryIteratorAdapter entries() {
+            return {*this};
+        }
+
+        ConstEntryIteratorAdapter entries() const {
+            return {*this};
+        }
+
+        ConstKeyIteratorAdapter keys() const {
+            return {*this};
+        }
+
+        ValueIteratorAdapter values() {
+            return {*this};
+        }
+
+        ConstValueIteratorAdapter values() const {
+            return {*this};
+        }
+
+        EntryIterator begin() {
+            return EntryIterator(nodes.begin());
+        }
+
+        EntryIterator end() {
+            return EntryIterator(nodes.end());
+        }
+
+        ConstEntryIterator begin() const {
+            return ConstEntryIterator(nodes.begin());
+        }
+
+        ConstEntryIterator end() const {
+            return ConstEntryIterator(nodes.end());
+        }
+
+        void toString(String& s) const {
+            s.append("[");
+            bool c = false;
+            for(const NodePointerList& list : nodePointers) {
+                for(const NodePointer& n : list) {
+                    if(c) {
+                        s.append(", ");
+                    }
+                    s.append(n->data.key).append(" = ").append(n->data.value);
+                    c = true;
+                }
+            }
+            s.append("]");
+        }
+
+    private:
+        template<typename H>
+        int hashIndex(const H& key) const {
+            return static_cast<int>(fullHash(key)) &
+                   (nodePointers.getLength() - 1);
+        }
+
+        template<typename H>
+        Hash fullHash(const H& key) const {
+            return key.hashCode();
+        }
+
+        Hash fullHash(int key) const {
+            static_assert(sizeof(key) == sizeof(Hash),
+                          "unwanted loose of precision in hash");
+            return static_cast<Hash>(key);
+        }
+
+        Hash fullHash(unsigned int key) const {
+            static_assert(sizeof(key) == sizeof(Hash),
+                          "unwanted loose of precision in hash");
+            return key;
+        }
+
+        const V* searchList(const K& key, int h) const {
+            if(nodePointers.getLength() == 0) {
+                return nullptr;
+            }
+            for(const NodePointer& n : nodePointers[h]) {
+                if(n->data.key == key) {
+                    return &n->data.value;
+                }
+            }
+            return nullptr;
+        }
+
+        V* searchList(const K& key, int h) {
+            return const_cast<V*>(
+                static_cast<const HashMap*>(this)->searchList(key, h));
+        }
+    };
+}
+
+#endif

+ 201 - 0
data/LinkedList.h

@@ -0,0 +1,201 @@
+#ifndef CORE_LINKED_LIST_H
+#define CORE_LINKED_LIST_H
+
+#include <stdio.h>
+
+#include "utils/Check.h"
+#include "utils/String.h"
+#include "utils/Utility.h"
+
+namespace Core {
+    template<typename T>
+    struct LinkedList final {
+        class Node {
+            friend LinkedList;
+
+            Node* next;
+            Node* previous;
+
+        public:
+            T data;
+
+            template<typename... Args>
+            Node(Args&&... args)
+                : next(nullptr), previous(nullptr),
+                  data(Core::forward<Args>(args)...) {
+            }
+        };
+
+        template<typename NT, typename R>
+        struct IteratorBase final {
+            NT* current;
+
+        public:
+            IteratorBase& operator++() {
+                current = current->next;
+                return *this;
+            }
+
+            bool operator!=(const IteratorBase& other) const {
+                return current != other.current;
+            }
+
+            R& operator*() const {
+                return current->data;
+            }
+        };
+
+        using Iterator = IteratorBase<Node, T>;
+        using ConstIterator = IteratorBase<const Node, const T>;
+
+    private:
+        Node* first;
+        Node* last;
+        int length;
+
+    public:
+        LinkedList() : first(nullptr), last(nullptr), length(0) {
+        }
+
+        LinkedList(const LinkedList& other) = delete;
+
+        LinkedList(LinkedList&& other) : LinkedList() {
+            swap(other);
+        }
+
+        ~LinkedList() {
+            clear();
+        }
+
+        LinkedList& operator=(const LinkedList& other) = delete;
+
+        LinkedList& operator=(LinkedList&& other) {
+            swap(other);
+            return *this;
+        }
+
+        // returns true on error
+        check_return bool copyFrom(const LinkedList& other) {
+            LinkedList copy;
+            for(const T& t : other) {
+                if(copy.add(t) == nullptr) {
+                    return true;
+                }
+            }
+            swap(copy);
+            return false;
+        }
+
+        // returns a nullptr on error
+        template<typename... Args>
+        check_return Node* add(Args&&... args) {
+            Node* n = new Node(Core::forward<Args>(args)...);
+            if(n == nullptr) {
+                return nullptr;
+            }
+            length++;
+            if(first == nullptr) {
+                first = n;
+                last = n;
+                return n;
+            }
+            last->next = n;
+            n->previous = last;
+            last = n;
+            return n;
+        }
+
+        Iterator begin() {
+            return {first};
+        }
+
+        Iterator end() {
+            return {nullptr};
+        }
+
+        ConstIterator begin() const {
+            return {first};
+        }
+
+        ConstIterator end() const {
+            return {nullptr};
+        }
+
+        int getLength() const {
+            return length;
+        }
+
+        void clear() {
+            Node* n = first;
+            while(n != nullptr) {
+                Node* next = n->next;
+                delete n;
+                n = next;
+            }
+            length = 0;
+            first = nullptr;
+            last = nullptr;
+        }
+
+        void toString(String& s) const {
+            s.append("[");
+            auto iter = begin();
+            for(int i = 0; i < length - 1; i++) {
+                s.append(*iter);
+                s.append(", ");
+                ++iter;
+            }
+            if(length > 0) {
+                s.append(*iter);
+            }
+            s.append("]");
+        }
+
+        void remove(Node*& n) {
+            if(n == nullptr) {
+                return;
+            }
+            if(n == first) {
+                if(first->next != nullptr) {
+                    first->next->previous = nullptr;
+                }
+                first = first->next;
+            }
+            if(n == last) {
+                if(last->previous != nullptr) {
+                    last->previous->next = nullptr;
+                }
+                last = last->previous;
+            }
+            if(n->previous != nullptr) {
+                n->previous->next = n->next;
+            }
+            if(n->next != nullptr) {
+                n->next->previous = n->previous;
+            }
+            length--;
+            delete n;
+            n = nullptr;
+        }
+
+        void removeFirst() {
+            Node* n = first; // prevent first from becoming null
+            remove(n);
+        }
+
+        void removeLast() {
+            Node* n = last; // prevent last from becoming null
+            remove(n);
+        }
+
+    private:
+        void swap(LinkedList& other) {
+            Core::swap(first, other.first);
+            Core::swap(last, other.last);
+            Core::swap(length, other.length);
+        }
+    };
+
+}
+
+#endif

+ 245 - 0
data/List.h

@@ -0,0 +1,245 @@
+#ifndef CORE_LIST_H
+#define CORE_LIST_H
+
+#include "math/Math.h"
+#include "utils/String.h"
+#include "utils/Utility.h"
+
+namespace Core {
+    template<typename T>
+    class List final {
+        int length;
+        int capacity;
+        T* data;
+
+    public:
+        List() : length(0), capacity(0), data(nullptr) {
+        }
+
+        List(const List& other) = delete;
+
+        List(List&& other) : List() {
+            swap(other);
+        }
+
+        ~List() {
+            clear();
+            delete[] reinterpret_cast<char*>(data);
+        }
+
+        List& operator=(const List& other) = delete;
+
+        List& operator=(List&& other) {
+            swap(other);
+            return *this;
+        }
+
+        // returns true on error
+        check_return bool copyFrom(const List& other) {
+            List copy;
+            copy.data = allocate(other.capacity);
+            if(copy.data == nullptr) {
+                return true;
+            }
+            copy.capacity = other.capacity;
+            for(int i = 0; i < other.length; i++) {
+                copy.unsafeAdd(other[i]);
+            }
+            swap(copy);
+            return false;
+        }
+
+        T* begin() {
+            return data;
+        }
+
+        T* end() {
+            return data + length;
+        }
+
+        const T* begin() const {
+            return data;
+        }
+
+        const T* end() const {
+            return data + length;
+        }
+
+        // returns true on error
+        check_return bool reserve(int n) {
+            if(n <= capacity) {
+                return false;
+            }
+            List copy;
+            copy.data = allocate(n);
+            if(copy.data == nullptr) {
+                return true;
+            }
+            copy.capacity = n;
+            for(int i = 0; i < length; i++) {
+                copy.unsafeAdd(Core::move(data[i]));
+            }
+            swap(copy);
+            return false;
+        }
+
+        // returns true on error
+        check_return bool shrink() {
+            if(length == capacity) {
+                return false;
+            }
+            List copy;
+            copy.data = allocate(length);
+            if(copy.data == nullptr) {
+                return true;
+            }
+            copy.capacity = length;
+            for(int i = 0; i < length; i++) {
+                copy.unsafeAdd(Core::move(data[i]));
+            }
+            swap(copy);
+            return false;
+        }
+
+        // returns true on error
+        check_return bool resize(int n, const T& t) {
+            if(length < n) {
+                if(reserve(n)) {
+                    return true;
+                }
+                for(int i = length; i < n; i++) {
+                    unsafeAdd(t);
+                }
+            } else if(length > n) {
+                for(int i = n; i < length; i++) {
+                    data[i].~T();
+                }
+                length = n;
+            }
+            return false;
+        }
+
+        // returns true on error
+        check_return bool resize(int n) {
+            if(length < n) {
+                if(reserve(n)) {
+                    return true;
+                }
+                for(int i = length; i < n; i++) {
+                    unsafeAdd(T());
+                }
+            } else if(length > n) {
+                for(int i = n; i < length; i++) {
+                    data[i].~T();
+                }
+                length = n;
+            }
+            return false;
+        }
+
+        // returns true on error
+        template<typename... Args>
+        check_return bool add(Args&&... args) {
+            if(ensureCapacity()) {
+                return true;
+            }
+            unsafeAdd(Core::forward<Args>(args)...);
+            return false;
+        }
+
+        T& operator[](int index) {
+            return data[index];
+        }
+
+        const T& operator[](int index) const {
+            return data[index];
+        }
+
+        int getLength() const {
+            return length;
+        }
+
+        int getCapacity() const {
+            return capacity;
+        }
+
+        void clear() {
+            for(int i = 0; i < length; i++) {
+                data[i].~T();
+            }
+            length = 0;
+        }
+
+        // returns true on error
+        check_return bool removeBySwap(int index) {
+            if(index < 0 || index >= length) {
+                return true;
+            }
+            length--;
+            if(index != length) {
+                data[index] = Core::move(data[length]);
+            }
+            data[length].~T();
+            return false;
+        }
+
+        // returns true on error
+        check_return bool remove(int index) {
+            if(index < 0 || index >= length) {
+                return true;
+            }
+            length--;
+            for(int i = index; i < length; i++) {
+                data[i] = Core::move(data[i + 1]);
+            }
+            data[length].~T();
+            return false;
+        }
+
+        void toString(String& s) const {
+            s.append("[");
+            for(int i = 0; i < length - 1; i++) {
+                s.append(data[i]);
+                s.append(", ");
+            }
+            if(length > 0) {
+                s.append(data[length - 1]);
+            }
+            s.append("]");
+        }
+
+    private:
+        struct alignas(T) Aligned {
+            char data[sizeof(T)];
+        };
+
+        static T* allocate(int n) {
+            if(n <= 0) {
+                return nullptr;
+            }
+            return reinterpret_cast<T*>(new Aligned[static_cast<size_t>(n)]);
+        }
+
+        void swap(List& other) {
+            Core::swap(length, other.length);
+            Core::swap(capacity, other.capacity);
+            Core::swap(data, other.data);
+        }
+
+        // returns true on error
+        check_return bool ensureCapacity() {
+            if(length >= capacity) {
+                return reserve(capacity + Core::Math::max(4, capacity / 4));
+            }
+            return false;
+        }
+
+        // does not check for capacity
+        template<typename... Args>
+        void unsafeAdd(Args&&... args) {
+            new(data + length++) T(Core::forward<Args>(args)...);
+        }
+    };
+}
+
+#endif

+ 4 - 0
meson.build

@@ -16,6 +16,10 @@ src_tests = [
     'tests/ArrayListTests.cpp',
     'tests/BitArrayTests.cpp',
     'tests/MathTests.cpp',
+    'tests/ListTests.cpp',
+    'tests/LinkedListTests.cpp',
+    'tests/UniquePointerTests.cpp',
+    'tests/HashMapTests.cpp',
 ]
 
 compiler = meson.get_compiler('cpp')

+ 8 - 0
test/Main.cpp

@@ -1,8 +1,12 @@
 #include "tests/ArrayListTests.h"
 #include "tests/ArrayTests.h"
 #include "tests/BitArrayTests.h"
+#include "tests/HashMapTests.h"
+#include "tests/LinkedListTests.h"
+#include "tests/ListTests.h"
 #include "tests/MathTests.h"
 #include "tests/StringTests.h"
+#include "tests/UniquePointerTests.h"
 #include "tests/UtilityTests.h"
 
 int main() {
@@ -12,5 +16,9 @@ int main() {
     Core::ArrayListTests::test();
     Core::BitArrayTests::test();
     Core::MathTests::test();
+    Core::ListTests::test();
+    Core::LinkedListTests::test();
+    Core::HashMapTests::test();
+    Core::UniquePointerTests::test();
     return 0;
 }

+ 9 - 9
tests/ArrayListTests.cpp

@@ -136,15 +136,15 @@ static void testRemove(Core::Test& test) {
     test.checkFalse(list.add(4), "add works 24");
     test.checkFalse(list.add(3), "add works 25");
     test.checkFalse(list.add(2), "add works 26");
-    test.checkFalse(list.remove(0), "remove works 1");
-    test.checkEqual(2, list[0], "remove 1");
-    test.checkEqual(3, list[1], "remove 2");
-    test.checkEqual(2, list.getLength(), "remove 3");
-    test.checkFalse(list.remove(1), "remove works 2");
-    test.checkEqual(2, list[0], "remove 4");
-    test.checkEqual(1, list.getLength(), "remove 5");
-    test.checkFalse(list.remove(0), "remove works 3");
-    test.checkEqual(0, list.getLength(), "remove 6");
+    test.checkFalse(list.removeBySwap(0), "remove by swap works 1");
+    test.checkEqual(2, list[0], "remove by swap 1");
+    test.checkEqual(3, list[1], "remove by swap 2");
+    test.checkEqual(2, list.getLength(), "remove by swap 3");
+    test.checkFalse(list.removeBySwap(1), "remove by swap works 2");
+    test.checkEqual(2, list[0], "remove by swap 4");
+    test.checkEqual(1, list.getLength(), "remove by swap 5");
+    test.checkFalse(list.removeBySwap(0), "remove by swap works 3");
+    test.checkEqual(0, list.getLength(), "remove by swap 6");
 }
 
 void Core::ArrayListTests::test() {

+ 1 - 1
tests/BitArrayTests.cpp

@@ -67,7 +67,7 @@ static void testReadOnly(Core::Test& test) {
     test.checkFalse(bits.resize(4, 3), "resize works 5");
     bits.set(0, 1).set(1, 2).set(2, 3).set(3, 4);
     Core::BitArray copy;
-    test.checkFalse(bits.copyTo(copy), "copy works");
+    test.checkFalse(copy.copyFrom(bits), "copy works");
     const Core::BitArray bits2 = Core::move(copy);
     test.checkEqual(1, bits2.get(0), "can read from const 1");
     test.checkEqual(2, bits2.get(1), "can read from const 2");

+ 322 - 0
tests/HashMapTests.cpp

@@ -0,0 +1,322 @@
+#include "tests/HashMapTests.h"
+
+#include "data/HashMap.h"
+#include "test/Test.h"
+
+using IntMap = Core::HashMap<int, int>;
+
+static void testAdd(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(5, 4), "add works 1");
+    int* value = map.search(5);
+    test.checkEqual(true, value != nullptr, "contains added value");
+    if(value != nullptr) {
+        test.checkEqual(4, *value, "search finds added value");
+    }
+}
+
+static void testMultipleAdd(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(5, 4), "add works 2");
+    test.checkFalse(map.add(10, 3), "add works 3");
+    test.checkFalse(map.add(15, 2), "add works 4");
+    test.checkEqual(true, map.contains(5), "contains added value 1");
+    test.checkEqual(true, map.contains(10), "contains added value 2");
+    test.checkEqual(true, map.contains(15), "contains added value 3");
+    int* a = map.search(5);
+    int* b = map.search(10);
+    int* c = map.search(15);
+    test.checkEqual(true, a != nullptr, "contains added value 1");
+    test.checkEqual(true, b != nullptr, "contains added value 2");
+    test.checkEqual(true, c != nullptr, "contains added value 3");
+    if(a != nullptr && b != nullptr && c != nullptr) {
+        test.checkEqual(4, *a, "search finds added value 1");
+        test.checkEqual(3, *b, "search finds added value 2");
+        test.checkEqual(2, *c, "search finds added value 3");
+    }
+}
+
+static void testSearch(Core::Test& test) {
+    IntMap map;
+    test.checkEqual(true, nullptr == map.search(6),
+                    "search does not find missing key");
+}
+
+static void testAddReplace(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(5, 4), "add works 5");
+    test.checkFalse(map.add(5, 10), "add works 6");
+    test.checkEqual(true, map.contains(5), "contains replaced value");
+    int* a = map.search(5);
+    test.checkEqual(true, a != nullptr, "contains replaced value");
+    if(a != nullptr) {
+        test.checkEqual(10, *a, "search finds replaced value");
+    }
+}
+
+static void testClear(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(5, 4), "add works 7");
+    test.checkFalse(map.add(4, 10), "add works 8");
+    map.clear();
+    test.checkEqual(false, map.contains(5),
+                    "does not contain cleared values 1");
+    test.checkEqual(false, map.contains(4),
+                    "does not contain cleared values 2");
+}
+
+static void testOverflow(Core::Test& test) {
+    IntMap map;
+    for(int i = 0; i < 1000000; i++) {
+        test.checkFalse(map.add(i, i), "add works in overflow");
+    }
+    for(int i = 0; i < 1000000; i++) {
+        test.checkTrue(map.contains(i), "still contains values after overflow");
+    }
+    test.checkTrue(true, "survives overflow");
+}
+
+struct A {
+    int a;
+    int b;
+
+    A(int a_, int b_) : a(a_), b(b_) {
+    }
+
+    // none of these should be needed for the hashmap
+    A(const A&) = delete;
+    A(A&&) = delete;
+    A& operator=(const A&) = delete;
+    A& operator=(A&&) = delete;
+
+    bool operator==(const A& other) const {
+        return a == other.a && b == other.b;
+    }
+
+    void toString(Core::String& s) const {
+        s.append("A(").append(a).append(", ").append(b).append(")");
+    }
+};
+
+static void testEmplace(Core::Test& test) {
+    Core::HashMap<int, A> map;
+
+    bool r1 = map.tryEmplace(0, 3, 4);
+    bool r2 = map.tryEmplace(3, 4, 5);
+    bool r3 = map.tryEmplace(20, 5, 6);
+    bool r4 = map.tryEmplace(3, 6, 7);
+    bool r5 = map.tryEmplace(20, 7, 8);
+
+    A* a = map.search(0);
+    A* b = map.search(3);
+    A* c = map.search(20);
+
+    test.checkEqual(true, a != nullptr, "contains emplaced value 1");
+    test.checkEqual(true, b != nullptr, "contains emplaced value 2");
+    test.checkEqual(true, c != nullptr, "contains emplaced value 3");
+
+    if(a != nullptr && b != nullptr && c != nullptr) {
+        test.checkEqual(A(3, 4), *a, "contains emplaced value 1");
+        test.checkEqual(A(4, 5), *b, "contains emplaced value 2");
+        test.checkEqual(A(5, 6), *c, "contains emplaced value 3");
+    }
+
+    test.checkEqual(false, r1, "emplacing returns correct value 1");
+    test.checkEqual(false, r2, "emplacing returns correct value 2");
+    test.checkEqual(false, r3, "emplacing returns correct value 3");
+    test.checkEqual(true, r4, "emplacing returns correct value 4");
+    test.checkEqual(true, r5, "emplacing returns correct value 5");
+}
+
+static void testToString1(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(1, 3), "add works to string 1");
+    test.checkFalse(map.add(2, 4), "add works to string 2");
+    test.checkFalse(map.add(3, 5), "add works to string 3");
+    test.checkEqual(Core::String("[1 = 3, 2 = 4, 3 = 5]"), Core::String(map),
+                    "to string 1");
+}
+
+static void testToString2(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(1, 3), "add works to string 4");
+    test.checkEqual(Core::String("[1 = 3]"), Core::String(map), "to string 2");
+}
+
+static void testToString3(Core::Test& test) {
+    IntMap map;
+    test.checkEqual(Core::String("[]"), Core::String(map), "to string 3");
+}
+
+static void testCopy(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(1, 3), "add works copy 1");
+    test.checkFalse(map.add(2, 4), "add works copy 2");
+    test.checkFalse(map.add(3, 5), "add works copy 3");
+    IntMap copy;
+    test.checkFalse(copy.copyFrom(map), "copy works");
+
+    int* a[6] = {map.search(1),  map.search(2),  map.search(3),
+                 copy.search(1), copy.search(2), copy.search(3)};
+    for(int i = 0; i < 3; i++) {
+        test.checkEqual(true, a[i] != nullptr && a[i + 3] != nullptr,
+                        "copy has same values");
+        if(a[i] != nullptr && a[i + 3] != nullptr) {
+            test.checkEqual(*(a[i]), *(a[i + 3]), "copy has same values");
+        }
+    }
+}
+
+static void testMove(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(1, 3), "add works move 5");
+    test.checkFalse(map.add(2, 4), "add works move 6");
+    test.checkFalse(map.add(3, 5), "add works move 7");
+    IntMap move(Core::move(map));
+
+    int* a = move.search(1);
+    int* b = move.search(2);
+    int* c = move.search(3);
+
+    test.checkEqual(true, a != nullptr, "move moves values 1");
+    test.checkEqual(true, b != nullptr, "move moves values 2");
+    test.checkEqual(true, c != nullptr, "move moves values 3");
+
+    if(a != nullptr && b != nullptr && c != nullptr) {
+        test.checkEqual(3, *a, "move moves values 1");
+        test.checkEqual(4, *b, "move moves values 2");
+        test.checkEqual(5, *c, "move moves values 3");
+    }
+}
+
+static void testMoveAssignment(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(1, 3), "add works move assignment 1");
+    test.checkFalse(map.add(2, 4), "add works move assignment 2");
+    test.checkFalse(map.add(3, 5), "add works move assignment 3");
+
+    IntMap move;
+    move = Core::move(map);
+
+    int* a = move.search(1);
+    int* b = move.search(2);
+    int* c = move.search(3);
+
+    test.checkEqual(true, a != nullptr, "move moves values 1");
+    test.checkEqual(true, b != nullptr, "move moves values 2");
+    test.checkEqual(true, c != nullptr, "move moves values 3");
+
+    if(a != nullptr && b != nullptr && c != nullptr) {
+        test.checkEqual(3, *a, "move moves values 1");
+        test.checkEqual(4, *b, "move moves values 2");
+        test.checkEqual(5, *c, "move moves values 3");
+    }
+}
+
+static void testRemove(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(1, 3), "add works remove 1");
+    test.checkFalse(map.add(2, 4), "add works remove 2");
+    test.checkFalse(map.add(3, 5), "add works remove 3");
+
+    bool remove1 = map.remove(2);
+    bool remove2 = map.remove(7);
+
+    int* a = map.search(1);
+    int* b = map.search(2);
+    int* c = map.search(3);
+
+    test.checkEqual(true, a != nullptr, "move moves values 1");
+    test.checkEqual(true, b == nullptr, "move moves values 2");
+    test.checkEqual(true, c != nullptr, "move moves values 3");
+
+    test.checkEqual(true, remove1, "remove returns true");
+    test.checkEqual(false, remove2, "remove returns false");
+
+    if(a != nullptr && c != nullptr) {
+        test.checkEqual(3, *a, "move moves values 1");
+        test.checkEqual(5, *c, "move moves values 3");
+    }
+}
+
+static void testEntryForEach(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(5, 4), "add works entry for each 1");
+    test.checkFalse(map.add(10, 3), "add works entry for each 2");
+    test.checkFalse(map.add(15, 2), "add works entry for each 3");
+
+    int counter = 0;
+    for(auto& entry : map.entries()) {
+        counter += entry.getKey() + entry.value;
+    }
+    test.checkEqual(39, counter, "entry iterator");
+
+    const IntMap& cmap = map;
+    counter = 0;
+    for(const auto& entry : cmap.entries()) {
+        counter += entry.getKey() + entry.value;
+    }
+    test.checkEqual(39, counter, "const entry iterator");
+}
+
+static void testKeyForEach(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(5, 4), "add works key for each 1");
+    test.checkFalse(map.add(10, 3), "add works key for each 2");
+    test.checkFalse(map.add(15, 2), "add works key for each 3");
+
+    int counter = 0;
+    for(const int& key : map.keys()) {
+        counter += key;
+    }
+    test.checkEqual(30, counter, "key iterator");
+
+    const IntMap& cmap = map;
+    counter = 0;
+    for(const int& key : cmap.keys()) {
+        counter += key;
+    }
+    test.checkEqual(30, counter, "const key iterator");
+}
+
+static void testValueForEach(Core::Test& test) {
+    IntMap map;
+    test.checkFalse(map.add(5, 4), "add works value for each 1");
+    test.checkFalse(map.add(10, 3), "add works value for each 2");
+    test.checkFalse(map.add(15, 2), "add works value for each 3");
+
+    int counter = 0;
+    for(int& value : map.values()) {
+        counter += value;
+    }
+    test.checkEqual(9, counter, "value iterator");
+
+    const IntMap& cmap = map;
+    counter = 0;
+    for(const int& value : cmap.values()) {
+        counter += value;
+    }
+    test.checkEqual(9, counter, "const value iterator");
+}
+
+void Core::HashMapTests::test() {
+    Core::Test test("HashMap");
+    testAdd(test);
+    testMultipleAdd(test);
+    testSearch(test);
+    testAddReplace(test);
+    testClear(test);
+    testOverflow(test);
+    testEmplace(test);
+    testToString1(test);
+    testToString2(test);
+    testToString3(test);
+    testCopy(test);
+    testMove(test);
+    testMoveAssignment(test);
+    testRemove(test);
+    testEntryForEach(test);
+    testKeyForEach(test);
+    testValueForEach(test);
+    test.finalize();
+}

+ 8 - 0
tests/HashMapTests.h

@@ -0,0 +1,8 @@
+#ifndef CORE_HASHMAP_TESTS_H
+#define CORE_HASHMAP_TESTS_H
+
+namespace Core::HashMapTests {
+    void test();
+}
+
+#endif

+ 263 - 0
tests/LinkedListTests.cpp

@@ -0,0 +1,263 @@
+#include "tests/LinkedListTests.h"
+
+#include "data/LinkedList.h"
+#include "test/Test.h"
+
+struct LinkedListTester {
+    int a;
+    LinkedListTester(int a_) : a(a_) {
+    }
+    LinkedListTester(const LinkedListTester&) = delete;
+    LinkedListTester(LinkedListTester&&) = delete;
+    LinkedListTester& operator=(const LinkedListTester&) = delete;
+    LinkedListTester& operator=(LinkedListTester&&) = delete;
+};
+
+using IntList = Core::LinkedList<int>;
+
+static void testWithoutCopyOrMove(Core::Test& test) {
+    Core::LinkedList<LinkedListTester> list;
+    test.checkFalse(list.add(3) == nullptr, "without copy or move");
+}
+
+static void testAdd(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(5) == nullptr, "add works 1");
+    auto iter = list.begin();
+    test.checkEqual(5, *iter, "contains added value");
+    test.checkEqual(1, list.getLength(), "sizes is increased by add");
+}
+
+static void testMultipleAdd(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(4) == nullptr, "add works 2");
+    test.checkFalse(list.add(3) == nullptr, "add works 3");
+    test.checkFalse(list.add(2) == nullptr, "add works 4");
+    auto iter = list.begin();
+    test.checkEqual(4, *iter, "contains added value 1");
+    test.checkEqual(3, *(++iter), "contains added value 2");
+    test.checkEqual(2, *(++iter), "contains added value 3");
+    test.checkEqual(3, list.getLength(), "sizes is increased by add");
+}
+
+static void testClear(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(5) == nullptr, "add works 6");
+    test.checkFalse(list.add(4) == nullptr, "add works 7");
+    list.clear();
+    test.checkEqual(0, list.getLength(), "length is 0 after clear");
+    test.checkFalse(list.begin() != list.end(),
+                    "iterator is at end after clear");
+}
+
+static void testBigAdd(Core::Test& test) {
+    IntList list;
+    for(int i = 0; i < 1000000; i++) {
+        test.checkFalse(list.add(i) == nullptr, "add works in big add");
+    }
+    auto iter = list.begin();
+    for(int i = 0; i < list.getLength(); i++) {
+        test.checkEqual(i, *iter, "big add");
+        ++iter;
+    }
+    test.checkEqual(1000000, list.getLength(), "big add length");
+}
+
+static void testCopy(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1) == nullptr, "add works 11");
+    test.checkFalse(list.add(2) == nullptr, "add works 12");
+    test.checkFalse(list.add(3) == nullptr, "add works 13");
+
+    IntList copy;
+    test.checkFalse(copy.copyFrom(list), "copy works 1");
+    test.checkEqual(list.getLength(), copy.getLength(), "copy has same length");
+    auto iterA = list.begin();
+    auto iterB = copy.begin();
+    for(int i = 0; i < copy.getLength() && i < list.getLength(); i++) {
+        test.checkEqual(*iterA, *iterB, "copy has same values");
+        ++iterA;
+        ++iterB;
+    }
+}
+
+static void testMove(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1) == nullptr, "add works 14");
+    test.checkFalse(list.add(2) == nullptr, "add works 15");
+    test.checkFalse(list.add(3) == nullptr, "add works 16");
+
+    const IntList move(Core::move(list));
+    test.checkEqual(0, list.getLength(), "moved has length 0");
+    test.checkEqual(3, move.getLength(), "moved passes length");
+    auto iter = move.begin();
+    test.checkEqual(1, *iter, "moved passes values");
+    test.checkEqual(2, *(++iter), "moved passes values");
+    test.checkEqual(3, *(++iter), "moved passes values");
+}
+
+static void testMoveAssignment(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1) == nullptr, "add works 17");
+    test.checkFalse(list.add(2) == nullptr, "add works 18");
+    test.checkFalse(list.add(3) == nullptr, "add works 19");
+
+    IntList move;
+    move = Core::move(list);
+    test.checkEqual(0, list.getLength(), "assignment moved has length 0");
+    test.checkEqual(3, move.getLength(), "assignment moved passes length");
+    auto iter = move.begin();
+    test.checkEqual(1, *iter, "assignment moved passes values");
+    test.checkEqual(2, *(++iter), "assignment moved passes values");
+    test.checkEqual(3, *(++iter), "assignment moved passes values");
+}
+
+static void testToString1(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1) == nullptr, "add works 20");
+    test.checkFalse(list.add(243) == nullptr, "add works 21");
+    test.checkFalse(list.add(-423) == nullptr, "add works 22");
+    test.checkEqual(Core::String("[1, 243, -423]"), Core::String(list),
+                    "to string 1");
+}
+
+static void testToString2(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1) == nullptr, "add works 23");
+    test.checkEqual(Core::String("[1]"), Core::String(list), "to string 2");
+}
+
+static void testToString3(Core::Test& test) {
+    IntList list;
+    test.checkEqual(Core::String("[]"), Core::String(list), "to string 3");
+}
+
+static void testRemove(Core::Test& test) {
+    IntList list;
+    IntList::Node* a = list.add(4);
+    IntList::Node* b = list.add(3);
+    IntList::Node* c = list.add(2);
+    IntList::Node* d = list.add(1);
+    test.checkFalse(a == nullptr, "add works 24");
+    test.checkFalse(b == nullptr, "add works 25");
+    test.checkFalse(c == nullptr, "add works 26");
+    test.checkFalse(c == nullptr, "add works 27");
+
+    list.remove(b);
+    auto iter = list.begin();
+    test.checkEqual(4, *iter, "value after remove 1");
+    test.checkEqual(2, *(++iter), "value after remove 2");
+    test.checkEqual(1, *(++iter), "value after remove 3");
+    test.checkEqual(3, list.getLength(), "size after remove 1");
+
+    list.remove(a);
+    iter = list.begin();
+    test.checkEqual(2, *iter, "value after remove 4");
+    test.checkEqual(1, *(++iter), "value after remove 5");
+    test.checkEqual(2, list.getLength(), "size after remove 2");
+
+    list.remove(d);
+    iter = list.begin();
+    test.checkEqual(2, *iter, "value after remove 6");
+    test.checkEqual(1, list.getLength(), "size after remove 3");
+
+    list.remove(c);
+    test.checkFalse(list.begin() != list.end(), "empty iterator after remove");
+    test.checkEqual(0, list.getLength(), "size after remove 4");
+
+    test.checkTrue(a == nullptr, "nodes are null after remove 1");
+    test.checkTrue(b == nullptr, "nodes are null after remove 2");
+    test.checkTrue(c == nullptr, "nodes are null after remove 3");
+    test.checkTrue(c == nullptr, "nodes are null after remove 4");
+}
+
+static void testRemoveFirst(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(4) == nullptr, "add works 28");
+    test.checkFalse(list.add(3) == nullptr, "add works 29");
+    test.checkFalse(list.add(2) == nullptr, "add works 30");
+    test.checkFalse(list.add(1) == nullptr, "add works 31");
+
+    list.removeFirst();
+    auto iter = list.begin();
+    test.checkEqual(3, *iter, "value after remove first 1");
+    test.checkEqual(2, *(++iter), "value after remove first 2");
+    test.checkEqual(1, *(++iter), "value after remove first 3");
+    test.checkEqual(3, list.getLength(), "size after remove first 1");
+
+    list.removeFirst();
+    iter = list.begin();
+    test.checkEqual(2, *iter, "value after remove first 4");
+    test.checkEqual(1, *(++iter), "value after remove first 5");
+    test.checkEqual(2, list.getLength(), "size after remove first 2");
+
+    list.removeFirst();
+    iter = list.begin();
+    test.checkEqual(1, *iter, "value after remove first 6");
+    test.checkEqual(1, list.getLength(), "size after remove first 3");
+
+    list.removeFirst();
+    test.checkFalse(list.begin() != list.end(),
+                    "empty iterator after remove first");
+    test.checkEqual(0, list.getLength(), "size after remove first 4");
+
+    list.removeFirst();
+    test.checkFalse(list.begin() != list.end(),
+                    "empty iterator after remove first");
+    test.checkEqual(0, list.getLength(), "size after remove first 5");
+}
+
+static void testRemoveLast(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(4) == nullptr, "add works 32");
+    test.checkFalse(list.add(3) == nullptr, "add works 33");
+    test.checkFalse(list.add(2) == nullptr, "add works 34");
+    test.checkFalse(list.add(1) == nullptr, "add works 35");
+
+    list.removeLast();
+    auto iter = list.begin();
+    test.checkEqual(4, *iter, "value after remove last 1");
+    test.checkEqual(3, *(++iter), "value after remove last 2");
+    test.checkEqual(2, *(++iter), "value after remove last 3");
+    test.checkEqual(3, list.getLength(), "size after remove last 1");
+
+    list.removeLast();
+    iter = list.begin();
+    test.checkEqual(4, *iter, "value after remove last 4");
+    test.checkEqual(3, *(++iter), "value after remove last 5");
+    test.checkEqual(2, list.getLength(), "size after remove last 2");
+
+    list.removeLast();
+    iter = list.begin();
+    test.checkEqual(4, *iter, "value after remove last 6");
+    test.checkEqual(1, list.getLength(), "size after remove last 3");
+
+    list.removeLast();
+    test.checkFalse(list.begin() != list.end(),
+                    "empty iterator after remove last");
+    test.checkEqual(0, list.getLength(), "size after remove last 4");
+
+    list.removeLast();
+    test.checkFalse(list.begin() != list.end(),
+                    "empty iterator after remove last");
+    test.checkEqual(0, list.getLength(), "size after remove 5");
+}
+
+void Core::LinkedListTests::test() {
+    Core::Test test("LinkedList");
+    testWithoutCopyOrMove(test);
+    testAdd(test);
+    testMultipleAdd(test);
+    testClear(test);
+    testBigAdd(test);
+    testCopy(test);
+    testMove(test);
+    testMoveAssignment(test);
+    testToString1(test);
+    testToString2(test);
+    testToString3(test);
+    testRemove(test);
+    testRemoveFirst(test);
+    testRemoveLast(test);
+    test.finalize();
+}

+ 8 - 0
tests/LinkedListTests.h

@@ -0,0 +1,8 @@
+#ifndef CORE_LINKED_LIST_TESTS_H
+#define CORE_LINKED_LIST_TESTS_H
+
+namespace Core::LinkedListTests {
+    void test();
+}
+
+#endif

+ 204 - 0
tests/ListTests.cpp

@@ -0,0 +1,204 @@
+#include "tests/ListTests.h"
+
+#include "data/List.h"
+#include "test/Test.h"
+
+using IntList = Core::List<int>;
+
+static void testAdd(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(5), "add works 1");
+    test.checkEqual(5, list[0], "contains added value");
+    test.checkEqual(1, list.getLength(), "sizes is increased by add");
+}
+
+static void testMultipleAdd(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(4), "add works 2");
+    test.checkFalse(list.add(3), "add works 3");
+    test.checkFalse(list.add(2), "add works 4");
+    test.checkEqual(4, list[0], "contains added value 1");
+    test.checkEqual(3, list[1], "contains added value 2");
+    test.checkEqual(2, list[2], "contains added value 3");
+    test.checkEqual(3, list.getLength(), "sizes is increased by add");
+}
+
+static void testAddReplace(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(5), "add works 5");
+    list[0] = 3;
+    test.checkEqual(3, list[0], "value is overwritten");
+}
+
+static void testClear(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(5), "add works 6");
+    test.checkFalse(list.add(4), "add works 7");
+    list.clear();
+    test.checkEqual(0, list.getLength(), "length is 0 after clear");
+}
+
+static void testShrink(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(5), "add works 8");
+    test.checkFalse(list.add(4), "add works 9");
+    test.checkFalse(list.add(3), "add works 10");
+    test.checkTrue(list.getCapacity() >= 3, "capacity is >= 3 after adding");
+    test.checkFalse(list.shrink(), "shrink works 1");
+    test.checkTrue(list.getLength() == 3, "length is 3 after shrink");
+    test.checkTrue(list.getCapacity() == 3, "capacity is 3 after shrink");
+    test.checkEqual(5, list[0], "data ok after shrink 1");
+    test.checkEqual(4, list[1], "data ok after shrink 2");
+    test.checkEqual(3, list[2], "data ok after shrink 3");
+}
+
+static void testBigAdd(Core::Test& test) {
+    IntList list;
+    for(int i = 0; i < 1000000; i++) {
+        test.checkFalse(list.add(i), "add works in big add");
+    }
+    for(int i = 0; i < list.getLength(); i++) {
+        test.checkEqual(i, list[i], "big add");
+    }
+    test.checkEqual(1000000, list.getLength(), "big add length");
+}
+
+static void testCopy(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1), "add works 11");
+    test.checkFalse(list.add(2), "add works 12");
+    test.checkFalse(list.add(3), "add works 13");
+
+    IntList copy;
+    test.checkFalse(copy.copyFrom(list), "copy works 1");
+    test.checkEqual(list.getLength(), copy.getLength(), "copy has same length");
+    for(int i = 0; i < copy.getLength() && i < list.getLength(); i++) {
+        test.checkEqual(list[i], copy[i], "copy has same values");
+    }
+}
+
+static void testMove(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1), "add works 14");
+    test.checkFalse(list.add(2), "add works 15");
+    test.checkFalse(list.add(3), "add works 16");
+
+    IntList move(Core::move(list));
+    test.checkEqual(0, list.getLength(), "moved has length 0");
+    test.checkEqual(3, move.getLength(), "moved passes length");
+    test.checkEqual(1, move[0], "moved passes values");
+    test.checkEqual(2, move[1], "moved passes values");
+    test.checkEqual(3, move[2], "moved passes values");
+}
+
+static void testMoveAssignment(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1), "add works 17");
+    test.checkFalse(list.add(2), "add works 18");
+    test.checkFalse(list.add(3), "add works 19");
+
+    IntList move;
+    move = Core::move(list);
+    test.checkEqual(0, list.getLength(), "assignment moved has length 0");
+    test.checkEqual(3, move.getLength(), "assignment moved passes length");
+    test.checkEqual(1, move[0], "assignment moved passes values");
+    test.checkEqual(2, move[1], "assignment moved passes values");
+    test.checkEqual(3, move[2], "assignment moved passes values");
+}
+
+static void testToString1(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1), "add works 20");
+    test.checkFalse(list.add(243), "add works 21");
+    test.checkFalse(list.add(-423), "add works 22");
+    test.checkEqual(Core::String("[1, 243, -423]"), Core::String(list),
+                    "to string 1");
+}
+
+static void testToString2(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(1), "add works 23");
+    test.checkEqual(Core::String("[1]"), Core::String(list), "to string 2");
+}
+
+static void testToString3(Core::Test& test) {
+    IntList list;
+    test.checkEqual(Core::String("[]"), Core::String(list), "to string 3");
+}
+
+static void testRemoveBySwap(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(4), "add works 24");
+    test.checkFalse(list.add(3), "add works 25");
+    test.checkFalse(list.add(2), "add works 26");
+    test.checkTrue(list.removeBySwap(-1), "remove by swap does not work 1");
+    test.checkFalse(list.removeBySwap(0), "remove by swap works 1");
+    test.checkTrue(list.removeBySwap(2), "remove by swap does not work 2");
+    test.checkEqual(2, list[0], "remove by swap 1");
+    test.checkEqual(3, list[1], "remove by swap 2");
+    test.checkEqual(2, list.getLength(), "remove by swap 3");
+    test.checkFalse(list.removeBySwap(1), "remove by swap works 2");
+    test.checkEqual(2, list[0], "remove by swap 4");
+    test.checkEqual(1, list.getLength(), "remove by swap 5");
+    test.checkFalse(list.removeBySwap(0), "remove by swap works 3");
+    test.checkEqual(0, list.getLength(), "remove by swap 6");
+    test.checkTrue(list.removeBySwap(0), "remove by swap does not work 3");
+}
+
+static void testRemove(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.add(4), "add works 27");
+    test.checkFalse(list.add(3), "add works 28");
+    test.checkFalse(list.add(2), "add works 29");
+    test.checkTrue(list.remove(-1), "remove does not work 1");
+    test.checkFalse(list.remove(0), "remove works 1");
+    test.checkTrue(list.remove(2), "remove does not work 2");
+    test.checkEqual(3, list[0], "remove 1");
+    test.checkEqual(2, list[1], "remove 2");
+    test.checkEqual(2, list.getLength(), "remove 3");
+    test.checkFalse(list.remove(1), "remove works 2");
+    test.checkEqual(3, list[0], "remove 4");
+    test.checkEqual(1, list.getLength(), "remove 5");
+    test.checkFalse(list.remove(0), "remove works 3");
+    test.checkEqual(0, list.getLength(), "remove 6");
+    test.checkTrue(list.remove(0), "remove does not work 3");
+}
+
+static void testResize(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.resize(5, 10), "resize works");
+    test.checkEqual(5, list.getLength(), "correct size after resize");
+    for(int i = 0; i < 5; i++) {
+        test.checkEqual(10, list[i], "correct content after resize");
+    }
+}
+
+static void testDefaultResize(Core::Test& test) {
+    IntList list;
+    test.checkFalse(list.resize(5), "default resize works 1");
+    test.checkEqual(5, list.getLength(), "correct size after default resize");
+    for(int i = 0; i < 5; i++) {
+        test.checkEqual(0, list[i], "correct content after default resize");
+    }
+}
+
+void Core::ListTests::test() {
+    Core::Test test("List");
+    testAdd(test);
+    testMultipleAdd(test);
+    testAddReplace(test);
+    testClear(test);
+    testShrink(test);
+    testBigAdd(test);
+    testCopy(test);
+    testMove(test);
+    testMoveAssignment(test);
+    testToString1(test);
+    testToString2(test);
+    testToString3(test);
+    testRemoveBySwap(test);
+    testRemove(test);
+    testResize(test);
+    testDefaultResize(test);
+    test.finalize();
+}

+ 8 - 0
tests/ListTests.h

@@ -0,0 +1,8 @@
+#ifndef CORE_LIST_TESTS_H
+#define CORE_LIST_TESTS_H
+
+namespace Core::ListTests {
+    void test();
+}
+
+#endif

+ 46 - 0
tests/UniquePointerTests.cpp

@@ -0,0 +1,46 @@
+#include "tests/UniquePointerTests.h"
+
+#include "test/Test.h"
+#include "utils/UniquePointer.h"
+#include "utils/Utility.h"
+
+struct B {
+    static int instances;
+
+    B() {
+        instances++;
+    }
+
+    ~B() {
+        instances--;
+    }
+};
+
+int B::instances = 0;
+
+static void testDestroy(Core::Test& test) {
+    {
+        Core::UniquePointer<B> p(new B());
+        test.checkEqual(1, B::instances, "one instance");
+    }
+    test.checkEqual(0, B::instances, "instance is destroyed");
+}
+
+static void testMoveDestroys(Core::Test& test) {
+    {
+        Core::UniquePointer<B> p1(new B());
+        Core::UniquePointer<B> p2(new B());
+        test.checkEqual(2, B::instances, "two instances");
+        p1 = Core::move(p2);
+        test.checkEqual(1, B::instances, "one after move");
+    }
+    test.checkEqual(0, B::instances,
+                    "everything destroyed correctly after move");
+}
+
+void Core::UniquePointerTests::test() {
+    Test test("UniquePointer");
+    testDestroy(test);
+    testMoveDestroys(test);
+    test.finalize();
+}

+ 8 - 0
tests/UniquePointerTests.h

@@ -0,0 +1,8 @@
+#ifndef CORE_UNIQUE_POINTER_TESTS_H
+#define CORE_UNIQUE_POINTER_TESTS_H
+
+namespace Core::UniquePointerTests {
+    void test();
+}
+
+#endif

+ 54 - 0
utils/UniquePointer.h

@@ -0,0 +1,54 @@
+#ifndef CORE_UNIQUE_POINTER_H
+#define CORE_UNIQUE_POINTER_H
+
+namespace Core {
+    template<typename T>
+    class UniquePointer {
+        T* t;
+
+    public:
+        UniquePointer(T* t_) : t(t_) {
+        }
+
+        UniquePointer() : UniquePointer(nullptr) {
+        }
+
+        ~UniquePointer() {
+            delete t;
+        }
+
+        UniquePointer(const UniquePointer&) = delete;
+        UniquePointer& operator=(const UniquePointer&) = delete;
+
+        UniquePointer(UniquePointer&& other) : UniquePointer(other.t) {
+            other.t = nullptr;
+        }
+
+        UniquePointer& operator=(UniquePointer&& other) {
+            if(this != &other) {
+                delete t;
+                t = other.t;
+                other.t = nullptr;
+            }
+            return *this;
+        };
+
+        T* operator->() {
+            return t;
+        }
+
+        const T* operator->() const {
+            return t;
+        }
+
+        operator T*() {
+            return t;
+        }
+
+        operator const T*() const {
+            return t;
+        }
+    };
+}
+
+#endif

+ 5 - 6
utils/Utility.cpp

@@ -1,26 +1,25 @@
 #include "utils/Utility.h"
 
-/*
 void* operator new(size_t bytes) noexcept {
     return malloc(bytes);
-}*/
+}
 
 void* operator new[](size_t bytes) noexcept {
     return malloc(bytes);
 }
 
-/*void operator delete(void* p) noexcept {
+void operator delete(void* p) noexcept {
     free(p);
-}*/
+}
 
 void operator delete[](void* p) noexcept {
     free(p);
 }
 
-/*void operator delete(void* p, size_t bytes) noexcept {
+void operator delete(void* p, size_t bytes) noexcept {
     (void)bytes;
     free(p);
-}*/
+}
 
 void operator delete[](void* p, size_t bytes) noexcept {
     (void)bytes;

+ 3 - 3
utils/Utility.h

@@ -71,11 +71,11 @@ namespace Core {
     }
 }
 
-// void* operator new(size_t bytes) noexcept;
+void* operator new(size_t bytes) noexcept;
 void* operator new[](size_t bytes) noexcept;
-// void operator delete(void* p) noexcept;
+void operator delete(void* p) noexcept;
 void operator delete[](void* p) noexcept;
-// void operator delete(void* p, size_t bytes) noexcept;
+void operator delete(void* p, size_t bytes) noexcept;
 void operator delete[](void* p, size_t bytes) noexcept;
 void* operator new(size_t bytes, void* p) noexcept;
 // void* operator new[](size_t bytes, void* p) noexcept;