using NUnit.Framework; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; 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"; const string k_CubePath3 = k_TestAssetsPath + "/Cube3.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); PrefabUtility.SaveAsPrefabAsset(GameObject.CreatePrimitive(PrimitiveType.Cube), k_CubePath3); #else PrefabUtility.CreatePrefab(k_CubePath, GameObject.CreatePrimitive(PrimitiveType.Cube)); PrefabUtility.CreatePrefab(k_CubePath2, GameObject.CreatePrimitive(PrimitiveType.Cube)); PrefabUtility.CreatePrefab(k_CubePath3, GameObject.CreatePrimitive(PrimitiveType.Cube)); #endif AssetDatabase.ImportAsset(k_CubePath); AssetDatabase.ImportAsset(k_CubePath2); AssetDatabase.ImportAsset(k_CubePath3); 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_CubePath3); 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(params IContextObject[] args) where T : IBuildTask { IBuildContext context = new BuildContext(args); IBuildTask instance = Activator.CreateInstance(); ContextInjector.Inject(context, instance); var result = instance.Run(); ContextInjector.Extract(context, instance); return result; } static void RequirePlayerBuildSupport() { // Building asset bundles compiles player scripts, which requires the player build // support for the active build target to be installed. var target = EditorUserBuildSettings.activeBuildTarget; if (!BuildPipeline.IsBuildTargetSupported(BuildPipeline.GetBuildTargetGroup(target), target)) Assert.Ignore($"Build support for {target} is not installed."); } 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 buildData = new List(); 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() { RequirePlayerBuildSupport(); 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(); var types = new HashSet(); 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() { RequirePlayerBuildSupport(); // 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(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(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(buildContent, dep, writeData, deterministicId); IBundleBuildParameters buildParams = GetBuildParameters(); ReturnCode exitCode = RunTask(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(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(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(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(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(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(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(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(), 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(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(), 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() { RequirePlayerBuildSupport(); 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() { RequirePlayerBuildSupport(); 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 BuildAssetBundles_WithDuplicateAddresses_InSeparateBundles_DoesNotLogErrorMessage() { RequirePlayerBuildSupport(); IBundleBuildParameters buildParameters = GetBuildParameters(); List buildData = new List(); var bundle1 = new AssetBundleBuild() { addressableNames = new[] { k_CubePath }, assetBundleName = k_CubePath, assetBundleVariant = "", assetNames = new[] { k_CubePath } }; var bundle2 = new AssetBundleBuild() { addressableNames = new[] { k_CubePath }, assetBundleName = k_CubePath2, assetBundleVariant = "", assetNames = new[] { k_CubePath2 } }; buildData.Add(bundle1); buildData.Add(bundle2); // different asset, same addressable name. IBundleBuildContent buildContent = new BundleBuildContent(buildData); IBundleBuildResults results; var taskList = DefaultBuildTasks.Create(DefaultBuildTasks.Preset.AssetBundleCompatible); ReturnCode exitCode = ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out results, taskList, new BuildLog()); Assert.AreEqual(ReturnCode.Success, exitCode); } #if UNITY_6000_0_OR_NEWER [Test] public void BuildAssetBundles_WithDuplicateAddresses_InSameBundle_LogsErrorMessage() { IBundleBuildParameters buildParameters = GetBuildParameters(); List buildData = new List(); var bundle1 = new AssetBundleBuild() { addressableNames = new[] { k_CubePath, k_CubePath }, assetBundleName = k_CubePath, assetBundleVariant = "", assetNames = new[] { k_CubePath, k_CubePath2 } }; buildData.Add(bundle1); // different asset, same addressable name. IBundleBuildContent buildContent = new BundleBuildContent(buildData); IBundleBuildResults results; LogAssert.Expect(LogType.Exception, new Regex($"Duplicate internal id '{k_CubePath}' for guid *")); var taskList = DefaultBuildTasks.Create(DefaultBuildTasks.Preset.AssetBundleCompatible); ReturnCode exitCode = ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out results, taskList, new BuildLog()); Assert.AreEqual(ReturnCode.Exception, exitCode); } #endif [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() { RequirePlayerBuildSupport(); 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() { RequirePlayerBuildSupport(); IBundleBuildParameters buildParameters = GetBuildParameters(); buildParameters.UseCache = false; IBundleBuildContent buildContent = GetBundleContent(true); ReturnCode exitCode = ContentPipeline.BuildAssetBundles(buildParameters, buildContent, out IBundleBuildResults results); Assert.AreEqual(ReturnCode.Success, exitCode); } BuildDependencyData BuildBundleDepData(params string[] paths) { var depData = new BuildDependencyData(); foreach (var p in paths) { GUID guid1; GUID.TryParse(AssetDatabase.AssetPathToGUID(p), out guid1); var objIds1 = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(guid1, EditorUserBuildSettings.activeBuildTarget); depData.AssetInfo.Add(guid1, new AssetLoadInfo { asset = guid1, address = p, includedObjects = objIds1.ToList(), referencedObjects = new List(ContentBuildInterface.GetPlayerDependenciesForObjects(objIds1, EditorUserBuildSettings.activeBuildTarget, null)) }); depData.AssetUsage.Add(guid1, new BuildUsageTagSet()); } return depData; } #if UNITY_6000_0_OR_NEWER [Test] public void ClusterBuildLayout_GenerateClusterName_Stress([Values(100, 1000)] int listCount, [Values(100, 1000)] int idCount) { var idLists = new List>(); for (int i = 0; i < listCount; i++) { var ids = new List(); for (int j = 0; j < idCount; j++) ids.Add(new ObjectIdentifier { filePath = $"long asset path to stress the hashing speed {i} - {j}", fileType = FileType.SerializedAssetType, guid = GUID.Generate(), localIdentifierInFile = i * listCount + j }); idLists.Add(ids); } //ensure that all hashes are unique var collisionDetection = new HashSet(); for (int i = 0; i < idLists.Count; i++) { var hash = ClusterBuildLayout.ComputeClusterId(idLists[i]); Assert.IsTrue(collisionDetection.Add(hash)); } //ensure that the hashes are deterministic for (int i = 0; i < idLists.Count; i++) { var hash = ClusterBuildLayout.ComputeClusterId(idLists[i]); Assert.IsTrue(collisionDetection.Contains(hash)); } } [Test] public void ClusterBuildLayout_Adding_New_Prefab_Preserves_Existing_Clusters() { var clusterResult1 = new ClusterOutput(); { var buildParams = GetBuildParameters(); var depData = BuildBundleDepData(k_CubePath, k_CubePath2); var writeData = new BundleWriteData(); var identifier = new PrefabPackedIdentifiers(); var result = ClusterBuildLayout.Run(buildParams, depData, writeData, identifier, clusterResult1, true); Assert.AreEqual(ReturnCode.Success, result); } var clusterResult2 = new ClusterOutput(); { var buildParams = GetBuildParameters(); var depData = BuildBundleDepData(k_CubePath, k_CubePath2, k_CubePath3); var writeData = new BundleWriteData(); var identifier = new PrefabPackedIdentifiers(); var result = ClusterBuildLayout.Run(buildParams, depData, writeData, identifier, clusterResult2, true); Assert.AreEqual(ReturnCode.Success, result); } Assert.Greater(clusterResult1.ObjectToCluster.Count, 0, "clusterResult1 should have clustered objects"); Assert.Greater(clusterResult2.ObjectToCluster.Count, 0, "clusterResult2 should have clustered objects"); // Check that all objects from clusterResult1 exist in clusterResult2 with the same cluster ID foreach (var kvp in clusterResult1.ObjectToCluster) { var objectId = kvp.Key; var cluster1 = kvp.Value; Assert.IsTrue(clusterResult2.ObjectToCluster.ContainsKey(objectId), $"Object {objectId} from clusterResult1 is missing in clusterResult2"); var cluster2 = clusterResult2.ObjectToCluster[objectId]; Assert.AreEqual(cluster1, cluster2, $"Object {objectId} has different cluster IDs: {cluster1} in clusterResult1 vs {cluster2} in clusterResult2"); } // Check that all local ID mappings from clusterResult1 exist in clusterResult2 with the same value foreach (var kvp in clusterResult1.ObjectToLocalID) { var objectId = kvp.Key; var localId1 = kvp.Value; Assert.IsTrue(clusterResult2.ObjectToLocalID.ContainsKey(objectId), $"Object {objectId} local ID mapping from clusterResult1 is missing in clusterResult2"); var localId2 = clusterResult2.ObjectToLocalID[objectId]; Assert.AreEqual(localId1, localId2, $"Object {objectId} has different local IDs: {localId1} in clusterResult1 vs {localId2} in clusterResult2"); } // Verify that clusterResult2 has more mappings due to the additional prefab Assert.Greater(clusterResult2.ObjectToCluster.Count, clusterResult1.ObjectToCluster.Count, "clusterResult2 should have more clustered objects due to the additional prefab"); Assert.Greater(clusterResult2.ObjectToLocalID.Count, clusterResult1.ObjectToLocalID.Count, "clusterResult2 should have more local ID mappings due to the additional prefab"); } [Test] public void ClusterBuildLayout_Produces_ExpectedResults([Values(true, false)] bool useContentIds) { // Create concrete test data for ClusterBuildLayout.Run parameters var buildParams = GetBuildParameters(); var depData = BuildBundleDepData(k_CubePath, k_CubePath2); var writeData = new BundleWriteData(); var identifier = new PrefabPackedIdentifiers(); var clusterResult = new ClusterOutput(); var result = ClusterBuildLayout.Run(buildParams, depData, writeData, identifier, clusterResult, true); Assert.AreEqual(ReturnCode.Success, result); // Verify that clustering produced expected outputs Assert.AreEqual(writeData.WriteOperations.Count, 3, "Expected 3 clusters"); for (int i = 0; i < writeData.WriteOperations.Count; i++) { var writeOp1 = writeData.WriteOperations[i]; if (writeOp1.Command == null || writeOp1.Command.serializeObjects == null) continue; var serializedObjects1 = new HashSet( writeOp1.Command.serializeObjects.Select(s => s.serializationObject)); for (int j = i + 1; j < writeData.WriteOperations.Count; j++) { var writeOp2 = writeData.WriteOperations[j]; if (writeOp2.Command == null || writeOp2.Command.serializeObjects == null) continue; foreach (var serializationInfo in writeOp2.Command.serializeObjects) { Assert.IsFalse(serializedObjects1.Contains(serializationInfo.serializationObject), $"WriteOperation {i} (fileName: {writeOp1.Command.fileName}) and WriteOperation {j} " + $"(fileName: {writeOp2.Command.fileName}) both contain the same serialized object: {serializationInfo.serializationObject}"); } } } } #endif 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; } } } }