Remove PoolSettings.ExtraPrototypes option (#18678)

This commit is contained in:
Leon Friedrich
2023-08-05 16:16:48 +12:00
committed by GitHub
parent c2beaff3ac
commit d58786faf4
51 changed files with 463 additions and 399 deletions

View File

@@ -1,6 +1,8 @@
#nullable enable
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Content.Client.IoC;
@@ -27,6 +29,7 @@ using Robust.Shared.Map.Components;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.UnitTesting;
[assembly: LevelOfParallelism(3)]
@@ -36,7 +39,7 @@ 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
public static partial class PoolManager
{
public const string TestMap = "Empty";
@@ -62,23 +65,12 @@ public static class PoolManager
private static int _pairId;
private static readonly object PairLock = new();
private static bool _initialized;
// Pair, IsBorrowed
private static readonly Dictionary<Pair, bool> Pairs = new();
private static bool _dead;
private static Exception _poolFailureReason;
private static async Task ConfigurePrototypes(RobustIntegrationTest.IntegrationInstance instance,
PoolSettings settings)
{
await instance.WaitPost(() =>
{
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var changes = new Dictionary<Type, HashSet<string>>();
prototypeManager.LoadString(settings.ExtraPrototypes.Trim(), true, changes);
prototypeManager.ReloadPrototypes(changes);
});
}
private static Exception? _poolFailureReason;
private static async Task<(RobustIntegrationTest.ServerIntegrationInstance, PoolTestLogHandler)> GenerateServer(
PoolSettings poolSettings,
@@ -86,7 +78,6 @@ public static class PoolManager
{
var options = new RobustIntegrationTest.ServerIntegrationOptions
{
ExtraPrototypes = poolSettings.ExtraPrototypes,
ContentStart = true,
Options = new ServerOptions()
{
@@ -144,6 +135,8 @@ public static class PoolManager
{
pair.Kill();
}
_initialized = false;
}
public static string DeathReport()
@@ -174,7 +167,6 @@ public static class PoolManager
{
FailureLogLevel = LogLevel.Warning,
ContentStart = true,
ExtraPrototypes = poolSettings.ExtraPrototypes,
ContentAssemblies = new[]
{
typeof(Shared.Entry.EntryPoint).Assembly,
@@ -257,7 +249,7 @@ public static class PoolManager
/// </summary>
/// <param name="poolSettings">See <see cref="PoolSettings"/></param>
/// <returns></returns>
public static async Task<PairTracker> GetServerClient(PoolSettings poolSettings = null)
public static async Task<PairTracker> GetServerClient(PoolSettings? poolSettings = null)
{
return await GetServerClientPair(poolSettings ?? new PoolSettings());
}
@@ -269,6 +261,9 @@ public static class PoolManager
private static async Task<PairTracker> GetServerClientPair(PoolSettings poolSettings)
{
if (!_initialized)
throw new InvalidOperationException($"Pool manager has not been initialized");
// Trust issues with the AsyncLocal that backs this.
var testContext = TestContext.CurrentContext;
var testOut = TestContext.Out;
@@ -277,7 +272,7 @@ public static class PoolManager
var currentTestName = poolSettings.TestName ?? GetDefaultTestName(testContext);
var poolRetrieveTimeWatch = new Stopwatch();
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Called by test {currentTestName}");
Pair pair = null;
Pair? pair = null;
try
{
poolRetrieveTimeWatch.Start();
@@ -386,11 +381,11 @@ public static class PoolManager
Assert.That(status, Is.EqualTo(expected));
}
private static Pair GrabOptimalPair(PoolSettings poolSettings)
private static Pair? GrabOptimalPair(PoolSettings poolSettings)
{
lock (PairLock)
{
Pair fallback = null;
Pair? fallback = null;
foreach (var pair in Pairs.Keys)
{
if (Pairs[pair])
@@ -457,7 +452,7 @@ public static class PoolManager
cNetMgr.ClientConnect(null!, 0, null!);
});
}
await ReallyBeIdle(pair, 11);
await ReallyBeIdle(pair, 5);
await testOut.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Disconnecting client, and restarting server");
@@ -466,43 +461,7 @@ public static class PoolManager
cNetMgr.ClientDisconnect("Test pooling cleanup disconnect");
});
await ReallyBeIdle(pair, 10);
if (!string.IsNullOrWhiteSpace(pair.Settings.ExtraPrototypes))
{
await testOut.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Removing prototypes");
if (!pair.Settings.NoServer)
{
var serverProtoManager = pair.Server.ResolveDependency<IPrototypeManager>();
await pair.Server.WaitPost(() =>
{
serverProtoManager.RemoveString(pair.Settings.ExtraPrototypes.Trim());
});
}
if (!pair.Settings.NoClient)
{
var clientProtoManager = pair.Client.ResolveDependency<IPrototypeManager>();
await pair.Client.WaitPost(() =>
{
clientProtoManager.RemoveString(pair.Settings.ExtraPrototypes.Trim());
});
}
await ReallyBeIdle(pair, 1);
}
if (poolSettings.ExtraPrototypes != null)
{
await testOut.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Adding prototypes");
if (!poolSettings.NoServer)
{
await ConfigurePrototypes(pair.Server, poolSettings);
}
if (!poolSettings.NoClient)
{
await ConfigurePrototypes(pair.Client, poolSettings);
}
}
await ReallyBeIdle(pair, 5);
configManager.SetCVar(CCVars.GameMap, poolSettings.Map);
await testOut.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Restarting server again");
@@ -548,6 +507,7 @@ we are just going to end this here to save a lot of time. This is the exception
Assert.Fail("The pool was shut down");
}
}
private static async Task<Pair> CreateServerClientPair(PoolSettings poolSettings, TextWriter testOut)
{
Pair pair;
@@ -563,6 +523,9 @@ we are just going to end this here to save a lot of time. This is the exception
ClientLogHandler = clientLog,
PairId = Interlocked.Increment(ref _pairId)
};
if (!poolSettings.NoLoadTestPrototypes)
await pair.LoadPrototypes(_testPrototypes!);
}
catch (Exception ex)
{
@@ -757,6 +720,19 @@ we are just going to end this here to save a lot of time. This is the exception
return list;
}
/// <summary>
/// Initialize the pool manager.
/// </summary>
/// <param name="assembly">Assembly to search for to discover extra test prototypes.</param>
public static void Startup(Assembly? assembly)
{
if (_initialized)
throw new InvalidOperationException("Already initialized");
_initialized = true;
DiscoverTestPrototypes(assembly);
}
}
/// <summary>
@@ -766,17 +742,15 @@ we are just going to end this here to save a lot of time. This is the exception
/// </summary>
public sealed class PoolSettings
{
// 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 || NoToolsExtraPrototypes;
public bool MustNotBeReused => Destructive || NoLoadContent || NoLoadTestPrototypes;
/// <summary>
/// If the given pair must be brand new
/// </summary>
public bool MustBeNew => Fresh || NoLoadContent || NoToolsExtraPrototypes;
public bool MustBeNew => Fresh || NoLoadContent || NoLoadTestPrototypes;
/// <summary>
/// If the given pair must not be connected
@@ -816,9 +790,11 @@ public sealed class PoolSettings
public bool NoLoadContent { get; init; }
/// <summary>
/// Set this to raw yaml text to load prototypes onto the given server/client pair.
/// This will return a server-client pair that has not loaded test prototypes.
/// Try avoiding this whenever possible, as this will always create & destroy a new pair.
/// Use <see cref="Pair.IsTestPrototype(EntityPrototype)"/> if you need to exclude test prototypees.
/// </summary>
public string ExtraPrototypes { get; init; }
public bool NoLoadTestPrototypes { get; init; }
/// <summary>
/// Set this to true to disable the NetInterp CVar on the given server/client pair
@@ -848,7 +824,7 @@ public sealed class PoolSettings
/// <summary>
/// Overrides the test name detection, and uses this in the test history instead
/// </summary>
public string TestName { get; set; }
public string? TestName { get; set; }
/// <summary>
/// Tries to guess if we can skip recycling the server/client pair.
@@ -870,19 +846,8 @@ public sealed class PoolSettings
return NotConnected == nextSettings.NotConnected
&& DummyTicker == nextSettings.DummyTicker
&& Map == nextSettings.Map
&& InLobby == nextSettings.InLobby
&& ExtraPrototypes == nextSettings.ExtraPrototypes;
&& InLobby == nextSettings.InLobby;
}
// Prototype hot reload is not available outside TOOLS builds,
// so we can't pool test instances that use ExtraPrototypes without TOOLS.
#if TOOLS
#pragma warning disable CA1822 // Can't be marked as static b/c the other branch exists but Omnisharp can't see both.
private bool NoToolsExtraPrototypes => false;
#pragma warning restore CA1822
#else
private bool NoToolsExtraPrototypes => !string.IsNullOrEmpty(ExtraPrototypes);
#endif
}
/// <summary>
@@ -893,7 +858,7 @@ public sealed class TestMapData
public EntityUid MapUid { get; set; }
public EntityUid GridUid { get; set; }
public MapId MapId { get; set; }
public MapGridComponent MapGrid { get; set; }
public MapGridComponent MapGrid { get; set; } = default!;
public EntityCoordinates GridCoords { get; set; }
public MapCoordinates MapCoords { get; set; }
public TileRef Tile { get; set; }
@@ -907,12 +872,15 @@ public sealed class Pair
public bool Dead { get; private set; }
public int PairId { get; init; }
public List<string> TestHistory { get; set; } = new();
public PoolSettings Settings { get; set; }
public RobustIntegrationTest.ServerIntegrationInstance Server { get; init; }
public RobustIntegrationTest.ClientIntegrationInstance Client { get; init; }
public PoolSettings Settings { get; set; } = default!;
public RobustIntegrationTest.ServerIntegrationInstance Server { get; init; } = default!;
public RobustIntegrationTest.ClientIntegrationInstance Client { get; init; } = default!;
public PoolTestLogHandler ServerLogHandler { get; init; }
public PoolTestLogHandler ClientLogHandler { get; init; }
public PoolTestLogHandler ServerLogHandler { get; init; } = default!;
public PoolTestLogHandler ClientLogHandler { get; init; } = default!;
private Dictionary<Type, HashSet<string>> _loadedPrototypes = new();
private HashSet<string> _loadedEntityPrototypes = new();
public void Kill()
{
@@ -932,6 +900,57 @@ public sealed class Pair
ServerLogHandler.ActivateContext(testOut);
ClientLogHandler.ActivateContext(testOut);
}
public async Task LoadPrototypes(List<string> prototypes)
{
await LoadPrototypes(Server, prototypes);
await LoadPrototypes(Client, prototypes);
}
private async Task LoadPrototypes(RobustIntegrationTest.IntegrationInstance instance, List<string> prototypes)
{
var changed = new Dictionary<Type, HashSet<string>>();
var protoMan = instance.ResolveDependency<IPrototypeManager>();
foreach (var file in prototypes)
{
protoMan.LoadString(file, changed: changed);
}
await instance.WaitPost(() => protoMan.ReloadPrototypes(changed));
foreach (var (kind, ids) in changed)
{
_loadedPrototypes.GetOrNew(kind).UnionWith(ids);
}
if (_loadedPrototypes.TryGetValue(typeof(EntityPrototype), out var entIds))
_loadedEntityPrototypes.UnionWith(entIds);
}
public bool IsTestPrototype(EntityPrototype proto)
{
return _loadedEntityPrototypes.Contains(proto.ID);
}
public bool IsTestEntityPrototype(string id)
{
return _loadedEntityPrototypes.Contains(id);
}
public bool IsTestPrototype<TPrototype>(string id) where TPrototype : IPrototype
{
return IsTestPrototype(typeof(TPrototype), id);
}
public bool IsTestPrototype<TPrototype>(TPrototype proto) where TPrototype : IPrototype
{
return IsTestPrototype(typeof(TPrototype), proto.ID);
}
public bool IsTestPrototype(Type kind, string id)
{
return _loadedPrototypes.TryGetValue(kind, out var ids) && ids.Contains(id);
}
}
/// <summary>
@@ -941,8 +960,8 @@ public sealed class PairTracker : IAsyncDisposable
{
private readonly TextWriter _testOut;
private int _disposed;
public Stopwatch UsageWatch { get; set; }
public Pair Pair { get; init; }
public Stopwatch UsageWatch { get; set; } = default!;
public Pair Pair { get; init; } = default!;
public PairTracker(TextWriter testOut)
{