forked from Nyanotrasen/Nyanotrasen
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPoolManager.cs
720 lines (641 loc) · 25.6 KB
/
PoolManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Content.Client.IoC;
using Content.Client.Parallax.Managers;
using Content.IntegrationTests.Tests;
using Content.IntegrationTests.Tests.Destructible;
using Content.IntegrationTests.Tests.DeviceNetwork;
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;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.UnitTesting;
[assembly: LevelOfParallelism(3)]
namespace Content.IntegrationTests;
public static class PoolManager
{
private static readonly (string cvar, string value, bool tryAdd)[] ServerTestCvars =
{
(CCVars.DatabaseSynchronous.Name, "true", false),
(CCVars.DatabaseSqliteDelay.Name, "0", false),
(CCVars.HolidaysEnabled.Name, "false", false),
(CCVars.GameMap.Name, "empty", true),
(CCVars.GameMapForced.Name, "true", true),
(CCVars.AdminLogsQueueSendDelay.Name, "0", true),
(CCVars.NetPVS.Name, "false", true),
(CCVars.NetInterp.Name, "false", true),
(CCVars.NPCMaxUpdates.Name, "999999", true),
(CCVars.GameMapForced.Name, "true", true),
(CCVars.SysWinTickPeriod.Name, "0", true),
(CCVars.ContactMinimumThreads.Name, "1", true),
(CCVars.ContactMultithreadThreshold.Name, "999", true),
(CCVars.PositionConstraintsMinimumThread.Name, "1", true),
(CCVars.PositionConstraintsPerThread.Name, "999", true),
(CCVars.VelocityConstraintMinimumThreads.Name, "1", true),
(CCVars.VelocityConstraintsPerThread.Name, "999", true),
(CCVars.ThreadParallelCount.Name, "1", true),
};
private static int PairId = 0;
private static object PairLock = new object();
private static List<Pair> Pairs = new();
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 async Task<RobustIntegrationTest.ServerIntegrationInstance> GenerateServer(PoolSettings poolSettings)
{
var options = new RobustIntegrationTest.ServerIntegrationOptions
{
ExtraPrototypes = poolSettings.ExtraPrototypes,
ContentStart = true,
Options = new ServerOptions()
{
LoadConfigAndUserData = false,
LoadContentResources = !poolSettings.NoLoadContent,
},
ContentAssemblies = new[]
{
typeof(Shared.Entry.EntryPoint).Assembly,
typeof(Server.Entry.EntryPoint).Assembly,
typeof(PoolManager).Assembly
}
};
options.BeforeStart += () =>
{
IoCManager.Resolve<IEntitySystemManager>()
.LoadExtraSystemType<SimplePredictReconcileTest.PredictionTestEntitySystem>();
IoCManager.Resolve<IComponentFactory>().RegisterClass<SimplePredictReconcileTest.PredictionTestComponent>();
IoCManager.Register<ResettingEntitySystemTests.TestRoundRestartCleanupEvent>();
IoCManager.Register<InteractionSystemTests.TestInteractionSystem>();
IoCManager.Register<DeviceNetworkTestSystem>();
IoCManager.Resolve<IEntitySystemManager>()
.LoadExtraSystemType<ResettingEntitySystemTests.TestRoundRestartCleanupEvent>();
IoCManager.Resolve<IEntitySystemManager>()
.LoadExtraSystemType<InteractionSystemTests.TestInteractionSystem>();
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<DeviceNetworkTestSystem>();
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<TestDestructibleListenerSystem>();
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
};
SetupCVars(poolSettings, options);
var server = new RobustIntegrationTest.ServerIntegrationInstance(options);
await server.WaitIdleAsync();
return server;
}
public static void Shutdown()
{
lock (PairLock)
{
var pairs = Pairs;
// We are trying to make things blow up if they are still happening after this method.
Pairs = null;
foreach (var pair in pairs)
{
pair.Client.Dispose();
pair.Server.Dispose();
}
}
}
private static async Task<RobustIntegrationTest.ClientIntegrationInstance> GenerateClient(PoolSettings poolSettings)
{
var options = new RobustIntegrationTest.ClientIntegrationOptions
{
FailureLogLevel = LogLevel.Warning,
ContentStart = true,
ExtraPrototypes = poolSettings.ExtraPrototypes,
ContentAssemblies = new[]
{
typeof(Shared.Entry.EntryPoint).Assembly,
typeof(Client.Entry.EntryPoint).Assembly,
typeof(PoolManager).Assembly
}
};
if (poolSettings.NoLoadContent)
{
Assert.Warn("NoLoadContent does not work on the client, ignoring");
}
options.Options = new GameControllerOptions()
{
LoadConfigAndUserData = false,
// LoadContentResources = !poolSettings.NoLoadContent
};
options.BeforeStart += () =>
{
IoCManager.Resolve<IModLoader>().SetModuleBaseCallbacks(new ClientModuleTestingCallbacks
{
ClientBeforeIoC = () =>
{
IoCManager.Resolve<IEntitySystemManager>()
.LoadExtraSystemType<SimplePredictReconcileTest.PredictionTestEntitySystem>();
IoCManager.Resolve<IComponentFactory>()
.RegisterClass<SimplePredictReconcileTest.PredictionTestComponent>();
IoCManager.Register<IParallaxManager, DummyParallaxManager>(true);
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
}
});
};
SetupCVars(poolSettings, options);
var client = new RobustIntegrationTest.ClientIntegrationInstance(options);
await client.WaitIdleAsync();
return client;
}
private static void SetupCVars(PoolSettings poolSettings, RobustIntegrationTest.IntegrationOptions options)
{
foreach (var serverTestCvar in ServerTestCvars)
{
options.CVarOverrides[serverTestCvar.cvar] = serverTestCvar.value;
}
if (poolSettings.DummyTicker)
{
options.CVarOverrides[CCVars.GameDummyTicker.Name] = "true";
}
if (poolSettings.InLobby)
{
options.CVarOverrides[CCVars.GameLobbyEnabled.Name] = "true";
}
if (poolSettings.DisableInterpolate)
{
options.CVarOverrides[CCVars.NetInterp.Name] = "false";
}
if (poolSettings.Map != null)
{
options.CVarOverrides[CCVars.GameMap.Name] = poolSettings.Map;
}
}
public static async Task<PairTracker> GetServerClient(PoolSettings poolSettings = null,
[System.Runtime.CompilerServices.CallerFilePath] string testMethodFilePath = "",
[System.Runtime.CompilerServices.CallerMemberName] string testMethodName = "") =>
await GetServerClientPair(poolSettings ?? new PoolSettings(), $"{testMethodFilePath}, {testMethodName}");
private static async Task<PairTracker> GetServerClientPair(PoolSettings poolSettings, string testMethodName)
{
Pair pair = null;
try
{
var poolRetrieveTimeWatch = new Stopwatch();
poolRetrieveTimeWatch.Start();
await TestContext.Out.WriteLineAsync("Getting server/client");
if (poolSettings.MustBeNew)
{
await TestContext.Out.WriteLineAsync($"Creating, because must be new pair");
pair = await CreateServerClientPair(poolSettings);
}
else
{
pair = GrabOptimalPair(poolSettings);
if (pair != null)
{
var canSkip = pair.Settings.CanFastRecycle(poolSettings);
if (!canSkip)
{
await TestContext.Out.WriteLineAsync($"Cleaning existing pair");
await CleanPooledPair(poolSettings, pair);
}
else
{
await TestContext.Out.WriteLineAsync($"Skip cleanup pair");
}
}
else
{
await TestContext.Out.WriteLineAsync($"Creating, because pool empty");
pair = await CreateServerClientPair(poolSettings);
}
}
var poolRetrieveTime = poolRetrieveTimeWatch.Elapsed;
await TestContext.Out.WriteLineAsync(
$"Got server/client (id:{pair.PairId},uses:{pair.TestHistory.Count}) in {poolRetrieveTime.TotalMilliseconds} ms");
pair.Settings = poolSettings;
pair.TestHistory.Add(testMethodName);
var usageWatch = new Stopwatch();
usageWatch.Start();
return new PairTracker()
{
Pair = pair,
UsageWatch = usageWatch
};
}
finally
{
if (pair != null)
{
TestContext.Out.WriteLine($"Test History|\n{string.Join('\n', pair.TestHistory)}\n|Test History End");
}
}
}
private static Pair GrabOptimalPair(PoolSettings poolSettings)
{
lock (PairLock)
{
if (Pairs.Count == 0) return null;
for (var i = 0; i < Pairs.Count; i++)
{
var pair = Pairs[i];
if (!pair.Settings.CanFastRecycle(poolSettings)) continue;
Pairs.RemoveAt(i);
return pair;
}
var defaultPair = Pairs[^1];
Pairs.RemoveAt(Pairs.Count - 1);
return defaultPair;
}
}
/// <summary>
/// Used after checking pairs, Don't use this directly
/// </summary>
/// <param name="pair"></param>
public static void NoCheckReturn(Pair pair)
{
lock (PairLock)
{
Pairs.Add(pair);
}
}
private static async Task CleanPooledPair(PoolSettings poolSettings, Pair pair)
{
var methodWatch = new Stopwatch();
methodWatch.Start();
await TestContext.Out.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Setting CVar ");
var configManager = pair.Server.ResolveDependency<IConfigurationManager>();
await pair.Server.WaitPost(() =>
{
configManager.SetCVar(CCVars.GameLobbyEnabled, poolSettings.InLobby);
});
var cNetMgr = pair.Client.ResolveDependency<IClientNetManager>();
if (!cNetMgr.IsConnected)
{
await TestContext.Out.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Connecting client, and restarting server");
pair.Client.SetConnectTarget(pair.Server);
await pair.Server.WaitPost(() =>
{
EntitySystem.Get<GameTicker>().RestartRound();
});
await pair.Client.WaitPost(() =>
{
cNetMgr.ClientConnect(null!, 0, null!);
});
}
await ReallyBeIdle(pair,11);
await TestContext.Out.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Disconnecting client, and restarting server");
await pair.Client.WaitPost(() =>
{
cNetMgr.ClientDisconnect("Test pooling cleanup disconnect");
});
await ReallyBeIdle(pair, 10);
if (!string.IsNullOrWhiteSpace(pair.Settings.ExtraPrototypes))
{
await TestContext.Out.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 TestContext.Out.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 TestContext.Out.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Restarting server again");
await pair.Server.WaitPost(() =>
{
EntitySystem.Get<GameTicker>().RestartRound();
});
if (!poolSettings.NotConnected)
{
await TestContext.Out.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Connecting client");
await ReallyBeIdle(pair);
pair.Client.SetConnectTarget(pair.Server);
await pair.Client.WaitPost(() =>
{
var netMgr = IoCManager.Resolve<IClientNetManager>();
if (!netMgr.IsConnected)
{
netMgr.ClientConnect(null!, 0, null!);
}
});
}
await ReallyBeIdle(pair);
await TestContext.Out.WriteLineAsync($"Recycling: {methodWatch.Elapsed.TotalMilliseconds} ms: Done recycling");
}
private static async Task<Pair> CreateServerClientPair(PoolSettings poolSettings)
{
var client = await GenerateClient(poolSettings);
var server = await GenerateServer(poolSettings);
var pair = new Pair { Server = server, Client = client, PairId = Interlocked.Increment(ref PairId)};
if (!poolSettings.NotConnected)
{
pair.Client.SetConnectTarget(pair.Server);
await pair.Client.WaitPost(() =>
{
var netMgr = IoCManager.Resolve<IClientNetManager>();
if (!netMgr.IsConnected)
{
netMgr.ClientConnect(null!, 0, null!);
}
});
await ReallyBeIdle(pair, 10);
await client.WaitRunTicks(1);
}
return pair;
}
public static async Task<TestMapData> CreateTestMap(PairTracker pairTracker)
{
var server = pairTracker.Pair.Server;
var settings = pairTracker.Pair.Settings;
if (settings.NoServer) throw new Exception("Cannot setup test map without server");
var mapData = new TestMapData();
await server.WaitPost(() =>
{
var mapManager = IoCManager.Resolve<IMapManager>();
mapData.MapId = mapManager.CreateMap();
mapData.MapGrid = mapManager.CreateGrid(mapData.MapId);
mapData.GridCoords = new EntityCoordinates(mapData.MapGrid.GridEntityId, 0, 0);
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
var plating = tileDefinitionManager["plating"];
var platingTile = new Tile(plating.TileId);
mapData.MapGrid.SetTile(mapData.GridCoords, platingTile);
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
mapData.Tile = mapData.MapGrid.GetAllTiles().First();
});
if (!settings.Disconnected)
{
await RunTicksSync(pairTracker.Pair, 10);
}
return mapData;
}
public static async Task RunTicksSync(Pair pair, int ticks)
{
for (var i = 0; i < ticks; i++)
{
await pair.Server.WaitRunTicks(1);
await pair.Client.WaitRunTicks(1);
}
}
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);
}
public static async Task ReallyBeIdle(Pair pair, int runTicks = 25)
{
for (int i = 0; i < runTicks; i++)
{
await pair.Client.WaitRunTicks(1);
await pair.Server.WaitRunTicks(1);
for (int idleCycles = 0; idleCycles < 4; idleCycles++)
{
await pair.Client.WaitIdleAsync();
await pair.Server.WaitIdleAsync();
}
}
}
public static async Task WaitUntil(RobustIntegrationTest.IntegrationInstance instance, Func<Task<bool>> func,
int maxTicks = 600,
int tickStep = 1)
{
var ticksAwaited = 0;
bool passed;
await instance.WaitIdleAsync();
while (!(passed = await func()) && ticksAwaited < maxTicks)
{
var ticksToRun = tickStep;
if (ticksAwaited + tickStep > maxTicks)
{
ticksToRun = maxTicks - ticksAwaited;
}
await instance.WaitRunTicks(ticksToRun);
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);
}
}
public sealed class PoolSettings
{
// Todo: We can make more of these pool-able, if we need enough of them for it to matter
public bool MustNotBeReused => Destructive || NoLoadContent || DisableInterpolate || DummyTicker;
public bool MustBeNew => Fresh || NoLoadContent || DisableInterpolate || DummyTicker;
public bool NotConnected => NoClient || NoServer || Disconnected;
/// <summary>
/// We are going to ruin this pair
/// </summary>
public bool Destructive { get; init; }
/// <summary>
/// We need a brand new pair
/// </summary>
public bool Fresh { get; init; }
/// <summary>
/// We need a pair that uses a dummy ticker
/// </summary>
public bool DummyTicker { get; init; }
/// <summary>
/// We need the client, and server to be disconnected
/// </summary>
public bool Disconnected { get; init; }
/// <summary>
/// We need the server to be in the lobby
/// </summary>
public bool InLobby { get; init; }
/// <summary>
/// We don't want content loaded
/// </summary>
public bool NoLoadContent { get; init; }
/// <summary>
/// We want to add some prototypes
/// </summary>
public string ExtraPrototypes { get; init; }
/// <summary>
/// Disables NetInterp
/// </summary>
public bool DisableInterpolate { get; init; }
/// <summary>
/// Tells the pool it has to clean up before the server/client can be used.
/// </summary>
public bool Dirty { get; init; }
/// <summary>
/// Sets the map Cvar, and loads 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)
/// </summary>
public bool NoClient { get; init; }
/// <summary>
/// The test won't use the client (so we can skip cleaning it)
/// </summary>
public bool NoServer { get; init; }
/// <summary>
/// Guess if skipping recycling is ok
/// </summary>
/// <param name="nextSettings">The next set of settings the old pair will be set to</param>
/// <returns></returns>
public bool CanFastRecycle(PoolSettings nextSettings)
{
if (Dirty) return false;
if (Destructive || nextSettings.Destructive) return false;
if (NotConnected != nextSettings.NotConnected) return false;
if (InLobby != nextSettings.InLobby) return false;
if (DisableInterpolate != nextSettings.DisableInterpolate) return false;
if (nextSettings.DummyTicker) return false;
if (Map != nextSettings.Map) return false;
if (NoLoadContent != nextSettings.NoLoadContent) return false;
if (nextSettings.Fresh) return false;
if (ExtraPrototypes != nextSettings.ExtraPrototypes) return false;
return true;
}
}
public sealed class TestMapData
{
public MapId MapId { get; set; }
public IMapGrid MapGrid { get; set; }
public EntityCoordinates GridCoords { get; set; }
public MapCoordinates MapCoords { get; set; }
public TileRef Tile { get; set; }
}
public sealed class Pair
{
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 sealed class PairTracker : IAsyncDisposable
{
private int _disposed;
public async Task OnDirtyDispose()
{
var usageTime = UsageWatch.Elapsed;
await TestContext.Out.WriteLineAsync($"Dirty: Test returned in {usageTime.TotalMilliseconds} ms");
var dirtyWatch = new Stopwatch();
dirtyWatch.Start();
Pair.Client.Dispose();
Pair.Server.Dispose();
var disposeTime = dirtyWatch.Elapsed;
await TestContext.Out.WriteLineAsync($"Dirty: Disposed in {disposeTime.TotalMilliseconds} ms");
}
public async Task OnCleanDispose()
{
var usageTime = UsageWatch.Elapsed;
await TestContext.Out.WriteLineAsync($"Clean: Test returned in {usageTime.TotalMilliseconds} ms");
var cleanWatch = new Stopwatch();
cleanWatch.Start();
// Let any last minute failures the test cause happen.
await PoolManager.ReallyBeIdle(Pair);
if (!Pair.Settings.Destructive)
{
if (Pair.Client.IsAlive == false)
{
throw new Exception("Test killed the client", Pair.Client.UnhandledException);
}
if (Pair.Server.IsAlive == false)
{
throw new Exception("Test killed the server", Pair.Server.UnhandledException);
}
}
if (Pair.Settings.MustNotBeReused)
{
Pair.Client.Dispose();
Pair.Server.Dispose();
var returnTime2 = cleanWatch.Elapsed;
await TestContext.Out.WriteLineAsync($"Clean: Clean disposed in {returnTime2.TotalMilliseconds} ms");
return;
}
var sRuntimeLog = Pair.Server.ResolveDependency<IRuntimeLog>();
if (sRuntimeLog.ExceptionCount > 0) throw new Exception("Server logged exceptions");
var cRuntimeLog = Pair.Client.ResolveDependency<IRuntimeLog>();
if (cRuntimeLog.ExceptionCount > 0) throw new Exception("Client logged exceptions");
PoolManager.NoCheckReturn(Pair);
var returnTime = cleanWatch.Elapsed;
await TestContext.Out.WriteLineAsync($"Clean: Clean returned to pool in {returnTime.TotalMilliseconds} ms");
}
public Stopwatch UsageWatch { get; set; }
public Pair Pair { get; init; }
public async ValueTask CleanReturnAsync()
{
var disposed = Interlocked.Exchange(ref _disposed, 1);
switch (disposed)
{
case 0:
await TestContext.Out.WriteLineAsync("Clean Return Start");
break;
case 1:
throw new Exception("Already called clean return before");
case 2:
throw new Exception("Already dirty disposed");
default:
throw new Exception("Unexpected disposed value");
}
await OnCleanDispose();
await TestContext.Out.WriteLineAsync($"Clean Return Exiting");
}
public async ValueTask DisposeAsync()
{
var disposed = Interlocked.Exchange(ref _disposed, 2);
switch (disposed)
{
case 0:
await TestContext.Out.WriteLineAsync("Dirty Return Start");
break;
case 1:
await TestContext.Out.WriteLineAsync("Dirty Return - Already Clean Disposed");
return;
case 2:
throw new Exception("Already called dirty return before");
default:
throw new Exception("Unexpected disposed value");
}
await OnDirtyDispose();
await TestContext.Out.WriteLineAsync($"Dirty Return Exiting");
}
}