1
0

TestPair.Recycle.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #nullable enable
  2. using System.IO;
  3. using System.Linq;
  4. using Content.Server.GameTicking;
  5. using Content.Server.Preferences.Managers;
  6. using Content.Shared.CCVar;
  7. using Content.Shared.GameTicking;
  8. using Content.Shared.Mind;
  9. using Content.Shared.Mind.Components;
  10. using Content.Shared.Preferences;
  11. using Robust.Client;
  12. using Robust.Server.Player;
  13. using Robust.Shared.Exceptions;
  14. using Robust.Shared.GameObjects;
  15. using Robust.Shared.Network;
  16. namespace Content.IntegrationTests.Pair;
  17. // This partial class contains logic related to recycling & disposing test pairs.
  18. public sealed partial class TestPair : IAsyncDisposable
  19. {
  20. public PairState State { get; private set; } = PairState.Ready;
  21. private async Task OnDirtyDispose()
  22. {
  23. var usageTime = Watch.Elapsed;
  24. Watch.Restart();
  25. await _testOut.WriteLineAsync($"{nameof(DisposeAsync)}: Test gave back pair {Id} in {usageTime.TotalMilliseconds} ms");
  26. Kill();
  27. var disposeTime = Watch.Elapsed;
  28. await _testOut.WriteLineAsync($"{nameof(DisposeAsync)}: Disposed pair {Id} in {disposeTime.TotalMilliseconds} ms");
  29. // Test pairs should only dirty dispose if they are failing. If they are not failing, this probably happened
  30. // because someone forgot to clean-return the pair.
  31. Assert.Warn("Test was dirty-disposed.");
  32. }
  33. private async Task OnCleanDispose()
  34. {
  35. await Server.WaitIdleAsync();
  36. await Client.WaitIdleAsync();
  37. await ResetModifiedPreferences();
  38. await Server.RemoveAllDummySessions();
  39. if (TestMap != null)
  40. {
  41. await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid));
  42. TestMap = null;
  43. }
  44. await RevertModifiedCvars();
  45. var usageTime = Watch.Elapsed;
  46. Watch.Restart();
  47. await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Test borrowed pair {Id} for {usageTime.TotalMilliseconds} ms");
  48. // Let any last minute failures the test cause happen.
  49. await ReallyBeIdle();
  50. if (!Settings.Destructive)
  51. {
  52. if (Client.IsAlive == false)
  53. {
  54. throw new Exception($"{nameof(CleanReturnAsync)}: Test killed the client in pair {Id}:", Client.UnhandledException);
  55. }
  56. if (Server.IsAlive == false)
  57. {
  58. throw new Exception($"{nameof(CleanReturnAsync)}: Test killed the server in pair {Id}:", Server.UnhandledException);
  59. }
  60. }
  61. if (Settings.MustNotBeReused)
  62. {
  63. Kill();
  64. await ReallyBeIdle();
  65. await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Clean disposed in {Watch.Elapsed.TotalMilliseconds} ms");
  66. return;
  67. }
  68. var sRuntimeLog = Server.ResolveDependency<IRuntimeLog>();
  69. if (sRuntimeLog.ExceptionCount > 0)
  70. throw new Exception($"{nameof(CleanReturnAsync)}: Server logged exceptions");
  71. var cRuntimeLog = Client.ResolveDependency<IRuntimeLog>();
  72. if (cRuntimeLog.ExceptionCount > 0)
  73. throw new Exception($"{nameof(CleanReturnAsync)}: Client logged exceptions");
  74. var returnTime = Watch.Elapsed;
  75. await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool");
  76. }
  77. private async Task ResetModifiedPreferences()
  78. {
  79. var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
  80. foreach (var user in _modifiedProfiles)
  81. {
  82. await Server.WaitPost(() => prefMan.SetProfile(user, 0, new HumanoidCharacterProfile()).Wait());
  83. }
  84. _modifiedProfiles.Clear();
  85. }
  86. public async ValueTask CleanReturnAsync()
  87. {
  88. if (State != PairState.InUse)
  89. throw new Exception($"{nameof(CleanReturnAsync)}: Unexpected state. Pair: {Id}. State: {State}.");
  90. await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Return of pair {Id} started");
  91. State = PairState.CleanDisposed;
  92. await OnCleanDispose();
  93. State = PairState.Ready;
  94. PoolManager.NoCheckReturn(this);
  95. ClearContext();
  96. }
  97. public async ValueTask DisposeAsync()
  98. {
  99. switch (State)
  100. {
  101. case PairState.Dead:
  102. case PairState.Ready:
  103. break;
  104. case PairState.InUse:
  105. await _testOut.WriteLineAsync($"{nameof(DisposeAsync)}: Dirty return of pair {Id} started");
  106. await OnDirtyDispose();
  107. PoolManager.NoCheckReturn(this);
  108. ClearContext();
  109. break;
  110. default:
  111. throw new Exception($"{nameof(DisposeAsync)}: Unexpected state. Pair: {Id}. State: {State}.");
  112. }
  113. }
  114. public async Task CleanPooledPair(PoolSettings settings, TextWriter testOut)
  115. {
  116. Settings = default!;
  117. Watch.Restart();
  118. await testOut.WriteLineAsync($"Recycling...");
  119. var gameTicker = Server.System<GameTicker>();
  120. var cNetMgr = Client.ResolveDependency<IClientNetManager>();
  121. await RunTicksSync(1);
  122. // Disconnect the client if they are connected.
  123. if (cNetMgr.IsConnected)
  124. {
  125. await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Disconnecting client.");
  126. await Client.WaitPost(() => cNetMgr.ClientDisconnect("Test pooling cleanup disconnect"));
  127. await RunTicksSync(1);
  128. }
  129. Assert.That(cNetMgr.IsConnected, Is.False);
  130. // Move to pre-round lobby. Required to toggle dummy ticker on and off
  131. if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby)
  132. {
  133. await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting round.");
  134. Server.CfgMan.SetCVar(CCVars.GameDummyTicker, false);
  135. Assert.That(gameTicker.DummyTicker, Is.False);
  136. Server.CfgMan.SetCVar(CCVars.GameLobbyEnabled, true);
  137. await Server.WaitPost(() => gameTicker.RestartRound());
  138. await RunTicksSync(1);
  139. }
  140. //Apply Cvars
  141. await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Setting CVar ");
  142. await PoolManager.SetupCVars(Client, settings);
  143. await PoolManager.SetupCVars(Server, settings);
  144. await RunTicksSync(1);
  145. // Restart server.
  146. await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting server again");
  147. await Server.WaitPost(() => Server.EntMan.FlushEntities());
  148. await Server.WaitPost(() => gameTicker.RestartRound());
  149. await RunTicksSync(1);
  150. // Connect client
  151. if (settings.ShouldBeConnected)
  152. {
  153. await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Connecting client");
  154. Client.SetConnectTarget(Server);
  155. await Client.WaitPost(() => cNetMgr.ClientConnect(null!, 0, null!));
  156. }
  157. await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Idling");
  158. await ReallyBeIdle();
  159. await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Done recycling");
  160. }
  161. public void ValidateSettings(PoolSettings settings)
  162. {
  163. var cfg = Server.CfgMan;
  164. Assert.That(cfg.GetCVar(CCVars.AdminLogsEnabled), Is.EqualTo(settings.AdminLogsEnabled));
  165. Assert.That(cfg.GetCVar(CCVars.GameLobbyEnabled), Is.EqualTo(settings.InLobby));
  166. Assert.That(cfg.GetCVar(CCVars.GameDummyTicker), Is.EqualTo(settings.UseDummyTicker));
  167. var entMan = Server.ResolveDependency<EntityManager>();
  168. var ticker = entMan.System<GameTicker>();
  169. Assert.That(ticker.DummyTicker, Is.EqualTo(settings.UseDummyTicker));
  170. var expectPreRound = settings.InLobby | settings.DummyTicker;
  171. var expectedLevel = expectPreRound ? GameRunLevel.PreRoundLobby : GameRunLevel.InRound;
  172. Assert.That(ticker.RunLevel, Is.EqualTo(expectedLevel));
  173. var baseClient = Client.ResolveDependency<IBaseClient>();
  174. var netMan = Client.ResolveDependency<INetManager>();
  175. Assert.That(netMan.IsConnected, Is.Not.EqualTo(!settings.ShouldBeConnected));
  176. if (!settings.ShouldBeConnected)
  177. return;
  178. Assert.That(baseClient.RunLevel, Is.EqualTo(ClientRunLevel.InGame));
  179. var cPlayer = Client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
  180. var sPlayer = Server.ResolveDependency<IPlayerManager>();
  181. Assert.That(sPlayer.Sessions.Count(), Is.EqualTo(1));
  182. var session = sPlayer.Sessions.Single();
  183. Assert.That(cPlayer.LocalSession?.UserId, Is.EqualTo(session.UserId));
  184. if (ticker.DummyTicker)
  185. return;
  186. var status = ticker.PlayerGameStatuses[session.UserId];
  187. var expected = settings.InLobby
  188. ? PlayerGameStatus.NotReadyToPlay
  189. : PlayerGameStatus.JoinedGame;
  190. Assert.That(status, Is.EqualTo(expected));
  191. if (settings.InLobby)
  192. {
  193. Assert.That(session.AttachedEntity, Is.Null);
  194. return;
  195. }
  196. Assert.That(session.AttachedEntity, Is.Not.Null);
  197. Assert.That(entMan.EntityExists(session.AttachedEntity));
  198. Assert.That(entMan.HasComponent<MindContainerComponent>(session.AttachedEntity));
  199. var mindCont = entMan.GetComponent<MindContainerComponent>(session.AttachedEntity!.Value);
  200. Assert.That(mindCont.Mind, Is.Not.Null);
  201. Assert.That(entMan.TryGetComponent(mindCont.Mind, out MindComponent? mind));
  202. Assert.That(mind!.VisitingEntity, Is.Null);
  203. Assert.That(mind.OwnedEntity, Is.EqualTo(session.AttachedEntity!.Value));
  204. Assert.That(mind.UserId, Is.EqualTo(session.UserId));
  205. }
  206. }