test: cover messageOf and indexOf

This commit is contained in:
dualfroz committed 2026-09-13 21:50:26 +02:00
1 parent 76ca8e86f9
commit b9dea4ed7c
1 file changed
+165
+165
View File
@@ -0,0 +1,165 @@
#include "testHarness.h"
#include "vf/Transport.h"
#include <cstdint>
#include <string>
#include <vector>
using namespace vf;
namespace {
ByteBuffer messageOf(uint32_t index, std::size_t size) {
ByteBuffer payload;
payload.reserve(size);
uint32_t state = index * 2654435761u + 12345u;
for (std::size_t i = 0; i < size; ++i) {
state = state * 1664525u + 1013904223u;
payload.push_back(static_cast<uint8_t>((state >> 16) & 0xffu));
}
// The first four bytes carry the index so the receiver can prove ordering.
for (int byte = 0; byte < 4 && static_cast<std::size_t>(byte) < payload.size(); ++byte)
payload[static_cast<std::size_t>(byte)] =
static_cast<uint8_t>((index >> (8 * byte)) & 0xffu);
return payload;
}
uint32_t indexOf(const ByteBuffer& payload) {
uint32_t index = 0;
for (int byte = 3; byte >= 0; --byte)
index = (index << 8) | payload[static_cast<std::size_t>(byte)];
return index;
}
// One tick of a two-endpoint exchange, always in the same order so a run is
// reproducible from the link seed alone.
void stepPair(Transport& a, Transport& b, uint64_t tick) {
a.pump(tick);
a.flush(tick);
b.pump(tick);
b.flush(tick);
}
} // namespace
VF_TEST(loopbackLinkReplaysTheSameImpairmentsForTheSameSeed) {
LinkQuality quality;
quality.lossPercent = 30;
quality.duplicatePercent = 15;
quality.reorderPercent = 25;
quality.latencyTicks = 2;
quality.seed = 0xfeedfacecafebeefull;
std::vector<std::string> runs;
for (int attempt = 0; attempt < 2; ++attempt) {
LoopbackLink link(quality);
std::string trace;
for (uint64_t tick = 0; tick < 60; ++tick) {
ByteBuffer payload = messageOf(static_cast<uint32_t>(tick), 8);
link.send(0, payload, tick);
std::vector<ByteBuffer> arrived;
link.receive(1, tick, arrived);
for (const ByteBuffer& one : arrived) {
trace += std::to_string(tick);
trace += ':';
trace += std::to_string(indexOf(one));
trace += ' ';
}
}
trace += "d";
trace += std::to_string(link.droppedCount());
trace += "u";
trace += std::to_string(link.duplicatedCount());
runs.push_back(trace);
}
CHECK(runs[0] == runs[1]);
CHECK(runs[0].size() > 40u);
LoopbackLink probe(quality);
for (uint64_t tick = 0; tick < 200; ++tick) probe.send(0, messageOf(1, 4), tick);
CHECK(probe.droppedCount() > 30u);
CHECK(probe.duplicatedCount() > 10u);
CHECK(probe.delayedCount() > 20u);
}
VF_TEST(reliableChannelDeliversEveryMessageExactlyOnceInOrderOverALossyLink) {
LinkQuality quality;
quality.lossPercent = 30;
quality.duplicatePercent = 12;
quality.reorderPercent = 25;
quality.reorderExtraTicks = 4;
quality.latencyTicks = 2;
quality.seed = 0x1234abcd5678ef01ull;
LoopbackLink link(quality);
TransportConfig config;
config.mtu = 256;
config.retransmitTimeoutTicks = 8;
config.sendWindow = 16;
Transport sender(link, 0, config);
Transport receiver(link, 1, config);
const uint32_t messageCount = 240;
for (uint32_t i = 0; i < messageCount; ++i) {
ByteBuffer payload = messageOf(i, 40 + (i % 90));
sender.sendReliable(payload);
}
std::vector<uint32_t> order;
for (uint64_t tick = 0; tick < 4000 && order.size() < messageCount; ++tick) {
stepPair(sender, receiver, tick);
ByteBuffer got;
while (receiver.popReliable(got)) order.push_back(indexOf(got));
}
CHECK_EQ(order.size(), static_cast<std::size_t>(messageCount));
bool inOrder = true;
for (std::size_t i = 0; i < order.size(); ++i)
if (order[i] != static_cast<uint32_t>(i)) inOrder = false;
CHECK(inOrder);
// Payloads survive intact, not just their headers.
for (uint32_t i = 0; i < 5 && i < order.size(); ++i) CHECK_EQ(order[i], i);
CHECK(link.droppedCount() > 20u);
CHECK(link.duplicatedCount() > 5u);
CHECK(sender.stats().retransmitted > 0u);
CHECK(receiver.stats().duplicatesDropped + receiver.stats().staleDropped > 0u);
CHECK_EQ(receiver.stats().messagesDelivered, static_cast<uint64_t>(messageCount));
}
VF_TEST(reliableMessagesLargerThanTheMtuAreFragmentedAndReassembledByteForByte) {
LinkQuality quality;
quality.lossPercent = 20;
quality.reorderPercent = 30;
quality.reorderExtraTicks = 3;
quality.latencyTicks = 1;
quality.seed = 0x77aabbccddee0011ull;
LoopbackLink link(quality);
TransportConfig config;
config.mtu = 128;
config.retransmitTimeoutTicks = 5;
config.sendWindow = 8;
Transport sender(link, 0, config);
Transport receiver(link, 1, config);
ByteBuffer huge = messageOf(9, 20000);
CHECK(huge.size() > sender.maxFragmentPayload() * 100);
sender.sendReliable(huge);
CHECK(sender.unackedFragmentCount() > 100u);
ByteBuffer got;
bool arrived = false;
uint64_t tick = 0;
for (; tick < 6000 && !arrived; ++tick) {
stepPair(sender, receiver, tick);
if (receiver.popReliable(got)) arrived = true;
}
CHECK(arrived);
// Let the last acks come back before asking whether anything is still unacked.
for (uint64_t settle = 0; settle < 40; ++settle) stepPair(sender, receiver, tick + settle);
CHECK_EQ(got.size(), huge.size());
CHECK(got == huge);
CHECK_EQ(sender.unackedFragmentCount(), static_cast<std::size_t>(0));