2025-05-01 01:48:08 -07:00

254 lines
10 KiB
C#

using UnityEngine;
using UnityEngine.Build.Pipeline;
using Unity.ScriptableBuildPipelineTests;
using NUnit.Framework;
using System.IO;
using System.Linq;
namespace UnityEditor.Build.Pipeline.Tests
{
[TestFixture]
class BundleDependencyTests
{
const string k_TmpAssetPath = "Assets/TempAssets";
const string k_BuildFolder = "TestBuild";
const int k_CntPrefabChain = 5;
[OneTimeSetUp]
public void Setup()
{
Directory.CreateDirectory(k_TmpAssetPath);
// Create scenario similar to BPSBP-740
var prefabRoots = new GameObject[k_CntPrefabChain];
for (int i = k_CntPrefabChain - 1; i >= 0; i--)
{
var gameObject = new GameObject();
var mb = gameObject.AddComponent<MonoBehaviourWithReference>();
var prefabPath = $"{k_TmpAssetPath}/prefab{i}.prefab";
if (i != k_CntPrefabChain - 1)
{
// Point to the next prefab in the chain
mb.Reference = prefabRoots[i+1];
Assert.IsNotNull(mb.Reference);
}
else
{
mb.Reference = gameObject; // Pointer to self, like in the original repro
}
prefabRoots[i] = PrefabUtility.SaveAsPrefabAsset(gameObject, prefabPath);
AssetDatabase.ImportAsset(prefabPath, ImportAssetOptions.ForceSynchronousImport & ImportAssetOptions.ForceUpdate);
}
}
[OneTimeTearDown]
public void Cleanup()
{
if (Directory.Exists(k_BuildFolder))
Directory.Delete(k_BuildFolder, true);
AssetDatabase.DeleteAsset(k_TmpAssetPath);
}
static CompatibilityAssetBundleManifest BuildPrefabBundles()
{
// Put each prefab into its own AssetBundle
var bundleDefinitions = new AssetBundleBuild[k_CntPrefabChain];
for (int i = 0; i < bundleDefinitions.Length; i++)
{
bundleDefinitions[i].assetBundleName = $"{i}";
bundleDefinitions[i].assetNames = new string[] { $"{k_TmpAssetPath}/prefab{i}.prefab" };
};
if (Directory.Exists(k_BuildFolder))
Directory.Delete(k_BuildFolder, true);
Directory.CreateDirectory(k_BuildFolder);
// Todo, confirm that the NonRecursive Mode is enabled, the test assumes that it is and i think that is the default but its not exposed in this API
var manifest = CompatibilityBuildPipeline.BuildAssetBundles(
k_BuildFolder,
bundleDefinitions,
BuildAssetBundleOptions.AppendHashToAssetBundleName,
EditorUserBuildSettings.activeBuildTarget);
Assert.IsNotNull(manifest);
return manifest;
}
#if UNITY_2023_2_OR_NEWER
[Test, Description("BPSBP-736")]
public void BundeHashChanges_WhenDirectDependencyChanges()
{
CompatibilityAssetBundleManifest manifest = BuildPrefabBundles();
//var outputFiles = Directory.EnumerateFiles(k_BuildFolder, "*", SearchOption.TopDirectoryOnly);
//Debug.Log("Output of the build:\n\t" + string.Join("\n\t", outputFiles));
var outputPaths = new string[k_CntPrefabChain];
for (int i = 0; i < k_CntPrefabChain; i++)
{
//e.g. a path like "TestBuild\0_135e9091b30805539e5f5f349375cd11"
outputPaths[i] = Directory.EnumerateFiles(k_BuildFolder, $"{i}_*", SearchOption.TopDirectoryOnly).ToArray()[0];
}
// Change bundle 3, e.g. remove its dependency on bundle 4
SetPrefabReferenceToNull(3);
CompatibilityAssetBundleManifest manifest2 = BuildPrefabBundles();
var rebuildPaths = new string[k_CntPrefabChain];
for (int i = 0; i < k_CntPrefabChain; i++)
{
rebuildPaths[i] = Directory.EnumerateFiles(k_BuildFolder, $"{i}_*", SearchOption.TopDirectoryOnly).ToArray()[0];
}
Assert.AreEqual(outputPaths[0], rebuildPaths[0], "Bundle hash changed");
Assert.AreEqual(outputPaths[1], rebuildPaths[1], "Bundle hash changed");
Assert.AreNotEqual(outputPaths[2], rebuildPaths[2]); //Direct dependency changed
Assert.AreNotEqual(outputPaths[3], rebuildPaths[3]); // We changed this bundle
Assert.AreEqual(outputPaths[4], rebuildPaths[4], "Bundle hash changed");
ResetPrefabReference(3);
}
[Test, Description("BPSBP-736")]
public void BundleHashDoesNotChange_IfListOfReferencedBundlesDoesNotChange()
{
CompatibilityAssetBundleManifest manifest = BuildPrefabBundles();
//var outputFiles = Directory.EnumerateFiles(k_BuildFolder, "*", SearchOption.TopDirectoryOnly);
//Debug.Log("Output of the build:\n\t" + string.Join("\n\t", outputFiles));
var outputPaths = new string[k_CntPrefabChain];
for (int i = 0; i < k_CntPrefabChain; i++)
{
//e.g. a path like "TestBuild\0_135e9091b30805539e5f5f349375cd11"
outputPaths[i] = Directory.EnumerateFiles(k_BuildFolder, $"{i}_*", SearchOption.TopDirectoryOnly).ToArray()[0];
}
// Change bundle 3, e.g. remove its dependency on bundle 4
AddToTransformValues(3);
CompatibilityAssetBundleManifest manifest2 = BuildPrefabBundles();
var rebuildPaths = new string[k_CntPrefabChain];
for (int i = 0; i < k_CntPrefabChain; i++)
{
rebuildPaths[i] = Directory.EnumerateFiles(k_BuildFolder, $"{i}_*", SearchOption.TopDirectoryOnly).ToArray()[0];
}
Assert.AreEqual(outputPaths[0], rebuildPaths[0], "Bundle hash changed");
Assert.AreEqual(outputPaths[1], rebuildPaths[1], "Bundle hash changed");
Assert.AreEqual(outputPaths[2], rebuildPaths[2], "Bundle hash changed");
Assert.AreNotEqual(outputPaths[3], rebuildPaths[3]); // We changed this bundle
Assert.AreEqual(outputPaths[4], rebuildPaths[4], "Bundle hash changed");
}
static void SetPrefabReferenceToNull(int prefabIndex)
{
string prefabPath = $"{k_TmpAssetPath}/prefab{prefabIndex}.prefab";
GameObject prefabRoot = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
var monoBehaviour = prefabRoot.GetComponent<MonoBehaviourWithReference>();
monoBehaviour.Reference = null;
PrefabUtility.SavePrefabAsset(prefabRoot);
AssetDatabase.ImportAsset(prefabPath, ImportAssetOptions.ForceSynchronousImport & ImportAssetOptions.ForceUpdate);
}
static void ResetPrefabReference(int prefabIndex)
{
string prefabPath = $"{k_TmpAssetPath}/prefab{prefabIndex}.prefab";
GameObject prefabRoot = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
var monoBehaviour = prefabRoot.GetComponent<MonoBehaviourWithReference>();
monoBehaviour.Reference = prefabRoot;
PrefabUtility.SavePrefabAsset(prefabRoot);
AssetDatabase.ImportAsset(prefabPath, ImportAssetOptions.ForceSynchronousImport & ImportAssetOptions.ForceUpdate);
}
static void AddToTransformValues(int prefabIndex)
{
string prefabPath = $"{k_TmpAssetPath}/prefab{prefabIndex}.prefab";
GameObject prefabRoot = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
var transform = prefabRoot.GetComponent<Transform>();
transform.position += new Vector3(1, 1, 1);
PrefabUtility.SavePrefabAsset(prefabRoot);
AssetDatabase.ImportAsset(prefabPath, ImportAssetOptions.ForceSynchronousImport & ImportAssetOptions.ForceUpdate);
}
#endif
[Ignore("BPSBP-737 / ADDR-3262")]
[Test, Description("BPSBP-737 / ADDR-3262")]
public void MonoScriptsAreNotNullInChainedBundles()
{
// Note: Test could also do variations, with MonoScript bundle enabled, maybe also NonRecursive=false
CompatibilityAssetBundleManifest manifest = BuildPrefabBundles();
string prefabBundleMatch = "*_*"; // Match bundle names like 0_f5b4234bbd5a5a599bd740802cc6f9cf and ignore other build output
// All the prefabs as loaded from the assetbundles should have valid MonoBehaviour with non-null reference, matching
// how we created them in the project.
var builtBundlePaths = Directory.EnumerateFiles(k_BuildFolder, prefabBundleMatch, SearchOption.TopDirectoryOnly).ToArray();
LoadBundlesAndCheckMonoScript(builtBundlePaths);
}
static void LoadBundlesAndCheckMonoScript(string[] bundleNames)
{
//Debug.Log("Loading " + string.Join("\n", bundleNames));
var bundleCount = bundleNames.Length;
var bundles = new AssetBundle[bundleCount];
for (int i = 0; i < bundleCount; i++)
{
bundles[i] = AssetBundle.LoadFromFile(bundleNames[i]);
}
try
{
for (int i = 0; i < bundleCount; i++)
{
var prefab = bundles[i].LoadAllAssets<GameObject>()[0];
var monoBehaviour = prefab.GetComponent<MonoBehaviourWithReference>();
//TEMPORARY Assert disabled until BPSBP-737 fixed
//Assert.IsNotNull(monoBehaviour, "Missing MonoScript or MonoBehaviourWithReference on " + bundleNames[i]);
//
if (monoBehaviour == null)
{
// Missing monoscript will result in the entire MonoBehaviour not being loaded
Debug.Log("Missing MonoScript or MonoBehaviourWithReference on " + bundleNames[i]);
continue;
}
var monoScript = MonoScript.FromMonoBehaviour(monoBehaviour);
Assert.IsNotNull(monoScript);
}
}
finally
{
for (int i = 0; i < bundleCount; i++)
bundles[i].Unload(true);
}
}
}
}