Skip to content

Commit

Permalink
Merge pull request #53 from TestCentric/issue-39h
Browse files Browse the repository at this point in the history
Further restructuring of dispatcher; set test not running before run finished notice
  • Loading branch information
CharliePoole authored Jun 16, 2022
2 parents 0a438b2 + 0294246 commit 3224150
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 146 deletions.
3 changes: 2 additions & 1 deletion src/TestEngine/testcentric.engine.core/Internal/XmlHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ public static XmlNode CreateXmlNode(string xml)
/// <param name="node">The node to which the attribute should be added.</param>
/// <param name="name">The name of the attribute.</param>
/// <param name="value">The value of the attribute.</param>
public static void AddAttribute(this XmlNode node, string name, string value)
public static XmlNode AddAttribute(this XmlNode node, string name, string value)
{
XmlAttribute attr = node.OwnerDocument.CreateAttribute(name);
attr.Value = value;
node.Attributes.Append(attr);
return node;
}

/// <summary>
Expand Down
93 changes: 92 additions & 1 deletion src/TestEngine/testcentric.engine/Internal/ResultHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,71 @@ public static TestEngineResult MakeProjectResult(this TestEngineResult result, T
return Aggregate(result, TEST_SUITE_ELEMENT, PROJECT_SUITE_TYPE, package.ID, package.Name, package.FullName);
}

// The TestEngineResult returned to MasterTestRunner contains no info
// about projects. At this point, if there are any projects, the result
// needs to be modified to include info about them. Doing it this way
// allows the lower-level runners to be completely ignorant of projects
public static TestEngineResult AddProjectPackages(this TestEngineResult result, TestPackage testPackage)
{
if (result == null) throw new ArgumentNullException("result");

// See if we have any projects to deal with. At this point,
// any subpackage, which itself has subpackages, is a project
// we expanded.
bool hasProjects = false;
foreach (var p in testPackage.SubPackages)
hasProjects |= p.HasSubPackages();

// If no Projects, there's nothing to do
if (!hasProjects)
return result;

// If there is just one subpackage, it has to be a project and we don't
// need to rebuild the XML but only wrap it with a project result.
if (testPackage.SubPackages.Count == 1)
return result.MakeProjectResult(testPackage.SubPackages[0]);

// Most complex case - we need to work with the XML in order to
// examine and rebuild the result to include project nodes.
// NOTE: The algorithm used here relies on the ordering of nodes in the
// result matching the ordering of subpackages under the top-level package.
// If that should change in the future, then we would need to implement
// identification and summarization of projects into each of the lower-
// level TestEngineRunners. In that case, we will be warned by failures
// of some of the MasterTestRunnerTests.

// Start a fresh TestEngineResult for top level
var topLevelResult = new TestEngineResult();
int nextTest = 0;

foreach (var subPackage in testPackage.SubPackages)
{
if (subPackage.HasSubPackages())
{
// This is a project, create an intermediate result
var projectResult = new TestEngineResult();

// Now move any children of this project under it. As noted
// above, we must rely on ordering here because (1) the
// fullname attribute is not reliable on all nunit framework
// versions, (2) we may have duplicates of the same assembly
// and (3) we have no info about the id of each assembly.
int numChildren = subPackage.SubPackages.Count;
while (numChildren-- > 0)
projectResult.Add(result.XmlNodes[nextTest++]);

topLevelResult.Add(projectResult.MakeProjectResult(subPackage).Xml);
}
else
{
// Add the next assembly package to our new result
topLevelResult.Add(result.XmlNodes[nextTest++]);
}
}

return topLevelResult;
}

/// <summary>
/// Aggregate all the separate assembly results of a test run as a single node.
/// </summary>
Expand All @@ -69,7 +134,33 @@ public static TestEngineResult MakeProjectResult(this TestEngineResult result, T
/// <returns>A TestEngineResult with a single top-level element.</returns>
public static TestEngineResult MakeTestRunResult(this TestEngineResult result, TestPackage package)
{
return Aggregate(result, TEST_RUN_ELEMENT, package.ID, package.Name, package.FullName);
return Aggregate(result.AddProjectPackages(package), TEST_RUN_ELEMENT, package.ID, package.Name, package.FullName);
}

public static TestEngineResult InsertFilterElement(this TestEngineResult result, TestFilter filter)
{
// Convert the filter to an XmlNode
var tempNode = XmlHelper.CreateXmlNode(filter.Text);

// Don't include it if it's an empty filter
if (tempNode.ChildNodes.Count > 0)
{
var doc = result.Xml.OwnerDocument;
if (doc != null)
{
var filterElement = doc.ImportNode(tempNode, true);
result.Xml.InsertAfter(filterElement, null);
}
}

return result;
}

public static TestEngineResult InsertCommandLineElement(this TestEngineResult result, string commandLine)
{
result.Xml.AddElementWithCDataSection("command-line", commandLine);

return result;
}

/// <summary>
Expand Down
164 changes: 34 additions & 130 deletions src/TestEngine/testcentric.engine/Runners/MasterTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Xml;
using NUnit.Engine;
using TestCentric.Engine.Internal;
Expand Down Expand Up @@ -103,7 +104,8 @@ protected bool IsPackageLoaded
/// <returns>An XmlNode representing the loaded assembly.</returns>
public XmlNode Load()
{
LoadResult = PrepareResult(GetEngineRunner().Load()).MakeTestRunResult(TestPackage);
LoadResult = GetEngineRunner().Load()
.MakeTestRunResult(TestPackage);

return LoadResult.Xml;
}
Expand All @@ -124,7 +126,8 @@ public void Unload()
/// <exception cref="InvalidOperationException">If no package has been loaded</exception>
public XmlNode Reload()
{
LoadResult = PrepareResult(GetEngineRunner().Reload()).MakeTestRunResult(TestPackage);
LoadResult = GetEngineRunner().Reload()
.MakeTestRunResult(TestPackage);

return LoadResult.Xml;
}
Expand Down Expand Up @@ -177,11 +180,13 @@ public void StopRun(bool force)

// When running under .NET Core, the test framework will not be able to kill the
// threads currently running tests. We handle cleanup here in case that happens.
if (force && !_eventDispatcher.WaitForCompletion(WAIT_FOR_CANCEL_TO_COMPLETE))
if (force)
{
// Send completion events for any tests, which were still running
_eventDispatcher.IssuePendingNotifications();

IsTestRunning = false;

// Signal completion of the run
_eventDispatcher.DispatchEvent($"<test-run id='{TestPackage.ID}' result='Failed' label='Cancelled' />");
}
Expand All @@ -195,7 +200,7 @@ public void StopRun(bool force)
/// <returns>An XmlNode representing the tests found.</returns>
public XmlNode Explore(TestFilter filter)
{
LoadResult = PrepareResult(GetEngineRunner().Explore(filter))
LoadResult = GetEngineRunner().Explore(filter)
.MakeTestRunResult(TestPackage);

return LoadResult.Xml;
Expand Down Expand Up @@ -247,71 +252,6 @@ internal ITestEngineRunner GetEngineRunner()
return _engineRunner;
}

// The TestEngineResult returned to MasterTestRunner contains no info
// about projects. At this point, if there are any projects, the result
// needs to be modified to include info about them. Doing it this way
// allows the lower-level runners to be completely ignorant of projects
private TestEngineResult PrepareResult(TestEngineResult result)
{
if (result == null) throw new ArgumentNullException("result");

// See if we have any projects to deal with. At this point,
// any subpackage, which itself has subpackages, is a project
// we expanded.
bool hasProjects = false;
foreach (var p in TestPackage.SubPackages)
hasProjects |= p.HasSubPackages();

// If no Projects, there's nothing to do
if (!hasProjects)
return result;

// If there is just one subpackage, it has to be a project and we don't
// need to rebuild the XML but only wrap it with a project result.
if (TestPackage.SubPackages.Count == 1)
return result.MakeProjectResult(TestPackage.SubPackages[0]);

// Most complex case - we need to work with the XML in order to
// examine and rebuild the result to include project nodes.
// NOTE: The algorithm used here relies on the ordering of nodes in the
// result matching the ordering of subpackages under the top-level package.
// If that should change in the future, then we would need to implement
// identification and summarization of projects into each of the lower-
// level TestEngineRunners. In that case, we will be warned by failures
// of some of the MasterTestRunnerTests.

// Start a fresh TestEngineResult for top level
var topLevelResult = new TestEngineResult();
int nextTest = 0;

foreach (var subPackage in TestPackage.SubPackages)
{
if (subPackage.HasSubPackages())
{
// This is a project, create an intermediate result
var projectResult = new TestEngineResult();

// Now move any children of this project under it. As noted
// above, we must rely on ordering here because (1) the
// fullname attribute is not reliable on all nunit framework
// versions, (2) we may have duplicates of the same assembly
// and (3) we have no info about the id of each assembly.
int numChildren = subPackage.SubPackages.Count;
while (numChildren-- > 0)
projectResult.Add(result.XmlNodes[nextTest++]);

topLevelResult.Add(projectResult.MakeProjectResult(subPackage).Xml);
}
else
{
// Add the next assembly package to our new result
topLevelResult.Add(result.XmlNodes[nextTest++]);
}
}

return topLevelResult;
}

/// <summary>
/// Unload any loaded TestPackage.
/// </summary>
Expand Down Expand Up @@ -345,7 +285,7 @@ private int CountTests(TestFilter filter)
/// <returns>A TestEngineResult giving the result of the test execution</returns>
private TestEngineResult RunTests(ITestEventListener listener, TestFilter filter)
{
_eventDispatcher.ClearListeners();
_eventDispatcher.InitializeForRun();

if (listener != null)
_eventDispatcher.Listeners.Add(listener);
Expand All @@ -362,22 +302,21 @@ private TestEngineResult RunTests(ITestEventListener listener, TestFilter filter

try
{
var startRunNode = XmlHelper.CreateTopLevelElement("start-run");
startRunNode.AddAttribute("count", CountTests(filter).ToString());
startRunNode.AddAttribute("start-time", XmlConvert.ToString(startTime, "u"));
startRunNode.AddAttribute("engine-version", engineVersion);
startRunNode.AddAttribute("clr-version", clrVersion);
var startRunNode = XmlHelper.CreateTopLevelElement("start-run")
.AddAttribute("count", CountTests(filter).ToString())
.AddAttribute("start-time", XmlConvert.ToString(startTime, "u"))
.AddAttribute("engine-version", engineVersion)
.AddAttribute("clr-version", clrVersion);

InsertCommandLineElement(startRunNode);
startRunNode.AddElementWithCDataSection("command-line", Environment.CommandLine);

_eventDispatcher.OnTestEvent(startRunNode.OuterXml);

TestEngineResult result = PrepareResult(GetEngineRunner().Run(_eventDispatcher, filter)).MakeTestRunResult(TestPackage);

// These are inserted in reverse order, since each is added as the first child.
InsertFilterElement(result.Xml, filter);

InsertCommandLineElement(result.Xml);
// Insertions are done in reverse order, since each is added as the first child.
TestEngineResult result = GetEngineRunner().Run(_eventDispatcher, filter)
.MakeTestRunResult(TestPackage)
.InsertFilterElement(filter)
.InsertCommandLineElement(Environment.CommandLine);

result.Xml.AddAttribute("engine-version", engineVersion);
result.Xml.AddAttribute("clr-version", clrVersion);
Expand All @@ -396,25 +335,27 @@ private TestEngineResult RunTests(ITestEventListener listener, TestFilter filter
{
IsTestRunning = false;

var resultXml = XmlHelper.CreateTopLevelElement("test-run");
resultXml.AddAttribute("id", TestPackage.ID);
resultXml.AddAttribute("result", "Failed");
resultXml.AddAttribute("label", "Error");
resultXml.AddAttribute("engine-version", engineVersion);
resultXml.AddAttribute("clr-version", clrVersion);
var result = CreateErrorResult(TestPackage);
result.Xml.AddAttribute("engine-version", engineVersion);
result.Xml.AddAttribute("clr-version", clrVersion);
double duration = (double)(Stopwatch.GetTimestamp() - startTicks) / Stopwatch.Frequency;
resultXml.AddAttribute("start-time", XmlConvert.ToString(startTime, "u"));
resultXml.AddAttribute("end-time", XmlConvert.ToString(DateTime.UtcNow, "u"));
resultXml.AddAttribute("duration", duration.ToString("0.000000", NumberFormatInfo.InvariantInfo));
result.Xml.AddAttribute("start-time", XmlConvert.ToString(startTime, "u"));
result.Xml.AddAttribute("end-time", XmlConvert.ToString(DateTime.UtcNow, "u"));
result.Xml.AddAttribute("duration", duration.ToString("0.000000", NumberFormatInfo.InvariantInfo));

_eventDispatcher.OnTestEvent(resultXml.OuterXml);
_eventDispatcher.OnTestEvent(result.Xml.OuterXml);

_eventDispatcher.OnTestEvent($"<unhandled-exception message=\"{ex.Message}\" />");

return new TestEngineResult(resultXml);
return result;
}
}

private TestEngineResult CreateErrorResult(TestPackage package)
{
return new TestEngineResult($"<test-run id='{package.ID}' result='Failed' label = 'Error' />");
}

private AsyncTestEngineResult RunTestsAsync(ITestEventListener listener, TestFilter filter)
{
var testRun = new AsyncTestEngineResult();
Expand All @@ -432,42 +373,5 @@ private AsyncTestEngineResult RunTestsAsync(ITestEventListener listener, TestFil

return testRun;
}

private static void InsertCommandLineElement(XmlNode resultNode)
{
var doc = resultNode.OwnerDocument;

if (doc == null)
{
return;
}

XmlNode cmd = doc.CreateElement("command-line");
resultNode.InsertAfter(cmd, null);

var cdata = doc.CreateCDataSection(Environment.CommandLine);
cmd.AppendChild(cdata);
}

private static void InsertFilterElement(XmlNode resultNode, TestFilter filter)
{
// Convert the filter to an XmlNode
var tempNode = XmlHelper.CreateXmlNode(filter.Text);

// Don't include it if it's an empty filter
if (tempNode.ChildNodes.Count <= 0)
{
return;
}

var doc = resultNode.OwnerDocument;
if (doc == null)
{
return;
}

var filterElement = doc.ImportNode(tempNode, true);
resultNode.InsertAfter(filterElement, null);
}
}
}
Loading

0 comments on commit 3224150

Please sign in to comment.