/*
 * Copyright (c) 2018-2019 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. 
 */

#include "TestHarness.h"
#include <functional>
#include <memory>
#include "pas_bootstrap_free_heap.h"
#include "pas_fast_large_free_heap.h"
#include "pas_heap_lock.h"
#include "pas_large_free.h"
#include "pas_large_free_heap_config.h"
#include "pas_page_malloc.h"
#include "pas_simple_large_free_heap.h"
#include <set>
#include <vector>

using namespace std;

namespace {

pas_alignment alignSimple(size_t size)
{
    return pas_alignment_create_traditional(size);
}

pas_aligned_allocation_result trappingAllocator(size_t size, pas_alignment alignment)
{
    CHECK(!"Should not have called allocator");
    return pas_aligned_allocation_result_create_empty();
}

void trappingDeallocator(void* base, size_t size)
{
    CHECK(!"Should not have called deallocator");
}

pas_aligned_allocation_result failingAllocator(size_t size, pas_alignment alignment)
{
    return pas_aligned_allocation_result_create_empty();
}

struct Action {
    enum Kind {
        Allocate,
        Deallocate
    };
    
    Action() = default;
    
    static Action allocate(
        size_t size, pas_alignment alignment, uintptr_t expectedResult,
        function<pas_aligned_allocation_result(size_t size, pas_alignment alignment)> allocator = trappingAllocator,
        function<void(void* base, size_t size)> deallocator = trappingDeallocator)
    {
        Action result;
        result.kind = Allocate;
        result.size = size;
        result.alignment = alignment;
        result.allocator = allocator;
        result.deallocator = deallocator;
        result.result = expectedResult;
        return result;
    }
    
    static Action deallocate(
        uintptr_t base, size_t size,
        function<pas_aligned_allocation_result(size_t size, pas_alignment alignment)> allocator = trappingAllocator,
        function<void(void* base, size_t size)> deallocator = trappingDeallocator)
    {
        Action result;
        result.kind = Deallocate;
        result.base = base;
        result.size = size;
        result.allocator = allocator;
        result.deallocator = deallocator;
        return result;
    }
    
    Kind kind { Allocate };
    uintptr_t base { 0 };
    size_t size { 0 };
    pas_alignment alignment { 0 };
    function<pas_aligned_allocation_result(size_t size, pas_alignment alignment)> allocator;
    function<void(void* base, size_t size)> deallocator;
    uintptr_t result { 0 };
};

struct Free {
    Free() = default;
    
    Free(uintptr_t begin, uintptr_t end)
        : begin(begin)
        , end(end)
    {
    }
    
    bool operator==(const Free& other) const
    {
        return begin == other.begin
            && end == other.end;
    }
    
    bool operator<(const Free& other) const
    {
        if (begin != other.begin)
            return begin < other.begin;
        return end < other.end;
    }
    
    uintptr_t begin;
    uintptr_t end;
};

struct Allocation {
    Allocation() = default;
    
    Allocation(size_t expectedSize, pas_alignment expectedAlignment,
               uintptr_t leftPadding, uintptr_t resultBase,
               uintptr_t rightPadding, uintptr_t end)
        : expectedSize(expectedSize)
        , expectedAlignment(expectedAlignment)
        , leftPadding(leftPadding)
        , resultBase(resultBase)
        , rightPadding(rightPadding)
        , end(end)
    {
    }
    
    size_t expectedSize { 0 };
    pas_alignment expectedAlignment { 0 };
    uintptr_t leftPadding { 0 };
    uintptr_t resultBase { 0 };
    uintptr_t rightPadding { 0 };
    uintptr_t end { 0 };
};

function<pas_aligned_allocation_result(size_t size, pas_alignment alignment)>
allocateFinite(vector<Allocation> allocations)
{
    shared_ptr<size_t> allocationCursor = make_shared<size_t>(0);
    return [=] (size_t size, pas_alignment alignment) -> pas_aligned_allocation_result {
        CHECK_LESS(*allocationCursor, allocations.size());
        
        Allocation allocation = allocations[(*allocationCursor)++];
        
        CHECK_EQUAL(size, allocation.expectedSize);
        CHECK_ALIGNMENT_EQUAL(alignment, allocation.expectedAlignment);
        
        pas_aligned_allocation_result result;
        result.left_padding = (void*)allocation.leftPadding;
        result.left_padding_size = allocation.resultBase - allocation.leftPadding;
        result.result = (void*)allocation.resultBase;
        result.result_size = allocation.rightPadding - allocation.resultBase;
        result.right_padding = (void*)allocation.rightPadding;
        result.right_padding_size = allocation.end - allocation.rightPadding;
        result.zero_mode = pas_zero_mode_may_have_non_zero;
        
        return result;
    };
}

function<pas_aligned_allocation_result(size_t size, pas_alignment alignment)>
allocateOnce(size_t expectedSize,
             pas_alignment expectedAlignment,
             uintptr_t leftPadding,
             uintptr_t resultBase,
             uintptr_t rightPadding,
             uintptr_t end)
{
    return allocateFinite({ Allocation(expectedSize, expectedAlignment,
                                       leftPadding, resultBase,
                                       rightPadding, end) });
}

function<void(void* base, size_t size)> deallocateFinite(vector<Free> frees)
{
    shared_ptr<size_t> freeCursor = make_shared<size_t>(0);
    return [=] (void* base, size_t size) {
        CHECK_LESS(*freeCursor, frees.size());
        CHECK_EQUAL(reinterpret_cast<uintptr_t>(base), frees[*freeCursor].begin);
        CHECK_EQUAL(reinterpret_cast<uintptr_t>(base) + size, frees[*freeCursor].end);
        (*freeCursor)++;
    };
}

ostream& operator<<(ostream& out, const Free& free)
{
    out << "{begin = " << free.begin << ", end = " << free.end
        << ", size = " << (free.end - free.begin) << "}";
    return out;
}

pas_aligned_allocation_result allocatorAdapter(size_t size, pas_alignment alignment, void* arg)
{
    const function<pas_aligned_allocation_result(size_t size, pas_alignment alignment)>* allocator =
        reinterpret_cast<const function<pas_aligned_allocation_result(size_t size, pas_alignment alignment)>*>(arg);
    return (*allocator)(size, alignment);
}

void deallocatorAdapter(void* base, size_t size, void* arg)
{
    const function<void(void* base, size_t size)>* deallocator =
        reinterpret_cast<const function<void(void* base, size_t size)>*>(arg);
    (*deallocator)(base, size);
}

bool iterateAdapter(pas_large_free free,
                    void* arg)
{
    std::function<bool(pas_large_free)>* capturedFunc =
        static_cast<std::function<bool(pas_large_free)>*>(arg);
    return (*capturedFunc)(free);
}

template<typename HeapType,
         typename IterateFunc,
         typename Func>
void iterateHeap(HeapType* heap,
                 const IterateFunc& iterateFunc,
                 const Func& func)
{
    std::function<bool(pas_large_free)> capturedFunc = func;
    iterateFunc(heap, iterateAdapter, &capturedFunc);
}

template<typename HeapType,
         typename AllocateFunc,
         typename DeallocateFunc,
         typename IterateFunc>
void testLargeFreeHeapImpl(HeapType* heap,
                           const vector<Action>& actions,
                           const set<Free>& frees,
                           size_t typeSize,
                           const AllocateFunc& allocateFunc,
                           const DeallocateFunc& deallocateFunc,
                           const IterateFunc& iterateFunc)
{
    for (const Action& action : actions) {
        pas_large_free_heap_config config;
        
        memset(&config, 0, sizeof(config));
        
        config.type_size = typeSize;
        config.min_alignment = 1;
        CHECK(!!action.allocator);
        config.aligned_allocator = allocatorAdapter;
        config.aligned_allocator_arg = const_cast<void*>(reinterpret_cast<const void*>(&action.allocator));
        if (action.deallocator) {
            config.deallocator = deallocatorAdapter;
            config.deallocator_arg = const_cast<void*>(reinterpret_cast<const void*>(&action.deallocator));
        } else {
            config.deallocator = nullptr;
            config.deallocator_arg = nullptr;
        }
        
        pas_heap_lock_lock();
        
        switch (action.kind) {
        case Action::Allocate: {
            pas_string_stream stream;
            stringStreamConstruct(&stream);
            pas_alignment_dump(action.alignment, reinterpret_cast<pas_stream*>(&stream));
            cout << "    Allocating size = " << action.size
                 << ", alignment = " << pas_string_stream_get_string(&stream) << endl;
            pas_string_stream_destruct(&stream);
            CHECK_EQUAL(allocateFunc(heap, action.size, action.alignment, &config).begin,
                        action.result);
            break;
        }
        case Action::Deallocate:
            cout << "    Freeing base = " << action.base << endl;
            deallocateFunc(heap, action.base, action.base + action.size,
                           pas_zero_mode_may_have_non_zero,
                           &config);
            break;
        }
        
        pas_heap_lock_unlock();
    }
    
    bool ok = true;
    set<Free> freesFound;
    iterateHeap(
        heap,
        iterateFunc,
        [&] (pas_large_free largeFree) {
            Free free(largeFree.begin, largeFree.end);
            if (freesFound.count(free)) {
                cout << "    FAIL: Found duplicate entry " << free << endl;
                ok = false;
            }
            freesFound.insert(free);
            return true;
        });
    
    if (freesFound != frees || !ok) {
        cout << "    FAIL: Free validation failed." << endl;
        
        auto dumpFree = [] (const set<Free>& frees) {
            bool first = true;
            for (const Free& free : frees) {
                if (first)
                    first = false;
                else
                    cout << ", ";
                cout << free;
            }
        };
        
        cout << "    Expected: ";
        dumpFree(frees);
        cout << endl;
        cout << "    Actual: ";
        dumpFree(freesFound);
        cout << endl;
        CHECK(!"Free validation failed.");
    }
}

void testSimpleLargeFreeHeap(const vector<Action>& actions,
                             const set<Free>& frees,
                             size_t typeSize)
{
    pas_simple_large_free_heap heap;
    pas_simple_large_free_heap_construct(&heap);
    testLargeFreeHeapImpl(
        &heap, actions, frees, typeSize,
        pas_simple_large_free_heap_try_allocate,
        pas_simple_large_free_heap_deallocate,
        pas_simple_large_free_heap_for_each_free);
}

void testFastLargeFreeHeap(const vector<Action>& actions,
                           const set<Free>& frees,
                           size_t typeSize)
{
    pas_fast_large_free_heap heap;
    pas_fast_large_free_heap_construct(&heap);
    testLargeFreeHeapImpl(
        &heap, actions, frees, typeSize,
        pas_fast_large_free_heap_try_allocate,
        pas_fast_large_free_heap_deallocate,
        pas_fast_large_free_heap_for_each_free);
}

void testBootstrapHeap(const vector<Action>& actions,
                       const set<Free>& frees,
                       size_t typeSize)
{
    static constexpr size_t slabSize = 1lu << 20;
    void* slabPtr = pas_page_malloc_try_allocate_without_deallocating_padding(
        slabSize, alignSimple(slabSize), false).result;
    CHECK(slabPtr);
    uintptr_t slab = reinterpret_cast<uintptr_t>(slabPtr);
    
    cout << "    Slab at " << slab << endl;
    
    vector<Action> newActions;
    set<Free> newFrees;
    
    for (const Action& action : actions) {
        Action newAction = action;
        
        if (newAction.base)
            newAction.base += slab;
        
        newAction.allocator = [=] (size_t size, pas_alignment alignment) -> pas_aligned_allocation_result {
            pas_aligned_allocation_result result = action.allocator(size, alignment);
            if (!result.result)
                return result;
            result.result = reinterpret_cast<void*>(
                reinterpret_cast<uintptr_t>(result.result) + slab);
            result.left_padding = reinterpret_cast<void*>(
                reinterpret_cast<uintptr_t>(result.left_padding) + slab);
            result.right_padding = reinterpret_cast<void*>(
                reinterpret_cast<uintptr_t>(result.right_padding) + slab);
            return result;
        };
        
        if (action.deallocator) {
            newAction.deallocator = [=] (void* base, size_t size) {
                action.deallocator(reinterpret_cast<void*>(
                                       reinterpret_cast<uintptr_t>(base) - slab),
                                   size);
            };
        }
        
        if (newAction.result)
            newAction.result += slab;
        
        newActions.push_back(newAction);
    }
    
    for (const Free& free : frees)
        newFrees.insert(Free(free.begin + slab, free.end + slab));
    
    testLargeFreeHeapImpl(
        &pas_bootstrap_free_heap, newActions, newFrees, typeSize,
        pas_simple_large_free_heap_try_allocate,
        pas_simple_large_free_heap_deallocate,
        pas_simple_large_free_heap_for_each_free);
}

size_t freeListSize(size_t size)
{
    return PAS_MAX(size, PAS_BOOTSTRAP_FREE_LIST_MINIMUM_SIZE) * sizeof(pas_large_free);
}

} // anonymous namespace

void addLargeFreeHeapTests()
{
    ADD_TEST(testSimpleLargeFreeHeap({ }, { }, 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(666, alignSimple(4), 0, failingAllocator, { })
                 },
                 {
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::deallocate(4200, 3500),
                     Action::allocate(100, alignSimple(16), 4208),
                     Action::allocate(1000, alignSimple(4), 4308),
                     Action::allocate(2000, alignSimple(8), 5312),
                 },
                 {
                     Free(4200, 4208),
                     Free(5308, 5312),
                     Free(7312, 7700)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 7700),
                                      { }),
                     Action::allocate(1000, alignSimple(4), 4308),
                     Action::allocate(2000, alignSimple(8), 5312),
                 },
                 {
                     Free(4200, 4208),
                     Free(5308, 5312),
                     Free(7312, 7700)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 7700),
                                      deallocateFinite({
                                                           Free(4200, 4208),
                                                           Free(4308, 7700)
                                                       }))
                 },
                 { },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 7700),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 7700)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4500),
                                      { }),
                     Action::allocate(1000, alignSimple(4), 4308,
                                      allocateOnce(1000, alignSimple(4),
                                                   4500, 4500,
                                                   5500, 7700),
                                      { }),
                     Action::allocate(2000, alignSimple(8), 5312),
                 },
                 {
                     Free(4200, 4208),
                     Free(5308, 5312),
                     Free(7312, 7700)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4500),
                                      { }),
                     Action::deallocate(7500, 200),
                     Action::allocate(1000, alignSimple(4), 4308,
                                      allocateOnce(1000, alignSimple(4),
                                                   4500, 4500,
                                                   5500, 7500),
                                      { }),
                     Action::allocate(2000, alignSimple(8), 5312),
                 },
                 {
                     Free(4200, 4208),
                     Free(5308, 5312),
                     Free(7312, 7700)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4500),
                                      { }),
                     Action::allocate(100, alignSimple(128), 4352)
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4352),
                     Free(4452, 4500)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4208, 4208,
                                                   4308, 4500),
                                      { }),
                     Action::allocate(100, alignSimple(128), 4352)
                 },
                 {
                     Free(4308, 4352),
                     Free(4452, 4500)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(128), 4352,
                                      allocateOnce(100, alignSimple(128),
                                                   4400, 4480,
                                                   4580, 4608),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4352),
                     Free(4452, 4608)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4400, 4608,
                                                   4708, 5120),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4608),
                     Free(4708, 5120)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(4), 4308,
                                      allocateOnce(100, alignSimple(4),
                                                   4400, 4400,
                                                   4500, 4600),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4408, 4600)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4400, 4608,
                                                   4708, 4708),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4608)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4608, 4608,
                                                   4708, 5120),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4400),
                     Free(4708, 5120)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4608, 4608,
                                                   4708, 4708),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4400)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(128), 4352,
                                      allocateOnce(100, alignSimple(128),
                                                   4400, 4480,
                                                   4580, 4608),
                                      deallocateFinite({
                                                           Free(4452, 4608)
                                                       }))
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4352)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4400, 4608,
                                                   4708, 5120),
                                      deallocateFinite({
                                                           Free(4400, 4608),
                                                           Free(4708, 5120)
                                                       }))
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4400)
                 },
                 1));
    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(4), 4308,
                                      allocateOnce(100, alignSimple(4),
                                                   4400, 4400,
                                                   4500, 4600),
                                      deallocateFinite({
                                                           Free(4408, 4600)
                                                       }))
                 },
                 {
                     Free(4200, 4208)
                 },
                 1));

    ADD_TEST(testFastLargeFreeHeap({ }, { }, 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(666, alignSimple(4), 0, failingAllocator, { })
                 },
                 {
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::deallocate(4200, 3500),
                     Action::allocate(100, alignSimple(16), 4208),
                     Action::allocate(1000, alignSimple(4), 4308),
                     Action::allocate(2000, alignSimple(8), 5312),
                 },
                 {
                     Free(4200, 4208),
                     Free(5308, 5312),
                     Free(7312, 7700)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 7700),
                                      { }),
                     Action::allocate(1000, alignSimple(4), 4308),
                     Action::allocate(2000, alignSimple(8), 5312),
                 },
                 {
                     Free(4200, 4208),
                     Free(5308, 5312),
                     Free(7312, 7700)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 7700),
                                      deallocateFinite({
                                                           Free(4200, 4208),
                                                           Free(4308, 7700)
                                                       }))
                 },
                 { },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 7700),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 7700)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4500),
                                      { }),
                     Action::allocate(1000, alignSimple(4), 4308,
                                      allocateOnce(1000, alignSimple(4),
                                                   4500, 4500,
                                                   5500, 7700),
                                      { }),
                     Action::allocate(2000, alignSimple(8), 5312),
                 },
                 {
                     Free(4200, 4208),
                     Free(5308, 5312),
                     Free(7312, 7700)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4500),
                                      { }),
                     Action::deallocate(7500, 200),
                     Action::allocate(1000, alignSimple(4), 4308,
                                      allocateOnce(1000, alignSimple(4),
                                                   4500, 4500,
                                                   5500, 7500),
                                      { }),
                     Action::allocate(2000, alignSimple(8), 5312),
                 },
                 {
                     Free(4200, 4208),
                     Free(5308, 5312),
                     Free(7312, 7700)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4500),
                                      { }),
                     Action::allocate(100, alignSimple(128), 4352)
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4352),
                     Free(4452, 4500)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4208, 4208,
                                                   4308, 4500),
                                      { }),
                     Action::allocate(100, alignSimple(128), 4352)
                 },
                 {
                     Free(4308, 4352),
                     Free(4452, 4500)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(128), 4352,
                                      allocateOnce(100, alignSimple(128),
                                                   4400, 4480,
                                                   4580, 4608),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4352),
                     Free(4452, 4608)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4400, 4608,
                                                   4708, 5120),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4608),
                     Free(4708, 5120)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(4), 4308,
                                      allocateOnce(100, alignSimple(4),
                                                   4400, 4400,
                                                   4500, 4600),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4408, 4600)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4400, 4608,
                                                   4708, 4708),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4608)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4608, 4608,
                                                   4708, 5120),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4400),
                     Free(4708, 5120)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4608, 4608,
                                                   4708, 4708),
                                      { })
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4400)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(128), 4352,
                                      allocateOnce(100, alignSimple(128),
                                                   4400, 4480,
                                                   4580, 4608),
                                      deallocateFinite({
                                                           Free(4452, 4608)
                                                       }))
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4352)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(512), 4608,
                                      allocateOnce(100, alignSimple(512),
                                                   4400, 4608,
                                                   4708, 5120),
                                      deallocateFinite({
                                                           Free(4400, 4608),
                                                           Free(4708, 5120)
                                                       }))
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4400)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 4400),
                                      { }),
                     Action::allocate(100, alignSimple(4), 4308,
                                      allocateOnce(100, alignSimple(4),
                                                   4400, 4400,
                                                   4500, 4600),
                                      deallocateFinite({
                                                           Free(4408, 4600)
                                                       }))
                 },
                 {
                     Free(4200, 4208)
                 },
                 1));

    ADD_TEST(testSimpleLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 5328),
                                      { }),
                     Action::allocate(100, alignSimple(16), 4320),
                     Action::allocate(100, alignSimple(16), 4432),
                     Action::allocate(100, alignSimple(16), 4544),
                     Action::allocate(100, alignSimple(16), 4656),
                     Action::allocate(100, alignSimple(16), 4768),
                     Action::allocate(100, alignSimple(16), 4880),
                     Action::allocate(100, alignSimple(16), 4992),
                     Action::allocate(100, alignSimple(16), 5104),
                     Action::allocate(100, alignSimple(16), 5216),
                     Action::deallocate(4320, 100),
                     Action::deallocate(4544, 100),
                     Action::deallocate(4768, 100),
                     Action::deallocate(4992, 100),
                     Action::deallocate(5216, 100),
                     Action::allocate(100, alignSimple(16), 4320),
                     Action::deallocate(5104, 100)
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4320),
                     Free(4420, 4432),
                     Free(4532, 4656),
                     Free(4756, 4880),
                     Free(4980, 5328)
                 },
                 1));
    ADD_TEST(testFastLargeFreeHeap(
                 {
                     Action::allocate(100, alignSimple(16), 4208,
                                      allocateOnce(100, alignSimple(16),
                                                   4200, 4208,
                                                   4308, 5328),
                                      { }),
                     Action::allocate(100, alignSimple(16), 4320),
                     Action::allocate(100, alignSimple(16), 4432),
                     Action::allocate(100, alignSimple(16), 4544),
                     Action::allocate(100, alignSimple(16), 4656),
                     Action::allocate(100, alignSimple(16), 4768),
                     Action::allocate(100, alignSimple(16), 4880),
                     Action::allocate(100, alignSimple(16), 4992),
                     Action::allocate(100, alignSimple(16), 5104),
                     Action::allocate(100, alignSimple(16), 5216),
                     Action::deallocate(4320, 100),
                     Action::deallocate(4544, 100),
                     Action::deallocate(4768, 100),
                     Action::deallocate(4992, 100),
                     Action::deallocate(5216, 100),
                     Action::allocate(100, alignSimple(16), 4320),
                     Action::deallocate(5104, 100)
                 },
                 {
                     Free(4200, 4208),
                     Free(4308, 4320),
                     Free(4420, 4432),
                     Free(4532, 4656),
                     Free(4756, 4880),
                     Free(4980, 5328)
                 },
                 1));

    ADD_TEST(testBootstrapHeap({ }, { }, 1));
    ADD_TEST(testBootstrapHeap(
                 {
                     Action::allocate(666, alignSimple(4), 0, failingAllocator, { })
                 },
                 {
                 },
                 1));
    ADD_TEST(testBootstrapHeap(
                 {
                     Action::deallocate(4200, 3500),
                     Action::allocate(104, alignSimple(8), freeListSize(1) + 4200),
                     Action::allocate(1000, alignSimple(8), freeListSize(1) + 4304),
                     Action::allocate(2000, alignSimple(8), freeListSize(1) + 5304),
                 },
                 {
                     Free(freeListSize(1) + 7304, 7700)
                 },
                 1));
    ADD_TEST(testBootstrapHeap(
                 {
                     Action::allocate(104, alignSimple(8), 4200,
                                      allocateOnce(104, alignSimple(8),
                                                   4200, 4200,
                                                   4304, 5000),
                                      { }),
                 },
                 {
                     Free(4304 + freeListSize(1), 5000)
                 },
                 1));
    ADD_TEST(testBootstrapHeap(
                 {
                     Action::allocate(104, alignSimple(8), 4200,
                                      allocateOnce(104, alignSimple(8),
                                                   4200, 4200,
                                                   4304, 4304),
                                      { }),
                 },
                 { },
                 1));
    ADD_TEST(testBootstrapHeap(
                 {
                     Action::allocate(104, alignSimple(8), 4200,
                                      allocateFinite(
                                          {
                                              Allocation(104, alignSimple(8),
                                                         4200, 4200,
                                                         4304, 4312),
                                              Allocation(freeListSize(1), alignSimple(sizeof(void*)),
                                                         4312, 4312,
                                                         4312 + freeListSize(1),
                                                         4312 + freeListSize(1))
                                          }),
                                      { }),
                 },
                 {
                     Free(4304 + freeListSize(1), 4312 + freeListSize(1))
                 },
                 1));
    ADD_TEST(testBootstrapHeap(
                 {
                     Action::deallocate(4200, 800),
                     Action::deallocate(5200, 800),
                     Action::deallocate(6200, 800),
                     Action::deallocate(7200, 800),
                     Action::deallocate(8200, 800),
                     Action::deallocate(9200, 2000),
                     Action::deallocate(12200, 800),
                     Action::deallocate(13200, 800),
                     Action::deallocate(14200, 800),
                     Action::deallocate(15200, 800)
                 },
                 {
                     Free(4200, 5000),
                     Free(5200 + freeListSize(22), 6000),
                     Free(6200, 7000),
                     Free(7200, 8000),
                     Free(8200, 9000),
                     Free(9200, 11200),
                     Free(12200, 13000),
                     Free(13200, 14000),
                     Free(14200, 15000),
                     Free(15200, 16000)
                 },
                 1));
}

