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; using static UnityEditor.Build.Pipeline.Tasks.CalculateAssetDependencyData; namespace UnityEditor.Build.Pipeline.Tests { /// /// CalculateAssetDependencyTests /// 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; } public override BuildSettings GetContentBuildSettings() { return new BuildSettings { group = Group, target = Target, typeDB = ScriptInfo, buildFlags = ContentBuildFlags }; } } class TestContent : TestBundleBuildContent { public List TestAssets = new List(); // Inputs public override List Assets => TestAssets; } class TestDependencyData : TestDependencyDataBase { public Dictionary TestAssetInfo = new Dictionary(); public Dictionary TestAssetUsage = new Dictionary(); public Dictionary TestSceneInfo = new Dictionary(); public Dictionary TestSceneUsage = new Dictionary(); public Dictionary TestDependencyHash = new Dictionary(); // Inputs public override BuildUsageCache DependencyUsageCache => null; public override BuildUsageTagGlobal GlobalUsage => new BuildUsageTagGlobal(); // Outputs public override Dictionary AssetInfo => TestAssetInfo; public override Dictionary AssetUsage => TestAssetUsage; public override Dictionary SceneInfo => TestSceneInfo; public override Dictionary SceneUsage => TestSceneUsage; public override Dictionary DependencyHash => TestDependencyHash; } class TestExtendedAssetData : TestBundleExtendedAssetData { public Dictionary TestExtendedData = new Dictionary(); public override Dictionary ExtendedData => TestExtendedData; } static CalculateAssetDependencyData CreateDefaultBuildTask(List 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(); } // Mimic the way the Task treats recursive dependencies static ReturnCode RunAndExpand(CalculateAssetDependencyData.TaskInput input, out CalculateAssetDependencyData.TaskOutput output) { var result = CalculateAssetDependencyData.RunInternal(input, out output); if (input.NonRecursiveDependencies || result != ReturnCode.Success) return result; var depsMap = new Dictionary>(); foreach (AssetOutput o in output.AssetResults) { foreach (var info in o.objectDependencyInfo) depsMap[info.Object] = info.Dependencies; } foreach (AssetOutput o in output.AssetResults) CalculateAssetDependencyData.ExpandReferences(o, depsMap); return result; } /// /// Setup /// [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(); } /// /// OneTimeTeardown /// [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(kSpriteTexture1Asset), AssetDatabase.LoadAssetAtPath(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; } /// /// RecursiveMode /// public enum RecursiveMode { /// /// NonRecursive /// NonRecursive, /// /// Recursive /// Recursive, } /// /// WhenAssetHasNoDependencies /// /// RecursiveMode [Test] public void WhenAssetHasNoDependencies([Values] RecursiveMode rm) { // Create an asset string assetPath = Path.Combine(kTestAssetFolder, "myPrefab.prefab"); GUID guid = CreateGameObject(assetPath); CalculateAssetDependencyData.TaskInput input = CreateDefaultInput(); input.Assets = new List() { guid }; input.NonRecursiveDependencies = (rm == RecursiveMode.NonRecursive); RunAndExpand(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); } /// /// WhenAssetDoesNotExist_AssetResultIsEmpty /// [Test] public void WhenAssetDoesNotExist_AssetResultIsEmpty() { CalculateAssetDependencyData.TaskInput input = CreateDefaultInput(); input.Assets = new List() { 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); } /// /// WhenSomeAssetDataIsCached_CachedVersionIsUsed /// [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 allGUIDs = new List(); List cachedGUIDs = new List(); 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(); } } /// /// WhenObjectInfluencesReferencedObjectBuildTags_BuildUsageTagsAreAdded /// [Test] public void WhenObjectInfluencesReferencedObjectBuildTags_BuildUsageTagsAreAdded() { string testShaderPath = Path.Combine(kTestAssetFolder, "TestShader.shader"); CreateTestShader(testShaderPath); Shader shader = AssetDatabase.LoadAssetAtPath(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() { 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() { 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, false }, new object[] { SpritePackerMode.BuildTimeOnlyAtlas, "", true, true, true }, new object[] { SpritePackerMode.BuildTimeOnlyAtlas, "", true, true, false }, new object[] { SpritePackerMode.BuildTimeOnlyAtlas, "", false, false, false }, #else new object[] { SpritePackerMode.BuildTimeOnly, "SomeTag", true, true, false }, new object[] { SpritePackerMode.BuildTimeOnly, "", true, false, false }, new object[] { SpritePackerMode.AlwaysOn, "SomeTag", true, true, false }, new object[] { SpritePackerMode.AlwaysOn, "", true, false, false }, new object[] { SpritePackerMode.Disabled, "", true, false, false }, new object[] { SpritePackerMode.BuildTimeOnlyAtlas, "", true, true, false }, new object[] { SpritePackerMode.AlwaysOnAtlas, "", true, true, false }, new object[] { SpritePackerMode.AlwaysOnAtlas, "", false, false, false } #endif }; /// /// WhenSpriteWithAtlas_SpriteImportDataCreated /// /// spriteMode /// spritePackingTag /// hasReferencingSpriteAtlas /// expectedPacked /// lateBinding [TestCaseSource("SpriteTestCases")] [Test] public void WhenSpriteWithAtlas_SpriteImportDataCreated(SpritePackerMode spriteMode, string spritePackingTag, bool hasReferencingSpriteAtlas, bool expectedPacked, bool lateBinding) { 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(kTestAsset) }; sa.Add(targetObjects); sa.SetIncludeInBuild(!lateBinding); 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() { spriteGUID }; CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output); Assert.AreEqual(expectedPacked, output.AssetResults[0].spriteData.PackedSprite); } static object[] SpriteUtilityTestCases = { new object[] { SpritePackerMode.BuildTimeOnlyAtlas } }; /// /// WhenSpriteWithAtlasUpdated_SpriteInfoUpdated /// /// spriteMode [TestCaseSource("SpriteUtilityTestCases")] [Test] #if UNITY_6000_6_OR_NEWER [Ignore("SpriteAtlas dependencies to their sprites have been removed.")] #endif public void WhenSpriteWithAtlasUpdated_SpriteInfoUpdated(SpritePackerMode spriteMode) { var spriteAtlas = AssetDatabase.LoadAssetAtPath(kSpriteAtlasAsset); var oldSprites = SpriteAtlasExtensions.GetPackables(spriteAtlas); spriteAtlas.Remove(oldSprites); List sprites = new List(); sprites.Add(AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset)); sprites.Add(AssetDatabase.LoadAssetAtPath(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() { spriteGUID }; CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output); Assert.AreEqual(3, output.AssetResults[0].assetInfo.referencedObjects.Count); spriteAtlas = AssetDatabase.LoadAssetAtPath(kSpriteAtlasAsset); oldSprites = SpriteAtlasExtensions.GetPackables(spriteAtlas); spriteAtlas.Remove(oldSprites); sprites = new List(); sprites.Add(AssetDatabase.LoadAssetAtPath(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 }; } } /// /// WhenAssetHasANullRepresentation_LogsWarning /// [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 }; 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 /// /// WhenAssetHasMultipleRepresentations_ExtendedDataContainsAllButMainAsset /// [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 }; CalculateAssetDependencyData.RunInternal(input, out CalculateAssetDependencyData.TaskOutput output); ObjectIdentifier[] allObjIDs = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(guid, EditorUserBuildSettings.activeBuildTarget); HashSet expectedReps = new HashSet(); 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)); } /// /// WhenExplicitSpriteAndAtlas_AtlasOnlyReferencesSprites /// /// RecursiveMode [Test] #if UNITY_6000_6_OR_NEWER [Ignore("SpriteAtlas dependencies to their sprites have been removed.")] #endif public void WhenExplicitSpriteAndAtlas_AtlasOnlyReferencesSprites([Values] RecursiveMode rm) { var sprite1 = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); var sprite2 = AssetDatabase.LoadAssetAtPath(kSpriteTexture2Asset); var texture1 = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); var texture2 = AssetDatabase.LoadAssetAtPath(kSpriteTexture2Asset); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(sprite1, out string sprite1GuidStr, out long sprite1LocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(sprite2, out string sprite2GuidStr, out long sprite2LocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(texture1, out string texture1GuidStr, out long texture1LocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(texture2, out string texture2GuidStr, out long texture2LocalId); GUID spriteAtlasGuid = new GUID(AssetDatabase.AssetPathToGUID(kSpriteAtlasAsset)); GUID sprite1Guid = new GUID(sprite1GuidStr); GUID sprite2Guid = new GUID(sprite2GuidStr); GUID texture1Guid = new GUID(texture1GuidStr); GUID texture2Guid = new GUID(texture2GuidStr); EditorSettings.spritePackerMode = SpritePackerMode.BuildTimeOnlyAtlas; CalculateAssetDependencyData.TaskInput input = CreateDefaultInput(); SpriteAtlasUtility.PackAllAtlases(input.Target); input.Assets = new List() { spriteAtlasGuid, sprite1Guid, sprite2Guid }; input.NonRecursiveDependencies = (rm == RecursiveMode.NonRecursive); RunAndExpand(input, out CalculateAssetDependencyData.TaskOutput output); List referencedObjs = output.AssetResults[0].assetInfo.referencedObjects; Assert.AreEqual(3, referencedObjs.Count); bool refsSprite1 = false; bool refsSprite2 = false; bool refsTexture1 = false; bool refsTexture2 = false; foreach (ObjectIdentifier id in referencedObjs) { if (id.guid == sprite1Guid && id.localIdentifierInFile == sprite1LocalId) refsSprite1 = true; else if (id.guid == texture1Guid && id.localIdentifierInFile == texture1LocalId) refsTexture1 = true; else if (id.guid == sprite2Guid && id.localIdentifierInFile == sprite2LocalId) refsSprite2 = true; else if (id.guid == texture2Guid && id.localIdentifierInFile == texture2LocalId) refsTexture2 = true; } Assert.IsTrue(refsSprite1, "Atlas should reference the first Sprite."); Assert.IsTrue(refsSprite2, "Atlas should reference the second Sprite."); Assert.IsFalse(refsTexture1, "Atlas should not reference the first source texture."); Assert.IsFalse(refsTexture2, "Atlas should not reference the second source texture."); } /// /// WhenExplicitSpritesAndImplicitAtlas_SpritesOnlyReferenceSprites /// /// RecursiveMode [Test] #if UNITY_6000_6_OR_NEWER [Ignore("SpriteAtlas dependencies to their sprites have been removed.")] #endif public void WhenExplicitSpritesAndImplicitAtlas_SpritesOnlyReferenceSprites([Values] RecursiveMode rm) { var sprite1 = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); var sprite2 = AssetDatabase.LoadAssetAtPath(kSpriteTexture2Asset); var texture1 = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); var texture2 = AssetDatabase.LoadAssetAtPath(kSpriteTexture2Asset); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(sprite1, out string sprite1GuidStr, out long sprite1LocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(sprite2, out string sprite2GuidStr, out long sprite2LocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(texture1, out string texture1GuidStr, out long texture1LocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(texture2, out string texture2GuidStr, out long texture2LocalId); GUID sprite1Guid = new GUID(sprite1GuidStr); GUID sprite2Guid = new GUID(sprite2GuidStr); GUID texture1Guid = new GUID(texture1GuidStr); GUID texture2Guid = new GUID(texture2GuidStr); EditorSettings.spritePackerMode = SpritePackerMode.BuildTimeOnlyAtlas; CalculateAssetDependencyData.TaskInput input = CreateDefaultInput(); SpriteAtlasUtility.PackAllAtlases(input.Target); input.Assets = new List() { sprite1Guid, sprite2Guid }; input.NonRecursiveDependencies = (rm == RecursiveMode.NonRecursive); RunAndExpand(input, out CalculateAssetDependencyData.TaskOutput output); List referencedObjs1 = output.AssetResults[0].assetInfo.referencedObjects; Assert.AreEqual(3, referencedObjs1.Count); bool refsSprite2 = false; bool refsTexture2 = false; foreach (ObjectIdentifier id in referencedObjs1) { if (id.guid == sprite2Guid && id.localIdentifierInFile == sprite2LocalId) refsSprite2 = true; else if (id.guid == texture2Guid && id.localIdentifierInFile == texture2LocalId) refsTexture2 = true; } Assert.IsTrue(refsSprite2, "Sprite should reference the second Sprite."); Assert.IsFalse(refsTexture2, "Sprite should not reference the second source texture."); List referencedObjs2 = output.AssetResults[1].assetInfo.referencedObjects; Assert.AreEqual(3, referencedObjs2.Count); bool refsSprite1 = false; bool refsTexture1 = false; foreach (ObjectIdentifier id in referencedObjs2) { if (id.guid == sprite1Guid && id.localIdentifierInFile == sprite1LocalId) refsSprite1 = true; else if (id.guid == texture1Guid && id.localIdentifierInFile == texture1LocalId) refsTexture1 = true; } Assert.IsTrue(refsSprite1, "Sprite should reference the first Sprite."); Assert.IsFalse(refsTexture1, "Sprite should not reference the first source texture."); } /// /// WhenAssetRefsExplicitPackedSprite_AssetOnlyRefsSprite /// /// RecursiveMode [Test] public void WhenAssetRefsExplicitPackedSprite_AssetOnlyRefsSprite([Values] RecursiveMode rm) { var sprite = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); var texture = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); string prefabName = "myPrefab.prefab"; string assetPath = Path.Combine(kTestAssetFolder, prefabName); GameObject go = new GameObject(prefabName); SpriteRenderer sr = go.AddComponent(); sr.sprite = sprite; PrefabUtility.SaveAsPrefabAsset(go, assetPath); UnityEngine.Object.DestroyImmediate(go, false); string prefabGuidStr = AssetDatabase.AssetPathToGUID(assetPath); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(sprite, out string spriteGuidStr, out long spriteLocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(texture, out string textureGuidStr, out long textureLocalId); GUID prefabGuid = new GUID(prefabGuidStr); GUID spriteGuid = new GUID(spriteGuidStr); GUID textureGuid = new GUID(textureGuidStr); EditorSettings.spritePackerMode = SpritePackerMode.BuildTimeOnlyAtlas; CalculateAssetDependencyData.TaskInput input = CreateDefaultInput(); SpriteAtlasUtility.PackAllAtlases(input.Target); input.Assets = new List() { prefabGuid, spriteGuid }; input.NonRecursiveDependencies = (rm == RecursiveMode.NonRecursive); RunAndExpand(input, out CalculateAssetDependencyData.TaskOutput output); List referencedObjs = output.AssetResults[0].assetInfo.referencedObjects; bool refsSprite = false; bool refsTexture = false; foreach (ObjectIdentifier id in referencedObjs) { if (id.guid == spriteGuid && id.localIdentifierInFile == spriteLocalId) refsSprite = true; else if (id.guid == textureGuid && id.localIdentifierInFile == textureLocalId) refsTexture = true; } Assert.IsTrue(refsSprite, "Prefab should reference the Sprite."); Assert.IsFalse(refsTexture, "Prefab should not reference the source texture."); } /// /// WhenAssetRefsExplicitPackedSprite_CachedAssetOnlyRefsSprite /// /// RecursiveMode [Test] public void WhenAssetRefsExplicitPackedSprite_CachedAssetOnlyRefsSprite([Values] RecursiveMode rm) { var sprite = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); var texture = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); string prefabName = "myPrefab.prefab"; string assetPath = Path.Combine(kTestAssetFolder, prefabName); GameObject go = new GameObject(prefabName); SpriteRenderer sr = go.AddComponent(); sr.sprite = sprite; PrefabUtility.SaveAsPrefabAsset(go, assetPath); UnityEngine.Object.DestroyImmediate(go, false); string prefabGuidStr = AssetDatabase.AssetPathToGUID(assetPath); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(sprite, out string spriteGuidStr, out long spriteLocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(texture, out string textureGuidStr, out long textureLocalId); GUID prefabGuid = new GUID(prefabGuidStr); GUID spriteGuid = new GUID(spriteGuidStr); GUID textureGuid = new GUID(textureGuidStr); EditorSettings.spritePackerMode = SpritePackerMode.BuildTimeOnlyAtlas; CalculateAssetDependencyData.TaskInput input = CreateDefaultInput(); SpriteAtlasUtility.PackAllAtlases(input.Target); input.Assets = new List() { prefabGuid, spriteGuid }; input.NonRecursiveDependencies = (rm == RecursiveMode.NonRecursive); using (BuildCache cache = new BuildCache()) { cache.ClearCacheEntryMaps(); // needed if running multiple times input.BuildCache = cache; RunAndExpand(input, out CalculateAssetDependencyData.TaskOutput output1); cache.SyncPendingSaves(); RunAndExpand(input, out CalculateAssetDependencyData.TaskOutput output2); List referencedObjs = output2.AssetResults[0].assetInfo.referencedObjects; bool refsSprite = false; bool refsTexture = false; foreach (ObjectIdentifier id in referencedObjs) { if (id.guid == spriteGuid && id.localIdentifierInFile == spriteLocalId) refsSprite = true; else if (id.guid == textureGuid && id.localIdentifierInFile == textureLocalId) refsTexture = true; } Assert.IsTrue(refsSprite, "Prefab should reference the Sprite."); Assert.IsFalse(refsTexture, "Prefab should not reference the source texture."); } } /// /// WhenAssetRefsExplicitSprite_AssetRefsSpriteAndTexture /// /// RecursiveMode [Test] public void WhenAssetRefsExplicitSprite_AssetRefsSpriteAndTexture([Values] RecursiveMode rm) { var sprite = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); var texture = AssetDatabase.LoadAssetAtPath(kSpriteTexture1Asset); string prefabName = "myPrefab.prefab"; string assetPath = Path.Combine(kTestAssetFolder, prefabName); GameObject go = new GameObject(prefabName); SpriteRenderer sr = go.AddComponent(); sr.sprite = sprite; PrefabUtility.SaveAsPrefabAsset(go, assetPath); UnityEngine.Object.DestroyImmediate(go, false); string prefabGuidStr = AssetDatabase.AssetPathToGUID(assetPath); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(sprite, out string spriteGuidStr, out long spriteLocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(texture, out string textureGuidStr, out long textureLocalId); GUID prefabGuid = new GUID(prefabGuidStr); GUID spriteGuid = new GUID(spriteGuidStr); GUID textureGuid = new GUID(textureGuidStr); CalculateAssetDependencyData.TaskInput input = CreateDefaultInput(); input.Assets = new List() { prefabGuid, spriteGuid }; input.NonRecursiveDependencies = (rm == RecursiveMode.NonRecursive); RunAndExpand(input, out CalculateAssetDependencyData.TaskOutput output); List referencedObjs = output.AssetResults[0].assetInfo.referencedObjects; Assert.AreEqual(4, referencedObjs.Count); bool refsSprite = false; bool refsTexture = false; foreach (ObjectIdentifier id in referencedObjs) { if (id.guid == spriteGuid && id.localIdentifierInFile == spriteLocalId) refsSprite = true; else if (id.guid == textureGuid && id.localIdentifierInFile == textureLocalId) refsTexture = true; } Assert.IsTrue(refsSprite, "Prefab should reference the Sprite."); Assert.IsTrue(refsTexture, "Prefab should reference the source texture."); } 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(); } } /// /// WhenCanceledThroughProgressTracker_ReturnsCanceled /// [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() { 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); } /// /// TaskIsRun_WhenAssetHasNoMultipleRepresentations_ExtendedDataIsEmpty /// [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 }, disableVisibleSubAssetRepresentations); buildTask.Run(); ExtractTestData(buildTask, out TestExtendedAssetData extendedAssetData); Assert.AreEqual(0, extendedAssetData.ExtendedData.Count); } /// /// TaskIsRun_WhenAssetHasMultipleRepresentations_ExtendedDataContainsEntryForAsset /// [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 }, disableVisibleSubAssetRepresentations); buildTask.Run(); ExtractTestData(buildTask, out TestExtendedAssetData extendedAssetData); Assert.AreEqual(1, extendedAssetData.ExtendedData.Count); Assert.IsTrue(extendedAssetData.ExtendedData.ContainsKey(guid)); } /// /// TaskIsRun_WhenAssetHasMultipleRepresentations_AndDisableVisibleSubAssetRepresentations_ExtendedDataIsEmpty /// [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 }, disableVisibleSubAssetRepresentations); buildTask.Run(); ExtractTestData(buildTask, out TestExtendedAssetData extendedAssetData); Assert.AreEqual(0, extendedAssetData.ExtendedData.Count); } // Embedding this model in code and only creating it when the test actually runs so it doesn't exist outside tests. string kTestModel = @"g cube # Vertices v 0.0 0.0 0.0 # 1 a v 0.0 1.0 0.0 # 2 b v 1.0 1.0 0.0 # 3 c v 1.0 0.0 0.0 # 4 d v 0.0 0.0 1.0 # 5 e v 0.0 1.0 1.0 # 6 f v 1.0 1.0 1.0 # 7 g v 1.0 0.0 1.0 # 8 h # Normal vectors # One for each face. Shared by all vertices in that face. vn 1.0 0.0 0.0 # 1 cghd vn -1.0 0.0 0.0 # 2 aefb vn 0.0 1.0 0.0 # 3 gcbf vn 0.0 -1.0 0.0 # 4 dhea vn 0.0 0.0 1.0 # 5 hgfe vn 0.0 0.0 -1.0 # 6 cdab # Faces v/vt/vn # 3-------2 # | - | # | # | Each face = 2 triangles (ccw) # | - | = 1-2-3 + 1-3-4 # 4-------1 # Face 1: cghd = cgh + chd f 3//1 7//1 8//1 f 3//1 8//1 4//1 # Face 2: aefb = aef + afb f 1//2 5//2 6//2 f 1//2 6//2 2//2 # Face 3: gcbf = gcb + gbf f 7//3 3//3 2//3 f 7//3 2//3 6//3 # Face 4: dhea = dhe + dea f 4//4 8//4 5//4 f 4//4 5//4 1//4 # Face 5: hgfe = hgf + hfe f 8//5 7//5 6//5 f 8//5 6//5 5//5 # Face 6: cdab = cda + cab f 3//6 4//6 1//6 f 3//6 1//6 2//6 "; private void CreateTestModel(string path) { if (!File.Exists(path)) { File.WriteAllText(path, kTestModel); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate); } } /// /// WhenAssetRefsImportedMesh_ReferenceToMainAsset /// /// RecursiveMode [Test] public void WhenAssetRefsImportedMesh_ReferenceToMainAsset([Values] RecursiveMode rm) { string testModelPath = Path.Combine(kTestAssetFolder, "model.obj"); CreateTestModel(testModelPath); var model = AssetDatabase.LoadAssetAtPath(testModelPath); var modelPref = AssetDatabase.LoadAssetAtPath(testModelPath); string prefabName = "prefabWithModel.prefab"; string assetPath = Path.Combine(kTestAssetFolder, prefabName); GameObject go = new GameObject(prefabName); var mf = go.AddComponent(); mf.sharedMesh = model; PrefabUtility.SaveAsPrefabAsset(go, assetPath); UnityEngine.Object.DestroyImmediate(go, false); string prefabGuidStr = AssetDatabase.AssetPathToGUID(assetPath); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(model, out string modelGuidStr, out long modelLocalId); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(modelPref, out string modelPrefGuidStr, out long modelPrefLocalId); GUID prefabGuid = new GUID(prefabGuidStr); GUID modelGuid = new GUID(modelGuidStr); CalculateAssetDependencyData.TaskInput input = CreateDefaultInput(); input.Assets = new List() { prefabGuid, modelGuid }; input.NonRecursiveDependencies = (rm == RecursiveMode.NonRecursive); RunAndExpand(input, out CalculateAssetDependencyData.TaskOutput output); bool refsMesh = false; bool refsPrefab = false; foreach (var res in output.AssetResults) { if (res.asset != prefabGuid) continue; foreach (var dep in res.assetInfo.referencedObjects) { if (dep.guid != modelGuid) continue; if (dep.localIdentifierInFile == modelLocalId) refsMesh = true; else if (dep.localIdentifierInFile == modelPrefLocalId) refsPrefab = true; } break; } Assert.IsTrue(refsMesh, "The reference to the mesh is missing"); if (rm == RecursiveMode.Recursive) Assert.IsFalse(refsPrefab, "Unnecessary reference to the Main Asset when in recursive mode"); else Assert.IsTrue(refsPrefab, "Missing reference to the Main Asset when in non-recursive mode"); } } }