Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring NUnit3TestExecutor #154

Merged
merged 2 commits into from
Apr 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/NUnitTestAdapter/NUnit3TestAdapter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
<Compile Include="NUnitTestAdapter.cs" />
<Compile Include="NUnit3TestDiscoverer.cs" />
<Compile Include="NUnit3TestExecutor.cs" />
<Compile Include="NUnitTestFilterBuilder.cs" />
<Compile Include="PackageSettings.cs" />
<Compile Include="TestConverter.cs">
<SubType>Code</SubType>
Expand Down
27 changes: 14 additions & 13 deletions src/NUnitTestAdapter/NUnit3TestDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ public void DiscoverTests(IEnumerable<string> sources, IDiscoveryContext discove
#endif
Initialize(discoveryContext, messageLogger);

Info("discovering tests", "started");
TestLog.Info(string.Format("NUnit Adapter {0}: Test discovery starting", AdapterVersion));

// Ensure any channels registered by other adapters are unregistered
CleanUpRegisteredChannels();

foreach (string sourceAssembly in sources)
{
TestLog.SendDebugMessage("Processing " + sourceAssembly);
TestLog.Debug("Processing " + sourceAssembly);

ITestRunner runner = null;

Expand All @@ -60,44 +60,44 @@ public void DiscoverTests(IEnumerable<string> sources, IDiscoveryContext discove
using (var testConverter = new TestConverter(TestLog, sourceAssembly))
{
int cases = ProcessTestCases(topNode, discoverySink, testConverter);
TestLog.SendDebugMessage(string.Format("Discovered {0} test cases", cases));
TestLog.Debug(string.Format("Discovered {0} test cases", cases));
}
}
else
{
var msgNode = loadResult.SelectSingleNode("properties/property[@name='_SKIPREASON']");
if (msgNode != null && (new[] { "contains no tests", "Has no TestFixtures" }).Any(msgNode.GetAttribute("value").Contains))
TestLog.SendInformationalMessage("Assembly contains no NUnit 3.0 tests: " + sourceAssembly);
TestLog.Info("Assembly contains no NUnit 3.0 tests: " + sourceAssembly);
else
TestLog.NUnitLoadError(sourceAssembly);
TestLog.Info("NUnit failed to load " + sourceAssembly);
}
}
catch (BadImageFormatException)
{
// we skip the native c++ binaries that we don't support.
TestLog.AssemblyNotSupportedWarning(sourceAssembly);
TestLog.Warning("Assembly not supported: " + sourceAssembly);
}
catch (FileNotFoundException ex)
{
// Either the NUnit framework was not referenced by the test assembly
// or some other error occured. Not a problem if not an NUnit assembly.
TestLog.DependentAssemblyNotFoundWarning(ex.FileName, sourceAssembly);
TestLog.Warning("Dependent Assembly " + ex.FileName + " of " + sourceAssembly + " not found. Can be ignored if not a NUnit project.");
}
catch (FileLoadException ex)
{
// Attempts to load an invalid assembly, or an assembly with missing dependencies
TestLog.LoadingAssemblyFailedWarning(ex.FileName, sourceAssembly);
TestLog.Warning("Assembly " + ex.FileName + " loaded through " + sourceAssembly + " failed. Assembly is ignored. Correct deployment of dependencies if this is an error.");
}
catch (TypeLoadException ex)
{
if (ex.TypeName == "NUnit.Framework.Api.FrameworkController")
TestLog.SendWarningMessage(" Skipping NUnit 2.x test assembly");
TestLog.Warning(" Skipping NUnit 2.x test assembly");
else
TestLog.SendErrorMessage("Exception thrown discovering tests in " + sourceAssembly, ex);
TestLog.Error("Exception thrown discovering tests in " + sourceAssembly, ex);
}
catch (Exception ex)
{
TestLog.SendErrorMessage("Exception thrown discovering tests in " + sourceAssembly, ex);
TestLog.Error("Exception thrown discovering tests in " + sourceAssembly, ex);
}
finally
{
Expand All @@ -109,7 +109,8 @@ public void DiscoverTests(IEnumerable<string> sources, IDiscoveryContext discove
}
}

Info("discovering test", "finished");
TestLog.Info(string.Format("NUnit Adapter {0}: Test discovery complete", AdapterVersion));

Unload();
}

Expand All @@ -135,7 +136,7 @@ private int ProcessTestCases(XmlNode topNode, ITestCaseDiscoverySink discoverySi
}
catch (Exception ex)
{
TestLog.SendErrorMessage("Exception converting " + testNode.GetAttribute("fullname"), ex);
TestLog.Error("Exception converting " + testNode.GetAttribute("fullname"), ex);
}
}

Expand Down
134 changes: 70 additions & 64 deletions src/NUnitTestAdapter/NUnit3TestExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,25 @@ namespace NUnit.VisualStudio.TestAdapter
[ExtensionUri(ExecutorUri)]
public sealed class NUnit3TestExecutor : NUnitTestAdapter, ITestExecutor, IDisposable
{
// TFS Filter in effect - may be empty
private TfsTestFilter _tfsFilter;

// Fields related to the currently executing assembly
private ITestRunner _testRunner;
private TestFilter _nunitFilter = TestFilter.Empty;
private ITestRunner _activeRunner;

#region Properties

// Properties set when either of the RunTests methods is called
public IRunContext RunContext { get; private set; }
public IFrameworkHandle FrameworkHandle { get; private set; }
private TfsTestFilter TfsFilter { get; set; }

// NOTE: an earlier version of this code had a FilterBuilder
// property. This seemed to make sense, because we instantiate
// it in two different places. However, the existence of an
// NUnitTestFilterBuilder, containing a reference to an engine
// service caused our second-level tests of the test executor
// to throw an exception. So if you consider doing this, beware!

#endregion

#region ITestExecutor Implementation

Expand All @@ -50,35 +63,26 @@ public void RunTests(IEnumerable<string> sources, IRunContext runContext, IFrame

try
{
_tfsFilter = new TfsTestFilter(runContext);
TestLog.SendDebugMessage("Keepalive:" + runContext.KeepAlive);
var enableShutdown = (Settings.UseVsKeepEngineRunning) ? !runContext.KeepAlive : true;
if (!_tfsFilter.HasTfsFilterValue)
{
if (!(enableShutdown && !runContext.KeepAlive)) // Otherwise causes exception when run as commandline, illegal to enableshutdown when Keepalive is false, might be only VS2012
frameworkHandle.EnableShutdownAfterTestRun = enableShutdown;
}

foreach (var source in sources)
{
var assemblyName = source;
if (!Path.IsPathRooted(assemblyName))
assemblyName = Path.Combine(Environment.CurrentDirectory, assemblyName);

TestLog.SendInformationalMessage("Running all tests in " + assemblyName);
TestLog.Info("Running all tests in " + assemblyName);

RunAssembly(assemblyName, frameworkHandle);
RunAssembly(assemblyName, TestFilter.Empty);
}
}
catch (Exception ex)
{
if (ex is TargetInvocationException)
ex = ex.InnerException;
TestLog.SendErrorMessage("Exception thrown executing tests", ex);
TestLog.Error("Exception thrown executing tests", ex);
}
finally
{
Info("executing tests", "finished");
TestLog.Info(string.Format("NUnit Adapter {0}: Test execution complete", AdapterVersion));
Unload();
}

Expand All @@ -98,32 +102,29 @@ public void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrame
#endif
Initialize(runContext, frameworkHandle);

var enableShutdown = (Settings.UseVsKeepEngineRunning) ? !runContext.KeepAlive : true;
frameworkHandle.EnableShutdownAfterTestRun = enableShutdown;
Debug("executing tests", "EnableShutdown set to " + enableShutdown);

var assemblyGroups = tests.GroupBy(tc => tc.Source);
foreach (var assemblyGroup in assemblyGroups)
{
var assemblyName = assemblyGroup.Key;
if (Debugger.IsAttached)
TestLog.SendInformationalMessage("Debugging selected tests in " + assemblyName);
TestLog.Info("Debugging selected tests in " + assemblyName);
else
TestLog.SendInformationalMessage("Running selected tests in " + assemblyName);
TestLog.Info("Running selected tests in " + assemblyName);

_nunitFilter = MakeTestFilter(assemblyGroup);
var filterBuilder = new NUnitTestFilterBuilder(TestEngine.Services.GetService<ITestFilterService>());
var filter = filterBuilder.MakeTestFilter(assemblyGroup);

RunAssembly(assemblyName, frameworkHandle);
RunAssembly(assemblyName, filter);
}

Info("executing tests", "finished");
TestLog.Info(string.Format("NUnit Adapter {0}: Test execution complete", AdapterVersion));
Unload();
}

void ITestExecutor.Cancel()
{
if (_testRunner != null)
_testRunner.StopRun(true);
if (_activeRunner != null)
_activeRunner.StopRun(true);
}

#endregion
Expand All @@ -139,37 +140,55 @@ public void Dispose()

#region Helper Methods

// The TestExecutor is constructed using the default constructor.
// We don't have any info to initialize it until one of the
// ITestExecutor methods is called.
protected override void Initialize(IDiscoveryContext context, IMessageLogger messageLogger)
public void Initialize(IRunContext runContext, IFrameworkHandle frameworkHandle)
{
base.Initialize(context, messageLogger);
base.Initialize(runContext, frameworkHandle);

Info("executing tests", "started");
TestLog.Info(string.Format("NUnit Adapter {0}: Test execution started", AdapterVersion));

RunContext = runContext;
FrameworkHandle = frameworkHandle;
TfsFilter = new TfsTestFilter(runContext);

// Ensure any channels registered by other adapters are unregistered
CleanUpRegisteredChannels();

TestLog.Debug("Keepalive: " + runContext.KeepAlive);
TestLog.Debug("UseVsKeepEngineRunning: " + Settings.UseVsKeepEngineRunning);

bool enableShutdown = true;
if (Settings.UseVsKeepEngineRunning )
{
enableShutdown = !runContext.KeepAlive;
}

if (TfsFilter.IsEmpty)
{
if (!(enableShutdown && !runContext.KeepAlive)) // Otherwise causes exception when run as commandline, illegal to enableshutdown when Keepalive is false, might be only VS2012
frameworkHandle.EnableShutdownAfterTestRun = enableShutdown;
}

TestLog.Debug("EnableShutdown: " + enableShutdown.ToString());
}

private void RunAssembly(string assemblyName, IFrameworkHandle frameworkHandle)
private void RunAssembly(string assemblyName, TestFilter filter)
{
#if LAUNCHDEBUGGER
if (!Debugger.IsAttached)
Debugger.Launch();
#endif
_testRunner = GetRunnerFor(assemblyName);
_activeRunner = GetRunnerFor(assemblyName);

try
{
var loadResult = _testRunner.Explore(TestFilter.Empty);
var loadResult = _activeRunner.Explore(TestFilter.Empty);

if (loadResult.Name == "test-run")
loadResult = loadResult.FirstChild;

if (loadResult.GetAttribute("runstate") == "Runnable")
{
TestLog.SendInformationalMessage(string.Format("Loading tests from {0}", assemblyName));
TestLog.Info(string.Format("Loading tests from {0}", assemblyName));

var nunitTestCases = loadResult.SelectNodes("//test-case");

Expand All @@ -184,62 +203,49 @@ private void RunAssembly(string assemblyName, IFrameworkHandle frameworkHandle)
loadedTestCases.Add(testConverter.ConvertTestCase(testNode));

// If we have a TFS Filter, convert it to an nunit filter
if (_tfsFilter != null && _tfsFilter.HasTfsFilterValue)
if (TfsFilter != null && !TfsFilter.IsEmpty)
{
var filteredTestCases = _tfsFilter.CheckFilter(loadedTestCases);
var testCases = filteredTestCases as TestCase[] ?? filteredTestCases.ToArray();
TestLog.SendInformationalMessage(string.Format("TFS Filter detected: LoadedTestCases {0}, Filterered Test Cases {1}", loadedTestCases.Count, testCases.Count()));
_nunitFilter = MakeTestFilter(testCases);
// NOTE This overwrites filter used in call
var filterBuilder = new NUnitTestFilterBuilder(TestEngine.Services.GetService<ITestFilterService>());
filter = filterBuilder.ConvertTfsFilterToNUnitFilter(TfsFilter, loadedTestCases);
}

using (var listener = new NUnitEventListener(frameworkHandle, testConverter))
using (var listener = new NUnitEventListener(FrameworkHandle, testConverter))
{
try
{
_testRunner.Run(listener, _nunitFilter);
_activeRunner.Run(listener, filter);
}
catch (NullReferenceException)
{
// this happens during the run when CancelRun is called.
TestLog.SendDebugMessage("Nullref caught");
TestLog.Debug("Nullref caught");
}
}
}
}
else
TestLog.NUnitLoadError(assemblyName);
TestLog.Info("NUnit failed to load " + assemblyName);
}
catch (BadImageFormatException)
{
// we skip the native c++ binaries that we don't support.
TestLog.AssemblyNotSupportedWarning(assemblyName);
TestLog.Warning("Assembly not supported: " + assemblyName);
}
catch (System.IO.FileNotFoundException ex)
{
// Probably from the GetExportedTypes in NUnit.core, attempting to find an assembly, not a problem if it is not NUnit here
TestLog.DependentAssemblyNotFoundWarning(ex.FileName, assemblyName);
TestLog.Warning("Dependent Assembly " + ex.FileName + " of " + assemblyName + " not found. Can be ignored if not a NUnit project.");
}
catch (Exception ex)
{
if (ex is TargetInvocationException)
ex = ex.InnerException;
TestLog.SendErrorMessage("Exception thrown executing tests in " + assemblyName, ex);
TestLog.Error("Exception thrown executing tests in " + assemblyName, ex);
}
_testRunner.Dispose();
}

private TestFilter MakeTestFilter(IEnumerable<TestCase> testCases)
{
ITestFilterService filterService = TestEngine.Services.GetService<ITestFilterService>();
if (filterService == null)
throw new NUnitEngineException("TestFilterService is not available. Engine in use is incorrect version.");

ITestFilterBuilder filterBuilder = filterService.GetTestFilterBuilder();

foreach (TestCase testCase in testCases)
filterBuilder.AddTest(testCase.FullyQualifiedName);

return filterBuilder.GetFilter();
_activeRunner.Dispose();
_activeRunner = null;
}

#endregion
Expand Down
Loading