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}