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