A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5using System.Reflection;
6using System.Threading;
7using NUnit.Framework;
8using NUnit.Framework.Interfaces;
9using NUnit.Framework.Internal;
10using NUnit.Framework.Internal.Commands;
11using NUnit.Framework.Internal.Execution;
12using UnityEngine.TestTools.Logging;
13
14namespace UnityEngine.TestRunner.NUnitExtensions.Runner
15{
16 internal class CompositeWorkItem : UnityWorkItem
17 {
18 private readonly TestSuite _suite;
19 private readonly TestSuiteResult _suiteResult;
20 private readonly ITestFilter _childFilter;
21 private TestCommand _setupCommand;
22 private TestCommand _teardownCommand;
23
24 public List<UnityWorkItem> Children { get; private set; }
25
26 private int _countOrder;
27
28 private CountdownEvent _childTestCountdown;
29
30 public CompositeWorkItem(TestSuite suite, ITestFilter childFilter, WorkItemFactory factory)
31 : base(suite, factory)
32 {
33 _suite = suite;
34 _suiteResult = Result as TestSuiteResult;
35 _childFilter = childFilter;
36 _countOrder = 0;
37 }
38
39 protected override IEnumerable PerformWork()
40 {
41 InitializeSetUpAndTearDownCommands();
42
43 if (UnityTestExecutionContext.CurrentContext != null && m_DontRunRestoringResult && EditModeTestCallbacks.RestoringTestContext != null)
44 {
45 EditModeTestCallbacks.RestoringTestContext();
46 }
47
48 if (!CheckForCancellation())
49 if (Test.RunState == RunState.Explicit && !_childFilter.IsExplicitMatch(Test))
50 SkipFixture(ResultState.Explicit, GetSkipReason(), null);
51 else
52 switch (Test.RunState)
53 {
54 default:
55 case RunState.Runnable:
56 case RunState.Explicit:
57 Result.SetResult(ResultState.Success);
58
59 CreateChildWorkItems();
60
61 if (Children.Count > 0)
62 {
63 if (!m_DontRunRestoringResult)
64 {
65 //This is needed to give the editor a chance to go out of playmode if needed before creating objects.
66 //If we do not, the objects could be automatically destroyed when exiting playmode and could result in errors later on
67 yield return null;
68 PerformOneTimeSetUp();
69 }
70
71 if (!CheckForCancellation())
72 {
73 switch (Result.ResultState.Status)
74 {
75 case TestStatus.Passed:
76 foreach (var child in RunChildren())
77 {
78 if (CheckForCancellation())
79 {
80 yield break;
81 }
82
83 yield return child;
84 }
85 break;
86 case TestStatus.Skipped:
87 case TestStatus.Inconclusive:
88 case TestStatus.Failed:
89 SkipChildren(_suite, Result.ResultState.WithSite(FailureSite.Parent), "OneTimeSetUp: " + Result.Message);
90 break;
91 }
92 }
93
94 if (Context.ExecutionStatus != TestExecutionStatus.AbortRequested && !m_DontRunRestoringResult)
95 {
96 PerformOneTimeTearDown();
97 }
98 }
99 break;
100
101 case RunState.Skipped:
102 SkipFixture(ResultState.Skipped, GetSkipReason(), null);
103 break;
104
105 case RunState.Ignored:
106 SkipFixture(ResultState.Ignored, GetSkipReason(), null);
107 break;
108
109 case RunState.NotRunnable:
110 SkipFixture(ResultState.NotRunnable, GetSkipReason(), GetProviderStackTrace());
111 break;
112 }
113 if (!ResultedInDomainReload)
114 {
115 WorkItemComplete();
116 }
117 }
118
119 private bool CheckForCancellation()
120 {
121 if (Context.ExecutionStatus != TestExecutionStatus.Running)
122 {
123 Result.SetResult(ResultState.Cancelled, "Test cancelled by user");
124 return true;
125 }
126
127 return false;
128 }
129
130 private void InitializeSetUpAndTearDownCommands()
131 {
132 List<SetUpTearDownItem> setUpTearDownItems = _suite.TypeInfo != null
133 ? CommandBuilder.BuildSetUpTearDownList(_suite.TypeInfo.Type, typeof(OneTimeSetUpAttribute), typeof(OneTimeTearDownAttribute))
134 : new List<SetUpTearDownItem>();
135
136 var actionItems = new List<TestActionItem>();
137 foreach (ITestAction action in Actions)
138 {
139 bool applyToSuite = (action.Targets & ActionTargets.Suite) == ActionTargets.Suite
140 || action.Targets == ActionTargets.Default && !(Test is ParameterizedMethodSuite);
141
142 bool applyToTest = (action.Targets & ActionTargets.Test) == ActionTargets.Test
143 && !(Test is ParameterizedMethodSuite);
144
145 if (applyToSuite)
146 actionItems.Add(new TestActionItem(action));
147
148 if (applyToTest)
149 Context.UpstreamActions.Add(action);
150 }
151
152 _setupCommand = CommandBuilder.MakeOneTimeSetUpCommand(_suite, setUpTearDownItems, actionItems);
153 _teardownCommand = CommandBuilder.MakeOneTimeTearDownCommand(_suite, setUpTearDownItems, actionItems);
154 }
155
156 private void PerformOneTimeSetUp()
157 {
158 var logScope = new LogScope();
159 try
160 {
161 _setupCommand.Execute(Context);
162 logScope.EvaluateLogScope(true);
163 }
164 catch (Exception ex)
165 {
166 if (ex is NUnitException || ex is TargetInvocationException)
167 ex = ex.InnerException;
168
169 Result.RecordException(ex, FailureSite.SetUp);
170 }
171
172 logScope.Dispose();
173 }
174
175 private IEnumerable RunChildren()
176 {
177 int childCount = Children.Count;
178 if (childCount == 0)
179 throw new InvalidOperationException("RunChildren called but item has no children");
180
181 _childTestCountdown = new CountdownEvent(childCount);
182
183 foreach (UnityWorkItem child in Children)
184 {
185 if (CheckForCancellation())
186 {
187 yield break;
188 }
189
190 var unityTestExecutionContext = new UnityTestExecutionContext(Context);
191 child.InitializeContext(unityTestExecutionContext);
192
193 var enumerable = child.Execute().GetEnumerator();
194
195 while (true)
196 {
197 if (!enumerable.MoveNext())
198 {
199 break;
200 }
201 ResultedInDomainReload |= child.ResultedInDomainReload;
202 yield return enumerable.Current;
203 }
204
205 _suiteResult.AddResult(child.Result);
206 childCount--;
207 }
208
209 if (childCount > 0)
210 {
211 while (childCount-- > 0)
212 CountDownChildTest();
213 }
214 }
215
216 private void CreateChildWorkItems()
217 {
218 Children = new List<UnityWorkItem>();
219 var testSuite = _suite;
220
221 foreach (ITest test in testSuite.Tests)
222 {
223 if (_childFilter.Pass(test))
224 {
225 var child = m_Factory.Create(test, _childFilter);
226
227 if (test.Properties.ContainsKey(PropertyNames.Order))
228 {
229 Children.Insert(0, child);
230 _countOrder++;
231 }
232 else
233 {
234 Children.Add(child);
235 }
236 }
237 }
238
239 if (_countOrder != 0) SortChildren();
240 }
241
242 private class UnityWorkItemOrderComparer : IComparer<UnityWorkItem>
243 {
244 public int Compare(UnityWorkItem x, UnityWorkItem y)
245 {
246 var xKey = int.MaxValue;
247 var yKey = int.MaxValue;
248
249 if (x.Test.Properties.ContainsKey(PropertyNames.Order))
250 xKey = (int)x.Test.Properties[PropertyNames.Order][0];
251
252 if (y.Test.Properties.ContainsKey(PropertyNames.Order))
253 yKey = (int)y.Test.Properties[PropertyNames.Order][0];
254
255 return xKey.CompareTo(yKey);
256 }
257 }
258
259 private void SortChildren()
260 {
261 Children.Sort(0, _countOrder, new UnityWorkItemOrderComparer());
262 }
263
264 private void SkipFixture(ResultState resultState, string message, string stackTrace)
265 {
266 Result.SetResult(resultState.WithSite(FailureSite.SetUp), message, StackFilter.Filter(stackTrace));
267 SkipChildren(_suite, resultState.WithSite(FailureSite.Parent), message);
268 }
269
270 private void SkipChildren(TestSuite suite, ResultState resultState, string message)
271 {
272 foreach (Test child in suite.Tests)
273 {
274 if (_childFilter.Pass(child))
275 {
276 if (!ShouldExecuteEvents(child))
277 {
278 continue;
279 }
280
281 Context.Listener.TestStarted(child);
282 TestResult childResult = child.MakeTestResult();
283 childResult.SetResult(resultState, message);
284 _suiteResult.AddResult(childResult);
285
286 if (child.IsSuite)
287 SkipChildren((TestSuite)child, resultState, message);
288
289 Context.Listener.TestFinished(childResult);
290 }
291 }
292 }
293
294 private static bool ShouldExecuteEvents(Test test)
295 {
296 return UnityWorkItemDataHolder.alreadyExecutedTests == null || UnityWorkItemDataHolder.alreadyExecutedTests.All(x => x != test.GetUniqueName());
297 }
298
299 private void PerformOneTimeTearDown()
300 {
301 var logScope = new LogScope();
302 try
303 {
304 _teardownCommand.Execute(Context);
305 logScope.EvaluateLogScope(true);
306 }
307 catch (Exception ex)
308 {
309 if (ex is NUnitException || ex is TargetInvocationException)
310 ex = ex.InnerException;
311
312 Result.RecordException(ex, FailureSite.SetUp);
313 }
314
315 logScope.Dispose();
316 }
317
318 private string GetSkipReason()
319 {
320 return (string)Test.Properties.Get(PropertyNames.SkipReason);
321 }
322
323 private string GetProviderStackTrace()
324 {
325 return (string)Test.Properties.Get(PropertyNames.ProviderStackTrace);
326 }
327
328 private void CountDownChildTest()
329 {
330 _childTestCountdown.Signal();
331 if (_childTestCountdown.CurrentCount == 0)
332 {
333 if (Context.ExecutionStatus != TestExecutionStatus.AbortRequested)
334 PerformOneTimeTearDown();
335
336 foreach (var childResult in _suiteResult.Children)
337 if (childResult.ResultState == ResultState.Cancelled)
338 {
339 Result.SetResult(ResultState.Cancelled, "Cancelled by user");
340 break;
341 }
342
343 WorkItemComplete();
344 }
345 }
346
347 public override void Cancel(bool force)
348 {
349 if (Children == null)
350 return;
351
352 foreach (var child in Children)
353 {
354 var ctx = child.Context;
355 if (ctx != null)
356 ctx.ExecutionStatus = force ? TestExecutionStatus.AbortRequested : TestExecutionStatus.StopRequested;
357
358 if (child.State == WorkItemState.Running)
359 child.Cancel(force);
360 }
361 }
362 }
363}