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;
 | |
|         }
 | |
|     }
 | |
| }
 |