diff --git a/src/NUnitTestAdapter/AdapterSettings.cs b/src/NUnitTestAdapter/AdapterSettings.cs index c5da0060..ddfc2971 100644 --- a/src/NUnitTestAdapter/AdapterSettings.cs +++ b/src/NUnitTestAdapter/AdapterSettings.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2014-2017 Charlie Poole, Terje Sandstrom +// Copyright (c) 2014-2019 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -62,6 +62,23 @@ public interface IAdapterSettings string DefaultTestNamePattern { get; } VsTestCategoryType VsTestCategoryType { get; } + string TestOutputXml { get; } + bool UseTestOutputXml { get; } + + /// + /// True if test run is triggered in an IDE/Editor context. + /// + bool DesignMode { get; } + + /// + /// If true, an adapter shouldn't create appdomains to run tests + /// + bool DisableAppDomain { get; } + + /// + /// If true, an adapter should disable any test case parallelization + /// + bool DisableParallelization { get; } void Load(IDiscoveryContext context); void Load(string settingsXml); @@ -135,7 +152,8 @@ public AdapterSettings(TestLogger logger) public string InternalTraceLevel { get; private set; } public string WorkDirectory { get; private set; } - + public string TestOutputXml { get; private set; } + public bool UseTestOutputXml => !string.IsNullOrEmpty(TestOutputXml); public int DefaultTimeout { get; private set; } public int NumberOfTestWorkers { get; private set; } @@ -182,7 +200,7 @@ public void Load(IDiscoveryContext context) if (context == null) throw new ArgumentNullException(nameof(context), "Load called with null context"); - Load(context?.RunSettings?.SettingsXml); + Load(context.RunSettings?.SettingsXml); } public void Load(string settingsXml) @@ -209,7 +227,8 @@ public void Load(string settingsXml) DisableAppDomain = GetInnerTextAsBool(runConfiguration, nameof(DisableAppDomain), false); DisableParallelization = GetInnerTextAsBool(runConfiguration, nameof(DisableParallelization), false); DesignMode = GetInnerTextAsBool(runConfiguration, nameof(DesignMode), false); - CollectDataForEachTestSeparately = GetInnerTextAsBool(runConfiguration, nameof(CollectDataForEachTestSeparately), false); + CollectDataForEachTestSeparately = + GetInnerTextAsBool(runConfiguration, nameof(CollectDataForEachTestSeparately), false); TestProperties = new Dictionary(); foreach (XmlNode node in doc.SelectNodes("RunSettings/TestRunParameters/Parameter")) @@ -221,7 +240,8 @@ public void Load(string settingsXml) } // NUnit settings - InternalTraceLevel = GetInnerTextWithLog(nunitNode, nameof(InternalTraceLevel), "Off", "Error", "Warning", "Info", "Verbose", "Debug"); + InternalTraceLevel = GetInnerTextWithLog(nunitNode, nameof(InternalTraceLevel), "Off", "Error", "Warning", + "Info", "Verbose", "Debug"); WorkDirectory = GetInnerTextWithLog(nunitNode, nameof(WorkDirectory)); DefaultTimeout = GetInnerTextAsInt(nunitNode, nameof(DefaultTimeout), 0); NumberOfTestWorkers = GetInnerTextAsInt(nunitNode, nameof(NumberOfTestWorkers), -1); @@ -229,6 +249,9 @@ public void Load(string settingsXml) UseVsKeepEngineRunning = GetInnerTextAsBool(nunitNode, nameof(UseVsKeepEngineRunning), false); BasePath = GetInnerTextWithLog(nunitNode, nameof(BasePath)); PrivateBinPath = GetInnerTextWithLog(nunitNode, nameof(PrivateBinPath)); + var testOutput = GetInnerTextWithLog(nunitNode, nameof(TestOutputXml)); + if (!string.IsNullOrEmpty(testOutput)) + TestOutputXml = ValidatedPath(testOutput, nameof(TestOutputXml)); RandomSeed = GetInnerTextAsNullableInt(nunitNode, nameof(RandomSeed)); RandomSeedSpecified = RandomSeed.HasValue; if (!RandomSeedSpecified) @@ -248,7 +271,8 @@ public void Load(string settingsXml) VsTestCategoryType = VsTestCategoryType.MsTest; break; default: - _logger.Warning($"Invalid value ({vsTestCategoryType}) for VsTestCategoryType, should be either NUnit or MsTest"); + _logger.Warning( + $"Invalid value ({vsTestCategoryType}) for VsTestCategoryType, should be either NUnit or MsTest"); break; } @@ -270,13 +294,17 @@ public void Load(string settingsXml) Verbosity = 1; #endif - var inProcDataCollectorNode = doc.SelectSingleNode("RunSettings/InProcDataCollectionRunSettings/InProcDataCollectors"); - InProcDataCollectorsAvailable = inProcDataCollectorNode != null && inProcDataCollectorNode.SelectNodes("InProcDataCollector").Count > 0; + var inProcDataCollectorNode = + doc.SelectSingleNode("RunSettings/InProcDataCollectionRunSettings/InProcDataCollectors"); + InProcDataCollectorsAvailable = inProcDataCollectorNode != null && + inProcDataCollectorNode.SelectNodes("InProcDataCollector").Count > 0; // Older versions of VS do not pass the CollectDataForEachTestSeparately configuration together with the LiveUnitTesting collector. // However, the adapter is expected to run in CollectDataForEachTestSeparately mode. // As a result for backwards compatibility reasons enable CollectDataForEachTestSeparately mode whenever LiveUnitTesting collector is being used. - var hasLiveUnitTestingDataCollector = inProcDataCollectorNode?.SelectSingleNode("InProcDataCollector[@uri='InProcDataCollector://Microsoft/LiveUnitTesting/1.0']") != null; + var hasLiveUnitTestingDataCollector = + inProcDataCollectorNode?.SelectSingleNode( + "InProcDataCollector[@uri='InProcDataCollector://Microsoft/LiveUnitTesting/1.0']") != null; // TestPlatform can opt-in to run tests one at a time so that the InProcDataCollectors can collect the data for each one of them separately. // In that case, we need to ensure that tests do not run in parallel and the test started/test ended events are sent synchronously. @@ -288,7 +316,8 @@ public void Load(string settingsXml) { if (!InProcDataCollectorsAvailable) { - _logger.Info("CollectDataForEachTestSeparately is set, which is used to make InProcDataCollectors collect data for each test separately. No InProcDataCollectors can be found, thus the tests will run slower unnecessarily."); + _logger.Info( + "CollectDataForEachTestSeparately is set, which is used to make InProcDataCollectors collect data for each test separately. No InProcDataCollectors can be found, thus the tests will run slower unnecessarily."); } } } @@ -301,6 +330,24 @@ public void Load(string settingsXml) // Update NumberOfTestWorkers based on the DisableParallelization and NumberOfTestWorkers from runsettings. UpdateNumberOfTestWorkers(); + + + string ValidatedPath(string path, string purpose) + { + try + { + if (string.IsNullOrEmpty(WorkDirectory)) + return Path.GetFullPath(path); + if (Path.IsPathRooted(path)) + return Path.GetFullPath(path); + return Path.GetFullPath(Path.Combine(WorkDirectory, path)); + } + catch (Exception) + { + _logger.Error($" Invalid path for {purpose}: {path}"); + throw; + } + } } public void SaveRandomSeed(string dirname) diff --git a/src/NUnitTestAdapter/NUnit3TestExecutor.cs b/src/NUnitTestAdapter/NUnit3TestExecutor.cs index a031b538..0c3a2059 100644 --- a/src/NUnitTestAdapter/NUnit3TestExecutor.cs +++ b/src/NUnitTestAdapter/NUnit3TestExecutor.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2018 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2019 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -192,7 +192,7 @@ public void Initialize(IRunContext runContext, IFrameworkHandle frameworkHandle) TestLog.Debug("UseVsKeepEngineRunning: " + Settings.UseVsKeepEngineRunning); bool enableShutdown = true; - if (Settings.UseVsKeepEngineRunning ) + if (Settings.UseVsKeepEngineRunning) { enableShutdown = !runContext.KeepAlive; } @@ -220,7 +220,7 @@ private void RunAssembly(string assemblyPath, TestFilter filter) // No need to restore if the seed was in runsettings file if (!Settings.RandomSeedSpecified) Settings.RestoreRandomSeed(Path.GetDirectoryName(assemblyPath)); - DumpXml dumpXml=null; + DumpXml dumpXml = null; if (Settings.DumpXmlTestResults) { dumpXml = new Dump.DumpXml(assemblyPath); @@ -273,7 +273,8 @@ private void RunAssembly(string assemblyPath, TestFilter filter) { try { - _activeRunner.Run(listener, filter); + var results = _activeRunner.Run(listener, filter); + GenerateTestOutput(results, assemblyPath); } catch (NullReferenceException) { @@ -329,6 +330,23 @@ private void RunAssembly(string assemblyPath, TestFilter filter) } } + + private void GenerateTestOutput(XmlNode testResults, string assemblyPath) + { + if (!Settings.UseTestOutputXml) + return; +#if NETCOREAPP1_0 +#else + var path = Path.Combine(Settings.TestOutputXml, Path.ChangeExtension(Path.GetFileName(assemblyPath),".xml")); + var resultService = TestEngine.Services.GetService(); + // Following null argument should work for nunit3 format. Empty array is OK as well. + // If you decide to handle other formats in the runsettings, it needs more work. + var resultWriter = resultService.GetResultWriter("nunit3", null); + resultWriter.WriteResultFile(testResults, path); + TestLog.Info($" Test results written to {path}"); +#endif + } + private NUnitTestFilterBuilder CreateTestFilterBuilder() { #if NETCOREAPP1_0 diff --git a/src/NUnitTestAdapter/NUnitTestAdapter.cs b/src/NUnitTestAdapter/NUnitTestAdapter.cs index 0c06471a..07a2fb4e 100644 --- a/src/NUnitTestAdapter/NUnitTestAdapter.cs +++ b/src/NUnitTestAdapter/NUnitTestAdapter.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2015 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2019 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -125,11 +125,11 @@ protected void Initialize(IDiscoveryContext context, IMessageLogger messageLogge TestLog = new TestLogger(messageLogger); Settings = new AdapterSettings(TestLog); TestLog.InitSettings(Settings); - try { Settings.Load(context); TestLog.Verbosity = Settings.Verbosity; + CheckDirectories(); } catch (Exception e) { @@ -138,6 +138,15 @@ protected void Initialize(IDiscoveryContext context, IMessageLogger messageLogge } } + private void CheckDirectories() + { + if (Settings.UseTestOutputXml) + { + Directory.CreateDirectory(Settings.TestOutputXml); + TestLog.Info($" Test Output folder checked/created : {Settings.TestOutputXml} "); + } + } + protected ITestRunner GetRunnerFor(string assemblyName) { var package = CreateTestPackage(assemblyName); diff --git a/src/NUnitTestAdapter/TestConverter.cs b/src/NUnitTestAdapter/TestConverter.cs index d2849c7a..d5ae9301 100644 --- a/src/NUnitTestAdapter/TestConverter.cs +++ b/src/NUnitTestAdapter/TestConverter.cs @@ -247,7 +247,7 @@ private static void FillResultFromOutputNodes(IEnumerable outputNodes, continue; } - // Add stdErr/Progress messages from TestOutput element to vstest result + // Add stdErr/Progress messages from TestOutputXml element to vstest result vsResult.Messages.Add(new TestResultMessage( "error".Equals(stream, StringComparison.OrdinalIgnoreCase) ? TestResultMessage.StandardErrorCategory diff --git a/src/NUnitTestAdapterTests/AdapterSettingsTests.cs b/src/NUnitTestAdapterTests/AdapterSettingsTests.cs index 581af870..11dba80b 100644 --- a/src/NUnitTestAdapterTests/AdapterSettingsTests.cs +++ b/src/NUnitTestAdapterTests/AdapterSettingsTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2017 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2019 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -21,6 +21,7 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // *********************************************************************** +using System.IO; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using NUnit.Framework; using NUnit.VisualStudio.TestAdapter.Tests.Fakes; @@ -29,14 +30,14 @@ namespace NUnit.VisualStudio.TestAdapter.Tests { public class AdapterSettingsTests { - private AdapterSettings _settings; + private IAdapterSettings _settings; [SetUp] public void SetUp() { - var testlogger = new TestLogger(new MessageLoggerStub()); - _settings = new AdapterSettings(testlogger); - testlogger.InitSettings(_settings); + var testLogger = new TestLogger(new MessageLoggerStub()); + _settings = new AdapterSettings(testLogger); + testLogger.InitSettings(_settings); } [Test] @@ -51,29 +52,33 @@ public void NullContextThrowsException() public void DefaultSettings(string xml) { _settings.Load(xml); - Assert.That(_settings.MaxCpuCount, Is.EqualTo(-1)); - Assert.Null(_settings.ResultsDirectory); - Assert.Null(_settings.TargetFrameworkVersion); - Assert.Null(_settings.TargetPlatform); - Assert.Null(_settings.TestAdapterPaths); - Assert.IsTrue(_settings.CollectSourceInformation); - Assert.IsEmpty(_settings.TestProperties); - Assert.Null(_settings.InternalTraceLevel); - Assert.Null(_settings.WorkDirectory); - Assert.That(_settings.NumberOfTestWorkers, Is.EqualTo(-1)); - Assert.That(_settings.DefaultTimeout, Is.EqualTo(0)); - Assert.That(_settings.Verbosity, Is.EqualTo(0)); - Assert.False(_settings.ShadowCopyFiles); - Assert.False(_settings.UseVsKeepEngineRunning); - Assert.Null(_settings.BasePath); - Assert.Null(_settings.PrivateBinPath); - Assert.NotNull(_settings.RandomSeed); - Assert.False(_settings.SynchronousEvents); - Assert.Null(_settings.DomainUsage); - Assert.False(_settings.InProcDataCollectorsAvailable); - Assert.IsFalse(_settings.DisableAppDomain); - Assert.IsFalse(_settings.DisableParallelization); - Assert.IsFalse(_settings.DesignMode); + Assert.Multiple(() => + { + Assert.That(_settings.MaxCpuCount, Is.EqualTo(-1)); + Assert.Null(_settings.ResultsDirectory); + Assert.Null(_settings.TargetFrameworkVersion); + Assert.Null(_settings.TargetPlatform); + Assert.Null(_settings.TestAdapterPaths); + Assert.IsTrue(_settings.CollectSourceInformation); + Assert.IsEmpty(_settings.TestProperties); + Assert.Null(_settings.InternalTraceLevel); + Assert.Null(_settings.WorkDirectory); + Assert.That(_settings.NumberOfTestWorkers, Is.EqualTo(-1)); + Assert.That(_settings.DefaultTimeout, Is.EqualTo(0)); + Assert.That(_settings.Verbosity, Is.EqualTo(0)); + Assert.False(_settings.ShadowCopyFiles); + Assert.False(_settings.UseVsKeepEngineRunning); + Assert.Null(_settings.BasePath); + Assert.Null(_settings.PrivateBinPath); + Assert.NotNull(_settings.RandomSeed); + Assert.False(_settings.SynchronousEvents); + Assert.Null(_settings.DomainUsage); + Assert.False(_settings.InProcDataCollectorsAvailable); + Assert.IsFalse(_settings.DisableAppDomain); + Assert.IsFalse(_settings.DisableParallelization); + Assert.IsFalse(_settings.DesignMode); + Assert.False(_settings.UseTestOutputXml); + }); } [Test] @@ -179,6 +184,40 @@ public void WorkDirectorySetting() Assert.That(_settings.WorkDirectory, Is.EqualTo("/my/work/dir")); } + /// + /// Workdir not set, TestOutput is relative + /// + [Test] + public void TestOutputSetting() + { + _settings.Load("/my/work/dir"); + Assert.That(_settings.UseTestOutputXml); + Assert.Multiple(() => + { + Assert.That(_settings.TestOutputXml, Does.Contain(@"\my\work\dir")); + Assert.That(Path.IsPathRooted(_settings.TestOutputXml), Is.True); + }); + + } + + /// + /// Workdir set, and is absolute, TestOutputXml is relative + /// + [Test] + public void TestOutputSettingWithWorkDir() + { + _settings.Load(@"C:\Whatever/my/testoutput/dir"); + Assert.That(_settings.UseTestOutputXml); + Assert.Multiple(() => + { + Assert.That(_settings.TestOutputXml, Does.Contain(@"\my\testoutput\dir")); + Assert.That(_settings, Does.StartWith(@"C:\")); + Assert.That(Path.IsPathRooted(_settings.TestOutputXml), Is.True); + }); + + } + + [Test] public void NumberOfTestWorkersSetting() { @@ -255,7 +294,7 @@ public void RandomSeedSetting() public void DefaultTestNamePattern() { _settings.Load("{m}{a:1000}"); - Assert.That(_settings.DefaultTestNamePattern,Is.EqualTo("{m}{a:1000}")); + Assert.That(_settings.DefaultTestNamePattern, Is.EqualTo("{m}{a:1000}")); } [Test] diff --git a/src/NUnitTestAdapterTests/TestDiscoveryTests.cs b/src/NUnitTestAdapterTests/TestDiscoveryTests.cs index 56a108c7..13c3b776 100644 --- a/src/NUnitTestAdapterTests/TestDiscoveryTests.cs +++ b/src/NUnitTestAdapterTests/TestDiscoveryTests.cs @@ -51,11 +51,11 @@ public class TestDiscoveryTests : ITestCaseDiscoverySink static readonly string MockAssemblyPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "mock-assembly.dll"); - List TestCases; + List testCases; - private static ITestDiscoverer nunittestDiscoverer; + private static readonly ITestDiscoverer nunittestDiscoverer; - private IDiscoveryContext _context; + private readonly IDiscoveryContext _context; public TestDiscoveryTests(IDiscoveryContext context) { @@ -69,7 +69,7 @@ public void LoadMockassembly() Assert.That(NUnit.Tests.Assemblies.MockAssembly.TestsAtRuntime, Is.EqualTo(NUnit.Tests.Assemblies.MockAssembly.Tests), "The reference to mock-assembly.dll appears to be the wrong version"); Assert.That(File.Exists(MockAssemblyPath), $"Can't locate mock-assembly.dll at {MockAssemblyPath}"); - TestCases = new List(); + testCases = new List(); // Load the NUnit mock-assembly.dll once for this test, saving // the list of test cases sent to the discovery sink @@ -83,7 +83,7 @@ public void LoadMockassembly() [Test] public void VerifyTestCaseCount() { - Assert.That(TestCases.Count, Is.EqualTo(NUnit.Tests.Assemblies.MockAssembly.Tests)); + Assert.That(testCases.Count, Is.EqualTo(NUnit.Tests.Assemblies.MockAssembly.Tests)); } [TestCase("MockTest3", "NUnit.Tests.Assemblies.MockTestFixture.MockTest3")] @@ -92,7 +92,7 @@ public void VerifyTestCaseCount() [TestCase("MethodWithParameters(9,11)", "NUnit.Tests.FixtureWithTestCases.MethodWithParameters(9,11)")] public void VerifyTestCaseIsFound(string name, string fullName) { - var testCase = TestCases.Find(tc => tc.DisplayName == name); + var testCase = testCases.Find(tc => tc.DisplayName == name); Assert.That(testCase.FullyQualifiedName, Is.EqualTo(fullName)); } @@ -102,7 +102,7 @@ public void VerifyTestCaseIsFound(string name, string fullName) [TestCase("NestedClassTest3")] // grandchild public void VerifyNestedTestCaseSourceIsAvailable(string name) { - var testCase = TestCases.Find(tc => tc.DisplayName == name); + var testCase = testCases.Find(tc => tc.DisplayName == name); Assert.That(!string.IsNullOrEmpty(testCase.Source)); Assert.Greater(testCase.LineNumber, 0); @@ -112,7 +112,7 @@ public void VerifyNestedTestCaseSourceIsAvailable(string name) void ITestCaseDiscoverySink.SendTestCase(TestCase discoveredTest) { - TestCases.Add(discoveredTest); + testCases.Add(discoveredTest); } #endregion