Add test pooling (#4961)

* Add test pooling

* WIP test pooling changes

* Fix Destructible tests

* Don't pool unpooled or dummy ticker instances

* Change ServerPathfindingDebugSystem to replace existing entries

* Fix SaveLoadSaveTest comment

* Don't pool StartTest

* Comment out global setup

* Fix puddle tests

* Move SolarPanelComponent initialize to PowerSolarSystem OnMapInit

* Update RobustToolbox

* Finish fixing tests, make test threads background threads

* Bring back pooling

* Fix nullable

* Update RobustToolbox

* Set cvars on server return

* Un-pool tests with custom cvars

* Update RobustToolbox

* Update RobustToolbox

* Change where the main tile coordinates are

* Remove DisposalUnitTest grid check

* Fix test pooling being a fickle bitch

* Fix EntitySystemExtensionsTest

* Update RobustToolbox

* Update RobustToolbox

* Make nullable pool settings true

* Update RobustToolbox

* Wait other way around

* We are unitystation now

* Update RobustToolbox

* Create global setup

* Pool some more tests

* Fix not properly disconnecting clients before restarting the round

* Give more info on ran tests

* Standardize default test cvars

* Update RobustToolbox

* Update RobustToolbox

* Pool clients

* Fix test order issue

* Fix cvars in character creation test not being set properly

* Update RobustToolbox

* Update RobustToolbox

* Rider shut

* Update RobustToolbox

* Format tests ran better

* Update RobustToolbox

* Reset RobustToolbox

* Reset RobustToolbox harder

* Fix one instance of test order causing destructible tests to fail
This commit is contained in:
Javier Guardia Fernández
2021-11-06 11:49:59 +01:00
committed by GitHub
parent 4b5168e1fe
commit 1508efff54
55 changed files with 364 additions and 267 deletions

View File

@@ -1,31 +1,56 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Content.Client.Entry;
using Content.Client.IoC;
using Content.Client.Parallax.Managers;
using Content.Server.GameTicking;
using Content.Server.IoC;
using Content.Shared.CCVar;
using Moq;
using NUnit.Framework;
using Robust.Client;
using Robust.Server;
using Robust.Server.Maps;
using Robust.Shared;
using Robust.Server.Player;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Prototypes;
using Robust.UnitTesting;
using EntryPoint = Content.Client.Entry.EntryPoint;
namespace Content.IntegrationTests
{
[Parallelizable(ParallelScope.All)]
public abstract class ContentIntegrationTest : RobustIntegrationTest
{
private static readonly (string cvar, string value, bool)[] ServerTestCvars = {
// Avoid funny race conditions with the database.
(CCVars.DatabaseSynchronous.Name, "true", false),
// Disable holidays as some of them might mess with the map at round start.
(CCVars.HolidaysEnabled.Name, "false", false),
// Avoid loading a large map by default for integration tests if none has been specified.
(CCVars.GameMap.Name, "Maps/Test/empty.yml", true)
};
private static void SetServerTestCvars(IntegrationOptions options)
{
foreach (var (cvar, value, tryAdd) in ServerTestCvars)
{
if (tryAdd)
{
options.CVarOverrides.TryAdd(cvar, value);
}
else
{
options.CVarOverrides[cvar] = value;
}
}
}
protected sealed override ClientIntegrationInstance StartClient(ClientIntegrationOptions options = null)
{
options ??= new ClientContentIntegrationOption()
@@ -33,6 +58,8 @@ namespace Content.IntegrationTests
FailureLogLevel = LogLevel.Warning
};
options.Pool = ShouldPool(options, false);
// Load content resources, but not config and user data.
options.Options = new GameControllerOptions()
{
@@ -71,11 +98,14 @@ namespace Content.IntegrationTests
protected override ServerIntegrationInstance StartServer(ServerIntegrationOptions options = null)
{
options ??= new ServerContentIntegrationOption()
options ??= new ServerContentIntegrationOption
{
FailureLogLevel = LogLevel.Warning
FailureLogLevel = LogLevel.Warning,
};
SetServerTestCvars(options);
options.Pool = ShouldPool(options, true);
// Load content resources, but not config and user data.
options.Options = new ServerOptions()
{
@@ -108,16 +138,6 @@ namespace Content.IntegrationTests
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
};
// Avoid funny race conditions with the database.
options.CVarOverrides[CCVars.DatabaseSynchronous.Name] = "true";
// Disable holidays as some of them might mess with the map at round start.
options.CVarOverrides[CCVars.HolidaysEnabled.Name] = "false";
// Avoid loading a large map by default for integration tests if none has been specified.
if(!options.CVarOverrides.ContainsKey(CCVars.GameMap.Name))
options.CVarOverrides[CCVars.GameMap.Name] = "Maps/Test/empty.yml";
return base.StartServer(options);
}
@@ -150,7 +170,6 @@ namespace Content.IntegrationTests
return (client, server);
}
protected async Task<(ClientIntegrationInstance client, ServerIntegrationInstance server)>
StartConnectedServerDummyTickerClientPair(ClientIntegrationOptions clientOptions = null,
ServerIntegrationOptions serverOptions = null)
@@ -163,30 +182,129 @@ namespace Content.IntegrationTests
return (client, server);
}
protected async Task<IMapGrid> InitializeMap(ServerIntegrationInstance server, string mapPath)
private bool ShouldPool(IntegrationOptions options, bool server)
{
await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>();
var pauseManager = server.ResolveDependency<IPauseManager>();
var mapLoader = server.ResolveDependency<IMapLoader>();
IMapGrid grid = null;
server.Post(() =>
if (options.Pool == false)
{
var mapId = mapManager.CreateMap();
return false;
}
pauseManager.AddUninitializedMap(mapId);
if (server)
{
if (options.CVarOverrides.Count != 3)
{
return false;
}
grid = mapLoader.LoadBlueprint(mapId, mapPath);
foreach (var (cvar, value, _) in ServerTestCvars)
{
if (!options.CVarOverrides.TryGetValue(cvar, out var actualValue) ||
actualValue != value)
{
return false;
}
}
}
pauseManager.DoMapInitialize(mapId);
if (options.CVarOverrides.TryGetValue(CCVars.GameDummyTicker.Name, out var dummy) &&
dummy == "true")
{
return false;
}
if (options.CVarOverrides.TryGetValue(CCVars.GameLobbyEnabled.Name, out var lobby) &&
lobby == "true")
{
return false;
}
if (options is ClientContentIntegrationOption {ContentBeforeIoC: { }}
or ServerContentIntegrationOption {ContentBeforeIoC: { }})
{
return false;
}
return options.InitIoC == null &&
options.BeforeStart == null &&
options.ContentAssemblies == null;
}
protected override async Task OnClientReturn(ClientIntegrationInstance client)
{
await base.OnClientReturn(client);
await client.WaitIdleAsync();
var net = client.ResolveDependency<IClientNetManager>();
var prototypes = client.ResolveDependency<IPrototypeManager>();
await client.WaitPost(() =>
{
net.ClientDisconnect("Test pooling disconnect");
if (client.PreviousOptions?.ExtraPrototypes is { } oldExtra)
{
prototypes.RemoveString(oldExtra);
}
if (client.Options?.ExtraPrototypes is { } extra)
{
prototypes.LoadString(extra, true);
prototypes.Resync();
}
});
await WaitUntil(client, () => !net.IsConnected);
}
protected override async Task OnServerReturn(ServerIntegrationInstance server)
{
await base.OnServerReturn(server);
await server.WaitIdleAsync();
return grid;
if (server.Options != null)
{
SetServerTestCvars(server.Options);
}
var systems = server.ResolveDependency<IEntitySystemManager>();
var prototypes = server.ResolveDependency<IPrototypeManager>();
var net = server.ResolveDependency<IServerNetManager>();
var players = server.ResolveDependency<IPlayerManager>();
var gameTicker = systems.GetEntitySystem<GameTicker>();
await server.WaitPost(() =>
{
foreach (var channel in net.Channels)
{
net.DisconnectChannel(channel, "Test pooling disconnect");
}
});
await WaitUntil(server, () => players.PlayerCount == 0);
await server.WaitPost(() =>
{
gameTicker.RestartRound();
if (server.PreviousOptions?.ExtraPrototypes is { } oldExtra)
{
prototypes.RemoveString(oldExtra);
}
if (server.Options?.ExtraPrototypes is { } extra)
{
prototypes.LoadString(extra, true);
prototypes.Resync();
}
});
if (!gameTicker.DummyTicker)
{
await WaitUntil(server, () => gameTicker.RunLevel == GameRunLevel.InRound);
}
}
protected async Task WaitUntil(IntegrationInstance instance, Func<bool> func, int maxTicks = 600,
@@ -211,6 +329,12 @@ namespace Content.IntegrationTests
ticksAwaited += ticksToRun;
}
if (!passed)
{
Assert.Fail($"Condition did not pass after {maxTicks} ticks.\n" +
$"Tests ran ({instance.TestsRan.Count}):\n" +
$"{string.Join('\n', instance.TestsRan)}");
}
Assert.That(passed);
}
@@ -239,6 +363,30 @@ namespace Content.IntegrationTests
}
}
protected MapId GetMainMapId(IMapManager manager)
{
// TODO a heuristic that is not this bad
return manager.GetAllMapIds().Last();
}
protected IMapGrid GetMainGrid(IMapManager manager)
{
// TODO a heuristic that is not this bad
return manager.GetAllGrids().First();
}
protected TileRef GetMainTile(IMapGrid grid)
{
// TODO a heuristic that is not this bad
return grid.GetAllTiles().First();
}
protected EntityCoordinates GetMainEntityCoordinates(IMapManager manager)
{
var gridId = GetMainGrid(manager).GridEntityId;
return new EntityCoordinates(gridId, -0.5f, -0.5f);
}
protected sealed class ClientContentIntegrationOption : ClientIntegrationOptions
{
public ClientContentIntegrationOption()