Tests: Add test for StagingBuffer

This commit is contained in:
2025-11-19 03:12:43 -04:00
parent 41b8385cb2
commit a910909ad5
2 changed files with 489 additions and 0 deletions
+16
View File
@@ -14,3 +14,19 @@ add_dependencies(qutex_tests gtest_main)
# Add the test to CTest
add_test(NAME qutex_tests COMMAND qutex_tests)
# Create a test executable for StagingBuffer
add_executable(stagingBuffer_tests commonLibs/attachmentSupport/stagingBuffer_tests.cpp)
# Link against Google Test and the attachmentSupport library
target_link_libraries(stagingBuffer_tests
gtest_main
attachmentSupport
${Boost_LIBRARIES}
)
# Ensure Google Test is built before our test executable
add_dependencies(stagingBuffer_tests gtest_main)
# Add the test to CTest
add_test(NAME stagingBuffer_tests COMMAND stagingBuffer_tests)
@@ -0,0 +1,473 @@
#include <gtest/gtest.h>
#include <user/stagingBuffer.h>
#include <user/frameAssemblyDesc.h>
#include <unistd.h>
#include <cstdint>
#include <algorithm>
#include <string>
namespace smo {
namespace stim_buff {
// Helper function to calculate expected buffer size using the same logic as computeSlotStrideAndBufferSize
static size_t calculateExpectedBufferSize(
size_t nSlots,
const StagingBuffer::IOEngineConstraints& constraints)
{
// Slot stride is the maximum of alignment and padding
size_t slotStrideNBytes = std::max(
constraints.slotStartAlignmentByteVal,
constraints.slotPadToNBytes);
// Calculate maximum alignment needed for first slot
size_t maxAlignment;
if (constraints.frameStartAlignmentByteVal >= constraints.slotStartAlignmentByteVal)
{
if (constraints.frameStartAlignmentByteVal % constraints.slotStartAlignmentByteVal == 0)
{
maxAlignment = constraints.frameStartAlignmentByteVal;
}
else
{
maxAlignment = std::max(
constraints.frameStartAlignmentByteVal,
constraints.slotStartAlignmentByteVal);
}
}
else
{
if (constraints.slotStartAlignmentByteVal % constraints.frameStartAlignmentByteVal == 0)
{
maxAlignment = constraints.slotStartAlignmentByteVal;
}
else
{
maxAlignment = std::max(
constraints.frameStartAlignmentByteVal,
constraints.slotStartAlignmentByteVal);
}
}
// Calculate minimum buffer size
size_t minBufferSize = std::max(
constraints.framePadToNBytes,
constraints.slotPadToNBytes);
// Calculate total size needed for nSlots slots
size_t slotAreaSize = nSlots * slotStrideNBytes;
// Add padding space at buffer start for alignment offset (worst case: max alignment - 1)
size_t alignmentPadding = maxAlignment - 1;
// Total size needed: alignment padding + slot area, then ensure minimum is met
size_t rawSize = alignmentPadding + slotAreaSize;
if (rawSize < minBufferSize)
{
rawSize = minBufferSize;
}
// Align up to the maximum alignment to ensure we can always find a valid offset
return ((rawSize + maxAlignment - 1) / maxAlignment) * maxAlignment;
}
// Helper function to calculate expected slot stride
static size_t calculateExpectedSlotStride(
const StagingBuffer::IOEngineConstraints& constraints)
{
return std::max(
constraints.slotStartAlignmentByteVal,
constraints.slotPadToNBytes);
}
// Helper function to create test constraints
static StagingBuffer::IOEngineConstraints createTestConstraints(
size_t slotStartAlignment,
size_t slotPadToNBytes,
size_t frameStartAlignment,
size_t framePadToNBytes)
{
return StagingBuffer::IOEngineConstraints(
slotStartAlignment,
slotPadToNBytes,
frameStartAlignment,
framePadToNBytes);
}
// Helper function to verify alignment
static void verifyAlignment(
uint8_t* addr,
size_t alignment,
const std::string& description)
{
uintptr_t addrValue = reinterpret_cast<uintptr_t>(addr);
EXPECT_EQ(addrValue % alignment, 0u)
<< description << ": Address " << (void*)addr
<< " is not aligned to " << alignment;
}
class StagingBufferTest : public ::testing::Test {
protected:
void SetUp() override {
pageSize = static_cast<size_t>(sysconf(_SC_PAGE_SIZE));
}
void TearDown() override {
}
size_t pageSize;
};
// Test 1: Single slot, minimal padding, small alignments
TEST_F(StagingBufferTest, SingleSlotMinimalAlignment) {
size_t nSlots = 1;
auto constraints = createTestConstraints(4, 16, 4, 16);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
// Verify FrameAssemblyDesc
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots);
EXPECT_EQ(frameDesc->slots.size(), nSlots);
// Verify first slot alignment
if (!frameDesc->slots.empty()) {
verifyAlignment(frameDesc->slots[0].vaddr, constraints.slotStartAlignmentByteVal,
"First slot");
verifyAlignment(frameDesc->slots[0].vaddr, constraints.frameStartAlignmentByteVal,
"First slot frame alignment");
}
}
// Test 2: Multiple slots, no alignment padding needed
TEST_F(StagingBufferTest, MultipleSlotsMinimalAlignment) {
size_t nSlots = 10;
auto constraints = createTestConstraints(4, 16, 4, 16);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
// Verify all slots are properly spaced
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots);
for (size_t i = 0; i < frameDesc->slots.size(); ++i) {
if (i > 0) {
size_t actualStride = reinterpret_cast<uintptr_t>(frameDesc->slots[i].vaddr) -
reinterpret_cast<uintptr_t>(frameDesc->slots[i-1].vaddr);
EXPECT_EQ(actualStride, expectedSlotStride)
<< "Slot " << i << " stride mismatch";
}
}
}
// Test 3: OpenCL Mesh constraints (real-world scenario)
TEST_F(StagingBufferTest, OpenClMeshConstraints) {
size_t nSlots = 909; // 30000ms / 33ms
size_t nDgramsPerFrame = 84;
size_t nPointsPerDgram = 96; // SingleFirst/Strongest/Dual return mode
size_t slotPadToNBytes = nDgramsPerFrame * nPointsPerDgram * sizeof(float) * 3; // 96768
auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
// Verify FrameAssemblyDesc
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots);
EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes);
// Verify first slot alignment
if (!frameDesc->slots.empty()) {
verifyAlignment(frameDesc->slots[0].vaddr, pageSize, "First slot frame alignment");
}
}
// Test 4: OpenCL Intensity constraints
TEST_F(StagingBufferTest, OpenClIntensityConstraints) {
size_t nSlots = 909; // 30000ms / 33ms
size_t nDgramsPerFrame = 84;
size_t nPointsPerDgram = 96;
size_t slotPadToNBytes = nDgramsPerFrame * nPointsPerDgram * sizeof(float) * 1; // 32256
auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots);
EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes);
}
// Test 5: OpenCL Ambience constraints
TEST_F(StagingBufferTest, OpenClAmbienceConstraints) {
size_t nSlots = 909; // 30000ms / 33ms
size_t nDgramsPerFrame = 84;
size_t slotPadToNBytes = nDgramsPerFrame * sizeof(float); // 336
auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots);
EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes);
}
// Test 6: io_uring constraints
TEST_F(StagingBufferTest, IoUringConstraints) {
size_t nSlots = 84;
auto constraints = createTestConstraints(
pageSize, // slotStartAlignment
1472, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(expectedSlotStride, pageSize) << "Slot stride should be max(alignment, padding) = pageSize";
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
}
// Test 7: Slot alignment larger than padding
TEST_F(StagingBufferTest, SlotAlignmentLargerThanPadding) {
size_t nSlots = 10;
auto constraints = createTestConstraints(64, 16, 64, 64);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
EXPECT_EQ(expectedSlotStride, 64u) << "Slot stride should be max(64, 16) = 64";
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
if (!frameDesc->slots.empty()) {
verifyAlignment(frameDesc->slots[0].vaddr, 64, "First slot");
}
}
// Test 8: Frame alignment larger than slot alignment
TEST_F(StagingBufferTest, FrameAlignmentLargerThanSlotAlignment) {
size_t nSlots = 10;
auto constraints = createTestConstraints(4, 16, pageSize, pageSize);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
if (!frameDesc->slots.empty()) {
verifyAlignment(frameDesc->slots[0].vaddr, pageSize, "First slot frame alignment");
verifyAlignment(frameDesc->slots[0].vaddr, 4, "First slot slot alignment");
}
}
// Test 9: Slot alignment larger than frame alignment
TEST_F(StagingBufferTest, SlotAlignmentLargerThanFrameAlignment) {
size_t nSlots = 10;
size_t largeAlignment = 8192;
auto constraints = createTestConstraints(largeAlignment, 16, pageSize, pageSize);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % largeAlignment, 0u)
<< "Buffer size should be aligned to max alignment";
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
if (!frameDesc->slots.empty()) {
verifyAlignment(frameDesc->slots[0].vaddr, largeAlignment, "First slot");
}
}
// Test 10: Minimum buffer size larger than calculated size
TEST_F(StagingBufferTest, MinimumBufferSizeEnforcement) {
size_t nSlots = 1;
auto constraints = createTestConstraints(4, 16, 4, 1024);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
size_t actualBufferSize = buffer.getIoUringRegisterIoVec().iov_len;
EXPECT_GE(actualBufferSize, 1024u) << "Buffer size should be at least framePadToNBytes";
EXPECT_EQ(actualBufferSize, expectedBufferSize);
}
// Test 11: Actual PcloudIntensityStimulusBuffer scenario
TEST_F(StagingBufferTest, RealWorldPcloudIntensityScenario) {
size_t nSlots = 909; // histbuffMs=30000 / CONFIG_STIMBUFF_FRAME_PERIOD_MS=33
size_t nDgramsPerFrame = 84;
size_t nPointsPerDgram = 96; // SingleFirst/Strongest/Dual
size_t slotPadToNBytes = nDgramsPerFrame * nPointsPerDgram * sizeof(float) * 1; // 32256
auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
// Verify the buffer can hold all slots
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots);
// Verify all slots fit within buffer
if (!frameDesc->slots.empty()) {
uint8_t* bufferStart = buffer.getIoUringRegisterIoVec().iov_base;
size_t bufferSize = buffer.getIoUringRegisterIoVec().iov_len;
uint8_t* lastSlotEnd = frameDesc->slots.back().vaddr + frameDesc->slots.back().nBytes;
EXPECT_LE(lastSlotEnd - bufferStart, static_cast<ptrdiff_t>(bufferSize))
<< "Last slot should fit within buffer";
}
}
// Test 12: Different return modes - Triple (90 points)
TEST_F(StagingBufferTest, TripleReturnModeMesh) {
size_t nSlots = 909;
size_t nDgramsPerFrame = 84;
size_t nPointsPerDgram = 90; // Triple return mode
size_t slotPadToNBytes = nDgramsPerFrame * nPointsPerDgram * sizeof(float) * 3; // 90720
auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes);
}
// Test 13: Verify firstSlotOffsetNBytes is valid
TEST_F(StagingBufferTest, FirstSlotOffsetValidation) {
size_t nSlots = 100;
auto constraints = createTestConstraints(4, 16, pageSize, pageSize);
StagingBuffer buffer(constraints, constraints, nSlots);
EXPECT_LE(buffer.firstSlotOffsetNBytes, buffer.getIoUringRegisterIoVec().iov_len)
<< "First slot offset should be within buffer";
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
if (!frameDesc->slots.empty()) {
uint8_t* bufferStart = buffer.getIoUringRegisterIoVec().iov_base;
uint8_t* firstSlot = frameDesc->slots[0].vaddr;
size_t calculatedOffset = firstSlot - bufferStart;
EXPECT_EQ(calculatedOffset, buffer.firstSlotOffsetNBytes)
<< "First slot offset should match calculated value";
}
}
// Test 14: Verify all slots are properly aligned
TEST_F(StagingBufferTest, AllSlotsProperlyAligned) {
size_t nSlots = 50;
auto constraints = createTestConstraints(64, 64, 64, 64);
StagingBuffer buffer(constraints, constraints, nSlots);
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
for (size_t i = 0; i < frameDesc->slots.size(); ++i) {
verifyAlignment(frameDesc->slots[i].vaddr, constraints.slotStartAlignmentByteVal,
"Slot " + std::to_string(i));
verifyAlignment(frameDesc->slots[i].vaddr, constraints.frameStartAlignmentByteVal,
"Slot " + std::to_string(i) + " frame alignment");
}
}
} // namespace stim_buff
} // namespace smo