1
0

PostMapInitTest.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. using System.Collections.Generic;
  2. using System.IO;
  3. using System.Linq;
  4. using Content.Server.Administration.Systems;
  5. using Content.Server.GameTicking;
  6. using Content.Server.Maps;
  7. using Content.Server.Shuttles.Components;
  8. using Content.Server.Shuttles.Systems;
  9. using Content.Server.Spawners.Components;
  10. using Content.Server.Station.Components;
  11. using Content.Shared.CCVar;
  12. using Content.Shared.Roles;
  13. using Robust.Shared.Configuration;
  14. using Robust.Shared.ContentPack;
  15. using Robust.Shared.GameObjects;
  16. using Robust.Shared.Map;
  17. using Robust.Shared.Map.Components;
  18. using Robust.Shared.Prototypes;
  19. using Content.Shared.Station.Components;
  20. using Robust.Shared.EntitySerialization;
  21. using Robust.Shared.EntitySerialization.Systems;
  22. using Robust.Shared.IoC;
  23. using Robust.Shared.Utility;
  24. using YamlDotNet.RepresentationModel;
  25. namespace Content.IntegrationTests.Tests
  26. {
  27. [TestFixture]
  28. public sealed class PostMapInitTest
  29. {
  30. private const bool SkipTestMaps = true;
  31. private const string TestMapsPath = "/Maps/Test/";
  32. private static readonly string[] NoSpawnMaps =
  33. {
  34. "CentComm",
  35. "Dart"
  36. };
  37. private static readonly string[] Grids =
  38. {
  39. "/Maps/centcomm.yml",
  40. AdminTestArenaSystem.ArenaMapPath
  41. };
  42. private static readonly string[] DoNotMapWhitelist =
  43. {
  44. };
  45. private static readonly string[] GameMaps =
  46. {
  47. "Nomads"
  48. };
  49. /// <summary>
  50. /// Asserts that specific files have been saved as grids and not maps.
  51. /// </summary>
  52. [Test, TestCaseSource(nameof(Grids))]
  53. public async Task GridsLoadableTest(string mapFile)
  54. {
  55. await using var pair = await PoolManager.GetServerClient();
  56. var server = pair.Server;
  57. var entManager = server.ResolveDependency<IEntityManager>();
  58. var mapLoader = entManager.System<MapLoaderSystem>();
  59. var mapSystem = entManager.System<SharedMapSystem>();
  60. var cfg = server.ResolveDependency<IConfigurationManager>();
  61. Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
  62. var path = new ResPath(mapFile);
  63. await server.WaitPost(() =>
  64. {
  65. mapSystem.CreateMap(out var mapId);
  66. try
  67. {
  68. Assert.That(mapLoader.TryLoadGrid(mapId, path, out var grid));
  69. }
  70. catch (Exception ex)
  71. {
  72. throw new Exception($"Failed to load map {mapFile}, was it saved as a map instead of a grid?", ex);
  73. }
  74. mapSystem.DeleteMap(mapId);
  75. });
  76. await server.WaitRunTicks(1);
  77. await pair.CleanReturnAsync();
  78. }
  79. /// <summary>
  80. /// Asserts that shuttles are loadable and have been saved as grids and not maps.
  81. /// </summary>
  82. [Test]
  83. public async Task ShuttlesLoadableTest()
  84. {
  85. await using var pair = await PoolManager.GetServerClient();
  86. var server = pair.Server;
  87. var entManager = server.ResolveDependency<IEntityManager>();
  88. var resMan = server.ResolveDependency<IResourceManager>();
  89. var mapLoader = entManager.System<MapLoaderSystem>();
  90. var mapSystem = entManager.System<SharedMapSystem>();
  91. var cfg = server.ResolveDependency<IConfigurationManager>();
  92. Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
  93. var shuttleFolder = new ResPath("/Maps/Shuttles");
  94. var shuttles = resMan
  95. .ContentFindFiles(shuttleFolder)
  96. .Where(filePath =>
  97. filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal))
  98. .ToArray();
  99. await server.WaitPost(() =>
  100. {
  101. Assert.Multiple(() =>
  102. {
  103. foreach (var path in shuttles)
  104. {
  105. mapSystem.CreateMap(out var mapId);
  106. try
  107. {
  108. Assert.That(mapLoader.TryLoadGrid(mapId, path, out _),
  109. $"Failed to load shuttle {path}, was it saved as a map instead of a grid?");
  110. }
  111. catch (Exception ex)
  112. {
  113. throw new Exception($"Failed to load shuttle {path}, was it saved as a map instead of a grid?",
  114. ex);
  115. }
  116. mapSystem.DeleteMap(mapId);
  117. }
  118. });
  119. });
  120. await server.WaitRunTicks(1);
  121. await pair.CleanReturnAsync();
  122. }
  123. [Test]
  124. public async Task NoSavedPostMapInitTest()
  125. {
  126. await using var pair = await PoolManager.GetServerClient();
  127. var server = pair.Server;
  128. var resourceManager = server.ResolveDependency<IResourceManager>();
  129. var protoManager = server.ResolveDependency<IPrototypeManager>();
  130. var loader = server.System<MapLoaderSystem>();
  131. var mapFolder = new ResPath("/Maps");
  132. var maps = resourceManager
  133. .ContentFindFiles(mapFolder)
  134. .Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal))
  135. .ToArray();
  136. var v7Maps = new List<ResPath>();
  137. foreach (var map in maps)
  138. {
  139. var rootedPath = map.ToRootedPath();
  140. // ReSharper disable once RedundantLogicalConditionalExpressionOperand
  141. if (SkipTestMaps && rootedPath.ToString().StartsWith(TestMapsPath, StringComparison.Ordinal))
  142. {
  143. continue;
  144. }
  145. if (!resourceManager.TryContentFileRead(rootedPath, out var fileStream))
  146. {
  147. Assert.Fail($"Map not found: {rootedPath}");
  148. }
  149. using var reader = new StreamReader(fileStream);
  150. var yamlStream = new YamlStream();
  151. yamlStream.Load(reader);
  152. var root = yamlStream.Documents[0].RootNode;
  153. var meta = root["meta"];
  154. var version = meta["format"].AsInt();
  155. // TODO MAP TESTS
  156. // Move this to some separate test?
  157. CheckDoNotMap(map, root, protoManager);
  158. if (version >= 7)
  159. {
  160. v7Maps.Add(map);
  161. continue;
  162. }
  163. var postMapInit = meta["postmapinit"].AsBool();
  164. Assert.That(postMapInit, Is.False, $"Map {map.Filename} was saved postmapinit");
  165. }
  166. var deps = server.ResolveDependency<IEntitySystemManager>().DependencyCollection;
  167. foreach (var map in v7Maps)
  168. {
  169. Assert.That(IsPreInit(map, loader, deps));
  170. }
  171. // Check that the test actually does manage to catch post-init maps and isn't just blindly passing everything.
  172. // To that end, create a new post-init map and try verify it.
  173. var mapSys = server.System<SharedMapSystem>();
  174. MapId id = default;
  175. await server.WaitPost(() => mapSys.CreateMap(out id, runMapInit: false));
  176. await server.WaitPost(() => server.EntMan.Spawn(null, new MapCoordinates(0, 0, id)));
  177. // First check that a pre-init version passes
  178. var path = new ResPath($"{nameof(NoSavedPostMapInitTest)}.yml");
  179. Assert.That(loader.TrySaveMap(id, path));
  180. Assert.That(IsPreInit(path, loader, deps));
  181. // and the post-init version fails.
  182. await server.WaitPost(() => mapSys.InitializeMap(id));
  183. Assert.That(loader.TrySaveMap(id, path));
  184. Assert.That(IsPreInit(path, loader, deps), Is.False);
  185. await pair.CleanReturnAsync();
  186. }
  187. /// <summary>
  188. /// Check that maps do not have any entities that belong to the DoNotMap entity category
  189. /// </summary>
  190. private void CheckDoNotMap(ResPath map, YamlNode node, IPrototypeManager protoManager)
  191. {
  192. if (DoNotMapWhitelist.Contains(map.ToString()))
  193. return;
  194. var yamlEntities = node["entities"];
  195. if (!protoManager.TryIndex<EntityCategoryPrototype>("DoNotMap", out var dnmCategory))
  196. return;
  197. Assert.Multiple(() =>
  198. {
  199. foreach (var yamlEntity in (YamlSequenceNode)yamlEntities)
  200. {
  201. var protoId = yamlEntity["proto"].AsString();
  202. // This doesn't properly handle prototype migrations, but thats not a significant issue.
  203. if (!protoManager.TryIndex(protoId, out var proto, false))
  204. continue;
  205. Assert.That(!proto.Categories.Contains(dnmCategory),
  206. $"\nMap {map} contains entities in the DO NOT MAP category ({proto.Name})");
  207. }
  208. });
  209. }
  210. private bool IsPreInit(ResPath map, MapLoaderSystem loader, IDependencyCollection deps)
  211. {
  212. if (!loader.TryReadFile(map, out var data))
  213. {
  214. Assert.Fail($"Failed to read {map}");
  215. return false;
  216. }
  217. var reader = new EntityDeserializer(deps, data, DeserializationOptions.Default);
  218. if (!reader.TryProcessData())
  219. {
  220. Assert.Fail($"Failed to process {map}");
  221. return false;
  222. }
  223. foreach (var mapId in reader.MapYamlIds)
  224. {
  225. var mapData = reader.YamlEntities[mapId];
  226. if (mapData.PostInit)
  227. return false;
  228. }
  229. return true;
  230. }
  231. [Test, TestCaseSource(nameof(GameMaps))]
  232. public async Task GameMapsLoadableTest(string mapProto)
  233. {
  234. await using var pair = await PoolManager.GetServerClient(new PoolSettings
  235. {
  236. Dirty = true // Stations spawn a bunch of nullspace entities and maps like centcomm.
  237. });
  238. var server = pair.Server;
  239. var mapManager = server.ResolveDependency<IMapManager>();
  240. var entManager = server.ResolveDependency<IEntityManager>();
  241. var mapLoader = entManager.System<MapLoaderSystem>();
  242. var mapSystem = entManager.System<SharedMapSystem>();
  243. var protoManager = server.ResolveDependency<IPrototypeManager>();
  244. var ticker = entManager.EntitySysManager.GetEntitySystem<GameTicker>();
  245. var shuttleSystem = entManager.EntitySysManager.GetEntitySystem<ShuttleSystem>();
  246. var cfg = server.ResolveDependency<IConfigurationManager>();
  247. Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
  248. await server.WaitPost(() =>
  249. {
  250. MapId mapId;
  251. try
  252. {
  253. var opts = DeserializationOptions.Default with { InitializeMaps = true };
  254. ticker.LoadGameMap(protoManager.Index<GameMapPrototype>(mapProto), out mapId, opts);
  255. }
  256. catch (Exception ex)
  257. {
  258. throw new Exception($"Failed to load map {mapProto}", ex);
  259. }
  260. mapSystem.CreateMap(out var shuttleMap);
  261. var largest = 0f;
  262. EntityUid? targetGrid = null;
  263. var memberQuery = entManager.GetEntityQuery<StationMemberComponent>();
  264. var grids = mapManager.GetAllGrids(mapId).ToList();
  265. var gridUids = grids.Select(o => o.Owner).ToList();
  266. targetGrid = gridUids.First();
  267. foreach (var grid in grids)
  268. {
  269. var gridEnt = grid.Owner;
  270. if (!memberQuery.HasComponent(gridEnt))
  271. continue;
  272. var area = grid.Comp.LocalAABB.Width * grid.Comp.LocalAABB.Height;
  273. if (area > largest)
  274. {
  275. largest = area;
  276. targetGrid = gridEnt;
  277. }
  278. }
  279. // Test shuttle can dock.
  280. // This is done inside gamemap test because loading the map takes ages and we already have it.
  281. var station = entManager.GetComponent<StationMemberComponent>(targetGrid!.Value).Station;
  282. if (entManager.TryGetComponent<StationEmergencyShuttleComponent>(station, out var stationEvac))
  283. {
  284. var shuttlePath = stationEvac.EmergencyShuttlePath;
  285. Assert.That(mapLoader.TryLoadGrid(shuttleMap, shuttlePath, out var shuttle),
  286. $"Failed to load {shuttlePath}");
  287. Assert.That(
  288. shuttleSystem.TryFTLDock(shuttle!.Value.Owner,
  289. entManager.GetComponent<ShuttleComponent>(shuttle!.Value.Owner),
  290. targetGrid.Value),
  291. $"Unable to dock {shuttlePath} to {mapProto}");
  292. }
  293. mapSystem.DeleteMap(shuttleMap);
  294. if (entManager.HasComponent<StationJobsComponent>(station))
  295. {
  296. // Test that the map has valid latejoin spawn points or container spawn points
  297. if (!NoSpawnMaps.Contains(mapProto))
  298. {
  299. var lateSpawns = 0;
  300. lateSpawns += GetCountLateSpawn<SpawnPointComponent>(gridUids, entManager);
  301. lateSpawns += GetCountLateSpawn<ContainerSpawnPointComponent>(gridUids, entManager);
  302. Assert.That(lateSpawns, Is.GreaterThan(0), $"Found no latejoin spawn points on {mapProto}");
  303. }
  304. // Test all availableJobs have spawnPoints
  305. // This is done inside gamemap test because loading the map takes ages and we already have it.
  306. var comp = entManager.GetComponent<StationJobsComponent>(station);
  307. var jobs = new HashSet<ProtoId<JobPrototype>>(comp.SetupAvailableJobs.Keys);
  308. var spawnPoints = entManager.EntityQuery<SpawnPointComponent>()
  309. .Where(x => x.SpawnType == SpawnPointType.Job && x.Job != null)
  310. .Select(x => x.Job.Value);
  311. jobs.ExceptWith(spawnPoints);
  312. spawnPoints = entManager.EntityQuery<ContainerSpawnPointComponent>()
  313. .Where(x => x.SpawnType is SpawnPointType.Job or SpawnPointType.Unset && x.Job != null)
  314. .Select(x => x.Job.Value);
  315. jobs.ExceptWith(spawnPoints);
  316. Assert.That(jobs, Is.Empty, $"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}.");
  317. }
  318. try
  319. {
  320. mapSystem.DeleteMap(mapId);
  321. }
  322. catch (Exception ex)
  323. {
  324. throw new Exception($"Failed to delete map {mapProto}", ex);
  325. }
  326. });
  327. await server.WaitRunTicks(1);
  328. await pair.CleanReturnAsync();
  329. }
  330. private static int GetCountLateSpawn<T>(List<EntityUid> gridUids, IEntityManager entManager)
  331. where T : ISpawnPoint, IComponent
  332. {
  333. var resultCount = 0;
  334. var queryPoint = entManager.AllEntityQueryEnumerator<T, TransformComponent>();
  335. #nullable enable
  336. while (queryPoint.MoveNext(out T? comp, out var xform))
  337. {
  338. var spawner = (ISpawnPoint)comp;
  339. if (spawner.SpawnType is not SpawnPointType.LateJoin
  340. || xform.GridUid == null
  341. || !gridUids.Contains(xform.GridUid.Value))
  342. {
  343. continue;
  344. }
  345. #nullable disable
  346. resultCount++;
  347. break;
  348. }
  349. return resultCount;
  350. }
  351. [Test]
  352. public async Task AllMapsTested()
  353. {
  354. await using var pair = await PoolManager.GetServerClient();
  355. var server = pair.Server;
  356. var protoMan = server.ResolveDependency<IPrototypeManager>();
  357. var gameMaps = protoMan.EnumeratePrototypes<GameMapPrototype>()
  358. .Where(x => !pair.IsTestPrototype(x))
  359. .Select(x => x.ID)
  360. .ToHashSet();
  361. Assert.That(gameMaps.Remove(PoolManager.TestMap));
  362. Assert.That(gameMaps, Is.EquivalentTo(GameMaps.ToHashSet()), "Game map prototype missing from test cases.");
  363. await pair.CleanReturnAsync();
  364. }
  365. [Test]
  366. public async Task NonGameMapsLoadableTest()
  367. {
  368. await using var pair = await PoolManager.GetServerClient();
  369. var server = pair.Server;
  370. var mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
  371. var resourceManager = server.ResolveDependency<IResourceManager>();
  372. var protoManager = server.ResolveDependency<IPrototypeManager>();
  373. var cfg = server.ResolveDependency<IConfigurationManager>();
  374. Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
  375. var gameMaps = protoManager.EnumeratePrototypes<GameMapPrototype>().Select(o => o.MapPath).ToHashSet();
  376. var mapFolder = new ResPath("/Maps");
  377. var maps = resourceManager
  378. .ContentFindFiles(mapFolder)
  379. .Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal))
  380. .ToArray();
  381. var mapPaths = new List<ResPath>();
  382. foreach (var map in maps)
  383. {
  384. if (gameMaps.Contains(map))
  385. continue;
  386. var rootedPath = map.ToRootedPath();
  387. if (SkipTestMaps && rootedPath.ToString().StartsWith(TestMapsPath, StringComparison.Ordinal))
  388. {
  389. continue;
  390. }
  391. mapPaths.Add(rootedPath);
  392. }
  393. await server.WaitPost(() =>
  394. {
  395. Assert.Multiple(() =>
  396. {
  397. // This bunch of files contains a random mixture of both map and grid files.
  398. // TODO MAPPING organize files
  399. var opts = MapLoadOptions.Default with
  400. {
  401. DeserializationOptions = DeserializationOptions.Default with
  402. {
  403. InitializeMaps = true,
  404. LogOrphanedGrids = false
  405. }
  406. };
  407. HashSet<Entity<MapComponent>> maps;
  408. foreach (var path in mapPaths)
  409. {
  410. try
  411. {
  412. Assert.That(mapLoader.TryLoadGeneric(path, out maps, out _, opts));
  413. }
  414. catch (Exception ex)
  415. {
  416. throw new Exception($"Failed to load map {path}", ex);
  417. }
  418. try
  419. {
  420. foreach (var map in maps)
  421. {
  422. server.EntMan.DeleteEntity(map);
  423. }
  424. }
  425. catch (Exception ex)
  426. {
  427. throw new Exception($"Failed to delete map {path}", ex);
  428. }
  429. }
  430. });
  431. });
  432. await server.WaitRunTicks(1);
  433. await pair.CleanReturnAsync();
  434. }
  435. }
  436. }