// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Tests; using MUnique.OpenMU.GameLogic.Bots; /// /// Tests how the bot population is split over the game servers of a deployment /// (): bots count towards the player limit of their server, so a server /// must never animate more of them than its reserved share allows - and the servers must agree on who /// animates whom without asking each other. /// [TestFixture] public class BotServerPartitionTest { /// /// A single game server animates the accounts which fit into its share; the rest stays offline /// instead of filling the server up, because the remaining capacity belongs to the players. /// [Test] public void SingleServerTakesWhatFits() { var (partition, assigned) = BotServerPartition.Split([(0, 120)], 0, 220); Assert.That(partition.FirstAccount, Is.EqualTo(1)); Assert.That(partition.AccountCount, Is.EqualTo(120)); Assert.That(partition.IsGenerator, Is.True); Assert.That(assigned, Is.EqualTo(120)); } /// /// The scenario the split is made for: one server was crowded, a second one is added, and the /// population spreads over BOTH of them - a player who picks the new server meets bots there, too. /// [Test] public void PopulationSpreadsOverBothServers() { List<(byte ServerId, int Capacity)> capacities = [(0, 120), (1, 120)]; var (first, assigned) = BotServerPartition.Split(capacities, 0, 220); var (second, _) = BotServerPartition.Split(capacities, 1, 220); Assert.That(assigned, Is.EqualTo(220)); Assert.That(first.AccountCount, Is.EqualTo(110)); Assert.That(second.AccountCount, Is.EqualTo(110)); Assert.That(second.FirstAccount, Is.EqualTo(111)); } /// /// The invariant which protects the characters: every account is animated by exactly one server. /// Two servers animating one account is the cross-context situation which corrupts it. /// /// The configured number of bot accounts. [TestCase(1)] [TestCase(7)] [TestCase(220)] [TestCase(1000)] public void EveryAccountIsAnimatedExactlyOnce(int requestedAccounts) { // Deliberately uneven capacities, so the rounding of the shares is exercised. List<(byte ServerId, int Capacity)> capacities = [(0, 37), (1, 90), (2, 113)]; var partitions = capacities .Select(server => BotServerPartition.Split(capacities, server.ServerId, requestedAccounts)) .ToList(); var assigned = partitions[0].AssignedAccounts; Assert.That(partitions.Sum(p => p.Partition.AccountCount), Is.EqualTo(assigned)); for (var account = 1; account <= assigned; account++) { Assert.That(partitions.Count(p => p.Partition.Owns(account)), Is.EqualTo(1), $"account {account}"); } } /// /// Exactly one server generates the population, so the accounts - and their unique character names - /// are never created twice at the same time. /// [Test] public void OnlyTheFirstServerGenerates() { List<(byte ServerId, int Capacity)> capacities = [(0, 50), (1, 50), (2, 50)]; Assert.That(BotServerPartition.Split(capacities, 0, 150).Partition.IsGenerator, Is.True); Assert.That(BotServerPartition.Split(capacities, 1, 150).Partition.IsGenerator, Is.False); Assert.That(BotServerPartition.Split(capacities, 2, 150).Partition.IsGenerator, Is.False); } /// /// The servers may have different player limits; the shares follow their capacity. /// [Test] public void SharesFollowTheServerCapacity() { List<(byte ServerId, int Capacity)> capacities = [(0, 30), (1, 90)]; var (small, _) = BotServerPartition.Split(capacities, 0, 120); var (big, _) = BotServerPartition.Split(capacities, 1, 120); Assert.That(small.AccountCount, Is.EqualTo(30)); Assert.That(big.AccountCount, Is.EqualTo(90)); Assert.That(big.FirstAccount, Is.EqualTo(31)); } /// /// A server without any bot capacity (its player limit is reserved for players entirely) animates /// nothing, and the other servers still cover the whole population. /// [Test] public void ServerWithoutCapacityAnimatesNothing() { List<(byte ServerId, int Capacity)> capacities = [(0, 100), (1, 0)]; var (empty, assigned) = BotServerPartition.Split(capacities, 1, 60); Assert.That(empty.AccountCount, Is.EqualTo(0)); Assert.That(empty.IsGenerator, Is.False); Assert.That(empty.Owns(1), Is.False); Assert.That(assigned, Is.EqualTo(60)); Assert.That(BotServerPartition.Split(capacities, 0, 60).Partition.AccountCount, Is.EqualTo(60)); } }