| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- using System.Collections.Generic;
- using System.Linq;
- using System.Numerics;
- using Robust.Shared;
- using Robust.Shared.Audio.Components;
- using Robust.Shared.Configuration;
- using Robust.Shared.GameObjects;
- using Robust.Shared.Log;
- using Robust.Shared.Map;
- using Robust.Shared.Maths;
- using Robust.Shared.Prototypes;
- namespace Content.IntegrationTests.Tests
- {
- [TestFixture]
- [TestOf(typeof(EntityUid))]
- public sealed class EntityTest
- {
- private static readonly ProtoId<EntityCategoryPrototype> SpawnerCategory = "Spawner";
- [Test]
- public async Task SpawnAndDeleteAllEntitiesOnDifferentMaps()
- {
- // This test dirties the pair as it simply deletes ALL entities when done. Overhead of restarting the round
- // is minimal relative to the rest of the test.
- var settings = new PoolSettings { Dirty = true };
- await using var pair = await PoolManager.GetServerClient(settings);
- var server = pair.Server;
- var entityMan = server.ResolveDependency<IEntityManager>();
- var mapManager = server.ResolveDependency<IMapManager>();
- var prototypeMan = server.ResolveDependency<IPrototypeManager>();
- var mapSystem = entityMan.System<SharedMapSystem>();
- await server.WaitPost(() =>
- {
- var protoIds = prototypeMan
- .EnumeratePrototypes<EntityPrototype>()
- .Where(p => !p.Abstract)
- .Where(p => !pair.IsTestPrototype(p))
- .Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
- .Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
- .Select(p => p.ID)
- .ToList();
- foreach (var protoId in protoIds)
- {
- mapSystem.CreateMap(out var mapId);
- var grid = mapManager.CreateGridEntity(mapId);
- // TODO: Fix this better in engine.
- mapSystem.SetTile(grid.Owner, grid.Comp, Vector2i.Zero, new Tile(1));
- var coord = new EntityCoordinates(grid.Owner, 0, 0);
- entityMan.SpawnEntity(protoId, coord);
- }
- });
- await server.WaitRunTicks(15);
- await server.WaitPost(() =>
- {
- static IEnumerable<(EntityUid, TComp)> Query<TComp>(IEntityManager entityMan)
- where TComp : Component
- {
- var query = entityMan.AllEntityQueryEnumerator<TComp>();
- while (query.MoveNext(out var uid, out var meta))
- {
- yield return (uid, meta);
- }
- }
- var entityMetas = Query<MetaDataComponent>(entityMan).ToList();
- foreach (var (uid, meta) in entityMetas)
- {
- if (!meta.EntityDeleted)
- entityMan.DeleteEntity(uid);
- }
- Assert.That(entityMan.EntityCount, Is.Zero);
- });
- await pair.CleanReturnAsync();
- }
- [Test]
- public async Task SpawnAndDeleteAllEntitiesInTheSameSpot()
- {
- // This test dirties the pair as it simply deletes ALL entities when done. Overhead of restarting the round
- // is minimal relative to the rest of the test.
- var settings = new PoolSettings { Dirty = true };
- await using var pair = await PoolManager.GetServerClient(settings);
- var server = pair.Server;
- var map = await pair.CreateTestMap();
- var entityMan = server.ResolveDependency<IEntityManager>();
- var prototypeMan = server.ResolveDependency<IPrototypeManager>();
- await server.WaitPost(() =>
- {
- var protoIds = prototypeMan
- .EnumeratePrototypes<EntityPrototype>()
- .Where(p => !p.Abstract)
- .Where(p => !pair.IsTestPrototype(p))
- .Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
- .Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
- .Select(p => p.ID)
- .ToList();
- foreach (var protoId in protoIds)
- {
- entityMan.SpawnEntity(protoId, map.GridCoords);
- }
- });
- await server.WaitRunTicks(15);
- await server.WaitPost(() =>
- {
- static IEnumerable<(EntityUid, TComp)> Query<TComp>(IEntityManager entityMan)
- where TComp : Component
- {
- var query = entityMan.AllEntityQueryEnumerator<TComp>();
- while (query.MoveNext(out var uid, out var meta))
- {
- yield return (uid, meta);
- }
- }
- var entityMetas = Query<MetaDataComponent>(entityMan).ToList();
- foreach (var (uid, meta) in entityMetas)
- {
- if (!meta.EntityDeleted)
- entityMan.DeleteEntity(uid);
- }
- Assert.That(entityMan.EntityCount, Is.Zero);
- });
- await pair.CleanReturnAsync();
- }
- /// <summary>
- /// Variant of <see cref="SpawnAndDeleteAllEntitiesOnDifferentMaps"/> that also launches a client and dirties
- /// all components on every entity.
- /// </summary>
- [Test]
- public async Task SpawnAndDirtyAllEntities()
- {
- // This test dirties the pair as it simply deletes ALL entities when done. Overhead of restarting the round
- // is minimal relative to the rest of the test.
- var settings = new PoolSettings { Connected = true, Dirty = true };
- await using var pair = await PoolManager.GetServerClient(settings);
- var server = pair.Server;
- var client = pair.Client;
- var cfg = server.ResolveDependency<IConfigurationManager>();
- var prototypeMan = server.ResolveDependency<IPrototypeManager>();
- var mapManager = server.ResolveDependency<IMapManager>();
- var sEntMan = server.ResolveDependency<IEntityManager>();
- var mapSys = server.System<SharedMapSystem>();
- Assert.That(cfg.GetCVar(CVars.NetPVS), Is.False);
- var protoIds = prototypeMan
- .EnumeratePrototypes<EntityPrototype>()
- .Where(p => !p.Abstract)
- .Where(p => !pair.IsTestPrototype(p))
- .Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
- .Select(p => p.ID)
- .ToList();
- await server.WaitPost(() =>
- {
- foreach (var protoId in protoIds)
- {
- mapSys.CreateMap(out var mapId);
- var grid = mapManager.CreateGridEntity(mapId);
- var ent = sEntMan.SpawnEntity(protoId, new EntityCoordinates(grid.Owner, 0.5f, 0.5f));
- foreach (var (_, component) in sEntMan.GetNetComponents(ent))
- {
- sEntMan.Dirty(ent, component);
- }
- }
- });
- await pair.RunTicksSync(15);
- // Make sure the client actually received the entities
- // 500 is completely arbitrary. Note that the client & sever entity counts aren't expected to match.
- Assert.That(client.ResolveDependency<IEntityManager>().EntityCount, Is.GreaterThan(500));
- await server.WaitPost(() =>
- {
- static IEnumerable<(EntityUid, TComp)> Query<TComp>(IEntityManager entityMan)
- where TComp : Component
- {
- var query = entityMan.AllEntityQueryEnumerator<TComp>();
- while (query.MoveNext(out var uid, out var meta))
- {
- yield return (uid, meta);
- }
- }
- var entityMetas = Query<MetaDataComponent>(sEntMan).ToList();
- foreach (var (uid, meta) in entityMetas)
- {
- if (!meta.EntityDeleted)
- sEntMan.DeleteEntity(uid);
- }
- Assert.That(sEntMan.EntityCount, Is.Zero);
- });
- await pair.CleanReturnAsync();
- }
- /// <summary>
- /// This test checks that spawning and deleting an entity doesn't somehow create other unrelated entities.
- /// </summary>
- /// <remarks>
- /// Unless an entity is intentionally designed to spawn other entities (e.g., mob spawners), they should
- /// generally not spawn unrelated / detached entities. Any entities that do get spawned should be parented to
- /// the spawned entity (e.g., in a container). If an entity needs to spawn an entity somewhere in null-space,
- /// it should delete that entity when it is no longer required. This test mainly exists to prevent "entity leak"
- /// bugs, where spawning some entity starts spawning unrelated entities in null space that stick around after
- /// the original entity is gone.
- ///
- /// Note that this isn't really a strict requirement, and there are probably quite a few edge cases. Its a pretty
- /// crude test to try catch issues like this, and possibly should just be disabled.
- /// </remarks>
- [Test]
- public async Task SpawnAndDeleteEntityCountTest()
- {
- var settings = new PoolSettings { Connected = true, Dirty = true };
- await using var pair = await PoolManager.GetServerClient(settings);
- var mapSys = pair.Server.System<SharedMapSystem>();
- var server = pair.Server;
- var client = pair.Client;
- var excluded = new[]
- {
- "MapGrid",
- "StationEvent",
- "TimedDespawn",
- // makes an announcement on mapInit.
- "AnnounceOnSpawn",
- };
- Assert.That(server.CfgMan.GetCVar(CVars.NetPVS), Is.False);
- var protoIds = server.ProtoMan
- .EnumeratePrototypes<EntityPrototype>()
- .Where(p => !p.Abstract)
- .Where(p => !pair.IsTestPrototype(p))
- .Where(p => !excluded.Any(p.Components.ContainsKey))
- .Where(p => p.Categories.All(x => x.ID != SpawnerCategory))
- .Select(p => p.ID)
- .ToList();
- protoIds.Sort();
- var mapId = MapId.Nullspace;
- await server.WaitPost(() =>
- {
- mapSys.CreateMap(out mapId);
- });
- var coords = new MapCoordinates(Vector2.Zero, mapId);
- await pair.RunTicksSync(3);
- // We consider only non-audio entities, as some entities will just play sounds when they spawn.
- int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
- foreach (var protoId in protoIds)
- {
- // TODO fix ninja
- // Currently ninja fails to equip their own loadout.
- if (protoId == "MobHumanSpaceNinja")
- continue;
- var count = Count(server.EntMan);
- var clientCount = Count(client.EntMan);
- EntityUid uid = default;
- await server.WaitPost(() => uid = server.EntMan.SpawnEntity(protoId, coords));
- await pair.RunTicksSync(3);
- // If the entity deleted itself, check that it didn't spawn other entities
- if (!server.EntMan.EntityExists(uid))
- {
- if (Count(server.EntMan) != count)
- {
- Assert.Fail($"Server prototype {protoId} failed on deleting itself");
- }
- if (Count(client.EntMan) != clientCount)
- {
- Assert.Fail($"Client prototype {protoId} failed on deleting itself\n" +
- $"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
- $"Server was {count}.");
- }
- continue;
- }
- // Check that the number of entities has increased.
- if (Count(server.EntMan) <= count)
- {
- Assert.Fail($"Server prototype {protoId} failed on spawning as entity count didn't increase");
- }
- if (Count(client.EntMan) <= clientCount)
- {
- Assert.Fail($"Client prototype {protoId} failed on spawning as entity count didn't increase" +
- $"Expected at least {clientCount} and found {Count(client.EntMan)}. " +
- $"Server was {count}");
- }
- await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
- await pair.RunTicksSync(3);
- // Check that the number of entities has gone back to the original value.
- if (Count(server.EntMan) != count)
- {
- Assert.Fail($"Server prototype {protoId} failed on deletion count didn't reset properly");
- }
- if (Count(client.EntMan) != clientCount)
- {
- Assert.Fail($"Client prototype {protoId} failed on deletion count didn't reset properly:\n" +
- $"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
- $"Server was {count}.");
- }
- }
- await pair.CleanReturnAsync();
- }
- [Test]
- public async Task AllComponentsOneToOneDeleteTest()
- {
- var skipComponents = new[]
- {
- "DebugExceptionOnAdd", // Debug components that explicitly throw exceptions
- "DebugExceptionExposeData",
- "DebugExceptionInitialize",
- "DebugExceptionStartup",
- "GridFill",
- "RoomFill",
- "Map", // We aren't testing a map entity in this test
- "MapGrid",
- "Broadphase",
- "StationData", // errors when removed mid-round
- "StationJobs",
- "Actor", // We aren't testing actor components, those need their player session set.
- "BlobFloorPlanBuilder", // Implodes if unconfigured.
- "DebrisFeaturePlacerController", // Above.
- "LoadedChunk", // Worldgen chunk loading malding.
- "BiomeSelection", // Whaddya know, requires config.
- "ActivatableUI", // Requires enum key
- };
- // TODO TESTS
- // auto ignore any components that have a "required" data field.
- await using var pair = await PoolManager.GetServerClient();
- var server = pair.Server;
- var entityManager = server.ResolveDependency<IEntityManager>();
- var componentFactory = server.ResolveDependency<IComponentFactory>();
- var logmill = server.ResolveDependency<ILogManager>().GetSawmill("EntityTest");
- await pair.CreateTestMap();
- await server.WaitRunTicks(5);
- var testLocation = pair.TestMap.GridCoords;
- await server.WaitAssertion(() =>
- {
- Assert.Multiple(() =>
- {
- foreach (var type in componentFactory.AllRegisteredTypes)
- {
- var component = (Component) componentFactory.GetComponent(type);
- var name = componentFactory.GetComponentName(type);
- // If this component is ignored
- if (skipComponents.Contains(name))
- {
- continue;
- }
- var entity = entityManager.SpawnEntity(null, testLocation);
- Assert.That(entityManager.GetComponent<MetaDataComponent>(entity).EntityInitialized);
- // The component may already exist if it is a mandatory component
- // such as MetaData or Transform
- if (entityManager.HasComponent(entity, type))
- {
- entityManager.DeleteEntity(entity);
- continue;
- }
- logmill.Debug($"Adding component: {name}");
- Assert.DoesNotThrow(() =>
- {
- entityManager.AddComponent(entity, component);
- }, "Component '{0}' threw an exception.",
- name);
- entityManager.DeleteEntity(entity);
- }
- });
- });
- await pair.CleanReturnAsync();
- }
- }
- }
|