test: extend coverage of testNetServer
This commit is contained in:
1 parent
b77f92afb0
commit
44fe6bb7c0
1 file changed
+189
@@ -320,3 +320,192 @@ VF_TEST(anUnbudgetedServerSendsTheSameChunksInFarFewerTicks) {
|
|||||||
CHECK(ticksNeeded[1] < 20u);
|
CHECK(ticksNeeded[1] < 20u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VF_TEST(theServerRefusesConnectionsOnceEverySlotIsTaken) {
|
||||||
|
ServerConfig config = quietServerConfig();
|
||||||
|
config.maxClients = 2;
|
||||||
|
World world;
|
||||||
|
NetServer server(world, config);
|
||||||
|
|
||||||
|
CHECK_EQ(server.slotCount(), static_cast<std::size_t>(2));
|
||||||
|
CHECK_EQ(server.acceptClient(), 0);
|
||||||
|
CHECK_EQ(server.acceptClient(), 1);
|
||||||
|
CHECK_EQ(server.acceptClient(), -1);
|
||||||
|
CHECK_EQ(server.acceptClient(), -1);
|
||||||
|
CHECK_EQ(server.stats().rejectedConnections, static_cast<uint64_t>(2));
|
||||||
|
CHECK_EQ(server.stats().acceptedConnections, static_cast<uint64_t>(2));
|
||||||
|
CHECK_EQ(server.activeClients(), 2u);
|
||||||
|
CHECK_EQ(server.linkFor(2), nullptr);
|
||||||
|
CHECK_EQ(server.sessionFor(-1), nullptr);
|
||||||
|
|
||||||
|
server.dropClient(0);
|
||||||
|
CHECK_EQ(server.activeClients(), 1u);
|
||||||
|
CHECK_EQ(server.acceptClient(), 0);
|
||||||
|
CHECK_EQ(server.activeClients(), 2u);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(aSilentClientTimesOutAndItsSlotComesBackForReuse) {
|
||||||
|
ServerConfig config = quietServerConfig();
|
||||||
|
config.maxClients = 1;
|
||||||
|
config.keepAliveIntervalTicks = 5;
|
||||||
|
config.keepAliveTimeoutTicks = 40;
|
||||||
|
World world;
|
||||||
|
NetServer server(world, config);
|
||||||
|
buildTerrain(world, -2, 2);
|
||||||
|
|
||||||
|
int slot = server.acceptClient();
|
||||||
|
TransportConfig transportConfig = config.transport;
|
||||||
|
{
|
||||||
|
RemoteClient leaving(server, slot, transportConfig, clientConfigFor(config), "leaving");
|
||||||
|
for (uint64_t tick = 0; tick < 30; ++tick) {
|
||||||
|
server.update(tick);
|
||||||
|
leaving.step(tick);
|
||||||
|
}
|
||||||
|
CHECK(server.sessionFor(slot) != nullptr);
|
||||||
|
CHECK(server.sessionFor(slot)->phase() == SessionPhase::Play);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint64_t tick = 30; tick < 200; ++tick) server.update(tick);
|
||||||
|
|
||||||
|
CHECK_EQ(server.activeClients(), 0u);
|
||||||
|
CHECK_EQ(server.stats().timeouts, static_cast<uint64_t>(1));
|
||||||
|
CHECK_EQ(server.stats().connectedClients, 0u);
|
||||||
|
CHECK_EQ(server.acceptClient(), 0);
|
||||||
|
CHECK_EQ(server.activeClients(), 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(entityMovesGoOnlyToTheClientsWhoseViewHoldsTheEntity) {
|
||||||
|
ServerConfig config = quietServerConfig();
|
||||||
|
World world;
|
||||||
|
NetServer server(world, config);
|
||||||
|
buildTerrain(world, -6, 6);
|
||||||
|
|
||||||
|
int slotNear = server.acceptClient();
|
||||||
|
int slotFar = server.acceptClient();
|
||||||
|
placeAt(server, slotNear, 8.0f, 8.0f, 8.0f);
|
||||||
|
placeAt(server, slotFar, 72.0f, 8.0f, 72.0f);
|
||||||
|
TransportConfig transportConfig = config.transport;
|
||||||
|
SessionConfig sessionConfig = clientConfigFor(config);
|
||||||
|
RemoteClient near(server, slotNear, transportConfig, sessionConfig, "near");
|
||||||
|
RemoteClient far(server, slotFar, transportConfig, sessionConfig, "far");
|
||||||
|
|
||||||
|
for (uint64_t tick = 0; tick < 40; ++tick) {
|
||||||
|
server.update(tick);
|
||||||
|
near.step(tick);
|
||||||
|
far.step(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint64_t tick = 40; tick < 70; ++tick) {
|
||||||
|
server.moveEntity(31, Vec3{9.0f, 5.0f, 9.0f}, 1.0f, 0.0f, true);
|
||||||
|
server.update(tick);
|
||||||
|
near.step(tick);
|
||||||
|
far.step(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityMove seen;
|
||||||
|
CHECK(near.session->entityMove(31, seen));
|
||||||
|
CHECK_NEAR(seen.x, 9.0f, 0.001f);
|
||||||
|
CHECK_EQ(far.session->trackedEntityCount(), static_cast<std::size_t>(0));
|
||||||
|
CHECK(far.session->stats().entityMovesApplied == 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(serverStatsAccountForClientsBytesChunksAndTheOutgoingQueue) {
|
||||||
|
ServerConfig config = quietServerConfig();
|
||||||
|
config.viewRadius = 1;
|
||||||
|
config.chunksPerClientPerTick = 1;
|
||||||
|
World world;
|
||||||
|
NetServer server(world, config);
|
||||||
|
buildTerrain(world, -2, 2);
|
||||||
|
|
||||||
|
CHECK_EQ(server.stats().connectedClients, 0u);
|
||||||
|
int slot = server.acceptClient();
|
||||||
|
placeAt(server, slot, 8.0f, 8.0f, 8.0f);
|
||||||
|
TransportConfig transportConfig = config.transport;
|
||||||
|
RemoteClient only(server, slot, transportConfig, clientConfigFor(config), "counted");
|
||||||
|
|
||||||
|
uint64_t bytesSeenInAnyTick = 0;
|
||||||
|
for (uint64_t tick = 0; tick < 40; ++tick) {
|
||||||
|
server.update(tick);
|
||||||
|
only.step(tick);
|
||||||
|
bytesSeenInAnyTick += server.stats().bytesSentThisTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(server.stats().connectedClients, 1u);
|
||||||
|
CHECK_EQ(server.stats().chunksSent, static_cast<uint64_t>(5));
|
||||||
|
CHECK_EQ(server.stats().totalBytesSent, bytesSeenInAnyTick);
|
||||||
|
CHECK(server.stats().totalBytesSent > 500u);
|
||||||
|
CHECK_EQ(server.stats().queuedPackets, static_cast<uint64_t>(0));
|
||||||
|
|
||||||
|
server.setBlock(1, 2, 3, Block::Ice);
|
||||||
|
server.setBlock(2, 2, 3, Block::Ice);
|
||||||
|
for (uint64_t tick = 40; tick < 60; ++tick) {
|
||||||
|
server.update(tick);
|
||||||
|
only.step(tick);
|
||||||
|
}
|
||||||
|
CHECK_EQ(server.stats().blockUpdatesSent, static_cast<uint64_t>(2));
|
||||||
|
CHECK_EQ(server.stats().editsApplied, static_cast<uint64_t>(2));
|
||||||
|
CHECK(only.world.blockAt(1, 2, 3) == Block::Ice);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(fourClientsSpreadAcrossTheWorldStayConsistentOverAnUnreliableLink) {
|
||||||
|
ServerConfig config = quietServerConfig();
|
||||||
|
config.maxClients = 4;
|
||||||
|
config.viewRadius = 1;
|
||||||
|
config.chunksPerClientPerTick = 2;
|
||||||
|
config.link.lossPercent = 25;
|
||||||
|
config.link.duplicatePercent = 10;
|
||||||
|
config.link.reorderPercent = 20;
|
||||||
|
config.link.latencyTicks = 2;
|
||||||
|
config.link.seed = 0xc0ffee1234ull;
|
||||||
|
config.transport.retransmitTimeoutTicks = 8;
|
||||||
|
World world;
|
||||||
|
NetServer server(world, config);
|
||||||
|
buildTerrain(world, -8, 8);
|
||||||
|
|
||||||
|
const float spots[4][2] = {{8.0f, 8.0f}, {72.0f, 8.0f}, {8.0f, 72.0f}, {72.0f, 72.0f}};
|
||||||
|
std::vector<std::unique_ptr<RemoteClient>> clients;
|
||||||
|
TransportConfig transportConfig = config.transport;
|
||||||
|
SessionConfig sessionConfig = clientConfigFor(config);
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
int slot = server.acceptClient();
|
||||||
|
CHECK_EQ(slot, i);
|
||||||
|
placeAt(server, slot, spots[i][0], 8.0f, spots[i][1]);
|
||||||
|
clients.push_back(std::unique_ptr<RemoteClient>(
|
||||||
|
new RemoteClient(server, slot, transportConfig, sessionConfig, "player")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Lcg rng(0x9911aabb);
|
||||||
|
for (uint64_t tick = 0; tick < 500; ++tick) {
|
||||||
|
if (tick > 100 && tick % 5 == 0) {
|
||||||
|
int corner = rng.range(4);
|
||||||
|
int baseX = static_cast<int>(spots[corner][0]);
|
||||||
|
int baseZ = static_cast<int>(spots[corner][1]);
|
||||||
|
server.setBlock(baseX + rng.range(8) - 4, rng.range(6), baseZ + rng.range(8) - 4,
|
||||||
|
Block::Glowstone);
|
||||||
|
}
|
||||||
|
server.update(tick);
|
||||||
|
for (auto& client : clients) client->step(tick);
|
||||||
|
}
|
||||||
|
for (uint64_t tick = 500; tick < 800; ++tick) {
|
||||||
|
server.update(tick);
|
||||||
|
for (auto& client : clients) client->step(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(server.activeClients(), 4u);
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
NetSession* session = server.sessionFor(i);
|
||||||
|
CHECK(session->phase() == SessionPhase::Play);
|
||||||
|
const ClientView& view = session->view();
|
||||||
|
CHECK_EQ(view.sent.size(), static_cast<std::size_t>(5));
|
||||||
|
for (const Vec3i& coord : view.sent) {
|
||||||
|
const Chunk* authoritative = world.chunkAt(coord);
|
||||||
|
const Chunk* mirror = clients[static_cast<std::size_t>(i)]->world.chunkAt(coord);
|
||||||
|
CHECK(mirror != nullptr);
|
||||||
|
if (mirror == nullptr) continue;
|
||||||
|
CHECK(authoritative->raw() == mirror->raw());
|
||||||
|
}
|
||||||
|
// Each client only ever holds the chunks it was told about.
|
||||||
|
CHECK_EQ(clients[static_cast<std::size_t>(i)]->world.chunkCount(),
|
||||||
|
static_cast<std::size_t>(5));
|
||||||
|
}
|
||||||
|
CHECK(server.stats().editsApplied > 50u);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user