432 lines
8.6 KiB
C++
432 lines
8.6 KiB
C++
#include <doctest/doctest.h>
|
|
#include <atomic>
|
|
#include "Util/SharedBuffer.h"
|
|
#include <string>
|
|
#include <vector>
|
|
#include <thread>
|
|
|
|
struct SimpleMetaData
|
|
{
|
|
int Id{};
|
|
float Value{};
|
|
};
|
|
|
|
TEST_SUITE("SharedBuffer")
|
|
{
|
|
TEST_CASE("default construction")
|
|
{
|
|
SharedBuffer<int, SimpleMetaData> buf;
|
|
CHECK_FALSE(buf);
|
|
}
|
|
|
|
TEST_CASE("sized construction")
|
|
{
|
|
SimpleMetaData meta{ 42, 3.14f };
|
|
SharedBuffer<int, SimpleMetaData> buf(5, meta);
|
|
|
|
CHECK(buf);
|
|
CHECK(buf.GetSize() == 5);
|
|
CHECK(buf.GetMetaData()->Id == 42);
|
|
CHECK(buf.GetMetaData()->Value == doctest::Approx(3.14f));
|
|
|
|
for (uint32_t i = 0; i < buf.GetSize(); ++i)
|
|
CHECK(buf[i] == 0);
|
|
}
|
|
|
|
TEST_CASE("element access and mutation")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> buf(3, meta);
|
|
|
|
buf[0] = 10;
|
|
buf[1] = 20;
|
|
buf[2] = 30;
|
|
|
|
CHECK(buf[0] == 10);
|
|
CHECK(buf[1] == 20);
|
|
CHECK(buf[2] == 30);
|
|
}
|
|
|
|
TEST_CASE("Ptr() access")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> buf(3, meta);
|
|
buf[0] = 100;
|
|
|
|
int* ptr = buf.Ptr();
|
|
CHECK(ptr[0] == 100);
|
|
|
|
ptr[1] = 200;
|
|
CHECK(buf[1] == 200);
|
|
}
|
|
|
|
TEST_CASE("GetData() returns valid span")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> buf(4, meta);
|
|
buf[0] = 1;
|
|
buf[1] = 2;
|
|
buf[2] = 3;
|
|
buf[3] = 4;
|
|
|
|
auto span = buf.GetData();
|
|
CHECK(span.size() == 4);
|
|
CHECK(span[0] == 1);
|
|
CHECK(span[3] == 4);
|
|
}
|
|
|
|
TEST_CASE("const access")
|
|
{
|
|
SimpleMetaData meta{ 7, 1.0f };
|
|
SharedBuffer<int, SimpleMetaData> buf(2, meta);
|
|
buf[0] = 99;
|
|
|
|
const auto& cbuf = buf;
|
|
CHECK(cbuf[0] == 99);
|
|
CHECK(cbuf.GetSize() == 2);
|
|
CHECK(cbuf.Ptr()[0] == 99);
|
|
CHECK(cbuf.GetMetaData()->Id == 7);
|
|
CHECK(cbuf.GetData().size() == 2);
|
|
}
|
|
|
|
TEST_CASE("copy constructor shares data")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(3, meta);
|
|
a[0] = 42;
|
|
|
|
SharedBuffer<int, SimpleMetaData> b(a);
|
|
|
|
CHECK(b);
|
|
CHECK(b.GetSize() == 3);
|
|
CHECK(b[0] == 42);
|
|
|
|
// They share the same underlying data
|
|
CHECK(a.Ptr() == b.Ptr());
|
|
|
|
// Mutation through one is visible in the other
|
|
a[1] = 77;
|
|
CHECK(b[1] == 77);
|
|
}
|
|
|
|
TEST_CASE("copy assignment shares data")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(2, meta);
|
|
a[0] = 10;
|
|
|
|
SharedBuffer<int, SimpleMetaData> b;
|
|
b = a;
|
|
|
|
CHECK(b);
|
|
CHECK(b[0] == 10);
|
|
CHECK(a.Ptr() == b.Ptr());
|
|
}
|
|
|
|
TEST_CASE("copy assignment from non-empty to non-empty")
|
|
{
|
|
SimpleMetaData meta1{};
|
|
SimpleMetaData meta2{};
|
|
SharedBuffer<int, SimpleMetaData> a(2, meta1);
|
|
SharedBuffer<int, SimpleMetaData> b(3, meta2);
|
|
a[0] = 1;
|
|
b[0] = 2;
|
|
|
|
int* oldBPtr = b.Ptr();
|
|
b = a;
|
|
|
|
CHECK(b.GetSize() == 2);
|
|
CHECK(b[0] == 1);
|
|
CHECK(b.Ptr() == a.Ptr());
|
|
CHECK(b.Ptr() != oldBPtr);
|
|
}
|
|
|
|
TEST_CASE("self copy assignment")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(2, meta);
|
|
a[0] = 55;
|
|
|
|
a = a;
|
|
|
|
CHECK(a);
|
|
CHECK(a[0] == 55);
|
|
CHECK(a.GetSize() == 2);
|
|
}
|
|
|
|
TEST_CASE("move constructor transfers ownership")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(3, meta);
|
|
a[0] = 42;
|
|
int* origPtr = a.Ptr();
|
|
|
|
SharedBuffer<int, SimpleMetaData> b(std::move(a));
|
|
|
|
CHECK(b);
|
|
CHECK(b[0] == 42);
|
|
CHECK(b.Ptr() == origPtr);
|
|
CHECK_FALSE(a); // source is empty
|
|
}
|
|
|
|
TEST_CASE("move assignment transfers ownership")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(3, meta);
|
|
a[0] = 42;
|
|
int* origPtr = a.Ptr();
|
|
|
|
SharedBuffer<int, SimpleMetaData> b;
|
|
b = std::move(a);
|
|
|
|
CHECK(b);
|
|
CHECK(b[0] == 42);
|
|
CHECK(b.Ptr() == origPtr);
|
|
CHECK_FALSE(a);
|
|
}
|
|
|
|
TEST_CASE("self move assignment")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(2, meta);
|
|
a[0] = 55;
|
|
|
|
a = std::move(a);
|
|
|
|
CHECK(a);
|
|
CHECK(a[0] == 55);
|
|
}
|
|
|
|
TEST_CASE("reference counting - last copy cleans up")
|
|
{
|
|
SimpleMetaData meta{};
|
|
int* ptr;
|
|
|
|
{
|
|
SharedBuffer<int, SimpleMetaData> a(3, meta);
|
|
a[0] = 1;
|
|
ptr = a.Ptr();
|
|
|
|
{
|
|
SharedBuffer<int, SimpleMetaData> b(a);
|
|
CHECK(b.Ptr() == ptr);
|
|
// b goes out of scope - should NOT free since a still alive
|
|
}
|
|
|
|
// a should still be valid
|
|
CHECK(a);
|
|
CHECK(a[0] == 1);
|
|
}
|
|
// a goes out of scope here - memory freed (no way to check, but no crash)
|
|
}
|
|
|
|
TEST_CASE("multiple copies and sequential destruction")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(2, meta);
|
|
a[0] = 100;
|
|
|
|
SharedBuffer<int, SimpleMetaData> b(a);
|
|
SharedBuffer<int, SimpleMetaData> c(b);
|
|
SharedBuffer<int, SimpleMetaData> d(c);
|
|
|
|
CHECK(a.Ptr() == d.Ptr());
|
|
|
|
// Destroy in various orders
|
|
b = SharedBuffer<int, SimpleMetaData>(); // release b's ref
|
|
CHECK_FALSE(b);
|
|
CHECK(a[0] == 100); // a still valid
|
|
|
|
d = SharedBuffer<int, SimpleMetaData>(); // release d's ref
|
|
CHECK(a[0] == 100); // a still valid
|
|
CHECK(c[0] == 100); // c still valid
|
|
}
|
|
|
|
TEST_CASE("reassignment releases old buffer")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(2, meta);
|
|
SharedBuffer<int, SimpleMetaData> b(3, meta);
|
|
|
|
a[0] = 1;
|
|
b[0] = 2;
|
|
|
|
// a had sole ownership of its buffer; assigning b should free old buffer
|
|
a = b;
|
|
CHECK(a[0] == 2);
|
|
CHECK(a.GetSize() == 3);
|
|
}
|
|
|
|
TEST_CASE("works with non-trivial element types")
|
|
{
|
|
struct Meta { int x{}; };
|
|
|
|
SharedBuffer<std::string, Meta> buf(3, Meta{ 1 });
|
|
buf[0] = "hello";
|
|
buf[1] = "world";
|
|
buf[2] = "test";
|
|
|
|
CHECK(buf[0] == "hello");
|
|
CHECK(buf[1] == "world");
|
|
CHECK(buf[2] == "test");
|
|
|
|
// Copy and verify strings are shared
|
|
SharedBuffer<std::string, Meta> copy(buf);
|
|
CHECK(copy[0] == "hello");
|
|
|
|
buf[0] = "modified";
|
|
CHECK(copy[0] == "modified"); // shared data
|
|
}
|
|
|
|
TEST_CASE("copy of non-trivial types cleans up properly")
|
|
{
|
|
struct Meta { int x{}; };
|
|
|
|
{
|
|
SharedBuffer<std::string, Meta> a(2, Meta{});
|
|
a[0] = "aaa";
|
|
a[1] = "bbb";
|
|
|
|
{
|
|
SharedBuffer<std::string, Meta> b(a);
|
|
CHECK(b[0] == "aaa");
|
|
}
|
|
// b destroyed, a still valid
|
|
CHECK(a[0] == "aaa");
|
|
}
|
|
// a destroyed, strings cleaned up (no leak/crash)
|
|
}
|
|
|
|
TEST_CASE("zero-size buffer")
|
|
{
|
|
SimpleMetaData meta{ 1, 2.0f };
|
|
SharedBuffer<int, SimpleMetaData> buf(0, meta);
|
|
|
|
CHECK(buf);
|
|
CHECK(buf.GetSize() == 0);
|
|
CHECK(buf.GetMetaData()->Id == 1);
|
|
CHECK(buf.GetData().empty());
|
|
}
|
|
|
|
TEST_CASE("metadata mutation")
|
|
{
|
|
SimpleMetaData meta{ 1, 0.0f };
|
|
SharedBuffer<int, SimpleMetaData> buf(1, meta);
|
|
|
|
buf.GetMetaData()->Id = 99;
|
|
buf.GetMetaData()->Value = 1.5f;
|
|
|
|
CHECK(buf.GetMetaData()->Id == 99);
|
|
CHECK(buf.GetMetaData()->Value == doctest::Approx(1.5f));
|
|
}
|
|
|
|
TEST_CASE("metadata shared between copies")
|
|
{
|
|
SimpleMetaData meta{ 10, 0.0f };
|
|
SharedBuffer<int, SimpleMetaData> a(1, meta);
|
|
SharedBuffer<int, SimpleMetaData> b(a);
|
|
|
|
a.GetMetaData()->Id = 50;
|
|
CHECK(b.GetMetaData()->Id == 50);
|
|
}
|
|
|
|
TEST_CASE("bool conversion")
|
|
{
|
|
SharedBuffer<int, SimpleMetaData> empty;
|
|
CHECK_FALSE(empty);
|
|
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> valid(1, meta);
|
|
CHECK(valid);
|
|
|
|
SharedBuffer<int, SimpleMetaData> moved(std::move(valid));
|
|
CHECK_FALSE(valid);
|
|
CHECK(moved);
|
|
}
|
|
|
|
TEST_CASE("copy from default-constructed buffer")
|
|
{
|
|
SharedBuffer<int, SimpleMetaData> empty;
|
|
SharedBuffer<int, SimpleMetaData> copy(empty);
|
|
CHECK_FALSE(copy);
|
|
}
|
|
|
|
TEST_CASE("assign default-constructed buffer")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(2, meta);
|
|
SharedBuffer<int, SimpleMetaData> empty;
|
|
|
|
a = empty;
|
|
CHECK_FALSE(a);
|
|
}
|
|
|
|
TEST_CASE("move from default-constructed buffer")
|
|
{
|
|
SharedBuffer<int, SimpleMetaData> empty;
|
|
SharedBuffer<int, SimpleMetaData> moved(std::move(empty));
|
|
CHECK_FALSE(moved);
|
|
}
|
|
|
|
TEST_CASE("large buffer")
|
|
{
|
|
SimpleMetaData meta{};
|
|
constexpr int N = 10000;
|
|
SharedBuffer<int, SimpleMetaData> buf(N, meta);
|
|
|
|
for (int i = 0; i < N; ++i)
|
|
buf[i] = i * 2;
|
|
|
|
for (int i = 0; i < N; ++i)
|
|
CHECK(buf[i] == i * 2);
|
|
}
|
|
|
|
TEST_CASE("chain of assignments")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> a(1, meta);
|
|
SharedBuffer<int, SimpleMetaData> b(1, meta);
|
|
SharedBuffer<int, SimpleMetaData> c(1, meta);
|
|
|
|
a[0] = 1;
|
|
b[0] = 2;
|
|
c[0] = 3;
|
|
|
|
a = b;
|
|
b = c;
|
|
|
|
CHECK(a[0] == 2);
|
|
CHECK(b[0] == 3);
|
|
CHECK(c[0] == 3);
|
|
CHECK(b.Ptr() == c.Ptr());
|
|
}
|
|
|
|
TEST_CASE("concurrent read access from copies")
|
|
{
|
|
SimpleMetaData meta{};
|
|
SharedBuffer<int, SimpleMetaData> buf(100, meta);
|
|
for (uint32_t i = 0; i < 100; ++i)
|
|
buf[i] = static_cast<int>(i);
|
|
|
|
auto copy1 = buf;
|
|
auto copy2 = buf;
|
|
|
|
std::thread t1([©1]() {
|
|
int sum = 0;
|
|
for (uint32_t i = 0; i < copy1.GetSize(); ++i)
|
|
sum += copy1[i];
|
|
CHECK(sum == 4950);
|
|
});
|
|
|
|
std::thread t2([©2]() {
|
|
int sum = 0;
|
|
for (uint32_t i = 0; i < copy2.GetSize(); ++i)
|
|
sum += copy2[i];
|
|
CHECK(sum == 4950);
|
|
});
|
|
|
|
t1.join();
|
|
t2.join();
|
|
}
|
|
}
|