378 lines
14 KiB
C#
378 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);
|
||
|
}
|
||
|
}
|
||
|
}
|