WuhuIslandTesting/Library/PackageCache/com.unity.addressables@1.21.12/Tests/Runtime/ResourceManager/Operations/BaseOperationBehaviorTests.cs
2025-01-07 02:06:59 +01:00

377 lines
14 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.ResourceManagement.Util;
using UnityEngine.TestTools;
using System.Linq;
using UnityEngine.Scripting;
using UnityEngine.TestTools.Constraints;
[assembly: Preserve]
namespace UnityEngine.ResourceManagement.Tests
{
public class BaseOperationBehaviorTests
{
Action<AsyncOperationHandle, Exception> m_PrevHandler;
ResourceManager m_RM;
[OneTimeSetUp]
public void OneTimeSetup()
{
m_PrevHandler = ResourceManager.ExceptionHandler;
ResourceManager.ExceptionHandler = null;
}
[OneTimeTearDown]
public void OneTimeTeardown()
{
ResourceManager.ExceptionHandler = m_PrevHandler;
}
[SetUp]
public void Setup()
{
m_RM = new ResourceManager();
m_RM.CallbackHooksEnabled = false; // default for tests. disabled callback hooks. we will call update manually
}
[TearDown]
public void TearDown()
{
Assert.Zero(m_RM.OperationCacheCount);
m_RM.Dispose();
}
[Test]
public void WhenReferenceCountReachesZero_DestroyCallbackInvoked()
{
var op = m_RM.CreateCompletedOperation<int>(1, string.Empty);
int resultInDestroyCallback = 0;
op.Destroyed += (x) => resultInDestroyCallback = x.Convert<int>().Result;
op.Release();
Assert.AreEqual(1, resultInDestroyCallback);
}
[Test]
public void WhileCompletedCallbackIsDeferredOnCompletedOperation_ReferenceCountIsHeld()
{
var op = m_RM.CreateCompletedOperation<int>(1, string.Empty);
int refCount = op.ReferenceCount;
bool completedCalled = false;
op.Completed += (x) => completedCalled = true; // callback is deferred to next update
Assert.AreEqual(refCount + 1, op.ReferenceCount);
m_RM.Update(0.0f);
Assert.AreEqual(refCount, op.ReferenceCount);
Assert.AreEqual(true, completedCalled);
op.Release();
}
[Test]
public void WhenInDestroyCallback_IncrementAndDecrementReferenceCount_Throws()
{
var op = m_RM.CreateCompletedOperation<int>(1, string.Empty);
int resultInDestroyCallback = 0;
Exception onInc = null;
Exception onDec = null;
op.Destroyed += (x) =>
{
try
{
x.Acquire();
}
catch (Exception e)
{
onInc = e;
}
try
{
x.Release();
}
catch (Exception e)
{
onDec = e;
}
resultInDestroyCallback = x.Convert<int>().Result;
};
op.Release();
Assert.NotNull(onInc);
Assert.NotNull(onDec);
}
class MockOperation<T> : AsyncOperationBase<T>
{
public Action ExecuteCallback = () => { };
protected override void Execute()
{
ExecuteCallback();
}
}
[Test]
public void WhenOperationHasDependency_ExecuteNotCalledUntilDependencyCompletes()
{
var op1 = new MockOperation<int>();
var op2 = new MockOperation<int>();
var handle1 = m_RM.StartOperation(op1, default(AsyncOperationHandle));
op2.ExecuteCallback = () => { op2.Complete(0, true, string.Empty); };
var handle2 = m_RM.StartOperation(op2, handle1);
m_RM.Update(0.0f);
Assert.AreEqual(false, handle2.IsDone);
op1.Complete(0, true, null);
Assert.AreEqual(true, handle2.IsDone);
handle1.Release();
handle2.Release();
}
[Test]
public void WhenOperationIsSuccessfulButHasErrorMsg_FailsSilently_CompletesButExceptionHandlerIsCalled()
{
bool exceptionHandlerCalled = false;
ResourceManager.ExceptionHandler += (h, ex) => exceptionHandlerCalled = true;
var op = m_RM.CreateCompletedOperationInternal<int>(1, true, new Exception("An exception occured."));
var status = AsyncOperationStatus.None;
op.Completed += (x) => status = x.Status;
// callbacks are deferred to next update
m_RM.Update(0.0f);
Assert.AreEqual(true, exceptionHandlerCalled);
Assert.AreEqual(AsyncOperationStatus.Succeeded, status);
op.Release();
}
[UnityTest]
public IEnumerator AsyncOperationHandle_TaskIsDelayedUntilAfterDelayedCompletedCallbacks()
{
var op = m_RM.CreateCompletedOperationInternal<int>(1, true, null);
var status = AsyncOperationStatus.None;
op.Completed += (x) => status = x.Status;
var t = op.Task;
Assert.IsFalse(t.IsCompleted);
// callbacks are deferred to next update
m_RM.Update(0.0f);
// the Task may not yet have continues after at this point on the update,
// give the Synchronization a little time with a yield
yield return null;
Assert.IsTrue(t.IsCompleted);
op.Release();
}
[Test]
public void AsyncOperationHandle_TaskIsCompletedWhenHandleIsCompleteWithoutDelayedCallbacks()
{
var op = m_RM.CreateCompletedOperationInternal<int>(1, true, null);
var t = op.Task;
Assert.IsTrue(t.IsCompleted);
op.Release();
}
// TODO:
// public void WhenOperationHasDependency_AndDependencyFails_DependentOpStillExecutes()
// Bad derived class behavior
// public void CustomOperation_WhenCompleteCalledBeforeStartOperation_ThrowsOperationDoesNotComplete
// public void CustomOperation_WhenCompleteCalledMultipleTimes_Throws
// public void CustomOperation_WhenProgressCallbackThrowsException_ErrorLoggedAndHandleReturnsZero
// public void CustomOperation_WhenDestroyThrowsException_ErrorLogged
// public void CustomOperation_WhenExecuteThrows_ErrorLoggedAndOperationSetAsFailed
// TEST: Per operation update behavior
// public void AsyncOperationHandle_WhenReleaseOnInvalidHandle_Throws
// public void AsyncOperationHandle_WhenConvertToIncompatibleHandleType_Throws
//
[Test]
public void AsyncOperationHandle_EventSubscriptions_UnsubscribingToNonSubbedEventsShouldHaveNoEffect()
{
var op = new MockOperation<int>();
var handle = m_RM.StartOperation(op, default(AsyncOperationHandle));
Assert.False(op.CompletedEventHasListeners);
handle.Completed -= oph => { };
Assert.False(op.CompletedEventHasListeners);
Assert.False(op.DestroyedEventHasListeners);
handle.Destroyed -= oph => { };
Assert.False(op.DestroyedEventHasListeners);
handle.Release();
}
internal class ManualDownloadPercentCompleteOperation : AsyncOperationBase<IAssetBundleResource>
{
public long m_bytesDownloaded = 0;
public long m_totalBytes = 1024;
public bool m_IsDone = false;
protected override void Execute()
{
}
public void CompleteNow()
{
m_bytesDownloaded = m_totalBytes;
Complete(null, true, null);
}
internal override DownloadStatus GetDownloadStatus(HashSet<object> visited)
{
return new DownloadStatus() {DownloadedBytes = m_bytesDownloaded, TotalBytes = m_totalBytes, IsDone = m_IsDone};
}
}
static void AssertExpectedDownloadStatus(DownloadStatus dls, long dl, long tot, float per)
{
Assert.AreEqual(dl, dls.DownloadedBytes);
Assert.AreEqual(tot, dls.TotalBytes);
Assert.AreEqual(per, dls.Percent);
}
[Test]
public void DownloadStatusWithNoBytes_WithIsDoneFalse_Returns_PercentCompleteZero()
{
var dls = new DownloadStatus() {DownloadedBytes = 0, TotalBytes = 0, IsDone = false};
Assert.AreEqual(0f, dls.Percent);
}
[Test]
public void DownloadStatusWithNoBytes_WithIsDoneTrue_Returns_PercentCompleteOne()
{
var dls = new DownloadStatus() {DownloadedBytes = 0, TotalBytes = 0, IsDone = true};
Assert.AreEqual(1f, dls.Percent);
}
[Test]
public void GroupOperation_WithOpsThatImplementGetDownloadStatus_ComputesExpectedDownloadPercentComplete()
{
var ops = new List<AsyncOperationHandle>();
var mdpco = new List<ManualDownloadPercentCompleteOperation>();
for (int i = 0; i < 4; i++)
{
var o = m_RM.CreateOperation<ManualDownloadPercentCompleteOperation>(typeof(ManualDownloadPercentCompleteOperation), 1, null, null);
o.Start(m_RM, default, null);
mdpco.Add(o);
ops.Add(new AsyncOperationHandle(o));
}
var gOp = m_RM.CreateGenericGroupOperation(ops, true);
AssertExpectedDownloadStatus(gOp.GetDownloadStatus(), 0, 4096, 0);
mdpco[0].m_bytesDownloaded = 512;
AssertExpectedDownloadStatus(gOp.GetDownloadStatus(), 512, 4096, .125f);
foreach (var o in mdpco)
o.CompleteNow();
AssertExpectedDownloadStatus(gOp.GetDownloadStatus(), 4096, 4096, 1f);
m_RM.Release(gOp);
}
[Test]
public void ChainOperation_WithOpThatImplementGetDownloadStatus_ComputesExpectedDownloadPercentComplete()
{
var depOp = m_RM.CreateOperation<ManualDownloadPercentCompleteOperation>(typeof(ManualDownloadPercentCompleteOperation), 1, null, null);
depOp.Start(m_RM, default, null);
var chainOp = m_RM.CreateChainOperation<object>(new AsyncOperationHandle(depOp), s => m_RM.CreateCompletedOperationInternal<object>(null, true, null));
AssertExpectedDownloadStatus(chainOp.GetDownloadStatus(), 0, 1024, 0f);
depOp.m_bytesDownloaded = 512;
AssertExpectedDownloadStatus(chainOp.GetDownloadStatus(), 512, 1024, .5f);
depOp.CompleteNow();
m_RM.Update(.1f);
Assert.IsTrue(chainOp.IsDone);
AssertExpectedDownloadStatus(chainOp.GetDownloadStatus(), 1024, 1024, 1f);
m_RM.Release(chainOp);
}
[Test]
public void PercentComplete_ReturnsZero_WhenChainOperationHasNotBegun()
{
var baseOperation = m_RM.CreateChainOperation<AsyncOperationHandle>(
new AsyncOperationHandle(new ManualPercentCompleteOperation(1f)),
(obj) => { return new AsyncOperationHandle<AsyncOperationHandle>(); });
Assert.AreEqual(0, baseOperation.PercentComplete);
}
[Test]
public void GroupOperation_WithDuplicateOpThatImplementGetDownloadStatus_DoesNotOverCountValues()
{
var ops = new List<AsyncOperationHandle>();
var o = m_RM.CreateOperation<ManualDownloadPercentCompleteOperation>(typeof(ManualDownloadPercentCompleteOperation), 1, null, null);
o.Start(m_RM, default, null);
for (int i = 0; i < 4; i++)
ops.Add(new AsyncOperationHandle(o));
var gOp = m_RM.CreateGenericGroupOperation(ops, true);
AssertExpectedDownloadStatus(gOp.GetDownloadStatus(), 0, 1024, 0);
o.m_bytesDownloaded = 512;
AssertExpectedDownloadStatus(gOp.GetDownloadStatus(), 512, 1024, .5f);
o.CompleteNow();
AssertExpectedDownloadStatus(gOp.GetDownloadStatus(), 1024, 1024, 1f);
m_RM.Release(gOp);
}
class TestOp : AsyncOperationBase<int>
{
protected override void Execute()
{
InvokeCompletionEvent();
}
}
[Test]
public void CompletionEvents_AreInvoked_InOrderAdded()
{
var op = new TestOp();
int count = 0;
op.Completed += o =>
{
Assert.AreEqual(0, count);
count++;
};
op.CompletedTypeless += o =>
{
Assert.AreEqual(1, count);
count++;
};
op.Completed += o =>
{
Assert.AreEqual(2, count);
count++;
};
op.CompletedTypeless += o =>
{
Assert.AreEqual(3, count);
count++;
};
op.Start(null, default, null);
op.Complete(1, true, null);
}
[Test]
public void WhenOperationIsReused_HasExecutedIsReset()
{
var op = new TestOp();
op.Start(null, default, null);
op.Complete(1, true, null);
Assert.IsTrue(op.HasExecuted);
var dep = new AsyncOperationHandle(new TestOp());
op.Start(null, dep, null);
Assert.IsFalse(op.HasExecuted);
}
}
}