Initial commit

This commit is contained in:
AbstractConcept 2022-09-13 00:36:34 -05:00
commit 3c7cc0c973
8391 changed files with 704313 additions and 0 deletions

View file

@ -0,0 +1,128 @@
using System;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using UnityEngine.Profiling;
namespace UnityEngine.TestTools.Constraints
{
/// <summary>
/// An NUnit test constraint class to test whether a given block of code makes any GC allocations.
///
/// Use this class with NUnit's Assert.That() method to make assertions about the GC behaviour of your code. The constraint executes the delegate you provide, and checks if it has caused any GC memory to be allocated. If any GC memory was allocated, the constraint passes; otherwise, the constraint fails.
///
/// Usually you negate this constraint to make sure that your delegate does not allocate any GC memory. This is easy to do using the Is class:
/// </summary>
/// <example>
/// <code>
/// using NUnit.Framework;
/// using UnityEngine.TestTools.Constraints;
/// using Is = UnityEngine.TestTools.Constraints.Is;
///
/// public class MyTestClass
/// {
/// [Test]
/// public void SettingAVariableDoesNotAllocate()
/// {
/// Assert.That(() => {
/// int a = 0;
/// a = 1;
/// }, Is.Not.AllocatingGCMemory());
/// }
/// }
/// </code>
/// </example>
public class AllocatingGCMemoryConstraint : Constraint
{
private class AllocatingGCMemoryResult : ConstraintResult
{
private readonly int diff;
public AllocatingGCMemoryResult(IConstraint constraint, object actualValue, int diff) : base(constraint, actualValue, diff > 0)
{
this.diff = diff;
}
public override void WriteMessageTo(MessageWriter writer)
{
if (diff == 0)
writer.WriteMessageLine("The provided delegate did not make any GC allocations.");
else
writer.WriteMessageLine("The provided delegate made {0} GC allocation(s).", diff);
}
}
private ConstraintResult ApplyTo(Action action, object original)
{
var recorder = Recorder.Get("GC.Alloc");
// The recorder was created enabled, which means it captured the creation of the Recorder object itself, etc.
// Disabling it flushes its data, so that we can retrieve the sample block count and have it correctly account
// for these initial allocations.
recorder.enabled = false;
#if !UNITY_WEBGL
recorder.FilterToCurrentThread();
#endif
recorder.enabled = true;
try
{
action();
}
finally
{
recorder.enabled = false;
#if !UNITY_WEBGL
recorder.CollectFromAllThreads();
#endif
}
return new AllocatingGCMemoryResult(this, original, recorder.sampleBlockCount);
}
/// <summary>
/// Applies GC memory constraint to the test.
/// </summary>
/// <param name="obj">An object to apply the GC constraint to. Should be a <see cref="TestDelegate"/>.</param>
/// <returns>A ConstraintResult</returns>
/// <exception cref="ArgumentNullException">Throws a <see cref="ArgumentNullException"/> if the provided object is null.</exception>
/// <exception cref="ArgumentException">Throws a <see cref="ArgumentException"/> if the provided object is not a <see cref="TestDelegate"/>.</exception>
public override ConstraintResult ApplyTo(object obj)
{
if (obj == null)
throw new ArgumentNullException();
TestDelegate d = obj as TestDelegate;
if (d == null)
throw new ArgumentException(string.Format("The actual value must be a TestDelegate but was {0}",
obj.GetType()));
return ApplyTo(() => d.Invoke(), obj);
}
/// <summary>
/// Test whether the constraint is satisfied by a given reference.
/// The default implementation simply dereferences the value but
/// derived classes may override it to provide for delayed processing.
/// </summary>
/// <typeparam name="TActual">The type of the actual value delegate to be tested.</typeparam>
/// <param name="del">A reference to the value delegate to be tested</param>
/// <returns>A ConstraintResult</returns>
/// <exception cref="ArgumentNullException">Throws a <see cref="ArgumentNullException"/> if the provided delegate is null.</exception>
public override ConstraintResult ApplyTo<TActual>(ActualValueDelegate<TActual> del)
{
if (del == null)
throw new ArgumentNullException();
return ApplyTo(() => del.Invoke(), del);
}
/// <summary>
/// The Description of what this constraint tests, for to use in messages and in the ConstraintResult.
/// </summary>
public override string Description
{
get { return "allocates GC memory"; }
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d09858396dd7adb4bbdb22ea0c8c3a37
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,14 @@
using NUnit.Framework.Constraints;
namespace UnityEngine.TestTools.Constraints
{
public static class ConstraintExtensions
{
public static AllocatingGCMemoryConstraint AllocatingGCMemory(this ConstraintExpression chain)
{
var constraint = new AllocatingGCMemoryConstraint();
chain.Append(constraint);
return constraint;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 68a48d1900320ed458e118415857faf6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,18 @@
using NUnit.Framework;
using NUnit.Framework.Interfaces;
namespace UnityEngine.TestTools.TestRunner
{
internal class InvalidSignatureException : ResultStateException
{
public InvalidSignatureException(string message)
: base(message)
{
}
public override ResultState ResultState
{
get { return ResultState.NotRunnable; }
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9650d910fcaefb34cb45f121c1993892
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,10 @@
namespace UnityEngine.TestTools.Constraints
{
public class Is : NUnit.Framework.Is
{
public static AllocatingGCMemoryConstraint AllocatingGCMemory()
{
return new AllocatingGCMemoryConstraint();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d5833966abeadb429de247e4316eef4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,78 @@
using System.Text.RegularExpressions;
using UnityEngine.TestTools.Logging;
namespace UnityEngine.TestTools
{
/// <summary>
/// A test fails if Unity logs a message other than a regular log or warning message. Use `LogAssert` to check for an expected message in the log so that the test does not fail when Unity logs the message.
/// Use `LogAssert.Expect` before running the code under test, as the check for expected logs runs at the end of each frame.
/// A test also reports a failure, if an expected message does not appear, or if Unity does not log any regular log or warning messages.
///
/// `LogAssert` lets you expect Unity log messages that would otherwise cause the test to fail.
/// </summary>
public static class LogAssert
{
/// <summary>
/// Verifies that a log message of a specified type appears in the log. A test won't fail from an expected error, assertion, or exception log message. It does fail if an expected message does not appear in the log.
/// </summary>
/// <param name="type">A type of log to expect. It can take one of the [LogType enum](https://docs.unity3d.com/ScriptReference/LogType.html) values.</param>
/// <param name="message">A string value that should equate to the expected message.</param>
/// <example>
/// <code>
/// [Test]
/// public void LogAssertExample()
/// {
/// // Expect a regular log message
/// LogAssert.Expect(LogType.Log, "Log message");
///
/// // The test fails without the following expected log message
/// Debug.Log("Log message");
///
/// // An error log
/// Debug.LogError("Error message");
///
/// // Without expecting an error log, the test would fail
/// LogAssert.Expect(LogType.Error, "Error message");
/// }
///
/// </code>
/// </example>
public static void Expect(LogType type, string message)
{
LogScope.Current.ExpectedLogs.Enqueue(new LogMatch() { LogType = type, Message = message });
}
/// <summary>
/// Verifies that a log message of a specified type appears in the log. A test won't fail from an expected error, assertion, or exception log message. It does fail if an expected message does not appear in the log.
/// </summary>
/// <param name="type">A type of log to expect. It can take one of the [LogType enum](https://docs.unity3d.com/ScriptReference/LogType.html) values.</param>
/// <param name="message">A regular expression pattern to match the expected message.</param>
public static void Expect(LogType type, Regex message)
{
LogScope.Current.ExpectedLogs.Enqueue(new LogMatch() { LogType = type, MessageRegex = message });
}
/// <summary>
/// Triggers an assertion when receiving any log messages and fails the test if some are unexpected messages. If multiple tests need to check for no received unexpected logs, consider using the <see cref="TestMustExpectAllLogsAttribute"/> attribute instead.
/// </summary>
public static void NoUnexpectedReceived()
{
LogScope.Current.NoUnexpectedReceived();
}
/// <summary>Set this property to `true` to prevent unexpected error log messages from triggering an assertion. By default, it is `false`.</summary>
/// <returns>The value of the ignoreFailingMessages boolean property.</returns>
public static bool ignoreFailingMessages
{
get
{
return LogScope.Current.IgnoreFailingMessages;
}
set
{
if (value != LogScope.Current.IgnoreFailingMessages)
{
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "\nIgnoreFailingMessages:" + (value? "true":"false"));
}
LogScope.Current.IgnoreFailingMessages = value;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c97b794b51780d349a16826a4c7898d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b1d8465ba1376b148bdab58965101f47
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.TestTools.Logging
{
internal interface ILogScope : IDisposable
{
Queue<LogMatch> ExpectedLogs { get; set; }
List<LogEvent> AllLogs { get; }
List<LogEvent> FailingLogs { get; }
bool IgnoreFailingMessages { get; set; }
bool IsNUnitException { get; }
bool IsNUnitSuccessException { get; }
bool IsNUnitInconclusiveException { get; }
bool IsNUnitIgnoreException { get; }
string NUnitExceptionMessage { get; }
void AddLog(string message, string stacktrace, LogType type);
bool AnyFailingLogs();
void ProcessExpectedLogs();
void NoUnexpectedReceived();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3504aa04cda851b44a65973f9aead6f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,18 @@
namespace UnityEngine.TestTools.Logging
{
internal class LogEvent
{
public string Message { get; set; }
public string StackTrace { get; set; }
public LogType LogType { get; set; }
public bool IsHandled { get; set; }
public override string ToString()
{
return string.Format("[{0}] {1}", LogType, Message);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0c56471f08a0f6846afc792f0b4205b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,103 @@
using System;
using System.Text.RegularExpressions;
namespace UnityEngine.TestTools.Logging
{
[Serializable]
internal class LogMatch
{
[SerializeField]
private bool m_UseRegex;
[SerializeField]
private string m_Message;
[SerializeField]
private string m_MessageRegex;
[SerializeField]
private string m_LogType;
public string Message
{
get { return m_Message; }
set
{
m_Message = value;
m_UseRegex = false;
}
}
public Regex MessageRegex
{
get
{
if (!m_UseRegex)
{
return null;
}
return new Regex(m_MessageRegex);
}
set
{
if (value != null)
{
m_MessageRegex = value.ToString();
m_UseRegex = true;
}
else
{
m_MessageRegex = null;
m_UseRegex = false;
}
}
}
public LogType? LogType
{
get
{
if (!string.IsNullOrEmpty(m_LogType))
{
return Enum.Parse(typeof(LogType), m_LogType) as LogType ? ;
}
return null;
}
set
{
if (value != null)
{
m_LogType = value.Value.ToString();
}
else
{
m_LogType = null;
}
}
}
public bool Matches(LogEvent log)
{
if (LogType != null && LogType != log.LogType)
{
return false;
}
if (m_UseRegex)
{
return MessageRegex.IsMatch(log.Message);
}
else
{
return Message.Equals(log.Message);
}
}
public override string ToString()
{
if (m_UseRegex)
return string.Format("[{0}] Regex: {1}", LogType, MessageRegex);
else
return string.Format("[{0}] {1}", LogType, Message);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9945ffed4692c6044b6d3acf81efd694
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.TestTools.TestRunner;
namespace UnityEngine.TestTools.Logging
{
sealed class LogScope : ILogScope
{
static List<LogScope> s_ActiveScopes = new List<LogScope>();
readonly object m_Lock = new object();
bool m_Disposed;
bool m_NeedToProcessLogs;
public Queue<LogMatch> ExpectedLogs { get; set; }
public List<LogEvent> AllLogs { get; }
public List<LogEvent> 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<LogEvent>();
FailingLogs = new List<LogEvent>();
ExpectedLogs = new Queue<LogMatch>();
IgnoreFailingMessages = false;
Activate();
}
void Activate()
{
s_ActiveScopes.Insert(0, this);
RegisterScope(this);
Application.logMessageReceivedThreaded -= AddLog;
Application.logMessageReceivedThreaded += AddLog;
}
void Deactivate()
{
Application.logMessageReceivedThreaded -= AddLog;
s_ActiveScopes.Remove(this);
UnregisterScope(this);
}
static void RegisterScope(LogScope logScope)
{
Application.logMessageReceivedThreaded += logScope.AddLog;
}
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);
}
}
}
static bool IsNUnitResultStateException(string stacktrace, LogType logType)
{
if (logType != LogType.Exception)
return false;
return string.IsNullOrEmpty(stacktrace) || stacktrace.StartsWith("NUnit.Framework.Assert.");
}
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);
}
void Dispose(bool disposing)
{
if (m_Disposed)
{
return;
}
m_Disposed = true;
if (disposing)
{
Deactivate();
}
}
public bool AnyFailingLogs()
{
ProcessExpectedLogs();
return FailingLogs.Any();
}
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 (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);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4bbc17b35884fdf468e4b52ae4222882
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,29 @@
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using UnityEngine.TestTools.Logging;
namespace UnityEngine.TestTools.TestRunner
{
internal class UnexpectedLogMessageException : ResultStateException
{
public LogMatch LogEvent;
public UnexpectedLogMessageException(LogMatch log)
: base(BuildMessage(log))
{
LogEvent = log;
}
private static string BuildMessage(LogMatch log)
{
return string.Format("Expected log did not appear: {0}", log);
}
public override ResultState ResultState
{
get { return ResultState.Failure; }
}
public override string StackTrace { get { return null; } }
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5b2eeca598284bd4abb4a15c30df1576
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,35 @@
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using UnityEngine.TestTools.Logging;
using UnityEngine.TestTools.Utils;
namespace UnityEngine.TestTools.TestRunner
{
internal class UnhandledLogMessageException : ResultStateException
{
public LogEvent LogEvent;
private readonly string m_CustomStackTrace;
public UnhandledLogMessageException(LogEvent log)
: base(BuildMessage(log))
{
LogEvent = log;
m_CustomStackTrace = StackTraceFilter.Filter(log.StackTrace);
}
private static string BuildMessage(LogEvent log)
{
return string.Format("Unhandled log message: '{0}'. Use UnityEngine.TestTools.LogAssert.Expect", log);
}
public override ResultState ResultState
{
get { return ResultState.Failure; }
}
public override string StackTrace
{
get { return m_CustomStackTrace; }
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8ed4063f2beecd41a234a582202f3c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,28 @@
using NUnit.Framework;
using NUnit.Framework.Interfaces;
namespace UnityEngine.TestTools.TestRunner
{
internal class UnityTestTimeoutException : ResultStateException
{
public UnityTestTimeoutException(int timeout)
: base(BuildMessage(timeout))
{
}
private static string BuildMessage(int timeout)
{
return string.Format("Timeout value of {0}ms was exceeded", timeout);
}
public override ResultState ResultState
{
get { return ResultState.Failure; }
}
public override string StackTrace
{
get { return ""; }
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ffb335140c799c4408411d81789fb05c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: