using System.IO; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using Robust.Server.GameObjects; using Robust.Server.Maps; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests { /// /// Tests that the /// [TestFixture] public sealed class SaveLoadSaveTest { [Test] public async Task SaveLoadSave() { await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings {Fresh = true, Disconnected = true}); var server = pairTracker.Pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); var mapManager = server.ResolveDependency(); await server.WaitPost(() => { var mapId0 = mapManager.CreateMap(); // TODO: Properly find the "main" station grid. var grid0 = mapManager.CreateGrid(mapId0); mapLoader.Save(grid0.Owner, "save load save 1.yml"); var mapId1 = mapManager.CreateMap(); var grid1 = mapLoader.LoadGrid(mapId1, "save load save 1.yml", new MapLoadOptions() {LoadMap = false}); mapLoader.Save(grid1!.Value, "save load save 2.yml"); }); await server.WaitIdleAsync(); var userData = server.ResolveDependency().UserData; string one; string two; var rp1 = new ResPath("/save load save 1.yml"); await using (var stream = userData.Open(rp1, FileMode.Open)) using (var reader = new StreamReader(stream)) { one = await reader.ReadToEndAsync(); } var rp2 = new ResPath("/save load save 2.yml"); await using (var stream = userData.Open(rp2, FileMode.Open)) using (var reader = new StreamReader(stream)) { two = await reader.ReadToEndAsync(); } Assert.Multiple(() => { Assert.That(two, Is.EqualTo(one)); var failed = TestContext.CurrentContext.Result.Assertions.FirstOrDefault(); if (failed != null) { var oneTmp = Path.GetTempFileName(); var twoTmp = Path.GetTempFileName(); File.WriteAllText(oneTmp, one); File.WriteAllText(twoTmp, two); TestContext.AddTestAttachment(oneTmp, "First save file"); TestContext.AddTestAttachment(twoTmp, "Second save file"); TestContext.Error.WriteLine("Complete output:"); TestContext.Error.WriteLine(oneTmp); TestContext.Error.WriteLine(twoTmp); } }); await pairTracker.CleanReturnAsync(); } const string TestMap = "Maps/bagel.yml"; /// /// Loads the default map, runs it for 5 ticks, then assert that it did not change. /// [Test] public async Task LoadSaveTicksSaveBagel() { await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true}); var server = pairTracker.Pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); var mapManager = server.ResolveDependency(); MapId mapId = default; // Load bagel.yml as uninitialized map, and save it to ensure it's up to date. server.Post(() => { mapId = mapManager.CreateMap(); mapManager.AddUninitializedMap(mapId); mapManager.SetMapPaused(mapId, true); mapLoader.LoadMap(mapId, TestMap); mapLoader.SaveMap(mapId, "load save ticks save 1.yml"); }); // Run 5 ticks. server.RunTicks(5); await server.WaitPost(() => { mapLoader.SaveMap(mapId, "/load save ticks save 2.yml"); }); await server.WaitIdleAsync(); var userData = server.ResolveDependency().UserData; string one; string two; await using (var stream = userData.Open(new ResPath("/load save ticks save 1.yml"), FileMode.Open)) using (var reader = new StreamReader(stream)) { one = await reader.ReadToEndAsync(); } await using (var stream = userData.Open(new ResPath("/load save ticks save 2.yml"), FileMode.Open)) using (var reader = new StreamReader(stream)) { two = await reader.ReadToEndAsync(); } Assert.Multiple(() => { Assert.That(two, Is.EqualTo(one)); var failed = TestContext.CurrentContext.Result.Assertions.FirstOrDefault(); if (failed != null) { var oneTmp = Path.GetTempFileName(); var twoTmp = Path.GetTempFileName(); File.WriteAllText(oneTmp, one); File.WriteAllText(twoTmp, two); TestContext.AddTestAttachment(oneTmp, "First save file"); TestContext.AddTestAttachment(twoTmp, "Second save file"); TestContext.Error.WriteLine("Complete output:"); TestContext.Error.WriteLine(oneTmp); TestContext.Error.WriteLine(twoTmp); } }); await server.WaitPost(() => mapManager.DeleteMap(mapId)); await pairTracker.CleanReturnAsync(); } /// /// Loads the same uninitialized map at slightly different times, and then checks that they are the same /// when getting saved. /// /// /// Should ensure that entities do not perform randomization prior to initialization and should prevents /// bugs like the one discussed in github.com/space-wizards/RobustToolbox/issues/3870. This test is somewhat /// similar to and , but neither of these /// caught the mentioned bug. /// [Test] public async Task LoadTickLoadBagel() { await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true}); var server = pairTracker.Pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); var mapManager = server.ResolveDependency(); var userData = server.ResolveDependency().UserData; MapId mapId = default; const string fileA = "/load tick load a.yml"; const string fileB = "/load tick load b.yml"; string yamlA; string yamlB; // Load & save the first map server.Post(() => { mapId = mapManager.CreateMap(); mapManager.AddUninitializedMap(mapId); mapManager.SetMapPaused(mapId, true); mapLoader.LoadMap(mapId, TestMap); mapLoader.SaveMap(mapId, fileA); }); await server.WaitIdleAsync(); await using (var stream = userData.Open(new ResPath(fileA), FileMode.Open)) using (var reader = new StreamReader(stream)) { yamlA = await reader.ReadToEndAsync(); } server.RunTicks(5); // Load & save the second map server.Post(() => { mapManager.DeleteMap(mapId); mapManager.CreateMap(mapId); mapManager.AddUninitializedMap(mapId); mapManager.SetMapPaused(mapId, true); mapLoader.LoadMap(mapId, TestMap); mapLoader.SaveMap(mapId, fileB); }); await server.WaitIdleAsync(); await using (var stream = userData.Open(new ResPath(fileB), FileMode.Open)) using (var reader = new StreamReader(stream)) { yamlB = await reader.ReadToEndAsync(); } Assert.That(yamlA, Is.EqualTo(yamlB)); await server.WaitPost(() => mapManager.DeleteMap(mapId)); await pairTracker.CleanReturnAsync(); } } }