initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -0,0 +1,115 @@
|
|||
#if UNITY_2022_2_OR_NEWER
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.IO.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests.ContentLoad
|
||||
{
|
||||
|
||||
[Serializable()]
|
||||
public class Catalog
|
||||
{
|
||||
[Serializable()]
|
||||
public class ContentFileInfo
|
||||
{
|
||||
public string Filename;
|
||||
public List<string> Dependencies;
|
||||
}
|
||||
|
||||
[Serializable()]
|
||||
public class AddressableLocation
|
||||
{
|
||||
public string AddressableName;
|
||||
public string Filename;
|
||||
public ulong LFID;
|
||||
}
|
||||
|
||||
public List<ContentFileInfo> ContentFiles = new List<ContentFileInfo>();
|
||||
public List<AddressableLocation> Locations = new List<AddressableLocation>();
|
||||
|
||||
private Dictionary<string, AddressableLocation> AddressToLocation =
|
||||
new Dictionary<string, AddressableLocation>();
|
||||
|
||||
private Dictionary<string, ContentFileInfo> FileToInfo = new Dictionary<string, ContentFileInfo>();
|
||||
|
||||
public Catalog()
|
||||
{
|
||||
}
|
||||
|
||||
unsafe public static string ReadAllTextVFS(string path)
|
||||
{
|
||||
FileInfoResult infoResult;
|
||||
ReadHandle h = AsyncReadManager.GetFileInfo(path, &infoResult);
|
||||
h.JobHandle.Complete();
|
||||
var getInfoStatus = h.Status;
|
||||
h.Dispose();
|
||||
|
||||
if (getInfoStatus != ReadStatus.Complete)
|
||||
throw new Exception($"Could not get file info for path {path}");
|
||||
|
||||
FileHandle fH = AsyncReadManager.OpenFileAsync(path);
|
||||
ReadCommand cmd;
|
||||
cmd.Buffer = UnsafeUtility.Malloc(infoResult.FileSize, 0, Unity.Collections.Allocator.Temp);
|
||||
cmd.Offset = 0;
|
||||
cmd.Size = infoResult.FileSize;
|
||||
var readHandle = AsyncReadManager.Read(path, &cmd, 1);
|
||||
readHandle.JobHandle.Complete();
|
||||
AsyncReadManager.CloseCachedFileAsync(path).Complete();
|
||||
|
||||
var readResult = readHandle.Status;
|
||||
readHandle.Dispose();
|
||||
|
||||
if (readResult != ReadStatus.Complete)
|
||||
{
|
||||
UnsafeUtility.Free(cmd.Buffer, Unity.Collections.Allocator.Temp);
|
||||
throw new Exception($"Failed to read data from {path}");
|
||||
}
|
||||
|
||||
// Convert to string
|
||||
string text = System.Text.Encoding.Default.GetString((byte*) cmd.Buffer, (int) cmd.Size);
|
||||
|
||||
UnsafeUtility.Free(cmd.Buffer, Unity.Collections.Allocator.Temp);
|
||||
return text;
|
||||
}
|
||||
|
||||
public static Catalog LoadFromFile(string path)
|
||||
{
|
||||
string jsonText = ReadAllTextVFS(path);
|
||||
Catalog catalog = JsonUtility.FromJson<Catalog>(jsonText);
|
||||
catalog.OnDeserialize();
|
||||
return catalog;
|
||||
}
|
||||
|
||||
public AddressableLocation GetLocation(string name)
|
||||
{
|
||||
return AddressToLocation[name];
|
||||
}
|
||||
|
||||
public ContentFileInfo GetFileInfo(string filename)
|
||||
{
|
||||
return FileToInfo[filename];
|
||||
}
|
||||
|
||||
void BuildMaps()
|
||||
{
|
||||
AddressToLocation = new Dictionary<string, AddressableLocation>();
|
||||
FileToInfo = new Dictionary<string, ContentFileInfo>();
|
||||
foreach (ContentFileInfo f in ContentFiles)
|
||||
FileToInfo[f.Filename] = f;
|
||||
foreach (AddressableLocation l in Locations)
|
||||
{
|
||||
AddressToLocation[l.AddressableName] = l;
|
||||
}
|
||||
}
|
||||
|
||||
[OnDeserializing()]
|
||||
public void OnDeserialize()
|
||||
{
|
||||
BuildMaps();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cd104a158e673469cab9e0947990aef2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,201 @@
|
|||
#if UNITY_2022_2_OR_NEWER
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Unity.Collections;
|
||||
using Unity.Content;
|
||||
using Unity.Loading;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using System.Linq;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests.ContentLoad
|
||||
{
|
||||
public abstract class ContentFileFixture : IPrebuildSetup
|
||||
{
|
||||
protected string m_ContentRoot;
|
||||
|
||||
protected string ContentRoot
|
||||
{
|
||||
get { return m_ContentRoot; }
|
||||
}
|
||||
|
||||
public virtual string ContentDir
|
||||
{
|
||||
get { return Path.Combine(ContentRoot, this.GetType().Name); }
|
||||
}
|
||||
|
||||
protected Catalog m_Catalog;
|
||||
protected string m_CatalogDir;
|
||||
protected ContentNamespace m_NS;
|
||||
Dictionary<string, ContentFile> m_LoadedFiles = new Dictionary<string, ContentFile>();
|
||||
List<ContentFile> m_ToUnload = new List<ContentFile>();
|
||||
|
||||
public ContentFileFixture()
|
||||
{
|
||||
m_ContentRoot = Application.streamingAssetsPath;
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
m_NS = ContentNamespace.GetOrCreateNamespace("Test");
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
for (int i = m_ToUnload.Count - 1; i >= 0; i--)
|
||||
m_ToUnload[i].UnloadAsync().WaitForCompletion(0);
|
||||
m_ToUnload.Clear();
|
||||
|
||||
if (TestContext.CurrentContext.Result.Outcome != ResultState.Failure)
|
||||
{
|
||||
Assert.AreEqual(0, ContentLoadInterface.GetContentFiles(m_NS).Length);
|
||||
Assert.AreEqual(0, ContentLoadInterface.GetSceneFiles(m_NS).Length);
|
||||
}
|
||||
|
||||
m_NS.Delete();
|
||||
}
|
||||
|
||||
public void LoadCatalog(string name, bool mountAllArchives = true)
|
||||
{
|
||||
m_CatalogDir = Path.Combine(ContentDir, name);
|
||||
string path = Path.Combine(m_CatalogDir, "catalog.json");
|
||||
m_Catalog = Catalog.LoadFromFile(path);
|
||||
}
|
||||
|
||||
public string GetVFSFilename(string fn)
|
||||
{
|
||||
return Path.Combine(m_CatalogDir, fn);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected abstract void PrepareBuildLayout();
|
||||
|
||||
Dictionary<string, List<AssetBundleBuild>> m_CurrentBuildLayouts =
|
||||
new Dictionary<string, List<AssetBundleBuild>>();
|
||||
|
||||
protected virtual void DefaultBuild()
|
||||
{
|
||||
PrepareBuildLayout();
|
||||
foreach (var kvp in m_CurrentBuildLayouts)
|
||||
{
|
||||
string outDir = Path.Combine(ContentDir, kvp.Key);
|
||||
BuildContentLoadsAndCatalog(outDir, kvp.Value.ToArray());
|
||||
}
|
||||
|
||||
// Create a file manifest
|
||||
List<string> allFiles = Directory.EnumerateFiles(ContentDir, "*", SearchOption.AllDirectories)
|
||||
.Select(x => x.Substring(ContentDir.Length + 1))
|
||||
.Where(x => !x.EndsWith(".meta") && x != "FileManifest.txt").ToList();
|
||||
string manifestDst = Path.Combine(ContentDir, "FileManifest.txt");
|
||||
File.WriteAllText(manifestDst, string.Join('\n', allFiles));
|
||||
|
||||
}
|
||||
|
||||
public class ContentBuildCatalog : IDisposable
|
||||
{
|
||||
List<AssetBundleBuild> m_Items = new List<AssetBundleBuild>();
|
||||
ContentFileFixture m_Fixture;
|
||||
string m_Name;
|
||||
|
||||
public ContentBuildCatalog(string name, ContentFileFixture fixture)
|
||||
{
|
||||
m_Name = name;
|
||||
m_Fixture = fixture;
|
||||
}
|
||||
|
||||
public void Add(AssetBundleBuild build)
|
||||
{
|
||||
m_Items.Add(build);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_Fixture.m_CurrentBuildLayouts.Add(m_Name, m_Items);
|
||||
}
|
||||
}
|
||||
|
||||
public ContentBuildCatalog CreateCatalog(string name)
|
||||
{
|
||||
return new ContentBuildCatalog(name, this);
|
||||
}
|
||||
|
||||
protected void BuildContentLoadsAndCatalog(string outdir, AssetBundleBuild[] bundles)
|
||||
{
|
||||
Directory.CreateDirectory(outdir);
|
||||
for (int i = 0; i < bundles.Length; i++)
|
||||
bundles[i].assetBundleName = "";
|
||||
var content = new BundleBuildContent(bundles);
|
||||
var target = EditorUserBuildSettings.activeBuildTarget;
|
||||
var group = BuildPipeline.GetBuildTargetGroup(target);
|
||||
var parameters = new BundleBuildParameters(target, group, outdir);
|
||||
parameters.UseCache = false;
|
||||
parameters.BundleCompression = UnityEngine.BuildCompression.Uncompressed;
|
||||
parameters.NonRecursiveDependencies = false;
|
||||
parameters.WriteLinkXML = true;
|
||||
var taskList = DefaultBuildTasks.ContentFileCompatible();
|
||||
ClusterOutput cOutput = new ClusterOutput();
|
||||
IBundleBuildResults results;
|
||||
ContentPipeline.BuildAssetBundles(parameters, content, out results, taskList,
|
||||
new ContentFileIdentifiers(), cOutput);
|
||||
|
||||
Catalog catalog = new Catalog();
|
||||
foreach (var result in results.WriteResults)
|
||||
{
|
||||
var fi = new Catalog.ContentFileInfo();
|
||||
fi.Filename = result.Key;
|
||||
fi.Dependencies = new List<string>();
|
||||
foreach (var extRef in result.Value.externalFileReferences)
|
||||
fi.Dependencies.Add(extRef.filePath);
|
||||
catalog.ContentFiles.Add(fi);
|
||||
}
|
||||
|
||||
foreach (AssetBundleBuild ab in bundles)
|
||||
{
|
||||
for (int i = 0; i < ab.assetNames.Length; i++)
|
||||
{
|
||||
string assetName = ab.assetNames[i];
|
||||
GUID guid = AssetDatabase.GUIDFromAssetPath(assetName);
|
||||
var loc = new Catalog.AddressableLocation();
|
||||
loc.AddressableName = ab.addressableNames[i];
|
||||
if (assetName.EndsWith(".unity"))
|
||||
{
|
||||
loc.Filename = guid.ToString();
|
||||
loc.LFID = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ObjectIdentifier oi =
|
||||
ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(guid,
|
||||
EditorUserBuildSettings.activeBuildTarget)[0];
|
||||
loc.Filename = cOutput.ObjectToCluster[oi].ToString();
|
||||
loc.LFID = (ulong) cOutput.ObjectToLocalID[oi];
|
||||
}
|
||||
|
||||
catalog.Locations.Add(loc);
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllText(Path.Combine(outdir, "catalog.json"), JsonUtility.ToJson(catalog));
|
||||
}
|
||||
#endif
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
DefaultBuild();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b827df2c3769047c0a5c00f7654e75a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,177 @@
|
|||
#if UNITY_2022_2_OR_NEWER
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Unity.Collections;
|
||||
using Unity.Content;
|
||||
using Unity.IO.Archive;
|
||||
using Unity.Loading;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditor.TestTools;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests.ContentLoad
|
||||
{
|
||||
abstract public class SceneTests : ContentFileFixture
|
||||
{
|
||||
public static IEnumerator UnloadAllScenesExceptInitTestSceneAsync()
|
||||
{
|
||||
#pragma warning disable 0618
|
||||
var allScenes = SceneManager.GetAllScenes();
|
||||
var allScenesNoInit = allScenes.Where(x => !x.name.Contains("InitTestScene")).ToList();
|
||||
if (allScenes.Length == allScenesNoInit.Count)
|
||||
SceneManager.CreateScene("InitTestScene");
|
||||
foreach (var allScene in allScenesNoInit)
|
||||
{
|
||||
yield return SceneManager.UnloadSceneAsync(allScene);
|
||||
}
|
||||
#pragma warning restore 0618
|
||||
}
|
||||
|
||||
[UnitySetUp]
|
||||
public IEnumerator UnloadAllScenesExceptInitTestScene()
|
||||
{
|
||||
Assert.AreEqual(1, SceneManager.sceneCount);
|
||||
yield return null;
|
||||
yield return UnloadAllScenesExceptInitTestSceneAsync();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ContentSceneFile LoadSceneHelper(string path, string sceneName, LoadSceneMode mode, ContentFile[] deps,
|
||||
bool integrate = true, bool autoIntegrate = false)
|
||||
{
|
||||
var sceneParams = new ContentSceneParameters();
|
||||
sceneParams.loadSceneMode = mode;
|
||||
sceneParams.localPhysicsMode = LocalPhysicsMode.None;
|
||||
sceneParams.autoIntegrate = autoIntegrate;
|
||||
|
||||
NativeArray<ContentFile> files =
|
||||
new NativeArray<ContentFile>(deps.Length, Allocator.Temp, NativeArrayOptions.ClearMemory);
|
||||
for (int i = 0; i < deps.Length; i++)
|
||||
{
|
||||
files[i] = deps[i];
|
||||
}
|
||||
|
||||
ContentSceneFile op = ContentLoadInterface.LoadSceneAsync(m_NS, path, sceneName, sceneParams, files);
|
||||
files.Dispose();
|
||||
|
||||
if (integrate)
|
||||
{
|
||||
op.WaitForLoadCompletion(0);
|
||||
if (op.Status == SceneLoadingStatus.WaitingForIntegrate)
|
||||
op.IntegrateAtEndOfFrame();
|
||||
}
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
private void AssertNoDepSceneLoaded(ContentSceneFile sceneFile)
|
||||
{
|
||||
LoadCatalog("nodepscene");
|
||||
Assert.AreEqual(SceneLoadingStatus.Complete, sceneFile.Status);
|
||||
|
||||
Scene scene = sceneFile.Scene;
|
||||
GameObject[] objs = scene.GetRootGameObjects();
|
||||
GameObject test = objs.First(x => x.name == "testobject");
|
||||
Assert.IsTrue(SceneManager.GetSceneByName("testscene").IsValid());
|
||||
|
||||
Assert.AreEqual(sceneFile, ContentLoadInterface.GetSceneFiles(m_NS)[0]);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator CanLoadSceneWithNoDependencies()
|
||||
{
|
||||
LoadCatalog("nodepscene");
|
||||
Catalog.AddressableLocation p1Loc = m_Catalog.GetLocation("nodepscene");
|
||||
|
||||
ArchiveHandle aHandle = ArchiveFileInterface.MountAsync(ContentNamespace.Default, GetVFSFilename(p1Loc.Filename), "a:");
|
||||
aHandle.JobHandle.Complete();
|
||||
Assert.True(aHandle.JobHandle.IsCompleted);
|
||||
Assert.True(aHandle.Status == ArchiveStatus.Complete);
|
||||
try
|
||||
{
|
||||
var mountPath = aHandle.GetMountPath();
|
||||
var vfsPath = Path.Combine(mountPath, p1Loc.Filename);
|
||||
var sceneFile = LoadSceneHelper(vfsPath, "testscene", LoadSceneMode.Additive,
|
||||
new ContentFile[] {ContentFile.GlobalTableDependency});
|
||||
|
||||
while (sceneFile.Status == SceneLoadingStatus.InProgress)
|
||||
yield return null;
|
||||
|
||||
Assert.AreEqual(SceneLoadingStatus.WillIntegrateNextFrame, sceneFile.Status);
|
||||
yield return null;
|
||||
|
||||
AssertNoDepSceneLoaded(sceneFile);
|
||||
sceneFile.UnloadAtEndOfFrame();
|
||||
yield return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
aHandle.Unmount();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void PrepareBuildLayout()
|
||||
{
|
||||
Directory.CreateDirectory("Assets/Temp");
|
||||
|
||||
// Create a scene with no dependencies
|
||||
using (var c = CreateCatalog("nodepscene"))
|
||||
{
|
||||
Scene scene1 = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
|
||||
RenderSettings.skybox = null;
|
||||
SceneManager.SetActiveScene(scene1);
|
||||
var go = new GameObject("testobject");
|
||||
EditorSceneManager.SaveScene(scene1, "Assets/Temp/nodepscene.unity");
|
||||
EditorSceneManager.CloseScene(scene1, true);
|
||||
c.Add(
|
||||
new AssetBundleBuild
|
||||
{
|
||||
assetNames = new string[] {"Assets/Temp/nodepscene.unity"},
|
||||
addressableNames = new string[] {"nodepscene"}
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[UnityPlatform(exclude = new RuntimePlatform[]
|
||||
{RuntimePlatform.LinuxEditor, RuntimePlatform.OSXEditor, RuntimePlatform.WindowsEditor})]
|
||||
class SceneTests_Standalone : SceneTests
|
||||
{
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityPlatform(RuntimePlatform.WindowsEditor)]
|
||||
[RequirePlatformSupport(BuildTarget.StandaloneWindows64)]
|
||||
class SceneTests_WindowsEditor : SceneTests
|
||||
{
|
||||
}
|
||||
|
||||
[UnityPlatform(RuntimePlatform.OSXEditor)]
|
||||
[RequirePlatformSupport(BuildTarget.StandaloneOSX)]
|
||||
class SceneTests_OSXEditor : SceneTests
|
||||
{
|
||||
}
|
||||
|
||||
[UnityPlatform(RuntimePlatform.LinuxEditor)]
|
||||
[RequirePlatformSupport(BuildTarget.StandaloneLinux64)]
|
||||
class SceneTests_LinuxEditor : SceneTests
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f5a22dad5548343f7b41b6f993d78696
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,109 @@
|
|||
#if UNITY_2022_2_OR_NEWER
|
||||
using System.Collections;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using Unity.Loading;
|
||||
using Unity.Collections;
|
||||
using Unity.Content;
|
||||
using System.IO;
|
||||
using Unity.IO.Archive;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.TestTools;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests.ContentLoad
|
||||
{
|
||||
abstract public class ContentFileTests : ContentFileFixture
|
||||
{
|
||||
const string kCatalogTextData = "TextData";
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator LoadFileAsync_CanLoadText()
|
||||
{
|
||||
LoadCatalog(kCatalogTextData);
|
||||
Catalog.AddressableLocation p1Loc = m_Catalog.GetLocation("Text");
|
||||
Catalog.ContentFileInfo fInfo = m_Catalog.GetFileInfo(p1Loc.Filename);
|
||||
|
||||
ArchiveHandle aHandle = ArchiveFileInterface.MountAsync(ContentNamespace.Default, GetVFSFilename(fInfo.Filename), "a:");
|
||||
aHandle.JobHandle.Complete();
|
||||
Assert.True(aHandle.JobHandle.IsCompleted);
|
||||
Assert.True(aHandle.Status == ArchiveStatus.Complete);
|
||||
try
|
||||
{
|
||||
var mountPath = aHandle.GetMountPath();
|
||||
var vfsPath = Path.Combine(mountPath, fInfo.Filename);
|
||||
ContentFile fileHandle = ContentLoadInterface.LoadContentFileAsync(m_NS, vfsPath, new NativeArray<ContentFile>());
|
||||
|
||||
while (fileHandle.LoadingStatus == LoadingStatus.InProgress)
|
||||
yield return null;
|
||||
|
||||
TextAsset text = (TextAsset)fileHandle.GetObject(p1Loc.LFID);
|
||||
Assert.NotZero(text.bytes.Length);
|
||||
|
||||
fileHandle.UnloadAsync().WaitForCompletion(0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
aHandle.Unmount();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static System.Int32 randomSeed = 1;
|
||||
|
||||
protected override void PrepareBuildLayout()
|
||||
{
|
||||
Directory.CreateDirectory("Assets/Temp");
|
||||
|
||||
using (var c = CreateCatalog(kCatalogTextData))
|
||||
{
|
||||
var path = AssetDatabase.GenerateUniqueAssetPath("Assets/Temp/textfile.txt");
|
||||
using (var fs = File.Create(path))
|
||||
{
|
||||
var bytes = new byte[64 * 1024];
|
||||
var rand = new System.Random(++randomSeed);
|
||||
rand.NextBytes(bytes);
|
||||
for (System.UInt32 j = 0; j < 10; j++)
|
||||
fs.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
AssetDatabase.ImportAsset(path);
|
||||
|
||||
c.Add(new AssetBundleBuild()
|
||||
{
|
||||
assetNames = new string[] { path },
|
||||
addressableNames = new string[] { "Text" }
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[UnityPlatform(exclude = new RuntimePlatform[]
|
||||
{RuntimePlatform.LinuxEditor, RuntimePlatform.OSXEditor, RuntimePlatform.WindowsEditor})]
|
||||
class ContentFileTests_Standalone : ContentFileTests
|
||||
{
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityPlatform(RuntimePlatform.WindowsEditor)]
|
||||
[RequirePlatformSupport(BuildTarget.StandaloneWindows64)]
|
||||
class ContentFileTests_WindowsEditor : ContentFileTests
|
||||
{
|
||||
}
|
||||
|
||||
[UnityPlatform(RuntimePlatform.OSXEditor)]
|
||||
[RequirePlatformSupport(BuildTarget.StandaloneOSX)]
|
||||
class ContentFileTests_OSXEditor : ContentFileTests
|
||||
{
|
||||
}
|
||||
|
||||
[UnityPlatform(RuntimePlatform.LinuxEditor)]
|
||||
[RequirePlatformSupport(BuildTarget.StandaloneLinux64)]
|
||||
class ContentFileTests_LinuxEditor : ContentFileTests
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c951970d3a8774823adda08337ad175b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d759189bc3e47411985027b7cb98f986
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,213 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Tests;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
public class ArchiveAndCompressTestFixture
|
||||
{
|
||||
internal static void AssertDirectoriesEqual(string expectedDirectory, string directory, int expectedCount = -1)
|
||||
{
|
||||
string[] expectedFiles = Directory.GetFiles(expectedDirectory);
|
||||
Array.Sort(expectedFiles);
|
||||
string[] files = Directory.GetFiles(directory);
|
||||
Array.Sort(files);
|
||||
if (expectedCount != -1)
|
||||
Assert.AreEqual(expectedCount, expectedFiles.Length);
|
||||
Assert.AreEqual(expectedFiles.Length, files.Length);
|
||||
|
||||
for (int i = 0; i < expectedFiles.Length; i++)
|
||||
FileAssert.AreEqual(expectedFiles[i], files[i]);
|
||||
}
|
||||
|
||||
internal string CreateTempDir(string testDir)
|
||||
{
|
||||
string tempDirectory = Path.Combine("Temp", testDir);
|
||||
Directory.CreateDirectory(tempDirectory);
|
||||
return tempDirectory;
|
||||
}
|
||||
|
||||
internal static void WriteRandomData(string filename, long size, int seed)
|
||||
{
|
||||
System.Random r = new System.Random(seed);
|
||||
using (var s = File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite))
|
||||
{
|
||||
long written = 0;
|
||||
byte[] bytes = new byte[Math.Min(1 * 1024 * 1024, size)];
|
||||
while (written < size)
|
||||
{
|
||||
r.NextBytes(bytes);
|
||||
int writeSize = (int)Math.Min(size - written, bytes.Length);
|
||||
s.Write(bytes, 0, writeSize);
|
||||
written += bytes.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal string CreateFileOfSize(string path, long size)
|
||||
{
|
||||
System.Random r = new System.Random(m_Seed);
|
||||
m_Seed++;
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
WriteRandomData(path, size, 0);
|
||||
return path;
|
||||
}
|
||||
|
||||
int m_Seed;
|
||||
internal string m_TestTempDir;
|
||||
internal string m_FixtureTempDir;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetup()
|
||||
{
|
||||
// Spooky hash has a static constructor that must be called on the main thread.
|
||||
m_FixtureTempDir = CreateTempDir("FixtureTemp");
|
||||
HashingMethods.CalculateStream(new MemoryStream(new byte[] { 1 }));
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTeardown()
|
||||
{
|
||||
Directory.Delete(m_FixtureTempDir, true);
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
m_Seed = 0;
|
||||
BuildCache.PurgeCache(false); // TOOD: If the build cache didn't use global directories, this wouldn't be necessary
|
||||
m_TestTempDir = CreateTempDir("TestTemp");
|
||||
m_SizeCounts = new Dictionary<int, int>();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
if (Directory.Exists(m_TestTempDir))
|
||||
Directory.Delete(m_TestTempDir, true);
|
||||
}
|
||||
|
||||
internal string[] RunWebExtract(string filePath)
|
||||
{
|
||||
var baseDir = Path.GetDirectoryName(EditorApplication.applicationPath);
|
||||
var webExtractFiles = Directory.GetFiles(baseDir, "WebExtract*", SearchOption.AllDirectories);
|
||||
string webExtractPath = webExtractFiles[0];
|
||||
|
||||
Assert.IsTrue(File.Exists(filePath), "Param filePath does not point to an existing file.");
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo =
|
||||
{
|
||||
FileName = webExtractPath,
|
||||
Arguments = string.Format(@"""{0}""", filePath),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
var exitCode = process.ExitCode;
|
||||
process.Close();
|
||||
|
||||
Assert.AreEqual(0, exitCode);
|
||||
//UnityEngine.Debug.Log(output);
|
||||
return Directory.GetFiles(filePath + "_data");
|
||||
}
|
||||
|
||||
internal ArchiveAndCompressBundles.TaskInput GetDefaultInput()
|
||||
{
|
||||
ArchiveAndCompressBundles.TaskInput input = new ArchiveAndCompressBundles.TaskInput();
|
||||
|
||||
input.InternalFilenameToWriteResults = new Dictionary<string, WriteResult>();
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
input.BundleNameToAdditionalFiles = new Dictionary<string, List<ResourceFile>>();
|
||||
#endif
|
||||
input.InternalFilenameToBundleName = new Dictionary<string, string>();
|
||||
input.AssetToFilesDependencies = new Dictionary<UnityEditor.GUID, List<string>>();
|
||||
input.InternalFilenameToWriteMetaData = new Dictionary<string, SerializedFileMetaData>();
|
||||
input.BuildCache = null;
|
||||
input.Threaded = false;
|
||||
input.ProgressTracker = null;
|
||||
input.OutCachedBundles = new List<string>();
|
||||
input.GetCompressionForIdentifier = (x) => UnityEngine.BuildCompression.LZ4;
|
||||
input.GetOutputFilePathForIdentifier = (x) => Path.Combine(m_TestTempDir, "bundleoutdir", x);
|
||||
input.TempOutputFolder = Path.Combine(m_TestTempDir, "temptestdir");
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
internal WriteResult AddSimpleBundle(ArchiveAndCompressBundles.TaskInput input, string bundleName, string internalName, string filePath)
|
||||
{
|
||||
WriteResult writeResult = new WriteResult();
|
||||
ResourceFile file = new ResourceFile();
|
||||
file.SetFileName(filePath);
|
||||
file.SetFileAlias(internalName);
|
||||
file.SetSerializedFile(false);
|
||||
writeResult.SetResourceFiles(new ResourceFile[] { file });
|
||||
input.InternalFilenameToWriteResults.Add(internalName, writeResult);
|
||||
input.InternalFilenameToBundleName.Add(internalName, bundleName);
|
||||
SerializedFileMetaData md = new SerializedFileMetaData();
|
||||
md.RawFileHash = HashingMethods.CalculateFile(filePath).ToHash128();
|
||||
md.ContentHash = HashingMethods.CalculateFile(filePath).ToHash128();
|
||||
input.InternalFilenameToWriteMetaData.Add(internalName, md);
|
||||
return writeResult;
|
||||
}
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
internal void AddRawFileThatTargetsBundle(ArchiveAndCompressBundles.TaskInput input, string targetBundleName, string rawFileInternalName, string filePath)
|
||||
{
|
||||
ResourceFile file = new ResourceFile();
|
||||
file.SetFileName(filePath);
|
||||
file.SetFileAlias(rawFileInternalName);
|
||||
file.SetSerializedFile(false);
|
||||
List<ResourceFile> files = new List<ResourceFile> { file };
|
||||
input.BundleNameToAdditionalFiles.Add(targetBundleName, files);
|
||||
}
|
||||
|
||||
internal void AddRawFileThatTargetsBundle(ArchiveAndCompressBundles.TaskInput input, string targetBundleName, string rawFileInternalName)
|
||||
{
|
||||
string tempFilename = CreateFileOfSize(GetUniqueFilename(Path.Combine(m_FixtureTempDir, "src", "rawfile.bin")), 1024);
|
||||
AddRawFileThatTargetsBundle(input, targetBundleName, rawFileInternalName, tempFilename);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public static string GetUniqueFilename(string desiredFilename)
|
||||
{
|
||||
string dir = Path.GetDirectoryName(desiredFilename);
|
||||
string noExtention = Path.GetFileNameWithoutExtension(desiredFilename);
|
||||
string ext = Path.GetExtension(desiredFilename);
|
||||
for (int i = 0; true; i++)
|
||||
{
|
||||
string testName = Path.Combine(dir, Path.Combine($"{noExtention}{i}{ext}"));
|
||||
if (!File.Exists(testName))
|
||||
{
|
||||
return testName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Dictionary<int, int> m_SizeCounts = new Dictionary<int, int>();
|
||||
internal WriteResult AddSimpleBundle(ArchiveAndCompressBundles.TaskInput input, string bundleName, string internalName, int size = 1024)
|
||||
{
|
||||
if (!m_SizeCounts.ContainsKey(size))
|
||||
m_SizeCounts[size] = 0;
|
||||
int count = m_SizeCounts[size]++;
|
||||
string tempFilename = Path.Combine(m_FixtureTempDir, "src", $"testfile_{size}_{count}.bin");
|
||||
if (!File.Exists(tempFilename))
|
||||
CreateFileOfSize(tempFilename, size);
|
||||
return AddSimpleBundle(input, bundleName, internalName, tempFilename);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6ab8ec987142fec4f84a3daa06a8b4c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,458 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Injector;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
public class ArchiveAndCompressTests : ArchiveAndCompressTestFixture
|
||||
{
|
||||
[Test]
|
||||
public void WhenAssetInBundleHasDependencies_DependenciesAreInDetails()
|
||||
{
|
||||
ArchiveAndCompressBundles.TaskInput input = GetDefaultInput();
|
||||
AddSimpleBundle(input, "mybundle", "internalName");
|
||||
AddSimpleBundle(input, "mybundle2", "internalName2");
|
||||
AddSimpleBundle(input, "mybundle3", "internalName3");
|
||||
|
||||
input.AssetToFilesDependencies.Add(new GUID(), new List<string>() { "internalName", "internalName2" });
|
||||
input.AssetToFilesDependencies.Add(GUID.Generate(), new List<string>() { "internalName", "internalName3" });
|
||||
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
|
||||
Assert.AreEqual(2, output.BundleDetails["mybundle"].Dependencies.Length);
|
||||
Assert.AreEqual("mybundle2", output.BundleDetails["mybundle"].Dependencies[0]);
|
||||
Assert.AreEqual("mybundle3", output.BundleDetails["mybundle"].Dependencies[1]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenBundleDoesNotHaveDependencies_DependenciesAreNotInDetails()
|
||||
{
|
||||
ArchiveAndCompressBundles.TaskInput input = GetDefaultInput();
|
||||
AddSimpleBundle(input, "mybundle", "internalName");
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
Assert.AreEqual(0, output.BundleDetails["mybundle"].Dependencies.Length);
|
||||
}
|
||||
|
||||
[TestCase("01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_d", ".")]
|
||||
[TestCase("C:/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars", ".")]
|
||||
[TestCase(".", "01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_d")]
|
||||
[TestCase(".", "C:/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars/01_long_directory_path_for_35chars")]
|
||||
[UnityPlatform(exclude = new[] { RuntimePlatform.LinuxEditor, RuntimePlatform.OSXEditor })]
|
||||
public void WhenUsingLongPath_CopyFileWithTimestampIfDifferent_ThrowsPathTooLongException(string path1, string path2)
|
||||
{
|
||||
Assert.Throws<PathTooLongException>(() => ArchiveAndCompressBundles.CopyFileWithTimestampIfDifferent(path1, path2, null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
// Windows has Unicode path notation for long paths: \\?\
|
||||
// however this does not work on all unity editor versions or windows version.
|
||||
// notably this fails on Yamato 2019.4 through 2021.1, but passed on trunk (on Nov 4, 2021)
|
||||
[UnityPlatform(exclude = new[] { RuntimePlatform.WindowsEditor })]
|
||||
public void PlatformCanHandle_LongPathReadingAndWriting()
|
||||
{
|
||||
string root = Path.GetFullPath("FolderDepthTest");
|
||||
|
||||
string fullPath = root;
|
||||
while (fullPath.Length < 300)
|
||||
fullPath = Path.Combine(fullPath, $"IncreaseFolderDepth_{fullPath.Length}");
|
||||
string file1 = Path.Combine(fullPath, "test1.txt");
|
||||
string file2 = Path.Combine(fullPath, "test2.txt");
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
// Can create folder > 260 characters deep
|
||||
Directory.CreateDirectory(fullPath);
|
||||
|
||||
// Can write file > 260 characters deep
|
||||
File.WriteAllText(file1, "Test file contents");
|
||||
|
||||
// Can move file > 260 characters deep
|
||||
File.Move(file1, file2);
|
||||
|
||||
// Can read file > 260 characters deep
|
||||
var contents = File.ReadAllText(file2);
|
||||
Assert.AreEqual("Test file contents", contents);
|
||||
|
||||
// Can delete file > 260 characters deep
|
||||
File.Delete(file2);
|
||||
|
||||
// Can delete folder > 260 characters deep
|
||||
Directory.Delete(fullPath);
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
Directory.Delete(root, true);
|
||||
}
|
||||
|
||||
public class RebuildTestContext
|
||||
{
|
||||
internal ArchiveAndCompressBundles.TaskInput input;
|
||||
internal ArchiveAndCompressTests _this;
|
||||
};
|
||||
|
||||
public static IEnumerable RebuildTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData(false, new Action<RebuildTestContext>((ctx) => { })).SetName("NoChanges");
|
||||
yield return new TestCaseData(true, new Action<RebuildTestContext>((ctx) =>
|
||||
{
|
||||
ctx.input.InternalFilenameToWriteMetaData["internalName"] = new SerializedFileMetaData() { ContentHash = new Hash128(0, 1), RawFileHash = new Hash128(1, 2) };
|
||||
})).SetName("SourceFileHashChanges");
|
||||
yield return new TestCaseData(true, new Action<RebuildTestContext>((ctx) =>
|
||||
{
|
||||
ctx.input.GetCompressionForIdentifier = (x) => UnityEngine.BuildCompression.Uncompressed;
|
||||
})).SetName("CompressionChanges");
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
yield return new TestCaseData(true, new Action<RebuildTestContext>((ctx) =>
|
||||
{
|
||||
ctx._this.AddRawFileThatTargetsBundle(ctx.input, "mybundle", "rawInternalName");
|
||||
})).SetName("AddAdditionalFile");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(typeof(ArchiveAndCompressTests), "RebuildTestCases")]
|
||||
public void WhenInputsChanges_AndRebuilt_CachedDataIsUsedAsExpected(bool shouldRebuild, Action<RebuildTestContext> postFirstBuildAction)
|
||||
{
|
||||
BuildCache.PurgeCache(false);
|
||||
using (BuildCache cache = new BuildCache())
|
||||
{
|
||||
RebuildTestContext ctx = new RebuildTestContext();
|
||||
ctx.input = GetDefaultInput();
|
||||
ctx._this = this;
|
||||
ctx.input.BuildCache = cache;
|
||||
|
||||
AddSimpleBundle(ctx.input, "mybundle", "internalName");
|
||||
|
||||
ArchiveAndCompressBundles.Run(ctx.input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
cache.SyncPendingSaves();
|
||||
Assert.AreEqual(0, ctx.input.OutCachedBundles.Count);
|
||||
|
||||
postFirstBuildAction(ctx);
|
||||
|
||||
ctx.input.OutCachedBundles.Clear();
|
||||
ArchiveAndCompressBundles.Run(ctx.input, out ArchiveAndCompressBundles.TaskOutput outputReRun);
|
||||
|
||||
if (shouldRebuild)
|
||||
Assert.AreEqual(0, ctx.input.OutCachedBundles.Count);
|
||||
else
|
||||
Assert.AreEqual(1, ctx.input.OutCachedBundles.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenArchiveIsAlreadyBuilt_CachedVersionIsUsed()
|
||||
{
|
||||
string bundleOutDir1 = Path.Combine(m_TestTempDir, "bundleoutdir1");
|
||||
string bundleOutDir2 = Path.Combine(m_TestTempDir, "bundleoutdir2");
|
||||
Directory.CreateDirectory(bundleOutDir1);
|
||||
Directory.CreateDirectory(bundleOutDir2);
|
||||
ArchiveAndCompressBundles.TaskInput input = GetDefaultInput();
|
||||
BuildCache cache = new BuildCache();
|
||||
input.BuildCache = cache;
|
||||
AddSimpleBundle(input, "mybundle", "internalName");
|
||||
input.GetOutputFilePathForIdentifier = (x) => Path.Combine(bundleOutDir1, x);
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
Assert.AreEqual(0, input.OutCachedBundles.Count);
|
||||
cache.SyncPendingSaves();
|
||||
|
||||
input.GetOutputFilePathForIdentifier = (x) => Path.Combine(bundleOutDir2, x);
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output2);
|
||||
Assert.AreEqual(1, input.OutCachedBundles.Count);
|
||||
Assert.AreEqual("mybundle", input.OutCachedBundles[0]);
|
||||
AssertDirectoriesEqual(bundleOutDir1, bundleOutDir2, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenArchiveIsCached_AndRebuildingArchive_HashIsAssignedToOutput()
|
||||
{
|
||||
string bundleName = "mybundle";
|
||||
ArchiveAndCompressBundles.TaskInput input = GetDefaultInput();
|
||||
BuildCache cache = new BuildCache();
|
||||
input.BuildCache = cache;
|
||||
AddSimpleBundle(input, bundleName, "internalName");
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
var hash = output.BundleDetails[bundleName].Hash;
|
||||
Assert.AreNotEqual(Hash128.Parse(""), output.BundleDetails[bundleName].Hash);
|
||||
cache.SyncPendingSaves();
|
||||
|
||||
//Now our bundle is cached so we'll run again and make sure the hashes are non-zero and equal
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output2);
|
||||
Assert.AreEqual(hash, output2.BundleDetails[bundleName].Hash);
|
||||
}
|
||||
|
||||
public class ContentHashTestContext
|
||||
{
|
||||
internal ArchiveAndCompressBundles.TaskInput input;
|
||||
internal GUID assetGUID;
|
||||
internal ArchiveAndCompressTests _this;
|
||||
};
|
||||
|
||||
public static IEnumerable ContentHashTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData(true, new Action<ContentHashTestContext>((ctx) =>
|
||||
{
|
||||
ctx.input.AssetToFilesDependencies[ctx.assetGUID] = new List<string>() { "internalName", "internalName3" };
|
||||
})).SetName("DependencyChanges");
|
||||
yield return new TestCaseData(true, new Action<ContentHashTestContext>((ctx) =>
|
||||
{
|
||||
ctx.input.InternalFilenameToWriteMetaData["internalName"].ContentHash = new Hash128(128, 128);
|
||||
})).SetName("ContentHashChanges");
|
||||
yield return new TestCaseData(false, new Action<ContentHashTestContext>((ctx) =>
|
||||
{
|
||||
ctx.input.InternalFilenameToWriteMetaData["internalName"].RawFileHash = new Hash128(128, 128);
|
||||
})).SetName("RawHashChanges");
|
||||
}
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(typeof(ArchiveAndCompressTests), "ContentHashTestCases")]
|
||||
public void WhenInputsChange_BundleOutputHashIsAffectedAsExpected(bool hashShouldChange, Action<ContentHashTestContext> postFirstBuildAction)
|
||||
{
|
||||
ContentHashTestContext ctx = new ContentHashTestContext();
|
||||
ctx.input = GetDefaultInput();
|
||||
WriteResult result = AddSimpleBundle(ctx.input, "mybundle", "internalName");
|
||||
WriteResult result2 = AddSimpleBundle(ctx.input, "mybundle2", "internalName2");
|
||||
WriteResult result3 = AddSimpleBundle(ctx.input, "mybundle3", "internalName3");
|
||||
ctx.assetGUID = GUID.Generate();
|
||||
ctx.input.AssetToFilesDependencies.Add(ctx.assetGUID, new List<string>() { "internalName", "internalName2" });
|
||||
|
||||
ArchiveAndCompressBundles.Run(ctx.input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
|
||||
postFirstBuildAction(ctx);
|
||||
|
||||
ArchiveAndCompressBundles.Run(ctx.input, out ArchiveAndCompressBundles.TaskOutput output2);
|
||||
|
||||
Hash128 prevHash = output.BundleDetails["mybundle"].Hash;
|
||||
Hash128 newHash = output2.BundleDetails["mybundle"].Hash;
|
||||
if (hashShouldChange)
|
||||
Assert.AreNotEqual(prevHash, newHash);
|
||||
else
|
||||
Assert.AreEqual(prevHash, newHash);
|
||||
}
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
[Test]
|
||||
public void WhenBuildingManyArchives_ThreadedAndNonThreadedResultsAreIdentical()
|
||||
{
|
||||
const int kBundleCount = 100;
|
||||
ArchiveAndCompressBundles.TaskInput input = GetDefaultInput();
|
||||
|
||||
for (int i = 0; i < kBundleCount; i++)
|
||||
AddSimpleBundle(input, $"mybundle{i}", $"internalName{i}");
|
||||
|
||||
input.Threaded = false;
|
||||
input.GetOutputFilePathForIdentifier = (x) => Path.Combine(m_TestTempDir, "bundleoutdir_nothreading", x);
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output1);
|
||||
|
||||
input.Threaded = true;
|
||||
input.GetOutputFilePathForIdentifier = (x) => Path.Combine(m_TestTempDir, "bundleoutdir_threading", x);
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output2);
|
||||
|
||||
AssertDirectoriesEqual(Path.Combine(m_TestTempDir, "bundleoutdir_nothreading"), Path.Combine(m_TestTempDir, "bundleoutdir_threading"), kBundleCount);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Start is called before the first frame update
|
||||
[Test]
|
||||
public void ResourceFilesAreAddedToBundles()
|
||||
{
|
||||
ArchiveAndCompressBundles.TaskInput input = GetDefaultInput();
|
||||
string bundleOutDir = Path.Combine(m_TestTempDir, "bundleoutdir");
|
||||
|
||||
AddSimpleBundle(input, "mybundle", "internalName");
|
||||
|
||||
string srcFile = input.InternalFilenameToWriteResults["internalName"].resourceFiles[0].fileName;
|
||||
|
||||
ReturnCode code = ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
Assert.AreEqual(ReturnCode.Success, code);
|
||||
|
||||
string[] files = RunWebExtract(Path.Combine(bundleOutDir, "mybundle"));
|
||||
Assert.AreEqual(1, files.Length);
|
||||
FileAssert.AreEqual(files[0], srcFile);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenBuildingArchive_BuildLogIsPopulated()
|
||||
{
|
||||
ArchiveAndCompressBundles.TaskInput input = GetDefaultInput();
|
||||
var log = new BuildLog();
|
||||
input.Log = log;
|
||||
AddSimpleBundle(input, "mybundle", "internalName");
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
var node = log.Root.Children.Find((x) => x.Name.StartsWith("ArchiveItems"));
|
||||
Assert.IsNotNull(node);
|
||||
}
|
||||
|
||||
class ScopeCapturer : IBuildLogger
|
||||
{
|
||||
public List<string> Scopes = new List<string>();
|
||||
public void AddEntry(LogLevel level, string msg)
|
||||
{
|
||||
}
|
||||
|
||||
public void BeginBuildStep(LogLevel level, string stepName, bool subStepsCanBeThreaded)
|
||||
{
|
||||
lock (Scopes)
|
||||
{
|
||||
Scopes.Add(stepName);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsScopeWithSubstring(string subString)
|
||||
{
|
||||
return Scopes.Count((x) => x.Contains(subString)) != 0;
|
||||
}
|
||||
|
||||
public void EndBuildStep()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSimpleBundleAndBuild(out ArchiveAndCompressBundles.TaskInput input, out string bundleBuildDir)
|
||||
{
|
||||
ScopeCapturer log1 = new ScopeCapturer();
|
||||
string bDir = Path.Combine(m_TestTempDir, "bundleoutdir1");
|
||||
Directory.CreateDirectory(bDir);
|
||||
input = GetDefaultInput();
|
||||
BuildCache cache = new BuildCache();
|
||||
input.BuildCache = cache;
|
||||
input.Log = log1;
|
||||
AddSimpleBundle(input, "mybundle", "internalName");
|
||||
input.GetOutputFilePathForIdentifier = (x) => Path.Combine(bDir, x);
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
Assert.AreEqual(0, input.OutCachedBundles.Count);
|
||||
Assert.IsTrue(log1.ContainsScopeWithSubstring("Copying From Cache"));
|
||||
cache.SyncPendingSaves();
|
||||
bundleBuildDir = bDir;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenArchiveIsAlreadyBuilt_AndArchiveIsInOutputDirectory_ArchiveIsNotCopied()
|
||||
{
|
||||
AddSimpleBundleAndBuild(out ArchiveAndCompressBundles.TaskInput input, out string bundleOutDir1);
|
||||
ScopeCapturer log2 = new ScopeCapturer();
|
||||
|
||||
input.GetOutputFilePathForIdentifier = (x) => Path.Combine(bundleOutDir1, x);
|
||||
input.Log = log2;
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output2);
|
||||
Assert.AreEqual(1, input.OutCachedBundles.Count);
|
||||
Assert.AreEqual("mybundle", input.OutCachedBundles[0]);
|
||||
Assert.IsFalse(log2.ContainsScopeWithSubstring("Copying From Cache"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenArchiveIsAlreadyBuilt_AndArchiveIsInOutputDirectoryButTimestampMismatch_ArchiveIsCopied()
|
||||
{
|
||||
AddSimpleBundleAndBuild(out ArchiveAndCompressBundles.TaskInput input, out string bundleOutDir1);
|
||||
|
||||
// Change the creation timestamp on the bundles
|
||||
string bundlePath = Path.Combine(bundleOutDir1, "mybundle");
|
||||
File.SetLastWriteTime(bundlePath, new DateTime(2019, 1, 1));
|
||||
|
||||
ScopeCapturer log2 = new ScopeCapturer();
|
||||
|
||||
input.GetOutputFilePathForIdentifier = (x) => Path.Combine(bundleOutDir1, x);
|
||||
input.Log = log2;
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output2);
|
||||
|
||||
Assert.AreEqual(1, input.OutCachedBundles.Count);
|
||||
Assert.AreEqual("mybundle", input.OutCachedBundles[0]);
|
||||
Assert.IsTrue(log2.ContainsScopeWithSubstring("Copying From Cache"));
|
||||
}
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
[Test]
|
||||
public void CanAddRawFilesToBundles()
|
||||
{
|
||||
ArchiveAndCompressBundles.TaskInput input = GetDefaultInput();
|
||||
AddSimpleBundle(input, "mybundle", "internalName");
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output);
|
||||
|
||||
AddRawFileThatTargetsBundle(input, "mybundle", "rawName");
|
||||
ArchiveAndCompressBundles.Run(input, out ArchiveAndCompressBundles.TaskOutput output2);
|
||||
Assert.IsTrue(output.BundleDetails["mybundle"].Hash.isValid);
|
||||
Assert.IsTrue(output2.BundleDetails["mybundle"].Hash.isValid);
|
||||
Assert.AreNotEqual(output.BundleDetails["mybundle"].Hash, output2.BundleDetails["mybundle"].Hash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SupportsMultiThreadedArchiving_WhenEditorIs20193OrLater_IsTrue()
|
||||
{
|
||||
Assert.IsTrue(ReflectionExtensions.SupportsMultiThreadedArchiving);
|
||||
}
|
||||
|
||||
#else
|
||||
[Test]
|
||||
public void SupportsMultiThreadedArchiving_WhenEditorIsBefore20193_IsFalse()
|
||||
{
|
||||
Assert.IsFalse(ReflectionExtensions.SupportsMultiThreadedArchiving);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[Test]
|
||||
public void CalculateBundleDependencies_ReturnsRecursiveDependencies_ForNonRecursiveInputs()
|
||||
{
|
||||
// Dictionary<string, string[]> CalculateBundleDependencies(List<List<string>> assetFileList, Dictionary<string, string> filenameToBundleName)
|
||||
// Inputs: assetFileList = per asset list of unique file dependencies, first entry is the main dependency
|
||||
// filenameToBundleName = mapping of file name to asset bundle name
|
||||
// Output: mapping of bundle name to array of bundle name dependencies
|
||||
|
||||
List<List<string>> assetFileList = new List<List<string>>();
|
||||
assetFileList.Add(new List<string> { "file1", "file2" });
|
||||
assetFileList.Add(new List<string> { "file2", "file3" });
|
||||
|
||||
Dictionary<string, string> filenameToBundleName = new Dictionary<string, string>();
|
||||
filenameToBundleName.Add("file1", "bundle1");
|
||||
filenameToBundleName.Add("file2", "bundle2");
|
||||
filenameToBundleName.Add("file3", "bundle3");
|
||||
|
||||
Dictionary<string, string[]> results = ArchiveAndCompressBundles.CalculateBundleDependencies(assetFileList, filenameToBundleName);
|
||||
|
||||
CollectionAssert.AreEquivalent(new string[] { "bundle1", "bundle2", "bundle3" }, results.Keys);
|
||||
CollectionAssert.AreEquivalent(new string[] { "bundle2", "bundle3" }, results["bundle1"]);
|
||||
CollectionAssert.AreEquivalent(new string[] { "bundle3" }, results["bundle2"]);
|
||||
CollectionAssert.AreEquivalent(new string[] { }, results["bundle3"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CalculateBundleDependencies_ReturnsRecursiveDependencies_ForRecursiveInputs()
|
||||
{
|
||||
// Dictionary<string, string[]> CalculateBundleDependencies(List<List<string>> assetFileList, Dictionary<string, string> filenameToBundleName)
|
||||
// Inputs: assetFileList = per asset list of unique file dependencies, first entry is the main dependency
|
||||
// filenameToBundleName = mapping of file name to asset bundle name
|
||||
// Output: mapping of bundle name to array of bundle name dependencies
|
||||
|
||||
List<List<string>> assetFileList = new List<List<string>>();
|
||||
assetFileList.Add(new List<string> { "file1", "file2", "file3" });
|
||||
assetFileList.Add(new List<string> { "file2", "file3" });
|
||||
|
||||
Dictionary<string, string> filenameToBundleName = new Dictionary<string, string>();
|
||||
filenameToBundleName.Add("file1", "bundle1");
|
||||
filenameToBundleName.Add("file2", "bundle2");
|
||||
filenameToBundleName.Add("file3", "bundle3");
|
||||
|
||||
Dictionary<string, string[]> results = ArchiveAndCompressBundles.CalculateBundleDependencies(assetFileList, filenameToBundleName);
|
||||
|
||||
CollectionAssert.AreEquivalent(new string[] { "bundle1", "bundle2", "bundle3" }, results.Keys);
|
||||
CollectionAssert.AreEquivalent(new string[] { "bundle2", "bundle3" }, results["bundle1"]);
|
||||
CollectionAssert.AreEquivalent(new string[] { "bundle3" }, results["bundle2"]);
|
||||
CollectionAssert.AreEquivalent(new string[] { }, results["bundle3"]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c2c7d61e655a9134eb902da0ddc663ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.ScriptableBuildPipeline.Editor.Tests")]
|
||||
[assembly: InternalsVisibleTo("PerformanceTests.Editor")]
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0437b76f03e61424abbe630113ecd0f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,249 @@
|
|||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
class AssetLoadInfoSortingTests
|
||||
{
|
||||
const string kTestAssetFolder = "Assets/TestAssets";
|
||||
const string kTestAsset = "Assets/TestAssets/SpriteTexture32x32.png";
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
Directory.CreateDirectory(kTestAssetFolder);
|
||||
CreateTestSpriteTexture();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
Directory.Delete(kTestAssetFolder, true);
|
||||
File.Delete(kTestAssetFolder + ".meta");
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
static void CreateTestSpriteTexture()
|
||||
{
|
||||
var data = ImageConversion.EncodeToPNG(new Texture2D(32, 32));
|
||||
File.WriteAllBytes(kTestAsset, data);
|
||||
AssetDatabase.ImportAsset(kTestAsset, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate);
|
||||
var importer = AssetImporter.GetAtPath(kTestAsset) as TextureImporter;
|
||||
importer.textureType = TextureImporterType.Sprite;
|
||||
importer.spriteImportMode = SpriteImportMode.Multiple;
|
||||
importer.spritesheet = new[]
|
||||
{
|
||||
new SpriteMetaData
|
||||
{
|
||||
name = "WhiteTexture32x32_0",
|
||||
rect = new Rect(0, 19, 32, 13),
|
||||
alignment = 0,
|
||||
pivot = new Vector2(0.5f, 0.5f),
|
||||
border = new Vector4(0, 0, 0, 0)
|
||||
},
|
||||
new SpriteMetaData
|
||||
{
|
||||
name = "WhiteTexture32x32_1",
|
||||
rect = new Rect(4, 19, 24, 11),
|
||||
alignment = 0,
|
||||
pivot = new Vector2(0.5f, 0.5f),
|
||||
border = new Vector4(0, 0, 0, 0)
|
||||
},
|
||||
new SpriteMetaData
|
||||
{
|
||||
name = "WhiteTexture32x32_2",
|
||||
rect = new Rect(9, 5, 12, 7),
|
||||
alignment = 0,
|
||||
pivot = new Vector2(0.5f, 0.5f),
|
||||
border = new Vector4(0, 0, 0, 0)
|
||||
}
|
||||
};
|
||||
importer.SaveAndReimport();
|
||||
}
|
||||
|
||||
static AssetLoadInfo GetTestAssetLoadInfo()
|
||||
{
|
||||
GUID asset = new GUID(AssetDatabase.AssetPathToGUID(kTestAsset));
|
||||
ObjectIdentifier[] oId = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(asset, EditorUserBuildSettings.activeBuildTarget);
|
||||
AssetLoadInfo loadInfo = new AssetLoadInfo()
|
||||
{
|
||||
asset = asset,
|
||||
address = kTestAsset,
|
||||
includedObjects = oId.ToList(),
|
||||
referencedObjects = new List<ObjectIdentifier>()
|
||||
};
|
||||
return loadInfo;
|
||||
}
|
||||
|
||||
static List<AssetLoadInfo> GetTestAssetLoadInfoList()
|
||||
{
|
||||
List<AssetLoadInfo> testList = new List<AssetLoadInfo>
|
||||
{
|
||||
GetTestAssetLoadInfo(),
|
||||
GetTestAssetLoadInfo()
|
||||
};
|
||||
return testList;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByGuid()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
testList[1].asset = new GUID();
|
||||
|
||||
var asset0 = testList[0].asset;
|
||||
var asset1 = testList[1].asset;
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.AreEqual(asset1, testList[0].asset);
|
||||
Assert.AreEqual(asset0, testList[1].asset);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByGuid_PreSorted()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
testList[0].asset = new GUID();
|
||||
|
||||
var asset0 = testList[0].asset;
|
||||
var asset1 = testList[1].asset;
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.AreEqual(asset0, testList[0].asset);
|
||||
Assert.AreEqual(asset1, testList[1].asset);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByIncludedObjects()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
|
||||
// Verify which one we should swap to ensure current ordering is different from expected ordering.
|
||||
if (testList[0].includedObjects[0].localIdentifierInFile < testList[0].includedObjects[1].localIdentifierInFile)
|
||||
testList[0].includedObjects.Swap(0, 1);
|
||||
else
|
||||
testList[1].includedObjects.Swap(0, 1);
|
||||
|
||||
var object0 = testList[0].includedObjects[0];
|
||||
var object1 = testList[1].includedObjects[0];
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.AreEqual(object1, testList[0].includedObjects[0]);
|
||||
Assert.AreEqual(object0, testList[1].includedObjects[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByIncludedObjects_PreSorted()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
|
||||
// Verify which one we should swap to ensure current ordering is different from expected ordering.
|
||||
if (testList[0].includedObjects[0].localIdentifierInFile < testList[0].includedObjects[1].localIdentifierInFile)
|
||||
testList[1].includedObjects.Swap(0, 1);
|
||||
else
|
||||
testList[0].includedObjects.Swap(0, 1);
|
||||
|
||||
var object0 = testList[0].includedObjects[0];
|
||||
var object1 = testList[1].includedObjects[0];
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.AreEqual(object0, testList[0].includedObjects[0]);
|
||||
Assert.AreEqual(object1, testList[1].includedObjects[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByIncludedObjects_SortsNullIncludedObjects()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
testList[1].includedObjects = null;
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.IsNull(testList[0].includedObjects);
|
||||
Assert.IsNotNull(testList[1].includedObjects);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByIncludedObjects_SortsNullIncludedObjects_PreSorted()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
testList[0].includedObjects = null;
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.IsNull(testList[0].includedObjects);
|
||||
Assert.IsNotNull(testList[1].includedObjects);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByAddress()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
testList[1].address = "A/short/path";
|
||||
|
||||
var address0 = testList[0].address;
|
||||
var address1 = testList[1].address;
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.AreEqual(address1, testList[0].address);
|
||||
Assert.AreEqual(address0, testList[1].address);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByAddress_PreSorted()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
testList[0].address = "A/short/path";
|
||||
|
||||
var address0 = testList[0].address;
|
||||
var address1 = testList[1].address;
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.AreEqual(address0, testList[0].address);
|
||||
Assert.AreEqual(address1, testList[1].address);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByAddress_SortsNullAdress()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
testList[1].address = null;
|
||||
|
||||
var address0 = testList[0].address;
|
||||
var address1 = testList[1].address;
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.AreEqual(address1, testList[0].address);
|
||||
Assert.AreEqual(address0, testList[1].address);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetLoadInfo_SortsByAddress_SortsNullAdress_PreSorted()
|
||||
{
|
||||
List<AssetLoadInfo> testList = GetTestAssetLoadInfoList();
|
||||
testList[0].address = null;
|
||||
|
||||
var address0 = testList[0].address;
|
||||
var address1 = testList[1].address;
|
||||
|
||||
testList.Sort(GenerateBundleCommands.AssetLoadInfoCompare);
|
||||
|
||||
Assert.AreEqual(address0, testList[0].address);
|
||||
Assert.AreEqual(address1, testList[1].address);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a8294b087f18cdb49a6dc0c48dccb3a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,459 @@
|
|||
using System;
|
||||
using NUnit.Framework;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEditor.SceneManagement;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
abstract internal class BuildCacheTestBase
|
||||
{
|
||||
protected const string kBuildCacheTestPath = "Assets/BuildCacheTestAssets";
|
||||
|
||||
protected string kTestFile1
|
||||
{
|
||||
get { return Path.Combine(kBuildCacheTestPath, "testfile1.txt"); }
|
||||
}
|
||||
|
||||
protected string kUncachedTestFilename
|
||||
{
|
||||
get { return Path.Combine(kBuildCacheTestPath, "uncached.txt"); }
|
||||
}
|
||||
|
||||
protected string kTempAssetFilename
|
||||
{
|
||||
get { return Path.Combine(kBuildCacheTestPath, "temporary.txt"); }
|
||||
}
|
||||
|
||||
protected string kTestScenePath
|
||||
{
|
||||
get { return Path.Combine(kBuildCacheTestPath, "testScene.unity"); }
|
||||
}
|
||||
|
||||
protected GUID TestFile1GUID
|
||||
{
|
||||
get { return new GUID(AssetDatabase.AssetPathToGUID(kTestFile1)); }
|
||||
}
|
||||
|
||||
protected GUID UncachedGUID
|
||||
{
|
||||
get { return new GUID(AssetDatabase.AssetPathToGUID(kUncachedTestFilename)); }
|
||||
}
|
||||
|
||||
protected GUID TempAssetGUID
|
||||
{
|
||||
get { return new GUID(AssetDatabase.AssetPathToGUID(kTempAssetFilename)); }
|
||||
}
|
||||
|
||||
protected GUID TestSceneGUID
|
||||
{
|
||||
get { return new GUID(AssetDatabase.AssetPathToGUID(kTestScenePath)); }
|
||||
}
|
||||
|
||||
protected BuildCache m_Cache;
|
||||
|
||||
internal virtual void OneTimeSetupDerived() {}
|
||||
internal virtual void OneTimeTearDownDerived() {}
|
||||
internal virtual void SetupDerived() {}
|
||||
internal virtual void TeardownDerived() {}
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
Directory.CreateDirectory(kBuildCacheTestPath);
|
||||
File.WriteAllText(kTestFile1, "t1");
|
||||
File.WriteAllText(kUncachedTestFilename, "uncached");
|
||||
File.WriteAllText(kTempAssetFilename, "delete me");
|
||||
AssetDatabase.Refresh();
|
||||
OneTimeSetupDerived();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
Directory.Delete(kBuildCacheTestPath, true);
|
||||
File.Delete(kBuildCacheTestPath + ".meta");
|
||||
AssetDatabase.Refresh();
|
||||
OneTimeTearDownDerived();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
BuildCacheUtility.ClearCacheHashes();
|
||||
PurgeBuildCache();
|
||||
RecreateBuildCache();
|
||||
SetupDerived();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (m_Cache != null)
|
||||
{
|
||||
m_Cache.Dispose();
|
||||
m_Cache = null;
|
||||
}
|
||||
TeardownDerived();
|
||||
}
|
||||
|
||||
protected virtual void RecreateBuildCache()
|
||||
{
|
||||
BuildCacheUtility.ClearCacheHashes();
|
||||
if (m_Cache != null)
|
||||
{
|
||||
m_Cache.SyncPendingSaves();
|
||||
m_Cache.Dispose();
|
||||
m_Cache = null;
|
||||
}
|
||||
m_Cache = new BuildCache();
|
||||
}
|
||||
|
||||
protected virtual void PurgeBuildCache()
|
||||
{
|
||||
if (m_Cache != null)
|
||||
m_Cache.SyncPendingSaves();
|
||||
BuildCache.PurgeCache(false);
|
||||
}
|
||||
|
||||
static protected CachedInfo LoadCachedInfoForGUID(BuildCache cache, GUID guid)
|
||||
{
|
||||
IList<CachedInfo> infos;
|
||||
CacheEntry entry1 = cache.GetCacheEntry(guid);
|
||||
cache.LoadCachedData(new List<CacheEntry>() { entry1 }, out infos);
|
||||
return infos[0];
|
||||
}
|
||||
|
||||
static protected string StoreDataInCacheWithGUID(BuildCache cache, GUID guid, object data, GUID depGUID = new GUID())
|
||||
{
|
||||
List<CacheEntry> deps = new List<CacheEntry>();
|
||||
if (!depGUID.Empty())
|
||||
deps.Add(cache.GetCacheEntry(depGUID));
|
||||
|
||||
CacheEntry entry1 = cache.GetCacheEntry(guid);
|
||||
CachedInfo info = new CachedInfo();
|
||||
info.Asset = entry1;
|
||||
info.Dependencies = deps.ToArray();
|
||||
info.Data = new object[] { data };
|
||||
cache.SaveCachedData(new List<CachedInfo>() { info });
|
||||
cache.SyncPendingSaves();
|
||||
return cache.GetCachedInfoFile(info.Asset);
|
||||
}
|
||||
|
||||
static protected GUID CreateTestTextAsset(string contents)
|
||||
{
|
||||
string filename;
|
||||
return CreateTestTextAsset(contents, out filename);
|
||||
}
|
||||
|
||||
static protected GUID CreateTestTextAsset(string contents, out string filename)
|
||||
{
|
||||
int fileIndex = 0;
|
||||
while (true)
|
||||
{
|
||||
filename = Path.Combine(kBuildCacheTestPath, "testasset" + fileIndex);
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
File.WriteAllText(filename, contents);
|
||||
AssetDatabase.Refresh();
|
||||
return new GUID(AssetDatabase.AssetPathToGUID(filename));
|
||||
}
|
||||
|
||||
fileIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
static protected void ModifyTestTextAsset(GUID guid, string text)
|
||||
{
|
||||
string filename = AssetDatabase.GUIDToAssetPath(guid.ToString());
|
||||
File.WriteAllText(filename, text);
|
||||
AssetDatabase.ImportAsset(filename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenLoadingCachedDataForGUIDWithModifiedDependency_CachedInfoIsNull()
|
||||
{
|
||||
GUID depGuid = CreateTestTextAsset("mytext");
|
||||
StoreDataInCacheWithGUID(m_Cache, TestFile1GUID, "data", depGuid);
|
||||
ModifyTestTextAsset(depGuid, "mytext2");
|
||||
RecreateBuildCache();
|
||||
CachedInfo info = LoadCachedInfoForGUID(m_Cache, TestFile1GUID);
|
||||
Assert.IsNull(info);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenLoadingCachedDataForModifiedGUID_CachedInfoIsNull()
|
||||
{
|
||||
GUID guid = CreateTestTextAsset("mytext");
|
||||
StoreDataInCacheWithGUID(m_Cache, guid, "data");
|
||||
ModifyTestTextAsset(guid, "mytext2");
|
||||
RecreateBuildCache();
|
||||
CachedInfo info = LoadCachedInfoForGUID(m_Cache, guid);
|
||||
Assert.IsNull(info);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenLoadingCachedDataForGUIDWithInvalidCacheData_CachedInfoIsNull()
|
||||
{
|
||||
GUID depGuid = CreateTestTextAsset("mytext");
|
||||
string path = StoreDataInCacheWithGUID(m_Cache, TestFile1GUID, "data", depGuid);
|
||||
RecreateBuildCache();
|
||||
File.WriteAllText(path, "Invalidating cache file! Good luck deserializing this! =P");
|
||||
CachedInfo info = LoadCachedInfoForGUID(m_Cache, TestFile1GUID);
|
||||
Assert.IsNull(info);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenLoadingStoredCachedData_CachedInfoIsValid()
|
||||
{
|
||||
StoreDataInCacheWithGUID(m_Cache, TestFile1GUID, "data");
|
||||
RecreateBuildCache();
|
||||
CachedInfo info = LoadCachedInfoForGUID(m_Cache, TestFile1GUID);
|
||||
Assert.AreEqual("data", (string)info.Data[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenLoadingUncachedData_CachedInfoIsNull()
|
||||
{
|
||||
CachedInfo info = LoadCachedInfoForGUID(m_Cache, UncachedGUID);
|
||||
Assert.IsNull(info);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenGlobalVersionChanges_OriginalCachedInfoDoesNotNeedRebuild()
|
||||
{
|
||||
m_Cache.OverrideGlobalHash(new Hash128(0, 1, 0, 0));
|
||||
|
||||
StoreDataInCacheWithGUID(m_Cache, TestFile1GUID, "data");
|
||||
CachedInfo info1 = LoadCachedInfoForGUID(m_Cache, TestFile1GUID);
|
||||
|
||||
RecreateBuildCache();
|
||||
m_Cache.OverrideGlobalHash(new Hash128(0, 2, 0, 0));
|
||||
CachedInfo info2 = LoadCachedInfoForGUID(m_Cache, TestFile1GUID);
|
||||
|
||||
Assert.IsFalse(m_Cache.HasAssetOrDependencyChanged(info1)); // original info still doesn't need rebuild. Required for content update
|
||||
Assert.IsNull(info2); // however the data cannot be recovered
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenLocalVersionChanges_AssetReturnsDifferentCacheEntry()
|
||||
{
|
||||
GUID guid = CreateTestTextAsset("mytext");
|
||||
|
||||
var entry1 = m_Cache.GetCacheEntry(guid, 2);
|
||||
var entry2 = m_Cache.GetCacheEntry(guid, 4);
|
||||
|
||||
Assert.AreEqual(entry1.Guid, entry2.Guid);
|
||||
Assert.AreEqual(entry1.File, entry2.File);
|
||||
Assert.AreEqual(entry1.Type, entry2.Type);
|
||||
Assert.AreNotEqual(entry1.Version, entry2.Version);
|
||||
Assert.AreNotEqual(entry1.Hash, entry2.Hash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenLocalVersionChanges_FileReturnsDifferentCacheEntry()
|
||||
{
|
||||
string filename;
|
||||
CreateTestTextAsset("mytext", out filename);
|
||||
|
||||
var entry1 = m_Cache.GetCacheEntry(filename, 2);
|
||||
var entry2 = m_Cache.GetCacheEntry(filename, 4);
|
||||
|
||||
Assert.AreEqual(entry1.Guid, entry2.Guid);
|
||||
Assert.AreEqual(entry1.File, entry2.File);
|
||||
Assert.AreEqual(entry1.Type, entry2.Type);
|
||||
Assert.AreNotEqual(entry1.Version, entry2.Version);
|
||||
Assert.AreNotEqual(entry1.Hash, entry2.Hash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUpdatedCacheEntry_ReturnsCacheEntryWithSameVersionAndHash_IfAssetHasNotChanged()
|
||||
{
|
||||
GUID guid = CreateTestTextAsset("mytext");
|
||||
|
||||
var entry1 = m_Cache.GetCacheEntry(guid, 2);
|
||||
m_Cache.ClearCacheEntryMaps();
|
||||
var entry2 = m_Cache.GetUpdatedCacheEntry(entry1);
|
||||
|
||||
Assert.AreEqual(entry1.Guid, entry2.Guid);
|
||||
Assert.AreEqual(entry1.File, entry2.File);
|
||||
Assert.AreEqual(entry1.Type, entry2.Type);
|
||||
Assert.AreEqual(entry1.Version, entry2.Version);
|
||||
Assert.AreEqual(entry1.Hash, entry2.Hash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUpdatedCacheEntry_ReturnsCacheEntryWithSameVersionAndHash_IfFileHasNotChanged()
|
||||
{
|
||||
string filename;
|
||||
CreateTestTextAsset("mytext", out filename);
|
||||
|
||||
var entry1 = m_Cache.GetCacheEntry(filename, 2);
|
||||
m_Cache.ClearCacheEntryMaps();
|
||||
var entry2 = m_Cache.GetUpdatedCacheEntry(entry1);
|
||||
|
||||
Assert.AreEqual(entry1.Guid, entry2.Guid);
|
||||
Assert.AreEqual(entry1.File, entry2.File);
|
||||
Assert.AreEqual(entry1.Type, entry2.Type);
|
||||
Assert.AreEqual(entry1.Version, entry2.Version);
|
||||
Assert.AreEqual(entry1.Hash, entry2.Hash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCacheEntry_InvalidPath_ReturnsInvalidCacheEntry()
|
||||
{
|
||||
var entry = m_Cache.GetCacheEntry("this/path/does/not/exist");
|
||||
Assert.IsFalse(entry.IsValid());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCacheEntry_InvalidGUID_ReturnsInvalidCacheEntry()
|
||||
{
|
||||
var entry = m_Cache.GetCacheEntry(new GUID("00000000000000000000000000000000"));
|
||||
Assert.IsFalse(entry.IsValid());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCacheEntry_FormerValidPathAndGUID_ReturnsInvalidCacheEntry()
|
||||
{
|
||||
var tempAssetGUID = TempAssetGUID;
|
||||
|
||||
var entry1 = m_Cache.GetCacheEntry(kTempAssetFilename);
|
||||
var entry2 = m_Cache.GetCacheEntry(tempAssetGUID);
|
||||
Assert.IsTrue(entry1.IsValid());
|
||||
Assert.IsTrue(entry2.IsValid());
|
||||
|
||||
File.Delete(kTempAssetFilename);
|
||||
AssetDatabase.Refresh();
|
||||
m_Cache.ClearCacheEntryMaps();
|
||||
|
||||
entry1 = m_Cache.GetCacheEntry(kTempAssetFilename);
|
||||
entry2 = m_Cache.GetCacheEntry(tempAssetGUID);
|
||||
Assert.IsFalse(entry1.IsValid());
|
||||
Assert.IsFalse(entry2.IsValid());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCacheEntry_PathForAsset_ReturnsAssetBasedCacheEntry()
|
||||
{
|
||||
var entry1 = m_Cache.GetCacheEntry(kTestFile1);
|
||||
var entry2 = m_Cache.GetCacheEntry(TestFile1GUID);
|
||||
|
||||
Assert.IsTrue(entry1.Type == CacheEntry.EntryType.Asset);
|
||||
Assert.AreEqual(entry2, entry1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCacheEntry_DiffStripUnusedMeshComponentsSettings_ReturnsDiffHashes()
|
||||
{
|
||||
Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
|
||||
EditorSceneManager.SaveScene(scene, kTestScenePath);
|
||||
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); // clear active scene
|
||||
|
||||
int version = 2;
|
||||
var kvp = new KeyValuePair<GUID, int>(TestSceneGUID, version);
|
||||
bool stripUnusedMeshComponents = PlayerSettings.stripUnusedMeshComponents;
|
||||
try
|
||||
{
|
||||
PlayerSettings.stripUnusedMeshComponents = false;
|
||||
CacheEntry entry1 = m_Cache.GetCacheEntry(TestSceneGUID, version);
|
||||
BuildCacheUtility.m_GuidToHash.Remove(kvp);
|
||||
|
||||
PlayerSettings.stripUnusedMeshComponents = true;
|
||||
CacheEntry entry2 = m_Cache.GetCacheEntry(TestSceneGUID);
|
||||
BuildCacheUtility.m_GuidToHash.Remove(kvp);
|
||||
|
||||
Assert.AreNotEqual(entry1.Hash, entry2.Hash);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PlayerSettings.stripUnusedMeshComponents = stripUnusedMeshComponents;
|
||||
AssetDatabase.DeleteAsset(kTestScenePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCachedInfoFile_IsInside_GetCachedArtifactsDirectory()
|
||||
{
|
||||
var entry = m_Cache.GetCacheEntry(kTestFile1);
|
||||
var infoFile = m_Cache.GetCachedInfoFile(entry);
|
||||
var artifactsDirectory = m_Cache.GetCachedArtifactsDirectory(entry);
|
||||
|
||||
StringAssert.Contains(artifactsDirectory, infoFile);
|
||||
}
|
||||
|
||||
void WriteRandomFile(string fileName, int sizeInMB)
|
||||
{
|
||||
byte[] data = new byte[sizeInMB * 1024 * 1024];
|
||||
var rng = new System.Random();
|
||||
rng.NextBytes(data);
|
||||
File.WriteAllBytes(fileName, data);
|
||||
}
|
||||
|
||||
void PopulateCache(out int filesWritten, out string[] artifactsDirectories)
|
||||
{
|
||||
var entries = new[]
|
||||
{
|
||||
m_Cache.GetCacheEntry(kTestFile1),
|
||||
m_Cache.GetCacheEntry(kUncachedTestFilename)
|
||||
};
|
||||
artifactsDirectories = new[]
|
||||
{
|
||||
Path.GetFullPath(m_Cache.GetCachedArtifactsDirectory(entries[0])),
|
||||
Path.GetFullPath(m_Cache.GetCachedArtifactsDirectory(entries[1]))
|
||||
};
|
||||
|
||||
// Setup cache
|
||||
filesWritten = 0;
|
||||
foreach (var directory in artifactsDirectories)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
WriteRandomFile($"{directory}/cachefile_{i}.bytes", 2);
|
||||
filesWritten++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PruneCache_ComputeCacheSizeAndFolders_ReturnsCorrectSizeAndFolders()
|
||||
{
|
||||
PopulateCache(out int filesWritten, out string[] artifactsDirectories);
|
||||
|
||||
BuildCache.ComputeCacheSizeAndFolders(out long currentCacheSize, out List<BuildCache.CacheFolder> cacheFolders);
|
||||
//filesWritten * 2mb each * 1024 to kb * 1024 to b
|
||||
Assert.AreEqual(filesWritten * 2 * 1024 * 1024, currentCacheSize);
|
||||
Assert.AreEqual(artifactsDirectories.Length, cacheFolders.Count);
|
||||
var folders = cacheFolders.Select(x => x.directory.FullName);
|
||||
CollectionAssert.AreEquivalent(artifactsDirectories, folders);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PruneCache_PruneCacheFolders_WillRemoveOldestFolders()
|
||||
{
|
||||
PopulateCache(out int filesWritten, out string[] artifactsDirectories);
|
||||
BuildCache.ComputeCacheSizeAndFolders(out long currentCacheSize, out List<BuildCache.CacheFolder> cacheFolders);
|
||||
|
||||
// Set folder older
|
||||
var folder = cacheFolders[0];
|
||||
folder.LastAccessTimeUtc = folder.LastAccessTimeUtc.Subtract(new TimeSpan(1, 0, 0));
|
||||
cacheFolders[0] = folder;
|
||||
|
||||
// delete just under the first folder size
|
||||
long maximumCacheSize = currentCacheSize - folder.Length + 1;
|
||||
BuildCache.PruneCacheFolders(maximumCacheSize, currentCacheSize, cacheFolders);
|
||||
BuildCache.ComputeCacheSizeAndFolders(out long newCurrentCacheSize, out List<BuildCache.CacheFolder> newCacheFolders);
|
||||
|
||||
Assert.AreNotEqual(0, newCurrentCacheSize);
|
||||
Assert.GreaterOrEqual(maximumCacheSize, newCurrentCacheSize);
|
||||
Assert.AreEqual(artifactsDirectories.Length - 1, newCacheFolders.Count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cd396e02e0371554e9bfc4ebb8035318
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,48 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
class MyContextObjectClass : IContextObject
|
||||
{
|
||||
}
|
||||
|
||||
interface ITestInterfaceWithContextDerivation : IContextObject {}
|
||||
class TestITestInterfaceWithContextDerivationImplementation : ITestInterfaceWithContextDerivation
|
||||
{}
|
||||
|
||||
public class BuildContextTests
|
||||
{
|
||||
BuildContext m_Ctx;
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
m_Ctx = new BuildContext();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetContextObject_WhenTypeDoesNotExist_AddsContextObject()
|
||||
{
|
||||
m_Ctx.SetContextObject(new MyContextObjectClass());
|
||||
Assert.NotNull(m_Ctx.GetContextObject<MyContextObjectClass>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetContextObject_WhenTypeHasInterfaceAssignableToContextObject_InterfaceAndObjectTypeUsedAsKey()
|
||||
{
|
||||
m_Ctx.SetContextObject(new TestITestInterfaceWithContextDerivationImplementation());
|
||||
Assert.NotNull(m_Ctx.GetContextObject<ITestInterfaceWithContextDerivation>());
|
||||
Assert.NotNull(m_Ctx.GetContextObject<TestITestInterfaceWithContextDerivationImplementation>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetContextObject_WhenTypeDoesNotExist_Throws()
|
||||
{
|
||||
Assert.Throws(typeof(Exception), () => m_Ctx.GetContextObject<ITestInterfaceWithContextDerivation>());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2406ad24700d76e4da5005529a6bafce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,158 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using static UnityEditor.Build.Pipeline.Utilities.BuildLog;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
public class BuildLogTests
|
||||
{
|
||||
[Test]
|
||||
public void WhenBeginAndEndScope_DurationIsCorrect()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
using (log.ScopedStep(LogLevel.Info, "TestStep"))
|
||||
Thread.Sleep(5);
|
||||
Assert.AreEqual("TestStep", log.Root.Children[0].Name);
|
||||
Assert.Greater(log.Root.Children[0].DurationMS, 4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenAddMessage_EntryIsCreated()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
using (log.ScopedStep(LogLevel.Info, "TestStep"))
|
||||
log.AddEntry(LogLevel.Info, "TestEntry");
|
||||
Assert.AreEqual("TestEntry", log.Root.Children[0].Entries[0].Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenMessageAddedWithScope_EntryIsCreated()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
((IDisposable)log.ScopedStep(LogLevel.Info, "TestStep", "TestEntry")).Dispose();
|
||||
Assert.AreEqual("TestEntry", log.Root.Children[0].Entries[0].Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenScopeIsThreaded_AndThreadAddsNode_NodeEnteredInThreadedScope()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
using (log.ScopedStep(LogLevel.Info, "TestStep", true))
|
||||
{
|
||||
var t = new Thread(() =>
|
||||
{
|
||||
log.AddEntry(LogLevel.Info, "ThreadedMsg1");
|
||||
using (log.ScopedStep(LogLevel.Info, "ThreadedStep"))
|
||||
{
|
||||
log.AddEntry(LogLevel.Info, "ThreadedMsg2");
|
||||
}
|
||||
});
|
||||
t.Start();
|
||||
t.Join();
|
||||
}
|
||||
Assert.AreEqual("ThreadedMsg1", log.Root.Children[0].Entries[0].Message);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, log.Root.Children[0].Entries[0].ThreadId);
|
||||
Assert.AreEqual("ThreadedStep", log.Root.Children[0].Children[0].Name);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, log.Root.Children[0].Children[0].ThreadId);
|
||||
Assert.AreEqual("ThreadedMsg2", log.Root.Children[0].Children[0].Entries[0].Message);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, log.Root.Children[0].Children[0].Entries[0].ThreadId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenBeginAndEndScopeOnThread_StartAndEndTimeAreWithinMainThreadScope()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
using (log.ScopedStep(LogLevel.Info, "TestStep", true))
|
||||
{
|
||||
var t = new Thread(() =>
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
log.AddEntry(LogLevel.Info, "ThreadedMsg1");
|
||||
Thread.Sleep(1);
|
||||
using (log.ScopedStep(LogLevel.Info, "ThreadedStep"))
|
||||
{
|
||||
Thread.Sleep(2);
|
||||
using (log.ScopedStep(LogLevel.Info, "ThreadedStepNested"))
|
||||
Thread.Sleep(2);
|
||||
}
|
||||
Thread.Sleep(1);
|
||||
});
|
||||
t.Start();
|
||||
t.Join();
|
||||
}
|
||||
|
||||
double testStepStart = log.Root.Children[0].StartTime;
|
||||
double threadedMessageStart = log.Root.Children[0].Entries[0].Time;
|
||||
double threadedScopeStart = log.Root.Children[0].Children[0].StartTime;
|
||||
double threadedScopeEnd = threadedScopeStart + log.Root.Children[0].Children[0].DurationMS;
|
||||
double threadedScopeNestedStart = log.Root.Children[0].Children[0].Children[0].StartTime;
|
||||
double testStepEnd = testStepStart + log.Root.Children[0].DurationMS;
|
||||
|
||||
Assert.Less(threadedScopeStart, threadedScopeNestedStart);
|
||||
Assert.Less(testStepStart, threadedMessageStart);
|
||||
Assert.Less(threadedMessageStart, threadedScopeStart);
|
||||
Assert.Less(threadedScopeStart, threadedScopeEnd);
|
||||
Assert.Less(threadedScopeEnd, testStepEnd);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenConvertingToTraceEventFormat_BackslashesAreEscaped()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
using (log.ScopedStep(LogLevel.Info, "TestStep\\AfterSlash"))
|
||||
log.AddEntry(LogLevel.Info, "TestEntry\\AfterSlash");
|
||||
string text = log.FormatForTraceEventProfiler();
|
||||
StringAssert.Contains("TestStep\\\\AfterSlash", text);
|
||||
StringAssert.Contains("TestEntry\\\\AfterSlash", text);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenConvertingToTraceEventFormat_MetaDataIsAdded()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
log.AddMetaData("SOMEKEY", "SOMEVALUE");
|
||||
string text = log.FormatForTraceEventProfiler();
|
||||
StringAssert.Contains("SOMEKEY", text);
|
||||
StringAssert.Contains("SOMEVALUE", text);
|
||||
}
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER || ENABLE_DETAILED_PROFILE_CAPTURING
|
||||
[Test]
|
||||
public void WhenBeginAndEndDeferredEventsDontMatchUp_HandleDeferredEventsStream_ThrowsException()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
DeferredEvent startEvent = new DeferredEvent() { Type = DeferredEventType.Begin };
|
||||
List<DeferredEvent> events = new List<DeferredEvent>() { startEvent };
|
||||
|
||||
Assert.Throws<Exception>(() => log.HandleDeferredEventStreamInternal(events));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenBeginAndEndDeferredEventsMatchUp_HandleDeferredEventsStream_CreatesLogEvents()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
DeferredEvent startEvent = new DeferredEvent() { Name = "Start", Type = DeferredEventType.Begin };
|
||||
DeferredEvent endEvent = new DeferredEvent() { Name = "End", Type = DeferredEventType.End };
|
||||
List<DeferredEvent> events = new List<DeferredEvent>() { startEvent, endEvent };
|
||||
|
||||
log.HandleDeferredEventStreamInternal(events);
|
||||
Assert.AreEqual(startEvent.Name, log.Root.Children[0].Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenDeferredEventsAreOnlyInfoTypes_HandleDeferredEventsStream_CreatesLogEntry()
|
||||
{
|
||||
BuildLog log = new BuildLog();
|
||||
DeferredEvent infoEvent = new DeferredEvent() { Name = "Info", Type = DeferredEventType.Info };
|
||||
List<DeferredEvent> events = new List<DeferredEvent>() { infoEvent };
|
||||
|
||||
log.HandleDeferredEventStreamInternal(events);
|
||||
Assert.AreEqual(infoEvent.Name, log.Root.Entries[0].Message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c9b3cd90055a04b48b05f0e458e05c07
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c655781b9e2c70945bf8e3f232c54d6e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace UnityEditor.CacheServerTests
|
||||
{
|
||||
internal class ByteArrayStream : Stream
|
||||
{
|
||||
private int m_pos;
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { return BackingBuffer.Length; }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { return m_pos; }
|
||||
set
|
||||
{
|
||||
m_pos = Math.Min((int)value, BackingBuffer.Length - 1);
|
||||
Debug.Assert(m_pos >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] BackingBuffer { get; private set; }
|
||||
|
||||
public ByteArrayStream(long size)
|
||||
{
|
||||
BackingBuffer = new byte[size];
|
||||
RandomNumberGenerator.Create().GetBytes(BackingBuffer);
|
||||
}
|
||||
|
||||
public override void SetLength(long value) {}
|
||||
public override void Flush() {}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Debug.Assert(count <= BackingBuffer.Length - m_pos); // can't write past out buffer length
|
||||
count = Math.Min(count, buffer.Length - offset); // Don't read past the input buffer length
|
||||
Buffer.BlockCopy(buffer, offset, BackingBuffer, m_pos, count);
|
||||
m_pos += count;
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
count = Math.Min(count, BackingBuffer.Length - m_pos); // Don't copy more bytes than we have
|
||||
count = Math.Min(count, buffer.Length - offset); // Don't overrun the destination buffer
|
||||
Buffer.BlockCopy(BackingBuffer, m_pos, buffer, offset, count);
|
||||
m_pos += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
Position = (int)offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.Current:
|
||||
Position += (int)offset;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
Position = BackingBuffer.Length - (int)offset - 1;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("origin", origin, null);
|
||||
}
|
||||
|
||||
return Position;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8cce051c0b60040d68cd27a80e58f787
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.CacheServerTests
|
||||
{
|
||||
internal class LocalCacheServer : ScriptableSingleton<LocalCacheServer>
|
||||
{
|
||||
[SerializeField] public string m_path;
|
||||
[SerializeField] public int m_port;
|
||||
[SerializeField] public int m_pid = -1;
|
||||
|
||||
private void Create(int port, ulong size, string cachePath)
|
||||
{
|
||||
var nodeExecutable = Utils.Paths.Combine(EditorApplication.applicationContentsPath, "Tools", "nodejs");
|
||||
nodeExecutable = Application.platform == RuntimePlatform.WindowsEditor
|
||||
? Utils.Paths.Combine(nodeExecutable, "node.exe")
|
||||
: Utils.Paths.Combine(nodeExecutable, "bin", "node");
|
||||
|
||||
if (!Directory.Exists(cachePath))
|
||||
Directory.CreateDirectory(cachePath);
|
||||
|
||||
m_path = cachePath;
|
||||
|
||||
var cacheServerJs = Utils.Paths.Combine(EditorApplication.applicationContentsPath, "Tools", "CacheServer", "main.js");
|
||||
var processStartInfo = new ProcessStartInfo(nodeExecutable)
|
||||
{
|
||||
Arguments = "\"" + cacheServerJs + "\""
|
||||
+ " --port " + port
|
||||
+ " --path \"" + m_path
|
||||
+ "\" --nolegacy"
|
||||
+ " --monitor-parent-process " + Process.GetCurrentProcess().Id
|
||||
// node.js has issues running on windows with stdout not redirected.
|
||||
// so we silence logging to avoid that. And also to avoid CacheServer
|
||||
// spamming the editor logs on OS X.
|
||||
+ " --silent"
|
||||
+ " --size " + size,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
var p = new Process { StartInfo = processStartInfo };
|
||||
p.Start();
|
||||
|
||||
m_port = port;
|
||||
m_pid = p.Id;
|
||||
Save(true);
|
||||
}
|
||||
|
||||
public static string CachePath
|
||||
{
|
||||
get { return instance.m_path; }
|
||||
}
|
||||
|
||||
public static int Port
|
||||
{
|
||||
get { return instance.m_port; }
|
||||
}
|
||||
|
||||
public static void Setup(ulong size, string cachePath)
|
||||
{
|
||||
Kill();
|
||||
instance.Create(GetRandomUnusedPort(), size, cachePath);
|
||||
WaitForServerToComeAlive(instance.m_port);
|
||||
}
|
||||
|
||||
public static void Kill()
|
||||
{
|
||||
if (instance.m_pid == -1)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var p = Process.GetProcessById(instance.m_pid);
|
||||
p.Kill();
|
||||
instance.m_pid = -1;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// if we could not get a process, there is non alive. continue.
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
Kill();
|
||||
if (Directory.Exists(instance.m_path))
|
||||
Directory.Delete(instance.m_path, true);
|
||||
}
|
||||
|
||||
private static void WaitForServerToComeAlive(int port)
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
var maximum = start.AddSeconds(5);
|
||||
while (DateTime.Now < maximum)
|
||||
{
|
||||
if (!PingHost("localhost", port, 10)) continue;
|
||||
Console.WriteLine("Server Came alive after {0} ms", (DateTime.Now - start).TotalMilliseconds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetRandomUnusedPort()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Any, 0);
|
||||
listener.Start();
|
||||
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
listener.Stop();
|
||||
return port;
|
||||
}
|
||||
|
||||
private static bool PingHost(string host, int port, int timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
var result = client.BeginConnect(host, port, null, null);
|
||||
result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout));
|
||||
return client.Connected;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1b623084ee8eb4b9199e34858add3cac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,30 @@
|
|||
using System.IO;
|
||||
using UnityEditor.Build.CacheServer;
|
||||
|
||||
namespace UnityEditor.CacheServerTests
|
||||
{
|
||||
internal class TestDownloadItem : IDownloadItem
|
||||
{
|
||||
private ByteArrayStream m_writeStream;
|
||||
|
||||
public FileId Id { get; private set; }
|
||||
public FileType Type { get; private set; }
|
||||
|
||||
public void Finish() {}
|
||||
public byte[] Bytes
|
||||
{
|
||||
get { return m_writeStream.BackingBuffer; }
|
||||
}
|
||||
|
||||
public Stream GetWriteStream(long size)
|
||||
{
|
||||
return m_writeStream ?? (m_writeStream = new ByteArrayStream(size));
|
||||
}
|
||||
|
||||
public TestDownloadItem(FileId fileId, FileType fileType)
|
||||
{
|
||||
Id = fileId;
|
||||
Type = fileType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b734b613791eb4b0381013f84745a995
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,460 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Build.CacheServer;
|
||||
using UnityEngine;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace UnityEditor.CacheServerTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class Tests
|
||||
{
|
||||
private const string KTestHost = "127.0.0.1";
|
||||
private const string KInvalidTestHost = "192.0.2.1";
|
||||
private Random rand;
|
||||
|
||||
private static int TestPort
|
||||
{
|
||||
get { return LocalCacheServer.Port; }
|
||||
}
|
||||
|
||||
private FileId GenerateFileId()
|
||||
{
|
||||
if (rand == null)
|
||||
rand = new Random();
|
||||
var guid = new byte[16];
|
||||
var hash = new byte[16];
|
||||
rand.NextBytes(guid);
|
||||
rand.NextBytes(hash);
|
||||
return FileId.From(guid, hash);
|
||||
}
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void BeforeAll()
|
||||
{
|
||||
var cachePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
LocalCacheServer.Setup(1024 * 1024, cachePath);
|
||||
rand = new Random();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void AfterAll()
|
||||
{
|
||||
LocalCacheServer.Clear();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FileDownloadItem()
|
||||
{
|
||||
var fileId = GenerateFileId();
|
||||
var readStream = new ByteArrayStream(128 * 1024);
|
||||
|
||||
var client = new Client(KTestHost, TestPort);
|
||||
client.Connect();
|
||||
|
||||
client.BeginTransaction(fileId);
|
||||
client.Upload(FileType.Asset, readStream);
|
||||
client.EndTransaction();
|
||||
Thread.Sleep(50); // give the server a little time to finish the transaction
|
||||
|
||||
var targetFile = Path.GetTempFileName();
|
||||
var downloadItem = new FileDownloadItem(fileId, FileType.Asset, targetFile);
|
||||
|
||||
var mre = new ManualResetEvent(false);
|
||||
Exception err = null;
|
||||
client.DownloadFinished += (sender, args) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.AreEqual(DownloadResult.Success, args.Result);
|
||||
Assert.AreEqual(args.DownloadItem.Id, fileId);
|
||||
Assert.IsTrue(File.Exists(targetFile));
|
||||
|
||||
var fileBytes = File.ReadAllBytes(targetFile);
|
||||
Assert.IsTrue(Util.ByteArraysAreEqual(readStream.BackingBuffer, fileBytes));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(targetFile))
|
||||
File.Delete(targetFile);
|
||||
|
||||
mre.Set();
|
||||
}
|
||||
};
|
||||
|
||||
client.QueueDownload(downloadItem);
|
||||
Assert.IsTrue(mre.WaitOne(2000));
|
||||
|
||||
if (err != null)
|
||||
throw err;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void QueueDownloadFromDownloadFinishedCallback()
|
||||
{
|
||||
var fileId = GenerateFileId();
|
||||
var readStream = new ByteArrayStream(128 * 1024);
|
||||
|
||||
var client = new Client(KTestHost, TestPort);
|
||||
client.Connect();
|
||||
|
||||
client.BeginTransaction(fileId);
|
||||
client.Upload(FileType.Asset, readStream);
|
||||
client.EndTransaction();
|
||||
Thread.Sleep(50); // give the server a little time to finish the transaction
|
||||
|
||||
var targetFile1 = Path.GetTempFileName();
|
||||
var downloadItem1 = new FileDownloadItem(fileId, FileType.Asset, targetFile1);
|
||||
|
||||
var targetFile2 = Path.GetTempFileName();
|
||||
var downloadItem2 = new FileDownloadItem(fileId, FileType.Asset, targetFile2);
|
||||
|
||||
var mre = new ManualResetEvent(false);
|
||||
Exception err = null;
|
||||
client.DownloadFinished += (sender1, args1) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.AreEqual(DownloadResult.Success, args1.Result);
|
||||
Assert.AreEqual(args1.DownloadItem.Id, fileId);
|
||||
Assert.IsTrue(File.Exists(targetFile1));
|
||||
|
||||
var fileBytes = File.ReadAllBytes(targetFile1);
|
||||
Assert.IsTrue(Util.ByteArraysAreEqual(readStream.BackingBuffer, fileBytes));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(targetFile1))
|
||||
File.Delete(targetFile1);
|
||||
|
||||
client.ResetDownloadFinishedEventHandler();
|
||||
client.DownloadFinished += (sender2, args2) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.AreEqual(DownloadResult.Success, args2.Result);
|
||||
Assert.AreEqual(args2.DownloadItem.Id, fileId);
|
||||
Assert.IsTrue(File.Exists(targetFile2));
|
||||
|
||||
var fileBytes = File.ReadAllBytes(targetFile2);
|
||||
Assert.IsTrue(Util.ByteArraysAreEqual(readStream.BackingBuffer, fileBytes));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(targetFile2))
|
||||
File.Delete(targetFile2);
|
||||
|
||||
mre.Set();
|
||||
}
|
||||
};
|
||||
client.QueueDownload(downloadItem2);
|
||||
}
|
||||
};
|
||||
|
||||
client.QueueDownload(downloadItem1);
|
||||
Assert.IsTrue(mre.WaitOne(2000));
|
||||
|
||||
if (err != null)
|
||||
throw err;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Connect()
|
||||
{
|
||||
var client = new Client(KTestHost, TestPort);
|
||||
client.Connect();
|
||||
client.Close();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConnectTimeout()
|
||||
{
|
||||
var client = new Client(KInvalidTestHost, TestPort);
|
||||
TimeoutException err = null;
|
||||
try
|
||||
{
|
||||
client.Connect(0);
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
err = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.Close();
|
||||
Debug.Assert(err != null);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TransactionIsolation()
|
||||
{
|
||||
var fileId = GenerateFileId();
|
||||
var readStream = new ByteArrayStream(16 * 1024);
|
||||
|
||||
var client = new Client(KTestHost, TestPort);
|
||||
client.Connect();
|
||||
|
||||
Assert.Throws<TransactionIsolationException>(() => client.Upload(FileType.Asset, readStream));
|
||||
Assert.Throws<TransactionIsolationException>(() => client.EndTransaction());
|
||||
|
||||
// Back-to-back begin transactions are allowed
|
||||
client.BeginTransaction(fileId);
|
||||
Assert.DoesNotThrow(() => client.BeginTransaction(fileId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UploadDownloadOne()
|
||||
{
|
||||
var fileId = GenerateFileId();
|
||||
var readStream = new ByteArrayStream(16 * 1024);
|
||||
|
||||
var client = new Client(KTestHost, TestPort);
|
||||
client.Connect();
|
||||
|
||||
client.BeginTransaction(fileId);
|
||||
client.Upload(FileType.Asset, readStream);
|
||||
client.EndTransaction();
|
||||
Thread.Sleep(50); // give the server a little time to finish the transaction
|
||||
|
||||
var downloadItem = new TestDownloadItem(fileId, FileType.Asset);
|
||||
|
||||
client.QueueDownload(downloadItem);
|
||||
|
||||
Exception err = null;
|
||||
var mre = new ManualResetEvent(false);
|
||||
client.DownloadFinished += (sender, args) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.AreEqual(0, args.DownloadQueueLength);
|
||||
Assert.AreEqual(DownloadResult.Success, args.Result);
|
||||
Assert.AreEqual(fileId, args.DownloadItem.Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
mre.Set();
|
||||
}
|
||||
};
|
||||
|
||||
Assert.IsTrue(mre.WaitOne(2000));
|
||||
|
||||
if (err != null)
|
||||
throw err;
|
||||
|
||||
Assert.IsTrue(Util.ByteArraysAreEqual(readStream.BackingBuffer, downloadItem.Bytes));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DownloadMany()
|
||||
{
|
||||
const int fileCount = 5;
|
||||
|
||||
var fileIds = new FileId[fileCount];
|
||||
var fileStreams = new ByteArrayStream[fileCount];
|
||||
|
||||
var client = new Client(KTestHost, TestPort);
|
||||
client.Connect();
|
||||
|
||||
// Upload files
|
||||
var rand = new Random();
|
||||
for (var i = 0; i < fileCount; i++)
|
||||
{
|
||||
fileIds[i] = GenerateFileId();
|
||||
fileStreams[i] = new ByteArrayStream(rand.Next(64 * 1024, 128 * 1024));
|
||||
|
||||
client.BeginTransaction(fileIds[i]);
|
||||
client.Upload(FileType.Asset, fileStreams[i]);
|
||||
client.EndTransaction();
|
||||
}
|
||||
|
||||
Thread.Sleep(50);
|
||||
|
||||
// Download
|
||||
var receivedCount = 0;
|
||||
Exception err = null;
|
||||
var mre = new ManualResetEvent(false);
|
||||
client.DownloadFinished += (sender, args) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.AreEqual(args.Result, DownloadResult.Success);
|
||||
Assert.AreEqual(args.DownloadItem.Id, fileIds[receivedCount]);
|
||||
|
||||
var downloadItem = (TestDownloadItem)args.DownloadItem;
|
||||
Assert.IsTrue(Util.ByteArraysAreEqual(fileStreams[receivedCount].BackingBuffer, downloadItem.Bytes));
|
||||
|
||||
receivedCount++;
|
||||
Assert.AreEqual(fileCount - receivedCount, args.DownloadQueueLength);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (err != null || receivedCount == fileCount)
|
||||
mre.Set();
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < fileCount; i++)
|
||||
client.QueueDownload(new TestDownloadItem(fileIds[i], FileType.Asset));
|
||||
|
||||
Assert.AreEqual(fileCount, client.DownloadQueueLength);
|
||||
|
||||
Assert.IsTrue(mre.WaitOne(2000));
|
||||
|
||||
if (err != null)
|
||||
throw err;
|
||||
|
||||
Assert.AreEqual(fileCount, receivedCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DonwloadFileNotFound()
|
||||
{
|
||||
var client = new Client(KTestHost, TestPort);
|
||||
client.Connect();
|
||||
|
||||
var fileId = FileId.From(new byte[16], new byte[16]);
|
||||
|
||||
var mre = new ManualResetEvent(false);
|
||||
var downloadItem = new TestDownloadItem(fileId, FileType.Asset);
|
||||
|
||||
client.QueueDownload(downloadItem);
|
||||
|
||||
Exception err = null;
|
||||
|
||||
client.DownloadFinished += (sender, args) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.AreEqual(args.Result, DownloadResult.FileNotFound);
|
||||
Assert.AreEqual(args.DownloadItem.Id, fileId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
mre.Set();
|
||||
}
|
||||
};
|
||||
|
||||
mre.WaitOne(500);
|
||||
|
||||
if (err != null)
|
||||
throw err;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResetDownloadFinishedEventHandler()
|
||||
{
|
||||
var fileId = GenerateFileId();
|
||||
var readStream = new ByteArrayStream(16 * 1024);
|
||||
|
||||
var client = new Client(KTestHost, TestPort);
|
||||
client.Connect();
|
||||
|
||||
client.BeginTransaction(fileId);
|
||||
client.Upload(FileType.Asset, readStream);
|
||||
client.EndTransaction();
|
||||
Thread.Sleep(50);
|
||||
|
||||
var downloadItem = new TestDownloadItem(fileId, FileType.Asset);
|
||||
|
||||
// Add two listeners that will assert if called
|
||||
client.DownloadFinished += (sender, args) => { Debug.Assert(false); };
|
||||
client.DownloadFinished += (sender, args) => { Debug.Assert(false); };
|
||||
|
||||
// Clear the listeners so they will not be called
|
||||
client.ResetDownloadFinishedEventHandler();
|
||||
|
||||
client.QueueDownload(downloadItem);
|
||||
|
||||
var mre = new ManualResetEvent(false);
|
||||
ThreadPool.QueueUserWorkItem(state =>
|
||||
{
|
||||
while (client.DownloadQueueLength > 0)
|
||||
Thread.Sleep(0);
|
||||
|
||||
mre.Set();
|
||||
});
|
||||
|
||||
Assert.IsTrue(mre.WaitOne(2000));
|
||||
}
|
||||
|
||||
static void WriteBytesToStream(Stream stream, byte[] buffer, int offset, int count)
|
||||
{
|
||||
// Write to stream, and reset position for read
|
||||
stream.Write(buffer, offset, count);
|
||||
stream.Position -= count;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadHeader_AppendsDataToBuffer()
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
var client = new Client(KTestHost, TestPort);
|
||||
client.m_stream = stream;
|
||||
|
||||
Exception err = null;
|
||||
var resetEvent = new ManualResetEvent(false);
|
||||
var testBuffer = new byte[] { (byte)'-', (byte)'0' };
|
||||
|
||||
WriteBytesToStream(stream, testBuffer, (int)stream.Length, 1);
|
||||
|
||||
// Setup checks / callback
|
||||
client.OnReadHeader += (bytesRead, readBuffer) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate written and read bytes match
|
||||
Assert.AreEqual((int)stream.Length, bytesRead);
|
||||
// Validate buffer contents are correct up to the current written bytes
|
||||
for (int i = 0; i < (int)stream.Length; i++)
|
||||
Assert.AreEqual(testBuffer[i], readBuffer[i]);
|
||||
|
||||
if ((int)stream.Length < testBuffer.Length)
|
||||
WriteBytesToStream(stream, testBuffer, (int)stream.Length, 1);
|
||||
else
|
||||
resetEvent.Set();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
err = e;
|
||||
resetEvent.Set();
|
||||
}
|
||||
};
|
||||
|
||||
// Run test
|
||||
client.ReadNextDownloadResult();
|
||||
Assert.IsTrue(resetEvent.WaitOne(2000));
|
||||
|
||||
if (err != null)
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 012e8bc53e96d4c638db9f5b04ef84de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "UnityEditor.CacheServerTests",
|
||||
"references": [
|
||||
"UnityEditor.CacheServer"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de0adf19d0a2a46a19acaa82fcb5a988
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace UnityEditor.CacheServerTests
|
||||
{
|
||||
public static class Utils
|
||||
{
|
||||
public static class Paths
|
||||
{
|
||||
public static string Combine(params string[] components)
|
||||
{
|
||||
if (components.Length < 1)
|
||||
throw new ArgumentException("At least one component must be provided!");
|
||||
|
||||
var path1 = components[0];
|
||||
|
||||
for (var index = 1; index < components.Length; ++index)
|
||||
path1 = Path.Combine(path1, components[index]);
|
||||
|
||||
return path1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 493e67a6e86284a8681aab6f10d05c34
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,33 @@
|
|||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
// Test suite against Build Cache with Cache Server backend, tests are implemented in BuildCacheTestBase
|
||||
//[Ignore("Disabled due to instability")]
|
||||
//class CacheServerBuildCacheTests : BuildCacheTestBase
|
||||
//{
|
||||
// internal override void OneTimeSetupDerived()
|
||||
// {
|
||||
// var cachePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
// LocalCacheServer.Setup(1024 * 1024, cachePath);
|
||||
// }
|
||||
|
||||
// internal override void OneTimeTearDownDerived()
|
||||
// {
|
||||
// LocalCacheServer.Clear();
|
||||
// }
|
||||
|
||||
// protected override void RecreateBuildCache()
|
||||
// {
|
||||
// // purge the local cache to make sure we don't load anything out of it again.
|
||||
// // these tests need to pull from the cache server
|
||||
// BuildCacheUtility.ClearCacheHashes();
|
||||
// if (m_Cache != null)
|
||||
// m_Cache.Dispose();
|
||||
// PurgeBuildCache();
|
||||
// m_Cache = new BuildCache("localhost", LocalCacheServer.instance.m_port);
|
||||
// }
|
||||
//}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cde708facb1abd24cb7b42fedf436309
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,659 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline;
|
||||
using UnityEditor.Build.Pipeline.Injector;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEditor.Build.Player;
|
||||
using UnityEditor.U2D;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
public class CalculateAssetDependencyTests
|
||||
{
|
||||
const string kTestAssetFolder = "Assets/TestAssets";
|
||||
const string kTestAsset = "Assets/TestAssets/SpriteTexture32x32.png";
|
||||
const string kSpriteTexture1Asset = "Assets/TestAssets/SpriteTexture1_32x32.png";
|
||||
const string kSpriteTexture2Asset = "Assets/TestAssets/SpriteTexture2_32x32.png";
|
||||
const string kSpriteAtlasAsset = "Assets/TestAssets/sadependencies.spriteAtlas";
|
||||
|
||||
|
||||
SpritePackerMode m_PrevMode;
|
||||
|
||||
class TestParams : TestBundleBuildParameters
|
||||
{
|
||||
bool m_DisableVisibleSubAssetRepresentations = false;
|
||||
public TestParams(bool disableVisibleSubAssetRepresentations)
|
||||
{
|
||||
m_DisableVisibleSubAssetRepresentations = disableVisibleSubAssetRepresentations;
|
||||
}
|
||||
|
||||
public override bool DisableVisibleSubAssetRepresentations { get => m_DisableVisibleSubAssetRepresentations; }
|
||||
|
||||
// Inputs
|
||||
public override bool UseCache { get; set; }
|
||||
public override BuildTarget Target { get => EditorUserBuildSettings.activeBuildTarget; }
|
||||
public override BuildTargetGroup Group { get => BuildTargetGroup.Unknown; }
|
||||
public override TypeDB ScriptInfo { get => null; }
|
||||
public override ContentBuildFlags ContentBuildFlags { get => ContentBuildFlags.None; }
|
||||
public override bool NonRecursiveDependencies { get => false; }
|
||||
|
||||
#if !UNITY_2019_3_OR_NEWER
|
||||
public override string TempOutputFolder => ContentPipeline.kTempBuildPath;
|
||||
#endif
|
||||
|
||||
public override BuildSettings GetContentBuildSettings()
|
||||
{
|
||||
return new BuildSettings
|
||||
{
|
||||
group = Group,
|
||||
target = Target,
|
||||
typeDB = ScriptInfo,
|
||||
buildFlags = ContentBuildFlags
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TestContent : TestBundleBuildContent
|
||||
{
|
||||
public List<GUID> TestAssets = new List<GUID>();
|
||||
|
||||
// Inputs
|
||||
public override List<GUID> Assets => TestAssets;
|
||||
}
|
||||
|
||||
class TestDependencyData : TestDependencyDataBase
|
||||
{
|
||||
public Dictionary<GUID, AssetLoadInfo> TestAssetInfo = new Dictionary<GUID, AssetLoadInfo>();
|
||||
public Dictionary<GUID, BuildUsageTagSet> TestAssetUsage = new Dictionary<GUID, BuildUsageTagSet>();
|
||||
public Dictionary<GUID, SceneDependencyInfo> TestSceneInfo = new Dictionary<GUID, SceneDependencyInfo>();
|
||||
public Dictionary<GUID, BuildUsageTagSet> TestSceneUsage = new Dictionary<GUID, BuildUsageTagSet>();
|
||||
public Dictionary<GUID, Hash128> TestDependencyHash = new Dictionary<GUID, Hash128>();
|
||||
|
||||
// Inputs
|
||||
public override BuildUsageCache DependencyUsageCache => null;
|
||||
public override BuildUsageTagGlobal GlobalUsage => new BuildUsageTagGlobal();
|
||||
|
||||
// Outputs
|
||||
public override Dictionary<GUID, AssetLoadInfo> AssetInfo => TestAssetInfo;
|
||||
public override Dictionary<GUID, BuildUsageTagSet> AssetUsage => TestAssetUsage;
|
||||
public override Dictionary<GUID, SceneDependencyInfo> SceneInfo => TestSceneInfo;
|
||||
public override Dictionary<GUID, BuildUsageTagSet> SceneUsage => TestSceneUsage;
|
||||
public override Dictionary<GUID, Hash128> DependencyHash => TestDependencyHash;
|
||||
}
|
||||
|
||||
class TestExtendedAssetData : TestBundleExtendedAssetData
|
||||
{
|
||||
public Dictionary<GUID, ExtendedAssetData> TestExtendedData = new Dictionary<GUID, ExtendedAssetData>();
|
||||
public override Dictionary<GUID, ExtendedAssetData> ExtendedData => TestExtendedData;
|
||||
}
|
||||
|
||||
static CalculateAssetDependencyData CreateDefaultBuildTask(List<GUID> assets, bool disableVisibleSubassetRepresentations = false)
|
||||
{
|
||||
var task = new CalculateAssetDependencyData();
|
||||
var testParams = new TestParams(disableVisibleSubassetRepresentations);
|
||||
var testContent = new TestContent { TestAssets = assets };
|
||||
var testDepData = new TestDependencyData();
|
||||
var testExtendedData = new TestExtendedAssetData();
|
||||
var testObjDependencyData = new ObjectDependencyData();
|
||||
|
||||
IBuildContext context = new BuildContext(testParams, testContent, testDepData, testExtendedData, testObjDependencyData);
|
||||
ContextInjector.Inject(context, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
static void ExtractTestData(IBuildTask task, out TestExtendedAssetData extendedAssetData)
|
||||
{
|
||||
IBuildContext context = new BuildContext();
|
||||
ContextInjector.Extract(context, task);
|
||||
extendedAssetData = (TestExtendedAssetData)context.GetContextObject<IBuildExtendedAssetData>();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
m_PrevMode = EditorSettings.spritePackerMode;
|
||||
Directory.CreateDirectory(kTestAssetFolder);
|
||||
CreateTestSpriteTexture(kTestAsset, false);
|
||||
CreateTestSpriteTexture(kSpriteTexture1Asset, true);
|
||||
CreateTestSpriteTexture(kSpriteTexture2Asset, true);
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate);
|
||||
CreateSpriteAtlas();
|
||||
|
||||
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void OneTimeTeardown()
|
||||
{
|
||||
AssetDatabase.DeleteAsset(kTestAssetFolder);
|
||||
File.Delete(kTestAssetFolder + ".meta");
|
||||
EditorSettings.spritePackerMode = m_PrevMode;
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
static void CreateTestSpriteTexture(string texturePath, bool single)
|
||||
{
|
||||
var data = ImageConversion.EncodeToPNG(new Texture2D(32, 32));
|
||||
File.WriteAllBytes(texturePath, data);
|
||||
AssetDatabase.ImportAsset(texturePath);//, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate);
|
||||
var importer = AssetImporter.GetAtPath(texturePath) as TextureImporter;
|
||||
importer.textureType = TextureImporterType.Sprite;
|
||||
if (single)
|
||||
{
|
||||
importer.spriteImportMode = SpriteImportMode.Single;
|
||||
}
|
||||
else
|
||||
{
|
||||
importer.spriteImportMode = SpriteImportMode.Multiple;
|
||||
importer.spritesheet = new[]
|
||||
{
|
||||
new SpriteMetaData
|
||||
{
|
||||
name = "WhiteTexture32x32_0",
|
||||
rect = new Rect(0, 19, 32, 13),
|
||||
alignment = 0,
|
||||
pivot = new Vector2(0.5f, 0.5f),
|
||||
border = new Vector4(0, 0, 0, 0)
|
||||
},
|
||||
new SpriteMetaData
|
||||
{
|
||||
name = "WhiteTexture32x32_1",
|
||||
rect = new Rect(4, 19, 24, 11),
|
||||
alignment = 0,
|
||||
pivot = new Vector2(0.5f, 0.5f),
|
||||
border = new Vector4(0, 0, 0, 0)
|
||||
},
|
||||
new SpriteMetaData
|
||||
{
|
||||
name = "WhiteTexture32x32_2",
|
||||
rect = new Rect(9, 5, 12, 7),
|
||||
alignment = 0,
|
||||
pivot = new Vector2(0.5f, 0.5f),
|
||||
border = new Vector4(0, 0, 0, 0)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
importer.SaveAndReimport();
|
||||
}
|
||||
|
||||
static void CreateSpriteAtlas()
|
||||
{
|
||||
var sa = new SpriteAtlas();
|
||||
var targetObjects = new UnityEngine.Object[] { AssetDatabase.LoadAssetAtPath<Texture>(kSpriteTexture1Asset), AssetDatabase.LoadAssetAtPath<Texture>(kSpriteTexture2Asset) };
|
||||
sa.Add(targetObjects);
|
||||
AssetDatabase.CreateAsset(sa, kSpriteAtlasAsset);
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate);
|
||||
}
|
||||
|
||||
CalculateAssetDependencyData.TaskInput CreateDefaultInput()
|
||||
{
|
||||
CalculateAssetDependencyData.TaskInput input = new CalculateAssetDependencyData.TaskInput();
|
||||
input.Target = EditorUserBuildSettings.activeBuildTarget;
|
||||
return input;
|
||||
}
|
||||
|
||||
// Create a prefab and writes it to the specified path. The target file will have 2 objects in it: the GameObject and the Transform
|
||||
GUID CreateGameObject(string assetPath, string name = "go")
|
||||
{
|
||||
GameObject go = new GameObject(name);
|
||||
PrefabUtility.SaveAsPrefabAsset(go, assetPath);
|
||||
UnityEngine.Object.DestroyImmediate(go, false);
|
||||
string guidString = AssetDatabase.AssetPathToGUID(assetPath);
|
||||
GUID.TryParse(guidString, out GUID guid);
|
||||
return guid;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenAssetHasNoDependencies()
|
||||
{
|
||||
// Create an asset
|
||||
string assetPath = Path.Combine(kTestAssetFolder, "myPrefab.prefab");
|
||||
GUID guid = CreateGameObject(assetPath);
|
||||
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
input.Assets = new List<GUID>() { guid };
|
||||
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
|
||||
Assert.AreEqual(1, output.AssetResults.Length);
|
||||
Assert.AreEqual(guid, output.AssetResults[0].asset);
|
||||
Assert.AreEqual(2, output.AssetResults[0].assetInfo.includedObjects.Count); // GameObject and Transform
|
||||
Assert.AreEqual(2, output.AssetResults[0].objectDependencyInfo.Count);
|
||||
Assert.AreEqual(0, output.AssetResults[0].assetInfo.referencedObjects.Count);
|
||||
Assert.IsNull(output.AssetResults[0].spriteData);
|
||||
Assert.IsNull(output.AssetResults[0].extendedData);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenAssetDoesNotExist_AssetResultIsEmpty()
|
||||
{
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
input.Assets = new List<GUID>() { GUID.Generate() };
|
||||
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
|
||||
Assert.IsNull(output.AssetResults[0].extendedData);
|
||||
Assert.AreEqual(0, output.AssetResults[0].assetInfo.includedObjects.Count);
|
||||
Assert.AreEqual(0, output.AssetResults[0].objectDependencyInfo.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenSomeAssetDataIsCached_CachedVersionIsUsed()
|
||||
{
|
||||
const int kCachedCount = 5;
|
||||
// Create 10 assets, import half of them,
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
string assetPath = Path.Combine(kTestAssetFolder, "myPrefab.prefab");
|
||||
List<GUID> allGUIDs = new List<GUID>();
|
||||
List<GUID> cachedGUIDs = new List<GUID>();
|
||||
for (int i = 0; i < kCachedCount; i++)
|
||||
{
|
||||
GUID cachedGUID = CreateGameObject(Path.Combine(kTestAssetFolder, $"myPrefab{i * 2}.prefab"), $"go{i * 2}");
|
||||
cachedGUIDs.Add(cachedGUID);
|
||||
allGUIDs.Add(cachedGUID);
|
||||
allGUIDs.Add(CreateGameObject(Path.Combine(kTestAssetFolder, $"myPrefab{i * 2 + 1}.prefab"), $"go{i * 2 + 1}"));
|
||||
}
|
||||
|
||||
using (BuildCache cache = new BuildCache())
|
||||
{
|
||||
cache.ClearCacheEntryMaps(); // needed if running multiple times
|
||||
input.BuildCache = cache;
|
||||
input.Assets = cachedGUIDs;
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
cache.SyncPendingSaves();
|
||||
input.Assets = allGUIDs;
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output2);
|
||||
|
||||
Assert.AreEqual(output.AssetResults[0].assetInfo.includedObjects.Count, output2.AssetResults[0].assetInfo.includedObjects.Count); // GameObject and Transform
|
||||
Assert.AreEqual(output.AssetResults[0].objectDependencyInfo.Count, output2.AssetResults[0].objectDependencyInfo.Count);
|
||||
Assert.AreEqual(0, output.CachedAssetCount);
|
||||
Assert.AreEqual(kCachedCount, output2.CachedAssetCount);
|
||||
|
||||
for (int i = 0; i < kCachedCount; i++)
|
||||
{
|
||||
bool seqEqual = Enumerable.SequenceEqual(output.AssetResults[i].assetInfo.includedObjects, output2.AssetResults[i * 2].assetInfo.includedObjects);
|
||||
Assert.IsTrue(seqEqual, "Included Objects where not the same when Cache was used.");
|
||||
|
||||
Assert.AreEqual(output.AssetResults[i].objectDependencyInfo.Count, output2.AssetResults[i * 2].objectDependencyInfo.Count);
|
||||
for (int o = 0; o < output.AssetResults[i].objectDependencyInfo.Count; ++o)
|
||||
{
|
||||
var left = output.AssetResults[i].objectDependencyInfo[o];
|
||||
bool found = false;
|
||||
foreach (ObjectDependencyInfo right in output2.AssetResults[i * 2].objectDependencyInfo)
|
||||
{
|
||||
if (right.Object == left.Object)
|
||||
{
|
||||
Assert.AreEqual(right.Dependencies.Count, left.Dependencies.Count, "Object dependency found between builds, but the dependencies are different count");
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.IsTrue(found, "Could not find matching object dependency between builds");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Embedding this shader in code and only creating it when the test actually runs so it doesn't exist outside tests.
|
||||
string kTestShader = @"Shader ""Custom / NewSurfaceShader""
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_Color(""Color"", Color) = (1, 1, 1, 1)
|
||||
_MainTex(""Albedo (RGB)"", 2D) = ""white"" {
|
||||
}
|
||||
_Glossiness(""Smoothness"", Range(0, 1)) = 0.5
|
||||
_Metallic(""Metallic"", Range(0, 1)) = 0.0
|
||||
}
|
||||
SubShader
|
||||
{
|
||||
Tags { ""RenderType"" = ""Opaque"" }
|
||||
LOD 200
|
||||
|
||||
CGPROGRAM
|
||||
#pragma surface surf Standard fullforwardshadows
|
||||
#pragma target 3.0
|
||||
|
||||
#pragma shader_feature _ TEST_DEFINE
|
||||
|
||||
sampler2D _MainTex;
|
||||
|
||||
struct Input
|
||||
{
|
||||
float2 uv_MainTex;
|
||||
};
|
||||
|
||||
half _Glossiness;
|
||||
half _Metallic;
|
||||
fixed4 _Color;
|
||||
|
||||
UNITY_INSTANCING_BUFFER_START(Props)
|
||||
UNITY_INSTANCING_BUFFER_END(Props)
|
||||
|
||||
void surf(Input IN, inout SurfaceOutputStandard o)
|
||||
{
|
||||
// Albedo comes from a texture tinted by color
|
||||
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
|
||||
o.Albedo = c.rgb;
|
||||
// Metallic and smoothness come from slider variables
|
||||
o.Metallic = _Metallic;
|
||||
o.Smoothness = _Glossiness;
|
||||
o.Alpha = c.a;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
FallBack ""Diffuse""
|
||||
}
|
||||
";
|
||||
|
||||
|
||||
private void CreateTestShader(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
File.WriteAllText(path, kTestShader);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenObjectInfluencesReferencedObjectBuildTags_BuildUsageTagsAreAdded()
|
||||
{
|
||||
string testShaderPath = Path.Combine(kTestAssetFolder, "TestShader.shader");
|
||||
CreateTestShader(testShaderPath);
|
||||
Shader shader = AssetDatabase.LoadAssetAtPath<Shader>(testShaderPath);
|
||||
string shaderGUIDString = AssetDatabase.AssetPathToGUID(testShaderPath);
|
||||
GUID.TryParse(shaderGUIDString, out GUID shaderGUID);
|
||||
|
||||
// Create a material that points to the test shader asset
|
||||
Material mat = new Material(shader);
|
||||
string matPath = Path.Combine(kTestAssetFolder, "testmat.mat");
|
||||
AssetDatabase.CreateAsset(mat, matPath);
|
||||
string guidMatString = AssetDatabase.AssetPathToGUID(matPath);
|
||||
GUID.TryParse(guidMatString, out GUID matGUID);
|
||||
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
input.Assets = new List<GUID>() { matGUID };
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
|
||||
// this define should get added to the shader build usage tags
|
||||
mat.shaderKeywords = new string[] { "TEST_DEFINE" };
|
||||
|
||||
CalculateAssetDependencyData.TaskInput input2 = CreateDefaultInput();
|
||||
input2.Assets = new List<GUID>() { matGUID };
|
||||
CalculateAssetDependencyData.RunInternal(input2, out CalculateAssetDependencyData.TaskOutput output2);
|
||||
|
||||
var ids = output2.AssetResults[0].usageTags.GetObjectIdentifiers();
|
||||
Assert.IsTrue(ids.Count((x) => x.guid == shaderGUID) == 1, "Shader is not in build usage tags");
|
||||
Assert.AreNotEqual(output.AssetResults[0].usageTags.GetHashCode(), output2.AssetResults[0].usageTags.GetHashCode(), "Build usage tags were not affected by material keywords");
|
||||
}
|
||||
|
||||
static object[] SpriteTestCases =
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
new object[] { SpritePackerMode.Disabled, "", false, false },
|
||||
new object[] { SpritePackerMode.BuildTimeOnlyAtlas, "", true, true },
|
||||
new object[] { SpritePackerMode.BuildTimeOnlyAtlas, "", false, false },
|
||||
#else
|
||||
new object[] { SpritePackerMode.BuildTimeOnly, "SomeTag", true, true },
|
||||
new object[] { SpritePackerMode.BuildTimeOnly, "", true, false },
|
||||
new object[] { SpritePackerMode.AlwaysOn, "SomeTag", true, true },
|
||||
new object[] { SpritePackerMode.AlwaysOn, "", true, false },
|
||||
new object[] { SpritePackerMode.Disabled, "", true, false },
|
||||
new object[] { SpritePackerMode.BuildTimeOnlyAtlas, "", true, true },
|
||||
new object[] { SpritePackerMode.AlwaysOnAtlas, "", true, true },
|
||||
new object[] { SpritePackerMode.AlwaysOnAtlas, "", false, false }
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
[TestCaseSource("SpriteTestCases")]
|
||||
[Test]
|
||||
public void WhenSpriteWithAtlas_SpriteImportDataCreated(SpritePackerMode spriteMode, string spritePackingTag, bool hasReferencingSpriteAtlas, bool expectedPacked)
|
||||
{
|
||||
TextureImporter importer = AssetImporter.GetAtPath(kTestAsset) as TextureImporter;
|
||||
importer.spritePackingTag = spritePackingTag;
|
||||
importer.SaveAndReimport();
|
||||
|
||||
if (hasReferencingSpriteAtlas)
|
||||
{
|
||||
var sa = new SpriteAtlas();
|
||||
var targetObjects = new UnityEngine.Object[] { AssetDatabase.LoadAssetAtPath<Texture>(kTestAsset) };
|
||||
sa.Add(targetObjects);
|
||||
string saPath = Path.Combine(kTestAssetFolder, "sa.spriteAtlas");
|
||||
AssetDatabase.CreateAsset(sa, saPath);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
GUID.TryParse(AssetDatabase.AssetPathToGUID(kTestAsset), out GUID spriteGUID);
|
||||
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
EditorSettings.spritePackerMode = spriteMode;
|
||||
SpriteAtlasUtility.PackAllAtlases(input.Target);
|
||||
input.Assets = new List<GUID>() { spriteGUID };
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
|
||||
Assert.AreEqual(expectedPacked, output.AssetResults[0].spriteData.PackedSprite);
|
||||
}
|
||||
|
||||
|
||||
static object[] SpriteUtilityTestCases =
|
||||
{
|
||||
new object[] { SpritePackerMode.BuildTimeOnlyAtlas }
|
||||
};
|
||||
|
||||
|
||||
[TestCaseSource("SpriteUtilityTestCases")]
|
||||
[Test]
|
||||
public void WhenSpriteWithAtlasUpdated_SpriteInfoUpdated(SpritePackerMode spriteMode)
|
||||
{
|
||||
var spriteAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(kSpriteAtlasAsset);
|
||||
var oldSprites = SpriteAtlasExtensions.GetPackables(spriteAtlas);
|
||||
spriteAtlas.Remove(oldSprites);
|
||||
|
||||
List<UnityEngine.Object> sprites = new List<UnityEngine.Object>();
|
||||
sprites.Add(AssetDatabase.LoadAssetAtPath<Sprite>(kSpriteTexture1Asset));
|
||||
sprites.Add(AssetDatabase.LoadAssetAtPath<Sprite>(kSpriteTexture2Asset));
|
||||
SpriteAtlasExtensions.Add(spriteAtlas, sprites.ToArray());
|
||||
|
||||
GUID.TryParse(AssetDatabase.AssetPathToGUID(kSpriteAtlasAsset), out GUID spriteGUID);
|
||||
|
||||
BuildCacheUtility.ClearCacheHashes();
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
input.BuildCache = new BuildCache();
|
||||
input.NonRecursiveDependencies = true;
|
||||
EditorSettings.spritePackerMode = spriteMode;
|
||||
SpriteAtlasUtility.PackAllAtlases(input.Target);
|
||||
input.Assets = new List<GUID>() { spriteGUID };
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
|
||||
Assert.AreEqual(3, output.AssetResults[0].assetInfo.referencedObjects.Count);
|
||||
|
||||
spriteAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(kSpriteAtlasAsset);
|
||||
oldSprites = SpriteAtlasExtensions.GetPackables(spriteAtlas);
|
||||
spriteAtlas.Remove(oldSprites);
|
||||
|
||||
sprites = new List<UnityEngine.Object>();
|
||||
sprites.Add(AssetDatabase.LoadAssetAtPath<Sprite>(kSpriteTexture1Asset));
|
||||
SpriteAtlasExtensions.Add(spriteAtlas, sprites.ToArray());
|
||||
SpriteAtlasUtility.PackAllAtlases(input.Target);
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output2);
|
||||
Assert.AreEqual(2, output2.AssetResults[0].assetInfo.referencedObjects.Count);
|
||||
}
|
||||
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
// This test is only important for going through AssetDatabase's LoadAllAssetRepresentationsAtPath
|
||||
// in 2020.2 and newer we have a new build api that handles nulls natively and this no longer applies.
|
||||
class NullLoadRepresentationFake : CalculateAssetDependencyHooks
|
||||
{
|
||||
public override UnityEngine.Object[] LoadAllAssetRepresentationsAtPath(string assetPath) { return new UnityEngine.Object[] { null }; }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenAssetHasANullRepresentation_LogsWarning()
|
||||
{
|
||||
// Create an asset
|
||||
string assetPath = Path.Combine(kTestAssetFolder, "myPrefab.prefab");
|
||||
GUID guid = CreateGameObject(assetPath);
|
||||
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
input.Assets = new List<GUID>() { guid };
|
||||
input.EngineHooks = new NullLoadRepresentationFake();
|
||||
|
||||
LogAssert.Expect(LogType.Warning, new Regex(".+It will not be included in the build"));
|
||||
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
|
||||
Assert.AreEqual(guid, output.AssetResults[0].asset);
|
||||
Assert.AreEqual(2, output.AssetResults[0].assetInfo.includedObjects.Count); // GameObject and Transform
|
||||
Assert.AreEqual(0, output.AssetResults[0].assetInfo.referencedObjects.Count);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[Test]
|
||||
public void WhenAssetHasMultipleRepresentations_ExtendedDataContainsAllButMainAsset()
|
||||
{
|
||||
const int kExtraRepresentations = 2;
|
||||
string assetPath = Path.Combine(kTestAssetFolder, "myPrefab.asset");
|
||||
Material mat = new Material(Shader.Find("Transparent/Diffuse"));
|
||||
AssetDatabase.CreateAsset(mat, assetPath);
|
||||
|
||||
for (int i = 0; i < kExtraRepresentations; i++)
|
||||
AssetDatabase.AddObjectToAsset(new Material(Shader.Find("Transparent/Diffuse")), assetPath);
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
GUID guid = new GUID(AssetDatabase.AssetPathToGUID(assetPath));
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
input.Assets = new List<GUID>() { guid };
|
||||
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
|
||||
ObjectIdentifier[] allObjIDs = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(guid, EditorUserBuildSettings.activeBuildTarget);
|
||||
HashSet<ObjectIdentifier> expectedReps = new HashSet<ObjectIdentifier>();
|
||||
for (int i = 1; i < allObjIDs.Length; i++)
|
||||
expectedReps.Add(allObjIDs[i]);
|
||||
|
||||
Assert.AreEqual(kExtraRepresentations, output.AssetResults[0].extendedData.Representations.Count);
|
||||
Assert.AreEqual(kExtraRepresentations, expectedReps.Count);
|
||||
foreach (var id in output.AssetResults[0].extendedData.Representations)
|
||||
Assert.IsTrue(expectedReps.Contains(id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenExplicitSpriteAndAtlas_AtlasOnlyReferencesSprites()
|
||||
{
|
||||
GUID spriteAtlasGuid = new GUID(AssetDatabase.AssetPathToGUID(kSpriteAtlasAsset));
|
||||
GUID sprite1Guid = new GUID(AssetDatabase.AssetPathToGUID(kSpriteTexture1Asset));
|
||||
GUID sprite2Guid = new GUID(AssetDatabase.AssetPathToGUID(kSpriteTexture2Asset));
|
||||
|
||||
EditorSettings.spritePackerMode = SpritePackerMode.BuildTimeOnlyAtlas;
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
SpriteAtlasUtility.PackAllAtlases(input.Target);
|
||||
input.Assets = new List<GUID>() { spriteAtlasGuid, sprite1Guid, sprite2Guid };
|
||||
|
||||
CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
Assert.AreEqual(3, output.AssetResults[0].assetInfo.referencedObjects.Count);
|
||||
}
|
||||
|
||||
class TestProgressTracker : IProgressTracker
|
||||
{
|
||||
int count = 0;
|
||||
public int TaskCount { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
public float Progress => throw new NotImplementedException();
|
||||
|
||||
public bool UpdateInfo(string taskInfo)
|
||||
{
|
||||
return count++ > 0;
|
||||
}
|
||||
|
||||
public bool UpdateTask(string taskTitle) { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenCanceledThroughProgressTracker_ReturnsCanceled()
|
||||
{
|
||||
string assetPath1 = Path.Combine(kTestAssetFolder, "myPrefab1.prefab");
|
||||
string assetPath2 = Path.Combine(kTestAssetFolder, "myPrefab2.prefab");
|
||||
|
||||
CalculateAssetDependencyData.TaskInput input = CreateDefaultInput();
|
||||
input.Assets = new List<GUID>() { CreateGameObject(assetPath1), CreateGameObject(assetPath2) };
|
||||
input.ProgressTracker = new TestProgressTracker();
|
||||
ReturnCode code = CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output);
|
||||
Assert.AreEqual(null, output.AssetResults[1].assetInfo);
|
||||
Assert.AreEqual(ReturnCode.Canceled, code);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TaskIsRun_WhenAssetHasNoMultipleRepresentations_ExtendedDataIsEmpty()
|
||||
{
|
||||
string assetPath = Path.Combine(kTestAssetFolder, "myPrefab.prefab");
|
||||
GUID guid = CreateGameObject(assetPath);
|
||||
|
||||
bool disableVisibleSubAssetRepresentations = false;
|
||||
CalculateAssetDependencyData buildTask = CreateDefaultBuildTask(new List<GUID>() { guid }, disableVisibleSubAssetRepresentations);
|
||||
buildTask.Run();
|
||||
ExtractTestData(buildTask, out TestExtendedAssetData extendedAssetData);
|
||||
|
||||
Assert.AreEqual(0, extendedAssetData.ExtendedData.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TaskIsRun_WhenAssetHasMultipleRepresentations_ExtendedDataContainsEntryForAsset()
|
||||
{
|
||||
string assetPath = Path.Combine(kTestAssetFolder, "myPrefab.asset");
|
||||
Material mat = new Material(Shader.Find("Transparent/Diffuse"));
|
||||
AssetDatabase.CreateAsset(mat, assetPath);
|
||||
AssetDatabase.AddObjectToAsset(new Material(Shader.Find("Transparent/Diffuse")), assetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
string guidString = AssetDatabase.AssetPathToGUID(assetPath);
|
||||
GUID.TryParse(guidString, out GUID guid);
|
||||
|
||||
bool disableVisibleSubAssetRepresentations = false;
|
||||
CalculateAssetDependencyData buildTask = CreateDefaultBuildTask(new List<GUID>() { guid }, disableVisibleSubAssetRepresentations);
|
||||
buildTask.Run();
|
||||
ExtractTestData(buildTask, out TestExtendedAssetData extendedAssetData);
|
||||
|
||||
Assert.AreEqual(1, extendedAssetData.ExtendedData.Count);
|
||||
Assert.IsTrue(extendedAssetData.ExtendedData.ContainsKey(guid));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TaskIsRun_WhenAssetHasMultipleRepresentations_AndDisableVisibleSubAssetRepresentations_ExtendedDataIsEmpty()
|
||||
{
|
||||
string assetPath = Path.Combine(kTestAssetFolder, "myPrefab.asset");
|
||||
Material mat = new Material(Shader.Find("Transparent/Diffuse"));
|
||||
AssetDatabase.CreateAsset(mat, assetPath);
|
||||
AssetDatabase.AddObjectToAsset(new Material(Shader.Find("Transparent/Diffuse")), assetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
string guidString = AssetDatabase.AssetPathToGUID(assetPath);
|
||||
GUID.TryParse(guidString, out GUID guid);
|
||||
|
||||
bool disableVisibleSubAssetRepresentations = true;
|
||||
CalculateAssetDependencyData buildTask = CreateDefaultBuildTask(new List<GUID>() { guid }, disableVisibleSubAssetRepresentations);
|
||||
buildTask.Run();
|
||||
ExtractTestData(buildTask, out TestExtendedAssetData extendedAssetData);
|
||||
|
||||
Assert.AreEqual(0, extendedAssetData.ExtendedData.Count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 155a61777399d7f48800d1791751daab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,214 @@
|
|||
#if UNITY_2019_3_OR_NEWER
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Injector;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEditor.Build.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
public class CalculateCustomDependencyTests
|
||||
{
|
||||
class TestBuildParameters : TestBuildParametersBase
|
||||
{
|
||||
// Optional Inputs
|
||||
public override BuildTarget Target => BuildTarget.NoTarget;
|
||||
public override TypeDB ScriptInfo => null;
|
||||
public override bool UseCache { get => false; set => base.UseCache = value; }
|
||||
}
|
||||
|
||||
class TestContent : TestBundleBuildContent
|
||||
{
|
||||
// Inputs
|
||||
List<CustomContent> m_CustomAssets;
|
||||
public override List<CustomContent> CustomAssets => m_CustomAssets;
|
||||
|
||||
// Outputs
|
||||
Dictionary<string, List<GUID>> m_BundleLayout;
|
||||
Dictionary<GUID, string> m_Addresses;
|
||||
public override Dictionary<string, List<GUID>> BundleLayout => m_BundleLayout;
|
||||
public override Dictionary<GUID, string> Addresses => m_Addresses;
|
||||
|
||||
public TestContent(List<CustomContent> customAssets)
|
||||
{
|
||||
m_CustomAssets = customAssets;
|
||||
m_BundleLayout = new Dictionary<string, List<GUID>>();
|
||||
m_Addresses = new Dictionary<GUID, string>();
|
||||
}
|
||||
}
|
||||
|
||||
class TestDependencyData : TestDependencyDataBase
|
||||
{
|
||||
// Input / Output
|
||||
Dictionary<GUID, AssetLoadInfo> m_AssetInfo;
|
||||
public override Dictionary<GUID, AssetLoadInfo> AssetInfo => m_AssetInfo;
|
||||
|
||||
// Optional Inputs
|
||||
BuildUsageTagGlobal m_GlobalUsage;
|
||||
Dictionary<GUID, SceneDependencyInfo> m_SceneInfo;
|
||||
public override BuildUsageTagGlobal GlobalUsage { get => m_GlobalUsage; set => m_GlobalUsage = value; }
|
||||
public override Dictionary<GUID, SceneDependencyInfo> SceneInfo => m_SceneInfo;
|
||||
public override BuildUsageCache DependencyUsageCache => null;
|
||||
|
||||
// Outputs
|
||||
Dictionary<GUID, BuildUsageTagSet> m_AssetUsage;
|
||||
public override Dictionary<GUID, BuildUsageTagSet> AssetUsage => m_AssetUsage;
|
||||
|
||||
public TestDependencyData(Dictionary<GUID, AssetLoadInfo> assetInfo)
|
||||
{
|
||||
m_AssetInfo = assetInfo;
|
||||
m_GlobalUsage = new BuildUsageTagGlobal();
|
||||
m_SceneInfo = new Dictionary<GUID, SceneDependencyInfo>();
|
||||
m_AssetUsage = new Dictionary<GUID, BuildUsageTagSet>();
|
||||
}
|
||||
}
|
||||
|
||||
static CalculateCustomDependencyData CreateDefaultBuildTask(List<CustomContent> customAssets, Dictionary<GUID, AssetLoadInfo> assetInfo = null)
|
||||
{
|
||||
var task = new CalculateCustomDependencyData();
|
||||
var testParams = new TestBuildParameters();
|
||||
var testContent = new TestContent(customAssets);
|
||||
if (assetInfo == null)
|
||||
assetInfo = new Dictionary<GUID, AssetLoadInfo>();
|
||||
var testData = new TestDependencyData(assetInfo);
|
||||
IBuildContext context = new BuildContext(testParams, testContent, testData);
|
||||
ContextInjector.Inject(context, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
static void ExtractTestData(IBuildTask task, out CustomAssets customAssets, out TestContent content, out TestDependencyData dependencyData)
|
||||
{
|
||||
IBuildContext context = new BuildContext();
|
||||
ContextInjector.Extract(context, task);
|
||||
customAssets = (CustomAssets)context.GetContextObject<ICustomAssets>();
|
||||
content = (TestContent)context.GetContextObject<IBundleBuildContent>();
|
||||
dependencyData = (TestDependencyData)context.GetContextObject<IDependencyData>();
|
||||
}
|
||||
|
||||
static ObjectIdentifier MakeObjectId(string guid, long localIdentifierInFile, FileType fileType, string filePath)
|
||||
{
|
||||
var objectId = new ObjectIdentifier();
|
||||
var boxed = (object)objectId;
|
||||
var type = typeof(ObjectIdentifier);
|
||||
type.GetField("m_GUID", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, new GUID(guid));
|
||||
type.GetField("m_LocalIdentifierInFile", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, localIdentifierInFile);
|
||||
type.GetField("m_FileType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, fileType);
|
||||
type.GetField("m_FilePath", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, filePath);
|
||||
return (ObjectIdentifier)boxed;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAssetEntryForObjectIdentifiers_ThrowsExceptionOnAssetGUIDCollision()
|
||||
{
|
||||
var assetPath = "temp/test_serialized_file.asset";
|
||||
UnityEditorInternal.InternalEditorUtility.SaveToSerializedFileAndForget(new[] { Texture2D.whiteTexture, Texture2D.redTexture }, assetPath, false);
|
||||
|
||||
var address = "CustomAssetAddress";
|
||||
var assetInfo = new Dictionary<GUID, AssetLoadInfo>();
|
||||
assetInfo.Add(HashingMethods.Calculate(address).ToGUID(), new AssetLoadInfo());
|
||||
|
||||
var customContent = new List<CustomContent>
|
||||
{
|
||||
new CustomContent
|
||||
{
|
||||
Asset = new GUID(),
|
||||
Processor = (guid, task) =>
|
||||
{
|
||||
task.GetObjectIdentifiersAndTypesForSerializedFile(assetPath, out var objectIdentifiers, out var types);
|
||||
var ex = Assert.Throws<System.ArgumentException>(() => task.CreateAssetEntryForObjectIdentifiers(objectIdentifiers, assetPath, "CustomAssetBundle", address, types[0]));
|
||||
var expected = string.Format("Custom Asset '{0}' already exists. Building duplicate asset entries is not supported.", address);
|
||||
Assert.That(ex.Message, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var buildTask = CreateDefaultBuildTask(customContent, assetInfo);
|
||||
buildTask.Run();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetObjectIdentifiersAndTypesForSerializedFile_ReturnsAllObjectIdentifiersAndTypes()
|
||||
{
|
||||
var assetPath = "temp/test_serialized_file.asset";
|
||||
UnityEditorInternal.InternalEditorUtility.SaveToSerializedFileAndForget(new[] { Texture2D.whiteTexture, Texture2D.redTexture }, assetPath, false);
|
||||
|
||||
var customContent = new List<CustomContent>
|
||||
{
|
||||
new CustomContent
|
||||
{
|
||||
Asset = new GUID(),
|
||||
Processor = (guid, task) =>
|
||||
{
|
||||
task.GetObjectIdentifiersAndTypesForSerializedFile(assetPath, out var objectIdentifiers, out var types);
|
||||
Assert.AreEqual(2, objectIdentifiers.Length);
|
||||
Assert.AreEqual(MakeObjectId("00000000000000000000000000000000", 1, FileType.NonAssetType, assetPath), objectIdentifiers[0]);
|
||||
Assert.AreEqual(MakeObjectId("00000000000000000000000000000000", 2, FileType.NonAssetType, assetPath), objectIdentifiers[1]);
|
||||
|
||||
Assert.AreEqual(1, types.Length);
|
||||
Assert.AreEqual(typeof(Texture2D), types[0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var buildTask = CreateDefaultBuildTask(customContent);
|
||||
buildTask.Run();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAssetEntryForObjectIdentifiers_AddsNewBundleAndAssetDataForCustomAsset()
|
||||
{
|
||||
var assetPath = "temp/test_serialized_file.asset";
|
||||
var bundleName = "CustomAssetBundle";
|
||||
var address = "CustomAssetAddress";
|
||||
var assetGuid = HashingMethods.Calculate(address).ToGUID();
|
||||
UnityEditorInternal.InternalEditorUtility.SaveToSerializedFileAndForget(new[] { Texture2D.whiteTexture, Texture2D.redTexture }, assetPath, false);
|
||||
|
||||
var customContent = new List<CustomContent>
|
||||
{
|
||||
new CustomContent
|
||||
{
|
||||
Asset = new GUID(),
|
||||
Processor = (guid, task) =>
|
||||
{
|
||||
task.GetObjectIdentifiersAndTypesForSerializedFile(assetPath, out var includedObjects, out var types);
|
||||
task.CreateAssetEntryForObjectIdentifiers(includedObjects, assetPath, bundleName, address, types[0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var buildTask = CreateDefaultBuildTask(customContent);
|
||||
buildTask.Run();
|
||||
|
||||
ExtractTestData(buildTask, out var customAssets, out var content, out var dependencyData);
|
||||
|
||||
// Ensure the bundle name was added, and the custom asset guid was added to that bundle
|
||||
Assert.IsTrue(content.BundleLayout.ContainsKey(bundleName));
|
||||
CollectionAssert.Contains(content.BundleLayout[bundleName], assetGuid);
|
||||
|
||||
// Ensure the custom address was added
|
||||
Assert.IsTrue(content.Addresses.ContainsKey(assetGuid));
|
||||
Assert.AreEqual(address, content.Addresses[assetGuid]);
|
||||
|
||||
// Ensure AssetInfo contains the calculated includes and references for the custom asset
|
||||
Assert.IsTrue(dependencyData.AssetInfo.ContainsKey(assetGuid));
|
||||
var loadInfo = dependencyData.AssetInfo[assetGuid];
|
||||
Assert.AreEqual(address, loadInfo.address);
|
||||
Assert.AreEqual(assetGuid, loadInfo.asset);
|
||||
Assert.AreEqual(2, loadInfo.includedObjects.Count);
|
||||
Assert.AreEqual(0, loadInfo.referencedObjects.Count);
|
||||
|
||||
// Ensure the usage tags were added
|
||||
Assert.IsTrue(dependencyData.AssetUsage.ContainsKey(assetGuid));
|
||||
Assert.IsNotNull(dependencyData.AssetUsage[assetGuid]);
|
||||
|
||||
// Ensure the custom asset was registered in the customAssets list
|
||||
CollectionAssert.Contains(customAssets.Assets, assetGuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ba3cb7b4776e6c24ba53e7f8f7e751b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,235 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Injector;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEditor.Build.Player;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
public class CalculateSceneDependencyTests
|
||||
{
|
||||
class TestParams : TestBuildParametersBase
|
||||
{
|
||||
// Inputs
|
||||
public override bool UseCache { get; set; }
|
||||
public override BuildTarget Target { get => BuildTarget.NoTarget; }
|
||||
public override BuildTargetGroup Group { get => BuildTargetGroup.Unknown; }
|
||||
public override TypeDB ScriptInfo { get => null; }
|
||||
public override ContentBuildFlags ContentBuildFlags { get => ContentBuildFlags.None; }
|
||||
public override bool NonRecursiveDependencies { get; set; }
|
||||
|
||||
#if !UNITY_2019_3_OR_NEWER
|
||||
public override string TempOutputFolder => ContentPipeline.kTempBuildPath;
|
||||
#endif
|
||||
|
||||
public override BuildSettings GetContentBuildSettings()
|
||||
{
|
||||
return new BuildSettings
|
||||
{
|
||||
group = Group,
|
||||
target = Target,
|
||||
typeDB = ScriptInfo,
|
||||
buildFlags = ContentBuildFlags
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TestContent : TestBundleBuildContent
|
||||
{
|
||||
public List<GUID> TestScenes = new List<GUID>();
|
||||
public List<GUID> TestAssets = new List<GUID>();
|
||||
|
||||
// Inputs
|
||||
public override List<GUID> Scenes => TestScenes;
|
||||
public override List<GUID> Assets => TestAssets;
|
||||
}
|
||||
|
||||
class TestDependencyData : TestDependencyDataBase
|
||||
{
|
||||
public Dictionary<GUID, SceneDependencyInfo> TestSceneInfo = new Dictionary<GUID, SceneDependencyInfo>();
|
||||
public Dictionary<GUID, BuildUsageTagSet> TestSceneUsage = new Dictionary<GUID, BuildUsageTagSet>();
|
||||
public Dictionary<GUID, Hash128> TestDependencyHash = new Dictionary<GUID, Hash128>();
|
||||
|
||||
// Inputs
|
||||
public override BuildUsageCache DependencyUsageCache => null;
|
||||
|
||||
// Outputs
|
||||
public override Dictionary<GUID, SceneDependencyInfo> SceneInfo => TestSceneInfo;
|
||||
public override Dictionary<GUID, BuildUsageTagSet> SceneUsage => TestSceneUsage;
|
||||
public override Dictionary<GUID, Hash128> DependencyHash => TestDependencyHash;
|
||||
}
|
||||
|
||||
const string k_FolderPath = "Test";
|
||||
const string k_TmpPath = "tmp";
|
||||
|
||||
const string k_ScenePath = "Assets/testScene.unity";
|
||||
const string k_TestAssetsPath = "Assets/TestAssetsOnlyWillBeDeleted";
|
||||
const string k_CubePath = k_TestAssetsPath + "/Cube.prefab";
|
||||
const string k_CubePath2 = k_TestAssetsPath + "/Cube2.prefab";
|
||||
|
||||
static CalculateSceneDependencyData CreateDefaultBuildTask(List<GUID> scenes, BuildCache optionalCache, bool nonRecursive = false)
|
||||
{
|
||||
var task = new CalculateSceneDependencyData();
|
||||
var testParams = new TestParams();
|
||||
testParams.UseCache = optionalCache != null;
|
||||
testParams.NonRecursiveDependencies = nonRecursive;
|
||||
var testContent = new TestContent { TestScenes = scenes };
|
||||
var testData = new TestDependencyData();
|
||||
IBuildContext context = new BuildContext(testParams, testContent, testData, optionalCache);
|
||||
ContextInjector.Inject(context, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
static void ExtractTestData(IBuildTask task, out TestDependencyData dependencyData)
|
||||
{
|
||||
IBuildContext context = new BuildContext();
|
||||
ContextInjector.Extract(context, task);
|
||||
dependencyData = (TestDependencyData)context.GetContextObject<IDependencyData>();
|
||||
}
|
||||
|
||||
static ObjectIdentifier MakeObjectId(string guid, long localIdentifierInFile, FileType fileType, string filePath)
|
||||
{
|
||||
var objectId = new ObjectIdentifier();
|
||||
var boxed = (object)objectId;
|
||||
var type = typeof(ObjectIdentifier);
|
||||
type.GetField("m_GUID", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, new GUID(guid));
|
||||
type.GetField("m_LocalIdentifierInFile", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, localIdentifierInFile);
|
||||
type.GetField("m_FileType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, fileType);
|
||||
type.GetField("m_FilePath", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, filePath);
|
||||
return (ObjectIdentifier)boxed;
|
||||
}
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
|
||||
Directory.CreateDirectory(k_TestAssetsPath);
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
PrefabUtility.SaveAsPrefabAsset(GameObject.CreatePrimitive(PrimitiveType.Cube), k_CubePath);
|
||||
PrefabUtility.SaveAsPrefabAsset(GameObject.CreatePrimitive(PrimitiveType.Cube), k_CubePath2);
|
||||
#else
|
||||
PrefabUtility.CreatePrefab(k_CubePath, GameObject.CreatePrimitive(PrimitiveType.Cube));
|
||||
PrefabUtility.CreatePrefab(k_CubePath2, GameObject.CreatePrimitive(PrimitiveType.Cube));
|
||||
#endif
|
||||
AssetDatabase.ImportAsset(k_CubePath);
|
||||
AssetDatabase.ImportAsset(k_CubePath2);
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void Cleanup()
|
||||
{
|
||||
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
|
||||
AssetDatabase.DeleteAsset(k_ScenePath);
|
||||
AssetDatabase.DeleteAsset(k_CubePath);
|
||||
AssetDatabase.DeleteAsset(k_CubePath2);
|
||||
AssetDatabase.DeleteAsset(k_TestAssetsPath);
|
||||
|
||||
if (Directory.Exists(k_FolderPath))
|
||||
Directory.Delete(k_FolderPath, true);
|
||||
if (Directory.Exists(k_TmpPath))
|
||||
Directory.Delete(k_TmpPath, true);
|
||||
}
|
||||
|
||||
static void SetupSceneForTest(out Scene scene, out GameObject prefab)
|
||||
{
|
||||
scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
|
||||
|
||||
prefab = AssetDatabase.LoadAssetAtPath<GameObject>(k_CubePath);
|
||||
prefab.transform.position = new Vector3(0, 0, 0);
|
||||
EditorUtility.SetDirty(prefab);
|
||||
AssetDatabase.SaveAssets();
|
||||
PrefabUtility.InstantiatePrefab(prefab);
|
||||
|
||||
EditorSceneManager.SaveScene(scene, k_ScenePath);
|
||||
}
|
||||
|
||||
static object[] DependencyHashTestCases =
|
||||
{
|
||||
new object[] { true, (Action<object, object>)Assert.AreNotEqual },
|
||||
new object[] { false, (Action<object, object>)Assert.AreEqual },
|
||||
};
|
||||
|
||||
[TestCaseSource("DependencyHashTestCases")]
|
||||
[Test]
|
||||
public void CalculateSceneDependencyData_DependencyHashTests(bool modifyPrefab, Action<object, object> assertType)
|
||||
{
|
||||
SetupSceneForTest(out Scene scene, out GameObject prefab);
|
||||
|
||||
List<GUID> scenes = new List<GUID>();
|
||||
GUID sceneGuid = new GUID(AssetDatabase.AssetPathToGUID(scene.path));
|
||||
scenes.Add(sceneGuid);
|
||||
|
||||
TestDependencyData dependencyData1;
|
||||
using (BuildCache cache = new BuildCache())
|
||||
{
|
||||
var buildTask = CreateDefaultBuildTask(scenes, cache);
|
||||
buildTask.Run();
|
||||
ExtractTestData(buildTask, out dependencyData1);
|
||||
}
|
||||
BuildCache.PurgeCache(false);
|
||||
|
||||
if (modifyPrefab)
|
||||
prefab.transform.position = new Vector3(1, 1, 1);
|
||||
EditorUtility.SetDirty(prefab);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
TestDependencyData dependencyData2;
|
||||
using (BuildCache cache = new BuildCache())
|
||||
{
|
||||
var buildTask = CreateDefaultBuildTask(scenes, cache);
|
||||
buildTask.Run();
|
||||
ExtractTestData(buildTask, out dependencyData2);
|
||||
}
|
||||
BuildCache.PurgeCache(false);
|
||||
|
||||
assertType(dependencyData1.DependencyHash[sceneGuid], dependencyData2.DependencyHash[sceneGuid]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CalculateSceneDependencyData_DependencyHash_IsZeroWhenNotUsingCashing()
|
||||
{
|
||||
SetupSceneForTest(out Scene scene, out var _);
|
||||
|
||||
List<GUID> scenes = new List<GUID>();
|
||||
GUID sceneGuid = new GUID(AssetDatabase.AssetPathToGUID(scene.path));
|
||||
scenes.Add(sceneGuid);
|
||||
|
||||
TestDependencyData dependencyData;
|
||||
var buildTask = CreateDefaultBuildTask(scenes, null);
|
||||
buildTask.Run();
|
||||
ExtractTestData(buildTask, out dependencyData);
|
||||
|
||||
Assert.AreEqual(new Hash128(), dependencyData.DependencyHash[sceneGuid]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CalcualteSceneDependencyData_ReturnsNonEmptyUsage_ForNonRecursiveDependencies()
|
||||
{
|
||||
SetupSceneForTest(out Scene scene, out var _);
|
||||
|
||||
List<GUID> scenes = new List<GUID>();
|
||||
GUID sceneGuid = new GUID(AssetDatabase.AssetPathToGUID(scene.path));
|
||||
scenes.Add(sceneGuid);
|
||||
|
||||
TestDependencyData dependencyData;
|
||||
var buildTask = CreateDefaultBuildTask(scenes, null, true);
|
||||
buildTask.Run();
|
||||
ExtractTestData(buildTask, out dependencyData);
|
||||
|
||||
BuildUsageTagSet usagetSet = dependencyData.SceneUsage[sceneGuid];
|
||||
var method = typeof(BuildUsageTagSet).GetMethod("SerializeToJson", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
var json = method.Invoke(usagetSet, new object[0]) as string;
|
||||
|
||||
Assert.AreNotEqual(json, "{\"m_objToUsage\":[]}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d6c85b3ea86fd7d45a0284311e03aa02
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Injector;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
class ContextInjectionTests
|
||||
{
|
||||
interface IInjectionContext : IContextObject
|
||||
{
|
||||
int State { get; set; }
|
||||
}
|
||||
|
||||
class InjectionClass : IInjectionContext
|
||||
{
|
||||
public int State { get; set; }
|
||||
}
|
||||
|
||||
struct InjectionStruct : IInjectionContext
|
||||
{
|
||||
public int State { get; set; }
|
||||
}
|
||||
|
||||
struct TaskStruct : IBuildTask
|
||||
{
|
||||
public int Version { get { return 1; } }
|
||||
public int NewState { get; private set; }
|
||||
|
||||
#pragma warning disable 649
|
||||
[InjectContext]
|
||||
internal IInjectionContext InjectedObject;
|
||||
#pragma warning restore 649
|
||||
|
||||
public TaskStruct(int newState)
|
||||
: this()
|
||||
{
|
||||
NewState = newState;
|
||||
}
|
||||
|
||||
public ReturnCode Run()
|
||||
{
|
||||
InjectedObject.State = NewState;
|
||||
return ReturnCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
class TaskClass : IBuildTask
|
||||
{
|
||||
public int Version { get { return 1; } }
|
||||
public int NewState { get; private set; }
|
||||
|
||||
#pragma warning disable 649
|
||||
[InjectContext]
|
||||
internal IInjectionContext InjectedObject;
|
||||
#pragma warning restore 649
|
||||
|
||||
public TaskClass(int newState)
|
||||
{
|
||||
NewState = newState;
|
||||
}
|
||||
|
||||
public ReturnCode Run()
|
||||
{
|
||||
InjectedObject.State = NewState;
|
||||
return ReturnCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
class TaskContext : IBuildTask
|
||||
{
|
||||
public int Version { get { return 1; } }
|
||||
|
||||
#pragma warning disable 649
|
||||
[InjectContext]
|
||||
internal IBuildContext InjectedContext;
|
||||
#pragma warning restore 649
|
||||
|
||||
public ReturnCode Run()
|
||||
{
|
||||
return ReturnCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void CanInjectAndExtractWithStructs()
|
||||
{
|
||||
IInjectionContext injection = new InjectionStruct();
|
||||
injection.State = 1;
|
||||
|
||||
IBuildContext context = new BuildContext();
|
||||
context.SetContextObject(injection);
|
||||
|
||||
TaskStruct task = new TaskStruct(2);
|
||||
Assert.IsNull(task.InjectedObject);
|
||||
|
||||
// Still need to box / unbox the struct task
|
||||
IBuildTask boxed = task;
|
||||
ContextInjector.Inject(context, boxed);
|
||||
task = (TaskStruct)boxed;
|
||||
|
||||
Assert.IsNotNull(task.InjectedObject);
|
||||
Assert.AreEqual(1, task.InjectedObject.State);
|
||||
|
||||
ReturnCode result = task.Run();
|
||||
Assert.AreEqual(ReturnCode.Success, result);
|
||||
|
||||
ContextInjector.Extract(context, task);
|
||||
|
||||
IInjectionContext modifiedInjection = context.GetContextObject<IInjectionContext>();
|
||||
Assert.AreEqual(task.NewState, modifiedInjection.State);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanInjectAndExtractWithClasses()
|
||||
{
|
||||
IInjectionContext injection = new InjectionClass();
|
||||
injection.State = 1;
|
||||
|
||||
IBuildContext context = new BuildContext();
|
||||
context.SetContextObject(injection);
|
||||
|
||||
TaskClass task = new TaskClass(2);
|
||||
Assert.IsNull(task.InjectedObject);
|
||||
|
||||
ContextInjector.Inject(context, task);
|
||||
|
||||
Assert.IsNotNull(task.InjectedObject);
|
||||
Assert.AreEqual(1, task.InjectedObject.State);
|
||||
|
||||
ReturnCode result = task.Run();
|
||||
Assert.AreEqual(ReturnCode.Success, result);
|
||||
|
||||
ContextInjector.Extract(context, task);
|
||||
|
||||
IInjectionContext modifiedInjection = context.GetContextObject<IInjectionContext>();
|
||||
Assert.AreEqual(task.NewState, modifiedInjection.State);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanInjectIBuildContextAsInOnly()
|
||||
{
|
||||
IBuildContext context = new BuildContext();
|
||||
|
||||
TaskContext task = new TaskContext();
|
||||
Assert.IsNull(task.InjectedContext);
|
||||
|
||||
ContextInjector.Inject(context, task);
|
||||
|
||||
Assert.IsNotNull(task.InjectedContext);
|
||||
Assert.AreEqual(context, task.InjectedContext);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
ContextInjector.Extract(context, task);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 288dd00735888d6419c1d6312c8a1d51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContextObjectSerializationTests
|
||||
{
|
||||
public Type[] GetIContextObjectTypes()
|
||||
{
|
||||
var blacklist = new[]
|
||||
{
|
||||
typeof(BuildCallbacks), typeof(Unity5PackedIdentifiers), typeof(PrefabPackedIdentifiers), typeof(LinearPackedIdentifiers), typeof(BuildCache),
|
||||
typeof(ProgressTracker), typeof(ProgressLoggingTracker), typeof(BuildInterfacesWrapper)
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
, typeof(ContentFileIdentifiers)
|
||||
, typeof(ClusterOutput)
|
||||
#endif
|
||||
};
|
||||
|
||||
var assembly = AppDomain.CurrentDomain.GetAssemblies().First(x => x.GetName().Name == "Unity.ScriptableBuildPipeline.Editor");
|
||||
return assembly.GetTypes().Where(x => typeof(IContextObject).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract).Where(x => !blacklist.Contains(x)).ToArray();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IContextObjects_SupportSerialization()
|
||||
{
|
||||
// This is just a generic catch all to ensure we properly setup C# serialization on IContextTypes
|
||||
// More explicit tests should be written per type to validate proper serialization in / out
|
||||
var types = GetIContextObjectTypes();
|
||||
foreach (var type in types)
|
||||
IContextObject_SupportSerialization(type);
|
||||
}
|
||||
|
||||
static T SerializedAndDeserializeObject<T>(T obj)
|
||||
{
|
||||
var formatter = new BinaryFormatter();
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
formatter.Serialize(stream, obj);
|
||||
stream.Position = 0;
|
||||
var obj2 = (T)formatter.Deserialize(stream);
|
||||
return obj2;
|
||||
}
|
||||
}
|
||||
|
||||
static void IContextObject_SupportSerialization(Type type)
|
||||
{
|
||||
var instance1 = (IContextObject)Activator.CreateInstance(type, true);
|
||||
var instance2 = SerializedAndDeserializeObject(instance1);
|
||||
Assert.NotNull(instance2);
|
||||
Assert.AreEqual(type, instance2.GetType());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fa12bf74f16f5e84f93111cdd55bf70b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,80 @@
|
|||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
class FileCompressorTests
|
||||
{
|
||||
const string k_SourceDirectory = "Compressor";
|
||||
|
||||
static readonly string[] k_SourceFiles =
|
||||
{
|
||||
"/File1.json",
|
||||
"/Subdir/File2.json",
|
||||
"\\File3.json",
|
||||
"\\Subdir\\File4.json"
|
||||
};
|
||||
|
||||
static string NormalizePath(string path)
|
||||
{
|
||||
return path.Replace("\\", "/");
|
||||
}
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
foreach (string file in k_SourceFiles)
|
||||
{
|
||||
var filePath = NormalizePath(k_SourceDirectory + file);
|
||||
var dir = Path.GetDirectoryName(filePath);
|
||||
Directory.CreateDirectory(dir);
|
||||
File.WriteAllText(filePath, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
Directory.Delete(k_SourceDirectory, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CompressAndDecompressCanHandleSubdirectories()
|
||||
{
|
||||
var targetDirectory = k_SourceDirectory + "2";
|
||||
var success = FileCompressor.Compress(k_SourceDirectory, "artifacts.sbpGz");
|
||||
Assert.IsTrue(success);
|
||||
|
||||
success = FileCompressor.Decompress("artifacts.sbpGz", targetDirectory);
|
||||
Assert.IsTrue(success);
|
||||
|
||||
for (int i = 0; i < k_SourceFiles.Length; i++)
|
||||
{
|
||||
var sourcePath = NormalizePath(k_SourceDirectory + k_SourceFiles[i]);
|
||||
var targetPath = NormalizePath(targetDirectory + k_SourceFiles[i]);
|
||||
FileAssert.Exists(targetPath);
|
||||
FileAssert.AreEqual(sourcePath, targetPath);
|
||||
}
|
||||
|
||||
File.Delete("artifacts.sbpGz");
|
||||
Directory.Delete(targetDirectory, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TrailingSlashDoesNotChangeResults()
|
||||
{
|
||||
var success = FileCompressor.Compress(k_SourceDirectory, "artifacts1.sbpGz");
|
||||
Assert.IsTrue(success);
|
||||
|
||||
success = FileCompressor.Compress(k_SourceDirectory + "/", "artifacts2.sbpGz");
|
||||
Assert.IsTrue(success);
|
||||
|
||||
FileAssert.AreEqual("artifacts1.sbpGz", "artifacts2.sbpGz");
|
||||
|
||||
File.Delete("artifacts1.sbpGz");
|
||||
File.Delete("artifacts2.sbpGz");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6d2f1f46c2a196649b1eb9cd319cc991
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,233 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
class GenerateBundlePackingTests
|
||||
{
|
||||
IDependencyData GetDependencyData(List<ObjectIdentifier> objects, params GUID[] guids)
|
||||
{
|
||||
IDependencyData dep = new BuildDependencyData();
|
||||
for (int i = 0; i < guids.Length; i++)
|
||||
{
|
||||
AssetLoadInfo loadInfo = new AssetLoadInfo()
|
||||
{
|
||||
asset = guids[i],
|
||||
address = $"path{i}",
|
||||
includedObjects = objects,
|
||||
referencedObjects = objects
|
||||
};
|
||||
dep.AssetInfo.Add(guids[i], loadInfo);
|
||||
}
|
||||
return dep;
|
||||
}
|
||||
|
||||
List<ObjectIdentifier> CreateObjectIdentifierList(string path, params GUID[] guids)
|
||||
{
|
||||
var objects = new List<ObjectIdentifier>();
|
||||
foreach (GUID guid in guids)
|
||||
{
|
||||
var obj = new ObjectIdentifier();
|
||||
obj.SetObjectIdentifier(guid, 0, FileType.SerializedAssetType, path);
|
||||
objects.Add(obj);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
string k_TempAsset = "Assets/temp.prefab";
|
||||
GUID k_TempGuid;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
var root = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
var child = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
child.transform.parent = root.transform;
|
||||
PrefabUtility.SaveAsPrefabAsset(root, k_TempAsset);
|
||||
k_TempGuid = new GUID(AssetDatabase.AssetPathToGUID(k_TempAsset));
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
AssetDatabase.DeleteAsset(k_TempAsset);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReferencesAreUnique_FilterReferencesForAsset_ReturnsReferences()
|
||||
{
|
||||
var assetInBundle = new GUID("00000000000000000000000000000000");
|
||||
List<ObjectIdentifier> objects = CreateObjectIdentifierList("path", assetInBundle, assetInBundle);
|
||||
IDependencyData dep = GetDependencyData(objects, assetInBundle);
|
||||
|
||||
var references = new List<ObjectIdentifier>(objects);
|
||||
List<GUID> results = GenerateBundlePacking.FilterReferencesForAsset(dep, assetInBundle, references);
|
||||
Assert.AreEqual(1, results.Count);
|
||||
Assert.AreEqual(assetInBundle, results[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReferencesContainsDefaultResources_FilterReferencesForAsset_PrunesDefaultResources()
|
||||
{
|
||||
var assetInBundle = new GUID("00000000000000000000000000000000");
|
||||
List<ObjectIdentifier> objects = CreateObjectIdentifierList(CommonStrings.UnityDefaultResourcePath, assetInBundle);
|
||||
IDependencyData dep = GetDependencyData(objects, assetInBundle);
|
||||
|
||||
var references = new List<ObjectIdentifier>(objects);
|
||||
GenerateBundlePacking.FilterReferencesForAsset(dep, assetInBundle, references);
|
||||
Assert.AreEqual(0, references.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReferencesContainsAssetsInBundles_FilterReferencesForAsset_PrunesAssetsInBundles()
|
||||
{
|
||||
var assetInBundle = new GUID("00000000000000000000000000000000");
|
||||
List<ObjectIdentifier> objects = CreateObjectIdentifierList("path", assetInBundle);
|
||||
IDependencyData dep = GetDependencyData(objects, assetInBundle);
|
||||
|
||||
var references = new List<ObjectIdentifier>(objects);
|
||||
GenerateBundlePacking.FilterReferencesForAsset(dep, assetInBundle, references);
|
||||
Assert.AreEqual(0, references.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReferencesDoesNotContainAssetsInBundles_FilterReferences_PrunesNothingAndReturnsNothing()
|
||||
{
|
||||
var assetInBundle = new GUID("00000000000000000000000000000000");
|
||||
List<ObjectIdentifier> objects = CreateObjectIdentifierList("path", assetInBundle);
|
||||
IDependencyData dep = new BuildDependencyData();
|
||||
|
||||
var references = new List<ObjectIdentifier>(objects);
|
||||
List<GUID> results = GenerateBundlePacking.FilterReferencesForAsset(dep, assetInBundle, references);
|
||||
Assert.AreEqual(1, references.Count);
|
||||
Assert.AreEqual(assetInBundle, references[0].guid);
|
||||
Assert.AreEqual(0, results.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReferencesContainsRefsIncludedByNonCircularAssets_FilterReferencesForAsset_PrunesRefsIncludedByNonCircularAssets()
|
||||
{
|
||||
var assetNotInBundle = new GUID("00000000000000000000000000000000");
|
||||
var referenceInBundle = new GUID("00000000000000000000000000000001");
|
||||
var referenceNotInBundle = new GUID("00000000000000000000000000000002");
|
||||
List<ObjectIdentifier> objects = CreateObjectIdentifierList("path", referenceNotInBundle);
|
||||
IDependencyData dep = GetDependencyData(objects, referenceInBundle);
|
||||
|
||||
List<ObjectIdentifier> references = CreateObjectIdentifierList("path", referenceInBundle, referenceNotInBundle);
|
||||
GenerateBundlePacking.FilterReferencesForAsset(dep, assetNotInBundle, references);
|
||||
Assert.AreEqual(0, references.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReferencesContainsRefsIncludedByCircularAssetsWithLowerGuid_FilterReferencesForAsset_PrunesRefsIncludedByCircularAssetsWithLowerGuid()
|
||||
{
|
||||
var assetNotInBundle = new GUID("00000000000000000000000000000001");
|
||||
var referenceInBundle = new GUID("00000000000000000000000000000000");
|
||||
List<ObjectIdentifier> objects = CreateObjectIdentifierList("path", assetNotInBundle); // circular reference to asset whose references we want to filter
|
||||
IDependencyData dep = GetDependencyData(objects, referenceInBundle);
|
||||
|
||||
List<ObjectIdentifier> references = CreateObjectIdentifierList("path", referenceInBundle, assetNotInBundle);
|
||||
GenerateBundlePacking.FilterReferencesForAsset(dep, assetNotInBundle, references);
|
||||
Assert.AreEqual(0, references.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReferencesContainsRefsIncludedByCircularAssetsWithHigherGuid_FilterReferencesForAsset_DoesNotPruneRefsIncludedByCircularAssetsWithHigherGuid()
|
||||
{
|
||||
var assetNotInBundle = new GUID("00000000000000000000000000000000");
|
||||
var referenceInBundle = new GUID("00000000000000000000000000000001");
|
||||
List<ObjectIdentifier> objects = CreateObjectIdentifierList("path", assetNotInBundle); // circular reference to asset whose references we want to filter
|
||||
IDependencyData dep = GetDependencyData(objects, referenceInBundle);
|
||||
|
||||
List<ObjectIdentifier> references = CreateObjectIdentifierList("path", referenceInBundle, assetNotInBundle);
|
||||
GenerateBundlePacking.FilterReferencesForAsset(dep, assetNotInBundle, references);
|
||||
Assert.AreEqual(1, references.Count);
|
||||
Assert.AreEqual(assetNotInBundle, references[0].guid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReferencesContainsPreviousSceneObjects_FilterReferencesForAsset_PrunesPreviousSceneObjects()
|
||||
{
|
||||
var assetInBundle = new GUID("00000000000000000000000000000001");
|
||||
var referenceNotInBundle = new GUID("00000000000000000000000000000000");
|
||||
List<ObjectIdentifier> objects = CreateObjectIdentifierList("path", referenceNotInBundle);
|
||||
IDependencyData dep = GetDependencyData(objects, assetInBundle);
|
||||
|
||||
var references = new List<ObjectIdentifier>(objects);
|
||||
GenerateBundlePacking.FilterReferencesForAsset(dep, assetInBundle, references, new HashSet<ObjectIdentifier>() { objects[0] });
|
||||
Assert.AreEqual(0, references.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReferencesContainPreviousSceneAssetDependencies_FilterReferencesForAsset_PrunesPreviousAssetDependencies([Values] bool containsPreviousSceneAsset)
|
||||
{
|
||||
var assetInBundle = new GUID("00000000000000000000000000000001");
|
||||
var referenceNotInBundle = new GUID("00000000000000000000000000000000");
|
||||
List<ObjectIdentifier> objects = CreateObjectIdentifierList("path", referenceNotInBundle);
|
||||
IDependencyData dep = GetDependencyData(objects, assetInBundle);
|
||||
|
||||
var references = new List<ObjectIdentifier>(objects);
|
||||
var previousSceneReferences = new HashSet<GUID>();
|
||||
if (containsPreviousSceneAsset)
|
||||
previousSceneReferences.Add(assetInBundle);
|
||||
GenerateBundlePacking.FilterReferencesForAsset(dep, assetInBundle, references, new HashSet<ObjectIdentifier>(), previousSceneReferences);
|
||||
|
||||
if (containsPreviousSceneAsset)
|
||||
Assert.AreEqual(0, references.Count);
|
||||
else
|
||||
Assert.AreEqual(1, references.Count);
|
||||
}
|
||||
|
||||
#if !UNITY_2019_1_OR_NEWER
|
||||
[Test]
|
||||
public void WhenPrefabContainsDuplicateTypes_GetSortedSceneObjectIdentifiers_DoesNotThorwError()
|
||||
{
|
||||
var includes = new List<ObjectIdentifier>(ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(k_TempGuid, EditorUserBuildSettings.activeBuildTarget));
|
||||
var sorted = GenerateBundleCommands.GetSortedSceneObjectIdentifiers(includes);
|
||||
Assert.AreEqual(includes.Count, sorted.Count);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Test]
|
||||
public void BuildCacheUtility_GetSortedUniqueTypesForObjects_ReturnsUniqueAndSortedTypeArray()
|
||||
{
|
||||
var includes = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(k_TempGuid, EditorUserBuildSettings.activeBuildTarget);
|
||||
|
||||
// Test prefab is created using 2 primitive cubes, one parented to the other, so the includes will in turn contain the sequence 2x:
|
||||
Type[] expectedTypes = new[] { typeof(GameObject), typeof(Transform), typeof(MeshFilter), typeof(MeshRenderer), typeof(BoxCollider) };
|
||||
Array.Sort(expectedTypes, (x, y) => x.AssemblyQualifiedName.CompareTo(y.AssemblyQualifiedName));
|
||||
|
||||
var actualTypes = BuildCacheUtility.GetSortedUniqueTypesForObjects(includes);
|
||||
Assert.AreEqual(expectedTypes.Length * 2, includes.Length);
|
||||
Assert.AreEqual(expectedTypes.Length, actualTypes.Length);
|
||||
CollectionAssert.AreEqual(expectedTypes, actualTypes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildCacheUtility_GetMainTypeForObjects_ReturnsUniqueAndSortedTypeArray()
|
||||
{
|
||||
var includes = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(k_TempGuid, EditorUserBuildSettings.activeBuildTarget);
|
||||
|
||||
// Test prefab is created using 2 primitive cubes, one parented to the other, so the includes will in turn contain the sequence:
|
||||
Type[] expectedTypes = new[] { typeof(GameObject), typeof(Transform), typeof(MeshFilter), typeof(MeshRenderer), typeof(BoxCollider),
|
||||
typeof(GameObject), typeof(Transform), typeof(MeshFilter), typeof(MeshRenderer), typeof(BoxCollider) };
|
||||
// One catch, the ordering of the expected types is based on the order of includes which is in turn ordered by the local identifier in file.
|
||||
// Since we are generating the prefab as part of the test, and lfids generation is random, we don't know what order they will be returned in.
|
||||
// So sort both expected types lists and compare exact.
|
||||
Array.Sort(expectedTypes, (x, y) => x.AssemblyQualifiedName.CompareTo(y.AssemblyQualifiedName));
|
||||
|
||||
var actualTypes = BuildCacheUtility.GetMainTypeForObjects(includes);
|
||||
Array.Sort(actualTypes, (x, y) => x.AssemblyQualifiedName.CompareTo(y.AssemblyQualifiedName));
|
||||
|
||||
Assert.AreEqual(expectedTypes.Length, includes.Length);
|
||||
Assert.AreEqual(expectedTypes.Length, actualTypes.Length);
|
||||
CollectionAssert.AreEqual(expectedTypes, actualTypes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a896103a5441cc040ab6d184ad038c6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,168 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
class HashingHelpersTests
|
||||
{
|
||||
class AssertExpectedLayout : Attribute
|
||||
{
|
||||
public Type CompareType;
|
||||
public AssertExpectedLayout(Type compareType)
|
||||
{
|
||||
CompareType = compareType;
|
||||
}
|
||||
}
|
||||
|
||||
[AssertExpectedLayout(typeof(PreloadInfo))]
|
||||
class ExpectedPreloadInfo
|
||||
{
|
||||
public List<ObjectIdentifier> preloadObjects { get; set; }
|
||||
}
|
||||
|
||||
[AssertExpectedLayout(typeof(AssetBundleInfo))]
|
||||
class ExpectedAssetBundleInfo
|
||||
{
|
||||
public string bundleName { get; set; }
|
||||
public List<AssetLoadInfo> bundleAssets { get; set; }
|
||||
}
|
||||
|
||||
[AssertExpectedLayout(typeof(WriteCommand))]
|
||||
class ExpectedWriteCommand
|
||||
{
|
||||
public string fileName { get; set; }
|
||||
public string internalName { get; set; }
|
||||
public List<SerializationInfo> serializeObjects { get; set; }
|
||||
}
|
||||
|
||||
[AssertExpectedLayout(typeof(SerializationInfo))]
|
||||
class ExpectedSerializationInfo
|
||||
{
|
||||
public ObjectIdentifier serializationObject { get; set; }
|
||||
public long serializationIndex { get; set; }
|
||||
}
|
||||
|
||||
[AssertExpectedLayout(typeof(ObjectIdentifier))]
|
||||
class ExpectedObjectIdentifier
|
||||
{
|
||||
public GUID guid { get; }
|
||||
public long localIdentifierInFile { get; }
|
||||
public FileType fileType { get; }
|
||||
public string filePath { get; }
|
||||
}
|
||||
|
||||
public static IEnumerable CustomHashTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
|
||||
foreach (AssertExpectedLayout attr in type.GetCustomAttributes<AssertExpectedLayout>(true))
|
||||
{
|
||||
yield return new TestCaseData(type, attr.CompareType).SetName($"CompareLayout_{type.Name}_{attr.CompareType.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// README! If this test fails, update the associated hashing function in HashingHelpers.cs and then
|
||||
// update the expected type above to reflect the new layout.
|
||||
// The purpose of this test is to make sure our custom hashing function is updated if the associate engine type changes.
|
||||
[Test, TestCaseSource(typeof(HashingHelpersTests), "CustomHashTypes")]
|
||||
public static void CompareLayout(Type expected, Type actual)
|
||||
{
|
||||
{
|
||||
PropertyInfo[] eInfo = expected.GetProperties();
|
||||
PropertyInfo[] aInfo = actual.GetProperties();
|
||||
Assert.AreEqual(eInfo.Length, aInfo.Length);
|
||||
for (int i = 0; i < eInfo.Length; i++)
|
||||
Assert.AreEqual(eInfo[i].PropertyType, aInfo[i].PropertyType);
|
||||
}
|
||||
|
||||
{
|
||||
FieldInfo[] eInfo = expected.GetFields();
|
||||
FieldInfo[] aInfo = actual.GetFields();
|
||||
Assert.AreEqual(eInfo.Length, aInfo.Length);
|
||||
for (int i = 0; i < eInfo.Length; i++)
|
||||
Assert.AreEqual(eInfo[i].FieldType, aInfo[i].FieldType);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public static void PreloadInfo_WhenValueChanges_HashesChange()
|
||||
{
|
||||
ObjectIdentifier obj1 = new ObjectIdentifier();
|
||||
obj1.SetFilePath("TestPath");
|
||||
PreloadInfo[] infos = new PreloadInfo[]
|
||||
{
|
||||
new PreloadInfo() { preloadObjects = new List<ObjectIdentifier>() },
|
||||
new PreloadInfo() { preloadObjects = new List<ObjectIdentifier>() { new ObjectIdentifier() } },
|
||||
new PreloadInfo() { preloadObjects = new List<ObjectIdentifier>() { obj1 } },
|
||||
};
|
||||
HashSet<Hash128> set = new HashSet<Hash128>(infos.Select(x => HashingHelpers.GetHash128(x)));
|
||||
Assert.AreEqual(infos.Length, set.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public static void SerializationInfo_WhenValueChanges_HashesChange()
|
||||
{
|
||||
ObjectIdentifier obj1 = new ObjectIdentifier();
|
||||
obj1.SetFilePath("TestPath");
|
||||
SerializationInfo[] infos = new SerializationInfo[]
|
||||
{
|
||||
new SerializationInfo() { serializationIndex = 0 },
|
||||
new SerializationInfo() { serializationIndex = 1 },
|
||||
new SerializationInfo() { serializationIndex = 0, serializationObject = obj1 }
|
||||
};
|
||||
HashSet<Hash128> set = new HashSet<Hash128>(infos.Select(x => HashingHelpers.GetHash128(x)));
|
||||
Assert.AreEqual(infos.Length, set.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public static void AssetBundleInfo_WhenValueChanges_HashesChange()
|
||||
{
|
||||
AssetBundleInfo[] infos = new AssetBundleInfo[]
|
||||
{
|
||||
new AssetBundleInfo() { bundleName = "Test" },
|
||||
new AssetBundleInfo() { bundleName = "Test2" },
|
||||
new AssetBundleInfo() { bundleAssets = new List<AssetLoadInfo>() { new AssetLoadInfo() { address = "a1" } } },
|
||||
new AssetBundleInfo() { bundleAssets = new List<AssetLoadInfo>() { new AssetLoadInfo() { address = "a2" } } }
|
||||
};
|
||||
HashSet<Hash128> set = new HashSet<Hash128>(infos.Select(x => HashingHelpers.GetHash128(x)));
|
||||
Assert.AreEqual(infos.Length, set.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public static void AssetLoadInfo_WhenValueChanges_HashesChange()
|
||||
{
|
||||
AssetLoadInfo[] infos = new AssetLoadInfo[]
|
||||
{
|
||||
new AssetLoadInfo() { address = "Test" },
|
||||
new AssetLoadInfo() { asset = GUID.Generate()},
|
||||
new AssetLoadInfo() { includedObjects = new List<ObjectIdentifier>(){ new ObjectIdentifier()} },
|
||||
new AssetLoadInfo() { referencedObjects = new List<ObjectIdentifier>() { new ObjectIdentifier() } }
|
||||
};
|
||||
HashSet<Hash128> set = new HashSet<Hash128>(infos.Select(x => HashingHelpers.GetHash128(x)));
|
||||
Assert.AreEqual(infos.Length, set.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public static void WriteCommand_WhenValueChanges_HashesChange()
|
||||
{
|
||||
WriteCommand[] infos = new WriteCommand[]
|
||||
{
|
||||
new WriteCommand() { fileName = "Test" },
|
||||
new WriteCommand() { internalName = "Test2" },
|
||||
new WriteCommand() { serializeObjects = new List<SerializationInfo>() { new SerializationInfo() }},
|
||||
new WriteCommand() { serializeObjects = new List<SerializationInfo>() { new SerializationInfo() { serializationIndex = 2 } }}
|
||||
};
|
||||
HashSet<Hash128> set = new HashSet<Hash128>(infos.Select(x => HashingHelpers.GetHash128(x)));
|
||||
Assert.AreEqual(infos.Length, set.Count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 93cc378d60f13644e9669c22e5cb4618
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,295 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
class HashingMethodsTests
|
||||
{
|
||||
[Test]
|
||||
public void CsMD4_Calcualte_FileNames_IdenticalTo_CppMD4()
|
||||
{
|
||||
// Test ensures our C# implementation of MD4 matches our expected C++ MD4 for compatibility
|
||||
// Until which time we no longer care about compatibility with BuildPipeline.BuildAssetBundles
|
||||
var sourceNames = new[]
|
||||
{
|
||||
"basic_sprite",
|
||||
"audio",
|
||||
"prefabs",
|
||||
"shaderwithcollection",
|
||||
"multi_sprite_packed"
|
||||
};
|
||||
|
||||
var expectedNames = new[]
|
||||
{
|
||||
"a67a7313ceb7840411094318a4aa7055",
|
||||
"d5ae2b5aa3edc0f73b4bb6b1ae125a53",
|
||||
"6fee5e41c4939eed80f81beb3e5e6ebc",
|
||||
"dc5d7d3a7d9efcf91a0314cdcc3af3c8",
|
||||
"ddc8dcea83a5ff418d94c6a1623e81ad"
|
||||
};
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
Assert.AreEqual(expectedNames[i], HashingMethods.Calculate<MD4>(sourceNames[i]).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CsMD4_Calcualte_FileIDs_IdenticalTo_CppMD4()
|
||||
{
|
||||
// Test ensures our C# implementation of MD4 matches our expected C++ MD4 for compatibility
|
||||
// Until which time we no longer care about compatibility with BuildPipeline.BuildAssetBundles
|
||||
Assert.AreEqual(-7588530676450950513, BitConverter.ToInt64(HashingMethods.Calculate<MD4>("fb3a9882e5510684697de78116693750", FileType.MetaAssetType, (long)21300000).ToBytes(), 0));
|
||||
Assert.AreEqual(-8666180608703991793, BitConverter.ToInt64(HashingMethods.Calculate<MD4>("library/atlascache/27/2799803afb660251e3b3049ba37cb15a", (long)2).ToBytes(), 0));
|
||||
}
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
[TestCase(false)] // Use old hasher (MD5)
|
||||
[TestCase(true)] // Use V2 hasher (Spooky)
|
||||
public void HashingMethods_Has128x2Fast_SameAsGeneric(bool useV2Hasher)
|
||||
{
|
||||
// Test ensures the HashingMethods.Calculate(Hash128, Hash128) fast path produces the same results as the general HashingMethods.Calculate(params object[] objects) one does
|
||||
Hash128 hash1 = new Hash128(0x1122334455667788, 0x99AABBCCDDEEFF00);
|
||||
Hash128 hash2 = new Hash128(0x123456789ABCDEF0, 0x1967AbC487Df2F12);
|
||||
|
||||
bool prevUseV2Hasher = ScriptableBuildPipeline.useV2Hasher;
|
||||
ScriptableBuildPipeline.useV2Hasher = useV2Hasher;
|
||||
|
||||
Assert.AreEqual(HashingMethods.Calculate(hash1, hash2), HashingMethods.Calculate(new object[] { hash1, hash2 }));
|
||||
|
||||
ScriptableBuildPipeline.useV2Hasher = prevUseV2Hasher;
|
||||
}
|
||||
#else
|
||||
[Test]
|
||||
public void HashingMethods_Has128x2Fast_SameAsGeneric()
|
||||
{
|
||||
// Test ensures the HashingMethods.Calculate(Hash128, Hash128) fast path produces the same results as the general HashingMethods.Calculate(params object[] objects) one does
|
||||
Hash128 hash1 = new Hash128(0x1122334455667788, 0x99AABBCCDDEEFF00);
|
||||
Hash128 hash2 = new Hash128(0x123456789ABCDEF0, 0x1967AbC487Df2F12);
|
||||
|
||||
Assert.AreEqual(HashingMethods.Calculate(hash1, hash2), HashingMethods.Calculate(new object[] { hash1, hash2 }));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Struct that is binary compatible with Hash128 but is not Hash128
|
||||
struct Hash128Proxy
|
||||
{
|
||||
public uint m_Value0;
|
||||
public uint m_Value1;
|
||||
public uint m_Value2;
|
||||
public uint m_Value3;
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("TestCases")]
|
||||
public void HashingMethods_Hash128RawBytes_SameAsGeneric(IHasher hashFunc)
|
||||
{
|
||||
// Test ensures the HashingMethods.GetRawBytes() function's fast-path for Hash128 type produces the same results as generic but slower reflection based path it replaces
|
||||
Hash128 hash128 = new Hash128(0x11223344, 0x55667788, 0x99AABBCC, 0xDDEEFF00);
|
||||
|
||||
Hash128Proxy hash128Proxy = new Hash128Proxy() { m_Value0 = 0x11223344, m_Value1 = 0x55667788, m_Value2 = 0x99AABBCC, m_Value3 = 0xDDEEFF00 };
|
||||
|
||||
Assert.AreEqual(hashFunc.Calculate(hash128), hashFunc.Calculate(hash128Proxy));
|
||||
}
|
||||
|
||||
// Struct that is binary compatible with GUID but is not GUID
|
||||
struct GUIDProxy
|
||||
{
|
||||
public uint m_Value0;
|
||||
public uint m_Value1;
|
||||
public uint m_Value2;
|
||||
public uint m_Value3;
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("TestCases")]
|
||||
public void HashingMethods_GuidRawBytes_SameAsGeneric(IHasher hashFunc)
|
||||
{
|
||||
// Test ensures the HashingMethods.GetRawBytes() function's fast-path for GUID type produces the same results as generic but slower reflection based path it replaces
|
||||
GUID guid = new GUID("4433221188776655CCBBAA9900FFEEDD");
|
||||
|
||||
GUIDProxy guidProxy = new GUIDProxy() { m_Value0 = 0x11223344, m_Value1 = 0x55667788, m_Value2 = 0x99AABBCC, m_Value3 = 0xDDEEFF00 };
|
||||
|
||||
Assert.AreEqual(hashFunc.Calculate(guid), hashFunc.Calculate(guidProxy));
|
||||
}
|
||||
|
||||
public interface IHasher
|
||||
{
|
||||
Type HashType();
|
||||
RawHash Calculate(object obj);
|
||||
RawHash Calculate(params object[] objects);
|
||||
RawHash CalculateStream(Stream stream);
|
||||
}
|
||||
|
||||
public class HashTester<T> : IHasher where T : HashAlgorithm
|
||||
{
|
||||
public Type HashType() => typeof(T);
|
||||
|
||||
public RawHash Calculate(object obj) => HashingMethods.Calculate<T>(obj);
|
||||
|
||||
public RawHash Calculate(params object[] objects) => HashingMethods.Calculate<T>(objects);
|
||||
|
||||
public RawHash CalculateStream(Stream stream) => HashingMethods.CalculateStream<T>(stream);
|
||||
|
||||
public override string ToString() => $"{typeof(T).Name}";
|
||||
}
|
||||
|
||||
public enum TestIndex
|
||||
{
|
||||
Array,
|
||||
List,
|
||||
HashSet,
|
||||
Dictionary,
|
||||
Offset,
|
||||
Unicode,
|
||||
Identical
|
||||
}
|
||||
|
||||
public static Dictionary<Type, string[]> TestResults = new Dictionary<Type, string[]>
|
||||
{
|
||||
// TestResults format:
|
||||
// { Hashing Type,
|
||||
// new[] { "Array Hash ", "List Hash ", "HashSet Hash",
|
||||
// "Dictionary Hash", "Offset Hash", "Unicode Hash",
|
||||
// "Identical Hash " } }
|
||||
{ typeof(MD4),
|
||||
new[] { "99944412d5093e431ba7ccdaf48f44f3", "99944412d5093e431ba7ccdaf48f44f3", "99944412d5093e431ba7ccdaf48f44f3",
|
||||
"34392e04ec079d34cd861df956db2099", "086143d6671971fcdd40d96af36c0e92", "452f83421f98bf3831b7fd4217af3f92",
|
||||
"65de0f26e6502fa975d8ea0b79806517" } },
|
||||
{ typeof(MD5),
|
||||
new[] { "6d489a02294c1a5ce775050cfa2cd363", "6d489a02294c1a5ce775050cfa2cd363", "6d489a02294c1a5ce775050cfa2cd363",
|
||||
"2844dc4c3aa734b2cae0f4a670d5346e", "384a563d6a14deb7553f7efc95d1b67f", "c434697711429f8205b09c47bcd87d85",
|
||||
"5e5335125abe521354a9f9a7c302d690" } }
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
, { typeof(SpookyHash),
|
||||
new[] { "6e59a12bc07db93b5f9e6a0a4acecbd1", "6e59a12bc07db93b5f9e6a0a4acecbd1", "6e59a12bc07db93b5f9e6a0a4acecbd1",
|
||||
"bca5ae54cb78244bd544d06111694efb", "0fa373cc7984b0e05e7052fd7a7e51eb", "2e6baf5f29327f5ea5d668c988307232",
|
||||
"5d212d4d612906870257cb183932d1a7" } }
|
||||
#endif
|
||||
};
|
||||
|
||||
public static IEnumerable<IHasher> TestCases()
|
||||
{
|
||||
yield return new HashTester<MD4>();
|
||||
yield return new HashTester<MD5>();
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
yield return new HashTester<SpookyHash>();
|
||||
#endif
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("TestCases")]
|
||||
public void HashingMethods_ProducesValidHashFor_Array(IHasher hashFunc)
|
||||
{
|
||||
var sourceNames = new[]
|
||||
{
|
||||
"basic_sprite",
|
||||
"audio",
|
||||
"prefabs",
|
||||
"shaderwithcollection",
|
||||
"multi_sprite_packed"
|
||||
};
|
||||
|
||||
// Use (object) cast so Calculate doesn't expand the array and use params object[] objects case
|
||||
Assert.AreEqual(TestResults[hashFunc.HashType()][(int)TestIndex.Array], hashFunc.Calculate((object)sourceNames).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("TestCases")]
|
||||
public void HashingMethods_ProducesValidHashFor_List(IHasher hashFunc)
|
||||
{
|
||||
var sourceNames = new List<string>
|
||||
{
|
||||
"basic_sprite",
|
||||
"audio",
|
||||
"prefabs",
|
||||
"shaderwithcollection",
|
||||
"multi_sprite_packed"
|
||||
};
|
||||
|
||||
Assert.AreEqual(TestResults[hashFunc.HashType()][(int)TestIndex.List], hashFunc.Calculate(sourceNames).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("TestCases")]
|
||||
public void HashingMethods_ProducesValidHashFor_HashSet(IHasher hashFunc)
|
||||
{
|
||||
var sourceNames = new HashSet<string>
|
||||
{
|
||||
"basic_sprite",
|
||||
"audio",
|
||||
"prefabs",
|
||||
"shaderwithcollection",
|
||||
"multi_sprite_packed"
|
||||
};
|
||||
|
||||
Assert.AreEqual(TestResults[hashFunc.HashType()][(int)TestIndex.HashSet], hashFunc.Calculate(sourceNames).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("TestCases")]
|
||||
public void HashingMethods_ProducesValidHashFor_Dictionary(IHasher hashFunc)
|
||||
{
|
||||
var sourceNames = new Dictionary<string, string>
|
||||
{
|
||||
{ "basic_sprite", "audio" },
|
||||
{ "prefabs", "shaderwithcollection" }
|
||||
};
|
||||
|
||||
Assert.AreEqual(TestResults[hashFunc.HashType()][(int)TestIndex.Dictionary], hashFunc.Calculate(sourceNames).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("TestCases")]
|
||||
public void HashingMethods_ProducesValidHashFor_OffsetStreams(IHasher hashFunc)
|
||||
{
|
||||
byte[] bytes = { 0xe1, 0x43, 0x2f, 0x83, 0xdf, 0xeb, 0xa8, 0x86, 0xfb, 0xfe, 0xc9, 0x97, 0x20, 0xfb, 0x53, 0x45,
|
||||
0x24, 0x5d, 0x92, 0x8b, 0xa2, 0xc4, 0xe1, 0xe2, 0x48, 0x4a, 0xbb, 0x66, 0x43, 0x9a, 0xbc, 0x84 };
|
||||
|
||||
using (var stream = new MemoryStream(bytes))
|
||||
{
|
||||
stream.Position = 16;
|
||||
RawHash hash1 = hashFunc.CalculateStream(stream);
|
||||
|
||||
stream.Position = 0;
|
||||
RawHash hash2 = hashFunc.CalculateStream(stream);
|
||||
|
||||
Assert.AreNotEqual(hash1.ToString(), hash2.ToString());
|
||||
Assert.AreEqual(TestResults[hashFunc.HashType()][(int)TestIndex.Offset], hash1.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("TestCases")]
|
||||
public void HashingMethods_ProducesValidHashFor_UnicodeStrings(IHasher hashFunc)
|
||||
{
|
||||
// It might not look it at first glance, but the below 2 strings are indeed different!
|
||||
// The ASCII byte representation of both of these strings is identical, which was causing
|
||||
// hashing methods to return identical hashes as we had used ASCII for everything.
|
||||
string str1 = "[기본]양손무기";
|
||||
string str2 = "[기본]한손무기";
|
||||
RawHash hash1 = hashFunc.Calculate(str1);
|
||||
RawHash hash2 = hashFunc.Calculate(str2);
|
||||
|
||||
Assert.AreNotEqual(str1, str2);
|
||||
Assert.AreNotEqual(hash1, hash2);
|
||||
Assert.AreEqual(TestResults[hashFunc.HashType()][(int)TestIndex.Unicode], hash1.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("TestCases")]
|
||||
public void HashingMethods_ProducesValidHashFor_IdenticalCalculateCalls(IHasher hashFunc)
|
||||
{
|
||||
// This test seems silly, but has exposed issues with HashingMethods not being deterministic internally when run back to back (SpookyHash)
|
||||
var hash1 = hashFunc.Calculate("HashingMethods_ProducesValidHashFor_IdenticalCalculateCalls");
|
||||
var hash2 = hashFunc.Calculate("HashingMethods_ProducesValidHashFor_IdenticalCalculateCalls");
|
||||
Assert.IsTrue(hash1.Equals(hash2));
|
||||
Assert.AreEqual(TestResults[hashFunc.HashType()][(int)TestIndex.Identical], hash1.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d628170b66f4d154eb5dda6859439c32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,201 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class LinkXMLGeneratorTests
|
||||
{
|
||||
const string k_LinkFile = "link.xml";
|
||||
|
||||
[TearDown]
|
||||
public void OnTearDown()
|
||||
{
|
||||
if (File.Exists(k_LinkFile))
|
||||
File.Delete(k_LinkFile);
|
||||
}
|
||||
|
||||
public static string ReadLinkXML(string linkFile, out int assemblyCount, out int typeCount)
|
||||
{
|
||||
FileAssert.Exists(linkFile);
|
||||
var fileText = File.ReadAllText(linkFile);
|
||||
assemblyCount = Regex.Matches(fileText, "<assembly").Count;
|
||||
typeCount = Regex.Matches(fileText, "<type").Count;
|
||||
return fileText;
|
||||
}
|
||||
|
||||
public static void AssertTypePreserved(string input, Type t)
|
||||
{
|
||||
StringAssert.IsMatch($"type.*?{t.FullName}.*?preserve=\"all\"", input);
|
||||
}
|
||||
|
||||
public static void AssertTypeWithAttributePreserved(string input, string fullName)
|
||||
{
|
||||
StringAssert.IsMatch($"type.*?{fullName}.*? preserve=\"nothing\" serialized=\"true\"", input);
|
||||
}
|
||||
|
||||
public static void AssertAssemblyPreserved(string input, Assembly a)
|
||||
{
|
||||
StringAssert.IsMatch($"assembly.*?{a.FullName}.*?preserve=\"all\"", input);
|
||||
}
|
||||
|
||||
public static void AssertAssemblyFullName(string input, string assemblyName)
|
||||
{
|
||||
StringAssert.IsMatch($"assembly.*?{assemblyName}", input);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDefault_Converts_ExpectedUnityEditorTypes()
|
||||
{
|
||||
var types = LinkXmlGenerator.GetEditorTypeConversions();
|
||||
var editorTypes = types.Select(x => x.Key).ToArray();
|
||||
var runtimeTypes = types.Select(x => x.Value).ToArray();
|
||||
var assemblies = runtimeTypes.Select(x => x.Assembly).Distinct().ToArray();
|
||||
|
||||
var link = LinkXmlGenerator.CreateDefault();
|
||||
link.AddTypes(editorTypes);
|
||||
link.Save(k_LinkFile);
|
||||
|
||||
var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount);
|
||||
Assert.AreEqual(assemblyCount, assemblies.Length);
|
||||
Assert.AreEqual(typeCount, runtimeTypes.Length);
|
||||
foreach (var t in runtimeTypes)
|
||||
AssertTypePreserved(xml, t);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDefault_DoesNotConvert_UnexpectedUnityEditorTypes()
|
||||
{
|
||||
var unexpectedType = typeof(UnityEditor.BuildPipeline);
|
||||
|
||||
var link = LinkXmlGenerator.CreateDefault();
|
||||
link.AddTypes(new[] { unexpectedType });
|
||||
link.Save(k_LinkFile);
|
||||
|
||||
var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount);
|
||||
Assert.AreEqual(assemblyCount, 1);
|
||||
Assert.AreEqual(typeCount, 1);
|
||||
AssertTypePreserved(xml, unexpectedType);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LinkXML_Preserves_MultipleTypes_FromMultipleAssemblies()
|
||||
{
|
||||
var types = new[] { typeof(UnityEngine.MonoBehaviour), typeof(UnityEngine.Build.Pipeline.CompatibilityAssetBundleManifest) };
|
||||
|
||||
var link = new LinkXmlGenerator();
|
||||
link.AddTypes(types);
|
||||
link.Save(k_LinkFile);
|
||||
|
||||
var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount);
|
||||
Assert.AreEqual(assemblyCount, 2);
|
||||
Assert.AreEqual(typeCount, types.Length);
|
||||
foreach (var t in types)
|
||||
AssertTypePreserved(xml, t);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LinkXML_Preserves_Assemblies()
|
||||
{
|
||||
var assemblies = new[] { typeof(UnityEngine.MonoBehaviour).Assembly, typeof(UnityEngine.Build.Pipeline.CompatibilityAssetBundleManifest).Assembly };
|
||||
|
||||
var link = new LinkXmlGenerator();
|
||||
link.AddAssemblies(assemblies);
|
||||
link.Save(k_LinkFile);
|
||||
|
||||
var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount);
|
||||
Assert.AreEqual(assemblyCount, assemblies.Length);
|
||||
Assert.AreEqual(typeCount, 0);
|
||||
foreach (var a in assemblies)
|
||||
AssertAssemblyPreserved(xml, a);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void LinkXML_Preserves_SerializeClasses()
|
||||
{
|
||||
var serializedRefClasses = new[] { "FantasticAssembly:AwesomeNS.Foo", "FantasticAssembly:AwesomeNS.Bar", "SuperFantasticAssembly:SuperAwesomeNS.Bar"};
|
||||
|
||||
var link = new LinkXmlGenerator();
|
||||
link.AddSerializedClass(serializedRefClasses);
|
||||
link.Save(k_LinkFile);
|
||||
|
||||
var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount);
|
||||
Assert.AreEqual(assemblyCount, 2);
|
||||
AssertAssemblyFullName(xml, "FantasticAssembly");
|
||||
AssertAssemblyFullName(xml, "SuperFantasticAssembly");
|
||||
Assert.AreEqual(typeCount,3);
|
||||
AssertTypeWithAttributePreserved(xml, "AwesomeNS.Foo");
|
||||
AssertTypeWithAttributePreserved(xml, "AwesomeNS.Bar");
|
||||
AssertTypeWithAttributePreserved(xml, "SuperAwesomeNS.Bar");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LinkXML_Is_Sorted()
|
||||
{
|
||||
// For incremental builds to perform well, we need link xml files
|
||||
// to be deterministic - if contents don't change, they should be bit-wise
|
||||
// identical. So we sort contents, since HashSets/Dictionary have no deterministic order.
|
||||
// This test verfies that assemblies and classes are sorted alphabetically.
|
||||
// Note that types added via AddSerializedClass will always come after types and assemblies
|
||||
// added via AddType/AddAssembly, regardless of alphabetical order.
|
||||
var serializedRefClasses = new[]
|
||||
{
|
||||
"AssemblyC:NamespaceA.ClassA",
|
||||
"AssemblyB:NamespaceC.ClassB",
|
||||
"AssemblyA:NamespaceB.ClassC",
|
||||
"AssemblyC:NamespaceC.ClassC",
|
||||
"AssemblyB:NamespaceB.ClassA",
|
||||
"AssemblyA:NamespaceA.ClassB",
|
||||
"AssemblyA:NamespaceA.ClassC",
|
||||
"AssemblyC:NamespaceB.ClassA",
|
||||
"AssemblyB:NamespaceC.ClassA",
|
||||
};
|
||||
var types = new[] { typeof(UnityEngine.Texture), typeof(UnityEngine.MonoBehaviour) , typeof(UnityEngine.Build.Pipeline.CompatibilityAssetBundleManifest) };
|
||||
|
||||
var link = new LinkXmlGenerator();
|
||||
link.AddSerializedClass(serializedRefClasses);
|
||||
link.AddTypes(types);
|
||||
link.Save(k_LinkFile);
|
||||
|
||||
var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount)
|
||||
.Replace(" ", "")
|
||||
.Replace("\n", "")
|
||||
.Replace("\r", "");
|
||||
Console.WriteLine(xml);
|
||||
var sorted = @"
|
||||
<linker>
|
||||
<assembly fullname=""Unity.ScriptableBuildPipeline,Version=0.0.0.0,Culture=neutral,PublicKeyToken=null"">
|
||||
<type fullname=""UnityEngine.Build.Pipeline.CompatibilityAssetBundleManifest"" preserve=""all"" />
|
||||
</assembly>
|
||||
<assembly fullname=""UnityEngine.CoreModule,Version=0.0.0.0,Culture=neutral,PublicKeyToken=null"">
|
||||
<type fullname=""UnityEngine.MonoBehaviour"" preserve=""all"" />
|
||||
<type fullname=""UnityEngine.Texture"" preserve=""all"" />
|
||||
</assembly>
|
||||
<assembly fullname=""AssemblyA"">
|
||||
<type fullname=""NamespaceA.ClassB"" preserve=""nothing"" serialized=""true"" />
|
||||
<type fullname=""NamespaceA.ClassC"" preserve=""nothing"" serialized=""true"" />
|
||||
<type fullname=""NamespaceB.ClassC"" preserve=""nothing"" serialized=""true"" />
|
||||
</assembly>
|
||||
<assembly fullname=""AssemblyB"">
|
||||
<type fullname=""NamespaceB.ClassA"" preserve=""nothing"" serialized=""true"" />
|
||||
<type fullname=""NamespaceC.ClassA"" preserve=""nothing"" serialized=""true"" />
|
||||
<type fullname=""NamespaceC.ClassB"" preserve=""nothing"" serialized=""true"" />
|
||||
</assembly>
|
||||
<assembly fullname=""AssemblyC"">
|
||||
<type fullname=""NamespaceA.ClassA"" preserve=""nothing"" serialized=""true"" />
|
||||
<type fullname=""NamespaceB.ClassA"" preserve=""nothing"" serialized=""true"" />
|
||||
<type fullname=""NamespaceC.ClassC"" preserve=""nothing"" serialized=""true"" />
|
||||
</assembly>
|
||||
</linker>
|
||||
".Replace(" ", "").Replace("\n", "").Replace("\r", "");
|
||||
Assert.AreEqual(sorted, xml);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2030ed9ba38c2be4bbbc6a3499d8a4d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,9 @@
|
|||
using NUnit.Framework;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
// Test suite against Build Cache, tests are implemented in BuildCacheTestBase
|
||||
class LocalBuildCacheTests : BuildCacheTestBase
|
||||
{
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3bf61af2f16576547894347350205854
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,146 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
class LocalCacheServer : ScriptableSingleton<LocalCacheServer>
|
||||
{
|
||||
[SerializeField] public string m_path;
|
||||
[SerializeField] public int m_port;
|
||||
[SerializeField] public int m_pid = -1;
|
||||
|
||||
public static string Combine(params string[] components)
|
||||
{
|
||||
if (components.Length < 1)
|
||||
throw new ArgumentException("At least one component must be provided!");
|
||||
|
||||
var path1 = components[0];
|
||||
|
||||
for (var index = 1; index < components.Length; ++index)
|
||||
path1 = Path.Combine(path1, components[index]);
|
||||
|
||||
return path1;
|
||||
}
|
||||
|
||||
private void Create(int port, ulong size, string cachePath)
|
||||
{
|
||||
var nodeExecutable = Combine(EditorApplication.applicationContentsPath, "Tools", "nodejs");
|
||||
nodeExecutable = Application.platform == RuntimePlatform.WindowsEditor
|
||||
? Combine(nodeExecutable, "node.exe")
|
||||
: Combine(nodeExecutable, "bin", "node");
|
||||
|
||||
if (!Directory.Exists(cachePath))
|
||||
Directory.CreateDirectory(cachePath);
|
||||
|
||||
m_path = cachePath;
|
||||
|
||||
var cacheServerJs = Combine(EditorApplication.applicationContentsPath, "Tools", "CacheServer", "main.js");
|
||||
var processStartInfo = new ProcessStartInfo(nodeExecutable)
|
||||
{
|
||||
Arguments = "\"" + cacheServerJs + "\""
|
||||
+ " --port " + port
|
||||
+ " --path \"" + m_path
|
||||
+ "\" --nolegacy"
|
||||
+ " --monitor-parent-process " + Process.GetCurrentProcess().Id
|
||||
// node.js has issues running on windows with stdout not redirected.
|
||||
// so we silence logging to avoid that. And also to avoid CacheServer
|
||||
// spamming the editor logs on OS X.
|
||||
+ " --silent"
|
||||
+ " --size " + size,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
var p = new Process { StartInfo = processStartInfo };
|
||||
p.Start();
|
||||
|
||||
m_port = port;
|
||||
m_pid = p.Id;
|
||||
Save(true);
|
||||
}
|
||||
|
||||
public static string CachePath
|
||||
{
|
||||
get { return instance.m_path; }
|
||||
}
|
||||
|
||||
public static int Port
|
||||
{
|
||||
get { return instance.m_port; }
|
||||
}
|
||||
|
||||
public static void Setup(ulong size, string cachePath)
|
||||
{
|
||||
Kill();
|
||||
instance.Create(GetRandomUnusedPort(), size, cachePath);
|
||||
WaitForServerToComeAlive(instance.m_port);
|
||||
}
|
||||
|
||||
public static void Kill()
|
||||
{
|
||||
if (instance.m_pid == -1)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var p = Process.GetProcessById(instance.m_pid);
|
||||
p.Kill();
|
||||
instance.m_pid = -1;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// if we could not get a process, there is non alive. continue.
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
Kill();
|
||||
if (Directory.Exists(instance.m_path))
|
||||
Directory.Delete(instance.m_path, true);
|
||||
}
|
||||
|
||||
private static void WaitForServerToComeAlive(int port)
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
var maximum = start.AddSeconds(5);
|
||||
while (DateTime.Now < maximum)
|
||||
{
|
||||
if (!PingHost("localhost", port, 10)) continue;
|
||||
Console.WriteLine("Server Came alive after {0} ms", (DateTime.Now - start).TotalMilliseconds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetRandomUnusedPort()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Any, 0);
|
||||
listener.Start();
|
||||
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
listener.Stop();
|
||||
return port;
|
||||
}
|
||||
|
||||
private static bool PingHost(string host, int port, int timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
var result = client.BeginConnect(host, port, null, null);
|
||||
result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout));
|
||||
return client.Connected;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5b2102a42c1d8884989d74c98d4442b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,128 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Injector;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEditor.Build.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
class ObjectExtractionTaskTests
|
||||
{
|
||||
internal class ExtractionDependencyData : TestDependencyDataBase
|
||||
{
|
||||
Dictionary<GUID, AssetLoadInfo> m_AssetInfo = new Dictionary<GUID, AssetLoadInfo>();
|
||||
public override Dictionary<GUID, AssetLoadInfo> AssetInfo => m_AssetInfo;
|
||||
|
||||
Dictionary<GUID, SceneDependencyInfo> m_SceneInfo = new Dictionary<GUID, SceneDependencyInfo>();
|
||||
public override Dictionary<GUID, SceneDependencyInfo> SceneInfo => m_SceneInfo;
|
||||
}
|
||||
|
||||
internal class ExtractionObjectLayout : TestBundleExplictObjectLayout
|
||||
{
|
||||
Dictionary<ObjectIdentifier, string> m_ExplicitObjectLocation = new Dictionary<ObjectIdentifier, string>();
|
||||
public override Dictionary<ObjectIdentifier, string> ExplicitObjectLocation { get => m_ExplicitObjectLocation; }
|
||||
}
|
||||
|
||||
static ObjectIdentifier MakeObjectId(string guid, long localIdentifierInFile, FileType fileType, string filePath)
|
||||
{
|
||||
var objectId = new ObjectIdentifier();
|
||||
var boxed = (object)objectId;
|
||||
var type = typeof(ObjectIdentifier);
|
||||
type.GetField("m_GUID", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, new GUID(guid));
|
||||
type.GetField("m_LocalIdentifierInFile", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, localIdentifierInFile);
|
||||
type.GetField("m_FileType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, fileType);
|
||||
type.GetField("m_FilePath", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, filePath);
|
||||
return (ObjectIdentifier)boxed;
|
||||
}
|
||||
|
||||
static void TestSetup(out ObjectIdentifier nonMonoScriptId, out ObjectIdentifier monoScriptId, out CreateMonoScriptBundle task, out ExtractionDependencyData dependencyData, out ExtractionObjectLayout layout)
|
||||
{
|
||||
nonMonoScriptId = MakeObjectId(GUID.Generate().ToString(), 20, FileType.MetaAssetType, "");
|
||||
monoScriptId = MakeObjectId(GUID.Generate().ToString(), 50, FileType.MetaAssetType, "");
|
||||
task = new CreateMonoScriptBundle("MonoScriptBundle");
|
||||
dependencyData = new ExtractionDependencyData();
|
||||
layout = new ExtractionObjectLayout();
|
||||
|
||||
BuildCacheUtility.ClearCacheHashes();
|
||||
BuildCacheUtility.SetTypeForObjects(new[] {
|
||||
new ObjectTypes(nonMonoScriptId, new[] {typeof(GameObject) }),
|
||||
new ObjectTypes(monoScriptId, new[] {typeof(MonoScript) })
|
||||
});
|
||||
|
||||
IBuildContext context = new BuildContext(dependencyData, layout);
|
||||
ContextInjector.Inject(context, task);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateMonoScriptBundle_ExtractsMonoScripts_FromAssetInfo()
|
||||
{
|
||||
TestSetup(out ObjectIdentifier nonMonoScriptId, out ObjectIdentifier monoScriptId, out CreateMonoScriptBundle task, out ExtractionDependencyData dependencyData, out ExtractionObjectLayout layout);
|
||||
|
||||
var assetInfo = new AssetLoadInfo();
|
||||
assetInfo.referencedObjects = new List<ObjectIdentifier> { nonMonoScriptId, monoScriptId };
|
||||
dependencyData.AssetInfo.Add(GUID.Generate(), assetInfo);
|
||||
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(nonMonoScriptId));
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(monoScriptId));
|
||||
|
||||
task.Run();
|
||||
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(nonMonoScriptId));
|
||||
Assert.IsTrue(layout.ExplicitObjectLocation.ContainsKey(monoScriptId));
|
||||
Assert.AreEqual(task.MonoScriptBundleName, layout.ExplicitObjectLocation[monoScriptId]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateMonoScriptBundle_ExtractsMonoScripts_FromSceneInfo()
|
||||
{
|
||||
TestSetup(out ObjectIdentifier nonMonoScriptId, out ObjectIdentifier monoScriptId, out CreateMonoScriptBundle task, out ExtractionDependencyData dependencyData, out ExtractionObjectLayout layout);
|
||||
|
||||
var sceneInfo = new SceneDependencyInfo();
|
||||
sceneInfo.SetReferencedObjects(new[] { nonMonoScriptId, monoScriptId });
|
||||
dependencyData.SceneInfo.Add(GUID.Generate(), sceneInfo);
|
||||
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(nonMonoScriptId));
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(monoScriptId));
|
||||
|
||||
task.Run();
|
||||
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(nonMonoScriptId));
|
||||
Assert.IsTrue(layout.ExplicitObjectLocation.ContainsKey(monoScriptId));
|
||||
Assert.AreEqual(task.MonoScriptBundleName, layout.ExplicitObjectLocation[monoScriptId]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateMonoScriptBundle_DoesNotExtractMonoScripts_FromDefaultResources()
|
||||
{
|
||||
TestSetup(out ObjectIdentifier nonMonoScriptId, out ObjectIdentifier monoScriptId, out CreateMonoScriptBundle task, out ExtractionDependencyData dependencyData, out ExtractionObjectLayout layout);
|
||||
|
||||
var defaultResourceMonoScriptId = MakeObjectId(CommonStrings.UnityDefaultResourceGuid, 50, FileType.MetaAssetType, CommonStrings.UnityDefaultResourcePath);
|
||||
BuildCacheUtility.SetTypeForObjects(new[] {
|
||||
new ObjectTypes(defaultResourceMonoScriptId, new[] {typeof(MonoScript) })
|
||||
});
|
||||
|
||||
var assetInfo = new AssetLoadInfo();
|
||||
assetInfo.referencedObjects = new List<ObjectIdentifier> { nonMonoScriptId, monoScriptId, defaultResourceMonoScriptId };
|
||||
dependencyData.AssetInfo.Add(GUID.Generate(), assetInfo);
|
||||
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(nonMonoScriptId));
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(monoScriptId));
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(defaultResourceMonoScriptId));
|
||||
|
||||
task.Run();
|
||||
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(nonMonoScriptId));
|
||||
Assert.IsTrue(layout.ExplicitObjectLocation.ContainsKey(monoScriptId));
|
||||
Assert.IsFalse(layout.ExplicitObjectLocation.ContainsKey(defaultResourceMonoScriptId));
|
||||
Assert.AreEqual(task.MonoScriptBundleName, layout.ExplicitObjectLocation[monoScriptId]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9311edf98412ecc45a1f6ce3bb6a9fea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,209 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
class PrefabPackedStressTest
|
||||
{
|
||||
static ObjectIdentifier MakeObjectId(GUID guid, long localIdentifierInFile, FileType fileType, string filePath)
|
||||
{
|
||||
var objectId = new ObjectIdentifier();
|
||||
var boxed = (object)objectId;
|
||||
var type = typeof(ObjectIdentifier);
|
||||
type.GetField("m_GUID", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, guid);
|
||||
type.GetField("m_LocalIdentifierInFile", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, localIdentifierInFile);
|
||||
type.GetField("m_FileType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, fileType);
|
||||
type.GetField("m_FilePath", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(boxed, filePath);
|
||||
return (ObjectIdentifier)boxed;
|
||||
}
|
||||
|
||||
static long LongRandom(System.Random rand)
|
||||
{
|
||||
byte[] buffer = new byte[8];
|
||||
rand.NextBytes(buffer);
|
||||
return BitConverter.ToInt64(buffer, 0);
|
||||
}
|
||||
|
||||
static unsafe GUID GuidRandom(System.Random rand)
|
||||
{
|
||||
GUID guid = new GUID();
|
||||
byte[] bytes = new byte[16];
|
||||
rand.NextBytes(bytes);
|
||||
fixed (byte* ptr = &bytes[0])
|
||||
UnsafeUtility.MemCpy(&guid, ptr, bytes.Length);
|
||||
return guid;
|
||||
}
|
||||
|
||||
void SetupSBP(out bool prevV2Hasher, out int prevSeed, out int prevHeader, bool v2Hasher = false, int seed = 0, int header = 2)
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
prevV2Hasher = ScriptableBuildPipeline.useV2Hasher;
|
||||
ScriptableBuildPipeline.useV2Hasher = v2Hasher;
|
||||
#else
|
||||
prevV2Hasher = false;
|
||||
#endif
|
||||
prevSeed = ScriptableBuildPipeline.fileIDHashSeed;
|
||||
ScriptableBuildPipeline.fileIDHashSeed = seed;
|
||||
prevHeader = ScriptableBuildPipeline.prefabPackedHeaderSize;
|
||||
ScriptableBuildPipeline.prefabPackedHeaderSize = header;
|
||||
}
|
||||
|
||||
void ResetSBP(bool prevV2Hasher, int prevSeed, int prevHeader)
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
ScriptableBuildPipeline.useV2Hasher = prevV2Hasher;
|
||||
#endif
|
||||
ScriptableBuildPipeline.fileIDHashSeed = prevSeed;
|
||||
ScriptableBuildPipeline.prefabPackedHeaderSize = prevHeader;
|
||||
}
|
||||
|
||||
// We want to ensure 1 million objects in a single asset is collision free with the default implementations we provide users.
|
||||
static int LfidStressRunCount = 1000000;
|
||||
// We want to ensure 1 object in a 1 million assets is collision free with the default implementations we provide users.
|
||||
static int GuidStressRunCount = 1000000;
|
||||
// We want to ensure that we have a low number of cluster collisions in a bundle as this ensures better loading performance.
|
||||
// Ideally we want 0 collisions, but working with 64 bits in the way we do for build, that might be impossible without getting more collisions
|
||||
// in the full lfid generation which will break a build, vs just slow downloading slightly.
|
||||
static int BatchStressRunCount_Large = 10000;
|
||||
static int BatchStressRunCount_Small = 100;
|
||||
|
||||
static int RandomSeed = 131072;
|
||||
|
||||
static ScriptableBuildPipeline.Settings DefaultSettings = new ScriptableBuildPipeline.Settings();
|
||||
static object[] LfidStressCases =
|
||||
{
|
||||
new object[] { new PrefabPackedIdentifiers(), false, DefaultSettings.fileIDHashSeed, DefaultSettings.prefabPackedHeaderSize },
|
||||
new object[] { new PrefabPackedIdentifiers(), true, DefaultSettings.fileIDHashSeed, DefaultSettings.prefabPackedHeaderSize }
|
||||
};
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(LfidStressCases))]
|
||||
public void SerializationIndexFromObjectIdentifier_Lfid_CollisionFreeStressTest(IDeterministicIdentifiers packingMethod, bool useV2Hasher, int seed, int headerSize)
|
||||
{
|
||||
SetupSBP(out bool prevV2Hasher, out int prevSeed, out int prevHeader, useV2Hasher, seed, headerSize);
|
||||
|
||||
System.Random rand = new System.Random(RandomSeed);
|
||||
ObjectIdentifier objId = MakeObjectId(GuidRandom(rand), 3867712242362628071, FileType.MetaAssetType, "");
|
||||
Dictionary<long, ObjectIdentifier> consumedIds = new Dictionary<long, ObjectIdentifier>(LfidStressRunCount);
|
||||
for (int i = 0; i < LfidStressRunCount; i++)
|
||||
{
|
||||
objId.SetLocalIdentifierInFile(LongRandom(rand));
|
||||
long lfid = packingMethod.SerializationIndexFromObjectIdentifier(objId);
|
||||
if (consumedIds.TryGetValue(lfid, out var prevObjId))
|
||||
{
|
||||
if (objId == prevObjId)
|
||||
continue;
|
||||
else
|
||||
Assert.Fail($"{objId} with {prevObjId} at {lfid}");
|
||||
}
|
||||
|
||||
consumedIds.Add(lfid, objId);
|
||||
}
|
||||
|
||||
ResetSBP(prevV2Hasher, prevSeed, prevHeader);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(LfidStressCases))]
|
||||
public void SerializationIndexFromObjectIdentifier_GUID_CollisionFreeStressTest(IDeterministicIdentifiers packingMethod, bool useV2Hasher, int seed, int headerSize)
|
||||
{
|
||||
SetupSBP(out bool prevV2Hasher, out int prevSeed, out int prevHeader, useV2Hasher, seed, headerSize);
|
||||
|
||||
System.Random rand = new System.Random(RandomSeed);
|
||||
ObjectIdentifier objId = MakeObjectId(GuidRandom(rand), 3867712242362628071, FileType.MetaAssetType, "");
|
||||
Dictionary<long, ObjectIdentifier> consumedIds = new Dictionary<long, ObjectIdentifier>(GuidStressRunCount);
|
||||
for (int i = 0; i < GuidStressRunCount; i++)
|
||||
{
|
||||
objId.SetGuid(GuidRandom(rand));
|
||||
long lfid = packingMethod.SerializationIndexFromObjectIdentifier(objId);
|
||||
if (consumedIds.TryGetValue(lfid, out var prevObjId))
|
||||
{
|
||||
if (objId == prevObjId)
|
||||
continue;
|
||||
else
|
||||
Assert.Fail($"{objId} with {prevObjId} at {lfid}");
|
||||
}
|
||||
|
||||
consumedIds.Add(lfid, objId);
|
||||
}
|
||||
|
||||
ResetSBP(prevV2Hasher, prevSeed, prevHeader);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(LfidStressCases))]
|
||||
public void SerializationIndexFromObjectIdentifier_BatchingQualityStressTest_Large(IDeterministicIdentifiers packingMethod, bool useV2Hasher, int seed, int headerSize)
|
||||
{
|
||||
BatchingQualityStressTest(packingMethod, useV2Hasher, seed, headerSize, BatchStressRunCount_Large);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(LfidStressCases))]
|
||||
public void SerializationIndexFromObjectIdentifier_BatchingQualityStressTest_Small(IDeterministicIdentifiers packingMethod, bool useV2Hasher, int seed, int headerSize)
|
||||
{
|
||||
BatchingQualityStressTest(packingMethod, useV2Hasher, seed, headerSize, BatchStressRunCount_Small);
|
||||
}
|
||||
|
||||
void BatchingQualityStressTest(IDeterministicIdentifiers packingMethod, bool useV2Hasher, int seed, int headerSize, int runCount)
|
||||
{
|
||||
// This test is to check the quality of default clustering per source asset falls within certain guidelines
|
||||
// For 10,000 unique assets and 100 unique assets, we want to ensure that at most we see <0.1% (10)
|
||||
// assets generate the same cluster and <10% collision of all clusters.
|
||||
SetupSBP(out bool prevV2Hasher, out int prevSeed, out int prevHeader, useV2Hasher, seed, headerSize);
|
||||
|
||||
System.Random rand = new System.Random(RandomSeed);
|
||||
ObjectIdentifier objId = MakeObjectId(GuidRandom(rand), 3867712242362628071, FileType.MetaAssetType, "");
|
||||
Dictionary<int, int> clusters = new Dictionary<int, int>();
|
||||
for (int i = 0; i < runCount; i++)
|
||||
{
|
||||
objId.SetGuid(GuidRandom(rand));
|
||||
long lfid = packingMethod.SerializationIndexFromObjectIdentifier(objId);
|
||||
byte[] bytes = BitConverter.GetBytes(lfid);
|
||||
byte[] header = new byte[4];
|
||||
for (int j = 0; j < ScriptableBuildPipeline.prefabPackedHeaderSize; j++)
|
||||
header[4 - ScriptableBuildPipeline.prefabPackedHeaderSize + j] = bytes[j];
|
||||
|
||||
int cluster = BitConverter.ToInt32(header, 0);
|
||||
clusters.TryGetValue(cluster, out int count);
|
||||
clusters[cluster] = count + 1;
|
||||
}
|
||||
|
||||
int[] collisionValues = clusters.Values.Where(x => x > 1).ToArray();
|
||||
Array.Sort(collisionValues);
|
||||
int collisions = collisionValues.Length;
|
||||
// Maximum assets per cluster with multiple assets, lower is better
|
||||
int maxCollisions = collisionValues.Length > 0 ? collisionValues.Last() : 0;
|
||||
// Median assets per cluster with multiple assets, lower is better
|
||||
int medCollisions = collisionValues.Length > 0 ? collisionValues[collisionValues.Length / 2] : 0;
|
||||
Debug.Log($"Reused Clusters {collisions} ({(float)collisions/runCount*100:n2}%), Max {maxCollisions} ({(float)maxCollisions/runCount*100}%), Med {medCollisions} ({(float)medCollisions/runCount*100}%)");
|
||||
Assert.IsTrue(runCount * 0.1f > collisions, "Reused cluster count > 10%");
|
||||
Assert.IsTrue(runCount * 0.001f > maxCollisions, "Max per cluster reuse > 0.1%");
|
||||
|
||||
ResetSBP(prevV2Hasher, prevSeed, prevHeader);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateWriteCommand_ThrowsBuildException_OnCollision()
|
||||
{
|
||||
SetupSBP(out bool prevV2Hasher, out int prevSeed, out int prevHeader, false, 0, 4);
|
||||
|
||||
List<ObjectIdentifier> objectIds = new List<ObjectIdentifier>();
|
||||
objectIds.Add(MakeObjectId(new GUID("066ce95d52fe15041854096a2145195e"), 3867712242362628071, FileType.MetaAssetType, ""));
|
||||
objectIds.Add(MakeObjectId(new GUID("066ce95d52fe15041854096a2145195e"), 7498449973661844796, FileType.MetaAssetType, ""));
|
||||
|
||||
Assert.Throws(typeof(BuildFailedException), () => GenerateBundleCommands.CreateWriteCommand("InternalName", objectIds, new PrefabPackedIdentifiers()));
|
||||
|
||||
ResetSBP(prevV2Hasher, prevSeed, prevHeader);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 254069fb351a0914c94c4751c2ee9851
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,107 @@
|
|||
using UnityEditor.Build.Content;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
internal static class ReflectionExtentions
|
||||
{
|
||||
public static void SetFileName(this ref ResourceFile file, string filename)
|
||||
{
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
file.fileName = filename;
|
||||
#else
|
||||
var fieldInfo = typeof(ResourceFile).GetField("m_FileName", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = file;
|
||||
fieldInfo.SetValue(boxed, filename);
|
||||
file = (ResourceFile)boxed;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetFileAlias(this ref ResourceFile file, string fileAlias)
|
||||
{
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
file.fileAlias = fileAlias;
|
||||
#else
|
||||
var fieldInfo = typeof(ResourceFile).GetField("m_FileAlias", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = file;
|
||||
fieldInfo.SetValue(boxed, fileAlias);
|
||||
file = (ResourceFile)boxed;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetSerializedFile(this ref ResourceFile file, bool serializedFile)
|
||||
{
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
file.serializedFile = serializedFile;
|
||||
#else
|
||||
var fieldInfo = typeof(ResourceFile).GetField("m_SerializedFile", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = file;
|
||||
fieldInfo.SetValue(boxed, serializedFile);
|
||||
file = (ResourceFile)boxed;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetHeader(this ref ObjectSerializedInfo osi, SerializedLocation serializedLocation)
|
||||
{
|
||||
var fieldInfo = typeof(ObjectSerializedInfo).GetField("m_Header", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = osi;
|
||||
fieldInfo.SetValue(boxed, serializedLocation);
|
||||
osi = (ObjectSerializedInfo)boxed;
|
||||
}
|
||||
|
||||
public static void SetFileName(this ref SerializedLocation serializedLocation, string filename)
|
||||
{
|
||||
var fieldInfo = typeof(SerializedLocation).GetField("m_FileName", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = serializedLocation;
|
||||
fieldInfo.SetValue(boxed, filename);
|
||||
serializedLocation = (SerializedLocation)boxed;
|
||||
}
|
||||
|
||||
public static void SetOffset(this ref SerializedLocation serializedLocation, ulong offset)
|
||||
{
|
||||
var fieldInfo = typeof(SerializedLocation).GetField("m_Offset", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = serializedLocation;
|
||||
fieldInfo.SetValue(boxed, offset);
|
||||
serializedLocation = (SerializedLocation)boxed;
|
||||
}
|
||||
|
||||
public static void SetObjectIdentifier(this ref ObjectIdentifier obj, GUID guid, long localIdentifierInFile, FileType fileType, string filePath)
|
||||
{
|
||||
SetGuid(ref obj, guid);
|
||||
SetLocalIdentifierInFile(ref obj, localIdentifierInFile);
|
||||
SetFileType(ref obj, fileType);
|
||||
SetFilePath(ref obj, filePath);
|
||||
}
|
||||
|
||||
public static void SetGuid(this ref ObjectIdentifier obj, GUID guid)
|
||||
{
|
||||
var fieldInfo = typeof(ObjectIdentifier).GetField("m_GUID", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = obj;
|
||||
fieldInfo.SetValue(boxed, guid);
|
||||
obj = (ObjectIdentifier)boxed;
|
||||
}
|
||||
|
||||
public static void SetLocalIdentifierInFile(this ref ObjectIdentifier obj, long localIdentifierInFile)
|
||||
{
|
||||
var fieldInfo = typeof(ObjectIdentifier).GetField("m_LocalIdentifierInFile", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = obj;
|
||||
fieldInfo.SetValue(boxed, localIdentifierInFile);
|
||||
obj = (ObjectIdentifier)boxed;
|
||||
}
|
||||
|
||||
public static void SetFileType(this ref ObjectIdentifier obj, FileType fileType)
|
||||
{
|
||||
var fieldInfo = typeof(ObjectIdentifier).GetField("m_FileType", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = obj;
|
||||
fieldInfo.SetValue(boxed, fileType);
|
||||
obj = (ObjectIdentifier)boxed;
|
||||
}
|
||||
|
||||
public static void SetFilePath(this ref ObjectIdentifier obj, string filePath)
|
||||
{
|
||||
var fieldInfo = typeof(ObjectIdentifier).GetField("m_FilePath", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
object boxed = obj;
|
||||
fieldInfo.SetValue(boxed, filePath);
|
||||
obj = (ObjectIdentifier)boxed;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ee92aa36338b87445ac230ed769ea068
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,637 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Injector;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEditor.Build.Pipeline.WriteTypes;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Build.Pipeline;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
|
||||
class ScriptableBuildPipelineTests
|
||||
{
|
||||
const string k_FolderPath = "Test";
|
||||
const string k_TmpPath = "tmp";
|
||||
|
||||
const string k_ScenePath = "Assets/testScene.unity";
|
||||
const string k_TestAssetsPath = "Assets/TestAssetsOnlyWillBeDeleted";
|
||||
const string k_CubePath = k_TestAssetsPath + "/Cube.prefab";
|
||||
const string k_CubePath2 = k_TestAssetsPath + "/Cube2.prefab";
|
||||
|
||||
ScriptableBuildPipeline.Settings m_Settings;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
|
||||
Directory.CreateDirectory(k_TestAssetsPath);
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
PrefabUtility.SaveAsPrefabAsset(GameObject.CreatePrimitive(PrimitiveType.Cube), k_CubePath);
|
||||
PrefabUtility.SaveAsPrefabAsset(GameObject.CreatePrimitive(PrimitiveType.Cube), k_CubePath2);
|
||||
#else
|
||||
PrefabUtility.CreatePrefab(k_CubePath, GameObject.CreatePrimitive(PrimitiveType.Cube));
|
||||
PrefabUtility.CreatePrefab(k_CubePath2, GameObject.CreatePrimitive(PrimitiveType.Cube));
|
||||
#endif
|
||||
AssetDatabase.ImportAsset(k_CubePath);
|
||||
AssetDatabase.ImportAsset(k_CubePath2);
|
||||
|
||||
m_Settings = LoadSettingsFromFile();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void Cleanup()
|
||||
{
|
||||
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
|
||||
AssetDatabase.DeleteAsset(k_ScenePath);
|
||||
AssetDatabase.DeleteAsset(k_CubePath);
|
||||
AssetDatabase.DeleteAsset(k_CubePath2);
|
||||
AssetDatabase.DeleteAsset(k_TestAssetsPath);
|
||||
|
||||
if (Directory.Exists(k_FolderPath))
|
||||
Directory.Delete(k_FolderPath, true);
|
||||
if (Directory.Exists(k_TmpPath))
|
||||
Directory.Delete(k_TmpPath, true);
|
||||
|
||||
ScriptableBuildPipeline.s_Settings = m_Settings;
|
||||
ScriptableBuildPipeline.SaveSettings();
|
||||
}
|
||||
|
||||
static ReturnCode RunTask<T>(params IContextObject[] args) where T : IBuildTask
|
||||
{
|
||||
IBuildContext context = new BuildContext(args);
|
||||
IBuildTask instance = Activator.CreateInstance<T>();
|
||||
ContextInjector.Inject(context, instance);
|
||||
var result = instance.Run();
|
||||
ContextInjector.Extract(context, instance);
|
||||
return result;
|
||||
}
|
||||
|
||||
static IBundleBuildParameters GetBuildParameters()
|
||||
{
|
||||
if (Directory.Exists(k_FolderPath))
|
||||
Directory.Delete(k_FolderPath, true);
|
||||
if (Directory.Exists(k_TmpPath))
|
||||
Directory.Delete(k_TmpPath, true);
|
||||
|
||||
Directory.CreateDirectory(k_FolderPath);
|
||||
Directory.CreateDirectory(k_TmpPath);
|
||||
|
||||
IBundleBuildParameters buildParams = new BundleBuildParameters(EditorUserBuildSettings.activeBuildTarget, EditorUserBuildSettings.selectedBuildTargetGroup, k_FolderPath);
|
||||
buildParams.TempOutputFolder = k_TmpPath;
|
||||
return buildParams;
|
||||
}
|
||||
|
||||
static AssetBundleBuild CreateBundleBuild(string name, string path)
|
||||
{
|
||||
return new AssetBundleBuild()
|
||||
{
|
||||
addressableNames = new[] { path },
|
||||
assetBundleName = name,
|
||||
assetBundleVariant = "",
|
||||
assetNames = new[] { path }
|
||||
};
|
||||
}
|
||||
|
||||
static IBundleBuildContent GetBundleContent(bool createAllBundles = false)
|
||||
{
|
||||
List<AssetBundleBuild> buildData = new List<AssetBundleBuild>();
|
||||
buildData.Add(CreateBundleBuild("bundle", k_CubePath));
|
||||
if (createAllBundles)
|
||||
buildData.Add(CreateBundleBuild("bundle2", k_CubePath2));
|
||||
|
||||
IBundleBuildContent buildContent = new BundleBuildContent(buildData);
|
||||
return buildContent;
|
||||
}
|
||||
|
||||
static IDependencyData GetDependencyData()
|
||||
{
|
||||
GUID guid;
|
||||
GUID.TryParse(AssetDatabase.AssetPathToGUID(k_CubePath), out guid);
|
||||
ObjectIdentifier[] oId = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(guid, EditorUserBuildSettings.activeBuildTarget);
|
||||
AssetLoadInfo loadInfo = new AssetLoadInfo()
|
||||
{
|
||||
asset = guid,
|
||||
address = k_CubePath,
|
||||
includedObjects = oId.ToList(),
|
||||
referencedObjects = oId.ToList()
|
||||
};
|
||||
|
||||
IDependencyData dep = new BuildDependencyData();
|
||||
dep.AssetInfo.Add(guid, loadInfo);
|
||||
dep.AssetUsage.Add(guid, new BuildUsageTagSet());
|
||||
|
||||
return dep;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator BuildPipeline_AssetBundleBuild_DoesNotResetUnsavedScene()
|
||||
{
|
||||
Scene s = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
|
||||
yield return null;
|
||||
EditorSceneManager.SaveScene(s, k_ScenePath);
|
||||
GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
EditorSceneManager.MarkSceneDirty(s);
|
||||
|
||||
GameObject objectWeAdded = GameObject.Find("Cube");
|
||||
Assert.IsNotNull(objectWeAdded, "No object before entering playmode");
|
||||
Assert.AreEqual("testScene", SceneManager.GetActiveScene().name);
|
||||
|
||||
IBundleBuildParameters buildParameters = GetBuildParameters();
|
||||
IBundleBuildContent buildContent = GetBundleContent();
|
||||
IBundleBuildResults results;
|
||||
|
||||
ReturnCode exitCode = ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out results);
|
||||
Assert.AreEqual(ReturnCode.UnsavedChanges, exitCode);
|
||||
|
||||
Assert.AreEqual("testScene", SceneManager.GetActiveScene().name);
|
||||
objectWeAdded = GameObject.Find("Cube");
|
||||
Assert.IsNotNull(objectWeAdded, "No object after entering playmode");
|
||||
|
||||
EditorSceneManager.SaveScene(s, k_ScenePath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildPipeline_AssetBundleBuild_WritesLinkXMLFile()
|
||||
{
|
||||
IBundleBuildParameters buildParameters = GetBuildParameters();
|
||||
buildParameters.WriteLinkXML = true;
|
||||
IBundleBuildContent buildContent = GetBundleContent();
|
||||
IBundleBuildResults results;
|
||||
|
||||
ReturnCode exitCode = ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out results);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
|
||||
|
||||
var assemblies = new HashSet<Assembly>();
|
||||
var types = new HashSet<Type>();
|
||||
foreach (var writeResult in results.WriteResults)
|
||||
{
|
||||
foreach (var type in writeResult.Value.includedTypes)
|
||||
{
|
||||
assemblies.Add(type.Assembly);
|
||||
types.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
var xml = LinkXMLGeneratorTests.ReadLinkXML(buildParameters.GetOutputFilePathForIdentifier("link.xml"), out int assemblyCount, out int typeCount);
|
||||
Assert.AreEqual(assemblyCount, assemblies.Count);
|
||||
Assert.AreEqual(typeCount, types.Count);
|
||||
foreach (var t in types)
|
||||
LinkXMLGeneratorTests.AssertTypePreserved(xml, t);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildPipeline_CompatibilityBuildPipeline_WritesManifest()
|
||||
{
|
||||
// Confirm that calling CompatibilityBuildPipeline.BuildAssetBundles generates a .manifest file similar to the top level file generated by BuildPipeline.BuildAssetBundles
|
||||
|
||||
var buildParameters = GetBuildParameters() as BundleBuildParameters;
|
||||
string outputPath = buildParameters.OutputFolder;
|
||||
|
||||
var bundleDefinitions = new AssetBundleBuild[2];
|
||||
bundleDefinitions[0] = CreateBundleBuild("bundle", k_CubePath);
|
||||
bundleDefinitions[1] = CreateBundleBuild("bundle2", k_CubePath2);
|
||||
|
||||
CompatibilityAssetBundleManifest manifestObject = CompatibilityBuildPipeline.BuildAssetBundles(
|
||||
outputPath,
|
||||
bundleDefinitions, BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.ForceRebuildAssetBundle,
|
||||
buildParameters.Target);
|
||||
Assert.IsNotNull(manifestObject);
|
||||
|
||||
// .manifest file is created with name that matches the output build directory name
|
||||
var buildDirectoryName = Path.GetFileName(k_FolderPath);
|
||||
var expectedManifestPath = outputPath + "/" + buildDirectoryName + ".manifest";
|
||||
|
||||
// GetOutputFilePathForIdentifier is an alternative way to calculate the expected output path
|
||||
Assert.AreEqual(buildParameters.GetOutputFilePathForIdentifier(buildDirectoryName + ".manifest"), expectedManifestPath);
|
||||
|
||||
FileAssert.Exists(expectedManifestPath);
|
||||
|
||||
// Confirm the .manifest file contains the expected content
|
||||
string manifestContent = File.ReadAllText(expectedManifestPath);
|
||||
string expectedManifestContent = manifestObject.ToString();
|
||||
Assert.AreEqual(expectedManifestContent, manifestContent);
|
||||
|
||||
// Sanity check a few items of expected content
|
||||
Assert.IsTrue(manifestContent.Contains("bundle"));
|
||||
Assert.IsTrue(manifestContent.Contains("bundle2"));
|
||||
Assert.IsTrue(manifestContent.Contains("CRC:"));
|
||||
Assert.IsTrue(manifestContent.Contains("Hash:"));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ValidationMethods_HasDirtyScenes()
|
||||
{
|
||||
Scene s = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
|
||||
yield return null;
|
||||
|
||||
bool dirty = ValidationMethods.HasDirtyScenes();
|
||||
Assert.IsFalse(dirty);
|
||||
|
||||
EditorSceneManager.MarkSceneDirty(s);
|
||||
|
||||
dirty = ValidationMethods.HasDirtyScenes();
|
||||
Assert.IsTrue(dirty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultBuildTasks_WriteSerializedFiles()
|
||||
{
|
||||
IBuildParameters buildParams = GetBuildParameters();
|
||||
IDependencyData dependencyData = new BuildDependencyData();
|
||||
IWriteData writeData = new BuildWriteData();
|
||||
IBuildResults results = new BuildResults();
|
||||
|
||||
ReturnCode exitCode = RunTask<WriteSerializedFiles>(buildParams, dependencyData, writeData, results);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultBuildTasks_GenerateBundlePacking()
|
||||
{
|
||||
IBundleBuildContent buildContent = GetBundleContent();
|
||||
IDependencyData dep = GetDependencyData();
|
||||
IBundleWriteData writeData = new BundleWriteData();
|
||||
IDeterministicIdentifiers deterministicId = new PrefabPackedIdentifiers();
|
||||
|
||||
ReturnCode exitCode = RunTask<GenerateBundlePacking>(buildContent, dep, writeData, deterministicId);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultBuildTasks_GenerateBundleCommands()
|
||||
{
|
||||
IBundleBuildContent buildContent = GetBundleContent();
|
||||
IDependencyData dep = GetDependencyData();
|
||||
IBundleWriteData writeData = new BundleWriteData();
|
||||
IDeterministicIdentifiers deterministicId = new PrefabPackedIdentifiers();
|
||||
|
||||
RunTask<GenerateBundlePacking>(buildContent, dep, writeData, deterministicId);
|
||||
|
||||
IBundleBuildParameters buildParams = GetBuildParameters();
|
||||
|
||||
ReturnCode exitCode = RunTask<GenerateBundleCommands>(buildParams, buildContent, dep, writeData, deterministicId);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultBuildTasks_GenerateBundleMaps()
|
||||
{
|
||||
IDependencyData dep = GetDependencyData();
|
||||
IBundleWriteData writeData = new BundleWriteData();
|
||||
|
||||
ReturnCode exitCode = RunTask<GenerateBundleMaps>(dep, writeData);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultBuildTasks_PostPackingCallback()
|
||||
{
|
||||
bool packingCallbackCalled = false;
|
||||
|
||||
IBuildParameters buildParams = GetBuildParameters();
|
||||
IDependencyData dep = GetDependencyData();
|
||||
IBundleWriteData writeData = new BundleWriteData();
|
||||
BuildCallbacks callback = new BuildCallbacks();
|
||||
callback.PostPackingCallback = (parameters, data, arg3) =>
|
||||
{
|
||||
packingCallbackCalled = true;
|
||||
return ReturnCode.Success;
|
||||
};
|
||||
|
||||
ReturnCode exitCode = RunTask<PostPackingCallback>(buildParams, dep, writeData, callback);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
Assert.IsTrue(packingCallbackCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultBuildTasks_PostWritingCallback()
|
||||
{
|
||||
bool writingCallbackCalled = false;
|
||||
|
||||
IBuildParameters buildParams = GetBuildParameters();
|
||||
IDependencyData dep = GetDependencyData();
|
||||
IWriteData writeData = new BuildWriteData();
|
||||
IBuildResults results = new BuildResults();
|
||||
BuildCallbacks callback = new BuildCallbacks();
|
||||
callback.PostWritingCallback = (parameters, data, arg3, arg4) =>
|
||||
{
|
||||
writingCallbackCalled = true;
|
||||
return ReturnCode.Success;
|
||||
};
|
||||
|
||||
ReturnCode exitCode = RunTask<PostWritingCallback>(buildParams, dep, writeData, results, callback);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
Assert.IsTrue(writingCallbackCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultBuildTasks_PostDependencyCallback()
|
||||
{
|
||||
bool dependencyCallbackCalled = false;
|
||||
|
||||
IBuildParameters buildParameters = GetBuildParameters();
|
||||
IDependencyData dep = GetDependencyData();
|
||||
BuildCallbacks callback = new BuildCallbacks();
|
||||
callback.PostDependencyCallback = (parameters, data) =>
|
||||
{
|
||||
dependencyCallbackCalled = true;
|
||||
return ReturnCode.Success;
|
||||
};
|
||||
|
||||
ReturnCode exitCode = RunTask<PostDependencyCallback>(buildParameters, dep, callback);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
Assert.IsTrue(dependencyCallbackCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultBuildTasks_PostScriptsCallbacks()
|
||||
{
|
||||
bool scriptsCallbackCalled = false;
|
||||
|
||||
IBuildParameters buildParameters = GetBuildParameters();
|
||||
IBuildResults results = new BuildResults();
|
||||
BuildCallbacks callback = new BuildCallbacks();
|
||||
callback.PostScriptsCallbacks = (parameters, buildResults) =>
|
||||
{
|
||||
scriptsCallbackCalled = true;
|
||||
return ReturnCode.Success;
|
||||
};
|
||||
|
||||
ReturnCode exitCode = RunTask<PostScriptsCallback>(buildParameters, results, callback);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
Assert.IsTrue(scriptsCallbackCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultBuildTasks_AppendBundleHash()
|
||||
{
|
||||
IBundleBuildParameters buildParameters = GetBuildParameters();
|
||||
buildParameters.AppendHash = true;
|
||||
var fileName = k_FolderPath + "/TestBundle";
|
||||
var fileHash = HashingMethods.Calculate(fileName).ToHash128();
|
||||
File.WriteAllText(fileName, fileName);
|
||||
IBundleBuildResults results = new BundleBuildResults();
|
||||
results.BundleInfos["TestBundle"] = new BundleDetails
|
||||
{
|
||||
Crc = 0,
|
||||
FileName = fileName,
|
||||
Hash = fileHash
|
||||
};
|
||||
|
||||
ReturnCode exitCode = RunTask<AppendBundleHash>(buildParameters, results);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
FileAssert.Exists(fileName + "_" + fileHash);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SceneDataWriteOperation_HashChanges_WhenPrefabDepenencyChanges()
|
||||
{
|
||||
Scene s = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
|
||||
yield return null;
|
||||
|
||||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(k_CubePath);
|
||||
prefab.transform.position = new Vector3(0, 0, 0);
|
||||
EditorUtility.SetDirty(prefab);
|
||||
AssetDatabase.SaveAssets();
|
||||
PrefabUtility.InstantiatePrefab(prefab);
|
||||
|
||||
EditorSceneManager.SaveScene(s, k_ScenePath);
|
||||
|
||||
var op = new SceneDataWriteOperation
|
||||
{
|
||||
Command = new WriteCommand(),
|
||||
PreloadInfo = new PreloadInfo(),
|
||||
#if !UNITY_2019_3_OR_NEWER
|
||||
ProcessedScene = k_ScenePath,
|
||||
#endif
|
||||
ReferenceMap = new BuildReferenceMap(),
|
||||
UsageSet = new BuildUsageTagSet(),
|
||||
Scene = k_ScenePath,
|
||||
DependencyHash = AssetDatabase.GetAssetDependencyHash(k_CubePath)
|
||||
};
|
||||
var cacheVersion1 = op.GetHash128();
|
||||
|
||||
prefab.transform.position = new Vector3(1, 1, 1);
|
||||
EditorUtility.SetDirty(prefab);
|
||||
AssetDatabase.SaveAssets();
|
||||
op.DependencyHash = AssetDatabase.GetAssetDependencyHash(k_CubePath);
|
||||
var cacheVersion2 = op.GetHash128();
|
||||
|
||||
Assert.AreNotEqual(cacheVersion1, cacheVersion2);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SceneBundleWriteOperation_HashChanges_WhenPrefabDepenencyChanges()
|
||||
{
|
||||
Scene s = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
|
||||
yield return null;
|
||||
|
||||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(k_CubePath);
|
||||
prefab.transform.position = new Vector3(0, 0, 0);
|
||||
EditorUtility.SetDirty(prefab);
|
||||
AssetDatabase.SaveAssets();
|
||||
PrefabUtility.InstantiatePrefab(prefab);
|
||||
|
||||
EditorSceneManager.SaveScene(s, k_ScenePath);
|
||||
|
||||
var op = new SceneBundleWriteOperation
|
||||
{
|
||||
Command = new WriteCommand(),
|
||||
PreloadInfo = new PreloadInfo(),
|
||||
#if !UNITY_2019_3_OR_NEWER
|
||||
ProcessedScene = k_ScenePath,
|
||||
#endif
|
||||
ReferenceMap = new BuildReferenceMap(),
|
||||
UsageSet = new BuildUsageTagSet(),
|
||||
Info = new SceneBundleInfo(),
|
||||
Scene = k_ScenePath,
|
||||
DependencyHash = AssetDatabase.GetAssetDependencyHash(k_CubePath)
|
||||
};
|
||||
var cacheVersion1 = op.GetHash128();
|
||||
|
||||
prefab.transform.position = new Vector3(1, 1, 1);
|
||||
EditorUtility.SetDirty(prefab);
|
||||
AssetDatabase.SaveAssets();
|
||||
op.DependencyHash = AssetDatabase.GetAssetDependencyHash(k_CubePath);
|
||||
var cacheVersion2 = op.GetHash128();
|
||||
|
||||
Assert.AreNotEqual(cacheVersion1, cacheVersion2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildAssetBundles_WhenNoBuildInContextLog_CreatesPerformanceLogReport()
|
||||
{
|
||||
IBundleBuildParameters buildParameters = GetBuildParameters();
|
||||
IBundleBuildContent buildContent = GetBundleContent();
|
||||
IBundleBuildResults results;
|
||||
|
||||
buildParameters.Group = EditorUserBuildSettings.selectedBuildTargetGroup;
|
||||
|
||||
ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out results);
|
||||
|
||||
string tepBuildLog = buildParameters.GetOutputFilePathForIdentifier("buildlogtep.json");
|
||||
FileAssert.Exists(tepBuildLog);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildAssetBundles_WhenBuildLogProvided_DoesNotCreatePerformanceLogReport()
|
||||
{
|
||||
IBundleBuildParameters buildParameters = GetBuildParameters();
|
||||
IBundleBuildContent buildContent = GetBundleContent();
|
||||
IBundleBuildResults results;
|
||||
|
||||
var taskList = DefaultBuildTasks.Create(DefaultBuildTasks.Preset.AssetBundleCompatible);
|
||||
ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out results, taskList, new BuildLog());
|
||||
|
||||
string tepBuildLog = buildParameters.GetOutputFilePathForIdentifier("buildlogtep.json");
|
||||
FileAssert.DoesNotExist(tepBuildLog);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildParameters_SetsBuildCacheServerParameters_WhenUseBuildCacheServerEnabled()
|
||||
{
|
||||
int port = 9999;
|
||||
string host = "fake host";
|
||||
|
||||
using (new StoreCacheServerConfig(true, host, port))
|
||||
{
|
||||
IBundleBuildParameters buildParameters = GetBuildParameters();
|
||||
|
||||
Assert.AreEqual(port, buildParameters.CacheServerPort);
|
||||
Assert.AreEqual(host, buildParameters.CacheServerHost);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildParameters_DoesNotSetBuildCacheServerParameters_WhenUseBuildCacheServerDisabled()
|
||||
{
|
||||
int port = 9999;
|
||||
string host = "testHost";
|
||||
|
||||
using (new StoreCacheServerConfig(false, host, port))
|
||||
{
|
||||
IBundleBuildParameters buildParameters = GetBuildParameters();
|
||||
|
||||
Assert.AreEqual(8126, buildParameters.CacheServerPort);
|
||||
Assert.AreEqual(null, buildParameters.CacheServerHost);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildAssetBundles_WithCache_Succeeds()
|
||||
{
|
||||
IBundleBuildParameters buildParameters = GetBuildParameters();
|
||||
IBundleBuildContent buildContent = GetBundleContent(true);
|
||||
|
||||
ReturnCode exitCode = ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out IBundleBuildResults results);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildAssetBundles_WithoutCache_Succeeds()
|
||||
{
|
||||
IBundleBuildParameters buildParameters = GetBuildParameters();
|
||||
buildParameters.UseCache = false;
|
||||
IBundleBuildContent buildContent = GetBundleContent(true);
|
||||
|
||||
ReturnCode exitCode = ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out IBundleBuildResults results);
|
||||
Assert.AreEqual(ReturnCode.Success, exitCode);
|
||||
}
|
||||
|
||||
ScriptableBuildPipeline.Settings LoadSettingsFromFile()
|
||||
{
|
||||
var settings = new ScriptableBuildPipeline.Settings();
|
||||
if (File.Exists(ScriptableBuildPipeline.kSettingPath))
|
||||
{
|
||||
var json = File.ReadAllText(ScriptableBuildPipeline.kSettingPath);
|
||||
EditorJsonUtility.FromJsonOverwrite(json, settings);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
void AssertSettingsChanged(ScriptableBuildPipeline.Settings preSettings, ScriptableBuildPipeline.Settings postSettings, FieldInfo[] fields, int fieldChanged)
|
||||
{
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
if (i == fieldChanged)
|
||||
Assert.AreNotEqual(fields[i].GetValue(preSettings), fields[i].GetValue(postSettings), $"Unexpected field '{fields[i].Name}' value is unchanged.");
|
||||
else
|
||||
Assert.AreEqual(fields[i].GetValue(preSettings), fields[i].GetValue(postSettings), $"Unexpected field '{fields[i].Name}' value is changed.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PreferencesProperties_ChangesSerializeToDisk()
|
||||
{
|
||||
PropertyInfo[] properties = typeof(ScriptableBuildPipeline).GetProperties(BindingFlags.Public | BindingFlags.Static);
|
||||
FieldInfo[] fields = typeof(ScriptableBuildPipeline.Settings).GetFields(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
{
|
||||
var preSettings = LoadSettingsFromFile();
|
||||
|
||||
if (properties[i].PropertyType == typeof(bool))
|
||||
{
|
||||
bool previousValue = (bool)fields[i].GetValue(preSettings);
|
||||
properties[i].SetValue(null, !previousValue);
|
||||
}
|
||||
else if (properties[i].PropertyType == typeof(int))
|
||||
{
|
||||
int previousValue = (int)fields[i].GetValue(preSettings);
|
||||
properties[i].SetValue(null, previousValue + 5);
|
||||
}
|
||||
else if (properties[i].PropertyType == typeof(string))
|
||||
{
|
||||
string previousValue = (string)fields[i].GetValue(preSettings);
|
||||
properties[i].SetValue(null, previousValue + "Test");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail($"Unhandled property type '{properties[i].PropertyType.Name}' in test '{nameof(PreferencesProperties_ChangesSerializeToDisk)}'");
|
||||
}
|
||||
|
||||
AssertSettingsChanged(preSettings, LoadSettingsFromFile(), fields, i);
|
||||
}
|
||||
}
|
||||
|
||||
internal class StoreCacheServerConfig : IDisposable
|
||||
{
|
||||
private bool m_StoredUseServerFlag;
|
||||
private string m_StoredServerHost;
|
||||
private int m_UseServerPort;
|
||||
|
||||
public StoreCacheServerConfig(bool useCacheServer, string host, int port)
|
||||
{
|
||||
m_StoredUseServerFlag = ScriptableBuildPipeline.UseBuildCacheServer;
|
||||
ScriptableBuildPipeline.UseBuildCacheServer = useCacheServer;
|
||||
|
||||
m_StoredServerHost = ScriptableBuildPipeline.CacheServerHost;
|
||||
ScriptableBuildPipeline.CacheServerHost = host;
|
||||
|
||||
m_UseServerPort = ScriptableBuildPipeline.CacheServerPort;
|
||||
ScriptableBuildPipeline.CacheServerPort = port;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ScriptableBuildPipeline.UseBuildCacheServer = m_StoredUseServerFlag;
|
||||
ScriptableBuildPipeline.CacheServerHost = m_StoredServerHost;
|
||||
ScriptableBuildPipeline.CacheServerPort = m_UseServerPort;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 170ac2c906650624cbab749adb2cc2ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,194 @@
|
|||
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<T> : IRunCachedCallbacks<T>
|
||||
{
|
||||
public int CreateCachedInfoCount;
|
||||
public Func<WorkItem<T>, CachedInfo> CreateCachedInfoCB;
|
||||
public int CreateCacheEntryCount;
|
||||
public Func<WorkItem<T>, CacheEntry> CreateCacheEntryCB;
|
||||
public int PostProcessCount;
|
||||
public Action<WorkItem<T>> PostProcessCB;
|
||||
public int ProcessCachedCount;
|
||||
public Action<WorkItem<T>, CachedInfo> ProcessCachedCB;
|
||||
public int ProcessUncachedCount;
|
||||
public Action<WorkItem<T>> ProcessUncachedCB;
|
||||
|
||||
public void ClearCounts()
|
||||
{
|
||||
CreateCachedInfoCount = 0;
|
||||
CreateCacheEntryCount = 0;
|
||||
PostProcessCount = 0;
|
||||
ProcessCachedCount = 0;
|
||||
ProcessUncachedCount = 0;
|
||||
}
|
||||
|
||||
public CachedInfo CreateCachedInfo(WorkItem<T> item)
|
||||
{
|
||||
CreateCachedInfoCount++;
|
||||
return CreateCachedInfoCB(item);
|
||||
}
|
||||
|
||||
public CacheEntry CreateCacheEntry(WorkItem<T> item)
|
||||
{
|
||||
CreateCacheEntryCount++;
|
||||
return CreateCacheEntryCB(item);
|
||||
}
|
||||
|
||||
public void PostProcess(WorkItem<T> item)
|
||||
{
|
||||
PostProcessCount++;
|
||||
PostProcessCB(item);
|
||||
}
|
||||
|
||||
public void ProcessCached(WorkItem<T> item, CachedInfo info)
|
||||
{
|
||||
ProcessCachedCount++;
|
||||
ProcessCachedCB(item, info);
|
||||
}
|
||||
|
||||
public void ProcessUncached(WorkItem<T> item)
|
||||
{
|
||||
ProcessUncachedCount++;
|
||||
ProcessUncachedCB(item);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RunCachedOperation_WhenSomeItemsCached_UncachedItemsAreProcessed()
|
||||
{
|
||||
Func<int, int> transform = (x) => x * 27;
|
||||
const int kFirstPassCount = 5;
|
||||
const int kSecondPassCount = 12;
|
||||
|
||||
Dictionary<int, int> indexToResult = new Dictionary<int, int>();
|
||||
|
||||
Action<int> AssertResults = (count) =>
|
||||
{
|
||||
Assert.AreEqual(count, indexToResult.Count);
|
||||
indexToResult.Keys.ToList().ForEach(x => Assert.AreEqual(transform(x), indexToResult[x]));
|
||||
};
|
||||
|
||||
List<int> pass1Inputs = Enumerable.Range(0, kFirstPassCount).Select(x => x * 2).ToList();
|
||||
List<int> pass2Inputs = Enumerable.Range(0, kSecondPassCount).ToList();
|
||||
|
||||
List<WorkItem<ItemContext>> workerInput1 =
|
||||
pass1Inputs.Select(i => new WorkItem<ItemContext>(new ItemContext(i))).ToList();
|
||||
|
||||
List<WorkItem<ItemContext>> workerInput2 =
|
||||
pass2Inputs.Select(i => new WorkItem<ItemContext>(new ItemContext(i))).ToList();
|
||||
|
||||
TestRunCachedCallbacks<ItemContext> callbacks = new TestRunCachedCallbacks<ItemContext>();
|
||||
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<ItemContext>(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<ItemContext>(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<int> callbacks = new TestRunCachedCallbacks<int>();
|
||||
List<WorkItem<int>> list = Enumerable.Range(0, 2).
|
||||
Select(i => new WorkItem<int>(i)).ToList();
|
||||
FakeTracker tracker = new FakeTracker();
|
||||
|
||||
callbacks.ProcessUncachedCB = (item) => { tracker.shouldCancel = true; };
|
||||
callbacks.PostProcessCB = (item) => {};
|
||||
ReturnCode code = TaskCachingUtility.RunCachedOperation<int>(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<ItemContext> callbacks = new TestRunCachedCallbacks<ItemContext>();
|
||||
Dictionary<int, int> indexToResult = new Dictionary<int, int>();
|
||||
List<WorkItem<ItemContext>> list = Enumerable.Range(0, kIterations).
|
||||
Select(i => new WorkItem<ItemContext>(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<ItemContext>(null, null, null, list, callbacks);
|
||||
Assert.AreEqual(5, callbacks.PostProcessCount);
|
||||
Assert.AreEqual(5, callbacks.ProcessUncachedCount);
|
||||
foreach (WorkItem<ItemContext> item in list)
|
||||
Assert.AreEqual(item.Context.result, indexToResult[item.Context.input]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c2dcb086cc041f2459f8cb57dee4d25f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,131 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
internal static class TestTracing
|
||||
{
|
||||
public static string Callsite([System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
return $"at {memberName} in {sourceFilePath}:{sourceLineNumber}";
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestBuildParametersBase : IBuildParameters
|
||||
{
|
||||
public virtual BuildTarget Target { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual BuildTargetGroup Group { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual ContentBuildFlags ContentBuildFlags { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual TypeDB ScriptInfo { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual ScriptCompilationOptions ScriptOptions { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual string TempOutputFolder { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual string ScriptOutputFolder { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual bool UseCache { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual string CacheServerHost { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual int CacheServerPort { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual bool WriteLinkXML { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
public virtual bool NonRecursiveDependencies { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
|
||||
public virtual UnityEngine.BuildCompression GetCompressionForIdentifier(string identifier)
|
||||
{
|
||||
throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
}
|
||||
|
||||
public virtual BuildSettings GetContentBuildSettings()
|
||||
{
|
||||
throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
}
|
||||
|
||||
public virtual string GetOutputFilePathForIdentifier(string identifier)
|
||||
{
|
||||
throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
}
|
||||
|
||||
public virtual ScriptCompilationSettings GetScriptCompilationSettings()
|
||||
{
|
||||
throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestBundleBuildParameters : TestBuildParametersBase, IBundleBuildParameters
|
||||
{
|
||||
public virtual bool AppendHash { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
|
||||
|
||||
public virtual bool ContiguousBundles { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
|
||||
|
||||
public virtual bool DisableVisibleSubAssetRepresentations { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
|
||||
}
|
||||
|
||||
internal class TestBundleBuildContent : IBundleBuildContent
|
||||
{
|
||||
public virtual Dictionary<string, List<GUID>> BundleLayout => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual Dictionary<GUID, string> Addresses => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual List<GUID> Assets => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual List<GUID> Scenes => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
public virtual Dictionary<string, List<ResourceFile>> AdditionalFiles => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual List<CustomContent> CustomAssets => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
#endif
|
||||
}
|
||||
|
||||
internal class TestDependencyDataBase : IDependencyData
|
||||
{
|
||||
public virtual Dictionary<GUID, AssetLoadInfo> AssetInfo => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual Dictionary<GUID, BuildUsageTagSet> AssetUsage => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual Dictionary<GUID, SceneDependencyInfo> SceneInfo => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual Dictionary<GUID, BuildUsageTagSet> SceneUsage => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual BuildUsageCache DependencyUsageCache => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual BuildUsageTagGlobal GlobalUsage { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
|
||||
|
||||
public virtual Dictionary<GUID, Hash128> DependencyHash => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
}
|
||||
|
||||
internal class TestWriteDataBase : IWriteData
|
||||
{
|
||||
public virtual Dictionary<GUID, List<string>> AssetToFiles => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual Dictionary<string, List<ObjectIdentifier>> FileToObjects => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual List<IWriteOperation> WriteOperations => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
}
|
||||
|
||||
internal class TestBuildResultsBase : IBuildResults
|
||||
{
|
||||
public virtual ScriptCompilationResult ScriptResults { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
|
||||
public virtual Dictionary<string, WriteResult> WriteResults => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public virtual Dictionary<string, SerializedFileMetaData> WriteResultsMetaData => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
|
||||
public Dictionary<GUID, AssetResultData> AssetResults => throw new System.NotImplementedException(TestTracing.Callsite());
|
||||
}
|
||||
|
||||
internal class TestBundleExplictObjectLayout : IBundleExplictObjectLayout
|
||||
{
|
||||
public virtual Dictionary<ObjectIdentifier, string> ExplicitObjectLocation { get => throw new System.NotImplementedException(TestTracing.Callsite()); set => throw new System.NotImplementedException(TestTracing.Callsite()); }
|
||||
}
|
||||
|
||||
internal class TestBundleExtendedAssetData : IBuildExtendedAssetData
|
||||
{
|
||||
public virtual Dictionary<GUID, ExtendedAssetData> ExtendedData => throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3e8cc8b5af2f62e418573550ab569877
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,144 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ThreadingManagerTasks
|
||||
{
|
||||
[TestCase(ThreadingManager.ThreadQueues.SaveQueue)]
|
||||
[TestCase(ThreadingManager.ThreadQueues.UploadQueue)]
|
||||
[TestCase(ThreadingManager.ThreadQueues.PruneQueue)]
|
||||
public void ThreadingTasks_SameQueues_ExecuteSequentially(object queue)
|
||||
{
|
||||
var testQueue = (ThreadingManager.ThreadQueues)queue;
|
||||
|
||||
SemaphoreSlim s1 = new SemaphoreSlim(0);
|
||||
SemaphoreSlim s2 = new SemaphoreSlim(0);
|
||||
ConcurrentQueue<int> callOrder = new ConcurrentQueue<int>();
|
||||
ThreadingManager.QueueTask(testQueue, (_) =>
|
||||
{
|
||||
s1.Wait();
|
||||
callOrder.Enqueue(1);
|
||||
Thread.Sleep(50);
|
||||
callOrder.Enqueue(2);
|
||||
}, null);
|
||||
|
||||
ThreadingManager.QueueTask(testQueue, (_) =>
|
||||
{
|
||||
callOrder.Enqueue(3);
|
||||
}, null);
|
||||
|
||||
Thread.Sleep(100);
|
||||
s1.Release();
|
||||
|
||||
ThreadingManager.WaitForOutstandingTasks();
|
||||
|
||||
Assert.AreEqual(3, callOrder.Count);
|
||||
CollectionAssert.AreEqual(new[] { 1, 2, 3 }, callOrder.ToArray());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThreadingTasks_SaveQueue_RunsInParallelWith_UploadQueue()
|
||||
{
|
||||
SemaphoreSlim s1 = new SemaphoreSlim(0);
|
||||
SemaphoreSlim s2 = new SemaphoreSlim(0);
|
||||
SemaphoreSlim s3 = new SemaphoreSlim(0);
|
||||
ConcurrentQueue<int> callOrder = new ConcurrentQueue<int>();
|
||||
ThreadingManager.QueueTask(ThreadingManager.ThreadQueues.SaveQueue, (_) =>
|
||||
{
|
||||
s1.Wait();
|
||||
callOrder.Enqueue(1);
|
||||
s2.Release();
|
||||
s3.Wait();
|
||||
callOrder.Enqueue(2);
|
||||
}, null);
|
||||
|
||||
ThreadingManager.QueueTask(ThreadingManager.ThreadQueues.UploadQueue, (_) =>
|
||||
{
|
||||
s2.Wait();
|
||||
callOrder.Enqueue(3);
|
||||
s3.Release();
|
||||
}, null);
|
||||
|
||||
Thread.Sleep(100);
|
||||
s1.Release();
|
||||
|
||||
ThreadingManager.WaitForOutstandingTasks();
|
||||
|
||||
Assert.AreEqual(3, callOrder.Count);
|
||||
CollectionAssert.AreEqual(new[] { 1, 3, 2 }, callOrder.ToArray());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThreadingTasks_PruneQueue_RunsAfter_SaveAndUploadQueue()
|
||||
{
|
||||
SemaphoreSlim s1 = new SemaphoreSlim(0);
|
||||
SemaphoreSlim s2 = new SemaphoreSlim(0);
|
||||
SemaphoreSlim s3 = new SemaphoreSlim(0);
|
||||
ConcurrentQueue<int> callOrder = new ConcurrentQueue<int>();
|
||||
ThreadingManager.QueueTask(ThreadingManager.ThreadQueues.SaveQueue, (_) =>
|
||||
{
|
||||
s1.Wait();
|
||||
callOrder.Enqueue(1);
|
||||
s2.Release();
|
||||
s3.Wait();
|
||||
callOrder.Enqueue(2);
|
||||
}, null);
|
||||
|
||||
ThreadingManager.QueueTask(ThreadingManager.ThreadQueues.UploadQueue, (_) =>
|
||||
{
|
||||
s2.Wait();
|
||||
callOrder.Enqueue(3);
|
||||
s3.Release();
|
||||
}, null);
|
||||
|
||||
ThreadingManager.QueueTask(ThreadingManager.ThreadQueues.PruneQueue, (_) =>
|
||||
{
|
||||
callOrder.Enqueue(4);
|
||||
}, null);
|
||||
|
||||
Thread.Sleep(100);
|
||||
s1.Release();
|
||||
|
||||
ThreadingManager.WaitForOutstandingTasks();
|
||||
|
||||
Assert.AreEqual(4, callOrder.Count);
|
||||
CollectionAssert.AreEqual(new[] { 1, 3, 2, 4 }, callOrder.ToArray());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenPruneTaskActive_SaveAndUploadTasksWaitForPruneCompletion()
|
||||
{
|
||||
SemaphoreSlim s1 = new SemaphoreSlim(0);
|
||||
ConcurrentQueue<int> callOrder = new ConcurrentQueue<int>();
|
||||
ThreadingManager.QueueTask(ThreadingManager.ThreadQueues.PruneQueue, (_) =>
|
||||
{
|
||||
s1.Wait();
|
||||
callOrder.Enqueue(1);
|
||||
}, null);
|
||||
|
||||
ThreadingManager.QueueTask(ThreadingManager.ThreadQueues.SaveQueue, (_) =>
|
||||
{
|
||||
callOrder.Enqueue(2);
|
||||
}, null);
|
||||
|
||||
ThreadingManager.QueueTask(ThreadingManager.ThreadQueues.UploadQueue, (_) =>
|
||||
{
|
||||
callOrder.Enqueue(3);
|
||||
}, null);
|
||||
|
||||
Thread.Sleep(100);
|
||||
s1.Release();
|
||||
|
||||
ThreadingManager.WaitForOutstandingTasks();
|
||||
|
||||
Assert.AreEqual(3, callOrder.Count);
|
||||
Assert.IsTrue(callOrder.TryDequeue(out int result));
|
||||
Assert.AreEqual(1, result);
|
||||
var results = callOrder.ToArray();
|
||||
CollectionAssert.AreEquivalent(new[] { 2, 3 }, results);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cb09304cefcaac544889b26c496a2cac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,767 @@
|
|||
// Define this to cause the before and after serialization DumpToText output for objects being tested to be echoed to the console.
|
||||
// Slows down tests (some considerably) but allows visual inspection of the data which can be helpful for debugging issues
|
||||
// #define DUMP_DATA_TEXT_TO_CONSOLE
|
||||
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEngine.Build.Pipeline;
|
||||
using UnityEditor.Build.Pipeline.Tests;
|
||||
using System.Reflection;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Utilities.USerialize.Tests
|
||||
{
|
||||
// Tests for the USerialize serialization code
|
||||
[TestFixture]
|
||||
class USerializeTests
|
||||
{
|
||||
// --------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Test Cases
|
||||
// --------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Test 'bool' is serialized/deserialized correctly
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestBoolSerializes(bool testValue) => TestSerializeData(new PrimitiveValue<bool>() { m_Value = testValue });
|
||||
|
||||
// Test 'Int32' is serialized/deserialized correctly
|
||||
[TestCase(int.MinValue)]
|
||||
[TestCase(int.MaxValue)]
|
||||
[TestCase(0)]
|
||||
[TestCase(1234)]
|
||||
public void TestInt32Serializes(int testValue) => TestSerializeData(new PrimitiveValue<int>() { m_Value = testValue });
|
||||
|
||||
// Test 'UInt32' is serialized/deserialized correctly
|
||||
[TestCase(uint.MinValue)]
|
||||
[TestCase(uint.MaxValue)]
|
||||
[TestCase(0u)]
|
||||
[TestCase(1234u)]
|
||||
public void TestUInt32Serializes(uint testValue) => TestSerializeData(new PrimitiveValue<uint>() { m_Value = testValue });
|
||||
|
||||
// Test 'Int64' is serialized/deserialized correctly
|
||||
[TestCase(long.MinValue)]
|
||||
[TestCase(long.MaxValue)]
|
||||
[TestCase(0)]
|
||||
[TestCase(1234)]
|
||||
public void TestInt64Serializes(long testValue) => TestSerializeData(new PrimitiveValue<long>() { m_Value = testValue });
|
||||
|
||||
// Test 'UInt64' is serialized/deserialized correctly
|
||||
[TestCase(ulong.MinValue)]
|
||||
[TestCase(ulong.MaxValue)]
|
||||
[TestCase(0u)]
|
||||
[TestCase(1234u)]
|
||||
public void TestUInt64Serializes(ulong testValue) => TestSerializeData(new PrimitiveValue<ulong>() { m_Value = testValue });
|
||||
|
||||
// Test trying to serialize an unsupported primitive type throws
|
||||
[Test]
|
||||
public void TestSerializingUnsupportedPrimitiveTypeThrows()
|
||||
{
|
||||
Assert.Throws(typeof(InvalidDataException), () => TestSerializeData(new PrimitiveValue<float>() { m_Value = 123.4f }));
|
||||
}
|
||||
|
||||
// Test trying to serialize an unsupported primitive array type throws
|
||||
[Test]
|
||||
public void TestSerializingUnsupportedArrayPrimitiveTypeThrows()
|
||||
{
|
||||
Assert.Throws(typeof(InvalidDataException), () => TestSerializeData(new PrimitiveValue<ClassWithUnsupportedPrimitiveArrayType>() { m_Value = new ClassWithUnsupportedPrimitiveArrayType() }));
|
||||
}
|
||||
|
||||
// Test trying to serialize an array of rank greater than one throws
|
||||
[Test]
|
||||
public void TestSerializingUnsupportedArrayRankThrows()
|
||||
{
|
||||
Assert.Throws(typeof(InvalidDataException), () => TestSerializeData(new PrimitiveValue<int[,]>() { m_Value = new int[1, 1] }));
|
||||
}
|
||||
|
||||
// Test writing string indices gives us back the values we expect and uses the expected number of bytes
|
||||
[TestCase(0, 2)]
|
||||
[TestCase(0x7FFF, 2)]
|
||||
[TestCase(0x8000, 4)]
|
||||
[TestCase(0x8001, 4)]
|
||||
[TestCase(0x12345678, 4)]
|
||||
[TestCase(USerialize.InvalidStringIndex, 4)]
|
||||
public void TestWriteReadStringIndices(int stringIndex, int expectedSizeBytes)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
// Write string index checking we wrote the expected number of bytes
|
||||
Serializer serializer = new Serializer();
|
||||
serializer.StartWritingToStream(stream);
|
||||
long streamPosBeforeWriting = stream.Position;
|
||||
serializer.WriteStringIndex(stringIndex);
|
||||
serializer.FinishWritingToStream();
|
||||
int numBytesWritten = (int)(stream.Position - streamPosBeforeWriting);
|
||||
Assert.AreEqual(expectedSizeBytes, numBytesWritten);
|
||||
|
||||
// Read back the string index checking we got back what we expected and read the expected number of bytes
|
||||
stream.Position = 0;
|
||||
DeSerializer deserializer = new DeSerializer();
|
||||
deserializer.StartReadingFromStream(stream);
|
||||
long streamPosBeforeReading = stream.Position;
|
||||
int readStringIndex = deserializer.ReadStringIndex();
|
||||
int numBytesRead = (int)(stream.Position - streamPosBeforeReading);
|
||||
Assert.AreEqual(expectedSizeBytes, numBytesRead);
|
||||
|
||||
Assert.AreEqual(stringIndex, readStringIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Test 'Hash128' is serialized/deserialized correctly
|
||||
[TestCase(0x0123456789ABCDEFU, 0x94F8A3B87F327E2CU)]
|
||||
public void TestHash128Serializes(ulong hashPart1, ulong hashPart2)
|
||||
{
|
||||
Hash128 testValue = new Hash128(hashPart1, hashPart2);
|
||||
TestSerializeData(new PrimitiveValue<Hash128>() { m_Value = testValue });
|
||||
}
|
||||
|
||||
// Test 'GUID' is serialized/deserialized correctly
|
||||
[TestCase("ABD386FE297853ADF2397542BC3EA875")]
|
||||
public void TestGUIDSerializes(string guidString)
|
||||
{
|
||||
GUID testValue = new GUID(guidString);
|
||||
TestSerializeData(new PrimitiveValue<GUID>() { m_Value = testValue });
|
||||
}
|
||||
|
||||
static readonly string SampleUnicodeString = "A Unicode string containing characters outside the ASCII code range, Pi (\u03a0) and Sigma (\u03a3) and a surrogate pair (\ud83e\udd70)";
|
||||
|
||||
// "The world is a better place with more creators in it" in various languages to exercise international characters. (done with Google translate, apologies for errors)
|
||||
static readonly string BetterPlaceArabic = "\u0627\u0644\u0639\u0627\u0644\u0645\u0020\u0645\u0643\u0627\u0646\u0020\u0623\u0641\u0636\u0644\u0020\u0628\u0647\u0020\u0639\u062f\u062f\u0020\u0623\u0643\u0628\u0631\u0020\u0645\u0646\u0020\u0627\u0644\u0645\u0628\u062f\u0639\u064a\u0646";
|
||||
static readonly string BetterPlaceHindi = "\u0907\u0938\u092e\u0947\u0902\u0020\u0905\u0927\u093f\u0915\u0020\u0930\u091a\u0928\u093e\u0915\u093e\u0930\u094b\u0902\u0020\u0915\u0947\u0020\u0938\u093e\u0925\u0020\u0926\u0941\u0928\u093f\u092f\u093e\u0020\u090f\u0915\u0020\u092c\u0947\u0939\u0924\u0930\u0020\u0938\u094d\u0925\u093e\u0928\u0020\u0939\u0948";
|
||||
static readonly string BetterPlaceRussian = "\u043c\u0438\u0440\u0020\u0441\u0442\u0430\u043b\u0020\u043b\u0443\u0447\u0448\u0435\u002c\u0020\u0432\u0020\u043d\u0435\u043c\u0020\u0431\u043e\u043b\u044c\u0448\u0435\u0020\u0442\u0432\u043e\u0440\u0446\u043e\u0432";
|
||||
static readonly string BetterPlaceIcelandic = "\u0068\u0065\u0069\u006d\u0075\u0072\u0069\u006e\u006e\u0020\u0065\u0072\u0020\u0062\u0065\u0074\u0072\u0069\u0020\u0073\u0074\u0061\u00f0\u0075\u0072\u0020\u006d\u0065\u00f0\u0020\u0066\u006c\u0065\u0069\u0072\u0069\u0020\u0068\u00f6\u0066\u0075\u006e\u0064\u0075\u006d\u0020\u00ed\u0020\u0068\u006f\u006e\u0075\u006d";
|
||||
static readonly string BetterPlaceJapanese = "\u4e16\u754c\u306f\u3088\u308a\u591a\u304f\u306e\u30af\u30ea\u30a8\u30a4\u30bf\u30fc\u304c\u3044\u308b\u3088\u308a\u826f\u3044\u5834\u6240\u3067\u3059";
|
||||
|
||||
// Test strings of various forms are serialized/deserialized correctly
|
||||
public static IEnumerable<string> TestStringsSerialize_TestCases()
|
||||
{
|
||||
yield return null;
|
||||
yield return "";
|
||||
yield return "\n";
|
||||
yield return "FooBar";
|
||||
yield return "\"FooBarQuotes\"";
|
||||
yield return "\\FooBarBackslash\\";
|
||||
yield return "Multi\nLine\nString";
|
||||
yield return SampleUnicodeString;
|
||||
yield return BetterPlaceArabic;
|
||||
yield return BetterPlaceHindi;
|
||||
yield return BetterPlaceRussian;
|
||||
yield return BetterPlaceIcelandic;
|
||||
yield return BetterPlaceJapanese;
|
||||
}
|
||||
[TestCaseSource("TestStringsSerialize_TestCases")]
|
||||
public void TestStringsSerialize(string testValue) => TestSerializeData(new PrimitiveValue<string>() { m_Value = testValue });
|
||||
|
||||
// Test string arrays of various forms are serialized/deserialized correctly
|
||||
public static IEnumerable<string[]> TestStringArraysSerialize_TestCases()
|
||||
{
|
||||
yield return null;
|
||||
yield return new string[] { "" };
|
||||
yield return new string[] { "foo" };
|
||||
yield return new string[] { "foo", "bar", "multi\nline", "table\\backslash" };
|
||||
yield return TestStringsSerialize_TestCases().ToArray();
|
||||
}
|
||||
[TestCaseSource("TestStringArraysSerialize_TestCases")]
|
||||
public void TestStringArraysSerialize(string[] stringArray)
|
||||
{
|
||||
TestSerializeData(new PrimitiveArray<string>() { m_ArrayElements = stringArray });
|
||||
}
|
||||
|
||||
// Test serializing a string array with more than 32,767 unique entries to ensure the standard 15bit string table index
|
||||
// is insufficient and the fallback 31bit stringtable index encoding has to be used for some of the strings
|
||||
[Test]
|
||||
public void TestStringIndicesLargerThan15bit()
|
||||
{
|
||||
string[] testStrings = new string[40000];
|
||||
for (int elementNum = 0; elementNum < testStrings.Length; elementNum++)
|
||||
testStrings[elementNum] = "Test_" + elementNum;
|
||||
TestSerializeData(new PrimitiveArray<string>() { m_ArrayElements = testStrings });
|
||||
}
|
||||
|
||||
// Test byte arrays of various forms are serialized/deserialized correctly
|
||||
[TestCase(null)]
|
||||
[TestCase(new byte[0])]
|
||||
[TestCase(new byte[] { 1, 2, 3, 4, 5 })]
|
||||
public void TestByteArraysSerialize(byte[] testValue) => TestSerializeData(new PrimitiveArray<byte>() { m_ArrayElements = testValue });
|
||||
|
||||
// Test ulong arrays of various forms are serialized/deserialized correctly
|
||||
[TestCase(null)]
|
||||
[TestCase(new ulong[0])]
|
||||
[TestCase(new ulong[] { 1, 2, 3, 4, 5 })]
|
||||
[TestCase(new ulong[] { ulong.MinValue, 0, 1, ulong.MaxValue })]
|
||||
public void TestULongArraysSerialize(ulong[] testValue) => TestSerializeData(new PrimitiveArray<ulong>() { m_ArrayElements = testValue });
|
||||
|
||||
// Test Type arrays of various forms are serialized/deserialized correctly
|
||||
public static IEnumerable<Type[]> TestTypeArraysSerialize_TestCases()
|
||||
{
|
||||
yield return null;
|
||||
yield return new Type[0];
|
||||
yield return new Type[] { typeof(int), typeof(uint), typeof(float), typeof(CachedInfo), typeof(MonoBehaviour) };
|
||||
yield return new Type[] { typeof(int), null, typeof(float), null, typeof(MonoBehaviour) };
|
||||
}
|
||||
[TestCaseSource("TestTypeArraysSerialize_TestCases")]
|
||||
public void TestTypeArraysSerialize(Type[] testValue) => TestSerializeData(new PrimitiveArray<Type>() { m_ArrayElements = testValue });
|
||||
|
||||
// Test structs are serialized/deserialized correctly
|
||||
[TestCase(-1)]
|
||||
[TestCase(0)]
|
||||
[TestCase(1)]
|
||||
public void TestLinearStructsSerialize(int scalar)
|
||||
{
|
||||
LinearStruct value = new LinearStruct(scalar);
|
||||
if (scalar != -1)
|
||||
value.m_LinearClass = new LinearClass(scalar * 2);
|
||||
TestSerializeData(value);
|
||||
}
|
||||
|
||||
// Test classes are serialized/deserialized correctly
|
||||
[Test]
|
||||
public void TestLinearClassSerializes() => TestSerializeData(new PrimitiveValue<LinearClass>() { m_Value = new LinearClass(1) });
|
||||
|
||||
// Test the DeSerializer.ObjectVersion property returns the correct value after deserialization
|
||||
[Test]
|
||||
public void TestClientObjectVersionPropertyReturnedCorrectly()
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
// Our data
|
||||
PrimitiveValue<int> data = new PrimitiveValue<int>() { m_Value = 3149123 };
|
||||
const int objectVersion = 12345678;
|
||||
|
||||
// Write to stream
|
||||
Serializer serializer = new Serializer();
|
||||
serializer.Serialize(stream, data, objectVersion);
|
||||
|
||||
// Read from stream
|
||||
stream.Position = 0;
|
||||
DeSerializer deserializer = new DeSerializer();
|
||||
PrimitiveValue<int> deserializedData = deserializer.DeSerialize<PrimitiveValue<int>>(stream);
|
||||
|
||||
// Test object contents and version is as expected
|
||||
Assert.AreEqual(objectVersion, deserializer.ObjectVersion);
|
||||
Assert.AreEqual(data.m_Value, deserializedData.m_Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Test that CachedInfo instances serialize/deserialize correctly
|
||||
[Test]
|
||||
public void TestCachedInfoSerialises()
|
||||
{
|
||||
CachedInfo cachedInfo = CreateSyntheticCachedInfo(12478324);
|
||||
|
||||
TestSerializeData(new PrimitiveValue<CachedInfo>() { m_Value = cachedInfo },
|
||||
BuildCache.CustomSerializers,
|
||||
BuildCache.ObjectFactories,
|
||||
new DumpToText.ICustomDumper[] { new CustomDumper_BuildUsageTagSet() },
|
||||
false); // full Equality not implemented for CachedInfo so we rely on the dumper text comparison
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Utilities & Supporting Code
|
||||
// --------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Wrapper for a value that provides an IEquatable interface so it can be compared post-serialization-deserialization to the original value
|
||||
class PrimitiveValue<ValueType> : IEquatable<PrimitiveValue<ValueType>>
|
||||
{
|
||||
internal ValueType m_Value;
|
||||
|
||||
public bool Equals(PrimitiveValue<ValueType> other)
|
||||
{
|
||||
return (other == null) ? false : (EqualityComparer<ValueType>.Default.Equals(m_Value, other.m_Value));
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals(obj as PrimitiveValue<ValueType>);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for an array of primitive values that provides an IEquatable interface so it can be compared post-serialization-deserialization to the original value
|
||||
class PrimitiveArray<ValueType> : IEquatable<PrimitiveArray<ValueType>>
|
||||
{
|
||||
internal ValueType[] m_ArrayElements;
|
||||
|
||||
public bool Equals(PrimitiveArray<ValueType> other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
return (m_ArrayElements == null) ? (other.m_ArrayElements == null) : m_ArrayElements.SequenceEqual(other.m_ArrayElements);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals(obj as PrimitiveArray<ValueType>);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
// A struct of various primitive and reference types that provides an IEquatable interface so it can be compared post-serialization-deserialization to the original value
|
||||
internal struct LinearStruct : IEquatable<LinearStruct>
|
||||
{
|
||||
internal bool m_Bool;
|
||||
internal byte m_Byte;
|
||||
internal int m_Int;
|
||||
internal uint m_UInt;
|
||||
internal long m_Long;
|
||||
internal ulong m_ULong;
|
||||
internal string m_String;
|
||||
|
||||
internal LinearClass m_LinearClass;
|
||||
|
||||
internal LinearStruct(int scalar)
|
||||
{
|
||||
m_Bool = ((scalar & 1) == 0);
|
||||
m_Byte = (byte)scalar;
|
||||
m_Int = 2 * scalar;
|
||||
m_UInt = (uint)(3 * scalar);
|
||||
m_Long = (long)(4 * scalar);
|
||||
m_ULong = (ulong)(5 * scalar);
|
||||
m_String = "(" + scalar.ToString() + ")";
|
||||
|
||||
m_LinearClass = null;
|
||||
}
|
||||
|
||||
public bool Equals(LinearStruct other)
|
||||
{
|
||||
return (m_Bool == other.m_Bool) && (m_Byte == other.m_Byte) && (m_Int == other.m_Int) && (m_UInt == other.m_UInt) && (m_Long == other.m_Long) && (m_ULong == other.m_ULong) && (m_String == other.m_String) && (System.Object.Equals(m_LinearClass, other.m_LinearClass));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
// A class of various primitive and reference types that provides an IEquatable interface so it can be compared post-serialization-deserialization to the original value
|
||||
internal class LinearClass : IEquatable<LinearClass>
|
||||
{
|
||||
internal bool m_Bool;
|
||||
internal byte m_Byte;
|
||||
internal int m_Int;
|
||||
internal uint m_UInt;
|
||||
internal long m_Long;
|
||||
internal ulong m_ULong;
|
||||
internal string m_String;
|
||||
|
||||
internal LinearClass m_LinearClass;
|
||||
internal LinearStruct m_LinearStruct;
|
||||
|
||||
public LinearClass()
|
||||
{
|
||||
}
|
||||
|
||||
internal LinearClass(int scalar)
|
||||
{
|
||||
m_Bool = ((scalar & 1) == 0);
|
||||
m_Byte = (byte)scalar;
|
||||
m_Int = 2 * scalar;
|
||||
m_UInt = (uint)(3 * scalar);
|
||||
m_Long = (long)(4 * scalar);
|
||||
m_ULong = (ulong)(5 * scalar);
|
||||
m_String = "(" + scalar.ToString() + ")";
|
||||
|
||||
m_LinearStruct = new LinearStruct(scalar * 10);
|
||||
}
|
||||
|
||||
public bool Equals(LinearClass other)
|
||||
{
|
||||
return (m_Bool == other.m_Bool) && (m_Byte == other.m_Byte) && (m_Int == other.m_Int) && (m_UInt == other.m_UInt) && (m_Long == other.m_Long) && (m_ULong == other.m_ULong) && (m_String == other.m_String) && (m_LinearClass == other.m_LinearClass) && m_LinearStruct.Equals(other.m_LinearStruct);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals(obj as LinearClass);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
// Class containing a field of an unsupported primitive array type
|
||||
internal class ClassWithUnsupportedPrimitiveArrayType
|
||||
{
|
||||
internal object[] m_InvalidArray = new object[1] { 123.4f }; // Only primitive type supported in object arrays is Int32
|
||||
}
|
||||
|
||||
// Utility to create a GUID from a random number generator
|
||||
static GUID CreateGuid(System.Random rnd)
|
||||
{
|
||||
string guidString = "";
|
||||
for (int charNum = 0; charNum < 32; charNum++)
|
||||
guidString = guidString + rnd.Next(10);
|
||||
return new GUID(guidString);
|
||||
}
|
||||
|
||||
// Utility to create a Hash128 from a random number generator
|
||||
static Hash128 CreateHash128(System.Random rnd)
|
||||
{
|
||||
return new Hash128((uint)rnd.Next(), (uint)rnd.Next(), (uint)rnd.Next(), (uint)rnd.Next());
|
||||
}
|
||||
|
||||
// Utility to create a CacheEntry instance from a random number generator
|
||||
static CacheEntry CreateSyntheticCacheEntry(System.Random rnd)
|
||||
{
|
||||
CacheEntry cacheEntry = new CacheEntry();
|
||||
cacheEntry.Hash = CreateHash128(rnd);
|
||||
cacheEntry.Guid = CreateGuid(rnd);
|
||||
cacheEntry.Version = rnd.Next(100);
|
||||
cacheEntry.Type = (CacheEntry.EntryType)rnd.Next(4);
|
||||
cacheEntry.File = "File_" + rnd.Next();
|
||||
cacheEntry.ScriptType = "ScriptType_" + rnd.Next();
|
||||
return cacheEntry;
|
||||
}
|
||||
|
||||
// Utility to create an ObjectIdentifier instance from a random number generator
|
||||
static ObjectIdentifier CreateObjectIdentifier(System.Random rnd)
|
||||
{
|
||||
ObjectIdentifier objectIdentifier = new ObjectIdentifier();
|
||||
objectIdentifier.SetObjectIdentifier(CreateGuid(rnd), rnd.Next(), (FileType)rnd.Next(4), "FilePath_" + rnd.Next());
|
||||
return objectIdentifier;
|
||||
}
|
||||
|
||||
// Create a CachedInfo instance with every field populated from a given random number seed
|
||||
// The data doesn't have to make sense, but everything has to have a value of some sort so we can check everything is being serialized/deserialized
|
||||
static CachedInfo CreateSyntheticCachedInfo(int seed)
|
||||
{
|
||||
System.Random rnd = new System.Random(seed);
|
||||
|
||||
CachedInfo cachedData = new CachedInfo();
|
||||
|
||||
cachedData.Asset = CreateSyntheticCacheEntry(rnd);
|
||||
|
||||
cachedData.Dependencies = new CacheEntry[2];
|
||||
cachedData.Dependencies[0] = CreateSyntheticCacheEntry(rnd);
|
||||
cachedData.Dependencies[1] = CreateSyntheticCacheEntry(rnd);
|
||||
|
||||
cachedData.Data = new object[]
|
||||
{
|
||||
CreateSyntheticAssetLoadInfo(rnd),
|
||||
CreateSyntheticBuildUsageTagSet(),
|
||||
CreateSyntheticSpriteImporterData(rnd),
|
||||
CreateSyntheticExtendedAssetData(rnd),
|
||||
CreateSyntheticObjectTypes(rnd),
|
||||
CreateSyntheticSceneDependencyInfo(rnd),
|
||||
CreateHash128(rnd),
|
||||
CreateSyntheticWriteResult(rnd),
|
||||
CreateSyntheticSerializedFileMetaData(rnd),
|
||||
CreateSyntheticBundleDetails(rnd)
|
||||
};
|
||||
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
static AssetLoadInfo CreateSyntheticAssetLoadInfo(System.Random rnd)
|
||||
{
|
||||
AssetLoadInfo assetInfo = new AssetLoadInfo();
|
||||
assetInfo.asset = CreateGuid(rnd);
|
||||
assetInfo.address = "AnyAddress";
|
||||
assetInfo.includedObjects = new List<ObjectIdentifier>();
|
||||
assetInfo.includedObjects.Add(CreateObjectIdentifier(rnd));
|
||||
assetInfo.includedObjects.Add(CreateObjectIdentifier(rnd));
|
||||
assetInfo.referencedObjects = new List<ObjectIdentifier>();
|
||||
assetInfo.referencedObjects.Add(CreateObjectIdentifier(rnd));
|
||||
assetInfo.referencedObjects.Add(CreateObjectIdentifier(rnd));
|
||||
return assetInfo;
|
||||
}
|
||||
|
||||
static BuildUsageTagSet CreateSyntheticBuildUsageTagSet()
|
||||
{
|
||||
// JSON for a BuildUsageTagSet instance. There is no API to create this programatically so this was captured from a real project to be used in these tests
|
||||
string buildUsageTagSetJsonData = "{\"m_objToUsage\":[{\"first\":{\"filePath\":\"\",\"fileType\":3,\"guid\":\"822642a2b47082c49966f0c54db535a4\",\"localIdentifierInFile\":-7728386467694932768},\"second\":{\"forceTextureReadable\":false,\"maxBonesPerVertex\":4,\"meshSupportedChannels\":12799,\"meshUsageFlags\":1,\"shaderIncludeInstancingVariants\":false,\"shaderUsageKeywordNames\":[],\"strippedPrefabObject\":false}},{\"first\":{\"filePath\":\"\",\"fileType\":3,\"guid\":\"90b695013db7b334cbcf925848031399\",\"localIdentifierInFile\":-1848259448780025149},\"second\":{\"forceTextureReadable\":false,\"maxBonesPerVertex\":0,\"meshSupportedChannels\":383,\"meshUsageFlags\":0,\"shaderIncludeInstancingVariants\":false,\"shaderUsageKeywordNames\":[],\"strippedPrefabObject\":false}},{\"first\":{\"filePath\":\"\",\"fileType\":3,\"guid\":\"db9aadf200fd84e4591cc30ea4d1358c\",\"localIdentifierInFile\":3883874861523733925},\"second\":{\"forceTextureReadable\":false,\"maxBonesPerVertex\":0,\"meshSupportedChannels\":383,\"meshUsageFlags\":0,\"shaderIncludeInstancingVariants\":false,\"shaderUsageKeywordNames\":[],\"strippedPrefabObject\":false}}]}";
|
||||
|
||||
BuildUsageTagSet buildUsageTagSet = new BuildUsageTagSet();
|
||||
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
buildUsageTagSet.DeserializeFromJson(buildUsageTagSetJsonData);
|
||||
#else
|
||||
typeof(BuildUsageTagSet).GetMethod("DeserializeFromJson", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(buildUsageTagSet, new object[] { buildUsageTagSetJsonData });
|
||||
#endif
|
||||
|
||||
return buildUsageTagSet;
|
||||
|
||||
}
|
||||
|
||||
static SpriteImporterData CreateSyntheticSpriteImporterData(System.Random rnd)
|
||||
{
|
||||
SpriteImporterData spriteImporterData = new SpriteImporterData();
|
||||
spriteImporterData.PackedSprite = true;
|
||||
spriteImporterData.SourceTexture = CreateObjectIdentifier(rnd);
|
||||
return spriteImporterData;
|
||||
}
|
||||
|
||||
static ExtendedAssetData CreateSyntheticExtendedAssetData(System.Random rnd)
|
||||
{
|
||||
ExtendedAssetData extendedAssetData = new ExtendedAssetData();
|
||||
extendedAssetData.Representations = new List<ObjectIdentifier>();
|
||||
extendedAssetData.Representations.Add(CreateObjectIdentifier(rnd));
|
||||
extendedAssetData.Representations.Add(CreateObjectIdentifier(rnd));
|
||||
return extendedAssetData;
|
||||
}
|
||||
|
||||
static List<ObjectTypes> CreateSyntheticObjectTypes(System.Random rnd)
|
||||
{
|
||||
List<ObjectTypes> objectTypes = new List<ObjectTypes>();
|
||||
objectTypes.Add(new ObjectTypes(CreateObjectIdentifier(rnd), new Type[] { typeof(int), typeof(CachedInfo) }));
|
||||
objectTypes.Add(new ObjectTypes(CreateObjectIdentifier(rnd), new Type[] { typeof(MonoBehaviour), typeof(ScriptableObject) }));
|
||||
return objectTypes;
|
||||
}
|
||||
|
||||
#if !UNITY_2019_4_OR_NEWER
|
||||
// Set the value of a named field on an instance using the reflection API. Required only for 2018.4 where we don't have direct access to internal fields
|
||||
static void SetFieldValue<ObjectType>(ObjectType instance, string fieldName, object value)
|
||||
{
|
||||
typeof(ObjectType).GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(instance, value);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create a populated 'SceneDependencyInfo' instance. Can set fields directly for 2019.4+ as internals are available but have to use reflection for 2018.4
|
||||
static SceneDependencyInfo CreateSceneDependencyInfo(string scene, ObjectIdentifier[] objectIdentifiers, BuildUsageTagGlobal globalUsage, Type[] includedTypes)
|
||||
{
|
||||
SceneDependencyInfo sceneDependencyInfo = new SceneDependencyInfo();
|
||||
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
sceneDependencyInfo.m_Scene = scene;
|
||||
sceneDependencyInfo.m_ReferencedObjects = objectIdentifiers;
|
||||
sceneDependencyInfo.m_GlobalUsage = globalUsage;
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
sceneDependencyInfo.m_IncludedTypes = includedTypes;
|
||||
#endif
|
||||
#else
|
||||
sceneDependencyInfo.SetScene(scene);
|
||||
sceneDependencyInfo.SetReferencedObjects(objectIdentifiers);
|
||||
SetFieldValue(sceneDependencyInfo, "m_GlobalUsage", globalUsage);
|
||||
#endif
|
||||
|
||||
return sceneDependencyInfo;
|
||||
}
|
||||
|
||||
// Create a populated 'BuildUsageTagGlobal' instance. Can set fields directly for 2019.4+ as internals are available but have to use reflection for 2018.4
|
||||
static BuildUsageTagGlobal CreateBuildUsageTagGlobal(uint lightmapModesUsed, uint legacyLightmapModesUsed, uint dynamicLightmapsUsed, uint fogModesUsed, bool forceInstancingStrip, bool forceInstancingKeep, bool shadowMasksUsed, bool subtractiveUsed, bool hybridRendererPackageUsed)
|
||||
{
|
||||
BuildUsageTagGlobal buildUsageTagGlobal = new BuildUsageTagGlobal();
|
||||
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
buildUsageTagGlobal.m_LightmapModesUsed = lightmapModesUsed;
|
||||
buildUsageTagGlobal.m_LegacyLightmapModesUsed = legacyLightmapModesUsed;
|
||||
buildUsageTagGlobal.m_DynamicLightmapsUsed = dynamicLightmapsUsed;
|
||||
buildUsageTagGlobal.m_FogModesUsed = fogModesUsed;
|
||||
buildUsageTagGlobal.m_ForceInstancingStrip = forceInstancingStrip;
|
||||
buildUsageTagGlobal.m_ForceInstancingKeep = forceInstancingKeep;
|
||||
buildUsageTagGlobal.m_ShadowMasksUsed = shadowMasksUsed;
|
||||
buildUsageTagGlobal.m_SubtractiveUsed = subtractiveUsed;
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
buildUsageTagGlobal.m_HybridRendererPackageUsed = hybridRendererPackageUsed;
|
||||
#endif
|
||||
#else
|
||||
SetFieldValue(buildUsageTagGlobal, "m_LightmapModesUsed", lightmapModesUsed);
|
||||
SetFieldValue(buildUsageTagGlobal, "m_LegacyLightmapModesUsed", legacyLightmapModesUsed);
|
||||
SetFieldValue(buildUsageTagGlobal, "m_DynamicLightmapsUsed", dynamicLightmapsUsed);
|
||||
SetFieldValue(buildUsageTagGlobal, "m_FogModesUsed", fogModesUsed);
|
||||
SetFieldValue(buildUsageTagGlobal, "m_ForceInstancingStrip", forceInstancingStrip);
|
||||
SetFieldValue(buildUsageTagGlobal, "m_ForceInstancingKeep", forceInstancingKeep);
|
||||
SetFieldValue(buildUsageTagGlobal, "m_ShadowMasksUsed", shadowMasksUsed);
|
||||
SetFieldValue(buildUsageTagGlobal, "m_SubtractiveUsed", subtractiveUsed);
|
||||
#endif
|
||||
|
||||
return buildUsageTagGlobal;
|
||||
}
|
||||
|
||||
static SceneDependencyInfo CreateSyntheticSceneDependencyInfo(System.Random rnd)
|
||||
{
|
||||
BuildUsageTagGlobal buildUsageTagGlobal = CreateBuildUsageTagGlobal((uint)rnd.Next(100), (uint)rnd.Next(100), (uint)rnd.Next(100), (uint)rnd.Next(100), ((rnd.Next() & 1) != 0), ((rnd.Next() & 1) != 0), ((rnd.Next() & 1) != 0), ((rnd.Next() & 1) != 0), ((rnd.Next() & 1) != 0));
|
||||
|
||||
return CreateSceneDependencyInfo("SceneName", new ObjectIdentifier[2] { CreateObjectIdentifier(rnd), CreateObjectIdentifier(rnd) }, buildUsageTagGlobal, new Type[] { typeof(MonoBehaviour), typeof(ScriptableObject) });
|
||||
}
|
||||
|
||||
// Create a populated 'WriteResult' instance. Can set fields directly for 2019.4+ as internals are available but have to use reflection for 2018.4
|
||||
static WriteResult CreateWriteResult(ObjectSerializedInfo[] serializedObjects, ResourceFile[] resourceFiles, Type[] includedTypes, String[] includedSerializeReferenceFQN)
|
||||
{
|
||||
WriteResult writeResult = new WriteResult();
|
||||
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
writeResult.m_SerializedObjects = serializedObjects;
|
||||
writeResult.m_ResourceFiles = resourceFiles;
|
||||
writeResult.m_IncludedTypes = includedTypes;
|
||||
writeResult.m_IncludedSerializeReferenceFQN = includedSerializeReferenceFQN;
|
||||
#else
|
||||
SetFieldValue(writeResult, "m_SerializedObjects", serializedObjects);
|
||||
SetFieldValue(writeResult, "m_ResourceFiles", resourceFiles);
|
||||
SetFieldValue(writeResult, "m_IncludedTypes", includedTypes);
|
||||
#endif
|
||||
|
||||
return writeResult;
|
||||
}
|
||||
|
||||
// Create a populated 'ObjectSerializedInfo' instance. Can set fields directly for 2019.4+ as internals are available but have to use reflection for 2018.4
|
||||
static ObjectSerializedInfo CreateObjectSerializedInfo(ObjectIdentifier serializedObject, SerializedLocation header, SerializedLocation rawData)
|
||||
{
|
||||
ObjectSerializedInfo objectSerializedInfo = new ObjectSerializedInfo();
|
||||
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
objectSerializedInfo.m_SerializedObject = serializedObject;
|
||||
objectSerializedInfo.m_Header = header;
|
||||
objectSerializedInfo.m_RawData = rawData;
|
||||
#else
|
||||
SetFieldValue(objectSerializedInfo, "m_SerializedObject", serializedObject);
|
||||
SetFieldValue(objectSerializedInfo, "m_Header", header);
|
||||
SetFieldValue(objectSerializedInfo, "m_RawData", rawData);
|
||||
#endif
|
||||
|
||||
return objectSerializedInfo;
|
||||
}
|
||||
|
||||
// Create a populated 'ResourceFile' instance. Can set fields directly for 2019.4+ as internals are available but have to use reflection for 2018.4
|
||||
static ResourceFile CreateResourceFile(string filename, string fileAlias, bool serializedFile)
|
||||
{
|
||||
ResourceFile resourceFile = new ResourceFile();
|
||||
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
resourceFile.m_FileName = filename;
|
||||
resourceFile.m_FileAlias = fileAlias;
|
||||
resourceFile.m_SerializedFile = serializedFile;
|
||||
#else
|
||||
SetFieldValue(resourceFile, "m_FileName", filename);
|
||||
SetFieldValue(resourceFile, "m_FileAlias", fileAlias);
|
||||
SetFieldValue(resourceFile, "m_SerializedFile", serializedFile);
|
||||
#endif
|
||||
|
||||
return resourceFile;
|
||||
}
|
||||
|
||||
// Create a populated 'SerializedLocation' instance. Can set fields directly for 2019.4+ as internals are available but have to use reflection for 2018.4
|
||||
static SerializedLocation CreateSerializedLocation(string filename, uint offset, uint size)
|
||||
{
|
||||
SerializedLocation serializedLocation = new SerializedLocation();
|
||||
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
serializedLocation.m_FileName = filename;
|
||||
serializedLocation.m_Offset = offset;
|
||||
serializedLocation.m_Size = size;
|
||||
#else
|
||||
SetFieldValue(serializedLocation, "m_FileName", filename);
|
||||
SetFieldValue(serializedLocation, "m_Offset", offset);
|
||||
SetFieldValue(serializedLocation, "m_Size", size);
|
||||
#endif
|
||||
|
||||
return serializedLocation;
|
||||
}
|
||||
|
||||
static WriteResult CreateSyntheticWriteResult(System.Random rnd)
|
||||
{
|
||||
ObjectSerializedInfo[] serializedObjects = new ObjectSerializedInfo[]
|
||||
{
|
||||
CreateObjectSerializedInfo(CreateObjectIdentifier(rnd), CreateSerializedLocation("Header_" + rnd.Next(), (uint)rnd.Next(), (uint)rnd.Next()), CreateSerializedLocation("RawData_" + rnd.Next(), (uint)rnd.Next(), (uint)rnd.Next())),
|
||||
CreateObjectSerializedInfo(CreateObjectIdentifier(rnd), CreateSerializedLocation("Header_" + rnd.Next(), (uint)rnd.Next(), (uint)rnd.Next()), CreateSerializedLocation("RawData_" + rnd.Next(), (uint)rnd.Next(), (uint)rnd.Next()))
|
||||
};
|
||||
|
||||
ResourceFile[] resourceFiles = new ResourceFile[]
|
||||
{
|
||||
CreateResourceFile("Filename_" + rnd.Next(), "FileAlias_" + rnd.Next(), ((rnd.Next() & 1) != 0)),
|
||||
CreateResourceFile("Filename_" + rnd.Next(), "FileAlias_" + rnd.Next(), ((rnd.Next() & 1) != 0))
|
||||
};
|
||||
|
||||
return CreateWriteResult(serializedObjects, resourceFiles, new Type[] { typeof(ScriptableObject), typeof(Vector2) }, new String[] { "IncludedSerializeReferenceFQN_" + rnd.Next(), "IncludedSerializeReferenceFQN_" + rnd.Next() });
|
||||
}
|
||||
|
||||
static BundleDetails CreateSyntheticBundleDetails(System.Random rnd)
|
||||
{
|
||||
BundleDetails bundleDetails = new BundleDetails();
|
||||
bundleDetails.FileName = "FileName_" + rnd.Next();
|
||||
bundleDetails.Crc = (uint)rnd.Next();
|
||||
bundleDetails.Hash = CreateHash128(rnd);
|
||||
bundleDetails.Dependencies = new string[] { "Dependencies_" + rnd.Next(), "Dependencies_" + rnd.Next(), "Dependencies_" + rnd.Next() };
|
||||
return bundleDetails;
|
||||
}
|
||||
|
||||
static SerializedFileMetaData CreateSyntheticSerializedFileMetaData(System.Random rnd)
|
||||
{
|
||||
SerializedFileMetaData serializedFileMetaData = new SerializedFileMetaData();
|
||||
serializedFileMetaData.RawFileHash = CreateHash128(rnd);
|
||||
serializedFileMetaData.ContentHash = CreateHash128(rnd);
|
||||
return serializedFileMetaData;
|
||||
}
|
||||
|
||||
// Test some data serializes/deserializes correctly
|
||||
// The 'data' instance is serialized to a MemoryStream then deserialized back again. The original and deserialized instances are then converted to text with the DumpToText utility class and the resultant text compared for equality
|
||||
// The deserialized object is then compared directly to the original.
|
||||
void TestSerializeData<DataType>(DataType data) where DataType : new()
|
||||
{
|
||||
TestSerializeData(data, null, null, null, true);
|
||||
}
|
||||
|
||||
// Test some data serializes/deserializes correctly optionally using a collection of custom serializers, deserializers, object factories and custom object->text dumpers
|
||||
// The 'data' instance is serialized to a MemoryStream then deserialized back again. The original and deserialized instances are then converted to text with the DumpToText utility class and the resultant text compared for equality
|
||||
// If the 'testEquality' flag is true then the deserialized object is compared directly to the original. CachedInfo is the only type that doesn't do this as the full equality operation is not supported there
|
||||
void TestSerializeData<DataType>(DataType data,
|
||||
ICustomSerializer[] customSerializers,
|
||||
(Type, DeSerializer.ObjectFactory)[] objectFactories,
|
||||
DumpToText.ICustomDumper[] customDumpers,
|
||||
bool testEquality) where DataType : new()
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
// Write to stream
|
||||
Serializer serializer = new Serializer(customSerializers);
|
||||
serializer.Serialize(stream, data, 1);
|
||||
|
||||
// Read from stream
|
||||
stream.Position = 0;
|
||||
DeSerializer deserializer = new DeSerializer(customSerializers, objectFactories);
|
||||
DataType deserializedData = deserializer.DeSerialize<DataType>(stream);
|
||||
|
||||
// Dump initial object to text
|
||||
DumpToText dumper = new DumpToText(customDumpers);
|
||||
string originalText = dumper.Dump("Data", data).ToString();
|
||||
|
||||
#if DUMP_DATA_TEXT_TO_CONSOLE
|
||||
Debug.Log("Original:");
|
||||
Array.ForEach(originalText.Split('\n'), (textLine) => Debug.Log(textLine));
|
||||
#endif
|
||||
|
||||
// Dump deserialized object to text
|
||||
dumper.Clear();
|
||||
string deserializedText = dumper.Dump("Data", deserializedData).ToString();
|
||||
|
||||
#if DUMP_DATA_TEXT_TO_CONSOLE
|
||||
Debug.Log("Deserialized:");
|
||||
Array.ForEach(deserializedText.Split('\n'), (textLine) => Debug.Log(textLine));
|
||||
#endif
|
||||
|
||||
Assert.AreEqual(originalText, deserializedText);
|
||||
|
||||
if (testEquality)
|
||||
Assert.AreEqual(data, deserializedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8680029523237e1458574f6c8f1a3aa3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "Unity.ScriptableBuildPipeline.Editor.Tests",
|
||||
"references": [
|
||||
"Unity.ScriptableBuildPipeline.Editor",
|
||||
"Unity.ScriptableBuildPipeline",
|
||||
"Unity.PerformanceTesting",
|
||||
"Unity.PerformanceTesting.Editor"
|
||||
],
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0244ca4987514734a84f74195d54113d
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,519 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Injector;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Tasks;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEditor.Build.Pipeline.WriteTypes;
|
||||
using UnityEditor.Build.Player;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using static UnityEditor.Build.Pipeline.Utilities.BuildLog;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
public class WriteSerializedFileTests
|
||||
{
|
||||
class TestBuildParameters : TestBundleBuildParameters
|
||||
{
|
||||
public override bool UseCache { get; set; }
|
||||
public override string TempOutputFolder { get; set; }
|
||||
public override bool NonRecursiveDependencies { get; set; }
|
||||
|
||||
internal BuildSettings TestBuildSettings;
|
||||
public override BuildSettings GetContentBuildSettings()
|
||||
{
|
||||
return TestBuildSettings;
|
||||
}
|
||||
}
|
||||
|
||||
class TestDependencyData : TestDependencyDataBase
|
||||
{
|
||||
public Dictionary<GUID, SceneDependencyInfo> TestSceneInfo = new Dictionary<GUID, SceneDependencyInfo>();
|
||||
public override Dictionary<GUID, SceneDependencyInfo> SceneInfo => TestSceneInfo;
|
||||
public override BuildUsageTagGlobal GlobalUsage => default(BuildUsageTagGlobal);
|
||||
}
|
||||
|
||||
class TestWriteData : TestWriteDataBase
|
||||
{
|
||||
internal List<IWriteOperation> TestOps = new List<IWriteOperation>();
|
||||
public override List<IWriteOperation> WriteOperations => TestOps;
|
||||
}
|
||||
|
||||
class TestBuildResults : TestBuildResultsBase
|
||||
{
|
||||
Dictionary<string, WriteResult> m_Results = new Dictionary<string, WriteResult>();
|
||||
Dictionary<string, SerializedFileMetaData> m_MetaData = new Dictionary<string, SerializedFileMetaData>();
|
||||
|
||||
public override Dictionary<string, WriteResult> WriteResults => m_Results;
|
||||
|
||||
public override Dictionary<string, SerializedFileMetaData> WriteResultsMetaData => m_MetaData;
|
||||
}
|
||||
|
||||
class TestWriteOperation : IWriteOperation
|
||||
{
|
||||
internal int TestWriteCount;
|
||||
public bool OutputSerializedFile = false;
|
||||
public bool WriteBundle = false;
|
||||
public WriteCommand TestCommand;
|
||||
public WriteCommand Command { get => TestCommand; set => throw new System.NotImplementedException(); }
|
||||
public BuildUsageTagSet UsageSet { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
|
||||
public BuildReferenceMap ReferenceMap { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
|
||||
public Hash128 DependencyHash { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
Hash128 debugHash = new Hash128();
|
||||
public void SetDebugHash(uint hash)
|
||||
{
|
||||
debugHash = new Hash128(0, 0, 0, hash);
|
||||
}
|
||||
|
||||
public Hash128 GetHash128(IBuildLogger log)
|
||||
{
|
||||
return HashingMethods.Calculate(debugHash, new Hash128(0, 0, 0, (uint)QualitySettingsApi.GetNumberOfLODsStripped())).ToHash128();
|
||||
}
|
||||
|
||||
public Hash128 GetHash128()
|
||||
{
|
||||
return GetHash128(null);
|
||||
}
|
||||
|
||||
internal static void WriteRandomData(Stream s, long size, int seed)
|
||||
{
|
||||
System.Random r = new System.Random(seed);
|
||||
|
||||
long written = 0;
|
||||
byte[] bytes = new byte[Math.Min(1 * 1024 * 1024, size)];
|
||||
while (written < size)
|
||||
{
|
||||
r.NextBytes(bytes);
|
||||
int writeSize = (int)Math.Min(size - written, bytes.Length);
|
||||
s.Write(bytes, 0, writeSize);
|
||||
written += bytes.Length;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void WriteRandomData(string filename, long size, int seed)
|
||||
{
|
||||
using (var s = File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite))
|
||||
{
|
||||
WriteRandomData(s, size, seed);
|
||||
}
|
||||
}
|
||||
|
||||
internal string CreateFileOfSize(string path, long size)
|
||||
{
|
||||
System.Random r = new System.Random(0);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
WriteRandomData(path, size, 0);
|
||||
return path;
|
||||
}
|
||||
|
||||
public WriteResult Write(string outputFolder, BuildSettings settings, BuildUsageTagGlobal globalUsage)
|
||||
{
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
if (WriteBundle)
|
||||
{
|
||||
return ContentBuildInterface.WriteSerializedFile(outputFolder, new WriteParameters
|
||||
{
|
||||
writeCommand = new WriteCommand() { fileName = "bundle", internalName = "bundle" },
|
||||
settings = settings,
|
||||
globalUsage = globalUsage,
|
||||
usageSet = new BuildUsageTagSet(),
|
||||
referenceMap = new BuildReferenceMap(),
|
||||
bundleInfo = new AssetBundleInfo() { bundleName = "bundle" }
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
string filename = Path.Combine(outputFolder, "resourceFilename");
|
||||
CreateFileOfSize(filename, 1024);
|
||||
TestWriteCount++;
|
||||
WriteResult result = new WriteResult();
|
||||
ResourceFile file = new ResourceFile();
|
||||
file.SetFileName(filename);
|
||||
|
||||
file.SetSerializedFile(OutputSerializedFile);
|
||||
result.SetResourceFiles(new ResourceFile[] { file });
|
||||
|
||||
if (OutputSerializedFile)
|
||||
{
|
||||
var obj1 = new ObjectSerializedInfo();
|
||||
SerializedLocation header = new SerializedLocation();
|
||||
header.SetFileName(result.resourceFiles[0].fileAlias);
|
||||
header.SetOffset(100);
|
||||
obj1.SetHeader(header);
|
||||
|
||||
var obj2 = new ObjectSerializedInfo();
|
||||
SerializedLocation header2 = new SerializedLocation();
|
||||
header2.SetFileName(result.resourceFiles[0].fileAlias);
|
||||
header2.SetOffset(200);
|
||||
obj2.SetHeader(header2);
|
||||
|
||||
result.SetSerializedObjects(new ObjectSerializedInfo[] { obj1, obj2 });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
TestBuildParameters m_BuildParameters;
|
||||
TestDependencyData m_DependencyData;
|
||||
TestWriteData m_WriteData;
|
||||
TestBuildResults m_BuildResults;
|
||||
WriteSerializedFiles m_Task;
|
||||
BuildCache m_Cache;
|
||||
BuildContext m_Context;
|
||||
BuildLog m_Log;
|
||||
string m_TestTempDir;
|
||||
bool m_PreviousSlimSettings;
|
||||
bool m_PreviousStripUnusedMeshComponents;
|
||||
bool m_PreviousBakeCollisionMeshes;
|
||||
int m_PreviousQualityLevel;
|
||||
List<int> m_PreviousMaximumLODLeve = new List<int>();
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
bool m_PreviousMipStripping;
|
||||
List<int> m_PreviousMasterTextureLimits = new List<int>();
|
||||
#endif
|
||||
GraphicsDeviceType[] m_PreviousGraphicsAPIs;
|
||||
bool m_PreviousUseDefaultGraphicsAPIs;
|
||||
|
||||
#if UNITY_EDITOR_WIN
|
||||
// NOTE: Direct3D12 is marked as experimental, so use Direct3D11 for the after test
|
||||
GraphicsDeviceType[] m_PlatformAPIsBefore = { GraphicsDeviceType.Direct3D12, GraphicsDeviceType.Direct3D11 };
|
||||
GraphicsDeviceType[] m_PlatformAPIsAfter = { GraphicsDeviceType.Direct3D11 };
|
||||
#elif UNITY_EDITOR_OSX
|
||||
GraphicsDeviceType[] m_PlatformAPIsBefore = { GraphicsDeviceType.OpenGLCore, GraphicsDeviceType.Metal };
|
||||
GraphicsDeviceType[] m_PlatformAPIsAfter = { GraphicsDeviceType.Metal };
|
||||
#elif UNITY_EDITOR_LINUX
|
||||
GraphicsDeviceType[] m_PlatformAPIsBefore = { GraphicsDeviceType.OpenGLCore, GraphicsDeviceType.Vulkan };
|
||||
GraphicsDeviceType[] m_PlatformAPIsAfter = { GraphicsDeviceType.Vulkan };
|
||||
#endif
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
m_PreviousSlimSettings = ScriptableBuildPipeline.slimWriteResults;
|
||||
ScriptableBuildPipeline.s_Settings.slimWriteResults = false;
|
||||
m_PreviousStripUnusedMeshComponents = PlayerSettings.stripUnusedMeshComponents;
|
||||
PlayerSettings.stripUnusedMeshComponents = false;
|
||||
m_PreviousBakeCollisionMeshes = PlayerSettings.bakeCollisionMeshes;
|
||||
PlayerSettings.bakeCollisionMeshes = false;
|
||||
QualitySettings_GetPrevious();
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
m_PreviousMipStripping = PlayerSettings.mipStripping;
|
||||
PlayerSettings.mipStripping = false;
|
||||
#endif
|
||||
m_PreviousUseDefaultGraphicsAPIs = PlayerSettings.GetUseDefaultGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget);
|
||||
PlayerSettings.SetUseDefaultGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget, false);
|
||||
m_PreviousGraphicsAPIs = PlayerSettings.GetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget);
|
||||
PlayerSettings.SetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget, m_PlatformAPIsBefore);
|
||||
|
||||
BuildCache.PurgeCache(false);
|
||||
|
||||
m_TestTempDir = Path.Combine("Temp", "test");
|
||||
Directory.CreateDirectory(m_TestTempDir);
|
||||
|
||||
m_BuildParameters = new TestBuildParameters();
|
||||
m_BuildParameters.UseCache = true;
|
||||
m_BuildParameters.TempOutputFolder = m_TestTempDir;
|
||||
m_BuildParameters.TestBuildSettings = new BuildSettings { target = EditorUserBuildSettings.activeBuildTarget };
|
||||
m_DependencyData = new TestDependencyData();
|
||||
m_WriteData = new TestWriteData();
|
||||
m_WriteData.TestOps = new List<IWriteOperation>();
|
||||
m_BuildResults = new TestBuildResults();
|
||||
m_Task = new WriteSerializedFiles();
|
||||
m_Cache = new BuildCache();
|
||||
m_Log = new BuildLog();
|
||||
|
||||
m_Context = new BuildContext(m_BuildParameters, m_DependencyData, m_WriteData, m_BuildResults, m_Cache, m_Log);
|
||||
ContextInjector.Inject(m_Context, m_Task);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
Directory.Delete(m_TestTempDir, true);
|
||||
ScriptableBuildPipeline.s_Settings.slimWriteResults = m_PreviousSlimSettings;
|
||||
PlayerSettings.stripUnusedMeshComponents = m_PreviousStripUnusedMeshComponents;
|
||||
PlayerSettings.bakeCollisionMeshes = m_PreviousBakeCollisionMeshes;
|
||||
QualitySettings_RestorePrevious();
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
PlayerSettings.mipStripping = m_PreviousMipStripping;
|
||||
#endif
|
||||
PlayerSettings.SetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget, m_PreviousGraphicsAPIs);
|
||||
PlayerSettings.SetUseDefaultGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget, m_PreviousUseDefaultGraphicsAPIs);
|
||||
m_Cache.Dispose();
|
||||
}
|
||||
|
||||
void QualitySettings_GetPrevious()
|
||||
{
|
||||
m_PreviousQualityLevel = QualitySettings.GetQualityLevel();
|
||||
QualitySettings.SetQualityLevel(0);
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
m_PreviousMasterTextureLimits.Clear();
|
||||
#endif
|
||||
m_PreviousMaximumLODLeve.Clear();
|
||||
|
||||
var count = QualitySettings.names.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
m_PreviousMasterTextureLimits.Add(QualitySettings.masterTextureLimit);
|
||||
QualitySettings.masterTextureLimit = 0;
|
||||
#endif
|
||||
m_PreviousMaximumLODLeve.Add(QualitySettings.maximumLODLevel);
|
||||
QualitySettings.maximumLODLevel = 0;
|
||||
QualitySettings.IncreaseLevel();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
void SetQualitySettings_MasterTextureLimits(int limit)
|
||||
{
|
||||
QualitySettings.SetQualityLevel(0);
|
||||
|
||||
for (int i = 0; i < m_PreviousMasterTextureLimits.Count; i++)
|
||||
{
|
||||
QualitySettings.masterTextureLimit = limit;
|
||||
QualitySettings.IncreaseLevel();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
void SetQualitySettings_MaximumLODLevel(int level)
|
||||
{
|
||||
QualitySettings.SetQualityLevel(0);
|
||||
|
||||
for (int i = 0; i < m_PreviousMaximumLODLeve.Count; i++)
|
||||
{
|
||||
QualitySettings.maximumLODLevel = level;
|
||||
QualitySettings.IncreaseLevel();
|
||||
}
|
||||
}
|
||||
|
||||
void QualitySettings_RestorePrevious()
|
||||
{
|
||||
QualitySettings.SetQualityLevel(0);
|
||||
|
||||
var count = QualitySettings.names.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
QualitySettings.masterTextureLimit = m_PreviousMasterTextureLimits[i];
|
||||
#endif
|
||||
QualitySettings.maximumLODLevel = m_PreviousMaximumLODLeve[i];
|
||||
QualitySettings.IncreaseLevel();
|
||||
}
|
||||
QualitySettings.SetQualityLevel(m_PreviousQualityLevel);
|
||||
}
|
||||
|
||||
TestWriteOperation AddTestOperation(string name = "testInternalName")
|
||||
{
|
||||
TestWriteOperation op = new TestWriteOperation();
|
||||
op.TestCommand = new WriteCommand();
|
||||
op.TestCommand.internalName = name;
|
||||
m_WriteData.WriteOperations.Add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public static IEnumerable RebuildTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData(false, new Action<WriteSerializedFileTests>((_this) => {})).SetName("NoChanges");
|
||||
yield return new TestCaseData(true, new Action<WriteSerializedFileTests>((_this) => { _this.m_BuildParameters.TestBuildSettings.buildFlags |= ContentBuildFlags.DisableWriteTypeTree; })).SetName("BuildSettings");
|
||||
yield return new TestCaseData(true, new Action<WriteSerializedFileTests>((_this) => { ((TestWriteOperation)_this.m_WriteData.WriteOperations[0]).SetDebugHash(27); })).SetName("OperationHash");
|
||||
yield return new TestCaseData(true, new Action<WriteSerializedFileTests>((_this) => { ScriptableBuildPipeline.s_Settings.slimWriteResults = true; })).SetName("SlimWriteResults");
|
||||
yield return new TestCaseData(true, new Action<WriteSerializedFileTests>((_this) => { PlayerSettings.stripUnusedMeshComponents = true; })).SetName("StripUnusedMeshComponents");
|
||||
yield return new TestCaseData(true, new Action<WriteSerializedFileTests>((_this) => { PlayerSettings.bakeCollisionMeshes = true; })).SetName("BakeCollisionMeshes");
|
||||
yield return new TestCaseData(true, new Action<WriteSerializedFileTests>((_this) => { _this.SetQualitySettings_MaximumLODLevel(1); })).SetName("LODStripping");
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
yield return new TestCaseData(true, new Action<WriteSerializedFileTests>((_this) => { _this.SetQualitySettings_MasterTextureLimits(1); PlayerSettings.mipStripping = true; })).SetName("MipStripping");
|
||||
#endif
|
||||
yield return new TestCaseData(true, new Action<WriteSerializedFileTests>((_this) => { PlayerSettings.SetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget, _this.m_PlatformAPIsAfter); })).SetName("GraphicsAPIs");
|
||||
}
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(typeof(WriteSerializedFileTests), "RebuildTestCases")]
|
||||
public void WhenInputsChanges_OnlyChangedDependenciesTriggersRebuild(bool shouldRebuild, Action<WriteSerializedFileTests> postFirstBuildAction)
|
||||
{
|
||||
TestWriteOperation op = AddTestOperation();
|
||||
m_Task.Run();
|
||||
Assert.AreEqual(1, op.TestWriteCount);
|
||||
m_Cache.SyncPendingSaves();
|
||||
postFirstBuildAction(this);
|
||||
m_BuildResults.WriteResults.Clear();
|
||||
m_BuildResults.WriteResultsMetaData.Clear();
|
||||
m_Task.Run();
|
||||
Assert.AreEqual(shouldRebuild ? 2 : 1, op.TestWriteCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenFileHasSerializedObjects_AndSlimMode_OnlyFirstObjectInWriteResults([Values] bool slimEnabled)
|
||||
{
|
||||
TestWriteOperation op = AddTestOperation();
|
||||
op.OutputSerializedFile = true;
|
||||
ScriptableBuildPipeline.s_Settings.slimWriteResults = slimEnabled;
|
||||
m_Task.Run();
|
||||
Assert.AreEqual(1, op.TestWriteCount);
|
||||
WriteResult r = m_BuildResults.WriteResults[op.TestCommand.internalName];
|
||||
if (slimEnabled)
|
||||
{
|
||||
Assert.AreEqual(1, r.serializedObjects.Count);
|
||||
Assert.AreEqual(100, r.serializedObjects[0].header.offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(2, r.serializedObjects.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenResourceFileIsNotASerializedFile_ContentHashIsFullFileHash()
|
||||
{
|
||||
TestWriteOperation op = AddTestOperation();
|
||||
|
||||
m_Task.Run();
|
||||
Assert.AreEqual(1, op.TestWriteCount);
|
||||
SerializedFileMetaData md = m_BuildResults.WriteResultsMetaData[op.TestCommand.internalName];
|
||||
WriteResult result = m_BuildResults.WriteResults[op.TestCommand.internalName];
|
||||
Assert.AreEqual(md.RawFileHash, md.ContentHash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenResourceFileIsASerializedFile_ContentHashBeginsAtFirstObject()
|
||||
{
|
||||
TestWriteOperation op = AddTestOperation();
|
||||
op.OutputSerializedFile = true;
|
||||
m_Task.Run();
|
||||
Assert.AreEqual(1, op.TestWriteCount);
|
||||
SerializedFileMetaData md = m_BuildResults.WriteResultsMetaData[op.TestCommand.internalName];
|
||||
WriteResult result = m_BuildResults.WriteResults[op.TestCommand.internalName];
|
||||
|
||||
Hash128 expectedContentHash;
|
||||
using (FileStream fs = File.OpenRead(result.resourceFiles[0].fileName))
|
||||
{
|
||||
fs.Position = (long)result.serializedObjects[0].header.offset;
|
||||
expectedContentHash = HashingMethods.Calculate(new List<object>() { HashingMethods.CalculateStream(fs) }).ToHash128();
|
||||
}
|
||||
var objs = new List<object>() { HashingMethods.CalculateFile(result.resourceFiles[0].fileName) };
|
||||
Hash128 fullFileHash = HashingMethods.Calculate(objs).ToHash128();
|
||||
|
||||
Assert.AreEqual(fullFileHash, md.RawFileHash);
|
||||
Assert.AreEqual(expectedContentHash, md.ContentHash);
|
||||
Assert.AreNotEqual(md.RawFileHash, md.ContentHash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenWriteResultsContainsMultipleSerializedFiles_ContentHashBeginsAtFirstObjectOfEachFile()
|
||||
{
|
||||
var resourceFiles = new ResourceFile[2];
|
||||
resourceFiles[0].SetFileAlias("sf1");
|
||||
resourceFiles[0].SetFileName($"{m_TestTempDir}/sf1");
|
||||
resourceFiles[0].SetSerializedFile(true);
|
||||
resourceFiles[1].SetFileAlias("sf2");
|
||||
resourceFiles[1].SetFileName($"{m_TestTempDir}/sf2");
|
||||
resourceFiles[1].SetSerializedFile(true);
|
||||
|
||||
var header1 = new SerializedLocation();
|
||||
header1.SetFileName(resourceFiles[0].fileAlias);
|
||||
header1.SetOffset(200);
|
||||
|
||||
var header2 = new SerializedLocation();
|
||||
header2.SetFileName(resourceFiles[1].fileAlias);
|
||||
header2.SetOffset(100);
|
||||
|
||||
var serializedObjects = new ObjectSerializedInfo[2];
|
||||
serializedObjects[0].SetHeader(header1);
|
||||
serializedObjects[1].SetHeader(header2);
|
||||
|
||||
WriteResult results = new WriteResult();
|
||||
results.SetResourceFiles(resourceFiles);
|
||||
results.SetSerializedObjects(serializedObjects);
|
||||
|
||||
// Create 2 files with bytes that are only different between the 2 m_Offsets
|
||||
byte[] bytes = new byte[400];
|
||||
for (int i = 0; i < 200; i++)
|
||||
bytes[i] = 1;
|
||||
for (int i = 200; i < 400; i++)
|
||||
bytes[i] = 2;
|
||||
File.WriteAllBytes(results.resourceFiles[0].fileName, bytes);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
bytes[i] = 1;
|
||||
for (int i = 100; i < 200; i++)
|
||||
bytes[i] = 3;
|
||||
for (int i = 200; i < 400; i++)
|
||||
bytes[i] = 2;
|
||||
File.WriteAllBytes(results.resourceFiles[1].fileName, bytes);
|
||||
|
||||
var data1 = WriteSerializedFiles.CalculateFileMetadata(ref results);
|
||||
|
||||
// Now update the file bytes between the 2 m_Offsets
|
||||
for (int i = 100; i < 200; i++)
|
||||
bytes[i] = 4;
|
||||
File.WriteAllBytes(results.resourceFiles[1].fileName, bytes);
|
||||
|
||||
var data2 = WriteSerializedFiles.CalculateFileMetadata(ref results);
|
||||
|
||||
Assert.AreNotEqual(data1.RawFileHash, data2.RawFileHash);
|
||||
Assert.AreNotEqual(data1.ContentHash, data2.ContentHash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Run_CallsWriteOnOperationAndOutputsWriteResult()
|
||||
{
|
||||
TestWriteOperation op = AddTestOperation();
|
||||
ReturnCode result = m_Task.Run();
|
||||
Assert.AreEqual(1, op.TestWriteCount);
|
||||
WriteResult reportedResult = m_BuildResults.WriteResults[op.Command.internalName];
|
||||
FileAssert.Exists(reportedResult.resourceFiles[0].fileName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Run_WithoutCache_Succeeds()
|
||||
{
|
||||
m_BuildParameters.UseCache = false;
|
||||
AddTestOperation("testOp1");
|
||||
AddTestOperation("testOp2");
|
||||
|
||||
ReturnCode result = m_Task.Run();
|
||||
Assert.AreEqual(ReturnCode.Success, result);
|
||||
|
||||
m_BuildParameters.UseCache = true;
|
||||
}
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER || ENABLE_DETAILED_PROFILE_CAPTURING
|
||||
[Test]
|
||||
public void WhenWritingSerializedFilesAndUsingDetailedBuildLog_ProfileCaptureScope_CreatesLogEventsWithinTaskThreshold()
|
||||
{
|
||||
TestWriteOperation op = AddTestOperation();
|
||||
op.WriteBundle = true;
|
||||
bool useDetailedBuildLog = ScriptableBuildPipeline.useDetailedBuildLog;
|
||||
ScriptableBuildPipeline.useDetailedBuildLog = true;
|
||||
|
||||
m_Task.Run();
|
||||
LogStep runCachedOp = m_Log.Root.Children.Find(x => x.Name == "RunCachedOperation");
|
||||
LogStep processEntries = runCachedOp.Children.Find(x => x.Name == "Process Entries");
|
||||
LogStep writingOp = processEntries.Children.Find(x => x.Name == "Writing TestWriteOperation");
|
||||
|
||||
Assert.IsTrue(writingOp.Children.Count > 0);
|
||||
|
||||
double taskEndTime = runCachedOp.StartTime + runCachedOp.DurationMS;
|
||||
foreach(LogStep e in writingOp.Children)
|
||||
{
|
||||
Assert.LessOrEqual(e.StartTime + e.DurationMS, taskEndTime);
|
||||
}
|
||||
ScriptableBuildPipeline.useDetailedBuildLog = useDetailedBuildLog;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6a7a6cf901dabe64586a9bd99395189a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,124 @@
|
|||
using NUnit.Framework;
|
||||
using UnityEditor.Build.Content;
|
||||
using UnityEditor.Build.Pipeline.Interfaces;
|
||||
using UnityEditor.Build.Pipeline.Utilities;
|
||||
using UnityEditor.Build.Pipeline.WriteTypes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Tests
|
||||
{
|
||||
class WriteTypesTests
|
||||
{
|
||||
IWriteOperation[] WriteOperations;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
WriteOperations = new IWriteOperation[5];
|
||||
WriteOperations[0] = new AssetBundleWriteOperation();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
WriteOperations[1] = new RawWriteOperation();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
WriteOperations[2] = new SceneBundleWriteOperation();
|
||||
WriteOperations[3] = new SceneDataWriteOperation();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
WriteOperations[4] = new SceneRawWriteOperation();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
var command = new WriteCommand
|
||||
{
|
||||
fileName = GUID.Generate().ToString(),
|
||||
internalName = GUID.Generate().ToString()
|
||||
};
|
||||
var usageSet = new BuildUsageTagSet();
|
||||
var referenceMap = new BuildReferenceMap();
|
||||
|
||||
for (int i = 0; i < WriteOperations.Length; i++)
|
||||
{
|
||||
WriteOperations[i].Command = command;
|
||||
WriteOperations[i].UsageSet = usageSet;
|
||||
WriteOperations[i].ReferenceMap = referenceMap;
|
||||
}
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OnetimeTearDown()
|
||||
{
|
||||
WriteOperations = null;
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
BuildInterfacesWrapper.SceneCallbackVersionHash = new Hash128();
|
||||
BuildInterfacesWrapper.ShaderCallbackVersionHash = new Hash128();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
BuildInterfacesWrapper.SceneCallbackVersionHash = new Hash128();
|
||||
BuildInterfacesWrapper.ShaderCallbackVersionHash = new Hash128();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Changing_SceneCallbackVersionHash_ChangesHashesOf_SceneWriteOperations()
|
||||
{
|
||||
Hash128[] preHash = new Hash128[5];
|
||||
for (int i = 0; i < WriteOperations.Length; i++)
|
||||
preHash[i] = WriteOperations[i].GetHash128();
|
||||
|
||||
BuildInterfacesWrapper.SceneCallbackVersionHash = HashingMethods.Calculate(GUID.Generate()).ToHash128();
|
||||
|
||||
Hash128[] postHash = new Hash128[5];
|
||||
for (int i = 0; i < WriteOperations.Length; i++)
|
||||
postHash[i] = WriteOperations[i].GetHash128();
|
||||
|
||||
// We expect AssetBundleWriteOperation [0] and RawWriteOperation [1] to not be changed by the scene callback version hash.
|
||||
for (int i = 0; i < 2; i++)
|
||||
Assert.AreEqual(preHash[i], postHash[i], "{0} hash changed.", WriteOperations[i].GetType().Name);
|
||||
|
||||
// We expect SceneBundleWriteOperation [2], SceneDataWriteOperation [3], and SceneRawWriteOperation [4] to be changed by the scene callback version hash.
|
||||
for (int i = 2; i < WriteOperations.Length; i++)
|
||||
Assert.AreNotEqual(preHash[i], postHash[i], "{0} hash unchanged. Not", WriteOperations[i].GetType().Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Changing_ShaderCallbackVersionHash_ChangesHashesOf_AllWriteOperations()
|
||||
{
|
||||
Hash128[] preHash = new Hash128[5];
|
||||
for (int i = 0; i < WriteOperations.Length; i++)
|
||||
preHash[i] = WriteOperations[i].GetHash128();
|
||||
|
||||
BuildInterfacesWrapper.ShaderCallbackVersionHash = HashingMethods.Calculate(GUID.Generate()).ToHash128();
|
||||
|
||||
Hash128[] postHash = new Hash128[5];
|
||||
for (int i = 0; i < WriteOperations.Length; i++)
|
||||
postHash[i] = WriteOperations[i].GetHash128();
|
||||
|
||||
// We expect all write operation hashes to be changed by the scene callback version hash.
|
||||
for (int i = 0; i < WriteOperations.Length; i++)
|
||||
Assert.AreNotEqual(preHash[i], postHash[i], "{0} hash unchanged.", WriteOperations[i].GetType().Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unchanged_CallbackVersionHashes_DoNotChangeHashesOf_AllWriteOperations()
|
||||
{
|
||||
Hash128[] preHash = new Hash128[5];
|
||||
for (int i = 0; i < WriteOperations.Length; i++)
|
||||
preHash[i] = WriteOperations[i].GetHash128();
|
||||
|
||||
// Just zero out the hash again
|
||||
BuildInterfacesWrapper.SceneCallbackVersionHash = new Hash128();
|
||||
BuildInterfacesWrapper.ShaderCallbackVersionHash = new Hash128();
|
||||
|
||||
Hash128[] postHash = new Hash128[5];
|
||||
for (int i = 0; i < WriteOperations.Length; i++)
|
||||
postHash[i] = WriteOperations[i].GetHash128();
|
||||
|
||||
// We expect no write operation hashes to be changed by the scene callback version hash.
|
||||
for (int i = 0; i < WriteOperations.Length; i++)
|
||||
Assert.AreEqual(preHash[i], postHash[i], "{0} hash changed.", WriteOperations[i].GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0b92edd448d83cc4d8558e4e11ffc3bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "Unity.ScriptableBuildPipeline.Tests",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:27619889b8ba8c24980f49ee34dbb44a",
|
||||
"GUID:c5ecc461727906345a35491a0440694f",
|
||||
"GUID:0acc523941302664db1f4e527237feb3"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: adbcba91892774149bea528141373ca2
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue