#nullable enable using System.Collections.Generic; using System.IO; using System.Linq; using Content.Server.GameTicking; using Content.Shared.Players; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.UnitTesting; namespace Content.IntegrationTests.Pair; /// /// This object wraps a pooled server+client pair. /// public sealed partial class TestPair { public readonly int Id; private bool _initialized; private TextWriter _testOut = default!; public readonly Stopwatch Watch = new(); public readonly List TestHistory = new(); public PoolSettings Settings = default!; public TestMapData? TestMap; private List _modifiedProfiles = new(); private int _nextServerSeed; private int _nextClientSeed; public int ServerSeed; public int ClientSeed; public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!; public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!; public void Deconstruct( out RobustIntegrationTest.ServerIntegrationInstance server, out RobustIntegrationTest.ClientIntegrationInstance client) { server = Server; client = Client; } public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value); public ContentPlayerData? PlayerData => Player?.Data.ContentData(); public PoolTestLogHandler ServerLogHandler { get; private set; } = default!; public PoolTestLogHandler ClientLogHandler { get; private set; } = default!; public TestPair(int id) { Id = id; } public async Task Initialize(PoolSettings settings, TextWriter testOut, List testPrototypes) { if (_initialized) throw new InvalidOperationException("Already initialized"); _initialized = true; Settings = settings; (Client, ClientLogHandler) = await PoolManager.GenerateClient(settings, testOut); (Server, ServerLogHandler) = await PoolManager.GenerateServer(settings, testOut); ActivateContext(testOut); Client.CfgMan.OnCVarValueChanged += OnClientCvarChanged; Server.CfgMan.OnCVarValueChanged += OnServerCvarChanged; if (!settings.NoLoadTestPrototypes) await LoadPrototypes(testPrototypes!); if (!settings.UseDummyTicker) { var gameTicker = Server.ResolveDependency().System(); await Server.WaitPost(() => gameTicker.RestartRound()); } // Always initially connect clients to generate an initial random set of preferences/profiles. // This is to try and prevent issues where if the first test that connects the client is consistently some test // that uses a fixed seed, it would effectively prevent it from beingrandomized. Client.SetConnectTarget(Server); await Client.WaitIdleAsync(); var netMgr = Client.ResolveDependency(); await Client.WaitPost(() => netMgr.ClientConnect(null!, 0, null!)); await ReallyBeIdle(10); await Client.WaitRunTicks(1); if (!settings.ShouldBeConnected) { await Client.WaitPost(() => netMgr.ClientDisconnect("Initial disconnect")); await ReallyBeIdle(10); } var cRand = Client.ResolveDependency(); var sRand = Server.ResolveDependency(); _nextClientSeed = cRand.Next(); _nextServerSeed = sRand.Next(); } public void Kill() { State = PairState.Dead; ServerLogHandler.ShuttingDown = true; ClientLogHandler.ShuttingDown = true; Server.Dispose(); Client.Dispose(); } private void ClearContext() { _testOut = default!; ServerLogHandler.ClearContext(); ClientLogHandler.ClearContext(); } public void ActivateContext(TextWriter testOut) { _testOut = testOut; ServerLogHandler.ActivateContext(testOut); ClientLogHandler.ActivateContext(testOut); } public void Use() { if (State != PairState.Ready) throw new InvalidOperationException($"Pair is not ready to use. State: {State}"); State = PairState.InUse; } public enum PairState : byte { Ready = 0, InUse = 1, CleanDisposed = 2, Dead = 3, } public void SetupSeed() { var sRand = Server.ResolveDependency(); if (Settings.ServerSeed is { } severSeed) { ServerSeed = severSeed; sRand.SetSeed(ServerSeed); } else { ServerSeed = _nextServerSeed; sRand.SetSeed(ServerSeed); _nextServerSeed = sRand.Next(); } var cRand = Client.ResolveDependency(); if (Settings.ClientSeed is { } clientSeed) { ClientSeed = clientSeed; cRand.SetSeed(ClientSeed); } else { ClientSeed = _nextClientSeed; cRand.SetSeed(ClientSeed); _nextClientSeed = cRand.Next(); } } }