From b9dea4ed7caf35137ba0ec84a35c147ccd500cf1 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 13 Sep 2026 21:50:26 +0200 Subject: [PATCH] test: cover messageOf and indexOf --- tests/testTransport.cpp | 165 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 tests/testTransport.cpp diff --git a/tests/testTransport.cpp b/tests/testTransport.cpp new file mode 100644 index 0000000..a890307 --- /dev/null +++ b/tests/testTransport.cpp @@ -0,0 +1,165 @@ +#include "testHarness.h" +#include "vf/Transport.h" + +#include +#include +#include + +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((state >> 16) & 0xffu)); + } + // The first four bytes carry the index so the receiver can prove ordering. + for (int byte = 0; byte < 4 && static_cast(byte) < payload.size(); ++byte) + payload[static_cast(byte)] = + static_cast((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(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 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(tick), 8); + link.send(0, payload, tick); + std::vector 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 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(messageCount)); + bool inOrder = true; + for (std::size_t i = 0; i < order.size(); ++i) + if (order[i] != static_cast(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(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(0)); +