No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

OrderedTestSuiteModifier.cs 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using NUnit.Framework.Interfaces;
  5. using NUnit.Framework.Internal;
  6. namespace UnityEngine.TestRunner.NUnitExtensions
  7. {
  8. internal class OrderedTestSuiteModifier : ITestSuiteModifier
  9. {
  10. internal const string suiteIsReorderedProperty = "suiteIsReordered";
  11. private string[] m_OrderedTestNames;
  12. private readonly int m_randomOrderSeed;
  13. public OrderedTestSuiteModifier(string[] orderedTestNames, int randomOrderSeed)
  14. {
  15. m_OrderedTestNames = orderedTestNames;
  16. m_randomOrderSeed = randomOrderSeed;
  17. }
  18. public TestSuite ModifySuite(TestSuite root)
  19. {
  20. if((m_OrderedTestNames == null || m_OrderedTestNames?.Length == 0) && m_randomOrderSeed == 0)
  21. {
  22. return root;
  23. }
  24. // If we don't have a orderList but we do have a random seed, we need to generate a random order list
  25. if ((m_OrderedTestNames == null || m_OrderedTestNames.Length == 0) && m_randomOrderSeed != 0)
  26. {
  27. var testlist = GetAllTestList(root);
  28. var rand = new System.Random(m_randomOrderSeed);
  29. var randomNumberFromSeed = rand.Next();
  30. var shuffledList = testlist.OrderBy(fullName => GetHash(fullName, randomNumberFromSeed)).ToList();
  31. m_OrderedTestNames = shuffledList.ToArray();
  32. }
  33. var suite = new TestSuite(root.Name);
  34. suite.Properties.Set(suiteIsReorderedProperty, true);
  35. var workingStack = new List<ITest> { suite };
  36. foreach (var fullName in m_OrderedTestNames)
  37. {
  38. var test = FindTest(root, fullName);
  39. if (test == null)
  40. {
  41. continue;
  42. }
  43. workingStack = InsertTestInCurrentStackIncludingAllMissingAncestors(test, workingStack);
  44. }
  45. return suite;
  46. }
  47. private static List<ITest> InsertTestInCurrentStackIncludingAllMissingAncestors(ITest test, List<ITest> newAncestorStack)
  48. {
  49. var originalAncestorStack = GetAncestorStack(test);
  50. // We can start looking at index 1 in the stack, as all elements are assumed to share the same top root.
  51. for (int i = 1; i < originalAncestorStack.Count; i++)
  52. {
  53. if (DoAncestorsDiverge(newAncestorStack, originalAncestorStack, i))
  54. {
  55. // The ancestor list diverges from the current working stack so insert a new element
  56. var commonParent = newAncestorStack[i - 1];
  57. var nodeToClone = originalAncestorStack[i];
  58. var newNode = CloneNode(nodeToClone);
  59. (commonParent as TestSuite).Add(newNode);
  60. if (i < newAncestorStack.Count)
  61. {
  62. // Remove the diverging element and all its children.
  63. newAncestorStack = newAncestorStack.Take(i).ToList();
  64. }
  65. newAncestorStack.Add(newNode);
  66. }
  67. }
  68. return newAncestorStack;
  69. }
  70. private static bool DoAncestorsDiverge(List<ITest> newAncestorStack, List<ITest> originalAncestorStack, int i)
  71. {
  72. return i >= newAncestorStack.Count || originalAncestorStack[i].Name != newAncestorStack[i].Name || !originalAncestorStack[i].HasChildren;
  73. }
  74. private static Test CloneNode(ITest test)
  75. {
  76. var type = test.GetType();
  77. Test newTest;
  78. if (type == typeof(TestSuite))
  79. {
  80. newTest = new TestSuite(test.Name);
  81. }
  82. else if (type == typeof(TestAssembly))
  83. {
  84. var testAssembly = (TestAssembly)test;
  85. newTest = new TestAssembly(testAssembly.Assembly, testAssembly.Name);;
  86. }
  87. else if (type == typeof(TestFixture))
  88. {
  89. var existingFixture = (TestFixture)test;
  90. newTest = new TestFixture(test.TypeInfo);
  91. if (existingFixture.Arguments?.Length > 0)
  92. {
  93. // 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.
  94. typeof(TestFixture).GetProperty(nameof(existingFixture.Arguments)).SetValue(newTest, existingFixture.Arguments);
  95. }
  96. }
  97. else if (type == typeof(TestMethod))
  98. {
  99. // On the testMethod level, it is safe to reuse the node.
  100. newTest = test as Test;
  101. }
  102. else if (type == typeof(ParameterizedMethodSuite))
  103. {
  104. newTest = new ParameterizedMethodSuite(test.Method);
  105. }
  106. else if (type == typeof(ParameterizedFixtureSuite))
  107. {
  108. newTest = new ParameterizedFixtureSuite(test.Tests[0].TypeInfo);
  109. }
  110. else if (type == typeof(SetUpFixture))
  111. {
  112. newTest = new SetUpFixture(test.TypeInfo);
  113. }
  114. else
  115. {
  116. // If there are any node types that we do not know how to handle, then we should fail hard, so they can be added.
  117. throw new NotImplementedException(type.FullName);
  118. }
  119. CloneProperties(newTest, test);
  120. newTest.RunState = test.RunState;
  121. newTest.Properties.Set(suiteIsReorderedProperty, true);
  122. return newTest;
  123. }
  124. private static void CloneProperties(ITest target, ITest source)
  125. {
  126. if (target == source)
  127. {
  128. // On the TestMethod level, the node is reused, so do not clone the node properties.
  129. return;
  130. }
  131. foreach (var key in source.Properties.Keys)
  132. {
  133. foreach (var value in source.Properties[key])
  134. {
  135. target.Properties.Set(key, value);
  136. }
  137. }
  138. }
  139. private static List<ITest> GetAncestorStack(ITest test)
  140. {
  141. var list = new List<ITest>();
  142. while (test != null)
  143. {
  144. list.Insert(0, test);
  145. test = test.Parent;
  146. }
  147. return list;
  148. }
  149. private static List<string> GetAllTestList(ITest test)
  150. {
  151. var listOfTests = new List<string>();
  152. if (test.IsSuite)
  153. {
  154. listOfTests.AddRange(test.Tests.SelectMany(GetAllTestList));
  155. }
  156. else
  157. {
  158. listOfTests.Add(test.FullName);
  159. }
  160. return listOfTests;
  161. }
  162. private static int GetHash(string fullName, int randomNumber)
  163. {
  164. var hash = 0;
  165. foreach (var c in fullName)
  166. {
  167. hash = hash * 31 + c;
  168. }
  169. return hash ^ randomNumber;
  170. }
  171. private static ITest FindTest(ITest node, string fullName)
  172. {
  173. if (node.HasChildren)
  174. {
  175. return node.Tests
  176. .Select(test => FindTest(test, fullName))
  177. .FirstOrDefault(match => match != null);
  178. }
  179. return node.FullName == fullName ? node : null;
  180. }
  181. }
  182. }