mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Robust.UnitTesting was both ALL tests for RT, and also API surface for content tests. Tests are now split into separate projects as appropriate, and the API side has also been split off.
375 lines
17 KiB
C#
375 lines
17 KiB
C#
using System.Threading.Tasks;
|
|
using NUnit.Framework;
|
|
using Robust.Shared.EntitySerialization;
|
|
using Robust.Shared.EntitySerialization.Systems;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Utility;
|
|
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
|
|
|
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
|
|
|
[TestFixture]
|
|
internal sealed partial class LifestageSerializationTest : RobustIntegrationTest
|
|
{
|
|
/// <summary>
|
|
/// Check that whether or not an entity has been paused or map-initialized is preserved across saves & loads.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestLifestageSerialization()
|
|
{
|
|
var server = StartServer();
|
|
await server.WaitIdleAsync();
|
|
var entMan = server.EntMan;
|
|
var mapSys = server.System<SharedMapSystem>();
|
|
var loader = server.System<MapLoaderSystem>();
|
|
var preInitPath = new ResPath($"{nameof(TestLifestageSerialization)}_preInit.yml");
|
|
var postInitPath = new ResPath($"{nameof(TestLifestageSerialization)}_postInit.yml");
|
|
var pausedPostInitPath = new ResPath($"{nameof(TestLifestageSerialization)}_paused.yml");
|
|
|
|
// Create a pre-init map, and spawn multiple entities on it
|
|
Entity<TransformComponent, EntitySaveTestComponent> map = default;
|
|
Entity<TransformComponent, EntitySaveTestComponent> entA = default;
|
|
Entity<TransformComponent, EntitySaveTestComponent> entB = default;
|
|
Entity<TransformComponent, EntitySaveTestComponent> childA = default;
|
|
Entity<TransformComponent, EntitySaveTestComponent> childB = default;
|
|
|
|
await server.WaitPost(() =>
|
|
{
|
|
var mapUid = mapSys.CreateMap(out var mapId, runMapInit: false);
|
|
var entAUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
|
var entBUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
|
var childAUid = entMan.SpawnEntity(null, new EntityCoordinates(entAUid, 0, 0));
|
|
var childBUid = entMan.SpawnEntity(null, new EntityCoordinates(entBUid, 0, 0));
|
|
map = Get(mapUid, entMan);
|
|
entA = Get(entAUid, entMan);
|
|
entB = Get(entBUid, entMan);
|
|
childA = Get(childAUid, entMan);
|
|
childB = Get(childBUid, entMan);
|
|
map.Comp2.Id = nameof(map);
|
|
entA.Comp2.Id = nameof(entA);
|
|
entB.Comp2.Id = nameof(entB);
|
|
childA.Comp2.Id = nameof(childA);
|
|
childB.Comp2.Id = nameof(childB);
|
|
});
|
|
|
|
void AssertPaused(bool expected, params EntityUid[] uids)
|
|
{
|
|
foreach (var uid in uids)
|
|
{
|
|
Assert.That(entMan.GetComponent<MetaDataComponent>(uid).EntityPaused, Is.EqualTo(expected));
|
|
}
|
|
}
|
|
|
|
void AssertPreInit(bool expected, params EntityUid[] uids)
|
|
{
|
|
foreach (var uid in uids)
|
|
{
|
|
Assert.That(entMan!.GetComponent<MetaDataComponent>(uid).EntityLifeStage,
|
|
expected
|
|
? Is.LessThan(EntityLifeStage.MapInitialized)
|
|
: Is.EqualTo(EntityLifeStage.MapInitialized));
|
|
}
|
|
}
|
|
|
|
// All entities should initially be un-initialized and paused.
|
|
AssertPaused(true, map, entA, entB, childA, childB);
|
|
AssertPreInit(true, map, entA, entB, childA, childB);
|
|
Assert.That(loader.TrySaveMap(map, preInitPath));
|
|
|
|
async Task Delete()
|
|
{
|
|
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(5));
|
|
await server.WaitPost(() => entMan.DeleteEntity(map));
|
|
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
|
}
|
|
|
|
async Task Load(ResPath f, DeserializationOptions? o)
|
|
{
|
|
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
|
await server.WaitAssertion(() => Assert.That(loader.TryLoadMap(f, out _, out _, o)));
|
|
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(5));
|
|
}
|
|
|
|
void FindAll()
|
|
{
|
|
map = Find(nameof(map), entMan);
|
|
entA = Find(nameof(entA), entMan);
|
|
entB = Find(nameof(entB), entMan);
|
|
childA = Find(nameof(childA), entMan);
|
|
childB = Find(nameof(childB), entMan);
|
|
}
|
|
|
|
async Task Reload(ResPath f, DeserializationOptions? o = null)
|
|
{
|
|
await Delete();
|
|
await Load(f, o);
|
|
FindAll();
|
|
}
|
|
|
|
// Saving and loading the pre-init map should have no effect.
|
|
await Reload(preInitPath);
|
|
AssertPaused(true, map, entA, entB, childA, childB);
|
|
AssertPreInit(true, map, entA, entB, childA, childB);
|
|
|
|
// Saving and loading with the map-init option set to true should initialize & unpause all entities
|
|
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
|
await Reload(preInitPath, opts);
|
|
AssertPaused(false, map, entA, entB, childA, childB);
|
|
AssertPreInit(false, map, entA, entB, childA, childB);
|
|
Assert.That(loader.TrySaveMap(map, postInitPath));
|
|
|
|
// re-loading the post-init map should keep everything initialized, even without explicitly asking to initialize maps.
|
|
await Reload(postInitPath);
|
|
AssertPaused(false, map, entA, entB, childA, childB);
|
|
AssertPreInit(false, map, entA, entB, childA, childB);
|
|
|
|
// Load & initialize a pre-init map, but with the pause maps option enabled.
|
|
opts = DeserializationOptions.Default with {InitializeMaps = true, PauseMaps = true};
|
|
await Reload(preInitPath, opts);
|
|
AssertPaused(true, map, entA, entB, childA, childB);
|
|
AssertPreInit(false, map, entA, entB, childA, childB);
|
|
Assert.That(loader.TrySaveMap(map, pausedPostInitPath));
|
|
|
|
// The pause map option also works when loading un-paused post-init maps
|
|
opts = DeserializationOptions.Default with {PauseMaps = true};
|
|
await Reload(postInitPath, opts);
|
|
AssertPaused(true, map, entA, entB, childA, childB);
|
|
AssertPreInit(false, map, entA, entB, childA, childB);
|
|
|
|
// loading & initializing a post-init map should cause no issues.
|
|
opts = DeserializationOptions.Default with {InitializeMaps = true};
|
|
await Reload(postInitPath, opts);
|
|
AssertPaused(false, map, entA, entB, childA, childB);
|
|
AssertPreInit(false, map, entA, entB, childA, childB);
|
|
|
|
// Loading a paused post init map does NOT automatically un-pause entities
|
|
await Reload(pausedPostInitPath);
|
|
AssertPaused(true, map, entA, entB, childA, childB);
|
|
AssertPreInit(false, map, entA, entB, childA, childB);
|
|
|
|
// The above holds even if we are explicitly initialising maps.
|
|
opts = DeserializationOptions.Default with {InitializeMaps = true};
|
|
await Reload(pausedPostInitPath, opts);
|
|
AssertPaused(true, map, entA, entB, childA, childB);
|
|
AssertPreInit(false, map, entA, entB, childA, childB);
|
|
|
|
// And re-paused an already paused map should have no impact.
|
|
opts = DeserializationOptions.Default with {InitializeMaps = true, PauseMaps = true};
|
|
await Reload(pausedPostInitPath, opts);
|
|
AssertPaused(true, map, entA, entB, childA, childB);
|
|
AssertPreInit(false, map, entA, entB, childA, childB);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Variant of <see cref="TestLifestageSerialization"/> that has multiple maps and combinations. E.g., a single
|
|
/// paused entity on an un-paused map.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestMixedLifestageSerialization()
|
|
{
|
|
var server = StartServer();
|
|
await server.WaitIdleAsync();
|
|
var entMan = server.EntMan;
|
|
var meta = server.System<MetaDataSystem>();
|
|
var mapSys = server.System<SharedMapSystem>();
|
|
var loader = server.System<MapLoaderSystem>();
|
|
var path = new ResPath($"{nameof(TestMixedLifestageSerialization)}.yml");
|
|
var altPath = new ResPath($"{nameof(TestMixedLifestageSerialization)}_alt.yml");
|
|
|
|
Entity<TransformComponent, EntitySaveTestComponent> mapA = default; // preinit Map
|
|
Entity<TransformComponent, EntitySaveTestComponent> mapB = default; // postinit unpaused Map
|
|
Entity<TransformComponent, EntitySaveTestComponent> entA = default; // postinit entity on preinit map
|
|
Entity<TransformComponent, EntitySaveTestComponent> entB = default; // paused entity on postinit unpaused map
|
|
Entity<TransformComponent, EntitySaveTestComponent> entC = default; // preinit entity on postinit map
|
|
Entity<TransformComponent, EntitySaveTestComponent> nullA = default; // postinit nullspace entity
|
|
Entity<TransformComponent, EntitySaveTestComponent> nullB = default; // preinit nullspace entity
|
|
Entity<TransformComponent, EntitySaveTestComponent> nullC = default; // paused postinit nullspace entity
|
|
|
|
await server.WaitPost(() =>
|
|
{
|
|
var mapAUid = mapSys.CreateMap(out var mapIdA, runMapInit: false);
|
|
var mapBUid = mapSys.CreateMap(out var mapIdB, runMapInit: true);
|
|
|
|
var entAUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapIdA));
|
|
entMan.RunMapInit(entAUid, entMan.GetComponent<MetaDataComponent>(entAUid));
|
|
meta.SetEntityPaused(entAUid, false);
|
|
|
|
var entBUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapIdB));
|
|
meta.SetEntityPaused(entBUid, true);
|
|
|
|
var entCUid = entMan.CreateEntityUninitialized(null, new MapCoordinates(0, 0, mapIdB));
|
|
entMan.InitializeAndStartEntity(entCUid, doMapInit: false);
|
|
|
|
var nullAUid = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
|
|
|
var nullBUid = entMan.CreateEntityUninitialized(null, MapCoordinates.Nullspace);
|
|
entMan.InitializeAndStartEntity(nullBUid, doMapInit: false);
|
|
|
|
var nullCUid = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
|
meta.SetEntityPaused(nullCUid, true);
|
|
|
|
mapA = Get(mapAUid, entMan);
|
|
mapB = Get(mapBUid, entMan);
|
|
entA = Get(entAUid, entMan);
|
|
entB = Get(entBUid, entMan);
|
|
entC = Get(entCUid, entMan);
|
|
nullA = Get(nullAUid, entMan);
|
|
nullB = Get(nullBUid, entMan);
|
|
nullC = Get(nullCUid, entMan);
|
|
|
|
mapA.Comp2.Id = nameof(mapA);
|
|
mapB.Comp2.Id = nameof(mapB);
|
|
entA.Comp2.Id = nameof(entA);
|
|
entB.Comp2.Id = nameof(entB);
|
|
entC.Comp2.Id = nameof(entC);
|
|
nullA.Comp2.Id = nameof(nullA);
|
|
nullB.Comp2.Id = nameof(nullB);
|
|
nullC.Comp2.Id = nameof(nullC);
|
|
});
|
|
|
|
string? Name(EntityUid uid)
|
|
{
|
|
return entMan.GetComponentOrNull<EntitySaveTestComponent>(uid)?.Id;
|
|
}
|
|
|
|
void AssertPaused(bool expected, params EntityUid[] uids)
|
|
{
|
|
foreach (var uid in uids)
|
|
{
|
|
Assert.That(entMan.GetComponent<MetaDataComponent>(uid).EntityPaused, Is.EqualTo(expected), Name(uid));
|
|
}
|
|
}
|
|
|
|
void AssertPreInit(bool expected, params EntityUid[] uids)
|
|
{
|
|
foreach (var uid in uids)
|
|
{
|
|
Assert.That(entMan!.GetComponent<MetaDataComponent>(uid).EntityLifeStage,
|
|
expected
|
|
? Is.LessThan(EntityLifeStage.MapInitialized)
|
|
: Is.EqualTo(EntityLifeStage.MapInitialized));
|
|
}
|
|
}
|
|
|
|
void Save(ResPath f)
|
|
{
|
|
Assert.That(loader.TrySaveGeneric([mapA, mapB, nullA, nullB, nullC], f, out _));
|
|
}
|
|
|
|
async Task Delete()
|
|
{
|
|
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(8));
|
|
await server.WaitPost(() => entMan.DeleteEntity(mapA));
|
|
await server.WaitPost(() => entMan.DeleteEntity(mapB));
|
|
await server.WaitPost(() => entMan.DeleteEntity(nullA));
|
|
await server.WaitPost(() => entMan.DeleteEntity(nullB));
|
|
await server.WaitPost(() => entMan.DeleteEntity(nullC));
|
|
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
|
}
|
|
|
|
async Task Load(ResPath f, DeserializationOptions? o)
|
|
{
|
|
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
|
var oo = MapLoadOptions.Default with
|
|
{
|
|
DeserializationOptions = o ?? DeserializationOptions.Default
|
|
};
|
|
await server.WaitAssertion(() => Assert.That(loader.TryLoadGeneric(f, out _, oo)));
|
|
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(8));
|
|
}
|
|
|
|
void FindAll()
|
|
{
|
|
mapA = Find(nameof(mapA), entMan);
|
|
mapB = Find(nameof(mapB), entMan);
|
|
entA = Find(nameof(entA), entMan);
|
|
entB = Find(nameof(entB), entMan);
|
|
entC = Find(nameof(entC), entMan);
|
|
nullA = Find(nameof(nullA), entMan);
|
|
nullB = Find(nameof(nullB), entMan);
|
|
nullC = Find(nameof(nullC), entMan);
|
|
}
|
|
|
|
async Task Reload(ResPath f, DeserializationOptions? o = null)
|
|
{
|
|
await Delete();
|
|
await Load(f, o);
|
|
FindAll();
|
|
}
|
|
|
|
// All entities should initially be in their respective expected states.
|
|
// entC (pre-mapinit entity on a post-mapinit map) is a bit fucky, and I don't know if that should even be allowed.
|
|
// Note that its just pre-init, not paused, as pre-mapinit entities get paused due to the maps state, not as a general result of being pre-mapinit.
|
|
// If this ever changes, these assers need fixing.
|
|
AssertPaused(true, mapA, entB, nullC);
|
|
AssertPaused(false, mapB, entA, entC, nullA, nullB);
|
|
AssertPreInit(true, mapA, entC, nullB);
|
|
AssertPreInit(false, mapB, entA, entB, nullA, nullC);
|
|
|
|
// Saving and re-loading entities should leave their metadata unchanged.
|
|
Save(path);
|
|
await Reload(path);
|
|
AssertPaused(true, mapA, entB, nullC);
|
|
AssertPaused(false, mapB, entA, entC, nullA, nullB);
|
|
AssertPreInit(true, mapA, entC, nullB);
|
|
AssertPreInit(false, mapB, entA, entB, nullA, nullC);
|
|
|
|
// reload maps with the mapinit option. This should only affect mapA, as entA is the only one on the map and it
|
|
// is already initialized,
|
|
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
|
await Reload(path, opts);
|
|
AssertPaused(true, entB, nullC);
|
|
AssertPaused(false, mapA, mapB, entA, entC, nullA, nullB);
|
|
AssertPreInit(true, entC, nullB);
|
|
AssertPreInit(false, mapA, mapB, entA, entB, nullA, nullC);
|
|
|
|
// Reloading the new configuration changes nothing
|
|
Save(altPath);
|
|
await Reload(altPath, opts);
|
|
AssertPaused(true, entB, nullC);
|
|
AssertPaused(false, mapA, mapB, entA, entC, nullA, nullB);
|
|
AssertPreInit(true, entC, nullB);
|
|
AssertPreInit(false, mapA, mapB, entA, entB, nullA, nullC);
|
|
|
|
// Pause all maps. This will not actually pause entityA, as mapA is already paused (due to being pre-init), so
|
|
// it will not iterate through its children. Maybe this will change in future, but I don't think we should even
|
|
// be trying to actively support having post-init entities on a pre-init map. This is subject to maybe change
|
|
// one day, though if it does the option should be changed to PauseEntities to clarify that it will pause ALL
|
|
// entities, not just maps.
|
|
opts = DeserializationOptions.Default with {PauseMaps = true};
|
|
await Reload(path, opts);
|
|
AssertPaused(true, mapA, mapB, entC, entB, nullC);
|
|
AssertPaused(false, entA, nullA, nullB);
|
|
AssertPreInit(true, mapA, entC, nullB);
|
|
AssertPreInit(false, mapB, entA, entB, nullA, nullC);
|
|
|
|
// Reloading the new configuration changes nothing
|
|
Save(altPath);
|
|
await Reload(altPath, opts);
|
|
AssertPaused(true, mapA, mapB, entC, entB, nullC);
|
|
AssertPaused(false, entA, nullA, nullB);
|
|
AssertPreInit(true, mapA, entC, nullB);
|
|
AssertPreInit(false, mapB, entA, entB, nullA, nullC);
|
|
|
|
// Initialise and pause all maps. Similar to the previous test with entA, this will not affect entC even
|
|
// though it is pre-init, because it is on a post-init map. Again, this is subject to maybe change one day.
|
|
// Though if it does, the option should be changed to MapInitializeEntities to clarify that it will mapinit ALL
|
|
// entities, not just maps.
|
|
opts = DeserializationOptions.Default with {InitializeMaps = true, PauseMaps = true};
|
|
await Reload(path, opts);
|
|
AssertPaused(true, mapA, mapB, entB, entC, nullC);
|
|
AssertPaused(false, entA, nullA, nullB);
|
|
AssertPreInit(true, entC, nullB);
|
|
AssertPreInit(false, mapA, mapB, entA, entB, nullA, nullC);
|
|
|
|
// Reloading the new configuration changes nothing
|
|
Save(altPath);
|
|
await Reload(altPath, opts);
|
|
AssertPaused(true, mapA, mapB, entB, entC, nullC);
|
|
AssertPaused(false, entA, nullA, nullB);
|
|
AssertPreInit(true, entC, nullB);
|
|
AssertPreInit(false, mapA, mapB, entA, entB, nullA, nullC);
|
|
}
|
|
}
|