using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework.Interfaces; using NUnit.Framework.Internal; namespace UnityEngine.TestRunner.NUnitExtensions { internal class OrderedTestSuiteModifier : ITestSuiteModifier { internal const string suiteIsReorderedProperty = "suiteIsReordered"; private string[] m_OrderedTestNames; public OrderedTestSuiteModifier(string[] orderedTestNames) { m_OrderedTestNames = orderedTestNames; } public TestSuite ModifySuite(TestSuite root) { if (m_OrderedTestNames.Length == 0) { // If no test list is given, return the original suite, which will perform the run as normal. return root; } var suite = new TestSuite(root.Name); suite.Properties.Set(suiteIsReorderedProperty, true); var workingStack = new List { suite }; foreach (var fullName in m_OrderedTestNames) { var test = FindTest(root, fullName); if (test == null) { continue; } workingStack = InsertTestInCurrentStackIncludingAllMissingAncestors(test, workingStack); } return suite; } private static List InsertTestInCurrentStackIncludingAllMissingAncestors(ITest test, List newAncestorStack) { var originalAncestorStack = GetAncestorStack(test); // We can start looking at index 1 in the stack, as all elements are assumed to share the same top root. for (int i = 1; i < originalAncestorStack.Count; i++) { if (DoAncestorsDiverge(newAncestorStack, originalAncestorStack, i)) { // The ancestor list diverges from the current working stack so insert a new element var commonParent = newAncestorStack[i - 1]; var nodeToClone = originalAncestorStack[i]; var newNode = CloneNode(nodeToClone); (commonParent as TestSuite).Add(newNode); if (i < newAncestorStack.Count) { // Remove the diverging element and all its children. newAncestorStack = newAncestorStack.Take(i).ToList(); } newAncestorStack.Add(newNode); } } return newAncestorStack; } private static bool DoAncestorsDiverge(List newAncestorStack, List originalAncestorStack, int i) { return i >= newAncestorStack.Count || originalAncestorStack[i].Name != newAncestorStack[i].Name || !originalAncestorStack[i].HasChildren; } private static Test CloneNode(ITest test) { var type = test.GetType(); Test newTest; if (type == typeof(TestSuite)) { newTest = new TestSuite(test.Name); } else if (type == typeof(TestAssembly)) { var testAssembly = (TestAssembly)test; newTest = new TestAssembly(testAssembly.Assembly, testAssembly.Name);; } else if (type == typeof(TestFixture)) { var existingFixture = (TestFixture)test; newTest = new TestFixture(test.TypeInfo); if (existingFixture.Arguments?.Length > 0) { // Newer versions of NUnit has a constructor that allows for setting this argument. Our custom NUnit version only allows for setting it through reflection at the moment. typeof(TestFixture).GetProperty(nameof(existingFixture.Arguments)).SetValue(newTest, existingFixture.Arguments); } } else if (type == typeof(TestMethod)) { // On the testMethod level, it is safe to reuse the node. newTest = test as Test; } else if (type == typeof(ParameterizedMethodSuite)) { newTest = new ParameterizedMethodSuite(test.Method); } else if (type == typeof(ParameterizedFixtureSuite)) { newTest = new ParameterizedFixtureSuite(test.Tests[0].TypeInfo); } else if (type == typeof(SetUpFixture)) { newTest = new SetUpFixture(test.TypeInfo); } else { // If there are any node types that we do not know how to handle, then we should fail hard, so they can be added. throw new NotImplementedException(type.FullName); } CloneProperties(newTest, test); newTest.RunState = test.RunState; newTest.Properties.Set(suiteIsReorderedProperty, true); return newTest; } private static void CloneProperties(ITest target, ITest source) { if (target == source) { // On the TestMethod level, the node is reused, so do not clone the node properties. return; } foreach (var key in source.Properties.Keys) { foreach (var value in source.Properties[key]) { target.Properties.Set(key, value); } } } private static List GetAncestorStack(ITest test) { var list = new List(); while (test != null) { list.Insert(0, test); test = test.Parent; } return list; } private static ITest FindTest(ITest node, string fullName) { if (node.HasChildren) { return node.Tests .Select(test => FindTest(test, fullName)) .FirstOrDefault(match => match != null); } return node.FullName == fullName ? node : null; } } }