using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;
using System.IO;
using UnityEngine.SceneManagement;
using UnityEngine.AddressableAssets.Initialization;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.AddressableAssets.ResourceProviders;
using UnityEngine.AddressableAssets.Utility;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Text.RegularExpressions;
using System.Linq;
using System.Text;
using UnityEngine.AddressableAssets.ResourceProviders.Tests;
using UnityEngine.AddressableAssets.Tests;
using UnityEngine.Lumin;
using UnityEngine.Networking;
using UnityEngine.U2D;
using Object = UnityEngine.Object;
using Texture2D = UnityEngine.Texture2D;

namespace AddressableAssetsIntegrationTests
{
    internal abstract partial class AddressablesIntegrationTests : IPrebuildSetup
    {
        [UnityTest]
        public IEnumerator AsyncCache_IsCleaned_OnFailedOperation()
        {
            yield return Init();

            AsyncOperationHandle<GameObject> op;
            using (new IgnoreFailingLogMessage())
                op = m_Addressables.LoadAssetAsync<GameObject>("notARealKey");

            op.Completed += handle => { Assert.AreEqual(0, m_Addressables.ResourceManager.CachedOperationCount()); };

            yield return op;
        }

        [UnityTest]
        public IEnumerator LoadResourceLocations_InvalidKeyDoesNotThrow()
        {
            //Setup
            yield return Init();

            //Test
            Assert.DoesNotThrow(() =>
            {
                var handle = m_Addressables.LoadResourceLocationsAsync("noSuchLabel", typeof(object));
                handle.WaitForCompletion();
                handle.Release();
            });
        }

        [UnityTest]
        public IEnumerator LoadResourceLocations_ValidKeyDoesNotThrow()
        {
            //Setup
            yield return Init();

            //Test
            Assert.DoesNotThrow(() => { m_Addressables.LoadResourceLocationsAsync(AddressablesTestUtility.GetPrefabLabel("BASE"), typeof(GameObject)); });
        }

        [UnityTest]
        public IEnumerator LoadResourceLocations_SubObjects_ReturnsCorrectResourceTypes()
        {
            yield return Init();
            string subObjectsAddress = "assetWithDifferentTypedSubAssets";

            var loadLocsHandle = m_Addressables.LoadResourceLocationsAsync(subObjectsAddress, typeof(object));
            yield return loadLocsHandle;

            Assert.AreEqual(3, loadLocsHandle.Result.Count);
            Assert.IsTrue(loadLocsHandle.Result.Any(loc => loc.ResourceType == typeof(Mesh)));
            Assert.IsTrue(loadLocsHandle.Result.Any(loc => loc.ResourceType == typeof(Material)));
            Assert.IsTrue(loadLocsHandle.Result.Any(loc => loc.ResourceType == typeof(TestObject)));

            loadLocsHandle.Release();
        }

        [UnityTest]
        public IEnumerator LoadResourceLocations_ReturnsCorrectResourceTypes()
        {
            yield return Init();
            string spriteAddress = "sprite";

            var loadLocsHandle = m_Addressables.LoadResourceLocationsAsync(spriteAddress, typeof(object));
            yield return loadLocsHandle;

            Assert.AreEqual(2, loadLocsHandle.Result.Count);
            Assert.IsTrue(loadLocsHandle.Result.Any(loc => loc.ResourceType == typeof(Texture2D)));
            Assert.IsTrue(loadLocsHandle.Result.Any(loc => loc.ResourceType == typeof(Sprite)));

            loadLocsHandle.Release();
        }

        [UnityTest]
        public IEnumerator LoadResourceLocations_SpecificType_ReturnsCorrectResourceTypes()
        {
            yield return Init();
            string spriteAddress = "sprite";

            var loadLocsHandle = m_Addressables.LoadResourceLocationsAsync(spriteAddress, typeof(Sprite));
            yield return loadLocsHandle;

            Assert.AreEqual(1, loadLocsHandle.Result.Count);
            Assert.AreEqual(typeof(Sprite), loadLocsHandle.Result[0].ResourceType);

            loadLocsHandle.Release();

            loadLocsHandle = m_Addressables.LoadResourceLocationsAsync(spriteAddress, typeof(Texture2D));
            yield return loadLocsHandle;

            Assert.AreEqual(1, loadLocsHandle.Result.Count);
            Assert.AreEqual(typeof(Texture2D), loadLocsHandle.Result[0].ResourceType);

            loadLocsHandle.Release();
        }

        [UnityTest]
        public IEnumerator LoadPrefabWithComponentType_Fails()
        {
            //Setup
            yield return Init();

            //Test
            AsyncOperationHandle<MeshRenderer> op = new AsyncOperationHandle<MeshRenderer>();
            using (new IgnoreFailingLogMessage())
            {
                op = m_Addressables.LoadAssetAsync<MeshRenderer>("test0BASE");
            }

            yield return op;
            Assert.IsNull(op.Result);
        }

        const string InvalidKeyExceptionBaseMessage = "Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown.";

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAsset_NoKeyFound()
        {
            //Setup
            yield return Init();
            string keyString = "noSuchLabel";
            AsyncOperationHandle handle = default(AsyncOperationHandle);

            try
            {
                //Test
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetAsync<GameObject>(keyString);
                    yield return handle;
                }

                string message = $"{InvalidKeyExceptionBaseMessage} No Location found for Key={keyString}";
                Assert.AreEqual(message, handle.OperationException.Message, "InvalidKeyException message not the same as expected for when the Location does not exist");
            }
            finally
            {
                //Cleanup
                if (handle.IsValid())
                    handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAsset_KeyFoundWithOtherType()
        {
            //Setup
            yield return Init();
            string keyString = "test0BASE";
            AsyncOperationHandle<TextAsset> handle = new AsyncOperationHandle<TextAsset>();

            try
            {
                //Test
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetAsync<TextAsset>(keyString);
                    yield return handle;
                }

                string message =
                    $"{InvalidKeyExceptionBaseMessage} No Asset found with for Key={keyString}. Key exists as Type={typeof(GameObject)}, which is not assignable from the requested Type={typeof(TextAsset)}";
                Assert.AreEqual(message, handle.OperationException.Message,
                    "InvalidKeyException message not the same as expected for when a similar Location exists with same key and a different type");
            }
            finally
            {
                //Cleanup
                if (handle.IsValid())
                    handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAsset_KeyFoundWithMultipleOtherType()
        {
            //Setup
            yield return Init();
            string keyString = "mixed";
            string otherAvailableTypesForKey = "UnityEngine.GameObject, UnityEngine.AddressableAssets.Tests.TestObject";
            AsyncOperationHandle<TextAsset> handle = new AsyncOperationHandle<TextAsset>();

            try //Test
            {
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetAsync<TextAsset>(keyString);
                    yield return handle;
                }

                string message =
                    $"{InvalidKeyExceptionBaseMessage} No Asset found with for Key={keyString}. Key exists as multiple Types={otherAvailableTypesForKey}, which is not assignable from the requested Type={typeof(TextAsset)}";
                bool isEqual = message == handle.OperationException.Message;
                if (!isEqual)
                {
                    // order isn't guaranteed
                    message = message.Replace("UnityEngine.GameObject, UnityEngine.AddressableAssets.Tests.TestObject", "UnityEngine.AddressableAssets.Tests.TestObject, UnityEngine.GameObject");
                    isEqual = message == handle.OperationException.Message;
                }

                Assert.IsTrue(isEqual,
                    $"InvalidKeyException message not the same as expected for when a similar Location exists with same key and a different type. Was expecting {message.ToString()}, but was {handle.OperationException.Message}");
            }
            finally // Cleanup
            {
                if (handle.IsValid())
                    handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAsset__AssetFromGUIDFoundWithDifferentType()
        {
            if (string.IsNullOrEmpty(TypeName) || TypeName != "BuildScriptFastMode" || TypeName == "BuildScriptVirtualMode")
            {
                Assert.Ignore($"Skipping test {nameof(InvalidKeyException_LoadAsset__AssetFromGUIDFoundWithDifferentType)} for {TypeName}, Editor AssetDatabase based test.");
            }


            //Setup
            yield return Init();
#if UNITY_EDITOR
            string keyString = "test0BASE";

            AsyncOperationHandle<TextAsset> handle = new AsyncOperationHandle<TextAsset>();
            AsyncOperationHandle<GameObject> goLoadHandle = new AsyncOperationHandle<GameObject>();

            try
            {
                //Test
                goLoadHandle = m_Addressables.LoadAssetAsync<GameObject>(keyString);
                yield return goLoadHandle;
                Assert.AreEqual(goLoadHandle.Status, AsyncOperationStatus.Succeeded);
                bool foundGuid =
                    UnityEditor.AssetDatabase.TryGetGUIDAndLocalFileIdentifier(goLoadHandle.Result, out string guid,
                        out long id);
                Assert.IsTrue(foundGuid, "Failed to get the Guid for loaded Object");
                goLoadHandle.Release();
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetAsync<TextAsset>(guid);
                    yield return handle;
                }

                string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
                string message =
                    $"{InvalidKeyExceptionBaseMessage} Could not load Asset with GUID={guid}, Path={path}. Asset exists with main Type={typeof(GameObject)}, which is not assignable from the requested Type={typeof(TextAsset)}";
                Assert.AreEqual(message, handle.OperationException.Message,
                    "InvalidKeyException message not the same as expected for when a similar Location exists with same key and a different type");
            }
            finally
            {
                //Cleanup
                if (handle.IsValid())
                    handle.Release();
                if (goLoadHandle.IsValid())
                    goLoadHandle.Release();
            }
#endif
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAssets_MixedTypesGivesCorrectError()
        {
            //Setup
            yield return Init();
            object[] keys = new object[] {"noSuchKey", 123};
            AsyncOperationHandle handle = default(AsyncOperationHandle);

            //Test
            try
            {
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetsAsync<GameObject>(keys, null, Addressables.MergeMode.Union, true);
                }

                string message = handle.OperationException.Message;
                string types = "Types=System.String, System.Int32";
                string expected = $"{InvalidKeyExceptionBaseMessage} Enumerable key contains multiple Types. {types}, all Keys are expected to be strings";

                bool equalOne = message == expected;
                bool equalTwo = message == expected.Replace("Types=System.String, System.Int32", "System.Int32, Types=System.String");
                Assert.IsTrue(equalOne || equalTwo, $"Failed to get correct message for mixedTypes being requested. was {message}. But expected {expected}");
                yield return handle;
            }
            finally
            {
                //Cleanup
                handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAsset_MultipleKeysNoMergeMode()
        {
            //Setup
            yield return Init();
            string[] keysArray = new string[] {"noSuchKey1", "noSuchKey2"};

            //Test
            AsyncOperationHandle handle = default(AsyncOperationHandle);
            using (new IgnoreFailingLogMessage())
            {
                handle = m_Addressables.LoadAssetAsync<GameObject>(keysArray);
            }

            string keysErrorString =
                "Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. No MergeMode is set to merge the multiple keys requested. Keys=noSuchKey1, noSuchKey2, Type=UnityEngine.GameObject";
            Assert.AreEqual(keysErrorString, handle.OperationException.Message);
            yield return handle;

            //Cleanup
            handle.Release();
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAssets_Union_NoKeysFound()
        {
            //Setup
            yield return Init();
            string[] keysArray = new[] {"noSuchKey1", "noSuchKey2"};
            AsyncOperationHandle handle = default(AsyncOperationHandle);

            try
            {
                //Test
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetsAsync<TextAsset>(keysArray, null, Addressables.MergeMode.Union, true);
                }

                string expected = "Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. " +
                                  "No Union of Assets between Keys=noSuchKey1, noSuchKey2 with Type=UnityEngine.TextAsset" +
                                  "\nNo Location found for Key=noSuchKey1" +
                                  "\nNo Location found for Key=noSuchKey2";

                Assert.AreEqual(expected, handle.OperationException.Message, "Incorrect invalidKeyMessage. Expected to inform the two locations have for other type");
                yield return handle;
            }
            finally
            {
                //Cleanup
                handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAssets_Union_IncorrectType()
        {
            //Setup
            yield return Init();
            string[] keysArray = new[] {"test0BASE", "test1BASE"};
            AsyncOperationHandle handle = default(AsyncOperationHandle);

            try
            {
                //Test
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetsAsync<TextAsset>(keysArray, null, Addressables.MergeMode.Union, true);
                }

                string expected = "Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. " +
                                  "No Union of Assets between Keys=test0BASE, test1BASE with Type=UnityEngine.TextAsset" +
                                  "\nUnion of Type=UnityEngine.GameObject found with Keys=test0BASE, test1BASE";

                Assert.AreEqual(expected, handle.OperationException.Message, "Incorrect invalidKeyMessage. Expected to inform the two locations have for other type");
                yield return handle;
            }
            finally
            {
                //Cleanup
                handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAssets_Union_MultipleTypesAvailable()
        {
            //Setup
            yield return Init();
            string[] keysArray = new[] {"test0BASE", "assetWithSubObjects"};
            AsyncOperationHandle handle = default(AsyncOperationHandle);

            try
            {
                //Test
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetsAsync<TextAsset>(keysArray, null, Addressables.MergeMode.Union, true);
                }

                string expected = "Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. " +
                                  "No Union of Assets between Keys=test0BASE, assetWithSubObjects with Type=UnityEngine.TextAsset" +
                                  "\nUnion of Type=UnityEngine.GameObject found with Key=test0BASE. Without Key=assetWithSubObjects" +
                                  "\nUnion of Type=UnityEngine.AddressableAssets.Tests.TestObject found with Key=assetWithSubObjects. Without Key=test0BASE";

                Assert.AreEqual(expected, handle.OperationException.Message, "Incorrect invalidKeyMessage. Expected to inform that a merge could be made for two different types");
                yield return handle;
            }
            finally
            {
                //Cleanup
                handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAssets_Intersection_OneMissingKey()
        {
            //Setup
            yield return Init();
            string[] keysArray = new[] {"test0BASE", "noSuchKey"};
            AsyncOperationHandle handle = default(AsyncOperationHandle);

            try
            {
                //Test
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetsAsync<GameObject>(keysArray, null, Addressables.MergeMode.Intersection, true);
                }

                string expected = "Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. " +
                                  "No Intersection of Assets between Keys=test0BASE, noSuchKey with Type=UnityEngine.GameObject" +
                                  "\nNo Location found for Key=noSuchKey";

                Assert.AreEqual(expected, handle.OperationException.Message, "Incorrect invalidKeyMessage. Expected to error due to noSuchKey");
                yield return handle;
            }
            finally
            {
                //Cleanup
                handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAssets_Intersection_PossibleForAnotherType()
        {
            //Setup
            yield return Init();
            string[] keysArray = new[] {"test0BASE", "mixed"};
            AsyncOperationHandle handle = default(AsyncOperationHandle);

            try
            {
                //Test
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetsAsync<TextAsset>(keysArray, null, Addressables.MergeMode.Intersection, true);
                }

                string expected = "Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. " +
                                  "No Intersection of Assets between Keys=test0BASE, mixed with Type=UnityEngine.TextAsset" +
                                  "\nAn Intersection exists for Type=UnityEngine.GameObject";

                Assert.AreEqual(expected, handle.OperationException.Message, "Incorrect invalidKeyMessage. Expected to inform that an intersection exists with GameObject");
                yield return handle;
            }
            finally
            {
                //Cleanup
                handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAssets_UseFirst_NoLocations()
        {
            //Setup
            yield return Init();
            string[] keysArray = new[] {"noSuchKey1", "noSuchKey2"};
            AsyncOperationHandle handle = default(AsyncOperationHandle);

            try
            {
                //Test
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetsAsync<GameObject>(keysArray, null, Addressables.MergeMode.UseFirst, true);
                }

                string expected = "Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. " +
                                  "No UseFirst Asset within Keys=noSuchKey1, noSuchKey2 with Type=UnityEngine.GameObject" +
                                  "\nNo Location found for Key=noSuchKey1" +
                                  "\nNo Location found for Key=noSuchKey2";

                Assert.AreEqual(expected, handle.OperationException.Message, "Incorrect invalidKeyMessage. Expected to inform that all keys have no location");
                yield return handle;
            }
            finally
            {
                //Cleanup
                handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator InvalidKeyException_LoadAssets_UseFirst_PossibleForAnotherType()
        {
            //Setup
            yield return Init();
            string[] keysArray = new[] {"test0BASE", "noSuchKey"};
            AsyncOperationHandle handle = default(AsyncOperationHandle);

            try
            {
                //Test
                using (new IgnoreFailingLogMessage())
                {
                    handle = m_Addressables.LoadAssetsAsync<TextAsset>(keysArray, null, Addressables.MergeMode.UseFirst, true);
                }

                string expected = "Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. " +
                                  "No UseFirst Asset within Keys=test0BASE, noSuchKey with Type=UnityEngine.TextAsset" +
                                  "\nNo Location found for Key=noSuchKey" +
                                  "\nType=UnityEngine.GameObject exists for Key=test0BASE";

                Assert.AreEqual(expected, handle.OperationException.Message,
                    "Incorrect invalidKeyMessage. Expected to inform that one key has no location and the other can be loaded with GameObject");
                yield return handle;
            }
            finally
            {
                //Cleanup
                handle.Release();
            }
        }

        [UnityTest]
        public IEnumerator CanLoadTextureAsSprite()
        {
            //Setup
            yield return Init();

            var op = m_Addressables.LoadAssetAsync<Sprite>("sprite");
            yield return op;
            Assert.IsNotNull(op.Result);
            Assert.AreEqual(typeof(Sprite), op.Result.GetType());
            op.Release();
        }

        [UnityTest]
        public IEnumerator CanLoadSpriteByName()
        {
            //Setup
            yield return Init();

            var op = m_Addressables.LoadAssetAsync<Sprite>("sprite[botright]");
            yield return op;
            Assert.IsNotNull(op.Result);
            Assert.AreEqual(typeof(Sprite), op.Result.GetType());
            Assert.AreEqual("botright", op.Result.name);
            op.Release();

            var op2 = m_Addressables.LoadAssetAsync<Sprite>("sprite[topleft]");
            yield return op2;
            Assert.IsNotNull(op2.Result);
            Assert.AreEqual(typeof(Sprite), op2.Result.GetType());
            Assert.AreEqual("topleft", op2.Result.name);
            op2.Release();
        }

        [UnityTest]
        public IEnumerator CanLoadFromFolderEntry_SpriteAtlas()
        {
            //Setup
            yield return Init();

            var op = m_Addressables.LoadAssetAsync<SpriteAtlas>("folderEntry/atlas.spriteatlas");
            yield return op;
            Assert.IsNotNull(op.Result);
            Assert.AreEqual(typeof(SpriteAtlas), op.Result.GetType());
            op.Release();
        }

        [UnityTest]
        public IEnumerator CanLoadFromFolderEntry_SpriteFromSpriteAtlas()
        {
            //Setup
            yield return Init();

            var op = m_Addressables.LoadAssetAsync<Sprite>("folderEntry/atlas.spriteatlas[sprite]");
            yield return op;
            Assert.IsNotNull(op.Result);
            Assert.AreEqual(typeof(Sprite), op.Result.GetType());
            op.Release();
        }

        [UnityTest]
        public IEnumerator CanLoadFromFolderEntry_Texture()
        {
            //Setup
            yield return Init();

            var op = m_Addressables.LoadAssetAsync<Texture2D>("folderEntry/spritesheet.png");
            yield return op;
            Assert.IsNotNull(op.Result);
            Assert.AreEqual(typeof(Texture2D), op.Result.GetType());
            op.Release();
        }

        [UnityTest]
        public IEnumerator CanLoadFromFolderEntry_SpriteFromTexture()
        {
            //Setup
            yield return Init();

            var op = m_Addressables.LoadAssetAsync<Sprite>("folderEntry/spritesheet.png[topleft]");
            yield return op;
            Assert.IsNotNull(op.Result);
            Assert.AreEqual(typeof(Sprite), op.Result.GetType());
            op.Release();
        }

        [UnityTest]
        public IEnumerator CanLoadAllSpritesAsArray()
        {
            //Setup
            yield return Init();

            var op = m_Addressables.LoadAssetAsync<Sprite[]>("sprite");
            yield return op;
            Assert.IsNotNull(op.Result);
            Assert.AreEqual(typeof(Sprite[]), op.Result.GetType());
            Assert.AreEqual(2, op.Result.Length);
            op.Release();
        }

        [UnityTest]
        public IEnumerator CanLoadAllSpritesAsList()
        {
            //Setup
            yield return Init();

            var op = m_Addressables.LoadAssetAsync<IList<Sprite>>("sprite");
            yield return op;
            Assert.IsNotNull(op.Result);
            Assert.IsTrue(typeof(IList<Sprite>).IsAssignableFrom(op.Result.GetType()));
            Assert.AreEqual(2, op.Result.Count);
            op.Release();
        }
#if !ENABLE_BINARY_CATALOG
        [UnityTest]
        public IEnumerator CanUseCustomAssetBundleResource_LoadFromCustomProvider()
        {
            //Setup
            yield return Init();
            if (string.IsNullOrEmpty(TypeName) || TypeName == "BuildScriptFastMode")
            {
                Assert.Ignore($"Skipping test {nameof(CanUseCustomAssetBundleResource_LoadFromCustomProvider)} for {TypeName}, AssetBundle based test.");
            }

            string hash = "123456789";
            string bundleName = $"test_{hash}";
            string key = "lockey_key";

            ResourceLocationBase location = null;
            TestCatalogProviderCustomAssetBundleResource testProvider;
            SetupBundleForProviderTests(bundleName, "bundle", key, out location, out testProvider);

            var op = m_Addressables.LoadAssetAsync<object>(key);
            yield return op;

            Assert.IsTrue(testProvider.TestInternalOp.Result.WasUsed);

            op.Release();
        }
#endif
        string TransFunc(IResourceLocation loc)
        {
            return "transformed";
        }

        [UnityTest]
        public IEnumerator InternalIdTranslationTest()
        {
            //Setup
            yield return Init();
            m_Addressables.InternalIdTransformFunc = TransFunc;
            var loc = new ResourceLocationBase("none", "original", "none", typeof(object));
            var transformedId = m_Addressables.ResourceManager.TransformInternalId(loc);
            Assert.AreEqual("transformed", transformedId);
            m_Addressables.InternalIdTransformFunc = null;
            var originalId = m_Addressables.ResourceManager.TransformInternalId(loc);
            Assert.AreEqual("original", originalId);
        }

        [UnityTest]
        public IEnumerator WebRequestOverrideTest()
        {
            yield return Init();
            var originalUrl = "http://127.0.0.1/original.asset";
            var replacedUrl = "http://127.0.0.1/replaced.asset";
            var uwr = new UnityWebRequest(originalUrl);

            m_Addressables.WebRequestOverride = request => request.url = replacedUrl;
            m_Addressables.ResourceManager.WebRequestOverride(uwr);
            var currentUrl = uwr.url;
            uwr.Dispose();
            m_Addressables.WebRequestOverride = null;
            Assert.AreEqual(replacedUrl, currentUrl);
        }

        [UnityTest]
        public IEnumerator CanLoadTextureAsTexture()
        {
            //Setup
            yield return Init();

            var op = m_Addressables.LoadAssetAsync<Texture2D>("sprite");
            yield return op;
            Assert.IsNotNull(op.Result);
            Assert.AreEqual(typeof(Texture2D), op.Result.GetType());
            op.Release();
        }

        [UnityTest]
        public IEnumerator LoadAsset_ValidKeyDoesNotThrow()
        {
            //Setup
            yield return Init();

            //Test
            AsyncOperationHandle handle = default(AsyncOperationHandle);
            Assert.DoesNotThrow(() => { handle = m_Addressables.LoadAssetAsync<GameObject>(AddressablesTestUtility.GetPrefabLabel("BASE")); });
            yield return handle;

            //Cleanup
            handle.Release();
        }

        [UnityTest]
        public IEnumerator VerifyChainOpPercentCompleteCalculation()
        {
            //Setup
            yield return Init();
            AsyncOperationHandle<GameObject> op = m_Addressables.LoadAssetAsync<GameObject>(AddressablesTestUtility.GetPrefabLabel("BASE"));

            //Test
            while (op.PercentComplete < 1)
            {
                Assert.False(op.IsDone);
                yield return null;
            }

            yield return null;
            Assert.True(op.PercentComplete == 1 && op.IsDone);
            yield return op;

            //Cleanup
            op.Release();
        }

        [UnityTest]
        public IEnumerator LoadResourceLocationsAsync_ReturnsCorrectNumberOfLocationsForStringKey()
        {
            yield return Init();

            var handle = m_Addressables.LoadResourceLocationsAsync("assetWithDifferentTypedSubAssets");
            yield return handle;

            Assert.AreEqual(3, handle.Result.Count);
            HashSet<Type> typesSeen = new HashSet<Type>();
            foreach (var result in handle.Result)
            {
                Assert.IsNotNull(result.ResourceType);
                typesSeen.Add(result.ResourceType);
            }

            Assert.AreEqual(3, typesSeen.Count);
            m_Addressables.Release(handle);
        }

        [UnityTest]
        public IEnumerator LoadResourceLocationsAsync_ReturnsCorrectNumberOfLocationsForSubStringKey()
        {
            yield return Init();

            var handle = m_Addressables.LoadResourceLocationsAsync("assetWithDifferentTypedSubAssets[Mesh]");
            yield return handle;

            Assert.AreEqual(3, handle.Result.Count);
            HashSet<Type> typesSeen = new HashSet<Type>();
            foreach (var result in handle.Result)
            {
                Assert.IsNotNull(result.ResourceType);
                typesSeen.Add(result.ResourceType);
            }

            Assert.AreEqual(3, typesSeen.Count);
            m_Addressables.Release(handle);
        }

        [UnityTest]
        public IEnumerator LoadResourceLocationsAsync_ReturnsCorrectNumberOfLocationsForSubStringKey_WhenTypeIsPassedIn()
        {
            yield return Init();

            var handle = m_Addressables.LoadResourceLocationsAsync("assetWithDifferentTypedSubAssets[Mesh]", typeof(Mesh));
            yield return handle;

            Assert.AreEqual(1, handle.Result.Count);
            Assert.AreEqual(typeof(Mesh), handle.Result[0].ResourceType);

            m_Addressables.Release(handle);
        }

        [UnityTest]
        public IEnumerator LoadResourceLocationsAsync_ReturnsCorrectNumberOfLocationsForAssetReference()
        {
            yield return Init();

            AsyncOperationHandle assetReferenceHandle = m_Addressables.InstantiateAsync(AssetReferenceObjectKey);
            yield return assetReferenceHandle;
            Assert.IsNotNull(assetReferenceHandle.Result as GameObject);
            AssetReferenceTestBehavior behavior =
                (assetReferenceHandle.Result as GameObject).GetComponent<AssetReferenceTestBehavior>();

            var handle = m_Addressables.LoadResourceLocationsAsync(behavior.ReferenceWithMultiTypedSubObject);
            yield return handle;

            Assert.AreEqual(3, handle.Result.Count);
            HashSet<Type> typesSeen = new HashSet<Type>();
            foreach (var result in handle.Result)
            {
                Assert.IsNotNull(result.ResourceType);
                typesSeen.Add(result.ResourceType);
            }

            Assert.AreEqual(3, typesSeen.Count);

            m_Addressables.Release(assetReferenceHandle);
            m_Addressables.Release(handle);
        }

        [UnityTest]
        public IEnumerator LoadResourceLocationsAsync_ReturnsCorrectNumberOfLocationsForSubAssetReference()
        {
            yield return Init();

            AsyncOperationHandle assetReferenceHandle = m_Addressables.InstantiateAsync(AssetReferenceObjectKey);
            yield return assetReferenceHandle;
            Assert.IsNotNull(assetReferenceHandle.Result as GameObject);
            AssetReferenceTestBehavior behavior =
                (assetReferenceHandle.Result as GameObject).GetComponent<AssetReferenceTestBehavior>();

            var handle = m_Addressables.LoadResourceLocationsAsync(behavior.ReferenceWithMultiTypedSubObjectSubReference);
            yield return handle;

            Assert.AreEqual(1, handle.Result.Count);
            Assert.AreEqual(typeof(Material), handle.Result[0].ResourceType);

            m_Addressables.Release(assetReferenceHandle);
            m_Addressables.Release(handle);
        }

        [UnityTest]
        public IEnumerator PercentComplete_NeverHasDecreasedValue_WhenLoadingAsset()
        {
            //Setup
            yield return Init();
            AsyncOperationHandle<GameObject> op = m_Addressables.LoadAssetAsync<GameObject>(AddressablesTestUtility.GetPrefabLabel("BASE"));

            //Test
            float lastPercentComplete = 0f;
            while (!op.IsDone)
            {
                Assert.IsFalse(lastPercentComplete > op.PercentComplete);
                lastPercentComplete = op.PercentComplete;
                yield return null;
            }

            Assert.True(op.PercentComplete == 1 && op.IsDone);
            yield return op;

            //Cleanup
            op.Release();
        }

#if !UNITY_SWITCH
        [UnityTest]
        [Platform(Exclude = "Switch")]
        public IEnumerator LoadContentCatalogAsync_SetsUpLocalAndRemoteLocations()
        {
            yield return Init();
            string catalogPath = "fakeCatalogPath" + kCatalogExt;
            string catalogHashPath = "fakeCatalogPath.hash";

            var loc = m_Addressables.CreateCatalogLocationWithHashDependencies<ContentCatalogProvider>(catalogPath);
            Assert.AreEqual(2, loc.Dependencies.Count);
            var remoteLocation = loc.Dependencies[(int)ContentCatalogProvider.DependencyHashIndex.Remote];
            var cacheLocation = loc.Dependencies[(int)ContentCatalogProvider.DependencyHashIndex.Cache];

            Assert.AreEqual(catalogHashPath, remoteLocation.ToString());
            Assert.AreEqual(m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + catalogHashPath.GetHashCode() + catalogHashPath.Substring(catalogHashPath.LastIndexOf("."))),
                cacheLocation.ToString());
        }
#endif
        [UnityTest]
        public IEnumerator IsCatalogCached_ReturnsFalse_WhenCatalogLocationDoesNotHaveDependencies()
        {
            yield return Init();

            ResourceLocationBase fakeCatlaogLoc = new ResourceLocationBase("name", "FakeCatalogID", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider));
            Hash128 hash = Hash128.Parse("1234");
            var result = m_Addressables.IsCatalogCached(fakeCatlaogLoc, hash);
            Assert.IsFalse(result);
        }

        [UnityTest]
        public IEnumerator IsCatalogCached_ReturnsFalse_WhenCatalogLocationDoesNotHaveRemoteHashFileDependency()
        {
            yield return Init();

            ResourceLocationBase fakeCatlaogLoc = new ResourceLocationBase("name", "FakeCatalogID", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider),
                new ResourceLocationBase("dep", "fakedep", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider)));
            Hash128 hash = Hash128.Parse("1234");
            var result = m_Addressables.IsCatalogCached(fakeCatlaogLoc, hash);
            Assert.IsFalse(result);
        }

        [UnityTest]
        public IEnumerator IsCatalogCached_ReturnsFalse_WhenCatalogLocationCacheFileDoesNotExist()
        {
            yield return Init();

            ResourceLocationBase fakeCatlaogLoc = new ResourceLocationBase("name", "FakeCatalogID", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider),
                new ResourceLocationBase("dep", "notarealfilepath", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider)),
                new ResourceLocationBase("dep", "fakedep", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider)));
            Hash128 hash = Hash128.Parse("1234");
            var result = m_Addressables.IsCatalogCached(fakeCatlaogLoc, hash);
            Assert.IsFalse(result);
        }

#if UNITY_EDITOR //these tests involve writing files, which can have problems on some of the consoles
        [UnityTest]
        public IEnumerator IsCatalogCached_ReturnsFalse_WhenCatalogLocationCacheFileHashDoesNotMatchProvdedRemoteHash()
        {
            yield return Init();
            Hash128 cacheHash = Hash128.Parse("1234");
            Hash128 remoteHash = Hash128.Parse("5678");
            string cacheHashFilePath = Path.Combine(kCatalogFolderPath, "CachedFilePath.hash");

            WriteHashFileForCatalog(cacheHashFilePath, cacheHash.ToString());

            ResourceLocationBase fakeCatlaogLoc = new ResourceLocationBase("name", "FakeCatalogID", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider),
                new ResourceLocationBase("dep", "fakedep", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider)),
                new ResourceLocationBase("dep", cacheHashFilePath, typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider)));
            var result = m_Addressables.IsCatalogCached(fakeCatlaogLoc, remoteHash);
            Assert.IsFalse(result);
            File.Delete(cacheHashFilePath);
        }

        [UnityTest]
        public IEnumerator IsCatalogCached_ReturnsTrue_WhenCatalogLocationCacheFileHashMatchesProvdedRemoteHash()
        {
            yield return Init();
            Hash128 cacheHash = Hash128.Parse("1234");
            Hash128 remoteHash = Hash128.Parse("1234");
            string cacheHashFilePath = Path.Combine(kCatalogFolderPath, "CachedFilePath.hash");

            WriteHashFileForCatalog(cacheHashFilePath, cacheHash.ToString());

            ResourceLocationBase fakeCatlaogLoc = new ResourceLocationBase("name", "FakeCatalogID", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider),
                new ResourceLocationBase("dep", "fakedep", typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider)),
                new ResourceLocationBase("dep", cacheHashFilePath, typeof(ContentCatalogProvider).FullName, typeof(ContentCatalogProvider)));
            var result = m_Addressables.IsCatalogCached(fakeCatlaogLoc, remoteHash);
            Assert.IsTrue(result);
            File.Delete(cacheHashFilePath);
        }
#endif
#if !UNITY_SWITCH
        [UnityTest]
        public IEnumerator LoadContentCatalogAsync_LocationsHaveTimeout()
        {
            yield return Init();
            string catalogPath = "fakeCatalogPath" + kCatalogExt;

            m_Addressables.CatalogRequestsTimeout = 13;
            var loc = m_Addressables.CreateCatalogLocationWithHashDependencies<ContentCatalogProvider>(catalogPath);
            Assert.AreEqual(2, loc.Dependencies.Count);
            var remoteLocation = loc.Dependencies[(int)ContentCatalogProvider.DependencyHashIndex.Remote];
            var cacheLocation = loc.Dependencies[(int)ContentCatalogProvider.DependencyHashIndex.Cache];

            var data = loc.Data as ProviderLoadRequestOptions;
            Assert.IsNotNull(data);
            Assert.AreEqual(data.WebRequestTimeout, m_Addressables.CatalogRequestsTimeout);
            data = remoteLocation.Data as ProviderLoadRequestOptions;
            Assert.IsNotNull(data);
            Assert.AreEqual(data.WebRequestTimeout, m_Addressables.CatalogRequestsTimeout);
            data = cacheLocation.Data as ProviderLoadRequestOptions;
            Assert.IsNotNull(data);
            Assert.AreEqual(data.WebRequestTimeout, m_Addressables.CatalogRequestsTimeout);
        }
#endif
#if UNITY_EDITOR
        [UnityTest]
        public IEnumerator LoadingContentCatalogTwice_DoesNotThrowException_WhenHandleIsntReleased()
        {
            yield return Init();

            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            Directory.CreateDirectory(kCatalogFolderPath);
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                ContentCatalogData data = new ContentCatalogData("test_catalog");
                data.SetData(new List<ContentCatalogDataEntry>
                {
                    new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"})
                });
                data.SaveToFile(fullRemotePath);
            }
            else
            {
                string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
                if (baseCatalogPath.StartsWith("file://"))
                    baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
                File.Copy(baseCatalogPath, fullRemotePath);
            }

            var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op1;

            var op2 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op2;

            Assert.AreEqual(AsyncOperationStatus.Succeeded, op1.Status);
            Assert.AreEqual(AsyncOperationStatus.Succeeded, op2.Status);

            m_Addressables.Release(op1);
            m_Addressables.Release(op2);
            if (Directory.Exists(kCatalogFolderPath))
                Directory.Delete(kCatalogFolderPath, true);
        }

#endif

#if UNITY_EDITOR
        [UnityTest]
        public IEnumerator LoadingContentCatalogWithCacheTwice_DoesNotThrowException_WhenHandleIsntReleased()
        {
            yield return Init();

            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            Directory.CreateDirectory(kCatalogFolderPath);
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                ContentCatalogData data = new ContentCatalogData("test_catalog");
                data.SetData(new List<ContentCatalogDataEntry>
                {
                    new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"})
                });
                data.SaveToFile(fullRemotePath);
            }
            else
            {
                string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
                if (baseCatalogPath.StartsWith("file://"))
                    baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
                File.Copy(baseCatalogPath, fullRemotePath);
            }

            WriteHashFileForCatalog(fullRemotePath, "123");

            var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op1;

            var op2 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op2;

            Assert.AreEqual(AsyncOperationStatus.Succeeded, op1.Status);
            Assert.AreEqual(AsyncOperationStatus.Succeeded, op2.Status);

            m_Addressables.Release(op1);
            m_Addressables.Release(op2);
            if (Directory.Exists(kCatalogFolderPath))
                Directory.Delete(kCatalogFolderPath, true);
        }

#endif

        [UnityTest]
        public IEnumerator LoadingContentCatalog_WithInvalidCatalogPath_Fails()
        {
            yield return Init();

            bool ignoreValue = LogAssert.ignoreFailingMessages;
            LogAssert.ignoreFailingMessages = true;

            var op1 = m_Addressables.LoadContentCatalogAsync("notarealpath" + kCatalogExt, false);
            yield return op1;

            Assert.AreEqual(AsyncOperationStatus.Failed, op1.Status);

            m_Addressables.Release(op1);
            LogAssert.ignoreFailingMessages = ignoreValue;
        }

        private const string kCatalogRemotePath = "remotecatalog" + kCatalogExt;
        private const string kCatalogFolderPath = "Assets/CatalogTestFolder";

        bool CreateCatalogAtFakeRemotePath(string fakeRemotePath, string catalogFolderPath = kCatalogFolderPath)
        {
            Directory.CreateDirectory(catalogFolderPath);
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
#if UNITY_EDITOR
                ContentCatalogData data = new ContentCatalogData("test_catalog");
                data.SetData(new List<ContentCatalogDataEntry>
                {
                    new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"})
                });
                data.SaveToFile(fakeRemotePath);
#else
                return false;
#endif
            }
            else
            {
                string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
                if (baseCatalogPath.StartsWith("file://"))
                    baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
                File.Copy(baseCatalogPath, fakeRemotePath);
            }

            return true;
        }

        private string WriteHashFileForCatalog(string catalogPath, string hash)
        {
            string hashPath = catalogPath.Replace(kCatalogExt, ".hash");
            Directory.CreateDirectory(Path.GetDirectoryName(hashPath));
            File.WriteAllText(hashPath, hash);
            return hashPath;
        }

        void StubTextAndJsonProviders()
        {
            var textProvider = m_Addressables.ResourceManager.ResourceProviders.FirstOrDefault(rp => rp.GetType() == typeof(TextDataProvider)) as TextDataProvider;
            var jsonProvider = m_Addressables.ResourceManager.ResourceProviders.FirstOrDefault(rp => rp.GetType() == typeof(JsonAssetProvider)) as JsonAssetProvider;

            var textDataProviderStub = new TextDataProviderStub(kCatalogFolderPath, textProvider);
            var jsonAssetProviderStub = new JsonAssetProviderStub(kCatalogFolderPath, jsonProvider);

            m_Addressables.ResourceManager.ResourceProviders.Remove(textProvider);
            m_Addressables.ResourceManager.ResourceProviders.Remove(jsonProvider);
            m_Addressables.ResourceManager.ResourceProviders.Add(textDataProviderStub);
            m_Addressables.ResourceManager.ResourceProviders.Add(jsonAssetProviderStub);
            m_Addressables.ResourceManager.m_providerMap.Clear();
        }

#if UNITY_EDITOR
        [UnityTest]
        public IEnumerator LoadingContentCatalog_CachesCatalogData_IfValidHashFound()
        {
            yield return Init();

            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            Directory.CreateDirectory(kCatalogFolderPath);
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                ContentCatalogData data = new ContentCatalogData("test_catalog");
                data.SetData(new List<ContentCatalogDataEntry>
                {
                    new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"})
                });
                data.SaveToFile(fullRemotePath);
            }
            else
            {
                string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
                if (baseCatalogPath.StartsWith("file://"))
                    baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
                File.Copy(baseCatalogPath, fullRemotePath);
            }

            WriteHashFileForCatalog(fullRemotePath, "123");

            var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op1;

            string fullRemoteHashPath = fullRemotePath.Replace(kCatalogExt, ".hash");
            string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + fullRemoteHashPath.GetHashCode() + fullRemotePath.Substring(fullRemotePath.LastIndexOf(".")));
            string cachedHashPath = cachedDataPath.Replace(kCatalogExt, ".hash");
            Assert.IsTrue(File.Exists(cachedDataPath));
            Assert.IsTrue(File.Exists(cachedHashPath));
            Assert.AreEqual("123", File.ReadAllText(cachedHashPath));

            m_Addressables.Release(op1);
            Directory.Delete(kCatalogFolderPath, true);
            File.Delete(cachedDataPath);
            File.Delete(cachedHashPath);
        }

#endif

#if UNITY_EDITOR
        [UnityTest]
        public IEnumerator LoadingContentCatalog_CachesCatalogData_IfValidHashFoundAndRemotePathContainsQueryParameters()
        {
            yield return Init();

            string fakeFullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            if (!CreateCatalogAtFakeRemotePath(fakeFullRemotePath))
                Assert.Ignore($"Skipping test {TestContext.CurrentContext.Test.Name} due to missing CatalogLocation.");
            WriteHashFileForCatalog(fakeFullRemotePath, "123");

            StubTextAndJsonProviders();

            string catalogRemotePath = "http://127.0.0.1/" + kCatalogRemotePath;
            string catalogRemotePathWithQueryParams = catalogRemotePath + "?param1=value1&param2=value2:date=number";
            var op1 = m_Addressables.LoadContentCatalogAsync(catalogRemotePathWithQueryParams, false);
            yield return op1;

            var expectedHash = catalogRemotePath.Replace(kCatalogExt, ".hash").GetHashCode();
            string expectedCatalogName = expectedHash + kCatalogExt;
            string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + expectedCatalogName);
            string cachedHashPath = cachedDataPath.Replace(kCatalogExt, ".hash");
            Assert.IsTrue(File.Exists(cachedDataPath));
            Assert.IsTrue(File.Exists(cachedHashPath));
            Assert.AreEqual("123", File.ReadAllText(cachedHashPath));

            m_Addressables.Release(op1);
            Directory.Delete(kCatalogFolderPath, true);
            File.Delete(cachedDataPath);
            File.Delete(cachedHashPath);
        }

        [UnityTest]
        public IEnumerator LoadingContentCatalog_CachesCatalogData_ForTwoCatalogsWithSameName()
        {
            yield return Init();

            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            string fullRemotePathTwo = Path.Combine(kCatalogFolderPath, "secondCatalog", kCatalogRemotePath);
            Directory.CreateDirectory(kCatalogFolderPath);
            Directory.CreateDirectory(Path.Combine(kCatalogFolderPath, "secondCatalog"));
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                ContentCatalogData data = new ContentCatalogData("test_catalog");
                data.SetData(new List<ContentCatalogDataEntry>
                {
                    new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"})
                });
                data.SaveToFile(fullRemotePath);
                data.SaveToFile(fullRemotePathTwo);
            }
            else
            {
                string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
                if (baseCatalogPath.StartsWith("file://"))
                    baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
                File.Copy(baseCatalogPath, fullRemotePath);
            }
            ContentCatalogData catalogData = new ContentCatalogData("test_catalog");
            catalogData.SetData(new List<ContentCatalogDataEntry>
                {
                    new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"})
                });

            catalogData.SaveToFile(fullRemotePathTwo);

            WriteHashFileForCatalog(fullRemotePath, "123");
            WriteHashFileForCatalog(fullRemotePathTwo, "123");

            var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op1;

            var op2 = m_Addressables.LoadContentCatalogAsync(fullRemotePathTwo, false);
            yield return op2;

            string fullRemoteHashPath = fullRemotePath.Replace(kCatalogExt, ".hash");
            string fullRemoteHashPathTwo = fullRemotePathTwo.Replace(kCatalogExt, ".hash");
            string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + fullRemoteHashPath.GetHashCode() + fullRemotePath.Substring(fullRemotePath.LastIndexOf(".")));
            string cachedDataPathTwo =
                m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + fullRemoteHashPathTwo.GetHashCode() + fullRemotePathTwo.Substring(fullRemotePathTwo.LastIndexOf(".")));
            string cachedHashPath = cachedDataPath.Replace(kCatalogExt, ".hash");
            string cachedHashPathTwo = cachedDataPathTwo.Replace(kCatalogExt, ".hash");
            Assert.IsTrue(File.Exists(cachedDataPath));
            Assert.IsTrue(File.Exists(cachedDataPathTwo));
            Assert.IsTrue(File.Exists(cachedHashPath));
            Assert.IsTrue(File.Exists(cachedHashPathTwo));
            Assert.AreEqual("123", File.ReadAllText(cachedHashPath));
            Assert.AreEqual("123", File.ReadAllText(cachedHashPathTwo));

            m_Addressables.Release(op1);
            m_Addressables.Release(op2);
            Directory.Delete(kCatalogFolderPath, true);
            File.Delete(cachedDataPath);
            File.Delete(cachedHashPath);
            File.Delete(cachedDataPathTwo);
            File.Delete(cachedHashPathTwo);
        }

#endif

#if UNITY_EDITOR
        [UnityTest]
        public IEnumerator LoadingContentCatalog_IfNoCachedHashFound_Succeeds()
        {
            yield return Init();

            ResourceManager.ExceptionHandler = m_PrevHandler;
            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            Directory.CreateDirectory(kCatalogFolderPath);
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                ContentCatalogData data = new ContentCatalogData("test_catalog");
                data.SetData(new List<ContentCatalogDataEntry>
                {
                    new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"})
                });

                data.SaveToFile(fullRemotePath);
            }
            else
            {
                string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
                if (baseCatalogPath.StartsWith("file://"))
                    baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
                File.Copy(baseCatalogPath, fullRemotePath);
            }

            WriteHashFileForCatalog(fullRemotePath, "123");

            string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + Path.GetFileName(kCatalogRemotePath));
            string cachedHashPath = cachedDataPath.Replace(kCatalogExt, ".hash");
            if (File.Exists(cachedDataPath))
                File.Delete(cachedDataPath);
            if (File.Exists(cachedHashPath))
                File.Delete(cachedHashPath);
            var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op1;

            Assert.IsTrue(op1.IsValid());
            Assert.AreEqual(AsyncOperationStatus.Succeeded, op1.Status);
            Assert.NotNull(op1.Result);

            // Cleanup
            Addressables.Release(op1);
            Directory.Delete(kCatalogFolderPath, true);
            File.Delete(cachedDataPath);
            File.Delete(cachedHashPath);
        }

        [UnityTest]
        public IEnumerator LoadingContentCatalog_IfNoHashFileForCatalog_DoesntThrowException()
        {
            yield return Init();
            ResourceManager.ExceptionHandler = m_PrevHandler;
            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            Directory.CreateDirectory(kCatalogFolderPath);
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                ContentCatalogData data = new ContentCatalogData("test_catalog");
                data.SetData(new List<ContentCatalogDataEntry>
                {
                    new ContentCatalogDataEntry(typeof(string), "testString", "test.provider", new[] {"key"})
                });
                data.SaveToFile(fullRemotePath);
            }
            else
            {
                string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
                if (baseCatalogPath.StartsWith("file://"))
                    baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
                File.Copy(baseCatalogPath, fullRemotePath);
            }


            string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + Path.GetFileName(kCatalogRemotePath));
            string cachedHashPath = cachedDataPath.Replace(kCatalogExt, ".hash");
            if (File.Exists(cachedDataPath))
                File.Delete(cachedDataPath);
            if (File.Exists(cachedHashPath))
                File.Delete(cachedHashPath);
            var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op1;

            Assert.IsTrue(op1.IsValid());
            Assert.AreEqual(AsyncOperationStatus.Succeeded, op1.Status);
            Assert.NotNull(op1.Result);
            Assert.IsFalse(File.Exists(cachedHashPath));

            // Cleanup
            Addressables.Release(op1);
            Directory.Delete(kCatalogFolderPath, true);
            File.Delete(cachedDataPath);
            File.Delete(cachedHashPath);
        }

#endif
        [UnityTest]
        public IEnumerator IResourceLocationComparing_SameKeySameTypeDifferentInternalId_ReturnsFalse()
        {
            yield return Init();

            IResourceLocation loc1 = new ResourceLocationBase("address", "internalid1", typeof(BundledAssetProvider).FullName, typeof(GameObject));
            IResourceLocation loc2 = new ResourceLocationBase("address", "internalid2", typeof(BundledAssetProvider).FullName, typeof(GameObject));

            Assert.IsFalse(m_Addressables.Equals(loc1, loc2));
        }

        [UnityTest]
        public IEnumerator IResourceLocationComparing_SameKeyTypeAndInternalId_ReturnsTrue()
        {
            yield return Init();

            IResourceLocation loc1 = new ResourceLocationBase("address", "internalid1", typeof(BundledAssetProvider).FullName, typeof(GameObject));
            IResourceLocation loc2 = new ResourceLocationBase("address", "internalid1", typeof(BundledAssetProvider).FullName, typeof(GameObject));

            Assert.IsTrue(m_Addressables.Equals(loc1, loc2));
        }

#if UNITY_EDITOR
        [UnityTest]
        public IEnumerator LoadingContentCatalog_UpdatesCachedData_IfHashFileUpdates()
        {
            yield return Init();
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                UnityEngine.Debug.Log($"Skipping test {nameof(LoadingContentCatalog_UpdatesCachedData_IfHashFileUpdates)} due to missing CatalogLocation.");
                yield break;
            }

            Directory.CreateDirectory(kCatalogFolderPath);
            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            string fullRemoteHashPath = fullRemotePath.Replace(kCatalogExt, ".hash");
            string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + fullRemoteHashPath.GetHashCode() + fullRemotePath.Substring(fullRemotePath.LastIndexOf(".")));
            string cachedHashPath = cachedDataPath.Replace(kCatalogExt, ".hash");
            string remoteHashPath = WriteHashFileForCatalog(fullRemotePath, "123");

            string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
            if (baseCatalogPath.StartsWith("file://"))
                baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
            File.Copy(baseCatalogPath, fullRemotePath);

            var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op1;
            m_Addressables.Release(op1);

            Assert.IsTrue(File.Exists(cachedDataPath));
            Assert.IsTrue(File.Exists(cachedHashPath));
            Assert.AreEqual("123", File.ReadAllText(cachedHashPath));

            remoteHashPath = WriteHashFileForCatalog(fullRemotePath, "456");

            var op2 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op2;

            Assert.AreEqual("456", File.ReadAllText(cachedHashPath));

            m_Addressables.Release(op2);
            Directory.Delete(kCatalogFolderPath, true);
            File.Delete(cachedDataPath);
            File.Delete(cachedHashPath);
        }

        [UnityTest]
        public IEnumerator UpdateContentCatalog_UpdatesCachedData_IfCacheCorrupted()
        {
            yield return Init();
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                UnityEngine.Debug.Log($"Skipping test {nameof(LoadingContentCatalog_UpdatesCachedData_IfHashFileUpdates)} due to missing CatalogLocation.");
                yield break;
            }

            Directory.CreateDirectory(kCatalogFolderPath);
            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            string fullRemoteHashPath = fullRemotePath.Replace(kCatalogExt, ".hash");
            string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + fullRemoteHashPath.GetHashCode() + fullRemotePath.Substring(fullRemotePath.LastIndexOf(".")));
            string cachedHashPath = cachedDataPath.Replace(kCatalogExt, ".hash");
            string remoteHashPath = WriteHashFileForCatalog(fullRemoteHashPath, "123");

            string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
            if (baseCatalogPath.StartsWith("file://"))
                baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
            File.Copy(baseCatalogPath, fullRemotePath);

            File.WriteAllText(remoteHashPath, File.ReadAllText(cachedHashPath));
            File.WriteAllText(cachedDataPath, "corrupted content");

            //load from fullRemotePath will first load cachedDataPath, then load fullRemotePath on error
            var op = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            LogAssert.Expect(LogType.Exception, new Regex(".*JSON parse error.*"));
            yield return op;

            Assert.IsTrue(File.Exists(cachedDataPath));
            Assert.IsTrue(File.Exists(cachedHashPath));
            Assert.AreEqual(File.ReadAllText(cachedDataPath), File.ReadAllText(fullRemotePath));

            m_Addressables.Release(op);
            Directory.Delete(kCatalogFolderPath, true);
            File.Delete(cachedDataPath);
            File.Delete(cachedHashPath);
        }

        [UnityTest]
        public IEnumerator LoadingContentCatalog_NoCacheDataCreated_IfRemoteHashDoesntExist()
        {
            yield return Init();
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                UnityEngine.Debug.Log($"Skipping test {nameof(LoadingContentCatalog_NoCacheDataCreated_IfRemoteHashDoesntExist)} due to missing CatalogLocation.");
                yield break;
            }

            Directory.CreateDirectory(kCatalogFolderPath);
            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);
            string cachedDataPath = m_Addressables.ResolveInternalId(AddressablesImpl.kCacheDataFolder + Path.GetFileName(kCatalogRemotePath));
            string cachedHashPath = cachedDataPath.Replace(kCatalogExt, ".hash");

            string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
            if (baseCatalogPath.StartsWith("file://"))
                baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
            File.Copy(baseCatalogPath, fullRemotePath);

            var op1 = m_Addressables.LoadContentCatalogAsync(fullRemotePath, false);
            yield return op1;
            m_Addressables.Release(op1);

            Assert.IsFalse(File.Exists(cachedDataPath));
            Assert.IsFalse(File.Exists(cachedHashPath));

            Directory.Delete(kCatalogFolderPath, true);
        }

        [UnityTest]
        public IEnumerator ContentCatalogData_IsCleared_WhenInitializationOperationLoadContentCatalogOp_IsReleased()
        {
            yield return Init();
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                UnityEngine.Debug.Log($"Skipping test {nameof(ContentCatalogData_IsCleared_WhenInitializationOperationLoadContentCatalogOp_IsReleased)} due to missing CatalogLocation.");
                yield break;
            }

            string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;

            if (baseCatalogPath.StartsWith("file://"))
                baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;

            var location = m_Addressables.CreateCatalogLocationWithHashDependencies<ContentCatalogProvider>(baseCatalogPath);
            var loadCatalogHandle = InitializationOperation.LoadContentCatalog(m_Addressables, location, string.Empty);

            yield return loadCatalogHandle;
            ContentCatalogProvider ccp = m_Addressables.ResourceManager.ResourceProviders
                .FirstOrDefault(rp => rp.GetType() == typeof(ContentCatalogProvider)) as ContentCatalogProvider;

            var ccd = ccp.m_LocationToCatalogLoadOpMap[location].m_ContentCatalogData;
            Assert.IsFalse(CatalogDataWasCleaned(ccd));

            loadCatalogHandle.Release();

            Assert.IsTrue(CatalogDataWasCleaned(ccd));

            PostTearDownEvent = ResetAddressables;
        }

#endif

        internal bool CatalogDataWasCleaned(ContentCatalogData data)
        {
#if ENABLE_BINARY_CATALOG
            return string.IsNullOrEmpty(data.m_LocatorId);
#else
            return string.IsNullOrEmpty(data.m_KeyDataString) &&
                   string.IsNullOrEmpty(data.m_BucketDataString) &&
                   string.IsNullOrEmpty(data.m_EntryDataString) &&
                   string.IsNullOrEmpty(data.m_ExtraDataString) &&
                   data.m_InternalIds == null &&
                   string.IsNullOrEmpty(data.m_LocatorId) &&
                   data.m_ProviderIds == null &&
                   data.m_ResourceProviderData == null &&
                   data.m_resourceTypes == null;
#endif
        }

#if UNITY_EDITOR
        [UnityTest]
        public IEnumerator ContentCatalogData_IsCleared_ForCorrectCatalogLoadOp_WhenOpIsReleased()
        {
            yield return Init();
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                UnityEngine.Debug.Log($"Skipping test {nameof(ContentCatalogData_IsCleared_ForCorrectCatalogLoadOp_WhenOpIsReleased)} due to missing CatalogLocation.");
                yield break;
            }

            Directory.CreateDirectory(kCatalogFolderPath);
            string fullRemotePath = Path.Combine(kCatalogFolderPath, kCatalogRemotePath);

            string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
            if (baseCatalogPath.StartsWith("file://"))
                baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;
            File.Copy(baseCatalogPath, fullRemotePath);

            var location = m_Addressables.CreateCatalogLocationWithHashDependencies<ContentCatalogProvider>(baseCatalogPath);
            var location2 = m_Addressables.CreateCatalogLocationWithHashDependencies<ContentCatalogProvider>(fullRemotePath);
            var loadCatalogHandle = InitializationOperation.LoadContentCatalog(m_Addressables, location, string.Empty);
            yield return loadCatalogHandle;
            var loadCatalogHandle2 = InitializationOperation.LoadContentCatalog(m_Addressables, location2, string.Empty);
            yield return loadCatalogHandle2;

            ContentCatalogProvider ccp = m_Addressables.ResourceManager.ResourceProviders
                .FirstOrDefault(rp => rp.GetType() == typeof(ContentCatalogProvider)) as ContentCatalogProvider;

            var ccd = ccp.m_LocationToCatalogLoadOpMap[location].m_ContentCatalogData;
            var ccd2 = ccp.m_LocationToCatalogLoadOpMap[location2].m_ContentCatalogData;

            Assert.IsFalse(CatalogDataWasCleaned(ccd));
            Assert.IsFalse(CatalogDataWasCleaned(ccd2));

            loadCatalogHandle.Release();

            Assert.IsTrue(CatalogDataWasCleaned(ccd));
            Assert.IsFalse(CatalogDataWasCleaned(ccd2));

            Directory.Delete(kCatalogFolderPath, true);
            loadCatalogHandle2.Release();

            PostTearDownEvent = ResetAddressables;
        }

#endif

        [UnityTest]
        public IEnumerator ContentCatalogProvider_RemovesEntryFromMap_WhenOperationHandleReleased()
        {
            yield return Init();
            if (m_Addressables.m_ResourceLocators[0].CatalogLocation == null)
            {
                UnityEngine.Debug.Log($"Skipping test {nameof(ContentCatalogProvider_RemovesEntryFromMap_WhenOperationHandleReleased)} due to missing CatalogLocation.");
                yield break;
            }

            string baseCatalogPath = m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId;
            if (baseCatalogPath.StartsWith("file://"))
                baseCatalogPath = new Uri(m_Addressables.m_ResourceLocators[0].CatalogLocation.InternalId).AbsolutePath;

            var handle = m_Addressables.LoadContentCatalogAsync(baseCatalogPath, false);
            yield return handle;

            ContentCatalogProvider ccp = m_Addressables.ResourceManager.ResourceProviders
                .FirstOrDefault(rp => rp.GetType() == typeof(ContentCatalogProvider)) as ContentCatalogProvider;

            Assert.AreEqual(1, ccp.m_LocationToCatalogLoadOpMap.Count);

            handle.Release();

            Assert.AreEqual(0, ccp.m_LocationToCatalogLoadOpMap.Count);

            PostTearDownEvent = ResetAddressables;
        }

        [UnityTest]
        public IEnumerator VerifyProfileVariableEvaluation()
        {
            yield return Init();
            Assert.AreEqual(string.Format("{0}", m_Addressables.RuntimePath), AddressablesRuntimeProperties.EvaluateString("{UnityEngine.AddressableAssets.Addressables.RuntimePath}"));
        }

        [UnityTest]
        public IEnumerator VerifyDownloadSize()
        {
            yield return Init();
            long expectedSize = 0;
            var locMap = new ResourceLocationMap("TestLocator");

            var bundleLoc1 = new ResourceLocationBase("sizeTestBundle1", "http://nonExistingUrlForAddressableTests1337.com/mybundle1.bundle", typeof(AssetBundleProvider).FullName, typeof(object));
            var sizeData1 = (bundleLoc1.Data = CreateLocationSizeData("sizeTestBundle1", 1000, 123, "hashstring1")) as ILocationSizeData;
            if (sizeData1 != null)
                expectedSize += sizeData1.ComputeSize(bundleLoc1, null);

            var bundleLoc2 = new ResourceLocationBase("sizeTestBundle2", "http://nonExistingUrlForAddressableTests1337.com/mybundle2.bundle", typeof(AssetBundleProvider).FullName, typeof(object));
            var sizeData2 = (bundleLoc2.Data = CreateLocationSizeData("sizeTestBundle2", 500, 123, "hashstring2")) as ILocationSizeData;
            if (sizeData2 != null)
                expectedSize += sizeData2.ComputeSize(bundleLoc2, null);

            var assetLoc = new ResourceLocationBase("sizeTestAsset", "myASset.asset", typeof(BundledAssetProvider).FullName, typeof(object), bundleLoc1, bundleLoc2);

            locMap.Add("sizeTestBundle1", bundleLoc1);
            locMap.Add("sizeTestBundle2", bundleLoc2);
            locMap.Add("sizeTestAsset", assetLoc);
            m_Addressables.AddResourceLocator(locMap);

            var dOp = m_Addressables.GetDownloadSizeAsync((object)"sizeTestAsset");
            yield return dOp;
            Assert.AreEqual(expectedSize, dOp.Result);
            dOp.Release();
        }

        public IEnumerator GetDownloadSize_CalculatesCachedBundlesInternal()
        {
#if ENABLE_CACHING
            yield return Init();
            long expectedSize = 0;
            long bundleSize1 = 1000;
            long bundleSize2 = 500;
            var locMap = new ResourceLocationMap("TestLocator");

            Caching.ClearCache();
            //Simulating a cached bundle
            string fakeCachePath = CreateFakeCachedBundle("cachedSizeTestBundle1", "be38e35d2177c282d5d6a2e54a803aab");

            var bundleLoc1 = new ResourceLocationBase("cachedSizeTestBundle1", "http://nonExistingUrlForAddressableTests1337.com/GetDownloadSize_CalculatesCachedBundlesBundle1.bundle",
                typeof(AssetBundleProvider).FullName, typeof(object));
            var sizeData1 =
                (bundleLoc1.Data = CreateLocationSizeData("cachedSizeTestBundle1", bundleSize1, 123,
                    "be38e35d2177c282d5d6a2e54a803aab")) as ILocationSizeData;
            if (sizeData1 != null)
                expectedSize += sizeData1.ComputeSize(bundleLoc1, null);

            var bundleLoc2 = new ResourceLocationBase("sizeTestBundle2", "http://nonExistingUrlForAddressableTests1337.com/GetDownloadSize_CalculatesCachedBundlesBundle2.bundle",
                typeof(AssetBundleProvider).FullName, typeof(object));
            var sizeData2 =
                (bundleLoc2.Data = CreateLocationSizeData("sizeTestBundle2", bundleSize2, 123,
                    "d9fe965a6b253fb9dbd3e1cb08b7d66f")) as ILocationSizeData;
            if (sizeData2 != null)
                expectedSize += sizeData2.ComputeSize(bundleLoc2, null);

            var assetLoc = new ResourceLocationBase("cachedSizeTestAsset", "myASset.asset",
                typeof(BundledAssetProvider).FullName, typeof(object), bundleLoc1, bundleLoc2);

            locMap.Add("cachedSizeTestBundle1", bundleLoc1);
            locMap.Add("cachedSizeTestBundle2", bundleLoc2);
            locMap.Add("cachedSizeTestAsset", assetLoc);
            m_Addressables.AddResourceLocator(locMap);

            var dOp = m_Addressables.GetDownloadSizeAsync((object)"cachedSizeTestAsset");
            yield return dOp;
            Assert.IsTrue((bundleSize1 + bundleSize2) > dOp.Result);
            Assert.AreEqual(expectedSize, dOp.Result);

            dOp.Release();
            m_Addressables.RemoveResourceLocator(locMap);
            Directory.Delete(fakeCachePath, true);
#else
            Assert.Ignore();
            yield break;
#endif
        }

        public IEnumerator GetDownloadSize_WithList_CalculatesCachedBundlesInternal()
        {
#if ENABLE_CACHING
            yield return Init();
            long expectedSize = 0;
            long bundleSize1 = 1000;
            long bundleSize2 = 500;
            var locMap = new ResourceLocationMap("TestLocator");

            Assert.IsTrue(Caching.ClearCache(), "Was unable to clear the cache.  Test results are affected");
            //Simulating a cached bundle
            string fakeCachePath = CreateFakeCachedBundle("cachedSizeTestBundle1", "0e38e35d2177c282d5d6a2e54a803aab");

            var bundleLoc1 = new ResourceLocationBase("cachedSizeTestBundle1", "http://nonExistingUrlForAddressableTests1337.com/GetDownloadSize_WithList_CalculatesCachedBundlesBundle1.bundle",
                typeof(AssetBundleProvider).FullName, typeof(object));
            var sizeData1 =
                (bundleLoc1.Data = CreateLocationSizeData("cachedSizeTestBundle1", bundleSize1, 123,
                    "0e38e35d2177c282d5d6a2e54a803aab")) as ILocationSizeData;
            if (sizeData1 != null)
                expectedSize += sizeData1.ComputeSize(bundleLoc1, null);

            var bundleLoc2 = new ResourceLocationBase("sizeTestBundle2", "http://nonExistingUrlForAddressableTests1337.com/GetDownloadSize_WithList_CalculatesCachedBundlesBundle2.bundle",
                typeof(AssetBundleProvider).FullName, typeof(object));
            var sizeData2 =
                (bundleLoc2.Data = CreateLocationSizeData("sizeTestBundle2", bundleSize2, 123,
                    "09fe965a6b253fb9dbd3e1cb08b7d66f")) as ILocationSizeData;
            if (sizeData2 != null)
                expectedSize += sizeData2.ComputeSize(bundleLoc2, null);

            var assetLoc = new ResourceLocationBase("cachedSizeTestAsset", "myASset.asset",
                typeof(BundledAssetProvider).FullName, typeof(object), bundleLoc1, bundleLoc2);

            locMap.Add("cachedSizeTestBundle1", bundleLoc1);
            locMap.Add("cachedSizeTestBundle2", bundleLoc2);
            locMap.Add("cachedSizeTestAsset", assetLoc);
            m_Addressables.AddResourceLocator(locMap);

            var dOp = m_Addressables.GetDownloadSizeAsync(new List<object>()
                {
                    "cachedSizeTestAsset",
                    bundleLoc1,
                    bundleLoc2
                }
            );
            yield return dOp;
            Assert.IsTrue((bundleSize1 + bundleSize2) > dOp.Result);
            Assert.AreEqual(expectedSize, dOp.Result);

            dOp.Release();
            m_Addressables.RemoveResourceLocator(locMap);
            Directory.Delete(fakeCachePath, true);
#else
            Assert.Ignore();
            yield break;
#endif
        }

        public IEnumerator GetDownloadSize_WithList_CalculatesCorrectSize_WhenAssetsReferenceSameBundleInternal()
        {
#if ENABLE_CACHING
            yield return Init();
            long bundleSize1 = 1000;
            long expectedSize = 0;

            var bundleLoc1 = new ResourceLocationBase("sizeTestBundle1", "http://nonExistingUrlForAddressableTests1337.com/GetDownloadSize_WithList_CalculatesCachedBundlesBundle1.bundle",
                typeof(AssetBundleProvider).FullName, typeof(object));
            var sizeData1 =
                (bundleLoc1.Data = CreateLocationSizeData("cachedSizeTestBundle1", bundleSize1, 123,
                    "0e38e35d2177c282d5d6a2e54a803aab")) as ILocationSizeData;
            if (sizeData1 != null)
                expectedSize += sizeData1.ComputeSize(bundleLoc1, null);

            var assetLoc1 = new ResourceLocationBase("cachedSizeTestAsset1", "myAsset1.asset",
                typeof(BundledAssetProvider).FullName, typeof(object), bundleLoc1);

            var assetLoc2 = new ResourceLocationBase("cachedSizeTestAsset2", "myAsset2.asset",
                typeof(BundledAssetProvider).FullName, typeof(object), bundleLoc1);

            var dOp = m_Addressables.GetDownloadSizeAsync(new List<object>()
                {
                    assetLoc1,
                    assetLoc2
                }
            );
            yield return dOp;
            Assert.IsTrue(bundleSize1 >= dOp.Result);
            Assert.AreEqual(expectedSize, dOp.Result);
#else
            Assert.Ignore();
            yield break;
#endif
        }

        [UnityTest]
        public IEnumerator GetDownloadSize_WithList_CalculatesCorrectSize_WhenAssetsReferenceDifferentBundle()
        {
#if ENABLE_CACHING
            yield return Init();
            long bundleSize1 = 1000;
            long bundleSize2 = 250;
            long expectedSize = 0;

            var bundleLoc1 = new ResourceLocationBase("sizeTestBundle1", "http://nonExistingUrlForAddressableTests1337.com/GetDownloadSize_WithList_CalculatesCachedBundlesBundle1.bundle",
                typeof(AssetBundleProvider).FullName, typeof(object));
            var sizeData1 =
                (bundleLoc1.Data = CreateLocationSizeData("cachedSizeTestBundle1", bundleSize1, 123,
                    "0e38e35d2177c282d5d6a2e54a803aab")) as ILocationSizeData;
            if (sizeData1 != null)
                expectedSize += sizeData1.ComputeSize(bundleLoc1, null);

            var bundleLoc2 = new ResourceLocationBase("cachedSizeTestBundle2", "http://nonExistingUrlForAddressableTests1337.com/GetDownloadSize_WithList_CalculatesCachedBundlesBundle2.bundle",
                typeof(AssetBundleProvider).FullName, typeof(object));
            var sizeData2 =
                (bundleLoc2.Data = CreateLocationSizeData("cachedSizeTestBundle2", bundleSize2, 123,
                    "09fe965a6b253fb9dbd3e1cb08b7d66f")) as ILocationSizeData;
            if (sizeData2 != null)
                expectedSize += sizeData2.ComputeSize(bundleLoc2, null);

            var assetLoc1 = new ResourceLocationBase("cachedSizeTestAsset1", "myAsset1.asset",
                typeof(BundledAssetProvider).FullName, typeof(object), bundleLoc1);

            var assetLoc2 = new ResourceLocationBase("cachedSizeTestAsset2", "myAsset2.asset",
                typeof(BundledAssetProvider).FullName, typeof(object), bundleLoc2);

            var dOp = m_Addressables.GetDownloadSizeAsync(new List<object>()
                {
                    assetLoc1,
                    assetLoc2
                }
            );
            yield return dOp;
            Assert.IsTrue((bundleSize1 + bundleSize2) >= dOp.Result);
            Assert.AreEqual(expectedSize, dOp.Result);
#else
            Assert.Ignore();
            yield break;
#endif
        }

        [UnityTest]
        public IEnumerator GetResourceLocationsWithCorrectKeyAndWrongTypeReturnsEmptyResult()
        {
            yield return Init();
            AsyncOperationHandle<IList<IResourceLocation>> op = m_Addressables.LoadResourceLocationsAsync("prefabs_evenBASE", typeof(Texture2D));
            yield return op;
            Assert.AreEqual(AsyncOperationStatus.Succeeded, op.Status);
            Assert.IsNotNull(op.Result);
            Assert.AreEqual(op.Result.Count, 0);
            op.Release();
        }

        [UnityTest]
        public IEnumerator CanGetResourceLocationsWithSingleKey()
        {
            yield return Init();
            int loadCount = 0;
            int loadedCount = 0;
            var ops = new List<AsyncOperationHandle<IList<IResourceLocation>>>();
            foreach (var k in m_KeysHashSet)
            {
                loadCount++;
                AsyncOperationHandle<IList<IResourceLocation>> op = m_Addressables.LoadResourceLocationsAsync(k.Key, typeof(object));
                ops.Add(op);
                op.Completed += op2 =>
                {
                    loadedCount++;
                    Assert.IsNotNull(op2.Result);
                    Assert.AreEqual(k.Value, op2.Result.Count);
                };
            }

            foreach (var op in ops)
            {
                yield return op;
                op.Release();
            }
        }

        [UnityTest]
        public IEnumerator GetResourceLocationsMergeModesFailsWithNoKeys(
            [Values(Addressables.MergeMode.UseFirst, Addressables.MergeMode.Intersection, Addressables.MergeMode.Union)] Addressables.MergeMode mode)
        {
            yield return Init();

            IList<IResourceLocation> results;
            var ret = m_Addressables.GetResourceLocations(new object[] { }, typeof(GameObject), mode, out results);
            Assert.IsFalse(ret);
            Assert.IsNull(results);
        }

        [UnityTest]
        public IEnumerator GetResourceLocationsMergeModesSucceedsWithSingleKey(
            [Values(Addressables.MergeMode.UseFirst, Addressables.MergeMode.Intersection, Addressables.MergeMode.Union)] Addressables.MergeMode mode)
        {
            yield return Init();

            IList<IResourceLocation> results;
            var ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE"}, typeof(GameObject), mode, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);
        }

        [UnityTest]
        public IEnumerator GetResourceLocationsMergeModeUnionSucceedsWithValidKeys()
        {
            yield return Init();

            IList<IResourceLocation> results;
            var ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);
            var evenCount = results.Count;

            ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_oddBASE"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);
            var oddCount = results.Count;

            ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE", "prefabs_oddBASE"}, typeof(GameObject), Addressables.MergeMode.Union, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);
            Assert.AreEqual(oddCount + evenCount, results.Count);
        }

        [UnityTest]
        public IEnumerator GetResourceLocationsMergeModeUnionSucceedsWithInvalidKeys()
        {
            yield return Init();

            IList<IResourceLocation> results;
            var ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);
            var evenCount = results.Count;

            ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_oddBASE"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);
            var oddCount = results.Count;

            ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE", "prefabs_oddBASE", "INVALIDKEY"}, typeof(GameObject), Addressables.MergeMode.Union, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);
            Assert.AreEqual(oddCount + evenCount, results.Count);
        }

        [UnityTest]
        public IEnumerator GetResourceLocationsMergeModeIntersectionFailsIfNoResultsDueToIntersection()
        {
            yield return Init();

            IList<IResourceLocation> results;
            var ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);

            ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_oddBASE"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);

            ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE", "prefabs_oddBASE"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsFalse(ret);
            Assert.IsNull(results);
        }

        [UnityTest]
        public IEnumerator GetResourceLocationsMergeModeIntersectionFailsIfNoResultsDueToInvalidKey()
        {
            yield return Init();

            IList<IResourceLocation> results;
            var ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);

            ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_oddBASE"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsTrue(ret);
            Assert.NotNull(results);
            Assert.GreaterOrEqual(results.Count, 1);

            ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE", "prefabs_oddBASE", "INVALIDKEY"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsFalse(ret);
            Assert.IsNull(results);

            ret = m_Addressables.GetResourceLocations(new object[] {"prefabs_evenBASE", "INVALIDKEY"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsFalse(ret);
            Assert.IsNull(results);

            ret = m_Addressables.GetResourceLocations(new object[] {"INVALIDKEY"}, typeof(GameObject), Addressables.MergeMode.Intersection, out results);
            Assert.IsFalse(ret);
            Assert.IsNull(results);
        }

        [UnityTest]
        public IEnumerator WhenLoadWithInvalidKey_ReturnedOpIsFailed()
        {
            yield return Init();
            List<object> keys = new List<object>() {"INVALID1", "INVALID2"};
            AsyncOperationHandle<IList<GameObject>> gop = new AsyncOperationHandle<IList<GameObject>>();
            using (new IgnoreFailingLogMessage())
            {
                gop = m_Addressables.LoadAssetsAsync<GameObject>(keys, null, Addressables.MergeMode.Intersection, true);
            }

            while (!gop.IsDone)
                yield return null;
            Assert.IsTrue(gop.IsDone);
            Assert.AreEqual(AsyncOperationStatus.Failed, gop.Status);
            m_Addressables.Release(gop);
        }

        [UnityTest]
        public IEnumerator CanLoadAssetsWithMultipleKeysMerged()
        {
            yield return Init();
            List<object> keys = new List<object>() {AddressablesTestUtility.GetPrefabLabel("BASE"), AddressablesTestUtility.GetPrefabUniqueLabel("BASE", 0)};
            AsyncOperationHandle<IList<GameObject>> gop = m_Addressables.LoadAssetsAsync<GameObject>(keys, null, Addressables.MergeMode.Intersection, true);
            while (!gop.IsDone)
                yield return null;
            Assert.IsTrue(gop.IsDone);
            Assert.AreEqual(AsyncOperationStatus.Succeeded, gop.Status);
            Assert.NotNull(gop.Result);
            Assert.AreEqual(1, gop.Result.Count);
            Assert.AreEqual(AsyncOperationStatus.Succeeded, gop.Status);
            m_Addressables.Release(gop);
        }

        [UnityTest]
        public IEnumerator Release_WhenObjectIsUnknown_LogsErrorAndDoesNotDestroy()
        {
            yield return Init();
            GameObject go = Object.Instantiate(GameObject.CreatePrimitive(PrimitiveType.Cube));
            go.name = "TestCube";

            m_Addressables.Release(go);
            LogAssert.Expect(LogType.Error, new Regex("Addressables.Release was called on.*"));
            yield return null;

            GameObject foundObj = GameObject.Find("TestCube");
            Assert.IsNotNull(foundObj);
            Object.Destroy(foundObj);
        }

        [UnityTest]
        public IEnumerator ReleaseInstance_WhenObjectIsUnknown_LogsErrorAndDestroys()
        {
            yield return Init();
            GameObject go = Object.Instantiate(GameObject.CreatePrimitive(PrimitiveType.Cube));
            go.name = "TestCube";

            Assert.IsFalse(m_Addressables.ReleaseInstance(go));
        }

        [UnityTest]
        public IEnumerator LoadAsset_WhenEntryExists_ReturnsAsset()
        {
            yield return Init();
            string label = AddressablesTestUtility.GetPrefabUniqueLabel("BASE", 0);
            AsyncOperationHandle<GameObject> op = m_Addressables.LoadAssetAsync<GameObject>(label);
            yield return op;
            Assert.AreEqual(AsyncOperationStatus.Succeeded, op.Status);
            Assert.IsTrue(op.Result != null);
            op.Release();
        }

        [UnityTest]
        public IEnumerator LoadAsset_SuccessfulWhenLoadAssetMode_LoadAllAssets()
        {
            yield return Init();
            if (string.IsNullOrEmpty(TypeName) || TypeName == "BuildScriptFastMode" || TypeName == "BuildScriptVirtualMode")
            {
                Assert.Ignore($"Skipping test {nameof(LoadAsset_SuccessfulWhenLoadAssetMode_LoadAllAssets)} for {TypeName}, AssetBundle based test.");
            }

            string label = AddressablesTestUtility.GetPrefabUniqueLabel("BASE", 0);

            var locationHandle = m_Addressables.LoadResourceLocationsAsync(label);
            yield return locationHandle;
            Assert.IsTrue(locationHandle.Result != null, "Failed to get Location for " + label);
            Assert.AreEqual(1, locationHandle.Result.Count, "Failed to get Location for " + label);
            IResourceLocation loc = locationHandle.Result[0];
            Addressables.Release(locationHandle);

            foreach (IResourceLocation dependency in loc.Dependencies)
            {
                var locOptions = dependency.Data as AssetBundleRequestOptions;
                Assert.IsNotNull(locOptions, "Location dependency did not contain expected AssetBundleRequestOptions data");
                locOptions.AssetLoadMode = AssetLoadMode.AllPackedAssetsAndDependencies;
            }

            AsyncOperationHandle<GameObject> op = m_Addressables.LoadAssetAsync<GameObject>(loc);
            yield return op;
            Assert.AreEqual(AsyncOperationStatus.Succeeded, op.Status, "Loading of " + label + " failed.");
            Assert.IsTrue(op.Result != null, "Loading of " + label + " was successful, but result was null.");
            op.Release();
        }

        [UnityTest]
        public IEnumerator LoadAssetWithWrongType_WhenEntryExists_Fails()
        {
            yield return Init();
            string label = AddressablesTestUtility.GetPrefabUniqueLabel("BASE", 0);
            AsyncOperationHandle<Texture> op = new AsyncOperationHandle<Texture>();
            using (new IgnoreFailingLogMessage())
            {
                op = m_Addressables.LoadAssetAsync<Texture>(label);
                yield return op;
            }

            Assert.AreEqual(AsyncOperationStatus.Failed, op.Status);
            Assert.IsNull(op.Result);
            op.Release();
        }

        [UnityTest]
        public IEnumerator LoadAsset_WhenEntryDoesNotExist_OperationFails()
        {
            yield return Init();
            AsyncOperationHandle<GameObject> op = new AsyncOperationHandle<GameObject>();
            using (new IgnoreFailingLogMessage())
            {
                op = m_Addressables.LoadAssetAsync<GameObject>("unknownlabel");
            }

            yield return op;
            Assert.AreEqual(AsyncOperationStatus.Failed, op.Status);
            Assert.IsTrue(op.Result == null);
            op.Release();
        }

        [UnityTest]
        public IEnumerator LoadAsset_CanReleaseThroughAddressablesInCallback([Values(true, false)] bool addressableRelease)
        {
            yield return Init();
            var op = m_Addressables.LoadAssetAsync<object>(m_PrefabKeysList[0]);
            op.Completed += x =>
            {
                Assert.IsNotNull(x.Result);
                if (addressableRelease)
                    m_Addressables.Release(x.Result);
                else
                    op.Release();
            };
            yield return op;
        }

        [UnityTest]
        public IEnumerator LoadAsset_WhenPrefabLoadedAsMultipleTypes_ResultIsEqual()
        {
            yield return Init();

            string label = AddressablesTestUtility.GetPrefabUniqueLabel("BASE", 0);
            AsyncOperationHandle<object> op1 = m_Addressables.LoadAssetAsync<object>(label);
            AsyncOperationHandle<GameObject> op2 = m_Addressables.LoadAssetAsync<GameObject>(label);
            yield return op1;
            yield return op2;
            Assert.AreEqual(op1.Result, op2.Result);
            Assert.AreEqual(AsyncOperationStatus.Succeeded, op1.Status);
            Assert.AreEqual(AsyncOperationStatus.Succeeded, op2.Status);
            op1.Release();
            op2.Release();
        }

        [UnityTest]
        public IEnumerator LoadAssets_InvokesCallbackPerAsset()
        {
            yield return Init();
            string label = AddressablesTestUtility.GetPrefabLabel("BASE");
            HashSet<GameObject> ops = new HashSet<GameObject>();
            var gop = m_Addressables.LoadAssetsAsync<GameObject>(label, x => { ops.Add(x); }, true);
            yield return gop;
            Assert.AreEqual(AddressablesTestUtility.kPrefabCount, ops.Count);
            for (int i = 0; i < ops.Count; i++)
                Assert.IsTrue(ops.Contains(gop.Result[i]));
            gop.Release();
        }

        [UnityTest]
        public IEnumerator LoadAssets_InvokesCallbackPerAssetBeforeCompletedCallback()
        {
            yield return Init();
            string label = AddressablesTestUtility.GetPrefabLabel("BASE");
            HashSet<GameObject> ops = new HashSet<GameObject>();
            int opsCompletedOnCompleted = 0;
            var gop = m_Addressables.LoadAssetsAsync<GameObject>(label, x => { ops.Add(x); }, true);
            gop.Completed += handle => { opsCompletedOnCompleted = ops.Count; };
            yield return gop;
            Assert.AreEqual(AddressablesTestUtility.kPrefabCount, ops.Count);
            Assert.AreEqual(AddressablesTestUtility.kPrefabCount, opsCompletedOnCompleted);
            for (int i = 0; i < ops.Count; i++)
                Assert.IsTrue(ops.Contains(gop.Result[i]));
            gop.Release();
        }

        // TODO: this doesn't actually check that something was downloaded. It is more: can load dependencies.
        // We really need to address the downloading feature
        [UnityTest]
        public IEnumerator DownloadDependencies_CanDownloadDependencies()
        {
            yield return Init();
            string label = AddressablesTestUtility.GetPrefabLabel("BASE");
            AsyncOperationHandle op = m_Addressables.DownloadDependenciesAsync(label);
            yield return op;
            op.Release();
        }

        [UnityTest]
        public IEnumerator DownloadDependencies_AutoReleaseHandle_ReleasesOnCompletion()
        {
            yield return Init();
            string label = AddressablesTestUtility.GetPrefabLabel("BASE");
            AsyncOperationHandle op = m_Addressables.DownloadDependenciesAsync(label, true);
            yield return op;
            Assert.IsFalse(op.IsValid());
        }

        [UnityTest]
        public IEnumerator DownloadDependenciesWithAddress_AutoReleaseHandle_ReleasesOnCompletion()
        {
            yield return Init();
            AsyncOperationHandle op = m_Addressables.DownloadDependenciesAsync(m_PrefabKeysList[0], true);
            yield return op;
            Assert.IsFalse(op.IsValid());
        }

        [UnityTest]
        public IEnumerator DownloadDependencies_DoesNotRetainLoadedBundles_WithAutoRelease()
        {
            yield return Init();
            int bundleCountBefore = AssetBundle.GetAllLoadedAssetBundles().Count();
            string label = AddressablesTestUtility.GetPrefabLabel("BASE");
            AsyncOperationHandle op = m_Addressables.DownloadDependenciesAsync(label, true);
            yield return op;
            AssetBundleProvider.WaitForAllUnloadingBundlesToComplete();
#if UNITY_2022_1_OR_NEWER
            Assert.AreEqual(bundleCountBefore, AssetBundleProvider.AssetBundleCount);
#else
            Assert.AreEqual(bundleCountBefore, AssetBundle.GetAllLoadedAssetBundles().Count());
#endif
        }

        [Test]
        public void AssetBundleProvider_CanSet_UnloadingBundles()
        {
#if UNITY_2022_1_OR_NEWER
            var unloadingBundles = AssetBundleProvider.UnloadingBundles;

            string key = "op1";
            var newBundles = new Dictionary<string, AssetBundleUnloadOperation>() {{ key, new AssetBundleUnloadOperation() }};
            AssetBundleProvider.UnloadingBundles = newBundles;
            Assert.IsTrue(AssetBundleProvider.UnloadingBundles.ContainsKey(key));

            AssetBundleProvider.UnloadingBundles = unloadingBundles;
#else
            Assert.Ignore($"Skipping test {nameof(AssetBundleProvider_CanSet_UnloadingBundles)}. Requires 2022.1+");
#endif
        }

        [UnityTest]
        [Ignore("Test is unstable until task refactor is finished.")]
        public IEnumerator DownloadDependencies_ReturnsValidTask()
        {
            yield return Init();
            string label = AddressablesTestUtility.GetPrefabLabel("BASE");
            AsyncOperationHandle op = m_Addressables.DownloadDependenciesAsync(label);

            Assert.IsNotNull(op.Task);
            yield return op;
            Assert.IsNotNull(op.Task);

            op.Release();
        }

        [UnityTest]
        public IEnumerator StressInstantiation()
        {
            yield return Init();

            // TODO: move this safety check to test fixture base
            var objs = SceneManager.GetActiveScene().GetRootGameObjects();
            foreach (var r in objs)
                Assert.False(r.name.EndsWith("(Clone)"), "All instances from previous test were not cleaned up");

            var ops = new List<AsyncOperationHandle<GameObject>>();
            for (int i = 0; i < 50; i++)
            {
                var key = m_PrefabKeysList[i % m_PrefabKeysList.Count];
                ops.Add(m_Addressables.InstantiateAsync(key));
            }

            foreach (AsyncOperationHandle<GameObject> op in ops)
                yield return op;

            foreach (AsyncOperationHandle<GameObject> op in ops)
                m_Addressables.ReleaseInstance(op.Result);

            yield return null;

            objs = SceneManager.GetActiveScene().GetRootGameObjects();
            foreach (var r in objs)
                Assert.False(r.name.EndsWith("(Clone)"), "All instances from this test were not cleaned up");
        }

        [UnityTest]
        public IEnumerator AssetReference_HandleIsInvalidated_WhenReleasingLoadOperation()
        {
            yield return Init();
            AsyncOperationHandle handle = m_Addressables.InstantiateAsync(AssetReferenceObjectKey);
            yield return handle;
            Assert.IsNotNull(handle.Result as GameObject);
            AssetReferenceTestBehavior behavior =
                (handle.Result as GameObject).GetComponent<AssetReferenceTestBehavior>();

            using (new IgnoreFailingLogMessage())
                yield return behavior.Reference.LoadAssetAsync<GameObject>();

            AsyncOperationHandle referenceHandle = behavior.Reference.OperationHandle;
            Assert.IsTrue(behavior.Reference.IsValid());
            m_Addressables.Release(referenceHandle);
            yield return referenceHandle;
            Assert.IsFalse(behavior.Reference.IsValid());

            handle.Release();
        }

        [UnityTest]
        public IEnumerator CanUnloadAssetReference_WithAddressables()
        {
            yield return Init();

            AsyncOperationHandle handle = m_Addressables.InstantiateAsync(AssetReferenceObjectKey);
            yield return handle;
            Assert.IsNotNull(handle.Result as GameObject);
            AssetReferenceTestBehavior behavior =
                (handle.Result as GameObject).GetComponent<AssetReferenceTestBehavior>();
            AsyncOperationHandle<GameObject> assetRefHandle = m_Addressables.InstantiateAsync(behavior.Reference);
            yield return assetRefHandle;
            Assert.IsNotNull(assetRefHandle.Result);

            string name = assetRefHandle.Result.name;
            Assert.IsNotNull(GameObject.Find(name));

            m_Addressables.ReleaseInstance(assetRefHandle.Result);
            yield return null;
            Assert.IsNull(GameObject.Find(name));

            handle.Release();
        }

        [UnityTest]
        public IEnumerator CanloadAssetReferenceSubObject()
        {
            yield return Init();

            var handle = m_Addressables.InstantiateAsync(AssetReferenceObjectKey);
            yield return handle;
            Assert.IsNotNull(handle.Result);
            AssetReferenceTestBehavior behavior = handle.Result.GetComponent<AssetReferenceTestBehavior>();

            AsyncOperationHandle<Object> assetRefHandle = m_Addressables.LoadAssetAsync<Object>(behavior.ReferenceWithSubObject);
            yield return assetRefHandle;
            Assert.IsNotNull(assetRefHandle.Result);
            m_Addressables.Release(assetRefHandle);
            handle.Release();
        }

        [UnityTest]
        public IEnumerator AddressablesIntegration_LoadAssetAsync_CanLoadAssetReferenceObjectList()
        {
            yield return Init();

            var assetRefHandle = m_Addressables.LoadAssetAsync<Object[]>("assetWithSubObjects");
            yield return assetRefHandle;
            Assert.IsNotNull(assetRefHandle.Result);
            Assert.AreEqual(assetRefHandle.Result.Length, 2);
            Assert.AreEqual(assetRefHandle.Result[0].name, "assetWithSubObjects");
            Assert.AreEqual(assetRefHandle.Result[1].name, "sub-shown");
            m_Addressables.Release(assetRefHandle);
        }

        [UnityTest]
        public IEnumerator LoadAssets_WithHiddenSubObjects_OnlyReturnsNonHidden_WithMainAssetFirst()
        {
            yield return Init();

            var handle = m_Addressables.LoadAssetAsync<IList<Object>>("assetWithSubObjects");
            yield return handle;
            Assert.IsNotNull(handle.Result);
            Assert.AreEqual(handle.Result.Count, 2);
            Assert.AreEqual(handle.Result[0].name, "assetWithSubObjects");
            Assert.AreEqual(handle.Result[1].name, "sub-shown");
            handle.Release();
        }

        [UnityTest]
        public IEnumerator RuntimeKeyIsValid_ReturnsTrueForSubObjects()
        {
            yield return Init();

            var handle = m_Addressables.InstantiateAsync(AssetReferenceObjectKey);
            yield return handle;
            Assert.IsNotNull(handle.Result);
            AssetReferenceTestBehavior behavior = handle.Result.GetComponent<AssetReferenceTestBehavior>();

            Assert.IsTrue(behavior.ReferenceWithSubObject.RuntimeKeyIsValid());

            handle.Release();
        }

        [UnityTest]
        public IEnumerator RuntimeKeyIsValid_ReturnsTrueForValidKeys()
        {
            yield return Init();

            AsyncOperationHandle handle = m_Addressables.InstantiateAsync(AssetReferenceObjectKey);
            yield return handle;
            Assert.IsNotNull(handle.Result as GameObject);
            AssetReferenceTestBehavior behavior =
                (handle.Result as GameObject).GetComponent<AssetReferenceTestBehavior>();

            Assert.IsTrue((behavior.Reference as IKeyEvaluator).RuntimeKeyIsValid());
            Assert.IsTrue((behavior.LabelReference as IKeyEvaluator).RuntimeKeyIsValid());

            handle.Release();
        }

        [UnityTest]
        public IEnumerator PercentComplete_CalculationIsCorrect_WhenInAGroupOperation()
        {
            yield return Init();
            GroupOperation groupOp = new GroupOperation();

            float handle1PercentComplete = 0.22f;
            float handle2PercentComplete = 0.78f;
            float handle3PercentComplete = 1.0f;
            float handle4PercentComplete = 0.35f;

            List<AsyncOperationHandle> handles = new List<AsyncOperationHandle>()
            {
                new ManualPercentCompleteOperation(handle1PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle2PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle3PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle4PercentComplete).Handle
            };

            groupOp.Init(handles);

            Assert.AreEqual((handle1PercentComplete + handle2PercentComplete + handle3PercentComplete + handle4PercentComplete) / 4, groupOp.PercentComplete);
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_SumDependencyNameHashCodes_ProperlyCalculatesForOneLayerOfDependencies()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);

            GroupOperation groupOp = new GroupOperation();


            float handle1PercentComplete = 0.22f;
            float handle2PercentComplete = 0.78f;
            float handle3PercentComplete = 1.0f;
            float handle4PercentComplete = 0.35f;

            List<AsyncOperationHandle> handles = new List<AsyncOperationHandle>()
            {
                new ManualPercentCompleteOperation(handle1PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle2PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle3PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle4PercentComplete).Handle
            };

            groupOp.Init(handles);

            var handle = groupOp.Handle;
            var dependencyNameHashSum = handle.DebugName.GetHashCode() + rmd.SumDependencyNameHashCodes(handle);
            var manualDepNameHashSum = handle.DebugName.GetHashCode();
            foreach (var h in handles)
                manualDepNameHashSum += h.DebugName.GetHashCode();
            Assert.AreEqual(manualDepNameHashSum, dependencyNameHashSum, "Calculation of hashcode was not completed as expected.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_SumDependencyNameHashCodes_ProperlyCalculatesForMultipleLayersOfDependencies()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);

            GroupOperation groupOp = new GroupOperation();
            GroupOperation embeddedOp = new GroupOperation();


            float handle1PercentComplete = 0.22f;
            float handle2PercentComplete = 0.78f;
            float handle3PercentComplete = 1.0f;
            float handle4PercentComplete = 0.35f;

            List<AsyncOperationHandle> embeddedHandles = new List<AsyncOperationHandle>()
            {
                new ManualPercentCompleteOperation(handle1PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle2PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle3PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle4PercentComplete).Handle
            };

            embeddedOp.Init(embeddedHandles);

            List<AsyncOperationHandle> handles = new List<AsyncOperationHandle>()
            {
                embeddedOp.Handle,
                new ManualPercentCompleteOperation(handle1PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle2PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle3PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle4PercentComplete).Handle
            };

            groupOp.Init(handles);

            var dependencyNameHashSum = groupOp.Handle.DebugName.GetHashCode() + rmd.SumDependencyNameHashCodes(groupOp.Handle);
            int manualDepNameHashSum;

            unchecked
            {
                manualDepNameHashSum = groupOp.Handle.DebugName.GetHashCode();
                foreach (var h in handles)
                    manualDepNameHashSum += h.DebugName.GetHashCode();
                foreach (var h in embeddedHandles)
                    manualDepNameHashSum += h.DebugName.GetHashCode();
            }

            Assert.AreEqual(dependencyNameHashSum, manualDepNameHashSum, "Calculation of hashcode was not completed as expected.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_CalculateHashCode_NonChangingNameCase()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);

            GroupOperation groupOp = new GroupOperation();


            float handle1PercentComplete = 0.22f;
            float handle2PercentComplete = 0.78f;
            float handle3PercentComplete = 1.0f;
            float handle4PercentComplete = 0.35f;

            List<AsyncOperationHandle> handles = new List<AsyncOperationHandle>()
            {
                new ManualPercentCompleteOperation(handle1PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle2PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle3PercentComplete).Handle,
                new ManualPercentCompleteOperation(handle4PercentComplete).Handle
            };

            groupOp.Init(handles);

            var handle = groupOp.Handle;
            var dependencyNameHashSum = rmd.CalculateHashCode(handle);
            var manualDepNameHashSum = handle.DebugName.GetHashCode();
            foreach (var h in handles)
                manualDepNameHashSum += h.DebugName.GetHashCode();
            Assert.AreEqual(manualDepNameHashSum, dependencyNameHashSum, "Calculation of hashcode was not completed as expected.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_CalculateCompletedOperationHashcode_DoesNotErrorOnNullResult()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);
            var completedOp = m_Addressables.ResourceManager.CreateCompletedOperation<string>(null, "x");
            int hashcode = rmd.CalculateCompletedOperationHashcode(completedOp);
            Assert.NotNull(hashcode, "CalculateCompletedOperationHashcode should not error when a completedOperation with a null result is passed in.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_CalculateCompletedOperationHashcode_DoesNotErrorOnEmptyResultList()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);
            var completedOp = m_Addressables.ResourceManager.CreateCompletedOperation<List<string>>(new List<string>(), null);
            int hashcode = rmd.CalculateCompletedOperationHashcode(completedOp);
            Assert.NotNull(hashcode, "CalculateCompletedOperationHashcode should not error when a completedOperation with an empty list is passed in.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_GenerateCompletedOperationDisplayName_DoesNotErrorOnEmptyResultList()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);
            var completedOp = m_Addressables.ResourceManager.CreateCompletedOperation<List<string>>(new List<string>(), null);
            string displayName = rmd.GenerateCompletedOperationDisplayName(completedOp);
            Assert.NotNull(displayName, "GenerateCompletedOperationDisplayName should not run into issues when an empty string is passed in.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_GenerateCompletedOperationDisplayName_DoesNotErrorOnTrivialList()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);
            var list = new List<string>();
            list.Add("x");
            var completedOp = m_Addressables.ResourceManager.CreateCompletedOperation<List<string>>(list, null);
            string displayName = rmd.GenerateCompletedOperationDisplayName(completedOp);
            Assert.NotNull(displayName, "GenerateCompletedOperationDisplayName should not run into issues when a simple string is passed in.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_GenerateCompletedOperationDisplayName_DoesNotErrorOnListWithEmptyElement()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);
            var list = new List<string>();
            list.Add("");
            var completedOp = m_Addressables.ResourceManager.CreateCompletedOperation<List<string>>(list, null);
            string displayName = rmd.GenerateCompletedOperationDisplayName(completedOp);
            Assert.NotNull(displayName, "GenerateCompletedOperationDisplayName should not run into issues when an empty string is passed in.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_GenerateCompletedOperationDisplayName_DoesNotErrorOnListWithManyEmptyElements()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);
            var list = new List<string>();
            for (int i = 0; i < 20; i++)
                list.Add("");
            var completedOp = m_Addressables.ResourceManager.CreateCompletedOperation<List<string>>(list, null);
            string displayName = rmd.GenerateCompletedOperationDisplayName(completedOp);
            Assert.NotNull(displayName, "GenerateCompletedOperationDisplayName should not run into issues when many empty strings are passed in.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_CalculateCompletedOperationDisplayName_DoesNotErrorOnNullResult()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);
            var completedOp = m_Addressables.ResourceManager.CreateCompletedOperation<string>(null, "x");
            string displayName = rmd.GenerateCompletedOperationDisplayName(completedOp);
            Assert.NotNull(displayName, "GenerateCompletedOperationDisplayName should not error when a completedOperation with a null result is passed in.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_GenerateCompletedOperationDisplayName_DoesNotErrorOnReallyLongList()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);
            var list = new List<string>();
            for (int i = 0; i < 20; i++)
                list.Add("this is a really long string used for illustrative purposes");
            var completedOp = m_Addressables.ResourceManager.CreateCompletedOperation<List<string>>(list, null);
            string displayName = rmd.GenerateCompletedOperationDisplayName(completedOp);
            Assert.NotNull(displayName, "GenerateCompletedOperationDisplayName should not run into issues when a bunch of long strings are passed in.");
        }

        private class DebugNameTestOperation : AsyncOperationBase<string>
        {
            string m_DebugName;
            List<AsyncOperationHandle> m_Dependencies;

            protected override void Execute()
            {
            }

            internal DebugNameTestOperation(string debugName)
            {
                m_DebugName = debugName;
                m_Dependencies = new List<AsyncOperationHandle>();
            }

            internal DebugNameTestOperation(string debugName, List<AsyncOperationHandle> deps)
            {
                m_DebugName = debugName;
                m_Dependencies = deps;
            }

            /// <inheritdoc />
            public override void GetDependencies(List<AsyncOperationHandle> dependencies)
            {
                foreach (var handle in m_Dependencies)
                    dependencies.Add(handle);
            }

            protected override string DebugName
            {
                get { return m_DebugName; }
            }
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_CalculateHashCode_NameChangingCase()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);

            AsyncOperationHandle changingHandle = new ManualPercentCompleteOperation(0.22f).Handle;

            Assert.AreEqual(changingHandle.GetHashCode(), rmd.CalculateHashCode(changingHandle),
                "Default hashcode should have been used since ManualPercentCompleteOperation includes its status in its DebugName");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_CalculateHashCode_SameNameGivesSameHashcode()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);

            DebugNameTestOperation op1 = new DebugNameTestOperation("Same name");
            AsyncOperationHandle handle1 = new AsyncOperationHandle(op1);

            DebugNameTestOperation op2 = new DebugNameTestOperation("Same name");
            AsyncOperationHandle handle2 = new AsyncOperationHandle(op2);

            Assert.AreEqual(rmd.CalculateHashCode(handle1), rmd.CalculateHashCode(handle2), "Two separate handles with the same DebugName should have the same hashcode. ");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_CalculateHashCode_SimilarNameGivesDifHashcode()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);

            DebugNameTestOperation op1 = new DebugNameTestOperation("Same name");
            AsyncOperationHandle handle1 = new AsyncOperationHandle(op1);

            DebugNameTestOperation op2 = new DebugNameTestOperation("SaMe name");
            AsyncOperationHandle handle2 = new AsyncOperationHandle(op2);

            Assert.AreNotEqual(rmd.CalculateHashCode(handle1), rmd.CalculateHashCode(handle2), "Two similar, but different names should have different hashcodes. ");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_CalculateHashCode_SameNameDifDepsGivesDifHashcode()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);

            var dependency1 = new DebugNameTestOperation("Dependency 1");
            var dependency2 = new DebugNameTestOperation("Dependency 2");

            var depList1 = new List<AsyncOperationHandle> {new AsyncOperationHandle(dependency1)};
            var depList2 = new List<AsyncOperationHandle> {new AsyncOperationHandle(dependency2)};

            DebugNameTestOperation op1 = new DebugNameTestOperation("Same name", depList1);
            AsyncOperationHandle handle1 = new AsyncOperationHandle(op1);

            DebugNameTestOperation op2 = new DebugNameTestOperation("Same name", depList2);
            AsyncOperationHandle handle2 = new AsyncOperationHandle(op2);

            Assert.AreNotEqual(rmd.CalculateHashCode(handle1), rmd.CalculateHashCode(handle2),
                "Two separate handles with the same DebugName, but different dependency names should not have the same hashcode.");
        }

        [UnityTest]
        public IEnumerator ResourceManagerDiagnostics_CalculateHashCode_SameNameSameDepNamesGivesSameHashcode()
        {
            yield return Init();
            var rmd = new ResourceManagerDiagnostics(m_Addressables.ResourceManager);

            var dependency1 = new DebugNameTestOperation("Dependency 1");
            var dependency2 = new DebugNameTestOperation("Dependency 2");

            var depList1 = new List<AsyncOperationHandle> {new AsyncOperationHandle(dependency1), new AsyncOperationHandle(dependency2)};
            var depList2 = new List<AsyncOperationHandle> {new AsyncOperationHandle(dependency2), new AsyncOperationHandle(dependency1)};

            DebugNameTestOperation op1 = new DebugNameTestOperation("Same name", depList1);
            AsyncOperationHandle handle1 = new AsyncOperationHandle(op1);

            DebugNameTestOperation op2 = new DebugNameTestOperation("Same name", depList2);
            AsyncOperationHandle handle2 = new AsyncOperationHandle(op2);

            Assert.AreEqual(rmd.CalculateHashCode(handle1), rmd.CalculateHashCode(handle2), "Two handles with the same DebugName and same dependency names should have the same hashcode.");
        }

        [UnityTest]
        public IEnumerator PercentComplete_CalculationIsCorrect_WhenInAChainOperation()
        {
            yield return Init();

            float handle1PercentComplete = 0.6f;
            float handle2PercentComplete = 0.98f;

            AsyncOperationHandle<GameObject> slowHandle1 = new ManualPercentCompleteOperation(handle1PercentComplete, new DownloadStatus() {DownloadedBytes = 1, IsDone = false, TotalBytes = 2})
                .Handle;
            AsyncOperationHandle<GameObject> slowHandle2 = new ManualPercentCompleteOperation(handle2PercentComplete, new DownloadStatus() {DownloadedBytes = 1, IsDone = false, TotalBytes = 2})
                .Handle;

            slowHandle1.m_InternalOp.m_RM = m_Addressables.ResourceManager;
            slowHandle2.m_InternalOp.m_RM = m_Addressables.ResourceManager;

            var chainOperation = m_Addressables.ResourceManager.CreateChainOperation(slowHandle1, (op) => { return slowHandle2; });

            chainOperation.m_InternalOp.Start(m_Addressables.ResourceManager, default, null);

            Assert.AreEqual((handle1PercentComplete + handle2PercentComplete) / 2, chainOperation.PercentComplete);
        }

        [UnityTest]
        public IEnumerator RuntimeKeyIsValid_ReturnsFalseForInValidKeys()
        {
            yield return Init();

            AsyncOperationHandle handle = m_Addressables.InstantiateAsync(AssetReferenceObjectKey);
            yield return handle;
            Assert.IsNotNull(handle.Result as GameObject);
            AssetReferenceTestBehavior behavior =
                (handle.Result as GameObject).GetComponent<AssetReferenceTestBehavior>();

            Assert.IsFalse((behavior.InValidAssetReference as IKeyEvaluator).RuntimeKeyIsValid());
            Assert.IsFalse((behavior.InvalidLabelReference as IKeyEvaluator).RuntimeKeyIsValid());

            handle.Release();
        }
#if !ENABLE_BINARY_CATALOG
        static ResourceLocationMap GetRLM(AddressablesImpl addr)
        {
            foreach (var rl in addr.m_ResourceLocators)
            {
                if (rl.Locator is ResourceLocationMap)
                    return rl.Locator as ResourceLocationMap;
            }

            return null;
        }
#endif
        private void SetupBundleForCacheDependencyClearTests(string bundleName, string depName, string hash, string key, out ResourceLocationBase location)
        {
            CreateFakeCachedBundle(bundleName, hash);
            location = new ResourceLocationBase(key, bundleName, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource),
                new ResourceLocationBase(depName, bundleName, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource)));
            (location.Dependencies[0] as ResourceLocationBase).Data = location.Data = new AssetBundleRequestOptions()
            {
                BundleName = bundleName
            };
        }

        class TestCatalogProviderCustomAssetBundleResource : BundledAssetProvider
        {
            public TestAssetBundleResourceInternalOp TestInternalOp;

            internal class TestAssetBundleResourceInternalOp : AsyncOperationBase<TestAssetBundleResource>
            {
                TestAssetBundleResource m_Resource;

                public TestAssetBundleResourceInternalOp(TestAssetBundleResource resource)
                {
                    m_Resource = resource;
                }

                protected override void Execute()
                {
                    Complete(m_Resource, m_Resource != null, m_Resource != null ? "" : "TestAssetBundleResource was null");
                }
            }

            /// <inheritdoc/>
            public override string ProviderId
            {
                get
                {
                    if (string.IsNullOrEmpty(m_ProviderId))
                        m_ProviderId = typeof(TestCatalogProviderCustomAssetBundleResource).FullName;

                    return m_ProviderId;
                }
            }

            public override void Provide(ProvideHandle provideHandle)
            {
                ProviderOperation<Object> op = new ProviderOperation<Object>();
                GroupOperation group = new GroupOperation();
                TestInternalOp = new TestAssetBundleResourceInternalOp(new TestAssetBundleResource());
                provideHandle.ResourceManager.Acquire(TestInternalOp.Handle);
                provideHandle.ResourceManager.Acquire(TestInternalOp.Handle);
                group.Init(new List<AsyncOperationHandle>() {new AsyncOperationHandle(TestInternalOp)});
                op.Init(provideHandle.ResourceManager, null, provideHandle.Location, group.Handle);
                ProvideHandle handle = new ProvideHandle(provideHandle.ResourceManager, op);
                TestInternalOp.InvokeExecute();
                base.Provide(handle);
            }

            internal class TestAssetBundleResource : IAssetBundleResource
            {
                public bool WasUsed = false;

                public AssetBundle GetAssetBundle()
                {
                    WasUsed = true;
                    return null;
                }
            }
        }
#if !ENABLE_BINARY_CATALOG
        private void SetupBundleForProviderTests(string bundleName, string depName, string key, out ResourceLocationBase location, out TestCatalogProviderCustomAssetBundleResource testProvider)
        {
            testProvider = new TestCatalogProviderCustomAssetBundleResource();
            m_Addressables.ResourceManager.ResourceProviders.Add(testProvider);
            location = new ResourceLocationBase(key, bundleName, typeof(TestCatalogProviderCustomAssetBundleResource).FullName,
                typeof(TestCatalogProviderCustomAssetBundleResource.TestAssetBundleResource),
                new ResourceLocationBase(depName, bundleName, typeof(TestCatalogProviderCustomAssetBundleResource).FullName,
                    typeof(TestCatalogProviderCustomAssetBundleResource.TestAssetBundleResource)));
            (location.Dependencies[0] as ResourceLocationBase).Data = location.Data = new AssetBundleRequestOptions()
            {
                BundleName = bundleName
            };

            GetRLM(m_Addressables).Add(key, new List<IResourceLocation>() {location});
        }
#endif
#if !ENABLE_BINARY_CATALOG
        [UnityTest]
        [Platform(Exclude = "PS5")]
        public IEnumerator ClearDependencyCache_ClearsAllCachedFilesForKey()
        {
            yield return Init();
            var rlm = GetRLM(m_Addressables);
            if (rlm == null)
                yield break;
#if ENABLE_CACHING

            string hash = "123456789";
            string bundleName = $"test_{hash}";
            string key = "lockey_key";

            List<Hash128> versions = new List<Hash128>();
            ResourceLocationBase location = null;
            SetupBundleForCacheDependencyClearTests(bundleName, "bundle", hash, key, out location);

            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(1, versions.Count);
            versions.Clear();
            rlm.Add(location.PrimaryKey, new List<IResourceLocation>() {location});

            yield return m_Addressables.ClearDependencyCacheAsync((object)key, true);
            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(0, versions.Count);
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [UnityTest]
        public IEnumerator ClearDependencyCache_ClearsAllCachedFilesForKeyWithDependencies()
        {
            yield return Init();
            var rlm = GetRLM(m_Addressables);
            if (rlm == null)
                yield break;
#if ENABLE_CACHING

            string hash = "123456789";
            string bundleName = $"test_{hash}";

            string depHash = "97564231";
            string depBundleName = $"test_{depHash}";
            ResourceLocationBase depLocation = null;

            string key = "lockey_deps_key";

            SetupBundleForCacheDependencyClearTests(depBundleName, "test", depHash, "depKey", out depLocation);
            CreateFakeCachedBundle(bundleName, hash);

            ResourceLocationBase location = new ResourceLocationBase(key, bundleName,
                typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource),
                new ResourceLocationBase("bundle", bundleName, typeof(AssetBundleProvider).FullName,
                    typeof(IAssetBundleResource)),
                depLocation);
            (location.Dependencies[0] as ResourceLocationBase).Data = location.Data = new AssetBundleRequestOptions()
            {
                BundleName = bundleName
            };

            rlm.Add(location.PrimaryKey, new List<IResourceLocation>() {location});

            yield return m_Addressables.ClearDependencyCacheAsync((object)key, true);

            List<Hash128> versions = new List<Hash128>();
            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(0, versions.Count);
            versions.Clear();
            Caching.GetCachedVersions(depBundleName, versions);
            Assert.AreEqual(0, versions.Count);
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [UnityTest]
        public IEnumerator ClearDependencyCache_ClearsAllCachedFilesForLocationWithDependencies()
        {
            yield return Init();
            var rlm = GetRLM(m_Addressables);
            if (rlm == null)
                yield break;
#if ENABLE_CACHING
            string hash = "123456789";
            string bundleName = $"test_{hash}";
            string key = "lockey_deps_location";

            string depHash = "97564231";
            string depBundleName = $"test_{depHash}";
            ResourceLocationBase depLocation = null;

            SetupBundleForCacheDependencyClearTests(depBundleName, "test", depHash, "depKey", out depLocation);
            CreateFakeCachedBundle(bundleName, hash);

            ResourceLocationBase location = new ResourceLocationBase(key, bundleName, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource),
                new ResourceLocationBase("bundle", bundleName, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource)),
                depLocation);
            (location.Dependencies[0] as ResourceLocationBase).Data = location.Data = new AssetBundleRequestOptions()
            {
                BundleName = bundleName
            };

            rlm.Add(location.PrimaryKey, new List<IResourceLocation>() {location});

            yield return m_Addressables.ClearDependencyCacheAsync(location, true);

            List<Hash128> versions = new List<Hash128>();
            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(0, versions.Count);
            versions.Clear();
            Caching.GetCachedVersions(depBundleName, versions);
            Assert.AreEqual(0, versions.Count);
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [UnityTest]
        [Platform(Exclude = "PS5")]
        public IEnumerator ClearDependencyCache_ClearsAllCachedFilesForLocationList()
        {
            yield return Init();
            var rlm = GetRLM(m_Addressables);
            if (rlm == null)
                yield break;
#if ENABLE_CACHING
            string hash = "123456789";
            string bundleName = $"test_{hash}";
            string key = "lockey_location_list";
            ResourceLocationBase location = null;
            SetupBundleForCacheDependencyClearTests(bundleName, "bundle", hash, key, out location);

            List<Hash128> versions = new List<Hash128>();
            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(1, versions.Count);
            versions.Clear();

            rlm.Add(location.PrimaryKey, new List<IResourceLocation>() {location});

            yield return m_Addressables.ClearDependencyCacheAsync(new List<IResourceLocation>() {location}, true);
            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(0, versions.Count);
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [UnityTest]
        [Platform(Exclude = "PS5")]
        public IEnumerator ClearDependencyCache_ClearsAllCachedFilesForKeyList()
        {
            yield return Init();
            var rlm = GetRLM(m_Addressables);
            if (rlm == null)
                yield break;
#if ENABLE_CACHING

            string hash = "123456789";
            string bundleName = $"test_{hash}";
            string key = "lockey_key_list";
            ResourceLocationBase location = null;

            SetupBundleForCacheDependencyClearTests(bundleName, "bundle", hash, key, out location);

            List<Hash128> versions = new List<Hash128>();
            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(1, versions.Count);
            versions.Clear();

            rlm.Add(location.PrimaryKey, new List<IResourceLocation>() {location});

            yield return m_Addressables.ClearDependencyCacheAsync(new List<object>() {(object)key}, true);
            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(0, versions.Count);
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [UnityTest]
        public IEnumerator ClearDependencyCache_ClearsAllCachedFilesForLocationListWithDependencies()
        {
            yield return Init();
            var rlm = GetRLM(m_Addressables);
            if (rlm == null)
                yield break;
#if ENABLE_CACHING

            string hash = "123456789";
            string bundleName = $"test_{hash}";
            string key = "lockey_deps_location_list";

            string depHash = "97564231";
            string depBundleName = $"test_{depHash}";
            ResourceLocationBase depLocation = null;

            SetupBundleForCacheDependencyClearTests(depBundleName, "test", depHash, "depKey", out depLocation);

            CreateFakeCachedBundle(bundleName, hash);
            ResourceLocationBase location = new ResourceLocationBase(key, bundleName, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource),
                new ResourceLocationBase("bundle", bundleName, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource)),
                depLocation);
            (location.Dependencies[0] as ResourceLocationBase).Data = location.Data = new AssetBundleRequestOptions()
            {
                BundleName = bundleName
            };

            rlm.Add(location.PrimaryKey, new List<IResourceLocation>() {location});

            yield return m_Addressables.ClearDependencyCacheAsync(new List<IResourceLocation>() {location}, true);

            List<Hash128> versions = new List<Hash128>();
            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(0, versions.Count);
            versions.Clear();
            Caching.GetCachedVersions(depBundleName, versions);
            Assert.AreEqual(0, versions.Count);
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [UnityTest]
        public IEnumerator ClearDependencyCache_ClearsAllCachedFilesForKeyListWithDependencies()
        {
            yield return Init();
            var rlm = GetRLM(m_Addressables);
            if (rlm == null)
                yield break;
#if ENABLE_CACHING

            string hash = "123456789";
            string bundleName = $"test_{hash}";
            string key = "lockey_deps_key_list";

            string depHash = "97564231";
            string depBundleName = $"test_{depHash}";
            ResourceLocationBase depLocation = null;

            SetupBundleForCacheDependencyClearTests(depBundleName, "test", depHash, "depKey", out depLocation);

            CreateFakeCachedBundle(bundleName, hash);
            ResourceLocationBase location = new ResourceLocationBase(key, bundleName, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource),
                new ResourceLocationBase("bundle", bundleName, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource)),
                depLocation);
            (location.Dependencies[0] as ResourceLocationBase).Data = location.Data = new AssetBundleRequestOptions()
            {
                BundleName = bundleName
            };

            rlm.Add(location.PrimaryKey, new List<IResourceLocation>() {location});


            yield return m_Addressables.ClearDependencyCacheAsync(new List<object>() {key}, true);

            List<Hash128> versions = new List<Hash128>();
            Caching.GetCachedVersions(bundleName, versions);
            Assert.AreEqual(0, versions.Count);
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }
#endif

        [UnityTest]
        public IEnumerator AssetBundleRequestOptions_ComputesCorrectSize_WhenLocationDoesNotMatchBundleName_WithoutHash()
        {
#if ENABLE_CACHING
            yield return Init();

            //Setup
            string bundleName = "bundle";
            string bundleLocation = "http://fake.com/default-bundle";
            long size = 123;
            IResourceLocation location = new ResourceLocationBase("test", bundleLocation,
                typeof(AssetBundleProvider).FullName, typeof(GameObject));

            Hash128 hash = Hash128.Compute("213412341242134");
            AssetBundleRequestOptions abro = new AssetBundleRequestOptions()
            {
                BundleName = bundleName,
                BundleSize = size,
            };

            //Test
            Assert.AreEqual(size, abro.ComputeSize(location, m_Addressables.ResourceManager));
            CreateFakeCachedBundle(bundleName, hash.ToString());

            // No hash, so the bundle should be downloaded
            Assert.AreEqual(size, abro.ComputeSize(location, m_Addressables.ResourceManager));

            //Cleanup
            Caching.ClearAllCachedVersions(bundleName);
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [UnityTest]
        public IEnumerator Autorelease_False_ClearDependencyCache_WithChainOperation_DoesNotThrowInvalidHandleError()
        {
            yield return Init();
            //This is to make sure we use the ShouldChainRequest
            var dumbUpdate = new DumbUpdateOperation();
            m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle<List<IResourceLocator>>(dumbUpdate);

            var clearCache = m_Addressables.ClearDependencyCacheAsync("NotARealKey", false);
            dumbUpdate.CallComplete();
            yield return clearCache;
            Assert.IsTrue(clearCache.IsValid());
            m_Addressables.Release(clearCache);
            Assert.IsFalse(clearCache.IsValid());
        }

        [UnityTest]
        public IEnumerator Autorelease_True_ClearDependencyCache_WithChainOperation_DoesNotThrowInvalidHandleError()
        {
            yield return Init();
            //This is to make sure we use the ShouldChainRequest
            var dumbUpdate = new DumbUpdateOperation();
            m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle<List<IResourceLocator>>(dumbUpdate);

            var clearCache = m_Addressables.ClearDependencyCacheAsync("NotARealKey", true);
            dumbUpdate.CallComplete();
            yield return clearCache;
            Assert.IsFalse(clearCache.IsValid());
        }

        [UnityTest]
        public IEnumerator Autorelease_False_ClearDependencyCacheList_WithChainOperation_DoesNotThrowInvalidHandleError()
        {
            yield return Init();
            //This is to make sure we use the ShouldChainRequest
            var dumbUpdate = new DumbUpdateOperation();
            m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle<List<IResourceLocator>>(dumbUpdate);

            var clearCache = m_Addressables.ClearDependencyCacheAsync(new List<object> {"NotARealKey"}, false);
            dumbUpdate.CallComplete();
            yield return clearCache;
            Assert.IsTrue(clearCache.IsValid());
            m_Addressables.Release(clearCache);
            Assert.IsFalse(clearCache.IsValid());
        }

        [UnityTest]
        public IEnumerator Autorelease_True_ClearDependencyCacheList_WithChainOperation_DoesNotThrowInvalidHandleError()
        {
            yield return Init();
            //This is to make sure we use the ShouldChainRequest
            var dumbUpdate = new DumbUpdateOperation();
            m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle<List<IResourceLocator>>(dumbUpdate);

            var clearCache = m_Addressables.ClearDependencyCacheAsync(new List<object> {"NotARealKey"}, true);
            dumbUpdate.CallComplete();
            yield return clearCache;
            Assert.IsFalse(clearCache.IsValid());
        }

        public IEnumerator Autorelease_False_ClearDependencyCacheObject_WithChainOperation_DoesNotThrowInvalidHandleError()
        {
            yield return Init();
            //This is to make sure we use the ShouldChainRequest
            var dumbUpdate = new DumbUpdateOperation();
            m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle<List<IResourceLocator>>(dumbUpdate);

            var clearCache = m_Addressables.ClearDependencyCacheAsync((object)"NotARealKey", false);
            dumbUpdate.CallComplete();
            yield return clearCache;
            Assert.IsTrue(clearCache.IsValid());
            m_Addressables.Release(clearCache);
            Assert.IsFalse(clearCache.IsValid());
        }

        [UnityTest]
        public IEnumerator Autorelease_True_ClearDependencyCacheObject_WithChainOperation_DoesNotThrowInvalidHandleError()
        {
            yield return Init();
            //This is to make sure we use the ShouldChainRequest
            var dumbUpdate = new DumbUpdateOperation();
            m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle<List<IResourceLocator>>(dumbUpdate);

            var clearCache = m_Addressables.ClearDependencyCacheAsync((object)"NotARealKey", true);
            dumbUpdate.CallComplete();
            yield return clearCache;
            Assert.IsFalse(clearCache.IsValid());
        }

        [UnityTest]
        public IEnumerator Autorelease_False_ClearDependencyCacheListIResourceLocs_WithChainOperation_DoesNotThrowInvalidHandleError()
        {
            yield return Init();
            //This is to make sure we use the ShouldChainRequest
            var dumbUpdate = new DumbUpdateOperation();
            m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle<List<IResourceLocator>>(dumbUpdate);

            List<IResourceLocation> locations = new List<IResourceLocation>()
            {
                new ResourceLocationBase("NotARealKey", "NotARealKey", typeof(BundledAssetProvider).FullName,
                    typeof(ResourceLocationBase))
            };
            var clearCache = m_Addressables.ClearDependencyCacheAsync(locations, false);
            dumbUpdate.CallComplete();
            yield return clearCache;
            Assert.IsTrue(clearCache.IsValid());
            m_Addressables.Release(clearCache);
            Assert.IsFalse(clearCache.IsValid());
        }

        [UnityTest]
        public IEnumerator Autorelease_True_ClearDependencyCacheListIResourceLocs_WithChainOperation_DoesNotThrowInvalidHandleError()
        {
            yield return Init();
            //This is to make sure we use the ShouldChainRequest
            var dumbUpdate = new DumbUpdateOperation();
            m_Addressables.m_ActiveUpdateOperation = new AsyncOperationHandle<List<IResourceLocator>>(dumbUpdate);

            List<IResourceLocation> locations = new List<IResourceLocation>()
            {
                new ResourceLocationBase("NotARealKey", "NotARealKey", typeof(BundledAssetProvider).FullName,
                    typeof(ResourceLocationBase))
            };
            var clearCache = m_Addressables.ClearDependencyCacheAsync(locations, true);
            dumbUpdate.CallComplete();
            yield return clearCache;
            Assert.IsFalse(clearCache.IsValid());
        }

        [UnityTest]
        [Platform(Exclude ="PS5")]
        public IEnumerator AssetBundleRequestOptions_ComputesCorrectSize_WhenLocationDoesNotMatchBundleName_WithHash()
        {
#if ENABLE_CACHING
            yield return Init();

            //Setup
            string bundleName = "bundle";
            string bundleLocation = "http://fake.com/default-bundle";
            long size = 123;
            IResourceLocation location = new ResourceLocationBase("test", bundleLocation,
                typeof(AssetBundleProvider).FullName, typeof(GameObject));

            Hash128 hash = Hash128.Compute("213412341242134");
            AssetBundleRequestOptions abro = new AssetBundleRequestOptions()
            {
                BundleName = bundleName,
                BundleSize = size,
                Hash = hash.ToString()
            };

            //Test
            Assert.AreEqual(size, abro.ComputeSize(location, m_Addressables.ResourceManager));
            CreateFakeCachedBundle(bundleName, hash.ToString());
            Assert.AreEqual(0, abro.ComputeSize(location, m_Addressables.ResourceManager));

            //Cleanup
            Caching.ClearAllCachedVersions(bundleName);
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [UnityTest]
        [Platform(Exclude ="PS5")]
        public IEnumerator AssetBundleResource_RemovesCachedBundle_OnLoadFailure()
        {
#if ENABLE_CACHING
            yield return Init();
            string bundleName = "bundleName";
            Hash128 hash = Hash128.Parse("123456");
            uint crc = 1;
            AssetBundleResource abr = new AssetBundleResource();
            abr.m_ProvideHandle = new ProvideHandle(m_Addressables.ResourceManager, new ProviderOperation<AssetBundleResource>());
            abr.m_Options = new AssetBundleRequestOptions()
            {
                BundleName = bundleName,
                Hash = hash.ToString(),
                Crc = crc,
                RetryCount = 3
            };
            CreateFakeCachedBundle(bundleName, hash.ToString());
            CachedAssetBundle cab = new CachedAssetBundle(bundleName, hash);
            var request = abr.CreateWebRequest(new ResourceLocationBase("testName", bundleName, typeof(AssetBundleProvider).FullName,
                typeof(IAssetBundleResource)));

            Assert.IsTrue(Caching.IsVersionCached(cab));
            yield return request.SendWebRequest();
            Assert.IsFalse(Caching.IsVersionCached(cab));
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [UnityTest]
        [Platform(Exclude ="PS5")]
        public IEnumerator AssetBundleResource_RemovesCachedBundle_OnLoadFailure_WhenRetryCountIsZero()
        {
#if ENABLE_CACHING
            yield return Init();
            string bundleName = "bundleName";
            Hash128 hash = Hash128.Parse("123456");
            uint crc = 1;
            AssetBundleResource abr = new AssetBundleResource();
            abr.m_ProvideHandle = new ProvideHandle(m_Addressables.ResourceManager, new ProviderOperation<AssetBundleResource>());
            abr.m_Options = new AssetBundleRequestOptions()
            {
                BundleName = bundleName,
                Hash = hash.ToString(),
                Crc = crc,
                RetryCount = 0
            };
            CreateFakeCachedBundle(bundleName, hash.ToString());
            CachedAssetBundle cab = new CachedAssetBundle(bundleName, hash);
            var request = abr.CreateWebRequest(new ResourceLocationBase("testName", bundleName, typeof(AssetBundleProvider).FullName,
                typeof(IAssetBundleResource)));

            Assert.IsTrue(Caching.IsVersionCached(cab));
            yield return request.SendWebRequest();
            Assert.IsFalse(Caching.IsVersionCached(cab));
#else
            Assert.Ignore("Caching not enabled.");
            yield return null;
#endif
        }

        [Test]
        [Platform(Exclude ="PS5")]
        public void AssetBundleResource_WhenNotLoaded_GetAssetPreloadRequest_ReturnsNull()
        {
            AssetBundleResource abr = new AssetBundleResource();
            Assert.AreEqual(null, abr.GetAssetPreloadRequest());
        }

        [Test]
        public void WebRequestQueueOperation_CanSetWebRequest()
        {
            string url = "https://www.mynewsite.com/";
            var op = new WebRequestQueueOperation(new UnityWebRequest("https://www.myoldsite.com/"));
            op.WebRequest = new UnityWebRequest(url);
            Assert.AreEqual(url, op.WebRequest.url);
        }

        [UnityTest]
        public IEnumerator WebRequestQueue_GetsSetOnInitialization_FromRuntimeData()
        {
            yield return Init();
            Assert.AreEqual(AddressablesTestUtility.kMaxWebRequestCount, WebRequestQueue.s_MaxRequest);
        }

        [Test]
        public void WebRequestQueue_WhenMaxConcurrentRequests_SetToZero_ThrowsException()
        {
            Assert.Throws<ArgumentException>(() => WebRequestQueue.SetMaxConcurrentRequests(0), "MaxRequests must be 1 or greater.");
        }

        internal static string CreateFakeCachedBundle(string bundleName, string hash)
        {
#if ENABLE_CACHING
            string fakeCachePath = string.Format("{0}/{1}/{2}", Caching.currentCacheForWriting.path, bundleName, hash);
            Directory.CreateDirectory(fakeCachePath);
            var dataFile = File.Create(Path.Combine(fakeCachePath, "__data"));
            var infoFile = File.Create(Path.Combine(fakeCachePath, "__info"));

            byte[] info = new UTF8Encoding(true).GetBytes(
                @"-1
1554740658
1
__data");
            infoFile.Write(info, 0, info.Length);

            dataFile.Dispose();
            infoFile.Dispose();

            return fakeCachePath;
#else
            return null;
#endif
        }

        class AsyncWaitForCompletion : MonoBehaviour
        {
            public string key1;
            public string key2;
            public bool done = false;
            public AsyncOperationHandle<IList<IResourceLocation>> op;
            public AsyncOperationHandle<GameObject> op2;
            public AddressablesImpl addressables;
            public string errorMsg;

            async void Start()
            {
                try
                {
                    op = addressables.LoadResourceLocationsAsync(key1, typeof(Texture2D));
                    await op.Task;
                    op2 = addressables.LoadAssetAsync<GameObject>(key2);
                    op2.WaitForCompletion();
                }
                catch (Exception e)
                {
                    errorMsg = e.Message;
                }
                finally
                {
                    done = true;
                }
            }
        }

        [UnityTest]
        public IEnumerator WhenCallingWaitForCompletion_InAsyncMethod_NoExceptionIsThrown()
        {
            yield return Init();
            var go = new GameObject("test", typeof(AsyncWaitForCompletion));
            var comp = go.GetComponent<AsyncWaitForCompletion>();
            comp.addressables = m_Addressables;
            comp.key1 = "prefabs_evenBASE";
            comp.key2 = AddressablesTestUtility.GetPrefabLabel("BASE");

            while (!comp.done)
                yield return null;
            Assert.IsTrue(string.IsNullOrEmpty(comp.errorMsg), comp.errorMsg);

            comp.op.Release();
            comp.op2.Release();
            GameObject.Destroy(go);
        }
    }
}