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");
}
}
}