Improve Do Not Map test to whitelist specific prototypes per map and whitelist entire directories (#36117)
* Enable whitelisting specific DNM prototypes per map * Enable whitelisting directories * Rename fields * Use a HashSet instead of an array * Add check for unused whitelist entries * Remove whitelisting for meta (warden's rubber stamp was removed) * Add glob support courtesy of @IProduceWidgets * Update xmldoc
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Maps;
|
||||
@@ -44,17 +45,43 @@ namespace Content.IntegrationTests.Tests
|
||||
AdminTestArenaSystem.ArenaMapPath
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary linking maps to collections of entity prototype ids that should be exempt from "DoNotMap" restrictions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This declares that the listed entity prototypes are allowed to be present on the map
|
||||
/// despite being categorized as "DoNotMap", while any unlisted prototypes will still
|
||||
/// cause the test to fail.
|
||||
/// </remarks>
|
||||
private static readonly Dictionary<string, HashSet<EntProtoId>> DoNotMapWhitelistSpecific = new()
|
||||
{
|
||||
{"/Maps/bagel.yml", ["RubberStampMime"]},
|
||||
{"/Maps/reach.yml", ["HandheldCrewMonitor"]},
|
||||
{"/Maps/Shuttles/ShuttleEvent/honki.yml", ["GoldenBikeHorn", "RubberStampClown"]},
|
||||
{"/Maps/Shuttles/ShuttleEvent/syndie_evacpod.yml", ["RubberStampSyndicate"]},
|
||||
{"/Maps/Shuttles/ShuttleEvent/cruiser.yml", ["ShuttleGunPerforator"]},
|
||||
{"/Maps/Shuttles/ShuttleEvent/instigator.yml", ["ShuttleGunFriendship"]},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Maps listed here are given blanket freedom to contain "DoNotMap" entities. Use sparingly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is also possible to whitelist entire directories here. For example, adding
|
||||
/// "/Maps/Shuttles/**" will whitelist all shuttle maps.
|
||||
/// </remarks>
|
||||
private static readonly string[] DoNotMapWhitelist =
|
||||
{
|
||||
"/Maps/centcomm.yml",
|
||||
"/Maps/bagel.yml", // Contains mime's rubber stamp --> Either fix this, remove the category, or remove this comment if intentional.
|
||||
"/Maps/reach.yml", // Contains handheld crew monitor
|
||||
"/Maps/Shuttles/ShuttleEvent/cruiser.yml", // Contains LSE-1200c "Perforator"
|
||||
"/Maps/Shuttles/ShuttleEvent/honki.yml", // Contains golden honker, clown's rubber stamp
|
||||
"/Maps/Shuttles/ShuttleEvent/instigator.yml", // Contains EXP-320g "Friendship"
|
||||
"/Maps/Shuttles/ShuttleEvent/syndie_evacpod.yml", // Contains syndicate rubber stamp
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts the above globs into regex so your eyes dont bleed trying to add filepaths.
|
||||
/// </summary>
|
||||
private static readonly Regex[] DoNotMapWhiteListRegexes = DoNotMapWhitelist
|
||||
.Select(glob => new Regex(GlobToRegex(glob), RegexOptions.IgnoreCase | RegexOptions.Compiled))
|
||||
.ToArray();
|
||||
|
||||
private static readonly string[] GameMaps =
|
||||
{
|
||||
"Dev",
|
||||
@@ -247,17 +274,30 @@ namespace Content.IntegrationTests.Tests
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
private bool IsWhitelistedForMap(EntProtoId protoId, ResPath map)
|
||||
{
|
||||
if (!DoNotMapWhitelistSpecific.TryGetValue(map.ToString(), out var allowedProtos))
|
||||
return false;
|
||||
|
||||
return allowedProtos.Contains(protoId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that maps do not have any entities that belong to the DoNotMap entity category
|
||||
/// </summary>
|
||||
private void CheckDoNotMap(ResPath map, YamlNode node, IPrototypeManager protoManager)
|
||||
{
|
||||
if (DoNotMapWhitelist.Contains(map.ToString()))
|
||||
return;
|
||||
foreach (var regex in DoNotMapWhiteListRegexes)
|
||||
{
|
||||
if (regex.IsMatch(map.ToString()))
|
||||
return;
|
||||
}
|
||||
|
||||
var yamlEntities = node["entities"];
|
||||
var dnmCategory = protoManager.Index(DoNotMapCategory);
|
||||
|
||||
// Make a set containing all the specific whitelisted proto ids for this map
|
||||
HashSet<EntProtoId> unusedExemptions = DoNotMapWhitelistSpecific.TryGetValue(map.ToString(), out var exemptions) ? new(exemptions) : [];
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var yamlEntity in (YamlSequenceNode)yamlEntities)
|
||||
@@ -268,10 +308,17 @@ namespace Content.IntegrationTests.Tests
|
||||
if (!protoManager.TryIndex(protoId, out var proto))
|
||||
continue;
|
||||
|
||||
Assert.That(!proto.Categories.Contains(dnmCategory),
|
||||
Assert.That(!proto.Categories.Contains(dnmCategory) || IsWhitelistedForMap(protoId, map),
|
||||
$"\nMap {map} contains entities in the DO NOT MAP category ({proto.Name})");
|
||||
|
||||
// The proto id is used on this map, so remove it from the set
|
||||
unusedExemptions.Remove(protoId);
|
||||
}
|
||||
});
|
||||
|
||||
// If there are any proto ids left, they must not have been used in the map!
|
||||
Assert.That(unusedExemptions, Is.Empty,
|
||||
$"Map {map} has DO NOT MAP entities whitelisted that are not present in the map: {string.Join(", ", unusedExemptions)}");
|
||||
}
|
||||
|
||||
private bool IsPreInit(ResPath map,
|
||||
@@ -332,7 +379,7 @@ namespace Content.IntegrationTests.Tests
|
||||
MapId mapId;
|
||||
try
|
||||
{
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
var opts = DeserializationOptions.Default with { InitializeMaps = true };
|
||||
ticker.LoadGameMap(protoManager.Index<GameMapPrototype>(mapProto), out mapId, opts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -439,7 +486,7 @@ namespace Content.IntegrationTests.Tests
|
||||
#nullable enable
|
||||
while (queryPoint.MoveNext(out T? comp, out var xform))
|
||||
{
|
||||
var spawner = (ISpawnPoint) comp;
|
||||
var spawner = (ISpawnPoint)comp;
|
||||
|
||||
if (spawner.SpawnType is not SpawnPointType.LateJoin
|
||||
|| xform.GridUid == null
|
||||
@@ -553,5 +600,20 @@ namespace Content.IntegrationTests.Tests
|
||||
await server.WaitRunTicks(1);
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lets us the convert the filepaths to regex without eyeglaze trying to add new paths.
|
||||
/// </summary>
|
||||
private static string GlobToRegex(string glob)
|
||||
{
|
||||
var regex = Regex.Escape(glob)
|
||||
.Replace(@"\*\*", "**") // replace **
|
||||
.Replace(@"\*", "*") // replace *
|
||||
.Replace("**", ".*") // ** → match across folders
|
||||
.Replace("*", @"[^/]*") // * → match within a single folder
|
||||
.Replace(@"\?", "."); // ? → any single character
|
||||
|
||||
return $"^{regex}$";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user