using NUnit.Framework; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEditor.Build.Pipeline.Interfaces; using UnityEditor.Build.Pipeline.Utilities; using UnityEngine; using static UnityEditor.Build.Pipeline.Utilities.TaskCachingUtility; namespace UnityEditor.Build.Pipeline.Tests { public class TaskCachingUtilityTests { class ItemContext { public ItemContext(int input) { this.input = input; } public int input; public int result; } class FakeTracker : IProgressTracker { public bool shouldCancel; int IProgressTracker.TaskCount { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } float IProgressTracker.Progress => throw new NotImplementedException(); bool IProgressTracker.UpdateInfo(string taskInfo) { return !shouldCancel; } bool IProgressTracker.UpdateTask(string taskTitle) { throw new NotImplementedException(); } } class TestRunCachedCallbacks : IRunCachedCallbacks { public int CreateCachedInfoCount; public Func, CachedInfo> CreateCachedInfoCB; public int CreateCacheEntryCount; public Func, CacheEntry> CreateCacheEntryCB; public int PostProcessCount; public Action> PostProcessCB; public int ProcessCachedCount; public Action, CachedInfo> ProcessCachedCB; public int ProcessUncachedCount; public Action> ProcessUncachedCB; public void ClearCounts() { CreateCachedInfoCount = 0; CreateCacheEntryCount = 0; PostProcessCount = 0; ProcessCachedCount = 0; ProcessUncachedCount = 0; } public CachedInfo CreateCachedInfo(WorkItem item) { CreateCachedInfoCount++; return CreateCachedInfoCB(item); } public CacheEntry CreateCacheEntry(WorkItem item) { CreateCacheEntryCount++; return CreateCacheEntryCB(item); } public void PostProcess(WorkItem item) { PostProcessCount++; PostProcessCB(item); } public void ProcessCached(WorkItem item, CachedInfo info) { ProcessCachedCount++; ProcessCachedCB(item, info); } public void ProcessUncached(WorkItem item) { ProcessUncachedCount++; ProcessUncachedCB(item); } } [Test] public void RunCachedOperation_WhenSomeItemsCached_UncachedItemsAreProcessed() { Func transform = (x) => x * 27; const int kFirstPassCount = 5; const int kSecondPassCount = 12; Dictionary indexToResult = new Dictionary(); Action AssertResults = (count) => { Assert.AreEqual(count, indexToResult.Count); indexToResult.Keys.ToList().ForEach(x => Assert.AreEqual(transform(x), indexToResult[x])); }; List pass1Inputs = Enumerable.Range(0, kFirstPassCount).Select(x => x * 2).ToList(); List pass2Inputs = Enumerable.Range(0, kSecondPassCount).ToList(); List> workerInput1 = pass1Inputs.Select(i => new WorkItem(new ItemContext(i))).ToList(); List> workerInput2 = pass2Inputs.Select(i => new WorkItem(new ItemContext(i))).ToList(); TestRunCachedCallbacks callbacks = new TestRunCachedCallbacks(); callbacks.CreateCacheEntryCB = (item) => { return new CacheEntry() { Guid = HashingMethods.Calculate("Test").ToGUID(), Hash = HashingMethods.Calculate(item.Context.input).ToHash128(), Type = CacheEntry.EntryType.Data }; }; callbacks.ProcessUncachedCB = (item) => item.Context.result = transform(item.Context.input); callbacks.ProcessCachedCB = (item, info) => item.Context.result = (int)info.Data[0]; callbacks.PostProcessCB = (item) => indexToResult.Add(item.Context.input, item.Context.result); callbacks.CreateCachedInfoCB = (item) => { return new CachedInfo() { Data = new object[] { item.Context.result }, Dependencies = new CacheEntry[0], Asset = item.entry }; }; BuildCache.PurgeCache(false); using (BuildCache cache = new BuildCache()) { TaskCachingUtility.RunCachedOperation(cache, null, null, workerInput1, callbacks); AssertResults(workerInput1.Count); indexToResult.Clear(); Assert.AreEqual(kFirstPassCount, callbacks.CreateCacheEntryCount); Assert.AreEqual(kFirstPassCount, callbacks.PostProcessCount); Assert.AreEqual(0, callbacks.ProcessCachedCount); Assert.AreEqual(kFirstPassCount, callbacks.ProcessUncachedCount); Assert.AreEqual(kFirstPassCount, callbacks.CreateCachedInfoCount); callbacks.ClearCounts(); cache.SyncPendingSaves(); TaskCachingUtility.RunCachedOperation(cache, null, null, workerInput2, callbacks); AssertResults(workerInput2.Count); Assert.AreEqual(kSecondPassCount, callbacks.CreateCacheEntryCount); Assert.AreEqual(kSecondPassCount, callbacks.PostProcessCount); Assert.AreEqual(kFirstPassCount, callbacks.ProcessCachedCount); Assert.AreEqual(kSecondPassCount - kFirstPassCount, callbacks.ProcessUncachedCount); Assert.AreEqual(kSecondPassCount - kFirstPassCount, callbacks.CreateCachedInfoCount); } } [Test] public void RunCachedOperation_WhenCancelled_DoesNotContinueProcessing() { TestRunCachedCallbacks callbacks = new TestRunCachedCallbacks(); List> list = Enumerable.Range(0, 2). Select(i => new WorkItem(i)).ToList(); FakeTracker tracker = new FakeTracker(); callbacks.ProcessUncachedCB = (item) => { tracker.shouldCancel = true; }; callbacks.PostProcessCB = (item) => {}; ReturnCode code = TaskCachingUtility.RunCachedOperation(null, null, tracker, list, callbacks); Assert.AreEqual(1, callbacks.ProcessUncachedCount); Assert.AreEqual(code, ReturnCode.Canceled); } [Test] public void RunCachedOperation_WhenNoCache_ProcessesAllItems() { const int kIterations = 5; TestRunCachedCallbacks callbacks = new TestRunCachedCallbacks(); Dictionary indexToResult = new Dictionary(); List> list = Enumerable.Range(0, kIterations). Select(i => new WorkItem(new ItemContext(i * 10))).ToList(); callbacks.ProcessUncachedCB = (item) => item.Context.result = item.Context.input * 100; callbacks.PostProcessCB = (item) => indexToResult.Add(item.Context.input, item.Context.result); TaskCachingUtility.RunCachedOperation(null, null, null, list, callbacks); Assert.AreEqual(5, callbacks.PostProcessCount); Assert.AreEqual(5, callbacks.ProcessUncachedCount); foreach (WorkItem item in list) Assert.AreEqual(item.Context.result, indexToResult[item.Context.input]); } } }