A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5using System.Reflection;
6using NUnit.Framework.Interfaces;
7using NUnit.Framework.Internal;
8using NUnit.Framework.Internal.Commands;
9using UnityEngine.TestRunner.NUnitExtensions;
10using UnityEngine.TestRunner.NUnitExtensions.Runner;
11using UnityEngine.TestTools.Logging;
12using UnityEngine.TestTools.TestRunner;
13
14namespace UnityEngine.TestTools
15{
16 internal abstract class BeforeAfterTestCommandBase<T> : DelegatingTestCommand, IEnumerableTestMethodCommand where T : class
17 {
18 private string m_BeforeErrorPrefix;
19 private string m_AfterErrorPrefix;
20 protected BeforeAfterTestCommandBase(TestCommand innerCommand, string beforeErrorPrefix, string afterErrorPrefix)
21 : base(innerCommand)
22 {
23 m_BeforeErrorPrefix = beforeErrorPrefix;
24 m_AfterErrorPrefix = afterErrorPrefix;
25 }
26
27 protected T[] BeforeActions = new T[0];
28
29 protected T[] AfterActions = new T[0];
30
31 protected static MethodInfo[] GetActions(IDictionary<Type, List<MethodInfo>> cacheStorage, Type fixtureType, Type attributeType, Type[] returnTypes)
32 {
33 if (cacheStorage.TryGetValue(fixtureType, out var result))
34 {
35 return result.ToArray();
36 }
37
38 cacheStorage[fixtureType] = GetMethodsWithAttributeFromFixture(fixtureType, attributeType, returnTypes);
39
40 return cacheStorage[fixtureType].ToArray();
41 }
42
43 protected static T[] GetTestActions(IDictionary<MethodInfo, List<T>> cacheStorage, MethodInfo methodInfo)
44 {
45 if (cacheStorage.TryGetValue(methodInfo, out var result))
46 {
47 return result.ToArray();
48 }
49
50 var attributesForMethodInfo = new List<T>();
51 var attributes = methodInfo.GetCustomAttributes(false);
52 foreach (var attribute in attributes)
53 {
54 if (attribute is T attribute1)
55 {
56 attributesForMethodInfo.Add(attribute1);
57 }
58 }
59
60 cacheStorage[methodInfo] = attributesForMethodInfo;
61
62 return cacheStorage[methodInfo].ToArray();
63 }
64
65 private static List<MethodInfo> GetMethodsWithAttributeFromFixture(Type fixtureType, Type setUpType, Type[] returnTypes)
66 {
67 MethodInfo[] methodsWithAttribute = Reflect.GetMethodsWithAttribute(fixtureType, setUpType, true);
68 var methodsInfo = new List<MethodInfo>();
69 methodsInfo.AddRange(methodsWithAttribute.Where(method => returnTypes.Any(type => type == method.ReturnType)));
70 return methodsInfo;
71 }
72
73 protected abstract IEnumerator InvokeBefore(T action, Test test, UnityTestExecutionContext context);
74
75 protected abstract IEnumerator InvokeAfter(T action, Test test, UnityTestExecutionContext context);
76
77 protected virtual bool MoveBeforeEnumerator(IEnumerator enumerator, Test test)
78 {
79 return enumerator.MoveNext();
80 }
81
82 protected virtual bool MoveAfterEnumerator(IEnumerator enumerator, Test test)
83 {
84 return enumerator.MoveNext();
85 }
86
87 protected abstract BeforeAfterTestCommandState GetState(UnityTestExecutionContext context);
88
89 protected virtual bool AllowFrameSkipAfterAction(T action)
90 {
91 return true;
92 }
93
94 public IEnumerable ExecuteEnumerable(ITestExecutionContext context)
95 {
96 var unityContext = (UnityTestExecutionContext)context;
97 var state = GetState(unityContext);
98 if (state == null)
99 {
100 throw new Exception($"No state in context for {GetType().Name}.");
101 }
102
103 if(state.ShouldRestore)
104 {
105 state.ApplyContext(unityContext);
106 }
107
108 while (state.NextBeforeStepIndex < BeforeActions.Length)
109 {
110 var action = BeforeActions[state.NextBeforeStepIndex];
111 IEnumerator enumerator;
112 try
113 {
114 enumerator = EnumeratorHelper.UnpackNestedEnumerators(InvokeBefore(action, Test, unityContext));
115 EnumeratorHelper.SetEnumeratorPC(state.NextBeforeStepPc);
116 }
117 catch (Exception ex)
118 {
119 state.TestHasRun = true;
120 context.CurrentResult.RecordPrefixedException(m_BeforeErrorPrefix, ex);
121 break;
122 }
123
124 using (var logScope = new LogScope())
125 {
126 while (true)
127 {
128 try
129 {
130 if (!enumerator.MoveNext())
131 {
132 logScope.EvaluateLogScope(true);
133 break;
134 }
135
136 if (!AllowFrameSkipAfterAction(action)) // Evaluate the log scope right away for the commands where we do not yield
137 {
138 logScope.EvaluateLogScope(true);
139 }
140 if (enumerator.Current is IEditModeTestYieldInstruction)
141 {
142 if (unityContext.TestMode == TestPlatform.PlayMode)
143 {
144 throw new Exception($"PlayMode test are not allowed to yield {enumerator.Current.GetType().Name}");
145 }
146
147 if (EnumeratorHelper.IsRunningNestedEnumerator)
148 {
149 throw new Exception($"Nested enumerators are not allowed to yield {enumerator.Current.GetType().Name}");
150 }
151 }
152 }
153 catch (Exception ex)
154 {
155 state.TestHasRun = true;
156 context.CurrentResult.RecordPrefixedException(m_BeforeErrorPrefix, ex);
157 state.StoreContext(unityContext);
158 break;
159 }
160
161 if (!EnumeratorHelper.IsRunningNestedEnumerator)
162 {
163 // Only store the state in the main enumerator. Domain reloads are not supported from nested enumerators.
164 state.NextBeforeStepPc = EnumeratorHelper.GetEnumeratorPC();
165 state.StoreContext(unityContext);
166 }
167
168 if (!AllowFrameSkipAfterAction(action))
169 {
170 break;
171 }
172
173 yield return enumerator.Current;
174 }
175 }
176
177 state.NextBeforeStepIndex++;
178 state.NextBeforeStepPc = 0;
179 }
180
181 if (!state.TestHasRun)
182 {
183 state.ShouldRestore = false; // Any inner commands that can perform domain reloads are responsible for restoring the context
184 if (innerCommand is IEnumerableTestMethodCommand)
185 {
186 var executeEnumerable = ((IEnumerableTestMethodCommand)innerCommand).ExecuteEnumerable(context);
187 foreach (var iterator in executeEnumerable)
188 {
189 yield return iterator;
190 }
191 }
192 else
193 {
194 context.CurrentResult = innerCommand.Execute(context);
195 }
196
197 state.TestHasRun = true;
198 }
199
200 while (state.NextAfterStepIndex < AfterActions.Length)
201 {
202 state.TestAfterStarted = true;
203 var action = AfterActions[state.NextAfterStepIndex];
204 IEnumerator enumerator;
205 try
206 {
207 enumerator = EnumeratorHelper.UnpackNestedEnumerators(InvokeAfter(action, Test, unityContext));
208 EnumeratorHelper.SetEnumeratorPC(state.NextAfterStepPc);
209 }
210 catch (Exception ex)
211 {
212 context.CurrentResult.RecordPrefixedException(m_AfterErrorPrefix, ex);
213 state.StoreContext(unityContext);
214 break;
215 }
216
217 using (var logScope = new LogScope())
218 {
219 while (true)
220 {
221 try
222 {
223 if (!enumerator.MoveNext())
224 {
225 logScope.EvaluateLogScope(true);
226 break;
227 }
228
229 if (!AllowFrameSkipAfterAction(action)) // Evaluate the log scope right away for the commands where we do not yield
230 {
231 logScope.EvaluateLogScope(true);
232 }
233 if (enumerator.Current is IEditModeTestYieldInstruction)
234 {
235 if (unityContext.TestMode == TestPlatform.PlayMode)
236 {
237 throw new Exception($"PlayMode test are not allowed to yield {enumerator.Current.GetType().Name}");
238 }
239
240 if (EnumeratorHelper.IsRunningNestedEnumerator)
241 {
242 throw new Exception($"Nested enumerators are not allowed to yield {enumerator.Current.GetType().Name}");
243 }
244 }
245 }
246 catch (Exception ex)
247 {
248 context.CurrentResult.RecordPrefixedException(m_AfterErrorPrefix, ex);
249 state.StoreContext(unityContext);
250 break;
251 }
252
253 if (!EnumeratorHelper.IsRunningNestedEnumerator)
254 {
255 // Only store the state in the main enumerator. Domain reloads are not supported from nested enumerators.
256 state.NextAfterStepPc = EnumeratorHelper.GetEnumeratorPC();
257 state.StoreContext(unityContext);
258 }
259
260 if (!AllowFrameSkipAfterAction(action))
261 {
262 break;
263 }
264
265 yield return enumerator.Current;
266 }
267 }
268
269 state.NextAfterStepIndex++;
270 state.NextAfterStepPc = 0;
271 }
272
273 state.Reset();
274 }
275
276 public override TestResult Execute(ITestExecutionContext context)
277 {
278 throw new NotImplementedException("Use ExecuteEnumerable");
279 }
280 }
281}