The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define C_LUCY_TESTMEMORYPOOL
#define C_LUCY_MEMORYPOOL
#include "Lucy/Util/ToolSet.h"

#include "Lucy/Test.h"
#include "Lucy/Test/Util/TestMemory.h"

static void
test_oversize__growth_rate(TestBatch *batch) {
    bool_t   success             = true;
    uint64_t size                = 0;
    double   growth_count        = 0;
    double   average_growth_rate = 0.0;

    while (size < SIZE_MAX) {
        uint64_t next_size = Memory_oversize((size_t)size + 1, sizeof(void*));
        if (next_size < size) {
            success = false;
            FAIL(batch, "Asked for %" I64P ", got smaller amount %" I64P,
                 size + 1, next_size);
            break;
        }
        if (size > 0) {
            growth_count += 1;
            double growth_rate = (double)next_size / (double)size;
            double sum = growth_rate + (growth_count - 1) * average_growth_rate;
            average_growth_rate = sum / growth_count;
            if (average_growth_rate < 1.1) {
                FAIL(batch, "Average growth rate dropped below 1.1x: %f",
                     average_growth_rate);
                success = false;
                break;
            }
        }
        size = next_size;
    }
    TEST_TRUE(batch, growth_count > 0, "Grew %f times", growth_count);
    if (success) {
        TEST_TRUE(batch, average_growth_rate > 1.1,
                  "Growth rate of oversize() averages above 1.1: %.3f",
                  average_growth_rate);
    }

    for (int minimum = 1; minimum < 8; minimum++) {
        uint64_t next_size = Memory_oversize(minimum, sizeof(void*));
        double growth_rate = (double)next_size / (double)minimum;
        TEST_TRUE(batch, growth_rate > 1.2,
                  "Growth rate is higher for smaller arrays (%d, %.3f)", minimum,
                  growth_rate);
    }
}

static void
test_oversize__ceiling(TestBatch *batch) {
    for (int width = 0; width < 10; width++) {
        size_t size = Memory_oversize(SIZE_MAX, width);
        TEST_TRUE(batch, size == SIZE_MAX,
                  "Memory_oversize hits ceiling at SIZE_MAX (width %d)", width);
        size = Memory_oversize(SIZE_MAX - 1, width);
        TEST_TRUE(batch, size == SIZE_MAX,
                  "Memory_oversize hits ceiling at SIZE_MAX (width %d)", width);
    }
}

static void
test_oversize__rounding(TestBatch *batch) {
    bool_t success = true;
    int widths[] = { 1, 2, 4, 0 };

    for (int width_tick = 0; widths[width_tick] != 0; width_tick++) {
        int width = widths[width_tick];
        for (int i = 0; i < 25; i++) {
            size_t size = Memory_oversize(i, width);
            size_t bytes = size * width;
            if (bytes % sizeof(void*) != 0) {
                FAIL(batch, "Rounding failure for %d, width %d",
                     i, width);
                success = false;
                return;
            }
        }
    }
    PASS(batch, "Round allocations up to the size of a pointer");
}

void
TestMemory_run_tests() {
    TestBatch *batch = TestBatch_new(30);

    TestBatch_Plan(batch);
    test_oversize__growth_rate(batch);
    test_oversize__ceiling(batch);
    test_oversize__rounding(batch);

    DECREF(batch);
}