Files
CoopAllTheThings/tests/protocol_test.cpp
BlackMark 30eccf749d Apply clang-format across the whole tree
Run clang-format (the repo's .clang-format: LLVM base, 120 cols, tabs,
Allman functions) over every source file so the tree is formatter-clean.
Whitespace only -- no behavior change; full x64 + x86 suites pass.

Also set SortIncludes: false in .clang-format. Windows include order is
load-bearing (windows.h must precede tlhelp32.h / mmreg.h / xinput.h /
dinput.h; winsock2.h must precede windows.h), and the default
alphabetical sort reorders tlhelp32.h ahead of windows.h -- a build
break. Leaving order alone keeps the manual, correct grouping.
2026-07-12 11:52:53 +02:00

122 lines
3.8 KiB
C++

// Unit test for the cross-process input contract in coop/protocol.hpp + the hook's IPC handshake:
// - the seqlock (publish_pads / read_pads): a concurrent reader never sees a torn snapshot, and a
// writer stuck mid-update (odd sequence) makes read_pads give up (returns false) rather than hang;
// - IpcClient::connect refuses a section whose magic or version doesn't match (the ABI safety net).
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <thread>
#include <windows.h>
#include "coop/protocol.hpp"
#include "coop/shared_memory.hpp"
#include "ipc_client.hpp" // hook-side IpcClient (header-only)
using namespace coop;
using coop::hook::IpcClient;
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
std::printf("%s %s\n", ok ? " ok:" : "FAIL:", what);
if (!ok) {
++g_failures;
}
}
} // namespace
int main()
{
// --- Seqlock: concurrent reader never sees a torn snapshot ---------------------------------
{
SharedBlock block{};
block.sequence.store(0, std::memory_order_relaxed);
std::atomic<bool> stop{false};
std::atomic<long long> torn{0};
std::atomic<long long> reads{0};
// Writer: publish all slots carrying the SAME generation in `packet`. A torn read would mix two
// generations, so the slots' packet values would disagree.
std::thread writer([&] {
std::uint32_t gen = 1;
while (!stop.load(std::memory_order_relaxed)) {
CoopPadState pads[kMaxPads];
for (auto& p : pads) {
p = CoopPadState{};
p.connected = 1;
p.packet = gen;
}
publish_pads(block, pads, kMaxPads);
++gen;
}
});
std::thread reader([&] {
while (!stop.load(std::memory_order_relaxed)) {
CoopPadState out[kMaxPads];
std::uint32_t count = 0;
if (read_pads(block, out, count)) {
reads.fetch_add(1, std::memory_order_relaxed);
for (std::uint32_t i = 1; i < kMaxPads; ++i) {
if (out[i].packet != out[0].packet) {
torn.fetch_add(1, std::memory_order_relaxed);
break;
}
}
}
}
});
std::this_thread::sleep_for(std::chrono::milliseconds(700));
stop.store(true, std::memory_order_relaxed);
writer.join();
reader.join();
std::printf(" seqlock reads=%lld torn=%lld\n", reads.load(), torn.load());
check(reads.load() > 1000, "seqlock: reader got many snapshots");
check(torn.load() == 0, "seqlock: never a torn snapshot under concurrent writes");
}
// --- Seqlock: a writer stuck mid-update (odd sequence) makes read_pads give up ----------------
{
SharedBlock block{};
block.sequence.store(1, std::memory_order_relaxed); // odd = write in progress, never completed
CoopPadState out[kMaxPads];
std::uint32_t count = 0;
check(!read_pads(block, out, count),
"seqlock: read_pads returns false when stuck mid-write (bounded, no hang)");
}
// --- Handshake: IpcClient::connect rejects a wrong magic / version ----------------------------
{
const unsigned long pid = GetCurrentProcessId(); // per-pid section name; no game needed
SharedMemory shm;
const bool created = shm.create(shared_memory_name(pid), sizeof(SharedBlock));
check(created, "handshake: created a section");
auto* block = shm.as<SharedBlock>();
// Wrong version.
block->version = kProtocolVersion - 1;
block->magic = kProtocolMagic;
{
IpcClient c;
check(!c.connect(/*attempts=*/3, /*delay_ms=*/1), "handshake: connect refuses a mismatched version");
}
// Wrong magic.
block->version = kProtocolVersion;
block->magic = kProtocolMagic ^ 0xDEAD;
{
IpcClient c;
check(!c.connect(3, 1), "handshake: connect refuses a bad magic");
}
// Correct header -> connects.
block->magic = kProtocolMagic;
{
IpcClient c;
check(c.connect(10, 1), "handshake: connect accepts a matching magic + version");
}
}
std::printf(g_failures == 0 ? "PASS protocol_test\n" : "FAILED protocol_test (%d)\n", g_failures);
return g_failures == 0 ? 0 : 1;
}