A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine.TestTools.TestRunner;
5
6namespace UnityEngine.TestTools.Logging
7{
8 internal sealed class LogScope : ILogScope
9 {
10 private static List<LogScope> s_ActiveScopes = new List<LogScope>();
11
12 private readonly object m_Lock = new object();
13 private bool m_Disposed;
14 private bool m_NeedToProcessLogs;
15
16 public Queue<LogMatch> ExpectedLogs { get; set; }
17 public List<LogEvent> AllLogs { get; }
18 public List<LogEvent> FailingLogs { get; }
19 public bool IgnoreFailingMessages { get; set; }
20 public bool IsNUnitException { get; private set; }
21 public bool IsNUnitSuccessException { get; private set; }
22 public bool IsNUnitInconclusiveException { get; private set; }
23 public bool IsNUnitIgnoreException { get; private set; }
24 public string NUnitExceptionMessage { get; private set; }
25
26 public static LogScope Current
27 {
28 get
29 {
30 if (s_ActiveScopes.Count == 0)
31 throw new InvalidOperationException("No log scope is available");
32 return s_ActiveScopes[0];
33 }
34 }
35
36 public static bool HasCurrentLogScope()
37 {
38 return s_ActiveScopes.Count > 0;
39 }
40
41 public LogScope()
42 {
43 AllLogs = new List<LogEvent>();
44 FailingLogs = new List<LogEvent>();
45 ExpectedLogs = new Queue<LogMatch>();
46 IgnoreFailingMessages = false;
47 Activate();
48 }
49
50 private void Activate()
51 {
52 s_ActiveScopes.Insert(0, this);
53 RegisterScope(this);
54 Application.logMessageReceivedThreaded -= AddLog;
55 Application.logMessageReceivedThreaded += AddLog;
56 }
57
58 private void Deactivate()
59 {
60 Application.logMessageReceivedThreaded -= AddLog;
61 s_ActiveScopes.Remove(this);
62 UnregisterScope(this);
63 }
64
65 private static void RegisterScope(LogScope logScope)
66 {
67 Application.logMessageReceivedThreaded += logScope.AddLog;
68 }
69
70 private static void UnregisterScope(LogScope logScope)
71 {
72 Application.logMessageReceivedThreaded -= logScope.AddLog;
73 }
74
75 public void AddLog(string message, string stacktrace, LogType type)
76 {
77 lock (m_Lock)
78 {
79 m_NeedToProcessLogs = true;
80 var log = new LogEvent
81 {
82 LogType = type,
83 Message = message,
84 StackTrace = stacktrace,
85 };
86
87 AllLogs.Add(log);
88
89 if (IsNUnitResultStateException(stacktrace, type))
90 {
91 if (message.StartsWith("SuccessException"))
92 {
93 IsNUnitException = true;
94 IsNUnitSuccessException = true;
95 if (message.StartsWith("SuccessException: "))
96 {
97 NUnitExceptionMessage = message.Substring("SuccessException: ".Length);
98 return;
99 }
100 }
101 else if (message.StartsWith("InconclusiveException"))
102 {
103 IsNUnitException = true;
104 IsNUnitInconclusiveException = true;
105 if (message.StartsWith("InconclusiveException: "))
106 {
107 NUnitExceptionMessage = message.Substring("InconclusiveException: ".Length);
108 return;
109 }
110 }
111 else if (message.StartsWith("IgnoreException"))
112 {
113 IsNUnitException = true;
114 IsNUnitIgnoreException = true;
115 if (message.StartsWith("IgnoreException: "))
116 {
117 NUnitExceptionMessage = message.Substring("IgnoreException: ".Length);
118 return;
119 }
120 }
121 }
122
123 if (IsFailingLog(type) && !IgnoreFailingMessages)
124 {
125 FailingLogs.Add(log);
126 }
127 }
128 }
129
130 private static bool IsNUnitResultStateException(string stacktrace, LogType logType)
131 {
132 if (logType != LogType.Exception)
133 return false;
134
135 return string.IsNullOrEmpty(stacktrace) || stacktrace.StartsWith("NUnit.Framework.Assert.");
136 }
137
138 private static bool IsFailingLog(LogType type)
139 {
140 switch (type)
141 {
142 case LogType.Assert:
143 case LogType.Error:
144 case LogType.Exception:
145 return true;
146 default:
147 return false;
148 }
149 }
150
151 public void Dispose()
152 {
153 Dispose(true);
154 GC.SuppressFinalize(this);
155 }
156
157 private void Dispose(bool disposing)
158 {
159 if (m_Disposed)
160 {
161 return;
162 }
163
164 m_Disposed = true;
165
166 if (disposing)
167 {
168 Deactivate();
169 }
170 }
171
172 public bool AnyFailingLogs()
173 {
174 ProcessExpectedLogs();
175 return FailingLogs.Any();
176 }
177
178 public void EvaluateLogScope(bool endOfScopeCheck)
179 {
180 ProcessExpectedLogs();
181 if (FailingLogs.Any())
182 {
183 var failureInWrongOrder = FailingLogs.FirstOrDefault(log => ExpectedLogs.Any(expected => expected.Matches(log)));
184 if (failureInWrongOrder != null)
185 {
186 var nextExpected = ExpectedLogs.Peek();
187 throw new OutOfOrderExpectedLogMessageException(failureInWrongOrder, nextExpected);
188 }
189
190 var failingLog = FailingLogs.First();
191 throw new UnhandledLogMessageException(failingLog);
192 }
193 if (endOfScopeCheck && ExpectedLogs.Any())
194 {
195 throw new UnexpectedLogMessageException(ExpectedLogs.Peek());
196 }
197 }
198
199 public void ProcessExpectedLogs()
200 {
201 lock (m_Lock)
202 {
203 if (!m_NeedToProcessLogs || !ExpectedLogs.Any())
204 return;
205
206 LogMatch expectedLog = null;
207 foreach (var logEvent in AllLogs)
208 {
209 if (!ExpectedLogs.Any())
210 break;
211 if (logEvent.IsHandled)
212 {
213 continue;
214 }
215 if (expectedLog == null && ExpectedLogs.Any())
216 expectedLog = ExpectedLogs.Peek();
217
218 if (expectedLog != null && expectedLog.Matches(logEvent))
219 {
220 ExpectedLogs.Dequeue();
221 logEvent.IsHandled = true;
222 if (FailingLogs.Any(expectedLog.Matches))
223 {
224 var failingLog = FailingLogs.First(expectedLog.Matches);
225 FailingLogs.Remove(failingLog);
226 }
227 expectedLog = null;
228 }
229 }
230 m_NeedToProcessLogs = false;
231 }
232 }
233
234 public void NoUnexpectedReceived()
235 {
236 lock (m_Lock)
237 {
238 ProcessExpectedLogs();
239
240 var unhandledLog = AllLogs.FirstOrDefault(x => !x.IsHandled);
241 if (unhandledLog != null)
242 {
243 throw new UnhandledLogMessageException(unhandledLog);
244 }
245 }
246 }
247 }
248}