using System; using System.Collections; using System.Collections.Generic; using System.IO; using NUnit.Framework; #if UNITY_EDITOR using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Settings.GroupSchemas; #endif using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets.Initialization; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.ResourceManagement.Util; using UnityEngine.TestTools; namespace AddressableAssetsIntegrationTests { public abstract class CleanBundleCacheTests : AddressablesTestFixture { const int kAssetCount = 5; const int kMaxConcurrentRequests = 3; const string kOldBuildId = "Old"; const string kNewBuildId = "New"; string GetNonRefAsset(int i) { return $"{kOldBuildId}{i}"; } string GetRefAsset(int i) { return $"{kNewBuildId}{i}"; } #if UNITY_EDITOR internal override void Setup(AddressableAssetSettings settings, string tempAssetFolder) { CreateGroup(settings, tempAssetFolder, kOldBuildId, GetNonRefAsset); RunBuilder(settings, m_UniqueTestName + kOldBuildId); settings = AddressableAssetSettings.Create(Path.Combine(tempAssetFolder, "Settings" + kNewBuildId), "AddressableAssetSettings.Tests", false, true); CreateGroup(settings, tempAssetFolder, kNewBuildId, GetRefAsset); RunBuilder(settings, m_UniqueTestName + kNewBuildId); } AddressableAssetGroup CreateGroup(AddressableAssetSettings settings, string tempAssetFolder, string buildId, Func objNaming) { AddressableAssetGroup remoteGroup = settings.CreateGroup($"Remote{buildId}", false, false, true, new List(), typeof(BundledAssetGroupSchema)); remoteGroup.GetSchema().UseUnityWebRequestForLocalBundles = true; remoteGroup.GetSchema().BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackSeparately; remoteGroup.GetSchema().BundleNaming = BundledAssetGroupSchema.BundleNamingStyle.OnlyHash; settings.MaxConcurrentWebRequests = kMaxConcurrentRequests; for (int i = 0; i < kAssetCount; i++) { string s = objNaming(i); string guid = CreatePrefab(tempAssetFolder + $"/{s}.prefab"); AddressableAssetEntry entry = settings.CreateOrMoveEntry(guid, remoteGroup); entry.address = s; } return remoteGroup; } protected override void RunBuilder(AddressableAssetSettings settings) { // Do nothing, we build content during our custom Setup() } #endif [UnitySetUp] public override IEnumerator RuntimeSetup() { // Do nothing, we initialize Addressables in our tests yield return null; } [TearDown] public override void RuntimeTeardown() { // Do nothing, we release Addressables in our tests } protected override void OnRuntimeSetup() { #if ENABLE_CACHING Caching.ClearCache(); #endif Assert.IsNull(m_Addressables); } IEnumerator InitializeSettings(string buildId) { if (m_Addressables != null) m_Addressables.ResourceManager.Dispose(); m_Addressables = new AddressablesImpl(new LRUCacheAllocationStrategy(1000, 1000, 100, 10)); m_RuntimeSettingsPath = m_Addressables.ResolveInternalId(GetRuntimeAddressablesSettingsPath(m_UniqueTestName + buildId)); var op = m_Addressables.InitializeAsync(m_RuntimeSettingsPath, null, false); yield return op; Assert.AreEqual(AsyncOperationStatus.Succeeded, op.Status); if (op.IsValid()) op.Release(); } void ReleaseAddressables() { if (m_Addressables != null) m_Addressables.ResourceManager.Dispose(); m_Addressables = null; } IEnumerator CacheEntries(Func keyNaming) { for (int i = 0; i < kAssetCount; i++) { var op = m_Addressables.LoadAssetAsync(keyNaming(i)); yield return op; op.Release(); } } Dictionary GetCacheEntries(string key) { var cacheEntries = new Dictionary(); m_Addressables.GetResourceLocations(new object[] {key}, typeof(GameObject), Addressables.MergeMode.Intersection, out IList locations); Assert.AreEqual(1, locations?.Count); if (locations[0].HasDependencies) { foreach (IResourceLocation dep in locations[0].Dependencies) { if (dep.Data is AssetBundleRequestOptions options) { if (!cacheEntries.ContainsKey(options.BundleName)) cacheEntries.Add(options.BundleName, options.Hash); } } } return cacheEntries; } #if ENABLE_CACHING HashSet GetAllEntries(Func keyNaming) { var cacheEntries = new HashSet(); for (int i = 0; i < kAssetCount; i++) { var result = GetCacheEntries(keyNaming(i)); foreach (var entry in result) { CachedAssetBundle cab = new CachedAssetBundle(entry.Key, Hash128.Parse(entry.Value)); if (!cacheEntries.Contains(cab)) cacheEntries.Add(cab); } } return cacheEntries; } void AssertEntriesAreRemoved(HashSet entries) { foreach (CachedAssetBundle entry in entries) { Assert.IsFalse(Caching.IsVersionCached(entry)); } } void AssertEntriesArePreserved(HashSet entries) { foreach (CachedAssetBundle entry in entries) { Assert.IsTrue(Caching.IsVersionCached(entry)); } } #endif [UnityTest] [Platform(Exclude = "PS5")] public IEnumerator WhenValidCatalogId_RemovesNonReferencedBundlesFromCache([Values(true, false)] bool forceSingleThreading) { #if ENABLE_CACHING if (BuildScriptMode == TestBuildScriptMode.Fast) Assert.Ignore("Bundle caching does not occur when using this playmode."); yield return InitializeSettings(kOldBuildId); yield return CacheEntries(GetNonRefAsset); HashSet entriesToRemove = GetAllEntries(GetNonRefAsset); yield return InitializeSettings(kNewBuildId); yield return CacheEntries(GetRefAsset); HashSet entriesToPreserve = GetAllEntries(GetRefAsset); entriesToRemove.ExceptWith(entriesToPreserve); string locatorId = m_Addressables.m_ResourceLocators[0].Locator.LocatorId; var handle = m_Addressables.CleanBundleCache(new List {locatorId}, forceSingleThreading); yield return handle; handle.Release(); AssertEntriesAreRemoved(entriesToRemove); AssertEntriesArePreserved(entriesToPreserve); ReleaseAddressables(); #else Assert.Ignore("Caching not enabled."); yield return null; #endif } [UnityTest] [Platform(Exclude = "PS5")] public IEnumerator WhenCatalogIdListNull_UsesLoadedCatalogs_AndRemovesNonReferencedBundlesFromCache() { #if ENABLE_CACHING if (BuildScriptMode == TestBuildScriptMode.Fast) Assert.Ignore("Bundle caching does not occur when using this playmode."); yield return InitializeSettings(kOldBuildId); yield return CacheEntries(GetNonRefAsset); HashSet entriesToRemove = GetAllEntries(GetNonRefAsset); yield return InitializeSettings(kNewBuildId); yield return CacheEntries(GetRefAsset); HashSet entriesToPreserve = GetAllEntries(GetRefAsset); entriesToRemove.ExceptWith(entriesToPreserve); var handle = m_Addressables.CleanBundleCache(null, false); yield return handle; handle.Release(); AssertEntriesAreRemoved(entriesToRemove); AssertEntriesArePreserved(entriesToPreserve); ReleaseAddressables(); #else Assert.Ignore("Caching not enabled."); yield return null; #endif } [UnityTest] public IEnumerator WhenCatalogIdListNull_AndUsingFastMode_ReturnsException() { #if ENABLE_CACHING if (BuildScriptMode != TestBuildScriptMode.Fast) Assert.Ignore("Test only intended to run on fast mode."); yield return InitializeSettings(kOldBuildId); var ifm = LogAssert.ignoreFailingMessages; LogAssert.ignoreFailingMessages = true; var handle = m_Addressables.CleanBundleCache(null, false); yield return handle; Assert.AreEqual("Provided catalogs do not load data from a catalog file. This can occur when using the \"Use Asset Database (fastest)\" playmode script. Bundle cache was not modified.", handle.OperationException.Message); handle.Release(); LogAssert.ignoreFailingMessages = ifm; ReleaseAddressables(); #else Assert.Ignore("Caching not enabled."); yield return null; #endif } [UnityTest] public IEnumerator WhenInvalidCatalogId_ReturnsException() { #if ENABLE_CACHING if (BuildScriptMode == TestBuildScriptMode.Fast) Assert.Ignore("Bundle caching does not occur when using this playmode."); yield return InitializeSettings(kOldBuildId); var ifm = LogAssert.ignoreFailingMessages; LogAssert.ignoreFailingMessages = true; var handle = m_Addressables.CleanBundleCache(new List {"invalidId"}, false); yield return handle; Assert.AreEqual("Provided catalogs do not load data from a catalog file. This can occur when using the \"Use Asset Database (fastest)\" playmode script. Bundle cache was not modified.", handle.OperationException.Message); handle.Release(); LogAssert.ignoreFailingMessages = ifm; ReleaseAddressables(); #else Assert.Ignore("Caching not enabled."); yield return null; #endif } [UnityTest] public IEnumerator WhenAnotherOpAlreadyInProgress_ReturnsException() { #if ENABLE_CACHING if (BuildScriptMode == TestBuildScriptMode.Fast) Assert.Ignore("Bundle caching does not occur when using this playmode."); yield return InitializeSettings(kOldBuildId); var ifm = LogAssert.ignoreFailingMessages; LogAssert.ignoreFailingMessages = true; var handle = m_Addressables.CleanBundleCache(null, false); var handle2 = m_Addressables.CleanBundleCache(null, false); yield return handle2; Assert.AreEqual("Bundle cache is already being cleaned.", handle2.OperationException.Message); handle2.Release(); yield return handle; handle.Release(); LogAssert.ignoreFailingMessages = ifm; ReleaseAddressables(); #else Assert.Ignore("Caching not enabled."); yield return null; #endif } [UnityTest] public IEnumerator WhenCachingDisabled_CleanBundleCache_ReturnsException() { #if !ENABLE_CACHING if (BuildScriptMode == TestBuildScriptMode.Fast) Assert.Ignore("Bundle caching does not occur when using this playmode."); yield return InitializeSettings(kOldBuildId); var ifm = LogAssert.ignoreFailingMessages; LogAssert.ignoreFailingMessages = true; var handle = m_Addressables.CleanBundleCache(null, false); yield return handle; Assert.AreEqual("Caching not enabled. There is no bundle cache to modify.", handle.OperationException.Message); handle.Release(); LogAssert.ignoreFailingMessages = ifm; ReleaseAddressables(); #else Assert.Ignore("Caching is enabled, but test expects to run on caching-disabled platforms."); yield return null; #endif } } #if UNITY_EDITOR class CleanBundleCacheTests_FastMode : CleanBundleCacheTests { protected override TestBuildScriptMode BuildScriptMode { get { return TestBuildScriptMode.Fast; } } } class CleanBundleCacheTests_PackedPlaymodeMode : CleanBundleCacheTests { protected override TestBuildScriptMode BuildScriptMode { get { return TestBuildScriptMode.PackedPlaymode; } } } #endif [UnityPlatform(exclude = new[] {RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor, RuntimePlatform.LinuxEditor})] class CleanBundleCacheTests_PackedMode : CleanBundleCacheTests { protected override TestBuildScriptMode BuildScriptMode { get { return TestBuildScriptMode.Packed; } } } }