using System; using System.Collections.Generic; using System.Linq; using UnityEngine.TestTools.TestRunner; namespace UnityEngine.TestTools.Logging { internal sealed class LogScope : ILogScope { private static List s_ActiveScopes = new List(); private readonly object m_Lock = new object(); private bool m_Disposed; private bool m_NeedToProcessLogs; public Queue ExpectedLogs { get; set; } public List AllLogs { get; } public List FailingLogs { get; } public bool IgnoreFailingMessages { get; set; } public bool IsNUnitException { get; private set; } public bool IsNUnitSuccessException { get; private set; } public bool IsNUnitInconclusiveException { get; private set; } public bool IsNUnitIgnoreException { get; private set; } public string NUnitExceptionMessage { get; private set; } public static LogScope Current { get { if (s_ActiveScopes.Count == 0) throw new InvalidOperationException("No log scope is available"); return s_ActiveScopes[0]; } } public static bool HasCurrentLogScope() { return s_ActiveScopes.Count > 0; } public LogScope() { AllLogs = new List(); FailingLogs = new List(); ExpectedLogs = new Queue(); IgnoreFailingMessages = false; Activate(); } private void Activate() { s_ActiveScopes.Insert(0, this); RegisterScope(this); Application.logMessageReceivedThreaded -= AddLog; Application.logMessageReceivedThreaded += AddLog; } private void Deactivate() { Application.logMessageReceivedThreaded -= AddLog; s_ActiveScopes.Remove(this); UnregisterScope(this); } private static void RegisterScope(LogScope logScope) { Application.logMessageReceivedThreaded += logScope.AddLog; } private static void UnregisterScope(LogScope logScope) { Application.logMessageReceivedThreaded -= logScope.AddLog; } public void AddLog(string message, string stacktrace, LogType type) { lock (m_Lock) { m_NeedToProcessLogs = true; var log = new LogEvent { LogType = type, Message = message, StackTrace = stacktrace, }; AllLogs.Add(log); if (IsNUnitResultStateException(stacktrace, type)) { if (message.StartsWith("SuccessException")) { IsNUnitException = true; IsNUnitSuccessException = true; if (message.StartsWith("SuccessException: ")) { NUnitExceptionMessage = message.Substring("SuccessException: ".Length); return; } } else if (message.StartsWith("InconclusiveException")) { IsNUnitException = true; IsNUnitInconclusiveException = true; if (message.StartsWith("InconclusiveException: ")) { NUnitExceptionMessage = message.Substring("InconclusiveException: ".Length); return; } } else if (message.StartsWith("IgnoreException")) { IsNUnitException = true; IsNUnitIgnoreException = true; if (message.StartsWith("IgnoreException: ")) { NUnitExceptionMessage = message.Substring("IgnoreException: ".Length); return; } } } if (IsFailingLog(type) && !IgnoreFailingMessages) { FailingLogs.Add(log); } } } private static bool IsNUnitResultStateException(string stacktrace, LogType logType) { if (logType != LogType.Exception) return false; return string.IsNullOrEmpty(stacktrace) || stacktrace.StartsWith("NUnit.Framework.Assert."); } private static bool IsFailingLog(LogType type) { switch (type) { case LogType.Assert: case LogType.Error: case LogType.Exception: return true; default: return false; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (m_Disposed) { return; } m_Disposed = true; if (disposing) { Deactivate(); } } public bool AnyFailingLogs() { ProcessExpectedLogs(); return FailingLogs.Any(); } public void EvaluateLogScope(bool endOfScopeCheck) { ProcessExpectedLogs(); if (FailingLogs.Any()) { var failureInWrongOrder = FailingLogs.FirstOrDefault(log => ExpectedLogs.Any(expected => expected.Matches(log))); if (failureInWrongOrder != null) { var nextExpected = ExpectedLogs.Peek(); throw new OutOfOrderExpectedLogMessageException(failureInWrongOrder, nextExpected); } var failingLog = FailingLogs.First(); throw new UnhandledLogMessageException(failingLog); } if (endOfScopeCheck && ExpectedLogs.Any()) { throw new UnexpectedLogMessageException(ExpectedLogs.Peek()); } } public void ProcessExpectedLogs() { lock (m_Lock) { if (!m_NeedToProcessLogs || !ExpectedLogs.Any()) return; LogMatch expectedLog = null; foreach (var logEvent in AllLogs) { if (!ExpectedLogs.Any()) break; if (logEvent.IsHandled) { continue; } if (expectedLog == null && ExpectedLogs.Any()) expectedLog = ExpectedLogs.Peek(); if (expectedLog != null && expectedLog.Matches(logEvent)) { ExpectedLogs.Dequeue(); logEvent.IsHandled = true; if (FailingLogs.Any(expectedLog.Matches)) { var failingLog = FailingLogs.First(expectedLog.Matches); FailingLogs.Remove(failingLog); } expectedLog = null; } } m_NeedToProcessLogs = false; } } public void NoUnexpectedReceived() { lock (m_Lock) { ProcessExpectedLogs(); var unhandledLog = AllLogs.FirstOrDefault(x => !x.IsHandled); if (unhandledLog != null) { throw new UnhandledLogMessageException(unhandledLog); } } } } }