Files
UnrealEngine/Engine/Source/Programs/UnrealVS/Unreal.TestAdapter/TestExecutor.cs
2025-05-18 13:04:45 +08:00

160 lines
4.1 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
namespace Unreal.TestAdapter
{
[ExtensionUri("executor://UnrealTestExecutor")]
class TestExecutor : ITestExecutor
{
bool WasCancelled = false;
public void Cancel()
{
WasCancelled = true;
}
public void RunTests(IEnumerable<string> sources, IRunContext runContext, IFrameworkHandle frameworkHandle)
{
}
public void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle)
{
WasCancelled = false;
if (runContext.IsBeingDebugged)
{
foreach (TestCase testCase in tests)
{
RunTestCase(testCase, frameworkHandle, true);
}
}
else
{
Parallel.ForEach(tests, testCase =>
{
RunTestCase(testCase, frameworkHandle, false);
});
}
}
void RunTestCase(TestCase testCase, IFrameworkHandle frameworkHandle, bool debug)
{
if (WasCancelled)
{
return;
}
if (testCase.GetPropertyValue(TestDiscoverer.DisabledTestProperty, false))
{
frameworkHandle.RecordResult(new TestResult(testCase) { Outcome = TestOutcome.Skipped });
return;
}
string tempFile = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
string args = string.Format("\"{0}\" --reporter unreal -o {1} --extra-args -Multiprocess", testCase.FullyQualifiedName, tempFile);
frameworkHandle.RecordStart(testCase);
if (debug)
{
// It does not appear to be safe to call LaunchProcessWithDebuggerAttached in parallel
string workingDir = Path.GetDirectoryName(testCase.Source);
int processId = frameworkHandle.LaunchProcessWithDebuggerAttached(testCase.Source, workingDir, args, null);
Process process = Process.GetProcessById(processId);
process.WaitForExit();
}
else
{
using (Process process = new Process())
{
process.StartInfo.FileName = testCase.Source;
process.StartInfo.Arguments = args;
process.StartInfo.UseShellExecute = false;
process.Start();
process.WaitForExit();
}
}
string xmlString = null;
if (File.Exists(tempFile))
{
xmlString = File.ReadAllText(tempFile);
File.Delete(tempFile);
}
var testResult = new TestResult(testCase) { Outcome = TestOutcome.NotFound };
ParseResult(xmlString, testResult);
frameworkHandle.RecordEnd(testCase, testResult.Outcome);
frameworkHandle.RecordResult(testResult);
}
void ParseResult(string xmlString, TestResult testResult)
{
TextReader textReader = new StringReader(xmlString);
using (XmlReader xml = XmlReader.Create(textReader, new XmlReaderSettings() { IgnoreWhitespace = true }))
{
while (xml.Read())
{
switch (xml.NodeType)
{
case XmlNodeType.Element:
if (xml.Name == "testcase" && xml.GetAttribute("name") == testResult.TestCase.FullyQualifiedName)
{
ParseTestCaseResult(xml, testResult);
}
break;
case XmlNodeType.EndElement:
if (xml.Name == "testrun")
{
return; //quit to avoid junk at the end of the output
}
break;
default:
break;
}
}
}
}
void ParseTestCaseResult(XmlReader xml, TestResult testResult)
{
xml.Read();
while (xml.NodeType != XmlNodeType.EndElement)
{
if (xml.Name == "failure")
{
string failure = xml.ReadElementContentAsString()?.Trim() ?? String.Empty;
testResult.ErrorStackTrace = failure;
testResult.Outcome = TestOutcome.Failed;
}
else if (xml.Name == "result")
{
string strSuccess = xml.GetAttribute("success");
bool.TryParse(strSuccess, out bool success);
testResult.Outcome = success ? TestOutcome.Passed : TestOutcome.Failed;
string strDuration = xml.GetAttribute("duration");
if (strDuration != null)
{
double.TryParse(strDuration, out double duraction);
testResult.Duration = TimeSpan.FromSeconds(duraction);
}
xml.Read();
}
else
{
xml.Skip();
}
}
}
}
}