blob: c91edfe61e3335398caea18e0fd81763f947b1d2 [file] [log] [blame]
/** @file
Test file for Extendible
@INFO license License
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 REQUIRE 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.
*/
#include "catch.hpp"
#include <iostream>
#include <string>
#include <array>
#include <ctime>
#include <thread>
#include "tscore/ink_atomic.h"
#include "tscore/Extendible.h"
using namespace std;
using namespace ext;
//////////////////////////////////////////////////////
// AtomicBit Tests
TEST_CASE("AtomicBit Atomic test")
{
// test the atomicity and isolation of operations
uint32_t bit_storage = 0;
auto job_set = [&bit_storage](int idx) { AtomicBit{(uint8_t *)&bit_storage + (idx / 8), (uint8_t)(1 << (idx % 8))} = 1; };
auto job_clear = [&bit_storage](int idx) { AtomicBit{(uint8_t *)&bit_storage + (idx / 8), (uint8_t)(1 << (idx % 8))} = 0; };
std::thread jobs[32];
// set all bits in parallel
for (int i = 0; i < 32; i++) {
jobs[i] = std::thread(job_set, i);
}
for (int i = 0; i < 32; i++) {
jobs[i].join();
}
REQUIRE(bit_storage == 0xffffffff);
// clear all bits in parallel
for (int i = 0; i < 32; i++) {
jobs[i] = std::thread(job_clear, i);
}
for (int i = 0; i < 32; i++) {
jobs[i].join();
}
REQUIRE(bit_storage == 0);
}
//////////////////////////////////////////////////////
// Extendible Inheritance Tests
struct A : public Extendible<A> {
using self_type = A;
DEF_EXT_NEW_DEL(self_type);
uint16_t a = {1};
};
ext::FieldId<A, std::atomic<uint16_t>> ext_a_1;
class B : public A
{
public:
using super_type = A;
using self_type = B;
DEF_EXT_NEW_DEL(self_type);
uint16_t b = {2};
};
class C : public B, public Extendible<C>
{
public:
using super_type = B;
using self_type = C;
DEF_EXT_NEW_DEL(self_type);
uint16_t c = {3};
// operator[]
template <typename F>
decltype(auto)
operator[](F field) const
{
return ext::get(*this, field);
}
template <typename F>
decltype(auto)
operator[](F field)
{
return ext::set(*this, field);
}
};
ext::FieldId<C, std::atomic<uint16_t>> ext_c_1;
uintptr_t
memDelta(void *p, void *q)
{
return uintptr_t(q) - uintptr_t(p);
}
A *a_ptr = nullptr;
TEST_CASE("Create A", "")
{
ext::details::areFieldsFinalized() = true;
a_ptr = ext::create<A>();
CHECK(Extendible<A>::schema.no_instances() == false);
}
TEST_CASE("Delete A", "")
{
delete a_ptr;
CHECK(Extendible<A>::schema.no_instances());
}
TEST_CASE("Create B", "")
{
a_ptr = ext::create<B>();
CHECK(Extendible<A>::schema.no_instances() == false);
}
TEST_CASE("Delete B", "")
{
delete a_ptr;
CHECK(Extendible<A>::schema.no_instances());
}
TEST_CASE("Create C", "")
{
a_ptr = ext::create<C>();
CHECK(Extendible<A>::schema.no_instances() == false);
CHECK(Extendible<C>::schema.no_instances() == false);
}
TEST_CASE("Delete C", "")
{
delete static_cast<C *>(a_ptr);
CHECK(Extendible<A>::schema.no_instances());
CHECK(Extendible<C>::schema.no_instances());
CHECK(Extendible<A>::schema.cnt_constructed == 3);
CHECK(Extendible<A>::schema.cnt_fld_constructed == 3);
CHECK(Extendible<A>::schema.cnt_destructed == 3);
CHECK(Extendible<C>::schema.cnt_constructed == 1);
CHECK(Extendible<C>::schema.cnt_fld_constructed == 1);
CHECK(Extendible<C>::schema.cnt_destructed == 1);
}
TEST_CASE("Extendible Memory Allocations", "")
{
ext::details::areFieldsFinalized() = false;
fieldAdd(ext_a_1, "ext_a_1");
fieldAdd(ext_c_1, "ext_c_1");
ext::details::areFieldsFinalized() = true;
size_t w = sizeof(uint16_t);
CHECK(ext::sizeOf<A>() == w * 3);
CHECK(ext::sizeOf<B>() == w * 4);
CHECK(ext::sizeOf<C>() == w * 7);
C &x = *(ext::create<C>());
// 0 1 2 3 4 5 6
//[ EA*, a, b,EC*, c, EA, EC]
//
uint16_t *mem = (uint16_t *)&x;
CHECK(memDelta(&x, &x.a) == w * 1);
CHECK(memDelta(&x, &x.b) == w * 2);
CHECK(memDelta(&x, &x.c) == w * 4);
CHECK(mem[0] == w * (5 - 0));
CHECK(mem[1] == 1);
CHECK(mem[2] == 2);
CHECK(mem[3] == w * (6 - 3));
CHECK(mem[4] == 3);
CHECK(mem[5] == 0);
CHECK(mem[6] == 0);
std::string format = "\n 1A | EXT | 2b |##________##__"
"\n 1A | BASE | 2b |__##__________"
"\n 1B | BASE | 2b |____##________"
"\n 1C | EXT | 2b |______##____##"
"\n 1C | BASE | 2b |________##____";
CHECK(ext::viewFormat(x) == format);
printf("\n");
delete &x;
}
TEST_CASE("Extendible Pointer Math", "")
{
C &x = *(ext::create<C>());
CHECK(x.a == 1);
CHECK(x.b == 2);
CHECK(x.c == 3);
ext::set(x, ext_a_1) = 4;
CHECK(ext::get(x, ext_a_1) == 4);
x[ext_c_1] = 5;
CHECK(ext::get(x, ext_c_1) == 5);
CHECK(x.a == 1);
CHECK(x.b == 2);
CHECK(x.c == 3);
CHECK(ext::get(x, ext_a_1) == 4);
CHECK(ext::get(x, ext_c_1) == 5);
std::string format = "\n 1A | EXT | 2b |##________##__"
"\n 1A | BASE | 2b |__##__________"
"\n 1B | BASE | 2b |____##________"
"\n 1C | EXT | 2b |______##____##"
"\n 1C | BASE | 2b |________##____";
CHECK(ext::viewFormat(x) == format);
ext::FieldId<A, bool> a_bit;
ext::FieldId<A, int> a_int;
static_assert(std::is_same<decltype(ext::get(x, a_bit)), bool>::value);
static_assert(std::is_same<decltype(ext::get(x, a_int)), int const &>::value);
delete &x;
}
// Extendible is abstract and must be derived in a CRTP
struct Derived : Extendible<Derived> {
using self_type = Derived;
DEF_EXT_NEW_DEL(self_type);
string m_str;
// operator[] for shorthand
template <typename F>
decltype(auto)
operator[](F field) const
{
return ext::get(*this, field);
}
template <typename F>
decltype(auto)
operator[](F field)
{
return ext::set(*this, field);
}
static const string
testFormat()
{
const size_t intenal_size = sizeof(Derived) - sizeof(ext::Extendible<Derived>::short_ptr_t);
std::stringstream format;
format << "\n 7Derived | EXT | 1b |##" << string(intenal_size, '_') << "#"
<< "\n 7Derived | BASE | " << setw(5) << intenal_size << "b |__" << string(intenal_size, '#')
<< "_";
return format.str();
}
};
///////////////////////////////////////
// C API for Derived
//
void *
DerivedExtalloc()
{
return ext::create<Derived>();
}
void
DerivedExtFree(void *ptr)
{
delete (Derived *)ptr;
}
ExtFieldContext
DerivedExtfieldAdd(char const *field_name, int size, void (*construct_fn)(void *), void (*destruct_fn)(void *))
{
// hack to avoid having to repeat this in testing.
ext::details::areFieldsFinalized() = false;
auto r = fieldAdd<Derived>(field_name, size, construct_fn, destruct_fn);
ext::details::areFieldsFinalized() = true;
return r;
}
ExtFieldContext
DerivedExtfieldFind(char const *field_name)
{
return fieldFind<Derived>(field_name);
}
///////////////////////////////////////
// something to store more complex than an int
struct testField {
std::array<uint8_t, 5> arr;
static int alive;
testField()
{
uint8_t x = 1;
for (uint8_t &a : arr) {
a = x;
x *= 2;
}
alive++;
}
~testField()
{
for (uint8_t &a : arr) {
a = 0;
}
alive--;
}
};
int testField::alive = 0;
namespace ext
{
template <>
void
serializeField(ostream &os, testField const &t)
{
serializeField(os, t.arr);
}
} // namespace ext
TEST_CASE("Extendible", "")
{
printf("\nsizeof(string) = %lu", sizeof(std::string));
printf("\nsizeof(Derived) = %lu", sizeof(Derived));
ext::FieldId<Derived, bool> bit_a, bit_b, bit_c;
ext::FieldId<Derived, std::atomic<int>> int_a, int_b, int_c;
Derived *ptr;
// test cases:
//[constructor] [operator] [type] [access] [capacity] [modifier] [operation] [compare] [find]
// I don't use SECTIONS because this modifies static variables many times, is not thread safe.
INFO("Extendible()")
{
ptr = ext::create<Derived>();
REQUIRE(ptr != nullptr);
}
INFO("~Extendible")
{
//
delete ptr;
}
INFO("Schema Reset")
{
ptr = ext::create<Derived>();
REQUIRE(Derived::schema.no_instances() == false);
REQUIRE(Derived::schema.reset() == false);
delete ptr;
REQUIRE(Derived::schema.no_instances() == true);
ext::details::areFieldsFinalized() = false;
REQUIRE(Derived::schema.reset() == true);
ext::details::areFieldsFinalized() = true;
}
INFO("shared_ptr")
{
shared_ptr<Derived> sptr(ext::create<Derived>());
REQUIRE(Derived::schema.no_instances() == false);
REQUIRE(sptr);
}
REQUIRE(Derived::schema.no_instances() == true);
INFO("add a bit field")
{
//
ext::details::areFieldsFinalized() = false;
REQUIRE(fieldAdd(bit_a, "bit_a"));
ext::details::areFieldsFinalized() = true;
}
INFO("Extendible delete ptr");
{
for (int i = 0; i < 10; i++) {
ptr = ext::create<Derived>();
REQUIRE(ptr != nullptr);
INFO(__LINE__);
REQUIRE(Derived::schema.no_instances() == false);
delete ptr;
INFO(__LINE__);
ptr = nullptr;
REQUIRE(Derived::schema.no_instances() == true);
}
}
INFO("test bit field");
{
shared_ptr<Derived> sptr{ext::create<Derived>()};
Derived &ref = *sptr;
CHECK(ext::viewFormat(ref) == Derived::testFormat());
AtomicBit bitref = ext::set(ref, bit_a);
bitref = 1;
CHECK(bitref == true);
bitref = true;
CHECK(bitref == true);
CHECK(ext::set(ref, bit_a) == true);
CHECK(ext::get(ref, bit_a) == true);
}
INFO("test bit packing")
{
struct size_test {
string s;
uint16_t i;
};
REQUIRE(Derived::schema.reset() == true);
CHECK(sizeof(Extendible<Derived>) == sizeof(uint16_t));
CHECK(sizeof(size_test) == ROUNDUP(sizeof(std::string) + sizeof(uint16_t), alignof(std::string)));
CHECK(sizeof(Derived) == sizeof(size_test));
CHECK(ext::sizeOf<Derived>() == sizeof(Derived));
ext::details::areFieldsFinalized() = false;
REQUIRE(fieldAdd(bit_a, "bit_a"));
size_t expected_size = sizeof(Derived) + 1;
CHECK(ext::sizeOf<Derived>() == expected_size);
REQUIRE(fieldAdd(bit_b, "bit_b"));
CHECK(ext::sizeOf<Derived>() == expected_size);
REQUIRE(fieldAdd(bit_c, "bit_c"));
CHECK(ext::sizeOf<Derived>() == expected_size);
ext::details::areFieldsFinalized() = true;
shared_ptr<Derived> sptr(ext::create<Derived>());
Derived &ref = *sptr;
CHECK(ext::viewFormat(ref) == Derived::testFormat());
using Catch::Matchers::Contains;
REQUIRE_THAT(ext::toString(ref), Contains("bit_a: 0"));
REQUIRE_THAT(ext::toString(ref), Contains("bit_b: 0"));
REQUIRE_THAT(ext::toString(ref), Contains("bit_c: 0"));
ext::set(ref, bit_a) = 1;
ext::set(ref, bit_b) = 0;
ext::set(ref, bit_c) = 1;
CHECK(ext::get(ref, bit_a) == true);
CHECK(ext::get(ref, bit_b) == false);
CHECK(ext::get(ref, bit_c) == true);
REQUIRE_THAT(ext::toString(ref), Contains("bit_a: 1"));
REQUIRE_THAT(ext::toString(ref), Contains("bit_b: 0"));
REQUIRE_THAT(ext::toString(ref), Contains("bit_c: 1"));
}
INFO("store int field")
{
ext::details::areFieldsFinalized() = false;
REQUIRE(fieldAdd(int_a, "int_a"));
REQUIRE(fieldAdd(int_b, "int_b"));
ext::details::areFieldsFinalized() = true;
size_t expected_size = sizeof(Derived) + 1 + sizeof(std::atomic_int) * 2;
CHECK(ext::sizeOf<Derived>() == expected_size);
shared_ptr<Derived> sptr(ext::create<Derived>());
Derived &ref = *sptr;
CHECK(ext::get(ref, int_a) == 0);
CHECK(ext::get(ref, int_b) == 0);
++ext::set(ref, int_a);
ext::set(ref, int_b) = 42;
ref.m_str = "Hello";
CHECK(ext::get(ref, int_a) == 1);
CHECK(ext::get(ref, int_b) == 42);
CHECK(ref.m_str == "Hello");
}
printf("\n");
INFO("Extendible Test Complete")
}
TEST_CASE("Extendible C API")
{
ext::details::areFieldsFinalized() = false;
Derived::schema.reset();
CHECK(Derived::schema.no_instances() == true);
ext::details::areFieldsFinalized() = true;
INFO("C API alloc instance")
{
void *d = DerivedExtalloc();
CHECK(d != nullptr);
CHECK(Derived::schema.no_instances() == false);
DerivedExtFree(d);
CHECK(Derived::schema.no_instances() == true);
}
INFO("C API add int field")
{
ExtFieldContext cf_a = DerivedExtfieldAdd("cf_a", 4, nullptr, nullptr);
size_t expected_size = sizeof(Derived) + 4;
CHECK(ext::sizeOf<Derived>() == expected_size);
CHECK(DerivedExtfieldFind("cf_a") == cf_a);
}
INFO("C API test int field")
{
void *d = DerivedExtalloc();
REQUIRE(d != nullptr);
ExtFieldContext cf_a = DerivedExtfieldFind("cf_a");
uint8_t *data8 = (uint8_t *)ExtFieldPtr(d, cf_a);
CHECK(data8[0] == 0);
ink_atomic_increment(&data8[0], 1);
data8[1] = 5;
data8[2] = 7;
uint32_t *data32 = (uint32_t *)ExtFieldPtr(d, cf_a);
CHECK(*data32 == 0x00070501);
DerivedExtFree(d);
}
INFO("Extendible C API Test Complete")
}
//*/