#include #include #include "Util/SharedBuffer.h" #include #include #include struct SimpleMetaData { int Id{}; float Value{}; }; TEST_SUITE("SharedBuffer") { TEST_CASE("default construction") { SharedBuffer buf; CHECK_FALSE(buf); } TEST_CASE("sized construction") { SimpleMetaData meta{ 42, 3.14f }; SharedBuffer 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 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 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 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 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 a(3, meta); a[0] = 42; SharedBuffer 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 a(2, meta); a[0] = 10; SharedBuffer 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 a(2, meta1); SharedBuffer 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 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 a(3, meta); a[0] = 42; int* origPtr = a.Ptr(); SharedBuffer 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 a(3, meta); a[0] = 42; int* origPtr = a.Ptr(); SharedBuffer 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 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 a(3, meta); a[0] = 1; ptr = a.Ptr(); { SharedBuffer 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 a(2, meta); a[0] = 100; SharedBuffer b(a); SharedBuffer c(b); SharedBuffer d(c); CHECK(a.Ptr() == d.Ptr()); // Destroy in various orders b = SharedBuffer(); // release b's ref CHECK_FALSE(b); CHECK(a[0] == 100); // a still valid d = SharedBuffer(); // 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 a(2, meta); SharedBuffer 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 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 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 a(2, Meta{}); a[0] = "aaa"; a[1] = "bbb"; { SharedBuffer 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 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 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 a(1, meta); SharedBuffer b(a); a.GetMetaData()->Id = 50; CHECK(b.GetMetaData()->Id == 50); } TEST_CASE("bool conversion") { SharedBuffer empty; CHECK_FALSE(empty); SimpleMetaData meta{}; SharedBuffer valid(1, meta); CHECK(valid); SharedBuffer moved(std::move(valid)); CHECK_FALSE(valid); CHECK(moved); } TEST_CASE("copy from default-constructed buffer") { SharedBuffer empty; SharedBuffer copy(empty); CHECK_FALSE(copy); } TEST_CASE("assign default-constructed buffer") { SimpleMetaData meta{}; SharedBuffer a(2, meta); SharedBuffer empty; a = empty; CHECK_FALSE(a); } TEST_CASE("move from default-constructed buffer") { SharedBuffer empty; SharedBuffer moved(std::move(empty)); CHECK_FALSE(moved); } TEST_CASE("large buffer") { SimpleMetaData meta{}; constexpr int N = 10000; SharedBuffer 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 a(1, meta); SharedBuffer b(1, meta); SharedBuffer 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 buf(100, meta); for (uint32_t i = 0; i < 100; ++i) buf[i] = static_cast(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(); } }