unity/EditorPlugin/UnitTesting/TestEventsSender.cs (192 lines of code) (raw):
using System;
using System.Collections;
using System.Text;
using JetBrains.Diagnostics;
using JetBrains.Lifetimes;
using JetBrains.Rider.Model.Unity.BackendUnity;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using TestResult = JetBrains.Rider.Model.Unity.BackendUnity.TestResult;
namespace JetBrains.Rider.Unity.Editor.UnitTesting
{
internal class TestEventsSender
{
private static readonly ILog ourLogger = Log.GetLog(nameof(TestEventsSender));
public TestEventsSender(Lifetime appDomainLifetime, UnitTestLaunch unitTestLaunch)
{
var assembly = RiderPackageInterop.GetAssembly();
if (assembly == null)
{
ourLogger.Error("EditorPlugin assembly is null.");
return;
}
var data = assembly.GetType("Packages.Rider.Editor.UnitTesting.CallbackData");
if (data == null) return;
ProcessQueue(data, unitTestLaunch);
SubscribeToChanged(appDomainLifetime, data, unitTestLaunch);
}
private static void SubscribeToChanged(Lifetime appDomainLifetime, Type data, UnitTestLaunch unitTestLaunch)
{
var eventInfo = data.GetEvent("Changed");
if (eventInfo != null)
{
var handler = new EventHandler((sender, e) => { ProcessQueue(data, unitTestLaunch); });
appDomainLifetime.Bracket(() => eventInfo.AddEventHandler(handler.Target, handler),
() => eventInfo.RemoveEventHandler(handler.Target, handler));
}
else
{
ourLogger.Error("Changed event subscription failed.");
}
}
private static void ProcessQueue(Type data, UnitTestLaunch unitTestLaunch)
{
if (!unitTestLaunch.IsBound)
return;
var baseType = data.BaseType;
if (baseType == null) return;
var instance = baseType.GetProperty("instance");
if (instance == null) return;
var instanceVal = instance.GetValue(null, new object[]{});
var listField = data.GetField("events");
if (listField == null) return;
var list = listField.GetValue(instanceVal);
var events = (IEnumerable) list;
foreach (var ev in events)
{
var type = (int)ev.GetType().GetField("type").GetValue(ev);
var id = (string)ev.GetType().GetField("id").GetValue(ev);
var assemblyName = (string)ev.GetType().GetField("assemblyName").GetValue(ev);
var output = (string)ev.GetType().GetField("output").GetValue(ev);
var resultState = (int)ev.GetType().GetField("testStatus").GetValue(ev);
var duration = (double)ev.GetType().GetField("duration").GetValue(ev);
var parentId = (string)ev.GetType().GetField("parentId").GetValue(ev);
switch (type)
{
case 0: // TestStarted
{
var tResult = new TestResult(id, assemblyName,string.Empty, 0, Status.Running, parentId);
TestStarted(unitTestLaunch, tResult);
break;
}
case 1: // TestFinished
{
var status = GetStatus(new ResultState((TestStatus)resultState));
var testResult = new TestResult(id, assemblyName, output, (int) duration,
status, parentId);
TestFinished(unitTestLaunch, testResult);
break;
}
case 2: // RunFinished
{
var runResult = new RunResult((TestStatus) resultState == TestStatus.Passed);
RunFinished(unitTestLaunch, runResult);
break;
}
case 3: // RunStarted
{
unitTestLaunch.RunStarted.Value = true;
break;
}
default:
{
ourLogger.Error("Unexpected TestEvent type.");
break;
}
}
}
var clearMethod = data.GetMethod("Clear");
clearMethod?.Invoke(instanceVal, new object[] {});
}
public static void RunFinished(UnitTestLaunch launch, RunResult result)
{
launch.RunResult(result);
}
public static void TestStarted(UnitTestLaunch launch, TestResult testResult)
{
launch.TestResult(testResult);
}
public static void TestFinished(UnitTestLaunch launch, TestResult testResult)
{
launch.TestResult(testResult);
}
internal static TestResult GetTestResult(ITestResult testResult)
{
var id = GetIdFromNUnitTest(testResult.Test);
var assemblyName = testResult.Test.TypeInfo.Assembly.GetName().Name;
var output = ExtractOutput(testResult);
var status = GetStatus(testResult.ResultState);
var parentId = GetIdFromNUnitTest(testResult.Test.Parent);
return new TestResult( id, assemblyName, output,
(int)(testResult.EndTime - testResult.StartTime).TotalMilliseconds,
status, parentId);
}
private static Status GetStatus(ResultState resultState)
{
Status status;
if (Equals(resultState, ResultState.Success))
status = Status.Success;
else if (Equals(resultState, ResultState.Ignored))
status = Status.Ignored;
else if (Equals(resultState, ResultState.Skipped))
status = Status.Ignored;
else if (Equals(resultState, ResultState.Inconclusive))
status = Status.Inconclusive;
else
status = Status.Failure;
return status;
}
private static string ExtractOutput(ITestResult testResult)
{
var stringBuilder = new StringBuilder();
if (testResult.Message != null)
{
stringBuilder.AppendLine("Message: ");
stringBuilder.AppendLine(testResult.Message);
}
if (!string.IsNullOrEmpty(testResult.Output))
{
stringBuilder.AppendLine("Output: ");
stringBuilder.AppendLine(testResult.Output);
}
if (!string.IsNullOrEmpty(testResult.StackTrace))
{
stringBuilder.AppendLine("Stacktrace: ");
stringBuilder.AppendLine(testResult.StackTrace);
}
var result = stringBuilder.ToString();
if (result.Length > 0)
return result;
return testResult.Output ?? String.Empty;
}
internal static string GetIdFromNUnitTest(ITest test)
{
var testMethod = test as TestMethod;
if (testMethod == null)
{
ourLogger.Verbose("{0} is not a TestMethod ", test.FullName);
return GetUniqueName(test);
}
return GetUniqueName(test);
}
// analog of UnityEngine.TestRunner.NUnitExtensions.TestExtensions.GetUniqueName
// I believe newer nunit has improved parameters presentation compared to the one used in Unity.
// https://github.com/nunit/nunit/blob/d56424858f97e19a5fe64905e42adf798ca655d1/src/NUnitFramework/framework/Internal/TestNameGenerator.cs#L223
// so once Unity updates its nunit, this hack would not be needed anymore
private static string GetUniqueName(ITest test)
{
string str = test.FullName;
if (HasChildIndex(test))
{
int childIndex = GetChildIndex(test);
if (childIndex >= 0)
str += childIndex;
}
return str;
}
private static int GetChildIndex(ITest test)
{
return (int) test.Properties["childIndex"][0];
}
private static bool HasChildIndex(ITest test)
{
return test.Properties["childIndex"].Count > 0;
}
}
}