using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using NUnit.Framework; using UnityEditor.AddressableAssets.Build; using UnityEditor.AddressableAssets.Build.DataBuilders; using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEditor.Build.Pipeline.Utilities; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.TestTools; namespace UnityEditor.AddressableAssets.Tests { public class BuildScriptTests : AddressableAssetTestBase { [TestFixture] class StreamingAssetTests : AddressableAssetTestBase { [SetUp] public void Setup() { DirectoryUtility.DeleteDirectory(Application.streamingAssetsPath, recursiveDelete: true); } [TearDown] public void TearDown() { DirectoryUtility.DeleteDirectory(Application.streamingAssetsPath, recursiveDelete: true); } [Test] public void ClearCachedData_CleansStreamingAssetFolder() { var context = new AddressablesDataBuilderInput(Settings); int builderCount = 0; for (int i = 0; i < Settings.DataBuilders.Count; i++) { var builder = Settings.DataBuilders[i] as IDataBuilder; if (builder.CanBuildData()) { builderCount++; var existingFiles = new HashSet(); if (System.IO.Directory.Exists("Assets/StreamingAssets")) { foreach (var f in System.IO.Directory.GetFiles("Assets/StreamingAssets")) existingFiles.Add(f); } builder.BuildData(context); builder.ClearCachedData(); if (System.IO.Directory.Exists("Assets/StreamingAssets")) { foreach (var f in System.IO.Directory.GetFiles("Assets/StreamingAssets")) Assert.IsTrue(existingFiles.Contains(f), string.Format("Data Builder {0} did not clean up file {1}", builder.Name, f)); } } } Assert.IsTrue(builderCount > 0); } [Test] public void Folder_WithSubAssets_GetsBundleFileIdAssigned_DuringBuild() { var context = new AddressablesDataBuilderInput(Settings); string folderGuid = AssetDatabase.CreateFolder(TestFolder, "FolderAsset"); string folderPath = $"{TestFolder}/FolderAsset"; PrefabUtility.SaveAsPrefabAsset(new GameObject(), $"{folderPath}/subfolderprefab.prefab"); AddressableAssetEntry folderEntry = Settings.CreateOrMoveEntry(folderGuid, Settings.DefaultGroup); Assert.IsTrue(string.IsNullOrEmpty(folderEntry.BundleFileId)); Settings.ActivePlayerDataBuilder.BuildData(context); Assert.IsTrue(folderEntry.IsFolder); Assert.IsFalse(string.IsNullOrEmpty(folderEntry.BundleFileId)); //Cleanup AssetDatabase.DeleteAsset(folderPath); Settings.RemoveAssetEntry(folderEntry); } [Test] public void Folder_WithNoSubAssets_DoesNotThrowErrorDuringBuild() { var context = new AddressablesDataBuilderInput(Settings); string folderGuid = AssetDatabase.CreateFolder(TestFolder, "FolderAsset"); string folderPath = $"{TestFolder}/FolderAsset"; AddressableAssetEntry folderEntry = Settings.CreateOrMoveEntry(folderGuid, Settings.DefaultGroup); Assert.IsTrue(string.IsNullOrEmpty(folderEntry.BundleFileId)); Settings.ActivePlayerDataBuilder.BuildData(context); Assert.IsTrue(folderEntry.IsFolder); Assert.IsTrue(string.IsNullOrEmpty(folderEntry.BundleFileId)); //Cleanup AssetDatabase.DeleteAsset(folderPath); Settings.RemoveAssetEntry(folderEntry); } [Test] public void Folder_DoesNotAssignBundleFileId_ForDynamicallyCreatedSubEntries() { var context = new AddressablesDataBuilderInput(Settings); string folderGuid = AssetDatabase.CreateFolder(TestFolder, "FolderAsset"); string folderPath = $"{TestFolder}/FolderAsset"; PrefabUtility.SaveAsPrefabAsset(new GameObject(), $"{folderPath}/subfolderprefab.prefab"); AddressableAssetEntry folderEntry = Settings.CreateOrMoveEntry(folderGuid, Settings.DefaultGroup); Settings.ActivePlayerDataBuilder.BuildData(context); Assert.True(string.IsNullOrEmpty(folderEntry.SubAssets[0].BundleFileId)); //Cleanup AssetDatabase.DeleteAsset(folderPath); Settings.RemoveAssetEntry(folderEntry); } #if !UNITY_2021_2_OR_NEWER [Test] public void CopiedStreamingAssetAreCorrectlyDeleted_DirectoriesWithoutImport() { var context = new AddressablesDataBuilderInput(Settings); int builderCount = 0; for (int i = 0; i < Settings.DataBuilders.Count; i++) { var builder = Settings.DataBuilders[i] as IDataBuilder; if (builder.CanBuildData()) { builderCount++; // confirm that StreamingAssets does not exists before the test Assert.IsFalse(Directory.Exists("Assets/StreamingAssets")); builder.BuildData(context); Assert.IsTrue(Directory.Exists(Addressables.BuildPath)); AddressablesPlayerBuildProcessor.CopyTemporaryPlayerBuildData(); builder.ClearCachedData(); Assert.IsTrue(Directory.Exists(Addressables.PlayerBuildDataPath)); AddressablesPlayerBuildProcessor.CleanTemporaryPlayerBuildData(); Assert.IsFalse(Directory.Exists(Addressables.PlayerBuildDataPath)); Assert.IsFalse(Directory.Exists("Assets/StreamingAssets")); } } Assert.IsTrue(builderCount > 0); } [Test] public void CopiedStreamingAssetAreCorrectlyDeleted_MetaFilesWithImport() { var context = new AddressablesDataBuilderInput(Settings); int builderCount = 0; for (int i = 0; i < Settings.DataBuilders.Count; i++) { var builder = Settings.DataBuilders[i] as IDataBuilder; if (builder.CanBuildData()) { builderCount++; // confirm that StreamingAssets does not exists before the test DirectoryUtility.DeleteDirectory(Application.streamingAssetsPath, recursiveDelete: true); Assert.IsFalse(Directory.Exists("Assets/StreamingAssets")); builder.BuildData(context); Assert.IsTrue(Directory.Exists(Addressables.BuildPath)); AddressablesPlayerBuildProcessor.CopyTemporaryPlayerBuildData(); builder.ClearCachedData(); // confirm that PlayerBuildDataPath is imported to AssetDatabase AssetDatabase.Refresh(); Assert.IsTrue(Directory.Exists(Addressables.PlayerBuildDataPath)); Assert.IsTrue(File.Exists(Addressables.PlayerBuildDataPath + ".meta")); string relativePath = Addressables.PlayerBuildDataPath.Replace(Application.dataPath, "Assets"); Assert.IsTrue(AssetDatabase.IsValidFolder(relativePath), "Copied StreamingAssets folder was not importer as expected"); AddressablesPlayerBuildProcessor.CleanTemporaryPlayerBuildData(); Assert.IsFalse(Directory.Exists(Addressables.PlayerBuildDataPath)); Assert.IsFalse(Directory.Exists("Assets/StreamingAssets")); } } Assert.IsTrue(builderCount > 0); } [Test] public void CopiedStreamingAssetAreCorrectlyDeleted_WithExistingFiles() { var context = new AddressablesDataBuilderInput(Settings); int builderCount = 0; for (int i = 0; i < Settings.DataBuilders.Count; i++) { var builder = Settings.DataBuilders[i] as IDataBuilder; if (builder.CanBuildData()) { builderCount++; // confirm that StreamingAssets does not exists before the test DirectoryUtility.DeleteDirectory(Application.streamingAssetsPath, recursiveDelete: true); Assert.IsFalse(Directory.Exists("Assets/StreamingAssets")); // create StreamingAssets and an extra folder as existing content AssetDatabase.CreateFolder("Assets", "StreamingAssets"); AssetDatabase.CreateFolder("Assets/StreamingAssets", "extraFolder"); builder.BuildData(context); Assert.IsTrue(Directory.Exists(Addressables.BuildPath)); AddressablesPlayerBuildProcessor.CopyTemporaryPlayerBuildData(); builder.ClearCachedData(); Assert.IsTrue(Directory.Exists(Addressables.PlayerBuildDataPath)); AddressablesPlayerBuildProcessor.CleanTemporaryPlayerBuildData(); Assert.IsFalse(Directory.Exists(Addressables.PlayerBuildDataPath)); Assert.IsTrue(Directory.Exists("Assets/StreamingAssets")); Assert.IsTrue(Directory.Exists("Assets/StreamingAssets/extraFolder")); AssetDatabase.DeleteAsset("Assets/StreamingAssets"); } } Assert.IsTrue(builderCount > 0); } #else [Test] public void AddressablesBuildPlayerProcessor_IncludeAdditionalStreamingAssetsWhenExist() { string path = Addressables.BuildPath; Directory.CreateDirectory(path); var paths = AddressablesPlayerBuildProcessor.GetStreamingAssetPaths(); Assert.AreEqual(1, paths.Count, "StreamingAssets paths expected to include Addressables.BuildPath"); // cleanup Directory.Delete(path); } [Test] public void AddressablesBuildPlayerProcessor_DoesntIncludeAdditionalStreamingAssetsWhenDontExist() { if (Directory.Exists(Addressables.BuildPath)) DirectoryUtility.DeleteDirectory(Addressables.BuildPath, false); var paths = AddressablesPlayerBuildProcessor.GetStreamingAssetPaths(); Assert.AreEqual(0, paths.Count, "StreamingAssets paths are expected to be empty"); // cleanup } #endif } [Test] public void BuildScriptBase_FailsCanBuildData() { var buildScript = ScriptableObject.CreateInstance(); Assert.IsFalse(buildScript.CanBuildData()); Assert.IsFalse(buildScript.CanBuildData()); Assert.IsFalse(buildScript.CanBuildData()); Assert.IsFalse(buildScript.CanBuildData()); } internal class BuildScriptTestClass : BuildScriptBase { public override string Name { get { return "Test Script"; } } public override bool CanBuildData() { return typeof(T).IsAssignableFrom(typeof(AddressablesPlayModeBuildResult)); } protected override TResult BuildDataImplementation(AddressablesDataBuilderInput builderInput) { Debug.LogError("Inside BuildDataInternal for test script!"); return base.BuildDataImplementation(builderInput); } } [Test] public void BuildScript_DoesNotBuildWrongDataType() { var context = new AddressablesDataBuilderInput(Settings); var baseScript = ScriptableObject.CreateInstance(); baseScript.BuildData(context); LogAssert.Expect(LogType.Error, new Regex("Data builder Test Script cannot build requested type.*")); baseScript.BuildData(context); LogAssert.Expect(LogType.Error, "Inside BuildDataInternal for test script!"); } [Test] public void GetNameWithHashNaming_ReturnsNoChangeIfNoHash() { string source = "x/y.bundle"; string hash = "123abc"; string expected = "x/y.bundle"; var actual = BuildUtility.GetNameWithHashNaming(BundledAssetGroupSchema.BundleNamingStyle.NoHash, hash, source); Assert.AreEqual(expected, actual); } [Test] public void GetNameWithHashNaming_CanAppendHash() { string source = "x/y.bundle"; string hash = "123abc"; string expected = "x/y_123abc.bundle"; var actual = BuildUtility.GetNameWithHashNaming(BundledAssetGroupSchema.BundleNamingStyle.AppendHash, hash, source); Assert.AreEqual(expected, actual); } [Test] public void GetNameWithHashNaming_CanReplaceFileNameWithHash() { string source = "x/y.bundle"; string hash = "123abc"; string expected = "123abc.bundle"; var actual = BuildUtility.GetNameWithHashNaming(BundledAssetGroupSchema.BundleNamingStyle.OnlyHash, hash, source); Assert.AreEqual(expected, actual); } [Test] public void GetNameWithHashNaming_CanReplaceFileNameWithFileNameHash() { string source = "x/y.bundle"; string hash = HashingMethods.Calculate(source).ToString(); string expected = hash + ".bundle"; var actual = BuildUtility.GetNameWithHashNaming(BundledAssetGroupSchema.BundleNamingStyle.FileNameHash, hash, source); Assert.AreEqual(expected, actual); } // regression test for https://jira.unity3d.com/browse/ADDR-1292 [Test] public void BuildScriptBaseWriteBuildLog_WhenDirectoryDoesNotExist_DirectoryCreated() { string dirName = "SomeTestDir"; string logFile = Path.Combine(dirName, "AddressablesBuildTEP.json"); try { BuildLog log = new BuildLog(); BuildScriptBase.WriteBuildLog(log, dirName); FileAssert.Exists(logFile); } finally { Directory.Delete(dirName, true); } } [Test] public void Building_CreatesPerformanceReportWithMetaData() { Settings.BuildPlayerContentImpl(); string path = Addressables.LibraryPath + "AddressablesBuildTEP.json"; FileAssert.Exists(path); string text = File.ReadAllText(path); StringAssert.Contains("com.unity.addressables", text); } [Test] public void Build_WithInvalidAssetInResourcesFolder_Succeeds() { var path = GetAssetPath("Resources/unknownAsset.plist"); if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(path))) System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); System.IO.File.WriteAllText(path, "nothing"); AssetDatabase.ImportAsset(path); var context = new AddressablesDataBuilderInput(Settings); foreach (IDataBuilder db in Settings.DataBuilders) if (db.CanBuildData()) db.BuildData(context); } [Test] public void Build_GroupWithPlayerDataGroupSchemaAndBundledAssetGroupSchema_LogsError() { const string groupName = "NewGroup"; var schemas = new List {ScriptableObject.CreateInstance(), ScriptableObject.CreateInstance()}; AddressableAssetGroup group = Settings.CreateGroup(groupName, false, false, false, schemas); var context = new AddressablesDataBuilderInput(Settings); foreach (IDataBuilder db in Settings.DataBuilders) { if (db.CanBuildData()) { AddressablesPlayerBuildResult result = db.BuildData(context); Assert.AreEqual(result.Error, $"Addressable group {groupName} cannot have both a {typeof(PlayerDataGroupSchema).Name} and a {typeof(BundledAssetGroupSchema).Name}"); } } Settings.RemoveGroup(group); } // ADDR-1755 [Test] public void WhenBundleLocalCatalogEnabled_BuildScriptPacked_DoesNotCreatePerformanceLogReport() { string logPath = $"Library/com.unity.addressables/aa/{PlatformMappingService.GetPlatformPathSubFolder()}/buildlogtep.json"; if (File.Exists(logPath)) File.Delete(logPath); Settings.BundleLocalCatalog = true; var context = new AddressablesDataBuilderInput(Settings); BuildScriptBase db = (BuildScriptBase)Settings.DataBuilders.Find(x => x.GetType() == typeof(BuildScriptPackedMode)); Assert.IsFalse(File.Exists(logPath)); // make sure file does not exist before build var res = db.BuildData(context); Assert.IsFalse(File.Exists(logPath)); } [Test] public void Build_WithDeletedAsset_Succeeds() { var context = new AddressablesDataBuilderInput(Settings); //make an entry with no actual AssetPath Settings.CreateOrMoveEntry("abcde", Settings.DefaultGroup); Settings.CreateOrMoveEntry(m_AssetGUID, Settings.DefaultGroup); foreach (BuildScriptBase db in Settings.DataBuilders.OfType()) { if (db is BuildScriptPackedPlayMode) continue; if (db.CanBuildData()) { var res = db.BuildData(context); Assert.IsTrue(db.IsDataBuilt()); Assert.IsTrue(string.IsNullOrEmpty(res.Error)); } else if (db.CanBuildData()) { var res = db.BuildData(context); Assert.IsTrue(db.IsDataBuilt()); Assert.IsTrue(string.IsNullOrEmpty(res.Error)); } } Settings.RemoveAssetEntry("abcde", false); Settings.RemoveAssetEntry(m_AssetGUID, false); } [Test] public void WhenAddressHasSquareBrackets_AndContentCatalogsAreCreated_BuildFails() { var context = new AddressablesDataBuilderInput(Settings); AddressableAssetEntry entry = Settings.CreateOrMoveEntry(m_AssetGUID, Settings.DefaultGroup); entry.address = "[test]"; LogAssert.Expect(LogType.Error, $"Address '{entry.address}' cannot contain '[ ]'."); foreach (IDataBuilder db in Settings.DataBuilders) { if (db.GetType() == typeof(BuildScriptFastMode) || db.GetType() == typeof(BuildScriptPackedPlayMode)) continue; if (db.CanBuildData()) db.BuildData(context); else if (db.CanBuildData()) db.BuildData(context); LogAssert.Expect(LogType.Error, "Address '[test]' cannot contain '[ ]'."); } Settings.RemoveAssetEntry(m_AssetGUID, false); } [Test] public void WhenFileTypeIsInvalid_AndContentCatalogsAreCreated_BuildFails() { var context = new AddressablesDataBuilderInput(Settings); string path = GetAssetPath("fake.file"); FileStream fs = File.Create(path); fs.Close(); AssetDatabase.ImportAsset(path); string guid = AssetDatabase.AssetPathToGUID(path); AddressableAssetEntry entry = Settings.CreateOrMoveEntry(guid, Settings.DefaultGroup); foreach (IDataBuilder db in Settings.DataBuilders) { if (db.GetType() == typeof(BuildScriptFastMode) || db.GetType() == typeof(BuildScriptPackedPlayMode)) continue; if (db.CanBuildData()) db.BuildData(context); else if (db.CanBuildData()) db.BuildData(context); LogAssert.Expect(LogType.Error, "Cannot recognize file type for entry located at 'Assets/UnityEditor.AddressableAssets.Tests.BuildScriptTests_Tests/fake.file'. Asset import failed for using an unsupported file type."); } Settings.RemoveAssetEntry(guid, false); AssetDatabase.DeleteAsset(path); } [Test] public void WhenFileTypeIsInvalid_AndContentCatalogsAreCreated_IgnoreUnsupportedFilesInBuildIsSet_BuildSucceedWithWarning() { bool oldValue = Settings.IgnoreUnsupportedFilesInBuild; Settings.IgnoreUnsupportedFilesInBuild = true; var context = new AddressablesDataBuilderInput(Settings); string path = GetAssetPath("fake.file"); FileStream fs = File.Create(path); fs.Close(); AssetDatabase.ImportAsset(path); string guid = AssetDatabase.AssetPathToGUID(path); AddressableAssetEntry entry = Settings.CreateOrMoveEntry(guid, Settings.DefaultGroup); foreach (BuildScriptBase db in Settings.DataBuilders.OfType()) { if (db.GetType() == typeof(BuildScriptFastMode) || db.GetType() == typeof(BuildScriptPackedPlayMode)) continue; if (db.CanBuildData()) { var res = db.BuildData(context); Assert.IsTrue(db.IsDataBuilt()); Assert.IsTrue(string.IsNullOrEmpty(res.Error)); } else if (db.CanBuildData()) { var res = db.BuildData(context); Assert.IsTrue(db.IsDataBuilt()); Assert.IsTrue(string.IsNullOrEmpty(res.Error)); } LogAssert.Expect(LogType.Warning, new Regex($".*{path}.*ignored")); LogAssert.Expect(LogType.Warning, new Regex($".*{path}.*stripped")); } Settings.RemoveAssetEntry(guid, false); AssetDatabase.DeleteAsset(path); Settings.IgnoreUnsupportedFilesInBuild = oldValue; } [Test] public void WhenLoadPathUsesHttp_AndInsecureHttpNotAllowed_BuildLogsWarning() { #if UNITY_2022_1_OR_NEWER InsecureHttpOption oldHttpOption = PlayerSettings.insecureHttpOption; PlayerSettings.insecureHttpOption = InsecureHttpOption.NotAllowed; string remoteLoadPathId = Settings.profileSettings.GetProfileDataByName(AddressableAssetSettings.kRemoteLoadPath).Id; string oldRemoteLoadPath = Settings.profileSettings.GetValueById(Settings.activeProfileId, remoteLoadPathId); Settings.profileSettings.SetValue(Settings.activeProfileId, AddressableAssetSettings.kRemoteLoadPath, "http://insecureconnection/"); AddressableAssetGroup assetGroup = Settings.CreateGroup("InsecureConnections", false, false, false, null, typeof(BundledAssetGroupSchema)); assetGroup.GetSchema().BuildPath.SetVariableById(Settings, Settings.profileSettings.GetProfileDataByName(AddressableAssetSettings.kRemoteBuildPath).Id); assetGroup.GetSchema().LoadPath.SetVariableById(Settings, Settings.profileSettings.GetProfileDataByName(AddressableAssetSettings.kRemoteLoadPath).Id); string assetPath = Path.Combine(TestFolder, "insecureConnectionsTest.prefab"); PrefabUtility.SaveAsPrefabAsset(new GameObject(), assetPath); Settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(assetPath), assetGroup, false, false); var context = new AddressablesDataBuilderInput(Settings); BuildScriptBase db = (BuildScriptBase)Settings.DataBuilders.Find(x => x.GetType() == typeof(BuildScriptPackedMode)); db.BuildData(context); LogAssert.Expect(LogType.Warning, $"Addressable group {assetGroup.Name} uses insecure http for its load path. To allow http connections for UnityWebRequests, change your settings in Edit > Project Settings > Player > Other Settings > Configuration > Allow downloads over HTTP."); Settings.RemoveGroup(assetGroup); Settings.profileSettings.SetValue(Settings.activeProfileId, AddressableAssetSettings.kRemoteLoadPath, oldRemoteLoadPath); AssetDatabase.DeleteAsset(assetPath); PlayerSettings.insecureHttpOption = oldHttpOption; #else Assert.Ignore($"Skipping test {nameof(WhenLoadPathUsesHttp_AndInsecureHttpNotAllowed_BuildLogsWarning)}."); #endif } [Test] public void WhenLoadPathUsesHttp_AndInsecureHttpAllowed_BuildSucceeds() { #if UNITY_2022_1_OR_NEWER InsecureHttpOption oldHttpOption = PlayerSettings.insecureHttpOption; PlayerSettings.insecureHttpOption = InsecureHttpOption.AlwaysAllowed; string remoteLoadPathId = Settings.profileSettings.GetProfileDataByName(AddressableAssetSettings.kRemoteLoadPath).Id; string oldRemoteLoadPath = Settings.profileSettings.GetValueById(Settings.activeProfileId, remoteLoadPathId); Settings.profileSettings.SetValue(Settings.activeProfileId, AddressableAssetSettings.kRemoteLoadPath, "http://insecureconnection/"); AddressableAssetGroup assetGroup = Settings.CreateGroup("InsecureConnections", false, false, false, null, typeof(BundledAssetGroupSchema)); assetGroup.GetSchema().BuildPath.SetVariableById(Settings, Settings.profileSettings.GetProfileDataByName(AddressableAssetSettings.kRemoteBuildPath).Id); assetGroup.GetSchema().LoadPath.SetVariableById(Settings, Settings.profileSettings.GetProfileDataByName(AddressableAssetSettings.kRemoteLoadPath).Id); string assetPath = Path.Combine(TestFolder, "insecureConnectionsTest.prefab"); PrefabUtility.SaveAsPrefabAsset(new GameObject(), assetPath); Settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(assetPath), assetGroup, false, false); var context = new AddressablesDataBuilderInput(Settings); BuildScriptBase db = (BuildScriptBase)Settings.DataBuilders.Find(x => x.GetType() == typeof(BuildScriptPackedMode)); db.BuildData(context); LogAssert.NoUnexpectedReceived(); Settings.RemoveGroup(assetGroup); Settings.profileSettings.SetValue(Settings.activeProfileId, AddressableAssetSettings.kRemoteLoadPath, oldRemoteLoadPath); AssetDatabase.DeleteAsset(assetPath); PlayerSettings.insecureHttpOption = oldHttpOption; #else Assert.Ignore($"Skipping test {nameof(WhenLoadPathUsesHttp_AndInsecureHttpAllowed_BuildSucceeds)}."); #endif } } }