|
5 | 5 |
|
6 | 6 | #include <gtest/gtest.h> |
7 | 7 |
|
| 8 | +#include <array> |
8 | 9 | #include <cstdint> |
9 | 10 | #include <functional> |
| 11 | +#include <memory> |
10 | 12 | #include <string> |
11 | 13 | #include <thread> |
12 | 14 | #include <vector> |
@@ -148,7 +150,7 @@ TEST(ObjectStoreTest, LatestAtExact) { |
148 | 150 | auto r = store.latestAt(id, 200); |
149 | 151 | ASSERT_TRUE(r.has_value()); |
150 | 152 | EXPECT_EQ(r->timestamp, 200); |
151 | | - EXPECT_EQ((*r->data)[0], 0x02); |
| 153 | + EXPECT_EQ(r->view[0], 0x02); |
152 | 154 | } |
153 | 155 |
|
154 | 156 | TEST(ObjectStoreTest, LatestAtBetween) { |
@@ -290,8 +292,68 @@ TEST(ObjectStoreTest, PushLazyResolves) { |
290 | 292 | auto r = store.latestAt(id, 100); |
291 | 293 | ASSERT_TRUE(r.has_value()); |
292 | 294 | EXPECT_EQ(call_count, 1); |
293 | | - EXPECT_EQ(r->data->size(), 2u); |
294 | | - EXPECT_EQ((*r->data)[0], 0xDE); |
| 295 | + EXPECT_EQ(r->view.size(), 2u); |
| 296 | + EXPECT_EQ(r->view[0], 0xDE); |
| 297 | +} |
| 298 | + |
| 299 | +// Regression: the lazy path must preserve the BufferAnchor's concrete type. |
| 300 | +// Anchor here is a shared_ptr<TestBuffer> — NOT a shared_ptr<vector>. If |
| 301 | +// resolveEntry static_pointer_cast'd to vector (the prior implementation), |
| 302 | +// view would point at garbage and ASAN would flag it. We assert that |
| 303 | +// (a) the bytes the producer published survive resolution unchanged, and |
| 304 | +// (b) the anchor's refcount stays > 0 through the resolve (the store does |
| 305 | +// not silently swap it for a different anchor type). |
| 306 | +TEST(ObjectStoreTest, PushLazyPreservesAnchorType) { |
| 307 | + struct TestBuffer { |
| 308 | + std::array<uint8_t, 4> bytes{0x11, 0x22, 0x33, 0x44}; |
| 309 | + }; |
| 310 | + ObjectStore store; |
| 311 | + auto id = registerTestTopic(store); |
| 312 | + |
| 313 | + auto buffer = std::make_shared<TestBuffer>(); |
| 314 | + std::weak_ptr<TestBuffer> weak_buffer = buffer; |
| 315 | + |
| 316 | + store.pushLazy(id, 100, [buffer]() -> sdk::PayloadView { |
| 317 | + return sdk::PayloadView{ |
| 318 | + Span<const uint8_t>{buffer->bytes.data(), buffer->bytes.size()}, |
| 319 | + sdk::BufferAnchor{buffer}, |
| 320 | + }; |
| 321 | + }); |
| 322 | + |
| 323 | + auto r = store.latestAt(id, 100); |
| 324 | + ASSERT_TRUE(r.has_value()); |
| 325 | + ASSERT_EQ(r->view.size(), 4u); |
| 326 | + EXPECT_EQ(r->view[0], 0x11); |
| 327 | + EXPECT_EQ(r->view[3], 0x44); |
| 328 | + EXPECT_FALSE(weak_buffer.expired()); // anchor still holds the buffer alive |
| 329 | +} |
| 330 | + |
| 331 | +// Regression: PayloadView::bytes is the *producer-chosen sub-range* of the |
| 332 | +// anchor's storage. resolveEntry must propagate that Span verbatim — not the |
| 333 | +// anchor's full extent. The prior implementation ignored the Span and |
| 334 | +// returned the anchor's whole vector. |
| 335 | +TEST(ObjectStoreTest, PushLazyHonorsSpanSubview) { |
| 336 | + ObjectStore store; |
| 337 | + auto id = registerTestTopic(store); |
| 338 | + |
| 339 | + auto chunk = std::make_shared<std::vector<uint8_t>>(100); |
| 340 | + for (size_t i = 0; i < chunk->size(); ++i) { |
| 341 | + (*chunk)[i] = static_cast<uint8_t>(i); |
| 342 | + } |
| 343 | + |
| 344 | + store.pushLazy(id, 100, [chunk]() -> sdk::PayloadView { |
| 345 | + return sdk::PayloadView{ |
| 346 | + Span<const uint8_t>{chunk->data() + 20, 10}, // bytes [20, 30) |
| 347 | + sdk::BufferAnchor{chunk}, |
| 348 | + }; |
| 349 | + }); |
| 350 | + |
| 351 | + auto r = store.latestAt(id, 100); |
| 352 | + ASSERT_TRUE(r.has_value()); |
| 353 | + EXPECT_EQ(r->view.size(), 10u); |
| 354 | + EXPECT_EQ(r->view.data(), chunk->data() + 20); |
| 355 | + EXPECT_EQ(r->view[0], 20); |
| 356 | + EXPECT_EQ(r->view[9], 29); |
295 | 357 | } |
296 | 358 |
|
297 | 359 | // ========================================================================= |
@@ -328,13 +390,13 @@ TEST(ObjectStoreTest, HandleSurvivesEviction) { |
328 | 390 |
|
329 | 391 | auto handle = store.latestAt(id, 100); |
330 | 392 | ASSERT_TRUE(handle.has_value()); |
331 | | - EXPECT_EQ((*handle->data)[0], 0xAA); |
| 393 | + EXPECT_EQ(handle->view[0], 0xAA); |
332 | 394 |
|
333 | 395 | store.evictBefore(id, 150); |
334 | 396 | EXPECT_EQ(store.entryCount(id), 1u); |
335 | 397 |
|
336 | | - EXPECT_EQ(handle->data->size(), 4u); |
337 | | - EXPECT_EQ((*handle->data)[0], 0xAA); |
| 398 | + EXPECT_EQ(handle->view.size(), 4u); |
| 399 | + EXPECT_EQ(handle->view[0], 0xAA); |
338 | 400 | } |
339 | 401 |
|
340 | 402 | // ========================================================================= |
|
0 commit comments