Added more docs to integration pool manager (#10632)

This commit is contained in:
wrexbe
2022-08-15 20:32:15 -07:00
committed by GitHub
parent 845ef758b3
commit ba9ee7300d

View File

@@ -12,12 +12,9 @@ using Content.IntegrationTests.Tests.Interaction.Click;
using Content.IntegrationTests.Tests.Networking;
using Content.Server.GameTicking;
using Content.Shared.CCVar;
using Content.Shared.Maps;
using NUnit.Framework;
using Robust.Client;
using Robust.Server;
using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
@@ -30,11 +27,13 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.UnitTesting;
[assembly: LevelOfParallelism(3)]
namespace Content.IntegrationTests;
/// <summary>
/// Making clients, and servers is slow, this manages a pool of them so tests can reuse them.
/// </summary>
public static class PoolManager
{
private static readonly (string cvar, string value, bool tryAdd)[] ServerTestCvars =
@@ -60,8 +59,8 @@ public static class PoolManager
(CCVars.GameRoleTimers.Name, "false", false),
};
private static int PairId = 0;
private static object PairLock = new object();
private static int PairId;
private static object PairLock = new();
private static List<Pair> Pairs = new();
private static async Task ConfigurePrototypes(RobustIntegrationTest.IntegrationInstance instance,
@@ -119,6 +118,10 @@ public static class PoolManager
return server;
}
/// <summary>
/// This shuts down the pool, and disposes all the server/client pairs.
/// This is a one time operation to be used when the testing program is exiting.
/// </summary>
public static void Shutdown()
{
lock (PairLock)
@@ -211,6 +214,11 @@ public static class PoolManager
}
}
/// <summary>
/// Gets a <see cref="PairTracker"/>, which can be used to get access to a server, and client <see cref="Pair"/>
/// </summary>
/// <param name="poolSettings">See <see cref="PoolSettings"/></param>
/// <returns></returns>
public static async Task<PairTracker> GetServerClient(PoolSettings poolSettings = null,
[System.Runtime.CompilerServices.CallerFilePath] string testMethodFilePath = "",
[System.Runtime.CompilerServices.CallerMemberName] string testMethodName = "") =>
@@ -294,7 +302,7 @@ public static class PoolManager
}
/// <summary>
/// Used after checking pairs, Don't use this directly
/// Used by PairTracker after checking the server/client pair, Don't use this.
/// </summary>
/// <param name="pair"></param>
public static void NoCheckReturn(Pair pair)
@@ -424,6 +432,11 @@ public static class PoolManager
return pair;
}
/// <summary>
/// Creates a map, a grid, and a tile, and gives back references to them.
/// </summary>
/// <param name="pairTracker">A pairTracker</param>
/// <returns>A TestMapData</returns>
public static async Task<TestMapData> CreateTestMap(PairTracker pairTracker)
{
var server = pairTracker.Pair.Server;
@@ -451,6 +464,11 @@ public static class PoolManager
return mapData;
}
/// <summary>
/// Runs a server/client pair in sync
/// </summary>
/// <param name="pair">A server/client pair</param>
/// <param name="ticks">How many ticks to run them for</param>
public static async Task RunTicksSync(Pair pair, int ticks)
{
for (var i = 0; i < ticks; i++)
@@ -460,13 +478,11 @@ public static class PoolManager
}
}
public static async Task WaitUntil(RobustIntegrationTest.IntegrationInstance instance, Func<bool> func,
int maxTicks = 600,
int tickStep = 1)
{
await WaitUntil(instance, async () => await Task.FromResult(func()), maxTicks, tickStep);
}
/// <summary>
/// Runs the server/client in sync, but also ensures they are both idle each tick.
/// </summary>
/// <param name="pair">The server/client pair</param>
/// <param name="runTicks">How many ticks to run</param>
public static async Task ReallyBeIdle(Pair pair, int runTicks = 25)
{
for (int i = 0; i < runTicks; i++)
@@ -481,6 +497,27 @@ public static class PoolManager
}
}
/// <summary>
/// Runs a server, or a client until a condition is true
/// </summary>
/// <param name="instance">The server or client</param>
/// <param name="func">The condition to check</param>
/// <param name="maxTicks">How many ticks to try before giving up</param>
/// <param name="tickStep">How many ticks to wait between checks</param>
public static async Task WaitUntil(RobustIntegrationTest.IntegrationInstance instance, Func<bool> func,
int maxTicks = 600,
int tickStep = 1)
{
await WaitUntil(instance, async () => await Task.FromResult(func()), maxTicks, tickStep);
}
/// <summary>
/// Runs a server, or a client until a condition is true
/// </summary>
/// <param name="instance">The server or client</param>
/// <param name="func">The async condition to check</param>
/// <param name="maxTicks">How many ticks to try before giving up</param>
/// <param name="tickStep">How many ticks to wait between checks</param>
public static async Task WaitUntil(RobustIntegrationTest.IntegrationInstance instance, Func<Task<bool>> func,
int maxTicks = 600,
int tickStep = 1)
@@ -515,78 +552,96 @@ public static class PoolManager
}
}
/// <summary>
/// Settings for the pooled server, and client pair.
/// Some options are for changing the pair, and others are
/// so the pool can properly clean up what you borrowed.
/// </summary>
public sealed class PoolSettings
{
// Todo: We can make more of these pool-able, if we need enough of them for it to matter
// TODO: We can make more of these pool-able, if we need enough of them for it to matter
/// <summary>
/// If the returned pair must not be reused
/// </summary>
public bool MustNotBeReused => Destructive || NoLoadContent || DisableInterpolate || DummyTicker;
/// <summary>
/// If the given pair must be brand new
/// </summary>
public bool MustBeNew => Fresh || NoLoadContent || DisableInterpolate || DummyTicker;
public bool NotConnected => NoClient || NoServer || Disconnected;
/// <summary>
/// We are going to ruin this pair
/// If the given pair must not be connected
/// </summary>
public bool NotConnected => NoClient || NoServer || Disconnected;
/// <summary>
/// Set to true if the test will ruin the server/client pair.
/// </summary>
public bool Destructive { get; init; }
/// <summary>
/// We need a brand new pair
/// Set to true if the given server/client pair should be created fresh.
/// </summary>
public bool Fresh { get; init; }
/// <summary>
/// We need a pair that uses a dummy ticker
/// Set to true if the given server should be using a dummy ticker.
/// </summary>
public bool DummyTicker { get; init; }
/// <summary>
/// We need the client, and server to be disconnected
/// Set to true if the given server/client pair should be disconnected from each other.
/// </summary>
public bool Disconnected { get; init; }
/// <summary>
/// We need the server to be in the lobby
/// Set to true if the given server/client pair should be in the lobby.
/// </summary>
public bool InLobby { get; init; }
/// <summary>
/// We don't want content loaded
/// Set this to true to skip loading the content files.
/// Note: This setting won't work with a client.
/// </summary>
public bool NoLoadContent { get; init; }
/// <summary>
/// We want to add some prototypes
/// Set this to raw yaml text to load prototypes onto the given server/client pair.
/// </summary>
public string ExtraPrototypes { get; init; }
/// <summary>
/// Disables NetInterp
/// Set this to true to disable the NetInterp CVar on the given server/client pair
/// </summary>
public bool DisableInterpolate { get; init; }
/// <summary>
/// Tells the pool it has to clean up before the server/client can be used.
/// Set this to true to always clean up the server/client pair before giving it to another borrower
/// </summary>
public bool Dirty { get; init; }
/// <summary>
/// Sets the map Cvar, and loads the map
/// Set this to the path of a map to have the given server/client pair load the map.
/// </summary>
public string Map { get; init; } // TODO for map painter
/// <summary>
/// The test won't use the client (so we can skip cleaning it)
/// Set to true if the test won't use the client (so we can skip cleaning it up)
/// </summary>
public bool NoClient { get; init; }
/// <summary>
/// The test won't use the client (so we can skip cleaning it)
/// Set to true if the test won't use the server (so we can skip cleaning it up)
/// </summary>
public bool NoServer { get; init; }
/// <summary>
/// Guess if skipping recycling is ok
/// Tries to guess if we can skip recycling the server/client pair.
/// </summary>
/// <param name="nextSettings">The next set of settings the old pair will be set to</param>
/// <returns></returns>
/// <returns>If we can skip cleaning it up</returns>
public bool CanFastRecycle(PoolSettings nextSettings)
{
if (Dirty) return false;
@@ -603,6 +658,9 @@ public sealed class PoolSettings
}
}
/// <summary>
/// Holds a reference to things commonly needed when testing on a map
/// </summary>
public sealed class TestMapData
{
public MapId MapId { get; set; }
@@ -612,6 +670,9 @@ public sealed class TestMapData
public TileRef Tile { get; set; }
}
/// <summary>
/// A server/client pair
/// </summary>
public sealed class Pair
{
public int PairId { get; init; }
@@ -621,11 +682,14 @@ public sealed class Pair
public RobustIntegrationTest.ClientIntegrationInstance Client { get; init; }
}
/// <summary>
/// Used by the pool to keep track of a borrowed server/client pair.
/// </summary>
public sealed class PairTracker : IAsyncDisposable
{
private int _disposed;
public async Task OnDirtyDispose()
private async Task OnDirtyDispose()
{
var usageTime = UsageWatch.Elapsed;
await TestContext.Out.WriteLineAsync($"Dirty: Test returned in {usageTime.TotalMilliseconds} ms");
@@ -637,7 +701,7 @@ public sealed class PairTracker : IAsyncDisposable
await TestContext.Out.WriteLineAsync($"Dirty: Disposed in {disposeTime.TotalMilliseconds} ms");
}
public async Task OnCleanDispose()
private async Task OnCleanDispose()
{
var usageTime = UsageWatch.Elapsed;
await TestContext.Out.WriteLineAsync($"Clean: Test returned in {usageTime.TotalMilliseconds} ms");