using NUnit.Framework; using System; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using UnityEditor.Build.Pipeline.Utilities; namespace UnityEditor.Build.Pipeline.Tests { /// /// LinkXMLGeneratorTests /// [TestFixture] public class LinkXMLGeneratorTests { const string k_LinkFile = "link.xml"; /// /// Teardown /// [TearDown] public void OnTearDown() { if (File.Exists(k_LinkFile)) File.Delete(k_LinkFile); } /// /// Read the LinkXML file /// /// linkFile /// assemblyCount /// typeCount /// The contents of the LinkXML file public static string ReadLinkXML(string linkFile, out int assemblyCount, out int typeCount) { FileAssert.Exists(linkFile); var fileText = File.ReadAllText(linkFile); assemblyCount = Regex.Matches(fileText, " /// AssertTypePreserved /// /// input /// type public static void AssertTypePreserved(string input, Type t) { StringAssert.IsMatch($"type.*?{t.FullName}.*?preserve=\"all\"", input); } /// /// AssertTypeWithAttributePreserved /// /// input /// fullName public static void AssertTypeWithAttributePreserved(string input, string fullName) { StringAssert.IsMatch($"type.*?{fullName}.*? preserve=\"nothing\" serialized=\"true\"", input); } /// /// AssertAssemblyPreserved /// /// input /// assemlby public static void AssertAssemblyPreserved(string input, Assembly a) { StringAssert.IsMatch($"assembly.*?{a.FullName}.*?preserve=\"all\"", input); } /// /// AssertAssemblyFullName /// /// input /// assemblyName public static void AssertAssemblyFullName(string input, string assemblyName) { StringAssert.IsMatch($"assembly.*?{assemblyName}", input); } /// /// CreateDefault_Converts_ExpectedUnityEditorTypes /// [Test] public void CreateDefault_Converts_ExpectedUnityEditorTypes() { var types = LinkXmlGenerator.GetEditorTypeConversions(); var editorTypes = types.Select(x => x.Key).ToArray(); var runtimeTypes = types.Select(x => x.Value).ToArray(); var assemblies = runtimeTypes.Select(x => x.Assembly).Distinct().ToArray(); var link = LinkXmlGenerator.CreateDefault(); link.AddTypes(editorTypes); link.Save(k_LinkFile); var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount); Assert.AreEqual(assemblyCount, assemblies.Length); Assert.AreEqual(typeCount, runtimeTypes.Length); foreach (var t in runtimeTypes) AssertTypePreserved(xml, t); } /// /// CreateDefault_DoesNotConvert_UnexpectedUnityEditorTypes /// [Test] public void CreateDefault_DoesNotConvert_UnexpectedUnityEditorTypes() { var unexpectedType = typeof(UnityEditor.BuildPipeline); var link = LinkXmlGenerator.CreateDefault(); link.AddTypes(new[] { unexpectedType }); link.Save(k_LinkFile); var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount); Assert.AreEqual(assemblyCount, 1); Assert.AreEqual(typeCount, 1); AssertTypePreserved(xml, unexpectedType); } /// /// LinkXML_Preserves_MultipleTypes_FromMultipleAssemblies /// [Test] public void LinkXML_Preserves_MultipleTypes_FromMultipleAssemblies() { var types = new[] { typeof(UnityEngine.MonoBehaviour), typeof(UnityEngine.Build.Pipeline.CompatibilityAssetBundleManifest) }; var link = new LinkXmlGenerator(); link.AddTypes(types); link.Save(k_LinkFile); var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount); Assert.AreEqual(assemblyCount, 2); Assert.AreEqual(typeCount, types.Length); foreach (var t in types) AssertTypePreserved(xml, t); } /// /// LinkXML_Preserves_Assemblies /// [Test] public void LinkXML_Preserves_Assemblies() { var assemblies = new[] { typeof(UnityEngine.MonoBehaviour).Assembly, typeof(UnityEngine.Build.Pipeline.CompatibilityAssetBundleManifest).Assembly }; var link = new LinkXmlGenerator(); link.AddAssemblies(assemblies); link.Save(k_LinkFile); var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount); Assert.AreEqual(assemblyCount, assemblies.Length); Assert.AreEqual(typeCount, 0); foreach (var a in assemblies) AssertAssemblyPreserved(xml, a); } /// /// LinkXML_Preserves_SerializeClasses /// [Test] public void LinkXML_Preserves_SerializeClasses() { var serializedRefClasses = new[] { "FantasticAssembly:AwesomeNS.Foo", "FantasticAssembly:AwesomeNS.Bar", "SuperFantasticAssembly:SuperAwesomeNS.Bar"}; var link = new LinkXmlGenerator(); link.AddSerializedClass(serializedRefClasses); link.Save(k_LinkFile); var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount); Assert.AreEqual(assemblyCount, 2); AssertAssemblyFullName(xml, "FantasticAssembly"); AssertAssemblyFullName(xml, "SuperFantasticAssembly"); Assert.AreEqual(typeCount,3); AssertTypeWithAttributePreserved(xml, "AwesomeNS.Foo"); AssertTypeWithAttributePreserved(xml, "AwesomeNS.Bar"); AssertTypeWithAttributePreserved(xml, "SuperAwesomeNS.Bar"); } /// /// LinkXML_Is_Sorted /// [Test] public void LinkXML_Is_Sorted() { // For incremental builds to perform well, we need link xml files // to be deterministic - if contents don't change, they should be bit-wise // identical. So we sort contents, since HashSets/Dictionary have no deterministic order. // This test verfies that assemblies and classes are sorted alphabetically. // Note that types added via AddSerializedClass will always come after types and assemblies // added via AddType/AddAssembly, regardless of alphabetical order. var serializedRefClasses = new[] { "AssemblyC:NamespaceA.ClassA", "AssemblyB:NamespaceC.ClassB", "AssemblyA:NamespaceB.ClassC", "AssemblyC:NamespaceC.ClassC", "AssemblyB:NamespaceB.ClassA", "AssemblyA:NamespaceA.ClassB", "AssemblyA:NamespaceA.ClassC", "AssemblyC:NamespaceB.ClassA", "AssemblyB:NamespaceC.ClassA", }; var types = new[] { typeof(UnityEngine.Texture), typeof(UnityEngine.MonoBehaviour) , typeof(UnityEngine.Build.Pipeline.CompatibilityAssetBundleManifest) }; var link = new LinkXmlGenerator(); link.AddSerializedClass(serializedRefClasses); link.AddTypes(types); link.Save(k_LinkFile); var xml = ReadLinkXML(k_LinkFile, out int assemblyCount, out int typeCount) .Replace(" ", "") .Replace("\n", "") .Replace("\r", ""); Console.WriteLine(xml); var sorted = @" ".Replace(" ", "").Replace("\n", "").Replace("\r", ""); Assert.AreEqual(sorted, xml); } } }