Files
AssetUsageAudit/Source/AssetUsageAuditTests/Private/AssetUsagePaths.spec.cpp
T
Admin 000cc5735c fix: correct authorship attribution to MagentaDolphin
The plugin was authored outside studio work but carried NextGenium
attribution in the .uplugin descriptor and in every source header.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-07 17:35:48 +07:00

164 lines
6.4 KiB
C++

// MagentaDolphin 2026. Asset Usage Audit.
#include "AssetUsagePaths.h"
#include "Misc/AutomationTest.h"
#if WITH_DEV_AUTOMATION_TESTS
BEGIN_DEFINE_SPEC(AssetUsagePathsSpec,
"AssetUsageAudit.Paths",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
END_DEFINE_SPEC(AssetUsagePathsSpec)
void AssetUsagePathsSpec::Define()
{
Describe("IsExternalPackage", [this]()
{
It("detects an OFPA external actor package", [this]()
{
// Real shape from this project: /Game/__ExternalActors__/Space/Maps/WP_Main/8/AB/<22 chars>
TestTrue(TEXT("external actor"),
AssetUsagePaths::IsExternalPackage(FName(TEXT("/Game/__ExternalActors__/Space/Maps/WP_Main/8/AB/QWERTYUIOPASDFGHJKLZXC"))));
});
It("detects an external objects package", [this]()
{
TestTrue(TEXT("external object"),
AssetUsagePaths::IsExternalPackage(FName(TEXT("/Game/__ExternalObjects__/Space/Maps/L_3i_Hub/1/2A/ABC"))));
});
It("does not flag an ordinary content package", [this]()
{
TestFalse(TEXT("ordinary asset"),
AssetUsagePaths::IsExternalPackage(FName(TEXT("/Game/Space/Art/SM_Rock"))));
});
It("does not flag a level that merely lives next to external actors", [this]()
{
TestFalse(TEXT("the level itself is not external"),
AssetUsagePaths::IsExternalPackage(FName(TEXT("/Game/Space/Maps/WP_Main"))));
});
});
Describe("IsScriptPackage", [this]()
{
It("flags /Script packages", [this]()
{
TestTrue(TEXT("script"), AssetUsagePaths::IsScriptPackage(FName(TEXT("/Script/Space"))));
});
It("does not flag content that starts with the same letters", [this]()
{
TestFalse(TEXT("not script"), AssetUsagePaths::IsScriptPackage(FName(TEXT("/Scripted/Foo"))));
});
});
Describe("NormalizeExclusionPrefix", [this]()
{
It("converts content-relative form to package form", [this]()
{
TestEqual(TEXT("Content/3rdParty"),
AssetUsagePaths::NormalizeExclusionPrefix(TEXT("Content/3rdParty")), FString(TEXT("/Game/3rdParty")));
});
It("strips a trailing slash", [this]()
{
TestEqual(TEXT("trailing slash"),
AssetUsagePaths::NormalizeExclusionPrefix(TEXT("Content/3rdParty/")), FString(TEXT("/Game/3rdParty")));
});
It("leaves package form untouched", [this]()
{
TestEqual(TEXT("already package form"),
AssetUsagePaths::NormalizeExclusionPrefix(TEXT("/Game/Zombie")), FString(TEXT("/Game/Zombie")));
});
It("maps bare Content to the game root", [this]()
{
TestEqual(TEXT("bare Content"),
AssetUsagePaths::NormalizeExclusionPrefix(TEXT("Content")), FString(TEXT("/Game")));
});
It("normalizes backslashes pasted from Explorer", [this]()
{
TestEqual(TEXT("backslashes"),
AssetUsagePaths::NormalizeExclusionPrefix(TEXT("Content\\3rdParty\\Fab")), FString(TEXT("/Game/3rdParty/Fab")));
});
It("returns empty for an empty or slash-only prefix", [this]()
{
TestTrue(TEXT("empty"), AssetUsagePaths::NormalizeExclusionPrefix(TEXT("")).IsEmpty());
TestTrue(TEXT("slashes only"), AssetUsagePaths::NormalizeExclusionPrefix(TEXT("///")).IsEmpty());
});
});
Describe("IsPathExcluded", [this]()
{
It("excludes a package under the prefix", [this]()
{
const TArray<FString> Excluded = { TEXT("Content/3rdParty") };
TestTrue(TEXT("under prefix"),
AssetUsagePaths::IsPathExcluded(FName(TEXT("/Game/3rdParty/Galaxy/SM_Star")), Excluded));
});
It("does not exclude a sibling folder sharing a name prefix", [this]()
{
// The bug this guards: a naive StartsWith would exclude /Game/ArtSource when the user
// only asked to exclude /Game/Art.
const TArray<FString> Excluded = { TEXT("/Game/Art") };
TestFalse(TEXT("ArtSource must survive"),
AssetUsagePaths::IsPathExcluded(FName(TEXT("/Game/ArtSource/SM_Rock")), Excluded));
TestTrue(TEXT("Art itself is excluded"),
AssetUsagePaths::IsPathExcluded(FName(TEXT("/Game/Art/SM_Rock")), Excluded));
});
It("matches case-insensitively", [this]()
{
const TArray<FString> Excluded = { TEXT("content/ZOMBIE") };
TestTrue(TEXT("case insensitive"),
AssetUsagePaths::IsPathExcluded(FName(TEXT("/Game/Zombie/SK_Walker")), Excluded));
});
It("returns false for an empty exclusion list", [this]()
{
const TArray<FString> Excluded;
TestFalse(TEXT("nothing excluded"),
AssetUsagePaths::IsPathExcluded(FName(TEXT("/Game/Space/Art/SM_Rock")), Excluded));
});
});
Describe("ToProjectRelativePath", [this]()
{
It("converts an asset package", [this]()
{
TestEqual(TEXT("asset"),
AssetUsagePaths::ToProjectRelativePath(FName(TEXT("/Game/Space/Art/SM_Rock")), false),
FString(TEXT("Content/Space/Art/SM_Rock.uasset")));
});
It("uses the umap extension for levels", [this]()
{
TestEqual(TEXT("level"),
AssetUsagePaths::ToProjectRelativePath(FName(TEXT("/Game/Space/Maps/WP_Main")), true),
FString(TEXT("Content/Space/Maps/WP_Main.umap")));
});
It("returns empty for script and engine packages rather than inventing a path", [this]()
{
TestTrue(TEXT("script"),
AssetUsagePaths::ToProjectRelativePath(FName(TEXT("/Script/Space")), false).IsEmpty());
TestTrue(TEXT("engine"),
AssetUsagePaths::ToProjectRelativePath(FName(TEXT("/Engine/BasicShapes/Cube")), false).IsEmpty());
});
It("converts an external actor package, which is still under /Game", [this]()
{
TestEqual(TEXT("external actor"),
AssetUsagePaths::ToProjectRelativePath(FName(TEXT("/Game/__ExternalActors__/Space/Maps/WP_Main/8/AB/ABC")), false),
FString(TEXT("Content/__ExternalActors__/Space/Maps/WP_Main/8/AB/ABC.uasset")));
});
});
}
#endif // WITH_DEV_AUTOMATION_TESTS