1
0

EntityTest.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Numerics;
  4. using Robust.Shared;
  5. using Robust.Shared.Audio.Components;
  6. using Robust.Shared.Configuration;
  7. using Robust.Shared.GameObjects;
  8. using Robust.Shared.Log;
  9. using Robust.Shared.Map;
  10. using Robust.Shared.Maths;
  11. using Robust.Shared.Prototypes;
  12. namespace Content.IntegrationTests.Tests
  13. {
  14. [TestFixture]
  15. [TestOf(typeof(EntityUid))]
  16. public sealed class EntityTest
  17. {
  18. private static readonly ProtoId<EntityCategoryPrototype> SpawnerCategory = "Spawner";
  19. [Test]
  20. public async Task SpawnAndDeleteAllEntitiesOnDifferentMaps()
  21. {
  22. // This test dirties the pair as it simply deletes ALL entities when done. Overhead of restarting the round
  23. // is minimal relative to the rest of the test.
  24. var settings = new PoolSettings { Dirty = true };
  25. await using var pair = await PoolManager.GetServerClient(settings);
  26. var server = pair.Server;
  27. var entityMan = server.ResolveDependency<IEntityManager>();
  28. var mapManager = server.ResolveDependency<IMapManager>();
  29. var prototypeMan = server.ResolveDependency<IPrototypeManager>();
  30. var mapSystem = entityMan.System<SharedMapSystem>();
  31. await server.WaitPost(() =>
  32. {
  33. var protoIds = prototypeMan
  34. .EnumeratePrototypes<EntityPrototype>()
  35. .Where(p => !p.Abstract)
  36. .Where(p => !pair.IsTestPrototype(p))
  37. .Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
  38. .Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
  39. .Select(p => p.ID)
  40. .ToList();
  41. foreach (var protoId in protoIds)
  42. {
  43. mapSystem.CreateMap(out var mapId);
  44. var grid = mapManager.CreateGridEntity(mapId);
  45. // TODO: Fix this better in engine.
  46. mapSystem.SetTile(grid.Owner, grid.Comp, Vector2i.Zero, new Tile(1));
  47. var coord = new EntityCoordinates(grid.Owner, 0, 0);
  48. entityMan.SpawnEntity(protoId, coord);
  49. }
  50. });
  51. await server.WaitRunTicks(15);
  52. await server.WaitPost(() =>
  53. {
  54. static IEnumerable<(EntityUid, TComp)> Query<TComp>(IEntityManager entityMan)
  55. where TComp : Component
  56. {
  57. var query = entityMan.AllEntityQueryEnumerator<TComp>();
  58. while (query.MoveNext(out var uid, out var meta))
  59. {
  60. yield return (uid, meta);
  61. }
  62. }
  63. var entityMetas = Query<MetaDataComponent>(entityMan).ToList();
  64. foreach (var (uid, meta) in entityMetas)
  65. {
  66. if (!meta.EntityDeleted)
  67. entityMan.DeleteEntity(uid);
  68. }
  69. Assert.That(entityMan.EntityCount, Is.Zero);
  70. });
  71. await pair.CleanReturnAsync();
  72. }
  73. [Test]
  74. public async Task SpawnAndDeleteAllEntitiesInTheSameSpot()
  75. {
  76. // This test dirties the pair as it simply deletes ALL entities when done. Overhead of restarting the round
  77. // is minimal relative to the rest of the test.
  78. var settings = new PoolSettings { Dirty = true };
  79. await using var pair = await PoolManager.GetServerClient(settings);
  80. var server = pair.Server;
  81. var map = await pair.CreateTestMap();
  82. var entityMan = server.ResolveDependency<IEntityManager>();
  83. var prototypeMan = server.ResolveDependency<IPrototypeManager>();
  84. await server.WaitPost(() =>
  85. {
  86. var protoIds = prototypeMan
  87. .EnumeratePrototypes<EntityPrototype>()
  88. .Where(p => !p.Abstract)
  89. .Where(p => !pair.IsTestPrototype(p))
  90. .Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
  91. .Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
  92. .Select(p => p.ID)
  93. .ToList();
  94. foreach (var protoId in protoIds)
  95. {
  96. entityMan.SpawnEntity(protoId, map.GridCoords);
  97. }
  98. });
  99. await server.WaitRunTicks(15);
  100. await server.WaitPost(() =>
  101. {
  102. static IEnumerable<(EntityUid, TComp)> Query<TComp>(IEntityManager entityMan)
  103. where TComp : Component
  104. {
  105. var query = entityMan.AllEntityQueryEnumerator<TComp>();
  106. while (query.MoveNext(out var uid, out var meta))
  107. {
  108. yield return (uid, meta);
  109. }
  110. }
  111. var entityMetas = Query<MetaDataComponent>(entityMan).ToList();
  112. foreach (var (uid, meta) in entityMetas)
  113. {
  114. if (!meta.EntityDeleted)
  115. entityMan.DeleteEntity(uid);
  116. }
  117. Assert.That(entityMan.EntityCount, Is.Zero);
  118. });
  119. await pair.CleanReturnAsync();
  120. }
  121. /// <summary>
  122. /// Variant of <see cref="SpawnAndDeleteAllEntitiesOnDifferentMaps"/> that also launches a client and dirties
  123. /// all components on every entity.
  124. /// </summary>
  125. [Test]
  126. public async Task SpawnAndDirtyAllEntities()
  127. {
  128. // This test dirties the pair as it simply deletes ALL entities when done. Overhead of restarting the round
  129. // is minimal relative to the rest of the test.
  130. var settings = new PoolSettings { Connected = true, Dirty = true };
  131. await using var pair = await PoolManager.GetServerClient(settings);
  132. var server = pair.Server;
  133. var client = pair.Client;
  134. var cfg = server.ResolveDependency<IConfigurationManager>();
  135. var prototypeMan = server.ResolveDependency<IPrototypeManager>();
  136. var mapManager = server.ResolveDependency<IMapManager>();
  137. var sEntMan = server.ResolveDependency<IEntityManager>();
  138. var mapSys = server.System<SharedMapSystem>();
  139. Assert.That(cfg.GetCVar(CVars.NetPVS), Is.False);
  140. var protoIds = prototypeMan
  141. .EnumeratePrototypes<EntityPrototype>()
  142. .Where(p => !p.Abstract)
  143. .Where(p => !pair.IsTestPrototype(p))
  144. .Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
  145. .Select(p => p.ID)
  146. .ToList();
  147. await server.WaitPost(() =>
  148. {
  149. foreach (var protoId in protoIds)
  150. {
  151. mapSys.CreateMap(out var mapId);
  152. var grid = mapManager.CreateGridEntity(mapId);
  153. var ent = sEntMan.SpawnEntity(protoId, new EntityCoordinates(grid.Owner, 0.5f, 0.5f));
  154. foreach (var (_, component) in sEntMan.GetNetComponents(ent))
  155. {
  156. sEntMan.Dirty(ent, component);
  157. }
  158. }
  159. });
  160. await pair.RunTicksSync(15);
  161. // Make sure the client actually received the entities
  162. // 500 is completely arbitrary. Note that the client & sever entity counts aren't expected to match.
  163. Assert.That(client.ResolveDependency<IEntityManager>().EntityCount, Is.GreaterThan(500));
  164. await server.WaitPost(() =>
  165. {
  166. static IEnumerable<(EntityUid, TComp)> Query<TComp>(IEntityManager entityMan)
  167. where TComp : Component
  168. {
  169. var query = entityMan.AllEntityQueryEnumerator<TComp>();
  170. while (query.MoveNext(out var uid, out var meta))
  171. {
  172. yield return (uid, meta);
  173. }
  174. }
  175. var entityMetas = Query<MetaDataComponent>(sEntMan).ToList();
  176. foreach (var (uid, meta) in entityMetas)
  177. {
  178. if (!meta.EntityDeleted)
  179. sEntMan.DeleteEntity(uid);
  180. }
  181. Assert.That(sEntMan.EntityCount, Is.Zero);
  182. });
  183. await pair.CleanReturnAsync();
  184. }
  185. /// <summary>
  186. /// This test checks that spawning and deleting an entity doesn't somehow create other unrelated entities.
  187. /// </summary>
  188. /// <remarks>
  189. /// Unless an entity is intentionally designed to spawn other entities (e.g., mob spawners), they should
  190. /// generally not spawn unrelated / detached entities. Any entities that do get spawned should be parented to
  191. /// the spawned entity (e.g., in a container). If an entity needs to spawn an entity somewhere in null-space,
  192. /// it should delete that entity when it is no longer required. This test mainly exists to prevent "entity leak"
  193. /// bugs, where spawning some entity starts spawning unrelated entities in null space that stick around after
  194. /// the original entity is gone.
  195. ///
  196. /// Note that this isn't really a strict requirement, and there are probably quite a few edge cases. Its a pretty
  197. /// crude test to try catch issues like this, and possibly should just be disabled.
  198. /// </remarks>
  199. [Test]
  200. public async Task SpawnAndDeleteEntityCountTest()
  201. {
  202. var settings = new PoolSettings { Connected = true, Dirty = true };
  203. await using var pair = await PoolManager.GetServerClient(settings);
  204. var mapSys = pair.Server.System<SharedMapSystem>();
  205. var server = pair.Server;
  206. var client = pair.Client;
  207. var excluded = new[]
  208. {
  209. "MapGrid",
  210. "StationEvent",
  211. "TimedDespawn",
  212. // makes an announcement on mapInit.
  213. "AnnounceOnSpawn",
  214. };
  215. Assert.That(server.CfgMan.GetCVar(CVars.NetPVS), Is.False);
  216. var protoIds = server.ProtoMan
  217. .EnumeratePrototypes<EntityPrototype>()
  218. .Where(p => !p.Abstract)
  219. .Where(p => !pair.IsTestPrototype(p))
  220. .Where(p => !excluded.Any(p.Components.ContainsKey))
  221. .Where(p => p.Categories.All(x => x.ID != SpawnerCategory))
  222. .Select(p => p.ID)
  223. .ToList();
  224. protoIds.Sort();
  225. var mapId = MapId.Nullspace;
  226. await server.WaitPost(() =>
  227. {
  228. mapSys.CreateMap(out mapId);
  229. });
  230. var coords = new MapCoordinates(Vector2.Zero, mapId);
  231. await pair.RunTicksSync(3);
  232. // We consider only non-audio entities, as some entities will just play sounds when they spawn.
  233. int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
  234. foreach (var protoId in protoIds)
  235. {
  236. // TODO fix ninja
  237. // Currently ninja fails to equip their own loadout.
  238. if (protoId == "MobHumanSpaceNinja")
  239. continue;
  240. var count = Count(server.EntMan);
  241. var clientCount = Count(client.EntMan);
  242. EntityUid uid = default;
  243. await server.WaitPost(() => uid = server.EntMan.SpawnEntity(protoId, coords));
  244. await pair.RunTicksSync(3);
  245. // If the entity deleted itself, check that it didn't spawn other entities
  246. if (!server.EntMan.EntityExists(uid))
  247. {
  248. if (Count(server.EntMan) != count)
  249. {
  250. Assert.Fail($"Server prototype {protoId} failed on deleting itself");
  251. }
  252. if (Count(client.EntMan) != clientCount)
  253. {
  254. Assert.Fail($"Client prototype {protoId} failed on deleting itself\n" +
  255. $"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
  256. $"Server was {count}.");
  257. }
  258. continue;
  259. }
  260. // Check that the number of entities has increased.
  261. if (Count(server.EntMan) <= count)
  262. {
  263. Assert.Fail($"Server prototype {protoId} failed on spawning as entity count didn't increase");
  264. }
  265. if (Count(client.EntMan) <= clientCount)
  266. {
  267. Assert.Fail($"Client prototype {protoId} failed on spawning as entity count didn't increase" +
  268. $"Expected at least {clientCount} and found {Count(client.EntMan)}. " +
  269. $"Server was {count}");
  270. }
  271. await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
  272. await pair.RunTicksSync(3);
  273. // Check that the number of entities has gone back to the original value.
  274. if (Count(server.EntMan) != count)
  275. {
  276. Assert.Fail($"Server prototype {protoId} failed on deletion count didn't reset properly");
  277. }
  278. if (Count(client.EntMan) != clientCount)
  279. {
  280. Assert.Fail($"Client prototype {protoId} failed on deletion count didn't reset properly:\n" +
  281. $"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
  282. $"Server was {count}.");
  283. }
  284. }
  285. await pair.CleanReturnAsync();
  286. }
  287. [Test]
  288. public async Task AllComponentsOneToOneDeleteTest()
  289. {
  290. var skipComponents = new[]
  291. {
  292. "DebugExceptionOnAdd", // Debug components that explicitly throw exceptions
  293. "DebugExceptionExposeData",
  294. "DebugExceptionInitialize",
  295. "DebugExceptionStartup",
  296. "GridFill",
  297. "RoomFill",
  298. "Map", // We aren't testing a map entity in this test
  299. "MapGrid",
  300. "Broadphase",
  301. "StationData", // errors when removed mid-round
  302. "StationJobs",
  303. "Actor", // We aren't testing actor components, those need their player session set.
  304. "BlobFloorPlanBuilder", // Implodes if unconfigured.
  305. "DebrisFeaturePlacerController", // Above.
  306. "LoadedChunk", // Worldgen chunk loading malding.
  307. "BiomeSelection", // Whaddya know, requires config.
  308. "ActivatableUI", // Requires enum key
  309. };
  310. // TODO TESTS
  311. // auto ignore any components that have a "required" data field.
  312. await using var pair = await PoolManager.GetServerClient();
  313. var server = pair.Server;
  314. var entityManager = server.ResolveDependency<IEntityManager>();
  315. var componentFactory = server.ResolveDependency<IComponentFactory>();
  316. var logmill = server.ResolveDependency<ILogManager>().GetSawmill("EntityTest");
  317. await pair.CreateTestMap();
  318. await server.WaitRunTicks(5);
  319. var testLocation = pair.TestMap.GridCoords;
  320. await server.WaitAssertion(() =>
  321. {
  322. Assert.Multiple(() =>
  323. {
  324. foreach (var type in componentFactory.AllRegisteredTypes)
  325. {
  326. var component = (Component) componentFactory.GetComponent(type);
  327. var name = componentFactory.GetComponentName(type);
  328. // If this component is ignored
  329. if (skipComponents.Contains(name))
  330. {
  331. continue;
  332. }
  333. var entity = entityManager.SpawnEntity(null, testLocation);
  334. Assert.That(entityManager.GetComponent<MetaDataComponent>(entity).EntityInitialized);
  335. // The component may already exist if it is a mandatory component
  336. // such as MetaData or Transform
  337. if (entityManager.HasComponent(entity, type))
  338. {
  339. entityManager.DeleteEntity(entity);
  340. continue;
  341. }
  342. logmill.Debug($"Adding component: {name}");
  343. Assert.DoesNotThrow(() =>
  344. {
  345. entityManager.AddComponent(entity, component);
  346. }, "Component '{0}' threw an exception.",
  347. name);
  348. entityManager.DeleteEntity(entity);
  349. }
  350. });
  351. });
  352. await pair.CleanReturnAsync();
  353. }
  354. }
  355. }