From a910909ad53e28d811618ba500d87a379dc4057c Mon Sep 17 00:00:00 2001 From: Hayodea Hekol Date: Wed, 19 Nov 2025 03:12:43 -0400 Subject: [PATCH] Tests: Add test for StagingBuffer --- tests/CMakeLists.txt | 16 + .../attachmentSupport/stagingBuffer_tests.cpp | 473 ++++++++++++++++++ 2 files changed, 489 insertions(+) create mode 100644 tests/commonLibs/attachmentSupport/stagingBuffer_tests.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7cf9fbc..3bea28e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/commonLibs/attachmentSupport/stagingBuffer_tests.cpp b/tests/commonLibs/attachmentSupport/stagingBuffer_tests.cpp new file mode 100644 index 0000000..81aea93 --- /dev/null +++ b/tests/commonLibs/attachmentSupport/stagingBuffer_tests.cpp @@ -0,0 +1,473 @@ +#include +#include +#include +#include +#include +#include +#include + +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(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(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>(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>(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(frameDesc->slots[i].vaddr) - + reinterpret_cast(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>(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>(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>(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>(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>(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>(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>(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(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>(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>(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>(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 +