2025-05-01 01:48:08 -07:00

428 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using UnityEngine;
using UnityEngine.ResourceManagement.Util;
using Object = UnityEngine.Object;
namespace UnityEditor.AddressableAssets.Tests
{
/*
* This is a series of tests to verify that our serialization is deterministic and does not
* trigger version control changes.
*
* If you get instabilities or one-off test failures, it's probably because something has
* changed in serialization of the object in question and we're not sorting the output.
*
* This test is made to be unstable if there are changes that are not deterministic. So if
* you see intermittent failures that's the sign there's a bug and should NOT be ignored.
*/
public class SerializationTests : AddressableAssetTestBase
{
public string groupGuid = "422b6705-092c-4699-b57b-abfe7a6245d0";
private System.Random m_Rnd;
private int m_Seed = 0;
private List<Type> m_SchemaTypes;
private string m_PackagePath;
private void Shuffle<T>(List<T> toShuffle)
{
toShuffle.Sort((x, y) => m_Rnd.Next() - m_Rnd.Next());
}
[OneTimeSetUp]
public new void Init()
{
base.Init();
m_Rnd = new System.Random(m_Seed);
m_SchemaTypes = new ()
{
typeof(ContentUpdateGroupSchema), typeof(BundledAssetGroupSchema)
};
}
//
[SetUp]
public void Setup()
{
Settings.groups.Clear();
// this lazy creates the default group
var defaultGroup = Settings.DefaultGroup;
// the way our package isolation tests work the tests are built into their own package
// which means we have to load expected files from a different location
m_PackagePath = "Packages/com.unity.addressables/Tests/Editor";
if (Directory.Exists("Packages/com.unity.addressables.tests"))
{
m_PackagePath = "Packages/com.unity.addressables.tests/Tests/Editor";
}
}
internal string GetExpectedPath(string filename)
{
return $"{m_PackagePath}/Expected/{filename}";
}
internal string GetFixturePath(string filename)
{
return $"{m_PackagePath}/Fixtures/{filename}";
}
[TestCase]
public void TestAssetGroupSerialization()
{
var group = Settings.CreateGroup("testGroup", false, false, false,
new List<AddressableAssetGroupSchema>(), m_SchemaTypes.ToArray());
var labels = CreateAndShuffleLabels();
AddAssetEntries(group, labels);
EditorUtility.SetDirty(group);
AssetDatabase.SaveAssetIfDirty(group);
AssetDatabase.SaveAssetIfDirty(Settings);
var groupPath = AssetDatabase.GetAssetPath(group);
RemapMetaGuids(group);
AssetDatabase.Refresh();
group = AssetDatabase.LoadAssetAtPath<AddressableAssetGroup>(groupPath);
Assert.AreEqual("16cd2736586abc441a3ef8bffa03b61f", group.Guid);
Shuffle(group.m_SerializeEntries);
Shuffle(group.Schemas);
EditorUtility.SetDirty(group);
AssetDatabase.SaveAssetIfDirty(group);
var expectedSerializedGroup = File.ReadAllText(GetExpectedPath("~SerializationTests_Group.unity"));
var serializedGroup = File.ReadAllText(groupPath);
AssertSerializedAreEqual(expectedSerializedGroup, serializedGroup);
}
private List<List<string>> CreateAndShuffleLabels()
{
var labels = new List<List<string>>
{
new() {"c", "a", "b"},
new() {"a5", "a2", "a"},
new() {"5", "22", "2"}
};
foreach (var label in labels)
{
Shuffle<string>(label);
}
return labels;
}
private void AddAssetEntries(AddressableAssetGroup group, List<List<string>> labels)
{
var entry1 = new AddressableAssetEntry("4df50598-ce2c-4265-a0f9-4e943a2991b0", "secondAsset", group, false);
foreach (var label in labels[0])
{
entry1.SetLabel(label, true, false, false);
}
var entry2 = new AddressableAssetEntry("2269b1fb-67ee-4b32-a936-4647ff4c45b4", "firstAsset", group, false);
foreach (var label in labels[1])
{
entry2.SetLabel(label, true, false, false);
}
var entry3 = new AddressableAssetEntry("9e86b64f-f58e-4d4f-aa9d-6e8be96505ec", "thirdAsset", group, false);
foreach (var label in labels[2])
{
entry3.SetLabel(label, true, false, false);
}
group.AddAssetEntry(entry1);
group.AddAssetEntry(entry2);
group.AddAssetEntry(entry3);
}
[TestCase]
public void TestAssetGroupTemplateSerialization()
{
var newAssetGroupTemplate = Settings.CreateAndAddGroupTemplateInternal("myTemplate", "my description\nwith carriage return", m_SchemaTypes.ToArray());
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(newAssetGroupTemplate, out string guid, out long templateFileId);
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(newAssetGroupTemplate.GetSchemaByType(typeof(ContentUpdateGroupSchema)), out string cugsguid, out long cugsFileId);
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(newAssetGroupTemplate.GetSchemaByType(typeof(BundledAssetGroupSchema)), out string bagsguid, out long bagsFileId);
var monoBehaviorMap = new Dictionary<string, string>()
{
{bagsFileId.ToString(), "-6794523166426839361"},
{cugsFileId.ToString(), "-1107740541918034454"},
{templateFileId.ToString(), "11400000"},
};
RemapMetaGuids(null, monoBehaviorMap);
AssetDatabase.Refresh();
Shuffle(newAssetGroupTemplate.SchemaObjects);
EditorUtility.SetDirty(newAssetGroupTemplate);
AssetDatabase.SaveAssetIfDirty(newAssetGroupTemplate);
var expectedSerializedTemplate = File.ReadAllText(GetExpectedPath("~SerializationTests_GroupTemplate.unity"));
var serializedTemplate = File.ReadAllText(AssetDatabase.GetAssetPath(newAssetGroupTemplate));
AssertSerializedAreEqual(serializedTemplate, expectedSerializedTemplate);
}
[TestCase]
public void TestProfileDataSourceSettingsSerialization()
{
var profileDataSourceSettings = ProfileDataSourceSettings.Create(ConfigFolder, "ProfileDataSourceSettings");
// another profile is added when CCD_ENABLED is defined. We remove that to keep the test consistent.
DeleteCcdProfile(profileDataSourceSettings);
AddProfileGroupTypes(profileDataSourceSettings);
AddEnvironments(profileDataSourceSettings);
AssetDatabase.SaveAssetIfDirty(profileDataSourceSettings);
RemapMetaGuids(null);
AssetDatabase.Refresh();
// shuffle
Shuffle(profileDataSourceSettings.profileGroupTypes);
foreach (var groupType in profileDataSourceSettings.profileGroupTypes)
{
Shuffle(groupType.Variables);
}
Shuffle(profileDataSourceSettings.environments);
AssetDatabase.SaveAssetIfDirty(profileDataSourceSettings);
var expectedProfileDataSourceSettings = File.ReadAllText(GetExpectedPath("~SerializationTests_ProfileDataSourceSettings.unity"));
var serializedProfileDataSourceSettings = File.ReadAllText(AssetDatabase.GetAssetPath(profileDataSourceSettings));
AssertSerializedAreEqual(expectedProfileDataSourceSettings, serializedProfileDataSourceSettings);
}
private void AddProfileGroupTypes(ProfileDataSourceSettings profileDataSourceSettings)
{
ProfileGroupType profileGroupType = new ProfileGroupType("testPrefix");
profileGroupType.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kBuildPath, "Build/"));
profileGroupType.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kLoadPath, "https://example.com/a/"));
profileGroupType.AddVariable(new ProfileGroupType.GroupTypeVariable(ProfileDataSourceSettings.ENVIRONMENT_NAME, "production"));
profileDataSourceSettings.profileGroupTypes.Add(profileGroupType);
}
private void DeleteCcdProfile(ProfileDataSourceSettings profileDataSourceSettings)
{
var toDelete = profileDataSourceSettings.profileGroupTypes.Find((x) => x.GroupTypePrefix == "Automatic");
if (toDelete!= null)
{
profileDataSourceSettings.profileGroupTypes.Remove(toDelete);
}
}
private void AddEnvironments(ProfileDataSourceSettings profileDataSourceSettings)
{
profileDataSourceSettings.environments = new List<ProfileDataSourceSettings.Environment>
{
new() {name = "production", id = "0214d8a4-af63-4534-814f-431d180926d6"},
new() {name = "staging", id = "7c9f9aeb-2b9d-4258-bf52-0d2b6118ac39"},
new() {name = "development", id = "a9ec150c-9a63-46ab-9a21-a3434afc7ab3"}
};
}
[TestCase]
public void TestAddressableAssetSettingsSerialization()
{
var group = Settings.CreateGroup("testGroup", false, false, false,
new List<AddressableAssetGroupSchema>(), m_SchemaTypes.ToArray());
Settings.DefaultGroup = Settings.groups.Find((g) => g.Default);
AddProfile();
AddInitializationObjects();
AssetDatabase.SaveAssetIfDirty(Settings);
RemapMetaGuids(group);
AssetDatabase.Refresh();
// shuffle
foreach (var profile in Settings.profileSettings.profiles)
{
Shuffle(profile.values);
}
Shuffle(Settings.profileSettings.profiles);
Shuffle(Settings.profileSettings.profileEntryNames);
Shuffle(Settings.groups);
EditorUtility.SetDirty(Settings);
AssetDatabase.SaveAssetIfDirty(Settings);
#if CCD_3_OR_NEWER
var expectedSerializedSettings = File.ReadAllText(GetExpectedPath("~SerializationTests_AddressableAssetSettings.ccd3.unity"));
#elif ENABLE_CCD
var expectedSerializedSettings = File.ReadAllText(GetExpectedPath("~SerializationTests_AddressableAssetSettings.ccd2.unity"));
#else
var expectedSerializedSettings = File.ReadAllText(GetExpectedPath("~SerializationTests_AddressableAssetSettings.unity"));
#endif
var serializedSettings = File.ReadAllText(AssetDatabase.GetAssetPath(Settings));
AssertSerializedAreEqual(expectedSerializedSettings, serializedSettings);
}
private void AddProfile()
{
// ok so we need to add a profile here
Settings.profileSettings.AddProfile("testProfile", null);
}
private void AddInitializationObjects()
{
Settings.AddInitializationObject(AssetDatabase.LoadAssetAtPath<ScriptableObject>(GetFixturePath("InitFixture1.asset")) as IObjectInitializationDataProvider);
Settings.AddInitializationObject(AssetDatabase.LoadAssetAtPath<ScriptableObject>(GetFixturePath("InitFixture2.asset")) as IObjectInitializationDataProvider);
}
private void AddProfileValueMappings(Dictionary<string, string> mappings)
{
foreach (var profile in Settings.profileSettings.profiles)
{
foreach (var value in profile.values)
{
switch (value.value)
{
case "[UnityEditor.EditorUserBuildSettings.activeBuildTarget]":
mappings.TryAdd(value.id, "0507cc90998e0a04f94da7055d0cc638");
break;
case "[UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]":
mappings.TryAdd(value.id, "8f726afcd2923be469c05fdfc9963d44");
break;
case "{UnityEngine.AddressableAssets.Addressables.RuntimePath}/[BuildTarget]":
mappings.TryAdd(value.id, "44693b9e8b6e8ab4e8bada109cd9e70d");
break;
case "ServerData/[BuildTarget]":
mappings.TryAdd(value.id, "0d5680944a6e4bb47a35cd423b95cb36");
break;
case "<undefined>":
mappings.TryAdd(value.id, "42ec52cd576b8b9439d83010f809d5b5");
break;
default:
throw new Exception($"unknown value in profile settings {value.value}");
}
}
}
}
private void AddProfileEntryMappings(Dictionary<string, string> mappings)
{
foreach (var profileEntryName in Settings.profileSettings.profileEntryNames)
{
switch (profileEntryName.ProfileName)
{
case "BuildTarget":
mappings.TryAdd(profileEntryName.m_Id, "0507cc90998e0a04f94da7055d0cc638");
break;
case "Local.BuildPath":
mappings.TryAdd(profileEntryName.m_Id, "8f726afcd2923be469c05fdfc9963d44");
break;
case "Local.LoadPath":
mappings.TryAdd(profileEntryName.m_Id, "44693b9e8b6e8ab4e8bada109cd9e70d");
break;
case "Remote.BuildPath":
mappings.TryAdd(profileEntryName.m_Id, "0d5680944a6e4bb47a35cd423b95cb36");
break;
case "Remote.LoadPath":
mappings.TryAdd(profileEntryName.m_Id, "42ec52cd576b8b9439d83010f809d5b5");
break;
default:
throw new Exception($"unknown value in profile entry names {profileEntryName.ProfileName}");
}
}
}
private void RemapMetaGuids(AddressableAssetGroup group)
{
var remapped = new Dictionary<string, string>();
RemapMetaGuids(group, remapped);
}
private void RemapMetaGuids(AddressableAssetGroup group, Dictionary<string, string> remapped)
{
// we should be order by GUID
var defaultGroup = Settings.groups.Find((v) => v.Default);
// var secondGroup = Settings.groups.Find((v) => !v.Default);
var buildScriptFast = Settings.DataBuilders.Find((b) => b.name == "BuildScriptFastMode");
var buildScriptPackedPlay = Settings.DataBuilders.Find((b) => b.name == "BuildScriptPackedPlayMode");
var buildScriptPacked = Settings.DataBuilders.Find((b) => b.name == "BuildScriptPackedMode");
var defaultProfile = Settings.profileSettings.profiles.Find((p) => p.profileName == "Default");
var secondProfile = Settings.profileSettings.profiles.Find((p) => p.profileName != "Default");
// this mapping is from the GUID in the file (ex. ~testAddressableAssetSettings.unity) to the currently in use guid
// we replace all the current guids with our static guids so that we can compare the sorting
remapped.Add(GetMetaGuidFromObject(Settings), "3bf47571d203fa84d8dd31832e7c9339");
remapped.Add(GetMetaGuidFromObject(buildScriptFast), "271b00b9e756a6d448f4ae7a08b88509");
remapped.Add(GetMetaGuidFromObject(buildScriptPacked), "1694decfa7f2ffd4983ca3978e171998");
remapped.Add(GetMetaGuidFromObject(buildScriptPackedPlay), "533ad9bddde5e2540a2a76c0203d0acb");
if (Settings?.DefaultGroup?.Guid != null) {
remapped.Add(Settings.DefaultGroup.Guid, "73831a73d82c83d4183d7e0477f7e745");
}
if (defaultGroup != null) {
remapped.Add(GetMetaGuidFromObject(defaultGroup), "2308cd47506141c4aae9737b7d567105");
}
if (Settings.GroupTemplateObjects.Count > 0) {
remapped.Add(GetMetaGuidFromObject(Settings.GroupTemplateObjects[0]), "97a492a095c0434448524d71cc7f0b0d");
}
if (group != null) {
remapped.TryAdd(group.Guid, "16cd2736586abc441a3ef8bffa03b61f");
remapped.TryAdd(GetMetaGuidFromObject(group), "e3940d5982f85734ca7aec5a9b7a90ee");
remapped.Add(GetMetaGuidFromObject(group.Schemas[0]), "798716054e8a18a479c179e6d6f5ad2d");
remapped.Add(GetMetaGuidFromObject(group.Schemas[1]), "7991916e228786548a8c905c2235f71f");
}
if (defaultProfile != null) {
remapped.Add(defaultProfile.id, "5550bbbe2a7ee8c4f9d4600df43218be");
}
if (secondProfile != null) {
remapped.Add(secondProfile.id, "c901f922cc200454b815a5b33a8427c6");
}
AddProfileValueMappings(remapped);
AddProfileEntryMappings(remapped);
RemapFiles(remapped, ConfigFolder);
// clear any caches
Settings.groups.Clear(); // this should be repopulated on AssetDatabase.Refresh()
Settings.ClearFindAssetEntryCache();
}
private void RemapFiles(Dictionary<string, string> mappings, string dirName)
{
foreach (string dir in Directory.EnumerateDirectories(dirName))
{
RemapFiles(mappings, dir);
}
foreach (string file in Directory.EnumerateFiles(dirName))
{
string line;
var inFile = file;
var outFile = $"{file}.tmp";
var reader = new StreamReader(inFile);
var writer = new StreamWriter(outFile);
while ((line = reader.ReadLine()) != null)
{
foreach (var pair in mappings)
{
if (line.Contains(pair.Key))
{
line = line.Replace(pair.Key, pair.Value);
}
}
writer.WriteLine(line);
}
reader.Close();
writer.Close();
File.Delete(inFile);
File.Move(outFile, inFile);
}
}
private string GetMetaGuidFromObject(Object obj)
{
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out string guid, out long templateFileId);
return guid;
}
private void AssertSerializedAreEqual(string expected, string actual)
{
expected = expected.Replace("\r\n", "\n");
actual = actual.Replace("\r\n", "\n");
Assert.AreEqual(expected, actual);
}
}
}