From 8a4ef69e867db2361367425655b4f7c258cf665f Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:25:43 +1200 Subject: [PATCH] Add random seed options to tests (#30735) * Add random seed options to tests * Ensure profile randomization --- Content.IntegrationTests/Pair/TestPair.cs | 67 ++++++++++++++++++----- Content.IntegrationTests/PoolManager.cs | 8 ++- Content.IntegrationTests/PoolSettings.cs | 46 +++++++++++----- 3 files changed, 90 insertions(+), 31 deletions(-) diff --git a/Content.IntegrationTests/Pair/TestPair.cs b/Content.IntegrationTests/Pair/TestPair.cs index 0b681dcde1..43b188fd32 100644 --- a/Content.IntegrationTests/Pair/TestPair.cs +++ b/Content.IntegrationTests/Pair/TestPair.cs @@ -9,6 +9,7 @@ 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; @@ -28,6 +29,12 @@ public sealed partial class TestPair 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!; @@ -74,22 +81,27 @@ public sealed partial class TestPair await Server.WaitPost(() => gameTicker.RestartRound()); } - if (settings.ShouldBeConnected) + // 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) { - Client.SetConnectTarget(Server); - await Client.WaitIdleAsync(); - var netMgr = Client.ResolveDependency(); - - await Client.WaitPost(() => - { - if (!netMgr.IsConnected) - { - netMgr.ClientConnect(null!, 0, null!); - } - }); + await Client.WaitPost(() => netMgr.ClientDisconnect("Initial disconnect")); await ReallyBeIdle(10); - await Client.WaitRunTicks(1); } + + var cRand = Client.ResolveDependency(); + var sRand = Server.ResolveDependency(); + _nextClientSeed = cRand.Next(); + _nextServerSeed = sRand.Next(); } public void Kill() @@ -129,4 +141,33 @@ public sealed partial class TestPair 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(); + } + } } diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index 21c93ea8ae..34ac4060dd 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -252,7 +252,7 @@ public static partial class PoolManager } finally { - if (pair != null && pair.TestHistory.Count > 1) + if (pair != null && pair.TestHistory.Count > 0) { await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Pair {pair.Id} Test History Start"); for (var i = 0; i < pair.TestHistory.Count; i++) @@ -268,12 +268,14 @@ public static partial class PoolManager var poolRetrieveTime = poolRetrieveTimeWatch.Elapsed; await testOut.WriteLineAsync( $"{nameof(GetServerClientPair)}: Retrieving pair {pair.Id} from pool took {poolRetrieveTime.TotalMilliseconds} ms"); - await testOut.WriteLineAsync( - $"{nameof(GetServerClientPair)}: Returning pair {pair.Id}"); pair.ClearModifiedCvars(); pair.Settings = poolSettings; pair.TestHistory.Add(currentTestName); + pair.SetupSeed(); + await testOut.WriteLineAsync( + $"{nameof(GetServerClientPair)}: Returning pair {pair.Id} with client/server seeds: {pair.ClientSeed}/{pair.ServerSeed}"); + pair.Watch.Restart(); return pair; } diff --git a/Content.IntegrationTests/PoolSettings.cs b/Content.IntegrationTests/PoolSettings.cs index a78173808f..9da514e66b 100644 --- a/Content.IntegrationTests/PoolSettings.cs +++ b/Content.IntegrationTests/PoolSettings.cs @@ -1,5 +1,7 @@ #nullable enable +using Robust.Shared.Random; + namespace Content.IntegrationTests; /// @@ -9,16 +11,6 @@ namespace Content.IntegrationTests; /// public sealed class PoolSettings { - /// - /// If the returned pair must not be reused - /// - public bool MustNotBeReused => Destructive || NoLoadContent || NoLoadTestPrototypes; - - /// - /// If the given pair must be brand new - /// - public bool MustBeNew => Fresh || NoLoadContent || NoLoadTestPrototypes; - /// /// Set to true if the test will ruin the server/client pair. /// @@ -34,8 +26,6 @@ public sealed class PoolSettings /// public bool DummyTicker { get; init; } = true; - public bool UseDummyTicker => !InLobby && DummyTicker; - /// /// If true, this enables the creation of admin logs during the test. /// @@ -48,8 +38,6 @@ public sealed class PoolSettings /// public bool Connected { get; init; } - public bool ShouldBeConnected => InLobby || Connected; - /// /// Set to true if the given server/client pair should be in the lobby. /// If the pair is not in the lobby at the end of the test, this test must be marked as dirty. @@ -92,6 +80,34 @@ public sealed class PoolSettings /// public string? TestName { get; set; } + /// + /// If set, this will be used to call + /// + public int? ServerSeed { get; set; } + + /// + /// If set, this will be used to call + /// + public int? ClientSeed { get; set; } + + #region Inferred Properties + + /// + /// If the returned pair must not be reused + /// + public bool MustNotBeReused => Destructive || NoLoadContent || NoLoadTestPrototypes; + + /// + /// If the given pair must be brand new + /// + public bool MustBeNew => Fresh || NoLoadContent || NoLoadTestPrototypes; + + public bool UseDummyTicker => !InLobby && DummyTicker; + + public bool ShouldBeConnected => InLobby || Connected; + + #endregion + /// /// Tries to guess if we can skip recycling the server/client pair. /// @@ -114,4 +130,4 @@ public sealed class PoolSettings && Map == nextSettings.Map && InLobby == nextSettings.InLobby; } -} \ No newline at end of file +} -- 2.52.0