211 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			211 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 
 | ||
|  | 
 | ||
|  | 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; | ||
|  |         private readonly int m_randomOrderSeed; | ||
|  | 
 | ||
|  |         public OrderedTestSuiteModifier(string[] orderedTestNames, int randomOrderSeed) | ||
|  |         { | ||
|  |             m_OrderedTestNames = orderedTestNames; | ||
|  |             m_randomOrderSeed = randomOrderSeed; | ||
|  |         } | ||
|  | 
 | ||
|  |         public TestSuite ModifySuite(TestSuite root) | ||
|  |         { | ||
|  |             if((m_OrderedTestNames == null || m_OrderedTestNames?.Length == 0) && m_randomOrderSeed == 0) | ||
|  |             { | ||
|  |                 return root; | ||
|  |             } | ||
|  |             // If we don't have a orderList but we do have a random seed, we need to generate a random order list | ||
|  |             if ((m_OrderedTestNames == null || m_OrderedTestNames.Length == 0) && m_randomOrderSeed != 0) | ||
|  |             { | ||
|  |                 var testlist = GetAllTestList(root); | ||
|  |                 var rand = new System.Random(m_randomOrderSeed); | ||
|  |                 var randomNumberFromSeed = rand.Next(); | ||
|  |                 var shuffledList = testlist.OrderBy(fullName => GetHash(fullName, randomNumberFromSeed)).ToList(); | ||
|  | 
 | ||
|  |                 m_OrderedTestNames = shuffledList.ToArray(); | ||
|  |             } | ||
|  | 
 | ||
|  |             var suite = new TestSuite(root.Name); | ||
|  |             suite.Properties.Set(suiteIsReorderedProperty, true); | ||
|  |             var workingStack = new List<ITest> { 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<ITest> InsertTestInCurrentStackIncludingAllMissingAncestors(ITest test, List<ITest> 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<ITest> newAncestorStack, List<ITest> 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<ITest> GetAncestorStack(ITest test) | ||
|  |         { | ||
|  |             var list = new List<ITest>(); | ||
|  |             while (test != null) | ||
|  |             { | ||
|  |                 list.Insert(0, test); | ||
|  |                 test = test.Parent; | ||
|  |             } | ||
|  | 
 | ||
|  |             return list; | ||
|  |         } | ||
|  | 
 | ||
|  |         private static List<string> GetAllTestList(ITest test) | ||
|  |         { | ||
|  |             var listOfTests = new List<string>(); | ||
|  | 
 | ||
|  |             if (test.IsSuite) | ||
|  |             { | ||
|  |                 listOfTests.AddRange(test.Tests.SelectMany(GetAllTestList)); | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 listOfTests.Add(test.FullName); | ||
|  |             } | ||
|  |             return listOfTests; | ||
|  |         } | ||
|  | 
 | ||
|  |         private static int GetHash(string fullName, int randomNumber) | ||
|  |         { | ||
|  |             var hash = 0; | ||
|  |             foreach (var c in fullName) | ||
|  |             { | ||
|  |                 hash = hash * 31 + c; | ||
|  |             } | ||
|  | 
 | ||
|  |             return hash ^ randomNumber; | ||
|  |         } | ||
|  | 
 | ||
|  |         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; | ||
|  |         } | ||
|  |     } | ||
|  | } |