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:
committed by
GitHub
parent
4b5168e1fe
commit
1508efff54
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user