using System.Threading.Tasks; using NUnit.Framework; using Robust.Shared.ContentPack; using Robust.Shared.EntitySerialization; using Robust.Shared.EntitySerialization.Components; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Utility; using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent; namespace Robust.UnitTesting.Shared.EntitySerialization; /// /// Test that older file formats can still be loaded. /// [TestFixture] internal sealed partial class BackwardsCompatibilityTest : RobustIntegrationTest { /// /// Check that v7 maps can be loaded. This just re-uses some map files that are generated by other tests, and then /// checks that the post-load debug asserts still pass. specifically, it uses the second to last file from /// and the initial file from /// . /// /// /// At the time of writing, v7 is the current version, but this is here for when the version increases in the future. /// [Test] public async Task TestLoadV7() { var server = StartServer(new ServerIntegrationOptions { ExtraPrototypes = PrototypeV7 }); await server.WaitIdleAsync(); var entMan = server.EntMan; var mapSys = server.System(); var loader = server.System(); var tileMan = server.ResolveDependency(); var resourceManager = server.ResolveDependency(); SerializationTestHelper.LoadTileDefs(server.ProtoMan, tileMan, "space"); void AssertCount(int expected) => Assert.That(entMan.Count(), Is.EqualTo(expected)); Assert.That(entMan.Count(), Is.EqualTo(0)); var mapLoadOpts = MapLoadOptions.Default with { DeserializationOptions = DeserializationOptions.Default with {LogOrphanedGrids = false} }; // Test the file from AutoIncludeSerializationTest { var path = new ResPath($"{nameof(MapDataV7)}.yml"); resourceManager.MountString(path.ToString(), MapDataV7); Entity grid; Entity onGrid; Entity otherMap; Entity otherEnt; Entity nullSpace; LoadResult? result = default; await server.WaitAssertion(() => Assert.That(loader.TryLoadGeneric(path, out result, mapLoadOpts))); Assert.That(result!.Version, Is.EqualTo(7)); Assert.That(result.Grids, Has.Count.EqualTo(1)); Assert.That(result.Orphans, Is.Empty); // Grid was orphaned, but was adopted after a new map was created Assert.That(result.Maps, Has.Count.EqualTo(2)); Assert.That(result.NullspaceEntities, Has.Count.EqualTo(1)); Assert.That(entMan.Count(), Is.EqualTo(1)); // auto-generated map isn't marked as "loaded" AssertCount(5); grid = Find(nameof(grid), entMan); onGrid = Find(nameof(onGrid), entMan); otherMap = Find(nameof(otherMap), entMan); otherEnt = Find(nameof(otherEnt), entMan); nullSpace = Find(nameof(nullSpace), entMan); Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner)); Assert.That(otherEnt.Comp1.ParentUid, Is.EqualTo(otherMap.Owner)); Assert.That(otherMap.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid)); Assert.That(nullSpace.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid)); await server.WaitPost(() => entMan.DeleteEntity(otherMap)); await server.WaitPost(() => entMan.DeleteEntity(grid.Comp1.ParentUid)); await server.WaitPost(() => entMan.DeleteEntity(nullSpace)); AssertCount(0); await server.WaitPost(() => loader.Delete(result)); } // Test the file from LifestageSerializationTest.TestMixedLifestageSerialization { var pathLifestage = new ResPath($"{nameof(MapDataV7Lifestage)}.yml"); resourceManager.MountString(pathLifestage.ToString(), MapDataV7Lifestage); Entity mapA; // preinit Map Entity mapB; // postinit unpaused Map Entity entA; // postinit entity on preinit map Entity entB; // paused entity on postinit unpaused map Entity entC; // preinit entity on postinit map Entity nullA; // postinit nullspace entity Entity nullB; // preinit nullspace entity Entity nullC; // paused postinit nullspace entity Assert.That(entMan.Count(), Is.EqualTo(0)); LoadResult? result = default; await server.WaitAssertion(() => Assert.That(loader.TryLoadGeneric(pathLifestage, out result))); Assert.That(result!.Version, Is.EqualTo(7)); Assert.That(entMan.Count(), Is.EqualTo(8)); 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); AssertPaused(true, mapA, entB, nullC); AssertPaused(false, mapB, entA, entC, nullA, nullB); AssertPreInit(true, mapA, entC, nullB); AssertPreInit(false, mapB, entA, entB, nullA, nullC); void AssertPaused(bool expected, params EntityUid[] uids) { foreach (var uid in uids) { Assert.That(entMan.GetComponent(uid).EntityPaused, Is.EqualTo(expected)); } } void AssertPreInit(bool expected, params EntityUid[] uids) { foreach (var uid in uids) { Assert.That(entMan!.GetComponent(uid).EntityLifeStage, expected ? Is.LessThan(EntityLifeStage.MapInitialized) : Is.EqualTo(EntityLifeStage.MapInitialized)); } } await server.WaitPost(() => loader.Delete(result)); } Assert.That(entMan.Count(), Is.EqualTo(0)); Assert.That(entMan.Count(), Is.EqualTo(0)); Assert.That(entMan.Count(), Is.EqualTo(0)); } private const string MapDataV7 = @" meta: format: 7 category: Unknown engineVersion: 238.0.0 forkId: """" forkVersion: """" time: 12/25/2024 00:40:09 entityCount: 5 maps: - 3 grids: - 1 orphans: - 1 nullspace: - 5 tilemap: 1: space 0: a entities: - proto: """" entities: - uid: 1 paused: false components: - type: MetaData name: grid - type: Transform parent: invalid - type: MapGrid chunks: 0,0: ind: 0,0 tiles: AAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAA version: 6 - type: Broadphase - type: Physics - type: Fixtures fixtures: {} - type: OccluderTree - type: EntitySaveTest list: [] id: grid - uid: 2 mapInit: true components: - type: MetaData - type: Transform pos: 0.5,0.5 parent: 1 - type: EntitySaveTest list: [] entity: 3 id: onGrid - uid: 3 mapInit: true components: - type: MetaData name: Map Entity - type: Transform - type: Map mapInitialized: True - type: GridTree - type: Broadphase - type: OccluderTree - type: EntitySaveTest list: [] id: otherMap - uid: 4 mapInit: true components: - type: MetaData - type: Transform parent: 3 - type: EntitySaveTest list: [] entity: 5 id: otherEnt - uid: 5 mapInit: true components: - type: MetaData - type: Transform - type: EntitySaveTest list: [] id: nullSpace "; private const string MapDataV7Lifestage = @" meta: format: 7 category: Unknown engineVersion: 238.0.0 forkId: """" forkVersion: """" time: 12/25/2024 00:50:59 entityCount: 8 maps: - 1 - 3 grids: [] orphans: [] nullspace: - 6 - 7 - 8 tilemap: {} entities: - proto: """" entities: - uid: 1 components: - type: MetaData name: Map Entity - type: Transform - type: Map mapPaused: True - type: GridTree - type: Broadphase - type: OccluderTree - type: EntitySaveTest list: [] id: mapA - uid: 2 mapInit: true components: - type: MetaData - type: Transform parent: 1 - type: EntitySaveTest list: [] id: entA - uid: 3 mapInit: true components: - type: MetaData name: Map Entity - type: Transform - type: Map mapInitialized: True - type: GridTree - type: Broadphase - type: OccluderTree - type: EntitySaveTest list: [] id: mapB - uid: 4 mapInit: true paused: true components: - type: MetaData - type: Transform parent: 3 - type: EntitySaveTest list: [] id: entB - uid: 5 paused: false components: - type: MetaData - type: Transform parent: 3 - type: EntitySaveTest list: [] id: entC - uid: 6 mapInit: true components: - type: MetaData - type: Transform - type: EntitySaveTest list: [] id: nullA - uid: 7 paused: false components: - type: MetaData - type: Transform - type: EntitySaveTest list: [] id: nullB - uid: 8 mapInit: true paused: true components: - type: MetaData - type: Transform - type: EntitySaveTest list: [] id: nullC "; private const string PrototypeV7 = @" - type: testTileDef id: space - type: testTileDef id: a "; }