From 1f9eb7edcdb51bb8ca8d1e8be811edeaaae030d5 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 28 Jun 2022 21:09:10 -0700 Subject: [PATCH 001/190] Recognize .NET 7 when installed --- global.json | 2 +- src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/global.json b/global.json index 69d60238d..8a46e9ab2 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "6.0.101", - "rollForward": "patch" + "rollForward": "feature" } } \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs index 33fc7116b..ce3a7f35b 100644 --- a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs +++ b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs @@ -201,6 +201,8 @@ private Version GetClrVersionForFramework(Version frameworkVersion) return new Version(5, 0, 1); case 6: return new Version(6, 0, 0); + case 7: + return new Version(7, 0, 0); } break; } From 9b1284d65783a59cd3f34d254163f00602c3d9a4 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 30 Jun 2022 06:50:55 -0700 Subject: [PATCH 002/190] Update global.json --- NUnitConsole.sln | 5 +++-- global.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 375b8a565..af5116a20 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29728.190 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{49D441DF-39FD-4F4D-AECA-86CF8EFE23AF}" ProjectSection(SolutionItems) = preProject @@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution src\Directory.Build.props = src\Directory.Build.props GitReleaseManager.yaml = GitReleaseManager.yaml GitVersion.yml = GitVersion.yml + global.json = global.json header-check.cake = header-check.cake LICENSE.txt = LICENSE.txt NetFXTests.nunit = NetFXTests.nunit diff --git a/global.json b/global.json index 69d60238d..8a46e9ab2 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "6.0.101", - "rollForward": "patch" + "rollForward": "feature" } } \ No newline at end of file From 7dc0f0375a2d28f9086200378aa5f09cb906c198 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 30 Jun 2022 08:14:39 -0700 Subject: [PATCH 003/190] Create dev release when merging into version3 branch --- cake/versioning.cake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cake/versioning.cake b/cake/versioning.cake index 12f0c9813..aded97410 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -77,6 +77,10 @@ public class BuildVersion string branchName = _gitVersion.BranchName; + // Treat version3 branch as an alternate "main" + if (branchName == "version3") + label = "dev"; + // We don't currently use this pattern, but check in case we do later. if (branchName.StartsWith("feature/")) branchName = branchName.Substring(8); From e86866896179549b41ceef0e9a9de920051e2d2e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 30 Jun 2022 13:05:32 -0700 Subject: [PATCH 004/190] Temporarily suppress publication step --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 96350d17b..49ec5e7d8 100644 --- a/build.cake +++ b/build.cake @@ -770,7 +770,7 @@ Task("BuildTestAndPackage") Task("Appveyor") .Description("Target we run in our AppVeyor CI") .IsDependentOn("BuildTestAndPackage") - .IsDependentOn("PublishPackages") + //.IsDependentOn("PublishPackages") .IsDependentOn("CreateDraftRelease") .IsDependentOn("CreateProductionRelease"); From 92b88e9b1090e4338e72860a767cf31dab8fc15f Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Mon, 27 Feb 2023 20:13:15 +0100 Subject: [PATCH 005/190] Fix for the adapter issues 1065 and 1066 --- GitVersion.yml | 2 +- src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/GitVersion.yml b/GitVersion.yml index af1579785..c80be9f5d 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 3.14.0 +next-version: 3.15.3 mode: ContinuousDelivery legacy-semver-padding: 5 build-metadata-padding: 5 diff --git a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs index ce3a7f35b..c04ed5766 100644 --- a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs +++ b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs @@ -203,8 +203,11 @@ private Version GetClrVersionForFramework(Version frameworkVersion) return new Version(6, 0, 0); case 7: return new Version(7, 0, 0); + case 8: + return new Version(8, 0, 0); + default: + return new Version(frameworkVersion.Major, 0, 0); } - break; } throw new ArgumentException("Unknown framework version " + frameworkVersion.ToString(), "version"); From b6b077ff68e3d14aeaf143845f9b1158758396c4 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Mon, 24 Apr 2023 11:07:20 +0200 Subject: [PATCH 006/190] Version 3.15.4. Enable NUnit 4. --- GitVersion.yml | 2 +- .../nunit.engine.core/Drivers/NUnit3DriverFactory.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GitVersion.yml b/GitVersion.yml index c80be9f5d..d574127ad 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 3.15.3 +next-version: 3.15.4 mode: ContinuousDelivery legacy-semver-padding: 5 build-metadata-padding: 5 diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs index a7dcc7a2d..c51ef78ef 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs @@ -20,7 +20,7 @@ public class NUnit3DriverFactory : IDriverFactory /// An AssemblyName referring to the possible test framework. public bool IsSupportedTestFramework(AssemblyName reference) { - return NUNIT_FRAMEWORK.Equals(reference.Name, StringComparison.OrdinalIgnoreCase) && reference.Version.Major == 3; + return NUNIT_FRAMEWORK.Equals(reference.Name, StringComparison.OrdinalIgnoreCase) && reference.Version.Major >= 3; } #if NETFRAMEWORK From 7f65d94f535257dc42e09b507e1559b3217a5caa Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Sat, 27 May 2023 12:43:44 +0200 Subject: [PATCH 007/190] added version315 as a kind of master branch --- cake/versioning.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cake/versioning.cake b/cake/versioning.cake index aded97410..d32d65f99 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -78,7 +78,7 @@ public class BuildVersion string branchName = _gitVersion.BranchName; // Treat version3 branch as an alternate "main" - if (branchName == "version3") + if (branchName == "version3" || branchName == "version315") label = "dev"; // We don't currently use this pattern, but check in case we do later. From 30149b8349c5fa70e8cad185173608ef672c9290 Mon Sep 17 00:00:00 2001 From: Andrew Armstrong Date: Mon, 22 May 2023 22:59:04 +1000 Subject: [PATCH 008/190] Detect the 'partition' token during filter element parsing This adds support for a new PartitionFilter at https://github.com/nunit/nunit/pull/4392 --- src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs b/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs index ef3fb4cf8..387836edf 100644 --- a/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs +++ b/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs @@ -126,6 +126,7 @@ public string ParseFilterElement() case "name": case "test": case "namespace": + case "partition": Token op = lhs.Text == "id" ? Expect(EQ_OPS) : Expect(REL_OPS); From 4481499226e1d8fb9269bd5af53b4f32bde4aa46 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Tue, 26 Sep 2023 14:36:52 +0200 Subject: [PATCH 009/190] Updated TestCentric.Metadata to version 2.0.0 and changed corresponding namespace references, thus no longer using Mono.Cecil namespace --- .../nunit.engine.core/Drivers/NUnitNetStandardDriver.cs | 2 +- .../nunit.engine.core/Extensibility/ExtensionAssembly.cs | 2 +- src/NUnitEngine/nunit.engine.core/Services/DriverService.cs | 2 +- .../nunit.engine.core/Services/ExtensionManager.cs | 2 +- .../nunit.engine.core/Services/ExtensionService.cs | 2 +- src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj | 2 +- .../nunit.engine/Services/RuntimeFrameworkService.cs | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs index 275ac2aef..7826a1669 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs @@ -7,7 +7,7 @@ using NUnit.Engine.Internal; using System.Reflection; using NUnit.Engine.Extensibility; -using Mono.Cecil; +using TestCentric.Metadata; namespace NUnit.Engine.Drivers { diff --git a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs index ff614bca4..0cf2e3747 100644 --- a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs +++ b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs @@ -2,7 +2,7 @@ using System; using System.IO; -using Mono.Cecil; +using TestCentric.Metadata; using NUnit.Engine.Internal; namespace NUnit.Engine.Extensibility diff --git a/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs b/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs index 8472e6fd2..d7dd37589 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; -using Mono.Cecil; +using TestCentric.Metadata; using NUnit.Common; using NUnit.Engine.Drivers; using NUnit.Engine.Extensibility; diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 70f1546d9..9148f90fd 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Reflection; -using Mono.Cecil; +using TestCentric.Metadata; using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; using NUnit.Engine.Internal.Backports; diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index b4aa6ee62..b35f1dc27 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Reflection; -using Mono.Cecil; +using TestCentric.Metadata; using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; using NUnit.Engine.Internal.Backports; diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index b6316e459..f5d02c59a 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs index bd5cecf99..025721c9b 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.IO; -using Mono.Cecil; +using TestCentric.Metadata; using NUnit.Common; using NUnit.Engine.Internal; #if NET20 @@ -195,7 +195,7 @@ public RuntimeFramework GetBestAvailableFramework(RuntimeFramework target) } /// - /// Use Mono.Cecil to get information about all assemblies and + /// Use TestCentric.Metadata to get information about all assemblies and /// apply it to the package using special internal keywords. /// /// From 153574ffbad0804b75a3ae635e8bf39b541ad06a Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Tue, 26 Sep 2023 15:28:55 +0200 Subject: [PATCH 010/190] Updating packages --- GitVersion.yml | 2 +- .../nunit3-console.tests/nunit3-console.tests.csproj | 2 +- src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj | 2 +- src/NUnitEngine/mock-assembly/mock-assembly.csproj | 2 +- src/NUnitEngine/notest-assembly/notest-assembly.csproj | 2 +- .../nunit.engine.core.tests/nunit.engine.core.tests.csproj | 4 ++-- src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/GitVersion.yml b/GitVersion.yml index d574127ad..8bd0940c1 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 3.15.4 +next-version: 3.15.5 mode: ContinuousDelivery legacy-semver-padding: 5 build-metadata-padding: 5 diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index 3b33d3e17..cf732deba 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj index 9d83868e6..f21146db6 100644 --- a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/NUnitEngine/mock-assembly/mock-assembly.csproj b/src/NUnitEngine/mock-assembly/mock-assembly.csproj index f60671084..7a775e1b3 100644 --- a/src/NUnitEngine/mock-assembly/mock-assembly.csproj +++ b/src/NUnitEngine/mock-assembly/mock-assembly.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/NUnitEngine/notest-assembly/notest-assembly.csproj b/src/NUnitEngine/notest-assembly/notest-assembly.csproj index 8721a24dd..07eeca2ba 100644 --- a/src/NUnitEngine/notest-assembly/notest-assembly.csproj +++ b/src/NUnitEngine/notest-assembly/notest-assembly.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index ed35a1169..0441bbc04 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -17,8 +17,8 @@ - - + + diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index e586bc682..bdb1a9898 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -17,8 +17,8 @@ - - + + From 4129d10e9a68de57a4b7c0731aa0f65b1e83d3ab Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 23 Apr 2023 10:00:49 +0300 Subject: [PATCH 011/190] Optimize WorkItemTracker * Use dictionary keyed by id instead of list for in-process items * Use XmlWriter to write XML * Use XmlReader to read XML * Use custom data holder to capture item order number, name and attributes * Reuse StringBuilder instance when sending notifications --- .../nunit.engine/Runners/WorkItemTracker.cs | 162 ++++++++++++------ 1 file changed, 113 insertions(+), 49 deletions(-) diff --git a/src/NUnitEngine/nunit.engine/Runners/WorkItemTracker.cs b/src/NUnitEngine/nunit.engine/Runners/WorkItemTracker.cs index 69a960861..479e4e12b 100644 --- a/src/NUnitEngine/nunit.engine/Runners/WorkItemTracker.cs +++ b/src/NUnitEngine/nunit.engine/Runners/WorkItemTracker.cs @@ -1,6 +1,9 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +using System; using System.Collections.Generic; +using System.IO; +using System.Text; using System.Threading; using System.Xml; @@ -24,18 +27,60 @@ namespace NUnit.Engine.Runners /// Once the test has been cancelled, it provide notifications to the runner /// so the information may be displayed. /// - internal class WorkItemTracker : ITestEventListener + internal sealed class WorkItemTracker : ITestEventListener { - private List _itemsInProcess = new List(); - private ManualResetEvent _allItemsComplete = new ManualResetEvent(false); - private object _trackerLock = new object(); - + /// + /// Holds data about recorded test that started. + /// + private sealed class InProgressItem : IComparable + { + private readonly int _order; + + public InProgressItem(int order, string name, XmlReader reader) + { + _order = order; + Name = name; + + var attributeCount = reader.AttributeCount; + Properties = new Dictionary(attributeCount); + for (var i = 0; i < attributeCount; i++) + { + reader.MoveToNextAttribute(); + Properties.Add(reader.Name, reader.Value); + } + } + + public string Name { get; } + public Dictionary Properties { get; } + + public int CompareTo(InProgressItem other) + { + // for signaling purposes, return in reverse order + return _order.CompareTo(other._order) * -1; + } + } + + // items are keyed by id + private readonly Dictionary _itemsInProcess = new Dictionary(); + private readonly ManualResetEvent _allItemsComplete = new ManualResetEvent(false); + private readonly object _trackerLock = new object(); + + // incrementing ordering id for work items so we can traverse in correct order + private int _itemOrderNumberCounter = 1; + + // when sending thousands of cancelled notifications, it makes sense to reuse string builder, used inside a lock + private readonly StringBuilder _notificationBuilder = new StringBuilder(); + + // we want to write just the main element without XML declarations + private static readonly XmlWriterSettings XmlWriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment }; + public void Clear() { lock (_trackerLock) { _itemsInProcess.Clear(); _allItemsComplete.Reset(); + _itemOrderNumberCounter = 1; } } @@ -48,66 +93,85 @@ public void SendPendingTestCompletionEvents(ITestEventListener listener) { lock (_trackerLock) { - int count = _itemsInProcess.Count; - // Signal completion of all pending suites, in reverse order - while (count > 0) - listener.OnTestEvent(CreateNotification(_itemsInProcess[--count])); - } - } + var toNotify = new List(_itemsInProcess.Values); + toNotify.Sort(); - private static string CreateNotification(XmlNode startElement) - { - bool isSuite = startElement.Name == "start-suite"; - - XmlNode notification = XmlHelper.CreateTopLevelElement(isSuite ? "test-suite" : "test-case"); - if (isSuite) - notification.AddAttribute("type", startElement.GetAttribute("type")); - notification.AddAttribute("id", startElement.GetAttribute("id")); - notification.AddAttribute("name", startElement.GetAttribute("name")); - notification.AddAttribute("fullname", startElement.GetAttribute("fullname")); - notification.AddAttribute("result", "Failed"); - notification.AddAttribute("label", "Cancelled"); - XmlNode failure = notification.AddElement("failure"); - XmlNode message = failure.AddElementWithCDataSection("message", "Test run cancelled by user"); - return notification.OuterXml; + foreach (var item in toNotify) + listener.OnTestEvent(CreateNotification(item)); + } } - void ITestEventListener.OnTestEvent(string report) + private string CreateNotification(InProgressItem item) { - XmlNode xmlNode = XmlHelper.CreateXmlNode(report); + _notificationBuilder.Length = 0; - lock (_trackerLock) + using (var stringWriter = new StringWriter(_notificationBuilder)) { - switch (xmlNode.Name) + using (var writer = XmlWriter.Create(stringWriter, XmlWriterSettings)) { - case "start-test": - case "start-suite": - _itemsInProcess.Add(xmlNode); - break; - - case "test-case": - case "test-suite": - string id = xmlNode.GetAttribute("id"); - RemoveItem(id); - - if (_itemsInProcess.Count == 0) - _allItemsComplete.Set(); - break; + bool isSuite = item.Name == "start-suite"; + writer.WriteStartElement(isSuite ? "test-suite" : "test-case"); + + if (isSuite) + writer.WriteAttributeString("type", item.Properties["type"]); + + writer.WriteAttributeString("id", item.Properties["id"]); + writer.WriteAttributeString("name", item.Properties["name"]); + writer.WriteAttributeString("fullname", item.Properties["fullname"]); + writer.WriteAttributeString("result", "Failed"); + writer.WriteAttributeString("label", "Cancelled"); + + writer.WriteStartElement("failure"); + writer.WriteStartElement("message"); + writer.WriteCData("Test run cancelled by user"); + writer.WriteEndElement(); + writer.WriteEndElement(); + + writer.WriteEndElement(); } + + return stringWriter.ToString(); } } - private void RemoveItem(string id) + void ITestEventListener.OnTestEvent(string report) { - foreach (XmlNode item in _itemsInProcess) + using (var stringReader = new StringReader(report)) + using (var reader = XmlReader.Create(stringReader)) { - if (item.GetAttribute("id") == id) + // go to starting point + reader.MoveToContent(); + + if (reader.NodeType != XmlNodeType.Element) + throw new InvalidOperationException("Expected to find root element"); + + lock (_trackerLock) { - _itemsInProcess.Remove(item); - return; + var name = reader.Name; + switch (name) + { + case "start-test": + case "start-suite": + var item = new InProgressItem(_itemOrderNumberCounter++, name, reader); + _itemsInProcess.Add(item.Properties["id"], item); + break; + + case "test-case": + case "test-suite": + RemoveItem(reader.GetAttribute("id")); + + if (_itemsInProcess.Count == 0) + _allItemsComplete.Set(); + break; + } } } } + + private void RemoveItem(string id) + { + _itemsInProcess.Remove(id); + } } -} +} \ No newline at end of file From 229f922b3fa8176123886d113c98d5d938f071b4 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 5 Jan 2023 22:04:06 -0800 Subject: [PATCH 012/190] Fix hang when test processes fail to terminate correctly --- .../Transport/Tcp/TcpServerTests.cs | 71 +++++++++++++++++++ .../Communication/Transports/Tcp/TcpServer.cs | 22 +++--- 2 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs diff --git a/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs b/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs new file mode 100644 index 000000000..f63e6c595 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using NUnit.Framework; + +namespace NUnit.Engine.Communication.Transports.Tcp +{ + public class TcpServerTests + { + private TcpServer _server; + private List _serverConnections; + + [SetUp] + public void StartServer() + { + _serverConnections = new List(); + _server = new TcpServer(); + _server.ClientConnected += (c, g) => _serverConnections.Add(c); + _server.Start(); + } + + [TearDown] + public void StopServer() + { + _server.Stop(); + } + + [Test] + public void SingleClientConnection() + { + using (TcpClient client = new TcpClient()) + { + client.Connect(_server.EndPoint); + client.Client.Send(new Guid().ToByteArray()); + + Thread.Sleep(1); // Allow the connection event to run + Assert.That(_serverConnections.Count, Is.EqualTo(1), "Should have received 1 connection event"); + Assert.That(_serverConnections[0].Connected, "Server is not connected to client"); + + Assert.True(client.Connected, "Client is not connected to server"); + } + } + + [Test] + public void MultipleClientConnections() + { + TcpClient[] clients = new[] { new TcpClient(), new TcpClient(), new TcpClient() }; + int num = clients.Length; + + foreach (var client in clients) + { + client.Connect(_server.EndPoint); + client.Client.Send(new Guid().ToByteArray()); + } + + Thread.Sleep(1); // Allow the connection events to run + Assert.That(_serverConnections.Count, Is.EqualTo(num), $"Should have received {num} connection events"); + + for (int i = 0; i < num; i++) + { + Assert.That(_serverConnections[i].Connected, $"Server is not connected to client {i + 1}"); + Assert.True(clients[i].Connected, $"Client {i + 1} is not connected to server"); + } + } + } +} diff --git a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs index 359704296..d003264d6 100644 --- a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs +++ b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs @@ -14,7 +14,7 @@ public class TcpServer private const int GUID_BUFFER_SIZE = 16; - TcpListener _listenerSocket; + TcpListener _tcpListener; Thread _listenerThread; volatile bool _running; @@ -24,17 +24,21 @@ public class TcpServer public TcpServer(int port = 0) { - _listenerSocket = new TcpListener(IPAddress.Loopback, port); + _tcpListener = new TcpListener(IPAddress.Loopback, port); } - public IPEndPoint EndPoint => (IPEndPoint)_listenerSocket.LocalEndpoint; + public IPEndPoint EndPoint => (IPEndPoint)_tcpListener.LocalEndpoint; public void Start() { - _listenerSocket.Start(); + _tcpListener.Start(); _running = true; - _listenerThread = new Thread(WaitForClientConnections); + _listenerThread = new Thread(WaitForClientConnections) + { + Name = "TCPLisstenerThread", + IsBackground = true + }; _listenerThread.Start(); } @@ -43,7 +47,7 @@ public void Stop() try { _running = false; - _listenerSocket.Stop(); + _tcpListener.Stop(); } catch (Exception exception) { @@ -57,7 +61,7 @@ private void WaitForClientConnections() { try { - var clientSocket = _listenerSocket.AcceptSocket(); + var clientSocket = _tcpListener.AcceptSocket(); if (clientSocket.Connected) { // Upon connection, remote agent must immediately send its Id as identification. @@ -74,7 +78,7 @@ private void WaitForClientConnections() // 1. We were trying to stop the socket // 2. The connection was dropped due to some external event // In either case, we stop the socket and wait a while - _listenerSocket.Stop(); + _tcpListener.Stop(); // If we were trying to stop, that's all if (!_running) @@ -84,7 +88,7 @@ private void WaitForClientConnections() Thread.Sleep(500); try { - _listenerSocket.Start(); + _tcpListener.Start(); } catch (Exception exception) { From aa4714db878adfac9645f8dffc179fb1fdada339 Mon Sep 17 00:00:00 2001 From: Valentijn Makkenze Date: Tue, 19 Dec 2023 12:37:01 +0100 Subject: [PATCH 013/190] Also copy changes from version3 branch --- .../nunit.engine.tests/Transport/Tcp/TcpServerTests.cs | 8 +++++--- .../Communication/Transports/Tcp/TcpServer.cs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs b/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs index f63e6c595..45ff3eb53 100644 --- a/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +#if NETFRAMEWORK using System; using System.Collections.Generic; using System.Linq; @@ -46,7 +47,7 @@ public void SingleClientConnection() } } - [Test] + [Test, Platform(Exclude = "Linux")] public void MultipleClientConnections() { TcpClient[] clients = new[] { new TcpClient(), new TcpClient(), new TcpClient() }; @@ -63,9 +64,10 @@ public void MultipleClientConnections() for (int i = 0; i < num; i++) { - Assert.That(_serverConnections[i].Connected, $"Server is not connected to client {i + 1}"); - Assert.True(clients[i].Connected, $"Client {i + 1} is not connected to server"); + Assert.That(_serverConnections[i].Connected, $"Server is not connected to client {i+1}"); + Assert.True(clients[i].Connected, $"Client {i+1} is not connected to server"); } } } } +#endif diff --git a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs index d003264d6..cc72a6635 100644 --- a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs +++ b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs @@ -36,7 +36,7 @@ public void Start() _listenerThread = new Thread(WaitForClientConnections) { - Name = "TCPLisstenerThread", + Name = "TcpListenerTread", IsBackground = true }; _listenerThread.Start(); From 68f4394c660928ab0321b0085f5a1e01a6963e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Le=C3=9Fmann?= Date: Wed, 10 Jan 2024 13:54:50 +0100 Subject: [PATCH 014/190] Add target framework to nunit-agent, disable NuGet audit --- global.json | 2 +- src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj | 1 + src/NUnitEngine/mock-assembly/mock-assembly.csproj | 3 ++- src/NUnitEngine/notest-assembly/notest-assembly.csproj | 1 + src/NUnitEngine/nunit-agent/Program.cs | 6 +++++- src/NUnitEngine/nunit-agent/nunit-agent.csproj | 2 +- .../nunit.engine.core.tests/nunit.engine.core.tests.csproj | 1 + .../nunit.engine.tests/nunit.engine.tests.csproj | 1 + src/NUnitEngine/nunit.engine/Services/AgentProcess.cs | 2 +- 9 files changed, 14 insertions(+), 5 deletions(-) diff --git a/global.json b/global.json index 8a46e9ab2..7eb9bc5b3 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.101", + "version": "8.0.100", "rollForward": "feature" } } \ No newline at end of file diff --git a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj index f21146db6..1a5677711 100644 --- a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj @@ -7,6 +7,7 @@ ..\..\nunit.snk x86 false + false diff --git a/src/NUnitEngine/mock-assembly/mock-assembly.csproj b/src/NUnitEngine/mock-assembly/mock-assembly.csproj index 7a775e1b3..74ad22266 100644 --- a/src/NUnitEngine/mock-assembly/mock-assembly.csproj +++ b/src/NUnitEngine/mock-assembly/mock-assembly.csproj @@ -2,10 +2,11 @@ NUnit.Tests - net35;net40;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0 + net35;net40;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 true ..\..\nunit.snk false + false diff --git a/src/NUnitEngine/notest-assembly/notest-assembly.csproj b/src/NUnitEngine/notest-assembly/notest-assembly.csproj index 07eeca2ba..babc040ef 100644 --- a/src/NUnitEngine/notest-assembly/notest-assembly.csproj +++ b/src/NUnitEngine/notest-assembly/notest-assembly.csproj @@ -4,6 +4,7 @@ notest_assembly net35;netcoreapp2.1;netcoreapp3.1 false + false diff --git a/src/NUnitEngine/nunit-agent/Program.cs b/src/NUnitEngine/nunit-agent/Program.cs index 4c98c0591..45f951ac7 100644 --- a/src/NUnitEngine/nunit-agent/Program.cs +++ b/src/NUnitEngine/nunit-agent/Program.cs @@ -3,7 +3,9 @@ using System; using System.Diagnostics; using System.IO; +using System.Reflection; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Security; using NUnit.Common; using NUnit.Engine; @@ -71,7 +73,9 @@ public static void Main(string[] args) LocateAgencyProcess(agencyPid); -#if NETCOREAPP3_1 +#if NET5_0_OR_GREATER + log.Info($"Running {typeof(NUnitTestAgent).Assembly.GetCustomAttribute().FrameworkDisplayName} agent under {RuntimeInformation.FrameworkDescription}"); +#elif NETCOREAPP3_1 log.Info($"Running .NET Core 3.1 agent under {RuntimeInformation.FrameworkDescription}"); #elif NET40 log.Info($"Running .NET 4.0 agent under {RuntimeFramework.CurrentFramework.DisplayName}"); diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index aeca8e54a..c7b24aee7 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -3,7 +3,7 @@ Exe nunit.agent - net20;net40;netcoreapp3.1;net5.0;net6.0 + net20;net40;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 app.manifest ..\..\..\nunit.ico false diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 0441bbc04..ed9a7df0b 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -8,6 +8,7 @@ ..\..\nunit.snk Full false + false diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index bdb1a9898..cd074fe74 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -8,6 +8,7 @@ ..\..\nunit.snk Full false + false diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 6411d810e..91f6e6371 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -127,7 +127,7 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re agentExtension = ".exe"; break; case RuntimeType.NetCore: - runtimeDir = major >= 6 ? "net6.0" : major == 5 ? "net5.0" : "netcoreapp3.1"; + runtimeDir = major >= 8 ? "net8.0" : major == 7 ? "net7.0" : major == 6 ? "net6.0" : major == 5 ? "net5.0" : "netcoreapp3.1"; agentName = "nunit-agent"; agentExtension = ".dll"; break; From 6d21729c0134ae87435d9d0983e0a14af7ab9ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Le=C3=9Fmann?= Date: Wed, 10 Jan 2024 14:42:22 +0100 Subject: [PATCH 015/190] Add .NET 8 console --- cake/constants.cake | 1 + cake/package-definitions.cake | 34 ++++++++++++++++--- cake/package-tests.cake | 14 +++++++- cake/utilities.cake | 2 +- choco/nunit-console-runner.nuspec | 20 +++++++++++ nuget/runners/nunit.console-runner.nuspec | 26 ++++++++++++++ .../nunit3-console.tests.csproj | 2 +- .../nunit3-console/OptionsUtils/Options.cs | 4 +-- .../nunit3-console/nunit3-console.csproj | 2 +- 9 files changed, 95 insertions(+), 10 deletions(-) diff --git a/cake/constants.cake b/cake/constants.cake index 4c3b713bc..c3b2682f2 100644 --- a/cake/constants.cake +++ b/cake/constants.cake @@ -39,6 +39,7 @@ var NOTEST_PROJECT = SOURCE_DIR + "NUnitEngine/notest-assembly/notest-assembly.c // Console Runner var NET20_CONSOLE = BIN_DIR + "net20/nunit3-console.exe"; var NET60_CONSOLE = BIN_DIR + "net6.0/nunit3-console.dll"; +var NET80_CONSOLE = BIN_DIR + "net8.0/nunit3-console.dll"; // Unit Tests var NETFX_ENGINE_CORE_TESTS = "nunit.engine.core.tests.exe"; var NETCORE_ENGINE_CORE_TESTS = "nunit.engine.core.tests.dll"; diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake index 93db0d309..f8786f0c7 100644 --- a/cake/package-definitions.cake +++ b/cake/package-definitions.cake @@ -5,6 +5,7 @@ PackageDefinition NUnitConsoleNuGetPackage; PackageDefinition NUnitConsoleRunnerNuGetPackage; PackageDefinition NUnitConsoleRunnerNet60Package; +PackageDefinition NUnitConsoleRunnerNet80Package; PackageDefinition NUnitEnginePackage; PackageDefinition NUnitEngineApiPackage; PackageDefinition NUnitConsoleRunnerChocolateyPackage; @@ -46,6 +47,8 @@ public void InitializePackageDefinitions(ICakeContext context) NetCore31Test, Net50Test, Net60Test, + Net70Test, + Net80Test, NetCore21PlusNetCore31Test, NetCore21PlusNetCore31PlusNet50PlusNet60Test }; @@ -71,7 +74,9 @@ public void InitializePackageDefinitions(ICakeContext context) HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins"), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins") + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins") }, symbols: new PackageCheck[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), @@ -79,11 +84,28 @@ public void InitializePackageDefinitions(ICakeContext context) HasDirectory("tools/agents/net40").WithFiles(AGENT_PDB_FILES), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net5.0").WithFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_PDB_FILES_NETCORE) + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_PDB_FILES_NETCORE) }, executable: "tools/nunit3-console.exe", tests: StandardRunnerTests), + NUnitConsoleRunnerNet80Package = new NuGetPackage( + context: context, + id: "NUnit.ConsoleRunner.NetCore", + version: ProductVersion, + source: NUGET_DIR + "runners/nunit.console-runner.netcore.nuspec", + checks: new PackageCheck[] { + HasFiles("LICENSE.txt", "NOTICES.txt"), + HasDirectory("tools/net8.0/any").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins") + }, + symbols: new PackageCheck[] { + HasDirectory("tools/net8.0/any").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) + }, + executable: "tools/net8.0/any/nunit3-console.exe", + tests: NetCoreRunnerTests), + NUnitConsoleRunnerNet60Package = new NuGetPackage( context: context, id: "NUnit.ConsoleRunner.NetCore", @@ -110,7 +132,9 @@ public void InitializePackageDefinitions(ICakeContext context) HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins"), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins") + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins") }, executable: "tools/nunit3-console.exe", tests: StandardRunnerTests), @@ -144,7 +168,9 @@ public void InitializePackageDefinitions(ICakeContext context) HasDirectory("bin/agents/net20").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net40").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) + HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("bin/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) }, executable: "bin/net20/nunit3-console.exe", tests: StandardRunnerTests.Concat(new[] { NUnitProjectTest })), diff --git a/cake/package-tests.cake b/cake/package-tests.cake index e28e90434..4683be511 100644 --- a/cake/package-tests.cake +++ b/cake/package-tests.cake @@ -42,6 +42,18 @@ static PackageTest Net35PlusNet40Test = new PackageTest( "net35/mock-assembly.dll net40/mock-assembly.dll", MockAssemblyExpectedResult(2)); +static PackageTest Net80Test = new PackageTest( + "Net80Test", + "Run mock-assembly.dll under .NET 8.0", + "net8.0/mock-assembly.dll", + MockAssemblyExpectedResult(1)); + +static PackageTest Net70Test = new PackageTest( + "Net70Test", + "Run mock-assembly.dll under .NET 7.0", + "net7.0/mock-assembly.dll", + MockAssemblyExpectedResult(1)); + static PackageTest Net60Test = new PackageTest( "Net60Test", "Run mock-assembly.dll under .NET 6.0", @@ -87,7 +99,7 @@ static PackageTest NetCore21PlusNetCore31Test = new PackageTest( static PackageTest NetCore21PlusNetCore31PlusNet50PlusNet60Test = new PackageTest( "NetCore21PlusNetCore31PlusNet50PlusNet60Test", "Run four copies of mock-assembly together", - "netcoreapp2.1/mock-assembly.dll netcoreapp3.1/mock-assembly.dll net5.0/mock-assembly.dll net6.0/mock-assembly.dll", + "netcoreapp2.1/mock-assembly.dll netcoreapp3.1/mock-assembly.dll net5.0/mock-assembly.dll net6.0/mock-assembly.dll net7.0/mock-assembly.dll net8.0/mock-assembly.dll", MockAssemblyExpectedResult(4)); static PackageTest Net40PlusNet60Test = new PackageTest( diff --git a/cake/utilities.cake b/cake/utilities.cake index 803c04a3a..23785a7ed 100644 --- a/cake/utilities.cake +++ b/cake/utilities.cake @@ -180,7 +180,7 @@ void RunNetCoreConsole(string testAssembly, string targetRuntime) "dotnet", new ProcessSettings { - Arguments = $"\"{NET60_CONSOLE}\" \"{assemblyPath}\" --result:{resultPath}", + Arguments = $"\"{NET80_CONSOLE}\" \"{assemblyPath}\" --result:{resultPath}", WorkingDirectory = workingDir }); diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index f8f321b5e..941311ce5 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -92,5 +92,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 032d4d088..a1159dcfa 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -93,6 +93,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index cf732deba..5c1212d21 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -2,7 +2,7 @@ NUnit.ConsoleRunner.Tests - net35;net6.0 + net35;net6.0;net8.0 1685 Full diff --git a/src/NUnitConsole/nunit3-console/OptionsUtils/Options.cs b/src/NUnitConsole/nunit3-console/OptionsUtils/Options.cs index 1e5ded4e7..d9d4db7af 100644 --- a/src/NUnitConsole/nunit3-console/OptionsUtils/Options.cs +++ b/src/NUnitConsole/nunit3-console/OptionsUtils/Options.cs @@ -771,7 +771,7 @@ public OptionException(string message, string optionName, Exception innerExcepti this.option = optionName; } -#if !PCL +#if !PCL && !NET8_0_OR_GREATER protected OptionException(SerializationInfo info, StreamingContext context) : base(info, context) { @@ -784,7 +784,7 @@ public string OptionName get { return this.option; } } -#if !PCL +#if !PCL && !NET8_0_OR_GREATER public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); diff --git a/src/NUnitConsole/nunit3-console/nunit3-console.csproj b/src/NUnitConsole/nunit3-console/nunit3-console.csproj index 657ed62a3..0ebf4f30e 100644 --- a/src/NUnitConsole/nunit3-console/nunit3-console.csproj +++ b/src/NUnitConsole/nunit3-console/nunit3-console.csproj @@ -4,7 +4,7 @@ Exe NUnit.ConsoleRunner nunit3-console - net20;net6.0 + net20;net6.0;net8.0 Major From cf48ddf70bcafbf4fb1b31247e7f48ac7c05799f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Le=C3=9Fmann?= Date: Wed, 10 Jan 2024 15:08:35 +0100 Subject: [PATCH 016/190] Engine Core .NET 8 --- build.cake | 19 ++++++++++++++++--- .../nunit.engine.core.tests.csproj | 2 +- .../nunit.engine.core.csproj | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/build.cake b/build.cake index 49ec5e7d8..a199acc4d 100644 --- a/build.cake +++ b/build.cake @@ -139,8 +139,8 @@ private void BuildEachProjectSeparately() BuildProject(AGENT_X86_PROJECT); BuildProject(ENGINE_TESTS_PROJECT, "net35", "netcoreapp2.1"); - BuildProject(ENGINE_CORE_TESTS_PROJECT, "net35", "netcoreapp2.1", "netcoreapp3.1", "net5.0", "net6.0"); - BuildProject(CONSOLE_TESTS_PROJECT, "net35", "net6.0"); + BuildProject(ENGINE_CORE_TESTS_PROJECT, "net35", "netcoreapp2.1", "netcoreapp3.1", "net5.0", "net6.0", "net8.0"); + BuildProject(CONSOLE_TESTS_PROJECT, "net35", "net6.0", "net8.0"); BuildProject(MOCK_ASSEMBLY_X86_PROJECT, "net35", "net40", "netcoreapp2.1", "netcoreapp3.1"); BuildProject(NOTEST_PROJECT, "net35", "netcoreapp2.1", "netcoreapp3.1"); @@ -317,7 +317,7 @@ Task("TestNet20Console") { RunNet20Console(CONSOLE_TESTS, "net35"); }); - + ////////////////////////////////////////////////////////////////////// // TEST .NET 6.0 CONSOLE ////////////////////////////////////////////////////////////////////// @@ -331,6 +331,19 @@ Task("TestNet60Console") RunNetCoreConsole(CONSOLE_TESTS, "net6.0"); }); +////////////////////////////////////////////////////////////////////// +// TEST .NET 8.0 CONSOLE +////////////////////////////////////////////////////////////////////// + +Task("TestNet80Console") + .Description("Tests the .NET 8.0 console runner") + .IsDependentOn("Build") + .OnError(exception => { UnreportedErrors.Add(exception.Message); }) + .Does(() => + { + RunNetCoreConsole(CONSOLE_TESTS, "net8.0"); + }); + ////////////////////////////////////////////////////////////////////// // FETCH BUNDLED EXTENSIONS ////////////////////////////////////////////////////////////////////// diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index ed9a7df0b..2a4c87d65 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Core.Tests - net35;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0 + net35;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net8.0 Exe true ..\..\nunit.snk diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index f5d02c59a..b7b629311 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net20;netstandard2.0;netcoreapp3.1;net5.0;net6.0 + net20;netstandard2.0;netcoreapp3.1;net5.0;net6.0;net8.0 $(NoWarn);SYSLIB0011;SYSLIB0012 true ..\..\nunit.snk From 31c20b7a1c9f5b157ab8e9633369ee72770af87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Le=C3=9Fmann?= Date: Wed, 10 Jan 2024 15:41:34 +0100 Subject: [PATCH 017/190] Install .NET SDKs on azure pipelines --- azure-pipelines.yml | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0edc93152..684fbabb1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,9 +9,19 @@ jobs: condition: not(or(startsWith(variables['Build.SourceBranchName'], 'azure-linux-'),startsWith(variables['Build.SourceBranchName'], 'azure-macOS-'))) pool: vmImage: windows-2022 - + steps: + - task: UseDotNet@2 + displayName: 'Install .NET 8.0' + inputs: + version: 8.x + + - task: UseDotNet@2 + displayName: 'Install .NET 7.0' + inputs: + version: 7.x + - task: UseDotNet@2 displayName: 'Install .NET 6.0' inputs: @@ -26,7 +36,7 @@ jobs: displayName: 'Install .NET Core SDK' inputs: version: 3.1.x - + - task: UseDotNet@2 displayName: 'Install .NET Core runtime 2.1' inputs: @@ -69,6 +79,16 @@ jobs: vmImage: ubuntu-20.04 steps: + - task: UseDotNet@2 + displayName: 'Install .NET 8.0' + inputs: + version: 8.x + + - task: UseDotNet@2 + displayName: 'Install .NET 7.0' + inputs: + version: 7.x + - task: UseDotNet@2 displayName: 'Install .NET 6.0' inputs: @@ -83,7 +103,7 @@ jobs: displayName: 'Install .NET Core SDK' inputs: version: 3.1.x - + - task: UseDotNet@2 displayName: 'Install .NET Core runtime 2.1' inputs: @@ -118,6 +138,16 @@ jobs: vmImage: macOS-11 steps: + - task: UseDotNet@2 + displayName: 'Install .NET 8.0' + inputs: + version: 8.x + + - task: UseDotNet@2 + displayName: 'Install .NET 7.0' + inputs: + version: 7.x + - task: UseDotNet@2 displayName: 'Install .NET 6.0' inputs: @@ -132,7 +162,7 @@ jobs: displayName: 'Install .NET Core SDK' inputs: version: 3.1.x - + - task: UseDotNet@2 displayName: 'Install .NET Core runtime 2.1' inputs: From 086a052d7243ce626d815e97f48a31dc1a1f2e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Le=C3=9Fmann?= Date: Thu, 11 Jan 2024 08:06:58 +0100 Subject: [PATCH 018/190] Fix NuGetAudit=false not working when building with CLI --- src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj | 2 +- src/NUnitEngine/mock-assembly/mock-assembly.csproj | 2 +- src/NUnitEngine/notest-assembly/notest-assembly.csproj | 2 +- src/NUnitEngine/nunit-agent/nunit-agent.csproj | 1 + .../nunit.engine.core.tests/nunit.engine.core.tests.csproj | 4 ++-- src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj index 1a5677711..5abc42dff 100644 --- a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj @@ -7,7 +7,7 @@ ..\..\nunit.snk x86 false - false + false diff --git a/src/NUnitEngine/mock-assembly/mock-assembly.csproj b/src/NUnitEngine/mock-assembly/mock-assembly.csproj index 74ad22266..8cf6ae646 100644 --- a/src/NUnitEngine/mock-assembly/mock-assembly.csproj +++ b/src/NUnitEngine/mock-assembly/mock-assembly.csproj @@ -6,7 +6,7 @@ true ..\..\nunit.snk false - false + false diff --git a/src/NUnitEngine/notest-assembly/notest-assembly.csproj b/src/NUnitEngine/notest-assembly/notest-assembly.csproj index babc040ef..8638fa6bf 100644 --- a/src/NUnitEngine/notest-assembly/notest-assembly.csproj +++ b/src/NUnitEngine/notest-assembly/notest-assembly.csproj @@ -4,7 +4,7 @@ notest_assembly net35;netcoreapp2.1;netcoreapp3.1 false - false + false diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index c7b24aee7..f00216562 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -8,6 +8,7 @@ ..\..\..\nunit.ico false ..\..\..\bin\$(Configuration)\agents\ + false diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 2a4c87d65..388576ee5 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -8,7 +8,7 @@ ..\..\nunit.snk Full false - false + false @@ -29,7 +29,7 @@ - + diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index cd074fe74..80b65e858 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -8,7 +8,7 @@ ..\..\nunit.snk Full false - false + false From d77f28847ca189ceb30d18580564686929a34e1c Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Thu, 11 Jan 2024 20:39:13 +1000 Subject: [PATCH 019/190] Version317 --- cake/versioning.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cake/versioning.cake b/cake/versioning.cake index d32d65f99..bbfc76707 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -78,7 +78,7 @@ public class BuildVersion string branchName = _gitVersion.BranchName; // Treat version3 branch as an alternate "main" - if (branchName == "version3" || branchName == "version315") + if (branchName == "version3" || branchName == "version315" || branchName == "version317") label = "dev"; // We don't currently use this pattern, but check in case we do later. From 0b73b33451805d3474da9fa3c2163070f362d53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Le=C3=9Fmann?= Date: Fri, 12 Jan 2024 07:29:39 +0100 Subject: [PATCH 020/190] Enable BinaryFormatter for nunit-agent --- src/NUnitEngine/nunit-agent/nunit-agent.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index f00216562..941602590 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -9,6 +9,7 @@ false ..\..\..\bin\$(Configuration)\agents\ false + true From 7ba3d66ba53d6751a279514b6e8b6ca1a7fc7716 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Tue, 2 Apr 2024 19:41:03 +0200 Subject: [PATCH 021/190] Fix for issue #1178 based on fix in PR #1214 for version 3.17++ --- src/NUnitConsole/nunit3-console/Program.cs | 8 ++++++-- src/NUnitEngine/nunit-agent/Program.cs | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index c9c5d6d3e..e3ab012e0 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -106,8 +106,12 @@ public static int Main(string[] args) if (Options.WorkDirectory != null) engine.WorkDirectory = Options.WorkDirectory; - if (Options.InternalTraceLevel != null) - engine.InternalTraceLevel = (InternalTraceLevel)Enum.Parse(typeof(InternalTraceLevel), Options.InternalTraceLevel); + //if (Options.InternalTraceLevel != null) + // engine.InternalTraceLevel = (InternalTraceLevel)Enum.Parse(typeof(InternalTraceLevel), Options.InternalTraceLevel); + // See PR 1114 https://github.com/nunit/nunit-console/pull/1214/files + engine.InternalTraceLevel = Options.InternalTraceLevel != null + ? (InternalTraceLevel)Enum.Parse(typeof(InternalTraceLevel), Options.InternalTraceLevel) + : InternalTraceLevel.Off; try { diff --git a/src/NUnitEngine/nunit-agent/Program.cs b/src/NUnitEngine/nunit-agent/Program.cs index 45f951ac7..437c565f9 100644 --- a/src/NUnitEngine/nunit-agent/Program.cs +++ b/src/NUnitEngine/nunit-agent/Program.cs @@ -62,8 +62,8 @@ public static void Main(string[] args) } } - var logName = $"nunit-agent_{pid}.log"; - InternalTrace.Initialize(Path.Combine(workDirectory, logName), traceLevel); + // var logName = $"nunit-agent_{pid}.log"; + // InternalTrace.Initialize(Path.Combine(workDirectory, logName), traceLevel); // See PR 1214 https://github.com/nunit/nunit-console/pull/1214/files log = InternalTrace.GetLogger(typeof(NUnitTestAgent)); log.Info("Agent process {0} starting", pid); From e5ec24e5c1a334b88fc7fcd921c51eb18ae81976 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 11:34:18 +0200 Subject: [PATCH 022/190] Added version3x branch to set of alternate mains --- cake/versioning.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cake/versioning.cake b/cake/versioning.cake index bbfc76707..02e509928 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -78,7 +78,7 @@ public class BuildVersion string branchName = _gitVersion.BranchName; // Treat version3 branch as an alternate "main" - if (branchName == "version3" || branchName == "version315" || branchName == "version317") + if (branchName == "version3X" || branchName == "version315" || branchName == "version317") label = "dev"; // We don't currently use this pattern, but check in case we do later. From 552e75e8b71d66b165888680916304c6412f0004 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 12:13:09 +0200 Subject: [PATCH 023/190] Adding yml file for CI build --- .../workflows/NUnitConsoleAndEngine.CI.yml | 108 ++++++++++++++++++ NUnitConsole.sln | 23 +++- 2 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/NUnitConsoleAndEngine.CI.yml diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml new file mode 100644 index 000000000..e5a1c2dd3 --- /dev/null +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -0,0 +1,108 @@ +name: NUnitConsoleAndEngine.CI + +on: + push: + branches: + - main + - release + - version3x + pull_request: + +env: + DOTNET_NOLOGO: true # Disable the .NET logo + DOTNET_CLI_TELEMETRY_OPTOUT: true # Disable sending .NET CLI telemetry + +jobs: + build-windows: + name: Windows Build + runs-on: windows-latest + + steps: + - name: ⤵️ Checkout Source + uses: actions/checkout@v4 + + - name: 🛠️ Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: 🛠️ Install dotnet tools + run: dotnet tool restore + + - name: 🔨 Build and Test + run: dotnet tool run dotnet-cake --target=Test --test-run-name=Windows --configuration=Release + + - name: 📦 Package + run: dotnet tool run dotnet-cake --target=Package + + - name: 💾 Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: Package + path: package + + - name: 💾 Upload test results + uses: actions/upload-artifact@v4 + with: + name: Test results (Windows) + path: test-results + # Use always() to always run this step to publish test results when there are test failures + if: ${{ always() }} + + # build-linux: + # name: Linux Build + # runs-on: ubuntu-latest + + # steps: + # - name: ⤵️ Checkout Source + # uses: actions/checkout@v4 + + # - name: 🛠️ Setup .NET + # uses: actions/setup-dotnet@v4 + # with: + # global-json-file: global.json + + # - name: 🛠️ Install F# + # run: sudo apt-get install fsharp + + # - name: 🛠️ Install dotnet tools + # run: dotnet tool restore + + # - name: 🔨 Build and Test + # run: dotnet tool run dotnet-cake --target=Test --test-run-name=Linux --configuration=Release + + # - name: 💾 Upload test results + # uses: actions/upload-artifact@v4 + # with: + # name: Test results (Linux) + # path: test-results + # # Use always() to always run this step to publish test results when there are test failures + # if: ${{ always() }} + + # build-macos: + # name: MacOS Build + # runs-on: macos-14 + + # steps: + # - name: ⤵️ Checkout Source + # uses: actions/checkout@v4 + + # - name: 🛠️ Setup .NET + # uses: actions/setup-dotnet@v4 + # with: + # global-json-file: global.json + # dotnet-version: 6.x + + # - name: 🛠️ Install dotnet tools + # run: dotnet tool restore + + # - name: 🔨 Build and Test + # run: dotnet tool run dotnet-cake --target=Test --test-run-name=Linux --configuration=Release + + # - name: 💾 Upload test results + # uses: actions/upload-artifact@v4 + # with: + # name: Test results (macOS) + # path: test-results + # # Use always() to always run this step to publish test results when there are test failures + # if: ${{ always() }} diff --git a/NUnitConsole.sln b/NUnitConsole.sln index af5116a20..23e7d1c01 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -56,11 +56,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit3-console", "src\NUnitConsole\nunit3-console\nunit3-console.csproj", "{0DE218CA-AFB8-423A-9CD2-E22DEAC55C46}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit3-console.tests", "src\NUnitConsole\nunit3-console.tests\nunit3-console.tests.csproj", "{B310A760-8AE1-41CA-81F8-03B12E2FCE30}" - ProjectSection(ProjectDependencies) = postProject - {B1D90742-39BD-429C-8E87-C5CD2991DF27} = {B1D90742-39BD-429C-8E87-C5CD2991DF27} - {D694CB69-6CFB-4762-86C2-EB27B808B282} = {D694CB69-6CFB-4762-86C2-EB27B808B282} - {C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8} = {C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8} - EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "engine", "engine", "{43A219A8-2995-4884-806F-FDB9CD25D403}" ProjectSection(SolutionItems) = preProject @@ -72,6 +67,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "engine", "engine", "{43A219 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runners", "runners", "{F3E87D0F-6F06-4C0B-AE06-42C0834C3C6E}" ProjectSection(SolutionItems) = preProject + nuget\runners\DotnetToolSettings.xml = nuget\runners\DotnetToolSettings.xml nuget\runners\nunit.agent.addins = nuget\runners\nunit.agent.addins nuget\runners\nunit.console-runner-with-extensions.nuspec = nuget\runners\nunit.console-runner-with-extensions.nuspec nuget\runners\nunit.console-runner.netcore.nuspec = nuget\runners\nunit.console-runner.netcore.nuspec @@ -135,6 +131,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "zip", "zip", "{20005864-BE8 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit.engine.core.tests", "src\NUnitEngine\nunit.engine.core.tests\nunit.engine.core.tests.csproj", "{CACC0520-B452-4310-A33C-DC944129ACDD}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{25DA12FE-6209-4524-9A37-8E51F815E198}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{08F8160E-E691-4F07-9F57-EA31B9736429}" + ProjectSection(SolutionItems) = preProject + .github\workflows\NUnitConsoleAndEngine.CI.yml = .github\workflows\NUnitConsoleAndEngine.CI.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemplates", "{50371E48-BEC3-4D53-BD37-F3A6149CFD0D}" + ProjectSection(SolutionItems) = preProject + .github\ISSUE_TEMPLATE\bug-report.md = .github\ISSUE_TEMPLATE\bug-report.md + .github\ISSUE_TEMPLATE\feature-suggestion.md = .github\ISSUE_TEMPLATE\feature-suggestion.md + .github\ISSUE_TEMPLATE\support-other.md = .github\ISSUE_TEMPLATE\support-other.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -215,6 +225,9 @@ Global {0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {20005864-BE82-412D-99BF-288E2D8370E9} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {CACC0520-B452-4310-A33C-DC944129ACDD} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} + {25DA12FE-6209-4524-9A37-8E51F815E198} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} + {08F8160E-E691-4F07-9F57-EA31B9736429} = {25DA12FE-6209-4524-9A37-8E51F815E198} + {50371E48-BEC3-4D53-BD37-F3A6149CFD0D} = {25DA12FE-6209-4524-9A37-8E51F815E198} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} From d69bbec2e7412b669ae6639d404a35f532895c09 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 12:22:31 +0200 Subject: [PATCH 024/190] Adding missing sdks --- .github/workflows/NUnitConsoleAndEngine.CI.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index e5a1c2dd3..75c57e806 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -25,6 +25,10 @@ jobs: uses: actions/setup-dotnet@v4 with: global-json-file: global.json + dotnet-version: | + '3.1.x' + '5.0.x' + - name: 🛠️ Install dotnet tools run: dotnet tool restore From 770b3071b7492f22c924e9de2872d86e492cb426 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 12:31:32 +0200 Subject: [PATCH 025/190] update gitversion --- build.cake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.cake b/build.cake index a199acc4d..d882731aa 100644 --- a/build.cake +++ b/build.cake @@ -13,8 +13,8 @@ static bool NoPush; NoPush = HasArgument("nopush"); #load cake/package-definitions.cake // Install Tools -#tool NuGet.CommandLine&version=6.0.0 -#tool dotnet:?package=GitVersion.Tool&version=5.6.3 +#tool NuGet.CommandLine&version=6.9.1 +#tool dotnet:?package=GitVersion.Tool&version=5.12.0 #tool dotnet:?package=GitReleaseManager.Tool&version=0.12.1 BuildVersion _buildVersion; From 789325fd9f7f14ab99b332cc8f7b75edc0c049c0 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 12:35:22 +0200 Subject: [PATCH 026/190] More frameworks --- .github/workflows/NUnitConsoleAndEngine.CI.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index 75c57e806..d13cc823d 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -26,7 +26,9 @@ jobs: with: global-json-file: global.json dotnet-version: | + '2.1.x' '3.1.x' + '5.0.0' '5.0.x' From c15a4881665813867004f6c866b057cc73891f56 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 12:37:16 +0200 Subject: [PATCH 027/190] removing global.json --- .github/workflows/NUnitConsoleAndEngine.CI.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index d13cc823d..a35f26632 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -24,12 +24,13 @@ jobs: - name: 🛠️ Setup .NET uses: actions/setup-dotnet@v4 with: - global-json-file: global.json + # global-json-file: global.json dotnet-version: | '2.1.x' '3.1.x' '5.0.0' '5.0.x' + '8.0.100' - name: 🛠️ Install dotnet tools From a85627fd7aad1428d677324ca156dbb2d3c08d90 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 12:39:14 +0200 Subject: [PATCH 028/190] formatting yml --- .github/workflows/NUnitConsoleAndEngine.CI.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index a35f26632..85d58f53a 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -26,11 +26,11 @@ jobs: with: # global-json-file: global.json dotnet-version: | - '2.1.x' - '3.1.x' - '5.0.0' - '5.0.x' - '8.0.100' + 2.1.x + 3.1.x + 5.0.0 + 5.0.x + 8.0.100 - name: 🛠️ Install dotnet tools From 300e72132da7efa15ecdb706d6cbb73fc69e2c2a Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 12:41:51 +0200 Subject: [PATCH 029/190] frameworks again --- .github/workflows/NUnitConsoleAndEngine.CI.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index 85d58f53a..81c206f02 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -28,7 +28,6 @@ jobs: dotnet-version: | 2.1.x 3.1.x - 5.0.0 5.0.x 8.0.100 From 6e03c18af7e35ccabb8a58ba9bcbc651f77c3a3c Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 12:50:30 +0200 Subject: [PATCH 030/190] log messages --- cake/versioning.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cake/versioning.cake b/cake/versioning.cake index 02e509928..a68bf4006 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -16,7 +16,7 @@ public class BuildVersion { _context = context; _gitVersion = context.GitVersion(); - + Information("Running GitVersion: {0}", _gitVersion.FullSemVer); BranchName = _gitVersion.BranchName; IsReleaseBranch = BranchName.StartsWith("release-"); From 95a7dcbd8d357e9f3b2e41a327c9f4550556722c Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 13:02:42 +0200 Subject: [PATCH 031/190] console writeline --- cake/versioning.cake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cake/versioning.cake b/cake/versioning.cake index a68bf4006..9a880fff0 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -16,7 +16,7 @@ public class BuildVersion { _context = context; _gitVersion = context.GitVersion(); - Information("Running GitVersion: {0}", _gitVersion.FullSemVer); + Console.WriteLine($"Running GitVersion: {_gitVersion.FullSemVer}"); BranchName = _gitVersion.BranchName; IsReleaseBranch = BranchName.StartsWith("release-"); @@ -90,7 +90,7 @@ public class BuildVersion label = "ci"; string suffix = "-" + label + _gitVersion.CommitsSinceVersionSourcePadded; - + Console.WriteLine($"CalculateProductVersion: {label} {branchName} {suffix}"); switch (label) { case "ci": From 72062fc1ce9a03f304fe7f353f2ed3b3bc236794 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 13:22:04 +0200 Subject: [PATCH 032/190] logging --- cake/versioning.cake | 1 + 1 file changed, 1 insertion(+) diff --git a/cake/versioning.cake b/cake/versioning.cake index 9a880fff0..3f2ef8781 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -14,6 +14,7 @@ public class BuildVersion // then parsing it to provide information that is used in the build. public BuildVersion(ISetupContext context) { + Console.WriteLine("Start BuildVersion"); _context = context; _gitVersion = context.GitVersion(); Console.WriteLine($"Running GitVersion: {_gitVersion.FullSemVer}"); From f30a4ad3f8f41ca6a80beef14c202af278f3ec02 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 13:26:38 +0200 Subject: [PATCH 033/190] check for null context --- cake/versioning.cake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cake/versioning.cake b/cake/versioning.cake index 3f2ef8781..81a4f28bd 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -16,6 +16,8 @@ public class BuildVersion { Console.WriteLine("Start BuildVersion"); _context = context; + if (context==null) + throw new ArgumentNullException(nameof(context)); _gitVersion = context.GitVersion(); Console.WriteLine($"Running GitVersion: {_gitVersion.FullSemVer}"); BranchName = _gitVersion.BranchName; From 09af4f3ee1e3c54d27d28b5d63eb7f3dd1c1d624 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 13:40:52 +0200 Subject: [PATCH 034/190] updated cake --- .config/dotnet-tools.json | 2 +- NUnitConsole.sln | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 31e896e98..da200cdae 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "cake.tool": { - "version": "2.0.0", + "version": "4.0.0", "commands": [ "dotnet-cake" ] diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 23e7d1c01..f978a232f 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution appveyor.yml = appveyor.yml azure-pipelines.yml = azure-pipelines.yml build.cake = build.cake + build.cmd = build.cmd build.ps1 = build.ps1 build.sh = build.sh BUILDING.md = BUILDING.md @@ -145,6 +146,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp .github\ISSUE_TEMPLATE\support-other.md = .github\ISSUE_TEMPLATE\support-other.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{C5B7120C-190B-4C38-95CB-83F12799598D}" + ProjectSection(SolutionItems) = preProject + .config\dotnet-tools.json = .config\dotnet-tools.json + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -228,6 +234,7 @@ Global {25DA12FE-6209-4524-9A37-8E51F815E198} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {08F8160E-E691-4F07-9F57-EA31B9736429} = {25DA12FE-6209-4524-9A37-8E51F815E198} {50371E48-BEC3-4D53-BD37-F3A6149CFD0D} = {25DA12FE-6209-4524-9A37-8E51F815E198} + {C5B7120C-190B-4C38-95CB-83F12799598D} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} From 924fbc822b61fdb7930a91b06d7846ef6d039e96 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 14:24:09 +0200 Subject: [PATCH 035/190] upping cake verbosity --- build.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cmd b/build.cmd index d060b75b8..0f633c831 100644 --- a/build.cmd +++ b/build.cmd @@ -8,4 +8,4 @@ set DOTNET_NOLOGO=1 dotnet tool restore @if %ERRORLEVEL% neq 0 goto :eof -dotnet cake %* +dotnet cake --verbosity=diagnostic %* From 36f318c8a05a10f161a807b9a530b5b124a39ca3 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 14:42:29 +0200 Subject: [PATCH 036/190] then at the right place too --- .github/workflows/NUnitConsoleAndEngine.CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index 81c206f02..af9521f9d 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -36,7 +36,7 @@ jobs: run: dotnet tool restore - name: 🔨 Build and Test - run: dotnet tool run dotnet-cake --target=Test --test-run-name=Windows --configuration=Release + run: dotnet tool run dotnet-cake --verbosity=diagnostic --target=Test --test-run-name=Windows --configuration=Release - name: 📦 Package run: dotnet tool run dotnet-cake --target=Package From 93694a7cff1b800eddb35b4daddd288f0cb87d99 Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 14:56:29 +0200 Subject: [PATCH 037/190] get all history --- .github/workflows/NUnitConsoleAndEngine.CI.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index af9521f9d..20a8d3e10 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -20,6 +20,8 @@ jobs: steps: - name: ⤵️ Checkout Source uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: 🛠️ Setup .NET uses: actions/setup-dotnet@v4 From ee67b1b0a03032eba4a5e4afae9234353b60043f Mon Sep 17 00:00:00 2001 From: Terje Sandstrom Date: Wed, 3 Apr 2024 16:06:17 +0200 Subject: [PATCH 038/190] Added net8 builds for netcore console. Removed diagnostics --- .../workflows/NUnitConsoleAndEngine.CI.yml | 38 ++++++++++--------- build.cmd | 2 +- .../nunit.console-runner.netcore.nuspec | 18 +++++++++ 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index 20a8d3e10..8730c8edc 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -38,24 +38,26 @@ jobs: run: dotnet tool restore - name: 🔨 Build and Test - run: dotnet tool run dotnet-cake --verbosity=diagnostic --target=Test --test-run-name=Windows --configuration=Release - - - name: 📦 Package - run: dotnet tool run dotnet-cake --target=Package - - - name: 💾 Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: Package - path: package - - - name: 💾 Upload test results - uses: actions/upload-artifact@v4 - with: - name: Test results (Windows) - path: test-results - # Use always() to always run this step to publish test results when there are test failures - if: ${{ always() }} + run: dotnet tool run dotnet-cake --target=Test --test-run-name=Windows --configuration=Release + # If you need to get more verbose logging, add the following to the dotnet-cake above: --verbosity=diagnostic + +# Wait with this until package errors (tests following packaging) are all fixed + # - name: 📦 Package + # run: dotnet tool run dotnet-cake --target=Package + + # - name: 💾 Upload build artifacts + # uses: actions/upload-artifact@v4 + # with: + # name: Package + # path: package + + # - name: 💾 Upload test results + # uses: actions/upload-artifact@v4 + # with: + # name: Test results (Windows) + # path: test-results + # # Use always() to always run this step to publish test results when there are test failures + # if: ${{ always() }} # build-linux: # name: Linux Build diff --git a/build.cmd b/build.cmd index 0f633c831..0a1979f68 100644 --- a/build.cmd +++ b/build.cmd @@ -8,4 +8,4 @@ set DOTNET_NOLOGO=1 dotnet tool restore @if %ERRORLEVEL% neq 0 goto :eof -dotnet cake --verbosity=diagnostic %* +dotnet cake %* diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index 1540808f3..f2b9ea169 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -45,6 +45,24 @@ + + + + + + + + + + + + + + + + + + From aca8f016c9ec440e93287d320326ec53bd3f4a41 Mon Sep 17 00:00:00 2001 From: Ben Randall Date: Tue, 9 Jan 2024 13:11:12 -0500 Subject: [PATCH 039/190] Use Microsoft.Extensions.DependencyModel to locate dependencies Cherry-picked from 3f854b20e4ea7e42ba7ae0d7bf81af902a6bc8fd --- cake/package-checks.cake | 3 +- choco/nunit-console-runner.nuspec | 3 + msi/nunit/engine-files.wxi | 38 +++++++--- .../nunit.console-runner.netcore.nuspec | 1 + nuget/runners/nunit.console-runner.nuspec | 3 + .../Drivers/NUnitNetCore31Driver.cs | 10 +-- .../Internal/CustomAssemblyLoadContext.cs | 52 -------------- .../Internal/TestAssemblyLoadContext.cs | 50 +++++++++++++ .../Internal/TestAssemblyResolver.cs | 71 +++++++++++++++++++ .../nunit.engine.core.csproj | 11 ++- 10 files changed, 169 insertions(+), 73 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core/Internal/CustomAssemblyLoadContext.cs create mode 100644 src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs create mode 100644 src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs diff --git a/cake/package-checks.cake b/cake/package-checks.cake index 6c19f51b9..5c715a54d 100644 --- a/cake/package-checks.cake +++ b/cake/package-checks.cake @@ -16,7 +16,8 @@ string[] AGENT_FILES = { "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll"}; string[] AGENT_FILES_NETCORE = { "nunit-agent.dll", "nunit-agent.dll.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll"}; + "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll", + "Microsoft.Extensions.DependencyModel.dll"}; string[] AGENT_PDB_FILES = { "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; string[] AGENT_PDB_FILES_NETCORE = { diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 941311ce5..72c58ba46 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -71,6 +71,7 @@ + @@ -81,6 +82,7 @@ + @@ -91,6 +93,7 @@ + diff --git a/msi/nunit/engine-files.wxi b/msi/nunit/engine-files.wxi index 457244bd6..10b0d070f 100644 --- a/msi/nunit/engine-files.wxi +++ b/msi/nunit/engine-files.wxi @@ -92,9 +92,11 @@ - + Source="$(var.InstallImage)bin/agents/netcoreapp3.1/nunit-agent-netcore31.dll" /> + + + + - + Source="$(var.InstallImage)bin/agents/net5.0/nunit-agent-net50.dll" /> + - + + + - + Source="$(var.InstallImage)bin/net6.0/nunit-agent-net60.dll" /> + + Source="$(var.InstallImage)bin/net6.0/nunit-agent.deps.json" /> + Source="$(var.InstallImage)bin/net6.0/nunit-agent.runtimeconfig.json" /> + + + diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index 1540808f3..5cb9ee781 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -43,6 +43,7 @@ + diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index a1159dcfa..328f54233 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -65,6 +65,7 @@ + @@ -78,6 +79,7 @@ + @@ -91,6 +93,7 @@ + diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs index 3dfacd130..10abde4cf 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs @@ -38,7 +38,7 @@ public class NUnitNetCore31Driver : IFrameworkDriver Assembly _frameworkAssembly; object _frameworkController; Type _frameworkControllerType; - CustomAssemblyLoadContext _assemblyLoadContext; + TestAssemblyLoadContext _assemblyLoadContext; /// /// An id prefix that will be passed to the test framework and used as part of the @@ -58,13 +58,7 @@ public string Load(string assemblyPath, IDictionary settings) var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; assemblyPath = Path.GetFullPath(assemblyPath); //AssemblyLoadContext requires an absolute path - _assemblyLoadContext = new CustomAssemblyLoadContext(assemblyPath); - - _assemblyLoadContext.Resolving += (context, assemblyName) => - { - var calc = context as CustomAssemblyLoadContext; - return calc?.LoadFallback(assemblyName); - }; + _assemblyLoadContext = new TestAssemblyLoadContext(assemblyPath); try { diff --git a/src/NUnitEngine/nunit.engine.core/Internal/CustomAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/CustomAssemblyLoadContext.cs deleted file mode 100644 index ca45ff936..000000000 --- a/src/NUnitEngine/nunit.engine.core/Internal/CustomAssemblyLoadContext.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -#if NETCOREAPP3_1_OR_GREATER - -using System.Reflection; -using System.Runtime.Loader; -using System.IO; - -namespace NUnit.Engine.Internal -{ - internal class CustomAssemblyLoadContext : AssemblyLoadContext - { - private readonly AssemblyDependencyResolver _resolver; - private readonly string _basePath; - - public CustomAssemblyLoadContext(string mainAssemblyToLoadPath) - { - _resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath); - _basePath = Path.GetDirectoryName(mainAssemblyToLoadPath); - } - - protected override Assembly Load(AssemblyName name) - { - var assemblyPath = _resolver.ResolveAssemblyToPath(name); - return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null; - } - - /// - /// Loads assemblies that are dependencies, and in the same folder as the parent assembly, - /// but are not fully specified in parent assembly deps.json file. This happens when the - /// dependencies reference in the csproj file has CopyLocal=false, and for example, the - /// reference is a projectReference and has the same output directory as the parent. - /// - /// LoadFallback should be called via the CustomAssemblyLoadContext.Resolving callback when - /// a dependent assembly of that referred to in a previous 'CustomAssemblyLoadContext.Load' call - /// could not be loaded by CustomAssemblyLoadContext.Load nor by the default ALC; to which the - /// runtime will fallback when CustomAssemblyLoadContext.Load fails (to let the default ALC - /// load system assemblies). - /// - /// - /// - public Assembly LoadFallback(AssemblyName name) - { - string assemblyPath = Path.Combine(_basePath, name.Name + ".dll"); - if (File.Exists(assemblyPath)) - return LoadFromAssemblyPath(assemblyPath); - return null; - } - } -} - -#endif diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs new file mode 100644 index 000000000..bac035bab --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -0,0 +1,50 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETCOREAPP3_1_OR_GREATER + +using System.Reflection; +using System.Runtime.Loader; +using System.IO; +using System; +using System.Linq; + +namespace NUnit.Engine.Internal +{ + internal sealed class TestAssemblyLoadContext : AssemblyLoadContext + { + private readonly string _testAssemblyPath; + private readonly string _basePath; + private readonly TestAssemblyResolver _resolver; + + public TestAssemblyLoadContext(string testAssemblyPath) + { + _testAssemblyPath = testAssemblyPath; + _resolver = new TestAssemblyResolver(this, testAssemblyPath); + _basePath = Path.GetDirectoryName(testAssemblyPath); + } + + protected override Assembly Load(AssemblyName name) + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var loadedAssembly = assemblies.FirstOrDefault(x => x.GetName().Name == name.Name); + + if (loadedAssembly != null) + loadedAssembly = base.Load(name); + + if (loadedAssembly == null) + { + // Load assemblies that are dependencies, and in the same folder as the test assembly, + // but are not fully specified in test assembly deps.json file. This happens when the + // dependencies reference in the csproj file has CopyLocal=false, and for example, the + // reference is a projectReference and has the same output directory as the parent. + string assemblyPath = Path.Combine(_basePath, name.Name + ".dll"); + if (File.Exists(assemblyPath)) + loadedAssembly = LoadFromAssemblyPath(assemblyPath); + } + + return loadedAssembly; + } + } +} + +#endif diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs new file mode 100644 index 000000000..3e0edaf78 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -0,0 +1,71 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETCOREAPP3_1_OR_GREATER + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.DependencyModel.Resolution; + +namespace NUnit.Engine.Internal +{ + internal sealed class TestAssemblyResolver : IDisposable + { + private readonly ICompilationAssemblyResolver _assemblyResolver; + private readonly DependencyContext _dependencyContext; + private readonly AssemblyLoadContext _loadContext; + private readonly string _basePath; + + public TestAssemblyResolver(AssemblyLoadContext loadContext, string assemblyPath) + { + _loadContext = loadContext; + _dependencyContext = DependencyContext.Load(loadContext.LoadFromAssemblyPath(assemblyPath)); + _basePath = Path.GetDirectoryName(assemblyPath); + + _assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[] + { + new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(assemblyPath)), + new ReferenceAssemblyPathResolver(), + new PackageCompilationAssemblyResolver() + }); + + _loadContext.Resolving += OnResolving; + } + + public void Dispose() + { + _loadContext.Resolving -= OnResolving; + } + + private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) + { + foreach (var library in _dependencyContext.RuntimeLibraries) + { + var wrapper = new CompilationLibrary( + library.Type, + library.Name, + library.Version, + library.Hash, + library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths), + library.Dependencies, + library.Serviceable); + + var assemblies = new List(); + _assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies); + + foreach (var assemblyPath in assemblies) + { + if (name.Name == Path.GetFileNameWithoutExtension(assemblyPath)) + return _loadContext.LoadFromAssemblyPath(assemblyPath); + } + } + + return null; + } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index b7b629311..4727eb922 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -20,11 +20,16 @@ - - + + - + + + + + + From 4635f54b00adff75b128c242e250cad1c58b8313 Mon Sep 17 00:00:00 2001 From: Ben Randall Date: Tue, 9 Jan 2024 13:26:34 -0500 Subject: [PATCH 040/190] fix assembly dependencies resolver for netcore Cherry picked from f11499a418d5b5bb7ecdd5c91b2686fff4e32f74 --- .../Internal/TestAssemblyResolver.cs | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index 3e0edaf78..6a9624a13 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -7,9 +7,11 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Loader; using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.DependencyModel.Resolution; +using Microsoft.Win32; namespace NUnit.Engine.Internal { @@ -18,13 +20,25 @@ internal sealed class TestAssemblyResolver : IDisposable private readonly ICompilationAssemblyResolver _assemblyResolver; private readonly DependencyContext _dependencyContext; private readonly AssemblyLoadContext _loadContext; - private readonly string _basePath; + + private static readonly string INSTALL_DIR = GetDotNetInstallDirectory(); + private static readonly string WINDOWS_DESKTOP_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.WindowsDesktop.App"); + private static readonly string ASP_NET_CORE_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.AspNetCore.App"); + private static readonly List AdditionalFrameworkDirectories; + + static TestAssemblyResolver() + { + AdditionalFrameworkDirectories = new List(); + if (Directory.Exists(WINDOWS_DESKTOP_DIR)) + AdditionalFrameworkDirectories.Add(WINDOWS_DESKTOP_DIR); + if (Directory.Exists(ASP_NET_CORE_DIR)) + AdditionalFrameworkDirectories.Add(ASP_NET_CORE_DIR); + } public TestAssemblyResolver(AssemblyLoadContext loadContext, string assemblyPath) { _loadContext = loadContext; _dependencyContext = DependencyContext.Load(loadContext.LoadFromAssemblyPath(assemblyPath)); - _basePath = Path.GetDirectoryName(assemblyPath); _assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[] { @@ -64,8 +78,76 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) } } + foreach (string frameworkDirectory in AdditionalFrameworkDirectories) + { + var versionDir = FindBestVersionDir(frameworkDirectory, name.Version); + if (versionDir != null) + { + string candidate = Path.Combine(frameworkDirectory, versionDir, name.Name + ".dll"); + if (File.Exists(candidate)) + return _loadContext.LoadFromAssemblyPath(candidate); + } + } + return null; } + + private static string GetDotNetInstallDirectory() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Running on Windows so use registry + RegistryKey key = Environment.Is64BitProcess + ? Registry.LocalMachine.OpenSubKey(@"Software\dotnet\SetUp\InstalledVersions\x64\sharedHost\") + : Registry.LocalMachine.OpenSubKey(@"Software\dotnet\SetUp\InstalledVersions\x86\sharedHost\"); + return (string)key?.GetValue("Path"); + } + else + return "/usr/shared/dotnet/"; + } + + private static string FindBestVersionDir(string libraryDir, Version targetVersion) + { + string target = targetVersion.ToString(); + Version bestVersion = new Version(0, 0); + foreach (var subdir in Directory.GetDirectories(libraryDir)) + { + Version version; + if (TryGetVersionFromString(Path.GetFileName(subdir), out version)) + if (version >= targetVersion) + if (bestVersion.Major == 0 || bestVersion > version) + bestVersion = version; + } + + return bestVersion.Major > 0 + ? bestVersion.ToString() + : null; + } + + private static bool TryGetVersionFromString(string text, out Version newVersion) + { + const string VERSION_CHARS = ".0123456789"; + + int len = 0; + foreach (char c in text) + { + if (VERSION_CHARS.IndexOf(c) >= 0) + len++; + else + break; + } + + try + { + newVersion = new Version(text.Substring(0, len)); + return true; + } + catch + { + newVersion = new Version(); + return false; + } + } } } #endif From 50e280cabc1dc1a89dab022374f22ca97d332615 Mon Sep 17 00:00:00 2001 From: svg2003 Date: Sat, 11 Feb 2023 23:44:19 -0800 Subject: [PATCH 041/190] fix assembly dependencies resolver for netcore --- .../Internal/TestAssemblyLoadContext.cs | 44 +++++++++++++++---- .../Internal/TestAssemblyResolver.cs | 5 +++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index bac035bab..69d432fb4 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -15,31 +15,57 @@ internal sealed class TestAssemblyLoadContext : AssemblyLoadContext private readonly string _testAssemblyPath; private readonly string _basePath; private readonly TestAssemblyResolver _resolver; + private readonly System.Runtime.Loader.AssemblyDependencyResolver _runtimeResolver; public TestAssemblyLoadContext(string testAssemblyPath) { _testAssemblyPath = testAssemblyPath; _resolver = new TestAssemblyResolver(this, testAssemblyPath); _basePath = Path.GetDirectoryName(testAssemblyPath); + _runtimeResolver = new AssemblyDependencyResolver(testAssemblyPath); } protected override Assembly Load(AssemblyName name) { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); var loadedAssembly = assemblies.FirstOrDefault(x => x.GetName().Name == name.Name); + if (loadedAssembly != null) + { + return loadedAssembly; + } + + loadedAssembly = base.Load(name); + if (loadedAssembly != null) + { + return loadedAssembly; + } + + var runtimeResolverPath = _runtimeResolver.ResolveAssemblyToPath(name); + if (string.IsNullOrEmpty(runtimeResolverPath) == false && + File.Exists(runtimeResolverPath)) + { + loadedAssembly = LoadFromAssemblyPath(runtimeResolverPath); + } + + if (loadedAssembly != null) + { + return loadedAssembly; + } + loadedAssembly = _resolver.Resolve(this, name); if (loadedAssembly != null) - loadedAssembly = base.Load(name); + { + return loadedAssembly; + } - if (loadedAssembly == null) + // Load assemblies that are dependencies, and in the same folder as the test assembly, + // but are not fully specified in test assembly deps.json file. This happens when the + // dependencies reference in the csproj file has CopyLocal=false, and for example, the + // reference is a projectReference and has the same output directory as the parent. + string assemblyPath = Path.Combine(_basePath, name.Name + ".dll"); + if (File.Exists(assemblyPath)) { - // Load assemblies that are dependencies, and in the same folder as the test assembly, - // but are not fully specified in test assembly deps.json file. This happens when the - // dependencies reference in the csproj file has CopyLocal=false, and for example, the - // reference is a projectReference and has the same output directory as the parent. - string assemblyPath = Path.Combine(_basePath, name.Name + ".dll"); - if (File.Exists(assemblyPath)) - loadedAssembly = LoadFromAssemblyPath(assemblyPath); + loadedAssembly = LoadFromAssemblyPath(assemblyPath); } return loadedAssembly; diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index 6a9624a13..c5c28f4d7 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -55,6 +55,11 @@ public void Dispose() _loadContext.Resolving -= OnResolving; } + public Assembly Resolve(AssemblyLoadContext context, AssemblyName name) + { + return OnResolving(context, name); + } + private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) { foreach (var library in _dependencyContext.RuntimeLibraries) From 5a94cb324b476b1c1d8125ef21d1f2d33226bc6b Mon Sep 17 00:00:00 2001 From: svg2003 Date: Wed, 5 Apr 2023 20:48:11 -0700 Subject: [PATCH 042/190] Use custom context only (don't use assemblies, loaded into AppDomain). Try to load assembly from trusted list, before to start using some complex logic and enumerate AdditinalFrameworkDirectories Improve logging for assembly resolving process --- .../Internal/TestAssemblyLoadContext.cs | 37 ++++++++-- .../Internal/TestAssemblyResolver.cs | 73 ++++++++++++++++++- 2 files changed, 101 insertions(+), 9 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index 69d432fb4..9be338baf 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -12,6 +12,8 @@ namespace NUnit.Engine.Internal { internal sealed class TestAssemblyLoadContext : AssemblyLoadContext { + private static readonly Logger _log = InternalTrace.GetLogger(typeof(TestAssemblyLoadContext)); + private readonly string _testAssemblyPath; private readonly string _basePath; private readonly TestAssemblyResolver _resolver; @@ -27,19 +29,16 @@ public TestAssemblyLoadContext(string testAssemblyPath) protected override Assembly Load(AssemblyName name) { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - var loadedAssembly = assemblies.FirstOrDefault(x => x.GetName().Name == name.Name); - if (loadedAssembly != null) - { - return loadedAssembly; - } + _log.Debug("Loading {0} assembly", name); - loadedAssembly = base.Load(name); + var loadedAssembly = base.Load(name); if (loadedAssembly != null) { + _log.Info("Assembly {0} ({1}) is loaded using default base.Load()", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } + var runtimeResolverPath = _runtimeResolver.ResolveAssemblyToPath(name); if (string.IsNullOrEmpty(runtimeResolverPath) == false && File.Exists(runtimeResolverPath)) @@ -49,12 +48,15 @@ protected override Assembly Load(AssemblyName name) if (loadedAssembly != null) { + _log.Info("Assembly {0} ({1}) is loaded using the deps.json info", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } loadedAssembly = _resolver.Resolve(this, name); if (loadedAssembly != null) { + _log.Info("Assembly {0} ({1}) is loaded using the TestAssembliesResolver", name, GetAssemblyLocationInfo(loadedAssembly)); + return loadedAssembly; } @@ -68,8 +70,29 @@ protected override Assembly Load(AssemblyName name) loadedAssembly = LoadFromAssemblyPath(assemblyPath); } + if (loadedAssembly != null) + { + _log.Info("Assembly {0} ({1}) is loaded using base path", name, GetAssemblyLocationInfo(loadedAssembly)); + return loadedAssembly; + } + return loadedAssembly; } + + private static string GetAssemblyLocationInfo(Assembly assembly) + { + if (assembly.IsDynamic) + { + return $"Dynamic {assembly.FullName}"; + } + + if (string.IsNullOrEmpty(assembly.Location)) + { + return $"No location for {assembly.FullName}"; + } + + return $"{assembly.FullName} from {assembly.Location}"; + } } } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index c5c28f4d7..a9c48dedb 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -17,6 +17,8 @@ namespace NUnit.Engine.Internal { internal sealed class TestAssemblyResolver : IDisposable { + private static readonly Logger _log = InternalTrace.GetLogger(typeof(TestAssemblyResolver)); + private readonly ICompilationAssemblyResolver _assemblyResolver; private readonly DependencyContext _dependencyContext; private readonly AssemblyLoadContext _loadContext; @@ -62,6 +64,14 @@ public Assembly Resolve(AssemblyLoadContext context, AssemblyName name) private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) { + context = context ?? _loadContext; + + if (TryLoadFromTrustedPlatformAssemblies(context, name, out var loadedAssembly)) + { + _log.Info("'{0}' assembly is loaded from trusted path '{1}'", name, loadedAssembly.Location); + return loadedAssembly; + } + foreach (var library in _dependencyContext.RuntimeLibraries) { var wrapper = new CompilationLibrary( @@ -79,24 +89,81 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) foreach (var assemblyPath in assemblies) { if (name.Name == Path.GetFileNameWithoutExtension(assemblyPath)) - return _loadContext.LoadFromAssemblyPath(assemblyPath); + { + loadedAssembly = context.LoadFromAssemblyPath(assemblyPath); + _log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", + name, + loadedAssembly.Location, + library.Name); + + return loadedAssembly; + } } } + if (name.Version == null) + { + return null; + } + foreach (string frameworkDirectory in AdditionalFrameworkDirectories) { var versionDir = FindBestVersionDir(frameworkDirectory, name.Version); + if (versionDir != null) { string candidate = Path.Combine(frameworkDirectory, versionDir, name.Name + ".dll"); if (File.Exists(candidate)) - return _loadContext.LoadFromAssemblyPath(candidate); + { + loadedAssembly = context.LoadFromAssemblyPath(candidate); + _log.Info("'{0}' ({1}) assembly is loaded from AdditionalFrameworkDirectory {2} dependencies with best candidate version {3}", + name, + loadedAssembly.Location, + frameworkDirectory, + versionDir); + + return loadedAssembly; + } + else + { + _log.Debug("Best version dir for {0} is {1}, but there is no {2} file", frameworkDirectory, versionDir, candidate); + } } } + _log.Info("Cannot resolve assembly '{0}'", name); return null; } + private static bool TryLoadFromTrustedPlatformAssemblies(AssemblyLoadContext context, AssemblyName assemblyName, out Assembly loadedAssembly) + { + // https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing + loadedAssembly = null; + var trustedAssemblies = System.AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; + if (string.IsNullOrEmpty(trustedAssemblies)) + { + return false; + } + + var separator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ";" : ":"; + foreach (var assemblyPath in trustedAssemblies.Split(separator)) + { + var fileName = Path.GetFileNameWithoutExtension(assemblyPath); + if (string.Equals(fileName, assemblyName.Name, StringComparison.InvariantCultureIgnoreCase) == false) + { + continue; + } + + if (File.Exists(assemblyPath)) + { + loadedAssembly = context.LoadFromAssemblyPath(assemblyPath); + return true; + } + } + + return false; + } + private static string GetDotNetInstallDirectory() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -119,9 +186,11 @@ private static string FindBestVersionDir(string libraryDir, Version targetVersio { Version version; if (TryGetVersionFromString(Path.GetFileName(subdir), out version)) + { if (version >= targetVersion) if (bestVersion.Major == 0 || bestVersion > version) bestVersion = version; + } } return bestVersion.Major > 0 From 3a6d2e8cc8e6580f7b9b884996f001bf5dee417a Mon Sep 17 00:00:00 2001 From: svg2003 Date: Sat, 15 Apr 2023 16:17:41 -0700 Subject: [PATCH 043/190] renamed "_log" variable into "log" (cherry picked from commit f642e99e5e77e014d0854e69df1a78903906d94a) --- .../Internal/TestAssemblyLoadContext.cs | 12 ++++++------ .../Internal/TestAssemblyResolver.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index 9be338baf..cf9ed03f9 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -12,7 +12,7 @@ namespace NUnit.Engine.Internal { internal sealed class TestAssemblyLoadContext : AssemblyLoadContext { - private static readonly Logger _log = InternalTrace.GetLogger(typeof(TestAssemblyLoadContext)); + private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyLoadContext)); private readonly string _testAssemblyPath; private readonly string _basePath; @@ -29,12 +29,12 @@ public TestAssemblyLoadContext(string testAssemblyPath) protected override Assembly Load(AssemblyName name) { - _log.Debug("Loading {0} assembly", name); + log.Debug("Loading {0} assembly", name); var loadedAssembly = base.Load(name); if (loadedAssembly != null) { - _log.Info("Assembly {0} ({1}) is loaded using default base.Load()", name, GetAssemblyLocationInfo(loadedAssembly)); + log.Info("Assembly {0} ({1}) is loaded using default base.Load()", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } @@ -48,14 +48,14 @@ protected override Assembly Load(AssemblyName name) if (loadedAssembly != null) { - _log.Info("Assembly {0} ({1}) is loaded using the deps.json info", name, GetAssemblyLocationInfo(loadedAssembly)); + log.Info("Assembly {0} ({1}) is loaded using the deps.json info", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } loadedAssembly = _resolver.Resolve(this, name); if (loadedAssembly != null) { - _log.Info("Assembly {0} ({1}) is loaded using the TestAssembliesResolver", name, GetAssemblyLocationInfo(loadedAssembly)); + log.Info("Assembly {0} ({1}) is loaded using the TestAssembliesResolver", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } @@ -72,7 +72,7 @@ protected override Assembly Load(AssemblyName name) if (loadedAssembly != null) { - _log.Info("Assembly {0} ({1}) is loaded using base path", name, GetAssemblyLocationInfo(loadedAssembly)); + log.Info("Assembly {0} ({1}) is loaded using base path", name, GetAssemblyLocationInfo(loadedAssembly)); return loadedAssembly; } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index a9c48dedb..c3cfc570a 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -17,7 +17,7 @@ namespace NUnit.Engine.Internal { internal sealed class TestAssemblyResolver : IDisposable { - private static readonly Logger _log = InternalTrace.GetLogger(typeof(TestAssemblyResolver)); + private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyResolver)); private readonly ICompilationAssemblyResolver _assemblyResolver; private readonly DependencyContext _dependencyContext; @@ -68,7 +68,7 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) if (TryLoadFromTrustedPlatformAssemblies(context, name, out var loadedAssembly)) { - _log.Info("'{0}' assembly is loaded from trusted path '{1}'", name, loadedAssembly.Location); + log.Info("'{0}' assembly is loaded from trusted path '{1}'", name, loadedAssembly.Location); return loadedAssembly; } @@ -91,7 +91,7 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) if (name.Name == Path.GetFileNameWithoutExtension(assemblyPath)) { loadedAssembly = context.LoadFromAssemblyPath(assemblyPath); - _log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", + log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", name, loadedAssembly.Location, library.Name); @@ -116,7 +116,7 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) if (File.Exists(candidate)) { loadedAssembly = context.LoadFromAssemblyPath(candidate); - _log.Info("'{0}' ({1}) assembly is loaded from AdditionalFrameworkDirectory {2} dependencies with best candidate version {3}", + log.Info("'{0}' ({1}) assembly is loaded from AdditionalFrameworkDirectory {2} dependencies with best candidate version {3}", name, loadedAssembly.Location, frameworkDirectory, @@ -126,12 +126,12 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) } else { - _log.Debug("Best version dir for {0} is {1}, but there is no {2} file", frameworkDirectory, versionDir, candidate); + log.Debug("Best version dir for {0} is {1}, but there is no {2} file", frameworkDirectory, versionDir, candidate); } } } - _log.Info("Cannot resolve assembly '{0}'", name); + log.Info("Cannot resolve assembly '{0}'", name); return null; } From 097056fc407da9b9056af2416ea60e4778affdb6 Mon Sep 17 00:00:00 2001 From: Ben Randall Date: Fri, 15 Dec 2023 12:29:16 -0500 Subject: [PATCH 044/190] Use same pattern for loading unmanaged assemblies as managed assemblies. Loading of unmanaged assemblies suffers from the same issues as loading managed assemblies in that the wrong deps.json is used by default. This updates the TestAssemblyLoadContext to use the exact same pattern for loading unmanaged assemblies as managed assemblies. 1. Use the default assembly loading logic (I'm unsure if we actually need this since, in both managed and unmanaged, the base AssemblyLoadContext logic is a no-op and it's actually the VM that has some logic for loading assemblies). 2. Use an AssemblyDependencyResolver for the test assembly. 3. Check in the same folder as the test assembly (in case the dependencies are not fully specified in deps.json). Resolves #1253 --- .../Internal/TestAssemblyLoadContext.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index cf9ed03f9..800de29da 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -3,6 +3,7 @@ #if NETCOREAPP3_1_OR_GREATER using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Loader; using System.IO; using System; @@ -56,7 +57,7 @@ protected override Assembly Load(AssemblyName name) if (loadedAssembly != null) { log.Info("Assembly {0} ({1}) is loaded using the TestAssembliesResolver", name, GetAssemblyLocationInfo(loadedAssembly)); - + return loadedAssembly; } @@ -79,6 +80,45 @@ protected override Assembly Load(AssemblyName name) return loadedAssembly; } + protected override IntPtr LoadUnmanagedDll(string name) + { + log.Debug("Loading {0} unmanaged dll", name); + + IntPtr loadedDllHandle = base.LoadUnmanagedDll(name); + if (loadedDllHandle != IntPtr.Zero) + { + log.Info("Unmanaged DLL {0} is loaded using default base.LoadUnmanagedDll()", name); + return loadedDllHandle; + } + + string runtimeResolverPath = _runtimeResolver.ResolveUnmanagedDllToPath(name); + if (string.IsNullOrEmpty(runtimeResolverPath) == false && + File.Exists(runtimeResolverPath)) + { + loadedDllHandle = LoadUnmanagedDllFromPath(runtimeResolverPath); + } + + if (loadedDllHandle != IntPtr.Zero) + { + log.Info("Unmanaged DLL {0} ({1}) is loaded using the deps.json info", name, runtimeResolverPath); + return loadedDllHandle; + } + + string unmanagedDllPath = Path.Combine(_basePath, name + ".dll"); + if (File.Exists(unmanagedDllPath)) + { + loadedDllHandle = LoadUnmanagedDllFromPath(unmanagedDllPath); + } + + if (loadedDllHandle != IntPtr.Zero) + { + log.Info("Unmanaged DLL {0} ({1}) is loaded using base path", name, unmanagedDllPath); + return loadedDllHandle; + } + + return IntPtr.Zero; + } + private static string GetAssemblyLocationInfo(Assembly assembly) { if (assembly.IsDynamic) From 87d6996701216b617155b8b983f2348cc7d3e8fe Mon Sep 17 00:00:00 2001 From: Ben Randall Date: Wed, 21 Feb 2024 10:15:00 -0500 Subject: [PATCH 045/190] Add extra dependencies for .NET 5 and .NET 6 --- src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index 4727eb922..cd6775630 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -25,7 +25,7 @@ - + From 5f9515f378b2c0676f3000df4381cf5fc31ff5da Mon Sep 17 00:00:00 2001 From: Ben Randall Date: Wed, 3 Apr 2024 21:24:11 -0400 Subject: [PATCH 046/190] Add Microsoft.Extensions.DependencyModel stuff ro .NET 7 and .NET 8 which were added/merged after this PR was created. --- choco/nunit-console-runner.nuspec | 2 ++ msi/nunit/engine-files.wxi | 3 ++- nuget/runners/nunit.console-runner.nuspec | 2 ++ src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj | 4 ++-- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 72c58ba46..068f8e2af 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -104,6 +104,7 @@ + @@ -114,6 +115,7 @@ + diff --git a/msi/nunit/engine-files.wxi b/msi/nunit/engine-files.wxi index 10b0d070f..eb681e215 100644 --- a/msi/nunit/engine-files.wxi +++ b/msi/nunit/engine-files.wxi @@ -158,7 +158,8 @@ - + + + @@ -120,6 +121,7 @@ + diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index cd6775630..f47de9dfd 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -1,5 +1,5 @@  - + NUnit.Engine net20;netstandard2.0;netcoreapp3.1;net5.0;net6.0;net8.0 @@ -25,7 +25,7 @@ - + From 6602d54956ea51b12a071a613f52807164bc681c Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 28 May 2024 20:28:05 -0700 Subject: [PATCH 047/190] Get Version3X branch building --- cake/package-definitions.cake | 13 +---- cake/package-tests.cake | 56 +++++++++---------- msi/nunit/engine-files.wxi | 18 +++--- .../nunit.console-runner.netcore.nuspec | 3 +- .../nunit.engine.core.csproj | 1 + 5 files changed, 41 insertions(+), 50 deletions(-) diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake index f8786f0c7..363b00130 100644 --- a/cake/package-definitions.cake +++ b/cake/package-definitions.cake @@ -25,32 +25,21 @@ public void InitializePackageDefinitions(ICakeContext context) Net40Test, Net40X86Test, Net35PlusNet40Test, - NetCore21Test, NetCore31Test, Net50Test, Net60Test, - NetCore21PlusNetCore31Test, - NetCore21PlusNetCore31PlusNet50PlusNet60Test, + Net50PlusNet60Test, Net40PlusNet60Test }; - if (dotnetX86Available) - { - StandardRunnerTests.Add(NetCore21X86Test); - StandardRunnerTests.Add(NetCore31X86Test); - } - // Tests run for the NETCORE runner package var NetCoreRunnerTests = new List { - NetCore21Test, NetCore31Test, Net50Test, Net60Test, Net70Test, Net80Test, - NetCore21PlusNetCore31Test, - NetCore21PlusNetCore31PlusNet50PlusNet60Test }; AllPackages.AddRange(new PackageDefinition[] { diff --git a/cake/package-tests.cake b/cake/package-tests.cake index 4683be511..58ab79dd0 100644 --- a/cake/package-tests.cake +++ b/cake/package-tests.cake @@ -72,35 +72,35 @@ static PackageTest NetCore31Test = new PackageTest( "netcoreapp3.1/mock-assembly.dll", MockAssemblyExpectedResult(1)); -static PackageTest NetCore31X86Test = new PackageTest( - "NetCore31X86Test", - "Run mock-assembly-x86.dll under .NET Core 3.1", - "netcoreapp3.1/mock-assembly-x86.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest NetCore21Test = new PackageTest( - "NetCore21Test", - "Run mock-assembly.dll targeting .NET Core 2.1", - "netcoreapp2.1/mock-assembly.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest NetCore21X86Test = new PackageTest( - "NetCore21X86Test", - "Run mock-assembly-x86.dll under .NET Core 2.1", - "netcoreapp2.1/mock-assembly-x86.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest NetCore21PlusNetCore31Test = new PackageTest( - "NetCore21PlusNetCore31Test", - "Run two copies of mock-assembly together", - "netcoreapp2.1/mock-assembly.dll netcoreapp3.1/mock-assembly.dll", - MockAssemblyExpectedResult(2)); - -static PackageTest NetCore21PlusNetCore31PlusNet50PlusNet60Test = new PackageTest( - "NetCore21PlusNetCore31PlusNet50PlusNet60Test", +//static PackageTest NetCore31X86Test = new PackageTest( +// "NetCore31X86Test", +// "Run mock-assembly-x86.dll under .NET Core 3.1", +// "netcoreapp3.1/mock-assembly-x86.dll", +// MockAssemblyExpectedResult(1)); + +//static PackageTest NetCore21Test = new PackageTest( +// "NetCore21Test", +// "Run mock-assembly.dll targeting .NET Core 2.1", +// "netcoreapp2.1/mock-assembly.dll", +// MockAssemblyExpectedResult(1)); + +//static PackageTest NetCore21X86Test = new PackageTest( +// "NetCore21X86Test", +// "Run mock-assembly-x86.dll under .NET Core 2.1", +// "netcoreapp2.1/mock-assembly-x86.dll", +// MockAssemblyExpectedResult(1)); + +//static PackageTest NetCore21PlusNetCore31Test = new PackageTest( +// "NetCore21PlusNetCore31Test", +// "Run two copies of mock-assembly together", +// "netcoreapp2.1/mock-assembly.dll netcoreapp3.1/mock-assembly.dll", +// MockAssemblyExpectedResult(2)); + +static PackageTest Net50PlusNet60Test = new PackageTest( + "Net50PlusNet60Test", "Run four copies of mock-assembly together", - "netcoreapp2.1/mock-assembly.dll netcoreapp3.1/mock-assembly.dll net5.0/mock-assembly.dll net6.0/mock-assembly.dll net7.0/mock-assembly.dll net8.0/mock-assembly.dll", - MockAssemblyExpectedResult(4)); + "net5.0/mock-assembly.dll net6.0/mock-assembly.dll",//" net7.0/mock-assembly.dll net8.0/mock-assembly.dll", + MockAssemblyExpectedResult(2)); static PackageTest Net40PlusNet60Test = new PackageTest( "Net40PlusNet60Test", diff --git a/msi/nunit/engine-files.wxi b/msi/nunit/engine-files.wxi index eb681e215..6422781b5 100644 --- a/msi/nunit/engine-files.wxi +++ b/msi/nunit/engine-files.wxi @@ -94,9 +94,9 @@ + Source="$(var.InstallImage)bin/agents/netcoreapp3.1/nunit-agent.dll" /> + Source="$(var.InstallImage)bin/agents/netcoreapp3.1/nunit-agent.dll.config" /> + Source="$(var.InstallImage)bin/agents/netcoreapp3.1/Microsoft.Extensions.DependencyModel.dll" /> + Source="$(var.InstallImage)bin/agents/net5.0/nunit-agent.dll" /> + Source="$(var.InstallImage)bin/agents/net5.0/nunit-agent.dll.config" /> + Source="$(var.InstallImage)bin/agents/net6.0/nunit-agent.dll" /> + Source="$(var.InstallImage)bin/agents/net6.0/nunit-agent.dll.config" /> + Source="$(var.InstallImage)bin/agents/net6.0/nunit-agent.deps.json" /> + Source="$(var.InstallImage)bin/agents/net6.0/nunit-agent.runtimeconfig.json" /> - + @@ -61,6 +61,7 @@ + diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index f47de9dfd..b40bd5cb9 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -8,6 +8,7 @@ ..\..\nunit.snk portable true + true From ad036512658d9b93a0edfc320d7f5910db404235 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 29 May 2024 03:00:33 -0700 Subject: [PATCH 048/190] Fix version3X HEAD so it builds --- build.cake | 10 +++---- cake/package-definitions.cake | 1 - cake/package-tests.cake | 26 +------------------ .../Services/ExtensionManagerTests.cs | 4 +-- .../nunit.engine.core.tests.csproj | 2 +- .../nunit.engine.tests.csproj | 4 +-- 6 files changed, 9 insertions(+), 38 deletions(-) diff --git a/build.cake b/build.cake index d882731aa..498be2407 100644 --- a/build.cake +++ b/build.cake @@ -114,11 +114,6 @@ public void BuildSolution() .WithProperty("TargetFramework", "netstandard2.0") .WithProperty("PublishDir", BIN_DIR + "netstandard2.0")); - DisplayBanner("Publishing ENGINE TESTS Project for NETCOREAPP2.1"); - MSBuild(ENGINE_TESTS_PROJECT, CreateMSBuildSettings("Publish") - .WithProperty("TargetFramework", "netcoreapp2.1") - .WithProperty("PublishDir", BIN_DIR + "netcoreapp2.1")); - // TODO: May not be needed foreach (var framework in new[] { "netcoreapp3.1", "net5.0" }) { @@ -129,6 +124,7 @@ public void BuildSolution() } } +// TODO: Test this on linux to see if changes are needed private void BuildEachProjectSeparately() { DotNetRestore(SOLUTION_FILE); @@ -237,7 +233,7 @@ Task("TestNetStandard20EngineCore") .OnError(exception => { UnreportedErrors.Add(exception.Message); }) .Does(() => { - RunDotnetNUnitLiteTests(NETCORE_ENGINE_CORE_TESTS, "netcoreapp2.1"); + RunDotnetNUnitLiteTests(NETCORE_ENGINE_CORE_TESTS, "netcoreapp3.1"); }); ////////////////////////////////////////////////////////////////////// @@ -302,7 +298,7 @@ Task("TestNetStandard20Engine") .OnError(exception => { UnreportedErrors.Add(exception.Message); }) .Does(() => { - RunDotnetNUnitLiteTests(NETCORE_ENGINE_TESTS, "netcoreapp2.1"); + RunDotnetNUnitLiteTests(NETCORE_ENGINE_TESTS, "netcoreapp3.1"); }); ////////////////////////////////////////////////////////////////////// diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake index 363b00130..04f431216 100644 --- a/cake/package-definitions.cake +++ b/cake/package-definitions.cake @@ -151,7 +151,6 @@ public void InitializePackageDefinitions(ICakeContext context) HasDirectory("bin/net20").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), HasDirectory("bin/net35").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), HasDirectory("bin/netstandard2.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), - HasDirectory("bin/netcoreapp2.1").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), //HasDirectory("bin/net5.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), HasDirectory("bin/agents/net20").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), diff --git a/cake/package-tests.cake b/cake/package-tests.cake index 58ab79dd0..26eee6af3 100644 --- a/cake/package-tests.cake +++ b/cake/package-tests.cake @@ -72,33 +72,9 @@ static PackageTest NetCore31Test = new PackageTest( "netcoreapp3.1/mock-assembly.dll", MockAssemblyExpectedResult(1)); -//static PackageTest NetCore31X86Test = new PackageTest( -// "NetCore31X86Test", -// "Run mock-assembly-x86.dll under .NET Core 3.1", -// "netcoreapp3.1/mock-assembly-x86.dll", -// MockAssemblyExpectedResult(1)); - -//static PackageTest NetCore21Test = new PackageTest( -// "NetCore21Test", -// "Run mock-assembly.dll targeting .NET Core 2.1", -// "netcoreapp2.1/mock-assembly.dll", -// MockAssemblyExpectedResult(1)); - -//static PackageTest NetCore21X86Test = new PackageTest( -// "NetCore21X86Test", -// "Run mock-assembly-x86.dll under .NET Core 2.1", -// "netcoreapp2.1/mock-assembly-x86.dll", -// MockAssemblyExpectedResult(1)); - -//static PackageTest NetCore21PlusNetCore31Test = new PackageTest( -// "NetCore21PlusNetCore31Test", -// "Run two copies of mock-assembly together", -// "netcoreapp2.1/mock-assembly.dll netcoreapp3.1/mock-assembly.dll", -// MockAssemblyExpectedResult(2)); - static PackageTest Net50PlusNet60Test = new PackageTest( "Net50PlusNet60Test", - "Run four copies of mock-assembly together", + "Run mock-assembly under .NET 5.0 and 6.0 together", "net5.0/mock-assembly.dll net6.0/mock-assembly.dll",//" net7.0/mock-assembly.dll net8.0/mock-assembly.dll", MockAssemblyExpectedResult(2)); diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index d0bd6a3f3..86b3b59bc 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -153,7 +153,7 @@ public void SkipsGracefullyLoadingOtherFrameworkExtensionAssembly() var assemblyName = Path.Combine(GetSiblingDirectory("net35"), "nunit.engine.core.tests.exe"); #else // Attempt to load the .NET Core 2.1 version of the extensions from the .NET 3.5 tests - var assemblyName = Path.Combine(GetSiblingDirectory("netcoreapp2.1"), "nunit.engine.core.tests.dll"); + var assemblyName = Path.Combine(GetSiblingDirectory("netcoreapp3.1"), "nunit.engine.core.tests.dll"); #endif Assert.That(assemblyName, Does.Exist); Console.WriteLine($"{assemblyName} does exist"); @@ -242,7 +242,7 @@ public static IEnumerable InvalidTargetFrameworkCombos() Assembly netFramework = typeof(ExtensionService).Assembly; var extNetStandard = new ExtensionAssembly(Path.Combine(GetSiblingDirectory("netstandard2.0"), "nunit.engine.dll"), false); - var extNetCore = new ExtensionAssembly(Path.Combine(GetSiblingDirectory("netcoreapp2.1"), "nunit.engine.tests.dll"), false); + var extNetCore = new ExtensionAssembly(Path.Combine(GetSiblingDirectory("netcoreapp3.1"), "nunit.engine.tests.dll"), false); yield return new TestCaseData(new FrameworkCombo(netFramework, extNetCore)).SetName("InvalidCombo(.NET Framework, .NET Core)"); #endif diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 388576ee5..676667fde 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Core.Tests - net35;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net8.0 + net35;netcoreapp3.1;net5.0;net6.0;net8.0 Exe true ..\..\nunit.snk diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index 80b65e858..97e45dd9d 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Tests - net35;netcoreapp2.1 + net35;netcoreapp3.1 Exe true ..\..\nunit.snk @@ -29,7 +29,7 @@ - + From 02d8e0d792b11c2cf577618fd3f89b8c09484d7e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 31 May 2024 11:04:11 -0700 Subject: [PATCH 049/190] Changes from code review --- GitVersion.yml | 3 ++- build.cake | 2 +- .../nunit.engine.core.tests/Services/ExtensionManagerTests.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/GitVersion.yml b/GitVersion.yml index 8bd0940c1..b8d28d02e 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,10 +1,11 @@ -next-version: 3.15.5 +next-version: 3.17.1 mode: ContinuousDelivery legacy-semver-padding: 5 build-metadata-padding: 5 commits-since-version-source-padding: 5 branches: master: + regex: ^(main|version3X)$ tag: dev release: tag: pre diff --git a/build.cake b/build.cake index 498be2407..2ec9efdc4 100644 --- a/build.cake +++ b/build.cake @@ -779,7 +779,7 @@ Task("BuildTestAndPackage") Task("Appveyor") .Description("Target we run in our AppVeyor CI") .IsDependentOn("BuildTestAndPackage") - //.IsDependentOn("PublishPackages") + .IsDependentOn("PublishPackages") .IsDependentOn("CreateDraftRelease") .IsDependentOn("CreateProductionRelease"); diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index 86b3b59bc..ab74dfb51 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -152,7 +152,7 @@ public void SkipsGracefullyLoadingOtherFrameworkExtensionAssembly() // Attempt to load the .NET 3.5 version of the extensions from the .NET Core 2.0 tests var assemblyName = Path.Combine(GetSiblingDirectory("net35"), "nunit.engine.core.tests.exe"); #else - // Attempt to load the .NET Core 2.1 version of the extensions from the .NET 3.5 tests + // Attempt to load the .NET Core 3.1 version of the extensions from the .NET 3.5 tests var assemblyName = Path.Combine(GetSiblingDirectory("netcoreapp3.1"), "nunit.engine.core.tests.dll"); #endif Assert.That(assemblyName, Does.Exist); From 823da3ddcaecdc16f091aeccc053c5dc4da571cc Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 6 Jun 2024 03:54:17 -0700 Subject: [PATCH 050/190] Move AvailableRuntimes to RuntimeFrameworkService --- GitVersion.yml | 2 +- .../RuntimeFrameworkTests.cs | 63 ++--- .../nunit.engine.core/RuntimeFramework.cs | 234 +----------------- .../Services/RuntimeFrameworkServiceTests.cs | 5 - .../Services/RuntimeFrameworkService.cs | 54 ++-- .../RuntimeLocators/MonoRuntimeLocator.cs | 137 ++++++++++ .../RuntimeLocators/NetCoreRuntimeLocator.cs | 127 ++++++++++ .../RuntimeLocators/NetFxRuntimeLocator.cs | 113 +++++++++ .../nunit.engine/Services/TestAgency.cs | 3 +- 9 files changed, 453 insertions(+), 285 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs create mode 100644 src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs create mode 100644 src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs diff --git a/GitVersion.yml b/GitVersion.yml index b8d28d02e..c02e85371 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -5,7 +5,7 @@ build-metadata-padding: 5 commits-since-version-source-padding: 5 branches: master: - regex: ^(main|version3X)$ + regex: ^(main|version4)$ tag: dev release: tag: pre diff --git a/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs b/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs index 3d1b650d5..6dc24c6c1 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs @@ -30,41 +30,42 @@ public void CurrentFrameworkHasBuildSpecified() Assert.That(RuntimeFramework.CurrentFramework.ClrVersion.Build, Is.GreaterThan(0)); } - [Test] - public void CurrentFrameworkMustBeAvailable() - { - var current = RuntimeFramework.CurrentFramework; - Console.WriteLine("Current framework is {0} ({1})", current.DisplayName, current.Id); - Assert.That(current.IsAvailable, "{0} not available", current); - } + // TODO: The commented tests should be in RuntimeFrameworkService Tests + //[Test] + //public void CurrentFrameworkMustBeAvailable() + //{ + // var current = RuntimeFramework.CurrentFramework; + // Console.WriteLine("Current framework is {0} ({1})", current.DisplayName, current.Id); + // Assert.That(current.IsAvailable, "{0} not available", current); + //} - [Test] - public void AvailableFrameworksList() - { - RuntimeFramework[] available = RuntimeFramework.AvailableFrameworks; - Assert.That(RuntimeFramework.AvailableFrameworks.Length, Is.GreaterThan(0) ); - foreach (var framework in RuntimeFramework.AvailableFrameworks) - Console.WriteLine("Available: {0}", framework.DisplayName); - } + //[Test] + //public void AvailableFrameworksList() + //{ + // RuntimeFramework[] available = RuntimeFramework.AvailableFrameworks; + // Assert.That(RuntimeFramework.AvailableFrameworks.Length, Is.GreaterThan(0) ); + // foreach (var framework in RuntimeFramework.AvailableFrameworks) + // Console.WriteLine("Available: {0}", framework.DisplayName); + //} - [Test] - public void AvailableFrameworksList_IncludesCurrentFramework() - { - foreach (var framework in RuntimeFramework.AvailableFrameworks) - if (RuntimeFramework.CurrentFramework.Supports(framework)) - return; + //[Test] + //public void AvailableFrameworksList_IncludesCurrentFramework() + //{ + // foreach (var framework in RuntimeFramework.AvailableFrameworks) + // if (RuntimeFramework.CurrentFramework.Supports(framework)) + // return; - Assert.Fail("CurrentFramework not listed as available"); - } + // Assert.Fail("CurrentFramework not listed as available"); + //} - [Test] - public void AvailableFrameworksList_ContainsNoDuplicates() - { - var names = new List(); - foreach (var framework in RuntimeFramework.AvailableFrameworks) - names.Add(framework.DisplayName); - Assert.That(names, Is.Unique); - } + //[Test] + //public void AvailableFrameworksList_ContainsNoDuplicates() + //{ + // var names = new List(); + // foreach (var framework in RuntimeFramework.AvailableFrameworks) + // names.Add(framework.DisplayName); + // Assert.That(names, Is.Unique); + //} [TestCaseSource(nameof(frameworkData))] public void CanCreateUsingFrameworkVersion(FrameworkData data) diff --git a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs index c04ed5766..bb1d8b111 100644 --- a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs +++ b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs @@ -98,7 +98,7 @@ public string Id #endregion private static RuntimeFramework _currentFramework; - private static List _availableFrameworks; + //private static List _availableFrameworks; private static readonly string DEFAULT_WINDOWS_MONO_DIR = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Mono"); @@ -314,20 +314,6 @@ public static RuntimeFramework CurrentFramework } } - /// - /// Gets an array of all available frameworks - /// - public static RuntimeFramework[] AvailableFrameworks - { - get - { - if (_availableFrameworks == null) - FindAvailableFrameworks(); - - return _availableFrameworks.ToArray(); - } - } - /// /// The version of Mono in use or null if no Mono runtime /// is available on this machine. @@ -356,25 +342,7 @@ public static string MonoExePath } } - /// - /// Returns true if the current RuntimeFramework is available. - /// In the current implementation, only Mono and Microsoft .NET - /// are supported. - /// - /// True if it's available, false if not - public bool IsAvailable - { - get - { - foreach (RuntimeFramework framework in AvailableFrameworks) - if (framework.Supports(this)) - return true; - - return false; - } - } - - /// + /// /// Parses a string representing a RuntimeFramework. /// The string may be just a RuntimeType name or just /// a Version or a hyphenated RuntimeType-Version or @@ -548,204 +516,6 @@ private static string GetMonoPrefixFromAssembly(Assembly assembly) return prefix; } - - private static void FindAvailableFrameworks() - { - _availableFrameworks = new List(); - - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - _availableFrameworks.AddRange(DotNetFrameworkLocator.FindDotNetFrameworks()); - - FindDefaultMonoFramework(); - FindDotNetCoreFrameworks(); - } - - private static void FindDefaultMonoFramework() - { - if (CurrentFramework.Runtime == RuntimeType.Mono) - UseCurrentMonoFramework(); - else - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - FindBestMonoFrameworkOnWindows(); - } - - private static void UseCurrentMonoFramework() - { - Debug.Assert(CurrentFramework.Runtime == RuntimeType.Mono && MonoPrefix != null && MonoVersion != null); - - // Multiple profiles are no longer supported with Mono 4.0 - if (MonoVersion.Major < 4 && FindAllMonoProfiles() > 0) - return; - - // If Mono 4.0+ or no profiles found, just use current runtime - _availableFrameworks.Add(RuntimeFramework.CurrentFramework); - } - - private static void FindBestMonoFrameworkOnWindows() - { - // First, look for recent frameworks that use the Software\Mono Key - RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Mono"); - - if (key != null && (int)key.GetValue("Installed", 0) == 1) - { - string version = key.GetValue("Version") as string; - MonoPrefix = key.GetValue("SdkInstallRoot") as string; - - if (version != null) - { - MonoVersion = new Version(version); - AddMonoFramework(new Version(4, 5), null); - return; - } - } - - // Some later 3.x Mono releases didn't use the registry - // so check in standard location for them. - if (Directory.Exists(DEFAULT_WINDOWS_MONO_DIR)) - { - MonoPrefix = DEFAULT_WINDOWS_MONO_DIR; - AddMonoFramework(new Version(4, 5), null); - return; - } - - // Look in the Software\Novell key for older versions - key = Registry.LocalMachine.OpenSubKey(@"Software\Novell\Mono"); - if (key != null) - { - string version = key.GetValue("DefaultCLR") as string; - if (version != null) - { - RegistryKey subKey = key.OpenSubKey(version); - if (subKey != null) - { - MonoPrefix = subKey.GetValue("SdkInstallRoot") as string; - MonoVersion = new Version(version); - - FindAllMonoProfiles(); - } - } - } - } - - private static int FindAllMonoProfiles() - { - int count = 0; - - if (MonoPrefix != null) - { - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/1.0/mscorlib.dll"))) - { - AddMonoFramework(new Version(1, 1, 4322), "1.0"); - count++; - } - - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/2.0/mscorlib.dll"))) - { - AddMonoFramework(new Version(2, 0), "2.0"); - count++; - } - - if (Directory.Exists(Path.Combine(MonoPrefix, "lib/mono/3.5"))) - { - AddMonoFramework(new Version(3, 5), "3.5"); - count++; - } - - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.0/mscorlib.dll"))) - { - AddMonoFramework(new Version(4, 0), "4.0"); - count++; - } - - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.5/mscorlib.dll"))) - { - AddMonoFramework(new Version(4, 5), "4.5"); - count++; - } - } - - return count; - } - - private static void AddMonoFramework(Version frameworkVersion, string profile) - { - var framework = new RuntimeFramework(RuntimeType.Mono, frameworkVersion) - { - Profile = profile, - DisplayName = MonoVersion != null - ? "Mono " + MonoVersion.ToString() + " - " + profile + " Profile" - : "Mono - " + profile + " Profile" - }; - - _availableFrameworks.Add(framework); - } - - private static void FindDotNetCoreFrameworks() - { - const string WINDOWS_INSTALL_DIR = "C:\\Program Files\\dotnet\\"; - const string LINUX_INSTALL_DIR = "/usr/shared/dotnet/"; - string INSTALL_DIR = Path.DirectorySeparatorChar == '\\' - ? WINDOWS_INSTALL_DIR - : LINUX_INSTALL_DIR; - - if (!Directory.Exists(INSTALL_DIR)) - return; - if (!File.Exists(Path.Combine(INSTALL_DIR, "dotnet.exe"))) - return; - - string runtimeDir = Path.Combine(INSTALL_DIR, Path.Combine("shared", "Microsoft.NETCore.App")); - if (!Directory.Exists(runtimeDir)) - return; - - var dirList = new DirectoryInfo(runtimeDir).GetDirectories(); - var dirNames = new List(); - foreach (var dir in dirList) - dirNames.Add(dir.Name); - var runtimes = GetNetCoreRuntimesFromDirectoryNames(dirNames); - - _availableFrameworks.AddRange(runtimes); - } - - // Deal with oddly named directories, which may sometimes appear when previews are installed - internal static IList GetNetCoreRuntimesFromDirectoryNames(IEnumerable dirNames) - { - const string VERSION_CHARS = ".0123456789"; - var runtimes = new List(); - - foreach (string dirName in dirNames) - { - int len = 0; - foreach (char c in dirName) - { - if (VERSION_CHARS.IndexOf(c) >= 0) - len++; - else - break; - } - - if (len == 0) - continue; - - Version fullVersion = null; - try - { - fullVersion = new Version(dirName.Substring(0, len)); - } - catch - { - continue; - } - - var newVersion = new Version(fullVersion.Major, fullVersion.Minor); - int count = runtimes.Count; - if (count > 0 && runtimes[count - 1].FrameworkVersion == newVersion) - continue; - - runtimes.Add(new RuntimeFramework(RuntimeType.NetCore, newVersion)); - } - - return runtimes; - } } } #endif diff --git a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs index 659c378af..d61517e06 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs @@ -2,11 +2,8 @@ #if NETFRAMEWORK using System; -using System.Collections.Generic; using System.IO; -using System.Text; using NUnit.Framework; -using NUnit.Tests; namespace NUnit.Engine.Services.Tests { @@ -76,8 +73,6 @@ public void EngineOptionPreferredOverImageTarget(string framework, int majorVers public void RuntimeFrameworkIsSetForSubpackages() { //Runtime Service verifies that requested frameworks are available, therefore this test can only currently be run on platforms with both CLR v2 and v4 available - Assume.That(new RuntimeFramework(RuntimeType.Net, new Version("2.0.50727")), Has.Property(nameof(RuntimeFramework.IsAvailable)).True); - Assume.That(new RuntimeFramework(RuntimeType.Net, new Version("4.0.30319")), Has.Property(nameof(RuntimeFramework.IsAvailable)).True); var topLevelPackage = new TestPackage(new [] {"a.dll", "b.dll"}); diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs index 025721c9b..dfe8d82b5 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -7,6 +7,7 @@ using TestCentric.Metadata; using NUnit.Common; using NUnit.Engine.Internal; +using NUnit.Engine.Services.RuntimeLocators; #if NET20 using FrameworkName = NUnit.Engine.Compatibility.FrameworkName; #endif @@ -17,18 +18,12 @@ public class RuntimeFrameworkService : Service, IRuntimeFrameworkService, IAvail { static readonly Logger log = InternalTrace.GetLogger(typeof(RuntimeFrameworkService)); - // HACK: This line forces RuntimeFramework to initialize the static property - // AvailableFrameworks before it is accessed by multiple threads. See comment - // on RuntimeFramework class for a more detailled explanation. - static readonly RuntimeFramework[] _availableRuntimes = RuntimeFramework.AvailableFrameworks; + private List _availableRuntimes = new List(); /// /// Gets a list of available runtimes. /// - public IList AvailableRuntimes - { - get { return _availableRuntimes; } - } + public IList AvailableRuntimes => _availableRuntimes.ToArray(); /// /// Returns true if the runtime framework represented by @@ -43,7 +38,7 @@ public bool IsAvailable(string name) if (!RuntimeFramework.TryParse(name, out RuntimeFramework requestedFramework)) throw new NUnitEngineException("Invalid or unknown framework requested: " + name); - foreach (var framework in RuntimeFramework.AvailableFrameworks) + foreach (var framework in _availableRuntimes) if (FrameworksMatch(requestedFramework, framework)) return true; @@ -159,12 +154,12 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) targetVersion = frameworkName.Version; } - if (!new RuntimeFramework(targetRuntime, targetVersion).IsAvailable) - { - log.Debug("Preferred version {0} is not installed or this NUnit installation does not support it", targetVersion); - if (targetVersion < currentFramework.FrameworkVersion) - targetVersion = currentFramework.FrameworkVersion; - } + //if (!new RuntimeFramework(targetRuntime, targetVersion).IsAvailable) + //{ + // log.Debug("Preferred version {0} is not installed or this NUnit installation does not support it", targetVersion); + // if (targetVersion < currentFramework.FrameworkVersion) + // targetVersion = currentFramework.FrameworkVersion; + //} RuntimeFramework targetFramework = new RuntimeFramework(targetRuntime, targetVersion); package.Settings[EnginePackageSettings.TargetRuntimeFramework] = targetFramework.ToString(); @@ -173,6 +168,20 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) return targetFramework; } + public override void StartService() + { + try + { + FindAvailableRuntimes(); + } + catch + { + Status = ServiceStatus.Error; + throw; + } + + Status = ServiceStatus.Started; + } /// /// Returns the best available framework that matches a target framework. @@ -194,6 +203,21 @@ public RuntimeFramework GetBestAvailableFramework(RuntimeFramework target) return result; } + private void FindAvailableRuntimes() + { + _availableRuntimes = new List(); + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + var netFxRuntimes = NetFxRuntimeLocator.FindRuntimes(); + _availableRuntimes.AddRange(netFxRuntimes); + } + + _availableRuntimes.AddRange(MonoRuntimeLocator.FindRuntimes()); + + _availableRuntimes.AddRange(NetCoreRuntimeLocator.FindRuntimes()); + } + /// /// Use TestCentric.Metadata to get information about all assemblies and /// apply it to the package using special internal keywords. diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs new file mode 100644 index 000000000..13f7837b1 --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs @@ -0,0 +1,137 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace NUnit.Engine.Services.RuntimeLocators +{ + public static class MonoRuntimeLocator + { + public static IEnumerable FindRuntimes() + { + var current = RuntimeFramework.CurrentFramework; + if (current.Runtime == RuntimeType.Mono) + { + yield return current; + } + //else + //if (Environment.OSVersion.Platform == PlatformID.Win32NT) + // FindBestMonoFrameworkOnWindows(); + } + + //private static void UseCurrentMonoFramework() + //{ + // Debug.Assert(CurrentFramework.Runtime == Runtime.Mono && MonoPrefix != null && MonoVersion != null); + + // // Multiple profiles are no longer supported with Mono 4.0 + // if (RuntimeFramework.MonoVersion.Major < 4 && FindAllMonoProfiles() > 0) + // return; + + // // If Mono 4.0+ or no profiles found, just use current runtime + // _availableFrameworks.Add(RuntimeFramework.CurrentFramework); + //} + + //private static void FindBestMonoFrameworkOnWindows() + //{ + // // First, look for recent frameworks that use the Software\Mono Key + // RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Mono"); + + // if (key != null && (int)key.GetValue("Installed", 0) == 1) + // { + // string version = key.GetValue("Version") as string; + // MonoPrefix = key.GetValue("SdkInstallRoot") as string; + + // if (version != null) + // { + // MonoVersion = new Version(version); + // AddMonoFramework(new Version(4, 5), null); + // return; + // } + // } + + // // Some later 3.x Mono releases didn't use the registry + // // so check in standard location for them. + // if (Directory.Exists(DEFAULT_WINDOWS_MONO_DIR)) + // { + // MonoPrefix = DEFAULT_WINDOWS_MONO_DIR; + // AddMonoFramework(new Version(4, 5), null); + // return; + // } + + // // Look in the Software\Novell key for older versions + // key = Registry.LocalMachine.OpenSubKey(@"Software\Novell\Mono"); + // if (key != null) + // { + // string version = key.GetValue("DefaultCLR") as string; + // if (version != null) + // { + // RegistryKey subKey = key.OpenSubKey(version); + // if (subKey != null) + // { + // MonoPrefix = subKey.GetValue("SdkInstallRoot") as string; + // MonoVersion = new Version(version); + + // FindAllMonoProfiles(); + // } + // } + // } + //} + + //private static int FindAllMonoProfiles() + //{ + // int count = 0; + + // if (MonoPrefix != null) + // { + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/1.0/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(1, 1, 4322), "1.0"); + // count++; + // } + + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/2.0/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(2, 0), "2.0"); + // count++; + // } + + // if (Directory.Exists(Path.Combine(MonoPrefix, "lib/mono/3.5"))) + // { + // AddMonoFramework(new Version(3, 5), "3.5"); + // count++; + // } + + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.0/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(4, 0), "4.0"); + // count++; + // } + + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.5/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(4, 5), "4.5"); + // count++; + // } + // } + + // return count; + //} + + //private static void AddMonoFramework(Version frameworkVersion, string profile) + //{ + // var framework = new RuntimeFramework(Runtime.Mono, frameworkVersion) + // { + // Profile = profile, + // DisplayName = MonoVersion != null + // ? "Mono " + MonoVersion.ToString() + " - " + profile + " Profile" + // : "Mono - " + profile + " Profile" + // }; + + // _availableFrameworks.Add(framework); + //} + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs new file mode 100644 index 000000000..537a14402 --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs @@ -0,0 +1,127 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace NUnit.Engine.Services.RuntimeLocators +{ + public static class NetCoreRuntimeLocator + { + public static IEnumerable FindRuntimes() + { + List alreadyFound = new List(); + + foreach (string dirName in GetRuntimeDirectories()) + { + Version newVersion; + if (TryGetVersionFromString(dirName, out newVersion) && !alreadyFound.Contains(newVersion)) + { + alreadyFound.Add(newVersion); + yield return new RuntimeFramework(RuntimeType.NetCore, newVersion); + } + } + + foreach (string line in GetRuntimeList()) + { + Version newVersion; + if (TryGetVersionFromString(line, out newVersion) && !alreadyFound.Contains(newVersion)) + { + alreadyFound.Add(newVersion); + yield return new RuntimeFramework(RuntimeType.NetCore, newVersion); + } + } + } + + private static IEnumerable GetRuntimeDirectories() + { + string installDir = GetDotNetInstallDirectory(); + + if (installDir != null && Directory.Exists(installDir) && + File.Exists(Path.Combine(installDir, "dotnet.exe"))) + { + string runtimeDir = Path.Combine(installDir, Path.Combine("shared", "Microsoft.NETCore.App")); + if (Directory.Exists(runtimeDir)) + foreach (var dir in new DirectoryInfo(runtimeDir).GetDirectories()) + yield return dir.Name; + } + } + + private static IEnumerable GetRuntimeList() + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = "--list-runtimes", + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + try + { + process.Start(); + } + catch (Exception) + { + // Failed to start dotnet command. Assume no versions are installed and just r eturn just return + yield break; + } + + const string PREFIX = "Microsoft.NETCore.App "; + const int VERSION_START = 22; + + while (!process.StandardOutput.EndOfStream) + { + var line = process.StandardOutput.ReadLine(); + if (line.StartsWith(PREFIX)) + yield return line.Substring(VERSION_START); + } + } + + private static bool TryGetVersionFromString(string text, out Version newVersion) + { + const string VERSION_CHARS = ".0123456789"; + + int len = 0; + foreach (char c in text) + { + if (VERSION_CHARS.IndexOf(c) >= 0) + len++; + else + break; + } + + try + { + var fullVersion = new Version(text.Substring(0, len)); + newVersion = new Version(fullVersion.Major, fullVersion.Minor); + return true; + } + catch + { + newVersion = new Version(); + return false; + } + } + + internal static string GetDotNetInstallDirectory() + { + if (Path.DirectorySeparatorChar == '\\') + { + RegistryKey key = + Registry.LocalMachine.OpenSubKey(@"Software\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); + return (string)key?.GetValue("Path"); + } + else + return "/usr/shared/dotnet/"; + } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs new file mode 100644 index 000000000..944a0838e --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs @@ -0,0 +1,113 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using Microsoft.Win32; + +namespace NUnit.Engine.Services.RuntimeLocators +{ + public static class NetFxRuntimeLocator + { + // Note: this method cannot be generalized past V4, because (a) it has + // specific code for detecting .NET 4.5 and (b) we don't know what + // microsoft will do in the future + public static IEnumerable FindRuntimes() + { + // Handle Version 1.0, using a different registry key + foreach (var framework in FindExtremelyOldDotNetFrameworkVersions()) + yield return framework; + + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\NET Framework Setup\NDP"); + if (key != null) + { + foreach (string name in key.GetSubKeyNames()) + { + if (name.StartsWith("v") && name != "v4.0") // v4.0 is a duplicate, legacy key + { + var versionKey = key.OpenSubKey(name); + if (versionKey == null) continue; + + if (name.StartsWith("v4", StringComparison.Ordinal)) + { + // Version 4 and 4.5 + foreach (var framework in FindDotNetFourFrameworkVersions(versionKey)) + { + yield return framework; + } + } + else if (CheckInstallDword(versionKey)) + { + // Versions 1.1, 2.0, 3.0 and 3.5 are possible here + yield return new RuntimeFramework(RuntimeType.Net, new Version(name.Substring(1, 3))); + } + } + } + } + } + + private static IEnumerable FindExtremelyOldDotNetFrameworkVersions() + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\.NETFramework\policy\v1.0"); + if (key == null) + yield break; + + foreach (var build in key.GetValueNames()) + yield return new RuntimeFramework(RuntimeType.Net, new Version("1.0." + build)); + } + + private struct MinimumRelease + { + public readonly int Release; + public readonly Version Version; + + public MinimumRelease(int release, Version version) + { + Release = release; + Version = version; + } + } + + private static readonly MinimumRelease[] ReleaseTable = new MinimumRelease[] + { + new MinimumRelease(378389, new Version(4, 5)), + new MinimumRelease(378675, new Version(4, 5, 1)), + new MinimumRelease(379893, new Version(4, 5, 2)), + new MinimumRelease(393295, new Version(4, 6)), + new MinimumRelease(394254, new Version(4, 6, 1)), + new MinimumRelease(394802, new Version(4, 6, 2)), + new MinimumRelease(460798, new Version(4, 7)), + new MinimumRelease(461308, new Version(4, 7, 1)), + new MinimumRelease(461808, new Version(4, 7, 2)), + new MinimumRelease(528040, new Version(4, 8)) + }; + + private static IEnumerable FindDotNetFourFrameworkVersions(RegistryKey versionKey) + { + foreach (string profile in new string[] { "Full", "Client" }) + { + var profileKey = versionKey.OpenSubKey(profile); + if (profileKey == null) continue; + + if (CheckInstallDword(profileKey)) + { + yield return new RuntimeFramework(RuntimeType.Net, new Version(4, 0), profile); + + var release = (int)profileKey.GetValue("Release", 0); + foreach (var entry in ReleaseTable) + if (release >= entry.Release) + yield return new RuntimeFramework(RuntimeType.Net, entry.Version); + + + yield break; //If full profile found don't check for client profile + } + } + } + + private static bool CheckInstallDword(RegistryKey key) + { + return ((int)key.GetValue("Install", 0) == 1); + } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs index 4f0591c19..30690d025 100644 --- a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs +++ b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs @@ -62,7 +62,8 @@ public ITestAgent GetAgent(TestPackage package) if (!_runtimeService.IsAvailable(targetRuntime.Id)) { string msg = $"The {targetRuntime} framework is not available.\r\nAvailable frameworks:"; - foreach (var runtime in RuntimeFramework.AvailableFrameworks) + // HACK + foreach (var runtime in ((RuntimeFrameworkService)_runtimeService).AvailableRuntimes) msg += $" {runtime}"; throw new ArgumentException(msg); } From e6bfef353e8cfda4b6d11b475a1b12681b75373e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 6 Jun 2024 19:54:47 -0700 Subject: [PATCH 051/190] Run under 32bit .NET Core as needed --- NUnitConsole.sln | 18 ++++ build.cake | 1 + cake/dotnet.cake | 88 +++++++++++++++++++ cake/package-definitions.cake | 20 +++++ cake/package-tests.cake | 30 +++++++ msi/nunit/engine-files.wxi | 82 +++++++++++++++-- msi/nunit/runner-directories.wxi | 2 + .../mock-assembly-x86.csproj | 2 +- .../IRuntimeFrameworkService.cs | 3 +- .../Internal/TestAssemblyResolver.cs | 24 +++-- .../Services/Fakes/FakeRuntimeService.cs | 2 +- .../nunit.engine/Runners/MasterTestRunner.cs | 6 +- .../Services/RuntimeFrameworkService.cs | 36 +++++--- .../RuntimeLocators/NetCoreRuntimeLocator.cs | 25 ++++-- .../nunit.engine/Services/TestAgency.cs | 9 +- 15 files changed, 309 insertions(+), 39 deletions(-) create mode 100644 cake/dotnet.cake diff --git a/NUnitConsole.sln b/NUnitConsole.sln index f978a232f..760d8c675 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -110,6 +110,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{9BCA00E2-D072-424B-A6DF-5BECF719C1FB}" ProjectSection(SolutionItems) = preProject cake\constants.cake = cake\constants.cake + cake\dotnet.cake = cake\dotnet.cake cake\header-check.cake = cake\header-check.cake cake\package-checks.cake = cake\package-checks.cake cake\package-definitions.cake = cake\package-definitions.cake @@ -122,6 +123,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{9BCA00E2-D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "msi", "msi", "{0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A}" ProjectSection(SolutionItems) = preProject + msi\nunit-install.sln = msi\nunit-install.sln msi\resources\nunit.bundle.addins = msi\resources\nunit.bundle.addins EndProjectSection EndProject @@ -151,6 +153,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{C5B712 .config\dotnet-tools.json = .config\dotnet-tools.json EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nunit", "nunit", "{93E5CAF4-D5DB-4915-AF0F-908A6043E462}" + ProjectSection(SolutionItems) = preProject + msi\nunit\addin-files.wxi = msi\nunit\addin-files.wxi + msi\nunit\banner.bmp = msi\nunit\banner.bmp + msi\nunit\console-files.wxi = msi\nunit\console-files.wxi + msi\nunit\dialog.bmp = msi\nunit\dialog.bmp + msi\nunit\engine-files.wxi = msi\nunit\engine-files.wxi + msi\nunit\nunit.wixproj = msi\nunit\nunit.wixproj + msi\nunit\nunit.wxs = msi\nunit\nunit.wxs + msi\nunit\runner-directories.wxi = msi\nunit\runner-directories.wxi + msi\nunit\runner-features.wxi = msi\nunit\runner-features.wxi + msi\nunit\utility-files.wxi = msi\nunit\utility-files.wxi + msi\nunit\variables.wxi = msi\nunit\variables.wxi + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -235,6 +252,7 @@ Global {08F8160E-E691-4F07-9F57-EA31B9736429} = {25DA12FE-6209-4524-9A37-8E51F815E198} {50371E48-BEC3-4D53-BD37-F3A6149CFD0D} = {25DA12FE-6209-4524-9A37-8E51F815E198} {C5B7120C-190B-4C38-95CB-83F12799598D} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} + {93E5CAF4-D5DB-4915-AF0F-908A6043E462} = {0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/build.cake b/build.cake index 2ec9efdc4..814abff27 100644 --- a/build.cake +++ b/build.cake @@ -3,6 +3,7 @@ static string Configuration; Configuration = GetArgument("configuration|c", "Rel static bool NoPush; NoPush = HasArgument("nopush"); #load cake/constants.cake +#load cake/dotnet.cake #load cake/header-check.cake #load cake/package-checks.cake #load cake/test-results.cake diff --git a/cake/dotnet.cake b/cake/dotnet.cake new file mode 100644 index 000000000..0fb801824 --- /dev/null +++ b/cake/dotnet.cake @@ -0,0 +1,88 @@ +public class DotnetInfo +{ + // Experimenting with finding dotnet installs for X64 vs x86 + // This code will end up moved into the engine as well. + + private ICakeContext _context; + private bool _onWindows; + + public DotnetInfo(ICakeContext context) + { + _context = context; + _onWindows = context.IsRunningOnWindows(); + + InstallPath = GetDotnetInstallDirectory(false); + X86InstallPath = GetDotnetInstallDirectory(true); + } + + // NOTES: + // * We don't need an IsInstalled property because our scripts all run under dotnet. + + public bool IsX86Installed => System.IO.Directory.Exists(X86InstallPath) && System.IO.File.Exists(X86Executable); + + public string InstallPath { get; } + public string Executable => InstallPath + "dotnet.exe"; + public List Runtimes { get; } + + public string X86InstallPath { get; } + public string X86Executable => X86InstallPath + "dotnet.exe"; + public List X86Runtimes { get; } + + public void Display() + { + _context.Information($"Install Path: {InstallPath}"); + _context.Information($"Executable: {Executable}"); + _context.Information("Runtimes:"); + foreach (string dir in System.IO.Directory.GetDirectories(System.IO.Path.Combine(InstallPath, "shared"))) + { + string runtime = System.IO.Path.GetFileName(dir); + foreach (string dir2 in System.IO.Directory.GetDirectories(dir)) + { + string version = System.IO.Path.GetFileName(dir2); + _context.Information($" {runtime} {version}"); + } + } + + if (IsX86Installed) + { + _context.Information($"\nX86 Install Path: {X86InstallPath}"); + _context.Information($"X86 Executable: {X86Executable}"); + _context.Information("Runtimes:"); + foreach (var dir in System.IO.Directory.GetDirectories(System.IO.Path.Combine(X86InstallPath, "shared"))) + { + string runtime = System.IO.Path.GetFileName(dir); + foreach (string dir2 in System.IO.Directory.GetDirectories(dir)) + { + string version = System.IO.Path.GetFileName(dir2); + _context.Information($" {runtime} {version}"); + } + } + } + else + _context.Information("\nDotnet X86 is not installed"); + } + + private string GetDotnetInstallDirectory(bool forX86 = false) + { + if (_onWindows) + { + if (forX86) + { + Microsoft.Win32.RegistryKey key = + Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); + return (string)key?.GetValue("InstallLocation"); + } + else + { + Microsoft.Win32.RegistryKey key = + Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); + return (string)key?.GetValue("Path"); + } + } + else // Assuming linux for now + return "/usr/shared/dotnet/"; + } +} + +// Use this task to verify that the script understands the dotnet environment +Task("DotnetInfo").Does(() => { new DotnetInfo(Context).Display(); }); diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake index 04f431216..2d8b40008 100644 --- a/cake/package-definitions.cake +++ b/cake/package-definitions.cake @@ -28,6 +28,8 @@ public void InitializePackageDefinitions(ICakeContext context) NetCore31Test, Net50Test, Net60Test, + Net70Test, + Net80Test, Net50PlusNet60Test, Net40PlusNet60Test }; @@ -42,6 +44,24 @@ public void InitializePackageDefinitions(ICakeContext context) Net80Test, }; + // TODO: Remove the limitation to Windows + if (IsRunningOnWindows() && dotnetX86Available) + { + StandardRunnerTests.Add(Net60X86Test); + // TODO: Make these tests run on AppVeyor + if (!context.BuildSystem().IsRunningOnAppVeyor) + { + StandardRunnerTests.Add(NetCore31X86Test); + StandardRunnerTests.Add(Net50X86Test); + StandardRunnerTests.Add(Net70X86Test); + StandardRunnerTests.Add(Net80X86Test); + } + // Currently, NetCoreRunner runs tests in process. As a result, + // X86 tests will work in our environment, although uses may run + // it as a tool using the X86 architecture. + } + + AllPackages.AddRange(new PackageDefinition[] { NUnitConsoleNuGetPackage = new NuGetPackage( diff --git a/cake/package-tests.cake b/cake/package-tests.cake index 26eee6af3..2b5413ab2 100644 --- a/cake/package-tests.cake +++ b/cake/package-tests.cake @@ -48,30 +48,60 @@ static PackageTest Net80Test = new PackageTest( "net8.0/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest Net80X86Test = new PackageTest( + "Net80X86Test", + "Run mock-assembly-x86.dll under .NET 8.0", + "net8.0/mock-assembly-x86.dll", + MockAssemblyExpectedResult(1)); + static PackageTest Net70Test = new PackageTest( "Net70Test", "Run mock-assembly.dll under .NET 7.0", "net7.0/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest Net70X86Test = new PackageTest( + "Net70X86Test", + "Run mock-assembly-x86.dll under .NET 7.0", + "net7.0/mock-assembly-x86.dll", + MockAssemblyExpectedResult(1)); + static PackageTest Net60Test = new PackageTest( "Net60Test", "Run mock-assembly.dll under .NET 6.0", "net6.0/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest Net60X86Test = new PackageTest( + "Net60X86Test", + "Run mock-assembly-x86.dll under .NET 6.0", + "net6.0/mock-assembly-x86.dll --trace:Debug", + MockAssemblyExpectedResult(1)); + static PackageTest Net50Test = new PackageTest( "Net50Test", "Run mock-assembly.dll under .NET 5.0", "net5.0/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest Net50X86Test = new PackageTest( + "Net50X86Test", + "Run mock-assembly-x86.dll under .NET 5.0", + "net5.0/mock-assembly-x86.dll", + MockAssemblyExpectedResult(1)); + static PackageTest NetCore31Test = new PackageTest( "NetCore31Test", "Run mock-assembly.dll under .NET Core 3.1", "netcoreapp3.1/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest NetCore31X86Test = new PackageTest( + "NetCore31X86Test", + "Run mock-assembly-x86.dll under .NET Core 3.1", + "netcoreapp3.1/mock-assembly-x86.dll", + MockAssemblyExpectedResult(1)); + static PackageTest Net50PlusNet60Test = new PackageTest( "Net50PlusNet60Test", "Run mock-assembly under .NET 5.0 and 6.0 together", diff --git a/msi/nunit/engine-files.wxi b/msi/nunit/engine-files.wxi index 6422781b5..2b914f015 100644 --- a/msi/nunit/engine-files.wxi +++ b/msi/nunit/engine-files.wxi @@ -49,6 +49,8 @@ + + @@ -175,24 +177,94 @@ + Source="$(var.InstallImage)bin/agents/net6.0/nunit.engine.api.dll" /> + Source="$(var.InstallImage)bin/agents/net6.0/nunit.engine.api.xml" /> + Source="$(var.InstallImage)bin/agents/net6.0/nunit.engine.core.dll" /> + Source="$(var.InstallImage)bin/agents/net6.0/testcentric.engine.metadata.dll" /> + Source="$(var.InstallImage)bin/agents/net6.0/Microsoft.Extensions.DependencyModel.dll" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/msi/nunit/runner-directories.wxi b/msi/nunit/runner-directories.wxi index 1f4eab58e..906654d74 100644 --- a/msi/nunit/runner-directories.wxi +++ b/msi/nunit/runner-directories.wxi @@ -16,6 +16,8 @@ + + diff --git a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj index 5abc42dff..34e36d39c 100644 --- a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net35;net40;netcoreapp2.1;netcoreapp3.1 + net35;net40;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 true ..\..\nunit.snk x86 diff --git a/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs index 0e48ebe6d..9f7021092 100644 --- a/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs @@ -16,8 +16,9 @@ public interface IRuntimeFrameworkService /// the string passed as an argument is available. /// /// A string representing a framework, like 'net-4.0' + /// A flag indicating whether the X86 architecture is needed. Defaults to false. /// True if the framework is available, false if unavailable or nonexistent - bool IsAvailable(string framework); + bool IsAvailable(string framework, bool x86); /// /// Selects a target runtime framework for a TestPackage based on diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index c3cfc570a..fe9d53336 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -23,13 +23,17 @@ internal sealed class TestAssemblyResolver : IDisposable private readonly DependencyContext _dependencyContext; private readonly AssemblyLoadContext _loadContext; - private static readonly string INSTALL_DIR = GetDotNetInstallDirectory(); - private static readonly string WINDOWS_DESKTOP_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.WindowsDesktop.App"); - private static readonly string ASP_NET_CORE_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.AspNetCore.App"); + private static readonly string INSTALL_DIR; + private static readonly string WINDOWS_DESKTOP_DIR; + private static readonly string ASP_NET_CORE_DIR; private static readonly List AdditionalFrameworkDirectories; static TestAssemblyResolver() { + INSTALL_DIR = GetDotNetInstallDirectory(); + WINDOWS_DESKTOP_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.WindowsDesktop.App"); + ASP_NET_CORE_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.AspNetCore.App"); + AdditionalFrameworkDirectories = new List(); if (Directory.Exists(WINDOWS_DESKTOP_DIR)) AdditionalFrameworkDirectories.Add(WINDOWS_DESKTOP_DIR); @@ -169,10 +173,16 @@ private static string GetDotNetInstallDirectory() if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // Running on Windows so use registry - RegistryKey key = Environment.Is64BitProcess - ? Registry.LocalMachine.OpenSubKey(@"Software\dotnet\SetUp\InstalledVersions\x64\sharedHost\") - : Registry.LocalMachine.OpenSubKey(@"Software\dotnet\SetUp\InstalledVersions\x86\sharedHost\"); - return (string)key?.GetValue("Path"); + if (Environment.Is64BitProcess) + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); + return (string)key?.GetValue("Path"); + } + else + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); + return (string)key?.GetValue("InstallLocation"); + } } else return "/usr/shared/dotnet/"; diff --git a/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs b/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs index 9b181e991..9d5558e7f 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs @@ -4,7 +4,7 @@ namespace NUnit.Engine.Services.Tests.Fakes { public class FakeRuntimeService : FakeService, IRuntimeFrameworkService { - bool IRuntimeFrameworkService.IsAvailable(string framework) + bool IRuntimeFrameworkService.IsAvailable(string framework, bool x86) { return true; } diff --git a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs index 41734a93d..06e3c989d 100644 --- a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs @@ -376,8 +376,10 @@ private void ValidatePackageSettings() { // Check requested framework is actually available var runtimeService = _services.GetService(); - if (!runtimeService.IsAvailable(frameworkSetting)) - throw new NUnitEngineException(string.Format("The requested framework {0} is unknown or not available.", frameworkSetting)); + if (!runtimeService.IsAvailable(frameworkSetting, runAsX86)) + throw new NUnitEngineException(runAsX86 + ? $"The requested framework {frameworkSetting} is unknown or not available for X86." + : $"The requested framework {frameworkSetting} is unknown or not available."); // If running in process, check requested framework is compatible if (runningInProcess) diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs index dfe8d82b5..673b76420 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -19,26 +19,34 @@ public class RuntimeFrameworkService : Service, IRuntimeFrameworkService, IAvail static readonly Logger log = InternalTrace.GetLogger(typeof(RuntimeFrameworkService)); private List _availableRuntimes = new List(); + private List _availableX86Runtimes = new List(); /// /// Gets a list of available runtimes. /// public IList AvailableRuntimes => _availableRuntimes.ToArray(); + /// + /// Gets a list of available runtimes. + /// + public IList AvailableX86Runtimes => _availableX86Runtimes.ToArray(); + /// /// Returns true if the runtime framework represented by /// the string passed as an argument is available. /// /// A string representing a framework, like 'net-4.0' + /// A flag indicating whether the X86 architecture is needed. Defaults to false. /// True if the framework is available, false if unavailable or nonexistent - public bool IsAvailable(string name) + public bool IsAvailable(string name, bool x86) { Guard.ArgumentNotNullOrEmpty(name, nameof(name)); if (!RuntimeFramework.TryParse(name, out RuntimeFramework requestedFramework)) throw new NUnitEngineException("Invalid or unknown framework requested: " + name); - foreach (var framework in _availableRuntimes) + var runtimes = x86 ? _availableX86Runtimes : _availableRuntimes; + foreach (var framework in runtimes) if (FrameworksMatch(requestedFramework, framework)) return true; @@ -107,6 +115,7 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) log.Debug("Current framework is " + currentFramework); string frameworkSetting = package.GetSetting(EnginePackageSettings.RequestedRuntimeFramework, ""); + bool runAsX86 = package.GetSetting(EnginePackageSettings.RunAsX86, false); if (frameworkSetting.Length > 0) { @@ -115,7 +124,7 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) log.Debug($"Requested framework for {package.Name} is {requestedFramework}"); - if (!IsAvailable(frameworkSetting)) + if (!IsAvailable(frameworkSetting, runAsX86)) throw new NUnitEngineException("Requested framework is not available: " + frameworkSetting); package.Settings[EnginePackageSettings.TargetRuntimeFramework] = frameworkSetting; @@ -154,12 +163,12 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) targetVersion = frameworkName.Version; } - //if (!new RuntimeFramework(targetRuntime, targetVersion).IsAvailable) - //{ - // log.Debug("Preferred version {0} is not installed or this NUnit installation does not support it", targetVersion); - // if (targetVersion < currentFramework.FrameworkVersion) - // targetVersion = currentFramework.FrameworkVersion; - //} + if (!IsAvailable(new RuntimeFramework(targetRuntime, targetVersion).Id, runAsX86)) + { + log.Debug("Preferred version {0} is not installed or this NUnit installation does not support it", targetVersion); + if (targetVersion < currentFramework.FrameworkVersion) + targetVersion = currentFramework.FrameworkVersion; + } RuntimeFramework targetFramework = new RuntimeFramework(targetRuntime, targetVersion); package.Settings[EnginePackageSettings.TargetRuntimeFramework] = targetFramework.ToString(); @@ -206,16 +215,21 @@ public RuntimeFramework GetBestAvailableFramework(RuntimeFramework target) private void FindAvailableRuntimes() { _availableRuntimes = new List(); + _availableX86Runtimes = new List(); if (Environment.OSVersion.Platform == PlatformID.Win32NT) { var netFxRuntimes = NetFxRuntimeLocator.FindRuntimes(); _availableRuntimes.AddRange(netFxRuntimes); + _availableX86Runtimes.AddRange(netFxRuntimes); } - _availableRuntimes.AddRange(MonoRuntimeLocator.FindRuntimes()); + var monoRuntimes = MonoRuntimeLocator.FindRuntimes(); + _availableRuntimes.AddRange(monoRuntimes); + _availableX86Runtimes.AddRange(monoRuntimes); - _availableRuntimes.AddRange(NetCoreRuntimeLocator.FindRuntimes()); + _availableRuntimes.AddRange(NetCoreRuntimeLocator.FindRuntimes(x86: false)); + _availableX86Runtimes.AddRange(NetCoreRuntimeLocator.FindRuntimes(x86: true)); } /// diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs index 537a14402..333e5db7b 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs @@ -11,11 +11,11 @@ namespace NUnit.Engine.Services.RuntimeLocators { public static class NetCoreRuntimeLocator { - public static IEnumerable FindRuntimes() + public static IEnumerable FindRuntimes(bool x86) { List alreadyFound = new List(); - foreach (string dirName in GetRuntimeDirectories()) + foreach (string dirName in GetRuntimeDirectories(x86)) { Version newVersion; if (TryGetVersionFromString(dirName, out newVersion) && !alreadyFound.Contains(newVersion)) @@ -36,9 +36,9 @@ public static IEnumerable FindRuntimes() } } - private static IEnumerable GetRuntimeDirectories() + private static IEnumerable GetRuntimeDirectories(bool x86) { - string installDir = GetDotNetInstallDirectory(); + string installDir = GetDotNetInstallDirectory(x86); if (installDir != null && Directory.Exists(installDir) && File.Exists(Path.Combine(installDir, "dotnet.exe"))) @@ -111,13 +111,22 @@ private static bool TryGetVersionFromString(string text, out Version newVersion) } } - internal static string GetDotNetInstallDirectory() + internal static string GetDotNetInstallDirectory(bool x86) { if (Path.DirectorySeparatorChar == '\\') { - RegistryKey key = - Registry.LocalMachine.OpenSubKey(@"Software\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); - return (string)key?.GetValue("Path"); + if (x86) + { + RegistryKey key = + Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); + return (string)key?.GetValue("InstallLocation"); + } + else + { + RegistryKey key = + Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); + return (string)key?.GetValue("Path"); + } } else return "/usr/shared/dotnet/"; diff --git a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs index 30690d025..0cfe65ede 100644 --- a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs +++ b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs @@ -55,15 +55,18 @@ public ITestAgent GetAgent(TestPackage package) // Target Runtime must be specified by this point string runtimeSetting = package.GetSetting(EnginePackageSettings.TargetRuntimeFramework, ""); Guard.OperationValid(runtimeSetting.Length > 0, "LaunchAgentProcess called with no runtime specified"); + bool runAsX86 = package.GetSetting(EnginePackageSettings.RunAsX86, false); // If target runtime is not available, something went wrong earlier. // We list all available frameworks to use in debugging. var targetRuntime = RuntimeFramework.Parse(runtimeSetting); - if (!_runtimeService.IsAvailable(targetRuntime.Id)) + if (!_runtimeService.IsAvailable(targetRuntime.Id, runAsX86)) { - string msg = $"The {targetRuntime} framework is not available.\r\nAvailable frameworks:"; + string msg = $"The {targetRuntime} framework is not available for X86={runAsX86}.\r\nAvailable frameworks:"; // HACK - foreach (var runtime in ((RuntimeFrameworkService)_runtimeService).AvailableRuntimes) + var service = _runtimeService as RuntimeFrameworkService; + var availableRuntimes = runAsX86 ? service.AvailableX86Runtimes : service.AvailableRuntimes; + foreach (var runtime in availableRuntimes) msg += $" {runtime}"; throw new ArgumentException(msg); } From 0d6a2aac10c96ad00bc2755a8538086dd2c6741e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 9 Jun 2024 16:54:44 -0700 Subject: [PATCH 052/190] Eliminate use of 'Any' subdirectory of the netcore runner --- appveyor.yml | 2 +- cake/package-checks.cake | 2 +- cake/package-definitions.cake | 12 ++-- .../nunit.console-runner.netcore.nuspec | 68 +++++++++---------- .../Services/ExtensionManager.cs | 6 ++ 5 files changed, 48 insertions(+), 42 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5eefdac67..5c62a8cc5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,7 @@ build_script: - ps: .\build.ps1 --target=Appveyor --configuration=Release # disable built-in tests. -test: off +test: false artifacts: - path: package\*.nupkg diff --git a/cake/package-checks.cake b/cake/package-checks.cake index 5c715a54d..be45ec4da 100644 --- a/cake/package-checks.cake +++ b/cake/package-checks.cake @@ -25,7 +25,7 @@ string[] AGENT_PDB_FILES_NETCORE = { string[] CONSOLE_FILES = { "nunit3-console.exe", "nunit3-console.exe.config" }; string[] CONSOLE_FILES_NETCORE = { - "nunit3-console.exe", "nunit3-console.dll", "nunit3-console.dll.config" }; + "nunit3-console.exe", "nunit3-console.dll" }; ////////////////////////////////////////////////////////////////////// // PACKAGE CHECK IMPLEMENTATION diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake index 2d8b40008..e492a86b8 100644 --- a/cake/package-definitions.cake +++ b/cake/package-definitions.cake @@ -107,12 +107,12 @@ public void InitializePackageDefinitions(ICakeContext context) source: NUGET_DIR + "runners/nunit.console-runner.netcore.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools/net8.0/any").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins") + HasDirectory("tools/net8.0").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_CORE_FILES).AndFile("nunit.console.nuget.addins") }, symbols: new PackageCheck[] { - HasDirectory("tools/net8.0/any").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) + HasDirectory("tools/net8.0").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) }, - executable: "tools/net8.0/any/nunit3-console.exe", + executable: "tools/net8.0/nunit3-console.exe", tests: NetCoreRunnerTests), NUnitConsoleRunnerNet60Package = new NuGetPackage( @@ -122,12 +122,12 @@ public void InitializePackageDefinitions(ICakeContext context) source: NUGET_DIR + "runners/nunit.console-runner.netcore.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools/net6.0/any").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins") + HasDirectory("tools/net6.0").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_CORE_FILES).AndFile("nunit.console.nuget.addins") }, symbols: new PackageCheck[] { - HasDirectory("tools/net6.0/any").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) + HasDirectory("tools/net6.0").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) }, - executable: "tools/net6.0/any/nunit3-console.exe", + executable: "tools/net6.0/nunit3-console.exe", tests: NetCoreRunnerTests), NUnitConsoleRunnerChocolateyPackage = new ChocolateyPackage( diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index 3f2860e37..d2147e507 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -29,41 +29,41 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 9148f90fd..6ff44e251 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -305,6 +305,12 @@ private void ProcessAddinsFile(IDirectory baseDir, IFile addinsFile, bool fromWi { bool isWild = fromWildCard || entry.Contains("*"); var args = GetBaseDirAndPattern(baseDir, entry); + // TODO: See if we can handle '/tools/*/' efficiently by examining every + // assembly in the directory. Otherwise try this approach: + // 1. Check entry for ending with '/tools/*/' + // 2. If so, examine the directory name to see if it matches a tfm. + // 3. If it does, check to see if the implied runtime would be loadable. + // 4. If so, process it, if not, skip it. if (entry.EndsWith("/")) { foreach (var dir in _directoryFinder.GetDirectories(args.Item1, args.Item2)) From f861b058387545a6334da9155631a252e0c1c623 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 10 Jun 2024 07:30:04 -0700 Subject: [PATCH 053/190] Add multi-target extension patterns to the .addins files --- choco/nunit.choco.addins | 4 ++++ nuget/runners/nunit.console.nuget.addins | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/choco/nunit.choco.addins b/choco/nunit.choco.addins index 905420e97..7eaefda90 100644 --- a/choco/nunit.choco.addins +++ b/choco/nunit.choco.addins @@ -1 +1,5 @@ +# Extensions built for a single runtime target ../../nunit-extension-*/tools/ # find extensions installed under chocolatey + +# Extensions built for multiple targets +../../nunit-extension-*/tools/*/ # find extensions installed under chocolatey diff --git a/nuget/runners/nunit.console.nuget.addins b/nuget/runners/nunit.console.nuget.addins index c5495ff20..f540c8540 100644 --- a/nuget/runners/nunit.console.nuget.addins +++ b/nuget/runners/nunit.console.nuget.addins @@ -1,6 +1,11 @@ +# Extensions built for a single runtime target ../../NUnit.Extension.*/**/tools/ # nuget v2 layout ../../../NUnit.Extension.*/**/tools/ # nuget v3 layout -../../../../NUnit.Extension.*/**/tools/ ../../nunit.extension.*/**/tools/ # nuget v2 layout ../../../nunit.extension.*/**/tools/ # nuget v3 layout -../../../../nunit.extension.*/**/tools/ + +# Extensions built for multiple targets +../../NUnit.Extension.*/**/tools/*/ # nuget v2 layout +../../../NUnit.Extension.*/**/tools/*/ # nuget v3 layout +../../nunit.extension.*/**/tools/*/ # nuget v2 layout +../../../nunit.extension.*/**/tools/*/ # nuget v3 layout From c8fe139052410e904ef0f36b6e69048fa7988757 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 10 Jun 2024 15:48:56 -0700 Subject: [PATCH 054/190] Addins files for agents; test NUnitProjectLoader extension --- GitVersion.yml | 2 +- NUnitConsole.sln | 6 +-- cake/package-definitions.cake | 37 ++++++++++--------- cake/package-tester.cake | 28 ++++++++++++++ choco/nunit-console-runner.nuspec | 16 ++++---- choco/nunit.agent.addins | 1 - ...hoco.addins => nunit.console.choco.addins} | 0 choco/nunit.console.choco.agent.addins | 5 +++ nuget/runners/nunit.agent.addins | 1 - nuget/runners/nunit.console-runner.nuspec | 14 +++---- .../runners/nunit.console.nuget.agent.addins | 11 ++++++ 11 files changed, 82 insertions(+), 39 deletions(-) delete mode 100644 choco/nunit.agent.addins rename choco/{nunit.choco.addins => nunit.console.choco.addins} (100%) create mode 100644 choco/nunit.console.choco.agent.addins delete mode 100644 nuget/runners/nunit.agent.addins create mode 100644 nuget/runners/nunit.console.nuget.agent.addins diff --git a/GitVersion.yml b/GitVersion.yml index c02e85371..19dfb1105 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 3.17.1 +next-version: 3.18.0 mode: ContinuousDelivery legacy-semver-padding: 5 build-metadata-padding: 5 diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 760d8c675..4b047daea 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -69,11 +69,11 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runners", "runners", "{F3E87D0F-6F06-4C0B-AE06-42C0834C3C6E}" ProjectSection(SolutionItems) = preProject nuget\runners\DotnetToolSettings.xml = nuget\runners\DotnetToolSettings.xml - nuget\runners\nunit.agent.addins = nuget\runners\nunit.agent.addins nuget\runners\nunit.console-runner-with-extensions.nuspec = nuget\runners\nunit.console-runner-with-extensions.nuspec nuget\runners\nunit.console-runner.netcore.nuspec = nuget\runners\nunit.console-runner.netcore.nuspec nuget\runners\nunit.console-runner.nuspec = nuget\runners\nunit.console-runner.nuspec nuget\runners\nunit.console.nuget.addins = nuget\runners\nunit.console.nuget.addins + nuget\runners\nunit.console.nuget.agent.addins = nuget\runners\nunit.console.nuget.agent.addins EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "src\NUnitEngine\mock-assembly\mock-assembly.csproj", "{D2C80E4B-1117-4F02-AB02-E453BDA0C58E}" @@ -85,9 +85,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "choco", "choco", "{4FDF7BFA choco\nunit-agent-x86.exe.ignore = choco\nunit-agent-x86.exe.ignore choco\nunit-agent.exe.ignore = choco\nunit-agent.exe.ignore choco\nunit-console-runner.nuspec = choco\nunit-console-runner.nuspec - choco\nunit.agent.addins = choco\nunit.agent.addins - choco\nunit.choco.addins = choco\nunit.choco.addins choco\VERIFICATION.txt = choco\VERIFICATION.txt + choco\nunit.console.choco.addins = choco\nunit.console.choco.addins + choco\nunit.console.choco.agent.addins = choco\nunit.console.choco.agent.addins EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deprecated", "deprecated", "{9A7C8370-ED1F-486F-A8F5-C5BF4221464E}" diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake index e492a86b8..b38bdc7a0 100644 --- a/cake/package-definitions.cake +++ b/cake/package-definitions.cake @@ -31,7 +31,8 @@ public void InitializePackageDefinitions(ICakeContext context) Net70Test, Net80Test, Net50PlusNet60Test, - Net40PlusNet60Test + Net40PlusNet60Test, + NUnitProjectTest }; // Tests run for the NETCORE runner package @@ -79,13 +80,13 @@ public void InitializePackageDefinitions(ICakeContext context) checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), HasDirectory("tools").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins"), - HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins") + HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins") }, symbols: new PackageCheck[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), @@ -136,14 +137,14 @@ public void InitializePackageDefinitions(ICakeContext context) version: ProductVersion, source: CHOCO_DIR + "nunit-console-runner.nuspec", checks: new PackageCheck[] { - HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt").AndFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.choco.addins"), - HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins"), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.agent.addins") + HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt").AndFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.choco.addins"), + HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins") }, executable: "tools/nunit3-console.exe", tests: StandardRunnerTests), @@ -159,7 +160,7 @@ public void InitializePackageDefinitions(ICakeContext context) HasDirectory("Nunit.org/nunit-console/addins").WithFiles("nunit.core.dll", "nunit.core.interfaces.dll", "nunit.v2.driver.dll", "nunit-project-loader.dll", "vs-project-loader.dll", "nunit-v2-result-writer.dll", "teamcity-event-listener.dll") }, executable: "NUnit.org/nunit-console/nunit3-console.exe", - tests: StandardRunnerTests.Concat(new[] { NUnitProjectTest })), + tests: StandardRunnerTests), NUnitConsoleZipPackage = new ZipPackage( context: context, @@ -181,7 +182,7 @@ public void InitializePackageDefinitions(ICakeContext context) HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) }, executable: "bin/net20/nunit3-console.exe", - tests: StandardRunnerTests.Concat(new[] { NUnitProjectTest })), + tests: StandardRunnerTests), // NOTE: Packages below this point have no direct tests diff --git a/cake/package-tester.cake b/cake/package-tester.cake index a3ee24f4d..f3d2c3142 100644 --- a/cake/package-tester.cake +++ b/cake/package-tester.cake @@ -70,9 +70,37 @@ public class PackageTester { Console.WriteLine($"Unzipping package to {_installDirectory}"); _context.Unzip(_packageUnderTest, _installDirectory); + + if (_packageType == PackageType.NuGet || _packageType == PackageType.Chocolatey) + { + foreach (string packageDir in System.IO.Directory.GetDirectories(EXTENSIONS_DIR)) + { + string subdir = _packageType.ToString().ToLower(); + string packageName = System.IO.Path.GetFileName(packageDir); + string targetDir = $"{PACKAGE_TEST_DIR}{subdir}/{packageName}"; + + _context.CopyDirectory(packageDir, targetDir); + + if (_packageType == PackageType.Chocolatey) + RenamePackageForChocolatey(targetDir); + } + } } } + private void RenamePackageForChocolatey(string nugetDir) + { + string chocoDir = nugetDir + .Replace("NUnit.Extension.NUnitProjectLoader", "nunit-extension-nunit-project-loader") + .Replace("NUnit.Extension.VSProjectLoader", "nunit-extension-vs-project-loader") + .Replace("NUnit.Extension.NUnitV2ResultWriter", "nunit-extension-v2-result-writer") + .Replace("NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver") + .Replace("NUnit.Extension.TeamCityEventListener", "nunit-extension-teamcity-event-listener"); + + if (chocoDir != nugetDir) + _context.MoveDirectory(nugetDir, chocoDir); + } + private void RunPackageTests() { var reporter = new ResultReporter(_packageName); diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 068f8e2af..f9658c5c9 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -29,7 +29,7 @@ - + @@ -49,7 +49,7 @@ - + @@ -61,7 +61,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -83,7 +83,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -105,7 +105,7 @@ - + @@ -116,6 +116,6 @@ - + diff --git a/choco/nunit.agent.addins b/choco/nunit.agent.addins deleted file mode 100644 index 1558d5f75..000000000 --- a/choco/nunit.agent.addins +++ /dev/null @@ -1 +0,0 @@ -../../ # refer to the directory containing the engine and runner diff --git a/choco/nunit.choco.addins b/choco/nunit.console.choco.addins similarity index 100% rename from choco/nunit.choco.addins rename to choco/nunit.console.choco.addins diff --git a/choco/nunit.console.choco.agent.addins b/choco/nunit.console.choco.agent.addins new file mode 100644 index 000000000..ddf5d016b --- /dev/null +++ b/choco/nunit.console.choco.agent.addins @@ -0,0 +1,5 @@ +# Extensions built for a single runtime target +../../../../nunit-extension-*/tools/ # find extensions installed under chocolatey + +# Extensions built for multiple targets +../../../../nunit-extension-*/tools/*/ # find extensions installed under chocolatey diff --git a/nuget/runners/nunit.agent.addins b/nuget/runners/nunit.agent.addins deleted file mode 100644 index 1558d5f75..000000000 --- a/nuget/runners/nunit.agent.addins +++ /dev/null @@ -1 +0,0 @@ -../../ # refer to the directory containing the engine and runner diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 290f218f1..45a728c16 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -38,7 +38,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -66,7 +66,7 @@ - + @@ -80,7 +80,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -108,7 +108,7 @@ - + @@ -122,7 +122,7 @@ - + diff --git a/nuget/runners/nunit.console.nuget.agent.addins b/nuget/runners/nunit.console.nuget.agent.addins new file mode 100644 index 000000000..ddfcb54ab --- /dev/null +++ b/nuget/runners/nunit.console.nuget.agent.addins @@ -0,0 +1,11 @@ +# Extensions built for a single runtime target +../../../../NUnit.Extension.*/**/tools/ # nuget v2 layout +../../../../../NUnit.Extension.*/**/tools/ # nuget v3 layout +../../../../nunit.extension.*/**/tools/ # nuget v2 layout +../../../../../nunit.extension.*/**/tools/ # nuget v3 layout + +# Extensions built for multiple targets +../../../../NUnit.Extension.*/**/tools/*/ # nuget v2 layout +../../../../../NUnit.Extension.*/**/tools/*/ # nuget v3 layout +../../../../nunit.extension.*/**/tools/*/ # nuget v2 layout +../../../../../nunit.extension.*/**/tools/*/ # nuget v3 layout From 2823733d711b01d7ded0c80492d56f5f65fbd1ec Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 11 Jun 2024 16:44:06 -0700 Subject: [PATCH 055/190] Make runner and agents long-path-aware --- src/NUnitConsole/nunit3-console/app.manifest | 9 +++++++++ src/NUnitEngine/nunit-agent-x86/app.manifest | 9 +++++++++ src/NUnitEngine/nunit-agent/app.manifest | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/src/NUnitConsole/nunit3-console/app.manifest b/src/NUnitConsole/nunit3-console/app.manifest index bca714dfa..e7efdd758 100644 --- a/src/NUnitConsole/nunit3-console/app.manifest +++ b/src/NUnitConsole/nunit3-console/app.manifest @@ -38,4 +38,13 @@ + + + + + + true + + + diff --git a/src/NUnitEngine/nunit-agent-x86/app.manifest b/src/NUnitEngine/nunit-agent-x86/app.manifest index 589e43719..7008d5001 100644 --- a/src/NUnitEngine/nunit-agent-x86/app.manifest +++ b/src/NUnitEngine/nunit-agent-x86/app.manifest @@ -38,4 +38,13 @@ + + + + + + true + + + diff --git a/src/NUnitEngine/nunit-agent/app.manifest b/src/NUnitEngine/nunit-agent/app.manifest index 589e43719..7008d5001 100644 --- a/src/NUnitEngine/nunit-agent/app.manifest +++ b/src/NUnitEngine/nunit-agent/app.manifest @@ -38,4 +38,13 @@ + + + + + + true + + + From 1bdc05080b55fb5821a91cae2be8b5e6e9da09de Mon Sep 17 00:00:00 2001 From: CharliePoole Date: Tue, 11 Jun 2024 22:17:56 -0700 Subject: [PATCH 056/190] Create platform-support.md --- platform-support.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 platform-support.md diff --git a/platform-support.md b/platform-support.md new file mode 100644 index 000000000..5457f017a --- /dev/null +++ b/platform-support.md @@ -0,0 +1,32 @@ +# Platform Support Lifecycle + +"Platform Support" for this project includes three somewhat different things: +1. The target platforms for which tests may be written. +2. The platforms under which those tests will run, which is equivalent to the list of agents we supply. +3. The minimum platforms required to execute the runner and engine themselves, without regard to the tests being run. + +## Test Assembly Target Runtimes + +We will continue to support tests targeting runtimes which are out of support from Microsoft's perspective, so long +as we are able to do so without significant additional effort or security risk. If no agent is available for a runtime, +the tests will be run on the closest higher runtime available. + +> _We currently support execution of tests written to target any version of the .NET Framework >= 2.0 and any version +> of .NET Core >= 3.1, including .NET 5.0 and higher._ + +## Agents Provided + +We will continue to provide agents for any Microsoft runtime for at least six months after its end of life. This is +intended to support continued testing of legacy applications while users are in the process of upgrade. However, agents +for runtimes which have been declared a security risk may be removed immediately. + +> _We currently supply agents for .NET Famework 2.0 and 4.6.2. .NET Core 2,1 and 3.1 and .NET 5.0, 6.0 and 7.0. The .NET +> Core 2.1 runner is being removed in version 3.18.0 and .NET 8.0 will probably be added before this document is finalized. +> Once this document is approved, we will begin to phase out agents according to the above criteria. + +## Required Runtimes + +The console runner and engine target .NET 4.6.2, so that runtime or greater is required to execute any tests at all +without loss of features. In individual cases, it may be possible to run using a lesser version of .NET 4.x. + +> **Note:** Editorial comments in italics will be removed in the final version of this document From 69a949070c65a0ff67800660083dabf5d9d27583 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 12 Jun 2024 11:31:49 -0700 Subject: [PATCH 057/190] Fix exception thrown for .NET Standard assemblies... again! --- cake/package-tests.cake | 2 +- .../nunit.engine/Services/RuntimeFrameworkService.cs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cake/package-tests.cake b/cake/package-tests.cake index 2b5413ab2..7b76d14fe 100644 --- a/cake/package-tests.cake +++ b/cake/package-tests.cake @@ -75,7 +75,7 @@ static PackageTest Net60Test = new PackageTest( static PackageTest Net60X86Test = new PackageTest( "Net60X86Test", "Run mock-assembly-x86.dll under .NET 6.0", - "net6.0/mock-assembly-x86.dll --trace:Debug", + "net6.0/mock-assembly-x86.dll", MockAssemblyExpectedResult(1)); static PackageTest Net50Test = new PackageTest( diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs index 673b76420..e1f7538d1 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -140,7 +140,7 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) if (string.IsNullOrEmpty(imageTargetFrameworkNameSetting)) { - // Assume .NET Framework + // Assume .NET Framework 2.0 targetRuntime = currentFramework.Runtime; targetVersion = package.GetSetting(InternalEnginePackageSettings.ImageRuntimeVersion, new Version(2, 0)); } @@ -152,15 +152,19 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) { case ".NETFramework": targetRuntime = RuntimeType.Net; + targetVersion = frameworkName.Version; break; case ".NETCoreApp": targetRuntime = RuntimeType.NetCore; + targetVersion = frameworkName.Version; + break; + case ".NETStandard": + targetRuntime = RuntimeType.NetCore; + targetVersion = new Version(3, 1); break; default: throw new NUnitEngineException("Unsupported Target Framework: " + imageTargetFrameworkNameSetting); } - - targetVersion = frameworkName.Version; } if (!IsAvailable(new RuntimeFramework(targetRuntime, targetVersion).Id, runAsX86)) From f032bb58fb2ae1f1c1ef9782bc25800a6ba4aec6 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 12 Jun 2024 12:22:31 -0700 Subject: [PATCH 058/190] Add a unit test we missed --- .../nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs index d61517e06..744cd0617 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs @@ -35,6 +35,7 @@ public void ServiceIsStarted() [TestCase("mock-assembly.dll", false)] [TestCase("../agents/net20/nunit-agent.exe", false)] [TestCase("../agents/net20/nunit-agent-x86.exe", true)] + [TestCase("../netstandard2.0/nunit.engine.api.dll", false)] public void SelectRuntimeFramework(string assemblyName, bool runAsX86) { var package = new TestPackage(Path.Combine(TestContext.CurrentContext.TestDirectory, assemblyName)); From ee1d830c657d45b0c02c334f016b62d7bfffa910 Mon Sep 17 00:00:00 2001 From: CharliePoole Date: Wed, 12 Jun 2024 14:35:16 -0700 Subject: [PATCH 059/190] Update platform-support.md --- platform-support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-support.md b/platform-support.md index 5457f017a..2f6146b08 100644 --- a/platform-support.md +++ b/platform-support.md @@ -22,7 +22,7 @@ for runtimes which have been declared a security risk may be removed immediately > _We currently supply agents for .NET Famework 2.0 and 4.6.2. .NET Core 2,1 and 3.1 and .NET 5.0, 6.0 and 7.0. The .NET > Core 2.1 runner is being removed in version 3.18.0 and .NET 8.0 will probably be added before this document is finalized. -> Once this document is approved, we will begin to phase out agents according to the above criteria. +> Once this document is approved, we will begin to phase out agents according to the above criteria._ ## Required Runtimes From 357ab4dbd29838826d6920d6f06f01652e0cfdc4 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 13 Jun 2024 12:13:11 -0700 Subject: [PATCH 060/190] Create separate folder for cake scripts used by all supported projects --- NUnitConsole.sln | 12 +- build.cake | 158 ++++++++++-------------- cake/build-settings.cake | 195 +++++++++++++++++++++++++++--- cake/common/common-constants.cake | 23 ++++ cake/{ => common}/dotnet.cake | 0 cake/common/tasks.cake | 45 +++++++ cake/constants.cake | 26 +--- cake/package-definitions.cake | 21 ++-- cake/utilities.cake | 19 +-- cake/versioning.cake | 5 +- 10 files changed, 341 insertions(+), 163 deletions(-) create mode 100644 cake/common/common-constants.cake rename cake/{ => common}/dotnet.cake (100%) create mode 100644 cake/common/tasks.cake diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 4b047daea..e352c619a 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -85,9 +85,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "choco", "choco", "{4FDF7BFA choco\nunit-agent-x86.exe.ignore = choco\nunit-agent-x86.exe.ignore choco\nunit-agent.exe.ignore = choco\nunit-agent.exe.ignore choco\nunit-console-runner.nuspec = choco\nunit-console-runner.nuspec - choco\VERIFICATION.txt = choco\VERIFICATION.txt choco\nunit.console.choco.addins = choco\nunit.console.choco.addins choco\nunit.console.choco.agent.addins = choco\nunit.console.choco.agent.addins + choco\VERIFICATION.txt = choco\VERIFICATION.txt EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deprecated", "deprecated", "{9A7C8370-ED1F-486F-A8F5-C5BF4221464E}" @@ -110,7 +110,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{9BCA00E2-D072-424B-A6DF-5BECF719C1FB}" ProjectSection(SolutionItems) = preProject cake\constants.cake = cake\constants.cake - cake\dotnet.cake = cake\dotnet.cake + cake\build-settings.cake = cake\build-settings.cake cake\header-check.cake = cake\header-check.cake cake\package-checks.cake = cake\package-checks.cake cake\package-definitions.cake = cake\package-definitions.cake @@ -168,6 +168,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nunit", "nunit", "{93E5CAF4 msi\nunit\variables.wxi = msi\nunit\variables.wxi EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{6EF61FBD-3081-4CC8-B909-94176A12DD1A}" + ProjectSection(SolutionItems) = preProject + cake\common\constants.cake = cake\common\constants.cake + cake\common\dotnet.cake = cake\common\dotnet.cake + cake\common\tasks.cake = cake\common\tasks.cake + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -253,6 +260,7 @@ Global {50371E48-BEC3-4D53-BD37-F3A6149CFD0D} = {25DA12FE-6209-4524-9A37-8E51F815E198} {C5B7120C-190B-4C38-95CB-83F12799598D} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {93E5CAF4-D5DB-4915-AF0F-908A6043E462} = {0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A} + {6EF61FBD-3081-4CC8-B909-94176A12DD1A} = {9BCA00E2-D072-424B-A6DF-5BECF719C1FB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/build.cake b/build.cake index 814abff27..32a2c57ea 100644 --- a/build.cake +++ b/build.cake @@ -2,27 +2,20 @@ static string Target; Target = GetArgument("target|t", "Default"); static string Configuration; Configuration = GetArgument("configuration|c", "Release"); static bool NoPush; NoPush = HasArgument("nopush"); -#load cake/constants.cake -#load cake/dotnet.cake -#load cake/header-check.cake -#load cake/package-checks.cake -#load cake/test-results.cake -#load cake/package-tests.cake -#load cake/package-tester.cake -#load cake/versioning.cake -#load cake/utilities.cake -#load cake/package-definitions.cake +// Load scripts +#load cake/common/*.cake +#load cake/*.cake // Install Tools #tool NuGet.CommandLine&version=6.9.1 #tool dotnet:?package=GitVersion.Tool&version=5.12.0 #tool dotnet:?package=GitReleaseManager.Tool&version=0.12.1 -BuildVersion _buildVersion; -string ProductVersion => _buildVersion.ProductVersion; -string SemVer => _buildVersion.SemVer; -string PreReleaseLabel => _buildVersion.PreReleaseLabel; -bool IsReleaseBranch => _buildVersion.IsReleaseBranch; +//BuildVersion _buildVersion; +//string ProductVersion => _buildVersion.ProductVersion; +//string SemVer => _buildVersion.SemVer; +//string PreReleaseLabel => _buildVersion.PreReleaseLabel; +//bool IsReleaseBranch => _buildVersion.IsReleaseBranch; var UnreportedErrors = new List(); var installedNetCoreRuntimes = GetInstalledNetCoreRuntimes(); @@ -30,19 +23,23 @@ var installedNetCoreRuntimes = GetInstalledNetCoreRuntimes(); ////////////////////////////////////////////////////////////////////// // SETUP AND TEARDOWN TASKS ////////////////////////////////////////////////////////////////////// -Setup(context => +Setup(context => { - Information("Creating BuildVersion"); - _buildVersion = new BuildVersion(context); + //Information("Creating BuildVersion"); + //_buildVersion = new BuildVersion(context); - Information("Building {0} version {1} of NUnit Console/Engine.", Configuration, ProductVersion); - Information("PreReleaseLabel is " + PreReleaseLabel); + //Information("Building {0} version {1} of NUnit Console/Engine.", Configuration, ProductVersion); + //Information("PreReleaseLabel is " + PreReleaseLabel); + + var settings = BuildSettings.Create(context); Information("Initializing PackageDefinitions"); - InitializePackageDefinitions(context); + InitializePackageDefinitions(settings); if (BuildSystem.IsRunningOnAppVeyor) - AppVeyor.UpdateBuildVersion(ProductVersion + "-" + AppVeyor.Environment.Build.Number); + AppVeyor.UpdateBuildVersion(settings.PackageVersion + "-" + AppVeyor.Environment.Build.Number); + + return settings; }); Teardown(context => @@ -51,37 +48,6 @@ Teardown(context => DisplayUnreportedErrors(); }); -////////////////////////////////////////////////////////////////////// -// CLEANING -////////////////////////////////////////////////////////////////////// - -Task("Clean") - .Description("Cleans directories.") - .Does(() => - { - CleanDirectory(BIN_DIR); - CleanDirectory(PACKAGE_DIR); - CleanDirectory(IMAGE_DIR); - CleanDirectory(EXTENSIONS_DIR); - CleanDirectory(PACKAGE_DIR); - }); - -Task("CleanAll") - .Description("Cleans both Debug and Release Directories followed by deleting object directories") - .Does(() => - { - Information("Cleaning both Debug and Release"); - CleanDirectory(PROJECT_DIR + "bin"); - CleanDirectory(PACKAGE_DIR); - CleanDirectory(IMAGE_DIR); - CleanDirectory(EXTENSIONS_DIR); - CleanDirectory(PACKAGE_DIR); - - Information("Deleting object directories"); - foreach (var dir in GetDirectories("src/**/obj/")) - DeleteDirectory(dir, new DeleteDirectorySettings() { Recursive = true }); - }); - ////////////////////////////////////////////////////////////////////// // BUILD ENGINE AND CONSOLE ////////////////////////////////////////////////////////////////////// @@ -90,28 +56,28 @@ Task("Build") .Description("Builds the engine and console") .IsDependentOn("CheckHeaders") .IsDependentOn("Clean") - .Does(() => + .Does((settings) => { - if (IsRunningOnWindows()) - BuildSolution(); + if (settings.IsRunningOnWindows) + BuildSolution(settings.BuildVersion); else - BuildEachProjectSeparately(); + BuildEachProjectSeparately(settings.BuildVersion); }); -public void BuildSolution() +public void BuildSolution(BuildVersion buildVersion) { - MSBuild(SOLUTION_FILE, CreateMSBuildSettings("Build").WithRestore()); + MSBuild(SOLUTION_FILE, CreateMSBuildSettings("Build", buildVersion).WithRestore()); // Publishing in place where needed to ensure that all references are present. // TODO: May not be needed DisplayBanner("Publishing ENGINE API Project for NETSTANDARD_2.0"); - MSBuild(ENGINE_API_PROJECT, CreateMSBuildSettings("Publish") + MSBuild(ENGINE_API_PROJECT, CreateMSBuildSettings("Publish", buildVersion) .WithProperty("TargetFramework", "netstandard2.0") .WithProperty("PublishDir", BIN_DIR + "netstandard2.0")); DisplayBanner("Publishing ENGINE Project for NETSTANDARD2.0"); - MSBuild(ENGINE_PROJECT, CreateMSBuildSettings("Publish") + MSBuild(ENGINE_PROJECT, CreateMSBuildSettings("Publish", buildVersion) .WithProperty("TargetFramework", "netstandard2.0") .WithProperty("PublishDir", BIN_DIR + "netstandard2.0")); @@ -119,42 +85,42 @@ public void BuildSolution() foreach (var framework in new[] { "netcoreapp3.1", "net5.0" }) { DisplayBanner($"Publishing AGENT Project for {framework.ToUpper()}"); - MSBuild(AGENT_PROJECT, CreateMSBuildSettings("Publish") + MSBuild(AGENT_PROJECT, CreateMSBuildSettings("Publish", buildVersion) .WithProperty("TargetFramework", framework) .WithProperty("PublishDir", BIN_DIR + "agents/" + framework)); } } // TODO: Test this on linux to see if changes are needed -private void BuildEachProjectSeparately() +private void BuildEachProjectSeparately(BuildVersion buildVersion) { DotNetRestore(SOLUTION_FILE); - BuildProject(ENGINE_PROJECT); - BuildProject(CONSOLE_PROJECT); - BuildProject(AGENT_PROJECT); - BuildProject(AGENT_X86_PROJECT); + BuildProject(ENGINE_PROJECT, buildVersion); + BuildProject(CONSOLE_PROJECT, buildVersion); + BuildProject(AGENT_PROJECT, buildVersion); + BuildProject(AGENT_X86_PROJECT, buildVersion); - BuildProject(ENGINE_TESTS_PROJECT, "net35", "netcoreapp2.1"); - BuildProject(ENGINE_CORE_TESTS_PROJECT, "net35", "netcoreapp2.1", "netcoreapp3.1", "net5.0", "net6.0", "net8.0"); - BuildProject(CONSOLE_TESTS_PROJECT, "net35", "net6.0", "net8.0"); + BuildProject(ENGINE_TESTS_PROJECT, buildVersion, "net35", "netcoreapp2.1"); + BuildProject(ENGINE_CORE_TESTS_PROJECT, buildVersion, "net35", "netcoreapp2.1", "netcoreapp3.1", "net5.0", "net6.0", "net8.0"); + BuildProject(CONSOLE_TESTS_PROJECT, buildVersion, "net35", "net6.0", "net8.0"); - BuildProject(MOCK_ASSEMBLY_X86_PROJECT, "net35", "net40", "netcoreapp2.1", "netcoreapp3.1"); - BuildProject(NOTEST_PROJECT, "net35", "netcoreapp2.1", "netcoreapp3.1"); + BuildProject(MOCK_ASSEMBLY_X86_PROJECT, buildVersion, "net35", "net40", "netcoreapp2.1", "netcoreapp3.1"); + BuildProject(NOTEST_PROJECT, buildVersion, "net35", "netcoreapp2.1", "netcoreapp3.1"); DisplayBanner("Publish .NET Core & Standard projects"); - MSBuild(ENGINE_PROJECT, CreateMSBuildSettings("Publish") + MSBuild(ENGINE_PROJECT, CreateMSBuildSettings("Publish", buildVersion) .WithProperty("TargetFramework", "netstandard2.0") .WithProperty("PublishDir", BIN_DIR + "netstandard2.0")); CopyFileToDirectory( BIN_DIR + "netstandard2.0/testcentric.engine.metadata.dll", BIN_DIR + "netcoreapp2.1"); - MSBuild(ENGINE_TESTS_PROJECT, CreateMSBuildSettings("Publish") + MSBuild(ENGINE_TESTS_PROJECT, CreateMSBuildSettings("Publish", buildVersion) .WithProperty("TargetFramework", "netcoreapp2.1") .WithProperty("PublishDir", BIN_DIR + "netcoreapp2.1")); - MSBuild(ENGINE_CORE_TESTS_PROJECT, CreateMSBuildSettings("Publish") + MSBuild(ENGINE_CORE_TESTS_PROJECT, CreateMSBuildSettings("Publish", buildVersion) .WithProperty("TargetFramework", "netcoreapp2.1") .WithProperty("PublishDir", BIN_DIR + "netcoreapp2.1")); } @@ -162,12 +128,12 @@ private void BuildEachProjectSeparately() // NOTE: If we use DotNet to build on Linux, then our net35 projects fail. // If we use MSBuild, then the net5.0 projects fail. So we build each project // differently depending on whether it has net35 as one of its targets. -private void BuildProject(string project, params string[] targetFrameworks) +private void BuildProject(string project, BuildVersion buildVersion, params string[] targetFrameworks) { if (targetFrameworks.Length == 0) { DisplayBanner($"Building {System.IO.Path.GetFileName(project)}"); - DotNetMSBuild(project, CreateDotNetMSBuildSettings("Build")); + DotNetMSBuild(project, CreateDotNetMSBuildSettings("Build", buildVersion)); } else { @@ -175,9 +141,9 @@ private void BuildProject(string project, params string[] targetFrameworks) { DisplayBanner($"Building {System.IO.Path.GetFileName(project)} for {framework}"); if (framework == "net35") - MSBuild(project, CreateMSBuildSettings("Build").WithProperty("TargetFramework", framework)); + MSBuild(project, CreateMSBuildSettings("Build", buildVersion).WithProperty("TargetFramework", framework)); else - DotNetMSBuild(project, CreateDotNetMSBuildSettings("Build").WithProperty("TargetFramework", framework)); + DotNetMSBuild(project, CreateDotNetMSBuildSettings("Build", buildVersion).WithProperty("TargetFramework", framework)); } } } @@ -189,15 +155,15 @@ private void BuildProject(string project, params string[] targetFrameworks) Task("BuildCppTestFiles") .Description("Builds the C++ mock test assemblies") .WithCriteria(IsRunningOnWindows) - .Does(() => + .Does((settings) => { MSBuild( SOURCE_DIR + "NUnitEngine/mock-cpp-clr/mock-cpp-clr-x86.vcxproj", - CreateMSBuildSettings("Build").WithProperty("Platform", "x86")); + CreateMSBuildSettings("Build", settings.BuildVersion).WithProperty("Platform", "x86")); MSBuild( SOURCE_DIR + "NUnitEngine/mock-cpp-clr/mock-cpp-clr-x64.vcxproj", - CreateMSBuildSettings("Build").WithProperty("Platform", "x64")); + CreateMSBuildSettings("Build", settings.BuildVersion).WithProperty("Platform", "x64")); }); ////////////////////////////////////////////////////////////////////// @@ -559,9 +525,9 @@ Task("PublishPackages") // which depends on it, or directly when recovering from errors. Task("PublishToMyGet") .Description("Publish packages to MyGet") - .Does(() => + .Does((settings) => { - if (!ShouldPublishToMyGet) + if (!settings.ShouldPublishToMyGet) Information("Nothing to publish to MyGet from this run."); else { @@ -591,9 +557,9 @@ Task("PublishToMyGet") // which depends on it, or directly when recovering from errors. Task("PublishToNuGet") .Description("Publish packages to NuGet") - .Does(() => + .Does((settings) => { - if (!ShouldPublishToNuGet) + if (!settings.ShouldPublishToNuGet) Information("Nothing to publish to NuGet from this run."); else { @@ -616,9 +582,9 @@ Task("PublishToNuGet") // which depends on it, or directly when recovering from errors. Task("PublishToChocolatey") .Description("Publish packages to Chocolatey") - .Does(() => + .Does((settings) => { - if (!ShouldPublishToChocolatey) + if (!settings.ShouldPublishToChocolatey) Information("Nothing to publish to Chocolatey from this run."); else { @@ -653,18 +619,18 @@ Task("ListInstalledNetCoreRuntimes") ////////////////////////////////////////////////////////////////////// Task("CreateDraftRelease") - .Does(() => + .Does((settings) => { bool isDirectTarget = Target == "CreateDraftRelease"; if (isDirectTarget && !HasArgument("productVersion")) throw new Exception("Must specify --productVersion with the CreateDraftRelease target."); - if (IsReleaseBranch || isDirectTarget) + if (settings.BuildVersion.IsReleaseBranch || isDirectTarget) { - string milestone = IsReleaseBranch - ? _buildVersion.BranchName.Substring(8) - : ProductVersion; + string milestone = settings.IsReleaseBranch + ? settings.BranchName.Substring(8) + : settings.PackageVersion; string releaseName = $"NUnit Console and Engine {milestone}"; Information($"Creating draft release for {releaseName}"); @@ -697,12 +663,12 @@ Task("CreateDraftRelease") ////////////////////////////////////////////////////////////////////// Task("CreateProductionRelease") - .Does(() => + .Does((settings) => { - if (IsProductionRelease) + if (settings.IsProductionRelease) { string token = EnvironmentVariable(GITHUB_ACCESS_TOKEN); - string tagName = ProductVersion; + string tagName = settings.PackageVersion; var assetList = new List(); foreach (var package in AllPackages) diff --git a/cake/build-settings.cake b/cake/build-settings.cake index 6aaf67882..0b43f9bc5 100644 --- a/cake/build-settings.cake +++ b/cake/build-settings.cake @@ -1,23 +1,182 @@ -#load ./ci.cake -#load ./packaging.cake -#load ./package-checks.cake -#load ./test-results.cake -#load ./package-tests.cake -#load ./package-tester.cake -#load ./header-check.cake -#load ./local-tasks.cake - public class BuildSettings { - public BuildSettings(ISetupContext context) - { - if (context == null) - throw new System.ArgumentNullException(nameof(context)); + private ISetupContext _context; + private BuildSystem _buildSystem; + + public static BuildSettings Create(ISetupContext context) + { + var parameters = new BuildSettings(context); + //parameters.Validate(); + + return parameters; + } + + private BuildSettings(ISetupContext context) + { + _context = context; + _buildSystem = context.BuildSystem(); + + Target = _context.TargetTask.Name; + TasksToExecute = _context.TasksToExecute.Select(t => t.Name); + + ProjectDirectory = _context.Environment.WorkingDirectory.FullPath + "/"; + + Configuration = _context.Argument("configuration", DEFAULT_CONFIGURATION); + + MyGetApiKey = _context.EnvironmentVariable(MYGET_API_KEY); + NuGetApiKey = _context.EnvironmentVariable(NUGET_API_KEY); + ChocolateyApiKey = _context.EnvironmentVariable(CHOCO_API_KEY); + GitHubAccessToken = _context.EnvironmentVariable(GITHUB_ACCESS_TOKEN); + + BuildVersion = new BuildVersion(context); + } + + public string Target { get; } + public IEnumerable TasksToExecute { get; } + + public ICakeContext Context => _context; + + public string Configuration { get; } + + public BuildVersion BuildVersion { get; } + public string PackageVersion => BuildVersion.ProductVersion; + public string AssemblyVersion => BuildVersion.AssemblyVersion; + public string AssemblyFileVersion => BuildVersion.AssemblyFileVersion; + public string AssemblyInformationalVersion => BuildVersion.AssemblyInformationalVersion; + + public int PackageTestLevel { get; } + + public bool IsLocalBuild => _buildSystem.IsLocalBuild; + public bool IsRunningOnUnix => _context.IsRunningOnUnix(); + public bool IsRunningOnWindows => _context.IsRunningOnWindows(); + public bool IsRunningOnAppVeyor => _buildSystem.AppVeyor.IsRunningOnAppVeyor; + + public string ProjectDirectory { get; } + public string OutputDirectory => ProjectDirectory + "bin/" + Configuration + "/"; + public string SourceDirectory => ProjectDirectory + "src/"; + public string PackageDirectory => ProjectDirectory + "output/"; + public string PackageTestDirectory => PackageDirectory + "tests/"; + public string ToolsDirectory => ProjectDirectory + "tools/"; + public string NuGetInstallDirectory => ToolsDirectory + NUGET_ID + "/"; + public string ChocolateyInstallDirectory => ToolsDirectory + CHOCO_ID + "/"; + + public string NuGetPackageName => NUGET_ID + "." + PackageVersion + ".nupkg"; + public string ChocolateyPackageName => CHOCO_ID + "." + PackageVersion + ".nupkg"; + + public string NuGetPackage => PackageDirectory + NuGetPackageName; + public string ChocolateyPackage => PackageDirectory + ChocolateyPackageName; + + public string MyGetPushUrl => MYGET_PUSH_URL; + public string NuGetPushUrl => NUGET_PUSH_URL; + public string ChocolateyPushUrl => CHOCO_PUSH_URL; + + public string MyGetApiKey { get; } + public string NuGetApiKey { get; } + public string ChocolateyApiKey { get; } + public string GitHubAccessToken { get; } + + public string BranchName => BuildVersion.BranchName; + public bool IsReleaseBranch => BuildVersion.IsReleaseBranch; + + public bool IsPreRelease => BuildVersion.IsPreRelease; + public bool ShouldPublishToMyGet => + !IsPreRelease || LABELS_WE_PUBLISH_ON_MYGET.Contains(BuildVersion.PreReleaseLabel); + public bool ShouldPublishToNuGet => + !IsPreRelease || LABELS_WE_PUBLISH_ON_NUGET.Contains(BuildVersion.PreReleaseLabel); + public bool ShouldPublishToChocolatey => + !IsPreRelease || LABELS_WE_PUBLISH_ON_CHOCOLATEY.Contains(BuildVersion.PreReleaseLabel); + public bool IsProductionRelease => + !IsPreRelease || LABELS_WE_RELEASE_ON_GITHUB.Contains(BuildVersion.PreReleaseLabel); + + private void Validate() + { + var validationErrors = new List(); + + if (TasksToExecute.Contains("PublishPackages")) + { + if (ShouldPublishToMyGet && string.IsNullOrEmpty(MyGetApiKey)) + validationErrors.Add("MyGet ApiKey was not set."); + if (ShouldPublishToNuGet && string.IsNullOrEmpty(NuGetApiKey)) + validationErrors.Add("NuGet ApiKey was not set."); + if (ShouldPublishToChocolatey && string.IsNullOrEmpty(ChocolateyApiKey)) + validationErrors.Add("Chocolatey ApiKey was not set."); + } + + if (TasksToExecute.Contains("CreateDraftRelease") && (IsReleaseBranch || IsProductionRelease)) + { + if (string.IsNullOrEmpty(GitHubAccessToken)) + validationErrors.Add("GitHub Access Token was not set."); + } + + if (validationErrors.Count > 0) + { + DumpSettings(); + + var msg = new StringBuilder("Parameter validation failed! See settings above.\n\nErrors found:\n"); + foreach (var error in validationErrors) + msg.AppendLine(" " + error); + + throw new InvalidOperationException(msg.ToString()); + } + } + + public void DumpSettings() + { + Console.WriteLine("\nTASKS"); + Console.WriteLine("Target: " + Target); + Console.WriteLine("TasksToExecute: " + string.Join(", ", TasksToExecute)); + + Console.WriteLine("\nENVIRONMENT"); + Console.WriteLine("IsLocalBuild: " + IsLocalBuild); + Console.WriteLine("IsRunningOnWindows: " + IsRunningOnWindows); + Console.WriteLine("IsRunningOnUnix: " + IsRunningOnUnix); + Console.WriteLine("IsRunningOnAppVeyor: " + IsRunningOnAppVeyor); + + Console.WriteLine("\nVERSIONING"); + Console.WriteLine("PackageVersion: " + PackageVersion); + Console.WriteLine("AssemblyVersion: " + AssemblyVersion); + Console.WriteLine("AssemblyFileVersion: " + AssemblyFileVersion); + Console.WriteLine("AssemblyInformationalVersion: " + AssemblyInformationalVersion); + Console.WriteLine("SemVer: " + BuildVersion.SemVer); + Console.WriteLine("IsPreRelease: " + BuildVersion.IsPreRelease); + Console.WriteLine("PreReleaseLabel: " + BuildVersion.PreReleaseLabel); + Console.WriteLine("PreReleaseSuffix: " + BuildVersion.PreReleaseSuffix); + + Console.WriteLine("\nDIRECTORIES"); + Console.WriteLine("Project: " + ProjectDirectory); + Console.WriteLine("Output: " + OutputDirectory); + //Console.WriteLine("Source: " + SourceDirectory); + //Console.WriteLine("NuGet: " + NuGetDirectory); + //Console.WriteLine("Choco: " + ChocoDirectory); + Console.WriteLine("Package: " + PackageDirectory); + //Console.WriteLine("ZipImage: " + ZipImageDirectory); + //Console.WriteLine("ZipTest: " + ZipTestDirectory); + //Console.WriteLine("NuGetTest: " + NuGetTestDirectory); + //Console.WriteLine("ChocoTest: " + ChocolateyTestDirectory); + + Console.WriteLine("\nBUILD"); + //Console.WriteLine("Build With: " + (UsingXBuild ? "XBuild" : "MSBuild")); + Console.WriteLine("Configuration: " + Configuration); + //Console.WriteLine("Engine Runtimes: " + string.Join(", ", SupportedEngineRuntimes)); + //Console.WriteLine("Core Runtimes: " + string.Join(", ", SupportedCoreRuntimes)); + //Console.WriteLine("Agent Runtimes: " + string.Join(", ", SupportedAgentRuntimes)); + + Console.WriteLine("\nPACKAGING"); + Console.WriteLine("MyGetPushUrl: " + MyGetPushUrl); + Console.WriteLine("NuGetPushUrl: " + NuGetPushUrl); + Console.WriteLine("ChocolateyPushUrl: " + ChocolateyPushUrl); + Console.WriteLine("MyGetApiKey: " + (!string.IsNullOrEmpty(MyGetApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); + Console.WriteLine("NuGetApiKey: " + (!string.IsNullOrEmpty(NuGetApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); + Console.WriteLine("ChocolateyApiKey: " + (!string.IsNullOrEmpty(ChocolateyApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); - Target = context.TargetTask.Name; - Configuration = context.Argument("configuration", "Release"); - } + Console.WriteLine("\nPUBLISHING"); + Console.WriteLine("ShouldPublishToMyGet: " + ShouldPublishToMyGet); + Console.WriteLine("ShouldPublishToNuGet: " + ShouldPublishToNuGet); + Console.WriteLine("ShouldPublishToChocolatey: " + ShouldPublishToChocolatey); - public string Target { get; } - public string Configuration { get; } + Console.WriteLine("\nRELEASING"); + Console.WriteLine("BranchName: " + BranchName); + Console.WriteLine("IsReleaseBranch: " + IsReleaseBranch); + Console.WriteLine("IsProductionRelease: " + IsProductionRelease); + } } \ No newline at end of file diff --git a/cake/common/common-constants.cake b/cake/common/common-constants.cake new file mode 100644 index 000000000..a6b5409f5 --- /dev/null +++ b/cake/common/common-constants.cake @@ -0,0 +1,23 @@ +//////////////////////////////////////////////////////////////////// +// COMMON CONSTANTS +////////////////////////////////////////////////////////////////////// + +// URLs for uploading packages +private const string MYGET_PUSH_URL = "https://www.myget.org/F/nunit/api/v2"; +private const string NUGET_PUSH_URL = "https://api.nuget.org/v3/index.json"; +private const string CHOCO_PUSH_URL = "https://push.chocolatey.org/"; + +// Environment Variable names holding API keys +private const string MYGET_API_KEY = "MYGET_API_KEY"; +private const string NUGET_API_KEY = "NUGET_API_KEY"; +private const string CHOCO_API_KEY = "CHOCO_API_KEY"; + +// GitHub Information +private const string GITHUB_OWNER = "nunit"; +private const string GITHUB_ACCESS_TOKEN = "GITHUB_ACCESS_TOKEN"; + +// Pre-release labels that we publish +private static readonly string[] LABELS_WE_PUBLISH_ON_MYGET = { "dev" }; +private static readonly string[] LABELS_WE_PUBLISH_ON_NUGET = { "alpha", "beta", "rc" }; +private static readonly string[] LABELS_WE_PUBLISH_ON_CHOCOLATEY = { "alpha", "beta", "rc" }; +private static readonly string[] LABELS_WE_RELEASE_ON_GITHUB = { "alpha", "beta", "rc" }; diff --git a/cake/dotnet.cake b/cake/common/dotnet.cake similarity index 100% rename from cake/dotnet.cake rename to cake/common/dotnet.cake diff --git a/cake/common/tasks.cake b/cake/common/tasks.cake new file mode 100644 index 000000000..f8ebe8f54 --- /dev/null +++ b/cake/common/tasks.cake @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////// +// COMMON TASKS - ADD PROJECT-SPECIFIC TASKS IN BUILD.CAKE +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// +// DUMP SETTINGS +////////////////////////////////////////////////////////////////////// + +Task("DumpSettings") + .Does((settings) => + { + settings.DumpSettings(); + }); + +////////////////////////////////////////////////////////////////////// +// CLEANING +////////////////////////////////////////////////////////////////////// + +Task("Clean") + .Description("Cleans directories.") + .Does((settings) => + { + CleanDirectory(BIN_DIR); + CleanDirectory(PACKAGE_DIR); + CleanDirectory(IMAGE_DIR); + CleanDirectory(EXTENSIONS_DIR); + CleanDirectory(PACKAGE_DIR); + }); + +Task("CleanAll") + .Description("Cleans both Debug and Release Directories followed by deleting object directories") + .Does((settings) => + { + Information("Cleaning both Debug and Release"); + CleanDirectory(PROJECT_DIR + "bin"); + CleanDirectory(PACKAGE_DIR); + CleanDirectory(IMAGE_DIR); + CleanDirectory(EXTENSIONS_DIR); + CleanDirectory(PACKAGE_DIR); + + Information("Deleting object directories"); + foreach (var dir in GetDirectories("src/**/obj/")) + DeleteDirectory(dir, new DeleteDirectorySettings() { Recursive = true }); + }); + diff --git a/cake/constants.cake b/cake/constants.cake index c3b2682f2..784f02eec 100644 --- a/cake/constants.cake +++ b/cake/constants.cake @@ -5,6 +5,11 @@ // Some values are static so they may be used in property initialization and in // classes. Initialization is separate to allow use of non-constant expressions. +private const string GITHUB_REPO = "nunit-console"; +const string NUGET_ID = "NUnit.Extension.NUnitProjectLoader"; +const string CHOCO_ID = "nunit-extension-nunit-project-loader"; +const string DEFAULT_CONFIGURATION = "Release"; + // Directories static string PROJECT_DIR; PROJECT_DIR = Context.Environment.WorkingDirectory.FullPath + "/"; static string PACKAGE_DIR; PACKAGE_DIR = Argument("artifact-dir", PROJECT_DIR + "package") + "/"; @@ -63,24 +68,3 @@ var BUNDLED_EXTENSIONS = new[] "NUnit.Extension.NUnitV2ResultWriter", "NUnit.Extension.TeamCityEventListener" }; - -// URLs for uploading packages -private const string MYGET_PUSH_URL = "https://www.myget.org/F/nunit/api/v2"; -private const string NUGET_PUSH_URL = "https://api.nuget.org/v3/index.json"; -private const string CHOCO_PUSH_URL = "https://push.chocolatey.org/"; - -// Environment Variable names holding API keys -private const string MYGET_API_KEY = "MYGET_API_KEY"; -private const string NUGET_API_KEY = "NUGET_API_KEY"; -private const string CHOCO_API_KEY = "CHOCO_API_KEY"; - -// GitHub Information -private const string GITHUB_OWNER = "nunit"; -private const string GITHUB_REPO = "nunit-console"; -private const string GITHUB_ACCESS_TOKEN = "GITHUB_ACCESS_TOKEN"; - -// Pre-release labels that we publish -private static readonly string[] LABELS_WE_PUBLISH_ON_MYGET = { "dev" }; -private static readonly string[] LABELS_WE_PUBLISH_ON_NUGET = { "alpha", "beta", "rc" }; -private static readonly string[] LABELS_WE_PUBLISH_ON_CHOCOLATEY = { "alpha", "beta", "rc" }; -private static readonly string[] LABELS_WE_RELEASE_ON_GITHUB = { "alpha", "beta", "rc" }; diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake index b38bdc7a0..1514bcc91 100644 --- a/cake/package-definitions.cake +++ b/cake/package-definitions.cake @@ -12,10 +12,11 @@ PackageDefinition NUnitConsoleRunnerChocolateyPackage; PackageDefinition NUnitConsoleMsiPackage; PackageDefinition NUnitConsoleZipPackage; -public void InitializePackageDefinitions(ICakeContext context) +public void InitializePackageDefinitions(BuildSettings settings) { const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); + var context = settings.Context; // Tests run for all runner packages except NETCORE runner var StandardRunnerTests = new List @@ -68,14 +69,14 @@ public void InitializePackageDefinitions(ICakeContext context) NUnitConsoleNuGetPackage = new NuGetPackage( context: context, id: "NUnit.Console", - version: ProductVersion, + version: settings.PackageVersion, source: NUGET_DIR + "runners/nunit.console-runner-with-extensions.nuspec", checks: new PackageCheck[] { HasFile("LICENSE.txt") }), NUnitConsoleRunnerNuGetPackage = new NuGetPackage( context: context, id: "NUnit.ConsoleRunner", - version: ProductVersion, + version: settings.PackageVersion, source: NUGET_DIR + "runners/nunit.console-runner.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), @@ -104,7 +105,7 @@ public void InitializePackageDefinitions(ICakeContext context) NUnitConsoleRunnerNet80Package = new NuGetPackage( context: context, id: "NUnit.ConsoleRunner.NetCore", - version: ProductVersion, + version: settings.PackageVersion, source: NUGET_DIR + "runners/nunit.console-runner.netcore.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), @@ -119,7 +120,7 @@ public void InitializePackageDefinitions(ICakeContext context) NUnitConsoleRunnerNet60Package = new NuGetPackage( context: context, id: "NUnit.ConsoleRunner.NetCore", - version: ProductVersion, + version: settings.PackageVersion, source: NUGET_DIR + "runners/nunit.console-runner.netcore.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), @@ -134,7 +135,7 @@ public void InitializePackageDefinitions(ICakeContext context) NUnitConsoleRunnerChocolateyPackage = new ChocolateyPackage( context: context, id: "nunit-console-runner", - version: ProductVersion, + version: settings.PackageVersion, source: CHOCO_DIR + "nunit-console-runner.nuspec", checks: new PackageCheck[] { HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt").AndFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.choco.addins"), @@ -152,7 +153,7 @@ public void InitializePackageDefinitions(ICakeContext context) NUnitConsoleMsiPackage = new MsiPackage( context: context, id: "NUnit.Console", - version: SemVer, + version: settings.BuildVersion.SemVer, source: MSI_DIR + "nunit/nunit.wixproj", checks: new PackageCheck[] { HasDirectory("NUnit.org").WithFiles("LICENSE.txt", "NOTICES.txt", "nunit.ico"), @@ -165,7 +166,7 @@ public void InitializePackageDefinitions(ICakeContext context) NUnitConsoleZipPackage = new ZipPackage( context: context, id: "NUnit.Console", - version: ProductVersion, + version: settings.PackageVersion, source: ZIP_IMG_DIR, checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt", "CHANGES.txt"), @@ -189,7 +190,7 @@ public void InitializePackageDefinitions(ICakeContext context) NUnitEnginePackage = new NuGetPackage( context: context, id: "NUnit.Engine", - version: ProductVersion, + version: settings.PackageVersion, source: NUGET_DIR + "engine/nunit.engine.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), @@ -210,7 +211,7 @@ public void InitializePackageDefinitions(ICakeContext context) NUnitEngineApiPackage = new NuGetPackage( context: context, id: "NUnit.Engine.Api", - version: ProductVersion, + version: settings.PackageVersion, source: NUGET_DIR + "engine/nunit.engine.api.nuspec", checks: new PackageCheck[] { HasFile("LICENSE.txt"), diff --git a/cake/utilities.cake b/cake/utilities.cake index 23785a7ed..47f4fb3b5 100644 --- a/cake/utilities.cake +++ b/cake/utilities.cake @@ -54,13 +54,13 @@ public static void DisplayBanner(string message) // HELPER METHODS - BUILD ////////////////////////////////////////////////////////////////////// -MSBuildSettings CreateMSBuildSettings(string target) +MSBuildSettings CreateMSBuildSettings(string target, BuildVersion buildVersion) { var settings = new MSBuildSettings() .SetConfiguration(Configuration) .SetVerbosity(Verbosity.Minimal) - .WithProperty("Version", ProductVersion) - .WithProperty("ApiFileVersion", SemVer + ".0") + .WithProperty("Version", buildVersion.ProductVersion) + .WithProperty("ApiFileVersion", buildVersion.SemVer + ".0") .WithTarget(target) // Workaround for https://github.com/Microsoft/msbuild/issues/3626 .WithProperty("AddSyntheticProjectReferencesForSolutionDependencies", "false"); @@ -87,12 +87,12 @@ MSBuildSettings CreateMSBuildSettings(string target) return settings; } -DotNetMSBuildSettings CreateDotNetMSBuildSettings(string target) +DotNetMSBuildSettings CreateDotNetMSBuildSettings(string target, BuildVersion buildVersion) { return new DotNetMSBuildSettings() .SetConfiguration(Configuration) - .WithProperty("Version", ProductVersion) - .WithProperty("ApiFileVersion", SemVer + ".0") + .WithProperty("Version", buildVersion.ProductVersion) + .WithProperty("ApiFileVersion", buildVersion.SemVer + ".0") .WithTarget(target); } @@ -247,10 +247,3 @@ private void CheckPackageExists(FilePath package) throw new InvalidOperationException( $"Package not found: {package.GetFilename()}.\nCode may have changed since package was last built."); } - -public bool IsPreRelease => !string.IsNullOrEmpty(PreReleaseLabel); - -public bool ShouldPublishToMyGet => IsPreRelease && LABELS_WE_PUBLISH_ON_MYGET.Contains(PreReleaseLabel); -public bool ShouldPublishToNuGet => !IsPreRelease || LABELS_WE_PUBLISH_ON_NUGET.Contains(PreReleaseLabel); -public bool ShouldPublishToChocolatey => !IsPreRelease || LABELS_WE_PUBLISH_ON_CHOCOLATEY.Contains(PreReleaseLabel); -public bool IsProductionRelease => !IsPreRelease || LABELS_WE_RELEASE_ON_GITHUB.Contains(PreReleaseLabel); diff --git a/cake/versioning.cake b/cake/versioning.cake index 81a4f28bd..35955c529 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -14,12 +14,11 @@ public class BuildVersion // then parsing it to provide information that is used in the build. public BuildVersion(ISetupContext context) { - Console.WriteLine("Start BuildVersion"); _context = context; if (context==null) throw new ArgumentNullException(nameof(context)); _gitVersion = context.GitVersion(); - Console.WriteLine($"Running GitVersion: {_gitVersion.FullSemVer}"); + BranchName = _gitVersion.BranchName; IsReleaseBranch = BranchName.StartsWith("release-"); @@ -93,7 +92,7 @@ public class BuildVersion label = "ci"; string suffix = "-" + label + _gitVersion.CommitsSinceVersionSourcePadded; - Console.WriteLine($"CalculateProductVersion: {label} {branchName} {suffix}"); + switch (label) { case "ci": From 350e4fade69a96654cc74f44427c3e95c3cbadb7 Mon Sep 17 00:00:00 2001 From: CharliePoole Date: Fri, 21 Jun 2024 20:57:24 -0700 Subject: [PATCH 061/190] Update platform-support.md --- platform-support.md | 59 +++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/platform-support.md b/platform-support.md index 2f6146b08..a05de6c42 100644 --- a/platform-support.md +++ b/platform-support.md @@ -1,32 +1,55 @@ # Platform Support Lifecycle "Platform Support" for this project includes three somewhat different things: -1. The target platforms for which tests may be written. -2. The platforms under which those tests will run, which is equivalent to the list of agents we supply. +1. Test assembly target platforms, that is, the target platforms for which tests may be written. +2. The target platforms under which those tests will actually run, which is equivalent to the list of agents we provide. 3. The minimum platforms required to execute the runner and engine themselves, without regard to the tests being run. ## Test Assembly Target Runtimes -We will continue to support tests targeting runtimes which are out of support from Microsoft's perspective, so long -as we are able to do so without significant additional effort or security risk. If no agent is available for a runtime, -the tests will be run on the closest higher runtime available. - -> _We currently support execution of tests written to target any version of the .NET Framework >= 2.0 and any version -> of .NET Core >= 3.1, including .NET 5.0 and higher._ +We currently support execution of tests written to target any version of the .NET Framework >= 2.0 and any version +of .NET Core >= 3.1, including .NET 5.0 and higher. We will continue to support tests targeting runtimes which are +out of support from Microsoft's perspective, so long as we are able to do so without significant additional effort +and without security risk. If no agent is available for a runtime, the tests will be run on the closest higher +runtime available. ## Agents Provided -We will continue to provide agents for any Microsoft runtime for at least six months after its end of life. This is -intended to support continued testing of legacy applications while users are in the process of upgrade. However, agents -for runtimes which have been declared a security risk may be removed immediately. - -> _We currently supply agents for .NET Famework 2.0 and 4.6.2. .NET Core 2,1 and 3.1 and .NET 5.0, 6.0 and 7.0. The .NET -> Core 2.1 runner is being removed in version 3.18.0 and .NET 8.0 will probably be added before this document is finalized. -> Once this document is approved, we will begin to phase out agents according to the above criteria._ +We currently supply a fairly large selection of agents with the console runner: +* .NET Framework 2.0 +* .NET Framework 4.6.2 +* .NET Core 2,1 +* ,NET Core 3.1 +* .NET 5.0 +* .NET 6.0 +* .NET 7.0 +* .NET 8.0 (coming in version 3.18.0) + +As a general policy, we will continue to provide agents for any Microsoft runtime for at least six months after its +official end of life. This is intended to support continued testing of legacy applications while users are in the +process of upgrade. However, agents for runtimes which have been declared a security risk may be removed immediately. + +Based on that policy and the planned end-of-life dates for runtimes, we expect to retire agents on or after the +dates listed in the following table. + +| Runtime | Retirement Date | +| -------------------- | --------------- | +| .NET Framework 2.0 | TBD | +| .NET Framework 4.6.2 | TBD | +| .NET Core 2.1 | July, 2024 | +| .NET Core 3.1 | July, 2024 | +| .NET 5.0 | July, 2024 | +| .NET 6.0 | May, 2025 | +| .NET 7.0 | November, 2024 | +| .NET 8.0 | May, 2027 | + +**NOTES:** +1. Some discussion is needed before we can determine when the .NET Framework agents will be removed and how they will be replaced. +2. July, 2024 is the estimated release date of version 3.18.0 of the console runner. ## Required Runtimes -The console runner and engine target .NET 4.6.2, so that runtime or greater is required to execute any tests at all -without loss of features. In individual cases, it may be possible to run using a lesser version of .NET 4.x. +The console runner and engine target .NET Framework 4.6.2, so that runtime or greater is required to execute any tests at all +without loss of features. In individual cases, it may be possible to run using a lesser version of .NET 4.x. It's possible +that we may require a higher level of the framework in a future 3.x release. -> **Note:** Editorial comments in italics will be removed in the final version of this document From 3d20424787b5b3b885c76914be473d7aee52b98a Mon Sep 17 00:00:00 2001 From: CharliePoole Date: Wed, 26 Jun 2024 18:53:58 -0700 Subject: [PATCH 062/190] Update platform-support.md --- platform-support.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/platform-support.md b/platform-support.md index a05de6c42..96e151ba3 100644 --- a/platform-support.md +++ b/platform-support.md @@ -18,7 +18,6 @@ runtime available. We currently supply a fairly large selection of agents with the console runner: * .NET Framework 2.0 * .NET Framework 4.6.2 -* .NET Core 2,1 * ,NET Core 3.1 * .NET 5.0 * .NET 6.0 @@ -36,7 +35,6 @@ dates listed in the following table. | -------------------- | --------------- | | .NET Framework 2.0 | TBD | | .NET Framework 4.6.2 | TBD | -| .NET Core 2.1 | July, 2024 | | .NET Core 3.1 | July, 2024 | | .NET 5.0 | July, 2024 | | .NET 6.0 | May, 2025 | From 4e5535a8e8400a70682deb81671da4618fa9e110 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 20 Jun 2024 09:35:48 -0700 Subject: [PATCH 063/190] Implement general recipe for future use with this and other NUnit projects --- NUnitConsole.sln | 53 +- build.cake | 1151 ++++++----------- cake/banner.cake | 15 + cake/build-settings.cake | 370 ++++-- cake/builder.cake | 22 + cake/chocolatey-package.cake | 42 + cake/command-line-options.cake | 118 ++ cake/common/common-constants.cake | 23 - cake/common/tasks.cake | 45 - cake/constants.cake | 110 +- cake/{common => }/dotnet.cake | 0 cake/extending.cake | 57 + cake/header-check.cake | 102 -- cake/headers.cake | 115 ++ cake/help-messages.cake | 77 ++ cake/known-extensions.cake | 48 + cake/msi-package.cake | 95 ++ cake/nuget-package.cake | 56 + cake/package-checks.cake | 296 ++--- cake/package-definition.cake | 263 ++++ cake/package-definitions.cake | 397 ------ cake/package-reference.cake | 90 ++ cake/package-test.cake | 34 + cake/package-tester.cake | 153 --- cake/package-tests.cake | 140 -- cake/publishing.cake | 244 ++++ cake/setup.cake | 51 + cake/task-builders.cake | 48 + cake/task-definitions.cake | 134 ++ cake/test-reports.cake | 171 +++ cake/test-results.cake | 264 ++-- cake/test-runners.cake | 117 ++ cake/tools.cake | 4 + cake/unit-testing.cake | 81 ++ cake/utilities.cake | 239 ---- cake/versioning.cake | 33 +- cake/zip-package.cake | 78 ++ choco/nunit.console.choco.addins | 6 +- msi/nunit/engine-files.wxi | 34 + .../nunit.console-runner.netcore.nuspec | 3 - .../nunit3-console/ConsoleRunner.cs | 1 - src/NUnitConsole/nunit3-console/Program.cs | 4 + src/NUnitConsole/nunit3-console/builder.cake | 22 + 43 files changed, 3040 insertions(+), 2366 deletions(-) create mode 100644 cake/banner.cake create mode 100644 cake/builder.cake create mode 100644 cake/chocolatey-package.cake create mode 100644 cake/command-line-options.cake delete mode 100644 cake/common/common-constants.cake delete mode 100644 cake/common/tasks.cake rename cake/{common => }/dotnet.cake (100%) create mode 100644 cake/extending.cake delete mode 100644 cake/header-check.cake create mode 100644 cake/headers.cake create mode 100644 cake/help-messages.cake create mode 100644 cake/known-extensions.cake create mode 100644 cake/msi-package.cake create mode 100644 cake/nuget-package.cake create mode 100644 cake/package-definition.cake delete mode 100644 cake/package-definitions.cake create mode 100644 cake/package-reference.cake create mode 100644 cake/package-test.cake delete mode 100644 cake/package-tester.cake delete mode 100644 cake/package-tests.cake create mode 100644 cake/publishing.cake create mode 100644 cake/setup.cake create mode 100644 cake/task-builders.cake create mode 100644 cake/task-definitions.cake create mode 100644 cake/test-reports.cake create mode 100644 cake/test-runners.cake create mode 100644 cake/tools.cake create mode 100644 cake/unit-testing.cake create mode 100644 cake/zip-package.cake create mode 100644 src/NUnitConsole/nunit3-console/builder.cake diff --git a/NUnitConsole.sln b/NUnitConsole.sln index e352c619a..d2766917b 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -30,9 +30,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution nunit.ico = nunit.ico package-checks.cake = package-checks.cake package-tests.cake = package-tests.cake + packages.cake = packages.cake README.md = README.md test-results.cake = test-results.cake VERSIONING.md = VERSIONING.md + cake\zip-package.cake = cake\zip-package.cake EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{A972031D-2F61-4183-AF75-99EE1A9F6B32}" @@ -107,20 +109,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deprecated", "deprecated", choco\deprecated\nunit-console-with-extensions.nuspec = choco\deprecated\nunit-console-with-extensions.nuspec EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{9BCA00E2-D072-424B-A6DF-5BECF719C1FB}" - ProjectSection(SolutionItems) = preProject - cake\constants.cake = cake\constants.cake - cake\build-settings.cake = cake\build-settings.cake - cake\header-check.cake = cake\header-check.cake - cake\package-checks.cake = cake\package-checks.cake - cake\package-definitions.cake = cake\package-definitions.cake - cake\package-tester.cake = cake\package-tester.cake - cake\package-tests.cake = cake\package-tests.cake - cake\test-results.cake = cake\test-results.cake - cake\utilities.cake = cake\utilities.cake - cake\versioning.cake = cake\versioning.cake - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "msi", "msi", "{0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A}" ProjectSection(SolutionItems) = preProject msi\nunit-install.sln = msi\nunit-install.sln @@ -168,11 +156,37 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nunit", "nunit", "{93E5CAF4 msi\nunit\variables.wxi = msi\nunit\variables.wxi EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{6EF61FBD-3081-4CC8-B909-94176A12DD1A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{D6449B7A-20FF-467B-A65E-174DD6992AEB}" ProjectSection(SolutionItems) = preProject - cake\common\constants.cake = cake\common\constants.cake - cake\common\dotnet.cake = cake\common\dotnet.cake - cake\common\tasks.cake = cake\common\tasks.cake + cake\banner.cake = cake\banner.cake + cake\build-settings.cake = cake\build-settings.cake + cake\builder.cake = cake\builder.cake + cake\chocolatey-package.cake = cake\chocolatey-package.cake + cake\command-line-options.cake = cake\command-line-options.cake + cake\constants.cake = cake\constants.cake + cake\dotnet.cake = cake\dotnet.cake + cake\extending.cake = cake\extending.cake + cake\headers.cake = cake\headers.cake + cake\help-messages.cake = cake\help-messages.cake + cake\known-extensions.cake = cake\known-extensions.cake + cake\msi-package.cake = cake\msi-package.cake + cake\nuget-package.cake = cake\nuget-package.cake + cake\package-checks.cake = cake\package-checks.cake + cake\package-definition.cake = cake\package-definition.cake + cake\package-reference.cake = cake\package-reference.cake + cake\package-test.cake = cake\package-test.cake + cake\publishing.cake = cake\publishing.cake + cake\setup.cake = cake\setup.cake + cake\task-definitions.cake = cake\task-definitions.cake + cake\test-reports.cake = cake\test-reports.cake + cake\task-builders.cake = cake\task-builders.cake + cake\test-results.cake = cake\test-results.cake + cake\test-runners.cake = cake\test-runners.cake + cake\tools.cake = cake\tools.cake + cake\unit-testing.cake = cake\unit-testing.cake + cake\utilities.cake = cake\utilities.cake + cake\versioning.cake = cake\versioning.cake + cake\zip-package.cake = cake\zip-package.cake EndProjectSection EndProject Global @@ -251,7 +265,6 @@ Global {333D2FBC-CCA7-46AF-9453-C310671A67B0} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} {9D3015EE-5B84-41B3-A1D3-1A439370C392} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} {068F6CA9-6108-4F45-8540-351AA5227259} = {4FDF7BFA-A337-41D3-898D-C6A98278E6AD} - {9BCA00E2-D072-424B-A6DF-5BECF719C1FB} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {20005864-BE82-412D-99BF-288E2D8370E9} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {CACC0520-B452-4310-A33C-DC944129ACDD} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} @@ -260,7 +273,7 @@ Global {50371E48-BEC3-4D53-BD37-F3A6149CFD0D} = {25DA12FE-6209-4524-9A37-8E51F815E198} {C5B7120C-190B-4C38-95CB-83F12799598D} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {93E5CAF4-D5DB-4915-AF0F-908A6043E462} = {0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A} - {6EF61FBD-3081-4CC8-B909-94176A12DD1A} = {9BCA00E2-D072-424B-A6DF-5BECF719C1FB} + {D6449B7A-20FF-467B-A65E-174DD6992AEB} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/build.cake b/build.cake index 32a2c57ea..72dc92264 100644 --- a/build.cake +++ b/build.cake @@ -1,761 +1,456 @@ -static string Target; Target = GetArgument("target|t", "Default"); -static string Configuration; Configuration = GetArgument("configuration|c", "Release"); -static bool NoPush; NoPush = HasArgument("nopush"); - // Load scripts -#load cake/common/*.cake #load cake/*.cake -// Install Tools -#tool NuGet.CommandLine&version=6.9.1 -#tool dotnet:?package=GitVersion.Tool&version=5.12.0 -#tool dotnet:?package=GitReleaseManager.Tool&version=0.12.1 - -//BuildVersion _buildVersion; -//string ProductVersion => _buildVersion.ProductVersion; -//string SemVer => _buildVersion.SemVer; -//string PreReleaseLabel => _buildVersion.PreReleaseLabel; -//bool IsReleaseBranch => _buildVersion.IsReleaseBranch; - -var UnreportedErrors = new List(); -var installedNetCoreRuntimes = GetInstalledNetCoreRuntimes(); - -////////////////////////////////////////////////////////////////////// -// SETUP AND TEARDOWN TASKS -////////////////////////////////////////////////////////////////////// -Setup(context => -{ - //Information("Creating BuildVersion"); - //_buildVersion = new BuildVersion(context); - - //Information("Building {0} version {1} of NUnit Console/Engine.", Configuration, ProductVersion); - //Information("PreReleaseLabel is " + PreReleaseLabel); - - var settings = BuildSettings.Create(context); - - Information("Initializing PackageDefinitions"); - InitializePackageDefinitions(settings); - - if (BuildSystem.IsRunningOnAppVeyor) - AppVeyor.UpdateBuildVersion(settings.PackageVersion + "-" + AppVeyor.Environment.Build.Number); - - return settings; -}); - -Teardown(context => -{ - // Executed AFTER the last task. - DisplayUnreportedErrors(); -}); +// Initialize BuildSettings +BuildSettings.Initialize( + Context, + "NUnit Console and Engine", + "nunit-console", + solutionFile: "NUnitConsole.sln", + exemptFiles: new [] { "Options.cs", "ProcessUtils.cs", "ProcessUtilsTests.cs" }, + unitTests: "**/*.tests.exe|**/nunit3-console.tests.dll", + unitTestRunner: new CustomTestRunner() ); ////////////////////////////////////////////////////////////////////// -// BUILD ENGINE AND CONSOLE +// PACKAGE TEST LISTS ////////////////////////////////////////////////////////////////////// -Task("Build") - .Description("Builds the engine and console") - .IsDependentOn("CheckHeaders") - .IsDependentOn("Clean") - .Does((settings) => + // Tests run for all runner packages except NETCORE runner + var StandardRunnerTests = new List { - if (settings.IsRunningOnWindows) - BuildSolution(settings.BuildVersion); - else - BuildEachProjectSeparately(settings.BuildVersion); - }); - -public void BuildSolution(BuildVersion buildVersion) -{ - MSBuild(SOLUTION_FILE, CreateMSBuildSettings("Build", buildVersion).WithRestore()); - - // Publishing in place where needed to ensure that all references are present. - - // TODO: May not be needed - DisplayBanner("Publishing ENGINE API Project for NETSTANDARD_2.0"); - MSBuild(ENGINE_API_PROJECT, CreateMSBuildSettings("Publish", buildVersion) - .WithProperty("TargetFramework", "netstandard2.0") - .WithProperty("PublishDir", BIN_DIR + "netstandard2.0")); - - DisplayBanner("Publishing ENGINE Project for NETSTANDARD2.0"); - MSBuild(ENGINE_PROJECT, CreateMSBuildSettings("Publish", buildVersion) - .WithProperty("TargetFramework", "netstandard2.0") - .WithProperty("PublishDir", BIN_DIR + "netstandard2.0")); - - // TODO: May not be needed - foreach (var framework in new[] { "netcoreapp3.1", "net5.0" }) - { - DisplayBanner($"Publishing AGENT Project for {framework.ToUpper()}"); - MSBuild(AGENT_PROJECT, CreateMSBuildSettings("Publish", buildVersion) - .WithProperty("TargetFramework", framework) - .WithProperty("PublishDir", BIN_DIR + "agents/" + framework)); - } -} - -// TODO: Test this on linux to see if changes are needed -private void BuildEachProjectSeparately(BuildVersion buildVersion) -{ - DotNetRestore(SOLUTION_FILE); - - BuildProject(ENGINE_PROJECT, buildVersion); - BuildProject(CONSOLE_PROJECT, buildVersion); - BuildProject(AGENT_PROJECT, buildVersion); - BuildProject(AGENT_X86_PROJECT, buildVersion); - - BuildProject(ENGINE_TESTS_PROJECT, buildVersion, "net35", "netcoreapp2.1"); - BuildProject(ENGINE_CORE_TESTS_PROJECT, buildVersion, "net35", "netcoreapp2.1", "netcoreapp3.1", "net5.0", "net6.0", "net8.0"); - BuildProject(CONSOLE_TESTS_PROJECT, buildVersion, "net35", "net6.0", "net8.0"); - - BuildProject(MOCK_ASSEMBLY_X86_PROJECT, buildVersion, "net35", "net40", "netcoreapp2.1", "netcoreapp3.1"); - BuildProject(NOTEST_PROJECT, buildVersion, "net35", "netcoreapp2.1", "netcoreapp3.1"); - - - DisplayBanner("Publish .NET Core & Standard projects"); - - MSBuild(ENGINE_PROJECT, CreateMSBuildSettings("Publish", buildVersion) - .WithProperty("TargetFramework", "netstandard2.0") - .WithProperty("PublishDir", BIN_DIR + "netstandard2.0")); - CopyFileToDirectory( - BIN_DIR + "netstandard2.0/testcentric.engine.metadata.dll", - BIN_DIR + "netcoreapp2.1"); - MSBuild(ENGINE_TESTS_PROJECT, CreateMSBuildSettings("Publish", buildVersion) - .WithProperty("TargetFramework", "netcoreapp2.1") - .WithProperty("PublishDir", BIN_DIR + "netcoreapp2.1")); - MSBuild(ENGINE_CORE_TESTS_PROJECT, CreateMSBuildSettings("Publish", buildVersion) - .WithProperty("TargetFramework", "netcoreapp2.1") - .WithProperty("PublishDir", BIN_DIR + "netcoreapp2.1")); -} - -// NOTE: If we use DotNet to build on Linux, then our net35 projects fail. -// If we use MSBuild, then the net5.0 projects fail. So we build each project -// differently depending on whether it has net35 as one of its targets. -private void BuildProject(string project, BuildVersion buildVersion, params string[] targetFrameworks) -{ - if (targetFrameworks.Length == 0) + Net35Test, + Net35X86Test, + Net40Test, + Net40X86Test, + Net35PlusNet40Test, + NetCore31Test, + Net50Test, + Net60Test, + Net70Test, + Net80Test, + Net50PlusNet60Test, + Net40PlusNet60Test, + NUnitProjectTest + }; + + // Tests run for the NETCORE runner package + var NetCoreRunnerTests = new List { - DisplayBanner($"Building {System.IO.Path.GetFileName(project)}"); - DotNetMSBuild(project, CreateDotNetMSBuildSettings("Build", buildVersion)); - } - else + NetCore31Test, + Net50Test, + Net60Test, + Net70Test, + Net80Test, + }; + + const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; + bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); + + // TODO: Remove the limitation to Windows + if (IsRunningOnWindows() && dotnetX86Available) { - foreach (var framework in targetFrameworks) + StandardRunnerTests.Add(Net60X86Test); + // TODO: Make these tests run on AppVeyor + if (!BuildSystem.IsRunningOnAppVeyor) { - DisplayBanner($"Building {System.IO.Path.GetFileName(project)} for {framework}"); - if (framework == "net35") - MSBuild(project, CreateMSBuildSettings("Build", buildVersion).WithProperty("TargetFramework", framework)); - else - DotNetMSBuild(project, CreateDotNetMSBuildSettings("Build", buildVersion).WithProperty("TargetFramework", framework)); + StandardRunnerTests.Add(NetCore31X86Test); + StandardRunnerTests.Add(Net50X86Test); + StandardRunnerTests.Add(Net70X86Test); + StandardRunnerTests.Add(Net80X86Test); } + // Currently, NetCoreRunner runs tests in process. As a result, + // X86 tests will work in our environment, although uses may run + // it as a tool using the X86 architecture. } -} - -////////////////////////////////////////////////////////////////////// -// BUILD C++ TESTS -////////////////////////////////////////////////////////////////////// - -Task("BuildCppTestFiles") - .Description("Builds the C++ mock test assemblies") - .WithCriteria(IsRunningOnWindows) - .Does((settings) => - { - MSBuild( - SOURCE_DIR + "NUnitEngine/mock-cpp-clr/mock-cpp-clr-x86.vcxproj", - CreateMSBuildSettings("Build", settings.BuildVersion).WithProperty("Platform", "x86")); - - MSBuild( - SOURCE_DIR + "NUnitEngine/mock-cpp-clr/mock-cpp-clr-x64.vcxproj", - CreateMSBuildSettings("Build", settings.BuildVersion).WithProperty("Platform", "x64")); - }); - -////////////////////////////////////////////////////////////////////// -// TEST -////////////////////////////////////////////////////////////////////// - -// All Unit Tests are run and any error results are saved in -// the global PendingUnitTestErrors. This method task displays them -// and throws if there were any errors. -Task("CheckForTestErrors") - .Description("Checks for errors running the test suites") - .Does(() => DisplayUnreportedErrors()); - -////////////////////////////////////////////////////////////////////// -// TEST .NET 2.0 ENGINE CORE -////////////////////////////////////////////////////////////////////// - -Task("TestNet20EngineCore") - .Description("Tests the engine core assembly") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunNUnitLiteTests(NETFX_ENGINE_CORE_TESTS, "net35"); - }); - -////////////////////////////////////////////////////////////////////// -// TEST NETSTANDARD 2.0 ENGINE CORE -////////////////////////////////////////////////////////////////////// - -Task("TestNetStandard20EngineCore") - .Description("Tests the .NET Standard Engine core assembly") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunDotnetNUnitLiteTests(NETCORE_ENGINE_CORE_TESTS, "netcoreapp3.1"); - }); - -////////////////////////////////////////////////////////////////////// -// TEST NETCORE 3.1 ENGINE CORE -////////////////////////////////////////////////////////////////////// - -Task("TestNetCore31EngineCore") - .Description("Tests the .NET Core 3.1 Engine core assembly") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunDotnetNUnitLiteTests(NETCORE_ENGINE_CORE_TESTS, "netcoreapp3.1"); - }); - -////////////////////////////////////////////////////////////////////// -// TEST NET 5.0 ENGINE CORE -////////////////////////////////////////////////////////////////////// - -Task("TestNet50EngineCore") - .Description("Tests the .NET 5.0 Engine core assembly") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunDotnetNUnitLiteTests(NETCORE_ENGINE_CORE_TESTS, "net5.0"); - }); - -////////////////////////////////////////////////////////////////////// -// TEST NET 6.0 ENGINE CORE -////////////////////////////////////////////////////////////////////// - -Task("TestNet60EngineCore") - .Description("Tests the .NET 6.0 Engine core assembly") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunDotnetNUnitLiteTests(NETCORE_ENGINE_CORE_TESTS, "net6.0"); - }); - -////////////////////////////////////////////////////////////////////// -// TEST .NET 2.0 ENGINE -////////////////////////////////////////////////////////////////////// - -Task("TestNet20Engine") - .Description("Tests the engine") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunNUnitLiteTests(NETFX_ENGINE_TESTS, "net35"); - }); - -////////////////////////////////////////////////////////////////////// -// TEST NETSTANDARD 2.0 ENGINE -////////////////////////////////////////////////////////////////////// - -Task("TestNetStandard20Engine") - .Description("Tests the .NET Standard Engine") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunDotnetNUnitLiteTests(NETCORE_ENGINE_TESTS, "netcoreapp3.1"); - }); - -////////////////////////////////////////////////////////////////////// -// TEST .NET 2.0 CONSOLE -////////////////////////////////////////////////////////////////////// - -Task("TestNet20Console") - .Description("Tests the .NET 2.0 console runner") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunNet20Console(CONSOLE_TESTS, "net35"); - }); - -////////////////////////////////////////////////////////////////////// -// TEST .NET 6.0 CONSOLE -////////////////////////////////////////////////////////////////////// - -Task("TestNet60Console") - .Description("Tests the .NET 6.0 console runner") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunNetCoreConsole(CONSOLE_TESTS, "net6.0"); - }); - -////////////////////////////////////////////////////////////////////// -// TEST .NET 8.0 CONSOLE -////////////////////////////////////////////////////////////////////// - -Task("TestNet80Console") - .Description("Tests the .NET 8.0 console runner") - .IsDependentOn("Build") - .OnError(exception => { UnreportedErrors.Add(exception.Message); }) - .Does(() => - { - RunNetCoreConsole(CONSOLE_TESTS, "net8.0"); - }); - -////////////////////////////////////////////////////////////////////// -// FETCH BUNDLED EXTENSIONS -////////////////////////////////////////////////////////////////////// - -Task("FetchBundledExtensions") - .Does(() => - { - CleanDirectory(EXTENSIONS_DIR); - - DisplayBanner("Fetching bundled extensions"); - - foreach (var extension in BUNDLED_EXTENSIONS) - { - DisplayBanner(extension); - - NuGetInstall(extension, new NuGetInstallSettings - { - OutputDirectory = EXTENSIONS_DIR, - Source = new[] { "https://www.nuget.org/api/v2" } - }); - } - }); - -////////////////////////////////////////////////////////////////////// -// CREATE MSI IMAGE -////////////////////////////////////////////////////////////////////// - -Task("CreateMsiImage") - .IsDependentOn("FetchBundledExtensions") - .Does(() => - { - CleanDirectory(MSI_IMG_DIR); - CopyFiles( - new FilePath[] { "LICENSE.txt", "NOTICES.txt", "CHANGES.txt", "nunit.ico" }, - MSI_IMG_DIR); - CopyDirectory(BIN_DIR, MSI_IMG_DIR + "bin/"); - - foreach (var framework in new[] { "net20", "net35" }) - { - var addinsImgDir = MSI_IMG_DIR + "bin/" + framework + "/addins/"; - - CopyDirectory(MSI_DIR + "resources/", MSI_IMG_DIR); - CleanDirectory(addinsImgDir); - - foreach (var packageDir in System.IO.Directory.GetDirectories(EXTENSIONS_DIR)) - CopyPackageContents(packageDir, addinsImgDir); - } - }); - -////////////////////////////////////////////////////////////////////// -// CREATE ZIP IMAGE -////////////////////////////////////////////////////////////////////// - -Task("CreateZipImage") - .IsDependentOn("FetchBundledExtensions") - .Does(() => - { - CleanDirectory(ZIP_IMG_DIR); - CopyFiles( - new FilePath[] { "LICENSE.txt", "NOTICES.txt", "CHANGES.txt", "nunit.ico" }, - ZIP_IMG_DIR); - CopyDirectory(BIN_DIR, ZIP_IMG_DIR + "bin/"); - - foreach (var framework in new[] { "net20", "net35" }) - { - var frameworkDir = ZIP_IMG_DIR + "bin/" + framework + "/"; - CopyFileToDirectory(ZIP_DIR + "nunit.bundle.addins", frameworkDir); - - var addinsDir = frameworkDir + "addins/"; - CleanDirectory(addinsDir); - - foreach (var packageDir in System.IO.Directory.GetDirectories(EXTENSIONS_DIR)) - CopyPackageContents(packageDir, addinsDir); - } - }); - -Task("BuildPackages") - .IsDependentOn("CreateMsiImage") - .IsDependentOn("CreateZipImage") - .Does(() => - { - EnsureDirectoryExists(PACKAGE_DIR); - - foreach (var package in AllPackages) - { - DisplayBanner($"Building package {package.PackageName}"); - - package.BuildPackage(); - } - }); - -////////////////////////////////////////////////////////////////////// -// VERIFY PACKAGES -////////////////////////////////////////////////////////////////////// - -Task("VerifyPackages") - .Description("Check content of all the packages we build") - .Does(() => - { - int failures = 0; - - foreach (var package in AllPackages) - { - if (!CheckPackage($"{PACKAGE_DIR}{package.PackageName}", package.PackageChecks)) - ++failures; - - if (package.HasSymbols && !CheckPackage($"{PACKAGE_DIR}{package.SymbolPackageName}", package.SymbolChecks)) - ++failures; - } - - if (failures == 0) - Information("\nAll packages passed verification."); - else - throw new System.Exception($"{failures} packages failed verification."); - }); - -////////////////////////////////////////////////////////////////////// -// TEST PACKAGES -////////////////////////////////////////////////////////////////////// - -Task("TestPackages") - .Does(() => - { - foreach (var package in AllPackages) - { - if (package.PackageTests != null) - new PackageTester(Context, package).RunTests(); - } - }); - -////////////////////////////////////////////////////////////////////// -// INSTALL SIGNING TOOL -////////////////////////////////////////////////////////////////////// - -Task("InstallSigningTool") - .Does(() => - { - var result = StartProcess("dotnet.exe", new ProcessSettings { Arguments = "tool install SignClient --global" }); - }); ////////////////////////////////////////////////////////////////////// -// SIGN PACKAGES +// INDIVIDUAL PACKAGE TEST DEFINITIONS ////////////////////////////////////////////////////////////////////// -Task("SignPackages") - .IsDependentOn("InstallSigningTool") - .IsDependentOn("Package") - .Does(() => - { - // Get the secret. - var secret = EnvironmentVariable("SIGNING_SECRET"); - if(string.IsNullOrWhiteSpace(secret)) { - throw new InvalidOperationException("Could not resolve signing secret."); - } - // Get the user. - var user = EnvironmentVariable("SIGNING_USER"); - if(string.IsNullOrWhiteSpace(user)) { - throw new InvalidOperationException("Could not resolve signing user."); - } - - var signClientPath = Context.Tools.Resolve("SignClient.exe") ?? Context.Tools.Resolve("SignClient") ?? throw new Exception("Failed to locate sign tool"); - - var settings = File("./signclient.json"); - - // Get the files to sign. - var files = GetFiles(string.Concat(PACKAGE_DIR, "*.nupkg")) + - GetFiles(string.Concat(PACKAGE_DIR, "*.msi")); - - foreach(var file in files) - { - Information("Signing {0}...", file.FullPath); - - // Build the argument list. - var arguments = new ProcessArgumentBuilder() - .Append("sign") - .AppendSwitchQuoted("-c", MakeAbsolute(settings.Path).FullPath) - .AppendSwitchQuoted("-i", MakeAbsolute(file).FullPath) - .AppendSwitchQuotedSecret("-s", secret) - .AppendSwitchQuotedSecret("-r", user) - .AppendSwitchQuoted("-n", "NUnit.org") - .AppendSwitchQuoted("-d", "NUnit is a unit-testing framework for all .NET languages.") - .AppendSwitchQuoted("-u", "https://nunit.org/"); - - // Sign the binary. - var result = StartProcess(signClientPath.FullPath, new ProcessSettings { Arguments = arguments }); - if(result != 0) - { - // We should not recover from this. - throw new InvalidOperationException("Signing failed!"); - } - } - }); - -////////////////////////////////////////////////////////////////////// -// PUBLISH PACKAGES -////////////////////////////////////////////////////////////////////// - -////////////////////////////////////////////////////////////////////// -// PUBLISH PACKAGES -////////////////////////////////////////////////////////////////////// - -static bool HadPublishingErrors = false; - -Task("PublishPackages") - .Description("Publish nuget and chocolatey packages according to the current settings") - .IsDependentOn("PublishToMyGet") - .IsDependentOn("PublishToNuGet") - .IsDependentOn("PublishToChocolatey") - .Does(() => - { - if (HadPublishingErrors) - throw new Exception("One of the publishing steps failed."); - }); - -// This task may either be run by the PublishPackages task, -// which depends on it, or directly when recovering from errors. -Task("PublishToMyGet") - .Description("Publish packages to MyGet") - .Does((settings) => - { - if (!settings.ShouldPublishToMyGet) - Information("Nothing to publish to MyGet from this run."); - else - { - var apiKey = EnvironmentVariable(MYGET_API_KEY); - - foreach (var package in AllPackages) - try - { - switch (package.PackageType) - { - case PackageType.NuGet: - PushNuGetPackage(PACKAGE_DIR + package.PackageName, apiKey, MYGET_PUSH_URL); - break; - case PackageType.Chocolatey: - PushChocolateyPackage(PACKAGE_DIR + package.PackageName, apiKey, MYGET_PUSH_URL); - break; - } - } - catch(Exception) - { - HadPublishingErrors = true; - } - } - }); - -// This task may either be run by the PublishPackages task, -// which depends on it, or directly when recovering from errors. -Task("PublishToNuGet") - .Description("Publish packages to NuGet") - .Does((settings) => - { - if (!settings.ShouldPublishToNuGet) - Information("Nothing to publish to NuGet from this run."); - else - { - var apiKey = EnvironmentVariable(NUGET_API_KEY); - - foreach (var package in AllPackages) - if (package.PackageType == PackageType.NuGet) - try - { - PushNuGetPackage(PACKAGE_DIR + package.PackageName, apiKey, NUGET_PUSH_URL); - } - catch (Exception) - { - HadPublishingErrors = true; - } - } - }); - -// This task may either be run by the PublishPackages task, -// which depends on it, or directly when recovering from errors. -Task("PublishToChocolatey") - .Description("Publish packages to Chocolatey") - .Does((settings) => - { - if (!settings.ShouldPublishToChocolatey) - Information("Nothing to publish to Chocolatey from this run."); - else - { - var apiKey = EnvironmentVariable(CHOCO_API_KEY); - - foreach (var package in AllPackages) - if (package.PackageType == PackageType.Chocolatey) - try - { - PushChocolateyPackage(PACKAGE_DIR + package.PackageName, apiKey, CHOCO_PUSH_URL); - } - catch (Exception) - { - HadPublishingErrors = true; - } - } - }); - -Task("ListInstalledNetCoreRuntimes") - .Description("Lists all installed .NET Core Runtimes") - .Does(() => - { - var runtimes = GetInstalledNetCoreRuntimes(); - foreach (var runtime in runtimes) - { - Information(runtime); - } - }); - -////////////////////////////////////////////////////////////////////// -// CREATE A DRAFT RELEASE -////////////////////////////////////////////////////////////////////// - -Task("CreateDraftRelease") - .Does((settings) => - { - bool isDirectTarget = Target == "CreateDraftRelease"; +static ExpectedResult MockAssemblyExpectedResult(int nCopies = 1) => new ExpectedResult("Failed") +{ + Total = 37 * nCopies, + Passed = 23 * nCopies, + Failed = 5 * nCopies, + Warnings = 1 * nCopies, + Inconclusive = 1 * nCopies, + Skipped = 7 * nCopies +}; + +static ExpectedResult MockAssemblyExpectedResult(params string[] runtimes) +{ + int nCopies = runtimes.Length; + var result = MockAssemblyExpectedResult(nCopies); + result.Assemblies = new ExpectedAssemblyResult[nCopies]; + for (int i = 0; i < nCopies; i++) + result.Assemblies[i] = new ExpectedAssemblyResult("mock-assembly.dll", runtimes[i]); + return result; +} - if (isDirectTarget && !HasArgument("productVersion")) - throw new Exception("Must specify --productVersion with the CreateDraftRelease target."); +static ExpectedResult MockAssemblyX86ExpectedResult(params string[] runtimes) +{ + int nCopies = runtimes.Length; + var result = MockAssemblyExpectedResult(nCopies); + result.Assemblies = new ExpectedAssemblyResult[nCopies]; + for (int i = 0; i < nCopies; i++) + result.Assemblies[i] = new ExpectedAssemblyResult("mock-assembly-x86.dll", runtimes[i]); + return result; +} - if (settings.BuildVersion.IsReleaseBranch || isDirectTarget) - { - string milestone = settings.IsReleaseBranch - ? settings.BranchName.Substring(8) - : settings.PackageVersion; - string releaseName = $"NUnit Console and Engine {milestone}"; - - Information($"Creating draft release for {releaseName}"); - - if (!NoPush) - try - { - GitReleaseManagerCreate(EnvironmentVariable(GITHUB_ACCESS_TOKEN), GITHUB_OWNER, GITHUB_REPO, new GitReleaseManagerCreateSettings() - { - Name = releaseName, - Milestone = milestone - }); - } - catch - { - Error($"Unable to create draft release for {releaseName}."); - Error($"Check that there is a {milestone} milestone with at least one closed issue."); - Error(""); - throw; - } - } - else - { - Information("Skipping Release creation because this is not a release branch"); - } - }); +static PackageTest Net35Test = new PackageTest( + 1, "Net35Test", + "Run mock-assembly.dll under .NET 3.5", + "net35/mock-assembly.dll", + MockAssemblyExpectedResult("net-2.0")); + +static PackageTest Net35X86Test = new PackageTest( + 1, "Net35X86Test", + "Run mock-assembly-x86.dll under .NET 3.5", + "net35/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("net-2.0")); + +static PackageTest Net40Test = new PackageTest( + 1, "Net40Test", + "Run mock-assembly.dll under .NET 4.x", + "net40/mock-assembly.dll", + MockAssemblyExpectedResult("net-4.0")); + +static PackageTest Net40X86Test = new PackageTest( + 1, "Net40X86Test", + "Run mock-assembly-x86.dll under .NET 4.x", + "net40/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("net-4.0")); + +static PackageTest Net35PlusNet40Test = new PackageTest( + 1, "Net35PlusNet40Test", + "Run both copies of mock-assembly together", + "net35/mock-assembly.dll net40/mock-assembly.dll", + MockAssemblyExpectedResult("net-2.0", "net-4.0")); + +static PackageTest Net80Test = new PackageTest( + 1, "Net80Test", + "Run mock-assembly.dll under .NET 8.0", + "net8.0/mock-assembly.dll", + MockAssemblyExpectedResult("netcore-8.0")); + +static PackageTest Net80X86Test = new PackageTest( + 1, "Net80X86Test", + "Run mock-assembly-x86.dll under .NET 8.0", + "net8.0/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("netcore-8.0")); + +static PackageTest Net70Test = new PackageTest( + 1, "Net70Test", + "Run mock-assembly.dll under .NET 7.0", + "net7.0/mock-assembly.dll", + MockAssemblyExpectedResult("netcore-7.0")); + +static PackageTest Net70X86Test = new PackageTest( + 1, "Net70X86Test", + "Run mock-assembly-x86.dll under .NET 7.0", + "net7.0/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("netcore-7.0")); + +static PackageTest Net60Test = new PackageTest( + 1, "Net60Test", + "Run mock-assembly.dll under .NET 6.0", + "net6.0/mock-assembly.dll", + MockAssemblyExpectedResult("netcore-6.0")); + +static PackageTest Net60X86Test = new PackageTest( + 1, "Net60X86Test", + "Run mock-assembly-x86.dll under .NET 6.0", + "net6.0/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("netcore-6.0")); + +static PackageTest Net50Test = new PackageTest( + 1, "Net50Test", + "Run mock-assembly.dll under .NET 5.0", + "net5.0/mock-assembly.dll", + MockAssemblyExpectedResult("netcore-5.0")); + +static PackageTest Net50X86Test = new PackageTest( + 1, "Net50X86Test", + "Run mock-assembly-x86.dll under .NET 5.0", + "net5.0/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("netcore-5.0")); + +static PackageTest NetCore31Test = new PackageTest( + 1, "NetCore31Test", + "Run mock-assembly.dll under .NET Core 3.1", + "netcoreapp3.1/mock-assembly.dll", + MockAssemblyExpectedResult("netcore-3.1")); + +static PackageTest NetCore31X86Test = new PackageTest( + 1, "NetCore31X86Test", + "Run mock-assembly-x86.dll under .NET Core 3.1", + "netcoreapp3.1/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("netcore-3.1")); + +static PackageTest Net50PlusNet60Test = new PackageTest( + 1, "Net50PlusNet60Test", + "Run mock-assembly under .NET 5.0 and 6.0 together", + "net5.0/mock-assembly.dll net6.0/mock-assembly.dll",//" net7.0/mock-assembly.dll net8.0/mock-assembly.dll", + MockAssemblyExpectedResult("netcore-5.0", "netcore-6.0")); + +static PackageTest Net40PlusNet60Test = new PackageTest( + 1, "Net40PlusNet60Test", + "Run mock-assembly under .Net Framework 4.0 and .Net 6.0 together", + "net40/mock-assembly.dll net6.0/mock-assembly.dll", + MockAssemblyExpectedResult("net-4.0", "netcore-6.0")); + +static PackageTest NUnitProjectTest = new PackageTest( + 1, "NUnitProjectTest", + "Run project with both copies of mock-assembly", + "../../NetFXTests.nunit --config=Release --trace=Debug", + MockAssemblyExpectedResult("net-2.0", "net-4.0"), + KnownExtensions.NUnitProjectLoader); + +////////////////////////////////////////////////////////////////////// +// LISTS OF FILES USED IN CHECKING PACKAGES +////////////////////////////////////////////////////////////////////// + +FilePath[] ENGINE_FILES = { + "nunit.engine.dll", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll" }; +FilePath[] ENGINE_PDB_FILES = { + "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; +FilePath[] ENGINE_CORE_FILES = { + "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll" }; +FilePath[] ENGINE_CORE_PDB_FILES = { + "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; +FilePath[] AGENT_FILES = { + "nunit-agent.exe", "nunit-agent.exe.config", + "nunit-agent-x86.exe", "nunit-agent-x86.exe.config", + "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll"}; +FilePath[] AGENT_FILES_NETCORE = { + "nunit-agent.dll", "nunit-agent.dll.config", + "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll", + "Microsoft.Extensions.DependencyModel.dll"}; +FilePath[] AGENT_PDB_FILES = { + "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; +FilePath[] AGENT_PDB_FILES_NETCORE = { + "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; +FilePath[] CONSOLE_FILES = { + "nunit3-console.exe", "nunit3-console.exe.config" }; +FilePath[] CONSOLE_FILES_NETCORE = { + "nunit3-console.exe", "nunit3-console.dll" }; + +////////////////////////////////////////////////////////////////////// +// INDIVIDUAL PACKAGE DEFINITIONS +////////////////////////////////////////////////////////////////////// + +PackageDefinition NUnitConsoleNuGetPackage; +PackageDefinition NUnitConsoleRunnerNuGetPackage; +PackageDefinition NUnitConsoleRunnerNet60Package; +PackageDefinition NUnitConsoleRunnerNet80Package; +PackageDefinition NUnitEnginePackage; +PackageDefinition NUnitEngineApiPackage; +PackageDefinition NUnitConsoleRunnerChocolateyPackage; +PackageDefinition NUnitConsoleMsiPackage; +PackageDefinition NUnitConsoleZipPackage; + +BuildSettings.Packages.AddRange(new PackageDefinition[] { + + NUnitConsoleRunnerNuGetPackage = new NuGetPackage( + id: "NUnit.ConsoleRunner", + source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.nuspec", + checks: new PackageCheck[] { + HasFiles("LICENSE.txt", "NOTICES.txt"), + HasDirectory("tools").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins"), + HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins") + }, + symbols: new PackageCheck[] { + HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), + HasDirectory("tools/agents/net20").WithFiles(AGENT_PDB_FILES), + HasDirectory("tools/agents/net40").WithFiles(AGENT_PDB_FILES), + HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("tools/agents/net5.0").WithFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_PDB_FILES_NETCORE) + }, + testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + + $"NUnit.ConsoleRunner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), + tests: StandardRunnerTests), + + // NOTE: Must follow ConsoleRunner, upon which it depends + NUnitConsoleNuGetPackage = new NuGetPackage( + id: "NUnit.Console", + source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner-with-extensions.nuspec", + checks: new PackageCheck[] { HasFile("LICENSE.txt") }), + + NUnitConsoleRunnerNet80Package = new NuGetPackage( + id: "NUnit.ConsoleRunner.NetCore", + source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.netcore.nuspec", + checks: new PackageCheck[] { + HasFiles("LICENSE.txt", "NOTICES.txt"), + HasDirectory("tools/net8.0").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_CORE_FILES).AndFile("nunit.console.nuget.addins") + }, + symbols: new PackageCheck[] { + HasDirectory("tools/net8.0").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) + }, + testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/tools/net8.0/nunit3-console.exe"), + tests: NetCoreRunnerTests), + + NUnitConsoleRunnerNet60Package = new NuGetPackage( + id: "NUnit.ConsoleRunner.NetCore", + source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.netcore.nuspec", + checks: new PackageCheck[] { + HasFiles("LICENSE.txt", "NOTICES.txt"), + HasDirectory("tools/net6.0").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_CORE_FILES).AndFile("nunit.console.nuget.addins") + }, + symbols: new PackageCheck[] { + HasDirectory("tools/net6.0").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) + }, + testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/tools/net6.0/nunit3-console.exe"), + tests: NetCoreRunnerTests), + + NUnitConsoleRunnerChocolateyPackage = new ChocolateyPackage( + id: "nunit-console-runner", + source: BuildSettings.ChocolateyDirectory + "nunit-console-runner.nuspec", + checks: new PackageCheck[] { + HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt").AndFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.choco.addins"), + HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins") + }, + testRunner: new ConsoleRunnerSelfTester(BuildSettings.ChocolateyTestDirectory + + $"nunit-console-runner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), + tests: StandardRunnerTests), + + NUnitConsoleMsiPackage = new MsiPackage( + id: "NUnit.Console", + source: BuildSettings.MsiDirectory + "nunit/nunit.wixproj", + checks: new PackageCheck[] { + HasDirectory("NUnit.org").WithFiles("LICENSE.txt", "NOTICES.txt", "nunit.ico"), + HasDirectory("NUnit.org/nunit-console").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.bundle.addins"), + HasDirectory("Nunit.org/nunit-console/addins").WithFiles("nunit.core.dll", "nunit.core.interfaces.dll", "nunit.v2.driver.dll", "nunit-project-loader.dll", "vs-project-loader.dll", "nunit-v2-result-writer.dll", "teamcity-event-listener.dll") + }, + testRunner: new ConsoleRunnerSelfTester(BuildSettings.MsiTestDirectory + + $"NUnit.Console.{BuildSettings.BuildVersion.SemVer}/NUnit.org/nunit-console/nunit3-console.exe"), + tests: StandardRunnerTests, + bundledExtensions: new [] { + new PackageReference("NUnit.Extension.VSProjectLoader", "3.9.0"), + new PackageReference("NUnit.Extension.NUnitProjectLoader", "3.7.1"), + new PackageReference("NUnit.Extension.NUnitV2Driver", "3.9.0"), + new PackageReference("NUnit.Extension.NUnitV2ResultWriter", "3.7.0"), + new PackageReference("NUnit.Extension.TeamCityEventListener", "1.0.9") + }), + + NUnitConsoleZipPackage = new ZipPackage( + id: "NUnit.Console", + source: BuildSettings.ZipImageDirectory, + checks: new PackageCheck[] { + HasFiles("LICENSE.txt", "NOTICES.txt", "CHANGES.txt"), + HasDirectory("bin/net20").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), + HasDirectory("bin/net35").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), + HasDirectory("bin/netstandard2.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), + HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), + //HasDirectory("bin/net5.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), + HasDirectory("bin/agents/net20").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), + HasDirectory("bin/agents/net40").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), + HasDirectory("bin/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("bin/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) + }, + testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory + + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net20/nunit3-console.exe"), + tests: StandardRunnerTests, + bundledExtensions: new [] { + new PackageReference("NUnit.Extension.VSProjectLoader", "3.9.0"), + new PackageReference("NUnit.Extension.NUnitProjectLoader", "3.7.1"), + new PackageReference("NUnit.Extension.NUnitV2Driver", "3.9.0"), + new PackageReference("NUnit.Extension.NUnitV2ResultWriter", "3.7.0"), + new PackageReference("NUnit.Extension.TeamCityEventListener", "1.0.9") + }), + + // NOTE: Packages below this point have no direct tests + + NUnitEnginePackage = new NuGetPackage( + id: "NUnit.Engine", + source: BuildSettings.NuGetDirectory + "engine/nunit.engine.nuspec", + checks: new PackageCheck[] { + HasFiles("LICENSE.txt", "NOTICES.txt"), + HasDirectory("lib/net20").WithFiles(ENGINE_FILES), + HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_FILES), + HasDirectory("contentFiles/any/lib/net20").WithFile("nunit.engine.nuget.addins"), + HasDirectory("contentFiles/any/lib/netstandard2.0").WithFile("nunit.engine.nuget.addins"), + HasDirectory("contentFiles/any/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins"), + HasDirectory("contentFiles/any/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins") + }, + symbols: new PackageCheck[] { + HasDirectory("lib/net20").WithFiles(ENGINE_PDB_FILES), + HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_PDB_FILES), + HasDirectory("contentFiles/any/agents/net20").WithFiles(AGENT_PDB_FILES), + HasDirectory("contentFiles/any/agents/net40").WithFiles(AGENT_PDB_FILES) + }), + + NUnitEngineApiPackage = new NuGetPackage( + id: "NUnit.Engine.Api", + source: BuildSettings.NuGetDirectory + "engine/nunit.engine.api.nuspec", + checks: new PackageCheck[] { + HasFile("LICENSE.txt"), + HasDirectory("lib/net20").WithFile("nunit.engine.api.dll"), + HasDirectory("lib/netstandard2.0").WithFile("nunit.engine.api.dll"), + }, + symbols: new PackageCheck[] { + HasDirectory("lib/net20").WithFile("nunit.engine.api.pdb"), + HasDirectory("lib/netstandard2.0").WithFile("nunit.engine.api.pdb") + }) +}); ////////////////////////////////////////////////////////////////////// -// CREATE A PRODUCTION RELEASE +// TEST RUNNERS ////////////////////////////////////////////////////////////////////// -Task("CreateProductionRelease") - .Does((settings) => +// Custom unit test runner to run console vs engine tests differently +// TODO: Use NUnitLite for all tests? +public class CustomTestRunner : UnitTestRunner +{ + public override int Run(FilePath testPath) { - if (settings.IsProductionRelease) - { - string token = EnvironmentVariable(GITHUB_ACCESS_TOKEN); - string tagName = settings.PackageVersion; - - var assetList = new List(); - foreach (var package in AllPackages) - assetList.Add(PACKAGE_DIR + package.PackageName); - string assets = $"\"{string.Join(',', assetList.ToArray())}\""; - - Information($"Publishing release {tagName} to GitHub"); - - if (NoPush) - { - Information($"Assets:"); - foreach (var asset in assetList) - Information(" " + asset); - } - else - { - GitReleaseManagerAddAssets(token, GITHUB_OWNER, GITHUB_REPO, tagName, assets); - GitReleaseManagerClose(token, GITHUB_OWNER, GITHUB_REPO, tagName); - } - } - else + // Run console tests under the just-built console + if(testPath.ToString().Contains("nunit3-console.tests.dll")) { - Information("Skipping CreateProductionRelease because this is not a production release"); + return BuildSettings.Context.StartProcess( + BuildSettings.OutputDirectory + "net20/nunit3-console.exe", + $"\"{testPath}\" {BuildSettings.UnitTestArguments}" ); } - }); -////////////////////////////////////////////////////////////////////// -// TASK TARGETS -////////////////////////////////////////////////////////////////////// + // All other tests use NUnitLite + return new NUnitLiteRunner().Run(testPath); + } +} -Task("TestConsole") - .Description("Builds and tests the console runner") - .IsDependentOn("TestNet20Console") - .IsDependentOn("TestNet60Console"); - -Task("TestEngineCore") - .Description("Builds and tests the engine core assembly") - .IsDependentOn("TestNet20EngineCore") - .IsDependentOn("TestNetStandard20EngineCore") - .IsDependentOn("TestNetCore31EngineCore") - .IsDependentOn("TestNet50EngineCore") - .IsDependentOn("TestNet60EngineCore"); - -Task("TestEngine") - .Description("Builds and tests the engine assembly") - .IsDependentOn("TestNet20Engine") - .IsDependentOn("TestNetStandard20Engine"); - -Task("Test") - .Description("Builds and tests the engine and console runner") - .IsDependentOn("TestEngineCore") - .IsDependentOn("TestEngine") - .IsDependentOn("TestConsole") - .IsDependentOn("CheckForTestErrors"); - -Task("Package") - .Description("Builds and tests all packages") - .IsDependentOn("Build") - .IsDependentOn("BuildPackages") - .IsDependentOn("VerifyPackages") - .IsDependentOn("TestPackages"); - -Task("PackageExistingBuild") - .Description("Builds and tests all packages, using previously build binaries") - .IsDependentOn("BuildPackages") - .IsDependentOn("VerifyPackages") - .IsDependentOn("TestPackages"); - -Task("BuildTestAndPackage") - .Description("Builds, tests and packages") - .IsDependentOn("Build") - .IsDependentOn("Test") - .IsDependentOn("Package"); - -Task("Appveyor") - .Description("Target we run in our AppVeyor CI") - .IsDependentOn("BuildTestAndPackage") - .IsDependentOn("PublishPackages") - .IsDependentOn("CreateDraftRelease") - .IsDependentOn("CreateProductionRelease"); - -Task("Default") - .Description("Builds the engine and console runner") - .IsDependentOn("Build"); +// Use the console runner we just built to run package tests +public class ConsoleRunnerSelfTester : PackageTestRunner +{ + public ConsoleRunnerSelfTester(string executablePath) + { + ExecutablePath = executablePath; + } + + public override int Run(string arguments) + { + return base.Run(arguments); + } +} ////////////////////////////////////////////////////////////////////// // EXECUTION ////////////////////////////////////////////////////////////////////// -RunTarget(Target); +Build.Run() diff --git a/cake/banner.cake b/cake/banner.cake new file mode 100644 index 000000000..760ea3c35 --- /dev/null +++ b/cake/banner.cake @@ -0,0 +1,15 @@ +// Banner class displays banners similar to those created by Cake +// We use this to display intermediate steps within a task +public static class Banner +{ + public static void Display(string message, char barChar='-', int length=0) + { + if (length == 0) length = message.Length; + var bar = new string(barChar, length); + + Console.WriteLine(); + Console.WriteLine(bar); + Console.WriteLine(message); + Console.WriteLine(bar); + } +} diff --git a/cake/build-settings.cake b/cake/build-settings.cake index 0b43f9bc5..af06202d0 100644 --- a/cake/build-settings.cake +++ b/cake/build-settings.cake @@ -1,112 +1,265 @@ -public class BuildSettings +public static class BuildSettings { - private ISetupContext _context; - private BuildSystem _buildSystem; - - public static BuildSettings Create(ISetupContext context) - { - var parameters = new BuildSettings(context); - //parameters.Validate(); - - return parameters; - } - - private BuildSettings(ISetupContext context) + private static BuildSystem _buildSystem; + + public static void Initialize( + // Required parameters + ICakeContext context, + string title, + string githubRepository, + + // Optional Parameters + bool suppressHeaderCheck = false, + string[] standardHeader = null, + string[] exemptFiles = null, + + string solutionFile = null, + string[] validConfigurations = null, + string githubOwner = "NUnit", + + bool msbuildAllowPreviewVersion = false, + Verbosity msbuildVerbosity = Verbosity.Minimal, + + string unitTests = null, // Defaults to "**/*.tests.dll|**/*.tests.exe" (case insensitive) + UnitTestRunner unitTestRunner = null, // If not set, NUnitLite is used + string unitTestArguments = null + ) { - _context = context; - _buildSystem = context.BuildSystem(); - - Target = _context.TargetTask.Name; - TasksToExecute = _context.TasksToExecute.Select(t => t.Name); - - ProjectDirectory = _context.Environment.WorkingDirectory.FullPath + "/"; - - Configuration = _context.Argument("configuration", DEFAULT_CONFIGURATION); - - MyGetApiKey = _context.EnvironmentVariable(MYGET_API_KEY); - NuGetApiKey = _context.EnvironmentVariable(NUGET_API_KEY); - ChocolateyApiKey = _context.EnvironmentVariable(CHOCO_API_KEY); - GitHubAccessToken = _context.EnvironmentVariable(GITHUB_ACCESS_TOKEN); + // Required arguments + if (context == null) + throw new ArgumentNullException(nameof(context)); + if (title == null) + throw new ArgumentNullException(nameof(title)); + if (githubRepository == null) + throw new ArgumentNullException(nameof(githubRepository)); + + Context = context; + Title = title; + GitHubRepository = githubRepository; + + // NOTE: Order of initialization can be sensitive. Obviously, + // we have to set any properties in this method before we + // make use of them. Less obviously, some of the classes we + // construct here have dependencies on certain properties + // being set before the constructor is called. I have + // tried to annotate such dependencies below. + + _buildSystem = context.BuildSystem(); + + // If not specified, uses TITLE.sln if it exists or uses solution + // found in the root directory provided there is only one. + SolutionFile = solutionFile ?? DeduceSolutionFile(); + + ValidConfigurations = validConfigurations ?? DEFAULT_VALID_CONFIGS; + + UnitTests = unitTests; + // NUnitLiteRunner depends indirectly on ValidConfigurations + UnitTestRunner = unitTestRunner ?? new NUnitLiteRunner(); + UnitTestArguments = unitTestArguments; BuildVersion = new BuildVersion(context); - } - public string Target { get; } - public IEnumerable TasksToExecute { get; } + GitHubOwner = githubOwner; + + // File Header Checks + SuppressHeaderCheck = suppressHeaderCheck && !CommandLineOptions.NoBuild; + StandardHeader = standardHeader ?? DEFAULT_STANDARD_HEADER; + ExemptFiles = exemptFiles ?? new string[0]; + + //if (defaultTarget != null) + // BuildTasks.DefaultTask.IsDependentOn(defaultTarget); - public ICakeContext Context => _context; + // Skip remaining initialization if help was requested + if (CommandLineOptions.Usage) + return; + + ValidateSettings(); - public string Configuration { get; } + context.Information($"{Title} {Configuration} version {PackageVersion}"); - public BuildVersion BuildVersion { get; } - public string PackageVersion => BuildVersion.ProductVersion; - public string AssemblyVersion => BuildVersion.AssemblyVersion; - public string AssemblyFileVersion => BuildVersion.AssemblyFileVersion; - public string AssemblyInformationalVersion => BuildVersion.AssemblyInformationalVersion; + // Output like this should go after the run title display + if (solutionFile == null && SolutionFile != null) + Context.Warning($" SolutionFile: '{SolutionFile}'"); + Context.Information($" PackageTestLevel: {PackageTestLevel}"); - public int PackageTestLevel { get; } + // Keep this last + if (IsRunningOnAppVeyor) + { + var buildNumber = _buildSystem.AppVeyor.Environment.Build.Number; + _buildSystem.AppVeyor.UpdateBuildVersion($"{PackageVersion}-{buildNumber}"); + } + } - public bool IsLocalBuild => _buildSystem.IsLocalBuild; - public bool IsRunningOnUnix => _context.IsRunningOnUnix(); - public bool IsRunningOnWindows => _context.IsRunningOnWindows(); - public bool IsRunningOnAppVeyor => _buildSystem.AppVeyor.IsRunningOnAppVeyor; + // Try to figure out solution file when not provided + private static string DeduceSolutionFile() + { + string solutionFile = null; - public string ProjectDirectory { get; } - public string OutputDirectory => ProjectDirectory + "bin/" + Configuration + "/"; - public string SourceDirectory => ProjectDirectory + "src/"; - public string PackageDirectory => ProjectDirectory + "output/"; - public string PackageTestDirectory => PackageDirectory + "tests/"; - public string ToolsDirectory => ProjectDirectory + "tools/"; - public string NuGetInstallDirectory => ToolsDirectory + NUGET_ID + "/"; - public string ChocolateyInstallDirectory => ToolsDirectory + CHOCO_ID + "/"; + if (System.IO.File.Exists(Title + ".sln")) + solutionFile = Title + ".sln"; + else + { + var files = System.IO.Directory.GetFiles(ProjectDirectory, "*.sln"); + if (files.Count() == 1 && System.IO.File.Exists(files[0])) + solutionFile = files[0]; + } - public string NuGetPackageName => NUGET_ID + "." + PackageVersion + ".nupkg"; - public string ChocolateyPackageName => CHOCO_ID + "." + PackageVersion + ".nupkg"; + return solutionFile; + } - public string NuGetPackage => PackageDirectory + NuGetPackageName; - public string ChocolateyPackage => PackageDirectory + ChocolateyPackageName; + private static int CalcPackageTestLevel() + { + if (!BuildVersion.IsPreRelease) + return 3; + + // TODO: The prerelease label is no longer being set to pr by GitVersion + // for some reason. This check in AppVeyor is a workaround. + if (IsRunningOnAppVeyor && _buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest) + return 2; + + switch (BuildVersion.PreReleaseLabel) + { + case "pre": + case "rc": + case "alpha": + case "beta": + return 3; + + case "dev": + case "pr": + return 2; + + case "ci": + default: + return 1; + } + } - public string MyGetPushUrl => MYGET_PUSH_URL; - public string NuGetPushUrl => NUGET_PUSH_URL; - public string ChocolateyPushUrl => CHOCO_PUSH_URL; + // Cake Context + public static ICakeContext Context { get; private set; } - public string MyGetApiKey { get; } - public string NuGetApiKey { get; } - public string ChocolateyApiKey { get; } - public string GitHubAccessToken { get; } + // NOTE: These are set in setup.cake + public static string Target { get; set; } + public static IEnumerable TasksToExecute { get; set; } + + // Arguments + public static string Configuration + { + get + { + // Correct casing on user-provided config if necessary + foreach (string config in ValidConfigurations) + if (string.Equals(config, CommandLineOptions.Configuration.Value, StringComparison.OrdinalIgnoreCase)) + return config; - public string BranchName => BuildVersion.BranchName; - public bool IsReleaseBranch => BuildVersion.IsReleaseBranch; + // Return the (invalid) user-provided config + return CommandLineOptions.Configuration.Value; + } + } - public bool IsPreRelease => BuildVersion.IsPreRelease; - public bool ShouldPublishToMyGet => + // Build Environment + public static bool IsLocalBuild => _buildSystem.IsLocalBuild; + public static bool IsRunningOnUnix => Context.IsRunningOnUnix(); + public static bool IsRunningOnWindows => Context.IsRunningOnWindows(); + public static bool IsRunningOnAppVeyor => _buildSystem.AppVeyor.IsRunningOnAppVeyor; + + // Versioning + public static BuildVersion BuildVersion { get; private set;} + public static string BranchName => BuildVersion.BranchName; + public static bool IsReleaseBranch => BuildVersion.IsReleaseBranch; + public static string PackageVersion => BuildVersion.PackageVersion; + public static string AssemblyVersion => BuildVersion.AssemblyVersion; + public static string AssemblyFileVersion => BuildVersion.AssemblyFileVersion; + public static string AssemblyInformationalVersion => BuildVersion.AssemblyInformationalVersion; + public static bool IsDevelopmentRelease => PackageVersion.Contains("-dev"); + + // Standard Directory Structure - not changeable by user + public static string ProjectDirectory => Context.Environment.WorkingDirectory.FullPath + "/"; + public static string SourceDirectory => ProjectDirectory + SRC_DIR; + public static string OutputDirectory => ProjectDirectory + BIN_DIR + Configuration + "/"; + public static string NuGetDirectory => ProjectDirectory + NUGET_DIR; + public static string ChocolateyDirectory => ProjectDirectory + CHOCO_DIR; + public static string MsiDirectory => ProjectDirectory + MSI_DIR; + public static string ZipDirectory => ProjectDirectory + ZIP_DIR; + public static string PackageDirectory => ProjectDirectory + PACKAGE_DIR; + public static string PackageTestDirectory => ProjectDirectory + PKG_TEST_DIR; + public static string NuGetTestDirectory => ProjectDirectory + NUGET_TEST_DIR; + public static string ChocolateyTestDirectory => ProjectDirectory + CHOCO_TEST_DIR; + public static string MsiTestDirectory => ProjectDirectory + MSI_TEST_DIR; + public static string ZipTestDirectory => ProjectDirectory + ZIP_TEST_DIR; + public static string PackageResultDirectory => ProjectDirectory + PKG_RSLT_DIR; + public static string NuGetResultDirectory => ProjectDirectory + NUGET_RSLT_DIR; + public static string ChocolateyResultDirectory => ProjectDirectory + CHOCO_RSLT_DIR; + public static string MsiResultDirectory => ProjectDirectory + MSI_RSLT_DIR; + public static string ZipResultDirectory => ProjectDirectory + ZIP_RSLT_DIR; + public static string ImageDirectory => ProjectDirectory + IMAGE_DIR; + public static string MsiImageDirectory => ProjectDirectory + MSI_IMG_DIR; + public static string ZipImageDirectory => ProjectDirectory + ZIP_IMG_DIR; + public static string ExtensionsDirectory => ProjectDirectory + "bundled-extensions/"; + public static string ToolsDirectory => ProjectDirectory + "tools/"; + + // Files + public static string SolutionFile { get; set; } + + // Building + public static string[] ValidConfigurations { get; set; } + public static bool MSBuildAllowPreviewVersion { get; set; } + public static Verbosity MSBuildVerbosity { get; set; } + public static MSBuildSettings MSBuildSettings => new MSBuildSettings { + Verbosity = MSBuildVerbosity, + Configuration = Configuration, + PlatformTarget = PlatformTarget.MSIL, + AllowPreviewVersion = MSBuildAllowPreviewVersion + }; + + // File Header Checks + public static bool SuppressHeaderCheck { get; private set; } + public static string[] StandardHeader { get; private set; } + public static string[] ExemptFiles { get; private set; } + + //Testing + public static string UnitTests { get; set; } + public static UnitTestRunner UnitTestRunner { get; private set; } + public static string UnitTestArguments { get; private set; } + + // Packaging + public static string Title { get; private set; } + public static List Packages { get; } = new List(); + + // Package Testing + public static int PackageTestLevel => + CommandLineOptions.TestLevel.Value > 0 + ? CommandLineOptions.TestLevel.Value + : CalcPackageTestLevel(); + + // Publishing + public static string MyGetPushUrl => MYGET_PUSH_URL; + public static string NuGetPushUrl => NUGET_PUSH_URL; + public static string ChocolateyPushUrl => CHOCO_PUSH_URL; + + public static string MyGetApiKey { get; private set; } + public static string NuGetApiKey { get; private set; } + public static string ChocolateyApiKey { get; private set;} + public static string GitHubOwner { get; private set; } + public static string GitHubRepository { get; private set; } + public static string GitHubAccessToken { get; private set; } + + public static bool IsPreRelease => BuildVersion.IsPreRelease; + public static bool ShouldPublishToMyGet => !IsPreRelease || LABELS_WE_PUBLISH_ON_MYGET.Contains(BuildVersion.PreReleaseLabel); - public bool ShouldPublishToNuGet => + public static bool ShouldPublishToNuGet => !IsPreRelease || LABELS_WE_PUBLISH_ON_NUGET.Contains(BuildVersion.PreReleaseLabel); - public bool ShouldPublishToChocolatey => + public static bool ShouldPublishToChocolatey => !IsPreRelease || LABELS_WE_PUBLISH_ON_CHOCOLATEY.Contains(BuildVersion.PreReleaseLabel); - public bool IsProductionRelease => + public static bool IsProductionRelease => !IsPreRelease || LABELS_WE_RELEASE_ON_GITHUB.Contains(BuildVersion.PreReleaseLabel); - private void Validate() + private static void ValidateSettings() { var validationErrors = new List(); - if (TasksToExecute.Contains("PublishPackages")) - { - if (ShouldPublishToMyGet && string.IsNullOrEmpty(MyGetApiKey)) - validationErrors.Add("MyGet ApiKey was not set."); - if (ShouldPublishToNuGet && string.IsNullOrEmpty(NuGetApiKey)) - validationErrors.Add("NuGet ApiKey was not set."); - if (ShouldPublishToChocolatey && string.IsNullOrEmpty(ChocolateyApiKey)) - validationErrors.Add("Chocolatey ApiKey was not set."); - } - - if (TasksToExecute.Contains("CreateDraftRelease") && (IsReleaseBranch || IsProductionRelease)) - { - if (string.IsNullOrEmpty(GitHubAccessToken)) - validationErrors.Add("GitHub Access Token was not set."); - } + if (!ValidConfigurations.Contains(Configuration)) + validationErrors.Add($"Invalid configuration: {Configuration}"); if (validationErrors.Count > 0) { @@ -120,7 +273,7 @@ public class BuildSettings } } - public void DumpSettings() + public static void DumpSettings() { Console.WriteLine("\nTASKS"); Console.WriteLine("Target: " + Target); @@ -143,23 +296,32 @@ public class BuildSettings Console.WriteLine("PreReleaseSuffix: " + BuildVersion.PreReleaseSuffix); Console.WriteLine("\nDIRECTORIES"); - Console.WriteLine("Project: " + ProjectDirectory); - Console.WriteLine("Output: " + OutputDirectory); - //Console.WriteLine("Source: " + SourceDirectory); - //Console.WriteLine("NuGet: " + NuGetDirectory); - //Console.WriteLine("Choco: " + ChocoDirectory); - Console.WriteLine("Package: " + PackageDirectory); - //Console.WriteLine("ZipImage: " + ZipImageDirectory); - //Console.WriteLine("ZipTest: " + ZipTestDirectory); - //Console.WriteLine("NuGetTest: " + NuGetTestDirectory); - //Console.WriteLine("ChocoTest: " + ChocolateyTestDirectory); + Console.WriteLine("Project: " + ProjectDirectory); + Console.WriteLine("Output: " + OutputDirectory); + Console.WriteLine("Source: " + SourceDirectory); + Console.WriteLine("NuGet: " + NuGetDirectory); + Console.WriteLine("Chocolatey: " + ChocolateyDirectory); + Console.WriteLine("Package: " + PackageDirectory); + Console.WriteLine("PackageTest: " + PackageTestDirectory); + Console.WriteLine("NuGetTest: " + NuGetTestDirectory); + Console.WriteLine("ChocoTest: " + ChocolateyTestDirectory); + Console.WriteLine("MsiTest: " + MsiTestDirectory); + Console.WriteLine("ZipTest: " + ZipTestDirectory); + Console.WriteLine("PackageResult: " + PackageResultDirectory); + Console.WriteLine("NuGetResult: " + NuGetResultDirectory); + Console.WriteLine("ChocoResult: " + ChocolateyResultDirectory); + Console.WriteLine("MsiResult: " + MsiResultDirectory); + Console.WriteLine("ZipResult: " + ZipResultDirectory); + Console.WriteLine("Image: " + ImageDirectory); + Console.WriteLine("MsiImage: " + MsiImageDirectory); + Console.WriteLine("ZipImage: " + ZipImageDirectory); Console.WriteLine("\nBUILD"); - //Console.WriteLine("Build With: " + (UsingXBuild ? "XBuild" : "MSBuild")); Console.WriteLine("Configuration: " + Configuration); - //Console.WriteLine("Engine Runtimes: " + string.Join(", ", SupportedEngineRuntimes)); - //Console.WriteLine("Core Runtimes: " + string.Join(", ", SupportedCoreRuntimes)); - //Console.WriteLine("Agent Runtimes: " + string.Join(", ", SupportedAgentRuntimes)); + + Console.WriteLine("\nUNIT TESTS"); + Console.WriteLine("UnitTests: " + UnitTests); + Console.WriteLine("UnitTestRunner: " + UnitTestRunner?.GetType().Name ?? ""); Console.WriteLine("\nPACKAGING"); Console.WriteLine("MyGetPushUrl: " + MyGetPushUrl); @@ -169,6 +331,16 @@ public class BuildSettings Console.WriteLine("NuGetApiKey: " + (!string.IsNullOrEmpty(NuGetApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); Console.WriteLine("ChocolateyApiKey: " + (!string.IsNullOrEmpty(ChocolateyApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); + Console.WriteLine("\nPACKAGES"); + foreach (var package in Packages) + { + Console.WriteLine(package.PackageId); + Console.WriteLine(" PackageType: " + package.PackageType); + Console.WriteLine(" PackageFileName: " + package.PackageFileName); + Console.WriteLine(" PackageInstallDirectory: " + package.PackageInstallDirectory); + Console.WriteLine(" PackageTestDirectory: " + package.PackageTestDirectory); + } + Console.WriteLine("\nPUBLISHING"); Console.WriteLine("ShouldPublishToMyGet: " + ShouldPublishToMyGet); Console.WriteLine("ShouldPublishToNuGet: " + ShouldPublishToNuGet); diff --git a/cake/builder.cake b/cake/builder.cake new file mode 100644 index 000000000..ea91e367d --- /dev/null +++ b/cake/builder.cake @@ -0,0 +1,22 @@ +////////////////////////////////////////////////////////////////////// +// EXECUTION +////////////////////////////////////////////////////////////////////// + +public Builder Build => CommandLineOptions.Usage + ? new Builder(() => Information(HelpMessages.Usage)) + : new Builder(() => RunTarget(CommandLineOptions.Target.Value)); + +public class Builder +{ + private Action _action; + + public Builder(Action action) + { + _action = action; + } + + public void Run() + { + _action(); + } +} diff --git a/cake/chocolatey-package.cake b/cake/chocolatey-package.cake new file mode 100644 index 000000000..fa65eebed --- /dev/null +++ b/cake/chocolatey-package.cake @@ -0,0 +1,42 @@ +public class ChocolateyPackage : PackageDefinition +{ + public ChocolateyPackage( + string id, + string source, + PackageTestRunner testRunner = null, + PackageCheck[] checks = null, + IEnumerable tests = null) + : base( + PackageType.Chocolatey, + id, + source, + testRunner: testRunner, + checks: checks, + tests: tests) + { + if (!source.EndsWith(".nuspec")) + throw new ArgumentException("Source must be a nuspec file", nameof(source)); + } + + // The file name of this package, including extension + public override string PackageFileName => $"{PackageId}.{PackageVersion}.nupkg"; + // The file name of any symbol package, including extension + public override string SymbolPackageName => System.IO.Path.ChangeExtension(PackageFileName, ".snupkg"); + // The directory into which this package is installed + public override string PackageInstallDirectory => BuildSettings.ChocolateyTestDirectory; + // The directory used to contain results of package tests for this package + public override string PackageResultDirectory => BuildSettings.ChocolateyResultDirectory + PackageId + "/"; + // The directory into which extensions to the test runner are installed + public override string ExtensionInstallDirectory => BuildSettings.PackageTestDirectory; + + public override void BuildPackage() + { + _context.ChocolateyPack(PackageSource, + new ChocolateyPackSettings() + { + Version = PackageVersion, + OutputDirectory = BuildSettings.PackageDirectory, + ArgumentCustomization = args => args.Append($"BIN_DIR={BuildSettings.OutputDirectory}") + }); + } +} diff --git a/cake/command-line-options.cake b/cake/command-line-options.cake new file mode 100644 index 000000000..8f878260f --- /dev/null +++ b/cake/command-line-options.cake @@ -0,0 +1,118 @@ +// *********************************************************************** +// Copyright (c) Charlie Poole and TestCentric GUI contributors. +// Licensed under the MIT License. See LICENSE.txt in root directory. +// *********************************************************************** + +CommandLineOptions.Initialize(Context); + +public static class CommandLineOptions +{ + static private ICakeContext _context; + + static public ValueOption Target; + static public ValueOption Configuration; + static public ValueOption PackageVersion; + static public ValueOption PackageSelector; + static public ValueOption TestLevel; + static public ValueOption TraceLevel; + static public SimpleOption NoBuild; + static public SimpleOption NoPush; + static public SimpleOption Usage; + + public static void Initialize(ICakeContext context) + { + _context = context; + + // The name of the TARGET task to be run, e.g. Test. + Target = new ValueOption("target", "Default", 1); + + Configuration = new ValueOption("configuration", DEFAULT_CONFIGURATION, 1); + + PackageVersion = new ValueOption("packageVersion", null, 4); + + PackageSelector = new ValueOption("where", null, 1); + + TestLevel = new ValueOption("level", 0, 3); + + TraceLevel = new ValueOption("trace", "Off", 2); + + NoBuild = new SimpleOption("nobuild", 3); + + NoPush = new SimpleOption("nopush", 3); + + Usage = new SimpleOption("usage", 2); + } + + // Nested classes to represent individual options + + // AbstractOption has a name and can tell us if it exists. + public abstract class AbstractOption + { + public string Name { get; } + + public int MinimumAbbreviation { get; internal set; } + + public bool Exists + { + get + { + for (int len = Name.Length; len >= MinimumAbbreviation; len--) + if (_context.HasArgument(Name.Substring(0,len))) + return true; + return false; + } + } + + public string Description { get; } + + public AbstractOption(string name, int minimumAbbreviation = 0, string description = null) + { + Name = name; + MinimumAbbreviation = minimumAbbreviation > 0 && minimumAbbreviation <= name.Length + ? minimumAbbreviation + : name.Length; + Description = description; + } + } + + // Simple Option adds an implicit boolean conversion operator. + // It throws an exception if you gave it a value on the command-line. + public class SimpleOption : AbstractOption + { + static public implicit operator bool(SimpleOption o) => o.Exists; + + public SimpleOption(string name, int minimumAbbreviation = 0, string description = null) + : base(name, minimumAbbreviation, description) + { + if (_context.Argument(name, (string)null) != null) + throw new Exception($"Option --{name} does not take a value."); + } + } + + // Generic ValueOption adds Value as well as a default value + public class ValueOption : AbstractOption + { + public T DefaultValue { get; } + + public ValueOption(string name, T defaultValue, int minimumAbbreviation = 0, string description = null) + : base(name, minimumAbbreviation, description) + { + DefaultValue = defaultValue; + } + + public T Value + { + get + { + for (int len = Name.Length; len >= MinimumAbbreviation; len--) + { + string abbrev = Name.Substring(0,len); + if (_context.HasArgument(abbrev)) + return _context.Argument(abbrev); + } + + return DefaultValue; + } + } + } +} diff --git a/cake/common/common-constants.cake b/cake/common/common-constants.cake deleted file mode 100644 index a6b5409f5..000000000 --- a/cake/common/common-constants.cake +++ /dev/null @@ -1,23 +0,0 @@ -//////////////////////////////////////////////////////////////////// -// COMMON CONSTANTS -////////////////////////////////////////////////////////////////////// - -// URLs for uploading packages -private const string MYGET_PUSH_URL = "https://www.myget.org/F/nunit/api/v2"; -private const string NUGET_PUSH_URL = "https://api.nuget.org/v3/index.json"; -private const string CHOCO_PUSH_URL = "https://push.chocolatey.org/"; - -// Environment Variable names holding API keys -private const string MYGET_API_KEY = "MYGET_API_KEY"; -private const string NUGET_API_KEY = "NUGET_API_KEY"; -private const string CHOCO_API_KEY = "CHOCO_API_KEY"; - -// GitHub Information -private const string GITHUB_OWNER = "nunit"; -private const string GITHUB_ACCESS_TOKEN = "GITHUB_ACCESS_TOKEN"; - -// Pre-release labels that we publish -private static readonly string[] LABELS_WE_PUBLISH_ON_MYGET = { "dev" }; -private static readonly string[] LABELS_WE_PUBLISH_ON_NUGET = { "alpha", "beta", "rc" }; -private static readonly string[] LABELS_WE_PUBLISH_ON_CHOCOLATEY = { "alpha", "beta", "rc" }; -private static readonly string[] LABELS_WE_RELEASE_ON_GITHUB = { "alpha", "beta", "rc" }; diff --git a/cake/common/tasks.cake b/cake/common/tasks.cake deleted file mode 100644 index f8ebe8f54..000000000 --- a/cake/common/tasks.cake +++ /dev/null @@ -1,45 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// COMMON TASKS - ADD PROJECT-SPECIFIC TASKS IN BUILD.CAKE -////////////////////////////////////////////////////////////////////// - -////////////////////////////////////////////////////////////////////// -// DUMP SETTINGS -////////////////////////////////////////////////////////////////////// - -Task("DumpSettings") - .Does((settings) => - { - settings.DumpSettings(); - }); - -////////////////////////////////////////////////////////////////////// -// CLEANING -////////////////////////////////////////////////////////////////////// - -Task("Clean") - .Description("Cleans directories.") - .Does((settings) => - { - CleanDirectory(BIN_DIR); - CleanDirectory(PACKAGE_DIR); - CleanDirectory(IMAGE_DIR); - CleanDirectory(EXTENSIONS_DIR); - CleanDirectory(PACKAGE_DIR); - }); - -Task("CleanAll") - .Description("Cleans both Debug and Release Directories followed by deleting object directories") - .Does((settings) => - { - Information("Cleaning both Debug and Release"); - CleanDirectory(PROJECT_DIR + "bin"); - CleanDirectory(PACKAGE_DIR); - CleanDirectory(IMAGE_DIR); - CleanDirectory(EXTENSIONS_DIR); - CleanDirectory(PACKAGE_DIR); - - Information("Deleting object directories"); - foreach (var dir in GetDirectories("src/**/obj/")) - DeleteDirectory(dir, new DeleteDirectorySettings() { Recursive = true }); - }); - diff --git a/cake/constants.cake b/cake/constants.cake index 784f02eec..d73edda5a 100644 --- a/cake/constants.cake +++ b/cake/constants.cake @@ -1,70 +1,54 @@ -////////////////////////////////////////////////////////////////////// -// RUNTIME CONSTANTS AND VARIABLES USED AS CONSTANTS -////////////////////////////////////////////////////////////////////// +// This file contains both real constants and static readonly variables used +// as constants. All values are initialized before any instance variables. -// Some values are static so they may be used in property initialization and in -// classes. Initialization is separate to allow use of non-constant expressions. +// GitHub owner is the NUnit organization +const string GITHUB_OWNER = "nunit"; -private const string GITHUB_REPO = "nunit-console"; -const string NUGET_ID = "NUnit.Extension.NUnitProjectLoader"; -const string CHOCO_ID = "nunit-extension-nunit-project-loader"; +// Defaults const string DEFAULT_CONFIGURATION = "Release"; +private static readonly string[] DEFAULT_VALID_CONFIGS = { "Release", "Debug" }; +static readonly string[] DEFAULT_STANDARD_HEADER = new[] { + "// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt" }; +const string DEFAULT_TEST_RESULT_FILE = "TestResult.xml"; -// Directories -static string PROJECT_DIR; PROJECT_DIR = Context.Environment.WorkingDirectory.FullPath + "/"; -static string PACKAGE_DIR; PACKAGE_DIR = Argument("artifact-dir", PROJECT_DIR + "package") + "/"; -static string PACKAGE_TEST_DIR; PACKAGE_TEST_DIR = PACKAGE_DIR + "tests/"; -static string PACKAGE_RESULT_DIR; PACKAGE_RESULT_DIR = PACKAGE_DIR + "results/"; -static string BIN_DIR; BIN_DIR = PROJECT_DIR + "bin/" + Configuration + "/"; -static string NUGET_DIR; NUGET_DIR = PROJECT_DIR + "nuget/"; -static string CHOCO_DIR; CHOCO_DIR = PROJECT_DIR + "choco/"; -static string MSI_DIR; MSI_DIR = PROJECT_DIR + "msi/"; -static string ZIP_DIR; ZIP_DIR = PROJECT_DIR + "zip/"; -static string TOOLS_DIR; TOOLS_DIR = PROJECT_DIR + "tools/"; -static string IMAGE_DIR; IMAGE_DIR = PROJECT_DIR + "images/"; -static string MSI_IMG_DIR; MSI_IMG_DIR = IMAGE_DIR + "msi/"; -static string ZIP_IMG_DIR; ZIP_IMG_DIR = IMAGE_DIR + "zip/"; -static string SOURCE_DIR; SOURCE_DIR = PROJECT_DIR + "src/"; -static string EXTENSIONS_DIR; EXTENSIONS_DIR = PROJECT_DIR + "bundled-extensions"; +// Standardized project directory structure - not changeable by user +const string SRC_DIR = "src/"; +const string BIN_DIR = "bin/"; +const string NUGET_DIR = "nuget/"; +const string CHOCO_DIR = "choco/"; +const string MSI_DIR = "msi/"; +const string ZIP_DIR = "zip/"; +const string PACKAGE_DIR = "package/"; +const string PKG_TEST_DIR = "package/tests/"; +const string NUGET_TEST_DIR = "package/tests/nuget/"; +//const string NUGET_RUNNER_DIR = "package/tests/nuget/runners/"; +const string CHOCO_TEST_DIR = "package/tests/choco/"; +//const string CHOCO_RUNNER_DIR = "package/tests/choco/runners/"; +const string MSI_TEST_DIR = "package/tests/msi/"; +const string ZIP_TEST_DIR = "package/tests/zip/"; +const string PKG_RSLT_DIR = "package/results/"; +const string NUGET_RSLT_DIR = "package/results/nuget/"; +const string CHOCO_RSLT_DIR = "package/results/choco/"; +const string MSI_RSLT_DIR = "package/results/msi/"; +const string ZIP_RSLT_DIR = "package/results/zip/"; +const string IMAGE_DIR = "package/images"; +const string MSI_IMG_DIR = "package/images/msi/"; +const string ZIP_IMG_DIR = "package/images/zip/"; +const string TOOLS_DIR = "tools/"; -// Solution and Projects -var SOLUTION_FILE = PROJECT_DIR + "NUnitConsole.sln"; -var ENGINE_PROJECT = SOURCE_DIR + "NUnitEngine/nunit.engine/nunit.engine.csproj"; -var AGENT_PROJECT = SOURCE_DIR + "NUnitEngine/nunit-agent/nunit-agent.csproj"; -var AGENT_X86_PROJECT = SOURCE_DIR + "NUnitEngine/nunit-agent-x86/nunit-agent-x86.csproj"; -var ENGINE_API_PROJECT = SOURCE_DIR + "NUnitEngine/nunit.engine.api/nunit.engine.api.csproj"; -var ENGINE_CORE_PROJECT = SOURCE_DIR + "NUnitEngine/nunit.engine.core/nunit.engine.core.csproj"; -var ENGINE_TESTS_PROJECT = SOURCE_DIR + "NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj"; -var ENGINE_CORE_TESTS_PROJECT = SOURCE_DIR + "NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj"; -var CONSOLE_PROJECT = SOURCE_DIR + "NUnitConsole/nunit3-console/nunit3-console.csproj"; -var CONSOLE_TESTS_PROJECT = SOURCE_DIR + "NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj"; -var MOCK_ASSEMBLY_PROJECT = SOURCE_DIR + "NUnitEngine/mock-assembly/mock-assembly.csproj"; -var MOCK_ASSEMBLY_X86_PROJECT = SOURCE_DIR + "NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj"; -var NOTEST_PROJECT = SOURCE_DIR + "NUnitEngine/notest-assembly/notest-assembly.csproj"; -// Console Runner -var NET20_CONSOLE = BIN_DIR + "net20/nunit3-console.exe"; -var NET60_CONSOLE = BIN_DIR + "net6.0/nunit3-console.dll"; -var NET80_CONSOLE = BIN_DIR + "net8.0/nunit3-console.dll"; -// Unit Tests -var NETFX_ENGINE_CORE_TESTS = "nunit.engine.core.tests.exe"; -var NETCORE_ENGINE_CORE_TESTS = "nunit.engine.core.tests.dll"; -var NETFX_ENGINE_TESTS = "nunit.engine.tests.exe"; -var NETCORE_ENGINE_TESTS = "nunit.engine.tests.dll"; -var CONSOLE_TESTS = "nunit3-console.tests.dll"; +// URLs for uploading packages +private const string MYGET_PUSH_URL = "https://www.myget.org/F/nunit/api/v2"; +private const string NUGET_PUSH_URL = "https://api.nuget.org/v3/index.json"; +private const string CHOCO_PUSH_URL = "https://push.chocolatey.org/"; -// Package sources for nuget restore -var PACKAGE_SOURCE = new string[] -{ - "https://www.nuget.org/api/v2", - "https://www.myget.org/F/nunit/api/v2" -}; +// Environment Variable names holding API keys +private const string MYGET_API_KEY = "MYGET_API_KEY"; +private const string NUGET_API_KEY = "NUGET_API_KEY"; +private const string CHOCO_API_KEY = "CHOCO_API_KEY"; +private const string GITHUB_ACCESS_TOKEN = "GITHUB_ACCESS_TOKEN"; -// Extensions we bundle -var BUNDLED_EXTENSIONS = new[] -{ - "NUnit.Extension.VSProjectLoader", - "NUnit.Extension.NUnitProjectLoader", - "NUnit.Extension.NUnitV2Driver", - "NUnit.Extension.NUnitV2ResultWriter", - "NUnit.Extension.TeamCityEventListener" -}; +// Pre-release labels that we publish +private static readonly string[] LABELS_WE_PUBLISH_ON_MYGET = { "dev" }; +private static readonly string[] LABELS_WE_PUBLISH_ON_NUGET = { "alpha", "beta", "rc" }; +private static readonly string[] LABELS_WE_PUBLISH_ON_CHOCOLATEY = { "alpha", "beta", "rc" }; +private static readonly string[] LABELS_WE_RELEASE_ON_GITHUB = { "alpha", "beta", "rc" }; diff --git a/cake/common/dotnet.cake b/cake/dotnet.cake similarity index 100% rename from cake/common/dotnet.cake rename to cake/dotnet.cake diff --git a/cake/extending.cake b/cake/extending.cake new file mode 100644 index 000000000..ed2eed10b --- /dev/null +++ b/cake/extending.cake @@ -0,0 +1,57 @@ +// This file provides information on how to change the pre-defined tasks in +// the NUnit Cake Recipe for a particular project, without changing the +// recipe files themselves. Those files should not be changed unless you +// are trying to make changes for all projects, which use the recipe. +// +// In addition, this file defines a few static methods intended to make it +// easier to override task definitions for a project. +// +// Code given in the following examples should be added to your file after +// the recipe itself has been loaded. +// +// SIMPLE EXAMPLES: +// +// Adding a new dependency after those already present +// BuildTasks.SomeTask.IsDependentOn("SomeOtherTask"); +// +// Adding a new action, which will be performed after the existing action. +// BuildTasks.SomeTask.Does(() => { +// // Code here to do something +// }); +// +// MORE COMPLEX EXAMPLES: +// +// Replacing the existing dependencies, criteria or actions is a bit harder. +// You must first clear the relevant items and then re-add them in the desired +// order along with any new items. The following static methods allow you to do +// so a bit more simply than would otherwise be possible. + +public static void ClearCriteria(CakeTaskBuilder builder) => ((CakeTask)builder.Task).Criterias.Clear(); +public static void ClearDependencies(CakeTaskBuilder builder) => ((CakeTask)builder.Task).Dependencies.Clear(); +public static void ClearActions(CakeTaskBuilder builder) => ((CakeTask)builder.Task).Actions.Clear(); + +// Redefining the action of the build task. Note that dependencies will still run, since we haven't changed them. +// ClearActions(BuildTasks.Build); +// BuildTasks.Build.Does(() => Information("I'm not building today!")); +// +// In some cases, you may wish to completely redefine what a task does. The following static method +// supports a fluent syntax for doing just that + +public static CakeTaskBuilder Redefine(CakeTaskBuilder builder) +{ + ClearCriteria(builder); + ClearDependencies(builder); + ClearActions(builder); + + return builder; +} + +// Redefine the build task completely. Note that in this case, dependencies are not run. +// Redefine(BuildTasks.BuildTask) +// .Does(() => { +// Information("Not Building today!"); +// }); +// +// Modify the Default task for a project to run "Test" rather than "Build". +// Redefine(BuildTasks.DefaultTask) +// .IsDependentOn("Test"); diff --git a/cake/header-check.cake b/cake/header-check.cake deleted file mode 100644 index c4f385131..000000000 --- a/cake/header-check.cake +++ /dev/null @@ -1,102 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// CHECK FOR MISSING AND NON-STANDARD FILE HEADERS -////////////////////////////////////////////////////////////////////// - -static readonly int CD_LENGTH = Environment.CurrentDirectory.Length + 1; - -static readonly string[] EXEMPT_FILES = new [] { - "Options.cs", - "ProcessUtils.cs", - "ProcessUtilsTests.cs" -}; - -// Standard Header. Change this for each project as needed. -static readonly string[] STD_HDR = new [] { - "// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt" -}; - -Task("CheckHeaders") - .Does(() => - { - var NoHeader = new List(); - var NonStandard = new List(); - var Exempted = new List(); - int examined = 0; - - foreach(var file in GetFiles("src/**/*.cs")) - { - // Ignore autogenerated files in an obj directory - if (file.ToString().Contains("/obj/")) - continue; - - var filename = file.GetFilename().ToString(); - if (filename == "AssemblyInfo.cs") - continue; - - examined++; - var header = GetHeader(file); - - if (EXEMPT_FILES.Contains(filename)) - Exempted.Add(file); - else if (header.Count == 0) - NoHeader.Add(file); - else if (!header.SequenceEqual(STD_HDR)) - NonStandard.Add(file); - } - - if (NoHeader.Count > 0) - { - Information("\nFILES WITH NO HEADER\n"); - foreach(var file in NoHeader) - Information(RelPathTo(file)); - } - - if (NonStandard.Count > 0) - { - Information("\nFILES WITH A NON-STANDARD HEADER\n"); - foreach(var file in NonStandard) - { - Information(RelPathTo(file)); - Information(""); - foreach(string line in GetHeader(file)) - Information(line); - Information(""); - } - } - - if (Exempted.Count > 0) - { - Information("\nEXEMPTED FILES (NO CHECK MADE)\n"); - foreach(var file in Exempted) - Information(RelPathTo(file)); - } - - Information($"\nFiles Examined: {examined}"); - Information($"Missing Headers: {NoHeader.Count}"); - Information($"Non-Standard Headers: {NonStandard.Count}"); - Information($"Exempted Files: {Exempted.Count}"); - - if (NoHeader.Count > 0 || NonStandard.Count > 0) - throw new Exception("Missing or invalid file headers found"); - }); - -private List GetHeader(FilePath file) -{ - var header = new List(); - var lines = System.IO.File.ReadLines(file.ToString()); - - foreach(string line in lines) - { - if (!line.StartsWith("//")) - break; - - header.Add(line); - } - - return header; -} - -private string RelPathTo(FilePath file) -{ - return file.ToString().Substring(CD_LENGTH); -} diff --git a/cake/headers.cake b/cake/headers.cake new file mode 100644 index 000000000..3ad3e77d4 --- /dev/null +++ b/cake/headers.cake @@ -0,0 +1,115 @@ +////////////////////////////////////////////////////////////////////// +// CHECK FOR MISSING AND NON-STANDARD FILE HEADERS +////////////////////////////////////////////////////////////////////// + +public static class Headers +{ + private static ICakeContext _context; + + static Headers() + { + _context = BuildSettings.Context; + } + + public static void Check() + { + var NoHeader = new List(); + var NonStandard = new List(); + var Exempted = new List(); + int examined = 0; + + var sourceFiles = _context.GetFiles(BuildSettings.SourceDirectory + "**/*.cs"); + var exemptFiles = BuildSettings.ExemptFiles; + foreach (var file in sourceFiles) + { + var path = file.ToString(); + + // Ignore autogenerated files in an obj directory + if (path.Contains("/obj/")) + continue; + + // Ignore designer files + if (path.EndsWith(".Designer.cs")) + continue; + + // Ignore AssemblyInfo files + if (System.IO.Path.GetFileName(path) == "AssemblyInfo.cs") + continue; + + examined++; + var header = GetHeader(file); + if (exemptFiles.Contains(file.GetFilename().ToString())) + Exempted.Add(file); + else if (header.Count == 0) + NoHeader.Add(file); + else if (!header.SequenceEqual(BuildSettings.StandardHeader)) + NonStandard.Add(file); + } + + _context.Information("\nSTANDARD HEADER\n"); + foreach (string line in BuildSettings.StandardHeader) + _context.Information(line); + _context.Information(""); + + if (NoHeader.Count > 0) + { + _context.Information("\nFILES WITH NO HEADER\n"); + foreach (var file in NoHeader) + _context.Information(RelPathTo(file)); + } + + if (NonStandard.Count > 0) + { + _context.Information("\nFILES WITH A NON-STANDARD HEADER\n"); + foreach (var file in NonStandard) + { + _context.Information(RelPathTo(file)); + _context.Information(""); + foreach (string line in GetHeader(file)) + _context.Information(line); + _context.Information(""); + } + } + + if (Exempted.Count > 0) + { + _context.Information("\nEXEMPTED FILES (NO CHECK MADE)\n"); + foreach (var file in Exempted) + _context.Information(RelPathTo(file)); + } + + _context.Information($"\nFiles Examined: {examined}"); + _context.Information($"Missing Headers: {NoHeader.Count}"); + _context.Information($"Non-Standard Headers: {NonStandard.Count}"); + _context.Information($"Exempted Files: {Exempted.Count}"); + + if (NoHeader.Count > 0 || NonStandard.Count > 0) + throw new Exception("Missing or invalid file headers found"); + + if (examined == 0) + _context.Warning("\nWARNING: There were no '*.cs' files in the source directory. Use of the 'CheckHeaders' task may not make sense for this project."); + + List GetHeader(FilePath file) + { + var header = new List(); + var lines = System.IO.File.ReadLines(file.ToString()); + + foreach (string line in lines) + { + if (!line.StartsWith("//")) + break; + + header.Add(line); + } + + return header; + } + + string RelPathTo(FilePath file) + { + int CD_LENGTH = Environment.CurrentDirectory.Length + 1; + + return file.ToString().Substring(CD_LENGTH); + } + } +} \ No newline at end of file diff --git a/cake/help-messages.cake b/cake/help-messages.cake new file mode 100644 index 000000000..d615f04c9 --- /dev/null +++ b/cake/help-messages.cake @@ -0,0 +1,77 @@ +static public class HelpMessages +{ + static public string Usage => $""" + BUILD.CAKE + + This script builds the {BuildSettings.Title} project. It makes use of + NUnit.Cake.Recipe, which provides a number of built-in options and + tasks. You may define additional options and tasks in build.cake or + in additional cake files you load from build.cake. Recipe options may + be specified in abbreviated form, down to the minimum length shown in + square braces. Single character abbreviations use a single dash. + + Usage: build [options] + + Options: + + --target=TARGET [-t] + The TARGET task to be run, e.g. Test. Default is Build. + + --configuration=CONFIG [-c] + The name of the configuration to build. Default is Release. + + --packageVersion [--pack] + Specifies the full package version, including any pre-release + suffix. If provided, this version is used directly in place of + the default version calculated by the script. + + --where=SELECTION [-w] + Specifies a selction expression used to choose which packages + to build and test, for use in debugging. Consists of one or + more specifications, separated by '|' and '&'. Each specification + is of the form "prop=value", where prop may be either id or type. + Examples: + --where type=msi + --where id=NUnit.Engine.Api + --where "type=msi|type=zip" + + --level=LEVEL [--lev] + Specifies the level of package testing, 1, 2 or 3. Defaults are + 1 = for normal CI tests run every time you build a package + 2 = for PRs and Dev builds uploaded to MyGet + 3 = prior to publishing a release + + --trace=LEVEL [--tr] + Specifies the default trace level for this run. Values are Off, + Error, Warning, Info or Debug. Default is value of environment + variable NUNIT_INTERNAL_TRACE_LEVEL if set. If not, + tracing is turned Off. + + --nobuild [--nob] + Indicates that the Build task should not be run even if other + tasks depend on it. The existing build is used instead. + + --nopush [--nop] + Indicates that no publishing or releasing should be done. If + publish or release targets are run, a message is displayed. + + --usage [--us] + Displays this help message. No targets are run. + + Selected Cake Options: + + --version + Displays the cake version in use. + + --description + Displays a list of the available tasks (targets). + + --tree + Displays the task dependency tree + + --help + Displays help information for cake itself. + + NOTE: The above Cake options bypass execution of the script. + """; +} diff --git a/cake/known-extensions.cake b/cake/known-extensions.cake new file mode 100644 index 000000000..b2bdc4c42 --- /dev/null +++ b/cake/known-extensions.cake @@ -0,0 +1,48 @@ +// Static class holding information about known extensions. +public static class KnownExtensions +{ + // Static Variables representing well-known Extensions with the latest tested version + public static ExtensionSpecifier NUnitV2Driver = new ExtensionSpecifier( + "NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver", "3.9.0"); + public static ExtensionSpecifier NUnitProjectLoader = new ExtensionSpecifier( + "NUnit.Extension.NUnitProjectLoader", "nunit-extension-nunit-project-loader", "3.7.1"); +} + +// Representation of an extension, for use by PackageTests. Because our +// extensions usually exist as both nuget and chocolatey packages, each +// extension may have a nuget id, a chocolatey id or both. A default version +// is used unless the user overrides it using SetVersion. +public class ExtensionSpecifier +{ + public ExtensionSpecifier(string nugetId, string chocoId, string version) + { + NuGetId = nugetId; + ChocoId = chocoId; + Version = version; + } + + public string NuGetId { get; } + public string ChocoId { get; } + public string Version { get; } + + public PackageReference NuGetPackage => new PackageReference(NuGetId, Version); + public PackageReference ChocoPackage => new PackageReference(ChocoId, Version); + public PackageReference LatestChocolateyRelease => ChocoPackage.LatestRelease; + + // Return an extension specifier using the same package ids as this + // one but specifying a particular version to be used. + public ExtensionSpecifier SetVersion(string version) + { + return new ExtensionSpecifier(NuGetId, ChocoId, version); + } + + // Install this extension for a package + public void InstallExtension(PackageDefinition targetPackage) + { + PackageReference extensionPackage = targetPackage.PackageType == PackageType.Chocolatey + ? ChocoPackage + : NuGetPackage; + + extensionPackage.Install(targetPackage.ExtensionInstallDirectory); + } +} diff --git a/cake/msi-package.cake b/cake/msi-package.cake new file mode 100644 index 000000000..224608282 --- /dev/null +++ b/cake/msi-package.cake @@ -0,0 +1,95 @@ +public class MsiPackage : PackageDefinition +{ + public MsiPackage( + string id, + string source, + PackageTestRunner testRunner = null, + PackageCheck[] checks = null, + IEnumerable tests = null, + PackageReference[] bundledExtensions = null) + : base( + PackageType.Msi, + id, + source, + testRunner: testRunner, + checks: checks, + tests: tests) + { + PackageVersion = BuildSettings.BuildVersion.SemVer; + BundledExtensions = bundledExtensions; + } + + // MSI and ZIP packages support bundling of extensions + // if any are specified in the definition. + public PackageReference[] BundledExtensions { get; } + + // The file name of this package, including extension + public override string PackageFileName => $"{PackageId}-{PackageVersion}.msi"; + // The directory into which this package is installed + public override string PackageInstallDirectory => BuildSettings.MsiTestDirectory; + // The directory used to contain results of package tests for this package + public override string PackageResultDirectory => BuildSettings.MsiResultDirectory + PackageId + "/"; + // The directory into which extensions to the test runner are installed + // TODO: FIx this for msi addins + public override string ExtensionInstallDirectory => BuildSettings.PackageTestDirectory; + + public override void BuildPackage() + { + FetchBundledExtensions(BundledExtensions); + + CreateMsiImage(); + + _context.MSBuild(PackageSource, new MSBuildSettings() + .WithTarget("Rebuild") + .SetConfiguration(BuildSettings.Configuration) + .WithProperty("Version", PackageVersion) + .WithProperty("DisplayVersion", PackageVersion) + .WithProperty("OutDir", BuildSettings.PackageDirectory) + .WithProperty("Image", BuildSettings.MsiImageDirectory) + .SetMSBuildPlatform(MSBuildPlatform.x86) + .SetNodeReuse(false)); + } + + private void CreateMsiImage() + { + _context.CleanDirectory(BuildSettings.MsiImageDirectory); + + _context.CopyFiles( + new FilePath[] { "LICENSE.txt", "NOTICES.txt", "CHANGES.txt", "nunit.ico" }, + BuildSettings.MsiImageDirectory); + + _context.CopyDirectory( + BuildSettings.OutputDirectory, + BuildSettings.MsiImageDirectory + "bin/" ); + + foreach (var runtime in new[] { "net20", "net35" }) + { + var addinsImgDir = BuildSettings.MsiImageDirectory + $"bin/{runtime}/addins/"; + + _context.CopyDirectory( + BuildSettings.MsiDirectory + "resources/", + BuildSettings.MsiImageDirectory); + + _context.CleanDirectory(addinsImgDir); + + foreach (var packageDir in System.IO.Directory.GetDirectories(BuildSettings.ExtensionsDirectory)) + { + var files = _context.GetFiles(packageDir + "/tools/*").Concat(_context.GetFiles(packageDir + "/tools/net20/*")); + _context.CopyFiles(files.Where(f => f.GetExtension() != ".addins"), addinsImgDir); + } + } + } + + public override void InstallPackage() + { + // Msiexec does not tolerate forward slashes! + string package = PackageFilePath.Replace("/", "\\"); + string testDir = System.IO.Path.Combine(PackageInstallDirectory.Replace("/", "\\"), $"{PackageId}.{PackageVersion}"); + + Console.WriteLine($"Installing msi to {testDir}"); + int rc = _context.StartProcess("msiexec", $"/a {package} TARGETDIR={testDir} /q"); + + if (rc != 0) + Console.WriteLine($" ERROR: Installer returned {rc.ToString()}"); + } +} diff --git a/cake/nuget-package.cake b/cake/nuget-package.cake new file mode 100644 index 000000000..979fb7c68 --- /dev/null +++ b/cake/nuget-package.cake @@ -0,0 +1,56 @@ +public class NuGetPackage : PackageDefinition +{ + public NuGetPackage( + string id, + string source, + PackageTestRunner testRunner = null, + PackageCheck[] checks = null, + PackageCheck[] symbols = null, + IEnumerable tests = null) + : base( + PackageType.NuGet, + id, + source, + testRunner: testRunner, + checks: checks, + symbols: symbols, + tests: tests) + { + if (!source.EndsWith(".nuspec")) + throw new ArgumentException("Source must be a nuspec file", nameof(source)); + + if (symbols != null) + { + HasSymbols = true; + SymbolChecks = symbols; + } + } + + // The file name of this package, including extension + public override string PackageFileName => $"{PackageId}.{PackageVersion}.nupkg"; + // The file name of any symbol package, including extension + public override string SymbolPackageName => System.IO.Path.ChangeExtension(PackageFileName, ".snupkg"); + // The directory into which this package is installed + public override string PackageInstallDirectory => BuildSettings.NuGetTestDirectory; + // The directory used to contain results of package tests for this package + public override string PackageResultDirectory => BuildSettings.NuGetResultDirectory + PackageId + "/"; + // The directory into which extensions to the test runner are installed + public override string ExtensionInstallDirectory => BuildSettings.PackageTestDirectory; + + public override void BuildPackage() + { + var nugetPackSettings = new NuGetPackSettings() + { + Version = PackageVersion, + BasePath = BuildSettings.OutputDirectory, + OutputDirectory = BuildSettings.PackageDirectory, + NoPackageAnalysis = true, + Symbols = HasSymbols + }; + + if (HasSymbols) + nugetPackSettings.SymbolPackageFormat = "snupkg"; + + _context.NuGetPack(PackageSource, nugetPackSettings); + } +} diff --git a/cake/package-checks.cake b/cake/package-checks.cake index be45ec4da..66e91df9b 100644 --- a/cake/package-checks.cake +++ b/cake/package-checks.cake @@ -1,228 +1,148 @@ ////////////////////////////////////////////////////////////////////// -// LISTS OF FILES USED IN CHECKING PACKAGES +// SYNTAX FOR EXPRESSING CHECKS ////////////////////////////////////////////////////////////////////// -string[] ENGINE_FILES = { - "nunit.engine.dll", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll" }; -string[] ENGINE_PDB_FILES = { - "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; -string[] ENGINE_CORE_FILES = { - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll" }; -string[] ENGINE_CORE_PDB_FILES = { - "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; -string[] AGENT_FILES = { - "nunit-agent.exe", "nunit-agent.exe.config", - "nunit-agent-x86.exe", "nunit-agent-x86.exe.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll"}; -string[] AGENT_FILES_NETCORE = { - "nunit-agent.dll", "nunit-agent.dll.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll", - "Microsoft.Extensions.DependencyModel.dll"}; -string[] AGENT_PDB_FILES = { - "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; -string[] AGENT_PDB_FILES_NETCORE = { - "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; -string[] CONSOLE_FILES = { - "nunit3-console.exe", "nunit3-console.exe.config" }; -string[] CONSOLE_FILES_NETCORE = { - "nunit3-console.exe", "nunit3-console.dll" }; - -////////////////////////////////////////////////////////////////////// -// PACKAGE CHECK IMPLEMENTATION -////////////////////////////////////////////////////////////////////// - -// NOTE: Package checks basically do no more than what the programmer might -// do in opening the package itself and examining the content. - -public bool CheckPackage(string package, params PackageCheck[] checks) +public static class Check { - Console.WriteLine("\nPackage Name: " + System.IO.Path.GetFileName(package)); + public static void That(DirectoryPath testDirPath, IList checks) + { + if (checks == null) + throw new ArgumentNullException(nameof(checks)); - if (!FileExists(package)) - { - WriteError("Package was not found!"); - return false; - } + bool allOK = true; - if (checks.Length == 0) - { - WriteWarning("Package found but no checks were specified."); - return true; - } - - bool isMsi = package.EndsWith(".msi"); - string tempDir = isMsi - ? InstallMsiToTempDir(package) - : UnzipToTempDir(package); - - if (!System.IO.Directory.Exists(tempDir)) - { - WriteError("Temporary directory was not created!"); - return false; - } - - try - { - bool allPassed = ApplyChecks(tempDir, checks); - if (allPassed) - WriteInfo("All checks passed!"); + foreach (var check in checks) + allOK &= check.ApplyTo(testDirPath); - return allPassed; + if (!allOK) throw new Exception("Verification failed!"); } - finally - { - DeleteDirectory(tempDir, new DeleteDirectorySettings() - { - Recursive = true, - Force = true - }); - } -} - -private string InstallMsiToTempDir(string package) -{ - // Msiexec does not tolerate forward slashes! - package = package.Replace("/", "\\"); - var tempDir = GetTempDirectoryPath(); - - WriteInfo("Installing to " + tempDir); - int rc = StartProcess("msiexec", $"/a {package} TARGETDIR={tempDir} /q"); - if (rc != 0) - WriteError($"Installer returned {rc.ToString()}"); - - return tempDir; -} - -private string UnzipToTempDir(string package) -{ - var tempDir = GetTempDirectoryPath(); - - WriteInfo("Unzipping to " + tempDir); - Unzip(package, tempDir); - - return tempDir; } -private string GetTempDirectoryPath() -{ - return System.IO.Path.GetTempPath() + System.IO.Path.GetRandomFileName() + "\\"; -} +private static FileCheck HasFile(FilePath file) => HasFiles(new[] { file }); +private static FileCheck HasFiles(params FilePath[] files) => new FileCheck(files); -private bool ApplyChecks(string dir, PackageCheck[] checks) -{ - bool allOK = true; - - foreach (var check in checks) - allOK &= check.Apply(dir); +private static DirectoryCheck HasDirectory(string dir) => new DirectoryCheck(dir); - return allOK; -} +////////////////////////////////////////////////////////////////////// +// PACKAGECHECK CLASS +////////////////////////////////////////////////////////////////////// public abstract class PackageCheck { - public abstract bool Apply(string dir); + protected ICakeContext _context; + + public PackageCheck() + { + _context = BuildSettings.Context; + } + + public abstract bool ApplyTo(DirectoryPath testDirPath); + + protected bool CheckDirectoryExists(DirectoryPath dirPath) + { + if (!_context.DirectoryExists(dirPath)) + { + DisplayError($"Directory {dirPath} was not found."); + return false; + } + + return true; + } + + protected bool CheckFileExists(FilePath filePath) + { + if (!_context.FileExists(filePath)) + { + DisplayError($"File {filePath} was not found."); + return false; + } + + return true; + } + + protected bool CheckFilesExist(IEnumerable filePaths) + { + bool isOK = true; + + foreach (var filePath in filePaths) + isOK &= CheckFileExists(filePath); + + return isOK; + } + + protected bool DisplayError(string msg) + { + _context.Error(" ERROR: " + msg); + + // The return value may be ignored or used as a shortcut + // for an immediate return from ApplyTo as in + // return DisplayError(...) + return false; + } } +////////////////////////////////////////////////////////////////////// +// FILECHECK CLASS +////////////////////////////////////////////////////////////////////// + public class FileCheck : PackageCheck { - string[] _paths; + FilePath[] _files; - public FileCheck(string[] paths) - { - _paths = paths; - } + public FileCheck(FilePath[] files) + { + _files = files; + } - public override bool Apply(string dir) - { - var isOK = true; - - foreach (string path in _paths) - { - if (!System.IO.File.Exists(dir + path)) - { - WriteError($"File {path} was not found."); - isOK = false; - } - } - - return isOK; - } + public override bool ApplyTo(DirectoryPath testDirPath) + { + return CheckFilesExist(_files.Select(file => testDirPath.CombineWithFilePath(file))); + } } +////////////////////////////////////////////////////////////////////// +// DIRECTORYCHECK CLASS +////////////////////////////////////////////////////////////////////// + public class DirectoryCheck : PackageCheck { - private string _path; - private List _files = new List(); + private DirectoryPath _relDirPath; + private List _files = new List(); - public DirectoryCheck(string path) - { - _path = path; - } + public DirectoryCheck(DirectoryPath relDirPath) + { + _relDirPath = relDirPath; + } - public DirectoryCheck WithFiles(params string[] files) - { - _files.AddRange(files); - return this; - } + public DirectoryCheck WithFiles(params FilePath[] files) + { + _files.AddRange(files); + return this; + } - public DirectoryCheck AndFiles(params string[] files) + public DirectoryCheck AndFiles(params FilePath[] files) { return WithFiles(files); } - public DirectoryCheck WithFile(string file) - { - _files.Add(file); - return this; - } + public DirectoryCheck WithFile(FilePath file) + { + _files.Add(file); + return this; + } - public DirectoryCheck AndFile(string file) + public DirectoryCheck AndFile(FilePath file) { return AndFiles(file); } - public override bool Apply(string dir) - { - if (!System.IO.Directory.Exists(dir + _path)) - { - WriteError($"Directory {_path} was not found."); - return false; - } - - bool isOK = true; - - if (_files != null) - { - foreach (var file in _files) - { - if (!System.IO.File.Exists(System.IO.Path.Combine(dir + _path, file))) - { - WriteError($"File {file} was not found in directory {_path}."); - isOK = false; - } - } - } - - return isOK; - } -} - -private FileCheck HasFile(string file) => HasFiles(new [] { file }); -private FileCheck HasFiles(params string[] files) => new FileCheck(files); + public override bool ApplyTo(DirectoryPath testDirPath) + { + DirectoryPath absDirPath = testDirPath.Combine(_relDirPath); -private DirectoryCheck HasDirectory(string dir) => new DirectoryCheck(dir); + if (!CheckDirectoryExists(absDirPath)) + return false; -private static void WriteError(string msg) -{ - Console.WriteLine(" ERROR: " + msg); -} - -private static void WriteWarning(string msg) -{ - Console.WriteLine(" WARNING: " + msg); -} - -private static void WriteInfo(string msg) -{ - Console.WriteLine(" " + msg); + return CheckFilesExist(_files.Select(file => absDirPath.CombineWithFilePath(file))); + } } diff --git a/cake/package-definition.cake b/cake/package-definition.cake new file mode 100644 index 000000000..fa7c55b3a --- /dev/null +++ b/cake/package-definition.cake @@ -0,0 +1,263 @@ +public enum PackageType +{ + NuGet, + Chocolatey, + Msi, + Zip +} + +public abstract class PackageDefinition +{ + protected ICakeContext _context; + + /// + /// Constructor + /// + /// A PackageType value specifying one of the four known package types + /// A string containing the package ID, used as the root of the PackageName + /// A string representing the source used to create the package, e.g. a nuspec file + /// A TestRunner instance used to run package tests. + /// An array of PackageChecks be made on the content of the package. Optional. + /// An array of PackageChecks to be made on the symbol package, if one is created. Optional. Only supported for nuget packages. + /// An array of PackageTests to be run against the package. Optional. + protected PackageDefinition( + PackageType packageType, + string id, + string source, + PackageTestRunner testRunner = null, + string extraTestArguments = null, + PackageCheck[] checks = null, + PackageCheck[] symbols = null, + IEnumerable tests = null) + { + if (testRunner == null && tests != null) + throw new System.ArgumentException($"Unable to create {packageType} package {id}: TestRunner must be provided if there are tests", nameof(testRunner)); + + _context = BuildSettings.Context; + + PackageType = packageType; + PackageId = id; + PackageVersion = BuildSettings.PackageVersion; + PackageSource = source; + BasePath = BuildSettings.OutputDirectory; + TestRunner = testRunner; + ExtraTestArguments = extraTestArguments; + PackageChecks = checks; + SymbolChecks = symbols; + PackageTests = tests; + } + + public PackageType PackageType { get; } + public string PackageId { get; } + public string PackageVersion { get; protected set; } + public string PackageSource { get; } + public string BasePath { get; } + public PackageTestRunner TestRunner { get; } + public string ExtraTestArguments { get; } + public PackageCheck[] PackageChecks { get; } + public PackageCheck[] SymbolChecks { get; protected set; } + public IEnumerable PackageTests { get; } + + public bool HasSymbols { get; protected set; } = false; + public virtual string SymbolPackageName => throw new System.NotImplementedException($"Symbols are not available for {PackageType} packages."); + + // The file name of this package, including extension + public abstract string PackageFileName { get; } + // The directory into which this package is installed + public abstract string PackageInstallDirectory { get; } + // The directory used to contain results of package tests for this package + public abstract string PackageResultDirectory { get; } + // The directory into which extensions to the test runner are installed + public abstract string ExtensionInstallDirectory { get; } + + public string PackageFilePath => BuildSettings.PackageDirectory + PackageFileName; + public string PackageTestDirectory => $"{PackageInstallDirectory}{PackageId}.{PackageVersion}/"; + + public bool IsSelectedBy(string selectionExpression) + { + return IsSelectedByAny(selectionExpression.Split("|", StringSplitOptions.RemoveEmptyEntries)); + + bool IsSelectedByAny(string[] terms) + { + foreach (var term in terms) + if (IsSelectedByAll(term.Split("&", StringSplitOptions.RemoveEmptyEntries))) + return true; + + return false; + } + + bool IsSelectedByAll(string[] factors) + { + foreach (string factor in factors) + { + int index = factor.IndexOf("="); + if (index <= 0) + throw new ArgumentException("Selection expression does not contain =", "where"); + string prop = factor.Substring(0, index).Trim(); + string val = factor.Substring(index+1).Trim(); + + switch(prop.ToUpper()) + { + case "ID": + return PackageId.ToLower() == val.ToLower(); + case "TYPE": + return PackageType.ToString().ToLower() == val.ToLower(); + default: + throw new Exception($"Not a valid selection property: {prop}"); + } + } + + return false; + } + } + + public void BuildVerifyAndTest() + { + _context.EnsureDirectoryExists(BuildSettings.PackageDirectory); + + Banner.Display($"Building {PackageFileName}"); + BuildPackage(); + + Banner.Display($"Installing {PackageFileName}"); + InstallPackage(); + + if (PackageChecks != null) + { + Banner.Display($"Verifying {PackageFileName}"); + VerifyPackage(); + } + + if (SymbolChecks != null) + { + // TODO: Override this in NuGetPackage + VerifySymbolPackage(); + } + + if (PackageTests != null) + { + Banner.Display($"Testing {PackageFileName}"); + RunPackageTests(); + } + } + + protected void FetchBundledExtensions(PackageReference[] extensions) + { + foreach (var extension in extensions) + if (!extension.IsInstalled(BuildSettings.ExtensionsDirectory)) + extension.Install(BuildSettings.ExtensionsDirectory); + } + + public abstract void BuildPackage(); + + // Base implementation is used for installing both NuGet and + // Chocolatey packages. Other package types should override. + public virtual void InstallPackage() + { + var installSettings = new NuGetInstallSettings + { + Source = new [] { + // Package will be found here + BuildSettings.PackageDirectory, + // Dependencies may be in any of these + "https://www.myget.org/F/nunit/api/v3/index.json", + "https://api.nuget.org/v3/index.json" }, + Version = PackageVersion, + OutputDirectory = PackageInstallDirectory, + //ExcludeVersion = true, + Prerelease = true, + NoCache = true + }; + + _context.NuGetInstall(PackageId, installSettings); + } + + public void VerifyPackage() + { + bool allOK = true; + + if (PackageChecks != null) + foreach (var check in PackageChecks) + allOK &= check.ApplyTo(PackageTestDirectory); + + if (allOK) + Console.WriteLine("All checks passed!"); + else + throw new Exception("Verification failed!"); + } + + public void RunPackageTests() + { + _context.Information($"Package tests will run at level {BuildSettings.PackageTestLevel}"); + + var reporter = new ResultReporter(PackageFileName); + + _context.CleanDirectory(PackageResultDirectory); + + // Ensure we start out each package with no extensions installed. + // If any package test installs an extension, it remains available + // for subsequent tests of the same package only. + //foreach (DirectoryPath dirPath in _context.GetDirectories(ExtensionInstallDirectory + "*")) + //{ + // _context.DeleteDirectory(dirPath, new DeleteDirectorySettings() { Recursive = true }); + // _context.Information("Deleted directory " + dirPath.GetDirectoryName()); + //} + + //if (TestRunner.RequiresInstallation) + // TestRunner.Install(); + + foreach (var packageTest in PackageTests) + { + if (packageTest.Level > BuildSettings.PackageTestLevel) + continue; + + foreach (ExtensionSpecifier extension in packageTest.ExtensionsNeeded) + extension.InstallExtension(this); + + var testResultDir = $"{PackageResultDirectory}/{packageTest.Name}/"; + var resultFile = testResultDir + "TestResult.xml"; + + Banner.Display(packageTest.Description); + + _context.CreateDirectory(testResultDir); + string arguments = $"{packageTest.Arguments} {ExtraTestArguments} --work={testResultDir}"; + if (CommandLineOptions.TraceLevel.Value != "Off") + arguments += $" --trace:{CommandLineOptions.TraceLevel.Value}"; + + int rc = TestRunner.Run(arguments); + + try + { + var result = new ActualResult(resultFile); + var report = new PackageTestReport(packageTest, result); + reporter.AddReport(report); + + Console.WriteLine(report.Errors.Count == 0 + ? "\nSUCCESS: Test Result matches expected result!" + : "\nERROR: Test Result not as expected!"); + } + catch (Exception ex) + { + reporter.AddReport(new PackageTestReport(packageTest, ex)); + + Console.WriteLine("\nERROR: No result found!"); + } + } + + // Create report as a string + var sw = new StringWriter(); + bool hadErrors = reporter.ReportResults(sw); + string reportText = sw.ToString(); + + //Display it on the console + Console.WriteLine(reportText); + + // Save it to the result directory as well + using (var reportFile = new StreamWriter($"{PackageResultDirectory}/PackageTestSummary.txt")) + reportFile.Write(reportText); + + if (hadErrors) + throw new Exception("One or more package tests had errors!"); + } + + public virtual void VerifySymbolPackage() { } // Does nothing. Overridden for NuGet packages. +} diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake deleted file mode 100644 index 1514bcc91..000000000 --- a/cake/package-definitions.cake +++ /dev/null @@ -1,397 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// INDIVIDUAL PACKAGE DEFINITIONS -////////////////////////////////////////////////////////////////////// - -PackageDefinition NUnitConsoleNuGetPackage; -PackageDefinition NUnitConsoleRunnerNuGetPackage; -PackageDefinition NUnitConsoleRunnerNet60Package; -PackageDefinition NUnitConsoleRunnerNet80Package; -PackageDefinition NUnitEnginePackage; -PackageDefinition NUnitEngineApiPackage; -PackageDefinition NUnitConsoleRunnerChocolateyPackage; -PackageDefinition NUnitConsoleMsiPackage; -PackageDefinition NUnitConsoleZipPackage; - -public void InitializePackageDefinitions(BuildSettings settings) -{ - const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; - bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); - var context = settings.Context; - - // Tests run for all runner packages except NETCORE runner - var StandardRunnerTests = new List - { - Net35Test, - Net35X86Test, - Net40Test, - Net40X86Test, - Net35PlusNet40Test, - NetCore31Test, - Net50Test, - Net60Test, - Net70Test, - Net80Test, - Net50PlusNet60Test, - Net40PlusNet60Test, - NUnitProjectTest - }; - - // Tests run for the NETCORE runner package - var NetCoreRunnerTests = new List - { - NetCore31Test, - Net50Test, - Net60Test, - Net70Test, - Net80Test, - }; - - // TODO: Remove the limitation to Windows - if (IsRunningOnWindows() && dotnetX86Available) - { - StandardRunnerTests.Add(Net60X86Test); - // TODO: Make these tests run on AppVeyor - if (!context.BuildSystem().IsRunningOnAppVeyor) - { - StandardRunnerTests.Add(NetCore31X86Test); - StandardRunnerTests.Add(Net50X86Test); - StandardRunnerTests.Add(Net70X86Test); - StandardRunnerTests.Add(Net80X86Test); - } - // Currently, NetCoreRunner runs tests in process. As a result, - // X86 tests will work in our environment, although uses may run - // it as a tool using the X86 architecture. - } - - - AllPackages.AddRange(new PackageDefinition[] { - - NUnitConsoleNuGetPackage = new NuGetPackage( - context: context, - id: "NUnit.Console", - version: settings.PackageVersion, - source: NUGET_DIR + "runners/nunit.console-runner-with-extensions.nuspec", - checks: new PackageCheck[] { HasFile("LICENSE.txt") }), - - NUnitConsoleRunnerNuGetPackage = new NuGetPackage( - context: context, - id: "NUnit.ConsoleRunner", - version: settings.PackageVersion, - source: NUGET_DIR + "runners/nunit.console-runner.nuspec", - checks: new PackageCheck[] { - HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins"), - HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins") - }, - symbols: new PackageCheck[] { - HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), - HasDirectory("tools/agents/net20").WithFiles(AGENT_PDB_FILES), - HasDirectory("tools/agents/net40").WithFiles(AGENT_PDB_FILES), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("tools/agents/net5.0").WithFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_PDB_FILES_NETCORE) - }, - executable: "tools/nunit3-console.exe", - tests: StandardRunnerTests), - - NUnitConsoleRunnerNet80Package = new NuGetPackage( - context: context, - id: "NUnit.ConsoleRunner.NetCore", - version: settings.PackageVersion, - source: NUGET_DIR + "runners/nunit.console-runner.netcore.nuspec", - checks: new PackageCheck[] { - HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools/net8.0").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_CORE_FILES).AndFile("nunit.console.nuget.addins") - }, - symbols: new PackageCheck[] { - HasDirectory("tools/net8.0").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) - }, - executable: "tools/net8.0/nunit3-console.exe", - tests: NetCoreRunnerTests), - - NUnitConsoleRunnerNet60Package = new NuGetPackage( - context: context, - id: "NUnit.ConsoleRunner.NetCore", - version: settings.PackageVersion, - source: NUGET_DIR + "runners/nunit.console-runner.netcore.nuspec", - checks: new PackageCheck[] { - HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools/net6.0").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_CORE_FILES).AndFile("nunit.console.nuget.addins") - }, - symbols: new PackageCheck[] { - HasDirectory("tools/net6.0").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) - }, - executable: "tools/net6.0/nunit3-console.exe", - tests: NetCoreRunnerTests), - - NUnitConsoleRunnerChocolateyPackage = new ChocolateyPackage( - context: context, - id: "nunit-console-runner", - version: settings.PackageVersion, - source: CHOCO_DIR + "nunit-console-runner.nuspec", - checks: new PackageCheck[] { - HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt").AndFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.choco.addins"), - HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins") - }, - executable: "tools/nunit3-console.exe", - tests: StandardRunnerTests), - - NUnitConsoleMsiPackage = new MsiPackage( - context: context, - id: "NUnit.Console", - version: settings.BuildVersion.SemVer, - source: MSI_DIR + "nunit/nunit.wixproj", - checks: new PackageCheck[] { - HasDirectory("NUnit.org").WithFiles("LICENSE.txt", "NOTICES.txt", "nunit.ico"), - HasDirectory("NUnit.org/nunit-console").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.bundle.addins"), - HasDirectory("Nunit.org/nunit-console/addins").WithFiles("nunit.core.dll", "nunit.core.interfaces.dll", "nunit.v2.driver.dll", "nunit-project-loader.dll", "vs-project-loader.dll", "nunit-v2-result-writer.dll", "teamcity-event-listener.dll") - }, - executable: "NUnit.org/nunit-console/nunit3-console.exe", - tests: StandardRunnerTests), - - NUnitConsoleZipPackage = new ZipPackage( - context: context, - id: "NUnit.Console", - version: settings.PackageVersion, - source: ZIP_IMG_DIR, - checks: new PackageCheck[] { - HasFiles("LICENSE.txt", "NOTICES.txt", "CHANGES.txt"), - HasDirectory("bin/net20").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), - HasDirectory("bin/net35").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), - HasDirectory("bin/netstandard2.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), - HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), - //HasDirectory("bin/net5.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), - HasDirectory("bin/agents/net20").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), - HasDirectory("bin/agents/net40").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), - HasDirectory("bin/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("bin/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) - }, - executable: "bin/net20/nunit3-console.exe", - tests: StandardRunnerTests), - - // NOTE: Packages below this point have no direct tests - - NUnitEnginePackage = new NuGetPackage( - context: context, - id: "NUnit.Engine", - version: settings.PackageVersion, - source: NUGET_DIR + "engine/nunit.engine.nuspec", - checks: new PackageCheck[] { - HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("lib/net20").WithFiles(ENGINE_FILES), - HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_FILES), - HasDirectory("contentFiles/any/lib/net20").WithFile("nunit.engine.nuget.addins"), - HasDirectory("contentFiles/any/lib/netstandard2.0").WithFile("nunit.engine.nuget.addins"), - HasDirectory("contentFiles/any/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins"), - HasDirectory("contentFiles/any/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins") - }, - symbols: new PackageCheck[] { - HasDirectory("lib/net20").WithFiles(ENGINE_PDB_FILES), - HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_PDB_FILES), - HasDirectory("contentFiles/any/agents/net20").WithFiles(AGENT_PDB_FILES), - HasDirectory("contentFiles/any/agents/net40").WithFiles(AGENT_PDB_FILES) - }), - - NUnitEngineApiPackage = new NuGetPackage( - context: context, - id: "NUnit.Engine.Api", - version: settings.PackageVersion, - source: NUGET_DIR + "engine/nunit.engine.api.nuspec", - checks: new PackageCheck[] { - HasFile("LICENSE.txt"), - HasDirectory("lib/net20").WithFile("nunit.engine.api.dll"), - HasDirectory("lib/netstandard2.0").WithFile("nunit.engine.api.dll"), - }, - symbols: new PackageCheck[] { - HasDirectory("lib/net20").WithFile("nunit.engine.api.pdb"), - HasDirectory("lib/netstandard2.0").WithFile("nunit.engine.api.pdb") - }) - }); -} - -////////////////////////////////////////////////////////////////////// -// LIST OF ALL PACKAGES -////////////////////////////////////////////////////////////////////// - -var AllPackages = new List(); - -////////////////////////////////////////////////////////////////////// -// PACKAGE DEFINITION IMPLEMENTATION -////////////////////////////////////////////////////////////////////// - -public enum PackageType -{ - NuGet, - Chocolatey, - Msi, - Zip -} - -/// -/// -/// -public abstract class PackageDefinition -{ - protected ICakeContext _context; - - /// - /// - /// - /// A PackageType value specifying one of the four known package types - /// A string containing the package ID, used as the root of the PackageName - /// A string representing the package version, used as part of the PackageName - /// A string representing the source used to create the package, e.g. a nuspec file - /// A string containing the path to the executable used in running tests. If relative, the path is contained within the package itself. - /// An array of PackageChecks be made on the content of the package. Optional. - /// An array of PackageChecks to be made on the symbol package, if one is created. Optional. Only supported for nuget packages. - /// An array of PackageTests to be run against the package. Optional. - protected PackageDefinition( - ICakeContext context, - PackageType packageType, - string id, - string version, - string source, - string executable = null, - PackageCheck[] checks = null, - PackageCheck[] symbols = null, - IEnumerable tests = null) - { - if (executable == null && tests != null) - throw new System.ArgumentException($"Unable to create {packageType} package {id}: Executable must be provided if there are tests", nameof(executable)); - - _context = context; - - PackageType = packageType; - PackageId = id; - PackageVersion = version; - PackageSource = source; - TestExecutable = executable; - PackageChecks = checks; - PackageTests = tests; - SymbolChecks = symbols; - } - - public PackageType PackageType { get; } - public string PackageId { get; } - public string PackageVersion { get; } - public string PackageSource { get; } - public string TestExecutable { get; } - public PackageCheck[] PackageChecks { get; } - public PackageCheck[] SymbolChecks { get; protected set; } - public IEnumerable PackageTests { get; } - - public abstract string PackageName { get; } - public abstract void BuildPackage(); - - public bool HasSymbols { get; protected set; } = false; - public virtual string SymbolPackageName => throw new System.NotImplementedException($"Symbols are not available for {PackageType} packages."); -} - -// Users may only instantiate the derived classes, which avoids -// exposing PackageType and makes it impossible to create a -// PackageDefinition with an unknown package type. -public class NuGetPackage : PackageDefinition -{ - public NuGetPackage(ICakeContext context, string id, string version, string source, string executable = null, - PackageCheck[] checks = null, PackageCheck[] symbols = null, IEnumerable tests = null) - : base(context, PackageType.NuGet, id, version, source, executable: executable, checks: checks, symbols: symbols, tests: tests) - { - if (symbols != null) - { - HasSymbols = true; - SymbolChecks = symbols; - } - } - - public override string PackageName => $"{PackageId}.{PackageVersion}.nupkg"; - public override string SymbolPackageName => System.IO.Path.ChangeExtension(PackageName, ".snupkg"); - - public override void BuildPackage() - { - var nugetPackSettings = new NuGetPackSettings() - { - Version = PackageVersion, - BasePath = BIN_DIR, - OutputDirectory = PACKAGE_DIR, - NoPackageAnalysis = true, - Symbols = HasSymbols - }; - - if (HasSymbols) - nugetPackSettings.SymbolPackageFormat = "snupkg"; - - _context.NuGetPack(PackageSource, nugetPackSettings); - } -} - -public class ChocolateyPackage : PackageDefinition -{ - public ChocolateyPackage(ICakeContext context, string id, string version, string source, string executable = null, - PackageCheck[] checks = null, IEnumerable tests = null) - : base(context, PackageType.Chocolatey, id, version, source, executable: executable, checks: checks, tests: tests) { } - - public override string PackageName => $"{PackageId}.{PackageVersion}.nupkg"; - - public override void BuildPackage() - { - _context.ChocolateyPack(PackageSource, - new ChocolateyPackSettings() - { - Version = PackageVersion, - OutputDirectory = PACKAGE_DIR, - ArgumentCustomization = args => args.Append($"BIN_DIR={BIN_DIR}") - }); - } -} - -public class MsiPackage : PackageDefinition -{ - public MsiPackage(ICakeContext context, string id, string version, string source, string executable = null, - PackageCheck[] checks = null, IEnumerable tests = null) - : base(context, PackageType.Msi, id, version, source, executable: executable, checks: checks, tests: tests) { } - - public override string PackageName => $"{PackageId}-{PackageVersion}.msi"; - - public override void BuildPackage() - { - _context.MSBuild(PackageSource, new MSBuildSettings() - .WithTarget("Rebuild") - .SetConfiguration(Configuration) - .WithProperty("Version", PackageVersion) - .WithProperty("DisplayVersion", PackageVersion) - .WithProperty("OutDir", PACKAGE_DIR) - .WithProperty("Image", MSI_IMG_DIR) - .SetMSBuildPlatform(MSBuildPlatform.x86) - .SetNodeReuse(false)); - } -} - -public class ZipPackage : PackageDefinition -{ - public ZipPackage(ICakeContext context, string id, string version, string source, string executable = null, - PackageCheck[] checks = null, IEnumerable tests = null) - : base(context, PackageType.Zip, id, version, source, executable: executable, checks: checks, tests: tests) { } - - public override string PackageName => $"{PackageId}-{PackageVersion}.zip"; - - public override void BuildPackage() - { - _context.Zip(ZIP_IMG_DIR, $"{PACKAGE_DIR}{PackageName}"); - } -} diff --git a/cake/package-reference.cake b/cake/package-reference.cake new file mode 100644 index 000000000..921ef9dac --- /dev/null +++ b/cake/package-reference.cake @@ -0,0 +1,90 @@ +// Representation of a package reference, containing everything needed to install it +public class PackageReference +{ + private ICakeContext _context; + + public string Id { get; } + public string Version { get; } + + public PackageReference(string id, string version) + { + _context = BuildSettings.Context; + + Id = id; + Version = version; + } + + public PackageReference LatestDevBuild => GetLatestDevBuild(); + public PackageReference LatestRelease => GetLatestRelease(); + + private PackageReference GetLatestDevBuild() + { + var packageList = _context.NuGetList(Id, new NuGetListSettings() + { + Prerelease = true, + Source = new [] { "https://www.myget.org/F/nunit/api/v3/index.json" } + } ); + + foreach (var package in packageList) + return new PackageReference(package.Name, package.Version); + + return this; + } + + private PackageReference GetLatestRelease() + { + var packageList = _context.NuGetList(Id, new NuGetListSettings() + { + Prerelease = true, + Source = new [] { + "https://www.nuget.org/api/v2/", + "https://community.chocolatey.org/api/v2/" } + } ); + + // TODO: There seems to be an error in NuGet or in Cake, causing the list to + // contain ALL NuGet packages, so we check the Id in this loop. + foreach (var package in packageList) + if (package.Name == Id) + return new PackageReference(Id, package.Version); + + return this; + } + + public bool IsInstalled(string installDirectory) + { + return _context.GetDirectories($"{installDirectory}{Id}.*").Count > 0; + } + + public void InstallExtension(PackageDefinition targetPackage) + { + Install(targetPackage.ExtensionInstallDirectory); + } + + public void Install(string installDirectory) + { + if (!IsInstalled(installDirectory)) + { + Banner.Display($"Installing {Id} version {Version}"); + + var packageSources = new [] + { + "https://www.myget.org/F/nunit/api/v3/index.json", + "https://api.nuget.org/v3/index.json", + "https://community.chocolatey.org/api/v2/" + }; + + Console.WriteLine("Package Sources:"); + foreach(var source in packageSources) + Console.WriteLine($" {source}"); + Console.WriteLine(); + + _context.NuGetInstall(Id, + new NuGetInstallSettings() + { + OutputDirectory = installDirectory, + Version = Version, + Source = packageSources + }); + } + } +} diff --git a/cake/package-test.cake b/cake/package-test.cake new file mode 100644 index 000000000..e80d160af --- /dev/null +++ b/cake/package-test.cake @@ -0,0 +1,34 @@ +// Representation of a single test to be run against a pre-built package. +// Each test has a Level, with the following values defined... +// 0 Do not run - used for temporarily disabling a test +// 1 Run for all CI tests - that is every time we test packages +// 2 Run only on PRs, dev builds and when publishing +// 3 Run only when publishing +public struct PackageTest +{ + public int Level; + public string Name; + public string Description; + public string Arguments; + public ExpectedResult ExpectedResult; + public ExtensionSpecifier[] ExtensionsNeeded; + + public PackageTest(int level, string name, string description, string arguments, ExpectedResult expectedResult, params ExtensionSpecifier[] extensionsNeeded) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (description == null) + throw new ArgumentNullException(nameof(description)); + if (arguments == null) + throw new ArgumentNullException(nameof(arguments)); + if (expectedResult == null) + throw new ArgumentNullException(nameof(expectedResult)); + + Level = level; + Name = name; + Description = description; + Arguments = arguments; + ExpectedResult = expectedResult; + ExtensionsNeeded = extensionsNeeded; + } +} diff --git a/cake/package-tester.cake b/cake/package-tester.cake deleted file mode 100644 index f3d2c3142..000000000 --- a/cake/package-tester.cake +++ /dev/null @@ -1,153 +0,0 @@ -/// -/// PackageTester knows how to run all the tests for a given package -/// -public class PackageTester -{ - private ICakeContext _context; - - private PackageType _packageType; - private string _packageName; - private string _installDirectory; - private string _resultDirectory; - private string _packageUnderTest; - private string _testExecutable; - private IEnumerable _packageTests; - - public PackageTester(ICakeContext context, PackageDefinition package) - { - _context = context; - - _packageType = package.PackageType; - _packageName = package.PackageName; - var subdir = $"{_packageType.ToString().ToLower()}/{package.PackageId}/"; - _installDirectory = PACKAGE_TEST_DIR + subdir; - _resultDirectory = PACKAGE_RESULT_DIR + subdir; - _packageUnderTest = PACKAGE_DIR + _packageName; - _testExecutable = package.TestExecutable; - _packageTests = package.PackageTests; - } - - public void RunTests() - { - DisplayBanner("Testing package " + _packageName); - - Console.WriteLine("Creating Test Directory..."); - CreatePackageInstallDirectory(); - - RunPackageTests(); - } - - public void CreatePackageInstallDirectory() - { - _context.CleanDirectory(_installDirectory); - - if (_packageType == PackageType.Msi) - { - // Msiexec does not tolerate forward slashes! - string package = _packageUnderTest.ToString().Replace("/", "\\"); - string testDir = _installDirectory.Replace("/", "\\"); - Console.WriteLine($"Installing msi to {testDir}"); - int rc = _context.StartProcess("msiexec", $"/a {package} TARGETDIR={testDir} /q"); - if (rc != 0) - Console.WriteLine($" ERROR: Installer returned {rc.ToString()}"); - else - { - var binDir = _installDirectory + "NUnit.org/nunit-console/"; - var dlls = _context.GetFiles(binDir + "*.dll"); - var pdbs = _context.GetFiles(binDir + "*.pdb"); - var filesToCopy = dlls.Concat(pdbs); - - // Administrative install is used to create a file image, from which - // users may do their own installls. For security reasons, we can't - // do a full install so we simulate the user portion of the install, - // copying certain files to their final destination. - Console.WriteLine("Copying agent files"); - _context.CopyFiles(filesToCopy, binDir + "agents/net20"); - _context.CopyFiles(filesToCopy, binDir + "agents/net40"); - } - } - else - { - Console.WriteLine($"Unzipping package to {_installDirectory}"); - _context.Unzip(_packageUnderTest, _installDirectory); - - if (_packageType == PackageType.NuGet || _packageType == PackageType.Chocolatey) - { - foreach (string packageDir in System.IO.Directory.GetDirectories(EXTENSIONS_DIR)) - { - string subdir = _packageType.ToString().ToLower(); - string packageName = System.IO.Path.GetFileName(packageDir); - string targetDir = $"{PACKAGE_TEST_DIR}{subdir}/{packageName}"; - - _context.CopyDirectory(packageDir, targetDir); - - if (_packageType == PackageType.Chocolatey) - RenamePackageForChocolatey(targetDir); - } - } - } - } - - private void RenamePackageForChocolatey(string nugetDir) - { - string chocoDir = nugetDir - .Replace("NUnit.Extension.NUnitProjectLoader", "nunit-extension-nunit-project-loader") - .Replace("NUnit.Extension.VSProjectLoader", "nunit-extension-vs-project-loader") - .Replace("NUnit.Extension.NUnitV2ResultWriter", "nunit-extension-v2-result-writer") - .Replace("NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver") - .Replace("NUnit.Extension.TeamCityEventListener", "nunit-extension-teamcity-event-listener"); - - if (chocoDir != nugetDir) - _context.MoveDirectory(nugetDir, chocoDir); - } - - private void RunPackageTests() - { - var reporter = new ResultReporter(_packageName); - - _context.CleanDirectory(_resultDirectory); - - foreach (var packageTest in _packageTests) - { - var testResultDir = _resultDirectory + packageTest.Name + "/"; - var resultFile = testResultDir + "TestResult.xml"; - - DisplayBanner(packageTest.Description); - - Console.WriteLine($"Running {_installDirectory + _testExecutable}"); - - var outputDir = System.IO.Path.GetFullPath( - $"bin/{Configuration}/"); - int rc = _context.StartProcess( - _installDirectory + _testExecutable, - new ProcessSettings() - { - Arguments = $"{packageTest.Arguments} --work={testResultDir}", - WorkingDirectory = outputDir - }); - - try - { - var result = new ActualResult(resultFile); - var report = new TestReport(packageTest, result); - reporter.AddReport(report); - - Console.WriteLine(report.Errors.Count == 0 - ? "\nSUCCESS: Test Result matches expected result!" - : "\nERROR: Test Result not as expected!"); - } - catch (Exception ex) - { - reporter.AddReport(new TestReport(packageTest, ex)); - - Console.WriteLine("\nERROR: No result found!"); - } - } - - bool hadErrors = reporter.ReportResults(); - Console.WriteLine(); - - if (hadErrors) - throw new Exception("One or more package tests had errors!"); - } -} diff --git a/cake/package-tests.cake b/cake/package-tests.cake deleted file mode 100644 index 7b76d14fe..000000000 --- a/cake/package-tests.cake +++ /dev/null @@ -1,140 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// INDIVIDUAL PACKAGE TEST DEFINITIONS -////////////////////////////////////////////////////////////////////// - -static ExpectedResult MockAssemblyExpectedResult(int nCopies = 1) => new ExpectedResult("Failed") -{ - Total = 37 * nCopies, - Passed = 23 * nCopies, - Failed = 5 * nCopies, - Warnings = 1 * nCopies, - Inconclusive = 1 * nCopies, - Skipped = 7 * nCopies -}; - -static PackageTest Net35Test = new PackageTest( - "Net35Test", - "Run mock-assembly.dll under .NET 3.5", - "net35/mock-assembly.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net35X86Test = new PackageTest( - "Net35X86Test", - "Run mock-assembly-x86.dll under .NET 3.5", - "net35/mock-assembly-x86.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net40Test = new PackageTest( - "Net40Test", - "Run mock-assembly.dll under .NET 4.x", - "net40/mock-assembly.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net40X86Test = new PackageTest( - "Net40X86Test", - "Run mock-assembly-x86.dll under .NET 4.x", - "net40/mock-assembly-x86.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net35PlusNet40Test = new PackageTest( - "Net35PlusNet40Test", - "Run both copies of mock-assembly together", - "net35/mock-assembly.dll net40/mock-assembly.dll", - MockAssemblyExpectedResult(2)); - -static PackageTest Net80Test = new PackageTest( - "Net80Test", - "Run mock-assembly.dll under .NET 8.0", - "net8.0/mock-assembly.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net80X86Test = new PackageTest( - "Net80X86Test", - "Run mock-assembly-x86.dll under .NET 8.0", - "net8.0/mock-assembly-x86.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net70Test = new PackageTest( - "Net70Test", - "Run mock-assembly.dll under .NET 7.0", - "net7.0/mock-assembly.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net70X86Test = new PackageTest( - "Net70X86Test", - "Run mock-assembly-x86.dll under .NET 7.0", - "net7.0/mock-assembly-x86.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net60Test = new PackageTest( - "Net60Test", - "Run mock-assembly.dll under .NET 6.0", - "net6.0/mock-assembly.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net60X86Test = new PackageTest( - "Net60X86Test", - "Run mock-assembly-x86.dll under .NET 6.0", - "net6.0/mock-assembly-x86.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net50Test = new PackageTest( - "Net50Test", - "Run mock-assembly.dll under .NET 5.0", - "net5.0/mock-assembly.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net50X86Test = new PackageTest( - "Net50X86Test", - "Run mock-assembly-x86.dll under .NET 5.0", - "net5.0/mock-assembly-x86.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest NetCore31Test = new PackageTest( - "NetCore31Test", - "Run mock-assembly.dll under .NET Core 3.1", - "netcoreapp3.1/mock-assembly.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest NetCore31X86Test = new PackageTest( - "NetCore31X86Test", - "Run mock-assembly-x86.dll under .NET Core 3.1", - "netcoreapp3.1/mock-assembly-x86.dll", - MockAssemblyExpectedResult(1)); - -static PackageTest Net50PlusNet60Test = new PackageTest( - "Net50PlusNet60Test", - "Run mock-assembly under .NET 5.0 and 6.0 together", - "net5.0/mock-assembly.dll net6.0/mock-assembly.dll",//" net7.0/mock-assembly.dll net8.0/mock-assembly.dll", - MockAssemblyExpectedResult(2)); - -static PackageTest Net40PlusNet60Test = new PackageTest( - "Net40PlusNet60Test", - "Run mock-assembly under .Net Framework 4.0 and .Net 6.0 together", - "net40/mock-assembly.dll net6.0/mock-assembly.dll", - MockAssemblyExpectedResult(2)); - -static PackageTest NUnitProjectTest; -NUnitProjectTest = new PackageTest( - "NUnitProjectTest", - "Run project with both copies of mock-assembly", - $"../../NetFXTests.nunit --config={Configuration}", - MockAssemblyExpectedResult(2)); - -// Representation of a single test to be run against a pre-built package. -public struct PackageTest -{ - public string Name; - public string Description; - public string Arguments; - public ExpectedResult ExpectedResult; - - public PackageTest(string name, string description, string arguments, ExpectedResult expectedResult) - { - Name = name; - Description = description; - Arguments = arguments; - ExpectedResult = expectedResult; - } -} - diff --git a/cake/publishing.cake b/cake/publishing.cake new file mode 100644 index 000000000..2981bd3d1 --- /dev/null +++ b/cake/publishing.cake @@ -0,0 +1,244 @@ +public static class PackageReleaseManager +{ + private static ICakeContext _context; + + static PackageReleaseManager() + { + _context = BuildSettings.Context; + } + + private static bool _hadErrors = false; + + public static void Publish() + { + _hadErrors = false; + + PublishToMyGet(); + PublishToNuGet(); + PublishToChocolatey(); + + if (_hadErrors) + throw new Exception("One of the publishing steps failed."); + } + + public static void PublishToMyGet() + { + if (!BuildSettings.ShouldPublishToMyGet) + _context.Information("Nothing to publish to MyGet from this run."); + else if (CommandLineOptions.NoPush) + _context.Information("NoPush option suppressing publication to MyGet"); + else + foreach (var package in BuildSettings.Packages) + { + var packageName = $"{package.PackageId}.{BuildSettings.PackageVersion}.nupkg"; + var packagePath = BuildSettings.PackageDirectory + packageName; + try + { + if (package.PackageType == PackageType.NuGet) + PushNuGetPackage(packagePath, BuildSettings.MyGetApiKey, BuildSettings.MyGetPushUrl); + else if (package.PackageType == PackageType.Chocolatey) + PushChocolateyPackage(packagePath, BuildSettings.MyGetApiKey, BuildSettings.MyGetPushUrl); + } + catch (Exception ex) + { + _context.Error(ex.Message); + _hadErrors = true; + } + } + } + + public static void PublishToNuGet() + { + if (!BuildSettings.ShouldPublishToNuGet) + _context.Information("Nothing to publish to NuGet from this run."); + else if (CommandLineOptions.NoPush) + _context.Information("NoPush option suppressing publication to NuGet"); + else + foreach (var package in BuildSettings.Packages) + { + var packageName = $"{package.PackageId}.{BuildSettings.PackageVersion}.nupkg"; + var packagePath = BuildSettings.PackageDirectory + packageName; + try + { + if (package.PackageType == PackageType.NuGet) + PushNuGetPackage(packagePath, BuildSettings.NuGetApiKey, BuildSettings.NuGetPushUrl); + } + catch (Exception ex) + { + _context.Error(ex.Message); + _hadErrors = true; + } + } + } + + public static void PublishToChocolatey() + { + if (!BuildSettings.ShouldPublishToChocolatey) + _context.Information("Nothing to publish to Chocolatey from this run."); + else if (CommandLineOptions.NoPush) + _context.Information("NoPush option suppressing publication to Chocolatey"); + else + foreach (var package in BuildSettings.Packages) + { + var packageName = $"{package.PackageId}.{BuildSettings.PackageVersion}.nupkg"; + var packagePath = BuildSettings.PackageDirectory + packageName; + try + { + if (package.PackageType == PackageType.Chocolatey) + PushChocolateyPackage(packagePath, BuildSettings.ChocolateyApiKey, BuildSettings.ChocolateyPushUrl); + } + catch (Exception ex) + { + _context.Error(ex.Message); + _hadErrors = true; + } + } + } + + private static void PushNuGetPackage(FilePath package, string apiKey, string url) + { + CheckPackageExists(package); + _context.NuGetPush(package, new NuGetPushSettings() { ApiKey = apiKey, Source = url }); + } + + private static void PushChocolateyPackage(FilePath package, string apiKey, string url) + { + CheckPackageExists(package); + _context.ChocolateyPush(package, new ChocolateyPushSettings() { ApiKey = apiKey, Source = url }); + } + + private static void CheckPackageExists(FilePath package) + { + if (!_context.FileExists(package)) + throw new InvalidOperationException( + $"Package not found: {package.GetFilename()}.\nCode may have changed since package was last built."); + } + + private const string DRAFT_RELEASE_ERROR = + "A direct call to CreateDraftRelease is permitted only:\r\n" + + " * On a release branch (release-x.x.x)\r\n" + + " * On the main branch tagged for a production release\r\n" + + " * Using option --packageVersion to specify a release version"; + + public static void CreateDraftRelease() + { + string releaseVersion = + CommandLineOptions.PackageVersion.Exists ? CommandLineOptions.PackageVersion.Value : + BuildSettings.IsReleaseBranch ? BuildSettings.BuildVersion.BranchName.Substring(8) : + BuildSettings.IsProductionRelease ? BuildSettings.PackageVersion : null; + + if (releaseVersion != null) + { + if (CommandLineOptions.NoPush) + _context.Information($"NoPush option skipping creation of draft release for version {releaseVersion}"); + else + { + string releaseName = $"{BuildSettings.Title} {releaseVersion}"; + _context.Information($"Creating draft release for {releaseName}"); + + try + { + _context.GitReleaseManagerCreate(BuildSettings.GitHubAccessToken, BuildSettings.GitHubOwner, BuildSettings.GitHubRepository, new GitReleaseManagerCreateSettings() + { + Name = releaseName, + Milestone = releaseVersion + }); + } + catch + { + _context.Error($"Unable to create draft release for {releaseName}."); + _context.Error($"Check that there is a {releaseVersion} milestone with at least one closed issue."); + _context.Error(""); + throw; + } + } + } + else + { + bool calledDirectly = CommandLineOptions.Target.Value == "CreateDraftRelease"; + if (calledDirectly) + throw new InvalidOperationException(DRAFT_RELEASE_ERROR); + else + _context.Information("Skipping creation of draft release because this is not a release branch"); + } + } + + private const string UPDATE_RELEASE_ERROR = + "A direct call to UpdateReleaseNotes is permitted only:\r\n" + + " * On the main branch tagged for a production release\r\n" + + " * Using option --packageVersion to specify a release version"; + + public static void UpdateReleaseNotes() + { + string releaseVersion = + CommandLineOptions.PackageVersion.Exists ? CommandLineOptions.PackageVersion.Value : + BuildSettings.IsProductionRelease ? BuildSettings.PackageVersion : null; + + if (releaseVersion == null) + throw new InvalidOperationException(UPDATE_RELEASE_ERROR); + + if (CommandLineOptions.NoPush) + _context.Information($"NoPush option skipping update of release notes for version {releaseVersion}"); + else + { + string releaseName = $"{BuildSettings.Title} {releaseVersion}"; + _context.Information($"Updating release notes for {releaseName}"); + + try + { + _context.GitReleaseManagerCreate(BuildSettings.GitHubAccessToken, BuildSettings.GitHubOwner, BuildSettings.GitHubRepository, new GitReleaseManagerCreateSettings() + { + Name = releaseName, + Milestone = releaseVersion + }); + } + catch + { + _context.Error($"Unable to update release notes for {releaseName}."); + _context.Error($"Check that there is a {releaseVersion} milestone with a matching release."); + _context.Error(""); + throw; + } + } + + } + + public static void DownloadDraftRelease() + { + if (!BuildSettings.IsReleaseBranch) + throw new Exception("DownloadDraftRelease requires a release branch!"); + + string milestone = BuildSettings.BranchName.Substring(8); + + _context.GitReleaseManagerExport(BuildSettings.GitHubAccessToken, BuildSettings.GitHubOwner, BuildSettings.GitHubRepository, "DraftRelease.md", + new GitReleaseManagerExportSettings() { TagName = milestone }); + } + + public static void CreateProductionRelease() + { + if (!BuildSettings.IsProductionRelease) + { + _context.Information("Skipping CreateProductionRelease because this is not a production release"); + } + else if (CommandLineOptions.NoPush) + _context.Information($"NoPush option skipping creation of production release for version {BuildSettings.PackageVersion}"); + else + { + string token = BuildSettings.GitHubAccessToken; + string owner = BuildSettings.GitHubOwner; + string repository = BuildSettings.GitHubRepository; + string tagName = BuildSettings.PackageVersion; + string assets = string.Join(',', BuildSettings.Packages.Select(p => p.PackageFilePath)); + + //IsRunningOnWindows() + // ? $"\"{BuildSettings.NuGetPackage},{BuildSettings.ChocolateyPackage}\"" + // : $"\"{BuildSettings.NuGetPackage}\""; + + _context.Information($"Publishing release {tagName} to GitHub"); + _context.Information($" Assets: {assets}"); + + _context.GitReleaseManagerAddAssets(token, owner, repository, tagName, assets); + _context.GitReleaseManagerClose(token, owner, repository, tagName); + } + } +} diff --git a/cake/setup.cake b/cake/setup.cake new file mode 100644 index 000000000..c9eec7ab3 --- /dev/null +++ b/cake/setup.cake @@ -0,0 +1,51 @@ +Setup((context) => +{ + var target = context.TargetTask.Name; + var tasksToExecute = context.TasksToExecute.Select(t => t.Name); + + // Ensure that BuildSettings have been initialized + if (BuildSettings.Context == null) + throw new Exception("BuildSettings have not been initialized. Call BuildSettings.Initialize() from your build.cake script."); + + // Ensure Api Keys and tokens are available if needed for tasks to be executed + + // MyGet Api Key + bool needMyGetApiKey = tasksToExecute.Contains("PublishToMyGet") && BuildSettings.ShouldPublishToMyGet && !CommandLineOptions.NoPush; + if (needMyGetApiKey && string.IsNullOrEmpty(BuildSettings.MyGetApiKey)) + DisplayErrorAndThrow("MyGet ApiKey is required but was not set."); + + // NuGet Api Key + bool needNuGetApiKey = tasksToExecute.Contains("PublishToNuGet") && BuildSettings.ShouldPublishToNuGet && !CommandLineOptions.NoPush; + if (needNuGetApiKey && string.IsNullOrEmpty(BuildSettings.NuGetApiKey)) + DisplayErrorAndThrow("NuGet ApiKey is required but was not set."); + + // Chocolatey Api Key + bool needChocolateyApiKey = tasksToExecute.Contains("PublishToChocolatey") && BuildSettings.ShouldPublishToChocolatey && !CommandLineOptions.NoPush; + if (needChocolateyApiKey && string.IsNullOrEmpty(BuildSettings.ChocolateyApiKey)) + DisplayErrorAndThrow("Chocolatey ApiKey is required but was not set."); + + // GitHub Access Token, Owner and Repository + if (!CommandLineOptions.NoPush) + if (tasksToExecute.Contains("CreateDraftRelease") && BuildSettings.IsReleaseBranch || + tasksToExecute.Contains("CreateProductionRelease") && BuildSettings.IsProductionRelease) + { + if (string.IsNullOrEmpty(BuildSettings.GitHubAccessToken)) + DisplayErrorAndThrow("GitHub Access Token is required but was not set."); + if (string.IsNullOrEmpty(BuildSettings.GitHubOwner)) + DisplayErrorAndThrow("GitHub Owner is required but was not set."); + if (string.IsNullOrEmpty(BuildSettings.GitHubRepository)) + DisplayErrorAndThrow("GitHub Repository is required but was not set."); + } + + // Add settings to BuildSettings + BuildSettings.Target = target; + BuildSettings.TasksToExecute = tasksToExecute; + + void DisplayErrorAndThrow(string message) + { + message += $"\r\n Tasks: {string.Join(", ", tasksToExecute)}"; + + context.Error(message); + throw new Exception(message); + } +}); diff --git a/cake/task-builders.cake b/cake/task-builders.cake new file mode 100644 index 000000000..4c14345bf --- /dev/null +++ b/cake/task-builders.cake @@ -0,0 +1,48 @@ +// All tasks incorporated in the recipe are defined using CakeTaskBuilders. +// The actual specification of criteria, dependencies and actions for each +// task is done separately in task-definitions.cake. +// +// This approach provides a level of indirection, permitting the user to +// modify or completely redefine what a task does in their build.cake file, +// without changing the definitions in the recipe. + +public static class BuildTasks +{ + // General + public static CakeTaskBuilder DumpSettingsTask { get; set; } + public static CakeTaskBuilder DefaultTask {get; set; } + + // Building + public static CakeTaskBuilder BuildTask { get; set; } + public static CakeTaskBuilder CheckHeadersTask { get; set; } + public static CakeTaskBuilder CleanTask { get; set; } + public static CakeTaskBuilder CleanAllTask { get; set; } + public static CakeTaskBuilder RestoreTask { get; set; } + + // Unit Testing + public static CakeTaskBuilder UnitTestTask { get; set; } + + // Packaging + public static CakeTaskBuilder PackageTask { get; set; } + public static CakeTaskBuilder BuildTestAndPackageTask { get; set; } + //public static CakeTaskBuilder PackageBuildTask { get; set; } + //public static CakeTaskBuilder PackageInstallTask { get; set; } + //public static CakeTaskBuilder PackageVerifyTask { get; set; } + //public static CakeTaskBuilder PackageTestTask { get; set; } + + // Publishing + public static CakeTaskBuilder PublishTask { get; set; } + public static CakeTaskBuilder PublishToMyGetTask { get; set; } + public static CakeTaskBuilder PublishToNuGetTask { get; set; } + public static CakeTaskBuilder PublishToChocolateyTask { get; set; } + + // Releasing + public static CakeTaskBuilder CreateDraftReleaseTask { get; set; } + //public static CakeTaskBuilder DownloadDraftReleaseTask { get; set; } + //public static CakeTaskBuilder UpdateReleaseNotesTask { get; set; } + public static CakeTaskBuilder CreateProductionReleaseTask { get; set; } + + // Continuous Integration + public static CakeTaskBuilder ContinuousIntegrationTask { get; set; } + public static CakeTaskBuilder AppveyorTask { get; set; } +} diff --git a/cake/task-definitions.cake b/cake/task-definitions.cake new file mode 100644 index 000000000..69233603c --- /dev/null +++ b/cake/task-definitions.cake @@ -0,0 +1,134 @@ +// This file defines what each of the tasks in the recipe actually does. +// You should not change these definitions unless you intend to change +// the behavior of a task for all projects that use the recipe. +// +// To make a change for a single project, you should add code to your build.cake +// or another project-specific cake file. See extending.cake for examples. + +BuildTasks.DefaultTask = Task("Default") + .Description("Default task if none specified by user") + .IsDependentOn("Build"); + +BuildTasks.DumpSettingsTask = Task("DumpSettings") + .Description("Display BuildSettings properties") + .Does(() => BuildSettings.DumpSettings()); + +BuildTasks.CheckHeadersTask = Task("CheckHeaders") + .Description("Check source files for valid copyright headers") + .WithCriteria(() => !CommandLineOptions.NoBuild) + .WithCriteria(() => !BuildSettings.SuppressHeaderCheck) + .Does(() => Headers.Check()); + +BuildTasks.CleanTask = Task("Clean") + .Description("Clean output and package directories") + .WithCriteria(() => !CommandLineOptions.NoBuild) + .Does(() => + { + foreach (var binDir in GetDirectories($"**/bin/{BuildSettings.Configuration}/")) + CleanDirectory(binDir); + + CleanDirectory(BuildSettings.PackageDirectory); + CleanDirectory(BuildSettings.ImageDirectory); + CleanDirectory(BuildSettings.ExtensionsDirectory); + + DeleteFiles(BuildSettings.ProjectDirectory + "*.log"); + }); + +BuildTasks.CleanAllTask = Task("CleanAll") + .Description("Clean everything!") + .Does(() => + { + foreach (var binDir in GetDirectories("**/bin/")) + CleanDirectory(binDir); + + CleanDirectory(BuildSettings.PackageDirectory); + CleanDirectory(BuildSettings.ImageDirectory); + CleanDirectory(BuildSettings.ExtensionsDirectory); + + DeleteFiles(BuildSettings.ProjectDirectory + "*.log"); + + foreach (var dir in GetDirectories("src/**/obj/")) + DeleteDirectory(dir, new DeleteDirectorySettings() { Recursive = true }); + }); + +BuildTasks.RestoreTask = Task("Restore") + .Description("Restore referenced packages") + .WithCriteria(() => BuildSettings.SolutionFile != null) + .WithCriteria(() => !CommandLineOptions.NoBuild) + .Does(() => { + NuGetRestore(BuildSettings.SolutionFile, new NuGetRestoreSettings() { + Source = new string[] { + "https://www.nuget.org/api/v2", + "https://www.myget.org/F/nunit/api/v2" } + }); + }); + +BuildTasks.BuildTask = Task("Build") + .WithCriteria(() => BuildSettings.SolutionFile != null) + .WithCriteria(() => !CommandLineOptions.NoBuild) + .IsDependentOn("Clean") + .IsDependentOn("Restore") + .IsDependentOn("CheckHeaders") + .Description("Build the solution") + .Does(() => { + MSBuild(BuildSettings.SolutionFile, BuildSettings.MSBuildSettings.WithProperty("Version", BuildSettings.PackageVersion)); + }); + +BuildTasks.UnitTestTask = Task("Test") + .Description("Run unit tests") + .IsDependentOn("Build") + .Does(() => UnitTesting.RunAllTests()); + +BuildTasks.PackageTask = Task("Package") + .IsDependentOn("Build") + .Description("Build, Install, Verify and Test all packages") + .Does(() => { + var selector = CommandLineOptions.PackageSelector; + foreach(var package in BuildSettings.Packages) + if (!selector.Exists || package.IsSelectedBy(selector.Value)) + package.BuildVerifyAndTest(); + }); + +BuildTasks.BuildTestAndPackageTask = Task("BuildTestAndPackage") + .Description("Do Build, Test and Package all in one run") + .IsDependentOn("Build") + .IsDependentOn("Test") + .IsDependentOn("Package"); + +BuildTasks.PublishTask = Task("Publish") + .Description("Publish all packages for current branch") + .IsDependentOn("Package") + .Does(() => PackageReleaseManager.Publish()); + +BuildTasks.PublishToMyGetTask = Task("PublishToMyGet") + .Description("Publish packages to MyGet") + .Does(() => PackageReleaseManager.PublishToMyGet() ); + +BuildTasks.PublishToNuGetTask = Task("PublishToNuGet") + .Description("Publish packages to NuGet") + .Does(() => PackageReleaseManager.PublishToNuGet() ); + +BuildTasks.PublishToChocolateyTask = Task("PublishToChocolatey") + .Description("Publish packages to Chocolatey") + .Does(() => PackageReleaseManager.PublishToChocolatey() ); + +BuildTasks.CreateDraftReleaseTask = Task("CreateDraftRelease") + .Description("Create a draft release on GitHub") + .Does(() => PackageReleaseManager.CreateDraftRelease() ); + +BuildTasks.CreateProductionReleaseTask = Task("CreateProductionRelease") + .Description("Create a production GitHub Release") + .Does(() => PackageReleaseManager.CreateProductionRelease() ); + +BuildTasks.ContinuousIntegrationTask = Task("ContinuousIntegration") + .Description("Perform continuous integration run") + .IsDependentOn("Build") + .IsDependentOn("Test") + .IsDependentOn("Package") + .IsDependentOn("Publish") + .IsDependentOn("CreateDraftRelease") + .IsDependentOn("CreateProductionRelease"); + +BuildTasks.AppveyorTask = Task("Appveyor") + .Description("Target for running on AppVeyor") + .IsDependentOn("ContinuousIntegration"); diff --git a/cake/test-reports.cake b/cake/test-reports.cake new file mode 100644 index 000000000..9faf03d3d --- /dev/null +++ b/cake/test-reports.cake @@ -0,0 +1,171 @@ +public class PackageTestReport +{ + public PackageTest Test; + public ActualResult Result; + public string ConsoleVersion; + public List Errors; + public List Warnings; + + public PackageTestReport(PackageTest test, ActualResult actualResult, string consoleVersion = null) + { + Test = test; + Result = actualResult; + ConsoleVersion = consoleVersion; + Errors = new List(); + Warnings = new List(); + + var expectedResult = test.ExpectedResult; + + ReportMissingFiles(); + + if (actualResult.OverallResult == null) + Errors.Add(" The test-run element has no result attribute."); + else if (expectedResult.OverallResult != actualResult.OverallResult) + Errors.Add($" Expected: Overall Result = {expectedResult.OverallResult} But was: {actualResult.OverallResult}"); + CheckCounter("Test Count", expectedResult.Total, actualResult.Total); + CheckCounter("Passed", expectedResult.Passed, actualResult.Passed); + CheckCounter("Failed", expectedResult.Failed, actualResult.Failed); + CheckCounter("Warnings", expectedResult.Warnings, actualResult.Warnings); + CheckCounter("Inconclusive", expectedResult.Inconclusive, actualResult.Inconclusive); + CheckCounter("Skipped", expectedResult.Skipped, actualResult.Skipped); + + var expectedAssemblies = expectedResult.Assemblies; + var actualAssemblies = actualResult.Assemblies; + + for (int i = 0; i < expectedAssemblies.Length && i < actualAssemblies.Length; i++) + { + var expected = expectedAssemblies[i]; + var actual = actualAssemblies[i]; + + if (expected.AssemblyName != actual.AssemblyName) + Errors.Add($" Expected: {expected.AssemblyName} But was: { actual.AssemblyName}"); + else if (consoleVersion == null || !consoleVersion.StartsWith("NetCore.")) + { + if (actual.Runtime == null) + Warnings.Add($"Unable to determine actual runtime used for {expected.AssemblyName}"); + else if (expected.Runtime != actual.Runtime) + Errors.Add($" Assembly {actual.AssemblyName} Expected: {expected.Runtime} But was: {actual.Runtime}"); + } + } + + for (int i = actualAssemblies.Length; i < expectedAssemblies.Length; i++) + Errors.Add($" Assembly {expectedAssemblies[i].AssemblyName} was not found"); + + for (int i = expectedAssemblies.Length; i < actualAssemblies.Length; i++) + Errors.Add($" Found unexpected assembly {actualAssemblies[i].AssemblyName}"); + } + + public PackageTestReport(PackageTest test, Exception ex, string consoleVersion = null) + { + Test = test; + Result = null; + Errors = new List(); + Errors.Add($" {ex.Message}"); + ConsoleVersion = consoleVersion; + } + + public void Display(int index, TextWriter writer) + { + writer.WriteLine(); + writer.WriteLine($"{index}. {Test.Description}"); + if (ConsoleVersion != null) + writer.WriteLine($" ConsoleVersion: {ConsoleVersion}"); + writer.WriteLine($" Args: {Test.Arguments}"); + writer.WriteLine(); + + foreach (var error in Errors) + writer.WriteLine(error); + + if (Errors.Count == 0) + { + writer.WriteLine(" SUCCESS: Test Result matches expected result!"); + } + else + { + writer.WriteLine(); + writer.WriteLine(" ERROR: Test Result not as expected!"); + } + + foreach (var warning in Warnings) + writer.WriteLine(" WARNING: " + warning); + } + + // File level errors, like missing or mal-formatted files, need to be highlighted + // because otherwise it's hard to detect the cause of the problem without debugging. + // This method finds and reports that type of error. + private void ReportMissingFiles() + { + // Start with all the top-level test suites. Note that files that + // cannot be found show up as Unknown as do unsupported file types. + var suites = Result.Xml.SelectNodes( + "//test-suite[@type='Unknown'] | //test-suite[@type='Project'] | //test-suite[@type='Assembly']"); + + // If there is no top-level suite, it generally means the file format could not be interpreted + if (suites.Count == 0) + Errors.Add(" No top-level suites! Possible empty command-line or misformed project."); + + foreach (XmlNode suite in suites) + { + // Narrow down to the specific failures we want + string runState = GetAttribute(suite, "runstate"); + string suiteResult = GetAttribute(suite, "result"); + string label = GetAttribute(suite, "label"); + string site = suite.Attributes["site"]?.Value ?? "Test"; + if (runState == "NotRunnable" || suiteResult == "Failed" && site == "Test" && (label == "Invalid" || label == "Error")) + { + string message = suite.SelectSingleNode("reason/message")?.InnerText; + Errors.Add($" {message}"); + } + } + } + + private void CheckCounter(string label, int expected, int actual) + { + // If expected value of counter is negative, it means no check is needed + if (expected >= 0 && expected != actual) + Errors.Add($" Expected: {label} = {expected} But was: {actual}"); + } + + private string GetAttribute(XmlNode node, string name) + { + return node.Attributes[name]?.Value; + } +} + +public class ResultReporter +{ + private string _packageName; + private List _reports = new List(); + + public ResultReporter(string packageName) + { + _packageName = packageName; + } + + public void AddReport(PackageTestReport report) + { + _reports.Add(report); + } + + public bool ReportResults(TextWriter writer) + { + writer.WriteLine("\n=================================================="); ; + writer.WriteLine($"Test Results for {_packageName}"); + writer.WriteLine("=================================================="); ; + + writer.WriteLine("\nTest Environment"); + writer.WriteLine($" OS Version: {Environment.OSVersion.VersionString}"); + writer.WriteLine($" CLR Version: {Environment.Version}\n"); + + int index = 0; + bool hasErrors = false; + + foreach (var report in _reports) + { + hasErrors |= report.Errors.Count > 0; + report.Display(++index, writer); + } + + return hasErrors; + } +} diff --git a/cake/test-results.cake b/cake/test-results.cake index dc34081ac..41ba05397 100644 --- a/cake/test-results.cake +++ b/cake/test-results.cake @@ -5,188 +5,164 @@ using System.Xml; public abstract class ResultSummary { - public string OverallResult { get; set; } - public int Total { get; set; } - public int Passed { get; set; } - public int Failed { get; set; } - public int Warnings { get; set; } - public int Inconclusive { get; set; } - public int Skipped { get; set; } + public string OverallResult { get; set; } + public int Total { get; set; } + public int Passed { get; set; } + public int Failed { get; set; } + public int Warnings { get; set; } + public int Inconclusive { get; set; } + public int Skipped { get; set; } } public class ExpectedResult : ResultSummary { public ExpectedResult(string overallResult) { - if (string.IsNullOrEmpty(overallResult)) - throw new ArgumentNullException(nameof(overallResult)); + if (string.IsNullOrEmpty(overallResult)) + throw new ArgumentNullException(nameof(overallResult)); OverallResult = overallResult; + // Initialize counters to -1, indicating no expected value. // Set properties of those items to be checked. Total = Passed = Failed = Warnings = Inconclusive = Skipped = -1; } + + public ExpectedAssemblyResult[] Assemblies { get; set; } = new ExpectedAssemblyResult[0]; +} + +public class ExpectedAssemblyResult +{ + public ExpectedAssemblyResult(string name, string expectedRuntime = null) + { + AssemblyName = name; + Runtime = expectedRuntime; + } + + public string AssemblyName { get; } + public string Runtime { get; } } public class ActualResult : ResultSummary { - public ActualResult(string resultFile) - { - var doc = new XmlDocument(); - doc.Load(resultFile); + public ActualResult(string resultFile) + { + var doc = new XmlDocument(); + doc.Load(resultFile); - Xml = doc.DocumentElement; - if (Xml.Name != "test-run") - throw new Exception("The test-run element was not found."); + Xml = doc.DocumentElement; + if (Xml.Name != "test-run") + throw new Exception("The test-run element was not found."); - OverallResult = GetAttribute(Xml, "result"); - Total = IntAttribute(Xml, "total"); - Passed = IntAttribute(Xml, "passed"); - Failed = IntAttribute(Xml, "failed"); - Warnings = IntAttribute(Xml, "warnings"); - Inconclusive = IntAttribute(Xml, "inconclusive"); - Skipped = IntAttribute(Xml, "skipped"); - } + OverallResult = GetAttribute(Xml, "result"); + Total = IntAttribute(Xml, "total"); + Passed = IntAttribute(Xml, "passed"); + Failed = IntAttribute(Xml, "failed"); + Warnings = IntAttribute(Xml, "warnings"); + Inconclusive = IntAttribute(Xml, "inconclusive"); + Skipped = IntAttribute(Xml, "skipped"); - public XmlNode Xml { get; } + var assemblies = new List(); + //var engineVersion = new Version(GetAttribute(Xml, "engine-version")); - private string GetAttribute(XmlNode node, string name) - { - return node.Attributes[name]?.Value; - } + foreach (XmlNode node in Xml.SelectNodes("//test-suite[@type='Assembly']")) + assemblies.Add(new ActualAssemblyResult(node)); - private int IntAttribute(XmlNode node, string name) - { - string s = GetAttribute(node, name); - // TODO: We should replace 0 with -1, representing a missing counter - // attribute, after issue #904 is fixed. - return s == null ? 0 : int.Parse(s); - } + //foreach (XmlNode node in Xml.SelectNodes("//test-suite[@type='Assembly']")) + // assemblies.Add(new ActualAssemblyResult(node, engineVersion)); + + Assemblies = assemblies.ToArray(); + } + + public XmlNode Xml { get; } + + public ActualAssemblyResult[] Assemblies { get; } + + private string GetAttribute(XmlNode node, string name) + { + return node.Attributes[name]?.Value; + } + + private int IntAttribute(XmlNode node, string name) + { + string s = GetAttribute(node, name); + // TODO: We should replace 0 with -1, representing a missing counter + // attribute, after issue #707 is fixed. + return s == null ? 0 : int.Parse(s); + } } -public class TestReport +public class ActualAssemblyResult { - public PackageTest Test; - public ActualResult Result; - public List Errors; - - public TestReport(PackageTest test, ActualResult result) + public ActualAssemblyResult(XmlNode xml) { - Test = test; - Result = result; - Errors = new List(); - - var expected = test.ExpectedResult; - - ReportMissingFiles(); - - if (result.OverallResult == null) - Errors.Add(" The test-run element has no result attribute."); - else if (expected.OverallResult != result.OverallResult) - Errors.Add($" Expected: Overall Result = {expected.OverallResult}\n But was: {result.OverallResult}"); - CheckCounter("Test Count", expected.Total, result.Total); - CheckCounter("Passed", expected.Passed, result.Passed); - CheckCounter("Failed", expected.Failed, result.Failed); - CheckCounter("Warnings", expected.Warnings, result.Warnings); - CheckCounter("Inconclusive", expected.Inconclusive, result.Inconclusive); - CheckCounter("Skipped", expected.Skipped, result.Skipped); - } - - public TestReport(PackageTest test, Exception ex) - { - Test = test; - Result = null; - Errors = new List(); - Errors.Add($" {ex.Message}"); - } + AssemblyName = xml.Attributes["name"]?.Value; - public void Display(int index) - { - Console.WriteLine($"\n{index}. {Test.Description}"); - Console.WriteLine($" Args: {Test.Arguments}\n"); + //var env = xml.SelectSingleNode("environment"); + var settings = xml.SelectSingleNode("settings"); - foreach (var error in Errors) - Console.WriteLine(error); + // If TargetRuntimeFramework setting is not present, the Runner will probably crash + var runtimeSetting = settings?.SelectSingleNode("setting[@name='TargetRuntimeFramework']"); + Runtime = runtimeSetting?.Attributes["value"]?.Value; - Console.WriteLine(Errors.Count == 0 - ? " SUCCESS: Test Result matches expected result!" - : "\n ERROR: Test Result not as expected!"); - } + var agentSetting = settings?.SelectSingleNode("setting[@name='SelectedAgentName']"); + AgentName = agentSetting?.Attributes["value"]?.Value; + } - // File level errors, like missing or mal-formatted files, need to be highlighted - // because otherwise it's hard to detect the cause of the problem without debugging. - // This method finds and reports that type of error. - private void ReportMissingFiles() - { - // Start with all the top-level test suites. Note that files that - // cannot be found show up as Unknown as do unsupported file types. - var suites = Result.Xml.SelectNodes( - "//test-suite[@type='Unknown'] | //test-suite[@type='Project'] | //test-suite[@type='Assembly']"); - - // If there is no top-level suite, it generally means the file format could not be interpreted - if (suites.Count == 0) - Errors.Add(" No top-level suites! Possible empty command-line or misformed project."); - - foreach (XmlNode suite in suites) - { - // Narrow down to the specific failures we want - string runState = GetAttribute(suite, "runstate"); - string suiteResult = GetAttribute(suite, "result"); - string label = GetAttribute(suite, "label"); - string site = suite.Attributes["site"]?.Value ?? "Test"; - if (runState == "NotRunnable" || suiteResult == "Failed" && site == "Test" && (label == "Invalid" || label=="Error")) - { - string message = suite.SelectSingleNode("reason/message")?.InnerText; - Errors.Add($" {message}"); - } - } - } - - private void CheckCounter(string label, int expected, int actual) - { - // If expected value of counter is negative, it means no check is needed - if (expected >=0 && expected != actual) - Errors.Add($" Expected: {label} = {expected}\n But was: {actual}"); - } + public string AssemblyName { get; } + public string AgentName { get; } - private string GetAttribute(XmlNode node, string name) - { - return node.Attributes[name]?.Value; - } + public string Runtime { get; } } -public class ResultReporter +#if false +public class ActualAssemblyResult { - private string _packageName; - private List _reports = new List(); - - public ResultReporter(string packageName) + public ActualAssemblyResult(XmlNode xml, Version engineVersion) { - _packageName = packageName; - } + Name = xml.Attributes["name"]?.Value; - public void AddReport(TestReport report) - { - _reports.Add(report); - } + var env = xml.SelectSingleNode("environment"); + var settings = xml.SelectSingleNode("settings"); - public bool ReportResults() - { - DisplayBanner($"Test Results for {_packageName}"); + // If TargetRuntimeFramework setting is not present, the GUI will have crashed anyway + var runtimeSetting = settings.SelectSingleNode("setting[@name='ImageTargetFrameworkName']"); + TargetRuntime = runtimeSetting?.Attributes["value"]?.Value; - Console.WriteLine("\nTest Environment"); - Console.WriteLine($" OS Version: {Environment.OSVersion.VersionString}"); - Console.WriteLine($" CLR Version: {Environment.Version}\n"); + // Engine 3.10 and earlier doesn't provide enough info + if (engineVersion >= new Version(3,11,0,0)) + Runtime = DeduceActualRuntime(xml); + } - int index = 0; - bool hasErrors = false; + public string Name { get; } + public string Runtime { get; } - foreach (var report in _reports) - { - hasErrors |= report.Errors.Count > 0; - report.Display(++index); - } + public string TargetRuntime { get; } - return hasErrors; - } + // Code to determine the runtime actually used is adhoc + // and doesn't work for all assemblies because the needed + // information may not be present in the result file. + // TODO: Modify result file schema so this can be cleaner + private static string DeduceActualRuntime(XmlNode assembly) + { + var env = assembly.SelectSingleNode("environment"); + // The TargetRuntimeFramework setting is only present + // under the 3.12 and later versions of the engine. + var runtimeSetting = assembly.SelectSingleNode("settings/setting[@name='TargetRuntimeFramework']"); + var targetRuntime = runtimeSetting?.Attributes["value"]?.Value; + if (targetRuntime != null) + return targetRuntime; + + var clrVersion = env.Attributes["clr-version"]?.Value; + if (clrVersion == null) + return null; + + if (clrVersion.StartsWith("2.0")) + return "net-2.0"; + if (clrVersion.StartsWith("4.0")) + return "net-4"; + + return null; + } } +#endif diff --git a/cake/test-runners.cake b/cake/test-runners.cake new file mode 100644 index 000000000..d876d9c39 --- /dev/null +++ b/cake/test-runners.cake @@ -0,0 +1,117 @@ +/// +/// The TestRunner class is the abstract base for all TestRunners used to run unit- +/// or package-tests. A TestRunner knows how to run a test assembly and provide a result. +/// +public abstract class TestRunner +{ + public virtual bool RequiresInstallation => false; + + protected FilePath ExecutablePath { get; set; } + + // Base install does nothing + public virtual void Install() { } +} + +public abstract class UnitTestRunner : TestRunner +{ + // Unit tests are run by providing the path to a test assembly. + public virtual int Run(FilePath testAssembly) + { + if (ExecutablePath == null) + throw new InvalidOperationException("Unable to run tests. Executable path has not been set."); + + var processSettings = new ProcessSettings() { + WorkingDirectory = BuildSettings.OutputDirectory, + // HACK: Equality indicates we are running under NUnitLite + Arguments = ExecutablePath == testAssembly + ? BuildSettings.UnitTestArguments + : $"{testAssembly} {BuildSettings.UnitTestArguments}" }; + + if (ExecutablePath.GetExtension() == ".dll") + return BuildSettings.Context.StartProcess("dotnet", processSettings); + else + return BuildSettings.Context.StartProcess(ExecutablePath,processSettings); + } +} + +public abstract class PackageTestRunner : TestRunner +{ + // Package Tests are run by providing the arguments, which + // will include one or more test assemblies. + public virtual int Run(string arguments=null) + { + if (ExecutablePath == null) + throw new InvalidOperationException("Unable to run tests. Executable path has not been set."); + + var processSettings = new ProcessSettings() { WorkingDirectory = BuildSettings.OutputDirectory }; + + if (ExecutablePath.GetExtension() == ".dll") + { + processSettings.Arguments = $"{ExecutablePath} {arguments}"; + return BuildSettings.Context.StartProcess("dotnet", processSettings); + } + else + { + processSettings.Arguments = arguments; + return BuildSettings.Context.StartProcess(ExecutablePath, processSettings); + } + } +} + +/// +/// The InstallableTestRunner class is the abstract base for TestRunners which +/// must be installed using a published package before they can be used. +/// +public abstract class InstallableTestRunner : TestRunner +{ + public override bool RequiresInstallation => true; + + public InstallableTestRunner(string packageId, string version) + { + if (packageId == null) + throw new ArgumentNullException(nameof(packageId)); + if (version == null) + throw new ArgumentNullException(nameof(version)); + + PackageId = packageId; + Version = version; + } + + public string PackageId { get; } + public string Version { get; } + + public abstract string InstallPath { get; } +} + +public class NUnitLiteRunner : UnitTestRunner +{ + public override int Run(FilePath testPath) + { + ExecutablePath = testPath; + return base.Run(testPath); + } +} + +/// +/// Class that knows how to run an agent directly. +/// +public class AgentRunner : PackageTestRunner +{ + private string _stdExecutable; + private string _x86Executable; + + public AgentRunner(string stdExecutable, string x86Executable = null) + { + _stdExecutable = stdExecutable; + _x86Executable = x86Executable; + } + + public override int Run(string arguments) + { + ExecutablePath = arguments.Contains("--x86") + ? _x86Executable + : _stdExecutable; + + return base.Run(arguments.Replace("--x86", string.Empty)); + } +} diff --git a/cake/tools.cake b/cake/tools.cake new file mode 100644 index 000000000..749e35817 --- /dev/null +++ b/cake/tools.cake @@ -0,0 +1,4 @@ +// Load all tools used by the recipe +#tool NuGet.CommandLine&version=6.9.1 +#tool dotnet:?package=GitVersion.Tool&version=5.12.0 +#tool dotnet:?package=GitReleaseManager.Tool&version=0.17.0 diff --git a/cake/unit-testing.cake b/cake/unit-testing.cake new file mode 100644 index 000000000..0d0f2ef7b --- /dev/null +++ b/cake/unit-testing.cake @@ -0,0 +1,81 @@ +////////////////////////////////////////////////////////////////////// +// UNIT TEST RUNNER +////////////////////////////////////////////////////////////////////// + +public static class UnitTesting +{ + static ICakeContext _context; + + static UnitTesting() + { + _context = BuildSettings.Context; + } + + public static void RunAllTests() + { + var unitTests = FindUnitTestFiles(BuildSettings.UnitTests); + + _context.Information($"Located {unitTests.Count} unit test assemblies."); + + foreach (var testPath in unitTests) + RunTest(testPath); + } + + private static void RunTest(FilePath testPath) + { + var testFile = testPath.GetFilename(); + var containingDir = testPath.GetDirectory().GetDirectoryName(); + var msg = "Running " + testFile; + if (IsValidRuntime(containingDir)) + msg += " under " + containingDir; + + Banner.Display(msg); + + var runner = BuildSettings.UnitTestRunner ?? new NUnitLiteRunner(); + runner.Run(testPath); + + static bool IsValidRuntime(string text) + { + string[] VALID_RUNTIMES = { + "net20", "net30", "net35", "net40", "net45", "net451", "net451", + "net46", "net461", "net462", "net47", "net471", "net472", "net48", "net481", + "netcoreapp1.1", "netcoreapp2.1", "netcoreapp3.1", + "net5.0", "net6.0", "net7.0", "net8.0" + }; + + return VALID_RUNTIMES.Contains(text); + } + } + + private static List FindUnitTestFiles(string patternSet) + { + var result = new List(); + + if (!string.IsNullOrEmpty(patternSet)) + { + // User supplied a set of patterns for the unit tests + foreach (string filePattern in patternSet.Split('|')) + foreach (var testPath in _context.GetFiles(BuildSettings.OutputDirectory + filePattern)) + result.Add(testPath); + } + else + { + // Use default patterns to find unit tests - case insensitive because + // we don't know how the user may have named test assemblies. + var defaultPatterns = new [] { "**/*.tests.dll", "**/*.tests.exe" }; + var globberSettings = new GlobberSettings { IsCaseSensitive = false }; + foreach (string filePattern in defaultPatterns) + foreach (var testPath in _context.GetFiles(BuildSettings.OutputDirectory + filePattern, globberSettings)) + result.Add(testPath); + } + + result.Sort(ComparePathsByFileName); + + return result; + + static int ComparePathsByFileName(FilePath x, FilePath y) + { + return x.GetFilename().ToString().CompareTo(y.GetFilename().ToString()); + } + } +} diff --git a/cake/utilities.cake b/cake/utilities.cake index 47f4fb3b5..e48fda992 100644 --- a/cake/utilities.cake +++ b/cake/utilities.cake @@ -1,218 +1,3 @@ -////////////////////////////////////////////////////////////////////// -// HELPER METHODS - GENERAL -////////////////////////////////////////////////////////////////////// - -T GetArgument(string pattern, T defaultValue) -{ - foreach (string name in pattern.Split('|')) - if (HasArgument(name)) - return Argument(name); - - return defaultValue; -} - -bool CheckIfDotNetCoreInstalled() -{ - try - { - Information("Checking if .NET Core SDK is installed..."); - StartProcess("dotnet", new ProcessSettings - { - Arguments = "--info" - }); - } - catch (Exception) - { - Warning(".NET Core SDK is not installed. It can be installed from https://www.microsoft.com/net/core"); - return false; - } - return true; -} - -void DisplayUnreportedErrors() -{ - if (UnreportedErrors.Count > 0) - { - string msg = "One or more unit tests failed, breaking the build.\r\n" - + UnreportedErrors.Aggregate((x, y) => x + "\r\n" + y); - - UnreportedErrors.Clear(); - throw new Exception(msg); - } -} - -public static void DisplayBanner(string message) -{ - var bar = new string('-', Math.Max(message.Length, 40)); - Console.WriteLine(); - Console.WriteLine(bar); - Console.WriteLine(message); - Console.WriteLine(bar); -} - -////////////////////////////////////////////////////////////////////// -// HELPER METHODS - BUILD -////////////////////////////////////////////////////////////////////// - -MSBuildSettings CreateMSBuildSettings(string target, BuildVersion buildVersion) -{ - var settings = new MSBuildSettings() - .SetConfiguration(Configuration) - .SetVerbosity(Verbosity.Minimal) - .WithProperty("Version", buildVersion.ProductVersion) - .WithProperty("ApiFileVersion", buildVersion.SemVer + ".0") - .WithTarget(target) - // Workaround for https://github.com/Microsoft/msbuild/issues/3626 - .WithProperty("AddSyntheticProjectReferencesForSolutionDependencies", "false"); - - if (IsRunningOnWindows()) - { - // The fallback is in case only a preview of VS is installed. - var vsInstallation = - VSWhereLatest(new VSWhereLatestSettings { Requires = "Microsoft.Component.MSBuild" }) - ?? VSWhereLatest(new VSWhereLatestSettings { Requires = "Microsoft.Component.MSBuild", IncludePrerelease = true }); - - if (vsInstallation != null) - { - var msBuildPath = vsInstallation.CombineWithFilePath(@"MSBuild\Current\Bin\MSBuild.exe"); - - if (!FileExists(msBuildPath)) - msBuildPath = vsInstallation.CombineWithFilePath(@"MSBuild\15.0\Bin\MSBuild.exe"); - - if (FileExists(msBuildPath)) - settings.ToolPath = msBuildPath; - } - } - - return settings; -} - -DotNetMSBuildSettings CreateDotNetMSBuildSettings(string target, BuildVersion buildVersion) -{ - return new DotNetMSBuildSettings() - .SetConfiguration(Configuration) - .WithProperty("Version", buildVersion.ProductVersion) - .WithProperty("ApiFileVersion", buildVersion.SemVer + ".0") - .WithTarget(target); -} - -////////////////////////////////////////////////////////////////////// -// HELPER METHODS - TEST -////////////////////////////////////////////////////////////////////// - -FilePath GetResultXmlPath(string testAssembly, string targetRuntime) -{ - var assemblyName = System.IO.Path.GetFileNameWithoutExtension(testAssembly); - - // Required for test suites running under NUnitLite - CreateDirectory($@"test-results\{targetRuntime}"); - - return MakeAbsolute(new FilePath($@"test-results\{targetRuntime}\{assemblyName}.xml")); -} - -void RunNUnitLiteTests(string testAssembly, string targetRuntime) -{ - var workingDir = BIN_DIR + targetRuntime + "/"; - var assemblyPath = workingDir + testAssembly; - var resultPath = GetResultXmlPath(assemblyPath, targetRuntime).FullPath; - - int rc = StartProcess( - assemblyPath, - new ProcessSettings() - { - Arguments = $"--result:{resultPath}", - WorkingDirectory = workingDir - }); - - if (rc > 0) - UnreportedErrors.Add($"{testAssembly}({targetRuntime}): {rc} tests failed"); - else if (rc < 0) - UnreportedErrors.Add($"{testAssembly}({targetRuntime}) returned rc = {rc}"); -} - -void RunDotnetNUnitLiteTests(string testAssembly, string targetRuntime) -{ - var workingDir = BIN_DIR + targetRuntime + "/"; - var assemblyPath = workingDir + testAssembly; - var resultPath = GetResultXmlPath(assemblyPath, targetRuntime).FullPath; - - int rc = StartProcess( - "dotnet", - new ProcessSettings - { - Arguments = $"\"{assemblyPath}\" --result:{resultPath}", - WorkingDirectory = workingDir - }); - - if (rc > 0) - UnreportedErrors.Add($"{testAssembly}({targetRuntime}): {rc} tests failed"); - else if (rc < 0) - UnreportedErrors.Add($"{testAssembly}({targetRuntime}) returned rc = {rc}"); -} - -void RunNet20Console(string testAssembly, string targetRuntime) -{ - var workingDir = BIN_DIR + targetRuntime + "/"; - var assemblyPath = workingDir + testAssembly; - var resultPath = GetResultXmlPath(assemblyPath, targetRuntime).FullPath; - - int rc = StartProcess( - NET20_CONSOLE, - new ProcessSettings() - { - Arguments = $"\"{assemblyPath}\" --result:{resultPath}", - WorkingDirectory = workingDir - }); - - if (rc > 0) - UnreportedErrors.Add($"{testAssembly}({targetRuntime}): {rc} tests failed"); - else if (rc < 0) - UnreportedErrors.Add($"{testAssembly}({targetRuntime}) returned rc = {rc}"); -} - -void RunNetCoreConsole(string testAssembly, string targetRuntime) -{ - var workingDir = BIN_DIR + targetRuntime + "/"; - var assemblyPath = workingDir + testAssembly; - var resultPath = GetResultXmlPath(assemblyPath, targetRuntime).FullPath; - - int rc = StartProcess( - "dotnet", - new ProcessSettings - { - Arguments = $"\"{NET80_CONSOLE}\" \"{assemblyPath}\" --result:{resultPath}", - WorkingDirectory = workingDir - }); - - if (rc > 0) - UnreportedErrors.Add($"{testAssembly}({targetRuntime}): {rc} tests failed"); - else if (rc < 0) - UnreportedErrors.Add($"{testAssembly}({targetRuntime}) returned rc = {rc}"); -} - -public List GetInstalledNetCoreRuntimes() -{ - var list = new List(); - - var process = StartProcess("dotnet", - new ProcessSettings - { - Arguments = "--list-runtimes", - RedirectStandardOutput = true, - RedirectedStandardOutputHandler = - s => { - if (s == null || !s.StartsWith("Microsoft.NETCore.App")) - return s; - - var version = s.Split(' ')[1]; - - list.Add(version); - return s; - } - }); - return list; -} - ////////////////////////////////////////////////////////////////////// // HELPER METHODS - PACKAGING ////////////////////////////////////////////////////////////////////// @@ -223,27 +8,3 @@ public void CopyPackageContents(DirectoryPath packageDir, DirectoryPath outDir) CopyFiles(files.Where(f => f.GetExtension() != ".addins"), outDir); } -public void PushNuGetPackage(FilePath package, string apiKey, string url) -{ - CheckPackageExists(package); - if (NoPush) - Information($"Push {package} to {url}"); - else - NuGetPush(package, new NuGetPushSettings() { ApiKey = apiKey, Source = url }); -} - -public void PushChocolateyPackage(FilePath package, string apiKey, string url) -{ - CheckPackageExists(package); - if (NoPush) - Information($"Push {package} to {url}"); - else - ChocolateyPush(package, new ChocolateyPushSettings() { ApiKey = apiKey, Source = url }); -} - -private void CheckPackageExists(FilePath package) -{ - if (!FileExists(package)) - throw new InvalidOperationException( - $"Package not found: {package.GetFilename()}.\nCode may have changed since package was last built."); -} diff --git a/cake/versioning.cake b/cake/versioning.cake index 35955c529..90f9d7060 100644 --- a/cake/versioning.cake +++ b/cake/versioning.cake @@ -2,7 +2,7 @@ using System.Text.RegularExpressions; public class BuildVersion { - private ISetupContext _context; + private ICakeContext _context; private GitVersion _gitVersion; // NOTE: This is complicated because (1) the user may have specified @@ -12,31 +12,30 @@ public class BuildVersion // // We simplify things a by figuring out the full package version and // then parsing it to provide information that is used in the build. - public BuildVersion(ISetupContext context) + public BuildVersion(ICakeContext context) { - _context = context; - if (context==null) + if (context==null) throw new ArgumentNullException(nameof(context)); + + _context = context; _gitVersion = context.GitVersion(); BranchName = _gitVersion.BranchName; IsReleaseBranch = BranchName.StartsWith("release-"); - string productVersion = context.HasArgument("productVersion") - ? context.Argument("productVersion") - : CalculateProductVersion(); + string packageVersion = CommandLineOptions.PackageVersion.Value ?? CalculatePackageVersion(); - int dash = productVersion.IndexOf('-'); + int dash = packageVersion.IndexOf('-'); IsPreRelease = dash > 0; - string versionPart = productVersion; + string versionPart = packageVersion; string suffix = ""; string label = ""; if (IsPreRelease) { - versionPart = productVersion.Substring(0, dash); - suffix = productVersion.Substring(dash + 1); + versionPart = packageVersion.Substring(0, dash); + suffix = packageVersion.Substring(dash + 1); foreach (char c in suffix) { if (!char.IsLetter(c)) @@ -50,16 +49,16 @@ public class BuildVersion PreReleaseLabel = label; PreReleaseSuffix = suffix; - ProductVersion = productVersion; + PackageVersion = packageVersion; AssemblyVersion = SemVer + ".0"; AssemblyFileVersion = SemVer; - AssemblyInformationalVersion = productVersion; + AssemblyInformationalVersion = packageVersion; } public string BranchName { get; } public bool IsReleaseBranch { get; } - public string ProductVersion { get; } + public string PackageVersion { get; } public string AssemblyVersion { get; } public string AssemblyFileVersion { get; } public string AssemblyInformationalVersion { get; } @@ -69,7 +68,7 @@ public class BuildVersion public string PreReleaseLabel { get; } public string PreReleaseSuffix { get; } - private string CalculateProductVersion() + private string CalculatePackageVersion() { string label = _gitVersion.PreReleaseLabel; @@ -79,10 +78,6 @@ public class BuildVersion string branchName = _gitVersion.BranchName; - // Treat version3 branch as an alternate "main" - if (branchName == "version3X" || branchName == "version315" || branchName == "version317") - label = "dev"; - // We don't currently use this pattern, but check in case we do later. if (branchName.StartsWith("feature/")) branchName = branchName.Substring(8); diff --git a/cake/zip-package.cake b/cake/zip-package.cake new file mode 100644 index 000000000..8d7b1ff5a --- /dev/null +++ b/cake/zip-package.cake @@ -0,0 +1,78 @@ +public class ZipPackage : PackageDefinition +{ + public ZipPackage( + string id, + string source, + PackageTestRunner testRunner = null, + PackageCheck[] checks = null, + IEnumerable tests = null, + PackageReference[] bundledExtensions = null ) + : base( + PackageType.Zip, + id, + source, + testRunner: testRunner, + checks: checks, + tests: tests) + { + BundledExtensions = bundledExtensions; + } + + // MSI and ZIP packages support bundling of extensions + // if any are specified in the definition. + public PackageReference[] BundledExtensions { get; } + + // The file name of this package, including extension + public override string PackageFileName => $"{PackageId}-{PackageVersion}.zip"; + // The directory into which this package is installed + public override string PackageInstallDirectory => BuildSettings.ZipTestDirectory; + // The directory used to contain results of package tests for this package + public override string PackageResultDirectory => $"{BuildSettings.ZipResultDirectory}{PackageId}/"; + // The directory into which extensions to the test runner are installed + public override string ExtensionInstallDirectory => $"{BuildSettings.ZipTestDirectory}{PackageId}.{PackageVersion}/bin/addins/"; + + public override void BuildPackage() + { + FetchBundledExtensions(BundledExtensions); + + CreateZipImage(); + + _context.Zip(BuildSettings.ZipImageDirectory, $"{BuildSettings.PackageDirectory}{PackageFileName}"); + } + + public override void InstallPackage() + { + _context.Unzip($"{BuildSettings.PackageDirectory}{PackageFileName}", $"{PackageInstallDirectory}{PackageId}.{PackageVersion}"); + } + + private void CreateZipImage() + { + _context.CleanDirectory(BuildSettings.ZipImageDirectory); + + _context.CopyFiles( + new FilePath[] { "LICENSE.txt", "NOTICES.txt", "CHANGES.txt", "nunit.ico" }, + BuildSettings.ZipImageDirectory); + + _context.CopyDirectory( + BuildSettings.OutputDirectory, + BuildSettings.ZipImageDirectory + "bin/" ); + + foreach (var runtime in new[] { "net20", "net35" }) + { + var runtimeDir = BuildSettings.ZipImageDirectory + $"bin/{runtime}/"; + + _context.CopyFileToDirectory( + BuildSettings.ZipDirectory + "nunit.bundle.addins", + BuildSettings.ZipImageDirectory); + + var addinsDir = runtimeDir + "addins/"; + _context.CleanDirectory(addinsDir); + + foreach (var packageDir in System.IO.Directory.GetDirectories(BuildSettings.ExtensionsDirectory)) + { + var files = _context.GetFiles(packageDir + "/tools/*").Concat(_context.GetFiles(packageDir + "/tools/net20/*")); + _context.CopyFiles(files.Where(f => f.GetExtension() != ".addins"), addinsDir); + } + } + } +} diff --git a/choco/nunit.console.choco.addins b/choco/nunit.console.choco.addins index 7eaefda90..d07fb7a21 100644 --- a/choco/nunit.console.choco.addins +++ b/choco/nunit.console.choco.addins @@ -1,5 +1,7 @@ # Extensions built for a single runtime target -../../nunit-extension-*/tools/ # find extensions installed under chocolatey +../../nunit-extension-*/**/tools/ # nuget v2 layout +../../../nunit-extension-*/**/tools/ # nuget v3 layout # Extensions built for multiple targets -../../nunit-extension-*/tools/*/ # find extensions installed under chocolatey +../../nunit-extension-*/**/tools/*/ # nuget v2 layout +../../../nunit-extension-*/**/tools/*/ # nuget v3 layout diff --git a/msi/nunit/engine-files.wxi b/msi/nunit/engine-files.wxi index 2b914f015..cf682b320 100644 --- a/msi/nunit/engine-files.wxi +++ b/msi/nunit/engine-files.wxi @@ -69,6 +69,23 @@ + + + + + + + + + + @@ -88,6 +105,23 @@ + + + + + + + + + + diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index d2147e507..5d44bdb9e 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -22,9 +22,6 @@ en-US nunit test testing tdd runner Copyright (c) 2021 Charlie Poole, Rob Prouse - - - diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index 6e11ec932..efbac43af 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -132,7 +132,6 @@ private int ExploreTests(TestPackage package, TestFilter filter) private int RunTests(TestPackage package, TestFilter filter) { - var writer = new ColorConsoleWriter(!_options.NoColor); foreach (var spec in _options.ResultOutputSpecifications) diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index e3ab012e0..dea0e6da9 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -40,6 +40,10 @@ private static ExtendedTextWriter OutWriter [STAThread] public static int Main(string[] args) { + Console.WriteLine($"Main called with {args.Length} arguments:"); + foreach (var arg in args) + Console.WriteLine(" " + arg); + Console.CancelKeyPress += new ConsoleCancelEventHandler(CancelHandler); try diff --git a/src/NUnitConsole/nunit3-console/builder.cake b/src/NUnitConsole/nunit3-console/builder.cake new file mode 100644 index 000000000..ea91e367d --- /dev/null +++ b/src/NUnitConsole/nunit3-console/builder.cake @@ -0,0 +1,22 @@ +////////////////////////////////////////////////////////////////////// +// EXECUTION +////////////////////////////////////////////////////////////////////// + +public Builder Build => CommandLineOptions.Usage + ? new Builder(() => Information(HelpMessages.Usage)) + : new Builder(() => RunTarget(CommandLineOptions.Target.Value)); + +public class Builder +{ + private Action _action; + + public Builder(Action action) + { + _action = action; + } + + public void Run() + { + _action(); + } +} From 35520563410f906cf9953fe64323c010ca1ebcfd Mon Sep 17 00:00:00 2001 From: CharliePoole Date: Sat, 29 Jun 2024 12:26:35 -0700 Subject: [PATCH 064/190] Update platform-support.md --- platform-support.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform-support.md b/platform-support.md index 96e151ba3..91c80c0d5 100644 --- a/platform-support.md +++ b/platform-support.md @@ -31,11 +31,11 @@ process of upgrade. However, agents for runtimes which have been declared a secu Based on that policy and the planned end-of-life dates for runtimes, we expect to retire agents on or after the dates listed in the following table. -| Runtime | Retirement Date | +| Runtime | Agent Supported
At Least Until | | -------------------- | --------------- | -| .NET Framework 2.0 | TBD | -| .NET Framework 4.6.2 | TBD | -| .NET Core 3.1 | July, 2024 | +| .NET Framework 2.0 | | +| .NET Framework 4.6.2 | | +| .NET Core 3.1 | | | .NET 5.0 | July, 2024 | | .NET 6.0 | May, 2025 | | .NET 7.0 | November, 2024 | From 869bda48150837454c2c755659344ab36cfb05ed Mon Sep 17 00:00:00 2001 From: CharliePoole Date: Sat, 29 Jun 2024 15:31:13 -0700 Subject: [PATCH 065/190] Update platform-support.md --- platform-support.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/platform-support.md b/platform-support.md index 91c80c0d5..8319b6cce 100644 --- a/platform-support.md +++ b/platform-support.md @@ -29,17 +29,17 @@ official end of life. This is intended to support continued testing of legacy ap process of upgrade. However, agents for runtimes which have been declared a security risk may be removed immediately. Based on that policy and the planned end-of-life dates for runtimes, we expect to retire agents on or after the -dates listed in the following table. - -| Runtime | Agent Supported
At Least Until | -| -------------------- | --------------- | -| .NET Framework 2.0 | | -| .NET Framework 4.6.2 | | -| .NET Core 3.1 | | -| .NET 5.0 | July, 2024 | -| .NET 6.0 | May, 2025 | -| .NET 7.0 | November, 2024 | -| .NET 8.0 | May, 2027 | +dates listed in the following table. + +| Runtime | Microsoft
End of Support | Agent Retirement | Notes | +| -------------------- | --------------- | --------------------- | --- | +| .NET Framework 2.0 | July, 2011 | July, 2024 | Will be removed in version 3.18.0 +| .NET Framework 4.6.2 | January, 2027 | after July, 2027 | May be upgraded to 4.8.1 before retirement date | +| .NET Core 3.1 | December, 2022 | after December, 2024 | +| .NET 5.0 | May, 2022 | July, 2024 | Will be removed in version 3.18.0 +| .NET 6.0 | November, 2024 | after May, 2025 | +| .NET 7.0 | May, 2024 | after November, 2024 | +| .NET 8.0 | November, 2027 | after May, 2027 | **NOTES:** 1. Some discussion is needed before we can determine when the .NET Framework agents will be removed and how they will be replaced. From 3b94aecb8379e34fd35aa61ebb5c01e51ed1238d Mon Sep 17 00:00:00 2001 From: CharliePoole Date: Sun, 30 Jun 2024 07:39:53 -0700 Subject: [PATCH 066/190] Update platform-support.md --- platform-support.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/platform-support.md b/platform-support.md index 8319b6cce..178b29fb2 100644 --- a/platform-support.md +++ b/platform-support.md @@ -41,10 +41,6 @@ dates listed in the following table. | .NET 7.0 | May, 2024 | after November, 2024 | | .NET 8.0 | November, 2027 | after May, 2027 | -**NOTES:** -1. Some discussion is needed before we can determine when the .NET Framework agents will be removed and how they will be replaced. -2. July, 2024 is the estimated release date of version 3.18.0 of the console runner. - ## Required Runtimes The console runner and engine target .NET Framework 4.6.2, so that runtime or greater is required to execute any tests at all From c2f734470d6ba58397b3df277093823552693bbd Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 30 Jun 2024 08:31:25 -0700 Subject: [PATCH 067/190] Update doc title; skip commits for docs only --- platform-support.md => PLATFORM_SUPPORT.md | 0 appveyor.yml | 5 +++++ 2 files changed, 5 insertions(+) rename platform-support.md => PLATFORM_SUPPORT.md (100%) diff --git a/platform-support.md b/PLATFORM_SUPPORT.md similarity index 100% rename from platform-support.md rename to PLATFORM_SUPPORT.md diff --git a/appveyor.yml b/appveyor.yml index 5c62a8cc5..765697b43 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,6 +5,11 @@ branches: except: - /^azure-/ +skip_commits: + files: + - ./*.txt + - ./*.md + build_script: - ps: .\build.ps1 --target=Appveyor --configuration=Release From 61b97108f4b3fe482b325ee2ba6c86be4b04e4a6 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 1 Jul 2024 17:45:57 -0700 Subject: [PATCH 068/190] Changes from code review --- cake/banner.cake | 10 +- cake/build-settings.cake | 48 +++--- cake/command-line-options.cake | 180 ++++++++++----------- src/NUnitConsole/nunit3-console/Program.cs | 4 - 4 files changed, 117 insertions(+), 125 deletions(-) diff --git a/cake/banner.cake b/cake/banner.cake index 760ea3c35..b882509ba 100644 --- a/cake/banner.cake +++ b/cake/banner.cake @@ -4,12 +4,20 @@ public static class Banner { public static void Display(string message, char barChar='-', int length=0) { - if (length == 0) length = message.Length; + if (length == 0) length = MaxLineLength(message); var bar = new string(barChar, length); Console.WriteLine(); Console.WriteLine(bar); Console.WriteLine(message); Console.WriteLine(bar); + + int MaxLineLength(string message) + { + int length = 0; + foreach (string line in message.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)) + length = Math.Max(length, line.Length); + return length; + } } } diff --git a/cake/build-settings.cake b/cake/build-settings.cake index af06202d0..6e87520c9 100644 --- a/cake/build-settings.cake +++ b/cake/build-settings.cake @@ -26,28 +26,19 @@ public static class BuildSettings ) { // Required arguments - if (context == null) - throw new ArgumentNullException(nameof(context)); - if (title == null) - throw new ArgumentNullException(nameof(title)); - if (githubRepository == null) - throw new ArgumentNullException(nameof(githubRepository)); - - Context = context; - Title = title; - GitHubRepository = githubRepository; - - // NOTE: Order of initialization can be sensitive. Obviously, - // we have to set any properties in this method before we - // make use of them. Less obviously, some of the classes we - // construct here have dependencies on certain properties - // being set before the constructor is called. I have - // tried to annotate such dependencies below. + Context = context ?? throw new ArgumentNullException(nameof(context)); + Title = title ?? throw new ArgumentNullException(nameof(title)); + GitHubRepository = githubRepository ?? throw new ArgumentNullException(nameof(githubRepository)); + + // NOTE: Order of initialization can be sensitive. Obviously, + // we have to set any properties in this method before we + // make use of them. Less obviously, some of the classes we + // construct here have dependencies on certain properties + // being set before the constructor is called. I have + // tried to annotate such dependencies below. _buildSystem = context.BuildSystem(); - // If not specified, uses TITLE.sln if it exists or uses solution - // found in the root directory provided there is only one. SolutionFile = solutionFile ?? DeduceSolutionFile(); ValidConfigurations = validConfigurations ?? DEFAULT_VALID_CONFIGS; @@ -90,21 +81,18 @@ public static class BuildSettings } } - // Try to figure out solution file when not provided + // If solution file was not provided, uses TITLE.sln if it exists or + // the solution found in the root directory provided there is only one. private static string DeduceSolutionFile() { - string solutionFile = null; - if (System.IO.File.Exists(Title + ".sln")) - solutionFile = Title + ".sln"; - else - { - var files = System.IO.Directory.GetFiles(ProjectDirectory, "*.sln"); - if (files.Count() == 1 && System.IO.File.Exists(files[0])) - solutionFile = files[0]; - } + return Title + ".sln"; + + var files = System.IO.Directory.GetFiles(ProjectDirectory, "*.sln"); + if (files.Length == 1 && System.IO.File.Exists(files[0])) + return files[0]; - return solutionFile; + return null; } private static int CalcPackageTestLevel() diff --git a/cake/command-line-options.cake b/cake/command-line-options.cake index 8f878260f..505fd7925 100644 --- a/cake/command-line-options.cake +++ b/cake/command-line-options.cake @@ -7,112 +7,112 @@ CommandLineOptions.Initialize(Context); public static class CommandLineOptions { - static private ICakeContext _context; + static private ICakeContext _context; - static public ValueOption Target; - static public ValueOption Configuration; - static public ValueOption PackageVersion; + static public ValueOption Target; + static public ValueOption Configuration; + static public ValueOption PackageVersion; static public ValueOption PackageSelector; - static public ValueOption TestLevel; - static public ValueOption TraceLevel; - static public SimpleOption NoBuild; - static public SimpleOption NoPush; - static public SimpleOption Usage; + static public ValueOption TestLevel; + static public ValueOption TraceLevel; + static public SimpleOption NoBuild; + static public SimpleOption NoPush; + static public SimpleOption Usage; - public static void Initialize(ICakeContext context) - { - _context = context; + public static void Initialize(ICakeContext context) + { + _context = context; - // The name of the TARGET task to be run, e.g. Test. - Target = new ValueOption("target", "Default", 1); + // The name of the TARGET task to be run, e.g. Test. + Target = new ValueOption("target", "Default", 1); - Configuration = new ValueOption("configuration", DEFAULT_CONFIGURATION, 1); + Configuration = new ValueOption("configuration", DEFAULT_CONFIGURATION, 1); - PackageVersion = new ValueOption("packageVersion", null, 4); + PackageVersion = new ValueOption("packageVersion", null, 4); PackageSelector = new ValueOption("where", null, 1); - TestLevel = new ValueOption("level", 0, 3); + TestLevel = new ValueOption("level", 0, 3); - TraceLevel = new ValueOption("trace", "Off", 2); + TraceLevel = new ValueOption("trace", "Off", 2); - NoBuild = new SimpleOption("nobuild", 3); + NoBuild = new SimpleOption("nobuild", 3); - NoPush = new SimpleOption("nopush", 3); + NoPush = new SimpleOption("nopush", 3); - Usage = new SimpleOption("usage", 2); - } + Usage = new SimpleOption("usage", 2); + } - // Nested classes to represent individual options + // Nested classes to represent individual options - // AbstractOption has a name and can tell us if it exists. - public abstract class AbstractOption - { - public string Name { get; } + // AbstractOption has a name and can tell us if it exists. + public abstract class AbstractOption + { + public string Name { get; } - public int MinimumAbbreviation { get; internal set; } + public int MinimumAbbreviation { get; internal set; } - public bool Exists - { - get - { - for (int len = Name.Length; len >= MinimumAbbreviation; len--) - if (_context.HasArgument(Name.Substring(0,len))) - return true; - return false; - } - } - - public string Description { get; } - - public AbstractOption(string name, int minimumAbbreviation = 0, string description = null) - { - Name = name; - MinimumAbbreviation = minimumAbbreviation > 0 && minimumAbbreviation <= name.Length - ? minimumAbbreviation - : name.Length; - Description = description; - } - } - - // Simple Option adds an implicit boolean conversion operator. - // It throws an exception if you gave it a value on the command-line. - public class SimpleOption : AbstractOption - { - static public implicit operator bool(SimpleOption o) => o.Exists; - - public SimpleOption(string name, int minimumAbbreviation = 0, string description = null) - : base(name, minimumAbbreviation, description) - { - if (_context.Argument(name, (string)null) != null) - throw new Exception($"Option --{name} does not take a value."); - } - } - - // Generic ValueOption adds Value as well as a default value - public class ValueOption : AbstractOption - { - public T DefaultValue { get; } - - public ValueOption(string name, T defaultValue, int minimumAbbreviation = 0, string description = null) - : base(name, minimumAbbreviation, description) - { - DefaultValue = defaultValue; - } - - public T Value - { - get - { - for (int len = Name.Length; len >= MinimumAbbreviation; len--) - { - string abbrev = Name.Substring(0,len); - if (_context.HasArgument(abbrev)) - return _context.Argument(abbrev); - } + public bool Exists + { + get + { + for (int len = Name.Length; len >= MinimumAbbreviation; len--) + if (_context.HasArgument(Name.Substring(0,len))) + return true; + return false; + } + } + + public string Description { get; } + + public AbstractOption(string name, int minimumAbbreviation = 0, string description = null) + { + Name = name; + MinimumAbbreviation = minimumAbbreviation > 0 && minimumAbbreviation <= name.Length + ? minimumAbbreviation + : name.Length; + Description = description; + } + } + + // Simple Option adds an implicit boolean conversion operator. + // It throws an exception if you gave it a value on the command-line. + public class SimpleOption : AbstractOption + { + static public implicit operator bool(SimpleOption o) => o.Exists; + + public SimpleOption(string name, int minimumAbbreviation = 0, string description = null) + : base(name, minimumAbbreviation, description) + { + if (_context.Argument(name, (string)null) != null) + throw new Exception($"Option --{name} does not take a value."); + } + } + + // Generic ValueOption adds Value as well as a default value + public class ValueOption : AbstractOption + { + public T DefaultValue { get; } + + public ValueOption(string name, T defaultValue, int minimumAbbreviation = 0, string description = null) + : base(name, minimumAbbreviation, description) + { + DefaultValue = defaultValue; + } + + public T Value + { + get + { + for (int len = Name.Length; len >= MinimumAbbreviation; len--) + { + string abbrev = Name.Substring(0,len); + if (_context.HasArgument(abbrev)) + return _context.Argument(abbrev); + } - return DefaultValue; - } - } - } + return DefaultValue; + } + } + } } diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index dea0e6da9..e3ab012e0 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -40,10 +40,6 @@ private static ExtendedTextWriter OutWriter [STAThread] public static int Main(string[] args) { - Console.WriteLine($"Main called with {args.Length} arguments:"); - foreach (var arg in args) - Console.WriteLine(" " + arg); - Console.CancelKeyPress += new ConsoleCancelEventHandler(CancelHandler); try From ad6848aa4949296cacb8d8e5903bdd36f13ae6c3 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 2 Jul 2024 15:11:42 -0700 Subject: [PATCH 069/190] Get Api keys from environment --- cake/build-settings.cake | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/cake/build-settings.cake b/cake/build-settings.cake index 6e87520c9..b356e22fa 100644 --- a/cake/build-settings.cake +++ b/cake/build-settings.cake @@ -220,17 +220,22 @@ public static class BuildSettings ? CommandLineOptions.TestLevel.Value : CalcPackageTestLevel(); - // Publishing + // Publishing - MyGet public static string MyGetPushUrl => MYGET_PUSH_URL; + public static string MyGetApiKey => Context.EnvironmentVariable(MYGET_API_KEY); + + // Publishing - NuGet public static string NuGetPushUrl => NUGET_PUSH_URL; + public static string NuGetApiKey => Context.EnvironmentVariable(NUGET_API_KEY); + + // Publishing - Chocolatey public static string ChocolateyPushUrl => CHOCO_PUSH_URL; + public static string ChocolateyApiKey => Context.EnvironmentVariable(CHOCO_API_KEY); - public static string MyGetApiKey { get; private set; } - public static string NuGetApiKey { get; private set; } - public static string ChocolateyApiKey { get; private set;} - public static string GitHubOwner { get; private set; } - public static string GitHubRepository { get; private set; } - public static string GitHubAccessToken { get; private set; } + // Publishing - GitHub + public static string GitHubOwner { get; set; } + public static string GitHubRepository { get; set; } + public static string GitHubAccessToken => Context.EnvironmentVariable(GITHUB_ACCESS_TOKEN); public static bool IsPreRelease => BuildVersion.IsPreRelease; public static bool ShouldPublishToMyGet => @@ -318,6 +323,7 @@ public static class BuildSettings Console.WriteLine("MyGetApiKey: " + (!string.IsNullOrEmpty(MyGetApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); Console.WriteLine("NuGetApiKey: " + (!string.IsNullOrEmpty(NuGetApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); Console.WriteLine("ChocolateyApiKey: " + (!string.IsNullOrEmpty(ChocolateyApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); + Console.WriteLine("GitHubAccessToken: " + (!string.IsNullOrEmpty(GitHubAccessToken) ? "AVAILABLE" : "NOT AVAILABLE")); Console.WriteLine("\nPACKAGES"); foreach (var package in Packages) From 77074c7ef71a3d3a27757af40a5667ee3b69ac2c Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 2 Jul 2024 16:47:38 -0700 Subject: [PATCH 070/190] Eliminate MSI package --- NUnitConsole.sln | 26 +-- build.cake | 20 -- cake/build-settings.cake | 7 - cake/constants.cake | 4 - cake/help-messages.cake | 4 +- cake/msi-package.cake | 95 ---------- cake/package-definition.cake | 1 - cake/zip-package.cake | 3 +- msi/nunit-install.sln | 27 --- msi/nunit/addin-files.wxi | 47 ----- msi/nunit/banner.bmp | Bin 29846 -> 0 bytes msi/nunit/console-files.wxi | 14 -- msi/nunit/dialog.bmp | Bin 155830 -> 0 bytes msi/nunit/engine-files.wxi | 305 ------------------------------ msi/nunit/nunit.wixproj | 51 ----- msi/nunit/nunit.wxs | 28 --- msi/nunit/runner-directories.wxi | 25 --- msi/nunit/runner-features.wxi | 55 ------ msi/nunit/utility-files.wxi | 16 -- msi/nunit/variables.wxi | 51 ----- msi/resources/License.rtf | Bin 1279 -> 0 bytes msi/resources/nunit.agent.addins | 1 - msi/resources/nunit.bundle.addins | 5 - msi/resources/nunit.ico | Bin 59679 -> 0 bytes 24 files changed, 4 insertions(+), 781 deletions(-) delete mode 100644 cake/msi-package.cake delete mode 100644 msi/nunit-install.sln delete mode 100644 msi/nunit/addin-files.wxi delete mode 100644 msi/nunit/banner.bmp delete mode 100644 msi/nunit/console-files.wxi delete mode 100644 msi/nunit/dialog.bmp delete mode 100644 msi/nunit/engine-files.wxi delete mode 100644 msi/nunit/nunit.wixproj delete mode 100644 msi/nunit/nunit.wxs delete mode 100644 msi/nunit/runner-directories.wxi delete mode 100644 msi/nunit/runner-features.wxi delete mode 100644 msi/nunit/utility-files.wxi delete mode 100644 msi/nunit/variables.wxi delete mode 100644 msi/resources/License.rtf delete mode 100644 msi/resources/nunit.agent.addins delete mode 100644 msi/resources/nunit.bundle.addins delete mode 100644 msi/resources/nunit.ico diff --git a/NUnitConsole.sln b/NUnitConsole.sln index d2766917b..42dde2e20 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -109,12 +109,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deprecated", "deprecated", choco\deprecated\nunit-console-with-extensions.nuspec = choco\deprecated\nunit-console-with-extensions.nuspec EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "msi", "msi", "{0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A}" - ProjectSection(SolutionItems) = preProject - msi\nunit-install.sln = msi\nunit-install.sln - msi\resources\nunit.bundle.addins = msi\resources\nunit.bundle.addins - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "zip", "zip", "{20005864-BE82-412D-99BF-288E2D8370E9}" ProjectSection(SolutionItems) = preProject zip\nunit.bundle.addins = zip\nunit.bundle.addins @@ -141,21 +135,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{C5B712 .config\dotnet-tools.json = .config\dotnet-tools.json EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nunit", "nunit", "{93E5CAF4-D5DB-4915-AF0F-908A6043E462}" - ProjectSection(SolutionItems) = preProject - msi\nunit\addin-files.wxi = msi\nunit\addin-files.wxi - msi\nunit\banner.bmp = msi\nunit\banner.bmp - msi\nunit\console-files.wxi = msi\nunit\console-files.wxi - msi\nunit\dialog.bmp = msi\nunit\dialog.bmp - msi\nunit\engine-files.wxi = msi\nunit\engine-files.wxi - msi\nunit\nunit.wixproj = msi\nunit\nunit.wixproj - msi\nunit\nunit.wxs = msi\nunit\nunit.wxs - msi\nunit\runner-directories.wxi = msi\nunit\runner-directories.wxi - msi\nunit\runner-features.wxi = msi\nunit\runner-features.wxi - msi\nunit\utility-files.wxi = msi\nunit\utility-files.wxi - msi\nunit\variables.wxi = msi\nunit\variables.wxi - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{D6449B7A-20FF-467B-A65E-174DD6992AEB}" ProjectSection(SolutionItems) = preProject cake\banner.cake = cake\banner.cake @@ -169,7 +148,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{D6449B7A-2 cake\headers.cake = cake\headers.cake cake\help-messages.cake = cake\help-messages.cake cake\known-extensions.cake = cake\known-extensions.cake - cake\msi-package.cake = cake\msi-package.cake cake\nuget-package.cake = cake\nuget-package.cake cake\package-checks.cake = cake\package-checks.cake cake\package-definition.cake = cake\package-definition.cake @@ -177,9 +155,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{D6449B7A-2 cake\package-test.cake = cake\package-test.cake cake\publishing.cake = cake\publishing.cake cake\setup.cake = cake\setup.cake + cake\task-builders.cake = cake\task-builders.cake cake\task-definitions.cake = cake\task-definitions.cake cake\test-reports.cake = cake\test-reports.cake - cake\task-builders.cake = cake\task-builders.cake cake\test-results.cake = cake\test-results.cake cake\test-runners.cake = cake\test-runners.cake cake\tools.cake = cake\tools.cake @@ -265,14 +243,12 @@ Global {333D2FBC-CCA7-46AF-9453-C310671A67B0} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} {9D3015EE-5B84-41B3-A1D3-1A439370C392} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} {068F6CA9-6108-4F45-8540-351AA5227259} = {4FDF7BFA-A337-41D3-898D-C6A98278E6AD} - {0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {20005864-BE82-412D-99BF-288E2D8370E9} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {CACC0520-B452-4310-A33C-DC944129ACDD} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} {25DA12FE-6209-4524-9A37-8E51F815E198} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {08F8160E-E691-4F07-9F57-EA31B9736429} = {25DA12FE-6209-4524-9A37-8E51F815E198} {50371E48-BEC3-4D53-BD37-F3A6149CFD0D} = {25DA12FE-6209-4524-9A37-8E51F815E198} {C5B7120C-190B-4C38-95CB-83F12799598D} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} - {93E5CAF4-D5DB-4915-AF0F-908A6043E462} = {0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A} {D6449B7A-20FF-467B-A65E-174DD6992AEB} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/build.cake b/build.cake index 72dc92264..42104e5a7 100644 --- a/build.cake +++ b/build.cake @@ -246,7 +246,6 @@ PackageDefinition NUnitConsoleRunnerNet80Package; PackageDefinition NUnitEnginePackage; PackageDefinition NUnitEngineApiPackage; PackageDefinition NUnitConsoleRunnerChocolateyPackage; -PackageDefinition NUnitConsoleMsiPackage; PackageDefinition NUnitConsoleZipPackage; BuildSettings.Packages.AddRange(new PackageDefinition[] { @@ -330,25 +329,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { + $"nunit-console-runner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), tests: StandardRunnerTests), - NUnitConsoleMsiPackage = new MsiPackage( - id: "NUnit.Console", - source: BuildSettings.MsiDirectory + "nunit/nunit.wixproj", - checks: new PackageCheck[] { - HasDirectory("NUnit.org").WithFiles("LICENSE.txt", "NOTICES.txt", "nunit.ico"), - HasDirectory("NUnit.org/nunit-console").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.bundle.addins"), - HasDirectory("Nunit.org/nunit-console/addins").WithFiles("nunit.core.dll", "nunit.core.interfaces.dll", "nunit.v2.driver.dll", "nunit-project-loader.dll", "vs-project-loader.dll", "nunit-v2-result-writer.dll", "teamcity-event-listener.dll") - }, - testRunner: new ConsoleRunnerSelfTester(BuildSettings.MsiTestDirectory - + $"NUnit.Console.{BuildSettings.BuildVersion.SemVer}/NUnit.org/nunit-console/nunit3-console.exe"), - tests: StandardRunnerTests, - bundledExtensions: new [] { - new PackageReference("NUnit.Extension.VSProjectLoader", "3.9.0"), - new PackageReference("NUnit.Extension.NUnitProjectLoader", "3.7.1"), - new PackageReference("NUnit.Extension.NUnitV2Driver", "3.9.0"), - new PackageReference("NUnit.Extension.NUnitV2ResultWriter", "3.7.0"), - new PackageReference("NUnit.Extension.TeamCityEventListener", "1.0.9") - }), - NUnitConsoleZipPackage = new ZipPackage( id: "NUnit.Console", source: BuildSettings.ZipImageDirectory, diff --git a/cake/build-settings.cake b/cake/build-settings.cake index b356e22fa..789735859 100644 --- a/cake/build-settings.cake +++ b/cake/build-settings.cake @@ -167,21 +167,17 @@ public static class BuildSettings public static string OutputDirectory => ProjectDirectory + BIN_DIR + Configuration + "/"; public static string NuGetDirectory => ProjectDirectory + NUGET_DIR; public static string ChocolateyDirectory => ProjectDirectory + CHOCO_DIR; - public static string MsiDirectory => ProjectDirectory + MSI_DIR; public static string ZipDirectory => ProjectDirectory + ZIP_DIR; public static string PackageDirectory => ProjectDirectory + PACKAGE_DIR; public static string PackageTestDirectory => ProjectDirectory + PKG_TEST_DIR; public static string NuGetTestDirectory => ProjectDirectory + NUGET_TEST_DIR; public static string ChocolateyTestDirectory => ProjectDirectory + CHOCO_TEST_DIR; - public static string MsiTestDirectory => ProjectDirectory + MSI_TEST_DIR; public static string ZipTestDirectory => ProjectDirectory + ZIP_TEST_DIR; public static string PackageResultDirectory => ProjectDirectory + PKG_RSLT_DIR; public static string NuGetResultDirectory => ProjectDirectory + NUGET_RSLT_DIR; public static string ChocolateyResultDirectory => ProjectDirectory + CHOCO_RSLT_DIR; - public static string MsiResultDirectory => ProjectDirectory + MSI_RSLT_DIR; public static string ZipResultDirectory => ProjectDirectory + ZIP_RSLT_DIR; public static string ImageDirectory => ProjectDirectory + IMAGE_DIR; - public static string MsiImageDirectory => ProjectDirectory + MSI_IMG_DIR; public static string ZipImageDirectory => ProjectDirectory + ZIP_IMG_DIR; public static string ExtensionsDirectory => ProjectDirectory + "bundled-extensions/"; public static string ToolsDirectory => ProjectDirectory + "tools/"; @@ -298,15 +294,12 @@ public static class BuildSettings Console.WriteLine("PackageTest: " + PackageTestDirectory); Console.WriteLine("NuGetTest: " + NuGetTestDirectory); Console.WriteLine("ChocoTest: " + ChocolateyTestDirectory); - Console.WriteLine("MsiTest: " + MsiTestDirectory); Console.WriteLine("ZipTest: " + ZipTestDirectory); Console.WriteLine("PackageResult: " + PackageResultDirectory); Console.WriteLine("NuGetResult: " + NuGetResultDirectory); Console.WriteLine("ChocoResult: " + ChocolateyResultDirectory); - Console.WriteLine("MsiResult: " + MsiResultDirectory); Console.WriteLine("ZipResult: " + ZipResultDirectory); Console.WriteLine("Image: " + ImageDirectory); - Console.WriteLine("MsiImage: " + MsiImageDirectory); Console.WriteLine("ZipImage: " + ZipImageDirectory); Console.WriteLine("\nBUILD"); diff --git a/cake/constants.cake b/cake/constants.cake index d73edda5a..810238fb4 100644 --- a/cake/constants.cake +++ b/cake/constants.cake @@ -16,7 +16,6 @@ const string SRC_DIR = "src/"; const string BIN_DIR = "bin/"; const string NUGET_DIR = "nuget/"; const string CHOCO_DIR = "choco/"; -const string MSI_DIR = "msi/"; const string ZIP_DIR = "zip/"; const string PACKAGE_DIR = "package/"; const string PKG_TEST_DIR = "package/tests/"; @@ -24,15 +23,12 @@ const string NUGET_TEST_DIR = "package/tests/nuget/"; //const string NUGET_RUNNER_DIR = "package/tests/nuget/runners/"; const string CHOCO_TEST_DIR = "package/tests/choco/"; //const string CHOCO_RUNNER_DIR = "package/tests/choco/runners/"; -const string MSI_TEST_DIR = "package/tests/msi/"; const string ZIP_TEST_DIR = "package/tests/zip/"; const string PKG_RSLT_DIR = "package/results/"; const string NUGET_RSLT_DIR = "package/results/nuget/"; const string CHOCO_RSLT_DIR = "package/results/choco/"; -const string MSI_RSLT_DIR = "package/results/msi/"; const string ZIP_RSLT_DIR = "package/results/zip/"; const string IMAGE_DIR = "package/images"; -const string MSI_IMG_DIR = "package/images/msi/"; const string ZIP_IMG_DIR = "package/images/zip/"; const string TOOLS_DIR = "tools/"; diff --git a/cake/help-messages.cake b/cake/help-messages.cake index d615f04c9..119f033ed 100644 --- a/cake/help-messages.cake +++ b/cake/help-messages.cake @@ -31,9 +31,9 @@ more specifications, separated by '|' and '&'. Each specification is of the form "prop=value", where prop may be either id or type. Examples: - --where type=msi + --where type=nuget --where id=NUnit.Engine.Api - --where "type=msi|type=zip" + --where "type=nuget|type=choco" --level=LEVEL [--lev] Specifies the level of package testing, 1, 2 or 3. Defaults are diff --git a/cake/msi-package.cake b/cake/msi-package.cake deleted file mode 100644 index 224608282..000000000 --- a/cake/msi-package.cake +++ /dev/null @@ -1,95 +0,0 @@ -public class MsiPackage : PackageDefinition -{ - public MsiPackage( - string id, - string source, - PackageTestRunner testRunner = null, - PackageCheck[] checks = null, - IEnumerable tests = null, - PackageReference[] bundledExtensions = null) - : base( - PackageType.Msi, - id, - source, - testRunner: testRunner, - checks: checks, - tests: tests) - { - PackageVersion = BuildSettings.BuildVersion.SemVer; - BundledExtensions = bundledExtensions; - } - - // MSI and ZIP packages support bundling of extensions - // if any are specified in the definition. - public PackageReference[] BundledExtensions { get; } - - // The file name of this package, including extension - public override string PackageFileName => $"{PackageId}-{PackageVersion}.msi"; - // The directory into which this package is installed - public override string PackageInstallDirectory => BuildSettings.MsiTestDirectory; - // The directory used to contain results of package tests for this package - public override string PackageResultDirectory => BuildSettings.MsiResultDirectory + PackageId + "/"; - // The directory into which extensions to the test runner are installed - // TODO: FIx this for msi addins - public override string ExtensionInstallDirectory => BuildSettings.PackageTestDirectory; - - public override void BuildPackage() - { - FetchBundledExtensions(BundledExtensions); - - CreateMsiImage(); - - _context.MSBuild(PackageSource, new MSBuildSettings() - .WithTarget("Rebuild") - .SetConfiguration(BuildSettings.Configuration) - .WithProperty("Version", PackageVersion) - .WithProperty("DisplayVersion", PackageVersion) - .WithProperty("OutDir", BuildSettings.PackageDirectory) - .WithProperty("Image", BuildSettings.MsiImageDirectory) - .SetMSBuildPlatform(MSBuildPlatform.x86) - .SetNodeReuse(false)); - } - - private void CreateMsiImage() - { - _context.CleanDirectory(BuildSettings.MsiImageDirectory); - - _context.CopyFiles( - new FilePath[] { "LICENSE.txt", "NOTICES.txt", "CHANGES.txt", "nunit.ico" }, - BuildSettings.MsiImageDirectory); - - _context.CopyDirectory( - BuildSettings.OutputDirectory, - BuildSettings.MsiImageDirectory + "bin/" ); - - foreach (var runtime in new[] { "net20", "net35" }) - { - var addinsImgDir = BuildSettings.MsiImageDirectory + $"bin/{runtime}/addins/"; - - _context.CopyDirectory( - BuildSettings.MsiDirectory + "resources/", - BuildSettings.MsiImageDirectory); - - _context.CleanDirectory(addinsImgDir); - - foreach (var packageDir in System.IO.Directory.GetDirectories(BuildSettings.ExtensionsDirectory)) - { - var files = _context.GetFiles(packageDir + "/tools/*").Concat(_context.GetFiles(packageDir + "/tools/net20/*")); - _context.CopyFiles(files.Where(f => f.GetExtension() != ".addins"), addinsImgDir); - } - } - } - - public override void InstallPackage() - { - // Msiexec does not tolerate forward slashes! - string package = PackageFilePath.Replace("/", "\\"); - string testDir = System.IO.Path.Combine(PackageInstallDirectory.Replace("/", "\\"), $"{PackageId}.{PackageVersion}"); - - Console.WriteLine($"Installing msi to {testDir}"); - int rc = _context.StartProcess("msiexec", $"/a {package} TARGETDIR={testDir} /q"); - - if (rc != 0) - Console.WriteLine($" ERROR: Installer returned {rc.ToString()}"); - } -} diff --git a/cake/package-definition.cake b/cake/package-definition.cake index fa7c55b3a..f97cb9fec 100644 --- a/cake/package-definition.cake +++ b/cake/package-definition.cake @@ -2,7 +2,6 @@ public enum PackageType { NuGet, Chocolatey, - Msi, Zip } diff --git a/cake/zip-package.cake b/cake/zip-package.cake index 8d7b1ff5a..1832e83b3 100644 --- a/cake/zip-package.cake +++ b/cake/zip-package.cake @@ -18,8 +18,7 @@ public class ZipPackage : PackageDefinition BundledExtensions = bundledExtensions; } - // MSI and ZIP packages support bundling of extensions - // if any are specified in the definition. + // ZIP package supports bundling of extensions public PackageReference[] BundledExtensions { get; } // The file name of this package, including extension diff --git a/msi/nunit-install.sln b/msi/nunit-install.sln deleted file mode 100644 index 58a1291ab..000000000 --- a/msi/nunit-install.sln +++ /dev/null @@ -1,27 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "nunit", "nunit\nunit.wixproj", "{809C00DC-3FD3-45BF-BC0E-E284F314D306}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FEC80CC8-E48A-41D2-ACBF-1C8C9FAD5E1F}" - ProjectSection(SolutionItems) = preProject - build.cake = build.cake - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x86 = Debug|x86 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {809C00DC-3FD3-45BF-BC0E-E284F314D306}.Debug|x86.ActiveCfg = Debug|x86 - {809C00DC-3FD3-45BF-BC0E-E284F314D306}.Debug|x86.Build.0 = Debug|x86 - {809C00DC-3FD3-45BF-BC0E-E284F314D306}.Release|x86.ActiveCfg = Release|x86 - {809C00DC-3FD3-45BF-BC0E-E284F314D306}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/msi/nunit/addin-files.wxi b/msi/nunit/addin-files.wxi deleted file mode 100644 index 549fdf165..000000000 --- a/msi/nunit/addin-files.wxi +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/msi/nunit/banner.bmp b/msi/nunit/banner.bmp deleted file mode 100644 index 2c7ea627c53d0e270d6e43c4f4d08b75aa36ea0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29846 zcmeI4XIKrl`I40CJ0AtPp0|+VxMAtaxfQm>k zVFWQ^L=goY{&}0-mM_)hZI1m zAq7$TmqI8rv@n@1$__1pa>I(E{P3cvFuWL<9V(8nLuL1ps65ghRov}SbyO)-8&w+B zN0&j3(dAHUYz4B4s7+R9TqV>UUj_9hR6~7_YG~k59SuEeqLEikIC#}Y<6mo|$;7(& zesVoDo7@1+r!;`$v_^28>VOv09ME!lW3-y_9h_z~fz!;UXifIRtY&bY)f~>Vo1+a` z+c}PCN7jB`D|DFWgpTuDqw@l1bn$MBt_$0u`=a*fvA8{YF7AMyOFE#}l8$7Z;NsH> zy?s05N8c`F-O$IcJNlCK^Y4NF{w^4}+y#SH^uf;oec`&YAKX?Cz>w7gF*I-xh6VnF z;eoCgzIHH11i4{k&|tW)bHk{0LojyZ2#k|$B*t$f+vJW3WFDJGla0Zo-^OF|HV;hM z?un_gd13m_iI_n)bJt|d-aQ3#_DsW^y<~f)W8U8Bn7?lZy!X$>!r(dZIp__aLkr-0 z$Qw&T7LqN3-{D2@4_%C9p*~oC#0M+Fd=U_~6f491uqt91);Mi+cI5yyE| zxR!hsab(w1uHi;X9Bw|kj$5fWa69!D?mWJQ__W)&_aq+o(&Lekei!%C?;%l>fCm|g zNP7AZ$(fQRAtfsrk0g7Rg4Ab^kfu$=6aED0&(olJu7T!7Ix=2p@bpCnGGAsQ^VL&i zy~@P1*ID3iw0Qo8NAG{KE;r8CgljNke2?r-Pv6lFk_t)krL-5WciK+r|EhVyL zy)IMMNm1Ja*YAu@<6L{fRsK5fPrhjryiN5`*(j_!0kj=;m-Fy3O_eh?>k`gWV>!Rz zB7G1X<8l=wc;^yCRl~5d#IJ4y*9J9{=o*?$w?mx&Tbitw3pj;TRiQ}MNP)PDgs*EN zKPg=jH*MB^Z}FJ=);7%1^pH+l8NGpU)ucd*L(7dPw`sVKohfT&RCPB`yq~t~ul-F< z+5~Sk5jM?rsI$E9&NWAyVnwBMIDW0HzwuuQ-o|RE%HL%ZEa$t$0`L0DjyN;FjbmPDalwr$x#}G^{(jEq`*NUw}juzJl2yO znP|jdtoMX*DFkj5U^`%M_pW zl?J19gKqo;t)+WQA-Pdl?}}bU3Zz$MYzW_=quNQze6wyaS{h8%4QyhyO&IGTh2$o9 z>y=Pff0n>)XD)ry zAJsz1ToI$B$qu_^lM z1aG|(>gt^s8^&k+3EpfccVx_6U!CBsS3+Gq@%G>zCcJ;sjZDwi*m~Yc@KzUGGuaYv zPd?U!_YU*C19?}$+l+$h3K8!SIX}AO^R-~}yti|@#Z4T~)rB`twzSXoHLR#9=jW$& zyeCK-8-9PYC|;8>FZ~QRDBsMosp#fCob~wod2va4v{t+r{~?2A17CiNpZnjQbBz!T!z?IM^)h+-&h(p zme)@E>1Nm%?oCT^srm{jtanAPA_X3oVQd2T(@|A;^W>RM-~Vz|sv=8w;f%3x{&&WH zFjOXZ8>CQ|FBfKPx?xK2Hb|i^XDgB0#61@HR-HE=N;^u@*dmUa1wo!FhZ+Ht4oe!~R-wlwMkE@6uBy z?Q%+^Sk6FrDpFuBp5nV1%@KzUPqih3+U3cvx>Go6$Z8T4;OW)o{jsA$X zG#Uw4MG9nfA$a5X153cIMn2QfvMXIzF=~Rhkr*m-uh0?QdKQ0V{*Z1vPn$82@U~Q* zLebSGc&iMuak`=Q1bm!U8>pP6(|zc5suNJTIUMH6J;|$W`T#gZjNd`{s#0K0LE_i_ z4o~ou7hassX|)GxQi#=9q~TGG$SE6cS`dXy17f5m1=i3W&MK|oJi)K6Z58=3lE?z3 zdT&VMX_Jc6RQ0f2)d=3X1X0;ARQeYWti>k!$(0nXRu{S+&I - - - - - - - - - - diff --git a/msi/nunit/dialog.bmp b/msi/nunit/dialog.bmp deleted file mode 100644 index f67e6fd33061655812d1c1e6eda0195ea996a886..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155830 zcmeI53!GI``^RUK6cwh(C6{-Q$R(Gk*R_ao&8ryK=webTnUwAsR8pb)ZPIY6dEWJ`z1QArpL6ESI%n@SpLPE8?9XSCMv~I4A?Biz{Vh)MLwSRz^l6Hd`50cs~A_SHqrJCKhBIes=~`yC(yx-(x}bdo8GO z??G_HeFw9-5**3qqgovTnfF(Qn)e^d<|iaXuDKW%Fqd9}lN@Iv(nFssm?qt^@VDoCs%jIT_A+1m{zrKAX?#dJ3G~?Nm6Y z>#1<=qo+ZGf7gZcaenL!HrIm-*nDC4GvT7{XF{{6qt| z^vUz#vL`Qq%X(i3m-oI1u3+<(PhAXG^|=_D^l1c5`!vtJk`^@E#-Tx}M zp3OJ(Zvr>wHH8}oTn#r3xCU-!^DWOdgXROTgoWu1*_)6!qwlw!ZqK)_p2AcqBRR) z$uEmw$=XG*?AIl*{MV(hV%;)WxqdnPuzn@{uwexhZdeIF{`Lc#3*o2Ve}q+k`~<5v zu7Wje{$;2!4<$= zei}@{SCl+(1&DXW+7O60@iuV(Ua_EPTw5C78pSoxHSsnOipG$Lw?=UdbWOYsgrYGd z;;m6!16>nu1EFXPiFj)i*Fe|A+dwE9Ln7W9#Wm11@iq{O#*m1&MsW>vO}q_+qA?`m ztx;S9T@!Bup=b<=cxx2bK-a|EKqwkRGK+WS4V?!~o;<2w%gRyjTxe9FV!1YocZUr^ z{DK?&z(qbS11ew;@63F8U7qj(mrKYFpnyTV^KdoBwPm~X<;*CmF9Q#-hSXG6z#!h! zSX|vWf51v~ZP{%-StmHpH{$YB^bU z@ePgKYRwzkW>$`SUfrZ^ZtmqgH@mr8&$2R`<+iNrrK`1cobjwWZb^L%(e=d&<&en^ zpg=6|RuP!(5a|HI%wB+fEN^95g;3N0i^l-;aH@R>&;iZtA}F*WWft&|3hG;x*-4mi zXWd=Mvo{UNv|CnY&axdRd~x}><&jPK%o*0>-8iMZ9sLQeKrHX^p450m;a0^ITYc{gCY4A|bSNsEE@-h_Cg zVj)jTynA4$d{2$L?6P^gTL6yRy8MV>W#PESJ%eR6!2L#dX83ud?;?H@n1dB)e~u!A zJt^_-$9m->*s3h3N&9n`t~jeCrU-HDZn6=-~;3L#I*yzN7;l``)cEbxI2PJTOu-5>Yy z#?$;&P6Z41?Zi7|*61!i@6NXFSOHZRkR_8JzpL?TYWs6Mwg1p|%P-1{n2o%_Ii_2D zA8vc3@r}Z0nd0*vVuQgR6Y*}stEutE%Q10GM|*+bZ98G-@z|SlW40OEG`>-Vkf->( z*Yeximt$Xvc-#AUJelSs-SczbewuF+&J7y-?ptNNF@ul4`1sCqDp+{=J;+Yr!Jc@d z2w_k0dE56#aW~HIVwbi*hqeq)79MN)*?U6X)@)X7Ex#7W%ex4UWX?Onwx~MU?Doqz zmR-cRh*r!Z-du#R|CGWTRt@TbmtIhS1FlrwP|!OM2djNs&EcKd7SGK#^=aD(+qzSu z2+LhNET=o(mA4yk-t5jDyWz$*nEE)tBcod7^6iXxqfDVsvAh?vJ_fq|!BztD&;9|(}eyTV1K;X_ajh~p|k$(nKW)r*e_U@n0W>3|` zv79^LRp?J}1!8#*!&eQuJ8b%KwecYr?z-88XVER$5+9)qs?B!+05)!`J1b+@aKFr_ zqB^{Z`==RhN;qw|Ut_WGx-18;!<=WP9h}E-A7(Q2cmnTEybk>du0SmBMiUn>ppAM= zTt4{;`;7&wa^8Z0&RDISSCGfYW~+JA*r0BiH)HXrT!(AcumZd*UON|`gl!*{*UZJ` z7}atIPA;4`qPzQQT8DW9t643^EFY6=UzIiJJ$`Zi7}kB(sKw4UVK)uFqU3=q5X(Cn zQVMB2E{kig6q?6rLbO6sfOv-_WSY1MlXrvJMdR6Zm1T8cD^KIw$SojD-t0_|pUYWz z-t9b3i?eS|CCfPjf43E1XmD?vrGhu6O*%?zWXxgmu0O44j7_S=xX%0} zK89PW8AfWCjzNXVJ5`tp?b16myMiA7sb#9N!RMmi?mMq<$x6!F$3t&xt2w~<)11x38INo%BI;%y`rZ9x%l zZPFU)n0OnBMO#qBTbr~-IwsymV$l{9@zy4-k&cPCkyx|^MZC31Youf1Z6p?LK@o3l z(i-WQcpHgDTTsMXo3utcCf-J3(H0c()+Viyj)}LCShNL2ytPSdq+{Z3Bo=K!5pQkM z8tIsL8;M0*P{doCv_?86-bP~478LQ;CasZ6myMiA7sb#9N!RMmi?mMq<$x6!F$3t&xt2w~<)11x38INo%BI;%y`rZ9x%lZPFU) zn0OnBMO#qBTbr~-IwsymV$l{9@zy4-k&cPCkyx|^MZC31Youf1Z6p?LK@o3l(i-WQ zcpHgDTTsMXo3utcCf-J3(H0c()+Viyj)}LCShNL2ytPSdq+{Z3Bo=K!5pQkM8tIsL z8;M0*P{doCv_?86-bP~478LQ;CasZ6myM ziA7sb#9N!RMmi?mMq<$x6!F$3t&xt2w~<)11x38INo%BI;%y`rZ9x%lZPFU)n0OnB zMO#qBTbr~-IwsymV$l{9@zy4-k&cPCkyx|^MZC31Youf1Z6p?LK@o3l(i-WQcpHgD zTTsMXo3utcCf-J3(H0c()+Viyj)}LCShNL2ytPSdq+{Z3Bo=K!5pQkM8tIsL8;M0* zP{doCv_?86-bP~478LQ;CasZ6myMiA7sb z#9N!RMmi?mMq<$x6!F$3t&xt2w~<)11x38INo%BI;%y`rZ9#>_J0rtluEd1473QRE z{VST|5WpbnIBb!9ob1%HDjF7T>l+YCSo-R<^%jmZL(gdl}dZ7Fy6-b)5e~i|exi?{lCEL3BD5eTLVJB3m zu0WE!{k*PNDR;E@=cV)K%*roVwDNa38=m*=*ZtdzZ(~T1@HwPJ>Ix*s+lTvK%iL~D zWz2an$4X^BA5wlo)AL1EAO(`*Eu*URlj!$AuM-Z92kVH7z7$pd zep0HC$_N*ZXc z!hPl%yM1x5stGZ%!Y0$5u3qtKCJ$VMOUr-?sPmR^-@ni9wR07ZVd++#rFQLAHQYLq zFE=lt5pXT3xlllvw}j|T-neAuVF?jQh1c09?E9~C^O7CmB3xPqR6v!t2dL^i-sP?v zU3g_1hYYa8eAuljZ9Z;Z0wdsBQgfkzGH(Xsyw`sC_NmO(yy+a}!EIA)ydRsDEZ!J_ zJ*)3vdC376;nFgo0*bsDokq?M^ z!U`Kku8+?&aqf80CtL2U#a?MlT-{eHH6=5M0!q9YnkyvSB^Ucxs3uO7T)@oa26&KF zq_&a*YP>D0SZV_=t0MN*n;^S@hDibUHKj!A3aIdAJKl4BJw(d9%i;s=Bz!L5xp-Ad z&GWaVRLTk{@*d~!;H*-?Bq;E^_?gvIKZ5()QYvKy6nG<*4gL-qC%{^2k>3SiJ!Rlg zZ7G?m0%h~&OneWz0a7|)^M|;Uc>4t@pOhl+XZ+EeTIwK9P~dmbi`P^g;QqFhN?Cz2 zdD}!P`8wE`fNJSQFUUSh>Je{QkMgn=d0XzQ3mBMp8{D$as((2n&%(Dk3F7TZrQcHH{a*}3 zz;c%ldEt$8Glk)meJQfMWqVdy;(S|1ygjM(TZwr)EDrgL1@f+k%dt9m;U&qN@9l@i zI4nKG>xryD3Mlcu6uUSnhDHG1c>6}252y_AKs{i!cJ`5}bH@ zIJ8>{c?%NjSQryxa9aV!_Yt^a*|tm3OKb;Tj&>a?N(V~3QH(a8_`(Xn+rqC$U$q?( z?{eJORCphb@yMV1I>3YQ9?V?sar`I%FGofGe|M$AJBLZIMGR_b8HD#;C@b+U_l--1 zH(q^v4@o@WL3lSN-sR33J50#iLHir4_qm7JwSw@jik-_($6c>+4XjO52nrr z^E@vOh9JBx_HY~?w4I86;&dUhLReFBfeNVb-oxs(63R=T*{8Jj&!VJW_fH|E+Q zo0wuR@c6ucW)*r8Z%?N3u~c|(W1%>^ULRIze6R8qV8RPOK5zUTZRfLM8sDB+x~)XK z1w(xK$T_tVS$rw+-gmIC#Ila|0*}j^f1ec9CPC8kMphsN67qHi9=^`BHVzDlH~79o zV0G{FqKeD=W-NDn()jko(ru~mK92-Tl}65{yLL*TU>(Y5ayke@HRi7SqtOda?3dmFxl<-;!9m&BVMNQm0@P zu`eY1c|+tb7p(l-xKQ2A_KX9};Z!eKrlnhk3gbU9Z!z?~VR{tDK#AfFu(kEsH4i)S z{JwZ_cBd6F?JVmIEWJHWL1|g8H!^}LkeIiSzaw^1TcpG?hIiah?iAIf<;Se%UMDTK zDXBQtjO&>)lUJY&-VTgP_=?L%(HK15fBJsD$sa{@E0(T`PhdWE3W&E~FTE)>-WER1 z+86zf3x36IPeoPN3p08t{alYr9?#aGmYr1l8T4qclul6rMc(}S=|vZqZQSPexx+dE zpMS)eMn<*4Bp==@odTudP1oOXq)jQS4Bp}j-1>@n-+!bZp7*1RbJ&l$#2w+^IM-ah zhMh6eD~NYWzydZ><$WRc^NA0eW#Nl2eBwXN@qtLW%aV9cQLV$Ud|6VsX?(ky1~kgz z?GErSu%oDh@l8(WAKUPn`YyWxRy2*;KJ4U7Y$bmDM{Rdbyd9+jFO+#_;VqJ1q5yW` z{ZBqbWS{PA^4yXYNP#kWy94}n?C05NGC6%)M;)TC>hx6K zF7fW6+D#9`uhBrXQ5@bhzEQL=CUxExf5h1=8hEnlIT-cRPAj#GF;+Q@RV5!x0R`S- z3|@nRMUv$kRlhGfS#zl19aUOt5?j#}f&yjp_Wm*4OW5C=Q3Pi7lz@ zhS-dz5EM}0?H<9cAF;!HaqKOdQcR5t@V+eEFY<{u%2BRUQoO74&}Io-NiPq_-)V$5 zMLTuebo>r$qfp`P4e4$G_*M|eGaOak*<+{8O9it2upczeK`^M6T^pVBe>YL0ug z+cpW@G``h|Ca$T-+hvo(-K`O0G>PRs{4I!mDd8sGi7Bd=s`2&)xRt|4cqG~D@Jge3 zOdD#5EyUe|L`K4;`U+9agbFC~mVh_6pOVDfX-mi}tB^;r*nR0y`A&=NHsLx@p}GR9 zyd~iEZSTKCJv>Ro`vv=T^nCX#vm|lT{#>1C;+o35CE(ThbvS!G8udKfdVpVVeINax z3(Wrh)I;H^Ok^H{7xHt0pdmZ6{mmT?=bRdfCtvHZdzz(?!3m! zO%^weZ%5g{i)469l&;|qPuQ)|kKDIbYi{&{@!n#(%|39moq>C%ru2hM-nPLl&oxrOo?VnxM_S#at1P!n;3%lQMWyn zNOxC|V(OiUZ9sUYWk3ajpok{k-`StXu*Wk^;m#Bq7tp4e?GBP$7j$yh9Q)P23Rg zGyxS-n8Z6IA=AVS@lF#^A%#i2LlQDg+z{_H0Toi1#5*J*)5Hz&P7_ceg-N_a5;9HP z5brbr6;hbQJ0u~~#0~LI6Hp - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/msi/nunit/nunit.wixproj b/msi/nunit/nunit.wixproj deleted file mode 100644 index 69f0b64c2..000000000 --- a/msi/nunit/nunit.wixproj +++ /dev/null @@ -1,51 +0,0 @@ - - - - Debug - x86 - 3.0 - bin\$(Configuration)\ - obj\$(Configuration)\ - ..\image - 3.9 - Build=$(Configuration) - ICE61 - 809C00DC-3FD3-45BF-BC0E-E284F314D306 - 2.0 - NUnit.Console-$(Version) - Package - false - $(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets - $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets - nunit - - - Debug;Version=$(Version);InstallImage=$(Image);DisplayVersion=$(DisplayVersion) - - - Version=$(Version);InstallImage=$(Image);DisplayVersion=$(DisplayVersion) - - - - - - - $(WixExtDir)\WixNetFxExtension.dll - WixNetFxExtension - - - $(WixExtDir)\WixUIExtension.dll - WixUIExtension - - - - - - - - - - - - - \ No newline at end of file diff --git a/msi/nunit/nunit.wxs b/msi/nunit/nunit.wxs deleted file mode 100644 index f7b1866b4..000000000 --- a/msi/nunit/nunit.wxs +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/msi/nunit/runner-directories.wxi b/msi/nunit/runner-directories.wxi deleted file mode 100644 index 906654d74..000000000 --- a/msi/nunit/runner-directories.wxi +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/msi/nunit/runner-features.wxi b/msi/nunit/runner-features.wxi deleted file mode 100644 index 57684c873..000000000 --- a/msi/nunit/runner-features.wxi +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/msi/nunit/utility-files.wxi b/msi/nunit/utility-files.wxi deleted file mode 100644 index b9160bfb8..000000000 --- a/msi/nunit/utility-files.wxi +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/msi/nunit/variables.wxi b/msi/nunit/variables.wxi deleted file mode 100644 index 60e95f30f..000000000 --- a/msi/nunit/variables.wxi +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/msi/resources/License.rtf b/msi/resources/License.rtf deleted file mode 100644 index 704b08394a1e130f698aea89c05c907fb2bec8c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1279 zcmZ8hUvHy05WnY2{SKo(?VeDN(Az!T-4i8FV=Vz>$o4AnV!#C75j(9C4EMRh0oKCG3VWvh=TZe3yI>aYs$?q2F%D{Gq( ze}r|fR;=FP-5a%Przh5h%O3Y72bxot54zQyVv@g z6zBh>>VB{1b_Lg0^@&eiZFm%hwHc#7KI8j1k9Jizp3Fp1?R5MJi9S6~@-SHV(0+s{ zj8>4Kh*1hCv>rtOOgB(WdN!hNLI-?(Rfy>*jgeY`G%$!sG|zCDxU!-Ppr3>Q-Gei8 zKme4x)ESE~E18``O+;!RkVT|tWnNI?nJK)CXR&vCHPPg+>D9f$m>*Xj6 ze6WLnn{t`B`RGYH!AFn^(FD9*!CBft&lV?B)<5nxk0&Yjcnc0J0YTo~NAPsBLo zXglya132nV_Rshg-qvYuMrnosI_aRB*zgbwG%SX{ou{7-uFoIhK=T{LV7 zl=-H5j2Qu1Lc#K{bWPT96PI|t3E+`d+k9UEcCa|Bo*>^qoISx8nymvM{}harCFBg~ zu1G1t8qJb)zoyv(+~Zu9SCG;jtq`=zp+dq1iV~dLz>ctFi^%akO=Rf#O|W=RP+5GRk_#1@O469_0$9hp_`#`TkfUJc+ZJ_?TjEQ|7vqyuG|!xkB+n{_ z#{gZj>V5Q)mL!0f(b5I7VL8IPIB~*dmbtl%Tu@xpUiJG%u*C(oN7L_G)+A053)hqV M(7bFE|Db;PA8ac_9832%w{P*1+697z}g9g9{0x$z~@&M3j^{+iXZv-YO1u3Z(IR%|Q zc^e*Og_qTV>F4g~W~<1BnOu&;gPT`Vd=f*vmy0b6bIo%Hn;qEobN7x;2Y0`})w#iz z^w@GSx*A@DZH?q~ydR-J5HV;Fm_oKjRyzOhi?~n#WvJWmqI9TTc~<+S-%Ws+-!<;- z)e`niQ_?H5_MRA~^VU}MDt>SBgS)q9&&&z}JeGX$i(4VljUwq7Yrpo4?()<+lP$0nM%HtodIqwlcg|2Y$C0 z(Q9$HJq$Q8mZIk4NYRU{1eyzAAn3tpMuU1`UHe??61$(RO+W1mas1-4%gM~h0)hB*g{rzdhMuy%PxG zsw*AQUeX}P^^J8xrZ10c803>fux1rJ>9HXv?}Qr`(`AlwUOlYXZF|Dk^Y|HTHJdmZ z6Fs=oGdS0(sS^+bkN2}e@f5A=XKcSdGnps;z*(1Qqr$w2&SpgwZxdEGDs8(j5FE=3 z2NK8%q}PbH7*Rl;`?;1kjO1w38J4wfpx@auET}wS{6){C8^w}&eNtgXwiRdrn|`m@ zkXPu%|Gr$mjcQ-`TOXWy;azgc8BL=bn}SpFk95y`zP$95_Xuy{XD4rO1&{rOvac#$ z%pax|DlnBRDI(@9s5mnbuUamYYF!^L$My2((VO^r#GluE(uLKtzjK<9EzG*rkO2Y{ zLtQ_nSuJ<;+>*WiPK#-V6ueH7AFs-+T6_cuU>d0(S$fKN@5L?qdQa@GPKe^CCmzm2 z6dR<5_q8(|pDHe=ii#!l8WKF&5P-0|Q3bXOV6ynt`os#H)I~(4A;b=DgXm&jwXZLg zKU~c?a@pUJtkK=%;YTm!58_bZ-#DSq zalcTM5#%4$KMMIqL(X*n&i8khcp7XOAsx?R+CMtB(!7sui>wp0p<$Y`KXS)6ps5%s zwr(#@e!Z=O@J6lX{MzZw$El#U`;e=b{l2d;WJ#}WuZQ(Y!U*e+_syc>jNdtllSDNJ z(R0}!RGj%VOzeo)xQxS3#!j=zklfT==9yaz)$C@EiP{7cnt?Zo4#3HROt#U-{wk@% z-4Crd=lDd_3CP+`(~6Y|+r^Y3tTwUWXX~CCqzb=K1eAC@U8cSs8QY@yJkV*rhHS%z z?vdYx8=8IQtB%+Ff|yhXW+8e#Q->?v$+3AHiin%UyrM{=YZv;|2HlK%Zk94vmduJM zSD6FT8jM?8?zFmZ&7n%4kd{TN#N`M@EE`l$mf)^B5i!Bnn~JUcj{fjR_oi6CVAP$W zv1ZGj=Hh71xPC2m|8yF=u_JQINUIJc;qDL)1lG&o!__h{*%4i;Vp!#F;%{BWLpnFM z#^EK)pYIPh89m-Io{m{67?+RXB`%Zy5j`90VjixznWK%Q#hWfn)c!*5Is9ZDlxG`R=tsIxb2cA!7*)%I zho_eYcYHi3gGCvv4VLO&zY%O{_8-8|g{_IHVL85~bGwT)Ur_XZI`#d^KGXknyvc1K zxh1N)D?M(xW74SSa*%?TY54|eq88te z>;}FXZf0|Y4^0hx(rK*Gudd1zD()~?By)Ny?Ry;+Q`v+1+1a$YtTrYgi?B7BbDLUH%a(cwZoplW-#juj759XL}N_5U+&B@j7YV#mfS#e6cNMA ziu1neeF+6&>Ps@%g}Qz+EYGB?I>Z%qUIrVSFHoJemmOWM{3;#K9aiMEdAY1Tn+O%} zn^Ql4vdtzK6m@=9P%knq!4ly!d^%d`b9N?0-5>x}ejyv?FM$t7Ox5CwKC=HUkkW95 zwW0~#Z;0$L$=cSGH8WJ)4}I7w>dILn-bni(<7Xm0>LJ-X>F?*})U$3yd>%x`qO{#5 zS<32kSjwUT<0LGAZ# z`NcPBuh_UFfcWTu@-HC?XAu#oeuP{3~f*8O0% z#pN~zyNVa;lgy35as?6rML@h@b(3kDW{B2^4cWW+T zx~em0EfNw9IuA$H(pE)@u6B+S)-S|yYtt7XI)nkOm+i8aD;D`UziG+gE*20jp;R!9w`Jr=zx<4@Be^JWU+qf=rKW7a2KcH5ks$NrKa zs&^;9Q&w(&PT(lUZc+X*fOoZQb9Y%`jJpZ9MIzIY;ww?UTT;;X*+0lL2RTD&zwf?~ zI>x0C9ThwCrF;ziyT5-?`sid+-2b)>Uwjgefn9!u_tNUSf1WOVI3_c^)D`;Wu5vg| zD^DVt_~oDv4!)66Hl0sWY~$f7BWDlVrV0#0NL zG(=`nPt?i*;RyJL(jR(?YF#KJdZmUwTn~}K-?Uxx12GWEHr0m~<0*StzJ-`=RNiL! zm`le}pd-cnWn6(Pv6@SV)`ez-zqYPOqgR9y0r;HL)3)e{x)sts{BC>UB93MwOAgcO zc>P#}c>S!;u#9{3sj!6(8&jY&uZBE+1iX|IAv_W@TGL1s^-?ClVM}M zav9K*;kx?Bz?v-kOySC({Yt=}eKVUJ9}YJdC!rpQitu`;pIOWsx04z3K15|U$F_|Z&r4H;{SwNOk{hn7H9pk_%D?)Q(3n=kAsAd>!`1dGVdl~7EHpWPlB56)qY>GGuFahS}= z^(OiX9&=J%0bTifAGWAVbo2zjKYO82zP>T3N%k1&8dCkts{9ca)`D zaS5im#X?p9^Fu{MPtWC0B0Yhn0NxL@yksP+ivm6pO|3tgjn3Vo-LZR}UVN@K!jwJp z!N~?aY5=rjFpRS5-g#+o+-_Iq=SE+DAFpNR@`V;kJ*LYiR$}2ZyA<{Z)x1jjyX^O| zDH>GLwuaJY9>%ee)%$i$RpgyXtNf%7X2LhUadV3|G)h+|6|VwYdszF#{BiLf)+!IF4L_x;$7*zCtC{UmGe7>FN$m?CnKKn-yOV&OomEAuVTGdjA(d`*nZs#|PFGqW*>6j~`}9woHhuV7!pbH+55M<^NB^ zq+v2&nS%7n3GX?-gG#t4)}>!@EuByoG~>!EsU(M!_onIxoSl=$2wTWQRv zaaoYy)~<GVG>6^gdBl;Q9(eKiJdA@~F7^zLDji@(r=@tWMxQgpLc3iTo&qKzF$kS^%=iq&+0Y)vitIYU3uG`>;=Z%Iv3`!55c4uQ>LLQ zrbUm_<*4cepG(T$YV?0M{DVBlZPf~OIE_9Se)dP$%h#0DKvusFkbP`^y(l#42&FKk zc7E=p`Ih;0gfWIHFCqmR`{#5MS2(3Y3ahkm|3#$e6-du6b}t&2jpaZfA5$xfJ-dj& zI3l8Z(607qGT6{Rp91-dbqddygv^>6RbWHg)_5~eG}LqQzK?}MXTte@^S@ub;a|fs z{6P*&;+}0`Zh56VWCjm@*WavkU#RI)r0>sw0z}#O@+j!)l4RsEReYbC{Kv;EH0_)B z8Heh!1N-+|WO4a7hTL`kc7Yw4&)Qmr&3SFCSzONt*bHh4B%iN&-E=CdploK$_O4uF zHjV^j1tct){z5qn3z>eu676xGOHN33G_qE_D{+Px$Z_^NLSYj-ii&Fp_D6tC@Z?NR ziJFs~QXGM*K0xDRQvdAWxvDLe|D3=xj*NmQgPoqH`#Ex)Uc2W}T2lD$H#}0ZTf7-s zToucH$o(m>{vxQ;b|mJ9%wH-;jat!ZynHa{jeva%h*9AzGdzNiGT{Xp54CFwdCMzj zaWME~ok}SuNiu-(okjDE{1B>FW~gX>pN;aR){v&*{Uh%MbYOn9!=ekg;;)F$M?T&Z zQK!47CO<5PTv+{+KyH9}33}3(JOH~KT(sD8HQ-vXP8# z8T}z%!F$iD>sFvWHJh|3abj((Y9ygFwOlR-q! zhNl%6)r@@ud1O`7y>o(BA@P>oLk6g))XWajI`9ArxNq*`AkN2966!0dU8vI-X;q4Y zB{3H2f`fFcG%ZYifPg_p?ndnQgbgIw$jI41FIHQ-)L$NTS4s)x=qToh z+3cW~puf{r$&M-T36-z-$D|bE#R+c6aQ1=Y)DB*mo__o&Lb^u10|lSOuFIsHiP$F! zHpvN^FayH^t>SlT-L~GV0!4C_5{GZ?X>)rTxj$p2l=!z9-5kp6Hwk`3GDZMq2Gn$# zGx^NI&F-f^uv^cslcU(5POnb)b0aD~eHM2=8F4^l`gPETyx5Bgs9c+Op2U&ORVcHJ z72oHIuOwxjYU3WcU!f(3^GaBuO6RGw^n1`n=s5`(wDYomg8JaU-X%s$F3UYawM@Hp zYRIDXLQ?>vNF$dUZGlVrvYq7{?)f{Mx%-o%I7;{K@>Bx3QcrUi`zm0S`tXa1ZFBkI z*^w$A9nxod*e(Q|&Jh74pYF!p7W&IjKq!mzx%KZ~B;gAAoKSPDg$-=wIsBps+D-(b z()}C=UlmD>CY{T;Y3mY{cX{pWMK0rKEyOhR=aH2PqkhVvsVXjNxiZHIK?K@l^B^6 zW9AJBd*ipD&WFzjEY_b}zAu-q$5MRMe-D>XDe_`3fb$Ozpugcan5$os<<23%GJ+C`2pWtxMuGlKAt}{w(Lv4t40V`QtqoGIeKz`$TCc}qm;9b z#q4%9R`4tD)q{6(lSAQCLQa$R6%?;>Dch))O>6FUp0SlfddFy#s|ZMZv%&cFP#je% zxxq%0)8d%Irn#lcJ|dTLTzxVd$JR52=PQ(p#t0G}bl`6(5!vrXTmxB(IuD$WZ`w5o zG$Mx>*|a`w?KW;;LvG}Ldi++%6)X476I9yWC$);a2&ZO~hMw}wCmcje=3)hzcV%C- zcNYu2=|TCMGO|YW7ljkk?4z)?j;{}Vv?wpT${p}<79Y9ZR~V)m zin|b$4F7cx?uv;ZlvUjjkoOqMjJ!PGm$m(;Z1MXg6r-B)%XEG%ueS7y^z-hKW)(=u z#38jIx=cD*R+Uc;nZ0G}Dc>fdr5&|g`l5>9r1pJKq}cE6xo}ixMTz(628~%N-^q0G z%8p=b2-E!rcpE)tc9AN}mp@J={`ucbuJ92x3|uwbP$N6@HVC`F;zx$4>RGzl$pW89 zZGi8~4Xgd1!co|dDJemNFH&GwaykGLA+q1>0VG~G;+>jG+PlWl90k6Hs&2T3V(q_1 z78Z!tN>AH{%LV zQXKzSB$85aK}C&#qWaA}V;o^A+oKb*&gkIcY@hR1!EDp6DWRMNu8lqoOY^S{A+!__ z_4}khFNkefENRS-0R4tr>THz9=}XHGGw+qC4BlIRu))%0M!%>5h|FXn2#wisW!upP zC2m1l=>`tUD$9xH1(^-9_ypRYm+{x8${z9>{IruL&9Xma&EkXZCvzcwl4j03W97Hn z<8dxO8lK`UFY-_UXJm~5ARZ2-A`Fo5-kjrmrtula#i628s8X2TP-TKQyD8vmuCM~hU-|ucM?Xd@aQSN+6shS@633>5HUwGPlDn_1CWS}7zw5&U>gSy( z&O%H?EB+=IP7+53+bY?xq8ws`Ug=IA7lP6C#$>+bO`k}1hy4Ij6r7`91)Z{yMo5NH zDBMitMEIoi!4fkAeq{uJ-)L?@rM<3!;iQtd$cZg1Xo-&3A)QZu$N0x~@KGl(uN_99y1q zp4EQSJIMzwa{Y_dJk$}zYoQ?rc{)G|Jwk*`A$bDi;|$asd%|VHWSM-vcB-T!O3kCp zez$jAj#TcD{!9*0zF=5xyU%KmX(G*G(VVE|{=ay3)NnS@JEXZre}x_|d0T zfJ+l`hGG&^ECs8L17!KuAPvwl|Kx&&H<{gqVhxdF=R>dx1czM%)HheA!|7^cfu-jV z>u11B&c@UPKG-~2as;;&^xR;j7`3Gat8`%oBxLwj{U0`T<~js9#Yhi9BMlk;)WmS3 zyUb7J#LUP1Ix_LHUAM6+ERauf-Gz3mU9BAlaav^f0Pgd(5?=tFlD<9?<^P`Lm^gez zk-+bX0f3Y0=?bQi0@Se#V+m+6N5B*%m7h!TFYj^kei2W^7A4}Xx$BhYc$4xyQbQhS z6rRbb7~~gLcZk9PdVVR}QxU#~!}Oml5%WQs1>Qf*-_WqG2zU|gfTVf0l#i)_Hx@fu zE4*0oyY6)=sgJh-u!a6iIGWf2&7M6d@OKy`5XEb0IQXgOggbE2Np=|VL0hv=HMl0$ zJQ5EnI`Y9vD>JMBP#*QSS?TX^Y$QfP4cMb9bYaxASI+*&ajv!I+oeTHisn}mway(H z?QG&19+dq-dLN~24RW@?ppG>jLXQxURCyNe3Anfwp1JxHGs)R2p+40DLIdUfMUoSn z17MiN4onV(($iEA^k4SYtTFrS`Q_BSAop=!S|RG&X&?@}6Ih`p&hxIgLZ1#_Rf@HC=Xj_%?FGks%2KQRVOH_-C!ZR}u$Y#<|^-mLQf%Km`blpJ=_n>BC2#r>SC=lcTR!KwmKRjt@IO z!h87OQ5k|Yu8buK6FztJ5oS~iROp^%<`{>eYbI$__(m?0=UGaB#tpgnU$jtRj5J+1(>fN)yTb?@%JUb&PZR z$R8NLECIKkdS32tPLL4=)v5@rZxh_TCD0=J{Wim|JiMOS${0*hscnX;5>pIOIp*(4uiS^*P`1Z%S5~!JpbT%++|=^CK_j!`Z4X+%{s#U;7tj zOjL^vvqYL@Ccs`OlX6)=D%|j9Y4I1JBy!K$vaGE6lzJn&{&Az?dP;p-?z#9JLtOEY zGT)#{Z#Jb*24UR?Jtpig?gkU``t2I`t}YukQGH!=%CLKdZ$7r+KvKo0HYVzUk9<#w z{7fUpV%%MlyKPgmqr>`4Sef2SHyz4#$0Xz~|9u;xr?%{*dh)`Bdmyt)r~tsU7tG?T zl5j(!Glc6EWIofA)Xk1EYdLVTvg*|BleDw59HoCG+0!r`;2aw1zJVtg7a&50`q@FJ z$yJ}w)pj3S6sIMQ5{N{SEI=!=FkN2%q2Wrt+iL%=@`1V15)*rj@joeiPjdJ@e_CxA zxRBkQiVD`+>AwF@q4p0UMHaXq4Fh}ET#MsL9k6Q$vA4u!p2X#XN014nzzP2j9)$cr zXj`YcmRec9G0<-em&4={1*u-Sa%S8@HbX>>W}+~!IaNU%-@>k z^C6Nm+}Z42{QRejE#)5jQD1Z^|IJGjN#zv(a=rTm>rduFe7v^12B0qK65b})aqdke z%8qWtMB##)$aRz7=fg0x%_pMynp7&jEeYdvD2@?%zxpgkGc%4lp-73BU3z#CyJEI6 z0RH=)dujmlIN@+3s7C;e^We0146(O?tJJ^dq*CL|yI=lcYhx6JVEn#l`!*CGHk9-7 zIuGk#3r8fw@Z=F^5z^LF0lX(VlqN!N{}=u|XS-@y(O*G=Y%1qv zaH7H{7ze~mWHOBZ5d`*}GX&iB@`vZ1IT5N}{@`4Fy{(m&v1NQHVpjLS@GLPBVAr zWI{%8!_1d)I;npVV(8z=xcd!f6Bpbz$U)FcBOoo{*`v?e)`X_Cbk+z(jc9gk=BJ-0 z$!NF$FrmM!KcSfGUV-yZZQ!1b{>yy z78g!VV}MmAO|qn<0`iQyzhiBF(;#}MJ!nnAXLZU9ba0cU?OtHUw)F&ipX}GFXP*EZRkq&HEW)!!lH7kH8RU-&(NXB9-geHaQ&h!5ThK_lYD) zjId!2F16x-F$7BA@3jB4W5Y1nyCMG}Jt>mI@b#s~Wpte962@O(32L8f;=MtUlk4Rw zJ1GK*dQ1?==0B6D49a;HplH|vROpt$;ia0wb@-qK7z*iIn;huC-ek`w2HKFsu`KHE z-jI6By?q#t8O{X%_v4O_+9mMX^Hn&*-*h^zT$l}!EL+pQe2(x3H%@l)lCXyXH<8%# z6SXP-@nm->k(^tU$jvNl(JlOs41JOk7JrC2Aj1B-m?+a2CJD+^u5Ryp=;>`05294K zJzlu~zu&aIIrr%O-;6lvA$7Aa<*_k2>)R413E(a5OViH3N~_lvS=N@)s*`~q+P;Pl zwRJM@M*O+BrW+tAf}_BYEtv;C^ZMqtPT|_wt;!t)2_a`qP^q1lG9&MC^GiZQUUf)N zdH8bPgU#qy-!0Ck2?cYJk|GMM0v4wLiQUb(p4 zTRO-iP9!|aDm0x4rap&w5*R&oBC-%AFNv`H2%*6FXA0%P)X{={Rnr^>+&~H8-<(a~ zD+bz7oEM$+zJKP9!@m4u9tU}H)3V?ADW%;J$NIj5L-WQlukA%)1wbk@i zcW%B;S;J1eATN^t=gXGm%o%RZTdKp(2bNl4%B3Ct9+RG5iJE?RKreO;MxWVyBM=we zAW87#2iuj0X_v}8(!rY-x&Q?y0R%hbW$2e+UeZI)s|^*j!yCc}80_s=n+{aI@=AE- znrm?CrNk1Edqn%c_(xHv<#n;Y2XtM|&%XL}WviYEb~O1d&YAwF9_fQnc(!}fR*WJ# zLhihJGdPAx?qTD^(w_m-F{Idd96AnZ1gSsfM#{W7)VybUUe(xs!X4fjXhn2z^B&tV zx;#`dG_frc|8fPCDpe0wI77nzuDlgK8earkf_`>6KHTKl%X!<--6>_fftVho(xs2f zod4)k3ObN&9Vue7JV1Gh?EGrS06SSI`sIWyNZI(}9dX6IH|gbs`D#R``2klemM&=a zw$CFxZ)4ySzCMD+v=H7m&z}4z_^5WQ_orIfIvD6+Ll0Vfhrxx6utq!x?qK*RS9(*y zM^@V?&$QNcVKXN-=AYmHIKLMn8d{_avKCH#(h->VN6Y`lC|p=UPG+}b;A-qi zGO144@~-DB)Uauzsv%kVC~Gt0SK~IQ87Wa*565)=cqMyHOE%s|AH5(BA>?=vDtF^^ z$n+V{0I*PhWbUzJQH5dy!>e>NZ{B^)bL_Q&EYBT|gk@Cyk@Hwr4x14&{OdH5xeF*A zm+rJi)lW7XENh=)-v+3~`Nd96A2IfS27W4dZB)2&;`43G5KHR5==f!{;qqt7p-7^3 z#m=JBjVEA{Q3f5|>(>6wyNbL*A13nYm9oXQI(HH)Qca)FzT;irizqaPYPMquQ@o?a z@yhC=`??k&P0f#CZ^RxCWe6%QcxC=wU#`t)AvE691xp;L_Xf5f0!LDp!WTd8{h)`r= z8vfp89oj)(p+ms&xN-(kAxilzV)+Walx;ASTZM{o*^XyoZ1GX+jHJH#k@0nc-e!H8 zMfCRVqROi==SDG3k-coi#N(8evCjCbR}F{gbuum?B!i|y-%AK{*<+4Bmu+sydXq+S zuuYjaPLgpKzIeq2-_AWfKscKBr`~Zh1h~}13NxQMMN{H($G6)lVrU;_F=2d>QZc~_ zs-uS9H41uSnF#A9r2O?9Pi;qp6#DxlE||E$pOra=HQNDKkkb8(9S$<&&hKF>!^bA6 zArG|wM%1XQ-{*ddBuTxMJ`g$UW6(@!I*KaUdG&{oFwobkU*fNDN5mG_Wo?w7i`qn+ z6hAvmm8D+%5O!n(JW%bT4d?saIRLWTi`$I}_rGC%we!abgkSLr}t`Xu*V zPLAU#@+Z5W(mJx&JIq%LxK@vp`GFws{Z$vsV~Fh{3RDOFlHwRQGOc}6bBO)fm*hVYx^EpDADbkD=WF5LQ`;nL7OGR!6x5iLUqm$J2 z&taH|>|$SYTmuS+^U=v4-)|G`Ye9poMFuF&87XUgSK03pQFA1?kMu)yjzE^a=I$VA zR1Jju^h%u!1mkcEelMc~*&=Y${o9$d7`>^Vth**j>UYlgS}?tda#WLrlT#v4v#5N@o|5s+~Jg;`J&aiSxMqkIjxWP z1ixiMSo0^8ddZWV;Kh7apFuoAgxlU_jhTAMIl-3#fPTeVOI0WM^##)$Ut4Coku~-i zX+>Vru~EV1*u;??BrX-O{Ce|~9-A^K-h;HDlzwmU-d<9XncgXR%3zi}Y7-$z4g_@KY5pXxSlyzv7i-4Xv6!Urx8e<$yrz6K@JBsm zgdgj#z-dw`(vdY_GJLT|&Wg3^2RY*4dyC0#CT4E5Uan`lu*sp-zb5=QHu1E3HmsU@k4# z)lnE^pnhhiq+E4$h-=_Iqm~Z&b;;CC-x@OR<8yqdM5P8n6jtZ8zQos_WyFniE>?Q| zYa=S3k7tvZw{i)?&#@z4_gl z(M07|Z){Tt<9_M8s3X`nH4l9Fm2ZYA>9VA^*~|mk`8+fD?3vXULeC+ZfM(VOqTr z0t#z?YJyY49rlI1tha88=i^uEJ_m(t?v^5&HnTt7CCu@Q(!h)Dhj!nWGn4 zDV)B7i~HhN9aHFb9^oACLIVj1@biuRDM6`6amMWt2TpNMcTR#)Rh+t=IvEFW)Q*gOk^EskxreSivm zGD*V&R%o}nEA8Jp5hNYBp+wK#qZflOXv-H{0+c?MI#F8aDxhdu7Q*)(*Q6d(`CK)* z<=|DkMZV~beSjhAo7`{mpaf6){YgSHw&Sr?KRUzy&y!ZRSW^hfd_v;~b-ln(rdsu} zcZB@@wmNg*dg1NF2ud)b3vmn*_&V6x^;0F^nVLLRU{z#}UlZ)|U2lN|*yw0keJh_y z6}-n|b4kKv9-6Yi*KM&AaPTG|rbkR*0no?fi~GLO!>Jm>gg= z7JN388m@p}94tn<^=VH+kXeyfUPnBbRmdnpyowcB+LB^g_~i5yzqDUvpI?d8e7z|2 z{wp5Vv{C`67cCj-d_On^~=ZHtHnP1)iu($-@I$^dP~5}g-Y zf=8?}<#gJf-egsE%Mp_60c_kIY;g4!i8rG0&eHZn7Pk`PB}%5GVVW>oKAajZXw+WJ zfkdcEvmc+)Pw^^3A2ixO`LS^XLNAB7Qd1YgjE9}HQ6^pAebs32ma@)m!7xR&MN=YM@f4kUtJt%}7%+Sc$aC8M{Q1C9^Q%d{`oVR6BD%nT6%UZtmI;H= zjOB&OJbG}y_&$QYLD*lJi)|GZfwbn;){B65%JBP!HgjpA-K~EJoZr|vb3PobBy9VJ z_W)m!*$Ww?_%5Nv;-2*kt%A9nmy@g!pPg3U^h0)vDn5d!z!Z87RH;v)O) zU0M~+UKjO^5$Ol*;}_;E=sr#Jfnh$=(|YcQWu@}4cUoS-&X&0_61t}^F|!()bztwd zBH+yt%*G*nLQ7r`M0nWCx-Qr^7(`(3#GFW5W;Cq;L@@(ugkn3OH11u>uiT|fr@^U z47YA)H~VLx-vJ7qIiGQ+AB@@lB9k@{$09`oR-QjL&iqDktqDv?ljY2~6%mtetUp5@ zF%g^DzGfd;nHH}So%!OT_!Vcw_1gC4Zc~5u0tG;vas|Pm|5f9!n&L!?hnyJ7Ji0ub zlW$y3lQUDn|0_yKUf_>7JFSMw8auI{ogR4Uddy1dZ(qNU^q5Tp6lVIh=95pzY?@>x z{Vkh%;7KnSQl3*;VPWy%uKQv%vI_aNq(=b0G9MXwkJwzFkr75XYb&cU4(8q^-L{Lz_9inYaR@o&!m(kaZVMcCj(HEgx*z^Ek5p@;_>4cK8rA#LAp) zYM${vGZ^lvT3Fl7a2I;LLtt>R)BlU_9t2!qVLxUZjdLiDUyWdc$75LNmZ&Gj`D>}1 zq?`3lU3EL2x9a`eQ%CUU?IdR0Cv5zPVV^sAmVrt5Wa`t2Y_f8|UccRLRPfQ6fMoOX znk#1YdZ@Fg`F(;LK=S9 zj`uBnwxtT&71Gddexse8e>C?YzM`)}R4|~c>?ihf)KpYU!7KH=*Hr8?``gx8Q@sI8nnr3Tp4U$B3; z>ti%^Me6-j=UzUyCFVZ?iP-bkrRvU41EIrCRr{)Bz+-b@y{SGX3nrY%loA+1NKg66o5@BHRJl+me`Lf}C- z9vfv03;Ml`alvqK4l!d+_DpOlUtG=x7$_nFgh9!D3%SJCg8V1TgO_q|SJ0asa;VLv zSLegB@2_yod=4>vE?*m=c_yEIkJB03WTB`Z%4*;x>VB0DElCQoWVYLU&Yo@9j#<&m znm72neN0E55Q^q|B-%`v0V(Qr!KbJ`GuYi8hpQX(y_k9&XTwdx`QLchxxgg)A9Lxkzs_e{bwvgJuk(`?7C3F zrFlpauH+14(0jL=tl3K6traV1ETFvRrL3_FwGvF3TcrO9sW}o~sXnJ;WU&d$2>Zdu zlv-a7HhKfgTIr;?2>8H|tf~u_Ei5*X-p*)g0k&>3fOyLPYRe?Qxqv?86sDw9CRanV zddi0Y&%GRFr3~bWoHdwK*ljyYthf-+1d4>h!6<}0D+6y0NNxT4pCvjN-g^v0vs3;u z__Kp&;Pyt*%{j{wN0j(7DT+GWMEAGl|*8nhop`>T7-&M~?WdD_U|M2tI=?l^k zb|m=k3C65@T@xJWDZ~xNnTfNe^fO_uPdyBH>s-j5jnDB~D(1VsWASr7e+VB8OTR*^ zeD~H$+?8s;WWs2O%N3B!=M!fihhp&)uSL0c)#Tx*`A?Q1u8?^9%J}2-5g-tina6!qp|!lZ_lx%eNQHs=g*k8w;7(J z4!!doX{z*L#VzPN@4Sy)!op;Y#QE2{|E2^PT0C_pDqoQKOjXqE*%D{|ssJj6kt--u z4Y}Kl_dwJ~!3uG&o$Z&SU3?jaq%(m`lo3FZrdO`u_R)>m7KR0u!Mh)EYyGpBON`Pg z6h6`q2A>tPbXK?W(7JuWy)ZXG+y*c^4=rM5O`@olcfsL72~dh17Vi^|H))J+HqvYb zT4GQukUMI^9x7VeQTceI6z-y_+vmXZBl=!boAnf1fp_mTvW|pcA_A6bAF39Hty)2R zV{u5U_@W{gs^K1Yi64`JC^jXOFj0G*J{6epiIOeE%_dI#_mHmML}hdV&V(gI{84*! z`UrM36<8i3I;IP#{2d0G=&A38I&J#N_iSXEDfjzH+BWv4^H2#Do$aBC>rR z-Ddn~>bJXw;s{en|B6^SQz)jGznsMF2VJRJ-N*4 zRfF-@Hd{TOu>%3O5g}nD#l$_7pqInn~`PILgrOL~CYnGxW1VFCDDSpfkzHbBUo1GwkG1&Da^01v$Q0I@gxfcRSh zK=PdsAnPLnz~74j@_ypLLxcpN;4cLz21oRyvNQyYuZ#i{t7E|QuSsBbeHvKYng>>XF99n%%fRaH3h--h4cORU z2ewd~z;Dzxuy?cz931ZhsFMTW&*>p>e0Bt!oSy*b3p8+XbphPm+yG^_t^o4(BQSPb z3H-Vp1#WL||I@7;f&c*51OOu!0Pu(ffanSUC>{oYb`$`N-jV_{x5B{UtroBb;V7KL97P=R73;7a4`U&Iso8yj1;(?5e9A-w1C@RuE6c@kH9Ue z61Y7b{lDc4l7o0w5HAGcWkI|;hzD(<96mRR1g7CKsr@GLJ%n_k!}G&KnbN0{&PV6-uH{&{nom7t-J2Jzgg>H&Y9VJ zPtUI548R9vCQJbQ1%SW)2mc(z2WbEbPJlu~;+GCLBW( z<;T!U|3C1t0Y2OVX8=Aez&{J{*#Z7JfGK2t*(iSJ@eb zx~j}c2!kLH1f0rOQ7C66T<}c%PY5I+jouuEvKGOFL4OBq5o;96j2^ra{S5)G47&r$ zSr+Fv)Y6T>mHmywN+NtWG=QUl3@mP!4dF z{=kzU1+AP>S84wR@>c@2J<8ci5bvK=8mYr>F>a_!%A=5Rl3!a}WXYdJAO6#~lTrNDp9qA(ulj(6_|NT?K7|+f%UB=~HmC~- zBv_Wr|Jt9XrxQpPhG+d%@=1Tf6XJBdz~ndyC+$T^z|ZI>vkYD+Z~#*tj2B?ka0glZ zeLkR28Z4v?S}=JBj1y=CLK$@hJpGw(SAT5_C-jYB$kQPC3mo$*3Ot_7x3dTYEea)k zn!yUd90)J}1~>UlUIc{#%k^~k1&}%@E9?^l9I)RgA~59~VGJkL-%Ab03Z)J5;p#vT zSaWBcQNKEoOzb=u5dI_CgZT)@O%Q|z$H8C) z!!cYih>zenDEmDC2MF2kcj1`vA2C1&1hU`%j%VO^tnBx{<0kkWE&KgH$H8zqBK!US zQ*jea@86U&1E>F`KELbvJO1bP{BGy(cK_~&KmF45tDgXYlYaZ%j{xxR{e0T*@YG`h zKUwhm@AOmzL~Mb6qNBokDcw~P=&Tb3#OzFUqK|U?(nopSSx$9PcXmj?ljAS?C@&-i zbWvCzl?f0z(M3=6(BQx8p_*X|kXE=Nq#dCQ=|rkPx{<0UdgMM8MJ5Jb2R#^9I~k^*|78PF4bfu8sP=!w-pPyF~7 zJ&_zNIA$GSSGWN|Qt=R^Sq4Fu0lvdJ1l;fjk5f6R>5Vb`bwg{TJLe?ELm*jI zEOq511O+AJbuE=xkVs&!_!H2Pm@<}{=TC$LQ2u#MOKA)QFl_t^m1(@R7ecBsCIKS^JMjasq*1)mFm%T?ce&T}zOCiVEX{06vePD|K1|374g%B`wIM zJW*AV2ns3yKo1hRh~Nq!*}x-U;mnqnXJH~|EiHlWKmiijV8MF6f>#Obn#M_(GPJqHqnPxFy*{z+Zs zm5`tS1XBA%EJpsy|Ba7CN&wLk2j8qd?yHU%5eGE2=UYQf%;i0P~)CI=@5P_7@Wj;xR$)4sSSafCJ+QWego*yRwe{vBl+RB&+ zn$vs;%r9kKez*?5@X`5ol?jl*n)WN4%ufv-ECimoI1U2qoTjA==3nL~;0y>Po2sR* ztfr-8e~{0_%hNFPSHgCvq;Ejt*>6_%~uJfZ+g= zAv&lf7R>wq+i)VL!6I5P;uy#tL9kc>WD`cfdWdF#1Oxw?fan4Lg28P1Z+^ewXMW+s zv|ySrZMY1$tP`;i6b{#w3jFxN@4u5%kPy)oAg9>iL6kQCu%*ND3T#7%Wfm47v#`2Q z1G$9;g5?&@>+~mfbl8UeTV@gQ;rW{lUCLkR#BQz}BK6B=E_-4#*N@gbv6b7Tm_p9! zwx{yQBYVgb*u|4x#6qx~@mD){cH^Tzw(h6RSx{+5;UD`pY}>9Ms6DZ5w+uBwZ-?JN zts~7RcI^+deJ6J9p}CJIw(YUcBY%@YVEguOyY|Y)@`){bXMg9!ehsawLnOy$kN}V` zbdSR!x8n>b;kW`SKkfi_>VMc{kwDJ)UpeJ}?dkuYwx=_YVPcRmfZgJ+ffWsbB%q=q z08++@E%8?#7J`CPP8$R{jx&GRaFLkY%0evU7?@-%Ldsm|C)OQ+fkq@QO$YC;aG;YB zCp^u-!n`2*7xM&;=miYGwt#(*2UsnBaT6E=Tbc{DTcJ@Z1N$z46kth61fMi8AkrzH zK?9o`C(MQ!wY32)ENyK@kSoNAfauZW&w_L&ZEc|A<7jKcW(F2HBm)V7HrVlze@iFR z#v{X^(uOS`ARSQt1&^ssg$2@K!vIJJq6qFO9gvQ3LI(+_1M3JmTrL7Zrj1Vu%7s}T zNEahQW7F2A1}|{Azv{`Pt$0ycNn2Y2up)#YY%)G=7qKim4wsz5dDyH%t4smf#f;#U z{7d(MevslmG3a7qii`Xu16$;@-+;hGBrf&G1Wqe;f#~-DAhFIV3!EiIM<-<#ROZ3> zoea1Lf|64n9!Ggj3fLCJV#M1|Qm=67)#N*q?&>`>y?Lv&edqV`xKv+-{ zga!ozuJil%2l$^Je&@q6j05AsG+uL$t*!u&`B=*(`JE|7cXHOK?#y)f_b&N=nT!hY1+{+B1ZEX-5hPj*5u4}tYq zn1{eV(Z#jT(6{ejA(&tMJo*Vi=Kv$n(19Sw8xRy04?)k$AgBc>oU`i?$=Wd_uyG9O zeh0p-tz#%*=NKyAJBIoX{^Hl-I}03(VqhcH1dc^p;8^qpj>S0OSj+`{s0#3*2Ed2f z0UsIwd}spjp#{K){^8ey_<&%h1GxSTW`KVV;EMu$S%Ck)eC&Vy!v8<;3zOoZq2ZB& zo#G@A{bC}pmjs00E+??V!aiAer~MNE1PqJ~wt7r>Z$&~UY5_ISh{)j`96<&fwGs5d zh<)b7LxOvvwfy59U z2^wt4zl_&#=BXa?PxS>5zuQA}(jM@K6c-H*_q4scV4t1={IdzLj$pu6;NO1=zw=>O z7zf7vFK)+>{q*a0j0_C_kDrg*WSq)h7*p}v@tcPWy}LpGjqr?lR2{w#^*2HVzt-yZ z8&uMmEt9V8eHIDwe8-El?~V?$+|?el`nXP&4p$p@u++>asWgDEp1)-sVy2pe$jHd% z_%WK^vQpUM21lx$dq)nDC7Imgf&^~w-w3Z$;l_6R@aBppf}~>vl_XD`*nDaFtJ;W4 zM)1#`=7zSESdzD;BgX-|hZx(J$R7uBYWlY|jEwAv1&@<$zsw_UFxwK{d|xBMiaH;Mzz>f19wl@#NAPC&jHGim5J5Z&BVwz zx}tvKZ_#J5r~DY23fe?-XwQrTcuxOw;-kb3H0jazZN-#vw$)v8@=QATT48#>l1 z8^4pVHE8)cE#&9{GKyllZRbDvrtRpX@dLTJC}j6D(BV32#cJUwhU zcbFn>m$f~N&Y~QSxvr_%L=K7c4ID-JXl*Au_XcuzSzK2Z<4Uy(kGOJ!>?*TIyRwHt z7M(tntRwRf!)NPb44^?QqvB`IS$ws#@_?}qB(v3m!5XyAxAt$twoU1RIgd*4EGUI#P1zcYT|WU{&c zqCdv%j3(knC_94_$+xLH1QKZGPK^ytI5PK~`Kg|h2IVa6-s=69vleyM#Lwzs|CNLm z3Gd5%ZMR-#n{0ea1K8f0hzMND`i;3)ZEqAC`#$5w_-xK`Yq1RXK7jW?tJw1Iu0^vG6~fc&Q`MU948z`SP^u!>YDYESES}#YwI} zWuJ#w-riX6RbHb@m9J+K=g``sm+M1^WJdO6qtvTh@jXkq?Q8)RRqYw<9Gg!&?n*j& zoE;at9M9#8X+mn^=^FkuMoW;P_U5tRrD^GCA)nsa_Zc3q_D0xV&a5dYa-fe6(M%H8 zM_xhB%%vc|H4ATByHu*bdsr$-mhCvy%$c~SLf&hHzBjO-#H_t&k?y~-%ywx)GV;#- zxS76`oK-Ky{fy``q<1-9>RoB!+OvKnx6qrW3rT6e@!72FURyB9VQjKlV;G2|PcCut7@H<$t?>dD@4ytuGo^Q4U2 z8fUEShY7lX8RSoP)c#mb<1$|*&1AB++K!j$M{XU0ExbYH{&RPq{yZ4z1fOUrUtYRs zLR8$8g4J`ijAII#plP9T?^{ZBRMM>9MBnv2elOi*y&6E5L#x1A&Z<0TBfZWyMJ!{t zJGJ!r0^59ys=RCiR;l~j7p~$2$N6}_imXcc zo{`X<)%yA>+fOzJy0Kg#-JWNSrK%+z(SbyYOJ*&UM5SeNvR6zLdZ{LfE#A^0*1vDs zTP{|d_oW^ToySSa4yYMQTno_CAkFDjy%+L=lydE*&1!B(VtR#Jyf(s=h5zF-|EDZ0 z?AXCjP?wdc1no5gG(lF4@FS61EsZ8ERE<$6VPPqg*D|V`DzCQUvXxcz6IzG52Y=$2 zn?c+x(upRHsOjN|(2NVLZo60(}D%mg(O#9K6Osr zE#U#5G*4nSPh0!$DI%FEI zardMt)o*Aa96x28@pi+b-FEm;X}VdpqE}9j`EmJ9C__)u9Ls6|)u-CF!S2VwMhjnX zimDg0=^SIMcK0oxVklf>yy$`P#LN)A{V=D{Q}tFA)rV^5(~e3JD_ipNV4C zoIKQe)b0vVz8$`1b(Qr#qq$OVRv@-?x#3y?qkw%;L{?u?*kkD7t5isM`w-#%a(YB! zvP95yCVkQdmwW!GBmTVQt($1K_*UEAbcM4m(u`3=W4qJUif-JqyYZCGE-R@_WVv@LiJqePOG1)MW0Jm>n% z(?ZpG+SzLDpXWLneb64W86?F;O?i4l-=ypFoTQF7(-l{p#fk+rHbJ!_WWIs?{9-_&|JI%?*Zh5k zzWhS#+1e%DXGtHui*MT8iVDWaDF6I`fyGmR-20`v35C0Cc&FXmBX|c7k7?T}^GT!m zI54%Rc-+5aA}yP=>GVpou%B$CEa|bNvaYo8pV;dBPgBiAtDVqeB1itDHa6m^>qa($YmMZ~) z?h}`%+(>rtFEVDQhi@+b^lkB#_E;9$x^IJbRrN8U+H2`+)Y1e2Vc^~xwemy9D%xzbLS-ux}H>? zaC!$H+rDLz3+`JFQm;gK>C8W!-NJfuEmm)$tKoPR^)YkIq@uF#!3ws5bx{SiO(h*k z4-3>C53QZ68>xF@YNSBzvH0RqXFy6OzNeqbfz9_0S5r}$AqD+AYDc#5E8}$LU2NZH zjlN4bXX0FLLP(aUv>1(X3o|&7K<;0zbcX#*vro_Rou3&C5Xj%|HD64&qu%MtjeJn$ z6-FYuR8HC%s5u_avO@IP*-z6&)b4_?=cHqC4smhl;1KpDVw?BkV6C{2yQo_hN9E;7 zxkied|JG8hSu5MlSFfTot=es)Rdx4zp!%Jj?;dOvKd&n_HTR6+)<(=?R}g&IC0B~x zKr$(9k`$Sq(H?T3tu39o==1t$DcAEhRgL=f&0~Z2`h=C~+6fix8zDpP&f1&(qWBN7 z8QGCtUKf-SY>J>=-~Ov>*2VY%?Q&N&N~<{&?`ltJS1{!i=iDw-zTo2?ZGzouO*d-A zf6>^n%{5A8G^QZ7w9xu0O9crruJyyOj{+T*g9I)?oPKNLVnJm-qt%>`@A(C;)HBFj z{bbv#P2u0Y_#$b0OY0G3g_p^MH=!QKrPgPXf-?mdUZkd)#g>CT1Ec}iI&w*&ZH7X2r+6C(XC8eiiO zS59cnGgU=bk1M(tB>&iPz(j5-ripl=Bvrj|u&z1ijvt-P+Q%U7Ou2_1UpjhOz7tZ1 ze@tJFYcW3R`$_(AJ;!9I*<{d{i!!G)>E)%I)%*fedtN^xv6r7r_Vqkmo@)0SLo|8o z@;8@oc-^=9osYMrX=~2y>TaqF5#zCo}-I^WhD zB>&^0*gkG>6u5@z!{m4h}ey#VZ{)QX`|{U2?QQuPh_2no%l=vap0 z2A@R6&4f+(&C~R$K!?0MqurL7KIPI%%PnJpx4A{~BE({H#ai~~(mln`u=mIf`kyJU z)yOJ{P!ra>c9BCi9Vrg)Ed0EM?^)A7v5cv`FA^}gfTyw7jL^l+V% zMzECBO^10^4adCwf|IEYmC`4f>vYh%h=P`BfW(vmVR?EIlP8ij|?ncZ5_?;7em|F)pbpZT3~@C)RO!^7qV z@6VT6yT`q^##sYysGpTM>a<-^9S>kP?jvq8!4j#Q!=plFV>pX+^P;& zsG^kbE1Wa^Va9_Xo>^>tH-uyqdmWU$RL^sf*T`S;>-f;a)o87tpSij(wag#VOdBUq zU=FSl_H9*v)BZNF%~Cez*xnToWJ(K5Zw^Wx1gzR_2ih?gi!OM+%;kB(E`T}4Y(8=| z_0t^J{LRM=ciHsadxUM6%R+pz4>7W&C5e086YA$EGHwfQuP)W;raYUDug>B3EM`Aa zVHdbJyM<{ZTRasNH|;YUJTKHFj@D0qbF{+t{Hrmh@0395lNHsWeplHpJy>V0sj#4Q zUgkATgOJ$+ia>TkId8UTw9omtB11y?jmei{Ji7MZ(10&>y)%aVU+a~%iL^>Brgn#a zYG$N;p}i4!e54W1YI%L&#k3;fyhK?YxX6yKhO}LJCj2&=F9&P2T$zK?4A1rNcn$Cf zbvL+cR4OPOquuf5++NRt2&-fY-}t|0rI5XC-#*1^_z6W$f816*$9eGw$JFx6J^diybNT zm%k9pZXeBKx-L`GQmHnfjPOTq5|g`#?RNgo+(3|7=2leYSjmxXee!b0+fYh!)5ojm z$+DwQ3SvszD#fj_bpW?Ad0^+$s~!2&3NTS z16baMb5vvprKXdlQIu#A`X(7u5F#+b)|%jJPA<9^a{X)#R`GS#PyHD{AfMaZO^PxX z;<{q|EfY`N^&JC^ydG|$yLcCq)`yRJmK~oTF~5J@=tBZ--Fq80mtGxpftI&wA?>X1 zMOeA>vsX*FyieD#sgAyeK9!h9x7RI$7Z4UNCfbp4F|_`h)B&x-w?|3aby33UiL04 zUAS2#Gdb)u+Ht6nP<KB?GB&q)LUv}?e1-4O!Zw=}|0wA=z7cOf4(^y@-h&IIh7}fb zDNIRXc122`zIKk@cO=<){~)J*TZ>|}t!K2J*pF6FnBgbSu?DL2(o6S|Q` zk78~OoagaojKQxJ(DQmvx^Uy+hwRd?G@(UYLbL(6&b<3>vhZeAnaeZ62lFz@+io!q)ygw+uAfIF9}M~njvc+_ zJ>PjOYh+7Zw~#3R?5DuB%qJOS#0BvB!d%~?TU{61rDIoYvhWA2sF5@E zcsndBv1&ii9CE9Are!wnPfXE5^WlB=v1k5kqIWXnW)d}Hx&36TV{e5j;#gD? zGmY^-d4^QUX&|z)62b0$-{_|;*#y0Vr`4lg>>owkD2_Y1)zo#1&E0J;eT8QcMK26) zFx}6xMb4~ThC;?OB~6u$g6w{B?9AJ(q%ayYn9ud2clYT*Hat~K4(({ zPpi>EjBad*U0~w&ev^^htS?nAmGFrx-br+n{OP4l0>Qj?)*51tO-)Bn{f$q98vX+U zl~tzSxP^IV!<7(~_Kf#|X~z84O@#n7#Z~2aUelb=C5#z`2^n0Q+_ABzw-zoXqH5_l zL)4?Tsh?4K_QVQ-oBVRN&3EbL!j06GIbt>QCMGA2@o0xCJjps*M>r3!zDOSX#;qgS z8ZK1Y1uo&Jts=_3mfn$rV~zb?mFCLprX`HvobSD{nPN}OO9U%oFWh?CC-=${4G4|}tTP2}h))Dyyfgg%kt zAW-Fq!M=h~;mPKrC9ZRvpK1QYESjo07om`|e}4(1saO8n-A;GU0Hkjb#!kG0ilRq@ z-PaG_J??*`0(!Km_!`ZKihEIivvXoDCM(h|wuob)?c)q7i!L9hbW6@P* ztF@9Hy}xt2F&nl&ZFQgFMN7GWtp%JH8TWsD(*3CcR-)n=feX%v^{q}UxtUKs8ebYX z9q-Z!s}>h=DdRz-y~Yg2>GuWHs``+#<<Nk1Fs7uplh{JkgUSdt% zPCLag>eg8-+=xV0rkJXq&uMhAWpz}aUc&6hf#^p}U1=TPIBM|u;`vrls^5B25~_=o zbz!wvHWFan!^H3um)X`H{ZxXku9jMsawI|QTqFJE95B#wtWc%xe;|K=rhAnn_7X%` zS$cQyg{<04ukS62Y@kO=UJAZW#RdVzy!FX+aoE-^<3=sc^ql1LUcS|;vsHVHED9Z* zkwX_9yA?!`giL)#%$qVQUiq1K!NyBNEgzQ^7oC8E@!}>0IEL<_QJaR;D z!i7pS_RtsNOEd^O2+XCnxyu@&`qIR>5i_3GKJk^ZV^RncED1}zyME#-Qr`0wYLIO* zM;}8nAL-leUgiualYTyRh8@kJ^DN7#?v458ug~*^>@YNj%G`HXK6ClYWWCz&R5Ax!SM%XINsuX{ zKeV*0M~@cl4b7vcFrhftjHWMK=|rjYX+Yq<^pH<2owSpae6M7F;x_DYKg;#Pzal}S zFw_|hZq?AqN{QbK>e>u@#y@LEl21c|N*Mf1t^27j0wXI@#Wb|8aO#?tjoP^ysB0GZ zeA>6Z$1O{^LVh^Gm6)nh7jAMyUFCY}L~k3y@nj9xSAZ=8BaOM=K#eeR+&_A;)FQuB za^f*fmt_EniGU4aed9c#m|Nr6#YXK+qBfOwo_B^m5OzI!)|XZ2oH((ar%xkO*`z<0 zV??Uv#6<$BqM!G^6)?BcSzM>Pd-yXNzR!A@hceU#8(;iKYbnd2gQZ5mHs#>PeNJsD zwvuEtrIIgICR5JOE3@uH$qbjN56;u^*Vb8PwHamcgy9^Qe(h#|5+LqdA~HW#f3(ZK zl@gsF%`{HQvqFLgaqu;rKQnIYK4)o1KOmGZ!+z~yK|>2Bpk%j{I^eW@EOU30SuaqL z_hG#YV(jPG;8F>9h&zT(4rce>?rWaN&kiT=c+#M{m-39S#kQ_ zdkK^UG00DPR_0p~(N+XMn+rHe5$m{DgY>>e=f5R*;121SOBgE>A+q{OViF&bj3Kfz zAYM0n1QRVzMM^vxhTJo&`~s4D<)!4;_&*~g&jl1267riiN^7|B^ z5bbM9F_8rKzBz{qpEIZIk~sgNiqMz>>ZCjL?kBt421Gkj49w(sFU?TJL@sa=@BD_A7 zpokgumayr7idHbcWb55i57o@~8=iW}ncr4kY}(|Bllfr>3$5KrM-oeEFHXqWl$O#R z9k0Y)XV&-eZ4f45IYPKBQC>|;SVfv#DnEN?IpWby?v>OO<>jNw#)R4=NpK0aJQ0Gb z&Pj0poN-3J8X53|GekW~X4Z8NKhB&h>E$wU-IG4Hx6=>CTP<8RW$dlxx*P3IE_nSc z3#G9VK&`Q+!z`k?>S@gKmOOZ=Mr1+q1H0G6o5hW;pr=bFbg!Dwnc5Z`?`cAuRF=xs zA`K6GERD`JbrF>?oAn+_UUb2RTX3u8A!xzWs?B_u^(Sv~)nOD}nQV zH^6{VRr#TZl@1S$D0AgPigAV5!QvP?!3@Yg)B^FnsS9tGN|YnGp#C`;eaM4Ehr|zC zcFi7*zei2M2Buw7j_r6IjTuGqi%jMv7x$Ws{JPwhis^z>|&5e7K{whv(hnsOAhHn zPndnu9$k>yk0`R;UBD!!Y^K?7ZKWP%oOMf)m*WRmwnzUVnKZmm97 zUJ)yBCqy}h3j$ux6NAh{Ik}nzdGgR9Z2UcDQw_U1XlrEH(0N6;CFFY>IW&AG_+dAD z_LUl=ir85S3Oadu6NRCGjdASb`}#KVhhM&bdW-G*-B(CJ_2UENOf52tRY0{NP|0n1 zn|dpyOCA41I}5?Yq#@Shye2GiNRIHTb6Gyqk`A-_03Kw9C-0wI#9 zM}R5+{g0W@QhUk+m&?^&d@JibG-sXzVI=C#rH#HT;Ajb$&iI+kTO=Q?vx{ttu#B66 zQeP{zsE!a8@-~}n>S6~E%VUQ)lRXU-@w2f*^ZoKDLwwcH;IsstPGR*7b}V&-_VXD- z7OV3}R|Ls1m*C;+gNpJy%Om=Oo5u1th@ljPq`JEA{TffFo;!s^v3(i=!4Gn+mOS#)e&hGhrw_YD+A;~~H7 zw?Ms1&s1#sRc#Y;ep)S!-HwZ4>0~;r3BgCly}Gjw54#!)E-4ywY5QLLT5mKY0DSNA z@2=N;%!KGq)=ET{2)`*_lESm{>hiBYE`h#!(2AZFFdr0)d&%N0_Y|7^o!0b zP%iWUxh}5$`FM8wMh7xJ8izkrv@Gm>^p8?qhLyP#tn1BO3>+cM**c2k;+|K=u=HPa z&+FEohuhY)=vg{}Lv}d-H8=CM7tP)Q<=g-y;coqq_k-)Jd<-k>i5sn($@NJuLm3eR zX|XD8n+VXlv%~~<%`xB!wEO6*3`%y-d=uaK%lBu!57BxPT2qCY8H3}n`zdB!MthD5 zUyr34bQ_bT`<0}6;frIkx!kwFrTm6_QhFXR)2JRCyVC_naL+B#N^N>!Mj{6zmEMRxU6B&rd}j0yqeYs*)zfzL3Pkl! z5ntB1S5x*ev8h63$Jg_Z*)ICmbITJ4&Vf7k@WVN2aFvwYFi!E>(I0EhF07A#q0g5z zvS9tHO47QCjT__6bJh>|f1B}Uv$aQpI%(S!WnXsE3-qI^ifpGEZbg&R3%Z+<3{spl z9{X~G9)oO6OVIU9zE&qB{ zl9AwbkK`0Rknt4(TSbRPsqgB(+u?;q& zwltmJw>ZKhaLjVg{&3B=)AZbXWB91iN7t{O@K*O?s2f;WBOFjweg=z|_q^s$`iiIT zLwB}!J}k?8Jx*eIM7JCEmhyWO4UMa?u8Nsztla$l-I%ef_Nu9b!|aa}0^FHHt{+vm ziB^1hBM43`V0rhK)M|=6KoU!!2Q$boxTnY^eUi@qso*s#VMZcHRD(b1K072+V7oy| zG_o#phS?3;CvRJmmsw*gdi_q)bf>MXp7TLD6!+xljy%V}UANt+*RNb8bxC)woSz_7 za)cIcKlP6{6$JjnD$9@EWzrPgda9)Rl1_X3&-=X0JeRZ|sgqHb=QS$yzn;gIbu}uJ zjC-uAlPJ(m;)u?V>SgxCI~=4}j~cQ$6~U37`f(%7A+)TA>RcQ?In6}mFICQPIU zQa|31-1FQOinTZ(GkiCg(9FG}cAQqv|D@psr9*clLHWj8iYo^ToIgCleWrJ?%*cP3 ziKFL;UFxg0OhvaanRcyNHQ0%jCn_|cuT+(jy+9oD>czZCs95Y_r6kw)%5x5uw409h zR&{&pJnx?6V~YbruhSyae2iA*Md>`?`Fb#AFdkj`F+zR!=O?+zeO4?}e2JYd0j-sp z{imMLrrqSI*n@-QEqzBt6&49;?Av8w7bFeug98GowZw1i?-}pck&X;lj&qEr+0yEu zq8f9({G%8YtYQz|PGk?F)X`anznT$>u#3vBM;0&RxkEI|u+h^LoOj* zVm3vHyy%4qmCJWu-sX_69}$*a*BkGlct&F@=koPRKzgrhv{K`WkV9@(-27p7D_sIO zCBZGOBJNT4QOGuydHAKcFprFVxaQ5>PYLG(`d4Tc9G56U_|0&OLR#>wHcJhHx&%dW z9iQf=c;=YX`5-NCaV+$I;q*P+Wc>2{`LMyva_B&NGV>jFd;JJU+%sR_zekCn@oI?m zKV40aL$N4`0ktJrKIh>|QqQt`jI`gL32xx`b3bZ26XywzpRw2t&n_C?rqAUz*^9sU zlmFEv%ajZ+y6ZR;biv+Yj`tPt{P<{t-O}@xJ`g?YYq&s=UTt+W)#@nKm~;N8PGX-j z0h%#UTf`Q(&%23rveYr}E^);Ma2=5R>(a^&<%?{tPiFn#5@iVv% zR`x89=A{X;?4EzA`~0@vVP}vs_pIB*+zjR|_rP>kk(~-IZH9%e0UEJ4TD8gp9CPsx z2INYlU&jK^!}C|lt?BKgXit$zR6-xOI8=~INc%Vb5S~XATh6cQ6Z<@#FtmI?7$1P;inZw%@hM@~`pg{x_M1-<6~5je>J4TqwI3(pVa(&kKch^o$E9B|g-&mZ)A(>~>`PEoqE&o? z+}GUgI#T#}*3^-i{XvV$+x(ch{-6b&$+1gE`18YAVz;sG=WLu)(%pUe#yN@7ciu7? z*yw$D*}GTf8McSp@3h}PANR>QMZQNK7^?cJd*rHv-u2b~Jj19xo6RM-`GelW(^ZU* z`!iR#$d#h?sM3i~N&8dwYTz3&^}y*IS|P6f#b{c^F-l$Ea1>i!3q6dpbMCo0tm*_#&s+wo=#ZO_#!%itrY@SZ|jZgBmH~~RCS5Xd#HWoEh-z}M)&{WW)Vb6!# zfEN^g)p>!dip0nDPqmi#ln4)>>CR!Wg^34jit39u>wWpb6Qn^8U z1B-@+#iX(3Qen&ryXW7F<96R|6C%mt6*RSu#l^iXdTR&iq!CfU+J^3k-dATT=47hq zz11)0wo!|<-Fo-nKE6{a#Z*CT)(!N&1cGA+&SL!xsA_kN7B$`1++rNd;O@A7p1fl- zr@%R72LFu`e4K*@qaX{M&-_3p;QPexCfPP-kW5f8_@=S-!NV&fZdYHH9O3!N=VWEN zr^+w9KJ(Pz^0a@7Jh7FumxPObu^nHr0 zB71C@NL6Oq#X&q-vRv8OZ^=!3k6DhVp39qcl$xTwWI9y1wr!g*PcGnc13SO#Rm$z>t80B}$cXMDeCxVWKK|L`gF zl2GuD)X$omO2CsXNaSss17QhbC*rdaKukkYQSMtgRbEQPKIb_A--#KThOhuQscGjuMNdxmIf&da}m zT{!*cn(#=rrSUPaQkuVvJJ3tWzV$>Oy|_`3iRz^>OKc%D#Zz5sZ+yu&Oe2)W#i;&+3Evr)IJwMhGM$z zNT|{hAQ2^+L@UE*X;f9D`lz+?OoHIb{H2^L@5Q9hLepNc7psf4zBU>V;mQ2GZ?~?X zuYWnhvFY>C1n+2->1399$ie82jXU8J47m#ORrB&5k>fgEv>(^(sDId8*D=puBwBm_Rj+lm+0?bwJ+#&9tR59PnEeL9o0z-g#c_vU zHfQQSj=r)dd15$hn&e-$_0hY#_WCD3or>NW2A*fFG7PVAZ{5Y)IX_!!miNr+Jg`UU z&FUM!8r^$96Pc4$YRV04L!vFL>I09n?NwboQcNNUi zaAr<8$Hk4r8APTy@C5B$RE#R`Cg|wYneVq`-%~~URh*iNxOdj1+aZ$J2&d2JfkSaiH`}!m$;Th z1z{X!VaSMh1Xl1k3D(7szt=}ged9bZ5q5!UD+*5dUmMC>hSUV&?4kA9XNMXV?tIGHAj^@JV9GCYJ z*MHe9|FS95D@NAk38Szcop~MK{VGqq?elVldj*plEb^gO@+0lfzNvjgCdIu+6x2mG zR4@8En(g3rcfFBE$2(i8S4iGPFa7{58!F+CmgM~D8n1(JO-*g(6`bbt5$4E%3sDWo zz6*CLSF`8S+N923Ps+J`*6df5C!AdCr2OOy#gm3uV}|bit0c{xLQ3e<>Ad; z#tddErm{o9h1lQQOBV``7ngXT&{78k_Vwhs$T~)T*99FtECx4BM~(0&*bi?jn$vbF z)jS!naau9qF4L8!){95+1GGEV=uwz-r*v8^B_#2=E1h!2g2ID%#p{=AFax(2(^*o2A z7BYBjb%l9Kvro4r4lq4P5F!3pj&H&nv%@1PfXfjdZ~VRdpg-Zek!Z}?J+9)37D^i4 z&S;Z3CNp|Rs})9iWt^tAr_3s&64E|j99Xyaam{X(Ch19i4^A}JdB5Gw@$!prs-~Sh z-kYkCym#t9^f}G6fi_#+^?~)1VCYQ@>wtZ|wt*jv?{J{mwG4tMH}BQ!Uk|6oh?FF= z@#qyXr@+J!JbdsX;6>Fj?Y>cx5);|oczT(=4Ij?oFf(jE)Ad5>>;^m05)#o7U8=5Y zi=Gm?nbt+{+>j=|%LI;ZPTsD*a_1>$>1(Fp9`8o)loxk2@8#jrF84Z1<2WVd&Kj`? zyzt^UaH$^Cj#&NX5-OXX?K>Sq2{rf1XZs~D_QzsYkUbc1=LXu^qUrgE!QL;oMTRl) znU$W*1II8XIBVy@>jR{}#3r8gaUVK_!f3aQ;<(Vfs?Z}eMBtRh1J2EQ1(_U;*6jyN zGHEVL0%N2!WRi%amp#HU;hrJr8OTn;;&IEg&l@4RH(Q2c-hiWERo|L%O~Y49swM+j-VoRKQg;VMm2w@cqyyXJc<^B;JyEP z-?V3q>2iqk`#{O+ls2Oatpm-z=)vGbKKWX!LXqL+>Y`lWfi@Xn(@83`C0}s`ZtD?` zBOl&6;uWSHl>7cZFx`E#pAPWTt~itgr+8mv5Z{QI+*T^<+%_4HniuXEUDtYm5t1Rl zbz_I_kX(?;bjmG(oswKmUbYUSD)HJAI>G6$mX%@!3#Cuh_I~UMKNwQ1lR7$HJUdlK z*%C!L3{EB!f|JRina0z4i<4_v7Kg7w*vuT{OdkugnqI>IKn z7#Sa*WK8K?a3l>`P%+YwQ`Kw;eIg;kZB8Ur;3@yOsz$gl+}qW(2NgF%X6Ch&LWTRv zFjV9%S9nMdSe%(Y0!G;@#5tr52x(-W`(54=Q!~q_E)Po z2hqDV_B5`(Hm&%+${?IUmUTaG(ZxI5P%g{0lp%%dO$|oyF0p?6rZ5lD(IQio)JTR@ zfbtOIQ|_TGmOkQSWa9aWm*gvPSaYpB3g_eq?r2uKoXKaO+)XA|cA0v8>rMktm}>dn z|0(V|z^Xc$_JJ5hide89_KGoypHV~Xg2n>2=x2~($4W68iBS{9h^SbxH#9(ye6hC_ zK|}~D7Ssd-wg`wsA_|Bi2zdT^m%G>H+ye-x|31&&-FtR-X12}F&h9zmx!w2hgk2}= z?K}9S!4%6gc?7hXzH35vs?CL`?lq>{e(yQ)+}bPk;%el$1p4W%SnRVW#3#AdY#R6e@KIm-6&gg({eIode;$p!5)!mA^5&3fUJ*4zOgpDF)bEqKV9WSFhSVC7 z=diG!-s-B3o2L6_g$E`2M+7=q#XMit;$l_#_)-)5XST7H2L{vYQo&2Z{>|Gv(5m;$ z{FOg8>ooSGbn{d5erjU$t!(qSZMWQ^LH2S*k`%@cun7_ANYRM)ppuM*Cd0K$lG*3 zs@;@RYkEvtyM~SkH*U+l{E4%jWYF8LM$V?Ms*Ol<_@L{~!A~q!yWMf=cq~6lo+kA7WbXzwzhBK^OeZ2K@SkyxuIq^Jbd=eEY(h z-;J)dI3UEhr`|`idg?Xn)xgMoO2(X%7BN*%(mQs>KNWTge>~Xbi9@dGjnBiLXLj>A zay9bOA`iPDpPTZn(@|U2X0}TCpINle*fk!#|G2f&!zJyWf0cH%+YBI+UCsI)nKSV6 zuKVT*hkA{CY<%KU65V&SKj*XPuh=}tcOR^3^f=9Q?!jcY{=L>DZrAS$%Sz2@?1!omK-=o~uu*r9vxd50Yy)biaE)^0Ox&6DVAxkZ<9ozC?v~zV z%A=iaqyLG2-`PH{kxhdcT{p}-*oai#%g--g+v@M^<2fx>8Q(bCrhVjxn{K3?_;vE* z32}W_IQty1aCdoaN&BANv~?`t%sbBk#sBF~!X zuMTM2>vnF?lD79xMm*~}a>DV?QeFRX_VZk~wHbXK=y>OeC%2vb+x+8&!L{`A{x{e1 zPXFy~wq`D=*Snp?<+z7OSH;GUpA_$47t}H@vqdU~#hGU2#`iiOG2iIE`J!X@QPW&= z4=mn`dpc=*LPC1=EHqhKFaOqO_3hlJuI|+#Y`KGepW(-n4p|u`EQ_`py3ximrw_fK zm1Ms!z-;%GR`bufZE570`Y5k%XzWAJdZEiq3|8JW+q-M}`ntbcPTVDZlfLDky{UnI z^jFEd77wg`%>DkjeYFZ!T1BokH~M0wv&G2ockE2Ju#BB}C18`|R1d%MXK|fADX?@VJZK)jaBN&)H{QOtJN|KCJ; zo!p0ULu2kFkrI$zDXr}t{6)6gvS8D`*S6dX`{cGsfO+`Xy4$wP=l{+cWFDkzs<(z{K6Xt?W}9?N^u z-2OUv{M&CIB!g=F9R0&yZSa;^gL|nO8mitn5}boV&f>wL{}6 z9qP}XIku|RoahldQuXxxmnO89n))Rh8Faed{^Zpmenyi|#a!xNHN|^_mAl9E9UZ?t zGG~OBNzZvR7WnLtZ>#2=r-^#nZZQ03lmMf}6-i)%Cbt^gI_9c_* zJzrSui@Xru;rbW1*N|e;V8SrB*$0~6-LZB}zb7rK1zD_*y1IXB?bFMhEvL4)7}Wdo zo`* zbz}FMzw5dEI_zdtM~`viKHMXHV;J0aWyZBNLyUZ^L#|9}Grzfy4GG?B+*h?y=$&ecDN%U1jGy3%=WIym+g7 zK%H!ZN%qfLEKiD>@U8ogcMf%;Bb}C8Z4zz04_}!7Zogle8_Z9d^K&#x&R*vovA<<^ z_f}P&dxY)iceay3vh%OkLng#LSkT|<%qV&dy86=%4mF>x?hu-I;_&X=Ha}VUUGU85 zI(X^$ZB74tm~$L2EgLVkI50on%kt+*y*Ewj5dZP5xsB7mzmggFA0vmp-(CM_ywwTo z17tlfY+ABk!;HJRM?-22F&*rr@BZtB*n@R#c6J^8JlW^!>J`PJ{Ktm~Zfo{{ym z*o8moy)((|Oii1F$tM=~d{V!;-Tfxn(=XQ_DIJ4WbpPCWZK^-jJKSdQ-4+ApMaRZ_ z;I-^8^Sh+p8cVNbUoon^z~e#9wl1O5_lKD7 zks2(W8Mx|;1>+hA4qv^(X2s}fvj*<^dVqtxWO1gy!F*5sJ?`O;=v>XX^U$PsL-#ib zHTOL<><5GVAro?DKRVa+@x0R?{t}hmq_^>BJ*wE^y%u=n zw7PWBgFf4zt}Lw*8ckN7sip%M@PT7Q8!9I+Llh$%nhcc z?n;U6D=Dl zJV{|P`KRP%jYm9LleUrK_C^k53*-OOO_2;uoFx~cO}K)HM+ab>mbvo3uXE=6#M$)tNhUC2W2wx zBh%-dRqJBnBn8$TB^ketdQ=}PH6gmMDQTzANy}s!NR?bN^COdaAel@CF^@Q&sX6)# zqfBO!)anseeYDiLGIb!D-y@$Q(%yJ#ZJ9m=Dbr`7wd9Z)A~`rJKqfOxa*G-D)o5YQ zkT!Px=PWPEa0ggHr+3Np*-K?vjMeulQitkJQX?vxB+^bMrDXIycmHOk$z%pfCNow_ zyk-6;vsYBsQ>M>Xa&R)4Zj$M9;mWcp29ES?wQ@L&J)-+Bk#_qv!{nU29EJ<+MJB1_ zhLcT|xXS&XK94T%2;HphPckPZlW8p_oVfDL2h1Ol_Rm)jmGcYo8SPn5Gu7ZL@s;~O z+J{VHF&x09QwL`$-N_7>41Ob~zs@+BK9kcj@3T6fA5_NDe@m?c z)>J1c5j1%QddT#tJZ-;b=RB6XFX;HjXy@%fX53`-F&xGzXrRyAbG*r=t#JfnA>hzw z^I3WE2fUEuc+=tOYvCel7I#`VlHtcXt=b3F_FN{wx~X*FcWQKlpU(SXw)|D#Oc`@g zEsyw&KCj677G;`Dt)rSBV@K)so(bW3)7ARG#8H~U@sNo-WpSB4*UQUmLox*@lj%Bb zUNUDVn~r%$o6h0!u!gg~Os4O&`N`y+3>Z9}hZr+T2N{H?Gnr4(3DovcJbmnj&y~el z`|@-i4)X4!ldpuel;I|`ggW^FgTvz?nHbc`=j2y3el&SbiR2qPOXl?a1usvg2U&SC z=_iv(LAGY{G%=*lIP2okj8icmkAn{@9boWuF&wwSp5e|X8sH!=3F(F6{=vveaum|! zyXncS44J7A%jm?(R3pnL{y>HhVTo}v_b6s%aljDsDC2vg0$Dz+`^D0T&+|T@v9p<{ z(dSCFIQX3BY0B_z(2suLP9b}5)Y9)Ho#M1j+U&%$%VdJHc%2n{thYfwWIpb&x8QVA z7$(rK-II_BPL>^Vm5^68{x;|b4fMHLMicfx0!_&R{m=Mw&O6D>r>8CS$iouqMc6bpV zr~g&t1vUD~R8KJ+BJ7IN&*ceK`l%BL$7>ruWa_p=`bA#C8kBy}24Wr6;(X0k9Y00% zYxjtioxfy)phSAegfAys8!()wlOC^?B` z$m}sI0-b>PTp2HkeiSA%%(C558~MGNO__d#%rSE^wBgg-Cvx2h)l0P1(X34Nv&VA1iAKtD+W+EMV+H7k%t7;LnIZIE3Yv=jhhBs0SrmB- zp)=<6BNXLl>=U^oQ`yB}iulM3H_ygG?2|EGA=j7Xuy3e0!LUTwzysY3cg6PIr1uqR z*U&#zq>W5&v!d8bV9z9`@i?AC2gDtj^=A3dZe#{UCbQZ+NgEav7RdjL4lYyo0^JPX z%R|q{aUxXPzc9VBlk|Y+qfg~)_wJ?fL^7!^lL>Q~Or6WM$5kzc2YXA%_hjar>2I;N zwQ;j3mWSh0hR`p;VGj>orI4Opk}in!%Zr43GMPr#!hn88OnZ~@kt=PrZKtybA>F-l z?C}3)6D1R(OS_gqAIbHF&@Dkf^CsyA?2qTZFOx|=)^6BGDfakxOKN*w2ZCWQ(ulqz zQwaojBy$V=EWL+=XNfIFVi0)g+o_~ywDk!7tdSI{}%4gkc7IZ$YeC^Fp|mb0sRg8Lxa0llXZ@U5qcEXzm2D^%r-pP!GtEWcs~Korp*abFZ2jTB;cUl zXm6Ysmvz=9;#J0RUJn?cK{&$>ryA>wNgAsoK=%o1GxRYW$}3@(_doQ&aDWM2A8?SK zW%$chrx?vuf_u$%hYQQge{7dp7sKDztD92iQ7j#M6s62OWLZBBj)lNMxDwbIg@y>C zXDI5WJhv6^c{r^7ObbWn4hxmV-j_R3zY9IA==eDd<53#?8onlv^euvs`0wB1-3(So z=n1*QoKyA##wd4+GhucwA(8Gt!D;o{&eAr9p7=SzVmuYPHS`nakrMuv-`2D8_))?~ z-gU=(wdu&KERT5$*w9~T(ozY(9`3^NytnV)lw)q3;pxS~%HjtO@GZ;*jt^^A4M*u? z{vUgE+@a(D#qf3D2hRi!4wv}srzGbN!+)$JaNrv}T?|G1*kg%lh?mv=$b)eMr(LsX zeZpKsT6yr}UYaicl?*n1)cU_9{Jj0KR$v{|(F+o7C@a?g@cI2Z_;-hI*Wy=eAHaYv z066%%g!b3)db`izRuIQ%tZg${PRD#mCX^g zHBPKQ5aI0a)9-r7WO|(4p%L{aINRYkct|G88Em2N?sIvK)<%Ei18;zvwR?t6d46_+ zbTTiFK-EE4q^5V`XN1F73@Cz4vYdwfNu2*e#z7z4dz7Mc{sSF39CZI!Pp560pw-86 zLdYDgsvZ^R8IT{)_WWKGmqk0 zc)$yo{6V_5%Fuvy5b_6LY3~phoe}BCA0PwzM8f|NG(i4BIkW@jQ(5PMD8oJa+xL(+ zt2bZ(mL@G(ydU+l>Mvt77Q0biyqCe+0et}n-4^Br<`L2`@4$25`PmK9aL$kM3x_u6 z^=9&?v-HtROeM!OG_ZC+pW%K0<^>$ixz62)E_UXdnsJZuEwmLJ>I}I{+uu%7I^^n- zG3ush`htxI)EDyx`~f@)w21r$aje~tACQO7F_a@8Ss8sL+kZJpe`sLweq^#5bf@44 zT&7awNvO>Jt5}O*|4A|;trt59M;gb2F{v1r_aVz6uX27DgIAe9B(LENpoWw53Gl!p zj%S>omj}F@X(}_-0cl;JN+3N%Q>YdaNzD*Jnhba^lGC`C%5Xd%!U#?IM1lpFfX&nu zgp1)M+|X0t!23VsE2^i^oQce!wm43G1D$-f#>cZ~j?#42K7^w@&Ulnmm*Du3&nGYA zo@^^ubGhd?otQ^lm}B|0U*86u;j7>P#92b`Ps14` zcS@NMeU355^3wdkJi++J7@VBE(q3*v0&yweKiByMa2w zb)3_-Sl)`j|H#vl!P4wi*__9`gj1f0i10CIIi01^4;XNON%^zLXTJ)3VV*KsfdQc( zW&0@LgJ0czmG%+O*ddE@r@U{++ecI11e$9#nHqOB{<<5-WCwoc4*GHDNcnyO_!ambV3MDq(G%q3E(7Eoaem<( z8}~tQc8@;A+yrg7t9&qKFRPCR4udz)yEmI(;Jfsk%`ePhu7`mR;6`dfh4=&4-GT2i zoLtWW{#L{z9;5GZpDgOHh;rcoZ16Vf?_`UCGfVq9TvgZIKC4t*QO1M-0HYUnO;ec`yo^$~pz zz9-&E;Nu?8kf!NN(4Ae@`p9vx^`>Y|(5~;8gM58Q9CJp~UU;whr;6Np;QX8B;4cI# z0eb;>#4#@^oKcp1@UXmX3R3GbC@jO4u0tR3KCSXGj zt4Y2Y`SQqN#tu?@qJJ&A*1LxWeDr>y_$K>*>z-IZcA<4%qEP6v39p!4T&Q3hqvR{R+Y!4rk|z}P)T zJX7YQalM9~T~j{Cnl0XCJaP3GwvH?F(f>H|SZj*GMtPBEmr|asgKFh3-afB|3BCrH z%6uB!s`O($fIbT{3*PI1E=Q$&9QEBg<#X_A)E~Mh$W3s`o From 862955df03c927c1f62b23301e556ab5b18c8e6e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 3 Jul 2024 09:04:00 -0700 Subject: [PATCH 071/190] Eliminate .NET 2.0, 3.5 and 4.0 builds --- NetFXTests.nunit | 8 +- build.cake | 104 ++++---- cake/utilities.cake | 2 +- cake/zip-package.cake | 23 +- choco/nunit-console-runner.nuspec | 48 ++-- nuget/engine/nunit.engine.api.nuspec | 4 +- nuget/engine/nunit.engine.nuspec | 54 ++-- nuget/runners/nunit.console-runner.nuspec | 61 ++--- .../nunit3-console.tests/BadFileTests.cs | 2 +- .../nunit3-console.tests/CommandLineTests.cs | 14 +- .../MakeTestPackageTests.cs | 4 +- .../NetCoreConsoleOptionsTest.cs | 2 +- .../nunit3-console.tests.csproj | 4 +- .../nunit3-console/ConsoleOptions.cs | 4 +- .../nunit3-console/ConsoleRunner.cs | 4 +- src/NUnitConsole/nunit3-console/Program.cs | 8 +- .../nunit3-console/TestEventHandler.cs | 4 +- .../nunit3-console/nunit3-console.csproj | 2 +- .../mock-assembly-x86.csproj | 2 +- .../mock-assembly/mock-assembly.csproj | 2 +- .../notest-assembly/notest-assembly.csproj | 2 +- .../nunit-agent-x86/nunit-agent-x86.csproj | 4 +- src/NUnitEngine/nunit-agent/Program.cs | 6 +- .../nunit-agent/nunit-agent.csproj | 4 +- .../nunit.engine.api/nunit.engine.api.csproj | 4 +- .../Extensibility/ExtensionAssemblyTests.cs | 2 +- .../Internal/Backports/TupleTests.cs | 19 -- .../Services/ExtensionManagerTests.cs | 8 +- .../nunit.engine.core.tests.csproj | 4 +- .../Compatibility/FrameworkName.cs | 232 ------------------ .../Drivers/NUnit3FrameworkDriver.cs | 7 +- .../Extensibility/ExtensionNode.cs | 2 +- .../Internal/Backports/Tuple.cs | 32 --- .../Internal/Logging/Logger.cs | 2 +- .../Runners/DomainManager.cs | 11 - .../Services/ExtensionManager.cs | 12 +- .../Services/ExtensionService.cs | 2 +- .../nunit.engine.core.csproj | 4 +- .../Services/AgentProcessTests.cs | 16 +- .../Services/RuntimeFrameworkServiceTests.cs | 4 +- .../nunit.engine.tests.csproj | 4 +- .../nunit.engine/Services/AgentProcess.cs | 4 +- .../ResultWriters/XmlTransformResultWriter.cs | 6 +- .../Services/RuntimeFrameworkService.cs | 6 +- .../nunit.engine/nunit.engine.csproj | 6 +- 45 files changed, 192 insertions(+), 567 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core.tests/Internal/Backports/TupleTests.cs delete mode 100644 src/NUnitEngine/nunit.engine.core/Compatibility/FrameworkName.cs delete mode 100644 src/NUnitEngine/nunit.engine.core/Internal/Backports/Tuple.cs diff --git a/NetFXTests.nunit b/NetFXTests.nunit index d7c02b751..ea8bbd140 100644 --- a/NetFXTests.nunit +++ b/NetFXTests.nunit @@ -1,11 +1,11 @@  - - + + - - + + \ No newline at end of file diff --git a/build.cake b/build.cake index 42104e5a7..0abb5989b 100644 --- a/build.cake +++ b/build.cake @@ -18,18 +18,16 @@ BuildSettings.Initialize( // Tests run for all runner packages except NETCORE runner var StandardRunnerTests = new List { - Net35Test, - Net35X86Test, - Net40Test, - Net40X86Test, - Net35PlusNet40Test, + Net462Test, + Net462X86Test, + Net462PlusNet462Test, NetCore31Test, Net50Test, Net60Test, Net70Test, Net80Test, Net50PlusNet60Test, - Net40PlusNet60Test, + Net462PlusNet60Test, NUnitProjectTest }; @@ -97,35 +95,23 @@ static ExpectedResult MockAssemblyX86ExpectedResult(params string[] runtimes) return result; } -static PackageTest Net35Test = new PackageTest( - 1, "Net35Test", - "Run mock-assembly.dll under .NET 3.5", - "net35/mock-assembly.dll", - MockAssemblyExpectedResult("net-2.0")); - -static PackageTest Net35X86Test = new PackageTest( - 1, "Net35X86Test", - "Run mock-assembly-x86.dll under .NET 3.5", - "net35/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("net-2.0")); - -static PackageTest Net40Test = new PackageTest( - 1, "Net40Test", - "Run mock-assembly.dll under .NET 4.x", - "net40/mock-assembly.dll", - MockAssemblyExpectedResult("net-4.0")); - -static PackageTest Net40X86Test = new PackageTest( - 1, "Net40X86Test", - "Run mock-assembly-x86.dll under .NET 4.x", - "net40/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("net-4.0")); - -static PackageTest Net35PlusNet40Test = new PackageTest( - 1, "Net35PlusNet40Test", - "Run both copies of mock-assembly together", - "net35/mock-assembly.dll net40/mock-assembly.dll", - MockAssemblyExpectedResult("net-2.0", "net-4.0")); +static PackageTest Net462Test = new PackageTest( + 1, "Net462Test", + "Run mock-assembly.dll under .NET 4.6.2", + "net462/mock-assembly.dll", + MockAssemblyExpectedResult("net-4.6.2")); + +static PackageTest Net462X86Test = new PackageTest( + 1, "Net462X86Test", + "Run mock-assembly-x86.dll under .NET 4.6.2", + "net462/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("net-4.6.2")); + +static PackageTest Net462PlusNet462Test = new PackageTest( + 1, "Net462PlusNet462Test", + "Run two copies of mock-assembly together", + "net462/mock-assembly.dll net462/mock-assembly.dll", + MockAssemblyExpectedResult("net-4.6.2", "net-4.6.2")); static PackageTest Net80Test = new PackageTest( 1, "Net80Test", @@ -193,17 +179,17 @@ static PackageTest Net50PlusNet60Test = new PackageTest( "net5.0/mock-assembly.dll net6.0/mock-assembly.dll",//" net7.0/mock-assembly.dll net8.0/mock-assembly.dll", MockAssemblyExpectedResult("netcore-5.0", "netcore-6.0")); -static PackageTest Net40PlusNet60Test = new PackageTest( - 1, "Net40PlusNet60Test", - "Run mock-assembly under .Net Framework 4.0 and .Net 6.0 together", - "net40/mock-assembly.dll net6.0/mock-assembly.dll", - MockAssemblyExpectedResult("net-4.0", "netcore-6.0")); +static PackageTest Net462PlusNet60Test = new PackageTest( + 1, "Net462PlusNet60Test", + "Run mock-assembly under .Net Framework 4.6.2 and .Net 6.0 together", + "net462/mock-assembly.dll net6.0/mock-assembly.dll", + MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0")); static PackageTest NUnitProjectTest = new PackageTest( 1, "NUnitProjectTest", "Run project with both copies of mock-assembly", "../../NetFXTests.nunit --config=Release --trace=Debug", - MockAssemblyExpectedResult("net-2.0", "net-4.0"), + MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"), KnownExtensions.NUnitProjectLoader); ////////////////////////////////////////////////////////////////////// @@ -256,8 +242,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), HasDirectory("tools").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins"), - HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), + HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), @@ -266,8 +251,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { }, symbols: new PackageCheck[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), - HasDirectory("tools/agents/net20").WithFiles(AGENT_PDB_FILES), - HasDirectory("tools/agents/net40").WithFiles(AGENT_PDB_FILES), + HasDirectory("tools/agents/net462").WithFiles(AGENT_PDB_FILES), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net5.0").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_PDB_FILES_NETCORE), @@ -317,8 +301,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.ChocolateyDirectory + "nunit-console-runner.nuspec", checks: new PackageCheck[] { HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt").AndFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.choco.addins"), - HasDirectory("tools/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), + HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), @@ -334,20 +317,17 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.ZipImageDirectory, checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt", "CHANGES.txt"), - HasDirectory("bin/net20").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), - HasDirectory("bin/net35").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), + HasDirectory("bin/net462").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), HasDirectory("bin/netstandard2.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), - //HasDirectory("bin/net5.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), - HasDirectory("bin/agents/net20").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), - HasDirectory("bin/agents/net40").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), + HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory - + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net20/nunit3-console.exe"), + + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), tests: StandardRunnerTests, bundledExtensions: new [] { new PackageReference("NUnit.Extension.VSProjectLoader", "3.9.0"), @@ -364,18 +344,16 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.NuGetDirectory + "engine/nunit.engine.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("lib/net20").WithFiles(ENGINE_FILES), + HasDirectory("lib/net462").WithFiles(ENGINE_FILES), HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_FILES), - HasDirectory("contentFiles/any/lib/net20").WithFile("nunit.engine.nuget.addins"), + HasDirectory("contentFiles/any/lib/net462").WithFile("nunit.engine.nuget.addins"), HasDirectory("contentFiles/any/lib/netstandard2.0").WithFile("nunit.engine.nuget.addins"), - HasDirectory("contentFiles/any/agents/net20").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins"), - HasDirectory("contentFiles/any/agents/net40").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins") + HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins") }, symbols: new PackageCheck[] { - HasDirectory("lib/net20").WithFiles(ENGINE_PDB_FILES), + HasDirectory("lib/net462").WithFiles(ENGINE_PDB_FILES), HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_PDB_FILES), - HasDirectory("contentFiles/any/agents/net20").WithFiles(AGENT_PDB_FILES), - HasDirectory("contentFiles/any/agents/net40").WithFiles(AGENT_PDB_FILES) + HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_PDB_FILES) }), NUnitEngineApiPackage = new NuGetPackage( @@ -383,11 +361,11 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.NuGetDirectory + "engine/nunit.engine.api.nuspec", checks: new PackageCheck[] { HasFile("LICENSE.txt"), - HasDirectory("lib/net20").WithFile("nunit.engine.api.dll"), + HasDirectory("lib/net462").WithFile("nunit.engine.api.dll"), HasDirectory("lib/netstandard2.0").WithFile("nunit.engine.api.dll"), }, symbols: new PackageCheck[] { - HasDirectory("lib/net20").WithFile("nunit.engine.api.pdb"), + HasDirectory("lib/net462").WithFile("nunit.engine.api.pdb"), HasDirectory("lib/netstandard2.0").WithFile("nunit.engine.api.pdb") }) }); @@ -406,7 +384,7 @@ public class CustomTestRunner : UnitTestRunner if(testPath.ToString().Contains("nunit3-console.tests.dll")) { return BuildSettings.Context.StartProcess( - BuildSettings.OutputDirectory + "net20/nunit3-console.exe", + BuildSettings.OutputDirectory + "net462/nunit3-console.exe", $"\"{testPath}\" {BuildSettings.UnitTestArguments}" ); } diff --git a/cake/utilities.cake b/cake/utilities.cake index e48fda992..4001f0452 100644 --- a/cake/utilities.cake +++ b/cake/utilities.cake @@ -4,7 +4,7 @@ public void CopyPackageContents(DirectoryPath packageDir, DirectoryPath outDir) { - var files = GetFiles(packageDir + "/tools/*").Concat(GetFiles(packageDir + "/tools/net20/*")); + var files = GetFiles(packageDir + "/tools/*").Concat(GetFiles(packageDir + "/tools/net462/*")); CopyFiles(files.Where(f => f.GetExtension() != ".addins"), outDir); } diff --git a/cake/zip-package.cake b/cake/zip-package.cake index 1832e83b3..b10946349 100644 --- a/cake/zip-package.cake +++ b/cake/zip-package.cake @@ -56,22 +56,17 @@ public class ZipPackage : PackageDefinition BuildSettings.OutputDirectory, BuildSettings.ZipImageDirectory + "bin/" ); - foreach (var runtime in new[] { "net20", "net35" }) - { - var runtimeDir = BuildSettings.ZipImageDirectory + $"bin/{runtime}/"; - - _context.CopyFileToDirectory( - BuildSettings.ZipDirectory + "nunit.bundle.addins", - BuildSettings.ZipImageDirectory); + _context.CopyFileToDirectory( + BuildSettings.ZipDirectory + "nunit.bundle.addins", + BuildSettings.ZipImageDirectory); - var addinsDir = runtimeDir + "addins/"; - _context.CleanDirectory(addinsDir); + var addinsDir = BuildSettings.ZipImageDirectory + "bin/net462/addins/"; + _context.CreateDirectory(addinsDir); - foreach (var packageDir in System.IO.Directory.GetDirectories(BuildSettings.ExtensionsDirectory)) - { - var files = _context.GetFiles(packageDir + "/tools/*").Concat(_context.GetFiles(packageDir + "/tools/net20/*")); - _context.CopyFiles(files.Where(f => f.GetExtension() != ".addins"), addinsDir); - } + foreach (var packageDir in System.IO.Directory.GetDirectories(BuildSettings.ExtensionsDirectory)) + { + var files = _context.GetFiles(packageDir + "/tools/*").Concat(_context.GetFiles(packageDir + "/tools/net462/*")); + _context.CopyFiles(files.Where(f => f.GetExtension() != ".addins"), addinsDir); } } } diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index f9658c5c9..9135e20b2 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -31,37 +31,25 @@ - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/nuget/engine/nunit.engine.api.nuspec b/nuget/engine/nunit.engine.api.nuspec index 568d00b16..ff327573f 100644 --- a/nuget/engine/nunit.engine.api.nuspec +++ b/nuget/engine/nunit.engine.api.nuspec @@ -21,8 +21,8 @@ - - + + diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index 271016e96..54714f138 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -19,7 +19,7 @@ nunit test testing tdd engine Copyright (c) 2021 Charlie Poole, Rob Prouse - + @@ -35,37 +35,25 @@ - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + @@ -78,8 +66,8 @@ - - + + diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 45a728c16..d2398b32f 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -26,33 +26,20 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -124,17 +111,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/NUnitConsole/nunit3-console.tests/BadFileTests.cs b/src/NUnitConsole/nunit3-console.tests/BadFileTests.cs index d21a882c3..6854fd00b 100644 --- a/src/NUnitConsole/nunit3-console.tests/BadFileTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/BadFileTests.cs @@ -22,7 +22,7 @@ public void MissingFileTest(string filename, string message) services.Add(new DefaultTestRunnerFactory()); services.Add(new ExtensionService()); services.Add(new DriverService()); -#if NET35 +#if NETFRAMEWORK services.Add(new RuntimeFrameworkService()); #endif diff --git a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs index 46ab30752..dfc6eda3f 100644 --- a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs @@ -144,7 +144,7 @@ public void NoInputFiles() [TestCase("DisposeRunners", "dispose-runners")] [TestCase("TeamCity", "teamcity")] [TestCase("SkipNonTestAssemblies", "skipnontestassemblies")] -#if NET35 +#if NETFRAMEWORK [TestCase("RunAsX86", "x86")] [TestCase("ShadowCopyFiles", "shadowcopy")] [TestCase("DebugTests", "debug")] @@ -195,7 +195,7 @@ public void CanRecognizeBooleanOptions(string propertyName, string pattern) [TestCase("InternalTraceLevel", "trace", new string[] { "Off", "Error", "Warning", "Info", "Debug", "Verbose" }, new string[] { "JUNK" })] [TestCase("DefaultTestNamePattern", "test-name-format", new string[] { "{m}{a}" }, new string[0])] [TestCase("ConsoleEncoding", "encoding", new string[] { "utf-8", "ascii", "unicode" }, new string[0])] -#if NET35 +#if NETFRAMEWORK [TestCase("ProcessModel", "process", new string[] { "InProcess", "Separate", "Multiple" }, new string[] { "JUNK" })] [TestCase("DomainUsage", "domain", new string[] { "None", "Single", "Multiple" }, new string[] { "JUNK" })] [TestCase("Framework", "framework", new string[] { "net-4.0" }, new string[0])] @@ -228,7 +228,7 @@ public void CanRecognizeStringOptions(string propertyName, string pattern, strin } } -#if NET35 +#if NETFRAMEWORK [Test] public void CanRecognizeInProcessOption() { @@ -238,7 +238,7 @@ public void CanRecognizeInProcessOption() } #endif -#if NET35 +#if NETFRAMEWORK [TestCase("ProcessModel", "process", new string[] { "InProcess", "Separate", "Multiple" })] [TestCase("DomainUsage", "domain", new string[] { "None", "Single", "Multiple" })] #endif @@ -262,7 +262,7 @@ public void CanRecognizeLowerCaseOptionValues(string propertyName, string option [TestCase("DefaultTimeout", "timeout")] [TestCase("RandomSeed", "seed")] [TestCase("NumberOfTestWorkers", "workers")] -#if NET35 +#if NETFRAMEWORK [TestCase("MaxAgents", "agents")] #endif public void CanRecognizeIntOptions(string propertyName, string pattern) @@ -288,7 +288,7 @@ public void CanRecognizeIntOptions(string propertyName, string pattern) [TestCase("--test-name-format")] [TestCase("--params")] [TestCase("--encoding")] -#if NET35 +#if NETFRAMEWORK [TestCase("--process")] [TestCase("--domain")] [TestCase("--framework")] @@ -317,7 +317,7 @@ public void AssemblyAloneIsValid() Assert.That(options.ErrorMessages.Count, Is.EqualTo(0), "command line should be valid"); } -#if NET35 +#if NETFRAMEWORK [Test] public void X86AndInProcessAreCompatibleIn32BitProcess() { diff --git a/src/NUnitConsole/nunit3-console.tests/MakeTestPackageTests.cs b/src/NUnitConsole/nunit3-console.tests/MakeTestPackageTests.cs index d65d451b9..7f8978a66 100644 --- a/src/NUnitConsole/nunit3-console.tests/MakeTestPackageTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/MakeTestPackageTests.cs @@ -40,7 +40,7 @@ public void MultipleAssemblies() [TestCase("--workers=0", "NumberOfTestWorkers", 0)] [TestCase("--params:X=5;Y=7", "TestParameters", "X=5;Y=7")] [TestCase("--skipnontestassemblies", "SkipNonTestAssemblies", true)] -#if NET35 +#if NETFRAMEWORK [TestCase("--x86", "RunAsX86", true)] [TestCase("--shadowcopy", "ShadowCopyFiles", true)] [TestCase("--process=Separate", "ProcessModel", "Separate")] @@ -69,7 +69,7 @@ public void WhenOptionIsSpecified_PackageIncludesSetting(string option, string k Assert.That(package.Settings[key], Is.EqualTo(val), "NumberOfTestWorkers not set correctly for {0}", option); } -#if NET35 +#if NETFRAMEWORK [Test] public void WhenDebugging_NumberOfTestWorkersDefaultsToZero() { diff --git a/src/NUnitConsole/nunit3-console.tests/NetCoreConsoleOptionsTest.cs b/src/NUnitConsole/nunit3-console.tests/NetCoreConsoleOptionsTest.cs index 2ed45e82a..2f981b7b5 100644 --- a/src/NUnitConsole/nunit3-console.tests/NetCoreConsoleOptionsTest.cs +++ b/src/NUnitConsole/nunit3-console.tests/NetCoreConsoleOptionsTest.cs @@ -6,7 +6,7 @@ namespace NUnit.ConsoleRunner.Tests { -#if !NET35 +#if !NETFRAMEWORK class NetCoreConsoleOptionsTest { [TestCaseSource(nameof(TestCases))] diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index 5c1212d21..6c94f3fb9 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -2,7 +2,7 @@ NUnit.ConsoleRunner.Tests - net35;net6.0;net8.0 + net462;net6.0;net8.0 1685 Full @@ -18,7 +18,7 @@ - + diff --git a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs index 82c616e15..fa16fa3db 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs @@ -393,7 +393,7 @@ private void ConfigureOptions() private void AddNetFxOnlyOption(string prototype, string description, Action action) { -#if NET20 +#if NETFRAMEWORK var isHidden = false; #else var isHidden = true; @@ -403,7 +403,7 @@ private void AddNetFxOnlyOption(string prototype, string description, Action NetFxOnlyOption(string optionName, Action action) { -#if NET20 +#if NETFRAMEWORK return action; #else return s => ErrorMessages.Add($"The {optionName} option is not available on this platform."); diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index efbac43af..4ddb88b0c 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -256,7 +256,7 @@ private void DisplayRuntimeEnvironment(ExtendedTextWriter OutWriter) { OutWriter.WriteLine(ColorStyle.SectionHeader, "Runtime Environment"); OutWriter.WriteLabelLine(" OS Version: ", GetOSVersion()); -#if NET20 +#if NETFRAMEWORK OutWriter.WriteLabelLine(" Runtime: ", ".NET Framework CLR v" + Environment.Version.ToString()); #else OutWriter.WriteLabelLine(" Runtime: ", RuntimeInformation.FrameworkDescription); @@ -268,7 +268,7 @@ private void DisplayRuntimeEnvironment(ExtendedTextWriter OutWriter) private static string GetOSVersion() { -#if NET20 +#if NETFRAMEWORK OperatingSystem os = Environment.OSVersion; string osString = os.ToString(); if (os.Platform == PlatformID.Unix) diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index e3ab012e0..bfc4a9413 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -4,15 +4,11 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; using System.Text; using NUnit.Common; using NUnit.Engine; - -#if !NET20 -using System.Linq; -#endif - using NUnit.Options; namespace NUnit.ConsoleRunner @@ -165,7 +161,7 @@ private static void WriteHeader() var header = $"{versionBlock.ProductName} {versionBlock.ProductVersion}"; -#if NET20 +#if NETFRAMEWORK object[] configurationAttributes = entryAssembly.GetCustomAttributes(typeof(AssemblyConfigurationAttribute), false); #else var configurationAttributes = entryAssembly.GetCustomAttributes().ToArray(); diff --git a/src/NUnitConsole/nunit3-console/TestEventHandler.cs b/src/NUnitConsole/nunit3-console/TestEventHandler.cs index 68c716ff9..23013310f 100644 --- a/src/NUnitConsole/nunit3-console/TestEventHandler.cs +++ b/src/NUnitConsole/nunit3-console/TestEventHandler.cs @@ -10,7 +10,7 @@ namespace NUnit.ConsoleRunner /// TestEventHandler processes events from the running /// test for the console runner. ///
-#if NET20 +#if NETFRAMEWORK public class TestEventHandler : MarshalByRefObject, ITestEventListener #else public class TestEventHandler : ITestEventListener @@ -195,7 +195,7 @@ private static ColorStyle GetColorForResultStatus(string status) } } -#if NET20 +#if NETFRAMEWORK public override object InitializeLifetimeService() { return null; diff --git a/src/NUnitConsole/nunit3-console/nunit3-console.csproj b/src/NUnitConsole/nunit3-console/nunit3-console.csproj index 0ebf4f30e..5b76fa1e9 100644 --- a/src/NUnitConsole/nunit3-console/nunit3-console.csproj +++ b/src/NUnitConsole/nunit3-console/nunit3-console.csproj @@ -4,7 +4,7 @@ Exe NUnit.ConsoleRunner nunit3-console - net20;net6.0;net8.0 + net462;net6.0;net8.0 Major diff --git a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj index 34e36d39c..e91f3dd71 100644 --- a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net35;net40;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + net462;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 true ..\..\nunit.snk x86 diff --git a/src/NUnitEngine/mock-assembly/mock-assembly.csproj b/src/NUnitEngine/mock-assembly/mock-assembly.csproj index 8cf6ae646..8dbee8fa4 100644 --- a/src/NUnitEngine/mock-assembly/mock-assembly.csproj +++ b/src/NUnitEngine/mock-assembly/mock-assembly.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net35;net40;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + net462;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 true ..\..\nunit.snk false diff --git a/src/NUnitEngine/notest-assembly/notest-assembly.csproj b/src/NUnitEngine/notest-assembly/notest-assembly.csproj index 8638fa6bf..2e5e477ef 100644 --- a/src/NUnitEngine/notest-assembly/notest-assembly.csproj +++ b/src/NUnitEngine/notest-assembly/notest-assembly.csproj @@ -2,7 +2,7 @@ notest_assembly - net35;netcoreapp2.1;netcoreapp3.1 + net462;netcoreapp3.1 false false diff --git a/src/NUnitEngine/nunit-agent-x86/nunit-agent-x86.csproj b/src/NUnitEngine/nunit-agent-x86/nunit-agent-x86.csproj index cb587e2b3..6ab049c97 100644 --- a/src/NUnitEngine/nunit-agent-x86/nunit-agent-x86.csproj +++ b/src/NUnitEngine/nunit-agent-x86/nunit-agent-x86.csproj @@ -3,7 +3,7 @@ Exe nunit.agent - net20;net40 + net462 app.manifest ..\..\..\nunit.ico x86 @@ -17,7 +17,7 @@ Agent used to run X86 tests out of process under .NET framework - + diff --git a/src/NUnitEngine/nunit-agent/Program.cs b/src/NUnitEngine/nunit-agent/Program.cs index 437c565f9..99bb8d0c2 100644 --- a/src/NUnitEngine/nunit-agent/Program.cs +++ b/src/NUnitEngine/nunit-agent/Program.cs @@ -77,10 +77,8 @@ public static void Main(string[] args) log.Info($"Running {typeof(NUnitTestAgent).Assembly.GetCustomAttribute().FrameworkDisplayName} agent under {RuntimeInformation.FrameworkDescription}"); #elif NETCOREAPP3_1 log.Info($"Running .NET Core 3.1 agent under {RuntimeInformation.FrameworkDescription}"); -#elif NET40 - log.Info($"Running .NET 4.0 agent under {RuntimeFramework.CurrentFramework.DisplayName}"); -#elif NET20 - log.Info($"Running .NET 2.0 agent under {RuntimeFramework.CurrentFramework.DisplayName}"); +#elif NET462 + log.Info($"Running .NET 4.6.2 agent under {RuntimeFramework.CurrentFramework.DisplayName}"); #endif // Create CoreEngine diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index 941602590..2953aa24a 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -3,7 +3,7 @@ Exe nunit.agent - net20;net40;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + net462;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 app.manifest ..\..\..\nunit.ico false @@ -18,7 +18,7 @@ Agent used to run tests out of process - + diff --git a/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj b/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj index 2bfa72d4f..aeeea093c 100644 --- a/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj +++ b/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net20;netstandard2.0 + net462;netstandard2.0 true ..\..\nunit.snk true @@ -18,7 +18,7 @@ 3.99.0.0 - + diff --git a/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs index 4a5a6121b..87a16bf9f 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs @@ -54,7 +54,7 @@ public void TargetFramework() Assert.Multiple(() => { Assert.That(_ea.TargetFramework, Has.Property(nameof(RuntimeFramework.Runtime)).EqualTo(RuntimeType.Any)); - Assert.That(_ea.TargetFramework, Has.Property(nameof(RuntimeFramework.FrameworkVersion)).EqualTo(new Version(2, 0))); + Assert.That(_ea.TargetFramework, Has.Property(nameof(RuntimeFramework.FrameworkVersion)).EqualTo(new Version(4, 0))); }); } #endif diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/Backports/TupleTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/Backports/TupleTests.cs deleted file mode 100644 index bcf6cb062..000000000 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/Backports/TupleTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using NUnit.Framework; - -namespace NUnit.Engine.Internal.Backports.Tests -{ - [TestFixture] - public sealed class TupleTests - { - [Test] - public void Init() - { - var tuple = new Tuple("foo", 4711); - - Assert.That(tuple.Item1, Is.EqualTo("foo")); - Assert.That(tuple.Item2, Is.EqualTo(4711)); - } - } -} diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index ab74dfb51..c74972521 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -150,7 +150,7 @@ public void SkipsGracefullyLoadingOtherFrameworkExtensionAssembly() #if NETCOREAPP // Attempt to load the .NET 3.5 version of the extensions from the .NET Core 2.0 tests - var assemblyName = Path.Combine(GetSiblingDirectory("net35"), "nunit.engine.core.tests.exe"); + var assemblyName = Path.Combine(GetSiblingDirectory("net462"), "nunit.engine.core.tests.exe"); #else // Attempt to load the .NET Core 3.1 version of the extensions from the .NET 3.5 tests var assemblyName = Path.Combine(GetSiblingDirectory("netcoreapp3.1"), "nunit.engine.core.tests.dll"); @@ -235,7 +235,7 @@ public static IEnumerable InvalidTargetFrameworkCombos() var extNetStandard = new ExtensionAssembly(netstandard.Location, false); var extNetCore = new ExtensionAssembly(netcore.Location, false); - var extNetFramework = new ExtensionAssembly(Path.Combine(GetSiblingDirectory("net35"), "nunit.engine.dll"), false); + var extNetFramework = new ExtensionAssembly(Path.Combine(GetSiblingDirectory("net462"), "nunit.engine.dll"), false); yield return new TestCaseData(new FrameworkCombo(netcore, extNetFramework)).SetName("InvalidCombo(.NET Core, .NET Framework)"); #else @@ -257,7 +257,7 @@ public static IEnumerable InvalidRunnerCombos() var extNetStandard = new ExtensionAssembly(netstandard.Location, false); var extNetCore = new ExtensionAssembly(netcore.Location, false); - var extNetFramework = new ExtensionAssembly(Path.Combine(GetSiblingDirectory("net35"), "nunit.engine.dll"), false); + var extNetFramework = new ExtensionAssembly(Path.Combine(GetSiblingDirectory("net462"), "nunit.engine.dll"), false); yield return new TestCaseData(new FrameworkCombo(netstandard, extNetStandard)).SetName("InvalidCombo(.NET Standard, .NET Standard)"); yield return new TestCaseData(new FrameworkCombo(netstandard, extNetCore)).SetName("InvalidCombo(.NET Standard, .NET Core)"); @@ -270,7 +270,7 @@ public static IEnumerable InvalidRunnerCombos() /// /// Returns a directory in the parent directory that the current test assembly is in. This /// is used to load assemblies that target different frameworks than the current tests. So - /// if these tests are in bin\release\net35 and dir is netstandard2.0, this will return + /// if these tests are in bin\release\net462 and dir is netstandard2.0, this will return /// bin\release\netstandard2.0. /// /// The sibling directory diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 676667fde..2a296dbc0 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Core.Tests - net35;netcoreapp3.1;net5.0;net6.0;net8.0 + net462;netcoreapp3.1;net5.0;net6.0;net8.0 Exe true ..\..\nunit.snk @@ -23,7 +23,7 @@ - + diff --git a/src/NUnitEngine/nunit.engine.core/Compatibility/FrameworkName.cs b/src/NUnitEngine/nunit.engine.core/Compatibility/FrameworkName.cs deleted file mode 100644 index ecd2238b3..000000000 --- a/src/NUnitEngine/nunit.engine.core/Compatibility/FrameworkName.cs +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -#if NET20 || NET35 -using System; -using System.Diagnostics; -using NUnit.Common; - -// NOTE: Since the .NET 4.5 engine refers to this assembly, we can't define -// FrameworkName in the System.Runtime.Versioning namespace. -namespace NUnit.Engine.Compatibility -{ - /// - /// Compatible implementation of FrameworkName, based on the corefx implementation - /// - public sealed class FrameworkName : IEquatable - { - private const string FRAMEWORK_NAME_INVALID = "Invalid FrameworkName"; - private const string FRAMEWORK_NAME_VERSION_REQUIRED = "FramweworkName must include Version"; - private const string FRAMEWORK_NAME_VERSION_INVALID = "The specified Version is invalid"; - private const string FRAMEWORK_NAME_COMPONENT_COUNT = "FrameworkName must specify either two or three components"; - - private readonly string _identifier; - private readonly Version _version = null; - private readonly string _profile; - private string _fullName; - - private const char COMPONENT_SEPARATOR = ','; - private const char KEY_VALUE_SEPARATOR = '='; - private const char VERSION_PREFIX = 'v'; - private const string VERSION_KEY = "Version"; - private const string PROFILE_KEY = "Profile"; - - private static readonly char[] COMPONENT_SPLIT_SEPARATOR = { COMPONENT_SEPARATOR }; - - public string Identifier - { - get - { - Debug.Assert(_identifier != null); - return _identifier; - } - } - - public Version Version - { - get - { - Debug.Assert(_version != null); - return _version; - } - } - - public string Profile - { - get - { - Debug.Assert(_profile != null); - return _profile; - } - } - - public string FullName - { - get - { - if (_fullName == null) - { - if (string.IsNullOrEmpty(Profile)) - { - _fullName = - Identifier + - COMPONENT_SEPARATOR + VERSION_KEY + KEY_VALUE_SEPARATOR + VERSION_PREFIX + - Version.ToString(); - } - else - { - _fullName = - Identifier + - COMPONENT_SEPARATOR + VERSION_KEY + KEY_VALUE_SEPARATOR + VERSION_PREFIX + - Version.ToString() + - COMPONENT_SEPARATOR + PROFILE_KEY + KEY_VALUE_SEPARATOR + - Profile; - } - } - - Debug.Assert(_fullName != null); - return _fullName; - } - } - - public override bool Equals(object obj) - { - return Equals(obj as FrameworkName); - } - - public bool Equals(FrameworkName other) - { - if (other is null) - { - return false; - } - - return Identifier == other.Identifier && - Version == other.Version && - Profile == other.Profile; - } - - public override int GetHashCode() - { - return Identifier.GetHashCode() ^ Version.GetHashCode() ^ Profile.GetHashCode(); - } - - public override string ToString() - { - return FullName; - } - - public FrameworkName(string identifier, Version version) - : this(identifier, version, null) - { - } - - public FrameworkName(string identifier, Version version, string profile=null) - { - Guard.ArgumentNotNull(identifier, nameof(identifier)); - Guard.ArgumentNotNull(version, nameof(version)); - - identifier = identifier.Trim(); - Guard.ArgumentNotNullOrEmpty(identifier, nameof(identifier)); - - _identifier = identifier; - _version = version; - _profile = (profile == null) ? string.Empty : profile.Trim(); - } - - // Parses strings in the following format: ", Version=[v|V], Profile=" - // - The identifier and version is required, profile is optional - // - Only three components are allowed. - // - The version string must be in the System.Version format; an optional "v" or "V" prefix is allowed - public FrameworkName(string frameworkName) - { - Guard.ArgumentNotNullOrEmpty(frameworkName, nameof(frameworkName)); - - string[] components = frameworkName.Split(COMPONENT_SPLIT_SEPARATOR); - - // Identifier and Version are required, Profile is optional. - Guard.ArgumentValid(components.Length == 2 || components.Length == 3, - FRAMEWORK_NAME_COMPONENT_COUNT, nameof(frameworkName)); - - // - // 1) Parse the "Identifier", which must come first. Trim any whitespace - // - _identifier = components[0].Trim(); - - Guard.ArgumentValid(_identifier.Length > 0, FRAMEWORK_NAME_INVALID, nameof(frameworkName)); - - bool versionFound = false; - _profile = string.Empty; - - // - // The required "Version" and optional "Profile" component can be in any order - // - for (int i = 1; i < components.Length; i++) - { - // Get the key/value pair separated by '=' - string component = components[i]; - int separatorIndex = component.IndexOf(KEY_VALUE_SEPARATOR); - - Guard.ArgumentValid(separatorIndex >= 0 && separatorIndex == component.LastIndexOf(KEY_VALUE_SEPARATOR), - FRAMEWORK_NAME_INVALID, nameof(frameworkName)); - - // Get the key and value, trimming any whitespace - string key = component.Substring(0, separatorIndex).Trim(); - string value = component.Substring(separatorIndex + 1).Trim(); - - // - // 2) Parse the required "Version" key value - // - if (key.Equals(VERSION_KEY, StringComparison.OrdinalIgnoreCase)) - { - versionFound = true; - - // Allow the version to include a 'v' or 'V' prefix... - if (value.Length > 0 && (value[0] == VERSION_PREFIX || value[0] == 'V')) - value = value.Substring(1); - - try - { - _version = new Version(value); - } - catch (Exception e) - { - throw new ArgumentException(FRAMEWORK_NAME_VERSION_INVALID, nameof(frameworkName), e); - } - } - // - // 3) Parse the optional "Profile" key value - // - else if (key.Equals(PROFILE_KEY, StringComparison.OrdinalIgnoreCase)) - { - if (value.Length > 0) - { - _profile = value.ToString(); - } - } - else - { - throw new ArgumentException(FRAMEWORK_NAME_INVALID, nameof(frameworkName)); - } - } - - if (!versionFound) - throw new ArgumentException(FRAMEWORK_NAME_VERSION_REQUIRED, nameof(frameworkName)); - } - - public static bool operator ==(FrameworkName left, FrameworkName right) - { - if (left is null) - { - return right is null; - } - - return left.Equals(right); - } - - public static bool operator !=(FrameworkName left, FrameworkName right) - { - return !(left == right); - } - } -} -#endif diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs index 2d8fa18ec..72ca6b34c 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs @@ -146,12 +146,7 @@ private object CreateObject(string typeName, params object[] args) try { return _testDomain.CreateInstanceAndUnwrap( - _reference.FullName, typeName, false, 0, -#if !NET_4_0 - null, args, null, null, null); -#else - null, args, null, null ); -#endif + _reference.FullName, typeName, false, 0, null, args, null, null ); } catch (TargetInvocationException ex) { diff --git a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionNode.cs b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionNode.cs index b53363c2a..031b2d914 100644 --- a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionNode.cs +++ b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionNode.cs @@ -128,7 +128,7 @@ public object CreateExtensionObject(params object[] args) } return Activator.CreateInstance(typeinfo.AsType(), args); #else - return AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap(AssemblyPath, TypeName, false, 0, null, args, null, null, null); + return AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap(AssemblyPath, TypeName, false, 0, null, args, null, null); #endif } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/Backports/Tuple.cs b/src/NUnitEngine/nunit.engine.core/Internal/Backports/Tuple.cs deleted file mode 100644 index caf5b7292..000000000 --- a/src/NUnitEngine/nunit.engine.core/Internal/Backports/Tuple.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -namespace NUnit.Engine.Internal.Backports -{ - /// - /// Represents a tuple with two elements. - /// - /// This is a partial backport of the Tuple-class described here: https://docs.microsoft.com/en-us/dotnet/api/system.tuple-2 - internal sealed class Tuple - { - /// - /// Initializes a new instance of the class. - /// - /// First element - /// Second element - public Tuple(T1 first, T2 second) - { - Item1 = first; - Item2 = second; - } - - /// - /// Gets the first element contained in the tuple. - /// - public T1 Item1 { get; } - - /// - /// Gets the second element contained in the tuple. - /// - public T2 Item2 { get; } - } -} diff --git a/src/NUnitEngine/nunit.engine.core/Internal/Logging/Logger.cs b/src/NUnitEngine/nunit.engine.core/Internal/Logging/Logger.cs index 26eb1578c..ce4b481c6 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/Logging/Logger.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/Logging/Logger.cs @@ -126,7 +126,7 @@ private void WriteLog(InternalTraceLevel level, string message) _writer.WriteLine(TraceFmt, DateTime.Now.ToString(TimeFmt), level, -#if NET20 +#if NETFRAMEWORK System.Threading.Thread.CurrentThread.ManagedThreadId, #else Environment.CurrentManagedThreadId, diff --git a/src/NUnitEngine/nunit.engine.core/Runners/DomainManager.cs b/src/NUnitEngine/nunit.engine.core/Runners/DomainManager.cs index 83f503a03..a83044184 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/DomainManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/DomainManager.cs @@ -41,18 +41,7 @@ public AppDomain CreateDomain( TestPackage package ) } string domainName = "domain-" + hashCode + package.Name; - // Setup the Evidence Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence); - if (evidence.Count == 0) - { - Zone zone = new Zone(SecurityZone.MyComputer); - evidence.AddHost(zone); - Assembly assembly = Assembly.GetExecutingAssembly(); - Url url = new Url(assembly.CodeBase); - evidence.AddHost(url); - Hash hash = new Hash(assembly); - evidence.AddHost(hash); - } log.Info("Creating application domain " + domainName); diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 6ff44e251..40fb360e2 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -6,12 +6,10 @@ using TestCentric.Metadata; using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; -using NUnit.Engine.Internal.Backports; using NUnit.Engine.Internal.FileSystemAccess; using NUnit.Engine.Internal.FileSystemAccess.Default; -using Backports = NUnit.Engine.Internal.Backports; -#if NET20 || NETSTANDARD2_0 +#if NET462 || NETSTANDARD2_0 using Path = NUnit.Engine.Internal.Backports.Path; #else using Path = System.IO.Path; @@ -328,22 +326,22 @@ private void ProcessAddinsFile(IDirectory baseDir, IFile addinsFile, bool fromWi } } - private Backports.Tuple GetBaseDirAndPattern(IDirectory baseDir, string path) + private Tuple GetBaseDirAndPattern(IDirectory baseDir, string path) { if (Path.IsPathFullyQualified(path)) { if (path.EndsWith("/")) { - return new Backports.Tuple(_fileSystem.GetDirectory(path), string.Empty); + return new Tuple(_fileSystem.GetDirectory(path), string.Empty); } else { - return new Backports.Tuple(_fileSystem.GetDirectory(System.IO.Path.GetDirectoryName(path)), System.IO.Path.GetFileName(path)); + return new Tuple(_fileSystem.GetDirectory(System.IO.Path.GetDirectoryName(path)), System.IO.Path.GetFileName(path)); } } else { - return new Backports.Tuple(baseDir, path); + return new Tuple(baseDir, path); } } diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index b35f1dc27..af3af5665 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -11,7 +11,7 @@ using NUnit.Engine.Internal.FileSystemAccess.Default; using Backports = NUnit.Engine.Internal.Backports; -#if NET20 || NETSTANDARD2_0 +#if NETFRAMEWORK || NETSTANDARD2_0 using Path = NUnit.Engine.Internal.Backports.Path; #else using Path = System.IO.Path; diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index b40bd5cb9..0ff5b8f68 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net20;netstandard2.0;netcoreapp3.1;net5.0;net6.0;net8.0 + net462;netstandard2.0;netcoreapp3.1;net5.0;net6.0;net8.0 $(NoWarn);SYSLIB0011;SYSLIB0012 true ..\..\nunit.snk @@ -17,7 +17,7 @@ Common code used by both the engine and agents - + diff --git a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs index b5b4ed323..89edc696d 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs @@ -27,14 +27,14 @@ public void SetUp() _package.Settings[EnginePackageSettings.TargetRuntimeFramework] = "net-4.5"; } - [TestCase("net-4.5", false, "../agents/net40/nunit-agent.exe")] - [TestCase("net-4.5", true, "../agents/net40/nunit-agent-x86.exe")] - [TestCase("net-4.0", false, "../agents/net40/nunit-agent.exe")] - [TestCase("net-4.0", true, "../agents/net40/nunit-agent-x86.exe")] - [TestCase("net-3.5", false, "../agents/net20/nunit-agent.exe")] - [TestCase("net-3.5", true, "../agents/net20/nunit-agent-x86.exe")] - [TestCase("net-2.0", false, "../agents/net20/nunit-agent.exe")] - [TestCase("net-2.0", true, "../agents/net20/nunit-agent-x86.exe")] + [TestCase("net-4.5", false, "../agents/net462/nunit-agent.exe")] + [TestCase("net-4.5", true, "../agents/net462/nunit-agent-x86.exe")] + [TestCase("net-4.0", false, "../agents/net462/nunit-agent.exe")] + [TestCase("net-4.0", true, "../agents/net462/nunit-agent-x86.exe")] + [TestCase("net-3.5", false, "../agents/net462/nunit-agent.exe")] + [TestCase("net-3.5", true, "../agents/net462/nunit-agent-x86.exe")] + [TestCase("net-2.0", false, "../agents/net462/nunit-agent.exe")] + [TestCase("net-2.0", true, "../agents/net462/nunit-agent-x86.exe")] //[TestCase("netcore-2.1", false, "agents/netcoreapp2.1/testcentric-agent.dll")] //[TestCase("netcore-2.1", true, "agents/netcoreapp2.1/testcentric-agent-x86.dll")] public void AgentSelection(string runtime, bool x86, string agentPath) diff --git a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs index 744cd0617..df4626746 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs @@ -33,8 +33,8 @@ public void ServiceIsStarted() } [TestCase("mock-assembly.dll", false)] - [TestCase("../agents/net20/nunit-agent.exe", false)] - [TestCase("../agents/net20/nunit-agent-x86.exe", true)] + [TestCase("../agents/net462/nunit-agent.exe", false)] + [TestCase("../agents/net462/nunit-agent-x86.exe", true)] [TestCase("../netstandard2.0/nunit.engine.api.dll", false)] public void SelectRuntimeFramework(string assemblyName, bool runAsX86) { diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index 97e45dd9d..3373d6904 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Tests - net35;netcoreapp3.1 + net462;netcoreapp3.1 Exe true ..\..\nunit.snk @@ -23,7 +23,7 @@ - + diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 91f6e6371..27448cd31 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -105,7 +105,7 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re if (!Directory.Exists(agentsDir)) { // When developing and running in the output directory, "agents" is a - // sibling directory the one holding the agent (e.g. net20). This is a + // sibling directory the one holding the agent (e.g. net462). This is a // bit of a kluge, but it's necessary unless we change the binary // output directory to match the distribution structure. agentsDir = Path.Combine(Path.GetDirectoryName(engineDir), "agents"); @@ -122,7 +122,7 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re { case RuntimeType.Net: case RuntimeType.Mono: - runtimeDir = major >= 4 ? "net40" : "net20"; + runtimeDir = "net462"; agentName = requires32Bit ? "nunit-agent-x86" : "nunit-agent"; agentExtension = ".exe"; break; diff --git a/src/NUnitEngine/nunit.engine/Services/ResultWriters/XmlTransformResultWriter.cs b/src/NUnitEngine/nunit.engine/Services/ResultWriters/XmlTransformResultWriter.cs index 5de2f322e..f99b5efc9 100644 --- a/src/NUnitEngine/nunit.engine/Services/ResultWriters/XmlTransformResultWriter.cs +++ b/src/NUnitEngine/nunit.engine/Services/ResultWriters/XmlTransformResultWriter.cs @@ -28,12 +28,10 @@ public XmlTransformResultWriter(object[] args) try { var settings = new XmlReaderSettings(); -#if NET20 - settings.ProhibitDtd = false; +#if NETFRAMEWORK settings.XmlResolver = null; -#else - settings.DtdProcessing = DtdProcessing.Ignore; #endif + settings.DtdProcessing = DtdProcessing.Ignore; using (var xmlReader = XmlReader.Create(_xsltFile, settings)) _transform.Load(xmlReader); } diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs index e1f7538d1..1d9c067fb 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -4,13 +4,11 @@ using System; using System.Collections.Generic; using System.IO; -using TestCentric.Metadata; +using System.Runtime.Versioning; using NUnit.Common; using NUnit.Engine.Internal; using NUnit.Engine.Services.RuntimeLocators; -#if NET20 -using FrameworkName = NUnit.Engine.Compatibility.FrameworkName; -#endif +using TestCentric.Metadata; namespace NUnit.Engine.Services { diff --git a/src/NUnitEngine/nunit.engine/nunit.engine.csproj b/src/NUnitEngine/nunit.engine/nunit.engine.csproj index e05330dd6..ab802c56e 100644 --- a/src/NUnitEngine/nunit.engine/nunit.engine.csproj +++ b/src/NUnitEngine/nunit.engine/nunit.engine.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net20;netstandard2.0 + net462;netstandard2.0 true ..\..\nunit.snk portable @@ -15,7 +15,7 @@ Provides for loading, exploring and running NUnit tests - + @@ -30,7 +30,7 @@ - + PreserveNewest From 49633eb04bc2f9579e374c480cd88517954c7ebf Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 3 Jul 2024 10:15:51 -0700 Subject: [PATCH 072/190] Eliminate .NET 5.0 builds --- NUnitConsole.sln | 1 + PLATFORM_SUPPORT.md | 8 ++--- build.cake | 31 ++++--------------- choco/nunit-console-runner.nuspec | 11 ------- nuget/runners/nunit.console-runner.nuspec | 14 --------- .../mock-assembly-x86.csproj | 2 +- .../mock-assembly/mock-assembly.csproj | 2 +- .../nunit-agent/nunit-agent.csproj | 2 +- .../nunit.engine.core.tests.csproj | 2 +- .../nunit.engine.core.csproj | 2 +- .../nunit.engine/Services/AgentProcess.cs | 2 +- 11 files changed, 16 insertions(+), 61 deletions(-) diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 42dde2e20..dd041f3e7 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -31,6 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution package-checks.cake = package-checks.cake package-tests.cake = package-tests.cake packages.cake = packages.cake + PLATFORM_SUPPORT.md = PLATFORM_SUPPORT.md README.md = README.md test-results.cake = test-results.cake VERSIONING.md = VERSIONING.md diff --git a/PLATFORM_SUPPORT.md b/PLATFORM_SUPPORT.md index 178b29fb2..18c2b8362 100644 --- a/PLATFORM_SUPPORT.md +++ b/PLATFORM_SUPPORT.md @@ -15,11 +15,9 @@ runtime available. ## Agents Provided -We currently supply a fairly large selection of agents with the console runner: -* .NET Framework 2.0 +We currently (July 3, 2024) supply the following agents with the console runner: * .NET Framework 4.6.2 * ,NET Core 3.1 -* .NET 5.0 * .NET 6.0 * .NET 7.0 * .NET 8.0 (coming in version 3.18.0) @@ -33,10 +31,10 @@ dates listed in the following table. | Runtime | Microsoft
End of Support | Agent Retirement | Notes | | -------------------- | --------------- | --------------------- | --- | -| .NET Framework 2.0 | July, 2011 | July, 2024 | Will be removed in version 3.18.0 +| .NET Framework 2.0 | July, 2011 | July, 2024 | Removed in version 3.18.0 | .NET Framework 4.6.2 | January, 2027 | after July, 2027 | May be upgraded to 4.8.1 before retirement date | | .NET Core 3.1 | December, 2022 | after December, 2024 | -| .NET 5.0 | May, 2022 | July, 2024 | Will be removed in version 3.18.0 +| .NET 5.0 | May, 2022 | July, 2024 | Removed in version 3.18.0 | .NET 6.0 | November, 2024 | after May, 2025 | | .NET 7.0 | May, 2024 | after November, 2024 | | .NET 8.0 | November, 2027 | after May, 2027 | diff --git a/build.cake b/build.cake index 0abb5989b..824b230ce 100644 --- a/build.cake +++ b/build.cake @@ -22,11 +22,10 @@ BuildSettings.Initialize( Net462X86Test, Net462PlusNet462Test, NetCore31Test, - Net50Test, Net60Test, Net70Test, Net80Test, - Net50PlusNet60Test, + Net60PlusNet80Test, Net462PlusNet60Test, NUnitProjectTest }; @@ -35,7 +34,6 @@ BuildSettings.Initialize( var NetCoreRunnerTests = new List { NetCore31Test, - Net50Test, Net60Test, Net70Test, Net80Test, @@ -52,7 +50,6 @@ BuildSettings.Initialize( if (!BuildSystem.IsRunningOnAppVeyor) { StandardRunnerTests.Add(NetCore31X86Test); - StandardRunnerTests.Add(Net50X86Test); StandardRunnerTests.Add(Net70X86Test); StandardRunnerTests.Add(Net80X86Test); } @@ -149,18 +146,6 @@ static PackageTest Net60X86Test = new PackageTest( "net6.0/mock-assembly-x86.dll", MockAssemblyX86ExpectedResult("netcore-6.0")); -static PackageTest Net50Test = new PackageTest( - 1, "Net50Test", - "Run mock-assembly.dll under .NET 5.0", - "net5.0/mock-assembly.dll", - MockAssemblyExpectedResult("netcore-5.0")); - -static PackageTest Net50X86Test = new PackageTest( - 1, "Net50X86Test", - "Run mock-assembly-x86.dll under .NET 5.0", - "net5.0/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("netcore-5.0")); - static PackageTest NetCore31Test = new PackageTest( 1, "NetCore31Test", "Run mock-assembly.dll under .NET Core 3.1", @@ -173,11 +158,11 @@ static PackageTest NetCore31X86Test = new PackageTest( "netcoreapp3.1/mock-assembly-x86.dll", MockAssemblyX86ExpectedResult("netcore-3.1")); -static PackageTest Net50PlusNet60Test = new PackageTest( - 1, "Net50PlusNet60Test", - "Run mock-assembly under .NET 5.0 and 6.0 together", - "net5.0/mock-assembly.dll net6.0/mock-assembly.dll",//" net7.0/mock-assembly.dll net8.0/mock-assembly.dll", - MockAssemblyExpectedResult("netcore-5.0", "netcore-6.0")); +static PackageTest Net60PlusNet80Test = new PackageTest( + 1, "Net60PlusNet80Test", + "Run mock-assembly under .NET6.0 and 8.0 together", + "net6.0/mock-assembly.dll net8.0/mock-assembly.dll", + MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0")); static PackageTest Net462PlusNet60Test = new PackageTest( 1, "Net462PlusNet60Test", @@ -244,7 +229,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins"), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins") @@ -253,7 +237,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), HasDirectory("tools/agents/net462").WithFiles(AGENT_PDB_FILES), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("tools/agents/net5.0").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net7.0").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_PDB_FILES_NETCORE) @@ -303,7 +286,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt").AndFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.choco.addins"), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins") @@ -321,7 +303,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("bin/netstandard2.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), - HasDirectory("bin/agents/net5.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 9135e20b2..a1c687367 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -62,17 +62,6 @@ - - - - - - - - - - - diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index d2398b32f..343dc1c81 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -55,20 +55,6 @@ - - - - - - - - - - - - - - diff --git a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj index e91f3dd71..62f67b383 100644 --- a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net462;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + net462;netcoreapp3.1;net6.0;net7.0;net8.0 true ..\..\nunit.snk x86 diff --git a/src/NUnitEngine/mock-assembly/mock-assembly.csproj b/src/NUnitEngine/mock-assembly/mock-assembly.csproj index 8dbee8fa4..a9e9578df 100644 --- a/src/NUnitEngine/mock-assembly/mock-assembly.csproj +++ b/src/NUnitEngine/mock-assembly/mock-assembly.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net462;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + net462;netcoreapp3.1;net6.0;net7.0;net8.0 true ..\..\nunit.snk false diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index 2953aa24a..71e07baa5 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -3,7 +3,7 @@ Exe nunit.agent - net462;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + net462;netcoreapp3.1;net6.0;net7.0;net8.0 app.manifest ..\..\..\nunit.ico false diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 2a296dbc0..66047d496 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Core.Tests - net462;netcoreapp3.1;net5.0;net6.0;net8.0 + net462;netcoreapp3.1;net6.0;net8.0 Exe true ..\..\nunit.snk diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index 0ff5b8f68..9f1239499 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net462;netstandard2.0;netcoreapp3.1;net5.0;net6.0;net8.0 + net462;netstandard2.0;netcoreapp3.1;net6.0;net8.0 $(NoWarn);SYSLIB0011;SYSLIB0012 true ..\..\nunit.snk diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 27448cd31..a1ee5c6d1 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -127,7 +127,7 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re agentExtension = ".exe"; break; case RuntimeType.NetCore: - runtimeDir = major >= 8 ? "net8.0" : major == 7 ? "net7.0" : major == 6 ? "net6.0" : major == 5 ? "net5.0" : "netcoreapp3.1"; + runtimeDir = major >= 8 ? "net8.0" : major == 7 ? "net7.0" : major == 6 ? "net6.0" : "netcoreapp3.1"; agentName = "nunit-agent"; agentExtension = ".dll"; break; From 313bc2a0d4826f104510e5dbff930f359634ca02 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 5 Jul 2024 08:17:39 -0700 Subject: [PATCH 073/190] Improved visibility of unit test errors --- cake/unit-testing.cake | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/cake/unit-testing.cake b/cake/unit-testing.cake index 0d0f2ef7b..766e8036d 100644 --- a/cake/unit-testing.cake +++ b/cake/unit-testing.cake @@ -16,23 +16,36 @@ public static class UnitTesting var unitTests = FindUnitTestFiles(BuildSettings.UnitTests); _context.Information($"Located {unitTests.Count} unit test assemblies."); + var errors = new List(); foreach (var testPath in unitTests) - RunTest(testPath); - } + { + var testFile = testPath.GetFilename(); + var containingDir = testPath.GetDirectory().GetDirectoryName(); + var runtime = IsValidRuntime(containingDir) ? containingDir : null; + + Banner.Display(runtime != null + ? $"Running {testFile} under {runtime}" + : $"Running {testFile}"); - private static void RunTest(FilePath testPath) - { - var testFile = testPath.GetFilename(); - var containingDir = testPath.GetDirectory().GetDirectoryName(); - var msg = "Running " + testFile; - if (IsValidRuntime(containingDir)) - msg += " under " + containingDir; + var runner = BuildSettings.UnitTestRunner ?? new NUnitLiteRunner(); + int rc = runner.Run(testPath); - Banner.Display(msg); + var name = runtime != null + ? $"{testFile}({runtime})" + : testFile; + if (rc > 0) + errors.Add($"{name}: {rc} tests failed"); + else if (rc < 0) + errors.Add($"{name} returned rc = {rc}"); + } - var runner = BuildSettings.UnitTestRunner ?? new NUnitLiteRunner(); - runner.Run(testPath); + if (unitTests.Count == 0) + _context.Warning("No unit tests were found"); + else if (errors.Count > 0) + throw new Exception( + "One or more unit tests failed, breaking the build.\r\n" + + errors.Aggregate((x, y) => x + "\r\n" + y) ); static bool IsValidRuntime(string text) { From 2b2d49a5b65d577b516ff8c02faa71502595eb2d Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 12 Jul 2024 11:12:33 -0700 Subject: [PATCH 074/190] Update recipe to match project loader --- NUnitConsole.sln | 58 ++--- build.cake | 19 +- cake/package-test.cake | 34 --- cake/test-runners.cake | 117 --------- cake/tools.cake | 4 - .../nunit.console-runner.netcore.nuspec | 2 +- {cake => recipe}/banner.cake | 0 {cake => recipe}/build-settings.cake | 11 +- {cake => recipe}/builder.cake | 0 {cake => recipe}/chocolatey-package.cake | 6 +- {cake => recipe}/command-line-options.cake | 0 {cake => recipe}/constants.cake | 3 + {cake => recipe}/dotnet.cake | 18 +- {cake => recipe}/extending.cake | 0 {cake => recipe}/headers.cake | 4 +- {cake => recipe}/help-messages.cake | 0 {cake => recipe}/known-extensions.cake | 0 {cake => recipe}/nuget-package.cake | 6 +- {cake => recipe}/package-checks.cake | 0 {cake => recipe}/package-definition.cake | 109 ++++++--- {cake => recipe}/package-reference.cake | 0 recipe/package-test.cake | 76 ++++++ {cake => recipe}/publishing.cake | 0 {cake => recipe}/setup.cake | 0 {cake => recipe}/task-builders.cake | 0 {cake => recipe}/task-definitions.cake | 0 {cake => recipe}/test-reports.cake | 16 +- {cake => recipe}/test-results.cake | 0 recipe/test-runners.cake | 224 ++++++++++++++++++ recipe/tools.cake | 27 +++ {cake => recipe}/unit-testing.cake | 5 +- {cake => recipe}/utilities.cake | 0 {cake => recipe}/versioning.cake | 0 {cake => recipe}/zip-package.cake | 8 +- 34 files changed, 492 insertions(+), 255 deletions(-) delete mode 100644 cake/package-test.cake delete mode 100644 cake/test-runners.cake delete mode 100644 cake/tools.cake rename {cake => recipe}/banner.cake (100%) rename {cake => recipe}/build-settings.cake (97%) rename {cake => recipe}/builder.cake (100%) rename {cake => recipe}/chocolatey-package.cake (86%) rename {cake => recipe}/command-line-options.cake (100%) rename {cake => recipe}/constants.cake (97%) rename {cake => recipe}/dotnet.cake (76%) rename {cake => recipe}/extending.cake (100%) rename {cake => recipe}/headers.cake (96%) rename {cake => recipe}/help-messages.cake (100%) rename {cake => recipe}/known-extensions.cake (100%) rename {cake => recipe}/nuget-package.cake (88%) rename {cake => recipe}/package-checks.cake (100%) rename {cake => recipe}/package-definition.cake (66%) rename {cake => recipe}/package-reference.cake (100%) create mode 100644 recipe/package-test.cake rename {cake => recipe}/publishing.cake (100%) rename {cake => recipe}/setup.cake (100%) rename {cake => recipe}/task-builders.cake (100%) rename {cake => recipe}/task-definitions.cake (100%) rename {cake => recipe}/test-reports.cake (93%) rename {cake => recipe}/test-results.cake (100%) create mode 100644 recipe/test-runners.cake create mode 100644 recipe/tools.cake rename {cake => recipe}/unit-testing.cake (96%) rename {cake => recipe}/utilities.cake (100%) rename {cake => recipe}/versioning.cake (100%) rename {cake => recipe}/zip-package.cake (90%) diff --git a/NUnitConsole.sln b/NUnitConsole.sln index dd041f3e7..ad28c0a8e 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -136,36 +136,36 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{C5B712 .config\dotnet-tools.json = .config\dotnet-tools.json EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{D6449B7A-20FF-467B-A65E-174DD6992AEB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "recipe", "recipe", "{D6449B7A-20FF-467B-A65E-174DD6992AEB}" ProjectSection(SolutionItems) = preProject - cake\banner.cake = cake\banner.cake - cake\build-settings.cake = cake\build-settings.cake - cake\builder.cake = cake\builder.cake - cake\chocolatey-package.cake = cake\chocolatey-package.cake - cake\command-line-options.cake = cake\command-line-options.cake - cake\constants.cake = cake\constants.cake - cake\dotnet.cake = cake\dotnet.cake - cake\extending.cake = cake\extending.cake - cake\headers.cake = cake\headers.cake - cake\help-messages.cake = cake\help-messages.cake - cake\known-extensions.cake = cake\known-extensions.cake - cake\nuget-package.cake = cake\nuget-package.cake - cake\package-checks.cake = cake\package-checks.cake - cake\package-definition.cake = cake\package-definition.cake - cake\package-reference.cake = cake\package-reference.cake - cake\package-test.cake = cake\package-test.cake - cake\publishing.cake = cake\publishing.cake - cake\setup.cake = cake\setup.cake - cake\task-builders.cake = cake\task-builders.cake - cake\task-definitions.cake = cake\task-definitions.cake - cake\test-reports.cake = cake\test-reports.cake - cake\test-results.cake = cake\test-results.cake - cake\test-runners.cake = cake\test-runners.cake - cake\tools.cake = cake\tools.cake - cake\unit-testing.cake = cake\unit-testing.cake - cake\utilities.cake = cake\utilities.cake - cake\versioning.cake = cake\versioning.cake - cake\zip-package.cake = cake\zip-package.cake + recipe\banner.cake = recipe\banner.cake + recipe\build-settings.cake = recipe\build-settings.cake + recipe\builder.cake = recipe\builder.cake + recipe\chocolatey-package.cake = recipe\chocolatey-package.cake + recipe\command-line-options.cake = recipe\command-line-options.cake + recipe\constants.cake = recipe\constants.cake + recipe\dotnet.cake = recipe\dotnet.cake + recipe\extending.cake = recipe\extending.cake + recipe\headers.cake = recipe\headers.cake + recipe\help-messages.cake = recipe\help-messages.cake + recipe\known-extensions.cake = recipe\known-extensions.cake + recipe\nuget-package.cake = recipe\nuget-package.cake + recipe\package-checks.cake = recipe\package-checks.cake + recipe\package-definition.cake = recipe\package-definition.cake + recipe\package-reference.cake = recipe\package-reference.cake + recipe\package-test.cake = recipe\package-test.cake + recipe\publishing.cake = recipe\publishing.cake + recipe\setup.cake = recipe\setup.cake + recipe\task-builders.cake = recipe\task-builders.cake + recipe\task-definitions.cake = recipe\task-definitions.cake + recipe\test-reports.cake = recipe\test-reports.cake + recipe\test-results.cake = recipe\test-results.cake + recipe\test-runners.cake = recipe\test-runners.cake + recipe\tools.cake = recipe\tools.cake + recipe\unit-testing.cake = recipe\unit-testing.cake + recipe\utilities.cake = recipe\utilities.cake + recipe\versioning.cake = recipe\versioning.cake + recipe\zip-package.cake = recipe\zip-package.cake EndProjectSection EndProject Global diff --git a/build.cake b/build.cake index 824b230ce..6b55254f1 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load scripts -#load cake/*.cake +#load recipe/*.cake // Initialize BuildSettings BuildSettings.Initialize( @@ -357,9 +357,9 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { // Custom unit test runner to run console vs engine tests differently // TODO: Use NUnitLite for all tests? -public class CustomTestRunner : UnitTestRunner +public class CustomTestRunner : TestRunner, IUnitTestRunner { - public override int Run(FilePath testPath) + public int RunUnitTest(FilePath testPath) { // Run console tests under the just-built console if(testPath.ToString().Contains("nunit3-console.tests.dll")) @@ -370,21 +370,24 @@ public class CustomTestRunner : UnitTestRunner } // All other tests use NUnitLite - return new NUnitLiteRunner().Run(testPath); + return new NUnitLiteRunner().RunUnitTest(testPath); } } // Use the console runner we just built to run package tests -public class ConsoleRunnerSelfTester : PackageTestRunner +public class ConsoleRunnerSelfTester : TestRunner, IPackageTestRunner { + private string _executablePath; + public ConsoleRunnerSelfTester(string executablePath) { - ExecutablePath = executablePath; + _executablePath = executablePath; } - public override int Run(string arguments) + public int RunPackageTest(string arguments) { - return base.Run(arguments); + Console.WriteLine("Running package test"); + return base.RunTest(_executablePath, arguments); } } diff --git a/cake/package-test.cake b/cake/package-test.cake deleted file mode 100644 index e80d160af..000000000 --- a/cake/package-test.cake +++ /dev/null @@ -1,34 +0,0 @@ -// Representation of a single test to be run against a pre-built package. -// Each test has a Level, with the following values defined... -// 0 Do not run - used for temporarily disabling a test -// 1 Run for all CI tests - that is every time we test packages -// 2 Run only on PRs, dev builds and when publishing -// 3 Run only when publishing -public struct PackageTest -{ - public int Level; - public string Name; - public string Description; - public string Arguments; - public ExpectedResult ExpectedResult; - public ExtensionSpecifier[] ExtensionsNeeded; - - public PackageTest(int level, string name, string description, string arguments, ExpectedResult expectedResult, params ExtensionSpecifier[] extensionsNeeded) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (description == null) - throw new ArgumentNullException(nameof(description)); - if (arguments == null) - throw new ArgumentNullException(nameof(arguments)); - if (expectedResult == null) - throw new ArgumentNullException(nameof(expectedResult)); - - Level = level; - Name = name; - Description = description; - Arguments = arguments; - ExpectedResult = expectedResult; - ExtensionsNeeded = extensionsNeeded; - } -} diff --git a/cake/test-runners.cake b/cake/test-runners.cake deleted file mode 100644 index d876d9c39..000000000 --- a/cake/test-runners.cake +++ /dev/null @@ -1,117 +0,0 @@ -/// -/// The TestRunner class is the abstract base for all TestRunners used to run unit- -/// or package-tests. A TestRunner knows how to run a test assembly and provide a result. -/// -public abstract class TestRunner -{ - public virtual bool RequiresInstallation => false; - - protected FilePath ExecutablePath { get; set; } - - // Base install does nothing - public virtual void Install() { } -} - -public abstract class UnitTestRunner : TestRunner -{ - // Unit tests are run by providing the path to a test assembly. - public virtual int Run(FilePath testAssembly) - { - if (ExecutablePath == null) - throw new InvalidOperationException("Unable to run tests. Executable path has not been set."); - - var processSettings = new ProcessSettings() { - WorkingDirectory = BuildSettings.OutputDirectory, - // HACK: Equality indicates we are running under NUnitLite - Arguments = ExecutablePath == testAssembly - ? BuildSettings.UnitTestArguments - : $"{testAssembly} {BuildSettings.UnitTestArguments}" }; - - if (ExecutablePath.GetExtension() == ".dll") - return BuildSettings.Context.StartProcess("dotnet", processSettings); - else - return BuildSettings.Context.StartProcess(ExecutablePath,processSettings); - } -} - -public abstract class PackageTestRunner : TestRunner -{ - // Package Tests are run by providing the arguments, which - // will include one or more test assemblies. - public virtual int Run(string arguments=null) - { - if (ExecutablePath == null) - throw new InvalidOperationException("Unable to run tests. Executable path has not been set."); - - var processSettings = new ProcessSettings() { WorkingDirectory = BuildSettings.OutputDirectory }; - - if (ExecutablePath.GetExtension() == ".dll") - { - processSettings.Arguments = $"{ExecutablePath} {arguments}"; - return BuildSettings.Context.StartProcess("dotnet", processSettings); - } - else - { - processSettings.Arguments = arguments; - return BuildSettings.Context.StartProcess(ExecutablePath, processSettings); - } - } -} - -/// -/// The InstallableTestRunner class is the abstract base for TestRunners which -/// must be installed using a published package before they can be used. -/// -public abstract class InstallableTestRunner : TestRunner -{ - public override bool RequiresInstallation => true; - - public InstallableTestRunner(string packageId, string version) - { - if (packageId == null) - throw new ArgumentNullException(nameof(packageId)); - if (version == null) - throw new ArgumentNullException(nameof(version)); - - PackageId = packageId; - Version = version; - } - - public string PackageId { get; } - public string Version { get; } - - public abstract string InstallPath { get; } -} - -public class NUnitLiteRunner : UnitTestRunner -{ - public override int Run(FilePath testPath) - { - ExecutablePath = testPath; - return base.Run(testPath); - } -} - -/// -/// Class that knows how to run an agent directly. -/// -public class AgentRunner : PackageTestRunner -{ - private string _stdExecutable; - private string _x86Executable; - - public AgentRunner(string stdExecutable, string x86Executable = null) - { - _stdExecutable = stdExecutable; - _x86Executable = x86Executable; - } - - public override int Run(string arguments) - { - ExecutablePath = arguments.Contains("--x86") - ? _x86Executable - : _stdExecutable; - - return base.Run(arguments.Replace("--x86", string.Empty)); - } -} diff --git a/cake/tools.cake b/cake/tools.cake deleted file mode 100644 index 749e35817..000000000 --- a/cake/tools.cake +++ /dev/null @@ -1,4 +0,0 @@ -// Load all tools used by the recipe -#tool NuGet.CommandLine&version=6.9.1 -#tool dotnet:?package=GitVersion.Tool&version=5.12.0 -#tool dotnet:?package=GitReleaseManager.Tool&version=0.17.0 diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index 5d44bdb9e..fd36e66a7 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -29,7 +29,7 @@ - + diff --git a/cake/banner.cake b/recipe/banner.cake similarity index 100% rename from cake/banner.cake rename to recipe/banner.cake diff --git a/cake/build-settings.cake b/recipe/build-settings.cake similarity index 97% rename from cake/build-settings.cake rename to recipe/build-settings.cake index 789735859..c503c798e 100644 --- a/cake/build-settings.cake +++ b/recipe/build-settings.cake @@ -21,7 +21,7 @@ public static class BuildSettings Verbosity msbuildVerbosity = Verbosity.Minimal, string unitTests = null, // Defaults to "**/*.tests.dll|**/*.tests.exe" (case insensitive) - UnitTestRunner unitTestRunner = null, // If not set, NUnitLite is used + IUnitTestRunner unitTestRunner = null, // If not set, NUnitLite is used string unitTestArguments = null ) { @@ -85,11 +85,11 @@ public static class BuildSettings // the solution found in the root directory provided there is only one. private static string DeduceSolutionFile() { - if (System.IO.File.Exists(Title + ".sln")) + if (SIO.File.Exists(Title + ".sln")) return Title + ".sln"; - var files = System.IO.Directory.GetFiles(ProjectDirectory, "*.sln"); - if (files.Length == 1 && System.IO.File.Exists(files[0])) + var files = SIO.Directory.GetFiles(ProjectDirectory, "*.sln"); + if (files.Length == 1 && SIO.File.Exists(files[0])) return files[0]; return null; @@ -203,7 +203,7 @@ public static class BuildSettings //Testing public static string UnitTests { get; set; } - public static UnitTestRunner UnitTestRunner { get; private set; } + public static IUnitTestRunner UnitTestRunner { get; private set; } public static string UnitTestArguments { get; private set; } // Packaging @@ -310,6 +310,7 @@ public static class BuildSettings Console.WriteLine("UnitTestRunner: " + UnitTestRunner?.GetType().Name ?? ""); Console.WriteLine("\nPACKAGING"); + Console.WriteLine("PackageTestLevel: " + PackageTestLevel); Console.WriteLine("MyGetPushUrl: " + MyGetPushUrl); Console.WriteLine("NuGetPushUrl: " + NuGetPushUrl); Console.WriteLine("ChocolateyPushUrl: " + ChocolateyPushUrl); diff --git a/cake/builder.cake b/recipe/builder.cake similarity index 100% rename from cake/builder.cake rename to recipe/builder.cake diff --git a/cake/chocolatey-package.cake b/recipe/chocolatey-package.cake similarity index 86% rename from cake/chocolatey-package.cake rename to recipe/chocolatey-package.cake index fa65eebed..7645d1bc8 100644 --- a/cake/chocolatey-package.cake +++ b/recipe/chocolatey-package.cake @@ -3,7 +3,8 @@ public class ChocolateyPackage : PackageDefinition public ChocolateyPackage( string id, string source, - PackageTestRunner testRunner = null, + IPackageTestRunner testRunner = null, + TestRunnerSource testRunnerSource = null, PackageCheck[] checks = null, IEnumerable tests = null) : base( @@ -11,6 +12,7 @@ public class ChocolateyPackage : PackageDefinition id, source, testRunner: testRunner, + testRunnerSource: testRunnerSource, checks: checks, tests: tests) { @@ -21,7 +23,7 @@ public class ChocolateyPackage : PackageDefinition // The file name of this package, including extension public override string PackageFileName => $"{PackageId}.{PackageVersion}.nupkg"; // The file name of any symbol package, including extension - public override string SymbolPackageName => System.IO.Path.ChangeExtension(PackageFileName, ".snupkg"); + public override string SymbolPackageName => SIO.Path.ChangeExtension(PackageFileName, ".snupkg"); // The directory into which this package is installed public override string PackageInstallDirectory => BuildSettings.ChocolateyTestDirectory; // The directory used to contain results of package tests for this package diff --git a/cake/command-line-options.cake b/recipe/command-line-options.cake similarity index 100% rename from cake/command-line-options.cake rename to recipe/command-line-options.cake diff --git a/cake/constants.cake b/recipe/constants.cake similarity index 97% rename from cake/constants.cake rename to recipe/constants.cake index 810238fb4..2a98b6873 100644 --- a/cake/constants.cake +++ b/recipe/constants.cake @@ -1,6 +1,9 @@ // This file contains both real constants and static readonly variables used // as constants. All values are initialized before any instance variables. +// Alias used throughout the recipe +using SIO = System.IO; + // GitHub owner is the NUnit organization const string GITHUB_OWNER = "nunit"; diff --git a/cake/dotnet.cake b/recipe/dotnet.cake similarity index 76% rename from cake/dotnet.cake rename to recipe/dotnet.cake index 0fb801824..9441de3ee 100644 --- a/cake/dotnet.cake +++ b/recipe/dotnet.cake @@ -18,7 +18,7 @@ public class DotnetInfo // NOTES: // * We don't need an IsInstalled property because our scripts all run under dotnet. - public bool IsX86Installed => System.IO.Directory.Exists(X86InstallPath) && System.IO.File.Exists(X86Executable); + public bool IsX86Installed => SIO.Directory.Exists(X86InstallPath) && SIO.File.Exists(X86Executable); public string InstallPath { get; } public string Executable => InstallPath + "dotnet.exe"; @@ -33,12 +33,12 @@ public class DotnetInfo _context.Information($"Install Path: {InstallPath}"); _context.Information($"Executable: {Executable}"); _context.Information("Runtimes:"); - foreach (string dir in System.IO.Directory.GetDirectories(System.IO.Path.Combine(InstallPath, "shared"))) + foreach (string dir in SIO.Directory.GetDirectories(SIO.Path.Combine(InstallPath, "shared"))) { - string runtime = System.IO.Path.GetFileName(dir); - foreach (string dir2 in System.IO.Directory.GetDirectories(dir)) + string runtime = SIO.Path.GetFileName(dir); + foreach (string dir2 in SIO.Directory.GetDirectories(dir)) { - string version = System.IO.Path.GetFileName(dir2); + string version = SIO.Path.GetFileName(dir2); _context.Information($" {runtime} {version}"); } } @@ -48,12 +48,12 @@ public class DotnetInfo _context.Information($"\nX86 Install Path: {X86InstallPath}"); _context.Information($"X86 Executable: {X86Executable}"); _context.Information("Runtimes:"); - foreach (var dir in System.IO.Directory.GetDirectories(System.IO.Path.Combine(X86InstallPath, "shared"))) + foreach (var dir in SIO.Directory.GetDirectories(SIO.Path.Combine(X86InstallPath, "shared"))) { - string runtime = System.IO.Path.GetFileName(dir); - foreach (string dir2 in System.IO.Directory.GetDirectories(dir)) + string runtime = SIO.Path.GetFileName(dir); + foreach (string dir2 in SIO.Directory.GetDirectories(dir)) { - string version = System.IO.Path.GetFileName(dir2); + string version = SIO.Path.GetFileName(dir2); _context.Information($" {runtime} {version}"); } } diff --git a/cake/extending.cake b/recipe/extending.cake similarity index 100% rename from cake/extending.cake rename to recipe/extending.cake diff --git a/cake/headers.cake b/recipe/headers.cake similarity index 96% rename from cake/headers.cake rename to recipe/headers.cake index 3ad3e77d4..479771469 100644 --- a/cake/headers.cake +++ b/recipe/headers.cake @@ -33,7 +33,7 @@ public static class Headers continue; // Ignore AssemblyInfo files - if (System.IO.Path.GetFileName(path) == "AssemblyInfo.cs") + if (SIO.Path.GetFileName(path) == "AssemblyInfo.cs") continue; examined++; @@ -92,7 +92,7 @@ public static class Headers List GetHeader(FilePath file) { var header = new List(); - var lines = System.IO.File.ReadLines(file.ToString()); + var lines = SIO.File.ReadLines(file.ToString()); foreach (string line in lines) { diff --git a/cake/help-messages.cake b/recipe/help-messages.cake similarity index 100% rename from cake/help-messages.cake rename to recipe/help-messages.cake diff --git a/cake/known-extensions.cake b/recipe/known-extensions.cake similarity index 100% rename from cake/known-extensions.cake rename to recipe/known-extensions.cake diff --git a/cake/nuget-package.cake b/recipe/nuget-package.cake similarity index 88% rename from cake/nuget-package.cake rename to recipe/nuget-package.cake index 979fb7c68..b784bf6aa 100644 --- a/cake/nuget-package.cake +++ b/recipe/nuget-package.cake @@ -3,7 +3,8 @@ public class NuGetPackage : PackageDefinition public NuGetPackage( string id, string source, - PackageTestRunner testRunner = null, + IPackageTestRunner testRunner = null, + TestRunnerSource testRunnerSource = null, PackageCheck[] checks = null, PackageCheck[] symbols = null, IEnumerable tests = null) @@ -12,6 +13,7 @@ public class NuGetPackage : PackageDefinition id, source, testRunner: testRunner, + testRunnerSource: testRunnerSource, checks: checks, symbols: symbols, tests: tests) @@ -29,7 +31,7 @@ public class NuGetPackage : PackageDefinition // The file name of this package, including extension public override string PackageFileName => $"{PackageId}.{PackageVersion}.nupkg"; // The file name of any symbol package, including extension - public override string SymbolPackageName => System.IO.Path.ChangeExtension(PackageFileName, ".snupkg"); + public override string SymbolPackageName => SIO.Path.ChangeExtension(PackageFileName, ".snupkg"); // The directory into which this package is installed public override string PackageInstallDirectory => BuildSettings.NuGetTestDirectory; // The directory used to contain results of package tests for this package diff --git a/cake/package-checks.cake b/recipe/package-checks.cake similarity index 100% rename from cake/package-checks.cake rename to recipe/package-checks.cake diff --git a/cake/package-definition.cake b/recipe/package-definition.cake similarity index 66% rename from cake/package-definition.cake rename to recipe/package-definition.cake index f97cb9fec..ebbc663b7 100644 --- a/cake/package-definition.cake +++ b/recipe/package-definition.cake @@ -23,14 +23,17 @@ public abstract class PackageDefinition PackageType packageType, string id, string source, - PackageTestRunner testRunner = null, + IPackageTestRunner testRunner = null, + TestRunnerSource testRunnerSource = null, string extraTestArguments = null, PackageCheck[] checks = null, PackageCheck[] symbols = null, IEnumerable tests = null) { - if (testRunner == null && tests != null) - throw new System.ArgumentException($"Unable to create {packageType} package {id}: TestRunner must be provided if there are tests", nameof(testRunner)); + if (testRunner == null && testRunnerSource == null && tests != null) + throw new System.InvalidOperationException($"Unable to create {packageType} package {id}: TestRunner or TestRunnerSource must be provided if there are tests."); + if (testRunner != null && testRunnerSource != null) + throw new System.InvalidOperationException($"Unable to create {packageType} package {id}: Either TestRunner or TestRunnerSource must be provided, but not both."); _context = BuildSettings.Context; @@ -40,6 +43,7 @@ public abstract class PackageDefinition PackageSource = source; BasePath = BuildSettings.OutputDirectory; TestRunner = testRunner; + TestRunnerSource = testRunnerSource; ExtraTestArguments = extraTestArguments; PackageChecks = checks; SymbolChecks = symbols; @@ -51,7 +55,8 @@ public abstract class PackageDefinition public string PackageVersion { get; protected set; } public string PackageSource { get; } public string BasePath { get; } - public PackageTestRunner TestRunner { get; } + public IPackageTestRunner TestRunner { get; } + public TestRunnerSource TestRunnerSource { get; } public string ExtraTestArguments { get; } public PackageCheck[] PackageChecks { get; } public PackageCheck[] SymbolChecks { get; protected set; } @@ -201,44 +206,55 @@ public abstract class PackageDefinition // _context.Information("Deleted directory " + dirPath.GetDirectoryName()); //} - //if (TestRunner.RequiresInstallation) - // TestRunner.Install(); + // Package was defined with either a TestRunnerSource or a single TestRunner. In either + // case, these will all be package test runners and may or may not require installation. + var defaultRunners = TestRunnerSource ?? new TestRunnerSource((TestRunner)TestRunner); + + // Preinstall all runners requiring installation + InstallRunners(defaultRunners.PackageTestRunners); foreach (var packageTest in PackageTests) { if (packageTest.Level > BuildSettings.PackageTestLevel) continue; - foreach (ExtensionSpecifier extension in packageTest.ExtensionsNeeded) - extension.InstallExtension(this); + InstallExtensions(packageTest.ExtensionsNeeded); + InstallRunners(packageTest.TestRunners); - var testResultDir = $"{PackageResultDirectory}/{packageTest.Name}/"; - var resultFile = testResultDir + "TestResult.xml"; + // Use runners from the test if provided, otherwise the default runners + var runners = packageTest.TestRunners.Length > 0 ? packageTest.TestRunners : defaultRunners.PackageTestRunners; + + foreach (var runner in runners) + { + Console.WriteLine(runner.Version); + var testResultDir = $"{PackageResultDirectory}/{packageTest.Name}/"; + var resultFile = testResultDir + "TestResult.xml"; - Banner.Display(packageTest.Description); + Banner.Display(packageTest.Description); - _context.CreateDirectory(testResultDir); - string arguments = $"{packageTest.Arguments} {ExtraTestArguments} --work={testResultDir}"; - if (CommandLineOptions.TraceLevel.Value != "Off") - arguments += $" --trace:{CommandLineOptions.TraceLevel.Value}"; + _context.CreateDirectory(testResultDir); + string arguments = $"{packageTest.Arguments} {ExtraTestArguments} --work={testResultDir}"; + if (CommandLineOptions.TraceLevel.Value != "Off") + arguments += $" --trace:{CommandLineOptions.TraceLevel.Value}"; - int rc = TestRunner.Run(arguments); + int rc = runner.RunPackageTest(arguments); - try - { - var result = new ActualResult(resultFile); - var report = new PackageTestReport(packageTest, result); - reporter.AddReport(report); + try + { + var result = new ActualResult(resultFile); + var report = new PackageTestReport(packageTest, result, runner); + reporter.AddReport(report); - Console.WriteLine(report.Errors.Count == 0 - ? "\nSUCCESS: Test Result matches expected result!" - : "\nERROR: Test Result not as expected!"); - } - catch (Exception ex) - { - reporter.AddReport(new PackageTestReport(packageTest, ex)); + Console.WriteLine(report.Errors.Count == 0 + ? "\nSUCCESS: Test Result matches expected result!" + : "\nERROR: Test Result not as expected!"); + } + catch (Exception ex) + { + reporter.AddReport(new PackageTestReport(packageTest, ex)); - Console.WriteLine("\nERROR: No result found!"); + Console.WriteLine("\nERROR: No result found!"); + } } } @@ -257,6 +273,41 @@ public abstract class PackageDefinition if (hadErrors) throw new Exception("One or more package tests had errors!"); } + + private void InstallExtensions(ExtensionSpecifier[] extensionsNeeded) + { + foreach (ExtensionSpecifier extension in extensionsNeeded) + extension.InstallExtension(this); + } + + private void InstallRunners(IEnumerable runners) + { + // Install any runners needing installation + foreach (var runner in runners) + if (runner is InstallableTestRunner) + InstallRunner((InstallableTestRunner)runner); + } + + private void InstallRunner(InstallableTestRunner runner) + { + runner.Install(PackageInstallDirectory); + + // We are using nuget packages for the runner, so it won't normally recognize + // chocolatey extensions. We add an extra addins file for that purpose. + if (PackageType == PackageType.Chocolatey) + { + var filePath = runner.ExecutablePath.GetDirectory().CombineWithFilePath("choco.engine.addins").ToString(); + Console.WriteLine($"Creating {filePath}"); + + using (var writer = new StreamWriter(filePath)) + { + writer.WriteLine("../../nunit-extension-*/tools/"); + writer.WriteLine("../../nunit-extension-*/tools/*/"); + writer.WriteLine("../../../nunit-extension-*/tools/"); + writer.WriteLine("../../../nunit-extension-*/tools/*/"); + } + } + } public virtual void VerifySymbolPackage() { } // Does nothing. Overridden for NuGet packages. } diff --git a/cake/package-reference.cake b/recipe/package-reference.cake similarity index 100% rename from cake/package-reference.cake rename to recipe/package-reference.cake diff --git a/recipe/package-test.cake b/recipe/package-test.cake new file mode 100644 index 000000000..98c291792 --- /dev/null +++ b/recipe/package-test.cake @@ -0,0 +1,76 @@ +// Representation of a single test to be run against a pre-built package. +// Each test has a Level, with the following values defined... +// 0 Do not run - used for temporarily disabling a test +// 1 Run for all CI tests - that is every time we test packages +// 2 Run only on PRs, dev builds and when publishing +// 3 Run only when publishing +public struct PackageTest +{ + public int Level; + public string Name; + public string Description; + public string Arguments; + public ExpectedResult ExpectedResult; + public IPackageTestRunner[] TestRunners; + public ExtensionSpecifier[] ExtensionsNeeded; + + public PackageTest(int level, string name, string description, string arguments, ExpectedResult expectedResult ) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (description == null) + throw new ArgumentNullException(nameof(description)); + if (arguments == null) + throw new ArgumentNullException(nameof(arguments)); + if (expectedResult == null) + throw new ArgumentNullException(nameof(expectedResult)); + + Level = level; + Name = name; + Description = description; + Arguments = arguments; + ExpectedResult = expectedResult; + ExtensionsNeeded = new ExtensionSpecifier[0]; + TestRunners = new IPackageTestRunner[0]; + } + + public PackageTest(int level, string name, string description, string arguments, ExpectedResult expectedResult, params ExtensionSpecifier[] extensionsNeeded ) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (description == null) + throw new ArgumentNullException(nameof(description)); + if (arguments == null) + throw new ArgumentNullException(nameof(arguments)); + if (expectedResult == null) + throw new ArgumentNullException(nameof(expectedResult)); + + Level = level; + Name = name; + Description = description; + Arguments = arguments; + ExpectedResult = expectedResult; + ExtensionsNeeded = extensionsNeeded; + TestRunners = new IPackageTestRunner[0]; + } + + public PackageTest(int level, string name, string description, string arguments, ExpectedResult expectedResult, params IPackageTestRunner[] testRunners ) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (description == null) + throw new ArgumentNullException(nameof(description)); + if (arguments == null) + throw new ArgumentNullException(nameof(arguments)); + if (expectedResult == null) + throw new ArgumentNullException(nameof(expectedResult)); + + Level = level; + Name = name; + Description = description; + Arguments = arguments; + ExpectedResult = expectedResult; + TestRunners = testRunners; + ExtensionsNeeded = new ExtensionSpecifier[0]; + } +} diff --git a/cake/publishing.cake b/recipe/publishing.cake similarity index 100% rename from cake/publishing.cake rename to recipe/publishing.cake diff --git a/cake/setup.cake b/recipe/setup.cake similarity index 100% rename from cake/setup.cake rename to recipe/setup.cake diff --git a/cake/task-builders.cake b/recipe/task-builders.cake similarity index 100% rename from cake/task-builders.cake rename to recipe/task-builders.cake diff --git a/cake/task-definitions.cake b/recipe/task-definitions.cake similarity index 100% rename from cake/task-definitions.cake rename to recipe/task-definitions.cake diff --git a/cake/test-reports.cake b/recipe/test-reports.cake similarity index 93% rename from cake/test-reports.cake rename to recipe/test-reports.cake index 9faf03d3d..3ecc9863f 100644 --- a/cake/test-reports.cake +++ b/recipe/test-reports.cake @@ -2,15 +2,15 @@ public class PackageTestReport { public PackageTest Test; public ActualResult Result; - public string ConsoleVersion; + public ITestRunner Runner; public List Errors; public List Warnings; - public PackageTestReport(PackageTest test, ActualResult actualResult, string consoleVersion = null) + public PackageTestReport(PackageTest test, ActualResult actualResult, ITestRunner runner = null) { Test = test; Result = actualResult; - ConsoleVersion = consoleVersion; + Runner = runner; Errors = new List(); Warnings = new List(); @@ -39,7 +39,7 @@ public class PackageTestReport if (expected.AssemblyName != actual.AssemblyName) Errors.Add($" Expected: {expected.AssemblyName} But was: { actual.AssemblyName}"); - else if (consoleVersion == null || !consoleVersion.StartsWith("NetCore.")) + else if (runner == null || runner.PackageId == "NUnit.ConsoleRunner.NetCore") { if (actual.Runtime == null) Warnings.Add($"Unable to determine actual runtime used for {expected.AssemblyName}"); @@ -55,21 +55,21 @@ public class PackageTestReport Errors.Add($" Found unexpected assembly {actualAssemblies[i].AssemblyName}"); } - public PackageTestReport(PackageTest test, Exception ex, string consoleVersion = null) + public PackageTestReport(PackageTest test, Exception ex, ITestRunner runner = null) { Test = test; Result = null; Errors = new List(); Errors.Add($" {ex.Message}"); - ConsoleVersion = consoleVersion; + Runner = runner; } public void Display(int index, TextWriter writer) { writer.WriteLine(); writer.WriteLine($"{index}. {Test.Description}"); - if (ConsoleVersion != null) - writer.WriteLine($" ConsoleVersion: {ConsoleVersion}"); + if (Runner != null) + writer.WriteLine($" Runner: {Runner.PackageId} {Runner.Version}"); writer.WriteLine($" Args: {Test.Arguments}"); writer.WriteLine(); diff --git a/cake/test-results.cake b/recipe/test-results.cake similarity index 100% rename from cake/test-results.cake rename to recipe/test-results.cake diff --git a/recipe/test-runners.cake b/recipe/test-runners.cake new file mode 100644 index 000000000..80f524dbe --- /dev/null +++ b/recipe/test-runners.cake @@ -0,0 +1,224 @@ +///////////////////////////////////////////////////////////////////////////// +// TEST RUNNER INTERFACES +///////////////////////////////////////////////////////////////////////////// + +/// +/// Common interface for all test runners +/// +public interface ITestRunner +{ + string PackageId { get; } + string Version { get; } +} + +/// +/// A runner capable of running unit tests +/// +public interface IUnitTestRunner : ITestRunner +{ + int RunUnitTest(FilePath testPath); +} + +/// +/// A runner capable of running package tests +/// +public interface IPackageTestRunner : ITestRunner +{ + int RunPackageTest(string arguments); +} + +///////////////////////////////////////////////////////////////////////////// +// ABSTRACT TEST RUNNER +///////////////////////////////////////////////////////////////////////////// + +/// +/// The TestRunner class is the abstract base for all TestRunners used to run unit- +/// or package-tests. A TestRunner knows how to run a test assembly and provide a result. +/// All base functionality is implemented in this class. Derived classes make that +/// functionality available selectively by implementing specific interfaces. +/// +public abstract class TestRunner : ITestRunner +{ + protected ICakeContext Context => BuildSettings.Context; + + public string PackageId { get; protected set; } + public string Version { get; protected set; } + + protected int RunTest(FilePath executablePath, string arguments = null) + { + return RunTest(executablePath, new ProcessSettings { Arguments = arguments }); + } + + protected int RunTest(FilePath executablePath, ProcessSettings processSettings=null) + { + if (executablePath == null) + throw new ArgumentNullException(nameof(executablePath)); + + if (processSettings == null) + processSettings = new ProcessSettings(); + + // Add default values to settings if not present + if (processSettings.WorkingDirectory == null) + processSettings.WorkingDirectory = BuildSettings.OutputDirectory; + + if (executablePath.GetExtension() == ".dll") + return Context.StartProcess("dotnet", processSettings); + else + return Context.StartProcess(executablePath, processSettings); + } +} + +/// +/// A TestRunner requiring some sort of installation before use. +/// +public abstract class InstallableTestRunner : TestRunner +{ + protected InstallableTestRunner(string packageId, string version) + { + PackageId = packageId; + Version = version; + } + + protected abstract FilePath ExecutableRelativePath { get; } + + // Path under tools directory where package would be installed by Cake #tool directive. + // NOTE: When used to run unit tests, a #tool directive is required. If derived package + // is only used for package tests, it is optional. + protected DirectoryPath ToolInstallDirectory => BuildSettings.ToolsDirectory + $"{PackageId}.{Version}"; + protected bool IsInstalledAsTool => + ToolInstallDirectory != null && Context.DirectoryExists(ToolInstallDirectory); + + protected DirectoryPath InstallDirectory; + + public FilePath ExecutablePath => InstallDirectory.CombineWithFilePath(ExecutableRelativePath); + + public void Install(DirectoryPath installDirectory) + { + InstallDirectory = installDirectory.Combine($"{PackageId}.{Version}"); + + // If the runner package is already installed as a cake tool, we just copy it + if (IsInstalledAsTool) + Context.CopyDirectory(ToolInstallDirectory, InstallDirectory); + // Otherwise, we install it to the requested location + else + Context.NuGetInstall( + PackageId, + new NuGetInstallSettings() { OutputDirectory = installDirectory, Version = Version }); + } +} + +///////////////////////////////////////////////////////////////////////////// +// TEST RUNNER SOURCE +///////////////////////////////////////////////////////////////////////////// + +/// +/// TestRunnerSource is a provider of TestRunners. It is used when the tests +/// are to be run under multiple TestRunners rather than just one. +/// +public class TestRunnerSource +{ + public TestRunnerSource(TestRunner runner1, params TestRunner[] moreRunners) + { + AllRunners.Add(runner1); + AllRunners.AddRange(moreRunners); + } + + public List AllRunners { get; } = new List(); + + public IEnumerable UnitTestRunners + { + get { foreach(var runner in AllRunners.Where(r => r is IUnitTestRunner)) yield return (IUnitTestRunner)runner; } + } + + public IEnumerable PackageTestRunners + { + get { foreach(var runner in AllRunners.Where(r => r is IPackageTestRunner)) yield return (IPackageTestRunner)runner; } + } +} + +///////////////////////////////////////////////////////////////////////////// +// NUNITLITE RUNNER +///////////////////////////////////////////////////////////////////////////// + +// For NUnitLite tests, the test is run directly +public class NUnitLiteRunner : TestRunner, IUnitTestRunner +{ + public int RunUnitTest(FilePath testPath) => + RunTest(testPath, BuildSettings.UnitTestArguments); +} + +///////////////////////////////////////////////////////////////////////////// +// NUNIT CONSOLE RUNNERS +///////////////////////////////////////////////////////////////////////////// + +// NUnitConsoleRunner is used for both unit and package tests. It must be pre-installed +// in the tools directory by use of a #tools directive. +public class NUnitConsoleRunner : InstallableTestRunner, IUnitTestRunner, IPackageTestRunner +{ + protected override FilePath ExecutableRelativePath => "tools/nunit3-console.exe"; + + public NUnitConsoleRunner(string version) : base("NUnit.ConsoleRunner", version) { } + + // Run a unit test + public int RunUnitTest(FilePath testPath) => RunTest(ToolInstallDirectory.CombineWithFilePath(ExecutableRelativePath), $"\"{testPath}\" {BuildSettings.UnitTestArguments}"); + + // Run a package test + public int RunPackageTest(string arguments) => RunTest(ExecutablePath, arguments); +} + +public class NUnitNetCoreConsoleRunner : InstallableTestRunner, IUnitTestRunner, IPackageTestRunner +{ + protected override FilePath ExecutableRelativePath => "tools/net6.0/nunit3-console.exe"; + + public NUnitNetCoreConsoleRunner(string version) : base("NUnit.ConsoleRunner.NetCore", version) { } + + // Run a unit test + public int RunUnitTest(FilePath testPath) => RunTest(ExecutablePath, $"\"{testPath}\" {BuildSettings.UnitTestArguments}"); + + // Run a package test + public int RunPackageTest(string arguments) => RunTest(ExecutablePath, arguments); +} + +public class EngineExtensionTestRunner : TestRunner, IPackageTestRunner +{ + private IPackageTestRunner[] _runners = new IPackageTestRunner[] { + new NUnitConsoleRunner("3.17.0"), + new NUnitConsoleRunner("3.15.5") + }; + + public int RunPackageTest(string arguments) + { + + return _runners[0].RunPackageTest(arguments); + } +} + +///////////////////////////////////////////////////////////////////////////// +// AGENT RUNNER +///////////////////////////////////////////////////////////////////////////// + +/// +/// Class that knows how to run an agent directly. (For future use) +/// +public class AgentRunner : TestRunner, IPackageTestRunner +{ + private string _stdExecutable; + private string _x86Executable; + + private FilePath _executablePath; + + public AgentRunner(string stdExecutable, string x86Executable = null) + { + _stdExecutable = stdExecutable; + _x86Executable = x86Executable; + } + + public int RunPackageTest(string arguments) + { + _executablePath = arguments.Contains("--x86") + ? _x86Executable + : _stdExecutable; + + return base.RunTest(_executablePath, arguments.Replace("--x86", string.Empty)); + } +} diff --git a/recipe/tools.cake b/recipe/tools.cake new file mode 100644 index 000000000..5aabb7680 --- /dev/null +++ b/recipe/tools.cake @@ -0,0 +1,27 @@ +// Load all tools used by the recipe +#tool NuGet.CommandLine&version=6.9.1 +#tool dotnet:?package=GitVersion.Tool&version=5.12.0 +#tool dotnet:?package=GitReleaseManager.Tool&version=0.17.0 + +public static class Tools +{ + public static DirectoryPath FindInstalledTool(string packageId) + { + if (SIO.Directory.Exists(BuildSettings.ToolsDirectory + packageId)) + return BuildSettings.ToolsDirectory + packageId; + + foreach(var dir in BuildSettings.Context.GetDirectories(BuildSettings.ToolsDirectory + $"{packageId}.*")) + return dir; // Use first one found + + return null; + } + + public static DirectoryPath FindInstalledTool(string packageId, string version) + { + if (version == null) + throw new ArgumentNullException(nameof(version)); + + var toolPath = BuildSettings.ToolsDirectory + $"{packageId}.{version}"; + return BuildSettings.ToolsDirectory + $"{packageId}.{version}"; + } +} \ No newline at end of file diff --git a/cake/unit-testing.cake b/recipe/unit-testing.cake similarity index 96% rename from cake/unit-testing.cake rename to recipe/unit-testing.cake index 766e8036d..24094d050 100644 --- a/cake/unit-testing.cake +++ b/recipe/unit-testing.cake @@ -18,6 +18,8 @@ public static class UnitTesting _context.Information($"Located {unitTests.Count} unit test assemblies."); var errors = new List(); + var runner = BuildSettings.UnitTestRunner ?? new NUnitLiteRunner(); + foreach (var testPath in unitTests) { var testFile = testPath.GetFilename(); @@ -28,8 +30,7 @@ public static class UnitTesting ? $"Running {testFile} under {runtime}" : $"Running {testFile}"); - var runner = BuildSettings.UnitTestRunner ?? new NUnitLiteRunner(); - int rc = runner.Run(testPath); + int rc = runner.RunUnitTest(testPath); var name = runtime != null ? $"{testFile}({runtime})" diff --git a/cake/utilities.cake b/recipe/utilities.cake similarity index 100% rename from cake/utilities.cake rename to recipe/utilities.cake diff --git a/cake/versioning.cake b/recipe/versioning.cake similarity index 100% rename from cake/versioning.cake rename to recipe/versioning.cake diff --git a/cake/zip-package.cake b/recipe/zip-package.cake similarity index 90% rename from cake/zip-package.cake rename to recipe/zip-package.cake index b10946349..4f27d03be 100644 --- a/cake/zip-package.cake +++ b/recipe/zip-package.cake @@ -3,7 +3,8 @@ public class ZipPackage : PackageDefinition public ZipPackage( string id, string source, - PackageTestRunner testRunner = null, + IPackageTestRunner testRunner = null, + TestRunnerSource testRunnerSource = null, PackageCheck[] checks = null, IEnumerable tests = null, PackageReference[] bundledExtensions = null ) @@ -11,7 +12,8 @@ public class ZipPackage : PackageDefinition PackageType.Zip, id, source, - testRunner: testRunner, + testRunner: testRunner, + testRunnerSource: testRunnerSource, checks: checks, tests: tests) { @@ -63,7 +65,7 @@ public class ZipPackage : PackageDefinition var addinsDir = BuildSettings.ZipImageDirectory + "bin/net462/addins/"; _context.CreateDirectory(addinsDir); - foreach (var packageDir in System.IO.Directory.GetDirectories(BuildSettings.ExtensionsDirectory)) + foreach (var packageDir in SIO.Directory.GetDirectories(BuildSettings.ExtensionsDirectory)) { var files = _context.GetFiles(packageDir + "/tools/*").Concat(_context.GetFiles(packageDir + "/tools/net462/*")); _context.CopyFiles(files.Where(f => f.GetExtension() != ".addins"), addinsDir); From 4a735ef4051e494cc68af402008d3ad50ed6ad86 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 14 Jul 2024 23:45:04 -0700 Subject: [PATCH 075/190] Use shared nunit cake recipe --- NUnitConsole.sln | 33 --- appveyor.yml | 3 +- build.cake | 6 +- recipe/banner.cake | 23 --- recipe/build-settings.cake | 342 ------------------------------- recipe/builder.cake | 22 -- recipe/chocolatey-package.cake | 44 ---- recipe/command-line-options.cake | 118 ----------- recipe/constants.cake | 53 ----- recipe/dotnet.cake | 88 -------- recipe/extending.cake | 57 ------ recipe/headers.cake | 115 ----------- recipe/help-messages.cake | 77 ------- recipe/known-extensions.cake | 48 ----- recipe/nuget-package.cake | 58 ------ recipe/package-checks.cake | 148 ------------- recipe/package-definition.cake | 313 ---------------------------- recipe/package-reference.cake | 90 -------- recipe/package-test.cake | 76 ------- recipe/publishing.cake | 244 ---------------------- recipe/setup.cake | 51 ----- recipe/task-builders.cake | 48 ----- recipe/task-definitions.cake | 134 ------------ recipe/test-reports.cake | 171 ---------------- recipe/test-results.cake | 168 --------------- recipe/test-runners.cake | 224 -------------------- recipe/tools.cake | 27 --- recipe/unit-testing.cake | 95 --------- recipe/utilities.cake | 10 - recipe/versioning.cake | 115 ----------- recipe/zip-package.cake | 74 ------- 31 files changed, 6 insertions(+), 3069 deletions(-) delete mode 100644 recipe/banner.cake delete mode 100644 recipe/build-settings.cake delete mode 100644 recipe/builder.cake delete mode 100644 recipe/chocolatey-package.cake delete mode 100644 recipe/command-line-options.cake delete mode 100644 recipe/constants.cake delete mode 100644 recipe/dotnet.cake delete mode 100644 recipe/extending.cake delete mode 100644 recipe/headers.cake delete mode 100644 recipe/help-messages.cake delete mode 100644 recipe/known-extensions.cake delete mode 100644 recipe/nuget-package.cake delete mode 100644 recipe/package-checks.cake delete mode 100644 recipe/package-definition.cake delete mode 100644 recipe/package-reference.cake delete mode 100644 recipe/package-test.cake delete mode 100644 recipe/publishing.cake delete mode 100644 recipe/setup.cake delete mode 100644 recipe/task-builders.cake delete mode 100644 recipe/task-definitions.cake delete mode 100644 recipe/test-reports.cake delete mode 100644 recipe/test-results.cake delete mode 100644 recipe/test-runners.cake delete mode 100644 recipe/tools.cake delete mode 100644 recipe/unit-testing.cake delete mode 100644 recipe/utilities.cake delete mode 100644 recipe/versioning.cake delete mode 100644 recipe/zip-package.cake diff --git a/NUnitConsole.sln b/NUnitConsole.sln index ad28c0a8e..d887627cc 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -136,38 +136,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{C5B712 .config\dotnet-tools.json = .config\dotnet-tools.json EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "recipe", "recipe", "{D6449B7A-20FF-467B-A65E-174DD6992AEB}" - ProjectSection(SolutionItems) = preProject - recipe\banner.cake = recipe\banner.cake - recipe\build-settings.cake = recipe\build-settings.cake - recipe\builder.cake = recipe\builder.cake - recipe\chocolatey-package.cake = recipe\chocolatey-package.cake - recipe\command-line-options.cake = recipe\command-line-options.cake - recipe\constants.cake = recipe\constants.cake - recipe\dotnet.cake = recipe\dotnet.cake - recipe\extending.cake = recipe\extending.cake - recipe\headers.cake = recipe\headers.cake - recipe\help-messages.cake = recipe\help-messages.cake - recipe\known-extensions.cake = recipe\known-extensions.cake - recipe\nuget-package.cake = recipe\nuget-package.cake - recipe\package-checks.cake = recipe\package-checks.cake - recipe\package-definition.cake = recipe\package-definition.cake - recipe\package-reference.cake = recipe\package-reference.cake - recipe\package-test.cake = recipe\package-test.cake - recipe\publishing.cake = recipe\publishing.cake - recipe\setup.cake = recipe\setup.cake - recipe\task-builders.cake = recipe\task-builders.cake - recipe\task-definitions.cake = recipe\task-definitions.cake - recipe\test-reports.cake = recipe\test-reports.cake - recipe\test-results.cake = recipe\test-results.cake - recipe\test-runners.cake = recipe\test-runners.cake - recipe\tools.cake = recipe\tools.cake - recipe\unit-testing.cake = recipe\unit-testing.cake - recipe\utilities.cake = recipe\utilities.cake - recipe\versioning.cake = recipe\versioning.cake - recipe\zip-package.cake = recipe\zip-package.cake - EndProjectSection -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -250,7 +218,6 @@ Global {08F8160E-E691-4F07-9F57-EA31B9736429} = {25DA12FE-6209-4524-9A37-8E51F815E198} {50371E48-BEC3-4D53-BD37-F3A6149CFD0D} = {25DA12FE-6209-4524-9A37-8E51F815E198} {C5B7120C-190B-4C38-95CB-83F12799598D} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} - {D6449B7A-20FF-467B-A65E-174DD6992AEB} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/appveyor.yml b/appveyor.yml index 765697b43..e255a721a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -29,4 +29,5 @@ environment: CHOCO_API_KEY: secure: aDsu1U+umVYFVybjkBVtVQsatSj3QKbD7VkGQci9mNF3493g9Giao/GABISIaHjT GITHUB_ACCESS_TOKEN: - secure: RJ6sKRBZzwXz8JQvj8zcp45mkHNDad1UlvmfCsiVx63V9/pXHcm2Y2Lg/G/Vyhlz \ No newline at end of file + secure: RJ6sKRBZzwXz8JQvj8zcp45mkHNDad1UlvmfCsiVx63V9/pXHcm2Y2Lg/G/Vyhlz + IGNORE_NORMALISATION_GIT_HEAD_MOVE: 1 \ No newline at end of file diff --git a/build.cake b/build.cake index 6b55254f1..b47d04288 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,7 @@ -// Load scripts -#load recipe/*.cake +// Load the recipe +#load nuget:?package=NUnit.Cake.Recipe&version=1.0.0-dev00001 +// Comment out above line and uncomment below for local tests of recipe changes +//#load ../NUnit.Cake.Recipe/recipe/*.cake // Initialize BuildSettings BuildSettings.Initialize( diff --git a/recipe/banner.cake b/recipe/banner.cake deleted file mode 100644 index b882509ba..000000000 --- a/recipe/banner.cake +++ /dev/null @@ -1,23 +0,0 @@ -// Banner class displays banners similar to those created by Cake -// We use this to display intermediate steps within a task -public static class Banner -{ - public static void Display(string message, char barChar='-', int length=0) - { - if (length == 0) length = MaxLineLength(message); - var bar = new string(barChar, length); - - Console.WriteLine(); - Console.WriteLine(bar); - Console.WriteLine(message); - Console.WriteLine(bar); - - int MaxLineLength(string message) - { - int length = 0; - foreach (string line in message.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)) - length = Math.Max(length, line.Length); - return length; - } - } -} diff --git a/recipe/build-settings.cake b/recipe/build-settings.cake deleted file mode 100644 index c503c798e..000000000 --- a/recipe/build-settings.cake +++ /dev/null @@ -1,342 +0,0 @@ -public static class BuildSettings -{ - private static BuildSystem _buildSystem; - - public static void Initialize( - // Required parameters - ICakeContext context, - string title, - string githubRepository, - - // Optional Parameters - bool suppressHeaderCheck = false, - string[] standardHeader = null, - string[] exemptFiles = null, - - string solutionFile = null, - string[] validConfigurations = null, - string githubOwner = "NUnit", - - bool msbuildAllowPreviewVersion = false, - Verbosity msbuildVerbosity = Verbosity.Minimal, - - string unitTests = null, // Defaults to "**/*.tests.dll|**/*.tests.exe" (case insensitive) - IUnitTestRunner unitTestRunner = null, // If not set, NUnitLite is used - string unitTestArguments = null - ) - { - // Required arguments - Context = context ?? throw new ArgumentNullException(nameof(context)); - Title = title ?? throw new ArgumentNullException(nameof(title)); - GitHubRepository = githubRepository ?? throw new ArgumentNullException(nameof(githubRepository)); - - // NOTE: Order of initialization can be sensitive. Obviously, - // we have to set any properties in this method before we - // make use of them. Less obviously, some of the classes we - // construct here have dependencies on certain properties - // being set before the constructor is called. I have - // tried to annotate such dependencies below. - - _buildSystem = context.BuildSystem(); - - SolutionFile = solutionFile ?? DeduceSolutionFile(); - - ValidConfigurations = validConfigurations ?? DEFAULT_VALID_CONFIGS; - - UnitTests = unitTests; - // NUnitLiteRunner depends indirectly on ValidConfigurations - UnitTestRunner = unitTestRunner ?? new NUnitLiteRunner(); - UnitTestArguments = unitTestArguments; - - BuildVersion = new BuildVersion(context); - - GitHubOwner = githubOwner; - - // File Header Checks - SuppressHeaderCheck = suppressHeaderCheck && !CommandLineOptions.NoBuild; - StandardHeader = standardHeader ?? DEFAULT_STANDARD_HEADER; - ExemptFiles = exemptFiles ?? new string[0]; - - //if (defaultTarget != null) - // BuildTasks.DefaultTask.IsDependentOn(defaultTarget); - - // Skip remaining initialization if help was requested - if (CommandLineOptions.Usage) - return; - - ValidateSettings(); - - context.Information($"{Title} {Configuration} version {PackageVersion}"); - - // Output like this should go after the run title display - if (solutionFile == null && SolutionFile != null) - Context.Warning($" SolutionFile: '{SolutionFile}'"); - Context.Information($" PackageTestLevel: {PackageTestLevel}"); - - // Keep this last - if (IsRunningOnAppVeyor) - { - var buildNumber = _buildSystem.AppVeyor.Environment.Build.Number; - _buildSystem.AppVeyor.UpdateBuildVersion($"{PackageVersion}-{buildNumber}"); - } - } - - // If solution file was not provided, uses TITLE.sln if it exists or - // the solution found in the root directory provided there is only one. - private static string DeduceSolutionFile() - { - if (SIO.File.Exists(Title + ".sln")) - return Title + ".sln"; - - var files = SIO.Directory.GetFiles(ProjectDirectory, "*.sln"); - if (files.Length == 1 && SIO.File.Exists(files[0])) - return files[0]; - - return null; - } - - private static int CalcPackageTestLevel() - { - if (!BuildVersion.IsPreRelease) - return 3; - - // TODO: The prerelease label is no longer being set to pr by GitVersion - // for some reason. This check in AppVeyor is a workaround. - if (IsRunningOnAppVeyor && _buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest) - return 2; - - switch (BuildVersion.PreReleaseLabel) - { - case "pre": - case "rc": - case "alpha": - case "beta": - return 3; - - case "dev": - case "pr": - return 2; - - case "ci": - default: - return 1; - } - } - - // Cake Context - public static ICakeContext Context { get; private set; } - - // NOTE: These are set in setup.cake - public static string Target { get; set; } - public static IEnumerable TasksToExecute { get; set; } - - // Arguments - public static string Configuration - { - get - { - // Correct casing on user-provided config if necessary - foreach (string config in ValidConfigurations) - if (string.Equals(config, CommandLineOptions.Configuration.Value, StringComparison.OrdinalIgnoreCase)) - return config; - - // Return the (invalid) user-provided config - return CommandLineOptions.Configuration.Value; - } - } - - // Build Environment - public static bool IsLocalBuild => _buildSystem.IsLocalBuild; - public static bool IsRunningOnUnix => Context.IsRunningOnUnix(); - public static bool IsRunningOnWindows => Context.IsRunningOnWindows(); - public static bool IsRunningOnAppVeyor => _buildSystem.AppVeyor.IsRunningOnAppVeyor; - - // Versioning - public static BuildVersion BuildVersion { get; private set;} - public static string BranchName => BuildVersion.BranchName; - public static bool IsReleaseBranch => BuildVersion.IsReleaseBranch; - public static string PackageVersion => BuildVersion.PackageVersion; - public static string AssemblyVersion => BuildVersion.AssemblyVersion; - public static string AssemblyFileVersion => BuildVersion.AssemblyFileVersion; - public static string AssemblyInformationalVersion => BuildVersion.AssemblyInformationalVersion; - public static bool IsDevelopmentRelease => PackageVersion.Contains("-dev"); - - // Standard Directory Structure - not changeable by user - public static string ProjectDirectory => Context.Environment.WorkingDirectory.FullPath + "/"; - public static string SourceDirectory => ProjectDirectory + SRC_DIR; - public static string OutputDirectory => ProjectDirectory + BIN_DIR + Configuration + "/"; - public static string NuGetDirectory => ProjectDirectory + NUGET_DIR; - public static string ChocolateyDirectory => ProjectDirectory + CHOCO_DIR; - public static string ZipDirectory => ProjectDirectory + ZIP_DIR; - public static string PackageDirectory => ProjectDirectory + PACKAGE_DIR; - public static string PackageTestDirectory => ProjectDirectory + PKG_TEST_DIR; - public static string NuGetTestDirectory => ProjectDirectory + NUGET_TEST_DIR; - public static string ChocolateyTestDirectory => ProjectDirectory + CHOCO_TEST_DIR; - public static string ZipTestDirectory => ProjectDirectory + ZIP_TEST_DIR; - public static string PackageResultDirectory => ProjectDirectory + PKG_RSLT_DIR; - public static string NuGetResultDirectory => ProjectDirectory + NUGET_RSLT_DIR; - public static string ChocolateyResultDirectory => ProjectDirectory + CHOCO_RSLT_DIR; - public static string ZipResultDirectory => ProjectDirectory + ZIP_RSLT_DIR; - public static string ImageDirectory => ProjectDirectory + IMAGE_DIR; - public static string ZipImageDirectory => ProjectDirectory + ZIP_IMG_DIR; - public static string ExtensionsDirectory => ProjectDirectory + "bundled-extensions/"; - public static string ToolsDirectory => ProjectDirectory + "tools/"; - - // Files - public static string SolutionFile { get; set; } - - // Building - public static string[] ValidConfigurations { get; set; } - public static bool MSBuildAllowPreviewVersion { get; set; } - public static Verbosity MSBuildVerbosity { get; set; } - public static MSBuildSettings MSBuildSettings => new MSBuildSettings { - Verbosity = MSBuildVerbosity, - Configuration = Configuration, - PlatformTarget = PlatformTarget.MSIL, - AllowPreviewVersion = MSBuildAllowPreviewVersion - }; - - // File Header Checks - public static bool SuppressHeaderCheck { get; private set; } - public static string[] StandardHeader { get; private set; } - public static string[] ExemptFiles { get; private set; } - - //Testing - public static string UnitTests { get; set; } - public static IUnitTestRunner UnitTestRunner { get; private set; } - public static string UnitTestArguments { get; private set; } - - // Packaging - public static string Title { get; private set; } - public static List Packages { get; } = new List(); - - // Package Testing - public static int PackageTestLevel => - CommandLineOptions.TestLevel.Value > 0 - ? CommandLineOptions.TestLevel.Value - : CalcPackageTestLevel(); - - // Publishing - MyGet - public static string MyGetPushUrl => MYGET_PUSH_URL; - public static string MyGetApiKey => Context.EnvironmentVariable(MYGET_API_KEY); - - // Publishing - NuGet - public static string NuGetPushUrl => NUGET_PUSH_URL; - public static string NuGetApiKey => Context.EnvironmentVariable(NUGET_API_KEY); - - // Publishing - Chocolatey - public static string ChocolateyPushUrl => CHOCO_PUSH_URL; - public static string ChocolateyApiKey => Context.EnvironmentVariable(CHOCO_API_KEY); - - // Publishing - GitHub - public static string GitHubOwner { get; set; } - public static string GitHubRepository { get; set; } - public static string GitHubAccessToken => Context.EnvironmentVariable(GITHUB_ACCESS_TOKEN); - - public static bool IsPreRelease => BuildVersion.IsPreRelease; - public static bool ShouldPublishToMyGet => - !IsPreRelease || LABELS_WE_PUBLISH_ON_MYGET.Contains(BuildVersion.PreReleaseLabel); - public static bool ShouldPublishToNuGet => - !IsPreRelease || LABELS_WE_PUBLISH_ON_NUGET.Contains(BuildVersion.PreReleaseLabel); - public static bool ShouldPublishToChocolatey => - !IsPreRelease || LABELS_WE_PUBLISH_ON_CHOCOLATEY.Contains(BuildVersion.PreReleaseLabel); - public static bool IsProductionRelease => - !IsPreRelease || LABELS_WE_RELEASE_ON_GITHUB.Contains(BuildVersion.PreReleaseLabel); - - private static void ValidateSettings() - { - var validationErrors = new List(); - - if (!ValidConfigurations.Contains(Configuration)) - validationErrors.Add($"Invalid configuration: {Configuration}"); - - if (validationErrors.Count > 0) - { - DumpSettings(); - - var msg = new StringBuilder("Parameter validation failed! See settings above.\n\nErrors found:\n"); - foreach (var error in validationErrors) - msg.AppendLine(" " + error); - - throw new InvalidOperationException(msg.ToString()); - } - } - - public static void DumpSettings() - { - Console.WriteLine("\nTASKS"); - Console.WriteLine("Target: " + Target); - Console.WriteLine("TasksToExecute: " + string.Join(", ", TasksToExecute)); - - Console.WriteLine("\nENVIRONMENT"); - Console.WriteLine("IsLocalBuild: " + IsLocalBuild); - Console.WriteLine("IsRunningOnWindows: " + IsRunningOnWindows); - Console.WriteLine("IsRunningOnUnix: " + IsRunningOnUnix); - Console.WriteLine("IsRunningOnAppVeyor: " + IsRunningOnAppVeyor); - - Console.WriteLine("\nVERSIONING"); - Console.WriteLine("PackageVersion: " + PackageVersion); - Console.WriteLine("AssemblyVersion: " + AssemblyVersion); - Console.WriteLine("AssemblyFileVersion: " + AssemblyFileVersion); - Console.WriteLine("AssemblyInformationalVersion: " + AssemblyInformationalVersion); - Console.WriteLine("SemVer: " + BuildVersion.SemVer); - Console.WriteLine("IsPreRelease: " + BuildVersion.IsPreRelease); - Console.WriteLine("PreReleaseLabel: " + BuildVersion.PreReleaseLabel); - Console.WriteLine("PreReleaseSuffix: " + BuildVersion.PreReleaseSuffix); - - Console.WriteLine("\nDIRECTORIES"); - Console.WriteLine("Project: " + ProjectDirectory); - Console.WriteLine("Output: " + OutputDirectory); - Console.WriteLine("Source: " + SourceDirectory); - Console.WriteLine("NuGet: " + NuGetDirectory); - Console.WriteLine("Chocolatey: " + ChocolateyDirectory); - Console.WriteLine("Package: " + PackageDirectory); - Console.WriteLine("PackageTest: " + PackageTestDirectory); - Console.WriteLine("NuGetTest: " + NuGetTestDirectory); - Console.WriteLine("ChocoTest: " + ChocolateyTestDirectory); - Console.WriteLine("ZipTest: " + ZipTestDirectory); - Console.WriteLine("PackageResult: " + PackageResultDirectory); - Console.WriteLine("NuGetResult: " + NuGetResultDirectory); - Console.WriteLine("ChocoResult: " + ChocolateyResultDirectory); - Console.WriteLine("ZipResult: " + ZipResultDirectory); - Console.WriteLine("Image: " + ImageDirectory); - Console.WriteLine("ZipImage: " + ZipImageDirectory); - - Console.WriteLine("\nBUILD"); - Console.WriteLine("Configuration: " + Configuration); - - Console.WriteLine("\nUNIT TESTS"); - Console.WriteLine("UnitTests: " + UnitTests); - Console.WriteLine("UnitTestRunner: " + UnitTestRunner?.GetType().Name ?? ""); - - Console.WriteLine("\nPACKAGING"); - Console.WriteLine("PackageTestLevel: " + PackageTestLevel); - Console.WriteLine("MyGetPushUrl: " + MyGetPushUrl); - Console.WriteLine("NuGetPushUrl: " + NuGetPushUrl); - Console.WriteLine("ChocolateyPushUrl: " + ChocolateyPushUrl); - Console.WriteLine("MyGetApiKey: " + (!string.IsNullOrEmpty(MyGetApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); - Console.WriteLine("NuGetApiKey: " + (!string.IsNullOrEmpty(NuGetApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); - Console.WriteLine("ChocolateyApiKey: " + (!string.IsNullOrEmpty(ChocolateyApiKey) ? "AVAILABLE" : "NOT AVAILABLE")); - Console.WriteLine("GitHubAccessToken: " + (!string.IsNullOrEmpty(GitHubAccessToken) ? "AVAILABLE" : "NOT AVAILABLE")); - - Console.WriteLine("\nPACKAGES"); - foreach (var package in Packages) - { - Console.WriteLine(package.PackageId); - Console.WriteLine(" PackageType: " + package.PackageType); - Console.WriteLine(" PackageFileName: " + package.PackageFileName); - Console.WriteLine(" PackageInstallDirectory: " + package.PackageInstallDirectory); - Console.WriteLine(" PackageTestDirectory: " + package.PackageTestDirectory); - } - - Console.WriteLine("\nPUBLISHING"); - Console.WriteLine("ShouldPublishToMyGet: " + ShouldPublishToMyGet); - Console.WriteLine("ShouldPublishToNuGet: " + ShouldPublishToNuGet); - Console.WriteLine("ShouldPublishToChocolatey: " + ShouldPublishToChocolatey); - - Console.WriteLine("\nRELEASING"); - Console.WriteLine("BranchName: " + BranchName); - Console.WriteLine("IsReleaseBranch: " + IsReleaseBranch); - Console.WriteLine("IsProductionRelease: " + IsProductionRelease); - } -} \ No newline at end of file diff --git a/recipe/builder.cake b/recipe/builder.cake deleted file mode 100644 index ea91e367d..000000000 --- a/recipe/builder.cake +++ /dev/null @@ -1,22 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// EXECUTION -////////////////////////////////////////////////////////////////////// - -public Builder Build => CommandLineOptions.Usage - ? new Builder(() => Information(HelpMessages.Usage)) - : new Builder(() => RunTarget(CommandLineOptions.Target.Value)); - -public class Builder -{ - private Action _action; - - public Builder(Action action) - { - _action = action; - } - - public void Run() - { - _action(); - } -} diff --git a/recipe/chocolatey-package.cake b/recipe/chocolatey-package.cake deleted file mode 100644 index 7645d1bc8..000000000 --- a/recipe/chocolatey-package.cake +++ /dev/null @@ -1,44 +0,0 @@ -public class ChocolateyPackage : PackageDefinition -{ - public ChocolateyPackage( - string id, - string source, - IPackageTestRunner testRunner = null, - TestRunnerSource testRunnerSource = null, - PackageCheck[] checks = null, - IEnumerable tests = null) - : base( - PackageType.Chocolatey, - id, - source, - testRunner: testRunner, - testRunnerSource: testRunnerSource, - checks: checks, - tests: tests) - { - if (!source.EndsWith(".nuspec")) - throw new ArgumentException("Source must be a nuspec file", nameof(source)); - } - - // The file name of this package, including extension - public override string PackageFileName => $"{PackageId}.{PackageVersion}.nupkg"; - // The file name of any symbol package, including extension - public override string SymbolPackageName => SIO.Path.ChangeExtension(PackageFileName, ".snupkg"); - // The directory into which this package is installed - public override string PackageInstallDirectory => BuildSettings.ChocolateyTestDirectory; - // The directory used to contain results of package tests for this package - public override string PackageResultDirectory => BuildSettings.ChocolateyResultDirectory + PackageId + "/"; - // The directory into which extensions to the test runner are installed - public override string ExtensionInstallDirectory => BuildSettings.PackageTestDirectory; - - public override void BuildPackage() - { - _context.ChocolateyPack(PackageSource, - new ChocolateyPackSettings() - { - Version = PackageVersion, - OutputDirectory = BuildSettings.PackageDirectory, - ArgumentCustomization = args => args.Append($"BIN_DIR={BuildSettings.OutputDirectory}") - }); - } -} diff --git a/recipe/command-line-options.cake b/recipe/command-line-options.cake deleted file mode 100644 index 505fd7925..000000000 --- a/recipe/command-line-options.cake +++ /dev/null @@ -1,118 +0,0 @@ -// *********************************************************************** -// Copyright (c) Charlie Poole and TestCentric GUI contributors. -// Licensed under the MIT License. See LICENSE.txt in root directory. -// *********************************************************************** - -CommandLineOptions.Initialize(Context); - -public static class CommandLineOptions -{ - static private ICakeContext _context; - - static public ValueOption Target; - static public ValueOption Configuration; - static public ValueOption PackageVersion; - static public ValueOption PackageSelector; - static public ValueOption TestLevel; - static public ValueOption TraceLevel; - static public SimpleOption NoBuild; - static public SimpleOption NoPush; - static public SimpleOption Usage; - - public static void Initialize(ICakeContext context) - { - _context = context; - - // The name of the TARGET task to be run, e.g. Test. - Target = new ValueOption("target", "Default", 1); - - Configuration = new ValueOption("configuration", DEFAULT_CONFIGURATION, 1); - - PackageVersion = new ValueOption("packageVersion", null, 4); - - PackageSelector = new ValueOption("where", null, 1); - - TestLevel = new ValueOption("level", 0, 3); - - TraceLevel = new ValueOption("trace", "Off", 2); - - NoBuild = new SimpleOption("nobuild", 3); - - NoPush = new SimpleOption("nopush", 3); - - Usage = new SimpleOption("usage", 2); - } - - // Nested classes to represent individual options - - // AbstractOption has a name and can tell us if it exists. - public abstract class AbstractOption - { - public string Name { get; } - - public int MinimumAbbreviation { get; internal set; } - - public bool Exists - { - get - { - for (int len = Name.Length; len >= MinimumAbbreviation; len--) - if (_context.HasArgument(Name.Substring(0,len))) - return true; - return false; - } - } - - public string Description { get; } - - public AbstractOption(string name, int minimumAbbreviation = 0, string description = null) - { - Name = name; - MinimumAbbreviation = minimumAbbreviation > 0 && minimumAbbreviation <= name.Length - ? minimumAbbreviation - : name.Length; - Description = description; - } - } - - // Simple Option adds an implicit boolean conversion operator. - // It throws an exception if you gave it a value on the command-line. - public class SimpleOption : AbstractOption - { - static public implicit operator bool(SimpleOption o) => o.Exists; - - public SimpleOption(string name, int minimumAbbreviation = 0, string description = null) - : base(name, minimumAbbreviation, description) - { - if (_context.Argument(name, (string)null) != null) - throw new Exception($"Option --{name} does not take a value."); - } - } - - // Generic ValueOption adds Value as well as a default value - public class ValueOption : AbstractOption - { - public T DefaultValue { get; } - - public ValueOption(string name, T defaultValue, int minimumAbbreviation = 0, string description = null) - : base(name, minimumAbbreviation, description) - { - DefaultValue = defaultValue; - } - - public T Value - { - get - { - for (int len = Name.Length; len >= MinimumAbbreviation; len--) - { - string abbrev = Name.Substring(0,len); - if (_context.HasArgument(abbrev)) - return _context.Argument(abbrev); - } - - return DefaultValue; - } - } - } -} diff --git a/recipe/constants.cake b/recipe/constants.cake deleted file mode 100644 index 2a98b6873..000000000 --- a/recipe/constants.cake +++ /dev/null @@ -1,53 +0,0 @@ -// This file contains both real constants and static readonly variables used -// as constants. All values are initialized before any instance variables. - -// Alias used throughout the recipe -using SIO = System.IO; - -// GitHub owner is the NUnit organization -const string GITHUB_OWNER = "nunit"; - -// Defaults -const string DEFAULT_CONFIGURATION = "Release"; -private static readonly string[] DEFAULT_VALID_CONFIGS = { "Release", "Debug" }; -static readonly string[] DEFAULT_STANDARD_HEADER = new[] { - "// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt" }; -const string DEFAULT_TEST_RESULT_FILE = "TestResult.xml"; - -// Standardized project directory structure - not changeable by user -const string SRC_DIR = "src/"; -const string BIN_DIR = "bin/"; -const string NUGET_DIR = "nuget/"; -const string CHOCO_DIR = "choco/"; -const string ZIP_DIR = "zip/"; -const string PACKAGE_DIR = "package/"; -const string PKG_TEST_DIR = "package/tests/"; -const string NUGET_TEST_DIR = "package/tests/nuget/"; -//const string NUGET_RUNNER_DIR = "package/tests/nuget/runners/"; -const string CHOCO_TEST_DIR = "package/tests/choco/"; -//const string CHOCO_RUNNER_DIR = "package/tests/choco/runners/"; -const string ZIP_TEST_DIR = "package/tests/zip/"; -const string PKG_RSLT_DIR = "package/results/"; -const string NUGET_RSLT_DIR = "package/results/nuget/"; -const string CHOCO_RSLT_DIR = "package/results/choco/"; -const string ZIP_RSLT_DIR = "package/results/zip/"; -const string IMAGE_DIR = "package/images"; -const string ZIP_IMG_DIR = "package/images/zip/"; -const string TOOLS_DIR = "tools/"; - -// URLs for uploading packages -private const string MYGET_PUSH_URL = "https://www.myget.org/F/nunit/api/v2"; -private const string NUGET_PUSH_URL = "https://api.nuget.org/v3/index.json"; -private const string CHOCO_PUSH_URL = "https://push.chocolatey.org/"; - -// Environment Variable names holding API keys -private const string MYGET_API_KEY = "MYGET_API_KEY"; -private const string NUGET_API_KEY = "NUGET_API_KEY"; -private const string CHOCO_API_KEY = "CHOCO_API_KEY"; -private const string GITHUB_ACCESS_TOKEN = "GITHUB_ACCESS_TOKEN"; - -// Pre-release labels that we publish -private static readonly string[] LABELS_WE_PUBLISH_ON_MYGET = { "dev" }; -private static readonly string[] LABELS_WE_PUBLISH_ON_NUGET = { "alpha", "beta", "rc" }; -private static readonly string[] LABELS_WE_PUBLISH_ON_CHOCOLATEY = { "alpha", "beta", "rc" }; -private static readonly string[] LABELS_WE_RELEASE_ON_GITHUB = { "alpha", "beta", "rc" }; diff --git a/recipe/dotnet.cake b/recipe/dotnet.cake deleted file mode 100644 index 9441de3ee..000000000 --- a/recipe/dotnet.cake +++ /dev/null @@ -1,88 +0,0 @@ -public class DotnetInfo -{ - // Experimenting with finding dotnet installs for X64 vs x86 - // This code will end up moved into the engine as well. - - private ICakeContext _context; - private bool _onWindows; - - public DotnetInfo(ICakeContext context) - { - _context = context; - _onWindows = context.IsRunningOnWindows(); - - InstallPath = GetDotnetInstallDirectory(false); - X86InstallPath = GetDotnetInstallDirectory(true); - } - - // NOTES: - // * We don't need an IsInstalled property because our scripts all run under dotnet. - - public bool IsX86Installed => SIO.Directory.Exists(X86InstallPath) && SIO.File.Exists(X86Executable); - - public string InstallPath { get; } - public string Executable => InstallPath + "dotnet.exe"; - public List Runtimes { get; } - - public string X86InstallPath { get; } - public string X86Executable => X86InstallPath + "dotnet.exe"; - public List X86Runtimes { get; } - - public void Display() - { - _context.Information($"Install Path: {InstallPath}"); - _context.Information($"Executable: {Executable}"); - _context.Information("Runtimes:"); - foreach (string dir in SIO.Directory.GetDirectories(SIO.Path.Combine(InstallPath, "shared"))) - { - string runtime = SIO.Path.GetFileName(dir); - foreach (string dir2 in SIO.Directory.GetDirectories(dir)) - { - string version = SIO.Path.GetFileName(dir2); - _context.Information($" {runtime} {version}"); - } - } - - if (IsX86Installed) - { - _context.Information($"\nX86 Install Path: {X86InstallPath}"); - _context.Information($"X86 Executable: {X86Executable}"); - _context.Information("Runtimes:"); - foreach (var dir in SIO.Directory.GetDirectories(SIO.Path.Combine(X86InstallPath, "shared"))) - { - string runtime = SIO.Path.GetFileName(dir); - foreach (string dir2 in SIO.Directory.GetDirectories(dir)) - { - string version = SIO.Path.GetFileName(dir2); - _context.Information($" {runtime} {version}"); - } - } - } - else - _context.Information("\nDotnet X86 is not installed"); - } - - private string GetDotnetInstallDirectory(bool forX86 = false) - { - if (_onWindows) - { - if (forX86) - { - Microsoft.Win32.RegistryKey key = - Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); - return (string)key?.GetValue("InstallLocation"); - } - else - { - Microsoft.Win32.RegistryKey key = - Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); - return (string)key?.GetValue("Path"); - } - } - else // Assuming linux for now - return "/usr/shared/dotnet/"; - } -} - -// Use this task to verify that the script understands the dotnet environment -Task("DotnetInfo").Does(() => { new DotnetInfo(Context).Display(); }); diff --git a/recipe/extending.cake b/recipe/extending.cake deleted file mode 100644 index ed2eed10b..000000000 --- a/recipe/extending.cake +++ /dev/null @@ -1,57 +0,0 @@ -// This file provides information on how to change the pre-defined tasks in -// the NUnit Cake Recipe for a particular project, without changing the -// recipe files themselves. Those files should not be changed unless you -// are trying to make changes for all projects, which use the recipe. -// -// In addition, this file defines a few static methods intended to make it -// easier to override task definitions for a project. -// -// Code given in the following examples should be added to your file after -// the recipe itself has been loaded. -// -// SIMPLE EXAMPLES: -// -// Adding a new dependency after those already present -// BuildTasks.SomeTask.IsDependentOn("SomeOtherTask"); -// -// Adding a new action, which will be performed after the existing action. -// BuildTasks.SomeTask.Does(() => { -// // Code here to do something -// }); -// -// MORE COMPLEX EXAMPLES: -// -// Replacing the existing dependencies, criteria or actions is a bit harder. -// You must first clear the relevant items and then re-add them in the desired -// order along with any new items. The following static methods allow you to do -// so a bit more simply than would otherwise be possible. - -public static void ClearCriteria(CakeTaskBuilder builder) => ((CakeTask)builder.Task).Criterias.Clear(); -public static void ClearDependencies(CakeTaskBuilder builder) => ((CakeTask)builder.Task).Dependencies.Clear(); -public static void ClearActions(CakeTaskBuilder builder) => ((CakeTask)builder.Task).Actions.Clear(); - -// Redefining the action of the build task. Note that dependencies will still run, since we haven't changed them. -// ClearActions(BuildTasks.Build); -// BuildTasks.Build.Does(() => Information("I'm not building today!")); -// -// In some cases, you may wish to completely redefine what a task does. The following static method -// supports a fluent syntax for doing just that - -public static CakeTaskBuilder Redefine(CakeTaskBuilder builder) -{ - ClearCriteria(builder); - ClearDependencies(builder); - ClearActions(builder); - - return builder; -} - -// Redefine the build task completely. Note that in this case, dependencies are not run. -// Redefine(BuildTasks.BuildTask) -// .Does(() => { -// Information("Not Building today!"); -// }); -// -// Modify the Default task for a project to run "Test" rather than "Build". -// Redefine(BuildTasks.DefaultTask) -// .IsDependentOn("Test"); diff --git a/recipe/headers.cake b/recipe/headers.cake deleted file mode 100644 index 479771469..000000000 --- a/recipe/headers.cake +++ /dev/null @@ -1,115 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// CHECK FOR MISSING AND NON-STANDARD FILE HEADERS -////////////////////////////////////////////////////////////////////// - -public static class Headers -{ - private static ICakeContext _context; - - static Headers() - { - _context = BuildSettings.Context; - } - - public static void Check() - { - var NoHeader = new List(); - var NonStandard = new List(); - var Exempted = new List(); - int examined = 0; - - var sourceFiles = _context.GetFiles(BuildSettings.SourceDirectory + "**/*.cs"); - var exemptFiles = BuildSettings.ExemptFiles; - foreach (var file in sourceFiles) - { - var path = file.ToString(); - - // Ignore autogenerated files in an obj directory - if (path.Contains("/obj/")) - continue; - - // Ignore designer files - if (path.EndsWith(".Designer.cs")) - continue; - - // Ignore AssemblyInfo files - if (SIO.Path.GetFileName(path) == "AssemblyInfo.cs") - continue; - - examined++; - var header = GetHeader(file); - if (exemptFiles.Contains(file.GetFilename().ToString())) - Exempted.Add(file); - else if (header.Count == 0) - NoHeader.Add(file); - else if (!header.SequenceEqual(BuildSettings.StandardHeader)) - NonStandard.Add(file); - } - - _context.Information("\nSTANDARD HEADER\n"); - foreach (string line in BuildSettings.StandardHeader) - _context.Information(line); - _context.Information(""); - - if (NoHeader.Count > 0) - { - _context.Information("\nFILES WITH NO HEADER\n"); - foreach (var file in NoHeader) - _context.Information(RelPathTo(file)); - } - - if (NonStandard.Count > 0) - { - _context.Information("\nFILES WITH A NON-STANDARD HEADER\n"); - foreach (var file in NonStandard) - { - _context.Information(RelPathTo(file)); - _context.Information(""); - foreach (string line in GetHeader(file)) - _context.Information(line); - _context.Information(""); - } - } - - if (Exempted.Count > 0) - { - _context.Information("\nEXEMPTED FILES (NO CHECK MADE)\n"); - foreach (var file in Exempted) - _context.Information(RelPathTo(file)); - } - - _context.Information($"\nFiles Examined: {examined}"); - _context.Information($"Missing Headers: {NoHeader.Count}"); - _context.Information($"Non-Standard Headers: {NonStandard.Count}"); - _context.Information($"Exempted Files: {Exempted.Count}"); - - if (NoHeader.Count > 0 || NonStandard.Count > 0) - throw new Exception("Missing or invalid file headers found"); - - if (examined == 0) - _context.Warning("\nWARNING: There were no '*.cs' files in the source directory. Use of the 'CheckHeaders' task may not make sense for this project."); - - List GetHeader(FilePath file) - { - var header = new List(); - var lines = SIO.File.ReadLines(file.ToString()); - - foreach (string line in lines) - { - if (!line.StartsWith("//")) - break; - - header.Add(line); - } - - return header; - } - - string RelPathTo(FilePath file) - { - int CD_LENGTH = Environment.CurrentDirectory.Length + 1; - - return file.ToString().Substring(CD_LENGTH); - } - } -} \ No newline at end of file diff --git a/recipe/help-messages.cake b/recipe/help-messages.cake deleted file mode 100644 index 119f033ed..000000000 --- a/recipe/help-messages.cake +++ /dev/null @@ -1,77 +0,0 @@ -static public class HelpMessages -{ - static public string Usage => $""" - BUILD.CAKE - - This script builds the {BuildSettings.Title} project. It makes use of - NUnit.Cake.Recipe, which provides a number of built-in options and - tasks. You may define additional options and tasks in build.cake or - in additional cake files you load from build.cake. Recipe options may - be specified in abbreviated form, down to the minimum length shown in - square braces. Single character abbreviations use a single dash. - - Usage: build [options] - - Options: - - --target=TARGET [-t] - The TARGET task to be run, e.g. Test. Default is Build. - - --configuration=CONFIG [-c] - The name of the configuration to build. Default is Release. - - --packageVersion [--pack] - Specifies the full package version, including any pre-release - suffix. If provided, this version is used directly in place of - the default version calculated by the script. - - --where=SELECTION [-w] - Specifies a selction expression used to choose which packages - to build and test, for use in debugging. Consists of one or - more specifications, separated by '|' and '&'. Each specification - is of the form "prop=value", where prop may be either id or type. - Examples: - --where type=nuget - --where id=NUnit.Engine.Api - --where "type=nuget|type=choco" - - --level=LEVEL [--lev] - Specifies the level of package testing, 1, 2 or 3. Defaults are - 1 = for normal CI tests run every time you build a package - 2 = for PRs and Dev builds uploaded to MyGet - 3 = prior to publishing a release - - --trace=LEVEL [--tr] - Specifies the default trace level for this run. Values are Off, - Error, Warning, Info or Debug. Default is value of environment - variable NUNIT_INTERNAL_TRACE_LEVEL if set. If not, - tracing is turned Off. - - --nobuild [--nob] - Indicates that the Build task should not be run even if other - tasks depend on it. The existing build is used instead. - - --nopush [--nop] - Indicates that no publishing or releasing should be done. If - publish or release targets are run, a message is displayed. - - --usage [--us] - Displays this help message. No targets are run. - - Selected Cake Options: - - --version - Displays the cake version in use. - - --description - Displays a list of the available tasks (targets). - - --tree - Displays the task dependency tree - - --help - Displays help information for cake itself. - - NOTE: The above Cake options bypass execution of the script. - """; -} diff --git a/recipe/known-extensions.cake b/recipe/known-extensions.cake deleted file mode 100644 index b2bdc4c42..000000000 --- a/recipe/known-extensions.cake +++ /dev/null @@ -1,48 +0,0 @@ -// Static class holding information about known extensions. -public static class KnownExtensions -{ - // Static Variables representing well-known Extensions with the latest tested version - public static ExtensionSpecifier NUnitV2Driver = new ExtensionSpecifier( - "NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver", "3.9.0"); - public static ExtensionSpecifier NUnitProjectLoader = new ExtensionSpecifier( - "NUnit.Extension.NUnitProjectLoader", "nunit-extension-nunit-project-loader", "3.7.1"); -} - -// Representation of an extension, for use by PackageTests. Because our -// extensions usually exist as both nuget and chocolatey packages, each -// extension may have a nuget id, a chocolatey id or both. A default version -// is used unless the user overrides it using SetVersion. -public class ExtensionSpecifier -{ - public ExtensionSpecifier(string nugetId, string chocoId, string version) - { - NuGetId = nugetId; - ChocoId = chocoId; - Version = version; - } - - public string NuGetId { get; } - public string ChocoId { get; } - public string Version { get; } - - public PackageReference NuGetPackage => new PackageReference(NuGetId, Version); - public PackageReference ChocoPackage => new PackageReference(ChocoId, Version); - public PackageReference LatestChocolateyRelease => ChocoPackage.LatestRelease; - - // Return an extension specifier using the same package ids as this - // one but specifying a particular version to be used. - public ExtensionSpecifier SetVersion(string version) - { - return new ExtensionSpecifier(NuGetId, ChocoId, version); - } - - // Install this extension for a package - public void InstallExtension(PackageDefinition targetPackage) - { - PackageReference extensionPackage = targetPackage.PackageType == PackageType.Chocolatey - ? ChocoPackage - : NuGetPackage; - - extensionPackage.Install(targetPackage.ExtensionInstallDirectory); - } -} diff --git a/recipe/nuget-package.cake b/recipe/nuget-package.cake deleted file mode 100644 index b784bf6aa..000000000 --- a/recipe/nuget-package.cake +++ /dev/null @@ -1,58 +0,0 @@ -public class NuGetPackage : PackageDefinition -{ - public NuGetPackage( - string id, - string source, - IPackageTestRunner testRunner = null, - TestRunnerSource testRunnerSource = null, - PackageCheck[] checks = null, - PackageCheck[] symbols = null, - IEnumerable tests = null) - : base( - PackageType.NuGet, - id, - source, - testRunner: testRunner, - testRunnerSource: testRunnerSource, - checks: checks, - symbols: symbols, - tests: tests) - { - if (!source.EndsWith(".nuspec")) - throw new ArgumentException("Source must be a nuspec file", nameof(source)); - - if (symbols != null) - { - HasSymbols = true; - SymbolChecks = symbols; - } - } - - // The file name of this package, including extension - public override string PackageFileName => $"{PackageId}.{PackageVersion}.nupkg"; - // The file name of any symbol package, including extension - public override string SymbolPackageName => SIO.Path.ChangeExtension(PackageFileName, ".snupkg"); - // The directory into which this package is installed - public override string PackageInstallDirectory => BuildSettings.NuGetTestDirectory; - // The directory used to contain results of package tests for this package - public override string PackageResultDirectory => BuildSettings.NuGetResultDirectory + PackageId + "/"; - // The directory into which extensions to the test runner are installed - public override string ExtensionInstallDirectory => BuildSettings.PackageTestDirectory; - - public override void BuildPackage() - { - var nugetPackSettings = new NuGetPackSettings() - { - Version = PackageVersion, - BasePath = BuildSettings.OutputDirectory, - OutputDirectory = BuildSettings.PackageDirectory, - NoPackageAnalysis = true, - Symbols = HasSymbols - }; - - if (HasSymbols) - nugetPackSettings.SymbolPackageFormat = "snupkg"; - - _context.NuGetPack(PackageSource, nugetPackSettings); - } -} diff --git a/recipe/package-checks.cake b/recipe/package-checks.cake deleted file mode 100644 index 66e91df9b..000000000 --- a/recipe/package-checks.cake +++ /dev/null @@ -1,148 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// SYNTAX FOR EXPRESSING CHECKS -////////////////////////////////////////////////////////////////////// - -public static class Check -{ - public static void That(DirectoryPath testDirPath, IList checks) - { - if (checks == null) - throw new ArgumentNullException(nameof(checks)); - - bool allOK = true; - - foreach (var check in checks) - allOK &= check.ApplyTo(testDirPath); - - if (!allOK) throw new Exception("Verification failed!"); - } -} - -private static FileCheck HasFile(FilePath file) => HasFiles(new[] { file }); -private static FileCheck HasFiles(params FilePath[] files) => new FileCheck(files); - -private static DirectoryCheck HasDirectory(string dir) => new DirectoryCheck(dir); - -////////////////////////////////////////////////////////////////////// -// PACKAGECHECK CLASS -////////////////////////////////////////////////////////////////////// - -public abstract class PackageCheck -{ - protected ICakeContext _context; - - public PackageCheck() - { - _context = BuildSettings.Context; - } - - public abstract bool ApplyTo(DirectoryPath testDirPath); - - protected bool CheckDirectoryExists(DirectoryPath dirPath) - { - if (!_context.DirectoryExists(dirPath)) - { - DisplayError($"Directory {dirPath} was not found."); - return false; - } - - return true; - } - - protected bool CheckFileExists(FilePath filePath) - { - if (!_context.FileExists(filePath)) - { - DisplayError($"File {filePath} was not found."); - return false; - } - - return true; - } - - protected bool CheckFilesExist(IEnumerable filePaths) - { - bool isOK = true; - - foreach (var filePath in filePaths) - isOK &= CheckFileExists(filePath); - - return isOK; - } - - protected bool DisplayError(string msg) - { - _context.Error(" ERROR: " + msg); - - // The return value may be ignored or used as a shortcut - // for an immediate return from ApplyTo as in - // return DisplayError(...) - return false; - } -} - -////////////////////////////////////////////////////////////////////// -// FILECHECK CLASS -////////////////////////////////////////////////////////////////////// - -public class FileCheck : PackageCheck -{ - FilePath[] _files; - - public FileCheck(FilePath[] files) - { - _files = files; - } - - public override bool ApplyTo(DirectoryPath testDirPath) - { - return CheckFilesExist(_files.Select(file => testDirPath.CombineWithFilePath(file))); - } -} - -////////////////////////////////////////////////////////////////////// -// DIRECTORYCHECK CLASS -////////////////////////////////////////////////////////////////////// - -public class DirectoryCheck : PackageCheck -{ - private DirectoryPath _relDirPath; - private List _files = new List(); - - public DirectoryCheck(DirectoryPath relDirPath) - { - _relDirPath = relDirPath; - } - - public DirectoryCheck WithFiles(params FilePath[] files) - { - _files.AddRange(files); - return this; - } - - public DirectoryCheck AndFiles(params FilePath[] files) - { - return WithFiles(files); - } - - public DirectoryCheck WithFile(FilePath file) - { - _files.Add(file); - return this; - } - - public DirectoryCheck AndFile(FilePath file) - { - return AndFiles(file); - } - - public override bool ApplyTo(DirectoryPath testDirPath) - { - DirectoryPath absDirPath = testDirPath.Combine(_relDirPath); - - if (!CheckDirectoryExists(absDirPath)) - return false; - - return CheckFilesExist(_files.Select(file => absDirPath.CombineWithFilePath(file))); - } -} diff --git a/recipe/package-definition.cake b/recipe/package-definition.cake deleted file mode 100644 index ebbc663b7..000000000 --- a/recipe/package-definition.cake +++ /dev/null @@ -1,313 +0,0 @@ -public enum PackageType -{ - NuGet, - Chocolatey, - Zip -} - -public abstract class PackageDefinition -{ - protected ICakeContext _context; - - /// - /// Constructor - /// - /// A PackageType value specifying one of the four known package types - /// A string containing the package ID, used as the root of the PackageName - /// A string representing the source used to create the package, e.g. a nuspec file - /// A TestRunner instance used to run package tests. - /// An array of PackageChecks be made on the content of the package. Optional. - /// An array of PackageChecks to be made on the symbol package, if one is created. Optional. Only supported for nuget packages. - /// An array of PackageTests to be run against the package. Optional. - protected PackageDefinition( - PackageType packageType, - string id, - string source, - IPackageTestRunner testRunner = null, - TestRunnerSource testRunnerSource = null, - string extraTestArguments = null, - PackageCheck[] checks = null, - PackageCheck[] symbols = null, - IEnumerable tests = null) - { - if (testRunner == null && testRunnerSource == null && tests != null) - throw new System.InvalidOperationException($"Unable to create {packageType} package {id}: TestRunner or TestRunnerSource must be provided if there are tests."); - if (testRunner != null && testRunnerSource != null) - throw new System.InvalidOperationException($"Unable to create {packageType} package {id}: Either TestRunner or TestRunnerSource must be provided, but not both."); - - _context = BuildSettings.Context; - - PackageType = packageType; - PackageId = id; - PackageVersion = BuildSettings.PackageVersion; - PackageSource = source; - BasePath = BuildSettings.OutputDirectory; - TestRunner = testRunner; - TestRunnerSource = testRunnerSource; - ExtraTestArguments = extraTestArguments; - PackageChecks = checks; - SymbolChecks = symbols; - PackageTests = tests; - } - - public PackageType PackageType { get; } - public string PackageId { get; } - public string PackageVersion { get; protected set; } - public string PackageSource { get; } - public string BasePath { get; } - public IPackageTestRunner TestRunner { get; } - public TestRunnerSource TestRunnerSource { get; } - public string ExtraTestArguments { get; } - public PackageCheck[] PackageChecks { get; } - public PackageCheck[] SymbolChecks { get; protected set; } - public IEnumerable PackageTests { get; } - - public bool HasSymbols { get; protected set; } = false; - public virtual string SymbolPackageName => throw new System.NotImplementedException($"Symbols are not available for {PackageType} packages."); - - // The file name of this package, including extension - public abstract string PackageFileName { get; } - // The directory into which this package is installed - public abstract string PackageInstallDirectory { get; } - // The directory used to contain results of package tests for this package - public abstract string PackageResultDirectory { get; } - // The directory into which extensions to the test runner are installed - public abstract string ExtensionInstallDirectory { get; } - - public string PackageFilePath => BuildSettings.PackageDirectory + PackageFileName; - public string PackageTestDirectory => $"{PackageInstallDirectory}{PackageId}.{PackageVersion}/"; - - public bool IsSelectedBy(string selectionExpression) - { - return IsSelectedByAny(selectionExpression.Split("|", StringSplitOptions.RemoveEmptyEntries)); - - bool IsSelectedByAny(string[] terms) - { - foreach (var term in terms) - if (IsSelectedByAll(term.Split("&", StringSplitOptions.RemoveEmptyEntries))) - return true; - - return false; - } - - bool IsSelectedByAll(string[] factors) - { - foreach (string factor in factors) - { - int index = factor.IndexOf("="); - if (index <= 0) - throw new ArgumentException("Selection expression does not contain =", "where"); - string prop = factor.Substring(0, index).Trim(); - string val = factor.Substring(index+1).Trim(); - - switch(prop.ToUpper()) - { - case "ID": - return PackageId.ToLower() == val.ToLower(); - case "TYPE": - return PackageType.ToString().ToLower() == val.ToLower(); - default: - throw new Exception($"Not a valid selection property: {prop}"); - } - } - - return false; - } - } - - public void BuildVerifyAndTest() - { - _context.EnsureDirectoryExists(BuildSettings.PackageDirectory); - - Banner.Display($"Building {PackageFileName}"); - BuildPackage(); - - Banner.Display($"Installing {PackageFileName}"); - InstallPackage(); - - if (PackageChecks != null) - { - Banner.Display($"Verifying {PackageFileName}"); - VerifyPackage(); - } - - if (SymbolChecks != null) - { - // TODO: Override this in NuGetPackage - VerifySymbolPackage(); - } - - if (PackageTests != null) - { - Banner.Display($"Testing {PackageFileName}"); - RunPackageTests(); - } - } - - protected void FetchBundledExtensions(PackageReference[] extensions) - { - foreach (var extension in extensions) - if (!extension.IsInstalled(BuildSettings.ExtensionsDirectory)) - extension.Install(BuildSettings.ExtensionsDirectory); - } - - public abstract void BuildPackage(); - - // Base implementation is used for installing both NuGet and - // Chocolatey packages. Other package types should override. - public virtual void InstallPackage() - { - var installSettings = new NuGetInstallSettings - { - Source = new [] { - // Package will be found here - BuildSettings.PackageDirectory, - // Dependencies may be in any of these - "https://www.myget.org/F/nunit/api/v3/index.json", - "https://api.nuget.org/v3/index.json" }, - Version = PackageVersion, - OutputDirectory = PackageInstallDirectory, - //ExcludeVersion = true, - Prerelease = true, - NoCache = true - }; - - _context.NuGetInstall(PackageId, installSettings); - } - - public void VerifyPackage() - { - bool allOK = true; - - if (PackageChecks != null) - foreach (var check in PackageChecks) - allOK &= check.ApplyTo(PackageTestDirectory); - - if (allOK) - Console.WriteLine("All checks passed!"); - else - throw new Exception("Verification failed!"); - } - - public void RunPackageTests() - { - _context.Information($"Package tests will run at level {BuildSettings.PackageTestLevel}"); - - var reporter = new ResultReporter(PackageFileName); - - _context.CleanDirectory(PackageResultDirectory); - - // Ensure we start out each package with no extensions installed. - // If any package test installs an extension, it remains available - // for subsequent tests of the same package only. - //foreach (DirectoryPath dirPath in _context.GetDirectories(ExtensionInstallDirectory + "*")) - //{ - // _context.DeleteDirectory(dirPath, new DeleteDirectorySettings() { Recursive = true }); - // _context.Information("Deleted directory " + dirPath.GetDirectoryName()); - //} - - // Package was defined with either a TestRunnerSource or a single TestRunner. In either - // case, these will all be package test runners and may or may not require installation. - var defaultRunners = TestRunnerSource ?? new TestRunnerSource((TestRunner)TestRunner); - - // Preinstall all runners requiring installation - InstallRunners(defaultRunners.PackageTestRunners); - - foreach (var packageTest in PackageTests) - { - if (packageTest.Level > BuildSettings.PackageTestLevel) - continue; - - InstallExtensions(packageTest.ExtensionsNeeded); - InstallRunners(packageTest.TestRunners); - - // Use runners from the test if provided, otherwise the default runners - var runners = packageTest.TestRunners.Length > 0 ? packageTest.TestRunners : defaultRunners.PackageTestRunners; - - foreach (var runner in runners) - { - Console.WriteLine(runner.Version); - var testResultDir = $"{PackageResultDirectory}/{packageTest.Name}/"; - var resultFile = testResultDir + "TestResult.xml"; - - Banner.Display(packageTest.Description); - - _context.CreateDirectory(testResultDir); - string arguments = $"{packageTest.Arguments} {ExtraTestArguments} --work={testResultDir}"; - if (CommandLineOptions.TraceLevel.Value != "Off") - arguments += $" --trace:{CommandLineOptions.TraceLevel.Value}"; - - int rc = runner.RunPackageTest(arguments); - - try - { - var result = new ActualResult(resultFile); - var report = new PackageTestReport(packageTest, result, runner); - reporter.AddReport(report); - - Console.WriteLine(report.Errors.Count == 0 - ? "\nSUCCESS: Test Result matches expected result!" - : "\nERROR: Test Result not as expected!"); - } - catch (Exception ex) - { - reporter.AddReport(new PackageTestReport(packageTest, ex)); - - Console.WriteLine("\nERROR: No result found!"); - } - } - } - - // Create report as a string - var sw = new StringWriter(); - bool hadErrors = reporter.ReportResults(sw); - string reportText = sw.ToString(); - - //Display it on the console - Console.WriteLine(reportText); - - // Save it to the result directory as well - using (var reportFile = new StreamWriter($"{PackageResultDirectory}/PackageTestSummary.txt")) - reportFile.Write(reportText); - - if (hadErrors) - throw new Exception("One or more package tests had errors!"); - } - - private void InstallExtensions(ExtensionSpecifier[] extensionsNeeded) - { - foreach (ExtensionSpecifier extension in extensionsNeeded) - extension.InstallExtension(this); - } - - private void InstallRunners(IEnumerable runners) - { - // Install any runners needing installation - foreach (var runner in runners) - if (runner is InstallableTestRunner) - InstallRunner((InstallableTestRunner)runner); - } - - private void InstallRunner(InstallableTestRunner runner) - { - runner.Install(PackageInstallDirectory); - - // We are using nuget packages for the runner, so it won't normally recognize - // chocolatey extensions. We add an extra addins file for that purpose. - if (PackageType == PackageType.Chocolatey) - { - var filePath = runner.ExecutablePath.GetDirectory().CombineWithFilePath("choco.engine.addins").ToString(); - Console.WriteLine($"Creating {filePath}"); - - using (var writer = new StreamWriter(filePath)) - { - writer.WriteLine("../../nunit-extension-*/tools/"); - writer.WriteLine("../../nunit-extension-*/tools/*/"); - writer.WriteLine("../../../nunit-extension-*/tools/"); - writer.WriteLine("../../../nunit-extension-*/tools/*/"); - } - } - } - - public virtual void VerifySymbolPackage() { } // Does nothing. Overridden for NuGet packages. -} diff --git a/recipe/package-reference.cake b/recipe/package-reference.cake deleted file mode 100644 index 921ef9dac..000000000 --- a/recipe/package-reference.cake +++ /dev/null @@ -1,90 +0,0 @@ -// Representation of a package reference, containing everything needed to install it -public class PackageReference -{ - private ICakeContext _context; - - public string Id { get; } - public string Version { get; } - - public PackageReference(string id, string version) - { - _context = BuildSettings.Context; - - Id = id; - Version = version; - } - - public PackageReference LatestDevBuild => GetLatestDevBuild(); - public PackageReference LatestRelease => GetLatestRelease(); - - private PackageReference GetLatestDevBuild() - { - var packageList = _context.NuGetList(Id, new NuGetListSettings() - { - Prerelease = true, - Source = new [] { "https://www.myget.org/F/nunit/api/v3/index.json" } - } ); - - foreach (var package in packageList) - return new PackageReference(package.Name, package.Version); - - return this; - } - - private PackageReference GetLatestRelease() - { - var packageList = _context.NuGetList(Id, new NuGetListSettings() - { - Prerelease = true, - Source = new [] { - "https://www.nuget.org/api/v2/", - "https://community.chocolatey.org/api/v2/" } - } ); - - // TODO: There seems to be an error in NuGet or in Cake, causing the list to - // contain ALL NuGet packages, so we check the Id in this loop. - foreach (var package in packageList) - if (package.Name == Id) - return new PackageReference(Id, package.Version); - - return this; - } - - public bool IsInstalled(string installDirectory) - { - return _context.GetDirectories($"{installDirectory}{Id}.*").Count > 0; - } - - public void InstallExtension(PackageDefinition targetPackage) - { - Install(targetPackage.ExtensionInstallDirectory); - } - - public void Install(string installDirectory) - { - if (!IsInstalled(installDirectory)) - { - Banner.Display($"Installing {Id} version {Version}"); - - var packageSources = new [] - { - "https://www.myget.org/F/nunit/api/v3/index.json", - "https://api.nuget.org/v3/index.json", - "https://community.chocolatey.org/api/v2/" - }; - - Console.WriteLine("Package Sources:"); - foreach(var source in packageSources) - Console.WriteLine($" {source}"); - Console.WriteLine(); - - _context.NuGetInstall(Id, - new NuGetInstallSettings() - { - OutputDirectory = installDirectory, - Version = Version, - Source = packageSources - }); - } - } -} diff --git a/recipe/package-test.cake b/recipe/package-test.cake deleted file mode 100644 index 98c291792..000000000 --- a/recipe/package-test.cake +++ /dev/null @@ -1,76 +0,0 @@ -// Representation of a single test to be run against a pre-built package. -// Each test has a Level, with the following values defined... -// 0 Do not run - used for temporarily disabling a test -// 1 Run for all CI tests - that is every time we test packages -// 2 Run only on PRs, dev builds and when publishing -// 3 Run only when publishing -public struct PackageTest -{ - public int Level; - public string Name; - public string Description; - public string Arguments; - public ExpectedResult ExpectedResult; - public IPackageTestRunner[] TestRunners; - public ExtensionSpecifier[] ExtensionsNeeded; - - public PackageTest(int level, string name, string description, string arguments, ExpectedResult expectedResult ) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (description == null) - throw new ArgumentNullException(nameof(description)); - if (arguments == null) - throw new ArgumentNullException(nameof(arguments)); - if (expectedResult == null) - throw new ArgumentNullException(nameof(expectedResult)); - - Level = level; - Name = name; - Description = description; - Arguments = arguments; - ExpectedResult = expectedResult; - ExtensionsNeeded = new ExtensionSpecifier[0]; - TestRunners = new IPackageTestRunner[0]; - } - - public PackageTest(int level, string name, string description, string arguments, ExpectedResult expectedResult, params ExtensionSpecifier[] extensionsNeeded ) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (description == null) - throw new ArgumentNullException(nameof(description)); - if (arguments == null) - throw new ArgumentNullException(nameof(arguments)); - if (expectedResult == null) - throw new ArgumentNullException(nameof(expectedResult)); - - Level = level; - Name = name; - Description = description; - Arguments = arguments; - ExpectedResult = expectedResult; - ExtensionsNeeded = extensionsNeeded; - TestRunners = new IPackageTestRunner[0]; - } - - public PackageTest(int level, string name, string description, string arguments, ExpectedResult expectedResult, params IPackageTestRunner[] testRunners ) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (description == null) - throw new ArgumentNullException(nameof(description)); - if (arguments == null) - throw new ArgumentNullException(nameof(arguments)); - if (expectedResult == null) - throw new ArgumentNullException(nameof(expectedResult)); - - Level = level; - Name = name; - Description = description; - Arguments = arguments; - ExpectedResult = expectedResult; - TestRunners = testRunners; - ExtensionsNeeded = new ExtensionSpecifier[0]; - } -} diff --git a/recipe/publishing.cake b/recipe/publishing.cake deleted file mode 100644 index 2981bd3d1..000000000 --- a/recipe/publishing.cake +++ /dev/null @@ -1,244 +0,0 @@ -public static class PackageReleaseManager -{ - private static ICakeContext _context; - - static PackageReleaseManager() - { - _context = BuildSettings.Context; - } - - private static bool _hadErrors = false; - - public static void Publish() - { - _hadErrors = false; - - PublishToMyGet(); - PublishToNuGet(); - PublishToChocolatey(); - - if (_hadErrors) - throw new Exception("One of the publishing steps failed."); - } - - public static void PublishToMyGet() - { - if (!BuildSettings.ShouldPublishToMyGet) - _context.Information("Nothing to publish to MyGet from this run."); - else if (CommandLineOptions.NoPush) - _context.Information("NoPush option suppressing publication to MyGet"); - else - foreach (var package in BuildSettings.Packages) - { - var packageName = $"{package.PackageId}.{BuildSettings.PackageVersion}.nupkg"; - var packagePath = BuildSettings.PackageDirectory + packageName; - try - { - if (package.PackageType == PackageType.NuGet) - PushNuGetPackage(packagePath, BuildSettings.MyGetApiKey, BuildSettings.MyGetPushUrl); - else if (package.PackageType == PackageType.Chocolatey) - PushChocolateyPackage(packagePath, BuildSettings.MyGetApiKey, BuildSettings.MyGetPushUrl); - } - catch (Exception ex) - { - _context.Error(ex.Message); - _hadErrors = true; - } - } - } - - public static void PublishToNuGet() - { - if (!BuildSettings.ShouldPublishToNuGet) - _context.Information("Nothing to publish to NuGet from this run."); - else if (CommandLineOptions.NoPush) - _context.Information("NoPush option suppressing publication to NuGet"); - else - foreach (var package in BuildSettings.Packages) - { - var packageName = $"{package.PackageId}.{BuildSettings.PackageVersion}.nupkg"; - var packagePath = BuildSettings.PackageDirectory + packageName; - try - { - if (package.PackageType == PackageType.NuGet) - PushNuGetPackage(packagePath, BuildSettings.NuGetApiKey, BuildSettings.NuGetPushUrl); - } - catch (Exception ex) - { - _context.Error(ex.Message); - _hadErrors = true; - } - } - } - - public static void PublishToChocolatey() - { - if (!BuildSettings.ShouldPublishToChocolatey) - _context.Information("Nothing to publish to Chocolatey from this run."); - else if (CommandLineOptions.NoPush) - _context.Information("NoPush option suppressing publication to Chocolatey"); - else - foreach (var package in BuildSettings.Packages) - { - var packageName = $"{package.PackageId}.{BuildSettings.PackageVersion}.nupkg"; - var packagePath = BuildSettings.PackageDirectory + packageName; - try - { - if (package.PackageType == PackageType.Chocolatey) - PushChocolateyPackage(packagePath, BuildSettings.ChocolateyApiKey, BuildSettings.ChocolateyPushUrl); - } - catch (Exception ex) - { - _context.Error(ex.Message); - _hadErrors = true; - } - } - } - - private static void PushNuGetPackage(FilePath package, string apiKey, string url) - { - CheckPackageExists(package); - _context.NuGetPush(package, new NuGetPushSettings() { ApiKey = apiKey, Source = url }); - } - - private static void PushChocolateyPackage(FilePath package, string apiKey, string url) - { - CheckPackageExists(package); - _context.ChocolateyPush(package, new ChocolateyPushSettings() { ApiKey = apiKey, Source = url }); - } - - private static void CheckPackageExists(FilePath package) - { - if (!_context.FileExists(package)) - throw new InvalidOperationException( - $"Package not found: {package.GetFilename()}.\nCode may have changed since package was last built."); - } - - private const string DRAFT_RELEASE_ERROR = - "A direct call to CreateDraftRelease is permitted only:\r\n" + - " * On a release branch (release-x.x.x)\r\n" + - " * On the main branch tagged for a production release\r\n" + - " * Using option --packageVersion to specify a release version"; - - public static void CreateDraftRelease() - { - string releaseVersion = - CommandLineOptions.PackageVersion.Exists ? CommandLineOptions.PackageVersion.Value : - BuildSettings.IsReleaseBranch ? BuildSettings.BuildVersion.BranchName.Substring(8) : - BuildSettings.IsProductionRelease ? BuildSettings.PackageVersion : null; - - if (releaseVersion != null) - { - if (CommandLineOptions.NoPush) - _context.Information($"NoPush option skipping creation of draft release for version {releaseVersion}"); - else - { - string releaseName = $"{BuildSettings.Title} {releaseVersion}"; - _context.Information($"Creating draft release for {releaseName}"); - - try - { - _context.GitReleaseManagerCreate(BuildSettings.GitHubAccessToken, BuildSettings.GitHubOwner, BuildSettings.GitHubRepository, new GitReleaseManagerCreateSettings() - { - Name = releaseName, - Milestone = releaseVersion - }); - } - catch - { - _context.Error($"Unable to create draft release for {releaseName}."); - _context.Error($"Check that there is a {releaseVersion} milestone with at least one closed issue."); - _context.Error(""); - throw; - } - } - } - else - { - bool calledDirectly = CommandLineOptions.Target.Value == "CreateDraftRelease"; - if (calledDirectly) - throw new InvalidOperationException(DRAFT_RELEASE_ERROR); - else - _context.Information("Skipping creation of draft release because this is not a release branch"); - } - } - - private const string UPDATE_RELEASE_ERROR = - "A direct call to UpdateReleaseNotes is permitted only:\r\n" + - " * On the main branch tagged for a production release\r\n" + - " * Using option --packageVersion to specify a release version"; - - public static void UpdateReleaseNotes() - { - string releaseVersion = - CommandLineOptions.PackageVersion.Exists ? CommandLineOptions.PackageVersion.Value : - BuildSettings.IsProductionRelease ? BuildSettings.PackageVersion : null; - - if (releaseVersion == null) - throw new InvalidOperationException(UPDATE_RELEASE_ERROR); - - if (CommandLineOptions.NoPush) - _context.Information($"NoPush option skipping update of release notes for version {releaseVersion}"); - else - { - string releaseName = $"{BuildSettings.Title} {releaseVersion}"; - _context.Information($"Updating release notes for {releaseName}"); - - try - { - _context.GitReleaseManagerCreate(BuildSettings.GitHubAccessToken, BuildSettings.GitHubOwner, BuildSettings.GitHubRepository, new GitReleaseManagerCreateSettings() - { - Name = releaseName, - Milestone = releaseVersion - }); - } - catch - { - _context.Error($"Unable to update release notes for {releaseName}."); - _context.Error($"Check that there is a {releaseVersion} milestone with a matching release."); - _context.Error(""); - throw; - } - } - - } - - public static void DownloadDraftRelease() - { - if (!BuildSettings.IsReleaseBranch) - throw new Exception("DownloadDraftRelease requires a release branch!"); - - string milestone = BuildSettings.BranchName.Substring(8); - - _context.GitReleaseManagerExport(BuildSettings.GitHubAccessToken, BuildSettings.GitHubOwner, BuildSettings.GitHubRepository, "DraftRelease.md", - new GitReleaseManagerExportSettings() { TagName = milestone }); - } - - public static void CreateProductionRelease() - { - if (!BuildSettings.IsProductionRelease) - { - _context.Information("Skipping CreateProductionRelease because this is not a production release"); - } - else if (CommandLineOptions.NoPush) - _context.Information($"NoPush option skipping creation of production release for version {BuildSettings.PackageVersion}"); - else - { - string token = BuildSettings.GitHubAccessToken; - string owner = BuildSettings.GitHubOwner; - string repository = BuildSettings.GitHubRepository; - string tagName = BuildSettings.PackageVersion; - string assets = string.Join(',', BuildSettings.Packages.Select(p => p.PackageFilePath)); - - //IsRunningOnWindows() - // ? $"\"{BuildSettings.NuGetPackage},{BuildSettings.ChocolateyPackage}\"" - // : $"\"{BuildSettings.NuGetPackage}\""; - - _context.Information($"Publishing release {tagName} to GitHub"); - _context.Information($" Assets: {assets}"); - - _context.GitReleaseManagerAddAssets(token, owner, repository, tagName, assets); - _context.GitReleaseManagerClose(token, owner, repository, tagName); - } - } -} diff --git a/recipe/setup.cake b/recipe/setup.cake deleted file mode 100644 index c9eec7ab3..000000000 --- a/recipe/setup.cake +++ /dev/null @@ -1,51 +0,0 @@ -Setup((context) => -{ - var target = context.TargetTask.Name; - var tasksToExecute = context.TasksToExecute.Select(t => t.Name); - - // Ensure that BuildSettings have been initialized - if (BuildSettings.Context == null) - throw new Exception("BuildSettings have not been initialized. Call BuildSettings.Initialize() from your build.cake script."); - - // Ensure Api Keys and tokens are available if needed for tasks to be executed - - // MyGet Api Key - bool needMyGetApiKey = tasksToExecute.Contains("PublishToMyGet") && BuildSettings.ShouldPublishToMyGet && !CommandLineOptions.NoPush; - if (needMyGetApiKey && string.IsNullOrEmpty(BuildSettings.MyGetApiKey)) - DisplayErrorAndThrow("MyGet ApiKey is required but was not set."); - - // NuGet Api Key - bool needNuGetApiKey = tasksToExecute.Contains("PublishToNuGet") && BuildSettings.ShouldPublishToNuGet && !CommandLineOptions.NoPush; - if (needNuGetApiKey && string.IsNullOrEmpty(BuildSettings.NuGetApiKey)) - DisplayErrorAndThrow("NuGet ApiKey is required but was not set."); - - // Chocolatey Api Key - bool needChocolateyApiKey = tasksToExecute.Contains("PublishToChocolatey") && BuildSettings.ShouldPublishToChocolatey && !CommandLineOptions.NoPush; - if (needChocolateyApiKey && string.IsNullOrEmpty(BuildSettings.ChocolateyApiKey)) - DisplayErrorAndThrow("Chocolatey ApiKey is required but was not set."); - - // GitHub Access Token, Owner and Repository - if (!CommandLineOptions.NoPush) - if (tasksToExecute.Contains("CreateDraftRelease") && BuildSettings.IsReleaseBranch || - tasksToExecute.Contains("CreateProductionRelease") && BuildSettings.IsProductionRelease) - { - if (string.IsNullOrEmpty(BuildSettings.GitHubAccessToken)) - DisplayErrorAndThrow("GitHub Access Token is required but was not set."); - if (string.IsNullOrEmpty(BuildSettings.GitHubOwner)) - DisplayErrorAndThrow("GitHub Owner is required but was not set."); - if (string.IsNullOrEmpty(BuildSettings.GitHubRepository)) - DisplayErrorAndThrow("GitHub Repository is required but was not set."); - } - - // Add settings to BuildSettings - BuildSettings.Target = target; - BuildSettings.TasksToExecute = tasksToExecute; - - void DisplayErrorAndThrow(string message) - { - message += $"\r\n Tasks: {string.Join(", ", tasksToExecute)}"; - - context.Error(message); - throw new Exception(message); - } -}); diff --git a/recipe/task-builders.cake b/recipe/task-builders.cake deleted file mode 100644 index 4c14345bf..000000000 --- a/recipe/task-builders.cake +++ /dev/null @@ -1,48 +0,0 @@ -// All tasks incorporated in the recipe are defined using CakeTaskBuilders. -// The actual specification of criteria, dependencies and actions for each -// task is done separately in task-definitions.cake. -// -// This approach provides a level of indirection, permitting the user to -// modify or completely redefine what a task does in their build.cake file, -// without changing the definitions in the recipe. - -public static class BuildTasks -{ - // General - public static CakeTaskBuilder DumpSettingsTask { get; set; } - public static CakeTaskBuilder DefaultTask {get; set; } - - // Building - public static CakeTaskBuilder BuildTask { get; set; } - public static CakeTaskBuilder CheckHeadersTask { get; set; } - public static CakeTaskBuilder CleanTask { get; set; } - public static CakeTaskBuilder CleanAllTask { get; set; } - public static CakeTaskBuilder RestoreTask { get; set; } - - // Unit Testing - public static CakeTaskBuilder UnitTestTask { get; set; } - - // Packaging - public static CakeTaskBuilder PackageTask { get; set; } - public static CakeTaskBuilder BuildTestAndPackageTask { get; set; } - //public static CakeTaskBuilder PackageBuildTask { get; set; } - //public static CakeTaskBuilder PackageInstallTask { get; set; } - //public static CakeTaskBuilder PackageVerifyTask { get; set; } - //public static CakeTaskBuilder PackageTestTask { get; set; } - - // Publishing - public static CakeTaskBuilder PublishTask { get; set; } - public static CakeTaskBuilder PublishToMyGetTask { get; set; } - public static CakeTaskBuilder PublishToNuGetTask { get; set; } - public static CakeTaskBuilder PublishToChocolateyTask { get; set; } - - // Releasing - public static CakeTaskBuilder CreateDraftReleaseTask { get; set; } - //public static CakeTaskBuilder DownloadDraftReleaseTask { get; set; } - //public static CakeTaskBuilder UpdateReleaseNotesTask { get; set; } - public static CakeTaskBuilder CreateProductionReleaseTask { get; set; } - - // Continuous Integration - public static CakeTaskBuilder ContinuousIntegrationTask { get; set; } - public static CakeTaskBuilder AppveyorTask { get; set; } -} diff --git a/recipe/task-definitions.cake b/recipe/task-definitions.cake deleted file mode 100644 index 69233603c..000000000 --- a/recipe/task-definitions.cake +++ /dev/null @@ -1,134 +0,0 @@ -// This file defines what each of the tasks in the recipe actually does. -// You should not change these definitions unless you intend to change -// the behavior of a task for all projects that use the recipe. -// -// To make a change for a single project, you should add code to your build.cake -// or another project-specific cake file. See extending.cake for examples. - -BuildTasks.DefaultTask = Task("Default") - .Description("Default task if none specified by user") - .IsDependentOn("Build"); - -BuildTasks.DumpSettingsTask = Task("DumpSettings") - .Description("Display BuildSettings properties") - .Does(() => BuildSettings.DumpSettings()); - -BuildTasks.CheckHeadersTask = Task("CheckHeaders") - .Description("Check source files for valid copyright headers") - .WithCriteria(() => !CommandLineOptions.NoBuild) - .WithCriteria(() => !BuildSettings.SuppressHeaderCheck) - .Does(() => Headers.Check()); - -BuildTasks.CleanTask = Task("Clean") - .Description("Clean output and package directories") - .WithCriteria(() => !CommandLineOptions.NoBuild) - .Does(() => - { - foreach (var binDir in GetDirectories($"**/bin/{BuildSettings.Configuration}/")) - CleanDirectory(binDir); - - CleanDirectory(BuildSettings.PackageDirectory); - CleanDirectory(BuildSettings.ImageDirectory); - CleanDirectory(BuildSettings.ExtensionsDirectory); - - DeleteFiles(BuildSettings.ProjectDirectory + "*.log"); - }); - -BuildTasks.CleanAllTask = Task("CleanAll") - .Description("Clean everything!") - .Does(() => - { - foreach (var binDir in GetDirectories("**/bin/")) - CleanDirectory(binDir); - - CleanDirectory(BuildSettings.PackageDirectory); - CleanDirectory(BuildSettings.ImageDirectory); - CleanDirectory(BuildSettings.ExtensionsDirectory); - - DeleteFiles(BuildSettings.ProjectDirectory + "*.log"); - - foreach (var dir in GetDirectories("src/**/obj/")) - DeleteDirectory(dir, new DeleteDirectorySettings() { Recursive = true }); - }); - -BuildTasks.RestoreTask = Task("Restore") - .Description("Restore referenced packages") - .WithCriteria(() => BuildSettings.SolutionFile != null) - .WithCriteria(() => !CommandLineOptions.NoBuild) - .Does(() => { - NuGetRestore(BuildSettings.SolutionFile, new NuGetRestoreSettings() { - Source = new string[] { - "https://www.nuget.org/api/v2", - "https://www.myget.org/F/nunit/api/v2" } - }); - }); - -BuildTasks.BuildTask = Task("Build") - .WithCriteria(() => BuildSettings.SolutionFile != null) - .WithCriteria(() => !CommandLineOptions.NoBuild) - .IsDependentOn("Clean") - .IsDependentOn("Restore") - .IsDependentOn("CheckHeaders") - .Description("Build the solution") - .Does(() => { - MSBuild(BuildSettings.SolutionFile, BuildSettings.MSBuildSettings.WithProperty("Version", BuildSettings.PackageVersion)); - }); - -BuildTasks.UnitTestTask = Task("Test") - .Description("Run unit tests") - .IsDependentOn("Build") - .Does(() => UnitTesting.RunAllTests()); - -BuildTasks.PackageTask = Task("Package") - .IsDependentOn("Build") - .Description("Build, Install, Verify and Test all packages") - .Does(() => { - var selector = CommandLineOptions.PackageSelector; - foreach(var package in BuildSettings.Packages) - if (!selector.Exists || package.IsSelectedBy(selector.Value)) - package.BuildVerifyAndTest(); - }); - -BuildTasks.BuildTestAndPackageTask = Task("BuildTestAndPackage") - .Description("Do Build, Test and Package all in one run") - .IsDependentOn("Build") - .IsDependentOn("Test") - .IsDependentOn("Package"); - -BuildTasks.PublishTask = Task("Publish") - .Description("Publish all packages for current branch") - .IsDependentOn("Package") - .Does(() => PackageReleaseManager.Publish()); - -BuildTasks.PublishToMyGetTask = Task("PublishToMyGet") - .Description("Publish packages to MyGet") - .Does(() => PackageReleaseManager.PublishToMyGet() ); - -BuildTasks.PublishToNuGetTask = Task("PublishToNuGet") - .Description("Publish packages to NuGet") - .Does(() => PackageReleaseManager.PublishToNuGet() ); - -BuildTasks.PublishToChocolateyTask = Task("PublishToChocolatey") - .Description("Publish packages to Chocolatey") - .Does(() => PackageReleaseManager.PublishToChocolatey() ); - -BuildTasks.CreateDraftReleaseTask = Task("CreateDraftRelease") - .Description("Create a draft release on GitHub") - .Does(() => PackageReleaseManager.CreateDraftRelease() ); - -BuildTasks.CreateProductionReleaseTask = Task("CreateProductionRelease") - .Description("Create a production GitHub Release") - .Does(() => PackageReleaseManager.CreateProductionRelease() ); - -BuildTasks.ContinuousIntegrationTask = Task("ContinuousIntegration") - .Description("Perform continuous integration run") - .IsDependentOn("Build") - .IsDependentOn("Test") - .IsDependentOn("Package") - .IsDependentOn("Publish") - .IsDependentOn("CreateDraftRelease") - .IsDependentOn("CreateProductionRelease"); - -BuildTasks.AppveyorTask = Task("Appveyor") - .Description("Target for running on AppVeyor") - .IsDependentOn("ContinuousIntegration"); diff --git a/recipe/test-reports.cake b/recipe/test-reports.cake deleted file mode 100644 index 3ecc9863f..000000000 --- a/recipe/test-reports.cake +++ /dev/null @@ -1,171 +0,0 @@ -public class PackageTestReport -{ - public PackageTest Test; - public ActualResult Result; - public ITestRunner Runner; - public List Errors; - public List Warnings; - - public PackageTestReport(PackageTest test, ActualResult actualResult, ITestRunner runner = null) - { - Test = test; - Result = actualResult; - Runner = runner; - Errors = new List(); - Warnings = new List(); - - var expectedResult = test.ExpectedResult; - - ReportMissingFiles(); - - if (actualResult.OverallResult == null) - Errors.Add(" The test-run element has no result attribute."); - else if (expectedResult.OverallResult != actualResult.OverallResult) - Errors.Add($" Expected: Overall Result = {expectedResult.OverallResult} But was: {actualResult.OverallResult}"); - CheckCounter("Test Count", expectedResult.Total, actualResult.Total); - CheckCounter("Passed", expectedResult.Passed, actualResult.Passed); - CheckCounter("Failed", expectedResult.Failed, actualResult.Failed); - CheckCounter("Warnings", expectedResult.Warnings, actualResult.Warnings); - CheckCounter("Inconclusive", expectedResult.Inconclusive, actualResult.Inconclusive); - CheckCounter("Skipped", expectedResult.Skipped, actualResult.Skipped); - - var expectedAssemblies = expectedResult.Assemblies; - var actualAssemblies = actualResult.Assemblies; - - for (int i = 0; i < expectedAssemblies.Length && i < actualAssemblies.Length; i++) - { - var expected = expectedAssemblies[i]; - var actual = actualAssemblies[i]; - - if (expected.AssemblyName != actual.AssemblyName) - Errors.Add($" Expected: {expected.AssemblyName} But was: { actual.AssemblyName}"); - else if (runner == null || runner.PackageId == "NUnit.ConsoleRunner.NetCore") - { - if (actual.Runtime == null) - Warnings.Add($"Unable to determine actual runtime used for {expected.AssemblyName}"); - else if (expected.Runtime != actual.Runtime) - Errors.Add($" Assembly {actual.AssemblyName} Expected: {expected.Runtime} But was: {actual.Runtime}"); - } - } - - for (int i = actualAssemblies.Length; i < expectedAssemblies.Length; i++) - Errors.Add($" Assembly {expectedAssemblies[i].AssemblyName} was not found"); - - for (int i = expectedAssemblies.Length; i < actualAssemblies.Length; i++) - Errors.Add($" Found unexpected assembly {actualAssemblies[i].AssemblyName}"); - } - - public PackageTestReport(PackageTest test, Exception ex, ITestRunner runner = null) - { - Test = test; - Result = null; - Errors = new List(); - Errors.Add($" {ex.Message}"); - Runner = runner; - } - - public void Display(int index, TextWriter writer) - { - writer.WriteLine(); - writer.WriteLine($"{index}. {Test.Description}"); - if (Runner != null) - writer.WriteLine($" Runner: {Runner.PackageId} {Runner.Version}"); - writer.WriteLine($" Args: {Test.Arguments}"); - writer.WriteLine(); - - foreach (var error in Errors) - writer.WriteLine(error); - - if (Errors.Count == 0) - { - writer.WriteLine(" SUCCESS: Test Result matches expected result!"); - } - else - { - writer.WriteLine(); - writer.WriteLine(" ERROR: Test Result not as expected!"); - } - - foreach (var warning in Warnings) - writer.WriteLine(" WARNING: " + warning); - } - - // File level errors, like missing or mal-formatted files, need to be highlighted - // because otherwise it's hard to detect the cause of the problem without debugging. - // This method finds and reports that type of error. - private void ReportMissingFiles() - { - // Start with all the top-level test suites. Note that files that - // cannot be found show up as Unknown as do unsupported file types. - var suites = Result.Xml.SelectNodes( - "//test-suite[@type='Unknown'] | //test-suite[@type='Project'] | //test-suite[@type='Assembly']"); - - // If there is no top-level suite, it generally means the file format could not be interpreted - if (suites.Count == 0) - Errors.Add(" No top-level suites! Possible empty command-line or misformed project."); - - foreach (XmlNode suite in suites) - { - // Narrow down to the specific failures we want - string runState = GetAttribute(suite, "runstate"); - string suiteResult = GetAttribute(suite, "result"); - string label = GetAttribute(suite, "label"); - string site = suite.Attributes["site"]?.Value ?? "Test"; - if (runState == "NotRunnable" || suiteResult == "Failed" && site == "Test" && (label == "Invalid" || label == "Error")) - { - string message = suite.SelectSingleNode("reason/message")?.InnerText; - Errors.Add($" {message}"); - } - } - } - - private void CheckCounter(string label, int expected, int actual) - { - // If expected value of counter is negative, it means no check is needed - if (expected >= 0 && expected != actual) - Errors.Add($" Expected: {label} = {expected} But was: {actual}"); - } - - private string GetAttribute(XmlNode node, string name) - { - return node.Attributes[name]?.Value; - } -} - -public class ResultReporter -{ - private string _packageName; - private List _reports = new List(); - - public ResultReporter(string packageName) - { - _packageName = packageName; - } - - public void AddReport(PackageTestReport report) - { - _reports.Add(report); - } - - public bool ReportResults(TextWriter writer) - { - writer.WriteLine("\n=================================================="); ; - writer.WriteLine($"Test Results for {_packageName}"); - writer.WriteLine("=================================================="); ; - - writer.WriteLine("\nTest Environment"); - writer.WriteLine($" OS Version: {Environment.OSVersion.VersionString}"); - writer.WriteLine($" CLR Version: {Environment.Version}\n"); - - int index = 0; - bool hasErrors = false; - - foreach (var report in _reports) - { - hasErrors |= report.Errors.Count > 0; - report.Display(++index, writer); - } - - return hasErrors; - } -} diff --git a/recipe/test-results.cake b/recipe/test-results.cake deleted file mode 100644 index 41ba05397..000000000 --- a/recipe/test-results.cake +++ /dev/null @@ -1,168 +0,0 @@ -// This file contains classes used to interpret the result XML that is -// produced by test runs of the GUI. - -using System.Xml; - -public abstract class ResultSummary -{ - public string OverallResult { get; set; } - public int Total { get; set; } - public int Passed { get; set; } - public int Failed { get; set; } - public int Warnings { get; set; } - public int Inconclusive { get; set; } - public int Skipped { get; set; } -} - -public class ExpectedResult : ResultSummary -{ - public ExpectedResult(string overallResult) - { - if (string.IsNullOrEmpty(overallResult)) - throw new ArgumentNullException(nameof(overallResult)); - - OverallResult = overallResult; - - // Initialize counters to -1, indicating no expected value. - // Set properties of those items to be checked. - Total = Passed = Failed = Warnings = Inconclusive = Skipped = -1; - } - - public ExpectedAssemblyResult[] Assemblies { get; set; } = new ExpectedAssemblyResult[0]; -} - -public class ExpectedAssemblyResult -{ - public ExpectedAssemblyResult(string name, string expectedRuntime = null) - { - AssemblyName = name; - Runtime = expectedRuntime; - } - - public string AssemblyName { get; } - public string Runtime { get; } -} - -public class ActualResult : ResultSummary -{ - public ActualResult(string resultFile) - { - var doc = new XmlDocument(); - doc.Load(resultFile); - - Xml = doc.DocumentElement; - if (Xml.Name != "test-run") - throw new Exception("The test-run element was not found."); - - OverallResult = GetAttribute(Xml, "result"); - Total = IntAttribute(Xml, "total"); - Passed = IntAttribute(Xml, "passed"); - Failed = IntAttribute(Xml, "failed"); - Warnings = IntAttribute(Xml, "warnings"); - Inconclusive = IntAttribute(Xml, "inconclusive"); - Skipped = IntAttribute(Xml, "skipped"); - - var assemblies = new List(); - //var engineVersion = new Version(GetAttribute(Xml, "engine-version")); - - foreach (XmlNode node in Xml.SelectNodes("//test-suite[@type='Assembly']")) - assemblies.Add(new ActualAssemblyResult(node)); - - //foreach (XmlNode node in Xml.SelectNodes("//test-suite[@type='Assembly']")) - // assemblies.Add(new ActualAssemblyResult(node, engineVersion)); - - Assemblies = assemblies.ToArray(); - } - - public XmlNode Xml { get; } - - public ActualAssemblyResult[] Assemblies { get; } - - private string GetAttribute(XmlNode node, string name) - { - return node.Attributes[name]?.Value; - } - - private int IntAttribute(XmlNode node, string name) - { - string s = GetAttribute(node, name); - // TODO: We should replace 0 with -1, representing a missing counter - // attribute, after issue #707 is fixed. - return s == null ? 0 : int.Parse(s); - } -} - -public class ActualAssemblyResult -{ - public ActualAssemblyResult(XmlNode xml) - { - AssemblyName = xml.Attributes["name"]?.Value; - - //var env = xml.SelectSingleNode("environment"); - var settings = xml.SelectSingleNode("settings"); - - // If TargetRuntimeFramework setting is not present, the Runner will probably crash - var runtimeSetting = settings?.SelectSingleNode("setting[@name='TargetRuntimeFramework']"); - Runtime = runtimeSetting?.Attributes["value"]?.Value; - - var agentSetting = settings?.SelectSingleNode("setting[@name='SelectedAgentName']"); - AgentName = agentSetting?.Attributes["value"]?.Value; - } - - public string AssemblyName { get; } - public string AgentName { get; } - - public string Runtime { get; } -} - -#if false -public class ActualAssemblyResult -{ - public ActualAssemblyResult(XmlNode xml, Version engineVersion) - { - Name = xml.Attributes["name"]?.Value; - - var env = xml.SelectSingleNode("environment"); - var settings = xml.SelectSingleNode("settings"); - - // If TargetRuntimeFramework setting is not present, the GUI will have crashed anyway - var runtimeSetting = settings.SelectSingleNode("setting[@name='ImageTargetFrameworkName']"); - TargetRuntime = runtimeSetting?.Attributes["value"]?.Value; - - // Engine 3.10 and earlier doesn't provide enough info - if (engineVersion >= new Version(3,11,0,0)) - Runtime = DeduceActualRuntime(xml); - } - - public string Name { get; } - public string Runtime { get; } - - public string TargetRuntime { get; } - - // Code to determine the runtime actually used is adhoc - // and doesn't work for all assemblies because the needed - // information may not be present in the result file. - // TODO: Modify result file schema so this can be cleaner - private static string DeduceActualRuntime(XmlNode assembly) - { - var env = assembly.SelectSingleNode("environment"); - // The TargetRuntimeFramework setting is only present - // under the 3.12 and later versions of the engine. - var runtimeSetting = assembly.SelectSingleNode("settings/setting[@name='TargetRuntimeFramework']"); - var targetRuntime = runtimeSetting?.Attributes["value"]?.Value; - if (targetRuntime != null) - return targetRuntime; - - var clrVersion = env.Attributes["clr-version"]?.Value; - if (clrVersion == null) - return null; - - if (clrVersion.StartsWith("2.0")) - return "net-2.0"; - if (clrVersion.StartsWith("4.0")) - return "net-4"; - - return null; - } -} -#endif diff --git a/recipe/test-runners.cake b/recipe/test-runners.cake deleted file mode 100644 index 80f524dbe..000000000 --- a/recipe/test-runners.cake +++ /dev/null @@ -1,224 +0,0 @@ -///////////////////////////////////////////////////////////////////////////// -// TEST RUNNER INTERFACES -///////////////////////////////////////////////////////////////////////////// - -/// -/// Common interface for all test runners -/// -public interface ITestRunner -{ - string PackageId { get; } - string Version { get; } -} - -/// -/// A runner capable of running unit tests -/// -public interface IUnitTestRunner : ITestRunner -{ - int RunUnitTest(FilePath testPath); -} - -/// -/// A runner capable of running package tests -/// -public interface IPackageTestRunner : ITestRunner -{ - int RunPackageTest(string arguments); -} - -///////////////////////////////////////////////////////////////////////////// -// ABSTRACT TEST RUNNER -///////////////////////////////////////////////////////////////////////////// - -/// -/// The TestRunner class is the abstract base for all TestRunners used to run unit- -/// or package-tests. A TestRunner knows how to run a test assembly and provide a result. -/// All base functionality is implemented in this class. Derived classes make that -/// functionality available selectively by implementing specific interfaces. -/// -public abstract class TestRunner : ITestRunner -{ - protected ICakeContext Context => BuildSettings.Context; - - public string PackageId { get; protected set; } - public string Version { get; protected set; } - - protected int RunTest(FilePath executablePath, string arguments = null) - { - return RunTest(executablePath, new ProcessSettings { Arguments = arguments }); - } - - protected int RunTest(FilePath executablePath, ProcessSettings processSettings=null) - { - if (executablePath == null) - throw new ArgumentNullException(nameof(executablePath)); - - if (processSettings == null) - processSettings = new ProcessSettings(); - - // Add default values to settings if not present - if (processSettings.WorkingDirectory == null) - processSettings.WorkingDirectory = BuildSettings.OutputDirectory; - - if (executablePath.GetExtension() == ".dll") - return Context.StartProcess("dotnet", processSettings); - else - return Context.StartProcess(executablePath, processSettings); - } -} - -/// -/// A TestRunner requiring some sort of installation before use. -/// -public abstract class InstallableTestRunner : TestRunner -{ - protected InstallableTestRunner(string packageId, string version) - { - PackageId = packageId; - Version = version; - } - - protected abstract FilePath ExecutableRelativePath { get; } - - // Path under tools directory where package would be installed by Cake #tool directive. - // NOTE: When used to run unit tests, a #tool directive is required. If derived package - // is only used for package tests, it is optional. - protected DirectoryPath ToolInstallDirectory => BuildSettings.ToolsDirectory + $"{PackageId}.{Version}"; - protected bool IsInstalledAsTool => - ToolInstallDirectory != null && Context.DirectoryExists(ToolInstallDirectory); - - protected DirectoryPath InstallDirectory; - - public FilePath ExecutablePath => InstallDirectory.CombineWithFilePath(ExecutableRelativePath); - - public void Install(DirectoryPath installDirectory) - { - InstallDirectory = installDirectory.Combine($"{PackageId}.{Version}"); - - // If the runner package is already installed as a cake tool, we just copy it - if (IsInstalledAsTool) - Context.CopyDirectory(ToolInstallDirectory, InstallDirectory); - // Otherwise, we install it to the requested location - else - Context.NuGetInstall( - PackageId, - new NuGetInstallSettings() { OutputDirectory = installDirectory, Version = Version }); - } -} - -///////////////////////////////////////////////////////////////////////////// -// TEST RUNNER SOURCE -///////////////////////////////////////////////////////////////////////////// - -/// -/// TestRunnerSource is a provider of TestRunners. It is used when the tests -/// are to be run under multiple TestRunners rather than just one. -/// -public class TestRunnerSource -{ - public TestRunnerSource(TestRunner runner1, params TestRunner[] moreRunners) - { - AllRunners.Add(runner1); - AllRunners.AddRange(moreRunners); - } - - public List AllRunners { get; } = new List(); - - public IEnumerable UnitTestRunners - { - get { foreach(var runner in AllRunners.Where(r => r is IUnitTestRunner)) yield return (IUnitTestRunner)runner; } - } - - public IEnumerable PackageTestRunners - { - get { foreach(var runner in AllRunners.Where(r => r is IPackageTestRunner)) yield return (IPackageTestRunner)runner; } - } -} - -///////////////////////////////////////////////////////////////////////////// -// NUNITLITE RUNNER -///////////////////////////////////////////////////////////////////////////// - -// For NUnitLite tests, the test is run directly -public class NUnitLiteRunner : TestRunner, IUnitTestRunner -{ - public int RunUnitTest(FilePath testPath) => - RunTest(testPath, BuildSettings.UnitTestArguments); -} - -///////////////////////////////////////////////////////////////////////////// -// NUNIT CONSOLE RUNNERS -///////////////////////////////////////////////////////////////////////////// - -// NUnitConsoleRunner is used for both unit and package tests. It must be pre-installed -// in the tools directory by use of a #tools directive. -public class NUnitConsoleRunner : InstallableTestRunner, IUnitTestRunner, IPackageTestRunner -{ - protected override FilePath ExecutableRelativePath => "tools/nunit3-console.exe"; - - public NUnitConsoleRunner(string version) : base("NUnit.ConsoleRunner", version) { } - - // Run a unit test - public int RunUnitTest(FilePath testPath) => RunTest(ToolInstallDirectory.CombineWithFilePath(ExecutableRelativePath), $"\"{testPath}\" {BuildSettings.UnitTestArguments}"); - - // Run a package test - public int RunPackageTest(string arguments) => RunTest(ExecutablePath, arguments); -} - -public class NUnitNetCoreConsoleRunner : InstallableTestRunner, IUnitTestRunner, IPackageTestRunner -{ - protected override FilePath ExecutableRelativePath => "tools/net6.0/nunit3-console.exe"; - - public NUnitNetCoreConsoleRunner(string version) : base("NUnit.ConsoleRunner.NetCore", version) { } - - // Run a unit test - public int RunUnitTest(FilePath testPath) => RunTest(ExecutablePath, $"\"{testPath}\" {BuildSettings.UnitTestArguments}"); - - // Run a package test - public int RunPackageTest(string arguments) => RunTest(ExecutablePath, arguments); -} - -public class EngineExtensionTestRunner : TestRunner, IPackageTestRunner -{ - private IPackageTestRunner[] _runners = new IPackageTestRunner[] { - new NUnitConsoleRunner("3.17.0"), - new NUnitConsoleRunner("3.15.5") - }; - - public int RunPackageTest(string arguments) - { - - return _runners[0].RunPackageTest(arguments); - } -} - -///////////////////////////////////////////////////////////////////////////// -// AGENT RUNNER -///////////////////////////////////////////////////////////////////////////// - -/// -/// Class that knows how to run an agent directly. (For future use) -/// -public class AgentRunner : TestRunner, IPackageTestRunner -{ - private string _stdExecutable; - private string _x86Executable; - - private FilePath _executablePath; - - public AgentRunner(string stdExecutable, string x86Executable = null) - { - _stdExecutable = stdExecutable; - _x86Executable = x86Executable; - } - - public int RunPackageTest(string arguments) - { - _executablePath = arguments.Contains("--x86") - ? _x86Executable - : _stdExecutable; - - return base.RunTest(_executablePath, arguments.Replace("--x86", string.Empty)); - } -} diff --git a/recipe/tools.cake b/recipe/tools.cake deleted file mode 100644 index 5aabb7680..000000000 --- a/recipe/tools.cake +++ /dev/null @@ -1,27 +0,0 @@ -// Load all tools used by the recipe -#tool NuGet.CommandLine&version=6.9.1 -#tool dotnet:?package=GitVersion.Tool&version=5.12.0 -#tool dotnet:?package=GitReleaseManager.Tool&version=0.17.0 - -public static class Tools -{ - public static DirectoryPath FindInstalledTool(string packageId) - { - if (SIO.Directory.Exists(BuildSettings.ToolsDirectory + packageId)) - return BuildSettings.ToolsDirectory + packageId; - - foreach(var dir in BuildSettings.Context.GetDirectories(BuildSettings.ToolsDirectory + $"{packageId}.*")) - return dir; // Use first one found - - return null; - } - - public static DirectoryPath FindInstalledTool(string packageId, string version) - { - if (version == null) - throw new ArgumentNullException(nameof(version)); - - var toolPath = BuildSettings.ToolsDirectory + $"{packageId}.{version}"; - return BuildSettings.ToolsDirectory + $"{packageId}.{version}"; - } -} \ No newline at end of file diff --git a/recipe/unit-testing.cake b/recipe/unit-testing.cake deleted file mode 100644 index 24094d050..000000000 --- a/recipe/unit-testing.cake +++ /dev/null @@ -1,95 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// UNIT TEST RUNNER -////////////////////////////////////////////////////////////////////// - -public static class UnitTesting -{ - static ICakeContext _context; - - static UnitTesting() - { - _context = BuildSettings.Context; - } - - public static void RunAllTests() - { - var unitTests = FindUnitTestFiles(BuildSettings.UnitTests); - - _context.Information($"Located {unitTests.Count} unit test assemblies."); - var errors = new List(); - - var runner = BuildSettings.UnitTestRunner ?? new NUnitLiteRunner(); - - foreach (var testPath in unitTests) - { - var testFile = testPath.GetFilename(); - var containingDir = testPath.GetDirectory().GetDirectoryName(); - var runtime = IsValidRuntime(containingDir) ? containingDir : null; - - Banner.Display(runtime != null - ? $"Running {testFile} under {runtime}" - : $"Running {testFile}"); - - int rc = runner.RunUnitTest(testPath); - - var name = runtime != null - ? $"{testFile}({runtime})" - : testFile; - if (rc > 0) - errors.Add($"{name}: {rc} tests failed"); - else if (rc < 0) - errors.Add($"{name} returned rc = {rc}"); - } - - if (unitTests.Count == 0) - _context.Warning("No unit tests were found"); - else if (errors.Count > 0) - throw new Exception( - "One or more unit tests failed, breaking the build.\r\n" - + errors.Aggregate((x, y) => x + "\r\n" + y) ); - - static bool IsValidRuntime(string text) - { - string[] VALID_RUNTIMES = { - "net20", "net30", "net35", "net40", "net45", "net451", "net451", - "net46", "net461", "net462", "net47", "net471", "net472", "net48", "net481", - "netcoreapp1.1", "netcoreapp2.1", "netcoreapp3.1", - "net5.0", "net6.0", "net7.0", "net8.0" - }; - - return VALID_RUNTIMES.Contains(text); - } - } - - private static List FindUnitTestFiles(string patternSet) - { - var result = new List(); - - if (!string.IsNullOrEmpty(patternSet)) - { - // User supplied a set of patterns for the unit tests - foreach (string filePattern in patternSet.Split('|')) - foreach (var testPath in _context.GetFiles(BuildSettings.OutputDirectory + filePattern)) - result.Add(testPath); - } - else - { - // Use default patterns to find unit tests - case insensitive because - // we don't know how the user may have named test assemblies. - var defaultPatterns = new [] { "**/*.tests.dll", "**/*.tests.exe" }; - var globberSettings = new GlobberSettings { IsCaseSensitive = false }; - foreach (string filePattern in defaultPatterns) - foreach (var testPath in _context.GetFiles(BuildSettings.OutputDirectory + filePattern, globberSettings)) - result.Add(testPath); - } - - result.Sort(ComparePathsByFileName); - - return result; - - static int ComparePathsByFileName(FilePath x, FilePath y) - { - return x.GetFilename().ToString().CompareTo(y.GetFilename().ToString()); - } - } -} diff --git a/recipe/utilities.cake b/recipe/utilities.cake deleted file mode 100644 index 4001f0452..000000000 --- a/recipe/utilities.cake +++ /dev/null @@ -1,10 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// HELPER METHODS - PACKAGING -////////////////////////////////////////////////////////////////////// - -public void CopyPackageContents(DirectoryPath packageDir, DirectoryPath outDir) -{ - var files = GetFiles(packageDir + "/tools/*").Concat(GetFiles(packageDir + "/tools/net462/*")); - CopyFiles(files.Where(f => f.GetExtension() != ".addins"), outDir); -} - diff --git a/recipe/versioning.cake b/recipe/versioning.cake deleted file mode 100644 index 90f9d7060..000000000 --- a/recipe/versioning.cake +++ /dev/null @@ -1,115 +0,0 @@ -using System.Text.RegularExpressions; - -public class BuildVersion -{ - private ICakeContext _context; - private GitVersion _gitVersion; - - // NOTE: This is complicated because (1) the user may have specified - // the package version on the command-line and (2) GitVersion may - // or may not be available. We'll work on solving (2) by getting - // GitVersion to run for us on Linux, but (1) will alwas remain. - // - // We simplify things a by figuring out the full package version and - // then parsing it to provide information that is used in the build. - public BuildVersion(ICakeContext context) - { - if (context==null) - throw new ArgumentNullException(nameof(context)); - - _context = context; - _gitVersion = context.GitVersion(); - - BranchName = _gitVersion.BranchName; - IsReleaseBranch = BranchName.StartsWith("release-"); - - string packageVersion = CommandLineOptions.PackageVersion.Value ?? CalculatePackageVersion(); - - int dash = packageVersion.IndexOf('-'); - IsPreRelease = dash > 0; - - string versionPart = packageVersion; - string suffix = ""; - string label = ""; - - if (IsPreRelease) - { - versionPart = packageVersion.Substring(0, dash); - suffix = packageVersion.Substring(dash + 1); - foreach (char c in suffix) - { - if (!char.IsLetter(c)) - break; - label += c; - } - } - - Version version = new Version(versionPart); - SemVer = version.ToString(3); - PreReleaseLabel = label; - PreReleaseSuffix = suffix; - - PackageVersion = packageVersion; - AssemblyVersion = SemVer + ".0"; - AssemblyFileVersion = SemVer; - AssemblyInformationalVersion = packageVersion; - } - - public string BranchName { get; } - public bool IsReleaseBranch { get; } - - public string PackageVersion { get; } - public string AssemblyVersion { get; } - public string AssemblyFileVersion { get; } - public string AssemblyInformationalVersion { get; } - - public string SemVer { get; } - public bool IsPreRelease { get; } - public string PreReleaseLabel { get; } - public string PreReleaseSuffix { get; } - - private string CalculatePackageVersion() - { - string label = _gitVersion.PreReleaseLabel; - - // Non pre-release is easy - if (string.IsNullOrEmpty(label)) - return _gitVersion.MajorMinorPatch; - - string branchName = _gitVersion.BranchName; - - // We don't currently use this pattern, but check in case we do later. - if (branchName.StartsWith("feature/")) - branchName = branchName.Substring(8); - - // Arbitrary branch names are ci builds - if (label == branchName) - label = "ci"; - - string suffix = "-" + label + _gitVersion.CommitsSinceVersionSourcePadded; - - switch (label) - { - case "ci": - branchName = Regex.Replace(branchName, "[^0-9A-Za-z-]+", "-"); - suffix += "-" + branchName; - // Nuget limits "special version part" to 20 chars. Add one for the hyphen. - if (suffix.Length > 21) - suffix = suffix.Substring(0, 21); - return _gitVersion.MajorMinorPatch + suffix; - - case "dev": - case "pre": - return _gitVersion.MajorMinorPatch + suffix; - - case "pr": - return _gitVersion.LegacySemVerPadded; - - case "rc": - case "alpha": - case "beta": - default: - return _gitVersion.LegacySemVer; - } - } -} diff --git a/recipe/zip-package.cake b/recipe/zip-package.cake deleted file mode 100644 index 4f27d03be..000000000 --- a/recipe/zip-package.cake +++ /dev/null @@ -1,74 +0,0 @@ -public class ZipPackage : PackageDefinition -{ - public ZipPackage( - string id, - string source, - IPackageTestRunner testRunner = null, - TestRunnerSource testRunnerSource = null, - PackageCheck[] checks = null, - IEnumerable tests = null, - PackageReference[] bundledExtensions = null ) - : base( - PackageType.Zip, - id, - source, - testRunner: testRunner, - testRunnerSource: testRunnerSource, - checks: checks, - tests: tests) - { - BundledExtensions = bundledExtensions; - } - - // ZIP package supports bundling of extensions - public PackageReference[] BundledExtensions { get; } - - // The file name of this package, including extension - public override string PackageFileName => $"{PackageId}-{PackageVersion}.zip"; - // The directory into which this package is installed - public override string PackageInstallDirectory => BuildSettings.ZipTestDirectory; - // The directory used to contain results of package tests for this package - public override string PackageResultDirectory => $"{BuildSettings.ZipResultDirectory}{PackageId}/"; - // The directory into which extensions to the test runner are installed - public override string ExtensionInstallDirectory => $"{BuildSettings.ZipTestDirectory}{PackageId}.{PackageVersion}/bin/addins/"; - - public override void BuildPackage() - { - FetchBundledExtensions(BundledExtensions); - - CreateZipImage(); - - _context.Zip(BuildSettings.ZipImageDirectory, $"{BuildSettings.PackageDirectory}{PackageFileName}"); - } - - public override void InstallPackage() - { - _context.Unzip($"{BuildSettings.PackageDirectory}{PackageFileName}", $"{PackageInstallDirectory}{PackageId}.{PackageVersion}"); - } - - private void CreateZipImage() - { - _context.CleanDirectory(BuildSettings.ZipImageDirectory); - - _context.CopyFiles( - new FilePath[] { "LICENSE.txt", "NOTICES.txt", "CHANGES.txt", "nunit.ico" }, - BuildSettings.ZipImageDirectory); - - _context.CopyDirectory( - BuildSettings.OutputDirectory, - BuildSettings.ZipImageDirectory + "bin/" ); - - _context.CopyFileToDirectory( - BuildSettings.ZipDirectory + "nunit.bundle.addins", - BuildSettings.ZipImageDirectory); - - var addinsDir = BuildSettings.ZipImageDirectory + "bin/net462/addins/"; - _context.CreateDirectory(addinsDir); - - foreach (var packageDir in SIO.Directory.GetDirectories(BuildSettings.ExtensionsDirectory)) - { - var files = _context.GetFiles(packageDir + "/tools/*").Concat(_context.GetFiles(packageDir + "/tools/net462/*")); - _context.CopyFiles(files.Where(f => f.GetExtension() != ".addins"), addinsDir); - } - } -} From c6e4af7cad7779409fb347d8b031d4c40a66a7b4 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 16 Jul 2024 08:51:46 -0700 Subject: [PATCH 076/190] Add test using V2 Result Writer --- build.cake | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/build.cake b/build.cake index b47d04288..12c8b9ba6 100644 --- a/build.cake +++ b/build.cake @@ -1,13 +1,13 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.0.0-dev00001 +#load nuget:?package=NUnit.Cake.Recipe&version=1.0.1-dev00001 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake // Initialize BuildSettings BuildSettings.Initialize( Context, - "NUnit Console and Engine", - "nunit-console", + title: "NUnit Console and Engine", + githubRepository: "nunit-console", solutionFile: "NUnitConsole.sln", exemptFiles: new [] { "Options.cs", "ProcessUtils.cs", "ProcessUtilsTests.cs" }, unitTests: "**/*.tests.exe|**/nunit3-console.tests.dll", @@ -29,7 +29,8 @@ BuildSettings.Initialize( Net80Test, Net60PlusNet80Test, Net462PlusNet60Test, - NUnitProjectTest + NUnitProjectTest, + V2ResultWriterTest }; // Tests run for the NETCORE runner package @@ -94,6 +95,7 @@ static ExpectedResult MockAssemblyX86ExpectedResult(params string[] runtimes) return result; } + static PackageTest Net462Test = new PackageTest( 1, "Net462Test", "Run mock-assembly.dll under .NET 4.6.2", @@ -172,12 +174,24 @@ static PackageTest Net462PlusNet60Test = new PackageTest( "net462/mock-assembly.dll net6.0/mock-assembly.dll", MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0")); +// Test with latest released version of each of our extensions + +static ExtensionSpecifier NUnitProjectLoader = KnownExtensions.NUnitProjectLoader.SetVersion("3.8.0"); +static ExtensionSpecifier NUnitV2ResultWriter = KnownExtensions.NUnitV2ResultWriter.SetVersion("3.8.0"); + static PackageTest NUnitProjectTest = new PackageTest( 1, "NUnitProjectTest", "Run project with both copies of mock-assembly", "../../NetFXTests.nunit --config=Release --trace=Debug", MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"), - KnownExtensions.NUnitProjectLoader); + NUnitProjectLoader); + +static PackageTest V2ResultWriterTest = new PackageTest( + 1, "V2ResultWriterTest", + "Run mock-assembly under .NET 6.0 and produce V2 output", + "net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", + MockAssemblyExpectedResult("netcore-6.0"), + NUnitV2ResultWriter); ////////////////////////////////////////////////////////////////////// // LISTS OF FILES USED IN CHECKING PACKAGES @@ -314,9 +328,9 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { tests: StandardRunnerTests, bundledExtensions: new [] { new PackageReference("NUnit.Extension.VSProjectLoader", "3.9.0"), - new PackageReference("NUnit.Extension.NUnitProjectLoader", "3.7.1"), + new PackageReference("NUnit.Extension.NUnitProjectLoader", "3.8.0"), new PackageReference("NUnit.Extension.NUnitV2Driver", "3.9.0"), - new PackageReference("NUnit.Extension.NUnitV2ResultWriter", "3.7.0"), + new PackageReference("NUnit.Extension.NUnitV2ResultWriter", "3.8.0"), new PackageReference("NUnit.Extension.TeamCityEventListener", "1.0.9") }), From 533876ccb9cece41c1420da5da20526358196d0a Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 17 Jul 2024 15:03:09 -0700 Subject: [PATCH 077/190] Add target for re-publishing symbol packages --- build.cake | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/build.cake b/build.cake index 12c8b9ba6..dd37c2dd3 100644 --- a/build.cake +++ b/build.cake @@ -407,6 +407,65 @@ public class ConsoleRunnerSelfTester : TestRunner, IPackageTestRunner } } +////////////////////////////////////////////////////////////////////// +// ADDITIONAL TARGETS USED FOR RECOVERY AND DEBUGGING +////////////////////////////////////////////////////////////////////// + +// Some of these targets may be moved into the recipe itself in the future. + +// When a NuGet package was published successfully but the corresponding symbols +// package failed, use this target locally after correcting the error. +// TODO: This task is extemely complicated because it has to copy lots of code +// from the recipe. It would be simpler if it were integrated in the recipe. +// TODO: This has been tested on NUnit.ConsoleRunner, so the branches with either +// zero or one packages are speculative at this point. They will need testing +// if this is incorporated into the recipe. +Task("PublishSymbolsPackage") + .Description("Re-publish a specific symbols package to NuGet after a failure") + .Does(() => + { + if (!BuildSettings.ShouldPublishToNuGet) + Information("Nothing to publish to NuGet from this run."); + else if (CommandLineOptions.NoPush) + Information("NoPush option suppressing publication to NuGet"); + else + { + List packages; + + if (BuildSettings.Packages.Count == 0) + throw new Exception("No packages exist!"); + else if (BuildSettings.Packages.Count == 1) + { + if (BuildSettings.Packages[0].PackageType != PackageType.NuGet) + throw new Exception("The only package is not a NuGet package"); + + packages = BuildSettings.Packages; + } + else // count is > 1 + { + if (!CommandLineOptions.PackageSelector.Exists) + throw new Exception("Multiple packages exist. Specify a nuget package id using the '--where' option"); + + packages = new List(); + + foreach (var package in BuildSettings.Packages) + if (package.IsSelectedBy(CommandLineOptions.PackageSelector.Value)) + packages.Add(package); + + if (packages.Count > 1) + throw new Exception("The '--where' option selected multiple packages"); + + if (packages[0].PackageType != PackageType.NuGet) + throw new Exception("The selected package is a {package.PackageType} package. It must be a package for nuget.org."); + } + + // At this point we have a single NuGet package in packages + var packageName = $"{packages[0].PackageId}.{BuildSettings.PackageVersion}.snupkg"; + var packagePath = BuildSettings.PackageDirectory + packageName; + NuGetPush(packagePath, new NuGetPushSettings() { ApiKey = BuildSettings.NuGetApiKey, Source = BuildSettings.NuGetPushUrl }); + } + }); + ////////////////////////////////////////////////////////////////////// // EXECUTION ////////////////////////////////////////////////////////////////////// From 14e009938d71e52b297138eda75c177a0f22c519 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 17 Jul 2024 16:38:50 -0700 Subject: [PATCH 078/190] Remove duplicate package id --- build.cake | 19 ++--------------- .../nunit.console-runner.netcore.nuspec | 21 ++----------------- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/build.cake b/build.cake index dd37c2dd3..3c64c10a8 100644 --- a/build.cake +++ b/build.cake @@ -228,8 +228,7 @@ FilePath[] CONSOLE_FILES_NETCORE = { PackageDefinition NUnitConsoleNuGetPackage; PackageDefinition NUnitConsoleRunnerNuGetPackage; -PackageDefinition NUnitConsoleRunnerNet60Package; -PackageDefinition NUnitConsoleRunnerNet80Package; +PackageDefinition NUnitConsoleRunnerNetCorePackage; PackageDefinition NUnitEnginePackage; PackageDefinition NUnitEngineApiPackage; PackageDefinition NUnitConsoleRunnerChocolateyPackage; @@ -267,21 +266,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner-with-extensions.nuspec", checks: new PackageCheck[] { HasFile("LICENSE.txt") }), - NUnitConsoleRunnerNet80Package = new NuGetPackage( - id: "NUnit.ConsoleRunner.NetCore", - source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.netcore.nuspec", - checks: new PackageCheck[] { - HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools/net8.0").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_CORE_FILES).AndFile("nunit.console.nuget.addins") - }, - symbols: new PackageCheck[] { - HasDirectory("tools/net8.0").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) - }, - testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory - + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/tools/net8.0/nunit3-console.exe"), - tests: NetCoreRunnerTests), - - NUnitConsoleRunnerNet60Package = new NuGetPackage( + NUnitConsoleRunnerNetCorePackage = new NuGetPackage( id: "NUnit.ConsoleRunner.NetCore", source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.netcore.nuspec", checks: new PackageCheck[] { diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index fd36e66a7..35dfac8d4 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -26,6 +26,7 @@ + @@ -43,25 +44,7 @@ - - - - - - - - - - - - - - - - - - - + From fc737227ae07bcc244bf9dc554ae60db74618366 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 17 Jul 2024 17:27:03 -0700 Subject: [PATCH 079/190] Update build.cake --- build.cake | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/build.cake b/build.cake index 3c64c10a8..3b04d4ca8 100644 --- a/build.cake +++ b/build.cake @@ -217,10 +217,6 @@ FilePath[] AGENT_PDB_FILES = { "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; FilePath[] AGENT_PDB_FILES_NETCORE = { "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; -FilePath[] CONSOLE_FILES = { - "nunit3-console.exe", "nunit3-console.exe.config" }; -FilePath[] CONSOLE_FILES_NETCORE = { - "nunit3-console.exe", "nunit3-console.dll" }; ////////////////////////////////////////////////////////////////////// // INDIVIDUAL PACKAGE DEFINITIONS @@ -241,7 +237,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins"), + HasDirectory("tools").WithFiles("nunit3-console.exe", "nunit3-console.exe.config", "nunit.console.nuget.addins").AndFiles(ENGINE_FILES), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), @@ -271,7 +267,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.netcore.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools/net6.0").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_CORE_FILES).AndFile("nunit.console.nuget.addins") + HasDirectory("tools/net6.0").WithFiles("nunit3-console.exe", "nunit3-console.dll", "nunit-console.nuget.addins").AndFiles(ENGINE_FILES) }, symbols: new PackageCheck[] { HasDirectory("tools/net6.0").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) @@ -284,7 +280,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { id: "nunit-console-runner", source: BuildSettings.ChocolateyDirectory + "nunit-console-runner.nuspec", checks: new PackageCheck[] { - HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt").AndFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit.console.choco.addins"), + HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt", "nunit3-console.exe", "nunit3-console.exe.config", "nunit.console.choco.addins").AndFiles(ENGINE_FILES), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), @@ -300,7 +296,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.ZipImageDirectory, checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt", "CHANGES.txt"), - HasDirectory("bin/net462").WithFiles(CONSOLE_FILES).AndFiles(ENGINE_FILES).AndFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES), + HasDirectory("bin/net462").WithFiles("nunit3-console.exe", "nunit3-console.exe.config", "nunit3-console.pdb").AndFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), HasDirectory("bin/netstandard2.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), From a2c6926d5e3f979a9ca2f8baa3214498eb982124 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 17 Jul 2024 20:58:41 -0700 Subject: [PATCH 080/190] Fix errors in dotnet tool package --- build.cake | 35 ++++++++++++----- .../nunit.console-runner.netcore.nuspec | 39 ++++++++++--------- .../nunit3-console/nunit3-console.csproj | 2 + 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/build.cake b/build.cake index 3b04d4ca8..24848675c 100644 --- a/build.cake +++ b/build.cake @@ -262,18 +262,12 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner-with-extensions.nuspec", checks: new PackageCheck[] { HasFile("LICENSE.txt") }), - NUnitConsoleRunnerNetCorePackage = new NuGetPackage( + NUnitConsoleRunnerNetCorePackage = new DotNetToolPackage( id: "NUnit.ConsoleRunner.NetCore", source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.netcore.nuspec", - checks: new PackageCheck[] { - HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools/net6.0").WithFiles("nunit3-console.exe", "nunit3-console.dll", "nunit-console.nuget.addins").AndFiles(ENGINE_FILES) - }, - symbols: new PackageCheck[] { - HasDirectory("tools/net6.0").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) - }, + checks: new PackageCheck[] { HasFiles("nunit.exe") }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory - + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/tools/net6.0/nunit3-console.exe"), + + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/nunit.exe"), tests: NetCoreRunnerTests), NUnitConsoleRunnerChocolateyPackage = new ChocolateyPackage( @@ -388,6 +382,29 @@ public class ConsoleRunnerSelfTester : TestRunner, IPackageTestRunner } } +////////////////////////////////////////////////////////////////////// +// DOTNET TOOL PACKAGE +////////////////////////////////////////////////////////////////////// + +// TODO: Temporary custom package class to be moved into the recipe + +public class DotNetToolPackage : NuGetPackage +{ + public DotNetToolPackage(string id, string source, string basePath = null, + IPackageTestRunner testRunner = null, TestRunnerSource testRunnerSource = null, + PackageCheck[] checks = null, PackageCheck[] symbols = null, IEnumerable tests = null) + : base(id, source, basePath: basePath, testRunner: testRunner, testRunnerSource: testRunnerSource, + checks: checks, symbols: symbols, tests: tests) { } + + public override void InstallPackage() + { + var arguments = $"tool install {PackageId} --version {BuildSettings.PackageVersion} " + + $"--add-source \"{BuildSettings.PackageDirectory}\" --tool-path \"{PackageTestDirectory}\""; + Console.WriteLine($"Executing dotnet {arguments}"); + _context.StartProcess("dotnet", arguments); + } +} + ////////////////////////////////////////////////////////////////////// // ADDITIONAL TARGETS USED FOR RECOVERY AND DEBUGGING ////////////////////////////////////////////////////////////////////// diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index 35dfac8d4..e297d4524 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -21,29 +21,32 @@ https://docs.nunit.org/articles/nunit/release-notes/console-and-engine.html en-US nunit test testing tdd runner - Copyright (c) 2021 Charlie Poole, Rob Prouse + Copyright (c) 2021-2024 Charlie Poole, Rob Prouse + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/NUnitConsole/nunit3-console/nunit3-console.csproj b/src/NUnitConsole/nunit3-console/nunit3-console.csproj index 5b76fa1e9..28d9448f3 100644 --- a/src/NUnitConsole/nunit3-console/nunit3-console.csproj +++ b/src/NUnitConsole/nunit3-console/nunit3-console.csproj @@ -6,6 +6,8 @@ nunit3-console net462;net6.0;net8.0 Major + portable + true From 6fec5cf6ea39595f8478a09c887749d42ac4b89f Mon Sep 17 00:00:00 2001 From: Ilia K Date: Fri, 9 Aug 2024 15:16:38 +0200 Subject: [PATCH 081/190] Probe assemblies with .exe extension when loading from the test assembly's folder in TestAssemblyLoadContext --- .../Internal/TestAssemblyLoadContext.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index 800de29da..93c37ced1 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -65,10 +65,14 @@ protected override Assembly Load(AssemblyName name) // but are not fully specified in test assembly deps.json file. This happens when the // dependencies reference in the csproj file has CopyLocal=false, and for example, the // reference is a projectReference and has the same output directory as the parent. - string assemblyPath = Path.Combine(_basePath, name.Name + ".dll"); - if (File.Exists(assemblyPath)) + foreach (var extension in new string[] { ".dll", ".exe" }) { - loadedAssembly = LoadFromAssemblyPath(assemblyPath); + string assemblyPath = Path.Combine(_basePath, name.Name + extension); + if (File.Exists(assemblyPath)) + { + loadedAssembly = LoadFromAssemblyPath(assemblyPath); + break; + } } if (loadedAssembly != null) From 0ff1ed03750fed4feea159db0f8a2e062e953a31 Mon Sep 17 00:00:00 2001 From: Ben Randall Date: Fri, 9 Aug 2024 23:04:51 -0400 Subject: [PATCH 082/190] Improve Github Actions CI workflow (#1453) * Improve Github Actions CI workflow - Switch to using a Matrix so we don't need to duplicate steps. - Install .NET 6 SDK instead of 5 (this seemed like it was causing a build fix. - Upload binary log as an artifact. - Upload test results as an artifact. --- .../workflows/NUnitConsoleAndEngine.CI.yml | 183 +++++++----------- .gitignore | 1 + build.cake | 111 +++++------ 3 files changed, 119 insertions(+), 176 deletions(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index 8730c8edc..a13753f32 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -1,118 +1,83 @@ name: NUnitConsoleAndEngine.CI on: - push: - branches: - - main - - release - - version3x pull_request: - + branches-ignore: + - "azure-*" + paths-ignore: + - "*.txt" + - "*.md" + env: - DOTNET_NOLOGO: true # Disable the .NET logo - DOTNET_CLI_TELEMETRY_OPTOUT: true # Disable sending .NET CLI telemetry + DOTNET_NOLOGO: true # Disable the .NET logo + DOTNET_CLI_TELEMETRY_OPTOUT: true # Disable sending .NET CLI telemetry jobs: - build-windows: - name: Windows Build - runs-on: windows-latest + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + include: + - os: windows-latest + testRunName: Windows + # - os: ubuntu-latest + # testRunName: Linux + # - os: macos-14 + # testRunName: Linux + fail-fast: false + + env: + MYGET_API_KEY: ${{ secrets.MYGET_API_KEY }} + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }} + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_ACCESS_TOKEN }} steps: - - name: ⤵️ Checkout Source - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: 🛠️ Setup .NET - uses: actions/setup-dotnet@v4 - with: - # global-json-file: global.json - dotnet-version: | - 2.1.x - 3.1.x - 5.0.x - 8.0.100 - - - - name: 🛠️ Install dotnet tools - run: dotnet tool restore - - - name: 🔨 Build and Test - run: dotnet tool run dotnet-cake --target=Test --test-run-name=Windows --configuration=Release - # If you need to get more verbose logging, add the following to the dotnet-cake above: --verbosity=diagnostic - -# Wait with this until package errors (tests following packaging) are all fixed - # - name: 📦 Package - # run: dotnet tool run dotnet-cake --target=Package - - # - name: 💾 Upload build artifacts - # uses: actions/upload-artifact@v4 - # with: - # name: Package - # path: package - - # - name: 💾 Upload test results - # uses: actions/upload-artifact@v4 - # with: - # name: Test results (Windows) - # path: test-results - # # Use always() to always run this step to publish test results when there are test failures - # if: ${{ always() }} - - # build-linux: - # name: Linux Build - # runs-on: ubuntu-latest - - # steps: - # - name: ⤵️ Checkout Source - # uses: actions/checkout@v4 - - # - name: 🛠️ Setup .NET - # uses: actions/setup-dotnet@v4 - # with: - # global-json-file: global.json - - # - name: 🛠️ Install F# - # run: sudo apt-get install fsharp - - # - name: 🛠️ Install dotnet tools - # run: dotnet tool restore - - # - name: 🔨 Build and Test - # run: dotnet tool run dotnet-cake --target=Test --test-run-name=Linux --configuration=Release - - # - name: 💾 Upload test results - # uses: actions/upload-artifact@v4 - # with: - # name: Test results (Linux) - # path: test-results - # # Use always() to always run this step to publish test results when there are test failures - # if: ${{ always() }} - - # build-macos: - # name: MacOS Build - # runs-on: macos-14 - - # steps: - # - name: ⤵️ Checkout Source - # uses: actions/checkout@v4 - - # - name: 🛠️ Setup .NET - # uses: actions/setup-dotnet@v4 - # with: - # global-json-file: global.json - # dotnet-version: 6.x - - # - name: 🛠️ Install dotnet tools - # run: dotnet tool restore - - # - name: 🔨 Build and Test - # run: dotnet tool run dotnet-cake --target=Test --test-run-name=Linux --configuration=Release - - # - name: 💾 Upload test results - # uses: actions/upload-artifact@v4 - # with: - # name: Test results (macOS) - # path: test-results - # # Use always() to always run this step to publish test results when there are test failures - # if: ${{ always() }} + - name: ⤵️ Checkout Source + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 🛠️ Setup .NET + uses: actions/setup-dotnet@v4 + with: + # global-json-file: global.json + dotnet-version: | + 3.1.x + 6.0.x + 8.0.100 + + - name: 🔧 Install dotnet tools + run: dotnet tool restore + + - name: 🍰 Run cake + shell: pwsh + # If you need to get more verbose logging, add the following to the dotnet-cake above: --verbosity=diagnostic + run: dotnet cake --target=Test --test-run-name=${{ matrix.testRunName }} --configuration=Release + + - name: 🪵 Upload build logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: NUnitConsoleLogs + # This path is defined in build-settings.cake + path: "build-results/*.binlog" + # if-no-files-found: error + + # Wait with this until package errors (tests following packaging) are all fixed + # - name: 📦 Package + # run: dotnet tool run dotnet-cake --target=Package + + # - name: 💾 Upload build artifacts + # uses: actions/upload-artifact@v4 + # with: + # name: Package + # path: package + + - name: 💾 Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: "Test results (${{ matrix.testRunName }})" + path: test-results diff --git a/.gitignore b/.gitignore index 24420f779..7d8aad284 100644 --- a/.gitignore +++ b/.gitignore @@ -176,6 +176,7 @@ TestResult.xml testCaseCollection.xml deploy lib +build-results test-results package images diff --git a/build.cake b/build.cake index 24848675c..9afa3f9ea 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ -// Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.0.1-dev00001 +// Load the recipe +#load nuget:?package=NUnit.Cake.Recipe&version=1.1.0-dev00007 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -9,16 +9,16 @@ BuildSettings.Initialize( title: "NUnit Console and Engine", githubRepository: "nunit-console", solutionFile: "NUnitConsole.sln", - exemptFiles: new [] { "Options.cs", "ProcessUtils.cs", "ProcessUtilsTests.cs" }, + exemptFiles: new[] { "Options.cs", "ProcessUtils.cs", "ProcessUtilsTests.cs" }, unitTests: "**/*.tests.exe|**/nunit3-console.tests.dll", - unitTestRunner: new CustomTestRunner() ); + unitTestRunner: new CustomTestRunner()); ////////////////////////////////////////////////////////////////////// // PACKAGE TEST LISTS ////////////////////////////////////////////////////////////////////// - // Tests run for all runner packages except NETCORE runner - var StandardRunnerTests = new List +// Tests run for all runner packages except NETCORE runner +var StandardRunnerTests = new List { Net462Test, Net462X86Test, @@ -33,8 +33,8 @@ BuildSettings.Initialize( V2ResultWriterTest }; - // Tests run for the NETCORE runner package - var NetCoreRunnerTests = new List +// Tests run for the NETCORE runner package +var NetCoreRunnerTests = new List { NetCore31Test, Net60Test, @@ -42,24 +42,24 @@ BuildSettings.Initialize( Net80Test, }; - const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; - bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); +const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; +bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); - // TODO: Remove the limitation to Windows - if (IsRunningOnWindows() && dotnetX86Available) +// TODO: Remove the limitation to Windows +if (IsRunningOnWindows() && dotnetX86Available) +{ + StandardRunnerTests.Add(Net60X86Test); + // TODO: Make these tests run on AppVeyor + if (!BuildSystem.IsRunningOnAppVeyor) { - StandardRunnerTests.Add(Net60X86Test); - // TODO: Make these tests run on AppVeyor - if (!BuildSystem.IsRunningOnAppVeyor) - { - StandardRunnerTests.Add(NetCore31X86Test); - StandardRunnerTests.Add(Net70X86Test); - StandardRunnerTests.Add(Net80X86Test); - } - // Currently, NetCoreRunner runs tests in process. As a result, - // X86 tests will work in our environment, although uses may run - // it as a tool using the X86 architecture. + StandardRunnerTests.Add(NetCore31X86Test); + StandardRunnerTests.Add(Net70X86Test); + StandardRunnerTests.Add(Net80X86Test); } + // Currently, NetCoreRunner runs tests in process. As a result, + // X86 tests will work in our environment, although uses may run + // it as a tool using the X86 architecture. +} ////////////////////////////////////////////////////////////////////// // INDIVIDUAL PACKAGE TEST DEFINITIONS @@ -252,7 +252,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net7.0").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_PDB_FILES_NETCORE) }, - testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + $"NUnit.ConsoleRunner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), tests: StandardRunnerTests), @@ -266,7 +266,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { id: "NUnit.ConsoleRunner.NetCore", source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.netcore.nuspec", checks: new PackageCheck[] { HasFiles("nunit.exe") }, - testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/nunit.exe"), tests: NetCoreRunnerTests), @@ -281,7 +281,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins") }, - testRunner: new ConsoleRunnerSelfTester(BuildSettings.ChocolateyTestDirectory + testRunner: new ConsoleRunnerSelfTester(BuildSettings.ChocolateyTestDirectory + $"nunit-console-runner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), tests: StandardRunnerTests), @@ -298,7 +298,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("bin/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) }, - testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory + testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), tests: StandardRunnerTests, bundledExtensions: new [] { @@ -353,11 +353,11 @@ public class CustomTestRunner : TestRunner, IUnitTestRunner public int RunUnitTest(FilePath testPath) { // Run console tests under the just-built console - if(testPath.ToString().Contains("nunit3-console.tests.dll")) + if (testPath.ToString().Contains("nunit3-console.tests.dll")) { return BuildSettings.Context.StartProcess( BuildSettings.OutputDirectory + "net462/nunit3-console.exe", - $"\"{testPath}\" {BuildSettings.UnitTestArguments}" ); + $"\"{testPath}\" {BuildSettings.UnitTestArguments}"); } // All other tests use NUnitLite @@ -370,38 +370,15 @@ public class ConsoleRunnerSelfTester : TestRunner, IPackageTestRunner { private string _executablePath; - public ConsoleRunnerSelfTester(string executablePath) - { - _executablePath = executablePath; - } - - public int RunPackageTest(string arguments) - { - Console.WriteLine("Running package test"); - return base.RunTest(_executablePath, arguments); - } -} - -////////////////////////////////////////////////////////////////////// -// DOTNET TOOL PACKAGE -////////////////////////////////////////////////////////////////////// - -// TODO: Temporary custom package class to be moved into the recipe - -public class DotNetToolPackage : NuGetPackage -{ - public DotNetToolPackage(string id, string source, string basePath = null, - IPackageTestRunner testRunner = null, TestRunnerSource testRunnerSource = null, - PackageCheck[] checks = null, PackageCheck[] symbols = null, IEnumerable tests = null) - : base(id, source, basePath: basePath, testRunner: testRunner, testRunnerSource: testRunnerSource, - checks: checks, symbols: symbols, tests: tests) { } + public ConsoleRunnerSelfTester(string executablePath) + { + _executablePath = executablePath; + } - public override void InstallPackage() + public int RunPackageTest(string arguments) { - var arguments = $"tool install {PackageId} --version {BuildSettings.PackageVersion} " + - $"--add-source \"{BuildSettings.PackageDirectory}\" --tool-path \"{PackageTestDirectory}\""; - Console.WriteLine($"Executing dotnet {arguments}"); - _context.StartProcess("dotnet", arguments); + Console.WriteLine("Running package test"); + return base.RunTest(_executablePath, arguments); } } @@ -411,7 +388,7 @@ public class DotNetToolPackage : NuGetPackage // Some of these targets may be moved into the recipe itself in the future. -// When a NuGet package was published successfully but the corresponding symbols +// When a NuGet package was published successfully but the corresponding symbols // package failed, use this target locally after correcting the error. // TODO: This task is extemely complicated because it has to copy lots of code // from the recipe. It would be simpler if it were integrated in the recipe. @@ -420,12 +397,12 @@ public class DotNetToolPackage : NuGetPackage // if this is incorporated into the recipe. Task("PublishSymbolsPackage") .Description("Re-publish a specific symbols package to NuGet after a failure") - .Does(() => + .Does(() => { - if (!BuildSettings.ShouldPublishToNuGet) - Information("Nothing to publish to NuGet from this run."); - else if (CommandLineOptions.NoPush) - Information("NoPush option suppressing publication to NuGet"); + if (!BuildSettings.ShouldPublishToNuGet) + Information("Nothing to publish to NuGet from this run."); + else if (CommandLineOptions.NoPush) + Information("NoPush option suppressing publication to NuGet"); else { List packages; @@ -458,8 +435,8 @@ Task("PublishSymbolsPackage") } // At this point we have a single NuGet package in packages - var packageName = $"{packages[0].PackageId}.{BuildSettings.PackageVersion}.snupkg"; - var packagePath = BuildSettings.PackageDirectory + packageName; + var packageName = $"{packages[0].PackageId}.{BuildSettings.PackageVersion}.snupkg"; + var packagePath = BuildSettings.PackageDirectory + packageName; NuGetPush(packagePath, new NuGetPushSettings() { ApiKey = BuildSettings.NuGetApiKey, Source = BuildSettings.NuGetPushUrl }); } }); From e3300f976a8b98cd3bc4f79878e81b68d7bc1407 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 12 Aug 2024 07:58:37 -0700 Subject: [PATCH 083/190] Update to use latest release of recipe --- .../workflows/NUnitConsoleAndEngine.CI.yml | 41 +++++++++---------- .gitignore | 1 + build.cake | 29 ++++++------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index a13753f32..1cb099adf 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -1,7 +1,9 @@ name: NUnitConsoleAndEngine.CI on: - pull_request: + workflow_dispatch: + + push: branches-ignore: - "azure-*" paths-ignore: @@ -13,25 +15,15 @@ env: DOTNET_CLI_TELEMETRY_OPTOUT: true # Disable sending .NET CLI telemetry jobs: - build: - runs-on: ${{ matrix.os }} - - strategy: - matrix: - include: - - os: windows-latest - testRunName: Windows - # - os: ubuntu-latest - # testRunName: Linux - # - os: macos-14 - # testRunName: Linux - fail-fast: false + build-windows: + name: Windows Build + runs-on: windows-latest env: - MYGET_API_KEY: ${{ secrets.MYGET_API_KEY }} - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} - CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }} - GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_ACCESS_TOKEN }} + MYGET_API_KEY: ${{ secrets.PUBLISH_MYGET_ORG }} + NUGET_API_KEY: ${{ secrets.PUBLISH_NUGET_ORG }} + CHOCO_API_KEY: ${{ secrets.PUBLISH_CHOCOLATEY_ORG }} + GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_CP }} steps: - name: ⤵️ Checkout Source @@ -42,7 +34,6 @@ jobs: - name: 🛠️ Setup .NET uses: actions/setup-dotnet@v4 with: - # global-json-file: global.json dotnet-version: | 3.1.x 6.0.x @@ -52,9 +43,15 @@ jobs: run: dotnet tool restore - name: 🍰 Run cake - shell: pwsh + env: + MYGET_API_KEY: ${{ secrets.PUBLISH_MYGET_ORG }} + NUGET_API_KEY: ${{ secrets.PUBLISH_NUGET_ORG }} + CHOCO_API_KEY: ${{ secrets.PUBLISH_CHOCOLATEY_ORG }} + GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_CP }} + + #shell: pwsh # If you need to get more verbose logging, add the following to the dotnet-cake above: --verbosity=diagnostic - run: dotnet cake --target=Test --test-run-name=${{ matrix.testRunName }} --configuration=Release + run: dotnet cake --target=Publish --configuration=Release - name: 🪵 Upload build logs if: always() @@ -79,5 +76,5 @@ jobs: uses: actions/upload-artifact@v4 if: always() with: - name: "Test results (${{ matrix.testRunName }})" + name: "Test Results" path: test-results diff --git a/.gitignore b/.gitignore index 7d8aad284..5fc1321fc 100644 --- a/.gitignore +++ b/.gitignore @@ -178,6 +178,7 @@ deploy lib build-results test-results +build-results/ package images MockAssemblyResult.xml diff --git a/build.cake b/build.cake index 9afa3f9ea..7fe36ed09 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ -// Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.1.0-dev00007 +// Load the recipe +#load nuget:?package=NUnit.Cake.Recipe&version=1.1.0 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -52,8 +52,8 @@ if (IsRunningOnWindows() && dotnetX86Available) // TODO: Make these tests run on AppVeyor if (!BuildSystem.IsRunningOnAppVeyor) { - StandardRunnerTests.Add(NetCore31X86Test); - StandardRunnerTests.Add(Net70X86Test); + //StandardRunnerTests.Add(NetCore31X86Test); + //StandardRunnerTests.Add(Net70X86Test); StandardRunnerTests.Add(Net80X86Test); } // Currently, NetCoreRunner runs tests in process. As a result, @@ -132,11 +132,11 @@ static PackageTest Net70Test = new PackageTest( "net7.0/mock-assembly.dll", MockAssemblyExpectedResult("netcore-7.0")); -static PackageTest Net70X86Test = new PackageTest( - 1, "Net70X86Test", - "Run mock-assembly-x86.dll under .NET 7.0", - "net7.0/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("netcore-7.0")); +//static PackageTest Net70X86Test = new PackageTest( +// 1, "Net70X86Test", +// "Run mock-assembly-x86.dll under .NET 7.0", +// "net7.0/mock-assembly-x86.dll", +// MockAssemblyX86ExpectedResult("netcore-7.0")); static PackageTest Net60Test = new PackageTest( 1, "Net60Test", @@ -156,11 +156,12 @@ static PackageTest NetCore31Test = new PackageTest( "netcoreapp3.1/mock-assembly.dll", MockAssemblyExpectedResult("netcore-3.1")); -static PackageTest NetCore31X86Test = new PackageTest( - 1, "NetCore31X86Test", - "Run mock-assembly-x86.dll under .NET Core 3.1", - "netcoreapp3.1/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("netcore-3.1")); +// TODO: Currently failing in github workflow +//static PackageTest NetCore31X86Test = new PackageTest( +// 1, "NetCore31X86Test", +// "Run mock-assembly-x86.dll under .NET Core 3.1", +// "netcoreapp3.1/mock-assembly-x86.dll", +// MockAssemblyX86ExpectedResult("netcore-3.1")); static PackageTest Net60PlusNet80Test = new PackageTest( 1, "Net60PlusNet80Test", From 9258f2281ecc2bcec1f7bd1816491ea725c42b8f Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 12 Aug 2024 17:10:46 -0700 Subject: [PATCH 084/190] Limit running of X86 tests under GitHub actions until we resolve issue more fully" --- .../workflows/NUnitConsoleAndEngine.CI.yml | 4 +- build.cake | 44 ++++++++++--------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index 1cb099adf..cd4ee621c 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -2,7 +2,7 @@ on: workflow_dispatch: - + pull_request: push: branches-ignore: - "azure-*" @@ -51,7 +51,7 @@ jobs: #shell: pwsh # If you need to get more verbose logging, add the following to the dotnet-cake above: --verbosity=diagnostic - run: dotnet cake --target=Publish --configuration=Release + run: dotnet cake --target=ContinuousIntegration --configuration=Release - name: 🪵 Upload build logs if: always() diff --git a/build.cake b/build.cake index 7fe36ed09..4a6f57dff 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.1.0 +#load nuget:?package=NUnit.Cake.Recipe&version=1.2.0-dev00003 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -43,21 +43,24 @@ var NetCoreRunnerTests = new List }; const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; +// TODO: Remove the limitation to Windows bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); -// TODO: Remove the limitation to Windows -if (IsRunningOnWindows() && dotnetX86Available) +if (dotnetX86Available) { + bool onAppVeyor = BuildSystem.IsRunningOnAppVeyor; + bool onGitHubActions = BuildSystem.IsRunningOnGitHubActions; + StandardRunnerTests.Add(Net60X86Test); - // TODO: Make these tests run on AppVeyor - if (!BuildSystem.IsRunningOnAppVeyor) - { - //StandardRunnerTests.Add(NetCore31X86Test); - //StandardRunnerTests.Add(Net70X86Test); + // TODO: Make these tests run on AppVeyor and GitHub Actions + if (!onAppVeyor && !onGitHubActions) + StandardRunnerTests.Add(NetCore31X86Test); + if (!onAppVeyor && !onGitHubActions) + StandardRunnerTests.Add(Net70X86Test); + if (!onAppVeyor) StandardRunnerTests.Add(Net80X86Test); - } // Currently, NetCoreRunner runs tests in process. As a result, - // X86 tests will work in our environment, although uses may run + // X86 tests will not work in our environment, although uses may run // it as a tool using the X86 architecture. } @@ -132,11 +135,11 @@ static PackageTest Net70Test = new PackageTest( "net7.0/mock-assembly.dll", MockAssemblyExpectedResult("netcore-7.0")); -//static PackageTest Net70X86Test = new PackageTest( -// 1, "Net70X86Test", -// "Run mock-assembly-x86.dll under .NET 7.0", -// "net7.0/mock-assembly-x86.dll", -// MockAssemblyX86ExpectedResult("netcore-7.0")); +static PackageTest Net70X86Test = new PackageTest( + 1, "Net70X86Test", + "Run mock-assembly-x86.dll under .NET 7.0", + "net7.0/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("netcore-7.0")); static PackageTest Net60Test = new PackageTest( 1, "Net60Test", @@ -156,12 +159,11 @@ static PackageTest NetCore31Test = new PackageTest( "netcoreapp3.1/mock-assembly.dll", MockAssemblyExpectedResult("netcore-3.1")); -// TODO: Currently failing in github workflow -//static PackageTest NetCore31X86Test = new PackageTest( -// 1, "NetCore31X86Test", -// "Run mock-assembly-x86.dll under .NET Core 3.1", -// "netcoreapp3.1/mock-assembly-x86.dll", -// MockAssemblyX86ExpectedResult("netcore-3.1")); +static PackageTest NetCore31X86Test = new PackageTest( + 1, "NetCore31X86Test", + "Run mock-assembly-x86.dll under .NET Core 3.1", + "netcoreapp3.1/mock-assembly-x86.dll", + MockAssemblyX86ExpectedResult("netcore-3.1")); static PackageTest Net60PlusNet80Test = new PackageTest( 1, "Net60PlusNet80Test", From a25dcade66901f61e43bf38dc175ccf4924781a4 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 13 Aug 2024 08:45:49 -0700 Subject: [PATCH 085/190] Run all unit tests under NUnitLite --- build.cake | 22 +--------------- .../nunit3-console.tests/CommandLineTests.cs | 22 ++++++++-------- .../nunit3-console.tests/ConsoleMocks.cs | 1 + .../DefaultOptionsProviderTests.cs | 3 ++- .../OutputSpecificationTests.cs | 26 ++++++++++--------- .../nunit3-console.tests/Program.cs | 18 +++++++++++++ .../nunit3-console.tests.csproj | 7 ++++- .../nunit3-console/ConsoleOptions.cs | 7 ++--- .../nunit3-console/ConsoleRunner.cs | 1 + .../DefaultOptionsProvider.cs | 2 +- .../IDefaultOptionsProvider.cs | 2 +- .../{OptionsUtils => Options}/IFileSystem.cs | 0 .../{OptionsUtils => Options}/OptionParser.cs | 2 +- .../{OptionsUtils => Options}/Options.cs | 2 +- .../OutputSpecification.cs | 2 +- .../TestNameParser.cs | 2 +- src/NUnitConsole/nunit3-console/Program.cs | 3 ++- 17 files changed, 66 insertions(+), 56 deletions(-) create mode 100644 src/NUnitConsole/nunit3-console.tests/Program.cs rename src/NUnitConsole/nunit3-console/{OptionsUtils => Options}/DefaultOptionsProvider.cs (93%) rename src/NUnitConsole/nunit3-console/{OptionsUtils => Options}/IDefaultOptionsProvider.cs (83%) rename src/NUnitConsole/nunit3-console/{OptionsUtils => Options}/IFileSystem.cs (100%) rename src/NUnitConsole/nunit3-console/{OptionsUtils => Options}/OptionParser.cs (98%) rename src/NUnitConsole/nunit3-console/{OptionsUtils => Options}/Options.cs (99%) rename src/NUnitConsole/nunit3-console/{OptionsUtils => Options}/OutputSpecification.cs (98%) rename src/NUnitConsole/nunit3-console/{OptionsUtils => Options}/TestNameParser.cs (98%) diff --git a/build.cake b/build.cake index 4a6f57dff..0c2c623a4 100644 --- a/build.cake +++ b/build.cake @@ -10,8 +10,7 @@ BuildSettings.Initialize( githubRepository: "nunit-console", solutionFile: "NUnitConsole.sln", exemptFiles: new[] { "Options.cs", "ProcessUtils.cs", "ProcessUtilsTests.cs" }, - unitTests: "**/*.tests.exe|**/nunit3-console.tests.dll", - unitTestRunner: new CustomTestRunner()); + unitTests: "**/*.tests.exe"); ////////////////////////////////////////////////////////////////////// // PACKAGE TEST LISTS @@ -349,25 +348,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { // TEST RUNNERS ////////////////////////////////////////////////////////////////////// -// Custom unit test runner to run console vs engine tests differently -// TODO: Use NUnitLite for all tests? -public class CustomTestRunner : TestRunner, IUnitTestRunner -{ - public int RunUnitTest(FilePath testPath) - { - // Run console tests under the just-built console - if (testPath.ToString().Contains("nunit3-console.tests.dll")) - { - return BuildSettings.Context.StartProcess( - BuildSettings.OutputDirectory + "net462/nunit3-console.exe", - $"\"{testPath}\" {BuildSettings.UnitTestArguments}"); - } - - // All other tests use NUnitLite - return new NUnitLiteRunner().RunUnitTest(testPath); - } -} - // Use the console runner we just built to run package tests public class ConsoleRunnerSelfTester : TestRunner, IPackageTestRunner { diff --git a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs index dfc6eda3f..54af95333 100644 --- a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs @@ -3,11 +3,11 @@ using System; using System.IO; using System.Reflection; -using NUnit.Common; using System.Collections.Generic; -using NUnit.Framework; -using NUnit.Options; +using NUnit.Common; +using NUnit.ConsoleRunner.Options; +using NUnit.Framework; namespace NUnit.ConsoleRunner.Tests { @@ -403,7 +403,7 @@ public void ResultOptionWithFilePath() Assert.That(options.InputFiles.Count, Is.EqualTo(1), "assembly should be set"); Assert.That(options.InputFiles[0], Is.EqualTo("tests.dll")); - OutputSpecification spec = options.ResultOutputSpecifications[0]; + var spec = options.ResultOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("nunit3")); Assert.Null(spec.Transform); @@ -417,7 +417,7 @@ public void ResultOptionWithFilePathAndFormat() Assert.That(options.InputFiles.Count, Is.EqualTo(1), "assembly should be set"); Assert.That(options.InputFiles[0], Is.EqualTo("tests.dll")); - OutputSpecification spec = options.ResultOutputSpecifications[0]; + var spec = options.ResultOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("nunit2")); Assert.Null(spec.Transform); @@ -437,7 +437,7 @@ public void ResultOptionWithFilePathAndTransform() Assert.That(options.InputFiles.Count, Is.EqualTo(1), "assembly should be set"); Assert.That(options.InputFiles[0], Is.EqualTo("tests.dll")); - OutputSpecification spec = options.ResultOutputSpecifications[0]; + var spec = options.ResultOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("user")); var fullFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, transformFile); @@ -524,7 +524,7 @@ public void InvalidResultSpecRecordsError() { var options = ConsoleMocks.Options("test.dll", "-result:userspecifed.xml;format=nunit2;format=nunit3"); Assert.That(options.ResultOutputSpecifications, Has.Exactly(1).Items - .And.Exactly(1).Property(nameof(OutputSpecification.OutputPath)).EqualTo("TestResult.xml")); + .And.Exactly(1).Property(nameof(Options.OutputSpecification.OutputPath)).EqualTo("TestResult.xml")); Assert.That(options.ErrorMessages, Has.Exactly(1).Contains("conflicting format options").IgnoreCase); } @@ -538,7 +538,7 @@ public void MissingXsltFileRecordsError() new VirtualFileSystem(), "test.dll", $"-result:userspecifed.xml;transform={missingXslt}"); Assert.That(options.ResultOutputSpecifications, Has.Exactly(1).Items - .And.Exactly(1).Property(nameof(OutputSpecification.Transform)).Null); + .And.Exactly(1).Property(nameof(Options.OutputSpecification.Transform)).Null); Assert.That(options.ErrorMessages, Has.Exactly(1).Contains($"{missingXslt} could not be found").IgnoreCase); } @@ -559,7 +559,7 @@ public void ExploreOptionWithFilePath() Assert.That(options.InputFiles[0], Is.EqualTo("tests.dll")); Assert.That(options.Explore, Is.True); - OutputSpecification spec = options.ExploreOutputSpecifications[0]; + var spec = options.ExploreOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("nunit3")); Assert.Null(spec.Transform); @@ -574,7 +574,7 @@ public void ExploreOptionWithFilePathAndFormat() Assert.That(options.InputFiles[0], Is.EqualTo("tests.dll")); Assert.That(options.Explore, Is.True); - OutputSpecification spec = options.ExploreOutputSpecifications[0]; + var spec = options.ExploreOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("cases")); Assert.Null(spec.Transform); @@ -594,7 +594,7 @@ public void ExploreOptionWithFilePathAndTransform() Assert.That(options.InputFiles[0], Is.EqualTo("tests.dll")); Assert.That(options.Explore, Is.True); - OutputSpecification spec = options.ExploreOutputSpecifications[0]; + var spec = options.ExploreOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("user")); var fullFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, transformFile); diff --git a/src/NUnitConsole/nunit3-console.tests/ConsoleMocks.cs b/src/NUnitConsole/nunit3-console.tests/ConsoleMocks.cs index 8f26a6c4c..26ffdefb4 100644 --- a/src/NUnitConsole/nunit3-console.tests/ConsoleMocks.cs +++ b/src/NUnitConsole/nunit3-console.tests/ConsoleMocks.cs @@ -2,6 +2,7 @@ using NSubstitute; using NUnit.Common; +using NUnit.ConsoleRunner.Options; namespace NUnit.ConsoleRunner.Tests { diff --git a/src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs b/src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs index 51280294b..8584260d2 100644 --- a/src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs @@ -1,7 +1,8 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt using System; -using NUnit.Common; + +using NUnit.ConsoleRunner.Options; using NUnit.Framework; namespace NUnit.ConsoleRunner.Tests diff --git a/src/NUnitConsole/nunit3-console.tests/OutputSpecificationTests.cs b/src/NUnitConsole/nunit3-console.tests/OutputSpecificationTests.cs index d79e8af23..8d7bb3299 100644 --- a/src/NUnitConsole/nunit3-console.tests/OutputSpecificationTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/OutputSpecificationTests.cs @@ -3,6 +3,8 @@ using System.IO; using NUnit.Framework; +using Spec = NUnit.ConsoleRunner.Options.OutputSpecification; + namespace NUnit.Common.Tests { public class OutputSpecificationTests @@ -11,7 +13,7 @@ public class OutputSpecificationTests public void SpecMayNotBeNull() { Assert.That( - () => new OutputSpecification(null, null), + () => new Spec(null, null), Throws.TypeOf()); } @@ -20,7 +22,7 @@ public void SpecMayNotBeNull() public void SpecOptionMustContainEqualSign() { Assert.That( - () => new OutputSpecification("MyFile.xml;transform.xslt", null), + () => new Spec("MyFile.xml;transform.xslt", null), Throws.TypeOf()); } @@ -28,14 +30,14 @@ public void SpecOptionMustContainEqualSign() public void SpecOptionMustContainJustOneEqualSign() { Assert.That( - () => new OutputSpecification("MyFile.xml;transform=xslt=transform.xslt", null), + () => new Spec("MyFile.xml;transform=xslt=transform.xslt", null), Throws.TypeOf()); } [Test] public void FileNameOnly() { - var spec = new OutputSpecification("MyFile.xml", null); + var spec = new Spec("MyFile.xml", null); Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); Assert.That(spec.Format, Is.EqualTo("nunit3")); Assert.Null(spec.Transform); @@ -44,7 +46,7 @@ public void FileNameOnly() [Test] public void FileNamePlusFormat() { - var spec = new OutputSpecification("MyFile.xml;format=nunit2", null); + var spec = new Spec("MyFile.xml;format=nunit2", null); Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); Assert.That(spec.Format, Is.EqualTo("nunit2")); Assert.Null(spec.Transform); @@ -54,7 +56,7 @@ public void FileNamePlusFormat() public void FileNamePlusTransform() { const string fileName = "transform.xslt"; - var spec = new OutputSpecification($"MyFile.xml;transform={fileName}", null); + var spec = new Spec($"MyFile.xml;transform={fileName}", null); Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); Assert.That(spec.Format, Is.EqualTo("user")); Assert.That(spec.Transform, Is.EqualTo(fileName)); @@ -64,7 +66,7 @@ public void FileNamePlusTransform() public void UserFormatMayBeIndicatedExplicitlyAfterTransform() { const string fileName = "transform.xslt"; - var spec = new OutputSpecification($"MyFile.xml;transform={fileName};format=user", null); + var spec = new Spec($"MyFile.xml;transform={fileName};format=user", null); Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); Assert.That(spec.Format, Is.EqualTo("user")); Assert.That(spec.Transform, Is.EqualTo(fileName)); @@ -74,7 +76,7 @@ public void UserFormatMayBeIndicatedExplicitlyAfterTransform() public void UserFormatMayBeIndicatedExplicitlyBeforeTransform() { const string fileName = "transform.xslt"; - var spec = new OutputSpecification($"MyFile.xml;format=user;transform={fileName}", null); + var spec = new Spec($"MyFile.xml;format=user;transform={fileName}", null); Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); Assert.That(spec.Format, Is.EqualTo("user")); Assert.That(spec.Transform, Is.EqualTo(fileName)); @@ -84,7 +86,7 @@ public void UserFormatMayBeIndicatedExplicitlyBeforeTransform() public void MultipleFormatSpecifiersNotAllowed() { Assert.That( - () => new OutputSpecification("MyFile.xml;format=nunit2;format=nunit3", null), + () => new Spec("MyFile.xml;format=nunit2;format=nunit3", null), Throws.TypeOf()); } @@ -92,7 +94,7 @@ public void MultipleFormatSpecifiersNotAllowed() public void MultipleTransformSpecifiersNotAllowed() { Assert.That( - () => new OutputSpecification("MyFile.xml;transform=transform1.xslt;transform=transform2.xslt", null), + () => new Spec("MyFile.xml;transform=transform1.xslt;transform=transform2.xslt", null), Throws.TypeOf()); } @@ -100,7 +102,7 @@ public void MultipleTransformSpecifiersNotAllowed() public void TransformWithNonUserFormatNotAllowed() { Assert.That( - () => new OutputSpecification("MyFile.xml;format=nunit2;transform=transform.xslt", null), + () => new Spec("MyFile.xml;format=nunit2;transform=transform.xslt", null), Throws.TypeOf()); } @@ -112,7 +114,7 @@ public void TransformWithNonUserFormatNotAllowed() public void TransformFolderIsUsedToSpecifyTransform(string transformFolder) { const string fileName = "transform.xslt"; - var spec = new OutputSpecification($"MyFile.xml;transform=transform.xslt", transformFolder); + var spec = new Spec($"MyFile.xml;transform=transform.xslt", transformFolder); var expectedTransform = Path.Combine(transformFolder ?? "", fileName); Assert.That(spec.Transform, Is.EqualTo(expectedTransform)); } diff --git a/src/NUnitConsole/nunit3-console.tests/Program.cs b/src/NUnitConsole/nunit3-console.tests/Program.cs new file mode 100644 index 000000000..37bd398c2 --- /dev/null +++ b/src/NUnitConsole/nunit3-console.tests/Program.cs @@ -0,0 +1,18 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System.Reflection; + +namespace NUnit.Engine.Tests +{ + class Program + { + static int Main(string[] args) + { +#if NETFRAMEWORK + return new NUnitLite.TextRunner(typeof(Program).Assembly).Execute(args); +#else + return new NUnitLite.TextRunner(typeof(Program).GetTypeInfo().Assembly).Execute(args); +#endif + } + } +} diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index 6c94f3fb9..f740358e0 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -3,6 +3,8 @@ NUnit.ConsoleRunner.Tests net462;net6.0;net8.0 + Exe + NUnit.Engine.Tests.Program 1685 Full @@ -16,6 +18,7 @@ + @@ -42,7 +45,9 @@ - + + + diff --git a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs index fa16fa3db..5eba632c4 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs @@ -5,10 +5,11 @@ using System.IO; using System.Text; using System.Text.RegularExpressions; -using NUnit.Options; -using NUnit.ConsoleRunner.OptionsUtils; -namespace NUnit.Common +using NUnit.Common; +using NUnit.ConsoleRunner.Options; + +namespace NUnit.ConsoleRunner { /// /// ConsoleOptions encapsulates the option settings for diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index 4ddb88b0c..dd778af77 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -5,6 +5,7 @@ using System.IO; using System.Xml; using NUnit.Common; +using NUnit.ConsoleRunner.Options; using NUnit.ConsoleRunner.Utilities; using NUnit.Engine; using NUnit.Engine.Extensibility; diff --git a/src/NUnitConsole/nunit3-console/OptionsUtils/DefaultOptionsProvider.cs b/src/NUnitConsole/nunit3-console/Options/DefaultOptionsProvider.cs similarity index 93% rename from src/NUnitConsole/nunit3-console/OptionsUtils/DefaultOptionsProvider.cs rename to src/NUnitConsole/nunit3-console/Options/DefaultOptionsProvider.cs index 70d0e6f13..33bdd1e16 100644 --- a/src/NUnitConsole/nunit3-console/OptionsUtils/DefaultOptionsProvider.cs +++ b/src/NUnitConsole/nunit3-console/Options/DefaultOptionsProvider.cs @@ -1,6 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -namespace NUnit.Common +namespace NUnit.ConsoleRunner.Options { using System; diff --git a/src/NUnitConsole/nunit3-console/OptionsUtils/IDefaultOptionsProvider.cs b/src/NUnitConsole/nunit3-console/Options/IDefaultOptionsProvider.cs similarity index 83% rename from src/NUnitConsole/nunit3-console/OptionsUtils/IDefaultOptionsProvider.cs rename to src/NUnitConsole/nunit3-console/Options/IDefaultOptionsProvider.cs index 8362735cf..543378afc 100644 --- a/src/NUnitConsole/nunit3-console/OptionsUtils/IDefaultOptionsProvider.cs +++ b/src/NUnitConsole/nunit3-console/Options/IDefaultOptionsProvider.cs @@ -1,6 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -namespace NUnit.Common +namespace NUnit.ConsoleRunner.Options { internal interface IDefaultOptionsProvider { diff --git a/src/NUnitConsole/nunit3-console/OptionsUtils/IFileSystem.cs b/src/NUnitConsole/nunit3-console/Options/IFileSystem.cs similarity index 100% rename from src/NUnitConsole/nunit3-console/OptionsUtils/IFileSystem.cs rename to src/NUnitConsole/nunit3-console/Options/IFileSystem.cs diff --git a/src/NUnitConsole/nunit3-console/OptionsUtils/OptionParser.cs b/src/NUnitConsole/nunit3-console/Options/OptionParser.cs similarity index 98% rename from src/NUnitConsole/nunit3-console/OptionsUtils/OptionParser.cs rename to src/NUnitConsole/nunit3-console/Options/OptionParser.cs index e15a00f2c..956346b59 100644 --- a/src/NUnitConsole/nunit3-console/OptionsUtils/OptionParser.cs +++ b/src/NUnitConsole/nunit3-console/Options/OptionParser.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using NUnit.Common; -namespace NUnit.ConsoleRunner.OptionsUtils +namespace NUnit.ConsoleRunner.Options { internal class OptionParser { diff --git a/src/NUnitConsole/nunit3-console/OptionsUtils/Options.cs b/src/NUnitConsole/nunit3-console/Options/Options.cs similarity index 99% rename from src/NUnitConsole/nunit3-console/OptionsUtils/Options.cs rename to src/NUnitConsole/nunit3-console/Options/Options.cs index d9d4db7af..1d7dc20ce 100644 --- a/src/NUnitConsole/nunit3-console/OptionsUtils/Options.cs +++ b/src/NUnitConsole/nunit3-console/Options/Options.cs @@ -182,7 +182,7 @@ using MessageLocalizerConverter = System.Converter; #endif -namespace NUnit.Options +namespace NUnit.ConsoleRunner.Options { static class StringCoda { diff --git a/src/NUnitConsole/nunit3-console/OptionsUtils/OutputSpecification.cs b/src/NUnitConsole/nunit3-console/Options/OutputSpecification.cs similarity index 98% rename from src/NUnitConsole/nunit3-console/OptionsUtils/OutputSpecification.cs rename to src/NUnitConsole/nunit3-console/Options/OutputSpecification.cs index 9c46d7f97..2390025bc 100644 --- a/src/NUnitConsole/nunit3-console/OptionsUtils/OutputSpecification.cs +++ b/src/NUnitConsole/nunit3-console/Options/OutputSpecification.cs @@ -4,7 +4,7 @@ using System.IO; using System.Text; -namespace NUnit.Common +namespace NUnit.ConsoleRunner.Options { /// /// OutputSpecification encapsulates a file output path and format diff --git a/src/NUnitConsole/nunit3-console/OptionsUtils/TestNameParser.cs b/src/NUnitConsole/nunit3-console/Options/TestNameParser.cs similarity index 98% rename from src/NUnitConsole/nunit3-console/OptionsUtils/TestNameParser.cs rename to src/NUnitConsole/nunit3-console/Options/TestNameParser.cs index a6026de1a..77840b686 100644 --- a/src/NUnitConsole/nunit3-console/OptionsUtils/TestNameParser.cs +++ b/src/NUnitConsole/nunit3-console/Options/TestNameParser.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; -namespace NUnit.Common +namespace NUnit.ConsoleRunner.Options { /// /// TestNameParser is used to parse the arguments to the diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index bfc4a9413..5a12026b2 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -7,9 +7,10 @@ using System.Linq; using System.Reflection; using System.Text; + using NUnit.Common; +using NUnit.ConsoleRunner.Options; using NUnit.Engine; -using NUnit.Options; namespace NUnit.ConsoleRunner { From 1f5720c1274303fc7b18fbe3dc4f82c225ec2e3f Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 13 Aug 2024 14:33:01 -0700 Subject: [PATCH 086/190] Replace AppVeyor with GitHub workflow --- .../workflows/NUnitConsoleAndEngine.CI.yml | 15 ++------- .github/workflows/PublishDraftRelease.yml | 33 +++++++++++++++++++ NUnitConsole.sln | 1 - appveyor.yml | 33 ------------------- 4 files changed, 35 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/PublishDraftRelease.yml delete mode 100644 appveyor.yml diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index cd4ee621c..71bd30336 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -15,8 +15,8 @@ env: DOTNET_CLI_TELEMETRY_OPTOUT: true # Disable sending .NET CLI telemetry jobs: - build-windows: - name: Windows Build + ContinuousIntegration: + name: Continuous Integration runs-on: windows-latest env: @@ -49,7 +49,6 @@ jobs: CHOCO_API_KEY: ${{ secrets.PUBLISH_CHOCOLATEY_ORG }} GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_CP }} - #shell: pwsh # If you need to get more verbose logging, add the following to the dotnet-cake above: --verbosity=diagnostic run: dotnet cake --target=ContinuousIntegration --configuration=Release @@ -62,16 +61,6 @@ jobs: path: "build-results/*.binlog" # if-no-files-found: error - # Wait with this until package errors (tests following packaging) are all fixed - # - name: 📦 Package - # run: dotnet tool run dotnet-cake --target=Package - - # - name: 💾 Upload build artifacts - # uses: actions/upload-artifact@v4 - # with: - # name: Package - # path: package - - name: 💾 Upload test results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/PublishDraftRelease.yml b/.github/workflows/PublishDraftRelease.yml new file mode 100644 index 000000000..c14bc02ea --- /dev/null +++ b/.github/workflows/PublishDraftRelease.yml @@ -0,0 +1,33 @@ +name: Publish Draft Release on GitHub + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version (must match a mileston)' + required: true + type: string + +jobs: + draft-release: + runs-on: ubuntu-latest + + steps: + - name: ⤵️ Checkout Source + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 🛠️ Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.100 + + - name: 🛠️ Install dotnet tools + run: dotnet tool restore + + - name: 🔨 Create Draft Release + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_CP }} + + run: dotnet cake --target=CreateDraftRelease --packageVersion={{inputs.version}} diff --git a/NUnitConsole.sln b/NUnitConsole.sln index d887627cc..fb214606c 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -8,7 +8,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore - appveyor.yml = appveyor.yml azure-pipelines.yml = azure-pipelines.yml build.cake = build.cake build.cmd = build.cmd diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index e255a721a..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,33 +0,0 @@ -image: Visual Studio 2022 - -# do not trigger build for branches intended to test azure pipeline only -branches: - except: - - /^azure-/ - -skip_commits: - files: - - ./*.txt - - ./*.md - -build_script: - - ps: .\build.ps1 --target=Appveyor --configuration=Release - -# disable built-in tests. -test: false - -artifacts: -- path: package\*.nupkg -- path: package\*.msi -- path: package\*.zip - -environment: - MYGET_API_KEY: - secure: wtAvJDVl2tfwiVcyLExFHLvZVfUWiQRHsfdHBFCNEATeCHo1Nd8JP642PfY8xhji - NUGET_API_KEY: - secure: PVHROoT0SmGkr9CHgrKapuA0/CcJGHSP63M3fZaNLvcEVbBnzYLeCwpc0PZHhdvD - CHOCO_API_KEY: - secure: aDsu1U+umVYFVybjkBVtVQsatSj3QKbD7VkGQci9mNF3493g9Giao/GABISIaHjT - GITHUB_ACCESS_TOKEN: - secure: RJ6sKRBZzwXz8JQvj8zcp45mkHNDad1UlvmfCsiVx63V9/pXHcm2Y2Lg/G/Vyhlz - IGNORE_NORMALISATION_GIT_HEAD_MOVE: 1 \ No newline at end of file From 1230675653dd85b197762de326bac161fb53c2d9 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 13 Aug 2024 15:22:52 -0700 Subject: [PATCH 087/190] Fix error in PublishDraftRelease.yml --- .github/workflows/PublishDraftRelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/PublishDraftRelease.yml b/.github/workflows/PublishDraftRelease.yml index c14bc02ea..e16d02d0c 100644 --- a/.github/workflows/PublishDraftRelease.yml +++ b/.github/workflows/PublishDraftRelease.yml @@ -30,4 +30,4 @@ jobs: env: GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_CP }} - run: dotnet cake --target=CreateDraftRelease --packageVersion={{inputs.version}} + run: dotnet cake --target=CreateDraftRelease --packageVersion="${{ inputs.version }}" From 9f7aae973ee7a5f3109ad112e40cf9bb21e360eb Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 13 Aug 2024 15:33:38 -0700 Subject: [PATCH 088/190] Fix error in PublishDraftRelease.yml\ --- .github/workflows/PublishDraftRelease.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/PublishDraftRelease.yml b/.github/workflows/PublishDraftRelease.yml index e16d02d0c..98136bc08 100644 --- a/.github/workflows/PublishDraftRelease.yml +++ b/.github/workflows/PublishDraftRelease.yml @@ -3,8 +3,8 @@ on: workflow_dispatch: inputs: - version: - description: 'Release version (must match a mileston)' + packageVersion: + description: "Package version for release (must match an existing milestone)" required: true type: string @@ -30,4 +30,4 @@ jobs: env: GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_CP }} - run: dotnet cake --target=CreateDraftRelease --packageVersion="${{ inputs.version }}" + run: dotnet cake --target=CreateDraftRelease --packageVersion="${{ inputs.packageVersion }}" From f900b1f74e6b61c3e32f9b0cfc1f90edd0c8e32a Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 17 Aug 2024 23:13:54 -0700 Subject: [PATCH 089/190] Refactoring: replace AddinFileReader with AddinFile; temporarily suspend ExtesionService tests --- .../Internal/AddinsFileReaderTests2.cs | 67 ---------------- ...sFileReaderTests.cs => AddinsFileTests.cs} | 65 +++++++--------- .../Services/ExtensionManagerTests.cs | 11 ++- .../nunit.engine.core/Internal/AddinsFile.cs | 76 +++++++++++++++++++ .../Internal/AddinsFileEntry.cs | 42 ++++++++++ .../Internal/AddinsFileReader.cs | 57 -------------- .../Internal/IAddinsFileReader.cs | 25 ------ .../Services/ExtensionManager.cs | 31 ++++---- .../Services/ExtensionService.cs | 10 +-- 9 files changed, 172 insertions(+), 212 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileReaderTests2.cs rename src/NUnitEngine/nunit.engine.core.tests/Internal/{AddinsFileReaderTests.cs => AddinsFileTests.cs} (51%) create mode 100644 src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs create mode 100644 src/NUnitEngine/nunit.engine.core/Internal/AddinsFileEntry.cs delete mode 100644 src/NUnitEngine/nunit.engine.core/Internal/AddinsFileReader.cs delete mode 100644 src/NUnitEngine/nunit.engine.core/Internal/IAddinsFileReader.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileReaderTests2.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileReaderTests2.cs deleted file mode 100644 index dd05a39d0..000000000 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileReaderTests2.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using NSubstitute; -using NUnit.Engine.Internal.FileSystemAccess; -using NUnit.Framework; -using System.IO; - -namespace NUnit.Engine.Internal.Tests -{ - /// - /// Tests the implementation of . - /// - /// All tests in this fixture modify the file-system. - [TestFixture, Category("WritesToDisk"), NonParallelizable] - public class AddinsFileReaderTests2 - { - private readonly string tempFileLocation; - - public AddinsFileReaderTests2() - { - string[] content = new string[] - { - "# This line is a comment and is ignored. The next (blank) line is ignored as well.", - "", - "*.dll # include all dlls in the same directory", - "addins/*.dll # include all dlls in the addins directory too", - "special/myassembly.dll # include a specific dll in a special directory", - "some/other/directory/ # process another directory, which may contain its own addins file", - "# note that an absolute path is allowed, but is probably not a good idea in most cases", - "/unix/absolute/directory" - }; - - this.tempFileLocation = Path.GetTempFileName(); - - using (var writer = new StreamWriter(this.tempFileLocation)) - { - foreach (var line in content) - { - writer.WriteLine(line); - } - } - } - - [TearDown] - public void DeleteTestFile() - { - File.Delete(this.tempFileLocation); - } - - [Test] - public void Read_IFile() - { - var reader = new AddinsFileReader(); - var file = Substitute.For(); - file.FullName.Returns(this.tempFileLocation); - - var result = reader.Read(file); - - Assert.That(result, Has.Count.EqualTo(5)); - Assert.That(result, Contains.Item("*.dll")); - Assert.That(result, Contains.Item("addins/*.dll")); - Assert.That(result, Contains.Item("special/myassembly.dll")); - Assert.That(result, Contains.Item("some/other/directory/")); - Assert.That(result, Contains.Item("/unix/absolute/directory")); - } - } -} diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileReaderTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs similarity index 51% rename from src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileReaderTests.cs rename to src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs index 0c46db71e..b41c3e9e9 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileReaderTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs @@ -5,28 +5,27 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; namespace NUnit.Engine.Internal.Tests { /// - /// Tests the implementation of . + /// Tests the implementation of . /// [TestFixture] - public class AddinsFileReaderTests + public class AddinsFileTests { [Test] public void Read_IFile_Null() { - var reader = new AddinsFileReader(); - - Assert.That(() => reader.Read((IFile)null), Throws.ArgumentNullException); + Assert.That(() => AddinsFile.Read((IFile)null), Throws.ArgumentNullException); } [Test] public void Read_Stream() { - var input = string.Join(Environment.NewLine, new string[] + var content = new[] { "# This line is a comment and is ignored. The next (blank) line is ignored as well.", "", @@ -36,59 +35,49 @@ public void Read_Stream() "some/other/directory/ # process another directory, which may contain its own addins file", "# note that an absolute path is allowed, but is probably not a good idea in most cases", "/unix/absolute/directory" - }); - - IEnumerable result; + }; - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(input))) + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, content)))) { - // Act - result = AddinsFileReader.Read(stream); - } + var result = AddinsFile.Read(stream); - Assert.That(result, Has.Count.EqualTo(5)); - Assert.That(result, Contains.Item("*.dll")); - Assert.That(result, Contains.Item("addins/*.dll")); - Assert.That(result, Contains.Item("special/myassembly.dll")); - Assert.That(result, Contains.Item("some/other/directory/")); - Assert.That(result, Contains.Item("/unix/absolute/directory")); + Assert.That(result, Has.Count.EqualTo(8)); + for (int i = 0; i < 8; i++) + Assert.That(result[i], Is.EqualTo( + new AddinsFileEntry(i + 1, content[i]))); + } } [Test] [Platform("win")] public void Read_Stream_TransformBackslash_Windows() { - var input = string.Join(Environment.NewLine, new string[] - { - "c:\\windows\\absolute\\directory" - }); + var content = "c:\\windows\\absolute\\directory"; - IEnumerable result; - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(input))) + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) { - // Act - result = AddinsFileReader.Read(stream); - } + var result = AddinsFile.Read(stream); - Assert.That(result, Has.Count.EqualTo(1)); - Assert.That(result, Contains.Item("c:/windows/absolute/directory")); + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.EqualTo(new AddinsFileEntry(1, content))); + Assert.That(result[0].Text, Is.EqualTo("c:/windows/absolute/directory")); + } } [Test] [Platform("linux,macosx,unix")] public void Read_Stream_TransformBackslash_NonWindows() { - IEnumerable result; + var content = "this/is/a\\ path\\ with\\ spaces/"; - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("this/is/a\\ path\\ with\\ spaces/"))) + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) { - // Act - result = AddinsFileReader.Read(stream); - } + var result = AddinsFile.Read(stream); - Assert.That(result, Has.Count.EqualTo(1)); - Assert.That(result, Contains.Item("this/is/a\\ path\\ with\\ spaces/")); + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.EqualTo(new AddinsFileEntry(1, content))); + Assert.That(result[0].Text, Is.EqualTo(content)); + } } } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index c74972521..a0e5d5eb5 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -40,19 +40,19 @@ public class ExtensionManagerTests #pragma warning restore 414 [SetUp] - public void CreateService() + public void CreateExtensionManager() { _extensionManager = new ExtensionManager(); - // Rather than actually starting the service, which would result - // in finding the extensions actually in use on the current system, - // we simulate the start using this assemblies dummy extensions. + // Find actual extension points. _extensionManager.FindExtensionPoints(typeof(CoreEngine).Assembly); _extensionManager.FindExtensionPoints(typeof(ITestEngine).Assembly); + // Find dummy extensions in this test assembly. _extensionManager.FindExtensionsInAssembly(new ExtensionAssembly(GetType().Assembly.Location, false)); } +#if SERVICE [Test] public void StartService_UseFileSystemAbstraction() { @@ -65,6 +65,7 @@ public void StartService_UseFileSystemAbstraction() fileSystem.Received().GetDirectory(workingDir); } +#endif [Test] public void AllExtensionPointsAreKnown() @@ -281,6 +282,7 @@ private static string GetSiblingDirectory(string dir) return Path.Combine(file.Directory.Parent.FullName, dir); } +#if SERVICE [Test] public void StartService_ReadsAddinsFile() { @@ -752,5 +754,6 @@ public void ProcessAddinsFile_Issue915_AddinsFilePointsToContainingDirectory_Non directoryFinder.Received().GetDirectories(startDirectory, "**/../"); directoryFinder.Received().GetDirectories(startDirectory, "**/./"); } +#endif } } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs new file mode 100644 index 000000000..361a830b3 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs @@ -0,0 +1,76 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using NUnit.Engine.Internal.FileSystemAccess; + +namespace NUnit.Engine.Internal +{ + internal class AddinsFile : List + { + public static AddinsFile Read(IFile file) + { + if (file == null) + throw new ArgumentNullException(nameof(file)); + + using (var stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + return Read(stream); + } + } + + /// + /// Reads the content of an addins-file from a stream. + /// + /// Input stream. Must be readable and positioned at the beginning of the file. + /// All entries contained in the file. + /// cannot be read + /// If the executing system uses backslashes ('\') to separate directories, these will be substituted with slashes ('/'). + internal static AddinsFile Read(Stream stream) + { + using (var reader = new StreamReader(stream)) + { + var addinsFile = new AddinsFile(); + + int lineNumber = 0; + while (!reader.EndOfStream) + addinsFile.Add(new AddinsFileEntry(++lineNumber, reader.ReadLine())); + + return addinsFile; + } + } + + private AddinsFile() { } + + public override string ToString() + { + var sb = new StringBuilder("AddinsFile:"); + foreach (var entry in this) + sb.Append($" {entry}"); + return sb.ToString(); + } + + public override bool Equals(object obj) + { + var other = obj as AddinsFile; + if (other == null) return false; + + if (Count != other.Count) return false; + + for (int i = 0; i < Count; i++) + if (this[i] != other[i]) return false; + + return true; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileEntry.cs b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileEntry.cs new file mode 100644 index 000000000..c7255b8fb --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileEntry.cs @@ -0,0 +1,42 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; + +namespace NUnit.Engine.Internal +{ + // Class representing a single line in an Addins file + internal class AddinsFileEntry + { + public int LineNumber { get; } + public string RawText { get; } + public string Text { get; } + + public AddinsFileEntry(int lineNumber, string rawText) + { + LineNumber = lineNumber; + RawText = rawText; + Text = rawText.Split(new char[] { '#' })[0].Trim() + .Replace(Path.DirectorySeparatorChar, '/'); + } + + public override string ToString() + { + return $"{LineNumber}: {RawText}"; + } + + public override bool Equals(object obj) + { + var other = obj as AddinsFileEntry; + if (other == null) return false; + + return LineNumber == other.LineNumber && RawText == other.RawText; + } + + public override int GetHashCode() + { + return LineNumber.GetHashCode() ^ RawText.GetHashCode(); + } + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileReader.cs b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileReader.cs deleted file mode 100644 index 3e0407898..000000000 --- a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileReader.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using NUnit.Engine.Internal.FileSystemAccess; -using System; -using System.Collections.Generic; -using System.IO; - -namespace NUnit.Engine.Internal -{ - /// - /// A reader for NUnit addins-files. - /// - /// - /// The format of an addins-file can be found at https://docs.nunit.org/articles/nunit-engine/extensions/Installing-Extensions.html. - /// - internal sealed class AddinsFileReader : IAddinsFileReader - { - /// - public IEnumerable Read(IFile file) - { - if (file == null) - { - throw new ArgumentNullException(nameof(file)); - } - - using (var reader = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - return Read(reader); - } - } - - /// - /// Reads the content of an addins-file from a stream. - /// - /// Input stream. Must be readable and positioned at the beginning of the file. - /// All entries contained in the file. - /// cannot be read - /// If the executing system uses backslashes ('\') to separate directories, these will be substituted with slashes ('/'). - internal static IEnumerable Read(Stream stream) - { - var result = new List(); - using (var reader = new StreamReader(stream)) - { - for(var line = reader.ReadLine(); line != null; line = reader.ReadLine()) - { - line = line.Split(new char[] { '#' })[0].Trim(); - if (line != string.Empty) - { - result.Add(line.Replace(Path.DirectorySeparatorChar, '/')); - } - } - } - - return result; - } - } -} diff --git a/src/NUnitEngine/nunit.engine.core/Internal/IAddinsFileReader.cs b/src/NUnitEngine/nunit.engine.core/Internal/IAddinsFileReader.cs deleted file mode 100644 index 511b38b1a..000000000 --- a/src/NUnitEngine/nunit.engine.core/Internal/IAddinsFileReader.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using NUnit.Engine.Internal.FileSystemAccess; -using System.Collections.Generic; - -namespace NUnit.Engine.Internal -{ - /// - /// A reader for NUnit addins-files. - /// - /// - /// The format of an addins-file can be found at https://docs.nunit.org/articles/nunit-engine/extensions/Installing-Extensions.html. - /// - internal interface IAddinsFileReader - { - /// - /// Reads all entries from an addins-file. - /// - /// Location of the file. - /// All entries contained in the file. - /// is - /// cannot be found or read - IEnumerable Read(IFile file); - } -} diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 40fb360e2..d4476448c 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -8,6 +8,8 @@ using NUnit.Engine.Internal; using NUnit.Engine.Internal.FileSystemAccess; using NUnit.Engine.Internal.FileSystemAccess.Default; +using System.Linq; + #if NET462 || NETSTANDARD2_0 using Path = NUnit.Engine.Internal.Backports.Path; @@ -23,7 +25,7 @@ public sealed class ExtensionManager : IDisposable static readonly Version ENGINE_VERSION = typeof(ExtensionService).Assembly.GetName().Version; private readonly IFileSystem _fileSystem; - private readonly IAddinsFileReader _addinsReader; + //private readonly IAddinsFileReader _addinsReader; private readonly IDirectoryFinder _directoryFinder; private readonly List _extensionPoints = new List(); @@ -33,18 +35,17 @@ public sealed class ExtensionManager : IDisposable private readonly List _assemblies = new List(); public ExtensionManager() - : this(new AddinsFileReader(), new FileSystem()) + : this(new FileSystem()) { } - internal ExtensionManager(IAddinsFileReader addinsReader, IFileSystem fileSystem) - : this(addinsReader, fileSystem, new DirectoryFinder(fileSystem)) + internal ExtensionManager(IFileSystem fileSystem) + : this(fileSystem, new DirectoryFinder(fileSystem)) { } - internal ExtensionManager(IAddinsFileReader addinsReader, IFileSystem fileSystem, IDirectoryFinder directoryFinder) + internal ExtensionManager(IFileSystem fileSystem, IDirectoryFinder directoryFinder) { - _addinsReader = addinsReader; _fileSystem = fileSystem; _directoryFinder = directoryFinder; } @@ -278,14 +279,12 @@ private int ProcessAddinsFiles(IDirectory startDir, bool fromWildCard) var addinsFiles = startDir.GetFiles("*.addins"); var addinsFileCount = 0; - if (addinsFiles.Any()) + foreach (var file in addinsFiles) { - foreach (var file in addinsFiles) - { - ProcessAddinsFile(startDir, file, fromWildCard); - addinsFileCount += 1; - } + ProcessAddinsFile(startDir, file, fromWildCard); + addinsFileCount++; } + return addinsFileCount; } @@ -299,17 +298,17 @@ private void ProcessAddinsFile(IDirectory baseDir, IFile addinsFile, bool fromWi { log.Info("Processing file " + addinsFile.FullName); - foreach (var entry in _addinsReader.Read(addinsFile)) + foreach (var entry in AddinsFile.Read(addinsFile).Where(e => e.Text != string.Empty)) { - bool isWild = fromWildCard || entry.Contains("*"); - var args = GetBaseDirAndPattern(baseDir, entry); + bool isWild = fromWildCard || entry.Text.Contains("*"); + var args = GetBaseDirAndPattern(baseDir, entry.Text); // TODO: See if we can handle '/tools/*/' efficiently by examining every // assembly in the directory. Otherwise try this approach: // 1. Check entry for ending with '/tools/*/' // 2. If so, examine the directory name to see if it matches a tfm. // 3. If it does, check to see if the implied runtime would be loadable. // 4. If so, process it, if not, skip it. - if (entry.EndsWith("/")) + if (entry.Text.EndsWith("/")) { foreach (var dir in _directoryFinder.GetDirectories(args.Item1, args.Item2)) { diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index af3af5665..1b78b320f 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -33,15 +33,15 @@ public ExtensionService() _extensionManager = new ExtensionManager(); } - internal ExtensionService(IAddinsFileReader addinsReader, IFileSystem fileSystem) - : this(addinsReader, fileSystem, new DirectoryFinder(fileSystem)) + internal ExtensionService(IFileSystem fileSystem) + : this(fileSystem, new DirectoryFinder(fileSystem)) { - _extensionManager = new ExtensionManager(addinsReader, fileSystem); + _extensionManager = new ExtensionManager(fileSystem); } - internal ExtensionService(IAddinsFileReader addinsReader, IFileSystem fileSystem, IDirectoryFinder directoryFinder) + internal ExtensionService(IFileSystem fileSystem, IDirectoryFinder directoryFinder) { - _extensionManager = new ExtensionManager(addinsReader, fileSystem, directoryFinder); + _extensionManager = new ExtensionManager(fileSystem, directoryFinder); } public IEnumerable ExtensionPoints => _extensionManager.ExtensionPoints; From c23d1e4803297c86817803d4c49b06f0d79d8811 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 18 Aug 2024 15:27:43 -0700 Subject: [PATCH 090/190] Refactoring: Split tests for ExtensionManager and ExtensionService --- .../Services/ExtensionManagerTests.cs | 530 +----------------- .../Services/ExtensionServiceTests.cs | 106 ++++ .../Drivers/NUnit2DriverFactory.cs | 9 +- .../Services/ExtensionManager.cs | 176 +++--- .../Services/ExtensionService.cs | 9 +- .../Services/IExtensionManager.cs | 43 ++ 6 files changed, 278 insertions(+), 595 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs create mode 100644 src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index a0e5d5eb5..14afab18b 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -37,6 +37,16 @@ public class ExtensionManagerTests }; private static readonly int[] KnownExtensionPointCounts = { 1, 1, 1, 2, 1, 1 }; + + private readonly string[] KnownExtensions = { + "NUnit.Engine.Tests.DummyFrameworkDriverExtension", + "NUnit.Engine.Tests.DummyProjectLoaderExtension", + "NUnit.Engine.Tests.DummyResultWriterExtension", + "NUnit.Engine.Tests.DummyEventListenerExtension", + "NUnit.Engine.Tests.DummyServiceExtension", + "NUnit.Engine.Tests.DummyDisabledExtension", + "NUnit.Engine.Tests.V2DriverExtension" + }; #pragma warning restore 414 [SetUp] @@ -52,25 +62,28 @@ public void CreateExtensionManager() _extensionManager.FindExtensionsInAssembly(new ExtensionAssembly(GetType().Assembly.Location, false)); } -#if SERVICE [Test] - public void StartService_UseFileSystemAbstraction() + public void AllKnownExtensionPointsAreFound() { - var addinsReader = Substitute.For(); - var fileSystem = Substitute.For(); - var service = new ExtensionService(addinsReader, fileSystem); - var workingDir = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - - service.StartService(); + Assert.That(_extensionManager.ExtensionPoints.Select(ep => ep.Path), + Is.EquivalentTo(KnownExtensionPointPaths)); + } - fileSystem.Received().GetDirectory(workingDir); + [Test] + public void AllKnownExtensionsAreFound() + { + Assert.That(_extensionManager.Extensions.Select(ext => ext.TypeName), + Is.EquivalentTo(KnownExtensions)); } -#endif [Test] - public void AllExtensionPointsAreKnown() + public void AllKnownExtensionsAreEnabledAsRequired() { - Assert.That(_extensionManager.ExtensionPoints.Select(ep => ep.Path), Is.EquivalentTo(KnownExtensionPointPaths)); + foreach (var node in _extensionManager.Extensions) + { + var shouldBeEnabled = node.TypeName != "NUnit.Engine.Tests.DummyDisabledExtension"; + Assert.That(node.Enabled, Is.EqualTo(shouldBeEnabled)); + } } [Test, Sequential] @@ -95,25 +108,6 @@ public void CanGetExtensionPointByType( Assert.That(ep.TypeName, Is.EqualTo(type.FullName)); } -#pragma warning disable 414 - private static readonly string[] KnownExtensions = { - "NUnit.Engine.Tests.DummyFrameworkDriverExtension", - "NUnit.Engine.Tests.DummyProjectLoaderExtension", - "NUnit.Engine.Tests.DummyResultWriterExtension", - "NUnit.Engine.Tests.DummyEventListenerExtension", - "NUnit.Engine.Tests.DummyServiceExtension", - "NUnit.Engine.Tests.V2DriverExtension" - }; -#pragma warning restore 414 - - [TestCaseSource(nameof(KnownExtensions))] - public void CanListExtensions(string typeName) - { - Assert.That(_extensionManager.Extensions, - Has.One.Property(nameof(ExtensionNode.TypeName)).EqualTo(typeName) - .And.Property(nameof(ExtensionNode.Enabled)).True); - } - [Test, Sequential] public void ExtensionsAreAddedToExtensionPoint( [ValueSource(nameof(KnownExtensionPointPaths))] string path, @@ -281,479 +275,5 @@ private static string GetSiblingDirectory(string dir) var file = new FileInfo(typeof(ExtensionManagerTests).Assembly.Location); return Path.Combine(file.Directory.Parent.FullName, dir); } - -#if SERVICE - [Test] - public void StartService_ReadsAddinsFile() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - var addinsReader = Substitute.For(); - var sut = new ExtensionService(addinsReader, fileSystem); - - // Act - sut.StartService(); - - // Assert - startDirectory.Received().GetFiles("*.addins"); - addinsReader.Received().Read(addinsFile); - } - - [Test] - public void StartService_ReadsAddinsFilesFromMultipleDirectories() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var subdirectoryPath = Path.Combine(startDirectoryPath, "subdirectory"); - var subdirectory = Substitute.For(); - subdirectory.FullName.Returns(subdirectoryPath); - subdirectory.GetDirectories(Arg.Any(), Arg.Any()).Returns(new IDirectory[] { }); - subdirectory.GetFiles(Arg.Any()).Returns(new IFile[] { }); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - var addinsFile2 = Substitute.For(); - addinsFile2.Parent.Returns(subdirectory); - addinsFile2.FullName.Returns(Path.Combine(subdirectoryPath, "second.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - startDirectory.GetDirectories("subdirectory", SearchOption.TopDirectoryOnly).Returns(new[] { subdirectory }); - subdirectory.GetFiles(Arg.Any()).Returns(ci => (string)ci[0] == "*.addins" ? new[] { addinsFile2 } : new IFile[] { }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - fileSystem.GetDirectory(subdirectoryPath).Returns(subdirectory); - var addinsReader = Substitute.For(); - addinsReader.Read(addinsFile).Returns(new[] { "subdirectory/" }); - var sut = new ExtensionService(addinsReader, fileSystem); - - // Act - sut.StartService(); - - // Assert - startDirectory.Received().GetFiles("*.addins"); - addinsReader.Received().Read(addinsFile); - subdirectory.Received().GetFiles("*.addins"); - addinsReader.Received().Read(addinsFile2); - } - - [Test] - public void ProcessAddinsFile_RelativePaths() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - var addinsReader = Substitute.For(); - addinsReader.Read(addinsFile).Returns( - new[] - { - "path/to/directory/", - "directory2/", - "**/wildcard-directory/", - "path/to/file/file1.dll", - "file2.dll", - "**/wildcard-file.dll" - }); - var directoryFinder = Substitute.For(); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - addinsReader.Received().Read(addinsFile); - directoryFinder.Received().GetDirectories(startDirectory, "path/to/directory/"); - directoryFinder.Received().GetDirectories(startDirectory, "directory2/"); - directoryFinder.Received().GetDirectories(startDirectory, "**/wildcard-directory/"); - directoryFinder.Received().GetFiles(startDirectory, "path/to/file/file1.dll"); - directoryFinder.Received().GetFiles(startDirectory, "file2.dll"); - directoryFinder.Received().GetFiles(startDirectory, "**/wildcard-file.dll"); - } - - [Test] - [Platform("win")] - public void ProcessAddinsFile_AbsolutePath_Windows() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var metamorphosatorDirectoryPath = "c:/tools/metamorphosator"; - var metamorphosatorDirectory = Substitute.For(); - metamorphosatorDirectory.FullName.Returns(metamorphosatorDirectoryPath); - var toolsDirectoryPath = "d:/tools"; - var toolsDirectory = Substitute.For(); - toolsDirectory.FullName.Returns(toolsDirectoryPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - fileSystem.GetDirectory(metamorphosatorDirectoryPath + "/").Returns(metamorphosatorDirectory); - fileSystem.GetDirectory("d:\\tools").Returns(toolsDirectory); - var addinsReader = Substitute.For(); - addinsReader.Read(addinsFile).Returns(new[] { "c:/tools/metamorphosator/", "d:/tools/frobuscator.dll" }); - var directoryFinder = Substitute.For(); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - addinsReader.Received().Read(addinsFile); - directoryFinder.Received().GetDirectories(metamorphosatorDirectory, string.Empty); - directoryFinder.Received().GetFiles(toolsDirectory, "frobuscator.dll"); - directoryFinder.DidNotReceive().GetDirectories(toolsDirectory, Arg.Is(s => s != "frobuscator.dll")); - } - - [Test] - [Platform("linux,macosx,unix")] - public void ProcessAddinsFile_AbsolutePath_NonWindows() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var metamorphosatorDirectoryPath = "/tmp/tools/metamorphosator"; - var metamorphosatorDirectory = Substitute.For(); - metamorphosatorDirectory.FullName.Returns(metamorphosatorDirectoryPath); - var usrDirectoryPath = "/usr"; - var toolsDirectory = Substitute.For(); - toolsDirectory.FullName.Returns(usrDirectoryPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - fileSystem.GetDirectory(metamorphosatorDirectoryPath + "/").Returns(metamorphosatorDirectory); - fileSystem.GetDirectory(usrDirectoryPath).Returns(toolsDirectory); - var addinsReader = Substitute.For(); - addinsReader.Read(addinsFile).Returns(new[] { "/tmp/tools/metamorphosator/", "/usr/frobuscator.dll" }); - var directoryFinder = Substitute.For(); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - addinsReader.Received().Read(addinsFile); - directoryFinder.Received().GetDirectories(metamorphosatorDirectory, string.Empty); - directoryFinder.Received().GetFiles(toolsDirectory, "frobuscator.dll"); - directoryFinder.DidNotReceive().GetDirectories(toolsDirectory, Arg.Is(s => s != "frobuscator.dll")); - } - - [Test] - [Platform("win")] - public void ProcessAddinsFile_InvalidAbsolutePathToFile_Windows() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - var addinsReader = Substitute.For(); - addinsReader.Read(addinsFile).Returns(new[] { "/absolute/unix/path" }); - var directoryFinder = Substitute.For(); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - addinsReader.Received().Read(addinsFile); - directoryFinder.Received().GetFiles(startDirectory, "/absolute/unix/path"); - } - - [Test] - [Platform("win")] - public void ProcessAddinsFile_InvalidAbsolutePathToDirectory_Windows() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - var addinsReader = Substitute.For(); - addinsReader.Read(addinsFile).Returns(new[] { "/absolute/unix/path/" }); - var directoryFinder = Substitute.For(); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - addinsReader.Received().Read(addinsFile); - directoryFinder.Received().GetDirectories(startDirectory, "/absolute/unix/path/"); - } - - [TestCase("c:/absolute/windows/path")] - [TestCase("c:\\absolute\\windows\\path")] - [TestCase("c:\\absolute\\windows\\path\\")] - [Platform("linux,macosx,unix")] - public void ProcessAddinsFile_InvalidAbsolutePathToFile_NonWindows(string windowsPath) - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - var addinsReader = Substitute.For(); - addinsReader.Read(addinsFile).Returns(new[] { windowsPath }); - var directoryFinder = Substitute.For(); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - addinsReader.Received().Read(addinsFile); - directoryFinder.Received().GetFiles(startDirectory, windowsPath); - } - - [TestCase("c:/absolute/windows/path/")] - [Platform("linux,macosx,unix")] - public void ProcessAddinsFile_InvalidAbsolutePathToDirectory_NonWindows(string windowsPath) - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - var addinsReader = Substitute.For(); - addinsReader.Read(addinsFile).Returns(new[] { windowsPath }); - var directoryFinder = Substitute.For(); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - addinsReader.Received().Read(addinsFile); - directoryFinder.Received().GetDirectories(startDirectory, windowsPath); - } - - [Test] - public void StartService_ReadsMultipleAddinsFilesFromSingleDirectory() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var addinsFile1 = Substitute.For(); - addinsFile1.Parent.Returns(startDirectory); - addinsFile1.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile1 }); - var addinsFile2 = Substitute.For(); - addinsFile1.Parent.Returns(startDirectory); - addinsFile1.FullName.Returns(Path.Combine(startDirectoryPath, "test2.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile1, addinsFile2 }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - var addinsReader = Substitute.For(); - var sut = new ExtensionService(addinsReader, fileSystem); - - // Act - sut.StartService(); - - // Assert - startDirectory.Received().GetFiles("*.addins"); - addinsReader.Received().Read(addinsFile1); - addinsReader.Received().Read(addinsFile2); - } - - [Test] - public void ProcessAddinsFile_ReadsAddinsFileFromReferencedDirectory() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - var referencedDirectoryPath = Path.Combine(startDirectoryPath, "metamorphosator"); - var referencedDirectory = Substitute.For(); - referencedDirectory.FullName.Returns(referencedDirectoryPath); - referencedDirectory.Parent.Returns(startDirectory); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "test.addins")); - var referencedAddinsFile = Substitute.For(); - referencedAddinsFile.Parent.Returns(referencedDirectory); - referencedAddinsFile.FullName.Returns(Path.Combine(referencedDirectoryPath, "test2.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - referencedDirectory.GetFiles("*.addins").Returns(new[] { referencedAddinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(referencedDirectoryPath).Returns(referencedDirectory); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - var addinsReader = Substitute.For(); - addinsReader.Read(addinsFile).Returns(new[] { "./metamorphosator/" }); - var directoryFinder = Substitute.For(); - directoryFinder.GetDirectories(startDirectory, "./metamorphosator/").Returns(new[] { referencedDirectory }); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - startDirectory.Received().GetFiles("*.addins"); - addinsReader.Received().Read(addinsFile); - referencedDirectory.Received().GetFiles("*.addins"); - addinsReader.Received().Read(referencedAddinsFile); - } - - [Test] - [Platform("win")] - public void ProcessAddinsFile_Issue915_AddinsFilePointsToContainingDirectory_Windows() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - - // Faking the presence of the assembly is required to reproduce the error described in GitHub issue 915... - var testAssembly = Substitute.For(); - testAssembly.Parent.Returns(startDirectory); - testAssembly.FullName.Returns(typeof(ExtensionService).Assembly.Location); - startDirectory.GetFiles("*.dll").Returns(new[] { testAssembly }); - - var parentPath = new DirectoryInfo(startDirectoryPath).Parent.FullName; - var parentDirectory = Substitute.For(); - parentDirectory.FullName.Returns(parentPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "my.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - var addinsReader = Substitute.For(); - var addinsContent = new[] { - "./", - startDirectoryPath + Path.DirectorySeparatorChar, - $"..{Path.DirectorySeparatorChar}{Path.GetFileName(startDirectoryPath)}{Path.DirectorySeparatorChar}", - @"*\..\", - @"**\..\", - @"**\.\" - }; - addinsReader.Read(addinsFile).Returns(addinsContent); - var directoryFinder = Substitute.For(); - directoryFinder.GetDirectories(startDirectory, "./").Returns(new[] { startDirectory }); - directoryFinder.GetFiles(startDirectory, string.Empty).Returns(new[] { testAssembly }); - directoryFinder.GetFiles(startDirectory, $"..{Path.DirectorySeparatorChar}{Path.GetFileName(startDirectoryPath)}{Path.DirectorySeparatorChar}").Returns(new[] { testAssembly }); - directoryFinder.GetFiles(startDirectory, @"*\..\").Returns(new[] { testAssembly }); - directoryFinder.GetFiles(startDirectory, @"**\..\").Returns(new[] { testAssembly }); - directoryFinder.GetFiles(startDirectory, @"**\.\").Returns(new[] { testAssembly }); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - startDirectory.Received().GetFiles("*.addins"); - startDirectory.DidNotReceive().GetFiles("*.dll"); - parentDirectory.DidNotReceive().GetFiles("*.dll"); - directoryFinder.Received().GetDirectories(startDirectory, "./"); - directoryFinder.Received().GetFiles(startDirectory, string.Empty); - directoryFinder.Received().GetFiles(startDirectory, $"..{Path.DirectorySeparatorChar}{Path.GetFileName(startDirectoryPath)}{Path.DirectorySeparatorChar}"); - directoryFinder.Received().GetFiles(startDirectory, @"*\..\"); - directoryFinder.Received().GetFiles(startDirectory, @"**\..\"); - directoryFinder.Received().GetFiles(startDirectory, @"**\.\"); - addinsReader.Received().Read(addinsFile); - } - - [Test] - [Platform("linux,macosx,unix")] - public void ProcessAddinsFile_Issue915_AddinsFilePointsToContainingDirectory_NonWindows() - { - // Arrange - var startDirectoryPath = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); - var startDirectory = Substitute.For(); - startDirectory.FullName.Returns(startDirectoryPath); - - // Faking the presence of the assembly is required to reproduce the error described in GitHub issue 915... - var testAssembly = Substitute.For(); - testAssembly.Parent.Returns(startDirectory); - testAssembly.FullName.Returns(typeof(ExtensionService).Assembly.Location); - startDirectory.GetFiles("*.dll").Returns(new[] { testAssembly }); - - var parentPath = new DirectoryInfo(startDirectoryPath).Parent.FullName; - var parentDirectory = Substitute.For(); - parentDirectory.FullName.Returns(parentPath); - var addinsFile = Substitute.For(); - addinsFile.Parent.Returns(startDirectory); - addinsFile.FullName.Returns(Path.Combine(startDirectoryPath, "my.addins")); - startDirectory.GetFiles("*.addins").Returns(new[] { addinsFile }); - var fileSystem = Substitute.For(); - fileSystem.GetDirectory(startDirectoryPath).Returns(startDirectory); - fileSystem.GetDirectory(startDirectoryPath + "/").Returns(startDirectory); - var addinsReader = Substitute.For(); - var addinsContent = new[] { - "./", - startDirectoryPath + "/", - $"../{Path.GetFileName(startDirectoryPath)}/", - "*/../", - "**/../", - "**/./" - }; - addinsReader.Read(addinsFile).Returns(addinsContent); - var directoryFinder = Substitute.For(); - directoryFinder.GetDirectories(startDirectory, "./").Returns(new[] { startDirectory }); - directoryFinder.GetFiles(startDirectory, string.Empty).Returns(new[] { testAssembly }); - directoryFinder.GetFiles(startDirectory, $"../{Path.GetFileName(startDirectoryPath)}/").Returns(new[] { testAssembly }); - directoryFinder.GetFiles(startDirectory, "*/../").Returns(new[] { testAssembly }); - directoryFinder.GetFiles(startDirectory, "**/../").Returns(new[] { testAssembly }); - directoryFinder.GetFiles(startDirectory, "**/./").Returns(new[] { testAssembly }); - var sut = new ExtensionService(addinsReader, fileSystem, directoryFinder); - - // Act - sut.StartService(); - - // Assert - startDirectory.Received().GetFiles("*.addins"); - addinsReader.Received().Read(addinsFile); - startDirectory.DidNotReceive().GetFiles("*.dll"); - parentDirectory.DidNotReceive().GetFiles("*.dll"); - directoryFinder.Received().GetDirectories(startDirectory, "./"); - directoryFinder.Received().GetDirectories(startDirectory, string.Empty); - directoryFinder.Received().GetDirectories(startDirectory, $"../{Path.GetFileName(startDirectoryPath)}/"); - directoryFinder.Received().GetDirectories(startDirectory, "*/../"); - directoryFinder.Received().GetDirectories(startDirectory, "**/../"); - directoryFinder.Received().GetDirectories(startDirectory, "**/./"); - } -#endif } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs new file mode 100644 index 000000000..3f6a5664f --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using NSubstitute; +using NUnit.Engine.Extensibility; +using NUnit.Engine.Internal; +using NUnit.Engine.Internal.FileSystemAccess; +using NUnit.Framework; + +namespace NUnit.Engine.Services.Tests +{ + public class ExtensionServiceTests + { + private ExtensionManager _extensionManager; + private ExtensionService _extensionService; + +#pragma warning disable 414 + private static readonly string[] KnownExtensionPointPaths = { + "/NUnit/Engine/TypeExtensions/IDriverFactory", + "/NUnit/Engine/TypeExtensions/IProjectLoader", + "/NUnit/Engine/TypeExtensions/IResultWriter", + "/NUnit/Engine/TypeExtensions/ITestEventListener", + "/NUnit/Engine/TypeExtensions/IService", + "/NUnit/Engine/NUnitV2Driver" + }; + + private static readonly Type[] KnownExtensionPointTypes = { + typeof(IDriverFactory), + typeof(IProjectLoader), + typeof(IResultWriter), + typeof(ITestEventListener), + typeof(IService), + typeof(IFrameworkDriver) + }; + + private static readonly int[] KnownExtensionPointCounts = { 1, 1, 1, 2, 1, 1 }; +#pragma warning restore 414 + + [SetUp] + public void CreateService() + { + _extensionManager = Substitute.For(); + _extensionService = new ExtensionService(_extensionManager); + } + + [Test] + public void StartServiceInitializesExtensionManager() + { + var workingDir = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); + + _extensionService.StartService(); + + _extensionManager.ReceivedWithAnyArgs().FindExtensionPoints(typeof(ExtensionService).Assembly, typeof(ITestEngine).Assembly); + _extensionManager.Received().FindExtensions(workingDir); + Assert.That(_extensionService.Status, Is.EqualTo(ServiceStatus.Started)); + } + + [Test] + public void GetExtensionPointCallsExtensionManager() + { + ((IExtensionService)_extensionService).GetExtensionPoint("SOMEPATH"); + _extensionManager.Received().GetExtensionPoint("SOMEPATH"); + } + + [Test] + public void GetExtensionNodesCallsExtensionManager() + { + ((IExtensionService)_extensionService).GetExtensionNodes("SOMEPATH"); + _extensionManager.Received().GetExtensionNodes("SOMEPATH"); + } + + [Test] + public void EnableExtensionCallsExtensionManager() + { + _extensionService.EnableExtension("TYPENAME", true); + _extensionManager.Received().EnableExtension("TYPENAME", true); + } + + [Test] + public void GetExtensionsTCallsExtensionManager() + { + _extensionService.GetExtensions(); + _extensionManager.Received().GetExtensions(); + } + + [Test] + public void GetExtensionNodeCallsExtensionManager() + { + _extensionService.GetExtensionNode("SOMEPATH"); + _extensionManager.Received().GetExtensionNode("SOMEPATH"); + } + + [Test] + public void GetExtensionNodesTCallsExtensionManager() + { + _extensionService.GetExtensionNodes(); + _extensionManager.Received().GetExtensionNodes(); + } + + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs index 6bc490cc8..5ab8ffa2f 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs @@ -3,6 +3,7 @@ #if NETFRAMEWORK using System; using System.Collections.Generic; +using System.Configuration.Assemblies; using System.Reflection; using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; @@ -13,13 +14,13 @@ public class NUnit2DriverFactory : IDriverFactory { private const string NUNIT_FRAMEWORK = "nunit.framework"; private const string NUNITLITE_FRAMEWORK = "nunitlite"; - private ExtensionNode _driverNode; + private IExtensionNode _driverNode; // TODO: This should be a central service but for now it's local private ProvidedPathsAssemblyResolver _resolver; bool _resolverInstalled; - public NUnit2DriverFactory(ExtensionNode driverNode) + public NUnit2DriverFactory(IExtensionNode driverNode) { _driverNode = driverNode; _resolver = new ProvidedPathsAssemblyResolver(); @@ -55,7 +56,9 @@ public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) _resolver.AddPathFromFile(_driverNode.AssemblyPath); } - return _driverNode.CreateExtensionObject(domain) as IFrameworkDriver; + return AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap( + _driverNode.AssemblyPath, _driverNode.TypeName, + false, 0, null, null, null, null) as IFrameworkDriver; } } } diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index d4476448c..d72a3d99c 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -19,7 +19,7 @@ namespace NUnit.Engine.Services { - public sealed class ExtensionManager : IDisposable + public class ExtensionManager : IExtensionManager { static readonly Logger log = InternalTrace.GetLogger(typeof(ExtensionService)); static readonly Version ENGINE_VERSION = typeof(ExtensionService).Assembly.GetName().Version; @@ -50,16 +50,7 @@ internal ExtensionManager(IFileSystem fileSystem, IDirectoryFinder directoryFind _directoryFinder = directoryFinder; } - internal void FindExtensions(string startDir) - { - // Create the list of possible extension assemblies, - // eliminating duplicates, start in the provided directory. - FindExtensionAssemblies(_fileSystem.GetDirectory(startDir)); - - // Check each assembly to see if it contains extensions - foreach (var candidate in _assemblies) - FindExtensionsInAssembly(candidate); - } + #region IExtensionManager Implementation /// /// Gets an enumeration of all ExtensionPoints in the engine. @@ -77,82 +68,10 @@ public IEnumerable Extensions get { return _extensions.ToArray(); } } - /// - /// Enable or disable an extension - /// - public void EnableExtension(string typeName, bool enabled) - { - foreach (var node in _extensions) - if (node.TypeName == typeName) - node.Enabled = enabled; - } - - /// - /// Get an ExtensionPoint based on its unique identifying path. - /// - public ExtensionPoint GetExtensionPoint(string path) - { - return _pathIndex.ContainsKey(path) ? _pathIndex[path] : null; - } - - /// - /// Get an ExtensionPoint based on the required Type for extensions. - /// - public ExtensionPoint GetExtensionPoint(Type type) - { - foreach (var ep in _extensionPoints) - if (ep.TypeName == type.FullName) - return ep; - - return null; - } - - /// - /// Get an ExtensionPoint based on a Cecil TypeReference. - /// - public ExtensionPoint GetExtensionPoint(TypeReference type) - { - foreach (var ep in _extensionPoints) - if (ep.TypeName == type.FullName) - return ep; - - return null; - } - - public IEnumerable GetExtensionNodes(string path) - { - var ep = GetExtensionPoint(path); - if (ep != null) - foreach (var node in ep.Extensions) - yield return node; - } - - public ExtensionNode GetExtensionNode(string path) - { - var ep = GetExtensionPoint(path); - - return ep != null && ep.Extensions.Count > 0 ? ep.Extensions[0] : null; - } - - public IEnumerable GetExtensionNodes(bool includeDisabled = false) - { - var ep = GetExtensionPoint(typeof(T)); - if (ep != null) - foreach (var node in ep.Extensions) - if (includeDisabled || node.Enabled) - yield return node; - } - - public IEnumerable GetExtensions() - { - foreach (var node in GetExtensionNodes()) - yield return (T)node.ExtensionObject; - } - /// /// Find the extension points in a loaded assembly. /// - public void FindExtensionPoints(params Assembly[] targetAssemblies) + public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) { foreach (var assembly in targetAssemblies) { @@ -207,6 +126,92 @@ public void FindExtensionPoints(params Assembly[] targetAssemblies) } } + public void FindExtensions(string startDir) + { + // Create the list of possible extension assemblies, + // eliminating duplicates, start in the provided directory. + FindExtensionAssemblies(_fileSystem.GetDirectory(startDir)); + + // Check each assembly to see if it contains extensions + foreach (var candidate in _assemblies) + FindExtensionsInAssembly(candidate); + } + + /// + /// Get an ExtensionPoint based on its unique identifying path. + /// + public IExtensionPoint GetExtensionPoint(string path) + { + return _pathIndex.ContainsKey(path) ? _pathIndex[path] : null; + } + + public IEnumerable GetExtensionNodes(string path) + { + var ep = GetExtensionPoint(path); + if (ep != null) + foreach (var node in ep.Extensions) + yield return node; + } + + public IExtensionNode GetExtensionNode(string path) + { + // TODO: Remove need for the cast + var ep = GetExtensionPoint(path) as ExtensionPoint; + + return ep != null && ep.Extensions.Count > 0 ? ep.Extensions[0] : null; + } + + public IEnumerable GetExtensionNodes(bool includeDisabled = false) + { + var ep = GetExtensionPoint(typeof(T)); + if (ep != null) + foreach (var node in ep.Extensions) + if (includeDisabled || node.Enabled) + yield return node; + } + + public IEnumerable GetExtensions() + { + foreach (var node in GetExtensionNodes()) + yield return (T)((ExtensionNode)node).ExtensionObject; // HACK + } + + /// + /// Enable or disable an extension + /// + public void EnableExtension(string typeName, bool enabled) + { + foreach (var node in _extensions) + if (node.TypeName == typeName) + node.Enabled = enabled; + } + + #endregion + + /// + /// Get an ExtensionPoint based on the required Type for extensions. + /// + public ExtensionPoint GetExtensionPoint(Type type) + { + foreach (var ep in _extensionPoints) + if (ep.TypeName == type.FullName) + return ep; + + return null; + } + + /// + /// Get an ExtensionPoint based on a Cecil TypeReference. + /// + public ExtensionPoint GetExtensionPoint(TypeReference type) + { + foreach (var ep in _extensionPoints) + if (ep.TypeName == type.FullName) + return ep; + + return null; + } + /// /// Deduce the extension point based on the Type of an extension. /// Returns null if no extension point can be found that would @@ -482,7 +487,8 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) } else { - ep = GetExtensionPoint(node.Path); + // TODO: Remove need for the cast + ep = GetExtensionPoint(node.Path) as ExtensionPoint; if (ep == null) { string msg = string.Format( diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index 1b78b320f..38628e43b 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -26,13 +26,18 @@ namespace NUnit.Engine.Services /// public class ExtensionService : Service, IExtensionService { - private readonly ExtensionManager _extensionManager; + private readonly IExtensionManager _extensionManager; public ExtensionService() { _extensionManager = new ExtensionManager(); } + public ExtensionService(ExtensionManager extensionManager) + { + _extensionManager = extensionManager; + } + internal ExtensionService(IFileSystem fileSystem) : this(fileSystem, new DirectoryFinder(fileSystem)) { @@ -72,7 +77,7 @@ public void EnableExtension(string typeName, bool enabled) public IEnumerable GetExtensions() => _extensionManager.GetExtensions(); - public ExtensionNode GetExtensionNode(string path) => _extensionManager.GetExtensionNode(path); + public IExtensionNode GetExtensionNode(string path) => _extensionManager.GetExtensionNode(path); public IEnumerable GetExtensionNodes() => _extensionManager.GetExtensionNodes(); diff --git a/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs new file mode 100644 index 000000000..c02704d5a --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs @@ -0,0 +1,43 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.Reflection; + +using NUnit.Engine.Extensibility; + +namespace NUnit.Engine.Services +{ + public interface IExtensionManager : IDisposable + { + IEnumerable ExtensionPoints { get; } + IEnumerable Extensions { get; } + + void FindExtensionPoints(params Assembly[] targetAssemblies); + void FindExtensions(string startDir); + + IExtensionPoint GetExtensionPoint(string path); + + IEnumerable GetExtensions(); + + IEnumerable GetExtensionNodes(string path); + IExtensionNode GetExtensionNode(string path); + /// + /// Returns all extension nodes for a given Type. + /// + /// The Type of the node + /// If true, disabled nodes are included + /// An enumeration of ExtensionNodes + /// + /// Unlike other methods, this method returns an actual ExtensionNode rather + /// than an IExtensionNode. It is required in order for classes that support + /// extensions to create the actual extension object. + /// + /// + // NOTE: + IEnumerable GetExtensionNodes(bool includeDisabled = false); + + void EnableExtension(string typeName, bool enabled); + } + +} From 9454108f680653c3f042106c9b04fc3daaf7229d Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 21 Aug 2024 05:14:45 -0700 Subject: [PATCH 091/190] Validate addins file while reading it --- .../Internal/AddinsFileTests.cs | 10 +++ .../Internal/Backports/PathTests.cs | 60 ------------------ .../Internal/PathUtilTests.cs | 29 ++++++++- .../nunit.engine.core/Internal/AddinsFile.cs | 15 ++++- .../Internal/AddinsFileEntry.cs | 8 +++ .../Internal/Backports/Path.cs | 34 ---------- .../nunit.engine.core/Internal/PathUtils.cs | 49 ++++++++++++--- .../Services/ExtensionManager.cs | 63 +++++++------------ .../Services/ExtensionService.cs | 8 --- 9 files changed, 120 insertions(+), 156 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core.tests/Internal/Backports/PathTests.cs delete mode 100644 src/NUnitEngine/nunit.engine.core/Internal/Backports/Path.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs index b41c3e9e9..25d4363b1 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs @@ -48,6 +48,16 @@ public void Read_Stream() } } + [Test] + public void Read_InvalidEntry() + { + var content = "// This is not valid"; + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + { + Assert.That(() => AddinsFile.Read(stream), Throws.Exception); + } + } + [Test] [Platform("win")] public void Read_Stream_TransformBackslash_Windows() diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/Backports/PathTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/Backports/PathTests.cs deleted file mode 100644 index 19034cf35..000000000 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/Backports/PathTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using NUnit.Framework; - -namespace NUnit.Engine.Internal.Backports.Tests -{ - [TestFixture] - public sealed class PathTests - { - [Platform("win")] - [TestCase("c:\\", ExpectedResult = true)] - [TestCase("c:\\foo\\bar\\", ExpectedResult = true)] - [TestCase("c:/foo/bar/", ExpectedResult = true)] - [TestCase("c:\\foo\\bar", ExpectedResult = true)] - [TestCase("c:/foo/bar", ExpectedResult = true)] - [TestCase("c:bar\\", ExpectedResult = false)] - [TestCase("c:bar/", ExpectedResult = false)] - [TestCase("c:bar", ExpectedResult = false)] - [TestCase("ä:\\bar", ExpectedResult = false)] - [TestCase("ä://bar", ExpectedResult = false)] - [TestCase("\\\\server01\\foo", ExpectedResult = true)] - [TestCase("\\server01\\foo", ExpectedResult = false)] - [TestCase("c:", ExpectedResult = false)] - [TestCase("/foo/bar", ExpectedResult = false)] - [TestCase("/", ExpectedResult = false)] - [TestCase("\\a\\b", ExpectedResult = false)] - public bool IsPathFullyQualified_Windows(string path) - { - return Path.IsPathFullyQualified(path); - } - - [Platform("linux,macosx,unix")] - [TestCase("/foo/bar", ExpectedResult = true)] - [TestCase("/", ExpectedResult = true)] - [TestCase("/z", ExpectedResult = true)] - [TestCase("c:\\foo\\bar\\", ExpectedResult = false)] - [TestCase("c:/foo/bar/", ExpectedResult = false)] - [TestCase("c:\\foo\\bar", ExpectedResult = false)] - [TestCase("c:/foo/bar", ExpectedResult = false)] - [TestCase("c:bar\\", ExpectedResult = false)] - [TestCase("c:bar/", ExpectedResult = false)] - [TestCase("c:bar", ExpectedResult = false)] - [TestCase("ä:\\bar", ExpectedResult = false)] - [TestCase("ä://bar", ExpectedResult = false)] - [TestCase("\\\\server01\\foo", ExpectedResult = false)] - [TestCase("\\server01\\foo", ExpectedResult = false)] - [TestCase("c:", ExpectedResult = false)] - [TestCase("\\a\\b", ExpectedResult = false)] - public bool IsPathFullyQualified_NonWindows(string path) - { - return Path.IsPathFullyQualified(path); - } - - [Test] - public void IsPathFullyQualified_PathIsNull() - { - Assert.That(() => Path.IsPathFullyQualified(null), Throws.ArgumentNullException); - } - } -} diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/PathUtilTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/PathUtilTests.cs index 44cc4799c..c8d6fd1d4 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/PathUtilTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/PathUtilTests.cs @@ -63,9 +63,36 @@ public void IsFullyQualifiedWindowsPath_PathIsNull() { Assert.That(() => PathUtils.IsFullyQualifiedWindowsPath(null), Throws.ArgumentNullException); } + + [TestCase("X")] + [TestCase("X/")] + [TestCase("/X/")] + [TestCase("\\X\\")] + [TestCase("X/Y/Z")] + [TestCase("X/Y/Z/")] + [TestCase("/X/Y/Z")] + [TestCase("/X/Y/Z/")] + [TestCase("\\X\\Y\\Z\\")] + [TestCase("C:X/Y/Z/")] + [TestCase("C:/X/Y/Z")] + [TestCase("C:/X/Y/Z/")] + [TestCase("C:\\X\\Y\\Z\\")] + public void IsValidPath(string path) + { + Assert.That(PathUtils.IsValidPath(path), Is.True); + } + + [TestCase(":")] + [TestCase("?")] + [TestCase("*")] + [TestCase("// Spurious comment")] + public void IsValidPath_Fails(string path) + { + Assert.That(PathUtils.IsValidPath(path), Is.False); + } } - [TestFixture] + [TestFixture] public class PathUtilDefaultsTests : PathUtils { [Test] diff --git a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs index 361a830b3..6b090c318 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs @@ -20,7 +20,7 @@ public static AddinsFile Read(IFile file) using (var stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { - return Read(stream); + return Read(stream, file.FullName); } } @@ -31,7 +31,7 @@ public static AddinsFile Read(IFile file) /// All entries contained in the file. /// cannot be read /// If the executing system uses backslashes ('\') to separate directories, these will be substituted with slashes ('/'). - internal static AddinsFile Read(Stream stream) + internal static AddinsFile Read(Stream stream, string fullName = null) { using (var reader = new StreamReader(stream)) { @@ -39,7 +39,16 @@ internal static AddinsFile Read(Stream stream) int lineNumber = 0; while (!reader.EndOfStream) - addinsFile.Add(new AddinsFileEntry(++lineNumber, reader.ReadLine())); + { + var entry = new AddinsFileEntry(++lineNumber, reader.ReadLine()); + if (entry.Text != "" && !entry.IsValid) + { + string msg = $"Invalid Entry in {fullName ?? "addins file"}:\r\n {entry}"; + throw new InvalidOperationException(msg); + } + + addinsFile.Add(entry); + } return addinsFile; } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileEntry.cs b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileEntry.cs index c7255b8fb..3bf6470d5 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileEntry.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileEntry.cs @@ -13,6 +13,14 @@ internal class AddinsFileEntry public string RawText { get; } public string Text { get; } + public bool IsFullyQualified => PathUtils.IsFullyQualifiedPath(Text); + public bool IsDirectory => Text.EndsWith("/"); + public bool IsPattern => Text.Contains("*"); + public bool IsValid => PathUtils.IsValidPath(Text.Replace('*', 'X')); + + public string DirectoryName => Path.GetDirectoryName(Text); + public string FileName => Path.GetFileName(Text); + public AddinsFileEntry(int lineNumber, string rawText) { LineNumber = lineNumber; diff --git a/src/NUnitEngine/nunit.engine.core/Internal/Backports/Path.cs b/src/NUnitEngine/nunit.engine.core/Internal/Backports/Path.cs deleted file mode 100644 index 2d800d68a..000000000 --- a/src/NUnitEngine/nunit.engine.core/Internal/Backports/Path.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; - -namespace NUnit.Engine.Internal.Backports -{ - /// - /// Backports of functionality that is only available in newer .NET versions. - /// - public static class Path - { - /// - /// Returns a value that indicates whether the specified file path is absolute or not. - /// - /// Path to check - /// if is an absolute or UNC path; otherwhise, false. - /// - /// See https://docs.microsoft.com/en-us/dotnet/api/system.io.path.ispathfullyqualified for original implementation. - public static bool IsPathFullyQualified(string path) - { - if (path == null) - { - throw new ArgumentNullException(nameof(path)); - } - - return RunningOnWindows() ? PathUtils.IsFullyQualifiedWindowsPath(path) : PathUtils.IsFullyQualifiedUnixPath(path); - } - - private static bool RunningOnWindows() - { - return System.IO.Path.DirectorySeparatorChar == '\\'; - } - } -} diff --git a/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs b/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs index b7d0c2220..bec00090b 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs @@ -144,10 +144,10 @@ public static bool SamePathOrUnder( string path1, string path2 ) // if lengths are the same, check for equality if ( length1 == length2 ) - return string.Compare( path1, path2, IsWindows() ) == 0; + return string.Compare( path1, path2, RunningOnWindows ) == 0; // path 2 is longer than path 1: see if initial parts match - if ( string.Compare( path1, path2.Substring( 0, length1 ), IsWindows() ) != 0 ) + if ( string.Compare( path1, path2.Substring( 0, length1 ), RunningOnWindows ) != 0 ) return false; // must match through or up to a directory separator boundary @@ -167,7 +167,20 @@ public static string Combine(string path1, params string[] morePaths) } /// - /// Returns a value that indicates whether the specified file path is absolute or not on Windows operating systems. + /// Returns a value that indicates whether the specified file path is fully qualified. + /// + /// Path to check + /// if is an absolute path; otherwhise, false. + /// + public static bool IsFullyQualifiedPath(string path ) + { + return RunningOnWindows + ? IsFullyQualifiedWindowsPath(path) + : IsFullyQualifiedUnixPath(path); + } + + /// + /// Returns a value that indicates whether the specified file path is fully qualified or not on Windows operating systems. /// /// Path to check /// if is an absolute or UNC path; otherwhise, false. @@ -206,6 +219,27 @@ public static bool IsFullyQualifiedUnixPath(string path) return path.Length > 0 && path[0] == '/'; } + public static bool IsValidPath(string path) + { + try + { + var info = GetFileSystemInfo(path); +#if NETCOREAPP2_1_OR_GREATER + var creation = info.CreationTime; +#endif + return true; // Whether it exists or not! + } + catch + { + return false; + } + } + + private static FileSystemInfo GetFileSystemInfo(string path) => + path.EndsWith("/") || path.EndsWith(@"\\") + ? new DirectoryInfo(path) as FileSystemInfo + : new FileInfo(path) as FileSystemInfo; + private static bool IsWindowsDirectorySeparator(char c) { return c == '\\' || c == '/'; @@ -216,14 +250,11 @@ private static bool IsValidDriveSpecifier(char c) return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); } - private static bool IsWindows() - { - return PathUtils.DirectorySeparatorChar == '\\'; - } + private static bool RunningOnWindows => DirectorySeparatorChar == '\\'; private static string[] SplitPath(string path) { - char[] separators = new char[] { PathUtils.DirectorySeparatorChar, PathUtils.AltDirectorySeparatorChar }; + char[] separators = new char[] { DirectorySeparatorChar, AltDirectorySeparatorChar }; string[] trialSplit = path.Split(separators); @@ -246,7 +277,7 @@ private static string[] SplitPath(string path) private static bool PathsEqual(string path1, string path2) { - if (PathUtils.IsWindows()) + if (RunningOnWindows) return path1.ToLower().Equals(path2.ToLower()); else return path1.Equals(path2); diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index d72a3d99c..476ceaee5 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Reflection; using TestCentric.Metadata; using NUnit.Engine.Extensibility; @@ -10,13 +11,6 @@ using NUnit.Engine.Internal.FileSystemAccess.Default; using System.Linq; - -#if NET462 || NETSTANDARD2_0 -using Path = NUnit.Engine.Internal.Backports.Path; -#else -using Path = System.IO.Path; -#endif - namespace NUnit.Engine.Services { public class ExtensionManager : IExtensionManager @@ -286,7 +280,7 @@ private int ProcessAddinsFiles(IDirectory startDir, bool fromWildCard) foreach (var file in addinsFiles) { - ProcessAddinsFile(startDir, file, fromWildCard); + ProcessAddinsFile(file, fromWildCard); addinsFileCount++; } @@ -299,56 +293,43 @@ private int ProcessAddinsFiles(IDirectory startDir, bool fromWildCard) /// path or a wildcard pattern used to find assemblies. Blank /// lines and comments started by # are ignored. /// - private void ProcessAddinsFile(IDirectory baseDir, IFile addinsFile, bool fromWildCard) + private void ProcessAddinsFile(IFile addinsFile, bool fromWildCard) { log.Info("Processing file " + addinsFile.FullName); foreach (var entry in AddinsFile.Read(addinsFile).Where(e => e.Text != string.Empty)) { - bool isWild = fromWildCard || entry.Text.Contains("*"); - var args = GetBaseDirAndPattern(baseDir, entry.Text); - // TODO: See if we can handle '/tools/*/' efficiently by examining every - // assembly in the directory. Otherwise try this approach: - // 1. Check entry for ending with '/tools/*/' - // 2. If so, examine the directory name to see if it matches a tfm. - // 3. If it does, check to see if the implied runtime would be loadable. - // 4. If so, process it, if not, skip it. - if (entry.Text.EndsWith("/")) + bool isWild = fromWildCard || entry.IsPattern; + IDirectory baseDir = addinsFile.Parent; + string entryDir = entry.DirectoryName; + string entryFile = entry.FileName; + + if (entry.IsDirectory) { - foreach (var dir in _directoryFinder.GetDirectories(args.Item1, args.Item2)) + if (entry.IsFullyQualified) { - ProcessDirectory(dir, isWild); + baseDir = _fileSystem.GetDirectory(entry.Text); + foreach (var dir in _directoryFinder.GetDirectories(_fileSystem.GetDirectory(entryDir), "")) + ProcessDirectory(dir, isWild); } + else + foreach (var dir in _directoryFinder.GetDirectories(baseDir, entry.Text)) + ProcessDirectory(dir, isWild); } else { - foreach (var file in _directoryFinder.GetFiles(args.Item1, args.Item2)) + if (entry.IsFullyQualified) { - ProcessCandidateAssembly(file.FullName, isWild); + foreach (var file in _directoryFinder.GetFiles(_fileSystem.GetDirectory(entryDir), entryFile)) + ProcessCandidateAssembly(file.FullName, isWild); } + else + foreach (var file in _directoryFinder.GetFiles(baseDir, entry.Text)) + ProcessCandidateAssembly(file.FullName, isWild); } } } - private Tuple GetBaseDirAndPattern(IDirectory baseDir, string path) - { - if (Path.IsPathFullyQualified(path)) - { - if (path.EndsWith("/")) - { - return new Tuple(_fileSystem.GetDirectory(path), string.Empty); - } - else - { - return new Tuple(_fileSystem.GetDirectory(System.IO.Path.GetDirectoryName(path)), System.IO.Path.GetFileName(path)); - } - } - else - { - return new Tuple(baseDir, path); - } - } - private void ProcessCandidateAssembly(string filePath, bool fromWildCard) { if (WasVisited(filePath, fromWildCard)) diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index 38628e43b..74ef1abcf 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -6,17 +6,9 @@ using TestCentric.Metadata; using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; -using NUnit.Engine.Internal.Backports; using NUnit.Engine.Internal.FileSystemAccess; using NUnit.Engine.Internal.FileSystemAccess.Default; -using Backports = NUnit.Engine.Internal.Backports; -#if NETFRAMEWORK || NETSTANDARD2_0 -using Path = NUnit.Engine.Internal.Backports.Path; -#else -using Path = System.IO.Path; -#endif - namespace NUnit.Engine.Services { /// From e743826920e5baf5869b77c34ebc05da2ae0849e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 6 Sep 2024 23:43:30 -0700 Subject: [PATCH 092/190] Eliminate unnecessary errors when processing non-test assemblies --- build.cake | 46 +++++++++- .../mock-assembly/mock-assembly.csproj | 1 + .../mock-assembly/mock-assembly.sln | 31 +++++++ .../notest-assembly/notest-assembly.csproj | 3 +- .../Services/ExtensionManagerTests.cs | 10 +-- .../Services/DriverService.cs | 6 +- .../Services/ExtensionManager.cs | 87 +++++++++++++++---- .../nunit.engine/Runners/MasterTestRunner.cs | 4 - 8 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 src/NUnitEngine/mock-assembly/mock-assembly.sln diff --git a/build.cake b/build.cake index 0c2c623a4..10ba91ed8 100644 --- a/build.cake +++ b/build.cake @@ -22,6 +22,7 @@ var StandardRunnerTests = new List Net462Test, Net462X86Test, Net462PlusNet462Test, + Net462ExeTest, NetCore31Test, Net60Test, Net70Test, @@ -29,7 +30,9 @@ var StandardRunnerTests = new List Net60PlusNet80Test, Net462PlusNet60Test, NUnitProjectTest, - V2ResultWriterTest + V2ResultWriterTest, + VSProjectLoaderTest_Project, + VSProjectLoaderTest_Solution }; // Tests run for the NETCORE runner package @@ -97,6 +100,26 @@ static ExpectedResult MockAssemblyX86ExpectedResult(params string[] runtimes) return result; } +static ExpectedResult MockAssemblySolutionResult = new ExpectedResult("Failed") +{ + Total = 37 * 5, + Passed = 23 * 5, + Failed = 5 * 5, + Warnings = 1 * 5, + Inconclusive = 1 * 5, + Skipped = 7 * 5, + Assemblies = new ExpectedAssemblyResult[] + { + new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-3.1"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), + new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), + new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), + new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0") + } +}; static PackageTest Net462Test = new PackageTest( 1, "Net462Test", @@ -110,6 +133,12 @@ static PackageTest Net462X86Test = new PackageTest( "net462/mock-assembly-x86.dll", MockAssemblyX86ExpectedResult("net-4.6.2")); +static PackageTest Net462ExeTest = new PackageTest( + 1, "Net462ExeTest", + "Run nunit.engine.core.tests.exe under .NET 4.6.2", + "net462/nunit.engine.core.tests.exe", + new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("nunit.engine.core.tests.exe")}}); + static PackageTest Net462PlusNet462Test = new PackageTest( 1, "Net462PlusNet462Test", "Run two copies of mock-assembly together", @@ -180,6 +209,7 @@ static PackageTest Net462PlusNet60Test = new PackageTest( static ExtensionSpecifier NUnitProjectLoader = KnownExtensions.NUnitProjectLoader.SetVersion("3.8.0"); static ExtensionSpecifier NUnitV2ResultWriter = KnownExtensions.NUnitV2ResultWriter.SetVersion("3.8.0"); +static ExtensionSpecifier VSProjectLoader = KnownExtensions.VSProjectLoader.SetVersion("3.9.0"); static PackageTest NUnitProjectTest = new PackageTest( 1, "NUnitProjectTest", @@ -195,6 +225,20 @@ static PackageTest V2ResultWriterTest = new PackageTest( MockAssemblyExpectedResult("netcore-6.0"), NUnitV2ResultWriter); +static PackageTest VSProjectLoaderTest_Project = new PackageTest( + 1, "VSProjectLoaderTest_Project", + "Run mock-assembly using the .csproj file", + "../../src/NUnitEngine/mock-assembly/mock-assembly.csproj --config=Release", + MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0"), + VSProjectLoader); + +static PackageTest VSProjectLoaderTest_Solution = new PackageTest( + 1, "VSProjectLoaderTest_Solution", + "Run mock-assembly using the .sln file", + "../../src/NUnitEngine/mock-assembly/mock-assembly.sln --config=Release", + MockAssemblySolutionResult, + VSProjectLoader); + ////////////////////////////////////////////////////////////////////// // LISTS OF FILES USED IN CHECKING PACKAGES ////////////////////////////////////////////////////////////////////// diff --git a/src/NUnitEngine/mock-assembly/mock-assembly.csproj b/src/NUnitEngine/mock-assembly/mock-assembly.csproj index a9e9578df..4b793f39b 100644 --- a/src/NUnitEngine/mock-assembly/mock-assembly.csproj +++ b/src/NUnitEngine/mock-assembly/mock-assembly.csproj @@ -3,6 +3,7 @@ NUnit.Tests net462;netcoreapp3.1;net6.0;net7.0;net8.0 + ..\..\..\bin\$(Configuration)\ true ..\..\nunit.snk false diff --git a/src/NUnitEngine/mock-assembly/mock-assembly.sln b/src/NUnitEngine/mock-assembly/mock-assembly.sln new file mode 100644 index 000000000..9b4908fa7 --- /dev/null +++ b/src/NUnitEngine/mock-assembly/mock-assembly.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34525.116 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "mock-assembly.csproj", "{FB3F6F62-37AB-4193-87A9-197C2E0CBC1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "..\notest-assembly\notest-assembly.csproj", "{F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FB3F6F62-37AB-4193-87A9-197C2E0CBC1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB3F6F62-37AB-4193-87A9-197C2E0CBC1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB3F6F62-37AB-4193-87A9-197C2E0CBC1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB3F6F62-37AB-4193-87A9-197C2E0CBC1B}.Release|Any CPU.Build.0 = Release|Any CPU + {F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1BD3B7CF-501E-4806-870A-328B37C21752} + EndGlobalSection +EndGlobal diff --git a/src/NUnitEngine/notest-assembly/notest-assembly.csproj b/src/NUnitEngine/notest-assembly/notest-assembly.csproj index 2e5e477ef..a96bbd8be 100644 --- a/src/NUnitEngine/notest-assembly/notest-assembly.csproj +++ b/src/NUnitEngine/notest-assembly/notest-assembly.csproj @@ -2,7 +2,8 @@ notest_assembly - net462;netcoreapp3.1 + net462;netstandard2.0;netcoreapp3.1 + ..\..\..\bin\$(Configuration)\ false false diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index 14afab18b..fc1523f85 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -164,22 +164,22 @@ public void SkipsGracefullyLoadingOtherFrameworkExtensionAssembly() [TestCaseSource(nameof(ValidCombos))] public void ValidTargetFrameworkCombinations(FrameworkCombo combo) { - Assert.That(() => ExtensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly), + Assert.That(() => _extensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly), Is.True); } [TestCaseSource(nameof(InvalidTargetFrameworkCombos))] public void InvalidTargetFrameworkCombinations(FrameworkCombo combo) { - Assert.That(() => ExtensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly), + Assert.That(() => _extensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly), Is.False); } [TestCaseSource(nameof(InvalidRunnerCombos))] public void InvalidRunnerTargetFrameworkCombinations(FrameworkCombo combo) { - Assert.That(() => ExtensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly), - Throws.Exception.TypeOf().And.Message.Contains("not .NET Standard")); + var ex = Assert.Catch(() => _extensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly)); + Assert.That(ex.Message.Contains("not .NET Standard")); } // ExtensionAssembly is internal, so cannot be part of the public test parameters @@ -217,7 +217,7 @@ public static IEnumerable ValidCombos() var extNetStandard = new ExtensionAssembly(Path.Combine(GetSiblingDirectory("netstandard2.0"), "nunit.engine.dll"), false); yield return new TestCaseData(new FrameworkCombo(netFramework, extNetFramework)).SetName("ValidCombo(.NET Framework, .NET Framework)"); - yield return new TestCaseData(new FrameworkCombo(netFramework, extNetStandard)).SetName("ValidCombo(.NET Framework, .NET Standard)"); + //yield return new TestCaseData(new FrameworkCombo(netFramework, extNetStandard)).SetName("ValidCombo(.NET Framework, .NET Standard)"); #endif } diff --git a/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs b/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs index d7dd37589..ad9a7b5fc 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs @@ -48,7 +48,11 @@ public IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, string : targetFramework.Split(new char[] { ',' })[0]; if (platform == "Silverlight" || platform == ".NETPortable" || platform == ".NETStandard" || platform == ".NETCompactFramework") - return new InvalidAssemblyFrameworkDriver(assemblyPath, platform + " test assemblies are not supported by this version of the engine"); + if (skipNonTestAssemblies) + return new SkippedAssemblyFrameworkDriver(assemblyPath); + else + return new InvalidAssemblyFrameworkDriver(assemblyPath, platform + + " test assemblies are not supported by this version of the engine"); } try diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 476ceaee5..8e9d6d449 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -484,38 +484,89 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) } } + private const string NETFRAMEWORK = ".NETFramework"; + private const string NETCOREAPP = ".NETCoreApp"; + private const string NETSTANDARD = ".NETStandard"; + /// /// Checks that the target framework of the current runner can load the extension assembly. For example, .NET Core /// cannot load .NET Framework assemblies and vice-versa. /// /// The executing runner /// The extension we are attempting to load - internal static bool CanLoadTargetFramework(Assembly runnerAsm, ExtensionAssembly extensionAsm) + internal bool CanLoadTargetFramework(Assembly runnerAsm, ExtensionAssembly extensionAsm) { if (runnerAsm == null) return true; - string extensionFrameworkName = AssemblyDefinition.ReadAssembly(extensionAsm.FilePath).GetFrameworkName(); - string runnerFrameworkName = AssemblyDefinition.ReadAssembly(runnerAsm.Location).GetFrameworkName(); - if (runnerFrameworkName?.StartsWith(".NETStandard") == true) - { - throw new NUnitEngineException($"{runnerAsm.FullName} test runner must target .NET Core or .NET Framework, not .NET Standard"); - } - else if (runnerFrameworkName?.StartsWith(".NETCoreApp") == true) + var runnerFrameworkName = GetTargetRuntime(runnerAsm.Location); + var extensionFrameworkName = GetTargetRuntime(extensionAsm.FilePath); + + switch (runnerFrameworkName.Identifier) { - if (extensionFrameworkName?.StartsWith(".NETStandard") != true && extensionFrameworkName?.StartsWith(".NETCoreApp") != true) - { - log.Info($".NET Core runners require .NET Core or .NET Standard extension for {extensionAsm.FilePath}"); - return false; - } + case NETSTANDARD: + throw new Exception($"{runnerAsm.FullName} test runner must target .NET Core or .NET Framework, not .NET Standard"); + + case NETCOREAPP: + switch (extensionFrameworkName.Identifier) + { + case NETSTANDARD: + case NETCOREAPP: + return true; + case NETFRAMEWORK: + default: + log.Info($".NET Core runners require .NET Core or .NET Standard extension for {extensionAsm.FilePath}"); + return false; + } + case NETFRAMEWORK: + default: + switch (extensionFrameworkName.Identifier) + { + case NETFRAMEWORK: + return runnerFrameworkName.Version.Major == 4 || extensionFrameworkName.Version.Major < 4; + // For .NET Framework calling .NET Standard, we only support if framework is 4.7.2 or higher + case NETSTANDARD: + return extensionFrameworkName.Version >= new Version(4, 7, 2); + case NETCOREAPP: + default: + log.Info($".NET Framework runners cannot load .NET Core extension {extensionAsm.FilePath}"); + return false; + } } - else if (extensionFrameworkName?.StartsWith(".NETCoreApp") == true) + + //string extensionFrameworkName = AssemblyDefinition.ReadAssembly(extensionAsm.FilePath).GetFrameworkName(); + //string runnerFrameworkName = AssemblyDefinition.ReadAssembly(runnerAsm.Location).GetFrameworkName(); + //if (runnerFrameworkName?.StartsWith(".NETStandard") == true) + //{ + // throw new NUnitEngineException($"{runnerAsm.FullName} test runner must target .NET Core or .NET Framework, not .NET Standard"); + //} + //else if (runnerFrameworkName?.StartsWith(".NETCoreApp") == true) + //{ + // if (extensionFrameworkName?.StartsWith(".NETStandard") != true && extensionFrameworkName?.StartsWith(".NETCoreApp") != true) + // { + // log.Info($".NET Core runners require .NET Core or .NET Standard extension for {extensionAsm.FilePath}"); + // return false; + // } + //} + //else if (extensionFrameworkName?.StartsWith(".NETCoreApp") == true) + //{ + // log.Info($".NET Framework runners cannot load .NET Core extension {extensionAsm.FilePath}"); + // return false; + //} + + //return true; + } + + private System.Runtime.Versioning.FrameworkName GetTargetRuntime(string filePath) + { + var assemblyDef = AssemblyDefinition.ReadAssembly(filePath); + var frameworkName = assemblyDef.GetFrameworkName(); + if (string.IsNullOrEmpty(frameworkName)) { - log.Info($".NET Framework runners cannot load .NET Core extension {extensionAsm.FilePath}"); - return false; + var runtimeVersion = assemblyDef.GetRuntimeVersion(); + frameworkName = $".NETFramework,Version=v{runtimeVersion.ToString(3)}"; } - - return true; + return new System.Runtime.Versioning.FrameworkName(frameworkName); } public void Dispose() diff --git a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs index 06e3c989d..f23c70572 100644 --- a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs @@ -331,14 +331,10 @@ private void EnsurePackagesAreExpanded(TestPackage package) if (package == null) throw new ArgumentNullException("package"); foreach (var subPackage in package.SubPackages) - { EnsurePackagesAreExpanded(subPackage); - } if (package.SubPackages.Count == 0 && IsProjectPackage(package)) - { _projectService.ExpandProjectPackage(package); - } } private bool IsProjectPackage(TestPackage package) From e3d96c142df51fcb0d29d2c416f391b8dd93a28c Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 8 Sep 2024 17:02:02 -0700 Subject: [PATCH 093/190] Add more package tests and refactor --- NUnitConsole.sln | 71 +++-- build.cake | 229 +--------------- package-tests.cake | 250 ++++++++++++++++++ .../nunit3-console.tests.csproj | 2 +- .../nunit.engine.core.tests.csproj | 2 +- .../Internal/TestAssemblyResolver.cs | 2 +- .../nunit.engine.tests.csproj | 2 +- .../TestData.sln} | 4 +- .../aspnetcore-test/AspNetCoreTest.cs | 25 ++ .../aspnetcore-test/aspnetcore-test.csproj | 13 + .../mock-assembly-x86.csproj | 0 ...cessesCurrentTestContextDuringDiscovery.cs | 0 .../mock-assembly/MockAssembly.cs | 0 .../mock-assembly/mock-assembly.csproj | 0 .../mock-cpp-clr/AssemblyInfo.cpp | 0 .../mock-cpp-clr/TestClass.cpp | 0 .../mock-cpp-clr/TestClass.h | 0 .../mock-cpp-clr/mock-cpp-clr-x64.vcxproj | 0 .../mock-cpp-clr/mock-cpp-clr-x86.vcxproj | 0 .../Properties/AssemblyInfo.cs | 0 .../notest-assembly/notest-assembly.csproj | 0 .../windows-forms-test/WindowsFormsTest.cs | 25 ++ .../windows-forms-test.csproj | 13 + src/TestData/wpf-test/WpfTest.cs | 26 ++ src/TestData/wpf-test/WpfTest.csproj | 12 + 25 files changed, 416 insertions(+), 260 deletions(-) create mode 100644 package-tests.cake rename src/{NUnitEngine/mock-assembly/mock-assembly.sln => TestData/TestData.sln} (89%) create mode 100644 src/TestData/aspnetcore-test/AspNetCoreTest.cs create mode 100644 src/TestData/aspnetcore-test/aspnetcore-test.csproj rename src/{NUnitEngine => TestData}/mock-assembly-x86/mock-assembly-x86.csproj (100%) rename src/{NUnitEngine => TestData}/mock-assembly/AccessesCurrentTestContextDuringDiscovery.cs (100%) rename src/{NUnitEngine => TestData}/mock-assembly/MockAssembly.cs (100%) rename src/{NUnitEngine => TestData}/mock-assembly/mock-assembly.csproj (100%) rename src/{NUnitEngine => TestData}/mock-cpp-clr/AssemblyInfo.cpp (100%) rename src/{NUnitEngine => TestData}/mock-cpp-clr/TestClass.cpp (100%) rename src/{NUnitEngine => TestData}/mock-cpp-clr/TestClass.h (100%) rename src/{NUnitEngine => TestData}/mock-cpp-clr/mock-cpp-clr-x64.vcxproj (100%) rename src/{NUnitEngine => TestData}/mock-cpp-clr/mock-cpp-clr-x86.vcxproj (100%) rename src/{NUnitEngine => TestData}/notest-assembly/Properties/AssemblyInfo.cs (100%) rename src/{NUnitEngine => TestData}/notest-assembly/notest-assembly.csproj (100%) create mode 100644 src/TestData/windows-forms-test/WindowsFormsTest.cs create mode 100644 src/TestData/windows-forms-test/windows-forms-test.csproj create mode 100644 src/TestData/wpf-test/WpfTest.cs create mode 100644 src/TestData/wpf-test/WpfTest.csproj diff --git a/NUnitConsole.sln b/NUnitConsole.sln index fb214606c..6726812b1 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -21,20 +21,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution GitReleaseManager.yaml = GitReleaseManager.yaml GitVersion.yml = GitVersion.yml global.json = global.json - header-check.cake = header-check.cake LICENSE.txt = LICENSE.txt NetFXTests.nunit = NetFXTests.nunit NOTICES.txt = NOTICES.txt NuGet.config = NuGet.config nunit.ico = nunit.ico - package-checks.cake = package-checks.cake package-tests.cake = package-tests.cake - packages.cake = packages.cake PLATFORM_SUPPORT.md = PLATFORM_SUPPORT.md README.md = README.md - test-results.cake = test-results.cake VERSIONING.md = VERSIONING.md - cake\zip-package.cake = cake\zip-package.cake EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{A972031D-2F61-4183-AF75-99EE1A9F6B32}" @@ -47,7 +42,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit.engine", "src\NUnitEn EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit.engine.tests", "src\NUnitEngine\nunit.engine.tests\nunit.engine.tests.csproj", "{D694CB69-6CFB-4762-86C2-EB27B808B282}" ProjectSection(ProjectDependencies) = postProject - {B1D90742-39BD-429C-8E87-C5CD2991DF27} = {B1D90742-39BD-429C-8E87-C5CD2991DF27} {C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8} = {C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8} {0DE218CA-AFB8-423A-9CD2-E22DEAC55C46} = {0DE218CA-AFB8-423A-9CD2-E22DEAC55C46} EndProjectSection @@ -78,10 +72,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runners", "runners", "{F3E8 nuget\runners\nunit.console.nuget.agent.addins = nuget\runners\nunit.console.nuget.agent.addins EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "src\NUnitEngine\mock-assembly\mock-assembly.csproj", "{D2C80E4B-1117-4F02-AB02-E453BDA0C58E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "src\NUnitEngine\notest-assembly\notest-assembly.csproj", "{B1D90742-39BD-429C-8E87-C5CD2991DF27}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "choco", "choco", "{4FDF7BFA-A337-41D3-898D-C6A98278E6AD}" ProjectSection(SolutionItems) = preProject choco\nunit-agent-x86.exe.ignore = choco\nunit-agent-x86.exe.ignore @@ -102,8 +92,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit.engine.core", "src\NU EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit-agent-x86", "src\NUnitEngine\nunit-agent-x86\nunit-agent-x86.csproj", "{333D2FBC-CCA7-46AF-9453-C310671A67B0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly-x86", "src\NUnitEngine\mock-assembly-x86\mock-assembly-x86.csproj", "{9D3015EE-5B84-41B3-A1D3-1A439370C392}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deprecated", "deprecated", "{068F6CA9-6108-4F45-8540-351AA5227259}" ProjectSection(SolutionItems) = preProject choco\deprecated\nunit-console-with-extensions.nuspec = choco\deprecated\nunit-console-with-extensions.nuspec @@ -135,6 +123,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{C5B712 .config\dotnet-tools.json = .config\dotnet-tools.json EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aspnetcore-test", "src\TestData\aspnetcore-test\aspnetcore-test.csproj", "{DB42594C-9A68-488A-A289-47C0F9E29808}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "windows-forms-test", "src\TestData\windows-forms-test\windows-forms-test.csproj", "{0DF71C73-52A4-4423-8CEC-8A24D4F83FF3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfTest", "src\TestData\wpf-test\WpfTest.csproj", "{A96876EE-1A1F-4096-9B6A-5739E66B3364}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestData", "TestData", "{2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "src\TestData\mock-assembly\mock-assembly.csproj", "{C3FF8716-052B-4D6C-81FF-E80F89AF9A80}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly-x86", "src\TestData\mock-assembly-x86\mock-assembly-x86.csproj", "{8FE8378E-5A8B-4708-8F86-35BE0DE121F7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "src\TestData\notest-assembly\notest-assembly.csproj", "{81E63A90-3191-4E99-92FF-01F9B1D3E3C5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -165,14 +167,6 @@ Global {B310A760-8AE1-41CA-81F8-03B12E2FCE30}.Debug|Any CPU.Build.0 = Debug|Any CPU {B310A760-8AE1-41CA-81F8-03B12E2FCE30}.Release|Any CPU.ActiveCfg = Release|Any CPU {B310A760-8AE1-41CA-81F8-03B12E2FCE30}.Release|Any CPU.Build.0 = Release|Any CPU - {D2C80E4B-1117-4F02-AB02-E453BDA0C58E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D2C80E4B-1117-4F02-AB02-E453BDA0C58E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D2C80E4B-1117-4F02-AB02-E453BDA0C58E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D2C80E4B-1117-4F02-AB02-E453BDA0C58E}.Release|Any CPU.Build.0 = Release|Any CPU - {B1D90742-39BD-429C-8E87-C5CD2991DF27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1D90742-39BD-429C-8E87-C5CD2991DF27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1D90742-39BD-429C-8E87-C5CD2991DF27}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1D90742-39BD-429C-8E87-C5CD2991DF27}.Release|Any CPU.Build.0 = Release|Any CPU {A19C026B-1C0F-4AA3-AC49-7D8B4C7231CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A19C026B-1C0F-4AA3-AC49-7D8B4C7231CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {A19C026B-1C0F-4AA3-AC49-7D8B4C7231CF}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -181,14 +175,34 @@ Global {333D2FBC-CCA7-46AF-9453-C310671A67B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {333D2FBC-CCA7-46AF-9453-C310671A67B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {333D2FBC-CCA7-46AF-9453-C310671A67B0}.Release|Any CPU.Build.0 = Release|Any CPU - {9D3015EE-5B84-41B3-A1D3-1A439370C392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9D3015EE-5B84-41B3-A1D3-1A439370C392}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9D3015EE-5B84-41B3-A1D3-1A439370C392}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9D3015EE-5B84-41B3-A1D3-1A439370C392}.Release|Any CPU.Build.0 = Release|Any CPU {CACC0520-B452-4310-A33C-DC944129ACDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CACC0520-B452-4310-A33C-DC944129ACDD}.Debug|Any CPU.Build.0 = Debug|Any CPU {CACC0520-B452-4310-A33C-DC944129ACDD}.Release|Any CPU.ActiveCfg = Release|Any CPU {CACC0520-B452-4310-A33C-DC944129ACDD}.Release|Any CPU.Build.0 = Release|Any CPU + {DB42594C-9A68-488A-A289-47C0F9E29808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB42594C-9A68-488A-A289-47C0F9E29808}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB42594C-9A68-488A-A289-47C0F9E29808}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB42594C-9A68-488A-A289-47C0F9E29808}.Release|Any CPU.Build.0 = Release|Any CPU + {0DF71C73-52A4-4423-8CEC-8A24D4F83FF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DF71C73-52A4-4423-8CEC-8A24D4F83FF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DF71C73-52A4-4423-8CEC-8A24D4F83FF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DF71C73-52A4-4423-8CEC-8A24D4F83FF3}.Release|Any CPU.Build.0 = Release|Any CPU + {A96876EE-1A1F-4096-9B6A-5739E66B3364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A96876EE-1A1F-4096-9B6A-5739E66B3364}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A96876EE-1A1F-4096-9B6A-5739E66B3364}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A96876EE-1A1F-4096-9B6A-5739E66B3364}.Release|Any CPU.Build.0 = Release|Any CPU + {C3FF8716-052B-4D6C-81FF-E80F89AF9A80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3FF8716-052B-4D6C-81FF-E80F89AF9A80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3FF8716-052B-4D6C-81FF-E80F89AF9A80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3FF8716-052B-4D6C-81FF-E80F89AF9A80}.Release|Any CPU.Build.0 = Release|Any CPU + {8FE8378E-5A8B-4708-8F86-35BE0DE121F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FE8378E-5A8B-4708-8F86-35BE0DE121F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FE8378E-5A8B-4708-8F86-35BE0DE121F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FE8378E-5A8B-4708-8F86-35BE0DE121F7}.Release|Any CPU.Build.0 = Release|Any CPU + {81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -203,13 +217,10 @@ Global {B310A760-8AE1-41CA-81F8-03B12E2FCE30} = {576DB1E6-C5EC-4FEF-A826-EC19D8BEE572} {43A219A8-2995-4884-806F-FDB9CD25D403} = {A972031D-2F61-4183-AF75-99EE1A9F6B32} {F3E87D0F-6F06-4C0B-AE06-42C0834C3C6E} = {A972031D-2F61-4183-AF75-99EE1A9F6B32} - {D2C80E4B-1117-4F02-AB02-E453BDA0C58E} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} - {B1D90742-39BD-429C-8E87-C5CD2991DF27} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} {4FDF7BFA-A337-41D3-898D-C6A98278E6AD} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {9A7C8370-ED1F-486F-A8F5-C5BF4221464E} = {A972031D-2F61-4183-AF75-99EE1A9F6B32} {A19C026B-1C0F-4AA3-AC49-7D8B4C7231CF} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} {333D2FBC-CCA7-46AF-9453-C310671A67B0} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} - {9D3015EE-5B84-41B3-A1D3-1A439370C392} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} {068F6CA9-6108-4F45-8540-351AA5227259} = {4FDF7BFA-A337-41D3-898D-C6A98278E6AD} {20005864-BE82-412D-99BF-288E2D8370E9} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {CACC0520-B452-4310-A33C-DC944129ACDD} = {31B45C4C-206F-4F31-9CC6-33BF11DFEE39} @@ -217,6 +228,12 @@ Global {08F8160E-E691-4F07-9F57-EA31B9736429} = {25DA12FE-6209-4524-9A37-8E51F815E198} {50371E48-BEC3-4D53-BD37-F3A6149CFD0D} = {25DA12FE-6209-4524-9A37-8E51F815E198} {C5B7120C-190B-4C38-95CB-83F12799598D} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} + {DB42594C-9A68-488A-A289-47C0F9E29808} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {0DF71C73-52A4-4423-8CEC-8A24D4F83FF3} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {A96876EE-1A1F-4096-9B6A-5739E66B3364} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {C3FF8716-052B-4D6C-81FF-E80F89AF9A80} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {8FE8378E-5A8B-4708-8F86-35BE0DE121F7} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {81E63A90-3191-4E99-92FF-01F9B1D3E3C5} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/build.cake b/build.cake index 10ba91ed8..87e9437fe 100644 --- a/build.cake +++ b/build.cake @@ -3,6 +3,8 @@ // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake +#load package-tests.cake + // Initialize BuildSettings BuildSettings.Initialize( Context, @@ -12,233 +14,6 @@ BuildSettings.Initialize( exemptFiles: new[] { "Options.cs", "ProcessUtils.cs", "ProcessUtilsTests.cs" }, unitTests: "**/*.tests.exe"); -////////////////////////////////////////////////////////////////////// -// PACKAGE TEST LISTS -////////////////////////////////////////////////////////////////////// - -// Tests run for all runner packages except NETCORE runner -var StandardRunnerTests = new List - { - Net462Test, - Net462X86Test, - Net462PlusNet462Test, - Net462ExeTest, - NetCore31Test, - Net60Test, - Net70Test, - Net80Test, - Net60PlusNet80Test, - Net462PlusNet60Test, - NUnitProjectTest, - V2ResultWriterTest, - VSProjectLoaderTest_Project, - VSProjectLoaderTest_Solution - }; - -// Tests run for the NETCORE runner package -var NetCoreRunnerTests = new List - { - NetCore31Test, - Net60Test, - Net70Test, - Net80Test, - }; - -const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; -// TODO: Remove the limitation to Windows -bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); - -if (dotnetX86Available) -{ - bool onAppVeyor = BuildSystem.IsRunningOnAppVeyor; - bool onGitHubActions = BuildSystem.IsRunningOnGitHubActions; - - StandardRunnerTests.Add(Net60X86Test); - // TODO: Make these tests run on AppVeyor and GitHub Actions - if (!onAppVeyor && !onGitHubActions) - StandardRunnerTests.Add(NetCore31X86Test); - if (!onAppVeyor && !onGitHubActions) - StandardRunnerTests.Add(Net70X86Test); - if (!onAppVeyor) - StandardRunnerTests.Add(Net80X86Test); - // Currently, NetCoreRunner runs tests in process. As a result, - // X86 tests will not work in our environment, although uses may run - // it as a tool using the X86 architecture. -} - -////////////////////////////////////////////////////////////////////// -// INDIVIDUAL PACKAGE TEST DEFINITIONS -////////////////////////////////////////////////////////////////////// - -static ExpectedResult MockAssemblyExpectedResult(int nCopies = 1) => new ExpectedResult("Failed") -{ - Total = 37 * nCopies, - Passed = 23 * nCopies, - Failed = 5 * nCopies, - Warnings = 1 * nCopies, - Inconclusive = 1 * nCopies, - Skipped = 7 * nCopies -}; - -static ExpectedResult MockAssemblyExpectedResult(params string[] runtimes) -{ - int nCopies = runtimes.Length; - var result = MockAssemblyExpectedResult(nCopies); - result.Assemblies = new ExpectedAssemblyResult[nCopies]; - for (int i = 0; i < nCopies; i++) - result.Assemblies[i] = new ExpectedAssemblyResult("mock-assembly.dll", runtimes[i]); - return result; -} - -static ExpectedResult MockAssemblyX86ExpectedResult(params string[] runtimes) -{ - int nCopies = runtimes.Length; - var result = MockAssemblyExpectedResult(nCopies); - result.Assemblies = new ExpectedAssemblyResult[nCopies]; - for (int i = 0; i < nCopies; i++) - result.Assemblies[i] = new ExpectedAssemblyResult("mock-assembly-x86.dll", runtimes[i]); - return result; -} - -static ExpectedResult MockAssemblySolutionResult = new ExpectedResult("Failed") -{ - Total = 37 * 5, - Passed = 23 * 5, - Failed = 5 * 5, - Warnings = 1 * 5, - Inconclusive = 1 * 5, - Skipped = 7 * 5, - Assemblies = new ExpectedAssemblyResult[] - { - new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-3.1"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), - new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), - new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), - new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0") - } -}; - -static PackageTest Net462Test = new PackageTest( - 1, "Net462Test", - "Run mock-assembly.dll under .NET 4.6.2", - "net462/mock-assembly.dll", - MockAssemblyExpectedResult("net-4.6.2")); - -static PackageTest Net462X86Test = new PackageTest( - 1, "Net462X86Test", - "Run mock-assembly-x86.dll under .NET 4.6.2", - "net462/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("net-4.6.2")); - -static PackageTest Net462ExeTest = new PackageTest( - 1, "Net462ExeTest", - "Run nunit.engine.core.tests.exe under .NET 4.6.2", - "net462/nunit.engine.core.tests.exe", - new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("nunit.engine.core.tests.exe")}}); - -static PackageTest Net462PlusNet462Test = new PackageTest( - 1, "Net462PlusNet462Test", - "Run two copies of mock-assembly together", - "net462/mock-assembly.dll net462/mock-assembly.dll", - MockAssemblyExpectedResult("net-4.6.2", "net-4.6.2")); - -static PackageTest Net80Test = new PackageTest( - 1, "Net80Test", - "Run mock-assembly.dll under .NET 8.0", - "net8.0/mock-assembly.dll", - MockAssemblyExpectedResult("netcore-8.0")); - -static PackageTest Net80X86Test = new PackageTest( - 1, "Net80X86Test", - "Run mock-assembly-x86.dll under .NET 8.0", - "net8.0/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("netcore-8.0")); - -static PackageTest Net70Test = new PackageTest( - 1, "Net70Test", - "Run mock-assembly.dll under .NET 7.0", - "net7.0/mock-assembly.dll", - MockAssemblyExpectedResult("netcore-7.0")); - -static PackageTest Net70X86Test = new PackageTest( - 1, "Net70X86Test", - "Run mock-assembly-x86.dll under .NET 7.0", - "net7.0/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("netcore-7.0")); - -static PackageTest Net60Test = new PackageTest( - 1, "Net60Test", - "Run mock-assembly.dll under .NET 6.0", - "net6.0/mock-assembly.dll", - MockAssemblyExpectedResult("netcore-6.0")); - -static PackageTest Net60X86Test = new PackageTest( - 1, "Net60X86Test", - "Run mock-assembly-x86.dll under .NET 6.0", - "net6.0/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("netcore-6.0")); - -static PackageTest NetCore31Test = new PackageTest( - 1, "NetCore31Test", - "Run mock-assembly.dll under .NET Core 3.1", - "netcoreapp3.1/mock-assembly.dll", - MockAssemblyExpectedResult("netcore-3.1")); - -static PackageTest NetCore31X86Test = new PackageTest( - 1, "NetCore31X86Test", - "Run mock-assembly-x86.dll under .NET Core 3.1", - "netcoreapp3.1/mock-assembly-x86.dll", - MockAssemblyX86ExpectedResult("netcore-3.1")); - -static PackageTest Net60PlusNet80Test = new PackageTest( - 1, "Net60PlusNet80Test", - "Run mock-assembly under .NET6.0 and 8.0 together", - "net6.0/mock-assembly.dll net8.0/mock-assembly.dll", - MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0")); - -static PackageTest Net462PlusNet60Test = new PackageTest( - 1, "Net462PlusNet60Test", - "Run mock-assembly under .Net Framework 4.6.2 and .Net 6.0 together", - "net462/mock-assembly.dll net6.0/mock-assembly.dll", - MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0")); - -// Test with latest released version of each of our extensions - -static ExtensionSpecifier NUnitProjectLoader = KnownExtensions.NUnitProjectLoader.SetVersion("3.8.0"); -static ExtensionSpecifier NUnitV2ResultWriter = KnownExtensions.NUnitV2ResultWriter.SetVersion("3.8.0"); -static ExtensionSpecifier VSProjectLoader = KnownExtensions.VSProjectLoader.SetVersion("3.9.0"); - -static PackageTest NUnitProjectTest = new PackageTest( - 1, "NUnitProjectTest", - "Run project with both copies of mock-assembly", - "../../NetFXTests.nunit --config=Release --trace=Debug", - MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"), - NUnitProjectLoader); - -static PackageTest V2ResultWriterTest = new PackageTest( - 1, "V2ResultWriterTest", - "Run mock-assembly under .NET 6.0 and produce V2 output", - "net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", - MockAssemblyExpectedResult("netcore-6.0"), - NUnitV2ResultWriter); - -static PackageTest VSProjectLoaderTest_Project = new PackageTest( - 1, "VSProjectLoaderTest_Project", - "Run mock-assembly using the .csproj file", - "../../src/NUnitEngine/mock-assembly/mock-assembly.csproj --config=Release", - MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0"), - VSProjectLoader); - -static PackageTest VSProjectLoaderTest_Solution = new PackageTest( - 1, "VSProjectLoaderTest_Solution", - "Run mock-assembly using the .sln file", - "../../src/NUnitEngine/mock-assembly/mock-assembly.sln --config=Release", - MockAssemblySolutionResult, - VSProjectLoader); - ////////////////////////////////////////////////////////////////////// // LISTS OF FILES USED IN CHECKING PACKAGES ////////////////////////////////////////////////////////////////////// diff --git a/package-tests.cake b/package-tests.cake new file mode 100644 index 000000000..82ed7bc82 --- /dev/null +++ b/package-tests.cake @@ -0,0 +1,250 @@ +// Tests run for all runner packages except NETCORE runner +var StandardRunnerTests = new List(); + +// Tests run for the NETCORE runner package +var NetCoreRunnerTests = new List(); + +// Method for adding to both lists +void AddToBothLists(PackageTest test) +{ + StandardRunnerTests.Add(test); + NetCoreRunnerTests.Add(test); +} + +////////////////////////////////////////////////////////////////////// +// RUN MOCK-ASSEMBLY UNDER EACH RUNTIME +////////////////////////////////////////////////////////////////////// + +class MockAssemblyExpectedResult : ExpectedResult +{ + public MockAssemblyExpectedResult(params string[] runtimes) : base("Failed") + { + int nCopies = runtimes.Length; + Total = 37 * nCopies; + Passed = 23 * nCopies; + Failed = 5 * nCopies; + Warnings = 1 * nCopies; + Inconclusive = 1 * nCopies; + Skipped = 7 * nCopies; + Assemblies = new ExpectedAssemblyResult[nCopies]; + for (int i = 0; i < nCopies; i++) + Assemblies[i] = new ExpectedAssemblyResult("mock-assembly.dll", runtimes[i]); + } +} + +StandardRunnerTests.Add(new PackageTest( + 1, "Net462Test", + "Run mock-assembly.dll under .NET 4.6.2", + "net462/mock-assembly.dll", + new MockAssemblyExpectedResult("net-4.6.2"))); + +AddToBothLists(new PackageTest( + 1, "Net80Test", + "Run mock-assembly.dll under .NET 8.0", + "net8.0/mock-assembly.dll", + new MockAssemblyExpectedResult("netcore-8.0"))); + +AddToBothLists(new PackageTest( + 1, "Net70Test", + "Run mock-assembly.dll under .NET 7.0", + "net7.0/mock-assembly.dll", + new MockAssemblyExpectedResult("netcore-7.0"))); + +AddToBothLists(new PackageTest( + 1, "Net60Test", + "Run mock-assembly.dll under .NET 6.0", + "net6.0/mock-assembly.dll", + new MockAssemblyExpectedResult("netcore-6.0"))); + +AddToBothLists(new PackageTest( + 1, "NetCore31Test", + "Run mock-assembly.dll under .NET Core 3.1", + "netcoreapp3.1/mock-assembly.dll", + new MockAssemblyExpectedResult("netcore-3.1"))); + +////////////////////////////////////////////////////////////////////// +// RUN MOCK-ASSEMBLY-X86 UNDER EACH RUNTIME +////////////////////////////////////////////////////////////////////// + +const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; +// TODO: Remove the limitation to Windows +bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); + +class MockAssemblyX86ExpectedResult : MockAssemblyExpectedResult +{ + public MockAssemblyX86ExpectedResult(params string[] runtimes) : base(runtimes) + { + for (int i = 0; i < runtimes.Length; i++) + Assemblies[i] = new ExpectedAssemblyResult("mock-assembly-x86.dll", runtimes[i]); + } +} + +// X86 is always available for .NET Framework +StandardRunnerTests.Add(new PackageTest( + 1, "Net462X86Test", + "Run mock-assembly-x86.dll under .NET 4.6.2", + "net462/mock-assembly-x86.dll", + new MockAssemblyX86ExpectedResult("net-4.6.2"))); + +if (dotnetX86Available) +{ + // TODO: Make tests run on all build platforms + bool onAppVeyor = BuildSystem.IsRunningOnAppVeyor; + bool onGitHubActions = BuildSystem.IsRunningOnGitHubActions; + + if (!onAppVeyor) + StandardRunnerTests.Add(new PackageTest( + 1, "Net80X86Test", + "Run mock-assembly-x86.dll under .NET 8.0", + "net8.0/mock-assembly-x86.dll", + new MockAssemblyX86ExpectedResult("netcore-8.0"))); + + if (!onAppVeyor && !onGitHubActions) + StandardRunnerTests.Add(new PackageTest( + 1, "Net70X86Test", + "Run mock-assembly-x86.dll under .NET 7.0", + "net7.0/mock-assembly-x86.dll", + new MockAssemblyX86ExpectedResult("netcore-7.0"))); + + StandardRunnerTests.Add(new PackageTest( + 1, "Net60X86Test", + "Run mock-assembly-x86.dll under .NET 6.0", + "net6.0/mock-assembly-x86.dll", + new MockAssemblyX86ExpectedResult("netcore-6.0"))); + + if (!onAppVeyor && !onGitHubActions) + StandardRunnerTests.Add(new PackageTest( + 1, "NetCore31X86Test", + "Run mock-assembly-x86.dll under .NET Core 3.1", + "netcoreapp3.1/mock-assembly-x86.dll", + new MockAssemblyX86ExpectedResult("netcore-3.1"))); +} + +////////////////////////////////////////////////////////////////////// +// RUN MULTIPLE COPIES OF MOCK-ASSEMBLY +////////////////////////////////////////////////////////////////////// + +StandardRunnerTests.Add(new PackageTest( + 1, "Net462PlusNet462Test", + "Run two copies of mock-assembly together", + "net462/mock-assembly.dll net462/mock-assembly.dll", + new MockAssemblyExpectedResult("net-4.6.2", "net-4.6.2"))); + +StandardRunnerTests.Add(new PackageTest( + 1, "Net60PlusNet80Test", + "Run mock-assembly under .NET6.0 and 8.0 together", + "net6.0/mock-assembly.dll net8.0/mock-assembly.dll", + new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0"))); + +StandardRunnerTests.Add(new PackageTest( + 1, "Net462PlusNet60Test", + "Run mock-assembly under .Net Framework 4.6.2 and .Net 6.0 together", + "net462/mock-assembly.dll net6.0/mock-assembly.dll", + new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"))); + +////////////////////////////////////////////////////////////////////// +// ASP.NETCORE TESTS +////////////////////////////////////////////////////////////////////// + +StandardRunnerTests.Add(new PackageTest( + 1, "Net60AspNetCoreTest", "Run test using AspNetCore targeting .NET 6.0", + "net6.0/aspnetcore-test.dll", new ExpectedResult("Passed") + { + Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-6.0") } + })); + +StandardRunnerTests.Add(new PackageTest( + 1, "Net80AspNetCoreTest", "Run test using AspNetCore targeting .NET 8.0", + "net8.0/aspnetcore-test.dll", new ExpectedResult("Passed") + { + Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-8.0") } + })); + +////////////////////////////////////////////////////////////////////// +// WINDOWS FORMS TESTS +////////////////////////////////////////////////////////////////////// + +StandardRunnerTests.Add(new PackageTest( + 1, "Net60WindowsFormsTest", "Run test using windows forms under .NET 6.0", + "net6.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") + { + Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("windows-forms-test.dll", "netcore-6.0") } + })); + +StandardRunnerTests.Add(new PackageTest( + 1, "Net80WindowsFormsTest", "Run test using windows forms under .NET 8.0", + "net8.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") + { + Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("windows-forms-test.dll", "netcore-8.0") } + })); + +////////////////////////////////////////////////////////////////////// +// WPF TESTS +////////////////////////////////////////////////////////////////////// + +StandardRunnerTests.Add(new PackageTest( + 1, "Net60WPFTest", "Run test using WPF under .NET 6.0", + "net6.0-windows/WpfTest.dll --trace=Debug", + new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-6.0") } })); + +StandardRunnerTests.Add(new PackageTest( + 1, "Net80WPFTest", "Run test using WPF under .NET 8.0", + "net8.0-windows/WpfTest.dll --trace=Debug", + new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-8.0") } })); + +////////////////////////////////////////////////////////////////////// +// RUN TESTS USING EACH OF OUR EXTENSIONS +////////////////////////////////////////////////////////////////////// + +StandardRunnerTests.Add(new PackageTest( + 1, "NUnitProjectTest", + "Run project with both copies of mock-assembly", + "../../NetFXTests.nunit --config=Release --trace=Debug", + new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"), + KnownExtensions.NUnitProjectLoader.SetVersion("3.8.0"))); + +StandardRunnerTests.Add(new PackageTest( + 1, "V2ResultWriterTest", + "Run mock-assembly under .NET 6.0 and produce V2 output", + "net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", + new MockAssemblyExpectedResult("netcore-6.0"), + KnownExtensions.NUnitV2ResultWriter.SetVersion("3.8.0"))); + +StandardRunnerTests.Add(new PackageTest( + 1, "VSProjectLoaderTest_Project", + "Run mock-assembly using the .csproj file", + "../../src/TestData/mock-assembly/mock-assembly.csproj --config=Release", + new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0"), + KnownExtensions.VSProjectLoader.SetVersion("3.9.0"))); + +static ExpectedResult MockAssemblySolutionResult = new ExpectedResult("Failed") +{ + Total = 37 * 5, + Passed = 23 * 5, + Failed = 5 * 5, + Warnings = 1 * 5, + Inconclusive = 1 * 5, + Skipped = 7 * 5, + Assemblies = new ExpectedAssemblyResult[] + { + new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-3.1"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), + new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), + new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), + new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0") + } +}; + +StandardRunnerTests.Add(new PackageTest( + 1, "VSProjectLoaderTest_Solution", + "Run mock-assembly using the .sln file", + "../../src/TestData/TestData.sln --config=Release --trace=Debug", + MockAssemblySolutionResult, + KnownExtensions.VSProjectLoader.SetVersion("3.9.0"))); diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index f740358e0..a9f1aec4d 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -43,7 +43,7 @@ - + diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 66047d496..c89e0b00b 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -34,7 +34,7 @@ - + diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index fe9d53336..1e8d82573 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -69,7 +69,7 @@ public Assembly Resolve(AssemblyLoadContext context, AssemblyName name) private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) { context = context ?? _loadContext; - + if (TryLoadFromTrustedPlatformAssemblies(context, name, out var loadedAssembly)) { log.Info("'{0}' assembly is loaded from trusted path '{1}'", name, loadedAssembly.Location); diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index 3373d6904..52e36ebdd 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -34,7 +34,7 @@ - + diff --git a/src/NUnitEngine/mock-assembly/mock-assembly.sln b/src/TestData/TestData.sln similarity index 89% rename from src/NUnitEngine/mock-assembly/mock-assembly.sln rename to src/TestData/TestData.sln index 9b4908fa7..2364ee3ab 100644 --- a/src/NUnitEngine/mock-assembly/mock-assembly.sln +++ b/src/TestData/TestData.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34525.116 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "mock-assembly.csproj", "{FB3F6F62-37AB-4193-87A9-197C2E0CBC1B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "mock-assembly\mock-assembly.csproj", "{FB3F6F62-37AB-4193-87A9-197C2E0CBC1B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "..\notest-assembly\notest-assembly.csproj", "{F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "notest-assembly\notest-assembly.csproj", "{F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/TestData/aspnetcore-test/AspNetCoreTest.cs b/src/TestData/aspnetcore-test/AspNetCoreTest.cs new file mode 100644 index 000000000..e4ba287b8 --- /dev/null +++ b/src/TestData/aspnetcore-test/AspNetCoreTest.cs @@ -0,0 +1,25 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using NUnit.Framework; +using Microsoft.AspNetCore.Components.Forms; + +// Test which resolves issue #1203 +namespace Test1 +{ + [TestFixture] + public class AspNetCoreTest + { + [Test] + public void WithoutFramework() + { + Assert.Pass(); + } + + [Test] + public void WithFramework() + { + InputCheckbox checkbox = new InputCheckbox(); + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/src/TestData/aspnetcore-test/aspnetcore-test.csproj b/src/TestData/aspnetcore-test/aspnetcore-test.csproj new file mode 100644 index 000000000..46fe89800 --- /dev/null +++ b/src/TestData/aspnetcore-test/aspnetcore-test.csproj @@ -0,0 +1,13 @@ + + + + net6.0;net8.0 + Library + false + + + + + + + diff --git a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj similarity index 100% rename from src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj rename to src/TestData/mock-assembly-x86/mock-assembly-x86.csproj diff --git a/src/NUnitEngine/mock-assembly/AccessesCurrentTestContextDuringDiscovery.cs b/src/TestData/mock-assembly/AccessesCurrentTestContextDuringDiscovery.cs similarity index 100% rename from src/NUnitEngine/mock-assembly/AccessesCurrentTestContextDuringDiscovery.cs rename to src/TestData/mock-assembly/AccessesCurrentTestContextDuringDiscovery.cs diff --git a/src/NUnitEngine/mock-assembly/MockAssembly.cs b/src/TestData/mock-assembly/MockAssembly.cs similarity index 100% rename from src/NUnitEngine/mock-assembly/MockAssembly.cs rename to src/TestData/mock-assembly/MockAssembly.cs diff --git a/src/NUnitEngine/mock-assembly/mock-assembly.csproj b/src/TestData/mock-assembly/mock-assembly.csproj similarity index 100% rename from src/NUnitEngine/mock-assembly/mock-assembly.csproj rename to src/TestData/mock-assembly/mock-assembly.csproj diff --git a/src/NUnitEngine/mock-cpp-clr/AssemblyInfo.cpp b/src/TestData/mock-cpp-clr/AssemblyInfo.cpp similarity index 100% rename from src/NUnitEngine/mock-cpp-clr/AssemblyInfo.cpp rename to src/TestData/mock-cpp-clr/AssemblyInfo.cpp diff --git a/src/NUnitEngine/mock-cpp-clr/TestClass.cpp b/src/TestData/mock-cpp-clr/TestClass.cpp similarity index 100% rename from src/NUnitEngine/mock-cpp-clr/TestClass.cpp rename to src/TestData/mock-cpp-clr/TestClass.cpp diff --git a/src/NUnitEngine/mock-cpp-clr/TestClass.h b/src/TestData/mock-cpp-clr/TestClass.h similarity index 100% rename from src/NUnitEngine/mock-cpp-clr/TestClass.h rename to src/TestData/mock-cpp-clr/TestClass.h diff --git a/src/NUnitEngine/mock-cpp-clr/mock-cpp-clr-x64.vcxproj b/src/TestData/mock-cpp-clr/mock-cpp-clr-x64.vcxproj similarity index 100% rename from src/NUnitEngine/mock-cpp-clr/mock-cpp-clr-x64.vcxproj rename to src/TestData/mock-cpp-clr/mock-cpp-clr-x64.vcxproj diff --git a/src/NUnitEngine/mock-cpp-clr/mock-cpp-clr-x86.vcxproj b/src/TestData/mock-cpp-clr/mock-cpp-clr-x86.vcxproj similarity index 100% rename from src/NUnitEngine/mock-cpp-clr/mock-cpp-clr-x86.vcxproj rename to src/TestData/mock-cpp-clr/mock-cpp-clr-x86.vcxproj diff --git a/src/NUnitEngine/notest-assembly/Properties/AssemblyInfo.cs b/src/TestData/notest-assembly/Properties/AssemblyInfo.cs similarity index 100% rename from src/NUnitEngine/notest-assembly/Properties/AssemblyInfo.cs rename to src/TestData/notest-assembly/Properties/AssemblyInfo.cs diff --git a/src/NUnitEngine/notest-assembly/notest-assembly.csproj b/src/TestData/notest-assembly/notest-assembly.csproj similarity index 100% rename from src/NUnitEngine/notest-assembly/notest-assembly.csproj rename to src/TestData/notest-assembly/notest-assembly.csproj diff --git a/src/TestData/windows-forms-test/WindowsFormsTest.cs b/src/TestData/windows-forms-test/WindowsFormsTest.cs new file mode 100644 index 000000000..64686341b --- /dev/null +++ b/src/TestData/windows-forms-test/WindowsFormsTest.cs @@ -0,0 +1,25 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System.Windows.Forms; +using NUnit.Framework; + +// Test which resolves issue #1203 +namespace Test1 +{ + [TestFixture] + public class WindowsFormsTest + { + [Test] + public void WithoutFramework() + { + Assert.Pass(); + } + + [Test] + public void WithFramework() + { + var checkbox = new CheckBox(); + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/src/TestData/windows-forms-test/windows-forms-test.csproj b/src/TestData/windows-forms-test/windows-forms-test.csproj new file mode 100644 index 000000000..9ad93a3c3 --- /dev/null +++ b/src/TestData/windows-forms-test/windows-forms-test.csproj @@ -0,0 +1,13 @@ + + + + net6.0-windows;net8.0-windows + true + false + + + + + + + diff --git a/src/TestData/wpf-test/WpfTest.cs b/src/TestData/wpf-test/WpfTest.cs new file mode 100644 index 000000000..d2b3632b0 --- /dev/null +++ b/src/TestData/wpf-test/WpfTest.cs @@ -0,0 +1,26 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Windows.Controls; +using NUnit.Framework; + +// Test which resolves issue #1203 +namespace Test1 +{ + [TestFixture] + public class WPFTest + { + [Test] + public void WithoutFramework() + { + Assert.Pass(); + } + + [Test] + public void WithFramework() + { + //CheckBox checkbox; + + } + } +} \ No newline at end of file diff --git a/src/TestData/wpf-test/WpfTest.csproj b/src/TestData/wpf-test/WpfTest.csproj new file mode 100644 index 000000000..a4c0ff808 --- /dev/null +++ b/src/TestData/wpf-test/WpfTest.csproj @@ -0,0 +1,12 @@ + + + + net6.0-windows;net8.0-windows + true + + + + + + + From 7ce369e3b2351ff76fa795066970e91f156ac4d3 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 9 Sep 2024 10:36:39 -0700 Subject: [PATCH 094/190] Hadnle unmanaged executables without throwing --- NUnitConsole.sln | 10 ++ package-tests.cake | 3 +- .../Runners/NotRunnableTestRunner.cs | 131 ++++++++++++++++++ .../nunit.engine/Runners/MasterTestRunner.cs | 2 +- .../Services/DefaultTestRunnerFactory.cs | 4 + .../Services/RuntimeFrameworkService.cs | 43 +++--- src/TestData/TestData.sln | 6 + src/TestData/WpfApp/Program.cs | 17 +++ src/TestData/WpfApp/WpfApp.csproj | 10 ++ 9 files changed, 208 insertions(+), 18 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs create mode 100644 src/TestData/WpfApp/Program.cs create mode 100644 src/TestData/WpfApp/WpfApp.csproj diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 6726812b1..acd529631 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -130,6 +130,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfTest", "src\TestData\wpf-test\WpfTest.csproj", "{A96876EE-1A1F-4096-9B6A-5739E66B3364}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestData", "TestData", "{2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F}" + ProjectSection(SolutionItems) = preProject + src\TestData\TestData.sln = src\TestData\TestData.sln + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "src\TestData\mock-assembly\mock-assembly.csproj", "{C3FF8716-052B-4D6C-81FF-E80F89AF9A80}" EndProject @@ -137,6 +140,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly-x86", "src\Te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "src\TestData\notest-assembly\notest-assembly.csproj", "{81E63A90-3191-4E99-92FF-01F9B1D3E3C5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "src\TestData\WpfApp\WpfApp.csproj", "{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -203,6 +208,10 @@ Global {81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Release|Any CPU.Build.0 = Release|Any CPU + {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -234,6 +243,7 @@ Global {C3FF8716-052B-4D6C-81FF-E80F89AF9A80} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {8FE8378E-5A8B-4708-8F86-35BE0DE121F7} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {81E63A90-3191-4E99-92FF-01F9B1D3E3C5} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/package-tests.cake b/package-tests.cake index 82ed7bc82..bd1faf66e 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -238,7 +238,8 @@ static ExpectedResult MockAssemblySolutionResult = new ExpectedResult("Failed") new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), - new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0") + new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0"), + new ExpectedAssemblyResult("WpfApp.exe") } }; diff --git a/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs new file mode 100644 index 000000000..b6e0fc5d0 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs @@ -0,0 +1,131 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection.Emit; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace NUnit.Engine.Runners +{ + public abstract class NotRunnableTestRunner : ITestEngineRunner + { + private const string LOAD_RESULT_FORMAT = + "" + + "" + + "" + + "" + + ""; + + private const string RUN_RESULT_FORMAT = + "" + + "" + + "" + + "" + + "" + + "{7}" + + "" + + ""; + + private string _name; + private string _fullname; + private string _message; + private string _type; + + protected string _runstate; + protected string _result; + protected string _label; + + public NotRunnableTestRunner(string assemblyPath, string message) + { + _name = Escape(Path.GetFileName(assemblyPath)); + _fullname = Escape(Path.GetFullPath(assemblyPath)); + _message = Escape(message); + _type = new List { ".dll", ".exe" }.Contains(Path.GetExtension(assemblyPath)) ? "Assembly" : "Unknown"; + } + + public string ID { get; set; } + + TestEngineResult ITestEngineRunner.Load() + { + return GetLoadResult(); + } + + void ITestEngineRunner.Unload() + { + } + + TestEngineResult ITestEngineRunner.Reload() + { + return GetLoadResult(); + } + + int ITestEngineRunner.CountTestCases(TestFilter filter) + { + return 0; + } + + TestEngineResult ITestEngineRunner.Run(ITestEventListener listener, TestFilter filter) + { + return new TestEngineResult(string.Format(RUN_RESULT_FORMAT, + _type, TestID, _name, _fullname, _runstate, _result, _label, _message)); + } + + AsyncTestEngineResult ITestEngineRunner.RunAsync(ITestEventListener listener, TestFilter filter) + { + throw new NotImplementedException(); + } + + void ITestEngineRunner.StopRun(bool force) + { + } + + TestEngineResult ITestEngineRunner.Explore(TestFilter filter) + { + return GetLoadResult(); + } + + void IDisposable.Dispose() + { + // Nothing to do here + } + + private static string Escape(string original) + { + return original + .Replace("&", "&") + .Replace("\"", """) + .Replace("'", "'") + .Replace("<", "<") + .Replace(">", ">"); + } + + private TestEngineResult GetLoadResult() + { + return new TestEngineResult(string.Format( + LOAD_RESULT_FORMAT, + _type, TestID, _name, _fullname, _runstate, _message)); + } + + private string TestID + { + get + { + return string.IsNullOrEmpty(ID) + ? "1" + : ID + "-1"; + } + } + } + + public class UnmanagedExecutableTestRunner : NotRunnableTestRunner + { + public UnmanagedExecutableTestRunner(string assemblyPath) + : base (assemblyPath, "Unmanaged libraries or applications are not supported") + { } + } +} diff --git a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs index f23c70572..3418ce60e 100644 --- a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs @@ -22,7 +22,7 @@ public class MasterTestRunner : ITestRunner // MasterTestRunner is the only runner that is passed back // to users asking for an ITestRunner. The actual details of // execution are handled by various internal runners, which - // impement ITestEngineRunner. + // implement ITestEngineRunner. // // Explore and execution results from MasterTestRunner are // returned as XmlNodes, created from the internal diff --git a/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs b/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs index 5fdb79de4..b53a66995 100644 --- a/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs +++ b/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs @@ -43,6 +43,10 @@ public ITestEngineRunner MakeTestRunner(TestPackage package) } #else + // Unmanaged code is handled in-process irrespective of the process model + if (package.GetSetting(InternalEnginePackageSettings.ImageTargetFrameworkName, "").StartsWith("Unmanaged,")) + return new UnmanagedExecutableTestRunner(package.FullName); + ProcessModel processModel = (ProcessModel)System.Enum.Parse( typeof(ProcessModel), package.GetSetting(EnginePackageSettings.ProcessModel, "Default")); diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs index 1d9c067fb..a977fbdb7 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -160,6 +160,8 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) targetRuntime = RuntimeType.NetCore; targetVersion = new Version(3, 1); break; + case "Unmanaged": + return null; default: throw new NUnitEngineException("Unsupported Target Framework: " + imageTargetFrameworkNameSetting); } @@ -283,26 +285,35 @@ private static void ApplyImageData(TestPackage package) } else if (File.Exists(packageName) && PathUtils.IsAssemblyFileType(packageName)) { - using (var assembly = AssemblyDefinition.ReadAssembly(packageName)) + try { - targetVersion = assembly.GetRuntimeVersion(); - log.Debug($"Assembly {packageName} uses version {targetVersion}"); - - frameworkName = assembly.GetFrameworkName(); - log.Debug($"Assembly {packageName} targets {frameworkName}"); - - if (assembly.RequiresX86()) + using (var assembly = AssemblyDefinition.ReadAssembly(packageName)) { - requiresX86 = true; - log.Debug($"Assembly {packageName} will be run x86"); - } - - if (assembly.HasAttribute("NUnit.Framework.TestAssemblyDirectoryResolveAttribute")) - { - requiresAssemblyResolver = true; - log.Debug($"Assembly {packageName} requires default app domain assembly resolver"); + targetVersion = assembly.GetRuntimeVersion(); + log.Debug($"Assembly {packageName} uses version {targetVersion}"); + + frameworkName = assembly.GetFrameworkName(); + log.Debug($"Assembly {packageName} targets {frameworkName}"); + + if (assembly.RequiresX86()) + { + requiresX86 = true; + log.Debug($"Assembly {packageName} will be run x86"); + } + + if (assembly.HasAttribute("NUnit.Framework.TestAssemblyDirectoryResolveAttribute")) + { + requiresAssemblyResolver = true; + log.Debug($"Assembly {packageName} requires default app domain assembly resolver"); + } } } + catch (BadImageFormatException) + { + // "Unmanaged" is not a valid framework identifier but we handle it upstream + // using UnmanagedCodeTestRunner, which doesn't actually try to run it. + frameworkName = "Unmanaged,Version=0.0"; + } } if (targetVersion.Major > 0) diff --git a/src/TestData/TestData.sln b/src/TestData/TestData.sln index 2364ee3ab..ac4378dac 100644 --- a/src/TestData/TestData.sln +++ b/src/TestData/TestData.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "mock-assem EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "notest-assembly\notest-assembly.csproj", "{F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "WpfApp\WpfApp.csproj", "{F831D879-E806-4DE7-8D80-9B94AD64744A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Debug|Any CPU.Build.0 = Debug|Any CPU {F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Release|Any CPU.ActiveCfg = Release|Any CPU {F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Release|Any CPU.Build.0 = Release|Any CPU + {F831D879-E806-4DE7-8D80-9B94AD64744A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F831D879-E806-4DE7-8D80-9B94AD64744A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F831D879-E806-4DE7-8D80-9B94AD64744A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F831D879-E806-4DE7-8D80-9B94AD64744A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/TestData/WpfApp/Program.cs b/src/TestData/WpfApp/Program.cs new file mode 100644 index 000000000..db1e92a02 --- /dev/null +++ b/src/TestData/WpfApp/Program.cs @@ -0,0 +1,17 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +namespace WpfApp +{ + internal class Program + { + static void Main() { } + } +} diff --git a/src/TestData/WpfApp/WpfApp.csproj b/src/TestData/WpfApp/WpfApp.csproj new file mode 100644 index 000000000..0dee5c4b5 --- /dev/null +++ b/src/TestData/WpfApp/WpfApp.csproj @@ -0,0 +1,10 @@ + + + + WinExe + net8.0-windows + ..\..\bin\$(Configuration)\ + true + + + From 3abcf8099a82be54510c94bfaeaa7323b3e4c591 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 13 Sep 2024 07:58:27 -0700 Subject: [PATCH 095/190] Update to latest dev build of recipe, 1.2.0-dev00007 --- build.cake | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.cake b/build.cake index 87e9437fe..8036092b4 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.2.0-dev00003 +#load nuget:?package=NUnit.Cake.Recipe&version=1.2.0-dev00007 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -11,8 +11,7 @@ BuildSettings.Initialize( title: "NUnit Console and Engine", githubRepository: "nunit-console", solutionFile: "NUnitConsole.sln", - exemptFiles: new[] { "Options.cs", "ProcessUtils.cs", "ProcessUtilsTests.cs" }, - unitTests: "**/*.tests.exe"); + exemptFiles: new[] { "Options.cs", "ProcessUtils.cs", "ProcessUtilsTests.cs" }); ////////////////////////////////////////////////////////////////////// // LISTS OF FILES USED IN CHECKING PACKAGES From 359e19c2431b9a1d8f6fffc11f67dfd4645db2da Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 14 Sep 2024 15:38:28 -0700 Subject: [PATCH 096/190] Handle invalid chars sent from the framework --- NUnitConsole.sln | 7 ++++ package-tests.cake | 40 ++++++++++++++++++- .../TestEventHandlerTests.cs | 6 +++ .../nunit3-console.tests.csproj | 8 ++-- .../Extensibility/IDriverFactory.cs | 2 +- .../nunit.engine.api/TestEngineActivator.cs | 2 +- .../Runners/TestEventDispatcher.cs | 5 +++ .../InvalidTestNames/InvalidTestNames.cs | 22 ++++++++++ .../InvalidTestNames/InvalidTestNames.csproj | 12 ++++++ 9 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 src/TestData/InvalidTestNames/InvalidTestNames.cs create mode 100644 src/TestData/InvalidTestNames/InvalidTestNames.csproj diff --git a/NUnitConsole.sln b/NUnitConsole.sln index acd529631..c197b9a6a 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -142,6 +142,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "src\Test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "src\TestData\WpfApp\WpfApp.csproj", "{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvalidTestNames", "src\TestData\InvalidTestNames\InvalidTestNames.csproj", "{58E18ACC-1F7E-4395-817E-E7EF943E0C77}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -212,6 +214,10 @@ Global {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Debug|Any CPU.Build.0 = Debug|Any CPU {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Release|Any CPU.ActiveCfg = Release|Any CPU {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Release|Any CPU.Build.0 = Release|Any CPU + {58E18ACC-1F7E-4395-817E-E7EF943E0C77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58E18ACC-1F7E-4395-817E-E7EF943E0C77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58E18ACC-1F7E-4395-817E-E7EF943E0C77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58E18ACC-1F7E-4395-817E-E7EF943E0C77}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -244,6 +250,7 @@ Global {8FE8378E-5A8B-4708-8F86-35BE0DE121F7} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {81E63A90-3191-4E99-92FF-01F9B1D3E3C5} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {58E18ACC-1F7E-4395-817E-E7EF943E0C77} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/package-tests.cake b/package-tests.cake index bd1faf66e..ea7fd4bb4 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -202,7 +202,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "NUnitProjectTest", - "Run project with both copies of mock-assembly", + "Run NUnit project with mock-assembly.dll built for .NET 4.6.2 and 6.0", "../../NetFXTests.nunit --config=Release --trace=Debug", new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"), KnownExtensions.NUnitProjectLoader.SetVersion("3.8.0"))); @@ -249,3 +249,41 @@ StandardRunnerTests.Add(new PackageTest( "../../src/TestData/TestData.sln --config=Release --trace=Debug", MockAssemblySolutionResult, KnownExtensions.VSProjectLoader.SetVersion("3.9.0"))); + +// Special Cases + +StandardRunnerTests.Add(new PackageTest( + 1, "InvalidTestNameTest_Net462", + "Ensure we handle invalid test names correctly under .NET 4.6.2", + "net462/InvalidTestNames.dll --trace:Debug", + new ExpectedResult("Passed") + { + Assemblies = new ExpectedAssemblyResult[] + { + new ExpectedAssemblyResult("InvalidTestNames.dll", "net-4.6.2") + } + })); + +AddToBothLists(new PackageTest( + 1, "InvalidTestNameTest_Net60", + "Ensure we handle invalid test names correctly under .NET 6.0", + "net6.0/InvalidTestNames.dll --trace:Debug", + new ExpectedResult("Passed") + { + Assemblies = new ExpectedAssemblyResult[] + { + new ExpectedAssemblyResult("InvalidTestNames.dll", "netcore-6.0") + } + })); + +AddToBothLists(new PackageTest( + 1, "InvalidTestNameTest_Net80", + "Ensure we handle invalid test names correctly under .NET 8.0", + "net8.0/InvalidTestNames.dll --trace:Debug", + new ExpectedResult("Passed") + { + Assemblies = new ExpectedAssemblyResult[] + { + new ExpectedAssemblyResult("InvalidTestNames.dll", "netcore-8.0") + } + })); diff --git a/src/NUnitConsole/nunit3-console.tests/TestEventHandlerTests.cs b/src/NUnitConsole/nunit3-console.tests/TestEventHandlerTests.cs index 48981fe5d..d00f47f7f 100644 --- a/src/NUnitConsole/nunit3-console.tests/TestEventHandlerTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/TestEventHandlerTests.cs @@ -21,6 +21,12 @@ public void CreateWriter() _writer = new ExtendedTextWrapper(new StringWriter(_output)); } + [TestCase(char.MaxValue)] + public void TestNameContainsInvalidChar(char c) + { + Console.WriteLine($"Test for char {c}"); + } + [TestCaseSource("SingleEventData")] public void SingleEventsWriteExpectedOutput(string report, string labels, string expected) { diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index a9f1aec4d..a668e6134 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -23,8 +23,8 @@ - - + @@ -45,9 +45,7 @@ - - - + diff --git a/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs b/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs index 9aadc8bfa..c8eb3c7e3 100644 --- a/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs @@ -19,7 +19,7 @@ public interface IDriverFactory /// An AssemblyName referring to the possible test framework. bool IsSupportedTestFramework(AssemblyName reference); -#if NETSTANDARD2_0 +#if NETSTANDARD || NETCOREAPP /// /// Gets a driver for a given test assembly and a framework /// which the assembly is already known to reference. diff --git a/src/NUnitEngine/nunit.engine.api/TestEngineActivator.cs b/src/NUnitEngine/nunit.engine.api/TestEngineActivator.cs index 14f596cd8..d69ec5f50 100644 --- a/src/NUnitEngine/nunit.engine.api/TestEngineActivator.cs +++ b/src/NUnitEngine/nunit.engine.api/TestEngineActivator.cs @@ -17,7 +17,7 @@ public static class TestEngineActivator private const string DefaultAssemblyName = "nunit.engine.dll"; internal const string DefaultTypeName = "NUnit.Engine.TestEngine"; -#if NETSTANDARD2_0 +#if NETSTANDARD || NETCOREAPP /// /// Create an instance of the test engine. /// diff --git a/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs b/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs index 5bd7f6830..316d98381 100644 --- a/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs +++ b/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs @@ -21,8 +21,13 @@ public TestEventDispatcher() public void OnTestEvent(string report) { + const string badChar = "\xffff"; + lock (_eventLock) { + // TODO: Review whether we need more checks + report = report.Replace(badChar, "?"); + foreach (var listener in Listeners) listener.OnTestEvent(report); } diff --git a/src/TestData/InvalidTestNames/InvalidTestNames.cs b/src/TestData/InvalidTestNames/InvalidTestNames.cs new file mode 100644 index 000000000..07d585dd5 --- /dev/null +++ b/src/TestData/InvalidTestNames/InvalidTestNames.cs @@ -0,0 +1,22 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace InvalidTestNames +{ + // Issue 1469 showed that the NUnit framework sometimes sends char + // '\uffff' in test names, if the user provides it as an argument. + // Therefore, the runner has to protect itself. If additional bad + // characters are detected, we can add more tests here. + public class InvalidTestNames + { + // Generates test name TestContainsInvalidCharacter("\uffff"); + [TestCase(char.MaxValue)] + public void TestNameContainsInvalidChar(char c) + { + Console.WriteLine($"Test for char {c}"); + } + } +} diff --git a/src/TestData/InvalidTestNames/InvalidTestNames.csproj b/src/TestData/InvalidTestNames/InvalidTestNames.csproj new file mode 100644 index 000000000..d08f867db --- /dev/null +++ b/src/TestData/InvalidTestNames/InvalidTestNames.csproj @@ -0,0 +1,12 @@ + + + + net462;net6.0;net8.0 + Library + + + + + + + From d2cf4f7dc9a5fb1d495306a34c0fd71bc4ff2b37 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 14 Sep 2024 23:19:28 -0700 Subject: [PATCH 097/190] Update header and help to clarify which console runner is in use --- src/NUnitConsole/nunit3-console/Program.cs | 50 ++++++++++++++++--- .../nunit3-console/nunit3-console.csproj | 12 +++-- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index 5a12026b2..bc6f6bcc6 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -160,7 +160,10 @@ private static void WriteHeader() Assembly entryAssembly = Assembly.GetEntryAssembly(); var versionBlock = FileVersionInfo.GetVersionInfo(entryAssembly.ManifestModule.FullyQualifiedName); - var header = $"{versionBlock.ProductName} {versionBlock.ProductVersion}"; + var version = versionBlock.ProductVersion; + int plus = version.IndexOf('+'); + if (plus > 0) version = version.Substring(0, plus); + var header = $"{versionBlock.ProductName} {version}"; #if NETFRAMEWORK object[] configurationAttributes = entryAssembly.GetCustomAttributes(typeof(AssemblyConfigurationAttribute), false); @@ -185,7 +188,22 @@ private static void WriteHelpText() OutWriter.WriteLine(); OutWriter.WriteLine(ColorStyle.Header, "NUNIT3-CONSOLE [inputfiles] [options]"); OutWriter.WriteLine(); - OutWriter.WriteLine(ColorStyle.Default, "Runs a set of NUnit tests from the console."); + OutWriter.WriteLine(ColorStyle.SectionHeader, "Description:"); + using (new ColorConsole(ColorStyle.Default)) + { +#if NETFRAMEWORK + OutWriter.WriteLine(" The standard NUnit Console Runner runs a set of NUnit tests from the"); + OutWriter.WriteLine(" console command-line. By default, all tests are run using separate agents"); + OutWriter.WriteLine(" for each test assembly. This allows each assembly to run independently"); + OutWriter.WriteLine(" and allows each assembly to run under the appropriate target runtime."); +#else + OutWriter.WriteLine(" The NetCore Console Runner runs a set of NUnit tests from the console"); + OutWriter.WriteLine(" command-line. All tests are run in-process and therefore execute under"); + OutWriter.WriteLine(" the same runtime as the runner itself. A number of options supported by"); + OutWriter.WriteLine(" the standard console runner are not available using the NetCore runner."); + OutWriter.WriteLine(" See \"Limitations\" below for more information."); +#endif + } OutWriter.WriteLine(); OutWriter.WriteLine(ColorStyle.SectionHeader, "InputFiles:"); OutWriter.WriteLine(ColorStyle.Default, " One or more assemblies or test projects of a recognized type."); @@ -196,7 +214,7 @@ private static void WriteHelpText() Options.WriteOptionDescriptions(Console.Out); } OutWriter.WriteLine(); - OutWriter.WriteLine(ColorStyle.SectionHeader, "Description:"); + OutWriter.WriteLine(ColorStyle.SectionHeader, "Operation:"); using (new ColorConsole(ColorStyle.Default)) { OutWriter.WriteLine(" By default, this command runs the tests contained in the"); @@ -242,10 +260,30 @@ private static void WriteHelpText() OutWriter.WriteLine(" To be able to load NUnit projects, file type .nunit, the engine"); OutWriter.WriteLine(" extension NUnitProjectLoader is required. For Visual Studio projects"); OutWriter.WriteLine(" and solutions the engine extension VSProjectLoader is required."); +#if NETCOREAPP OutWriter.WriteLine(); - //writer.WriteLine("Options that take values may use an equal sign, a colon"); - //writer.WriteLine("or a space to separate the option from its value."); - //writer.WriteLine(); + OutWriter.WriteLine(ColorStyle.SectionHeader, "Limitations:"); + OutWriter.WriteLine(" The NetCore Runner is primarily intended for use as a dotnet tool."); + OutWriter.WriteLine(" When used in this way, a single assembly is usually being tested and"); + OutWriter.WriteLine(" the assembly must be compatible with execution under the same runtime"); + OutWriter.WriteLine(" as the runner itself, normally .NET 6.0."); + OutWriter.WriteLine(); + OutWriter.WriteLine(" Using this runner, the following options are not available. A brief"); + OutWriter.WriteLine(" rationale is given for each option excluded."); + OutWriter.WriteLine(" --configFile Config of the runner itself is used."); + OutWriter.WriteLine(" --process Not designed to run out of process."); + OutWriter.WriteLine(" --inprocess Redundant. We always run in process."); + OutWriter.WriteLine(" --domain Not applicable to .NET Core."); + OutWriter.WriteLine(" --framework Runtime of the runner is used."); + OutWriter.WriteLine(" --x86 Bitness of the runner is used."); + OutWriter.WriteLine(" --shadowcopy Not available."); + OutWriter.WriteLine(" --loaduserprofile Not avalable."); + OutWriter.WriteLine(" --agents No agents are used."); + OutWriter.WriteLine(" --debug Debug in process directly."); + OutWriter.WriteLine(" --pause Used for debugging agents."); + OutWriter.WriteLine(" --set-principal-policy Not available."); + OutWriter.WriteLine(" --debug-agent No agents are used."); +#endif } } diff --git a/src/NUnitConsole/nunit3-console/nunit3-console.csproj b/src/NUnitConsole/nunit3-console/nunit3-console.csproj index 28d9448f3..feef472f2 100644 --- a/src/NUnitConsole/nunit3-console/nunit3-console.csproj +++ b/src/NUnitConsole/nunit3-console/nunit3-console.csproj @@ -10,10 +10,16 @@ true - - NUnit Console + + NUnit Console Runner NUnit Console Runner ($(TargetFramework)) - The console command-line runner for NUnit + The standard command-line runner for NUnit + + + + NUnit NetCore Console Runner + NUnit NetCore Console Runner ($(TargetFramework)) + The dotnet command-line runner for NUnit From 04af472c44bb2266e4e823e715f8277fdfca718f Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 15 Sep 2024 09:57:05 -0700 Subject: [PATCH 098/190] Add test to confirm issue 1383 is fixed --- package-tests.cake | 4 ++-- src/NUnitConsole/nunit3-console/Program.cs | 2 +- src/TestData/aspnetcore-test/AspNetCoreTest.cs | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/package-tests.cake b/package-tests.cake index ea7fd4bb4..dc7ff9f1c 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -150,7 +150,7 @@ StandardRunnerTests.Add(new PackageTest( 1, "Net60AspNetCoreTest", "Run test using AspNetCore targeting .NET 6.0", "net6.0/aspnetcore-test.dll", new ExpectedResult("Passed") { - Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, + Total = 3, Passed = 3, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-6.0") } })); @@ -158,7 +158,7 @@ StandardRunnerTests.Add(new PackageTest( 1, "Net80AspNetCoreTest", "Run test using AspNetCore targeting .NET 8.0", "net8.0/aspnetcore-test.dll", new ExpectedResult("Passed") { - Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, + Total = 3, Passed = 3, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-8.0") } })); diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index bc6f6bcc6..827358005 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -277,7 +277,7 @@ private static void WriteHelpText() OutWriter.WriteLine(" --framework Runtime of the runner is used."); OutWriter.WriteLine(" --x86 Bitness of the runner is used."); OutWriter.WriteLine(" --shadowcopy Not available."); - OutWriter.WriteLine(" --loaduserprofile Not avalable."); + OutWriter.WriteLine(" --loaduserprofile Not available."); OutWriter.WriteLine(" --agents No agents are used."); OutWriter.WriteLine(" --debug Debug in process directly."); OutWriter.WriteLine(" --pause Used for debugging agents."); diff --git a/src/TestData/aspnetcore-test/AspNetCoreTest.cs b/src/TestData/aspnetcore-test/AspNetCoreTest.cs index e4ba287b8..65cfd1422 100644 --- a/src/TestData/aspnetcore-test/AspNetCoreTest.cs +++ b/src/TestData/aspnetcore-test/AspNetCoreTest.cs @@ -1,5 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +using System.Reflection; using NUnit.Framework; using Microsoft.AspNetCore.Components.Forms; @@ -21,5 +22,11 @@ public void WithFramework() InputCheckbox checkbox = new InputCheckbox(); Assert.Pass(); } + + [Test] + public void LoadAspNetCore() + { + Assembly.Load("Microsoft.AspNetCore, Version=8.0.0.0, Culture=Neutral, PublicKeyToken=adb9793829ddae60"); + } } } \ No newline at end of file From fac89148a8b27b4caca33a817bd34b09991e698f Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 16 Sep 2024 14:14:17 -0700 Subject: [PATCH 099/190] Reorganize assembly resolution; fix error loading WindowsBase.dll --- .../Internal/TestAssemblyLoadContext.cs | 2 - .../Internal/TestAssemblyResolver.cs | 256 ++++++++++++------ src/TestData/wpf-test/WpfTest.cs | 16 +- 3 files changed, 180 insertions(+), 94 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index 93c37ced1..66a218e47 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -15,14 +15,12 @@ internal sealed class TestAssemblyLoadContext : AssemblyLoadContext { private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyLoadContext)); - private readonly string _testAssemblyPath; private readonly string _basePath; private readonly TestAssemblyResolver _resolver; private readonly System.Runtime.Loader.AssemblyDependencyResolver _runtimeResolver; public TestAssemblyLoadContext(string testAssemblyPath) { - _testAssemblyPath = testAssemblyPath; _resolver = new TestAssemblyResolver(this, testAssemblyPath); _basePath = Path.GetDirectoryName(testAssemblyPath); _runtimeResolver = new AssemblyDependencyResolver(testAssemblyPath); diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index 1e8d82573..b913d2b6f 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -2,6 +2,9 @@ #if NETCOREAPP3_1_OR_GREATER +using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.DependencyModel.Resolution; +using Microsoft.Win32; using System; using System.Collections.Generic; using System.IO; @@ -9,9 +12,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Loader; -using Microsoft.Extensions.DependencyModel; -using Microsoft.Extensions.DependencyModel.Resolution; -using Microsoft.Win32; +using TestCentric.Metadata; namespace NUnit.Engine.Internal { @@ -19,154 +20,233 @@ internal sealed class TestAssemblyResolver : IDisposable { private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyResolver)); - private readonly ICompilationAssemblyResolver _assemblyResolver; - private readonly DependencyContext _dependencyContext; private readonly AssemblyLoadContext _loadContext; private static readonly string INSTALL_DIR; private static readonly string WINDOWS_DESKTOP_DIR; private static readonly string ASP_NET_CORE_DIR; - private static readonly List AdditionalFrameworkDirectories; + + // Our Strategies for resolving references + List ResolutionStrategies; static TestAssemblyResolver() { INSTALL_DIR = GetDotNetInstallDirectory(); WINDOWS_DESKTOP_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.WindowsDesktop.App"); ASP_NET_CORE_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.AspNetCore.App"); - - AdditionalFrameworkDirectories = new List(); - if (Directory.Exists(WINDOWS_DESKTOP_DIR)) - AdditionalFrameworkDirectories.Add(WINDOWS_DESKTOP_DIR); - if (Directory.Exists(ASP_NET_CORE_DIR)) - AdditionalFrameworkDirectories.Add(ASP_NET_CORE_DIR); } - public TestAssemblyResolver(AssemblyLoadContext loadContext, string assemblyPath) + public TestAssemblyResolver(AssemblyLoadContext loadContext, string testAssemblyPath) { _loadContext = loadContext; - _dependencyContext = DependencyContext.Load(loadContext.LoadFromAssemblyPath(assemblyPath)); - _assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[] - { - new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(assemblyPath)), - new ReferenceAssemblyPathResolver(), - new PackageCompilationAssemblyResolver() - }); + InitializeResolutionStrategies(loadContext, testAssemblyPath); _loadContext.Resolving += OnResolving; } + private void InitializeResolutionStrategies(AssemblyLoadContext loadContext, string testAssemblyPath) + { + // First, looking only at direct references by the test assembly, try to determine if + // this assembly is using WindowsDesktop (either SWF or WPF) and/or AspNetCore. + AssemblyDefinition assemblyDef = AssemblyDefinition.ReadAssembly(testAssemblyPath); + bool isWindowsDesktop = false; + bool isAspNetCore = false; + foreach (var reference in assemblyDef.MainModule.GetTypeReferences()) + { + string fn = reference.FullName; + if (fn.StartsWith("System.Windows.") || fn.StartsWith("PresentationFramework")) + isWindowsDesktop = true; + if (fn.StartsWith("Microsoft.AspNetCore.")) + isAspNetCore = true; + } + + // Initialize the list of ResolutionStrategies in the best order depending on + // what we learned. + ResolutionStrategies = new List(); + + if (isWindowsDesktop && Directory.Exists(WINDOWS_DESKTOP_DIR)) + ResolutionStrategies.Add(new AdditionalDirectoryStrategy(WINDOWS_DESKTOP_DIR)); + if (isAspNetCore && Directory.Exists(ASP_NET_CORE_DIR)) + ResolutionStrategies.Add(new AdditionalDirectoryStrategy(ASP_NET_CORE_DIR)); + ResolutionStrategies.Add(new TrustedPlatformAssembliesStrategy()); + ResolutionStrategies.Add(new RuntimeLibrariesStrategy(loadContext, testAssemblyPath)); + if (!isWindowsDesktop && Directory.Exists(WINDOWS_DESKTOP_DIR)) + ResolutionStrategies.Add(new AdditionalDirectoryStrategy(WINDOWS_DESKTOP_DIR)); + if (!isAspNetCore && Directory.Exists(ASP_NET_CORE_DIR)) + ResolutionStrategies.Add(new AdditionalDirectoryStrategy(ASP_NET_CORE_DIR)); + } + public void Dispose() { _loadContext.Resolving -= OnResolving; } - public Assembly Resolve(AssemblyLoadContext context, AssemblyName name) + public Assembly Resolve(AssemblyLoadContext context, AssemblyName assemblyName) + { + return OnResolving(context, assemblyName); + } + + private Assembly OnResolving(AssemblyLoadContext loadContext, AssemblyName assemblyName) { - return OnResolving(context, name); + if (loadContext == null) throw new ArgumentNullException("context"); + + Assembly loadedAssembly; + foreach (var strategy in ResolutionStrategies) + if (strategy.TryToResolve(loadContext, assemblyName, out loadedAssembly)) + return loadedAssembly; + + log.Info("Cannot resolve assembly '{0}'", assemblyName); + return null; } - private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) + #region Nested ResolutionStrategy Classes + + public abstract class ResolutionStrategy { - context = context ?? _loadContext; + public abstract bool TryToResolve( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly); + } - if (TryLoadFromTrustedPlatformAssemblies(context, name, out var loadedAssembly)) + public class TrustedPlatformAssembliesStrategy : ResolutionStrategy + { + public override bool TryToResolve( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) { - log.Info("'{0}' assembly is loaded from trusted path '{1}'", name, loadedAssembly.Location); - return loadedAssembly; + return TryLoadFromTrustedPlatformAssemblies(loadContext, assemblyName, out loadedAssembly); } - foreach (var library in _dependencyContext.RuntimeLibraries) + private static bool TryLoadFromTrustedPlatformAssemblies( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) { - var wrapper = new CompilationLibrary( - library.Type, - library.Name, - library.Version, - library.Hash, - library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths), - library.Dependencies, - library.Serviceable); - - var assemblies = new List(); - _assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies); - - foreach (var assemblyPath in assemblies) + // https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing + loadedAssembly = null; + var trustedAssemblies = System.AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; + if (string.IsNullOrEmpty(trustedAssemblies)) { - if (name.Name == Path.GetFileNameWithoutExtension(assemblyPath)) + return false; + } + + var separator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ";" : ":"; + foreach (var assemblyPath in trustedAssemblies.Split(separator)) + { + var fileName = Path.GetFileNameWithoutExtension(assemblyPath); + if (FileMatchesAssembly(fileName) && File.Exists(assemblyPath)) { - loadedAssembly = context.LoadFromAssemblyPath(assemblyPath); - log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", - name, - loadedAssembly.Location, - library.Name); + loadedAssembly = loadContext.LoadFromAssemblyPath(assemblyPath); + log.Info("'{0}' assembly is loaded from trusted path '{1}'", assemblyPath, loadedAssembly.Location); + + return true; + } + } + + return false; + + bool FileMatchesAssembly(string fileName) => + string.Equals(fileName, assemblyName.Name, StringComparison.InvariantCultureIgnoreCase); + } + } + + public class RuntimeLibrariesStrategy : ResolutionStrategy + { + private DependencyContext _dependencyContext; + private readonly ICompilationAssemblyResolver _assemblyResolver; + + public RuntimeLibrariesStrategy(AssemblyLoadContext loadContext, string testAssemblyPath) + { + _dependencyContext = DependencyContext.Load(loadContext.LoadFromAssemblyPath(testAssemblyPath)); + + _assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[] + { + new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(testAssemblyPath)), + new ReferenceAssemblyPathResolver(), + new PackageCompilationAssemblyResolver() + }); + } - return loadedAssembly; + public override bool TryToResolve( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) + { + foreach (var library in _dependencyContext.RuntimeLibraries) + { + var wrapper = new CompilationLibrary( + library.Type, + library.Name, + library.Version, + library.Hash, + library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths), + library.Dependencies, + library.Serviceable); + + var assemblies = new List(); + _assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies); + + foreach (var assemblyPath in assemblies) + { + if (assemblyName.Name == Path.GetFileNameWithoutExtension(assemblyPath)) + { + loadedAssembly = loadContext.LoadFromAssemblyPath(assemblyPath); + log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", + assemblyName, + loadedAssembly.Location, + library.Name); + + return true; + } } } + + loadedAssembly = null; + return false; } + } + + public class AdditionalDirectoryStrategy : ResolutionStrategy + { + private string _frameworkDirectory; - if (name.Version == null) + public AdditionalDirectoryStrategy(string frameworkDirectory) { - return null; + _frameworkDirectory = frameworkDirectory; } - foreach (string frameworkDirectory in AdditionalFrameworkDirectories) + public override bool TryToResolve( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) { - var versionDir = FindBestVersionDir(frameworkDirectory, name.Version); + loadedAssembly = null; + if (assemblyName.Version == null) + return false; + + var versionDir = FindBestVersionDir(_frameworkDirectory, assemblyName.Version); if (versionDir != null) { - string candidate = Path.Combine(frameworkDirectory, versionDir, name.Name + ".dll"); + string candidate = Path.Combine(_frameworkDirectory, versionDir, assemblyName.Name + ".dll"); if (File.Exists(candidate)) { - loadedAssembly = context.LoadFromAssemblyPath(candidate); + loadedAssembly = loadContext.LoadFromAssemblyPath(candidate); log.Info("'{0}' ({1}) assembly is loaded from AdditionalFrameworkDirectory {2} dependencies with best candidate version {3}", - name, + assemblyName, loadedAssembly.Location, - frameworkDirectory, + _frameworkDirectory, versionDir); - return loadedAssembly; + return true; } else { - log.Debug("Best version dir for {0} is {1}, but there is no {2} file", frameworkDirectory, versionDir, candidate); + log.Debug("Best version dir for {0} is {1}, but there is no {2} file", _frameworkDirectory, versionDir, candidate); + return false; } } - } - - log.Info("Cannot resolve assembly '{0}'", name); - return null; - } - private static bool TryLoadFromTrustedPlatformAssemblies(AssemblyLoadContext context, AssemblyName assemblyName, out Assembly loadedAssembly) - { - // https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing - loadedAssembly = null; - var trustedAssemblies = System.AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; - if (string.IsNullOrEmpty(trustedAssemblies)) - { return false; } + } - var separator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ";" : ":"; - foreach (var assemblyPath in trustedAssemblies.Split(separator)) - { - var fileName = Path.GetFileNameWithoutExtension(assemblyPath); - if (string.Equals(fileName, assemblyName.Name, StringComparison.InvariantCultureIgnoreCase) == false) - { - continue; - } - - if (File.Exists(assemblyPath)) - { - loadedAssembly = context.LoadFromAssemblyPath(assemblyPath); - return true; - } - } + #endregion - return false; - } + #region HelperMethods private static string GetDotNetInstallDirectory() { @@ -232,6 +312,8 @@ private static bool TryGetVersionFromString(string text, out Version newVersion) return false; } } + + #endregion } } #endif diff --git a/src/TestData/wpf-test/WpfTest.cs b/src/TestData/wpf-test/WpfTest.cs index d2b3632b0..64624c647 100644 --- a/src/TestData/wpf-test/WpfTest.cs +++ b/src/TestData/wpf-test/WpfTest.cs @@ -1,6 +1,7 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt using System; +using System.Windows; using System.Windows.Controls; using NUnit.Framework; @@ -8,19 +9,24 @@ namespace Test1 { [TestFixture] - public class WPFTest + public class WPFTest : IWeakEventListener { [Test] - public void WithoutFramework() + public void AssertPass() { Assert.Pass(); } - [Test] - public void WithFramework() + [Test, Apartment(System.Threading.ApartmentState.STA)] + public void CreateCheckBox() { - //CheckBox checkbox; + CheckBox checkbox; + checkbox = new CheckBox(); + } + public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + throw new NotImplementedException(); } } } \ No newline at end of file From 1d0b96a055780958d50121daa98fa7c702a8c53d Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 16 Sep 2024 17:04:08 -0700 Subject: [PATCH 100/190] Upgrade to use C# 11 --- src/Directory.Build.props | 2 +- .../nunit3-console.tests/nunit3-console.tests.csproj | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2168a2e85..983510678 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ ..\..\..\bin\$(Configuration)\ true - 7 + 11 true NUnit Software Copyright (c) 2022 Charlie Poole, Rob Prouse diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index a668e6134..19d6f2950 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -23,8 +23,6 @@ - From e1c975190f7dd2e70d3afcc36f7966cc1ed1f767 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 17 Sep 2024 07:31:20 -0700 Subject: [PATCH 101/190] Upgrade to use C# 11 --- src/Directory.Build.props | 2 +- .../nunit3-console.tests/nunit3-console.tests.csproj | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2168a2e85..983510678 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ ..\..\..\bin\$(Configuration)\ true - 7 + 11 true NUnit Software Copyright (c) 2022 Charlie Poole, Rob Prouse diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index a668e6134..19d6f2950 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -23,8 +23,6 @@ - From cf1dea6195d3db710ef918dced75baeef26ef6e9 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 17 Sep 2024 09:34:03 -0700 Subject: [PATCH 102/190] Place output from TestData projects under a separate directory --- NetFXTests.nunit | 8 ++-- package-tests.cake | 46 +++++++++---------- .../nunit.engine.core.tests.csproj | 1 + .../InvalidTestNames/InvalidTestNames.csproj | 1 + src/TestData/WpfApp/WpfApp.csproj | 2 +- .../aspnetcore-test/aspnetcore-test.csproj | 5 +- .../mock-assembly-x86.csproj | 1 + .../mock-assembly/mock-assembly.csproj | 2 +- .../notest-assembly/notest-assembly.csproj | 2 +- .../windows-forms-test.csproj | 1 + src/TestData/wpf-test/WpfTest.csproj | 1 + 11 files changed, 38 insertions(+), 32 deletions(-) diff --git a/NetFXTests.nunit b/NetFXTests.nunit index ea8bbd140..9c26ee71b 100644 --- a/NetFXTests.nunit +++ b/NetFXTests.nunit @@ -1,11 +1,11 @@  - - + + - - + + \ No newline at end of file diff --git a/package-tests.cake b/package-tests.cake index dc7ff9f1c..bf0465e05 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -35,31 +35,31 @@ class MockAssemblyExpectedResult : ExpectedResult StandardRunnerTests.Add(new PackageTest( 1, "Net462Test", "Run mock-assembly.dll under .NET 4.6.2", - "net462/mock-assembly.dll", + "testdata/net462/mock-assembly.dll", new MockAssemblyExpectedResult("net-4.6.2"))); AddToBothLists(new PackageTest( 1, "Net80Test", "Run mock-assembly.dll under .NET 8.0", - "net8.0/mock-assembly.dll", + "testdata/net8.0/mock-assembly.dll", new MockAssemblyExpectedResult("netcore-8.0"))); AddToBothLists(new PackageTest( 1, "Net70Test", "Run mock-assembly.dll under .NET 7.0", - "net7.0/mock-assembly.dll", + "testdata/net7.0/mock-assembly.dll", new MockAssemblyExpectedResult("netcore-7.0"))); AddToBothLists(new PackageTest( 1, "Net60Test", "Run mock-assembly.dll under .NET 6.0", - "net6.0/mock-assembly.dll", + "testdata/net6.0/mock-assembly.dll", new MockAssemblyExpectedResult("netcore-6.0"))); AddToBothLists(new PackageTest( 1, "NetCore31Test", "Run mock-assembly.dll under .NET Core 3.1", - "netcoreapp3.1/mock-assembly.dll", + "testdata/netcoreapp3.1/mock-assembly.dll", new MockAssemblyExpectedResult("netcore-3.1"))); ////////////////////////////////////////////////////////////////////// @@ -83,7 +83,7 @@ class MockAssemblyX86ExpectedResult : MockAssemblyExpectedResult StandardRunnerTests.Add(new PackageTest( 1, "Net462X86Test", "Run mock-assembly-x86.dll under .NET 4.6.2", - "net462/mock-assembly-x86.dll", + "testdata/net462/mock-assembly-x86.dll", new MockAssemblyX86ExpectedResult("net-4.6.2"))); if (dotnetX86Available) @@ -96,27 +96,27 @@ if (dotnetX86Available) StandardRunnerTests.Add(new PackageTest( 1, "Net80X86Test", "Run mock-assembly-x86.dll under .NET 8.0", - "net8.0/mock-assembly-x86.dll", + "testdata/net8.0/mock-assembly-x86.dll", new MockAssemblyX86ExpectedResult("netcore-8.0"))); if (!onAppVeyor && !onGitHubActions) StandardRunnerTests.Add(new PackageTest( 1, "Net70X86Test", "Run mock-assembly-x86.dll under .NET 7.0", - "net7.0/mock-assembly-x86.dll", + "testdata/net7.0/mock-assembly-x86.dll", new MockAssemblyX86ExpectedResult("netcore-7.0"))); StandardRunnerTests.Add(new PackageTest( 1, "Net60X86Test", "Run mock-assembly-x86.dll under .NET 6.0", - "net6.0/mock-assembly-x86.dll", + "testdata/net6.0/mock-assembly-x86.dll", new MockAssemblyX86ExpectedResult("netcore-6.0"))); if (!onAppVeyor && !onGitHubActions) StandardRunnerTests.Add(new PackageTest( 1, "NetCore31X86Test", "Run mock-assembly-x86.dll under .NET Core 3.1", - "netcoreapp3.1/mock-assembly-x86.dll", + "testdata/netcoreapp3.1/mock-assembly-x86.dll", new MockAssemblyX86ExpectedResult("netcore-3.1"))); } @@ -127,19 +127,19 @@ if (dotnetX86Available) StandardRunnerTests.Add(new PackageTest( 1, "Net462PlusNet462Test", "Run two copies of mock-assembly together", - "net462/mock-assembly.dll net462/mock-assembly.dll", + "testdata/net462/mock-assembly.dll testdata/net462/mock-assembly.dll", new MockAssemblyExpectedResult("net-4.6.2", "net-4.6.2"))); StandardRunnerTests.Add(new PackageTest( 1, "Net60PlusNet80Test", "Run mock-assembly under .NET6.0 and 8.0 together", - "net6.0/mock-assembly.dll net8.0/mock-assembly.dll", + "testdata/net6.0/mock-assembly.dll testdata/net8.0/mock-assembly.dll", new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0"))); StandardRunnerTests.Add(new PackageTest( 1, "Net462PlusNet60Test", "Run mock-assembly under .Net Framework 4.6.2 and .Net 6.0 together", - "net462/mock-assembly.dll net6.0/mock-assembly.dll", + "testdata/net462/mock-assembly.dll testdata/net6.0/mock-assembly.dll", new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"))); ////////////////////////////////////////////////////////////////////// @@ -148,7 +148,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "Net60AspNetCoreTest", "Run test using AspNetCore targeting .NET 6.0", - "net6.0/aspnetcore-test.dll", new ExpectedResult("Passed") + "testdata/net6.0/aspnetcore-test.dll", new ExpectedResult("Passed") { Total = 3, Passed = 3, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-6.0") } @@ -156,7 +156,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "Net80AspNetCoreTest", "Run test using AspNetCore targeting .NET 8.0", - "net8.0/aspnetcore-test.dll", new ExpectedResult("Passed") + "testdata/net8.0/aspnetcore-test.dll", new ExpectedResult("Passed") { Total = 3, Passed = 3, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-8.0") } @@ -168,7 +168,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "Net60WindowsFormsTest", "Run test using windows forms under .NET 6.0", - "net6.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") + "testdata/net6.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") { Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("windows-forms-test.dll", "netcore-6.0") } @@ -176,7 +176,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "Net80WindowsFormsTest", "Run test using windows forms under .NET 8.0", - "net8.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") + "testdata/net8.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") { Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("windows-forms-test.dll", "netcore-8.0") } @@ -188,12 +188,12 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "Net60WPFTest", "Run test using WPF under .NET 6.0", - "net6.0-windows/WpfTest.dll --trace=Debug", + "testdata/net6.0-windows/WpfTest.dll --trace=Debug", new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-6.0") } })); StandardRunnerTests.Add(new PackageTest( 1, "Net80WPFTest", "Run test using WPF under .NET 8.0", - "net8.0-windows/WpfTest.dll --trace=Debug", + "testdata/net8.0-windows/WpfTest.dll --trace=Debug", new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-8.0") } })); ////////////////////////////////////////////////////////////////////// @@ -210,7 +210,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "V2ResultWriterTest", "Run mock-assembly under .NET 6.0 and produce V2 output", - "net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", + "testdata/net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", new MockAssemblyExpectedResult("netcore-6.0"), KnownExtensions.NUnitV2ResultWriter.SetVersion("3.8.0"))); @@ -255,7 +255,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "InvalidTestNameTest_Net462", "Ensure we handle invalid test names correctly under .NET 4.6.2", - "net462/InvalidTestNames.dll --trace:Debug", + "testdata/net462/InvalidTestNames.dll --trace:Debug", new ExpectedResult("Passed") { Assemblies = new ExpectedAssemblyResult[] @@ -267,7 +267,7 @@ StandardRunnerTests.Add(new PackageTest( AddToBothLists(new PackageTest( 1, "InvalidTestNameTest_Net60", "Ensure we handle invalid test names correctly under .NET 6.0", - "net6.0/InvalidTestNames.dll --trace:Debug", + "testdata/net6.0/InvalidTestNames.dll --trace:Debug", new ExpectedResult("Passed") { Assemblies = new ExpectedAssemblyResult[] @@ -279,7 +279,7 @@ AddToBothLists(new PackageTest( AddToBothLists(new PackageTest( 1, "InvalidTestNameTest_Net80", "Ensure we handle invalid test names correctly under .NET 8.0", - "net8.0/InvalidTestNames.dll --trace:Debug", + "testdata/net8.0/InvalidTestNames.dll --trace:Debug", new ExpectedResult("Passed") { Assemblies = new ExpectedAssemblyResult[] diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index c89e0b00b..bef0e53c6 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -35,6 +35,7 @@ + diff --git a/src/TestData/InvalidTestNames/InvalidTestNames.csproj b/src/TestData/InvalidTestNames/InvalidTestNames.csproj index d08f867db..35d836912 100644 --- a/src/TestData/InvalidTestNames/InvalidTestNames.csproj +++ b/src/TestData/InvalidTestNames/InvalidTestNames.csproj @@ -2,6 +2,7 @@ net462;net6.0;net8.0 + ..\..\..\bin\$(Configuration)\testdata\ Library diff --git a/src/TestData/WpfApp/WpfApp.csproj b/src/TestData/WpfApp/WpfApp.csproj index 0dee5c4b5..dd182afd4 100644 --- a/src/TestData/WpfApp/WpfApp.csproj +++ b/src/TestData/WpfApp/WpfApp.csproj @@ -3,7 +3,7 @@ WinExe net8.0-windows - ..\..\bin\$(Configuration)\ + ..\..\..\bin\$(Configuration)\testdata\ true diff --git a/src/TestData/aspnetcore-test/aspnetcore-test.csproj b/src/TestData/aspnetcore-test/aspnetcore-test.csproj index 46fe89800..0ad4cf31d 100644 --- a/src/TestData/aspnetcore-test/aspnetcore-test.csproj +++ b/src/TestData/aspnetcore-test/aspnetcore-test.csproj @@ -2,8 +2,9 @@ net6.0;net8.0 - Library - false + ..\..\..\bin\$(Configuration)\testdata\ + Library + false diff --git a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj index 62f67b383..c020eeacc 100644 --- a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj @@ -3,6 +3,7 @@ NUnit.Tests net462;netcoreapp3.1;net6.0;net7.0;net8.0 + ..\..\..\bin\$(Configuration)\testdata\ true ..\..\nunit.snk x86 diff --git a/src/TestData/mock-assembly/mock-assembly.csproj b/src/TestData/mock-assembly/mock-assembly.csproj index 4b793f39b..c2289731b 100644 --- a/src/TestData/mock-assembly/mock-assembly.csproj +++ b/src/TestData/mock-assembly/mock-assembly.csproj @@ -3,7 +3,7 @@ NUnit.Tests net462;netcoreapp3.1;net6.0;net7.0;net8.0 - ..\..\..\bin\$(Configuration)\ + ..\..\..\bin\$(Configuration)\testdata\ true ..\..\nunit.snk false diff --git a/src/TestData/notest-assembly/notest-assembly.csproj b/src/TestData/notest-assembly/notest-assembly.csproj index a96bbd8be..277df29b8 100644 --- a/src/TestData/notest-assembly/notest-assembly.csproj +++ b/src/TestData/notest-assembly/notest-assembly.csproj @@ -3,7 +3,7 @@ notest_assembly net462;netstandard2.0;netcoreapp3.1 - ..\..\..\bin\$(Configuration)\ + ..\..\..\bin\$(Configuration)\testdata\ false false diff --git a/src/TestData/windows-forms-test/windows-forms-test.csproj b/src/TestData/windows-forms-test/windows-forms-test.csproj index 9ad93a3c3..1161098ec 100644 --- a/src/TestData/windows-forms-test/windows-forms-test.csproj +++ b/src/TestData/windows-forms-test/windows-forms-test.csproj @@ -2,6 +2,7 @@ net6.0-windows;net8.0-windows + ..\..\..\bin\$(Configuration)\testdata\ true false diff --git a/src/TestData/wpf-test/WpfTest.csproj b/src/TestData/wpf-test/WpfTest.csproj index a4c0ff808..a04564b47 100644 --- a/src/TestData/wpf-test/WpfTest.csproj +++ b/src/TestData/wpf-test/WpfTest.csproj @@ -2,6 +2,7 @@ net6.0-windows;net8.0-windows + ..\..\..\bin\$(Configuration)\testdata\ true From 1089e1416d745f76ccbaeb2a54cce2b29bcb100b Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 17 Sep 2024 20:18:17 -0700 Subject: [PATCH 103/190] Update tests to NUnit 4.1.0 and 3.14.0 --- .../nunit3-console.tests/CommandLineTests.cs | 20 +++++----- .../OutputSpecificationTests.cs | 4 +- .../ResultReporterTests.cs | 2 +- .../nunit3-console.tests.csproj | 6 +-- .../Internal/DirectoryFinderTests.cs | 38 +++++++++---------- .../Default/DirectoryTests.cs | 24 ++++++------ .../Default/DirectoryTests2.cs | 10 ++--- .../Default/FileSystemTests.cs | 4 +- .../FileSystemAccess/Default/FileTests.cs | 8 ++-- .../Internal/PathUtilTests.cs | 6 +-- .../Runners/DomainManagerTests.cs | 4 +- .../Services/ExtensionManagerTests.cs | 4 +- .../nunit.engine.core.tests.csproj | 14 +++++-- .../Api/ServiceLocatorTests.cs | 2 +- .../Api/TestPackageTests.cs | 2 +- .../Internal/SettingsGroupTests.cs | 10 ++--- .../Services/AgentProcessTests.cs | 8 ++-- .../Services/RecentFilesTests.cs | 2 +- .../Services/ResultServiceTests.cs | 2 +- .../Transport/Tcp/TcpServerTests.cs | 4 +- .../nunit.engine.tests.csproj | 14 +++++-- .../InvalidTestNames/InvalidTestNames.csproj | 2 +- .../mock-assembly-x86.csproj | 9 ++++- .../mock-assembly/mock-assembly.csproj | 8 +++- .../notest-assembly/notest-assembly.csproj | 8 +++- 25 files changed, 120 insertions(+), 95 deletions(-) diff --git a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs index 54af95333..cf69e7c5e 100644 --- a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs @@ -99,7 +99,7 @@ public void GetArgsFromFiles(string commandline, string files, params string[] e // Then Assert.That(expandedArgs, Is.EqualTo(expectedArgs)); - Assert.IsEmpty(options.ErrorMessages); + Assert.That(options.ErrorMessages, Is.Empty); } [TestCase("--arg1 @file1.txt --arg2", "The file \"file1.txt\" was not found.")] @@ -406,7 +406,7 @@ public void ResultOptionWithFilePath() var spec = options.ResultOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("nunit3")); - Assert.Null(spec.Transform); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -420,7 +420,7 @@ public void ResultOptionWithFilePathAndFormat() var spec = options.ResultOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("nunit2")); - Assert.Null(spec.Transform); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -479,12 +479,12 @@ public void ResultOptionMayBeRepeated() var spec1 = specs[0]; Assert.That(spec1.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec1.Format, Is.EqualTo("nunit3")); - Assert.Null(spec1.Transform); + Assert.That(spec1.Transform, Is.Null); var spec2 = specs[1]; Assert.That(spec2.OutputPath, Is.EqualTo("nunit2results.xml")); Assert.That(spec2.Format, Is.EqualTo("nunit2")); - Assert.Null(spec2.Transform); + Assert.That(spec2.Transform, Is.Null); var spec3 = specs[2]; Assert.That(spec3.OutputPath, Is.EqualTo("myresult.xml")); @@ -502,7 +502,7 @@ public void DefaultResultSpecification() var spec = options.ResultOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("TestResult.xml")); Assert.That(spec.Format, Is.EqualTo("nunit3")); - Assert.Null(spec.Transform); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -562,7 +562,7 @@ public void ExploreOptionWithFilePath() var spec = options.ExploreOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("nunit3")); - Assert.Null(spec.Transform); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -577,7 +577,7 @@ public void ExploreOptionWithFilePathAndFormat() var spec = options.ExploreOutputSpecifications[0]; Assert.That(spec.OutputPath, Is.EqualTo("results.xml")); Assert.That(spec.Format, Is.EqualTo("cases")); - Assert.Null(spec.Transform); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -869,14 +869,14 @@ private static IFileSystem GetFileSystemContainingFile(string fileName) private static FieldInfo GetFieldInfo(string fieldName) { FieldInfo field = typeof(ConsoleOptions).GetField(fieldName); - Assert.IsNotNull(field, "The field '{0}' is not defined", fieldName); + Assert.That(field, Is.Not.Null, "The field '{0}' is not defined", fieldName); return field; } private static PropertyInfo GetPropertyInfo(string propertyName) { PropertyInfo property = typeof(ConsoleOptions).GetProperty(propertyName); - Assert.IsNotNull(property, "The property '{0}' is not defined", propertyName); + Assert.That(property, Is.Not.Null, "The property '{0}' is not defined", propertyName); return property; } diff --git a/src/NUnitConsole/nunit3-console.tests/OutputSpecificationTests.cs b/src/NUnitConsole/nunit3-console.tests/OutputSpecificationTests.cs index 8d7bb3299..871c53f5c 100644 --- a/src/NUnitConsole/nunit3-console.tests/OutputSpecificationTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/OutputSpecificationTests.cs @@ -40,7 +40,7 @@ public void FileNameOnly() var spec = new Spec("MyFile.xml", null); Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); Assert.That(spec.Format, Is.EqualTo("nunit3")); - Assert.Null(spec.Transform); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -49,7 +49,7 @@ public void FileNamePlusFormat() var spec = new Spec("MyFile.xml;format=nunit2", null); Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); Assert.That(spec.Format, Is.EqualTo("nunit2")); - Assert.Null(spec.Transform); + Assert.That(spec.Transform, Is.Null); } [Test] diff --git a/src/NUnitConsole/nunit3-console.tests/ResultReporterTests.cs b/src/NUnitConsole/nunit3-console.tests/ResultReporterTests.cs index 3594c66a2..e25db6983 100644 --- a/src/NUnitConsole/nunit3-console.tests/ResultReporterTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/ResultReporterTests.cs @@ -42,7 +42,7 @@ public void CreateResult() var engineResult = AddMetadata(new TestEngineResult(xmlText)); _result = engineResult.Xml; - Assert.NotNull(_result, "Unable to create report result."); + Assert.That(_result, Is.Not.Null, "Unable to create report result."); } [SetUp] diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index 19d6f2950..71b66bae2 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/DirectoryFinderTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/DirectoryFinderTests.cs index a8ccf177c..6ce73b26b 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/DirectoryFinderTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/DirectoryFinderTests.cs @@ -109,7 +109,7 @@ public void GetDirectories_Asterisk_Tools() var result = finder.GetDirectories(baseDir, "*"); var actual = result.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Received().GetDirectories("*", SIO.SearchOption.TopDirectoryOnly); this.GetFakeDirectory("tools", "frobuscator").DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); @@ -126,7 +126,7 @@ public void GetDirectories_Asterisk_Metamorphosator() var result = finder.GetDirectories(baseDir, "*"); var actual = result.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Received().GetDirectories(Arg.Any(), Arg.Any()); this.GetFakeDirectory("tools", "frobuscator").DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); @@ -144,7 +144,7 @@ public void GetDirectories_Greedy_Tools() var result = finder.GetDirectories(baseDir, "**"); var actual = result.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Received().GetDirectories("*", SIO.SearchOption.AllDirectories); this.GetFakeDirectory("tools", "frobuscator").DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); @@ -171,7 +171,7 @@ public void GetDirectories_Greedy_Metamorphosator() var result = finder.GetDirectories(baseDir, "**"); var actual = result.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Received().GetDirectories("*", SIO.SearchOption.AllDirectories); this.GetFakeDirectory("tools", "frobuscator").DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); @@ -207,7 +207,7 @@ public void GetDirectories_WordWithWildcard_OneMatch(string pattern) var result = finder.GetDirectories(baseDir, pattern); var actual = result.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Received().GetDirectories(pattern, SIO.SearchOption.TopDirectoryOnly); } @@ -225,7 +225,7 @@ public void GetDirectories_WordWithWildcard_MultipleMatches(string pattern) var result = finder.GetDirectories(baseDir, pattern); var actual = result.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Received().GetDirectories(pattern, SIO.SearchOption.TopDirectoryOnly); } @@ -285,7 +285,7 @@ public void GetDirectories_MultipleComponents_MultipleMatches(string pattern) var result = finder.GetDirectories(baseDir, pattern); var actual = result.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); } [TestCase("*/tests/v*")] @@ -307,7 +307,7 @@ public void GetDirectories_MultipleComponents_MultipleMatches_Asterisk(string pa var result = finder.GetDirectories(baseDir, pattern); var actual = result.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); testsDir.Received().GetDirectories("v*", SIO.SearchOption.TopDirectoryOnly); } @@ -331,7 +331,7 @@ public void GetDirectories_MultipleComponents_MultipleMatches_QuestionMark(strin var result = finder.GetDirectories(baseDir, pattern); var actual = result.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); testsDir.Received().GetDirectories("v?", SIO.SearchOption.TopDirectoryOnly); } @@ -346,7 +346,7 @@ public void GetDirectories_MultipleComponents_AllDirectories(string pattern) var actual = finder.GetDirectories(baseDir, pattern); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Received().GetDirectories("*", SIO.SearchOption.AllDirectories); foreach (var dir in this.fakedDirectories.Values.Where(x => x != baseDir)) @@ -375,7 +375,7 @@ public void GetDirectories_GreedyThenWordThenGreedy() var actual = finder.GetDirectories(baseDir, "**/tests/**"); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Received().GetDirectories("*", SIO.SearchOption.AllDirectories); this.GetFakeDirectory("tools", "frobuscator").Received().GetDirectories("tests", SIO.SearchOption.TopDirectoryOnly); @@ -396,7 +396,7 @@ public void GetDirectories_WordWithAsteriskThenGreedyThenWord() var actual = finder.GetDirectories(baseDir, "meta*/**/v1"); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Received().GetDirectories("meta*", SIO.SearchOption.TopDirectoryOnly); this.GetFakeDirectory("tools", "frobuscator").DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); @@ -413,7 +413,7 @@ public void GetDirectories_Parent() var actual = finder.GetDirectories(baseDir, "../"); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); } @@ -429,7 +429,7 @@ public void GetDirectories_ParentThenParentThenWordThenWord() var actual = finder.GetDirectories(baseDir, "../../metamorphosator/addins"); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); baseDir.Parent.Parent.Received().GetDirectories("metamorphosator", SIO.SearchOption.TopDirectoryOnly); @@ -476,7 +476,7 @@ public void GetFiles_WordWithWildcard(string pattern) var actual = finder.GetFiles(baseDir, pattern); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Received().GetFiles(pattern); baseDir.Parent.DidNotReceive().GetFiles(Arg.Any()); } @@ -497,7 +497,7 @@ public void GetFiles_AsteriskThenWordWithWildcard(string pattern) var actual = finder.GetFiles(baseDir, pattern); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); abcDir.Received().GetFiles(filePattern); defDir.Received().GetFiles(filePattern); baseDir.Parent.DidNotReceive().GetFiles(Arg.Any()); @@ -534,7 +534,7 @@ public void GetFiles_GreedyThenWordWithWildcard(string pattern) var actual = finder.GetFiles(baseDir, pattern); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); foreach (var dir in this.fakedDirectories.Values.Where(x => x.FullName != GetRoot())) { dir.Received().GetFiles(filePattern); @@ -561,7 +561,7 @@ public void GetFiles_WordThenParentThenWordWithWildcardThenWord() var actual = finder.GetFiles(baseDir, pattern); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); targetDir.Received().GetFiles(filename); foreach (var dir in this.fakedDirectories.Values.Where(x => x != targetDir)) { @@ -583,7 +583,7 @@ public void GetFiles_CurrentDirThenAsterisk() var actual = finder.GetFiles(baseDir, pattern); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); baseDir.Received().GetFiles("*"); baseDir.Parent.DidNotReceive().GetFiles(Arg.Any()); baseDir.Parent.DidNotReceive().GetDirectories(Arg.Any(), Arg.Any()); diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/DirectoryTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/DirectoryTests.cs index 33fefe374..34efad3f2 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/DirectoryTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/DirectoryTests.cs @@ -18,8 +18,8 @@ public void Init() var directory = new Directory(path); - Assert.AreEqual(path, directory.FullName); - Assert.AreEqual(parent, directory.Parent.FullName); + Assert.That(directory.FullName, Is.EqualTo(path)); + Assert.That(directory.Parent.FullName, Is.EqualTo(parent)); } [Test] @@ -53,8 +53,8 @@ public void Init_TrailingDirectorySeparator() var directory = new Directory(path); - Assert.AreEqual(path, directory.FullName); - Assert.AreEqual(parent, directory.Parent.FullName); + Assert.That(directory.FullName, Is.EqualTo(path)); + Assert.That(directory.Parent.FullName, Is.EqualTo(parent)); } // Skip this test on non-Windows systems since System.IO.DirectoryInfo appends '\\server\share' to the current working-directory, making this test useless. @@ -64,8 +64,8 @@ public void Init_NoParent_SMB() var path = "\\\\server\\share"; var directory = new Directory(path); - Assert.AreEqual(path, directory.FullName); - Assert.IsNull(directory.Parent); + Assert.That(directory.FullName, Is.EqualTo(path)); + Assert.That(directory.Parent, Is.Null); } // Skip this test on non-Windows systems since System.IO.DirectoryInfo appends 'x:\' to the current working-directory, making this test useless. @@ -75,8 +75,8 @@ public void Init_NoParent_Drive() var path = "x:\\"; var directory = new Directory(path); - Assert.AreEqual(path, directory.FullName); - Assert.IsNull(directory.Parent); + Assert.That(directory.FullName, Is.EqualTo(path)); + Assert.That(directory.Parent, Is.Null); } [Test] @@ -87,8 +87,8 @@ public void Init_NoParent_Root() var directory = new Directory(path); - Assert.AreEqual(expected, directory.FullName); - Assert.IsNull(directory.Parent); + Assert.That(directory.FullName, Is.EqualTo(expected)); + Assert.That(directory.Parent, Is.Null); } [Test] @@ -101,7 +101,7 @@ public void GetFiles() var actualFiles = directory.GetFiles("*"); var actual = actualFiles.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); } [Test] @@ -127,7 +127,7 @@ public void GetFiles_WithPattern() var actualFiles = directory.GetFiles("*.dll"); var actual = actualFiles.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); } [Test] diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/DirectoryTests2.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/DirectoryTests2.cs index a3a3fa951..4bf258235 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/DirectoryTests2.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/DirectoryTests2.cs @@ -68,7 +68,7 @@ public void GetDirectories() var actualDirectories = directory.GetDirectories("*", SIO.SearchOption.TopDirectoryOnly); var actual = actualDirectories.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); } [Test] @@ -79,7 +79,7 @@ public void GetDirectories_AllSubDirectories() var actualDirectories = directory.GetDirectories("*", SIO.SearchOption.AllDirectories); var actual = actualDirectories.Select(x => x.FullName); - CollectionAssert.AreEquivalent(this.subDirectories, actual); + Assert.That(actual, Is.EquivalentTo(this.subDirectories)); } [Test] @@ -91,7 +91,7 @@ public void GetDirectories_WithPattern() var actualDirectories = directory.GetDirectories("a??", SIO.SearchOption.TopDirectoryOnly); var actual = actualDirectories.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); } [Test] @@ -101,7 +101,7 @@ public void GetDirectories_WithPattern_NoMatch() var actual = directory.GetDirectories("z*", SIO.SearchOption.AllDirectories); - CollectionAssert.IsEmpty(actual); + Assert.That(actual, Is.Empty); } [Test] @@ -113,7 +113,7 @@ public void GetDirectories_WithPattern_AllSubDirectories() var actualDirectories = directory.GetDirectories("?e?", SIO.SearchOption.AllDirectories); var actual = actualDirectories.Select(x => x.FullName); - CollectionAssert.AreEquivalent(expected, actual); + Assert.That(actual, Is.EquivalentTo(expected)); } } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/FileSystemTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/FileSystemTests.cs index 5b7da4c76..16073b1a7 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/FileSystemTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/FileSystemTests.cs @@ -45,8 +45,8 @@ public void GetFile() var file = fileSystem.GetFile(path); - Assert.AreEqual(path, file.FullName); - Assert.AreEqual(parent, file.Parent.FullName); + Assert.That(file.FullName, Is.EquivalentTo(path)); + Assert.That(file.Parent.FullName, Is.EqualTo(parent)); } [Test] diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/FileTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/FileTests.cs index 64d32562e..61b46623a 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/FileTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/FileSystemAccess/Default/FileTests.cs @@ -19,8 +19,8 @@ public void Init() var file = new File(path); - Assert.AreEqual(path, file.FullName); - Assert.AreEqual(parent, file.Parent.FullName); + Assert.That(file.FullName, Is.EqualTo(path)); + Assert.That(file.Parent.FullName, Is.EqualTo(parent)); } [Test] @@ -75,8 +75,8 @@ public void Init_NonExistingFile() var file = new File(path); - Assert.AreEqual(path, file.FullName); - Assert.AreEqual(parent, file.Parent.FullName); + Assert.That(file.FullName, Is.EqualTo(path)); + Assert.That(file.Parent.FullName, Is.EqualTo(parent)); } [Test] diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/PathUtilTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/PathUtilTests.cs index c8d6fd1d4..485aeade8 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/PathUtilTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/PathUtilTests.cs @@ -166,11 +166,11 @@ public void RelativePath() @"c:\folder1", @"c:\folder2\folder3" ), Is.EqualTo(@"..\folder2\folder3")); Assert.That(PathUtils.RelativePath( @"c:\folder1", @"bin\debug" ), Is.EqualTo(@"bin\debug")); - Assert.IsNull( PathUtils.RelativePath( @"C:\folder", @"D:\folder" ), + Assert.That( PathUtils.RelativePath( @"C:\folder", @"D:\folder"), Is.Null, "Unrelated paths should return null" ); - Assert.IsNull(PathUtils.RelativePath(@"C:\", @"D:\"), + Assert.That(PathUtils.RelativePath(@"C:\", @"D:\"), Is.Null, "Unrelated roots should return null"); - Assert.IsNull(PathUtils.RelativePath(@"C:", @"D:"), + Assert.That(PathUtils.RelativePath(@"C:", @"D:"), Is.Null, "Unrelated roots (no trailing separators) should return null"); Assert.That(PathUtils.RelativePath(@"C:\folder1", @"C:\folder1"), Is.EqualTo(string.Empty)); Assert.That(PathUtils.RelativePath(@"C:\", @"C:\"), Is.EqualTo(string.Empty)); diff --git a/src/NUnitEngine/nunit.engine.core.tests/Runners/DomainManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Runners/DomainManagerTests.cs index 11009aa9f..8ca9c4faa 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Runners/DomainManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Runners/DomainManagerTests.cs @@ -30,7 +30,7 @@ public void CanCreateDomain() { var domain = _domainManager.CreateDomain(_package); - Assert.NotNull(domain); + Assert.That(domain, Is.Not.Null); var setup = domain.SetupInformation; Assert.That(setup.ApplicationName, Does.StartWith("Tests_")); @@ -54,7 +54,7 @@ public void CanCreateDomainWithApplicationBaseSpecified() _package.Settings["BasePath"] = basePath; var domain = _domainManager.CreateDomain(_package); - Assert.NotNull(domain); + Assert.That(domain, Is.Not.Null); var setup = domain.SetupInformation; Assert.That(setup.ApplicationName, Does.StartWith("Tests_")); diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index fc1523f85..f1568f764 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -92,7 +92,7 @@ public void CanGetExtensionPointByPath( [ValueSource(nameof(KnownExtensionPointTypes))] Type type) { var ep = _extensionManager.GetExtensionPoint(path); - Assert.NotNull(ep); + Assert.That(ep, Is.Not.Null); Assert.That(ep.Path, Is.EqualTo(path)); Assert.That(ep.TypeName, Is.EqualTo(type.FullName)); } @@ -103,7 +103,7 @@ public void CanGetExtensionPointByType( [ValueSource(nameof(KnownExtensionPointTypes))] Type type) { var ep = _extensionManager.GetExtensionPoint(type); - Assert.NotNull(ep); + Assert.That(ep, Is.Not.Null); Assert.That(ep.Path, Is.EqualTo(path)); Assert.That(ep.TypeName, Is.EqualTo(type.FullName)); } diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index bef0e53c6..f3601b719 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -17,19 +17,25 @@ Tests of nunit.engine.core assembly - - - + + + + + + + + + - + diff --git a/src/NUnitEngine/nunit.engine.tests/Api/ServiceLocatorTests.cs b/src/NUnitEngine/nunit.engine.tests/Api/ServiceLocatorTests.cs index fd0f120d9..d6767ea9c 100644 --- a/src/NUnitEngine/nunit.engine.tests/Api/ServiceLocatorTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Api/ServiceLocatorTests.cs @@ -27,7 +27,7 @@ public void TearDown() public void CanAccessService(Type serviceType) { IService service = _testEngine.Services.GetService(serviceType) as IService; - Assert.NotNull(service, "GetService(Type) returned null"); + Assert.That(service, Is.Not.Null, "GetService(Type) returned null"); Assert.That(service, Is.InstanceOf(serviceType)); Assert.That(service.Status, Is.EqualTo(ServiceStatus.Started)); } diff --git a/src/NUnitEngine/nunit.engine.tests/Api/TestPackageTests.cs b/src/NUnitEngine/nunit.engine.tests/Api/TestPackageTests.cs index 84ed3b3bc..cfe1cbfe9 100644 --- a/src/NUnitEngine/nunit.engine.tests/Api/TestPackageTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Api/TestPackageTests.cs @@ -54,7 +54,7 @@ public void CreatePackage() [Test] public void PackageIsAnonymous() { - Assert.Null(package.FullName); + Assert.That(package.FullName, Is.Null); } [Test] diff --git a/src/NUnitEngine/nunit.engine.tests/Internal/SettingsGroupTests.cs b/src/NUnitEngine/nunit.engine.tests/Internal/SettingsGroupTests.cs index 435ccdb31..c427017b6 100644 --- a/src/NUnitEngine/nunit.engine.tests/Internal/SettingsGroupTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Internal/SettingsGroupTests.cs @@ -23,8 +23,8 @@ public void BeforeEachTest() [Test] public void WhenSettingIsNotInitialized_NullIsReturned() { - Assert.IsNull(settings.GetSetting("X")); - Assert.IsNull(settings.GetSetting("NAME")); + Assert.That(settings.GetSetting("X"), Is.Null); + Assert.That(settings.GetSetting("NAME"), Is.Null); } [TestCase("X", 5)] @@ -37,7 +37,7 @@ public void WhenSettingIsInitialized_ValueIsReturned(string name, object expecte settings.SaveSetting(name, expected); object actual = settings.GetSetting(name); Assert.That(actual, Is.EqualTo(expected)); - Assert.IsInstanceOf(expected.GetType(),actual); + Assert.That(actual, Is.InstanceOf(expected.GetType())); } private enum PriorityValue @@ -54,11 +54,11 @@ public void WhenSettingIsRemoved_NullIsReturnedAndOtherSettingsAreNotAffected() settings.SaveSetting("NAME", "Charlie"); settings.RemoveSetting("X"); - Assert.IsNull(settings.GetSetting("X"), "X not removed"); + Assert.That(settings.GetSetting("X"), Is.Null, "X not removed"); Assert.That(settings.GetSetting("NAME"), Is.EqualTo("Charlie")); settings.RemoveSetting("NAME"); - Assert.IsNull(settings.GetSetting("NAME"), "NAME not removed"); + Assert.That(settings.GetSetting("NAME"), Is.Null, "NAME not removed"); } [Test] diff --git a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs index 89edc696d..33e7ee8a5 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs @@ -64,12 +64,12 @@ public void DefaultValues(string framework) Assert.That(process.AgentArgs.ToString(), Is.EqualTo(REQUIRED_ARGS)); - Assert.True(process.EnableRaisingEvents, "EnableRaisingEvents"); + Assert.That(process.EnableRaisingEvents, "EnableRaisingEvents"); var startInfo = process.StartInfo; - Assert.False(startInfo.UseShellExecute, "UseShellExecute"); - Assert.True(startInfo.CreateNoWindow, "CreateNoWindow"); - Assert.False(startInfo.LoadUserProfile, "LoadUserProfile"); + Assert.That(startInfo.UseShellExecute, Is.False, "UseShellExecute"); + Assert.That(startInfo.CreateNoWindow, "CreateNoWindow"); + Assert.That(startInfo.LoadUserProfile, Is.False, "LoadUserProfile"); var targetRuntime = RuntimeFramework.Parse(framework); if (targetRuntime.Runtime == RuntimeType.Mono) diff --git a/src/NUnitEngine/nunit.engine.tests/Services/RecentFilesTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/RecentFilesTests.cs index 314757890..5e9223d52 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/RecentFilesTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/RecentFilesTests.cs @@ -36,7 +36,7 @@ public void ServiceIsStarted() [Test] public void EmptyList() { - Assert.IsNotNull( _recentFiles.Entries, "Entries should never be null" ); + Assert.That(_recentFiles.Entries, Is.Not.Null, "Entries should never be null"); Assert.That(_recentFiles.Entries.Count, Is.EqualTo(0)); } diff --git a/src/NUnitEngine/nunit.engine.tests/Services/ResultServiceTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/ResultServiceTests.cs index 44143a269..1c9872391 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/ResultServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/ResultServiceTests.cs @@ -41,7 +41,7 @@ public string CanGetWriter(string format, object[] args) { var writer = _resultService.GetResultWriter(format, args); - Assert.NotNull(writer); + Assert.That(writer, Is.Not.Null); return writer.GetType().Name; } diff --git a/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs b/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs index 45ff3eb53..9fbcc825e 100644 --- a/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs @@ -43,7 +43,7 @@ public void SingleClientConnection() Assert.That(_serverConnections.Count, Is.EqualTo(1), "Should have received 1 connection event"); Assert.That(_serverConnections[0].Connected, "Server is not connected to client"); - Assert.True(client.Connected, "Client is not connected to server"); + Assert.That(client.Connected, "Client is not connected to server"); } } @@ -65,7 +65,7 @@ public void MultipleClientConnections() for (int i = 0; i < num; i++) { Assert.That(_serverConnections[i].Connected, $"Server is not connected to client {i+1}"); - Assert.True(clients[i].Connected, $"Client {i+1} is not connected to server"); + Assert.That(clients[i].Connected, $"Client {i+1} is not connected to server"); } } } diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index 52e36ebdd..7414c0f2e 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -17,12 +17,18 @@ Tests of nunit.engine assembly - - - + + + - + + + + + + + diff --git a/src/TestData/InvalidTestNames/InvalidTestNames.csproj b/src/TestData/InvalidTestNames/InvalidTestNames.csproj index 35d836912..071365c2d 100644 --- a/src/TestData/InvalidTestNames/InvalidTestNames.csproj +++ b/src/TestData/InvalidTestNames/InvalidTestNames.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj index c020eeacc..8d665d8ed 100644 --- a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj @@ -17,9 +17,14 @@ Assembly used in testing the engine - - + + + + + + + diff --git a/src/TestData/mock-assembly/mock-assembly.csproj b/src/TestData/mock-assembly/mock-assembly.csproj index c2289731b..31f9bcb83 100644 --- a/src/TestData/mock-assembly/mock-assembly.csproj +++ b/src/TestData/mock-assembly/mock-assembly.csproj @@ -16,8 +16,12 @@ Assembly used in testing the engine - - + + + + + + diff --git a/src/TestData/notest-assembly/notest-assembly.csproj b/src/TestData/notest-assembly/notest-assembly.csproj index 277df29b8..2228cbcd9 100644 --- a/src/TestData/notest-assembly/notest-assembly.csproj +++ b/src/TestData/notest-assembly/notest-assembly.csproj @@ -14,8 +14,12 @@ Assembly with NonTestAssembly attribute used in testing the engine - - + + + + + + \ No newline at end of file From 55d30d7a11284cca12b57e0a5adab6fb3a790724 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 17 Sep 2024 20:25:00 -0700 Subject: [PATCH 104/190] Upgrade from NUnit 4.1.0 to 4.2.2 --- .../nunit3-console.tests/nunit3-console.tests.csproj | 4 ++-- .../nunit.engine.core.tests/nunit.engine.core.tests.csproj | 4 ++-- src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj | 4 ++-- src/TestData/InvalidTestNames/InvalidTestNames.csproj | 2 +- src/TestData/aspnetcore-test/aspnetcore-test.csproj | 2 +- src/TestData/mock-assembly-x86/mock-assembly-x86.csproj | 2 +- src/TestData/mock-assembly/mock-assembly.csproj | 2 +- src/TestData/notest-assembly/notest-assembly.csproj | 2 +- src/TestData/windows-forms-test/windows-forms-test.csproj | 2 +- src/TestData/wpf-test/WpfTest.csproj | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index 71b66bae2..ec2a90e9e 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -17,8 +17,8 @@ - - + + diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index f3601b719..5a6350858 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index 7414c0f2e..23ac9be3f 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/src/TestData/InvalidTestNames/InvalidTestNames.csproj b/src/TestData/InvalidTestNames/InvalidTestNames.csproj index 071365c2d..7ac8e0a38 100644 --- a/src/TestData/InvalidTestNames/InvalidTestNames.csproj +++ b/src/TestData/InvalidTestNames/InvalidTestNames.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/TestData/aspnetcore-test/aspnetcore-test.csproj b/src/TestData/aspnetcore-test/aspnetcore-test.csproj index 0ad4cf31d..c6d0f546f 100644 --- a/src/TestData/aspnetcore-test/aspnetcore-test.csproj +++ b/src/TestData/aspnetcore-test/aspnetcore-test.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj index 8d665d8ed..58003c7be 100644 --- a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/TestData/mock-assembly/mock-assembly.csproj b/src/TestData/mock-assembly/mock-assembly.csproj index 31f9bcb83..4c7613a41 100644 --- a/src/TestData/mock-assembly/mock-assembly.csproj +++ b/src/TestData/mock-assembly/mock-assembly.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/TestData/notest-assembly/notest-assembly.csproj b/src/TestData/notest-assembly/notest-assembly.csproj index 2228cbcd9..76194de1c 100644 --- a/src/TestData/notest-assembly/notest-assembly.csproj +++ b/src/TestData/notest-assembly/notest-assembly.csproj @@ -19,7 +19,7 @@ - + \ No newline at end of file diff --git a/src/TestData/windows-forms-test/windows-forms-test.csproj b/src/TestData/windows-forms-test/windows-forms-test.csproj index 1161098ec..5e16e9243 100644 --- a/src/TestData/windows-forms-test/windows-forms-test.csproj +++ b/src/TestData/windows-forms-test/windows-forms-test.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/TestData/wpf-test/WpfTest.csproj b/src/TestData/wpf-test/WpfTest.csproj index a04564b47..9df05e4b0 100644 --- a/src/TestData/wpf-test/WpfTest.csproj +++ b/src/TestData/wpf-test/WpfTest.csproj @@ -7,7 +7,7 @@ - + From 64fb37505b103c5ca773fa990e6f86cea43f779d Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 19 Sep 2024 07:46:46 -0700 Subject: [PATCH 105/190] Add info about @FILE to help options; refactor ShowHelp() --- .../nunit3-console/ColorConsole.cs | 5 + .../nunit3-console/Options/Options.cs | 5 +- src/NUnitConsole/nunit3-console/Program.cs | 177 ++++++++++-------- 3 files changed, 104 insertions(+), 83 deletions(-) diff --git a/src/NUnitConsole/nunit3-console/ColorConsole.cs b/src/NUnitConsole/nunit3-console/ColorConsole.cs index 84ca437b3..babc890c5 100644 --- a/src/NUnitConsole/nunit3-console/ColorConsole.cs +++ b/src/NUnitConsole/nunit3-console/ColorConsole.cs @@ -39,6 +39,11 @@ public static ConsoleColor GetColor(ColorStyle style) return color; } + public static void SetForeground(ColorStyle style) + { + Console.ForegroundColor = GetColorForStyle(style); + } + private static ConsoleColor GetColorForStyle(ColorStyle style) { switch (Console.BackgroundColor) diff --git a/src/NUnitConsole/nunit3-console/Options/Options.cs b/src/NUnitConsole/nunit3-console/Options/Options.cs index 1d7dc20ce..e85baf5c9 100644 --- a/src/NUnitConsole/nunit3-console/Options/Options.cs +++ b/src/NUnitConsole/nunit3-console/Options/Options.cs @@ -1352,8 +1352,9 @@ public void WriteOptionDescriptions(TextWriter o) o.Write(new string(' ', OptionWidth)); } - WriteDescription(o, p.Description, new string(' ', OptionWidth + 2), + WriteDescription(o, p.Description, new string(' ', OptionWidth), Description_FirstWidth, Description_RemWidth); + o.WriteLine(); } foreach (ArgumentSource s in sources) @@ -1406,7 +1407,7 @@ void WriteDescription(TextWriter o, string value, string prefix, int firstWidth, { if (indent) o.Write(prefix); - o.WriteLine(line); + o.WriteLine(line.Trim()); indent = true; } } diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index 827358005..06e8a96b1 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -2,9 +2,11 @@ using System; using System.Diagnostics; +using System.Drawing; using System.Globalization; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Text; @@ -189,102 +191,115 @@ private static void WriteHelpText() OutWriter.WriteLine(ColorStyle.Header, "NUNIT3-CONSOLE [inputfiles] [options]"); OutWriter.WriteLine(); OutWriter.WriteLine(ColorStyle.SectionHeader, "Description:"); - using (new ColorConsole(ColorStyle.Default)) - { #if NETFRAMEWORK - OutWriter.WriteLine(" The standard NUnit Console Runner runs a set of NUnit tests from the"); - OutWriter.WriteLine(" console command-line. By default, all tests are run using separate agents"); - OutWriter.WriteLine(" for each test assembly. This allows each assembly to run independently"); - OutWriter.WriteLine(" and allows each assembly to run under the appropriate target runtime."); + OutWriter.WriteLine(ColorStyle.Default, """ + The standard NUnit Console Runner runs a set of NUnit tests from the + console command-line. By default, all tests are run using separate agents + for each test assembly. This allows each assembly to run independently + and allows each assembly to run under the appropriate target runtime. + + """); #else - OutWriter.WriteLine(" The NetCore Console Runner runs a set of NUnit tests from the console"); - OutWriter.WriteLine(" command-line. All tests are run in-process and therefore execute under"); - OutWriter.WriteLine(" the same runtime as the runner itself. A number of options supported by"); - OutWriter.WriteLine(" the standard console runner are not available using the NetCore runner."); - OutWriter.WriteLine(" See \"Limitations\" below for more information."); + OutWriter.WriteLine(ColorStyle.Default, """ + The NetCore Console Runner runs a set of NUnit tests from the console + command-line. All tests are run in-process and therefore execute under + the same runtime as the runner itself. A number of options supported by + the standard console runner are not available using the NetCore runner. + See \"Limitations\" below for more information. + + """); #endif - } - OutWriter.WriteLine(); OutWriter.WriteLine(ColorStyle.SectionHeader, "InputFiles:"); OutWriter.WriteLine(ColorStyle.Default, " One or more assemblies or test projects of a recognized type."); OutWriter.WriteLine(); OutWriter.WriteLine(ColorStyle.SectionHeader, "Options:"); using (new ColorConsole(ColorStyle.Default)) { + OutWriter.WriteLine(""" + @FILE Specifies the name(or path) of a FILE containing + additional command-line arguments to be inserted + at the point where the @FILE expression appears. + Each line in the file represents one argument + to the console runner. If an option takes a value, + that value must appear on the same line. + + """); + Options.WriteOptionDescriptions(Console.Out); } OutWriter.WriteLine(); OutWriter.WriteLine(ColorStyle.SectionHeader, "Operation:"); - using (new ColorConsole(ColorStyle.Default)) - { - OutWriter.WriteLine(" By default, this command runs the tests contained in the"); - OutWriter.WriteLine(" assemblies and projects specified. If the --explore option"); - OutWriter.WriteLine(" is used, no tests are executed but a description of the tests"); - OutWriter.WriteLine(" is saved in the specified or default format."); - OutWriter.WriteLine(); - OutWriter.WriteLine(" The --where option is intended to extend or replace the earlier"); - OutWriter.WriteLine(" --test, --include and --exclude options by use of a selection expression"); - OutWriter.WriteLine(" describing exactly which tests to use. Examples of usage are:"); - OutWriter.WriteLine(" --where:cat==Data"); - OutWriter.WriteLine(" --where \"method =~ /DataTest*/ && cat = Slow\""); - OutWriter.WriteLine(); - OutWriter.WriteLine(" Care should be taken in combining --where with --test or --testlist."); - OutWriter.WriteLine(" The test and where specifications are implicitly joined using &&, so"); - OutWriter.WriteLine(" that BOTH sets of criteria must be satisfied in order for a test to run."); - OutWriter.WriteLine(" See the docs for more information and a full description of the syntax"); - OutWriter.WriteLine(" information and a full description of the syntax."); - OutWriter.WriteLine(); - OutWriter.WriteLine(" Several options that specify processing of XML output take"); - OutWriter.WriteLine(" an output specification as a value. A SPEC may take one of"); - OutWriter.WriteLine(" the following forms:"); - OutWriter.WriteLine(" --OPTION:filename"); - OutWriter.WriteLine(" --OPTION:filename;format=formatname"); - OutWriter.WriteLine(" --OPTION:filename;transform=xsltfile"); - OutWriter.WriteLine(); - OutWriter.WriteLine(" The --result option may use any of the following formats:"); - OutWriter.WriteLine(" nunit3 - the native XML format for NUnit 3"); - OutWriter.WriteLine(" nunit2 - legacy XML format used by earlier releases of NUnit"); - OutWriter.WriteLine(" Requires the engine extension NUnitV2ResultWriter."); - OutWriter.WriteLine(); - OutWriter.WriteLine(" The --explore option may use any of the following formats:"); - OutWriter.WriteLine(" nunit3 - the native XML format for NUnit 3"); - OutWriter.WriteLine(" cases - a text file listing the full names of all test cases."); - OutWriter.WriteLine(" If --explore is used without any specification following, a list of"); - OutWriter.WriteLine(" test cases is output to the writer."); - OutWriter.WriteLine(); - OutWriter.WriteLine(" If none of the options {--result, --explore, --noresult} is used,"); - OutWriter.WriteLine(" NUnit saves the results to TestResult.xml in nunit3 format."); - OutWriter.WriteLine(); - OutWriter.WriteLine(" Any transforms provided must handle input in the native nunit3 format."); - OutWriter.WriteLine(); - OutWriter.WriteLine(" To be able to load NUnit projects, file type .nunit, the engine"); - OutWriter.WriteLine(" extension NUnitProjectLoader is required. For Visual Studio projects"); - OutWriter.WriteLine(" and solutions the engine extension VSProjectLoader is required."); + OutWriter.WriteLine(ColorStyle.Default, """ + By default, this command runs the tests contained in the"); + assemblies and projects specified. If the --explore option"); + is used, no tests are executed but a description of the tests"); + is saved in the specified or default format. + + The --where option is intended to extend or replace the earlier + --test, --include and --exclude options by use of a selection expression + describing exactly which tests to use. Examples of usage are: + --where:cat==Data + --where \"method =~ /DataTest*/ && cat = Slow\" + + Care should be taken in combining --where with --test or --testlist. + The test and where specifications are implicitly joined using &&, so + that BOTH sets of criteria must be satisfied in order for a test to run. + See the docs for more information and a full description of the syntax + information and a full description of the syntax. + + Several options that specify processing of XML output take + an output specification as a value. A SPEC may take one of + the following forms: + --OPTION:filename + --OPTION:filename;format=formatname + --OPTION:filename;transform=xsltfile + + The --result option may use any of the following formats + nunit3 - the native XML format for NUnit 3 + nunit2 - legacy XML format used by earlier releases of NUnit + Requires the engine extension NUnitV2ResultWriter. + + The --explore option may use any of the following formats: + nunit3 - the native XML format for NUnit 3 + cases - a text file listing the full names of all test cases. + If --explore is used without any specification following, a list of + test cases is output to the writer. + + If none of the options {--result, --explore, --noresult} is used, + NUnit saves the results to TestResult.xml in nunit3 format. + + Any transforms provided must handle input in the native nunit3 format. + + To be able to load NUnit projects, file type .nunit, the engine + extension NUnitProjectLoader is required. For Visual Studio projects + and solutions the engine extension VSProjectLoader is required. + """); #if NETCOREAPP - OutWriter.WriteLine(); - OutWriter.WriteLine(ColorStyle.SectionHeader, "Limitations:"); - OutWriter.WriteLine(" The NetCore Runner is primarily intended for use as a dotnet tool."); - OutWriter.WriteLine(" When used in this way, a single assembly is usually being tested and"); - OutWriter.WriteLine(" the assembly must be compatible with execution under the same runtime"); - OutWriter.WriteLine(" as the runner itself, normally .NET 6.0."); - OutWriter.WriteLine(); - OutWriter.WriteLine(" Using this runner, the following options are not available. A brief"); - OutWriter.WriteLine(" rationale is given for each option excluded."); - OutWriter.WriteLine(" --configFile Config of the runner itself is used."); - OutWriter.WriteLine(" --process Not designed to run out of process."); - OutWriter.WriteLine(" --inprocess Redundant. We always run in process."); - OutWriter.WriteLine(" --domain Not applicable to .NET Core."); - OutWriter.WriteLine(" --framework Runtime of the runner is used."); - OutWriter.WriteLine(" --x86 Bitness of the runner is used."); - OutWriter.WriteLine(" --shadowcopy Not available."); - OutWriter.WriteLine(" --loaduserprofile Not available."); - OutWriter.WriteLine(" --agents No agents are used."); - OutWriter.WriteLine(" --debug Debug in process directly."); - OutWriter.WriteLine(" --pause Used for debugging agents."); - OutWriter.WriteLine(" --set-principal-policy Not available."); - OutWriter.WriteLine(" --debug-agent No agents are used."); + OutWriter.WriteLine(); + OutWriter.WriteLine(ColorStyle.SectionHeader, "Limitations:"); + OutWriter.WriteLine(ColorStyle.Default, """ + The NetCore Runner is primarily intended for use as a dotnet tool. + When used in this way, a single assembly is usually being tested and + the assembly must be compatible with execution under the same runtime + as the runner itself, normally .NET 6.0. + + Using this runner, the following options are not available. A brief + rationale is given for each option excluded. + --configFile Config of the runner itself is used. + --process Not designed to run out of process. + --inprocess Redundant. We always run in process. + --domain Not applicable to .NET Core. + --framework Runtime of the runner is used. + --x86 Bitness of the runner is used. + --shadowcopy Not available. + --loaduserprofile Not available. + --agents No agents are used. + --debug Debug in process directly. + --pause Used for debugging agents. + --set-principal-policy Not available. + --debug-agent No agents are used. + """); #endif - } } private static void CancelHandler(object sender, ConsoleCancelEventArgs args) From c0bf8561def2811e1244d2d28e96fc90a15a2972 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 30 Sep 2024 08:16:24 -0700 Subject: [PATCH 106/190] Set `AppContext.BaseDirectory` when running under .NET 8.0 --- NUnitConsole.sln | 9 ++++++++- package-tests.cake | 13 +++++++++++- .../nunit.engine.core.tests/AppContextTest.cs | 20 +++++++++++++++++++ .../Internal/TestAssemblyLoadContext.cs | 4 +++- src/TestData/AppContextTest/AppContextTest.cs | 20 +++++++++++++++++++ .../AppContextTest/AppContextTest.csproj | 16 +++++++++++++++ 6 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core.tests/AppContextTest.cs create mode 100644 src/TestData/AppContextTest/AppContextTest.cs create mode 100644 src/TestData/AppContextTest/AppContextTest.csproj diff --git a/NUnitConsole.sln b/NUnitConsole.sln index c197b9a6a..8e9350066 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -142,7 +142,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "src\Test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "src\TestData\WpfApp\WpfApp.csproj", "{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvalidTestNames", "src\TestData\InvalidTestNames\InvalidTestNames.csproj", "{58E18ACC-1F7E-4395-817E-E7EF943E0C77}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InvalidTestNames", "src\TestData\InvalidTestNames\InvalidTestNames.csproj", "{58E18ACC-1F7E-4395-817E-E7EF943E0C77}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppContextTest", "src\TestData\AppContextTest\AppContextTest.csproj", "{E43A3E4B-B050-471B-B43C-0DF60FD44376}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -218,6 +220,10 @@ Global {58E18ACC-1F7E-4395-817E-E7EF943E0C77}.Debug|Any CPU.Build.0 = Debug|Any CPU {58E18ACC-1F7E-4395-817E-E7EF943E0C77}.Release|Any CPU.ActiveCfg = Release|Any CPU {58E18ACC-1F7E-4395-817E-E7EF943E0C77}.Release|Any CPU.Build.0 = Release|Any CPU + {E43A3E4B-B050-471B-B43C-0DF60FD44376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E43A3E4B-B050-471B-B43C-0DF60FD44376}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E43A3E4B-B050-471B-B43C-0DF60FD44376}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E43A3E4B-B050-471B-B43C-0DF60FD44376}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -251,6 +257,7 @@ Global {81E63A90-3191-4E99-92FF-01F9B1D3E3C5} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {58E18ACC-1F7E-4395-817E-E7EF943E0C77} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {E43A3E4B-B050-471B-B43C-0DF60FD44376} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/package-tests.cake b/package-tests.cake index bf0465e05..728891fd3 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -250,7 +250,9 @@ StandardRunnerTests.Add(new PackageTest( MockAssemblySolutionResult, KnownExtensions.VSProjectLoader.SetVersion("3.9.0"))); -// Special Cases +////////////////////////////////////////////////////////////////////// +// SPECIAL CASES +////////////////////////////////////////////////////////////////////// StandardRunnerTests.Add(new PackageTest( 1, "InvalidTestNameTest_Net462", @@ -287,3 +289,12 @@ AddToBothLists(new PackageTest( new ExpectedAssemblyResult("InvalidTestNames.dll", "netcore-8.0") } })); + +StandardRunnerTests.Add(new PackageTest( + 1, "AppContextBaseDirectory_NET80", + "Test Setting the BaseDirectory to match test assembly location under .NET 8.0", + "testdata/net8.0/AppContextTest.dll", + new ExpectedResult("Passed") + { + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("AppContextTest.dll", "netcore-8.0") } + })); diff --git a/src/NUnitEngine/nunit.engine.core.tests/AppContextTest.cs b/src/NUnitEngine/nunit.engine.core.tests/AppContextTest.cs new file mode 100644 index 000000000..e9b96d3ec --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/AppContextTest.cs @@ -0,0 +1,20 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.IO; +using NUnit.Framework; + +namespace NUnit.Engine.Core.Tests +{ + public class AppContextTest + { + [Test] + public void VerifyBasePath() + { + var thisAssembly = GetType().Assembly; + var expectedPath = Path.GetDirectoryName(GetType().Assembly.Location); + + Assert.That(AppContext.BaseDirectory, Is.SamePath(expectedPath)); + } + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index 66a218e47..3dd54cd52 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -24,6 +24,9 @@ public TestAssemblyLoadContext(string testAssemblyPath) _resolver = new TestAssemblyResolver(this, testAssemblyPath); _basePath = Path.GetDirectoryName(testAssemblyPath); _runtimeResolver = new AssemblyDependencyResolver(testAssemblyPath); +#if NET8_0_OR_GREATER + AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", _basePath); +#endif } protected override Assembly Load(AssemblyName name) @@ -37,7 +40,6 @@ protected override Assembly Load(AssemblyName name) return loadedAssembly; } - var runtimeResolverPath = _runtimeResolver.ResolveAssemblyToPath(name); if (string.IsNullOrEmpty(runtimeResolverPath) == false && File.Exists(runtimeResolverPath)) diff --git a/src/TestData/AppContextTest/AppContextTest.cs b/src/TestData/AppContextTest/AppContextTest.cs new file mode 100644 index 000000000..e9b96d3ec --- /dev/null +++ b/src/TestData/AppContextTest/AppContextTest.cs @@ -0,0 +1,20 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.IO; +using NUnit.Framework; + +namespace NUnit.Engine.Core.Tests +{ + public class AppContextTest + { + [Test] + public void VerifyBasePath() + { + var thisAssembly = GetType().Assembly; + var expectedPath = Path.GetDirectoryName(GetType().Assembly.Location); + + Assert.That(AppContext.BaseDirectory, Is.SamePath(expectedPath)); + } + } +} diff --git a/src/TestData/AppContextTest/AppContextTest.csproj b/src/TestData/AppContextTest/AppContextTest.csproj new file mode 100644 index 000000000..2ff431284 --- /dev/null +++ b/src/TestData/AppContextTest/AppContextTest.csproj @@ -0,0 +1,16 @@ + + + + net462;netcoreapp3.1;net6.0;net8.0 + ..\..\..\bin\$(Configuration)\testdata\ + + + + + + + + + + + From 6aa879a8e361365ed701cd42aaa3812ac14abfe9 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 1 Oct 2024 17:07:50 -0700 Subject: [PATCH 107/190] Reinstante teamcity extension as part of our packaging --- build.cake | 2 +- nuget/runners/nunit.console-runner-with-extensions.nuspec | 8 ++++---- package-tests.cake | 7 +++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/build.cake b/build.cake index 8036092b4..7be220651 100644 --- a/build.cake +++ b/build.cake @@ -126,7 +126,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { new PackageReference("NUnit.Extension.NUnitProjectLoader", "3.8.0"), new PackageReference("NUnit.Extension.NUnitV2Driver", "3.9.0"), new PackageReference("NUnit.Extension.NUnitV2ResultWriter", "3.8.0"), - new PackageReference("NUnit.Extension.TeamCityEventListener", "1.0.9") + new PackageReference("NUnit.Extension.TeamCityEventListener", "1.0.7") }), // NOTE: Packages below this point have no direct tests diff --git a/nuget/runners/nunit.console-runner-with-extensions.nuspec b/nuget/runners/nunit.console-runner-with-extensions.nuspec index 660e55bad..94ffdd643 100644 --- a/nuget/runners/nunit.console-runner-with-extensions.nuspec +++ b/nuget/runners/nunit.console-runner-with-extensions.nuspec @@ -32,10 +32,10 @@ - - - - + + + + diff --git a/package-tests.cake b/package-tests.cake index 728891fd3..a0b9e9986 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -250,6 +250,13 @@ StandardRunnerTests.Add(new PackageTest( MockAssemblySolutionResult, KnownExtensions.VSProjectLoader.SetVersion("3.9.0"))); +StandardRunnerTests.Add(new PackageTest( + 1, "TeamCityListenerTest", + "Run mock-assembly with --teamcity enabled", + "testdata/net462/mock-assembly.dll --teamcity", + new MockAssemblyExpectedResult("net-4.6.2"), + new ExtensionSpecifier("NUnit.Extension.TeamCityEventListener", "nunit-extension-teamcity-event-listener", "1.0.7"))); + ////////////////////////////////////////////////////////////////////// // SPECIAL CASES ////////////////////////////////////////////////////////////////////// From fd9ff7434b760b39c89750f5a5c6082e7f01c776 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 2 Oct 2024 11:57:03 -0700 Subject: [PATCH 108/190] Create net80 runner --- NUnitConsole.sln | 7 ++ build.cake | 9 +++ nuget/runners/net8.0/DotnetToolSettings.xml | 6 ++ .../runners/nunit.console-runner.net80.nuspec | 53 +++++++++++++ .../Services/DriverServiceTests.cs | 78 ++++++++++++------- .../RuntimeLocators/NetCoreRuntimeLocator.cs | 2 +- 6 files changed, 128 insertions(+), 27 deletions(-) create mode 100644 nuget/runners/net8.0/DotnetToolSettings.xml create mode 100644 nuget/runners/nunit.console-runner.net80.nuspec diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 8e9350066..f8919dc54 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -66,6 +66,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runners", "runners", "{F3E8 ProjectSection(SolutionItems) = preProject nuget\runners\DotnetToolSettings.xml = nuget\runners\DotnetToolSettings.xml nuget\runners\nunit.console-runner-with-extensions.nuspec = nuget\runners\nunit.console-runner-with-extensions.nuspec + nuget\runners\nunit.console-runner.net80.nuspec = nuget\runners\nunit.console-runner.net80.nuspec nuget\runners\nunit.console-runner.netcore.nuspec = nuget\runners\nunit.console-runner.netcore.nuspec nuget\runners\nunit.console-runner.nuspec = nuget\runners\nunit.console-runner.nuspec nuget\runners\nunit.console.nuget.addins = nuget\runners\nunit.console.nuget.addins @@ -146,6 +147,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InvalidTestNames", "src\Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppContextTest", "src\TestData\AppContextTest\AppContextTest.csproj", "{E43A3E4B-B050-471B-B43C-0DF60FD44376}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net8.0", "net8.0", "{303CF83E-2A87-4882-8CAC-3EB59AAD81FC}" + ProjectSection(SolutionItems) = preProject + nuget\runners\net8.0\DotnetToolSettings.xml = nuget\runners\net8.0\DotnetToolSettings.xml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -258,6 +264,7 @@ Global {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {58E18ACC-1F7E-4395-817E-E7EF943E0C77} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {E43A3E4B-B050-471B-B43C-0DF60FD44376} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {303CF83E-2A87-4882-8CAC-3EB59AAD81FC} = {F3E87D0F-6F06-4C0B-AE06-42C0834C3C6E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/build.cake b/build.cake index 7be220651..ed7f68201 100644 --- a/build.cake +++ b/build.cake @@ -45,6 +45,7 @@ FilePath[] AGENT_PDB_FILES_NETCORE = { PackageDefinition NUnitConsoleNuGetPackage; PackageDefinition NUnitConsoleRunnerNuGetPackage; PackageDefinition NUnitConsoleRunnerNetCorePackage; +PackageDefinition NUnitConsoleRunnerNet80Package; PackageDefinition NUnitEnginePackage; PackageDefinition NUnitEngineApiPackage; PackageDefinition NUnitConsoleRunnerChocolateyPackage; @@ -90,6 +91,14 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/nunit.exe"), tests: NetCoreRunnerTests), + NUnitConsoleRunnerNet80Package = new DotNetToolPackage( + id: "NUnit.ConsoleRunner.Net80", + source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.net80.nuspec", + checks: new PackageCheck[] { HasFiles("nunit-net80.exe") }, + testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + + $"NUnit.ConsoleRunner.Net80.{BuildSettings.PackageVersion}/nunit-net80.exe"), + tests: NetCoreRunnerTests), + NUnitConsoleRunnerChocolateyPackage = new ChocolateyPackage( id: "nunit-console-runner", source: BuildSettings.ChocolateyDirectory + "nunit-console-runner.nuspec", diff --git a/nuget/runners/net8.0/DotnetToolSettings.xml b/nuget/runners/net8.0/DotnetToolSettings.xml new file mode 100644 index 000000000..e49f98228 --- /dev/null +++ b/nuget/runners/net8.0/DotnetToolSettings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/nuget/runners/nunit.console-runner.net80.nuspec b/nuget/runners/nunit.console-runner.net80.nuspec new file mode 100644 index 000000000..00b997e5b --- /dev/null +++ b/nuget/runners/nunit.console-runner.net80.nuspec @@ -0,0 +1,53 @@ + + + + NUnit.ConsoleRunner.Net80 + NUnit Console Runner (.NET 8.0) + $version$ + Charlie Poole, Rob Prouse + Charlie Poole, Rob Prouse + LICENSE.txt + https://nunit.org + + https://cdn.rawgit.com/nunit/resources/master/images/icon/nunit_256.png + images\nunit_256.png + false + .NET Core build of the console runner for the NUnit unit-testing framework. + + This package includes the .NET 8.0 build of the NUnit console runner and test engine. + + Any extensions, if needed, may be installed as separate packages. + + https://docs.nunit.org/articles/nunit/release-notes/console-and-engine.html + en-US + nunit test testing tdd runner + Copyright (c) 2021-2024 Charlie Poole, Rob Prouse + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs index cb76a3872..e7a2ab637 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs @@ -30,37 +30,63 @@ public void ServiceIsStarted() Assert.That(_driverService.Status, Is.EqualTo(ServiceStatus.Started), "Failed to start service"); } + [TestCaseSource(nameof(DriverSelectionTestCases))] + public void CorrectDriverIsUsed(string fileName, bool skipNonTestAssemblies, Type expectedType) + { + var driver = _driverService.GetDriver(AppDomain.CurrentDomain, Path.Combine(TestContext.CurrentContext.TestDirectory, fileName), null, skipNonTestAssemblies); + Assert.That(driver, Is.InstanceOf(expectedType)); + } -#if NET5_0_OR_GREATER - [TestCase("mock-assembly.dll", false, typeof(NUnitNetCore31Driver))] - [TestCase("mock-assembly.dll", true, typeof(NUnitNetCore31Driver))] - //[TestCase("notest-assembly.dll", false, typeof(NUnitNetCore31Driver))] + static TestCaseData[] DriverSelectionTestCases = new[] + { +#if NETFRAMEWORK + new TestCaseData("mock-assembly.dll", false, typeof(NUnit3FrameworkDriver)), + new TestCaseData("mock-assembly.dll", true, typeof(NUnit3FrameworkDriver)), + new TestCaseData("notest-assembly.dll", false, typeof(NUnit3FrameworkDriver)), +#elif NET5_0_OR_GREATER + new TestCaseData("mock-assembly.dll", false, typeof(NUnitNetCore31Driver)), + new TestCaseData("mock-assembly.dll", true, typeof(NUnitNetCore31Driver)), + //new TestCaseData("notest-assembly.dll", false, typeof(NUnitNetCore31Driver)), #elif NETCOREAPP3_1 - [TestCase("mock-assembly.dll", false, typeof(NUnitNetCore31Driver))] - [TestCase("mock-assembly.dll", true, typeof(NUnitNetCore31Driver))] - [TestCase("notest-assembly.dll", false, typeof(NUnitNetCore31Driver))] + new TestCaseData("mock-assembly.dll", false, typeof(NUnitNetCore31Driver)), + new TestCaseData("mock-assembly.dll", true, typeof(NUnitNetCore31Driver)), + new TestCaseData("notest-assembly.dll", false, typeof(NUnitNetCore31Driver)), +// TODO: This is never used. We need to test net standard driver in some way, possibly +// by forcing it's use in a separate test. #elif NETCOREAPP2_1 - [TestCase("mock-assembly.dll", false, typeof(NUnitNetStandardDriver))] - [TestCase("mock-assembly.dll", true, typeof(NUnitNetStandardDriver))] - [TestCase("notest-assembly.dll", false, typeof(NUnitNetStandardDriver))] -#else - [TestCase("mock-assembly.dll", false, typeof(NUnit3FrameworkDriver))] - [TestCase("mock-assembly.dll", true, typeof(NUnit3FrameworkDriver))] - [TestCase("notest-assembly.dll", false, typeof(NUnit3FrameworkDriver))] -#endif - [TestCase("mock-assembly.pdb", false, typeof(InvalidAssemblyFrameworkDriver))] - [TestCase("mock-assembly.pdb", true, typeof(InvalidAssemblyFrameworkDriver))] - [TestCase("junk.dll", false, typeof(InvalidAssemblyFrameworkDriver))] - [TestCase("junk.dll", true, typeof(InvalidAssemblyFrameworkDriver))] - [TestCase("nunit.engine.core.dll", false, typeof(InvalidAssemblyFrameworkDriver))] - [TestCase("nunit.engine.core.dll", true, typeof(SkippedAssemblyFrameworkDriver))] -#if !NET5_0_OR_GREATER // Not yet working - [TestCase("notest-assembly.dll", true, typeof(SkippedAssemblyFrameworkDriver))] + new TestCaseData("mock-assembly.dll", false, typeof(NUnitNetStandardDriver)), + new TestCaseData("mock-assembly.dll", true, typeof(NUnitNetStandardDriver)), + new TestCaseData("notest-assembly.dll", false, typeof(NUnitNetStandardDriver)), #endif - public void CorrectDriverIsUsed(string fileName, bool skipNonTestAssemblies, Type expectedType) +// Invalid cases should work with all target runtimes + new TestCaseData("mock-assembly.pdb", false, typeof(InvalidAssemblyFrameworkDriver)), + new TestCaseData("mock-assembly.pdb", true, typeof(InvalidAssemblyFrameworkDriver)), + new TestCaseData("junk.dll", false, typeof(InvalidAssemblyFrameworkDriver)), + new TestCaseData("junk.dll", true, typeof(InvalidAssemblyFrameworkDriver)), + new TestCaseData("nunit.engine.core.dll", false, typeof(InvalidAssemblyFrameworkDriver)), + new TestCaseData("nunit.engine.core.dll", true, typeof(SkippedAssemblyFrameworkDriver)) +//#if !NET5_0_OR_GREATER // Not yet working +// new TestCaseData"notest-assembly.dll", true, typeof(SkippedAssemblyFrameworkDriver)) +//#endif + }; + + [Test] + public void EnsureWeHaveSomeValidTestCases() { - var driver = _driverService.GetDriver(AppDomain.CurrentDomain, Path.Combine(TestContext.CurrentContext.TestDirectory, fileName), null, skipNonTestAssemblies); - Assert.That(driver, Is.InstanceOf(expectedType)); + // We currently build these tests for net462, net 8.0, net 6.0 and net core 3.1. + // This test is needed because of the conditional compilation used in generating + // the test cases. If the test project is updated to add a new target runtime, + // and no test cases are added for that runtime, this test will fail. + foreach (var testcase in DriverSelectionTestCases) + { + // Third argument is the Type of the driver + var driverType = testcase.Arguments[2] as Type; + if (!(driverType.BaseType == typeof(NotRunnableFrameworkDriver))) + break; + + // All expected drivers derive from NotRunnableFrameworkDriver + Assert.Fail("Only invalid test cases were provided for this runtime. Update DriverServiceTests.cs to include some valid cases."); + } } } } diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs index 333e5db7b..9e94709b6 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs @@ -70,7 +70,7 @@ private static IEnumerable GetRuntimeList() } catch (Exception) { - // Failed to start dotnet command. Assume no versions are installed and just r eturn just return + // Failed to start dotnet command. Assume no versions are installed and just return yield break; } From 8d9a36b9022c120000b975a8d02c70a2951d1545 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 2 Oct 2024 22:30:33 -0700 Subject: [PATCH 109/190] Build Engine under .NET 6.0 and 8.0 --- src/NUnitEngine/nunit.engine.tests/Helpers/ShadowCopyUtils.cs | 2 ++ .../Integration/DirectoryWithNeededAssemblies.cs | 2 ++ .../Integration/MockAssemblyInDirectoryWithFramework.cs | 2 ++ .../Integration/RunnerInDirectoryWithoutFramework.cs | 2 ++ src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj | 4 ++-- src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs | 3 +++ src/NUnitEngine/nunit.engine/nunit.engine.csproj | 2 +- 7 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.tests/Helpers/ShadowCopyUtils.cs b/src/NUnitEngine/nunit.engine.tests/Helpers/ShadowCopyUtils.cs index 771491452..dd1d3a9a0 100644 --- a/src/NUnitEngine/nunit.engine.tests/Helpers/ShadowCopyUtils.cs +++ b/src/NUnitEngine/nunit.engine.tests/Helpers/ShadowCopyUtils.cs @@ -1,5 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +#if NETFRAMEWORK using System; using System.Collections.Generic; using System.IO; @@ -40,3 +41,4 @@ from assemblyName in assemblyNames } } } +#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.tests/Integration/DirectoryWithNeededAssemblies.cs b/src/NUnitEngine/nunit.engine.tests/Integration/DirectoryWithNeededAssemblies.cs index 23afea7e2..547e42c3c 100644 --- a/src/NUnitEngine/nunit.engine.tests/Integration/DirectoryWithNeededAssemblies.cs +++ b/src/NUnitEngine/nunit.engine.tests/Integration/DirectoryWithNeededAssemblies.cs @@ -1,5 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +#if NETFRAMEWORK using System; using System.IO; using NUnit.Engine.Tests.Helpers; @@ -31,3 +32,4 @@ public void Dispose() } } } +#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.tests/Integration/MockAssemblyInDirectoryWithFramework.cs b/src/NUnitEngine/nunit.engine.tests/Integration/MockAssemblyInDirectoryWithFramework.cs index 1e59a7939..d59b5cde8 100644 --- a/src/NUnitEngine/nunit.engine.tests/Integration/MockAssemblyInDirectoryWithFramework.cs +++ b/src/NUnitEngine/nunit.engine.tests/Integration/MockAssemblyInDirectoryWithFramework.cs @@ -1,5 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +#if NETFRAMEWORK using System; using System.IO; using NUnit.Framework; @@ -25,3 +26,4 @@ public void Dispose() } } } +#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.tests/Integration/RunnerInDirectoryWithoutFramework.cs b/src/NUnitEngine/nunit.engine.tests/Integration/RunnerInDirectoryWithoutFramework.cs index b7f5ddf69..088a02a1e 100644 --- a/src/NUnitEngine/nunit.engine.tests/Integration/RunnerInDirectoryWithoutFramework.cs +++ b/src/NUnitEngine/nunit.engine.tests/Integration/RunnerInDirectoryWithoutFramework.cs @@ -1,5 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +#if NETFRAMEWORK using System; using System.IO; using System.Threading; @@ -39,3 +40,4 @@ public void Dispose() } } } +#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index 23ac9be3f..3398bbcd8 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Tests - net462;netcoreapp3.1 + net462;netcoreapp3.1;net6.0;net8.0 Exe true ..\..\nunit.snk @@ -23,7 +23,7 @@ - + diff --git a/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs b/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs index 316d98381..ce623a191 100644 --- a/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs +++ b/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs @@ -33,6 +33,9 @@ public void OnTestEvent(string report) } } +#if NET6_0 || NET8_0 + [Obsolete] +#endif public override object InitializeLifetimeService() { return null; diff --git a/src/NUnitEngine/nunit.engine/nunit.engine.csproj b/src/NUnitEngine/nunit.engine/nunit.engine.csproj index ab802c56e..040bd46fe 100644 --- a/src/NUnitEngine/nunit.engine/nunit.engine.csproj +++ b/src/NUnitEngine/nunit.engine/nunit.engine.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net462;netstandard2.0 + net462;netstandard2.0;net6.0;net8.0 true ..\..\nunit.snk portable From c21d773483e6195a16561fa822d13303ab9283d3 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 4 Oct 2024 11:55:07 -0700 Subject: [PATCH 110/190] Fix packaging errors --- build.cake | 110 ++++++++++++++++++++++++++----- nuget/engine/nunit.engine.nuspec | 20 ++++++ 2 files changed, 115 insertions(+), 15 deletions(-) diff --git a/build.cake b/build.cake index ed7f68201..d310430b2 100644 --- a/build.cake +++ b/build.cake @@ -15,28 +15,31 @@ BuildSettings.Initialize( ////////////////////////////////////////////////////////////////////// // LISTS OF FILES USED IN CHECKING PACKAGES -////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +FilePath[] ConsoleFiles = { + "nunit3-console.dll", "nunit3-console.dll.config", "nunit3-console.exe", "nunit3-console.pdb", + "nunit3-console.deps.json", "nunit3-console.runtimeconfig.json", "nunit.console.nuget.addins" }; FilePath[] ENGINE_FILES = { - "nunit.engine.dll", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll" }; + "nunit.engine.dll", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll" }; FilePath[] ENGINE_PDB_FILES = { - "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; + "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; FilePath[] ENGINE_CORE_FILES = { - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll" }; + "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll" }; FilePath[] ENGINE_CORE_PDB_FILES = { - "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; + "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; FilePath[] AGENT_FILES = { - "nunit-agent.exe", "nunit-agent.exe.config", - "nunit-agent-x86.exe", "nunit-agent-x86.exe.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll"}; + "nunit-agent.exe", "nunit-agent.exe.config", + "nunit-agent-x86.exe", "nunit-agent-x86.exe.config", + "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll"}; FilePath[] AGENT_FILES_NETCORE = { - "nunit-agent.dll", "nunit-agent.dll.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll", - "Microsoft.Extensions.DependencyModel.dll"}; + "nunit-agent.dll", "nunit-agent.dll.config", + "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll", + "Microsoft.Extensions.DependencyModel.dll"}; FilePath[] AGENT_PDB_FILES = { - "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; + "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; FilePath[] AGENT_PDB_FILES_NETCORE = { - "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; + "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"}; ////////////////////////////////////////////////////////////////////// // INDIVIDUAL PACKAGE DEFINITIONS @@ -86,7 +89,12 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { NUnitConsoleRunnerNetCorePackage = new DotNetToolPackage( id: "NUnit.ConsoleRunner.NetCore", source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.netcore.nuspec", - checks: new PackageCheck[] { HasFiles("nunit.exe") }, + checks: new PackageCheck[] + { + HasFiles("nunit.exe"), + HasSomeDirectory(".store/nunit.consolerunner.netcore/**/tools/net6.0/any") + .WithFiles(ENGINE_FILES).AndFiles(ConsoleFiles).AndFile("Microsoft.Extensions.DependencyModel.dll") + }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/nunit.exe"), tests: NetCoreRunnerTests), @@ -94,7 +102,11 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { NUnitConsoleRunnerNet80Package = new DotNetToolPackage( id: "NUnit.ConsoleRunner.Net80", source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.net80.nuspec", - checks: new PackageCheck[] { HasFiles("nunit-net80.exe") }, + checks: new PackageCheck[] { + HasFiles("nunit-net80.exe"), + HasSomeDirectory(".store/nunit.consolerunner.net80/**/tools/net8.0/any") + .WithFiles(ENGINE_FILES).AndFiles(ConsoleFiles).AndFile("Microsoft.Extensions.DependencyModel.dll") + }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + $"NUnit.ConsoleRunner.Net80.{BuildSettings.PackageVersion}/nunit-net80.exe"), tests: NetCoreRunnerTests), @@ -147,13 +159,19 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasFiles("LICENSE.txt", "NOTICES.txt"), HasDirectory("lib/net462").WithFiles(ENGINE_FILES), HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_FILES), + HasDirectory("lib/net6.0").WithFiles(ENGINE_FILES).AndFile("Microsoft.Extensions.DependencyModel.dll"), + HasDirectory("lib/net8.0").WithFiles(ENGINE_FILES).AndFile("Microsoft.Extensions.DependencyModel.dll"), HasDirectory("contentFiles/any/lib/net462").WithFile("nunit.engine.nuget.addins"), HasDirectory("contentFiles/any/lib/netstandard2.0").WithFile("nunit.engine.nuget.addins"), + HasDirectory("contentFiles/any/lib/net6.0").WithFile("nunit.engine.nuget.addins"), + HasDirectory("contentFiles/any/lib/net8.0").WithFile("nunit.engine.nuget.addins"), HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins") }, symbols: new PackageCheck[] { HasDirectory("lib/net462").WithFiles(ENGINE_PDB_FILES), HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_PDB_FILES), + HasDirectory("lib/net6.0").WithFiles(ENGINE_PDB_FILES), + HasDirectory("lib/net8.0").WithFiles(ENGINE_PDB_FILES), HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_PDB_FILES) }), @@ -171,6 +189,68 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { }) }); +// Adhoc code to check content of a dotnet standalone executable +// TODO: Incorporate this in the recipe itself + +private static ExtendedDirectoryCheck HasSomeDirectory(string pattern) => new ExtendedDirectoryCheck(pattern); + +public class ExtendedDirectoryCheck : PackageCheck +{ + private string _directoryPattern; + private List _files = new List(); + + public ExtendedDirectoryCheck(string directoryPattern) + { + // Assume it has no wildcard - checked in ApplyTo method + _directoryPattern = directoryPattern; + } + + public ExtendedDirectoryCheck WithFiles(params FilePath[] files) + { + _files.AddRange(files); + return this; + } + + public ExtendedDirectoryCheck AndFiles(params FilePath[] files) + { + return WithFiles(files); + } + + public ExtendedDirectoryCheck WithFile(FilePath file) + { + _files.Add(file); + return this; + } + + public ExtendedDirectoryCheck AndFile(FilePath file) + { + return AndFiles(file); + } + + public override bool ApplyTo(DirectoryPath testDirPath) + { + if (_directoryPattern.Contains('*') || _directoryPattern.Contains('?')) // Wildcard + { + var absDirPattern = testDirPath.Combine(_directoryPattern).ToString(); + foreach (var dir in _context.GetDirectories(absDirPattern)) + { + // Use first one found + return CheckFilesExist(_files.Select(file => dir.CombineWithFilePath(file))); + } + } + else // No wildcard + { + var absDirPath = testDirPath.Combine(_directoryPattern); + if (!CheckDirectoryExists(absDirPath)) + return false; + + return CheckFilesExist(_files.Select(file => absDirPath.CombineWithFilePath(file))); + } + + return false; + } +} + ////////////////////////////////////////////////////////////////////// // TEST RUNNERS ////////////////////////////////////////////////////////////////////// diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index 54714f138..83f60a5cb 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -80,6 +80,26 @@ + + + + + + + + + + + + + + + + + + + + From f05b3d9a09e50d0a0c0498f1e02e9f3783cf46ad Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 5 Oct 2024 00:29:45 -0700 Subject: [PATCH 111/190] Honor DOTNET_ROOT environment variables --- .../nunit.engine.core/DotNetHelper.cs | 66 +++++++++++++++++++ .../Internal/TestAssemblyResolver.cs | 22 +------ .../nunit.engine/Services/AgentProcess.cs | 2 +- .../RuntimeLocators/NetCoreRuntimeLocator.cs | 23 +------ 4 files changed, 69 insertions(+), 44 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core/DotNetHelper.cs diff --git a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs new file mode 100644 index 000000000..bba07a275 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -0,0 +1,66 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using Microsoft.Win32; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace NUnit.Engine +{ + public static class DotNet + { + public static string GetInstallDirectory() => Environment.Is64BitProcess + ? GetX64InstallDirectory() : GetX86InstallDirectory(); + + public static string GetInstallDirectory(bool x86) => x86 + ? GetX86InstallDirectory() : GetX64InstallDirectory(); + + private static string _x64InstallDirectory; + public static string GetX64InstallDirectory() + { + if (_x64InstallDirectory == null) + _x64InstallDirectory = Environment.GetEnvironmentVariable("DOTNET_ROOT"); + + if (_x64InstallDirectory == null) + { +#if NETFRAMEWORK + if (Path.DirectorySeparatorChar == '\\') +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) +#endif + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); + _x64InstallDirectory = (string)key?.GetValue("Path"); + } + else + _x64InstallDirectory = "/usr/shared/dotnet/"; + } + + return _x64InstallDirectory; + } + + private static string _x86InstallDirectory; + public static string GetX86InstallDirectory() + { + if (_x86InstallDirectory == null) + _x86InstallDirectory = Environment.GetEnvironmentVariable("DOTNET_ROOT_X86"); + + if (_x86InstallDirectory == null) + { +#if NETFRAMEWORK + if (Path.DirectorySeparatorChar == '\\') +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) +#endif + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); + _x86InstallDirectory = (string)key?.GetValue("InstallLocation"); + } + else + _x86InstallDirectory = "/usr/shared/dotnet/"; + } + + return _x86InstallDirectory; + } + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index b913d2b6f..1d7059d05 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -31,7 +31,7 @@ internal sealed class TestAssemblyResolver : IDisposable static TestAssemblyResolver() { - INSTALL_DIR = GetDotNetInstallDirectory(); + INSTALL_DIR = DotNet.GetInstallDirectory(); WINDOWS_DESKTOP_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.WindowsDesktop.App"); ASP_NET_CORE_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.AspNetCore.App"); } @@ -248,26 +248,6 @@ public override bool TryToResolve( #region HelperMethods - private static string GetDotNetInstallDirectory() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Running on Windows so use registry - if (Environment.Is64BitProcess) - { - RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); - return (string)key?.GetValue("Path"); - } - else - { - RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); - return (string)key?.GetValue("InstallLocation"); - } - } - else - return "/usr/shared/dotnet/"; - } - private static string FindBestVersionDir(string libraryDir, Version targetVersion) { string target = targetVersion.ToString(); diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index a1ee5c6d1..bbdc6f284 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -73,7 +73,7 @@ public AgentProcess(TestAgency agency, TestPackage package, Guid agentId) if (Path.DirectorySeparatorChar != '\\') throw new Exception("Running .NET Core as X86 is currently only supported on Windows"); - var x86_dotnet_exe = @"C:\Program Files (x86)\dotnet\dotnet.exe"; + var x86_dotnet_exe = Path.Combine(DotNet.GetX86InstallDirectory(), "dotnet.exe"); if (!File.Exists(x86_dotnet_exe)) throw new Exception("The X86 version of dotnet.exe is not installed"); diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs index 9e94709b6..2418ff345 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs @@ -38,7 +38,7 @@ public static IEnumerable FindRuntimes(bool x86) private static IEnumerable GetRuntimeDirectories(bool x86) { - string installDir = GetDotNetInstallDirectory(x86); + string installDir = DotNet.GetInstallDirectory(x86); if (installDir != null && Directory.Exists(installDir) && File.Exists(Path.Combine(installDir, "dotnet.exe"))) @@ -110,27 +110,6 @@ private static bool TryGetVersionFromString(string text, out Version newVersion) return false; } } - - internal static string GetDotNetInstallDirectory(bool x86) - { - if (Path.DirectorySeparatorChar == '\\') - { - if (x86) - { - RegistryKey key = - Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); - return (string)key?.GetValue("InstallLocation"); - } - else - { - RegistryKey key = - Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); - return (string)key?.GetValue("Path"); - } - } - else - return "/usr/shared/dotnet/"; - } } } #endif From 9169a71b95a41c960ffcd7f9068c7c2fe252d43d Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 6 Oct 2024 08:19:36 -0700 Subject: [PATCH 112/190] Add tests of NUnit V2 Driver --- .../workflows/NUnitConsoleAndEngine.CI.yml | 9 + NUnitConsole.sln | 7 + choco/nunit.console.choco.agent.addins | 2 + package-tests.cake | 81 ++++-- .../Drivers/NUnit2DriverFactory.cs | 2 +- src/TestData/mock-assembly-v2/MockAssembly.cs | 269 ++++++++++++++++++ .../mock-assembly-v2/mock-assembly-v2.csproj | 16 ++ 7 files changed, 362 insertions(+), 24 deletions(-) create mode 100644 src/TestData/mock-assembly-v2/MockAssembly.cs create mode 100644 src/TestData/mock-assembly-v2/mock-assembly-v2.csproj diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index 71bd30336..d218973ba 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -61,6 +61,15 @@ jobs: path: "build-results/*.binlog" # if-no-files-found: error + - name: 🪵 Upload InternalTrace logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: InternalTraceLogs + # This path is defined in build-settings.cake + path: "*.log" + # if-no-files-found: error + - name: 💾 Upload test results uses: actions/upload-artifact@v4 if: always() diff --git a/NUnitConsole.sln b/NUnitConsole.sln index f8919dc54..5a33c0c61 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -152,6 +152,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net8.0", "net8.0", "{303CF8 nuget\runners\net8.0\DotnetToolSettings.xml = nuget\runners\net8.0\DotnetToolSettings.xml EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly-v2", "src\TestData\mock-assembly-v2\mock-assembly-v2.csproj", "{AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -230,6 +232,10 @@ Global {E43A3E4B-B050-471B-B43C-0DF60FD44376}.Debug|Any CPU.Build.0 = Debug|Any CPU {E43A3E4B-B050-471B-B43C-0DF60FD44376}.Release|Any CPU.ActiveCfg = Release|Any CPU {E43A3E4B-B050-471B-B43C-0DF60FD44376}.Release|Any CPU.Build.0 = Release|Any CPU + {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -265,6 +271,7 @@ Global {58E18ACC-1F7E-4395-817E-E7EF943E0C77} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {E43A3E4B-B050-471B-B43C-0DF60FD44376} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {303CF83E-2A87-4882-8CAC-3EB59AAD81FC} = {F3E87D0F-6F06-4C0B-AE06-42C0834C3C6E} + {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/choco/nunit.console.choco.agent.addins b/choco/nunit.console.choco.agent.addins index ddf5d016b..d326d241b 100644 --- a/choco/nunit.console.choco.agent.addins +++ b/choco/nunit.console.choco.agent.addins @@ -1,5 +1,7 @@ # Extensions built for a single runtime target ../../../../nunit-extension-*/tools/ # find extensions installed under chocolatey +../../../../../nunit-extension-*/tools/ # find extensions installed under chocolatey # Extensions built for multiple targets ../../../../nunit-extension-*/tools/*/ # find extensions installed under chocolatey +../../../../../nunit-extension-*/tools/*/ # find extensions installed under chocolatey diff --git a/package-tests.cake b/package-tests.cake index a0b9e9986..4e0b9ce36 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -200,6 +200,7 @@ StandardRunnerTests.Add(new PackageTest( // RUN TESTS USING EACH OF OUR EXTENSIONS ////////////////////////////////////////////////////////////////////// +// NUnit Project Loader Test StandardRunnerTests.Add(new PackageTest( 1, "NUnitProjectTest", "Run NUnit project with mock-assembly.dll built for .NET 4.6.2 and 6.0", @@ -207,6 +208,7 @@ StandardRunnerTests.Add(new PackageTest( new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"), KnownExtensions.NUnitProjectLoader.SetVersion("3.8.0"))); +// V2 Result Writer Test StandardRunnerTests.Add(new PackageTest( 1, "V2ResultWriterTest", "Run mock-assembly under .NET 6.0 and produce V2 output", @@ -214,6 +216,7 @@ StandardRunnerTests.Add(new PackageTest( new MockAssemblyExpectedResult("netcore-6.0"), KnownExtensions.NUnitV2ResultWriter.SetVersion("3.8.0"))); +// VS Project Loader Tests StandardRunnerTests.Add(new PackageTest( 1, "VSProjectLoaderTest_Project", "Run mock-assembly using the .csproj file", @@ -221,35 +224,34 @@ StandardRunnerTests.Add(new PackageTest( new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0"), KnownExtensions.VSProjectLoader.SetVersion("3.9.0"))); -static ExpectedResult MockAssemblySolutionResult = new ExpectedResult("Failed") -{ - Total = 37 * 5, - Passed = 23 * 5, - Failed = 5 * 5, - Warnings = 1 * 5, - Inconclusive = 1 * 5, - Skipped = 7 * 5, - Assemblies = new ExpectedAssemblyResult[] - { - new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-3.1"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), - new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), - new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), - new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0"), - new ExpectedAssemblyResult("WpfApp.exe") - } -}; - StandardRunnerTests.Add(new PackageTest( 1, "VSProjectLoaderTest_Solution", "Run mock-assembly using the .sln file", "../../src/TestData/TestData.sln --config=Release --trace=Debug", - MockAssemblySolutionResult, + new ExpectedResult("Failed") + { + Total = 37 * 5, + Passed = 23 * 5, + Failed = 5 * 5, + Warnings = 1 * 5, + Inconclusive = 1 * 5, + Skipped = 7 * 5, + Assemblies = new ExpectedAssemblyResult[] + { + new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-3.1"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), + new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), + new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), + new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0"), + new ExpectedAssemblyResult("WpfApp.exe") + } + }, KnownExtensions.VSProjectLoader.SetVersion("3.9.0"))); +// TeamCity Event Listener Test StandardRunnerTests.Add(new PackageTest( 1, "TeamCityListenerTest", "Run mock-assembly with --teamcity enabled", @@ -257,6 +259,39 @@ StandardRunnerTests.Add(new PackageTest( new MockAssemblyExpectedResult("net-4.6.2"), new ExtensionSpecifier("NUnit.Extension.TeamCityEventListener", "nunit-extension-teamcity-event-listener", "1.0.7"))); +// V2 Framework Driver Tests +StandardRunnerTests.Add(new PackageTest( + 1, "V2FrameworkDriverTest", + "Run mock-assembly-v2 using the V2 Driver in process", + "v2-tests/net462/mock-assembly-v2.dll --inprocess", + new ExpectedResult("Failed") + { + Total = 28, + Passed = 18, + Failed = 5, + Warnings = 0, + Inconclusive = 1, + Skipped = 4, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } + }, + new ExtensionSpecifier("NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver", "3.9.0"))); + +StandardRunnerTests.Add(new PackageTest( + 1, "V2FrameworkDriverTest", + "Run mock-assembly-v2 using the V2 Driver out of process", + "v2-tests/net462/mock-assembly-v2.dll --trace:Debug", + new ExpectedResult("Failed") + { + Total = 28, + Passed = 18, + Failed = 5, + Warnings = 0, + Inconclusive = 1, + Skipped = 4, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } + }, + new ExtensionSpecifier("NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver", "3.9.0"))); + ////////////////////////////////////////////////////////////////////// // SPECIAL CASES ////////////////////////////////////////////////////////////////////// diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs index 5ab8ffa2f..8d6994587 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs @@ -58,7 +58,7 @@ public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) return AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap( _driverNode.AssemblyPath, _driverNode.TypeName, - false, 0, null, null, null, null) as IFrameworkDriver; + false, 0, null, new[] { domain }, null, null) as IFrameworkDriver; } } } diff --git a/src/TestData/mock-assembly-v2/MockAssembly.cs b/src/TestData/mock-assembly-v2/MockAssembly.cs new file mode 100644 index 000000000..b9439abce --- /dev/null +++ b/src/TestData/mock-assembly-v2/MockAssembly.cs @@ -0,0 +1,269 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using NUnit.Framework; + +namespace TestCentric.Tests +{ + namespace Assemblies + { + /// + /// Constant definitions for the mock-assembly dll. + /// + public class MockAssembly + { + public static int Classes = 9; + + public static int NamespaceSuites = 6; // assembly, NUnit, Tests, Assemblies, Singletons, TestAssembly + + public static int Tests = MockTestFixture.Tests + + Singletons.OneTestCase.Tests + + TestAssembly.MockTestFixture.Tests + + IgnoredFixture.Tests + + ExplicitFixture.Tests + + BadFixture.Tests + + FixtureWithTestCases.Tests + + ParameterizedFixture.Tests + + GenericFixtureConstants.Tests; + + public static int Suites = MockTestFixture.Suites + + Singletons.OneTestCase.Suites + + TestAssembly.MockTestFixture.Suites + + IgnoredFixture.Suites + + ExplicitFixture.Suites + + BadFixture.Suites + + FixtureWithTestCases.Suites + + ParameterizedFixture.Suites + + GenericFixtureConstants.Suites + + NamespaceSuites; + + public static readonly int Nodes = Tests + Suites; + + public static int ExplicitFixtures = 1; + public static int SuitesRun = Suites - ExplicitFixtures; + + public static int Ignored = MockTestFixture.Ignored + IgnoredFixture.Tests; + public static int Explicit = MockTestFixture.Explicit + ExplicitFixture.Tests; + public static int NotRunnable = MockTestFixture.NotRunnable + BadFixture.Tests; + public static int NotRun = Ignored + Explicit + NotRunnable; + public static int TestsRun = Tests - NotRun; + public static int ResultCount = Tests - Explicit; + + public static int Errors = MockTestFixture.Errors; + public static int Failures = MockTestFixture.Failures; + + public static int Categories = MockTestFixture.Categories; + } + + [TestFixture(Description="Fake Test Fixture")] + [Category("FixtureCategory")] + public class MockTestFixture + { + public static readonly int Tests = 11; + public static readonly int Suites = 1; + + public static readonly int Ignored = 1; + public static readonly int Explicit = 1; + public static readonly int NotRunnable = 2; + public static readonly int NotRun = Ignored + Explicit + NotRunnable; + public static readonly int TestsRun = Tests - NotRun; + public static readonly int ResultCount = Tests - Explicit; + + public static readonly int Failures = 1; + public static readonly int Errors = 1; + + public static readonly int Categories = 5; + public static readonly int MockCategoryTests = 2; + + [Test(Description="Mock Test #1")] + public void MockTest1() + {} + + [Test] + [Category("MockCategory")] + [Property("Severity","Critical")] + [Description("This is a really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really long description")] + public void MockTest2() + {} + + [Test] + [Category("MockCategory")] + [Category("AnotherCategory")] + public void MockTest3() + { Assert.Pass("Succeeded!"); } + + [Test] + protected static void MockTest5() + {} + + [Test] + public void FailingTest() + { + Assert.Fail("Intentional failure"); + } + + [Test, Property("TargetMethod", "SomeClassName"), Property("Size", 5), /*Property("TargetType", typeof( System.Threading.Thread ))*/] + public void TestWithManyProperties() + {} + + [Test] + [Ignore("ignoring this test method for now")] + [Category("Foo")] + public void MockTest4() + {} + + [Test, Explicit] + [Category( "Special" )] + public void ExplicitlyRunTest() + {} + + [Test] + public void NotRunnableTest( int a, int b) + { + } + + [Test] + public void InconclusiveTest() + { + Assert.Inconclusive("No valid data"); + } + + [Test] + public void TestWithException() + { + MethodThrowsException(); + } + + private void MethodThrowsException() + { + throw new ApplicationException("Intentional Exception"); + } + } + } + + namespace Singletons + { + [TestFixture] + public class OneTestCase + { + public static readonly int Tests = 1; + public static readonly int Suites = 1; + + [Test] + public virtual void TestCase() + {} + } + } + + namespace TestAssembly + { + [TestFixture] + public class MockTestFixture + { + public static readonly int Tests = 1; + public static readonly int Suites = 1; + + [Test] + public void MyTest() + { + } + } + } + + [TestFixture, Ignore] + public class IgnoredFixture + { + public static readonly int Tests = 3; + public static readonly int Suites = 1; + + [Test] + public void Test1() { } + + [Test] + public void Test2() { } + + [Test] + public void Test3() { } + } + + [TestFixture,Explicit] + public class ExplicitFixture + { + public static readonly int Tests = 2; + public static readonly int Suites = 1; + public static readonly int Nodes = Tests + Suites; + + [Test] + public void Test1() { } + + [Test] + public void Test2() { } + } + + [TestFixture] + public class BadFixture + { + public static readonly int Tests = 1; + public static readonly int Suites = 1; + + public BadFixture(int val) { } + + [Test] + public void SomeTest() { } + } + + [TestFixture] + public class FixtureWithTestCases + { + public static readonly int Tests = 4; + public static readonly int Suites = 3; + + [TestCase(2, 2, Result=4)] + [TestCase(9, 11, Result=20)] + public int MethodWithParameters(int x, int y) + { + return x+y; + } + + [TestCase(2, 4)] + [TestCase(9.2, 11.7)] + public void GenericMethod(T x, T y) + { + } + } + + [TestFixture(5)] + [TestFixture(42)] + public class ParameterizedFixture + { + public static readonly int Tests = 4; + public static readonly int Suites = 3; + + public ParameterizedFixture(int num) { } + + [Test] + public void Test1() { } + + [Test] + public void Test2() { } + } + + public class GenericFixtureConstants + { + public static readonly int Tests = 4; + public static readonly int Suites = 3; + } + + [TestFixture(5)] + [TestFixture(11.5)] + public class GenericFixture + { + public GenericFixture(T num){ } + + [Test] + public void Test1() { } + + [Test] + public void Test2() { } + } +} diff --git a/src/TestData/mock-assembly-v2/mock-assembly-v2.csproj b/src/TestData/mock-assembly-v2/mock-assembly-v2.csproj new file mode 100644 index 000000000..5d760a26f --- /dev/null +++ b/src/TestData/mock-assembly-v2/mock-assembly-v2.csproj @@ -0,0 +1,16 @@ + + + + NUnit.Tests + net462 + true + ..\..\nunit.snk + ..\..\..\bin\$(Configuration)\v2-tests\ + true + + + + + + + \ No newline at end of file From dee87640beef3a713a4cfd670687c5070be584ec Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 7 Oct 2024 10:40:55 -0700 Subject: [PATCH 113/190] Add tests using NUnit V2 Driver --- build.cake | 26 ++++++++++++++++++- .../nunit-agent/nunit-agent.csproj | 8 +++++- .../nunit-agent/nunit.agent.addins | 5 ++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/NUnitEngine/nunit-agent/nunit.agent.addins diff --git a/build.cake b/build.cake index d310430b2..1c2439f79 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.2.0-dev00007 +#load nuget:?package=NUnit.Cake.Recipe&version=1.2.0-dev00009 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -189,6 +189,30 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { }) }); +Task("BuildZipPackage") + .Does(() => + { + NUnitConsoleZipPackage.BuildPackage(); + }); + +Task("InstallZipPackage") + .Does(() => + { + NUnitConsoleZipPackage.InstallPackage(); + }); + +Task("VerifyZipPackage") + .Does(() => + { + NUnitConsoleZipPackage.VerifyPackage(); + }); + +Task("TestZipPackage") + .Does(() => + { + NUnitConsoleZipPackage.RunPackageTests(); + }); + // Adhoc code to check content of a dotnet standalone executable // TODO: Incorporate this in the recipe itself diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index 71e07baa5..b7ee2341a 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -25,7 +25,13 @@ - + + + + PreserveNewest + + + nunit.ico diff --git a/src/NUnitEngine/nunit-agent/nunit.agent.addins b/src/NUnitEngine/nunit-agent/nunit.agent.addins new file mode 100644 index 000000000..74b562fd7 --- /dev/null +++ b/src/NUnitEngine/nunit-agent/nunit.agent.addins @@ -0,0 +1,5 @@ +../../net462/addins/nunit.v2.driver.dll +../../net462/addins/nunit-v2-result-writer.dll +../../net462/addins/nunit-project-loader.dll +../../net462/addins/vs-project-loader.dll +../../net462/addins/teamcity-event-listener.dll From 7e668cb49ef6f93365cca4b680b6e8eea8556487 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 8 Oct 2024 07:54:30 -0700 Subject: [PATCH 114/190] Update recipe to version 1.2.0 --- build.cake | 12 ++++++------ package-tests.cake | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/build.cake b/build.cake index 1c2439f79..5a5707155 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.2.0-dev00009 +#load nuget:?package=NUnit.Cake.Recipe&version=1.2.0 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -143,11 +143,11 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), tests: StandardRunnerTests, bundledExtensions: new [] { - new PackageReference("NUnit.Extension.VSProjectLoader", "3.9.0"), - new PackageReference("NUnit.Extension.NUnitProjectLoader", "3.8.0"), - new PackageReference("NUnit.Extension.NUnitV2Driver", "3.9.0"), - new PackageReference("NUnit.Extension.NUnitV2ResultWriter", "3.8.0"), - new PackageReference("NUnit.Extension.TeamCityEventListener", "1.0.7") + KnownExtensions.VSProjectLoader.NuGetPackage, + KnownExtensions.NUnitProjectLoader.NuGetPackage, + KnownExtensions.NUnitV2Driver.NuGetPackage, + KnownExtensions.NUnitV2ResultWriter.NuGetPackage, + KnownExtensions.TeamCityEventListener.NuGetPackage }), // NOTE: Packages below this point have no direct tests diff --git a/package-tests.cake b/package-tests.cake index 4e0b9ce36..92ad4e6ba 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -206,7 +206,7 @@ StandardRunnerTests.Add(new PackageTest( "Run NUnit project with mock-assembly.dll built for .NET 4.6.2 and 6.0", "../../NetFXTests.nunit --config=Release --trace=Debug", new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"), - KnownExtensions.NUnitProjectLoader.SetVersion("3.8.0"))); + KnownExtensions.NUnitProjectLoader)); // V2 Result Writer Test StandardRunnerTests.Add(new PackageTest( @@ -214,7 +214,7 @@ StandardRunnerTests.Add(new PackageTest( "Run mock-assembly under .NET 6.0 and produce V2 output", "testdata/net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", new MockAssemblyExpectedResult("netcore-6.0"), - KnownExtensions.NUnitV2ResultWriter.SetVersion("3.8.0"))); + KnownExtensions.NUnitV2ResultWriter)); // VS Project Loader Tests StandardRunnerTests.Add(new PackageTest( @@ -222,7 +222,7 @@ StandardRunnerTests.Add(new PackageTest( "Run mock-assembly using the .csproj file", "../../src/TestData/mock-assembly/mock-assembly.csproj --config=Release", new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0"), - KnownExtensions.VSProjectLoader.SetVersion("3.9.0"))); + KnownExtensions.VSProjectLoader)); StandardRunnerTests.Add(new PackageTest( 1, "VSProjectLoaderTest_Solution", @@ -249,7 +249,7 @@ StandardRunnerTests.Add(new PackageTest( new ExpectedAssemblyResult("WpfApp.exe") } }, - KnownExtensions.VSProjectLoader.SetVersion("3.9.0"))); + KnownExtensions.VSProjectLoader)); // TeamCity Event Listener Test StandardRunnerTests.Add(new PackageTest( @@ -257,7 +257,7 @@ StandardRunnerTests.Add(new PackageTest( "Run mock-assembly with --teamcity enabled", "testdata/net462/mock-assembly.dll --teamcity", new MockAssemblyExpectedResult("net-4.6.2"), - new ExtensionSpecifier("NUnit.Extension.TeamCityEventListener", "nunit-extension-teamcity-event-listener", "1.0.7"))); + KnownExtensions.TeamCityEventListener)); // V2 Framework Driver Tests StandardRunnerTests.Add(new PackageTest( @@ -274,7 +274,7 @@ StandardRunnerTests.Add(new PackageTest( Skipped = 4, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } }, - new ExtensionSpecifier("NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver", "3.9.0"))); + KnownExtensions.NUnitV2Driver)); StandardRunnerTests.Add(new PackageTest( 1, "V2FrameworkDriverTest", @@ -290,7 +290,7 @@ StandardRunnerTests.Add(new PackageTest( Skipped = 4, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } }, - new ExtensionSpecifier("NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver", "3.9.0"))); + KnownExtensions.NUnitV2Driver)); ////////////////////////////////////////////////////////////////////// // SPECIAL CASES From 8f04dfa0f999302cc7c1dc2042b1d06adfd11c65 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 8 Oct 2024 14:45:37 -0700 Subject: [PATCH 115/190] Additional tests for the netcore runners --- package-tests.cake | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package-tests.cake b/package-tests.cake index 92ad4e6ba..6f889e268 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -146,7 +146,7 @@ StandardRunnerTests.Add(new PackageTest( // ASP.NETCORE TESTS ////////////////////////////////////////////////////////////////////// -StandardRunnerTests.Add(new PackageTest( +AddToBothLists(new PackageTest( 1, "Net60AspNetCoreTest", "Run test using AspNetCore targeting .NET 6.0", "testdata/net6.0/aspnetcore-test.dll", new ExpectedResult("Passed") { @@ -154,7 +154,7 @@ StandardRunnerTests.Add(new PackageTest( Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-6.0") } })); -StandardRunnerTests.Add(new PackageTest( +AddToBothLists(new PackageTest( 1, "Net80AspNetCoreTest", "Run test using AspNetCore targeting .NET 8.0", "testdata/net8.0/aspnetcore-test.dll", new ExpectedResult("Passed") { @@ -166,7 +166,7 @@ StandardRunnerTests.Add(new PackageTest( // WINDOWS FORMS TESTS ////////////////////////////////////////////////////////////////////// -StandardRunnerTests.Add(new PackageTest( +AddToBothLists(new PackageTest( 1, "Net60WindowsFormsTest", "Run test using windows forms under .NET 6.0", "testdata/net6.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") { @@ -174,6 +174,7 @@ StandardRunnerTests.Add(new PackageTest( Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("windows-forms-test.dll", "netcore-6.0") } })); +// Runs under Net80 runner but not NetCore StandardRunnerTests.Add(new PackageTest( 1, "Net80WindowsFormsTest", "Run test using windows forms under .NET 8.0", "testdata/net8.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") @@ -186,12 +187,12 @@ StandardRunnerTests.Add(new PackageTest( // WPF TESTS ////////////////////////////////////////////////////////////////////// -StandardRunnerTests.Add(new PackageTest( +AddToBothLists(new PackageTest( 1, "Net60WPFTest", "Run test using WPF under .NET 6.0", "testdata/net6.0-windows/WpfTest.dll --trace=Debug", new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-6.0") } })); -StandardRunnerTests.Add(new PackageTest( +AddToBothLists(new PackageTest( 1, "Net80WPFTest", "Run test using WPF under .NET 8.0", "testdata/net8.0-windows/WpfTest.dll --trace=Debug", new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-8.0") } })); From 89ec63554f8b77ed26e0b4b197d7d740bd6bd80d Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 19 Oct 2024 18:13:26 -0700 Subject: [PATCH 116/190] Eliminate .NET 6.0 build of the engine --- NUnitConsole.sln | 1 - build.cake | 16 +-------- nuget/engine/nunit.engine.nuspec | 10 ------ .../nunit.console-runner.netcore.nuspec | 36 +++++++++---------- .../nunit.engine.tests.csproj | 2 +- .../nunit.engine/nunit.engine.csproj | 2 +- 6 files changed, 21 insertions(+), 46 deletions(-) diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 5a33c0c61..ca1fc006d 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -66,7 +66,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runners", "runners", "{F3E8 ProjectSection(SolutionItems) = preProject nuget\runners\DotnetToolSettings.xml = nuget\runners\DotnetToolSettings.xml nuget\runners\nunit.console-runner-with-extensions.nuspec = nuget\runners\nunit.console-runner-with-extensions.nuspec - nuget\runners\nunit.console-runner.net80.nuspec = nuget\runners\nunit.console-runner.net80.nuspec nuget\runners\nunit.console-runner.netcore.nuspec = nuget\runners\nunit.console-runner.netcore.nuspec nuget\runners\nunit.console-runner.nuspec = nuget\runners\nunit.console-runner.nuspec nuget\runners\nunit.console.nuget.addins = nuget\runners\nunit.console.nuget.addins diff --git a/build.cake b/build.cake index 5a5707155..288c9877e 100644 --- a/build.cake +++ b/build.cake @@ -92,25 +92,13 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { checks: new PackageCheck[] { HasFiles("nunit.exe"), - HasSomeDirectory(".store/nunit.consolerunner.netcore/**/tools/net6.0/any") + HasSomeDirectory(".store/nunit.consolerunner.netcore/**/tools/net8.0/any") .WithFiles(ENGINE_FILES).AndFiles(ConsoleFiles).AndFile("Microsoft.Extensions.DependencyModel.dll") }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/nunit.exe"), tests: NetCoreRunnerTests), - NUnitConsoleRunnerNet80Package = new DotNetToolPackage( - id: "NUnit.ConsoleRunner.Net80", - source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.net80.nuspec", - checks: new PackageCheck[] { - HasFiles("nunit-net80.exe"), - HasSomeDirectory(".store/nunit.consolerunner.net80/**/tools/net8.0/any") - .WithFiles(ENGINE_FILES).AndFiles(ConsoleFiles).AndFile("Microsoft.Extensions.DependencyModel.dll") - }, - testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory - + $"NUnit.ConsoleRunner.Net80.{BuildSettings.PackageVersion}/nunit-net80.exe"), - tests: NetCoreRunnerTests), - NUnitConsoleRunnerChocolateyPackage = new ChocolateyPackage( id: "nunit-console-runner", source: BuildSettings.ChocolateyDirectory + "nunit-console-runner.nuspec", @@ -159,11 +147,9 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasFiles("LICENSE.txt", "NOTICES.txt"), HasDirectory("lib/net462").WithFiles(ENGINE_FILES), HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_FILES), - HasDirectory("lib/net6.0").WithFiles(ENGINE_FILES).AndFile("Microsoft.Extensions.DependencyModel.dll"), HasDirectory("lib/net8.0").WithFiles(ENGINE_FILES).AndFile("Microsoft.Extensions.DependencyModel.dll"), HasDirectory("contentFiles/any/lib/net462").WithFile("nunit.engine.nuget.addins"), HasDirectory("contentFiles/any/lib/netstandard2.0").WithFile("nunit.engine.nuget.addins"), - HasDirectory("contentFiles/any/lib/net6.0").WithFile("nunit.engine.nuget.addins"), HasDirectory("contentFiles/any/lib/net8.0").WithFile("nunit.engine.nuget.addins"), HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins") }, diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index 83f60a5cb..743232e34 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -80,16 +80,6 @@ - - - - - - - - - - diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index e297d4524..a2005cdec 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -29,24 +29,24 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index 3398bbcd8..35822ea83 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Tests - net462;netcoreapp3.1;net6.0;net8.0 + net462;netcoreapp3.1;net8.0 Exe true ..\..\nunit.snk diff --git a/src/NUnitEngine/nunit.engine/nunit.engine.csproj b/src/NUnitEngine/nunit.engine/nunit.engine.csproj index 040bd46fe..1db72dfa5 100644 --- a/src/NUnitEngine/nunit.engine/nunit.engine.csproj +++ b/src/NUnitEngine/nunit.engine/nunit.engine.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net462;netstandard2.0;net6.0;net8.0 + net462;netstandard2.0;net8.0 true ..\..\nunit.snk portable From 63d671bb7e3889985fa529e2f56fb69469f79780 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 20 Oct 2024 11:31:38 -0700 Subject: [PATCH 117/190] Eliminate .NET Standard 2.0 build of nunit.engine --- NUnitConsole.sln | 8 +++ build.cake | 13 +++-- nuget/engine/nunit.engine.nuspec | 9 ---- package-tests.cake | 4 +- .../nunit3-console.tests.csproj | 2 +- .../Services/ExtensionManagerTests.cs | 52 ++++++++++++++++--- .../nunit.engine.tests.csproj | 14 +---- .../nunit.engine/nunit.engine.csproj | 2 +- .../FakeExtensions/FakeExtensions.cs} | 0 .../FakeExtensions/FakeExtensions.csproj | 15 ++++++ src/TestData/mock-assembly-v2/MockAssembly.cs | 2 +- zip/nunit.bundle.agent.addins | 5 ++ 12 files changed, 89 insertions(+), 37 deletions(-) rename src/{NUnitEngine/nunit.engine.core.tests/DummyExtensions.cs => TestData/FakeExtensions/FakeExtensions.cs} (100%) create mode 100644 src/TestData/FakeExtensions/FakeExtensions.csproj create mode 100644 zip/nunit.bundle.agent.addins diff --git a/NUnitConsole.sln b/NUnitConsole.sln index ca1fc006d..9b322fbb5 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -100,6 +100,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "zip", "zip", "{20005864-BE82-412D-99BF-288E2D8370E9}" ProjectSection(SolutionItems) = preProject zip\nunit.bundle.addins = zip\nunit.bundle.addins + zip\nunit.bundle.agent.addins = zip\nunit.bundle.agent.addins EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit.engine.core.tests", "src\NUnitEngine\nunit.engine.core.tests\nunit.engine.core.tests.csproj", "{CACC0520-B452-4310-A33C-DC944129ACDD}" @@ -153,6 +154,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net8.0", "net8.0", "{303CF8 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly-v2", "src\TestData\mock-assembly-v2\mock-assembly-v2.csproj", "{AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FakeExtensions", "src\TestData\FakeExtensions\FakeExtensions.csproj", "{D6C217E0-BFB7-4C80-8D50-C969F46EBC59}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -235,6 +238,10 @@ Global {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Release|Any CPU.Build.0 = Release|Any CPU + {D6C217E0-BFB7-4C80-8D50-C969F46EBC59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6C217E0-BFB7-4C80-8D50-C969F46EBC59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6C217E0-BFB7-4C80-8D50-C969F46EBC59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6C217E0-BFB7-4C80-8D50-C969F46EBC59}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -271,6 +278,7 @@ Global {E43A3E4B-B050-471B-B43C-0DF60FD44376} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {303CF83E-2A87-4882-8CAC-3EB59AAD81FC} = {F3E87D0F-6F06-4C0B-AE06-42C0834C3C6E} {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {D6C217E0-BFB7-4C80-8D50-C969F46EBC59} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/build.cake b/build.cake index 288c9877e..98f78f8e8 100644 --- a/build.cake +++ b/build.cake @@ -119,13 +119,18 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.ZipImageDirectory, checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt", "CHANGES.txt"), - HasDirectory("bin/net462").WithFiles("nunit3-console.exe", "nunit3-console.exe.config", "nunit3-console.pdb").AndFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), - HasDirectory("bin/netstandard2.0").WithFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), + HasDirectory("bin/net462").WithFiles("nunit3-console.exe", "nunit3-console.exe.config", + "nunit3-console.pdb").AndFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), + HasDirectory("bin/net462/addins").WithFiles( + "nunit.core.dll", "nunit.core.interfaces.dll", "nunit.engine.api.dll", + "nunit.v2.driver.dll", "nunit-project-loader.dll", "nunit-v2-result-writer.dll", + "teamcity-event-listener.dll", "vs-project-loader.dll"), + HasDirectory("bin/netstandard2.0").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) + HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), @@ -146,10 +151,8 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), HasDirectory("lib/net462").WithFiles(ENGINE_FILES), - HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_FILES), HasDirectory("lib/net8.0").WithFiles(ENGINE_FILES).AndFile("Microsoft.Extensions.DependencyModel.dll"), HasDirectory("contentFiles/any/lib/net462").WithFile("nunit.engine.nuget.addins"), - HasDirectory("contentFiles/any/lib/netstandard2.0").WithFile("nunit.engine.nuget.addins"), HasDirectory("contentFiles/any/lib/net8.0").WithFile("nunit.engine.nuget.addins"), HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins") }, diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index 743232e34..e3eed7538 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -71,15 +71,6 @@ - - - - - - - - - diff --git a/package-tests.cake b/package-tests.cake index 6f889e268..7c893672e 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -264,7 +264,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "V2FrameworkDriverTest", "Run mock-assembly-v2 using the V2 Driver in process", - "v2-tests/net462/mock-assembly-v2.dll --inprocess", + "v2-tests/net462/mock-assembly-v2.dll", new ExpectedResult("Failed") { Total = 28, @@ -280,7 +280,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "V2FrameworkDriverTest", "Run mock-assembly-v2 using the V2 Driver out of process", - "v2-tests/net462/mock-assembly-v2.dll --trace:Debug", + "v2-tests/net462/mock-assembly-v2.dll --list-extensions", new ExpectedResult("Failed") { Total = 28, diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index ec2a90e9e..2ad1519ba 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -2,7 +2,7 @@ NUnit.ConsoleRunner.Tests - net462;net6.0;net8.0 + net462;net8.0 Exe NUnit.Engine.Tests.Program 1685 diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index f1568f764..eca71b122 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -10,6 +10,7 @@ using NUnit.Engine.Internal; using NSubstitute; using NUnit.Engine.Internal.FileSystemAccess; +using System.Diagnostics; namespace NUnit.Engine.Services.Tests { @@ -57,9 +58,12 @@ public void CreateExtensionManager() // Find actual extension points. _extensionManager.FindExtensionPoints(typeof(CoreEngine).Assembly); _extensionManager.FindExtensionPoints(typeof(ITestEngine).Assembly); - - // Find dummy extensions in this test assembly. - _extensionManager.FindExtensionsInAssembly(new ExtensionAssembly(GetType().Assembly.Location, false)); + // Find extensions. +#if NETCOREAPP + _extensionManager.FindExtensionsInAssembly(FakeExtensions("netstandard2.0")); +#else + _extensionManager.FindExtensionsInAssembly(FakeExtensions("net462")); +#endif } [Test] @@ -161,21 +165,39 @@ public void SkipsGracefullyLoadingOtherFrameworkExtensionAssembly() Assert.That(() => service.FindExtensionsInAssembly(extensionAssembly), Throws.Nothing); } - [TestCaseSource(nameof(ValidCombos))] +#if NETCOREAPP + [TestCase("netstandard2.0", ExpectedResult = true)] + [TestCase("net462", ExpectedResult = false)] + //[TestCase("net20", ExpectedResult = false)] +#elif NET40_OR_GREATER + [TestCase("netstandard2.0", ExpectedResult = false)] + [TestCase("net462", ExpectedResult = true)] + //[TestCase("net20", ExpectedResult = true)] +#else + [TestCase("netstandard2.0", ExpectedResult = false)] + [TestCase("net462", ExpectedResult = false)] + //[TestCase("net20", ExpectedResult = true)] +#endif + public bool LoadTargetFramework(string tfm) + { + return _extensionManager.CanLoadTargetFramework(THIS_ASSEMBLY, FakeExtensions(tfm)); + } + + //[TestCaseSource(nameof(ValidCombos))] public void ValidTargetFrameworkCombinations(FrameworkCombo combo) { Assert.That(() => _extensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly), Is.True); } - [TestCaseSource(nameof(InvalidTargetFrameworkCombos))] + //[TestCaseSource(nameof(InvalidTargetFrameworkCombos))] public void InvalidTargetFrameworkCombinations(FrameworkCombo combo) { Assert.That(() => _extensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly), Is.False); } - [TestCaseSource(nameof(InvalidRunnerCombos))] + //[TestCaseSource(nameof(InvalidRunnerCombos))] public void InvalidRunnerTargetFrameworkCombinations(FrameworkCombo combo) { var ex = Assert.Catch(() => _extensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly)); @@ -275,5 +297,23 @@ private static string GetSiblingDirectory(string dir) var file = new FileInfo(typeof(ExtensionManagerTests).Assembly.Location); return Path.Combine(file.Directory.Parent.FullName, dir); } + + private static readonly Assembly THIS_ASSEMBLY = typeof(ExtensionManagerTests).Assembly; + private static readonly string THIS_ASSEMBLY_DIRECTORY = Path.GetDirectoryName(THIS_ASSEMBLY.Location); + private const string FAKE_EXTENSIONS_FILENAME = "FakeExtensions.dll"; + private static readonly string FAKE_EXTENSIONS_PARENT_DIRECTORY = + Path.Combine(new DirectoryInfo(THIS_ASSEMBLY_DIRECTORY).Parent.FullName, "fakes"); + + /// + /// Returns an ExtensionAssembly referring to a particular build of the fake test extensions + /// assembly based on the argument provided. + /// + /// A test framework moniker. Must be one for which the fake extensions are built. + /// + private static ExtensionAssembly FakeExtensions(string tfm) + { + return new ExtensionAssembly( + Path.Combine(FAKE_EXTENSIONS_PARENT_DIRECTORY, Path.Combine(tfm, FAKE_EXTENSIONS_FILENAME)), false); + } } } diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index 35822ea83..08b96fc58 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Tests - net462;netcoreapp3.1;net8.0 + net462;net8.0 Exe true ..\..\nunit.snk @@ -17,13 +17,7 @@ Tests of nunit.engine assembly - - - - - - - + @@ -35,10 +29,6 @@ - - - - diff --git a/src/NUnitEngine/nunit.engine/nunit.engine.csproj b/src/NUnitEngine/nunit.engine/nunit.engine.csproj index 1db72dfa5..c8a16bcdf 100644 --- a/src/NUnitEngine/nunit.engine/nunit.engine.csproj +++ b/src/NUnitEngine/nunit.engine/nunit.engine.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net462;netstandard2.0;net8.0 + net462;net8.0 true ..\..\nunit.snk portable diff --git a/src/NUnitEngine/nunit.engine.core.tests/DummyExtensions.cs b/src/TestData/FakeExtensions/FakeExtensions.cs similarity index 100% rename from src/NUnitEngine/nunit.engine.core.tests/DummyExtensions.cs rename to src/TestData/FakeExtensions/FakeExtensions.cs diff --git a/src/TestData/FakeExtensions/FakeExtensions.csproj b/src/TestData/FakeExtensions/FakeExtensions.csproj new file mode 100644 index 000000000..066d31608 --- /dev/null +++ b/src/TestData/FakeExtensions/FakeExtensions.csproj @@ -0,0 +1,15 @@ + + + + net462;netstandard2.0 + TestCentric.Extensibility + true + ..\..\nunit.snk + ..\..\..\bin\$(Configuration)\fakes + + + + + + + diff --git a/src/TestData/mock-assembly-v2/MockAssembly.cs b/src/TestData/mock-assembly-v2/MockAssembly.cs index b9439abce..bf764e8d3 100644 --- a/src/TestData/mock-assembly-v2/MockAssembly.cs +++ b/src/TestData/mock-assembly-v2/MockAssembly.cs @@ -3,7 +3,7 @@ using System; using NUnit.Framework; -namespace TestCentric.Tests +namespace NUnit.Tests { namespace Assemblies { diff --git a/zip/nunit.bundle.agent.addins b/zip/nunit.bundle.agent.addins new file mode 100644 index 000000000..9977bd6e9 --- /dev/null +++ b/zip/nunit.bundle.agent.addins @@ -0,0 +1,5 @@ +../../addins/nunit.v2.driver.dll +../../addins/nunit-v2-result-writer.dll +../../addins/nunit-project-loader.dll +../../addins/vs-project-loader.dll +../../addins/teamcity-event-listener.dll From 3b9cb28fa41235a8c005b0461a9c1e5aee3107b0 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 20 Oct 2024 21:17:19 -0700 Subject: [PATCH 118/190] Eliminate .NET Standard 2.0 build of nunit.engine.core --- build.cake | 1 - src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/build.cake b/build.cake index 98f78f8e8..d6377ab16 100644 --- a/build.cake +++ b/build.cake @@ -125,7 +125,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { "nunit.core.dll", "nunit.core.interfaces.dll", "nunit.engine.api.dll", "nunit.v2.driver.dll", "nunit-project-loader.dll", "nunit-v2-result-writer.dll", "teamcity-event-listener.dll", "vs-project-loader.dll"), - HasDirectory("bin/netstandard2.0").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index 9f1239499..ad931a802 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net462;netstandard2.0;netcoreapp3.1;net6.0;net8.0 + net462;netcoreapp3.1;net6.0;net8.0 $(NoWarn);SYSLIB0011;SYSLIB0012 true ..\..\nunit.snk @@ -22,10 +22,6 @@ - - - - From d8c16df30c878a4db85f38c7b1d00c68d366ffcd Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 30 Oct 2024 12:22:15 -0700 Subject: [PATCH 119/190] Update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ebdb0bdc..a4f0bfa65 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,12 @@ NUnit is a unit-testing framework for all .NET languages. Initially ported from The latest stable release of the NUnit Console is [available on NuGet](https://www.nuget.org/packages/NUnit.ConsoleRunner/), [Chocolatey](https://chocolatey.org/packages/nunit-console-runner), or can be [downloaded from GitHub](https://github.com/nunit/nunit-console/releases). Pre-release builds are [available on MyGet](https://www.myget.org/feed/nunit/package/nuget/NUnit.ConsoleRunner). The Console/Engine are available in various packages: -- [NUnit.ConsoleRunner](https://www.nuget.org/packages/NUnit.ConsoleRunner/): The NUnit Console, with no extensions. -- [NUnit.Console](https://www.nuget.org/packages/NUnit.Console/): The NUnit Console, with key extensions additionally packaged. Also available as an [msi installer](https://github.com/nunit/nunit-console/releases), you may need to add your **actual** msi install location to the `Path` environment variable after installing, for example: `C:\Program Files (x86)\NUnit.org\nunit-console`. +- [NUnit.ConsoleRunner](https://www.nuget.org/packages/NUnit.ConsoleRunner/): The NUnit Console itself, with no extensions. Also available as a [Chocolatey package](https://community.chocolatey.org/packages/nunit-console-runner). +- [NUnit.ConsoleRunner.NetCore](https://www.nuget.org/packages/NUnit.ConsoleRunner.NetCore/): A version of the runner built for .NET 8, which runs .NET Core tests in process. +- [NUnit.Console](https://www.nuget.org/packages/NUnit.Console/): The NUnit Console, with key extensions additionally packaged. - [NUnit.Engine](https://www.nuget.org/packages/NUnit.Engine/) & [NUnit.Engine.Api](https://www.nuget.org/packages/NUnit.Engine.Api/): Packages intended for custom runners integrating directly with the NUnit Engine. +Development builds of all packages are available from our MyGet feed at https://www.myget.org/feed/Packages/nunit ## Documentation ## Documentation for all NUnit projects are available at [https://docs.nunit.org/](https://docs.nunit.org/). From de2add558b66eaea7de7cc47682728237e050c3a Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 26 Nov 2024 21:57:14 -0800 Subject: [PATCH 120/190] Throw exception if --teamcity is specified but the extension is not present --- build.cake | 2 +- .../ConsoleRunnerTests.cs | 73 +++++++++++++------ .../nunit3-console/ConsoleRunner.cs | 16 +++- 3 files changed, 66 insertions(+), 25 deletions(-) diff --git a/build.cake b/build.cake index d6377ab16..b3ccae960 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.2.0 +#load nuget:?package=NUnit.Cake.Recipe&version=1.2.1-dev00002 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake diff --git a/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs b/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs index cbc18bbe3..991322567 100644 --- a/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt using System; +using System.Collections.Generic; using System.IO; using System.Xml; using NSubstitute; @@ -14,25 +15,43 @@ namespace NUnit.ConsoleRunner.Tests { class ConsoleRunnerTests { + private ITestEngine _testEngine; + private IResultService _resultService; + + [SetUp] + public void Setup() + { + _testEngine = Substitute.For(); + _resultService = new FakeResultService(); + + _testEngine.Services.GetService().Returns(_resultService); + } + [Test] public void ThrowsNUnitEngineExceptionWhenTestResultsAreNotWriteable() { - using (var testEngine = new TestEngine()) - { - testEngine.Services.Add(new FakeResultService()); - testEngine.Services.Add(new TestFilterService()); - testEngine.Services.Add(Substitute.For()); + ((FakeResultService)_resultService).ThrowsUnauthorizedAccessException = true; - var consoleRunner = new ConsoleRunner(testEngine, ConsoleMocks.Options("mock-assembly.dll"), new ColorConsoleWriter()); + var consoleRunner = new ConsoleRunner(_testEngine, ConsoleMocks.Options("mock-assembly.dll"), new ColorConsoleWriter()); - var ex = Assert.Throws(() => { consoleRunner.Execute(); }); - Assert.That(ex, Has.Message.EqualTo("The path specified in --result TestResult.xml could not be written to")); - } + var ex = Assert.Throws(() => { consoleRunner.Execute(); }); + Assert.That(ex, Has.Message.EqualTo("The path specified in --result TestResult.xml could not be written to")); + } + + [Test] + public void ThrowsNUnitExceptionWhenTeamcityOptionIsSpecifiedButNotAvailable() + { + var ex = Assert.Throws( + () => new ConsoleRunner(_testEngine, ConsoleMocks.Options("mock-assembly.dll", "--teamcity"), new ColorConsoleWriter())); + + Assert.That(ex, Has.Message.Contains("teamcity")); } } internal class FakeResultService : Service, IResultService { + public bool ThrowsUnauthorizedAccessException; + public string[] Formats { get @@ -43,25 +62,33 @@ public string[] Formats public IResultWriter GetResultWriter(string format, object[] args) { - return new FakeResultWriter(); + return new FakeResultWriter(this); } - } - internal class FakeResultWriter : IResultWriter - { - public void CheckWritability(string outputPath) + class FakeResultWriter : IResultWriter { - throw new UnauthorizedAccessException(); - } + private FakeResultService _service; - public void WriteResultFile(XmlNode resultNode, string outputPath) - { - throw new System.NotImplementedException(); - } + public FakeResultWriter(FakeResultService service) + { + _service = service; + } - public void WriteResultFile(XmlNode resultNode, TextWriter writer) - { - throw new System.NotImplementedException(); + public void CheckWritability(string outputPath) + { + if (_service.ThrowsUnauthorizedAccessException) + throw new UnauthorizedAccessException(); + } + + public void WriteResultFile(XmlNode resultNode, string outputPath) + { + throw new System.NotImplementedException(); + } + + public void WriteResultFile(XmlNode resultNode, TextWriter writer) + { + throw new System.NotImplementedException(); + } } } } diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index dd778af77..f06503cec 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -25,6 +25,9 @@ public class ConsoleRunner // ourselves so as to stay in that range. private const int MAXIMUM_RETURN_CODE_ALLOWED = 100; // In case we are running on Unix + private const string EVENT_LISTENER_EXTENSION_PATH = "/NUnit/Engine/TypeExtensions/ITestEventListener"; + private const string TEAMCITY_EVENT_LISTENER = "NUnit.Engine.Listeners.TeamCityEventListener"; + public static readonly int OK = 0; public static readonly int INVALID_ARG = -1; public static readonly int INVALID_ASSEMBLY = -2; @@ -58,8 +61,19 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri _filterService = _engine.Services.GetService(); _extensionService = _engine.Services.GetService(); + // TODO: Exit with error if any of the services are not found + + if (_options.TeamCity) + { + bool teamcityInstalled = false; + foreach (var node in _extensionService.GetExtensionNodes(EVENT_LISTENER_EXTENSION_PATH)) + if (teamcityInstalled = node.TypeName == TEAMCITY_EVENT_LISTENER) + break; + if (!teamcityInstalled) throw new NUnitEngineException("Option --teamcity specified but the extension is not installed."); + } + // Enable TeamCityEventListener immediately, before the console is redirected - _extensionService?.EnableExtension("NUnit.Engine.Listeners.TeamCityEventListener", _options.TeamCity); + _extensionService.EnableExtension("NUnit.Engine.Listeners.TeamCityEventListener", _options.TeamCity); } /// From 653876abf8734b9a17a93d6d1be64a36d53b86cb Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 21 Oct 2024 16:43:37 -0700 Subject: [PATCH 121/190] Implement an algorithm to find extensions --- GitVersion.yml | 2 +- NUnitConsole.sln | 10 +--- build.cake | 36 ++++++----- nuget/engine/nunit.agent.addins | 1 - nuget/engine/nunit.engine.nuget.addins | 4 -- nuget/engine/nunit.engine.nuspec | 6 -- nuget/runners/net8.0/DotnetToolSettings.xml | 6 -- .../runners/nunit.console-runner.net80.nuspec | 53 ----------------- .../nunit.console-runner.netcore.nuspec | 1 - nuget/runners/nunit.console-runner.nuspec | 6 -- nuget/runners/nunit.console.nuget.addins | 11 ---- .../runners/nunit.console.nuget.agent.addins | 11 ---- package-tests.cake | 2 +- .../Services/ExtensionManagerTests.cs | 4 +- .../Services/ExtensionManager.cs | 59 ++++++++++++++++--- .../Services/ExtensionService.cs | 14 ++++- .../Services/IExtensionManager.cs | 17 +++++- 17 files changed, 104 insertions(+), 139 deletions(-) delete mode 100644 nuget/engine/nunit.agent.addins delete mode 100644 nuget/engine/nunit.engine.nuget.addins delete mode 100644 nuget/runners/net8.0/DotnetToolSettings.xml delete mode 100644 nuget/runners/nunit.console-runner.net80.nuspec delete mode 100644 nuget/runners/nunit.console.nuget.addins delete mode 100644 nuget/runners/nunit.console.nuget.agent.addins diff --git a/GitVersion.yml b/GitVersion.yml index 19dfb1105..3b27dee15 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 3.18.0 +next-version: 3.19.0 mode: ContinuousDelivery legacy-semver-padding: 5 build-metadata-padding: 5 diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 9b322fbb5..8702696b9 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -77,8 +77,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "choco", "choco", "{4FDF7BFA choco\nunit-agent-x86.exe.ignore = choco\nunit-agent-x86.exe.ignore choco\nunit-agent.exe.ignore = choco\nunit-agent.exe.ignore choco\nunit-console-runner.nuspec = choco\nunit-console-runner.nuspec - choco\nunit.console.choco.addins = choco\nunit.console.choco.addins - choco\nunit.console.choco.agent.addins = choco\nunit.console.choco.agent.addins choco\VERIFICATION.txt = choco\VERIFICATION.txt EndProjectSection EndProject @@ -147,14 +145,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InvalidTestNames", "src\Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppContextTest", "src\TestData\AppContextTest\AppContextTest.csproj", "{E43A3E4B-B050-471B-B43C-0DF60FD44376}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net8.0", "net8.0", "{303CF83E-2A87-4882-8CAC-3EB59AAD81FC}" - ProjectSection(SolutionItems) = preProject - nuget\runners\net8.0\DotnetToolSettings.xml = nuget\runners\net8.0\DotnetToolSettings.xml - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly-v2", "src\TestData\mock-assembly-v2\mock-assembly-v2.csproj", "{AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FakeExtensions", "src\TestData\FakeExtensions\FakeExtensions.csproj", "{D6C217E0-BFB7-4C80-8D50-C969F46EBC59}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FakeExtensions", "src\TestData\FakeExtensions\FakeExtensions.csproj", "{D6C217E0-BFB7-4C80-8D50-C969F46EBC59}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -276,7 +269,6 @@ Global {6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {58E18ACC-1F7E-4395-817E-E7EF943E0C77} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {E43A3E4B-B050-471B-B43C-0DF60FD44376} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} - {303CF83E-2A87-4882-8CAC-3EB59AAD81FC} = {F3E87D0F-6F06-4C0B-AE06-42C0834C3C6E} {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {D6C217E0-BFB7-4C80-8D50-C969F46EBC59} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} EndGlobalSection diff --git a/build.cake b/build.cake index b3ccae960..9dfb9e07e 100644 --- a/build.cake +++ b/build.cake @@ -19,7 +19,7 @@ BuildSettings.Initialize( FilePath[] ConsoleFiles = { "nunit3-console.dll", "nunit3-console.dll.config", "nunit3-console.exe", "nunit3-console.pdb", - "nunit3-console.deps.json", "nunit3-console.runtimeconfig.json", "nunit.console.nuget.addins" }; + "nunit3-console.deps.json", "nunit3-console.runtimeconfig.json" }; FilePath[] ENGINE_FILES = { "nunit.engine.dll", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.engine.metadata.dll" }; FilePath[] ENGINE_PDB_FILES = { @@ -61,12 +61,12 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.nuspec", checks: new PackageCheck[] { HasFiles("LICENSE.txt", "NOTICES.txt"), - HasDirectory("tools").WithFiles("nunit3-console.exe", "nunit3-console.exe.config", "nunit.console.nuget.addins").AndFiles(ENGINE_FILES), - HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins"), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.nuget.agent.addins") + HasDirectory("tools").WithFiles("nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), + HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), + HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE) }, symbols: new PackageCheck[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), @@ -103,12 +103,12 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { id: "nunit-console-runner", source: BuildSettings.ChocolateyDirectory + "nunit-console-runner.nuspec", checks: new PackageCheck[] { - HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt", "nunit3-console.exe", "nunit3-console.exe.config", "nunit.console.choco.addins").AndFiles(ENGINE_FILES), - HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins"), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFile("nunit.console.choco.agent.addins") + HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt", "nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), + HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), + HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ChocolateyTestDirectory + $"nunit-console-runner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), @@ -151,9 +151,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasFiles("LICENSE.txt", "NOTICES.txt"), HasDirectory("lib/net462").WithFiles(ENGINE_FILES), HasDirectory("lib/net8.0").WithFiles(ENGINE_FILES).AndFile("Microsoft.Extensions.DependencyModel.dll"), - HasDirectory("contentFiles/any/lib/net462").WithFile("nunit.engine.nuget.addins"), - HasDirectory("contentFiles/any/lib/net8.0").WithFile("nunit.engine.nuget.addins"), - HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_FILES).AndFile("nunit.agent.addins") + HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_FILES) }, symbols: new PackageCheck[] { HasDirectory("lib/net462").WithFiles(ENGINE_PDB_FILES), @@ -201,6 +199,12 @@ Task("TestZipPackage") NUnitConsoleZipPackage.RunPackageTests(); }); +Task("TestNetCorePackage") + .Does(() => + { + NUnitConsoleRunnerNetCorePackage.RunPackageTests(); + }); + // Adhoc code to check content of a dotnet standalone executable // TODO: Incorporate this in the recipe itself diff --git a/nuget/engine/nunit.agent.addins b/nuget/engine/nunit.agent.addins deleted file mode 100644 index 1558d5f75..000000000 --- a/nuget/engine/nunit.agent.addins +++ /dev/null @@ -1 +0,0 @@ -../../ # refer to the directory containing the engine and runner diff --git a/nuget/engine/nunit.engine.nuget.addins b/nuget/engine/nunit.engine.nuget.addins deleted file mode 100644 index ffb7ae1b7..000000000 --- a/nuget/engine/nunit.engine.nuget.addins +++ /dev/null @@ -1,4 +0,0 @@ -../../../NUnit.Extension.*/**/tools/ # nuget v2 layout -../../../../NUnit.Extension.*/**/tools/ # nuget v3 layout -../../../nunit.extension.*/**/tools/ # nuget v2 layout -../../../../nunit.extension.*/**/tools/ # nuget v3 layout diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index e3eed7538..76dfd2651 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -66,11 +66,6 @@ - - - - - @@ -79,7 +74,6 @@ - diff --git a/nuget/runners/net8.0/DotnetToolSettings.xml b/nuget/runners/net8.0/DotnetToolSettings.xml deleted file mode 100644 index e49f98228..000000000 --- a/nuget/runners/net8.0/DotnetToolSettings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/nuget/runners/nunit.console-runner.net80.nuspec b/nuget/runners/nunit.console-runner.net80.nuspec deleted file mode 100644 index 00b997e5b..000000000 --- a/nuget/runners/nunit.console-runner.net80.nuspec +++ /dev/null @@ -1,53 +0,0 @@ - - - - NUnit.ConsoleRunner.Net80 - NUnit Console Runner (.NET 8.0) - $version$ - Charlie Poole, Rob Prouse - Charlie Poole, Rob Prouse - LICENSE.txt - https://nunit.org - - https://cdn.rawgit.com/nunit/resources/master/images/icon/nunit_256.png - images\nunit_256.png - false - .NET Core build of the console runner for the NUnit unit-testing framework. - - This package includes the .NET 8.0 build of the NUnit console runner and test engine. - - Any extensions, if needed, may be installed as separate packages. - - https://docs.nunit.org/articles/nunit/release-notes/console-and-engine.html - en-US - nunit test testing tdd runner - Copyright (c) 2021-2024 Charlie Poole, Rob Prouse - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index a2005cdec..d1aa3df2b 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -45,7 +45,6 @@ - diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 343dc1c81..3489e8a10 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -39,7 +39,6 @@ - @@ -53,7 +52,6 @@ - @@ -67,7 +65,6 @@ - @@ -81,7 +78,6 @@ - @@ -95,7 +91,6 @@ - @@ -108,7 +103,6 @@ - diff --git a/nuget/runners/nunit.console.nuget.addins b/nuget/runners/nunit.console.nuget.addins deleted file mode 100644 index f540c8540..000000000 --- a/nuget/runners/nunit.console.nuget.addins +++ /dev/null @@ -1,11 +0,0 @@ -# Extensions built for a single runtime target -../../NUnit.Extension.*/**/tools/ # nuget v2 layout -../../../NUnit.Extension.*/**/tools/ # nuget v3 layout -../../nunit.extension.*/**/tools/ # nuget v2 layout -../../../nunit.extension.*/**/tools/ # nuget v3 layout - -# Extensions built for multiple targets -../../NUnit.Extension.*/**/tools/*/ # nuget v2 layout -../../../NUnit.Extension.*/**/tools/*/ # nuget v3 layout -../../nunit.extension.*/**/tools/*/ # nuget v2 layout -../../../nunit.extension.*/**/tools/*/ # nuget v3 layout diff --git a/nuget/runners/nunit.console.nuget.agent.addins b/nuget/runners/nunit.console.nuget.agent.addins deleted file mode 100644 index ddfcb54ab..000000000 --- a/nuget/runners/nunit.console.nuget.agent.addins +++ /dev/null @@ -1,11 +0,0 @@ -# Extensions built for a single runtime target -../../../../NUnit.Extension.*/**/tools/ # nuget v2 layout -../../../../../NUnit.Extension.*/**/tools/ # nuget v3 layout -../../../../nunit.extension.*/**/tools/ # nuget v2 layout -../../../../../nunit.extension.*/**/tools/ # nuget v3 layout - -# Extensions built for multiple targets -../../../../NUnit.Extension.*/**/tools/*/ # nuget v2 layout -../../../../../NUnit.Extension.*/**/tools/*/ # nuget v3 layout -../../../../nunit.extension.*/**/tools/*/ # nuget v2 layout -../../../../../nunit.extension.*/**/tools/*/ # nuget v3 layout diff --git a/package-tests.cake b/package-tests.cake index 7c893672e..eab3b20c9 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -210,7 +210,7 @@ StandardRunnerTests.Add(new PackageTest( KnownExtensions.NUnitProjectLoader)); // V2 Result Writer Test -StandardRunnerTests.Add(new PackageTest( +AddToBothLists(new PackageTest( 1, "V2ResultWriterTest", "Run mock-assembly under .NET 6.0 and produce V2 output", "testdata/net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index eca71b122..dc3e8e12d 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -12,7 +12,7 @@ using NUnit.Engine.Internal.FileSystemAccess; using System.Diagnostics; -namespace NUnit.Engine.Services.Tests +namespace NUnit.Engine.Services { public class ExtensionManagerTests { @@ -58,7 +58,7 @@ public void CreateExtensionManager() // Find actual extension points. _extensionManager.FindExtensionPoints(typeof(CoreEngine).Assembly); _extensionManager.FindExtensionPoints(typeof(ITestEngine).Assembly); - // Find extensions. + // Find extensions directly in the their assemblies #if NETCOREAPP _extensionManager.FindExtensionsInAssembly(FakeExtensions("netstandard2.0")); #else diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 8e9d6d449..731ec1ab7 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -15,8 +15,9 @@ namespace NUnit.Engine.Services { public class ExtensionManager : IExtensionManager { - static readonly Logger log = InternalTrace.GetLogger(typeof(ExtensionService)); - static readonly Version ENGINE_VERSION = typeof(ExtensionService).Assembly.GetName().Version; + static readonly Logger log = InternalTrace.GetLogger(typeof(ExtensionManager)); + static readonly Assembly THIS_ASSEMBLY = Assembly.GetExecutingAssembly(); + static readonly Version ENGINE_VERSION = THIS_ASSEMBLY.GetName().Version; private readonly IFileSystem _fileSystem; //private readonly IAddinsFileReader _addinsReader; @@ -120,6 +121,11 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) } } + /// + /// Find and install extensions starting from a given base directory, + /// and using the contained '.addins' files to direct the search. + /// + /// public void FindExtensions(string startDir) { // Create the list of possible extension assemblies, @@ -131,6 +137,21 @@ public void FindExtensions(string startDir) FindExtensionsInAssembly(candidate); } + /// + /// Find and install extensions starting from a given base directory, + /// and using the provided list of patterns to direct the search using + /// a built-in algorithm. + /// + /// Path to the initial directory. + /// A list of patterns used to identify potential candidates. + public void FindExtensions(string startDir, string[] patterns) + { + FindExtensionAssemblies(_fileSystem.GetDirectory(startDir), patterns); + + foreach (var candidate in _assemblies) + FindExtensionsInAssembly(candidate); + } + /// /// Get an ExtensionPoint based on its unique identifying path. /// @@ -233,14 +254,36 @@ private ExtensionPoint DeduceExtensionPointFromType(TypeReference typeRef) } /// - /// Find candidate extension assemblies starting from a - /// given base directory. + /// Find candidate extension assemblies starting from a given base directory + /// and using the contained '.addins' files to direct the search. /// - /// - private void FindExtensionAssemblies(IDirectory startDir) + /// Path to the initial directory. + private void FindExtensionAssemblies(IDirectory initialDirectory) + { + // Since no patterns are provided, look for addins files + ProcessAddinsFiles(initialDirectory, false); + } + + /// + /// Find candidate extension assemblies starting from a given base directory + /// and using the provided list of patterns to direct the search using + /// a built-in algorithm. + /// + /// Path to the initial directory. + /// A list of patterns used to identify potential candidates. + private void FindExtensionAssemblies(IDirectory initialDirectory, string[] patterns) { - // First check the directory itself - ProcessAddinsFiles(startDir, false); + // Start looking two levels above initial directory + var startDir = initialDirectory.Parent.Parent; + + while (startDir != null) + { + foreach (var pattern in patterns) + foreach (var dir in _directoryFinder.GetDirectories(startDir, pattern)) + ProcessDirectory(dir, true); + + startDir = startDir.Parent; + } } /// diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index 74ef1abcf..d3dba41b4 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -8,6 +8,7 @@ using NUnit.Engine.Internal; using NUnit.Engine.Internal.FileSystemAccess; using NUnit.Engine.Internal.FileSystemAccess.Default; +using System.IO; namespace NUnit.Engine.Services { @@ -18,6 +19,13 @@ namespace NUnit.Engine.Services /// public class ExtensionService : Service, IExtensionService { + private static readonly Assembly THIS_ASSEMBLY = typeof(ExtensionService).Assembly; + + private static readonly string[] CHOCO_PATTERNS = new[] { + "nunit-extension-*/**/tools/", "nunit-extension-*/**/tools/*/" }; + private static readonly string[] NUGET_PATTERNS = new[] { + "NUnit.Extension.*/**/tools/", "NUnit.Extension.*/**/tools/*/" }; + private readonly IExtensionManager _extensionManager; public ExtensionService() @@ -81,8 +89,10 @@ public override void StartService() Assembly.GetExecutingAssembly(), typeof(ITestEngine).Assembly); - var thisAssembly = Assembly.GetExecutingAssembly(); - _extensionManager.FindExtensions(AssemblyHelper.GetDirectoryName(thisAssembly)); + var initialDirectory = AssemblyHelper.GetDirectoryName(THIS_ASSEMBLY); + bool isChocolateyPackage = System.IO.File.Exists(Path.Combine(THIS_ASSEMBLY.Location, "VERIFICATION.txt")); + + _extensionManager.FindExtensions(initialDirectory, isChocolateyPackage ? CHOCO_PATTERNS : NUGET_PATTERNS); Status = ServiceStatus.Started; } diff --git a/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs index c02704d5a..d688eb328 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs @@ -14,7 +14,22 @@ public interface IExtensionManager : IDisposable IEnumerable Extensions { get; } void FindExtensionPoints(params Assembly[] targetAssemblies); - void FindExtensions(string startDir); + + /// + /// Find and install extensions starting from a given base directory, + /// and using the contained '.addins' files to direct the search. + /// + /// Path to the initial directory. + void FindExtensions(string initialDirectory); + + /// + /// Find and install extensions starting from a given base directory, + /// and using the provided list of patterns to direct the search using + /// a built-in algorithm. + /// + /// Path to the initial directory. + /// A list of patterns used to identify potential candidates. + void FindExtensions(string initialDirectory, string[] patterns); IExtensionPoint GetExtensionPoint(string path); From bf591987ebcf4baa6f8a8af3b18b2de5820f4da7 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 1 Nov 2024 12:07:11 -0700 Subject: [PATCH 122/190] Revise api in response to comments --- choco/nunit-console-runner.nuspec | 6 -- choco/nunit.console.choco.addins | 7 --- choco/nunit.console.choco.agent.addins | 7 --- .../Services/ExtensionManagerTests.cs | 2 +- .../Services/ExtensionServiceTests.cs | 5 +- .../Services/ExtensionManager.cs | 63 +++++++++++-------- .../Services/ExtensionService.cs | 20 ++---- .../Services/IExtensionManager.cs | 10 ++- 8 files changed, 50 insertions(+), 70 deletions(-) delete mode 100644 choco/nunit.console.choco.addins delete mode 100644 choco/nunit.console.choco.agent.addins diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index a1c687367..d5eededcc 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -29,7 +29,6 @@ - @@ -49,7 +48,6 @@ - @@ -60,7 +58,6 @@ - @@ -71,7 +68,6 @@ - @@ -82,7 +78,6 @@ - @@ -93,6 +88,5 @@ - diff --git a/choco/nunit.console.choco.addins b/choco/nunit.console.choco.addins deleted file mode 100644 index d07fb7a21..000000000 --- a/choco/nunit.console.choco.addins +++ /dev/null @@ -1,7 +0,0 @@ -# Extensions built for a single runtime target -../../nunit-extension-*/**/tools/ # nuget v2 layout -../../../nunit-extension-*/**/tools/ # nuget v3 layout - -# Extensions built for multiple targets -../../nunit-extension-*/**/tools/*/ # nuget v2 layout -../../../nunit-extension-*/**/tools/*/ # nuget v3 layout diff --git a/choco/nunit.console.choco.agent.addins b/choco/nunit.console.choco.agent.addins deleted file mode 100644 index d326d241b..000000000 --- a/choco/nunit.console.choco.agent.addins +++ /dev/null @@ -1,7 +0,0 @@ -# Extensions built for a single runtime target -../../../../nunit-extension-*/tools/ # find extensions installed under chocolatey -../../../../../nunit-extension-*/tools/ # find extensions installed under chocolatey - -# Extensions built for multiple targets -../../../../nunit-extension-*/tools/*/ # find extensions installed under chocolatey -../../../../../nunit-extension-*/tools/*/ # find extensions installed under chocolatey diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index dc3e8e12d..ccf1382dd 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -12,7 +12,7 @@ using NUnit.Engine.Internal.FileSystemAccess; using System.Diagnostics; -namespace NUnit.Engine.Services +namespace NUnit.Engine.Services.Tests { public class ExtensionManagerTests { diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs index 3f6a5664f..9b8f3bc21 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -51,12 +52,12 @@ public void CreateService() [Test] public void StartServiceInitializesExtensionManager() { - var workingDir = AssemblyHelper.GetDirectoryName(typeof(ExtensionService).Assembly); + Assembly hostAssembly = typeof(ExtensionService).Assembly; _extensionService.StartService(); _extensionManager.ReceivedWithAnyArgs().FindExtensionPoints(typeof(ExtensionService).Assembly, typeof(ITestEngine).Assembly); - _extensionManager.Received().FindExtensions(workingDir); + _extensionManager.Received().FindStandardExtensions(hostAssembly); Assert.That(_extensionService.Status, Is.EqualTo(ServiceStatus.Started)); } diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 731ec1ab7..5becf2a7d 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -16,8 +16,6 @@ namespace NUnit.Engine.Services public class ExtensionManager : IExtensionManager { static readonly Logger log = InternalTrace.GetLogger(typeof(ExtensionManager)); - static readonly Assembly THIS_ASSEMBLY = Assembly.GetExecutingAssembly(); - static readonly Version ENGINE_VERSION = THIS_ASSEMBLY.GetName().Version; private readonly IFileSystem _fileSystem; //private readonly IAddinsFileReader _addinsReader; @@ -138,15 +136,18 @@ public void FindExtensions(string startDir) } /// - /// Find and install extensions starting from a given base directory, - /// and using the provided list of patterns to direct the search using - /// a built-in algorithm. + /// Find and install standard extensions for a host assembly using + /// a built-in algorithm that searches in certain known locations. /// - /// Path to the initial directory. - /// A list of patterns used to identify potential candidates. - public void FindExtensions(string startDir, string[] patterns) + /// An assembly that supports extensions. + public void FindStandardExtensions(Assembly hostAssembly) { - FindExtensionAssemblies(_fileSystem.GetDirectory(startDir), patterns); + bool isChocolateyPackage = System.IO.File.Exists(Path.Combine(hostAssembly.Location, "VERIFICATION.txt")); + string[] extensionPatterns = isChocolateyPackage + ? new[] { "nunit-extension-*/**/tools/", "nunit-extension-*/**/tools/*/" } + : new[] { "NUnit.Extension.*/**/tools/", "NUnit.Extension.*/**/tools/*/" }; + + FindExtensionAssemblies(hostAssembly, extensionPatterns); foreach (var candidate in _assemblies) FindExtensionsInAssembly(candidate); @@ -265,16 +266,15 @@ private void FindExtensionAssemblies(IDirectory initialDirectory) } /// - /// Find candidate extension assemblies starting from a given base directory - /// and using the provided list of patterns to direct the search using + /// Find candidate extension assemblies for a given host assembly, + /// using the provided list of patterns to direct the search using /// a built-in algorithm. /// - /// Path to the initial directory. + /// An assembly that supports extensions /// A list of patterns used to identify potential candidates. - private void FindExtensionAssemblies(IDirectory initialDirectory, string[] patterns) + private void FindExtensionAssemblies(Assembly hostAssembly, string[] patterns) { - // Start looking two levels above initial directory - var startDir = initialDirectory.Parent.Parent; + IDirectory startDir = _fileSystem.GetDirectory(AssemblyHelper.GetDirectoryName(hostAssembly)); while (startDir != null) { @@ -459,18 +459,29 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) } #endif - foreach (var type in assembly.MainModule.GetTypes()) + foreach (var extensionType in assembly.MainModule.GetTypes()) { - CustomAttribute extensionAttr = type.GetAttribute("NUnit.Engine.Extensibility.ExtensionAttribute"); + CustomAttribute extensionAttr = extensionType.GetAttribute("NUnit.Engine.Extensibility.ExtensionAttribute"); if (extensionAttr == null) continue; - object versionArg = extensionAttr.GetNamedArgument("EngineVersion"); - if (versionArg != null && new Version((string)versionArg) > ENGINE_VERSION) - continue; + // TODO: This is a remnant of older code. In principle, this should be generalized + // to something like "HostVersion". However, this can safely remain until + // we separate ExtensionManager into its own assembly. + string versionArg = extensionAttr.GetNamedArgument("EngineVersion") as string; + if (versionArg != null) + { + Assembly THIS_ASSEMBLY = Assembly.GetExecutingAssembly(); + Version ENGINE_VERSION = THIS_ASSEMBLY.GetName().Version; + if (new Version(versionArg) > ENGINE_VERSION) + { + log.Warning($" Ignoring {extensionType.Name}. It requires version {versionArg}."); + continue; + } + } - var node = new ExtensionNode(assembly.FilePath, assembly.AssemblyVersion, type.FullName, assemblyTargetFramework) + var node = new ExtensionNode(assembly.FilePath, assembly.AssemblyVersion, extensionType.FullName, assemblyTargetFramework) { Path = extensionAttr.GetNamedArgument("Path") as string, Description = extensionAttr.GetNamedArgument("Description") as string @@ -479,9 +490,9 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) object enabledArg = extensionAttr.GetNamedArgument("Enabled"); node.Enabled = enabledArg == null || (bool)enabledArg; - log.Info(" Found ExtensionAttribute on Type " + type.Name); + log.Info(" Found ExtensionAttribute on Type " + extensionType.Name); - foreach (var attr in type.GetAttributes("NUnit.Engine.Extensibility.ExtensionPropertyAttribute")) + foreach (var attr in extensionType.GetAttributes("NUnit.Engine.Extensibility.ExtensionPropertyAttribute")) { string name = attr.ConstructorArguments[0].Value as string; string value = attr.ConstructorArguments[1].Value as string; @@ -498,12 +509,12 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) ExtensionPoint ep; if (node.Path == null) { - ep = DeduceExtensionPointFromType(type); + ep = DeduceExtensionPointFromType(extensionType); if (ep == null) { string msg = string.Format( "Unable to deduce ExtensionPoint for Type {0}. Specify Path on ExtensionAttribute to resolve.", - type.FullName); + extensionType.FullName); throw new NUnitEngineException(msg); } @@ -517,7 +528,7 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) { string msg = string.Format( "Unable to locate ExtensionPoint for Type {0}. The Path {1} cannot be found.", - type.FullName, + extensionType.FullName, node.Path); throw new NUnitEngineException(msg); } diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index d3dba41b4..7459ed8ca 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -19,13 +19,6 @@ namespace NUnit.Engine.Services /// public class ExtensionService : Service, IExtensionService { - private static readonly Assembly THIS_ASSEMBLY = typeof(ExtensionService).Assembly; - - private static readonly string[] CHOCO_PATTERNS = new[] { - "nunit-extension-*/**/tools/", "nunit-extension-*/**/tools/*/" }; - private static readonly string[] NUGET_PATTERNS = new[] { - "NUnit.Extension.*/**/tools/", "NUnit.Extension.*/**/tools/*/" }; - private readonly IExtensionManager _extensionManager; public ExtensionService() @@ -83,16 +76,13 @@ public void EnableExtension(string typeName, bool enabled) public override void StartService() { + Assembly thisAssembly = Assembly.GetExecutingAssembly(); + Assembly apiAssembly = typeof(ITestEngine).Assembly; + try { - _extensionManager.FindExtensionPoints( - Assembly.GetExecutingAssembly(), - typeof(ITestEngine).Assembly); - - var initialDirectory = AssemblyHelper.GetDirectoryName(THIS_ASSEMBLY); - bool isChocolateyPackage = System.IO.File.Exists(Path.Combine(THIS_ASSEMBLY.Location, "VERIFICATION.txt")); - - _extensionManager.FindExtensions(initialDirectory, isChocolateyPackage ? CHOCO_PATTERNS : NUGET_PATTERNS); + _extensionManager.FindExtensionPoints(thisAssembly, apiAssembly); + _extensionManager.FindStandardExtensions(thisAssembly); Status = ServiceStatus.Started; } diff --git a/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs index d688eb328..c6e9ebd63 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs @@ -23,13 +23,11 @@ public interface IExtensionManager : IDisposable void FindExtensions(string initialDirectory); /// - /// Find and install extensions starting from a given base directory, - /// and using the provided list of patterns to direct the search using - /// a built-in algorithm. + /// Find and install standard extensions for a host assembly using + /// a built-in algorithm that searches in certain known locations. /// - /// Path to the initial directory. - /// A list of patterns used to identify potential candidates. - void FindExtensions(string initialDirectory, string[] patterns); + /// An assembly that supports NUnit extensions. + void FindStandardExtensions(Assembly hostAssembly); IExtensionPoint GetExtensionPoint(string path); From 40f826a11be0178f9bf0878c97dd818b69f367c4 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 28 Nov 2024 08:48:01 -0800 Subject: [PATCH 123/190] Extract code from loop --- .../nunit.engine.core/Services/ExtensionManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 5becf2a7d..ecd770db8 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -15,6 +15,8 @@ namespace NUnit.Engine.Services { public class ExtensionManager : IExtensionManager { + static readonly Version CURRENT_ENGINE_VERSION = Assembly.GetExecutingAssembly().GetName().Version; + static readonly Logger log = InternalTrace.GetLogger(typeof(ExtensionManager)); private readonly IFileSystem _fileSystem; @@ -472,9 +474,7 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) string versionArg = extensionAttr.GetNamedArgument("EngineVersion") as string; if (versionArg != null) { - Assembly THIS_ASSEMBLY = Assembly.GetExecutingAssembly(); - Version ENGINE_VERSION = THIS_ASSEMBLY.GetName().Version; - if (new Version(versionArg) > ENGINE_VERSION) + if (new Version(versionArg) > CURRENT_ENGINE_VERSION) { log.Warning($" Ignoring {extensionType.Name}. It requires version {versionArg}."); continue; From d106b605e5156d8a9b6932aea9f8f420195c9417 Mon Sep 17 00:00:00 2001 From: stevenaw Date: Fri, 8 Nov 2024 21:32:05 -0500 Subject: [PATCH 124/190] Add a few tests for an issue under open investigation --- .../Services/TestSelectionParserTests.cs | 5 ++--- .../Services/TokenizerTests.cs | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs index a067bc972..47500507a 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs @@ -1,10 +1,8 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +using NUnit.Framework; using System; -using System.Collections.Generic; -using System.Text; using System.Xml; -using NUnit.Framework; namespace NUnit.Engine.Tests { @@ -40,6 +38,7 @@ public void CreateParser() [TestCase("test='My.Test.Fixture.Method(\"abc\\'s\")'", "My.Test.Fixture.Method("abc's")")] [TestCase("test='My.Test.Fixture.Method(\"x&y&z\")'", "My.Test.Fixture.Method("x&y&z")")] [TestCase("test='My.Test.Fixture.Method(\"\")'", "My.Test.Fixture.Method("<xyz>")")] + [TestCase("test=='Issue1510.TestSomething(Option1,\"ABC\")'", "Issue1510.TestSomething(Option1,"ABC")")] [TestCase("cat==Urgent && test=='My.Tests'", "UrgentMy.Tests")] [TestCase("cat==Urgent and test=='My.Tests'", "UrgentMy.Tests")] [TestCase("cat==Urgent || test=='My.Tests'", "UrgentMy.Tests")] diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs index 29c826a76..8a55594df 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs @@ -1,8 +1,5 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -using System; -using System.Collections.Generic; -using System.Text; using NUnit.Framework; namespace NUnit.Engine.Tests @@ -75,6 +72,19 @@ public void StringWithSlashes() Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Eof))); } + [Test] + public void TestNameWithParameters() + { + var tokenizer = new Tokenizer("test==Issue1510.TestSomething(Option1,\"ABC\")"); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "test"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, "=="))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "Issue1510.TestSomething"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, "("))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "Option1,\"ABC\""))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, ")"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Eof))); + } + [Test] public void StringsMayContainEscapedQuoteChar() { From 9ae2fe13008e83d0ed8b701dc2360eb814834e5e Mon Sep 17 00:00:00 2001 From: stevenaw Date: Fri, 8 Nov 2024 21:42:23 -0500 Subject: [PATCH 125/190] update format to one which works --- .../nunit.engine.tests/Services/TokenizerTests.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs index 8a55594df..4d5ad1818 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs @@ -75,13 +75,10 @@ public void StringWithSlashes() [Test] public void TestNameWithParameters() { - var tokenizer = new Tokenizer("test==Issue1510.TestSomething(Option1,\"ABC\")"); + var tokenizer = new Tokenizer("test=='Issue1510.TestSomething(Option1,\"ABC\")'"); Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "test"))); Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, "=="))); - Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "Issue1510.TestSomething"))); - Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, "("))); - Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "Option1,\"ABC\""))); - Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, ")"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.String, "Issue1510.TestSomething(Option1,\"ABC\")"))); Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Eof))); } From 1af35ed44618c64fa44d9d33028648ee879d7f06 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 30 Nov 2024 12:32:20 -0800 Subject: [PATCH 126/190] Fix exception arising from parsing test name --- .../Services/TestSelectionParserTests.cs | 125 +++++++++++++----- .../Services/TestSelectionParser.cs | 91 ++++++++++++- .../nunit.engine/Services/Tokenizer.cs | 3 +- 3 files changed, 176 insertions(+), 43 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs index 47500507a..334e690f7 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs @@ -2,6 +2,8 @@ using NUnit.Framework; using System; +using System.Collections; +using System.Collections.Generic; using System.Xml; namespace NUnit.Engine.Tests @@ -16,47 +18,19 @@ public void CreateParser() _parser = new TestSelectionParser(); } - [TestCase("cat=Urgent", "Urgent")] - [TestCase("cat==Urgent", "Urgent")] - [TestCase("cat!=Urgent", "Urgent")] - [TestCase("cat =~ Urgent", "Urgent")] - [TestCase("cat !~ Urgent", "Urgent")] - [TestCase("cat = Urgent || cat = High", "UrgentHigh")] - [TestCase("Priority == High", "High")] - [TestCase("Priority != Urgent", "Urgent")] - [TestCase("Author =~ Jones", "Jones")] - [TestCase("Author !~ Jones", "Jones")] - [TestCase("name='SomeTest'", "SomeTest")] - [TestCase("method=TestMethod", "TestMethod")] - [TestCase("method=Test1||method=Test2||method=Test3", "Test1Test2Test3")] - [TestCase("namespace=Foo", "Foo")] - [TestCase("namespace=Foo.Bar", "Foo.Bar")] - [TestCase("namespace=Foo||namespace=Bar", "FooBar")] - [TestCase("namespace=Foo.Bar||namespace=Bar.Baz", "Foo.BarBar.Baz")] - [TestCase("test='My.Test.Fixture.Method(42)'", "My.Test.Fixture.Method(42)")] - [TestCase("test='My.Test.Fixture.Method(\"xyz\")'", "My.Test.Fixture.Method("xyz")")] - [TestCase("test='My.Test.Fixture.Method(\"abc\\'s\")'", "My.Test.Fixture.Method("abc's")")] - [TestCase("test='My.Test.Fixture.Method(\"x&y&z\")'", "My.Test.Fixture.Method("x&y&z")")] - [TestCase("test='My.Test.Fixture.Method(\"\")'", "My.Test.Fixture.Method("<xyz>")")] - [TestCase("test=='Issue1510.TestSomething(Option1,\"ABC\")'", "Issue1510.TestSomething(Option1,"ABC")")] - [TestCase("cat==Urgent && test=='My.Tests'", "UrgentMy.Tests")] - [TestCase("cat==Urgent and test=='My.Tests'", "UrgentMy.Tests")] - [TestCase("cat==Urgent || test=='My.Tests'", "UrgentMy.Tests")] - [TestCase("cat==Urgent or test=='My.Tests'", "UrgentMy.Tests")] - [TestCase("cat==Urgent || test=='My.Tests' && cat == high", "UrgentMy.Testshigh")] - [TestCase("cat==Urgent && test=='My.Tests' || cat == high", "UrgentMy.Testshigh")] - [TestCase("cat==Urgent && (test=='My.Tests' || cat == high)", "UrgentMy.Testshigh")] - [TestCase("cat==Urgent && !(test=='My.Tests' || cat == high)", "UrgentMy.Testshigh")] - [TestCase("!(test!='My.Tests')", "My.Tests")] - [TestCase("!(cat!=Urgent)", "Urgent")] - public void TestParser(string input, string output) + [TestCaseSource(nameof(UniqueOutputs))] + public void AllOutputsAreValidXml(string output) { - Assert.That(_parser.Parse(input), Is.EqualTo(output)); - XmlDocument doc = new XmlDocument(); Assert.DoesNotThrow(() => doc.LoadXml(output)); } + [TestCaseSource(nameof(ParserTestCases))] + public void TestParser(string input, string output) + { + Assert.That(_parser.Parse(input), Is.EqualTo(output)); + } + [TestCase(null, typeof(ArgumentNullException))] [TestCase("", typeof(TestSelectionParserException))] [TestCase(" ", typeof(TestSelectionParserException))] @@ -65,5 +39,84 @@ public void TestParser_InvalidInput(string input, Type type) { Assert.That(() => _parser.Parse(input), Throws.TypeOf(type)); } + + private static readonly TestCaseData[] ParserTestCases = new[] + { + // Category Filter + new TestCaseData("cat=Urgent", "Urgent"), + new TestCaseData("cat=/Urgent/", "Urgent"), + new TestCaseData("cat='Urgent'", "Urgent"), + new TestCaseData("cat==Urgent", "Urgent"), + new TestCaseData("cat!=Urgent", "Urgent"), + new TestCaseData("cat =~ Urgent", "Urgent"), + new TestCaseData("cat !~ Urgent", "Urgent"), + // Property Filter + new TestCaseData("Priority == High", "High"), + new TestCaseData("Priority != Urgent", "Urgent"), + new TestCaseData("Author =~ Jones", "Jones"), + new TestCaseData("Author !~ Jones", "Jones"), + // Name Filter + new TestCaseData("name='SomeTest'", "SomeTest"), + // Method Filter + new TestCaseData("method=TestMethod", "TestMethod"), + new TestCaseData("method=Test1||method=Test2||method=Test3", "Test1Test2Test3"), + // Namespace Filter + new TestCaseData("namespace=Foo", "Foo"), + new TestCaseData("namespace=Foo.Bar", "Foo.Bar"), + new TestCaseData("namespace=Foo||namespace=Bar", "FooBar"), + new TestCaseData("namespace=Foo.Bar||namespace=Bar.Baz", "Foo.BarBar.Baz"), + // Test Filter + new TestCaseData("test='My.Test.Fixture.Method(42)'", "My.Test.Fixture.Method(42)"), + new TestCaseData("test='My.Test.Fixture.Method(\"xyz\")'", "My.Test.Fixture.Method("xyz")"), + new TestCaseData("test='My.Test.Fixture.Method(\"abc\\'s\")'", "My.Test.Fixture.Method("abc's")"), + new TestCaseData("test='My.Test.Fixture.Method(\"x&y&z\")'", "My.Test.Fixture.Method("x&y&z")"), + new TestCaseData("test='My.Test.Fixture.Method(\"\")'", "My.Test.Fixture.Method("<xyz>")"), + new TestCaseData("test=='Issue1510.TestSomething ( Option1 , \"ABC\" ) '", "Issue1510.TestSomething(Option1,"ABC")"), + new TestCaseData("test=='Issue1510.TestSomething ( Option1 , \"A B C\" ) '", "Issue1510.TestSomething(Option1,"A B C")"), + new TestCaseData("test=/My.Test.Fixture.Method(42)/", "My.Test.Fixture.Method(42)"), + new TestCaseData("test=/My.Test.Fixture.Method(\"xyz\")/", "My.Test.Fixture.Method("xyz")"), + new TestCaseData("test=/My.Test.Fixture.Method(\"abc\\'s\")/", "My.Test.Fixture.Method("abc's")"), + new TestCaseData("test=/My.Test.Fixture.Method(\"x&y&z\")/", "My.Test.Fixture.Method("x&y&z")"), + new TestCaseData("test=/My.Test.Fixture.Method(\"\")/", "My.Test.Fixture.Method("<xyz>")"), + new TestCaseData("test==/Issue1510.TestSomething ( Option1 , \"ABC\" ) /", "Issue1510.TestSomething(Option1,"ABC")"), + new TestCaseData("test==/Issue1510.TestSomething ( Option1 , \"A B C\" ) /", "Issue1510.TestSomething(Option1,"A B C")"), + new TestCaseData("test=My.Test.Fixture.Method(42)", "My.Test.Fixture.Method(42)"), + new TestCaseData("test=My.Test.Fixture.Method(\"xyz\")", "My.Test.Fixture.Method("xyz")"), + new TestCaseData("test=My.Test.Fixture.Method(\"abc\\'s\")", "My.Test.Fixture.Method("abc's")"), + new TestCaseData("test=My.Test.Fixture.Method(\"x&y&z\")", "My.Test.Fixture.Method("x&y&z")"), + new TestCaseData("test=My.Test.Fixture.Method(\"\")", "My.Test.Fixture.Method("<xyz>")"), + new TestCaseData("test==Issue1510.TestSomething ( Option1 , \"ABC\" ) ", "Issue1510.TestSomething(Option1,"ABC")"), + new TestCaseData("test==Issue1510.TestSomething ( Option1 , \"A B C\" ) ", "Issue1510.TestSomething(Option1,"A B C")"), + // And Filter + new TestCaseData("cat==Urgent && test=='My.Tests'", "UrgentMy.Tests"), + new TestCaseData("cat==Urgent and test=='My.Tests'", "UrgentMy.Tests"), + // Or Filter + new TestCaseData("cat==Urgent || test=='My.Tests'", "UrgentMy.Tests"), + new TestCaseData("cat==Urgent or test=='My.Tests'", "UrgentMy.Tests"), + // Mixed And Filter with Or Filter + new TestCaseData("cat = Urgent || cat = High", "UrgentHigh"), + new TestCaseData("cat==Urgent || test=='My.Tests' && cat == high", "UrgentMy.Testshigh"), + new TestCaseData("cat==Urgent && test=='My.Tests' || cat == high", "UrgentMy.Testshigh"), + new TestCaseData("cat==Urgent && (test=='My.Tests' || cat == high)", "UrgentMy.Testshigh"), + new TestCaseData("cat==Urgent && !(test=='My.Tests' || cat == high)", "UrgentMy.Testshigh"), + // Not Filter + new TestCaseData("!(test!='My.Tests')", "My.Tests"), + new TestCaseData("!(cat!=Urgent)", "Urgent") + }; + + private static IEnumerable UniqueOutputs() + { + List alreadyReturned = new List(); + + foreach (var testCase in ParserTestCases) + { + var output = testCase.Arguments[1] as string; + if (!alreadyReturned.Contains(output)) + { + alreadyReturned.Add(output); + yield return output; + } + } + } } } diff --git a/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs b/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs index 387836edf..8873d172c 100644 --- a/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs +++ b/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; // Missing XML Docs @@ -37,6 +38,7 @@ public class TestSelectionParser private static readonly Token[] REL_OPS = new Token[] { EQ_OP1, EQ_OP2, NE_OP, MATCH_OP, NOMATCH_OP }; private static readonly Token EOF = new Token(TokenKind.Eof); + private static readonly Token COMMA = new Token(TokenKind.Symbol, ","); public string Parse(string input) { @@ -116,21 +118,29 @@ public string ParseFilterElement() return ParseExpressionInParentheses(); Token lhs = Expect(TokenKind.Word); + Token op; + Token rhs; switch (lhs.Text) { - case "id": + case "test": + op = Expect(REL_OPS); + rhs = GetTestName(); + return EmitFilterElement(lhs, op, rhs); + case "cat": case "method": case "class": case "name": - case "test": case "namespace": case "partition": - Token op = lhs.Text == "id" - ? Expect(EQ_OPS) - : Expect(REL_OPS); - Token rhs = Expect(TokenKind.String, TokenKind.Word); + op = Expect(REL_OPS); + rhs = Expect(TokenKind.String, TokenKind.Word); + return EmitFilterElement(lhs, op, rhs); + + case "id": + op = Expect(EQ_OPS); + rhs = Expect(TokenKind.String, TokenKind.Word); return EmitFilterElement(lhs, op, rhs); default: @@ -142,6 +152,75 @@ public string ParseFilterElement() } } + // TODO: We do extra work for test names due to the fact that + // Windows drops double quotes from arguments in many situations. + // It would be better to parse the command-line directly but + // that will mean a significant rewrite. + private Token GetTestName() + { + var result = Expect(TokenKind.String, TokenKind.Word); + var sb = new StringBuilder(); + + if (result.Kind == TokenKind.String) + { + int index = result.Text.IndexOf('('); + + if (index < 0) + return result; + + // Remove white space around arguments + string testName = result.Text; + sb = new StringBuilder(testName.Substring(0, index).Trim()); + sb.Append('('); + bool done = false; + + while (++index < testName.Length && !done) + { + char ch = testName[index]; + switch (ch) + { + case '"': + sb.Append(ch); + while (++index < testName.Length && testName[index] != '"') + sb.Append(testName[index]); + sb.Append('"'); + break; + case ' ': + break; + default: + sb.Append(ch); + done = ch == ')'; + break; + } + } + } + else + { + // Word Token - check to see if it's followed by a left parenthesis + if (_tokenizer.LookAhead != LPAREN) return result; + + // We have a "Word" token followed by a left parenthesis + // This may be a testname entered without quotes or one + // using double quotes, which were removed by the shell. + + sb = new StringBuilder(result.Text); + var token = NextToken(); + + while (token != EOF) + { + bool isString = token.Kind == TokenKind.String; + + if (isString) sb.Append('"'); + sb.Append(token.Text); + if (isString) sb.Append('"'); + + token = NextToken(); + } + } + + return new Token(TokenKind.String, sb.ToString()); + } + private static string EmitFilterElement(Token lhs, Token op, Token rhs) { string fmt = null; diff --git a/src/NUnitEngine/nunit.engine/Services/Tokenizer.cs b/src/NUnitEngine/nunit.engine/Services/Tokenizer.cs index e8d4fd4c5..ece264277 100644 --- a/src/NUnitEngine/nunit.engine/Services/Tokenizer.cs +++ b/src/NUnitEngine/nunit.engine/Services/Tokenizer.cs @@ -83,7 +83,7 @@ public class Tokenizer private int _index; private const char EOF_CHAR = '\0'; - private const string WORD_BREAK_CHARS = "=!()&|"; + private const string WORD_BREAK_CHARS = "=!()&| \t,"; private readonly string[] DOUBLE_CHAR_SYMBOLS = new string[] { "==", "=~", "!=", "!~", "&&", "||" }; private Token _lookahead; @@ -130,6 +130,7 @@ private Token GetNextToken() // Single char symbols case '(': case ')': + case ',': GetChar(); return new Token(TokenKind.Symbol, ch) { Pos = pos }; From 61113fffd5e7df3697829c08177ce282bfcf69a7 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 30 Nov 2024 19:56:52 -0800 Subject: [PATCH 127/190] Update IExtensionManager doc comments --- .../Services/ExtensionManager.cs | 48 +++++++------------ .../Services/IExtensionManager.cs | 40 ++++++++++++---- 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index ecd770db8..5ebd6f0bf 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -47,25 +47,19 @@ internal ExtensionManager(IFileSystem fileSystem, IDirectoryFinder directoryFind #region IExtensionManager Implementation - /// - /// Gets an enumeration of all ExtensionPoints in the engine. - /// + /// public IEnumerable ExtensionPoints { get { return _extensionPoints.ToArray(); } } - /// - /// Gets an enumeration of all installed Extensions. - /// + /// public IEnumerable Extensions { get { return _extensions.ToArray(); } } - /// - /// Find the extension points in a loaded assembly. - /// + /// public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) { foreach (var assembly in targetAssemblies) @@ -121,11 +115,7 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) } } - /// - /// Find and install extensions starting from a given base directory, - /// and using the contained '.addins' files to direct the search. - /// - /// + /// public void FindExtensions(string startDir) { // Create the list of possible extension assemblies, @@ -137,11 +127,7 @@ public void FindExtensions(string startDir) FindExtensionsInAssembly(candidate); } - /// - /// Find and install standard extensions for a host assembly using - /// a built-in algorithm that searches in certain known locations. - /// - /// An assembly that supports extensions. + /// public void FindStandardExtensions(Assembly hostAssembly) { bool isChocolateyPackage = System.IO.File.Exists(Path.Combine(hostAssembly.Location, "VERIFICATION.txt")); @@ -155,14 +141,20 @@ public void FindStandardExtensions(Assembly hostAssembly) FindExtensionsInAssembly(candidate); } - /// - /// Get an ExtensionPoint based on its unique identifying path. - /// + /// public IExtensionPoint GetExtensionPoint(string path) { return _pathIndex.ContainsKey(path) ? _pathIndex[path] : null; } + /// + public IEnumerable GetExtensions() + { + foreach (var node in GetExtensionNodes()) + yield return (T)((ExtensionNode)node).ExtensionObject; // HACK + } + + /// public IEnumerable GetExtensionNodes(string path) { var ep = GetExtensionPoint(path); @@ -171,6 +163,7 @@ public IEnumerable GetExtensionNodes(string path) yield return node; } + /// public IExtensionNode GetExtensionNode(string path) { // TODO: Remove need for the cast @@ -179,6 +172,7 @@ public IExtensionNode GetExtensionNode(string path) return ep != null && ep.Extensions.Count > 0 ? ep.Extensions[0] : null; } + /// public IEnumerable GetExtensionNodes(bool includeDisabled = false) { var ep = GetExtensionPoint(typeof(T)); @@ -188,15 +182,7 @@ public IEnumerable GetExtensionNodes(bool includeDisabled = fa yield return node; } - public IEnumerable GetExtensions() - { - foreach (var node in GetExtensionNodes()) - yield return (T)((ExtensionNode)node).ExtensionObject; // HACK - } - - /// - /// Enable or disable an extension - /// + /// public void EnableExtension(string typeName, bool enabled) { foreach (var node in _extensions) diff --git a/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs index c6e9ebd63..e9c7858b3 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs @@ -10,9 +10,19 @@ namespace NUnit.Engine.Services { public interface IExtensionManager : IDisposable { + /// + /// Gets an enumeration of all ExtensionPoints in the engine. + /// IEnumerable ExtensionPoints { get; } + + /// + /// Gets an enumeration of all installed Extensions. + /// IEnumerable Extensions { get; } + /// + /// Find the extension points in a loaded assembly. + /// void FindExtensionPoints(params Assembly[] targetAssemblies); /// @@ -29,27 +39,37 @@ public interface IExtensionManager : IDisposable /// An assembly that supports NUnit extensions. void FindStandardExtensions(Assembly hostAssembly); + /// + /// Get an ExtensionPoint based on its unique identifying path. + /// IExtensionPoint GetExtensionPoint(string path); + /// + /// Get extension objects for all nodes of a given type + /// IEnumerable GetExtensions(); + /// + /// Get all ExtensionNodes for a give path + /// IEnumerable GetExtensionNodes(string path); + + /// + /// Get the first or only ExtensionNode for a given ExtensionPoint + /// + /// The identifying path for an ExtensionPoint + /// IExtensionNode GetExtensionNode(string path); + /// - /// Returns all extension nodes for a given Type. + /// Get all extension nodes of a given Type. /// - /// The Type of the node /// If true, disabled nodes are included - /// An enumeration of ExtensionNodes - /// - /// Unlike other methods, this method returns an actual ExtensionNode rather - /// than an IExtensionNode. It is required in order for classes that support - /// extensions to create the actual extension object. - /// - /// - // NOTE: IEnumerable GetExtensionNodes(bool includeDisabled = false); + /// + /// Enable or disable an extension + /// void EnableExtension(string typeName, bool enabled); } From 37039939c507321440d4acc9c8230ca83e694d35 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 1 Dec 2024 03:46:48 -0800 Subject: [PATCH 128/190] Add tests to show that the .NET Core runner can load compatible extensions --- NetFXTests.nunit => MixedTests.nunit | 0 NUnitConsole.sln | 3 ++- NetCoreTests.nunit | 11 +++++++++++ package-tests.cake | 13 ++++++++++--- 4 files changed, 23 insertions(+), 4 deletions(-) rename NetFXTests.nunit => MixedTests.nunit (100%) create mode 100644 NetCoreTests.nunit diff --git a/NetFXTests.nunit b/MixedTests.nunit similarity index 100% rename from NetFXTests.nunit rename to MixedTests.nunit diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 8702696b9..5150d355e 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -22,7 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution GitVersion.yml = GitVersion.yml global.json = global.json LICENSE.txt = LICENSE.txt - NetFXTests.nunit = NetFXTests.nunit + NetCoreTests.nunit = NetCoreTests.nunit + MixedTests.nunit = MixedTests.nunit NOTICES.txt = NOTICES.txt NuGet.config = NuGet.config nunit.ico = nunit.ico diff --git a/NetCoreTests.nunit b/NetCoreTests.nunit new file mode 100644 index 000000000..093078013 --- /dev/null +++ b/NetCoreTests.nunit @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/package-tests.cake b/package-tests.cake index eab3b20c9..0f371e852 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -201,12 +201,19 @@ AddToBothLists(new PackageTest( // RUN TESTS USING EACH OF OUR EXTENSIONS ////////////////////////////////////////////////////////////////////// -// NUnit Project Loader Test +// NUnit Project Loader Tests StandardRunnerTests.Add(new PackageTest( 1, "NUnitProjectTest", "Run NUnit project with mock-assembly.dll built for .NET 4.6.2 and 6.0", - "../../NetFXTests.nunit --config=Release --trace=Debug", - new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"), + "../../MixedTests.nunit --config=Release", + new MockAssemblyExpectedResult("net-4.6.2", "net-6.0"), + KnownExtensions.NUnitProjectLoader)); + +NetCoreRunnerTests.Add(new PackageTest( + 1, "NUnitProjectTest", + "Run NUnit project with mock-assembly.dll built for .NET 6.0 and 8.0", + "../../NetCoreTests.nunit --config=Release", + new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0"), KnownExtensions.NUnitProjectLoader)); // V2 Result Writer Test From 16128cdb139fc547c92bde7b1251ffc18c7b4b3f Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 2 Dec 2024 07:28:19 -0800 Subject: [PATCH 129/190] Add more tests --- .../Services/TokenizerTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs index 4d5ad1818..babc0dd5f 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs @@ -42,6 +42,40 @@ public void WordsInUnicode() Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Eof))); } + [Test] + public void WordsWithSpecialCharacters() + { + var tokenizer = new Tokenizer("word_with_underscores word-with-dashes word.with.dots"); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "word_with_underscores"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "word-with-dashes"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "word.with.dots"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Eof))); + } + + private const string WORD_BREAK_CHARS = "=!()&| \t,"; + [Test] + public void WordBreakCharacters() + { + var tokenizer = new Tokenizer("word1==word2!=word3 func(arg1, arg2) this&&that||both"); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "word1"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, "=="))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "word2"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, "!="))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "word3"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "func"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, "("))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "arg1"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, ","))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "arg2"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, ")"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "this"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, "&&"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "that"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Symbol, "||"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Word, "both"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Eof))); + } + [Test] public void StringWithDoubleQuotes() { From 4330169d24cfc8728c0fbd38f602360a66f345f0 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 6 Dec 2024 19:56:20 -0800 Subject: [PATCH 130/190] Add support for additional extension directories --- .../nunit3-console.tests/CommandLineTests.cs | 7 +++ .../ConsoleRunnerTests.cs | 4 +- .../MakeTestPackageTests.cs | 2 +- .../nunit3-console/ConsoleOptions.cs | 12 +++-- .../nunit3-console/ConsoleRunner.cs | 47 ++++++++++++++----- src/NUnitConsole/nunit3-console/Program.cs | 5 ++ .../RequiredExtensionException.cs | 47 +++++++++++++++++++ .../nunit.engine.api/IExtensionService.cs | 9 +++- .../Services/ExtensionServiceTests.cs | 14 ++++++ .../Services/ExtensionManager.cs | 20 +++++--- .../Services/ExtensionService.cs | 31 +++++++----- 11 files changed, 159 insertions(+), 39 deletions(-) create mode 100644 src/NUnitConsole/nunit3-console/RequiredExtensionException.cs diff --git a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs index cf69e7c5e..1bdcc2470 100644 --- a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs @@ -859,6 +859,13 @@ public void DeprecatedLabelsOptionsAreReplacedCorrectly(string oldOption, string Assert.That(options.DisplayTestLabels, Is.EqualTo(newOption)); } + public void UserExtensionDirectoryTest() + { + ConsoleOptions options = ConsoleMocks.Options("--extensionDirectory=/a/b/c"); + Assert.That(options.Validate); + Assert.That(options.ExtensionDirectories.Contains("/a/b/c")); + } + private static IFileSystem GetFileSystemContainingFile(string fileName) { var fileSystem = new VirtualFileSystem(); diff --git a/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs b/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs index 991322567..17f038de4 100644 --- a/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs @@ -39,9 +39,9 @@ public void ThrowsNUnitEngineExceptionWhenTestResultsAreNotWriteable() } [Test] - public void ThrowsNUnitExceptionWhenTeamcityOptionIsSpecifiedButNotAvailable() + public void ThrowsRequiredExtensionExceptionWhenTeamcityOptionIsSpecifiedButNotAvailable() { - var ex = Assert.Throws( + var ex = Assert.Throws( () => new ConsoleRunner(_testEngine, ConsoleMocks.Options("mock-assembly.dll", "--teamcity"), new ColorConsoleWriter())); Assert.That(ex, Has.Message.Contains("teamcity")); diff --git a/src/NUnitConsole/nunit3-console.tests/MakeTestPackageTests.cs b/src/NUnitConsole/nunit3-console.tests/MakeTestPackageTests.cs index 7f8978a66..3f3173f64 100644 --- a/src/NUnitConsole/nunit3-console.tests/MakeTestPackageTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/MakeTestPackageTests.cs @@ -65,7 +65,7 @@ public void WhenOptionIsSpecified_PackageIncludesSetting(string option, string k var options = ConsoleMocks.Options("test.dll", option); var package = ConsoleRunner.MakeTestPackage(options); - Assert.That(package.Settings.ContainsKey(key), "Setting not included for {0}", option); + Assert.That(package.Settings.ContainsKey(key), $"Setting not included for {option}"); Assert.That(package.Settings[key], Is.EqualTo(val), "NumberOfTestWorkers not set correctly for {0}", option); } diff --git a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs index 5eba632c4..e5441f89a 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs @@ -41,7 +41,7 @@ internal ConsoleOptions(IDefaultOptionsProvider defaultOptionsProvider, IFileSys Parse(args); } - // Action to Perform + // Action to Perform ( Default is to run the tests ) public bool Explore { get; private set; } @@ -49,6 +49,11 @@ internal ConsoleOptions(IDefaultOptionsProvider defaultOptionsProvider, IFileSys public bool ShowVersion { get; private set; } + public bool ListExtensions { get; private set; } + + // Additional directories to be used to search for user extensions + public IList ExtensionDirectories { get; } = new List(); + // Select tests public IList InputFiles { get; } = new List(); @@ -153,8 +158,6 @@ public IList ResultOutputSpecifications public bool DebugAgent { get; private set; } - public bool ListExtensions { get; private set; } - public bool PauseBeforeRun { get; private set; } public string PrincipalPolicy { get; private set; } @@ -383,6 +386,9 @@ private void ConfigureOptions() this.Add("list-extensions", "List all extension points and the extensions for each.", v => ListExtensions = v != null); + this.Add("extensionDirectory=", "Specifies an additional directory to be examined for extensions. May be repeated.", + v => { ExtensionDirectories.Add(Path.GetFullPath(v)); }); + this.AddNetFxOnlyOption("set-principal-policy=", "Set PrincipalPolicy for the test domain.", NetFxOnlyOption("set-principal-policy=", v => PrincipalPolicy = parser.RequiredValue(v, "--set-principal-policy", "UnauthenticatedPrincipal", "NoPrincipal", "WindowsPrincipal"))); diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index f06503cec..9f5fe2b79 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -26,7 +26,10 @@ public class ConsoleRunner private const int MAXIMUM_RETURN_CODE_ALLOWED = 100; // In case we are running on Unix private const string EVENT_LISTENER_EXTENSION_PATH = "/NUnit/Engine/TypeExtensions/ITestEventListener"; - private const string TEAMCITY_EVENT_LISTENER = "NUnit.Engine.Listeners.TeamCityEventListener"; + private const string TEAMCITY_EVENT_LISTENER_FULLNAME = "NUnit.Engine.Listeners.TeamCityEventListener"; + private const string TEAMCITY_EVENT_LISTENER_NAME = "TeamCityEventListener"; + + private const string NUNIT_EXTENSION_DIRECTORIES = "NUNIT_EXTENSION_DIRECTORIES"; public static readonly int OK = 0; public static readonly int INVALID_ARG = -1; @@ -48,28 +51,40 @@ public class ConsoleRunner public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWriter writer) { - _engine = engine; - _options = options; - _outWriter = writer; - - _workDirectory = options.WorkDirectory ?? Directory.GetCurrentDirectory(); - - if (!Directory.Exists(_workDirectory)) - Directory.CreateDirectory(_workDirectory); + Guard.ArgumentNotNull(_engine = engine, nameof(engine)); + Guard.ArgumentNotNull(_options = options, nameof(options)); + Guard.ArgumentNotNull(_outWriter = writer, nameof(writer)); _resultService = _engine.Services.GetService(); + Guard.OperationValid(_resultService != null, "Internal Error: ResultService was not found"); + _filterService = _engine.Services.GetService(); + Guard.OperationValid(_filterService != null, "Internal Error: TestFilterService was not found"); + _extensionService = _engine.Services.GetService(); + Guard.OperationValid(_extensionService != null, "Internal Error: ExtensionService was not found"); + + var extensionPath = Environment.GetEnvironmentVariable(NUNIT_EXTENSION_DIRECTORIES); + if (!string.IsNullOrEmpty(extensionPath)) + foreach (string extensionDirectory in extensionPath.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) + _extensionService.FindExtensions(extensionDirectory); - // TODO: Exit with error if any of the services are not found + foreach (string extensionDirectory in _options.ExtensionDirectories) + _extensionService.FindExtensions(extensionDirectory); + + _workDirectory = options.WorkDirectory ?? Directory.GetCurrentDirectory(); + if (!Directory.Exists(_workDirectory)) + Directory.CreateDirectory(_workDirectory); if (_options.TeamCity) { bool teamcityInstalled = false; foreach (var node in _extensionService.GetExtensionNodes(EVENT_LISTENER_EXTENSION_PATH)) - if (teamcityInstalled = node.TypeName == TEAMCITY_EVENT_LISTENER) + if (teamcityInstalled = node.TypeName == TEAMCITY_EVENT_LISTENER_FULLNAME) break; - if (!teamcityInstalled) throw new NUnitEngineException("Option --teamcity specified but the extension is not installed."); + //Guard.ArgumentValid(teamcityInstalled, "Option --teamcity specified but the extension is not installed.", "--teamCity"); + if (!teamcityInstalled) + throw new RequiredExtensionException(TEAMCITY_EVENT_LISTENER_NAME, "--teamcity"); } // Enable TeamCityEventListener immediately, before the console is redirected @@ -310,6 +325,14 @@ private static string GetOSVersion() private void DisplayExtensionList() { + if (_options.ExtensionDirectories.Count > 0) + { + _outWriter.WriteLine(ColorStyle.SectionHeader, "User Extension Directories"); + foreach (var dir in _options.ExtensionDirectories) + _outWriter.WriteLine($" {Path.GetFullPath(dir)}"); + _outWriter.WriteLine(); + } + _outWriter.WriteLine(ColorStyle.SectionHeader, "Installed Extensions"); foreach (var ep in _extensionService?.ExtensionPoints ?? new IExtensionPoint[0]) diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index 06e8a96b1..02a5ad179 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -116,6 +116,11 @@ public static int Main(string[] args) { return new ConsoleRunner(engine, Options, OutWriter).Execute(); } + catch (RequiredExtensionException ex) + { + OutWriter.WriteLine(ColorStyle.Error, ex.Message); + return ConsoleRunner.INVALID_ARG; + } catch (TestSelectionParserException ex) { OutWriter.WriteLine(ColorStyle.Error, ex.Message); diff --git a/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs b/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs new file mode 100644 index 000000000..38de0082a --- /dev/null +++ b/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs @@ -0,0 +1,47 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; + +namespace NUnit.ConsoleRunner +{ + /// + /// RequiredExtensionException is thrown when the console runner is executed + /// with a command-line option that requires a particular extension and + /// that extension has not been installed. + /// + public class RequiredExtensionException : Exception + { + private static string Message1(string extensionName) => $"Required extension {extensionName} is not installed."; + private static string Message2(string extensionName, string option) => $"Option {option} specified but {extensionName} is not installed."; + + /// + /// Construct with the name of an extension + /// + public RequiredExtensionException(string extensionName) : base(Message1(extensionName)) + { + } + + /// + /// Construct with the name of an extension and the command-line option requiring that extension. + /// + public RequiredExtensionException(string extensionName, string option) : base(Message2(extensionName, option)) + { + } + + /// + /// Construct with the name of an extension and inner exception + /// + public RequiredExtensionException(string extensionName, Exception innerException) + : base(Message1(extensionName), innerException) + { + } + + /// + /// Construct with the name of an extension, a command-line option and inner exception + /// + public RequiredExtensionException(string extensionName, string option, Exception innerException) + : base(Message2(extensionName, option), innerException) + { + } + } +} diff --git a/src/NUnitEngine/nunit.engine.api/IExtensionService.cs b/src/NUnitEngine/nunit.engine.api/IExtensionService.cs index 56ab7a7a8..2a368be5a 100644 --- a/src/NUnitEngine/nunit.engine.api/IExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.api/IExtensionService.cs @@ -21,6 +21,13 @@ public interface IExtensionService /// IEnumerable Extensions { get; } + /// + /// Find and install extensions starting from a given base directory, + /// and using the contained '.addins' files to direct the search. + /// + /// Path to the initial directory. + void FindExtensions(string initialDirectory); + /// /// Get an ExtensionPoint based on its unique identifying path. /// @@ -34,8 +41,6 @@ public interface IExtensionService /// /// Enable or disable an extension /// - /// - /// void EnableExtension(string typeName, bool enabled); } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs index 9b8f3bc21..ead9f7a0d 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Text; @@ -61,6 +62,19 @@ public void StartServiceInitializesExtensionManager() Assert.That(_extensionService.Status, Is.EqualTo(ServiceStatus.Started)); } + [Test] + public void StartServiceInitializesExtensionManagerUsingAdditionalDirectories() + { + Assembly hostAssembly = typeof(ExtensionService).Assembly; + _extensionService.StartService(); + + var tempPath = Path.GetTempPath(); + _extensionService.FindExtensions(tempPath); + + _extensionManager.Received().FindExtensions(tempPath); + Assert.That(_extensionService.Status, Is.EqualTo(ServiceStatus.Started)); + } + [Test] public void GetExtensionPointCallsExtensionManager() { diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 5ebd6f0bf..0498d8d27 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -29,6 +29,8 @@ public class ExtensionManager : IExtensionManager private readonly List _extensions = new List(); private readonly List _assemblies = new List(); + private readonly List _extensionDirectories = new List(); + public ExtensionManager() : this(new FileSystem()) { @@ -118,13 +120,19 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) /// public void FindExtensions(string startDir) { - // Create the list of possible extension assemblies, - // eliminating duplicates, start in the provided directory. - FindExtensionAssemblies(_fileSystem.GetDirectory(startDir)); + // Ignore a call for a directory we have already used + if (!_extensionDirectories.Contains(startDir)) + { + _extensionDirectories.Add(startDir); - // Check each assembly to see if it contains extensions - foreach (var candidate in _assemblies) - FindExtensionsInAssembly(candidate); + // Create the list of possible extension assemblies, + // eliminating duplicates, start in the provided directory. + FindExtensionAssemblies(_fileSystem.GetDirectory(startDir)); + + // Check each assembly to see if it contains extensions + foreach (var candidate in _assemblies) + FindExtensionsInAssembly(candidate); + } } /// diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index 7459ed8ca..ac8142cfb 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -1,14 +1,10 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -using System; -using System.Collections.Generic; -using System.Reflection; -using TestCentric.Metadata; using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; using NUnit.Engine.Internal.FileSystemAccess; -using NUnit.Engine.Internal.FileSystemAccess.Default; -using System.IO; +using System.Collections.Generic; +using System.Reflection; namespace NUnit.Engine.Services { @@ -42,32 +38,41 @@ internal ExtensionService(IFileSystem fileSystem, IDirectoryFinder directoryFind _extensionManager = new ExtensionManager(fileSystem, directoryFinder); } + #region IExtensionService Implementation + + /// public IEnumerable ExtensionPoints => _extensionManager.ExtensionPoints; + /// public IEnumerable Extensions => _extensionManager.Extensions; - /// - /// Get an ExtensionPoint based on its unique identifying path. - /// + /// + public void FindExtensions(string initialDirectory) + { + _extensionManager.FindExtensions(initialDirectory); + } + + /// IExtensionPoint IExtensionService.GetExtensionPoint(string path) { return _extensionManager.GetExtensionPoint(path); } - /// - /// Get an enumeration of ExtensionNodes based on their identifying path. - /// + /// IEnumerable IExtensionService.GetExtensionNodes(string path) { foreach (var node in _extensionManager.GetExtensionNodes(path)) yield return node; } + /// public void EnableExtension(string typeName, bool enabled) { _extensionManager.EnableExtension(typeName, enabled); } + #endregion + public IEnumerable GetExtensions() => _extensionManager.GetExtensions(); public IExtensionNode GetExtensionNode(string path) => _extensionManager.GetExtensionNode(path); @@ -78,7 +83,7 @@ public override void StartService() { Assembly thisAssembly = Assembly.GetExecutingAssembly(); Assembly apiAssembly = typeof(ITestEngine).Assembly; - + try { _extensionManager.FindExtensionPoints(thisAssembly, apiAssembly); From 2550a113a2318b0e93edf9686c09b0ed5f7487db Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 8 Dec 2024 22:49:06 -0800 Subject: [PATCH 131/190] Changes from review and refactoring --- .../nunit3-console.tests/CommandLineTests.cs | 15 +--- .../nunit3-console/ConsoleOptions.cs | 29 +++---- .../nunit3-console/ConsoleRunner.cs | 83 ++++++++++--------- .../Services/ExtensionManager.cs | 28 ++++--- .../Services/ExtensionService.cs | 2 +- .../Services/IExtensionManager.cs | 76 ----------------- 6 files changed, 77 insertions(+), 156 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs diff --git a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs index 1bdcc2470..f2fda3363 100644 --- a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs @@ -144,6 +144,7 @@ public void NoInputFiles() [TestCase("DisposeRunners", "dispose-runners")] [TestCase("TeamCity", "teamcity")] [TestCase("SkipNonTestAssemblies", "skipnontestassemblies")] + [TestCase("NoResult", "noresult")] #if NETFRAMEWORK [TestCase("RunAsX86", "x86")] [TestCase("ShadowCopyFiles", "shadowcopy")] @@ -505,20 +506,6 @@ public void DefaultResultSpecification() Assert.That(spec.Transform, Is.Null); } - [Test] - public void NoResultSuppressesDefaultResultSpecification() - { - var options = ConsoleMocks.Options("test.dll", "-noresult"); - Assert.That(options.ResultOutputSpecifications.Count, Is.EqualTo(0)); - } - - [Test] - public void NoResultSuppressesAllResultSpecifications() - { - var options = ConsoleMocks.Options("test.dll", "-result:results.xml", "-noresult", "-result:nunit2results.xml;format=nunit2"); - Assert.That(options.ResultOutputSpecifications.Count, Is.EqualTo(0)); - } - [Test] public void InvalidResultSpecRecordsError() { diff --git a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs index e5441f89a..23b9922b8 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs @@ -21,8 +21,7 @@ public class ConsoleOptions : OptionSet { private static readonly string CURRENT_DIRECTORY_ON_ENTRY = Directory.GetCurrentDirectory(); - private bool validated; - private bool noresult; + private bool _validated; /// /// An abstraction of the file system @@ -52,13 +51,13 @@ internal ConsoleOptions(IDefaultOptionsProvider defaultOptionsProvider, IFileSys public bool ListExtensions { get; private set; } // Additional directories to be used to search for user extensions - public IList ExtensionDirectories { get; } = new List(); + public List ExtensionDirectories { get; } = new List(); // Select tests - public IList InputFiles { get; } = new List(); + public List InputFiles { get; } = new List(); - public IList TestList { get; } = new List(); + public List TestList { get; } = new List(); public IDictionary TestParameters { get; } = new Dictionary(); @@ -104,13 +103,15 @@ public string WorkDirectory public string InternalTraceLevel { get; private set; } public bool InternalTraceLevelSpecified { get { return InternalTraceLevel != null; } } + public bool NoResult { get; private set; } + private readonly List resultOutputSpecifications = new List(); - public IList ResultOutputSpecifications + public List ResultOutputSpecifications { get { - if (noresult) - return new OutputSpecification[0]; + //if (noresult) + // return new List(); if (resultOutputSpecifications.Count == 0) resultOutputSpecifications.Add( @@ -120,7 +121,7 @@ public IList ResultOutputSpecifications } } - public IList ExploreOutputSpecifications { get; } = new List(); + public List ExploreOutputSpecifications { get; } = new List(); public string ActiveConfig { get; private set; } public bool ActiveConfigSpecified { get { return ActiveConfig != null; } } @@ -162,9 +163,9 @@ public IList ResultOutputSpecifications public string PrincipalPolicy { get; private set; } - public IList WarningMessages { get; } = new List(); + public List WarningMessages { get; } = new List(); - public IList ErrorMessages { get; } = new List(); + public List ErrorMessages { get; } = new List(); private void ConfigureOptions() { @@ -277,7 +278,7 @@ private void ConfigureOptions() }); this.Add("noresult", "Don't save any test results.", - v => noresult = v != null); + v => NoResult = v != null); this.Add("labels=", "Specify whether to write test case names to the output. Values: Off, OnOutputOnly, Before, After, BeforeAndAfter", v => { @@ -419,11 +420,11 @@ private Action NetFxOnlyOption(string optionName, Action action) public bool Validate() { - if (!validated) + if (!_validated) { CheckOptionCombinations(); - validated = true; + _validated = true; } return ErrorMessages.Count == 0; diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index 9f5fe2b79..bbd1b6484 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -11,6 +11,7 @@ using NUnit.Engine.Extensibility; using System.Runtime.InteropServices; using System.Text; +using System.Linq; namespace NUnit.ConsoleRunner { @@ -72,9 +73,11 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri foreach (string extensionDirectory in _options.ExtensionDirectories) _extensionService.FindExtensions(extensionDirectory); - _workDirectory = options.WorkDirectory ?? Directory.GetCurrentDirectory(); - if (!Directory.Exists(_workDirectory)) + _workDirectory = options.WorkDirectory; + if (_workDirectory != null) Directory.CreateDirectory(_workDirectory); + else + _workDirectory = null; if (_options.TeamCity) { @@ -164,49 +167,49 @@ private int RunTests(TestPackage package, TestFilter filter) { var writer = new ColorConsoleWriter(!_options.NoColor); - foreach (var spec in _options.ResultOutputSpecifications) - { - var outputPath = Path.Combine(_workDirectory, spec.OutputPath); + if (!_options.NoResult) + foreach (var spec in _options.ResultOutputSpecifications) + { + var outputPath = Path.Combine(_workDirectory, spec.OutputPath); - IResultWriter resultWriter; + IResultWriter resultWriter; - try - { - resultWriter = GetResultWriter(spec); - } - catch (Exception ex) - { - throw new NUnitEngineException($"Error encountered in resolving output specification: {spec}", ex); - } + try + { + resultWriter = GetResultWriter(spec); + } + catch (Exception ex) + { + throw new NUnitEngineException($"Error encountered in resolving output specification: {spec}", ex); + } - try - { - var outputDirectory = Path.GetDirectoryName(outputPath); - Directory.CreateDirectory(outputDirectory); - } - catch (Exception ex) - { - writer.WriteLine(ColorStyle.Error, String.Format( - "The directory in --result {0} could not be created", - spec.OutputPath)); - writer.WriteLine(ColorStyle.Error, ExceptionHelper.BuildMessage(ex)); - return ConsoleRunner.UNEXPECTED_ERROR; - } + try + { + var outputDirectory = Path.GetDirectoryName(outputPath); + Directory.CreateDirectory(outputDirectory); + } + catch (Exception ex) + { + writer.WriteLine(ColorStyle.Error, String.Format( + "The directory in --result {0} could not be created", + spec.OutputPath)); + writer.WriteLine(ColorStyle.Error, ExceptionHelper.BuildMessage(ex)); + return ConsoleRunner.UNEXPECTED_ERROR; + } - try - { - resultWriter.CheckWritability(outputPath); - } - catch (Exception ex) - { - throw new NUnitEngineException( - String.Format( - "The path specified in --result {0} could not be written to", - spec.OutputPath), ex); + try + { + resultWriter.CheckWritability(outputPath); + } + catch (Exception ex) + { + throw new NUnitEngineException( + String.Format( + "The path specified in --result {0} could not be written to", + spec.OutputPath), ex); + } } - } - var labels = _options.DisplayTestLabels != null ? _options.DisplayTestLabels.ToUpperInvariant() : "ON"; @@ -335,7 +338,7 @@ private void DisplayExtensionList() _outWriter.WriteLine(ColorStyle.SectionHeader, "Installed Extensions"); - foreach (var ep in _extensionService?.ExtensionPoints ?? new IExtensionPoint[0]) + foreach (var ep in _extensionService.ExtensionPoints) { _outWriter.WriteLabelLine(" Extension Point: ", ep.Path); foreach (var node in ep.Extensions) diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 0498d8d27..bc6795abb 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -13,7 +13,7 @@ namespace NUnit.Engine.Services { - public class ExtensionManager : IExtensionManager + public class ExtensionManager { static readonly Version CURRENT_ENGINE_VERSION = Assembly.GetExecutingAssembly().GetName().Version; @@ -29,8 +29,12 @@ public class ExtensionManager : IExtensionManager private readonly List _extensions = new List(); private readonly List _assemblies = new List(); + // List of all extensionDirectories specified on command-line or in environment private readonly List _extensionDirectories = new List(); + // Dictionary containing all directory paths already examined + private readonly Dictionary _directoriesExamined = new Dictionary(); + public ExtensionManager() : this(new FileSystem()) { @@ -66,7 +70,7 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) { foreach (var assembly in targetAssemblies) { - log.Info("Scanning {0} assembly for extension points", assembly.GetName().Name); + log.Info("FindExtensionPoints scanning {0} assembly", assembly.GetName().Name); foreach (ExtensionPointAttribute attr in assembly.GetCustomAttributes(typeof(ExtensionPointAttribute), false)) { @@ -86,7 +90,7 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) _extensionPoints.Add(ep); _pathIndex.Add(ep.Path, ep); - log.Info(" Found Path={0}, Type={1}", ep.Path, ep.TypeName); + log.Info(" Found ExtensionPoint: Path={0}, Type={1}", ep.Path, ep.TypeName); } foreach (Type type in assembly.GetExportedTypes()) @@ -111,7 +115,7 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) _extensionPoints.Add(ep); _pathIndex.Add(path, ep); - log.Info(" Found Path={0}, Type={1}", ep.Path, ep.TypeName); + log.Info(" Found ExtensionPoint: Path={0}, Type={1}", ep.Path, ep.TypeName); } } } @@ -125,6 +129,8 @@ public void FindExtensions(string startDir) { _extensionDirectories.Add(startDir); + log.Info($"FindExtensions examining extension directory {startDir}"); + // Create the list of possible extension assemblies, // eliminating duplicates, start in the provided directory. FindExtensionAssemblies(_fileSystem.GetDirectory(startDir)); @@ -138,6 +144,8 @@ public void FindExtensions(string startDir) /// public void FindStandardExtensions(Assembly hostAssembly) { + log.Info($"FindStandardExtensions called for host {hostAssembly.FullName}"); + bool isChocolateyPackage = System.IO.File.Exists(Path.Combine(hostAssembly.Location, "VERIFICATION.txt")); string[] extensionPatterns = isChocolateyPackage ? new[] { "nunit-extension-*/**/tools/", "nunit-extension-*/**/tools/*/" } @@ -273,7 +281,7 @@ private void FindExtensionAssemblies(Assembly hostAssembly, string[] patterns) IDirectory startDir = _fileSystem.GetDirectory(AssemblyHelper.GetDirectoryName(hostAssembly)); while (startDir != null) - { + { foreach (var pattern in patterns) foreach (var dir in _directoryFinder.GetDirectories(startDir, pattern)) ProcessDirectory(dir, true); @@ -290,7 +298,7 @@ private void FindExtensionAssemblies(Assembly hostAssembly, string[] patterns) private void ProcessDirectory(IDirectory startDir, bool fromWildCard) { var directoryName = startDir.FullName; - if (WasVisited(startDir.FullName, fromWildCard)) + if (WasVisited(directoryName, fromWildCard)) { log.Warning($"Skipping directory '{directoryName}' because it was already visited."); } @@ -371,7 +379,7 @@ private void ProcessAddinsFile(IFile addinsFile, bool fromWildCard) private void ProcessCandidateAssembly(string filePath, bool fromWildCard) { - if (WasVisited(filePath, fromWildCard)) + if (WasVisited(filePath, fromWildCard)) return; MarkAsVisited(filePath, fromWildCard); @@ -410,16 +418,14 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) } } - private readonly Dictionary _visited = new Dictionary(); - private bool WasVisited(string filePath, bool fromWildcard) { - return _visited.ContainsKey($"path={ filePath }_visited={fromWildcard}"); + return _directoriesExamined.ContainsKey($"path={filePath}_visited={fromWildcard}"); } private void MarkAsVisited(string filePath, bool fromWildcard) { - _visited.Add($"path={ filePath }_visited={fromWildcard}", null); + _directoriesExamined.Add($"path={filePath}_visited={fromWildcard}", null); } /// diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index ac8142cfb..536ea3974 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -15,7 +15,7 @@ namespace NUnit.Engine.Services /// public class ExtensionService : Service, IExtensionService { - private readonly IExtensionManager _extensionManager; + private readonly ExtensionManager _extensionManager; public ExtensionService() { diff --git a/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs deleted file mode 100644 index e9c7858b3..000000000 --- a/src/NUnitEngine/nunit.engine.core/Services/IExtensionManager.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.Collections.Generic; -using System.Reflection; - -using NUnit.Engine.Extensibility; - -namespace NUnit.Engine.Services -{ - public interface IExtensionManager : IDisposable - { - /// - /// Gets an enumeration of all ExtensionPoints in the engine. - /// - IEnumerable ExtensionPoints { get; } - - /// - /// Gets an enumeration of all installed Extensions. - /// - IEnumerable Extensions { get; } - - /// - /// Find the extension points in a loaded assembly. - /// - void FindExtensionPoints(params Assembly[] targetAssemblies); - - /// - /// Find and install extensions starting from a given base directory, - /// and using the contained '.addins' files to direct the search. - /// - /// Path to the initial directory. - void FindExtensions(string initialDirectory); - - /// - /// Find and install standard extensions for a host assembly using - /// a built-in algorithm that searches in certain known locations. - /// - /// An assembly that supports NUnit extensions. - void FindStandardExtensions(Assembly hostAssembly); - - /// - /// Get an ExtensionPoint based on its unique identifying path. - /// - IExtensionPoint GetExtensionPoint(string path); - - /// - /// Get extension objects for all nodes of a given type - /// - IEnumerable GetExtensions(); - - /// - /// Get all ExtensionNodes for a give path - /// - IEnumerable GetExtensionNodes(string path); - - /// - /// Get the first or only ExtensionNode for a given ExtensionPoint - /// - /// The identifying path for an ExtensionPoint - /// - IExtensionNode GetExtensionNode(string path); - - /// - /// Get all extension nodes of a given Type. - /// - /// If true, disabled nodes are included - IEnumerable GetExtensionNodes(bool includeDisabled = false); - - /// - /// Enable or disable an extension - /// - void EnableExtension(string typeName, bool enabled); - } - -} From a4ddc7446f7cca99992917b36fc90357dbc3398f Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 9 Dec 2024 19:51:53 -0800 Subject: [PATCH 132/190] Revise API in order to avoid loading duplicate extensions --- .../nunit3-console/ConsoleRunner.cs | 9 +- .../nunit.engine.api/IExtensionService.cs | 2 +- .../Services/ExtensionServiceTests.cs | 6 +- .../Services/ExtensionManager.cs | 151 ++++++++++++------ .../Services/ExtensionService.cs | 6 +- 5 files changed, 116 insertions(+), 58 deletions(-) diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index bbd1b6484..2f6ffaede 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -56,6 +56,7 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri Guard.ArgumentNotNull(_options = options, nameof(options)); Guard.ArgumentNotNull(_outWriter = writer, nameof(writer)); + // NOTE: Accessing Services triggerss the engine to initialize all services _resultService = _engine.Services.GetService(); Guard.OperationValid(_resultService != null, "Internal Error: ResultService was not found"); @@ -68,10 +69,14 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri var extensionPath = Environment.GetEnvironmentVariable(NUNIT_EXTENSION_DIRECTORIES); if (!string.IsNullOrEmpty(extensionPath)) foreach (string extensionDirectory in extensionPath.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) - _extensionService.FindExtensions(extensionDirectory); + _extensionService.FindExtensionAssemblies(extensionDirectory); foreach (string extensionDirectory in _options.ExtensionDirectories) - _extensionService.FindExtensions(extensionDirectory); + _extensionService.FindExtensionAssemblies(extensionDirectory); + + // Trigger lazy loading of extensions + var dummy = _extensionService.Extensions; + _workDirectory = options.WorkDirectory; if (_workDirectory != null) diff --git a/src/NUnitEngine/nunit.engine.api/IExtensionService.cs b/src/NUnitEngine/nunit.engine.api/IExtensionService.cs index 2a368be5a..6f2cc7f12 100644 --- a/src/NUnitEngine/nunit.engine.api/IExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.api/IExtensionService.cs @@ -26,7 +26,7 @@ public interface IExtensionService /// and using the contained '.addins' files to direct the search. /// /// Path to the initial directory. - void FindExtensions(string initialDirectory); + void FindExtensionAssemblies(string initialDirectory); /// /// Get an ExtensionPoint based on its unique identifying path. diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs index ead9f7a0d..19263fe9d 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs @@ -58,7 +58,7 @@ public void StartServiceInitializesExtensionManager() _extensionService.StartService(); _extensionManager.ReceivedWithAnyArgs().FindExtensionPoints(typeof(ExtensionService).Assembly, typeof(ITestEngine).Assembly); - _extensionManager.Received().FindStandardExtensions(hostAssembly); + _extensionManager.Received().FindExtensionAssemblies(hostAssembly); Assert.That(_extensionService.Status, Is.EqualTo(ServiceStatus.Started)); } @@ -69,9 +69,9 @@ public void StartServiceInitializesExtensionManagerUsingAdditionalDirectories() _extensionService.StartService(); var tempPath = Path.GetTempPath(); - _extensionService.FindExtensions(tempPath); + _extensionService.FindExtensionAssemblies(tempPath); - _extensionManager.Received().FindExtensions(tempPath); + _extensionManager.Received().FindExtensionAssemblies(tempPath); Assert.That(_extensionService.Status, Is.EqualTo(ServiceStatus.Started)); } diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index bc6795abb..b033414cd 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -23,17 +23,29 @@ public class ExtensionManager //private readonly IAddinsFileReader _addinsReader; private readonly IDirectoryFinder _directoryFinder; + // List of all ExtensionPoints discovered private readonly List _extensionPoints = new List(); + + // Index to ExtensionPoints based on the Path private readonly Dictionary _pathIndex = new Dictionary(); - private readonly List _extensions = new List(); - private readonly List _assemblies = new List(); + // List of ExtensionNodes for all extensions discovered. + private List _extensions = new List(); + + private bool _extensionsAreLoaded; - // List of all extensionDirectories specified on command-line or in environment + // List of candidate ExtensionAssemblies, which should be examined for extensions. + // This list must be completely built before we examine any of the assemblies, since + // it's possible that the same assembly may be found in multiple versions. + private readonly List _candidateAssemblies = new List(); + + // List of all extensionDirectories specified on command-line or in environment, + // used to ignore duplicate calls to FindExtensions. private readonly List _extensionDirectories = new List(); - // Dictionary containing all directory paths already examined - private readonly Dictionary _directoriesExamined = new Dictionary(); + // Dictionary containing all directory paths and assembly paths already processed, + // used to prevent processing a directory or assembly more than once. + private readonly Dictionary _visited = new Dictionary(); public ExtensionManager() : this(new FileSystem()) @@ -53,19 +65,30 @@ internal ExtensionManager(IFileSystem fileSystem, IDirectoryFinder directoryFind #region IExtensionManager Implementation - /// + /// + /// Gets an enumeration of all ExtensionPoints in the engine. + /// public IEnumerable ExtensionPoints { get { return _extensionPoints.ToArray(); } } - /// + /// + /// Gets an enumeration of all installed Extensions. + /// public IEnumerable Extensions { - get { return _extensions.ToArray(); } + get + { + EnsureExtensionsAreLoaded(); + + return _extensions.ToArray(); + } } - /// + /// + /// Find the extension points in a loaded assembly. + /// public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) { foreach (var assembly in targetAssemblies) @@ -121,30 +144,35 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) } } - /// - public void FindExtensions(string startDir) + /// + /// Find extension assemblies starting from a given base directory, + /// and using the contained '.addins' files to direct the search. + /// + /// Path to the initial directory. + public void FindExtensionAssemblies(string startDir) { // Ignore a call for a directory we have already used if (!_extensionDirectories.Contains(startDir)) { _extensionDirectories.Add(startDir); - log.Info($"FindExtensions examining extension directory {startDir}"); + log.Info($"FindExtensionAssemblies examining extension directory {startDir}"); // Create the list of possible extension assemblies, // eliminating duplicates, start in the provided directory. - FindExtensionAssemblies(_fileSystem.GetDirectory(startDir)); - - // Check each assembly to see if it contains extensions - foreach (var candidate in _assemblies) - FindExtensionsInAssembly(candidate); + // In this top level directory, we only look at .addins files. + ProcessAddinsFiles(_fileSystem.GetDirectory(startDir), false); } } - /// - public void FindStandardExtensions(Assembly hostAssembly) + /// + /// Find ExtensionAssemblies for a host assembly using + /// a built-in algorithm that searches in certain known locations. + /// + /// An assembly that supports NUnit extensions. + public void FindExtensionAssemblies(Assembly hostAssembly) { - log.Info($"FindStandardExtensions called for host {hostAssembly.FullName}"); + log.Info($"FindExtensionAssemblies called for host {hostAssembly.FullName}"); bool isChocolateyPackage = System.IO.File.Exists(Path.Combine(hostAssembly.Location, "VERIFICATION.txt")); string[] extensionPatterns = isChocolateyPackage @@ -152,45 +180,77 @@ public void FindStandardExtensions(Assembly hostAssembly) : new[] { "NUnit.Extension.*/**/tools/", "NUnit.Extension.*/**/tools/*/" }; FindExtensionAssemblies(hostAssembly, extensionPatterns); + } + + /// + /// We can only load extensions after all candidate assemblies are identified. + /// This method is called internally to ensure the load happens before any + /// extensions are used. + /// + private void EnsureExtensionsAreLoaded() + { + if (!_extensionsAreLoaded) + { + _extensionsAreLoaded = true; - foreach (var candidate in _assemblies) - FindExtensionsInAssembly(candidate); + foreach (var candidate in _candidateAssemblies) + FindExtensionsInAssembly(candidate); + } } - /// + /// + /// Get an ExtensionPoint based on its unique identifying path. + /// public IExtensionPoint GetExtensionPoint(string path) { return _pathIndex.ContainsKey(path) ? _pathIndex[path] : null; } - /// + /// + /// Get extension objects for all nodes of a given type + /// public IEnumerable GetExtensions() { foreach (var node in GetExtensionNodes()) - yield return (T)((ExtensionNode)node).ExtensionObject; // HACK + yield return (T)node.ExtensionObject; } - /// + /// + /// Get all ExtensionNodes for a path + /// public IEnumerable GetExtensionNodes(string path) { + EnsureExtensionsAreLoaded(); + var ep = GetExtensionPoint(path); if (ep != null) foreach (var node in ep.Extensions) yield return node; } - /// + /// + /// Get the first or only ExtensionNode for a given ExtensionPoint + /// + /// The identifying path for an ExtensionPoint + /// public IExtensionNode GetExtensionNode(string path) { + EnsureExtensionsAreLoaded(); + // TODO: Remove need for the cast var ep = GetExtensionPoint(path) as ExtensionPoint; return ep != null && ep.Extensions.Count > 0 ? ep.Extensions[0] : null; } - /// + /// + /// Get all extension nodes of a given Type. + /// + /// If true, disabled nodes are included public IEnumerable GetExtensionNodes(bool includeDisabled = false) { + EnsureExtensionsAreLoaded(); + var ep = GetExtensionPoint(typeof(T)); if (ep != null) foreach (var node in ep.Extensions) @@ -198,9 +258,13 @@ public IEnumerable GetExtensionNodes(bool includeDisabled = fa yield return node; } - /// + /// + /// Enable or disable an extension + /// public void EnableExtension(string typeName, bool enabled) { + EnsureExtensionsAreLoaded(); + foreach (var node in _extensions) if (node.TypeName == typeName) node.Enabled = enabled; @@ -258,17 +322,6 @@ private ExtensionPoint DeduceExtensionPointFromType(TypeReference typeRef) : null; } - /// - /// Find candidate extension assemblies starting from a given base directory - /// and using the contained '.addins' files to direct the search. - /// - /// Path to the initial directory. - private void FindExtensionAssemblies(IDirectory initialDirectory) - { - // Since no patterns are provided, look for addins files - ProcessAddinsFiles(initialDirectory, false); - } - /// /// Find candidate extension assemblies for a given host assembly, /// using the provided list of patterns to direct the search using @@ -391,20 +444,20 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), candidate)) return; - for (var i = 0; i < _assemblies.Count; i++) + for (var i = 0; i < _candidateAssemblies.Count; i++) { - var assembly = _assemblies[i]; + var assembly = _candidateAssemblies[i]; if (candidate.IsDuplicateOf(assembly)) { if (candidate.IsBetterVersionOf(assembly)) - _assemblies[i] = candidate; + _candidateAssemblies[i] = candidate; return; } } - _assemblies.Add(candidate); + _candidateAssemblies.Add(candidate); } catch (BadImageFormatException e) { @@ -418,14 +471,14 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) } } - private bool WasVisited(string filePath, bool fromWildcard) + private bool WasVisited(string path, bool fromWildcard) { - return _directoriesExamined.ContainsKey($"path={filePath}_visited={fromWildcard}"); + return _visited.ContainsKey($"path={path}_visited={fromWildcard}"); } - private void MarkAsVisited(string filePath, bool fromWildcard) + private void MarkAsVisited(string path, bool fromWildcard) { - _directoriesExamined.Add($"path={filePath}_visited={fromWildcard}", null); + _visited.Add($"path={path}_visited={fromWildcard}", null); } /// @@ -626,7 +679,7 @@ private System.Runtime.Versioning.FrameworkName GetTargetRuntime(string filePath public void Dispose() { // Make sure all assemblies release the underlying file streams. - foreach (var assembly in _assemblies) + foreach (var assembly in _candidateAssemblies) { assembly.Dispose(); } diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index 536ea3974..d4f55929a 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -47,9 +47,9 @@ internal ExtensionService(IFileSystem fileSystem, IDirectoryFinder directoryFind public IEnumerable Extensions => _extensionManager.Extensions; /// - public void FindExtensions(string initialDirectory) + public void FindExtensionAssemblies(string initialDirectory) { - _extensionManager.FindExtensions(initialDirectory); + _extensionManager.FindExtensionAssemblies(initialDirectory); } /// @@ -87,7 +87,7 @@ public override void StartService() try { _extensionManager.FindExtensionPoints(thisAssembly, apiAssembly); - _extensionManager.FindStandardExtensions(thisAssembly); + _extensionManager.FindExtensionAssemblies(thisAssembly); Status = ServiceStatus.Started; } From f88cf0091b9d6281f6ac5a686eaf74c532c3739a Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 10 Dec 2024 21:12:12 -0800 Subject: [PATCH 133/190] Refactoring and new tests --- .../Extensibility/ExtensionAssemblyTests.cs | 13 -- .../Internal/ExtensionAssemblyTrackerTests.cs | 79 ++++++++++ .../Extensibility/ExtensionAssembly.cs | 27 ++-- .../Internal/ExtensionAssemblyTracker.cs | 25 +++ .../Services/ExtensionManager.cs | 146 ++++++++---------- 5 files changed, 181 insertions(+), 109 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs create mode 100644 src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs index 87a16bf9f..a90c47ae0 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs @@ -11,7 +11,6 @@ public class ExtensionAssemblyTests { private static readonly Assembly THIS_ASSEMBLY = Assembly.GetExecutingAssembly(); private static readonly string THIS_ASSEMBLY_PATH = THIS_ASSEMBLY.Location; - private static readonly string THIS_ASSEMBLY_FULL_NAME = THIS_ASSEMBLY.GetName().FullName; private static readonly string THIS_ASSEMBLY_NAME = THIS_ASSEMBLY.GetName().Name; private static readonly Version THIS_ASSEMBLY_VERSION = THIS_ASSEMBLY.GetName().Version; @@ -23,18 +22,6 @@ public void CreateExtensionAssemblies() _ea = new ExtensionAssembly(THIS_ASSEMBLY_PATH, false); } - [Test] - public void AssemblyDefinition() - { - Assert.That(_ea.Assembly.FullName, Is.EqualTo(THIS_ASSEMBLY_FULL_NAME)); - } - - [Test] - public void MainModule() - { - Assert.That(_ea.MainModule.Assembly.FullName, Is.EqualTo(THIS_ASSEMBLY_FULL_NAME)); - } - [Test] public void AssemblyName() { diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs new file mode 100644 index 000000000..dc1e6bd18 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.ComponentModel; +using System.Reflection; +using NUnit.Engine.Extensibility; +using NUnit.Framework; + +namespace NUnit.Engine.Internal.Tests +{ + public class ExtensionAssemblyTrackerTests + { + private static readonly Assembly THIS_ASSEMBLY = typeof(ExtensionAssemblyTrackerTests).Assembly; + private static readonly string THIS_ASSEMBLY_PATH = THIS_ASSEMBLY.Location; + private static readonly string THIS_ASSEMBLY_NAME = THIS_ASSEMBLY.GetName().Name; + private static readonly Version THIS_ASSEMBLY_VERSION = THIS_ASSEMBLY.GetName().Version; + private static readonly ExtensionAssembly TEST_EXTENSION_ASSEMBLY = + new ExtensionAssembly(THIS_ASSEMBLY_PATH, false, THIS_ASSEMBLY_NAME, THIS_ASSEMBLY_VERSION); + + private ExtensionAssemblyTracker _tracker; + + [SetUp] + public void CreateTracker() + { + _tracker = new ExtensionAssemblyTracker(); + } + + [Test] + public void AddToList() + { + _tracker.Add(TEST_EXTENSION_ASSEMBLY); + + Assert.That(_tracker.Count, Is.EqualTo(1)); + Assert.That(_tracker[0].FilePath, Is.EqualTo(THIS_ASSEMBLY_PATH)); + Assert.That(_tracker[0].AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(_tracker[0].AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); + } + + [Test] + public void AddUpdatesNameIndex() + { + _tracker.Add(TEST_EXTENSION_ASSEMBLY); + + Assert.That(_tracker.ByName.ContainsKey(THIS_ASSEMBLY_NAME)); + Assert.That(_tracker.ByName[THIS_ASSEMBLY_NAME].AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(_tracker.ByName[THIS_ASSEMBLY_NAME].FilePath, Is.EqualTo(THIS_ASSEMBLY_PATH)); + Assert.That(_tracker.ByName[THIS_ASSEMBLY_NAME].AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); + } + [Test] + public void AddUpdatesPathIndex() + { + _tracker.Add(TEST_EXTENSION_ASSEMBLY); + + Assert.That(_tracker.ByPath.ContainsKey(THIS_ASSEMBLY_PATH)); + Assert.That(_tracker.ByPath[THIS_ASSEMBLY_PATH].AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(_tracker.ByPath[THIS_ASSEMBLY_PATH].FilePath, Is.EqualTo(THIS_ASSEMBLY_PATH)); + Assert.That(_tracker.ByPath[THIS_ASSEMBLY_PATH].AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); + } + + [Test] + public void AddDuplicatePathThrowsArgumentException() + { + _tracker.Add(TEST_EXTENSION_ASSEMBLY); + + Assert.That(() => + _tracker.Add(TEST_EXTENSION_ASSEMBLY), + Throws.TypeOf()); + } + + [Test] + public void AddDuplicateAssemblyNameThrowsArgumentException() + { + _tracker.Add(TEST_EXTENSION_ASSEMBLY); + + Assert.That(() => _tracker.Add(new ExtensionAssembly("Some/Other/Path", false, THIS_ASSEMBLY_NAME, THIS_ASSEMBLY_VERSION)), + Throws.TypeOf()); + } + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs index 0cf2e3747..774f10da5 100644 --- a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs +++ b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs @@ -1,6 +1,7 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt using System; +using System.Collections.Generic; using System.IO; using TestCentric.Metadata; using NUnit.Engine.Internal; @@ -14,26 +15,26 @@ public ExtensionAssembly(string filePath, bool fromWildCard) FilePath = filePath; FromWildCard = fromWildCard; Assembly = GetAssemblyDefinition(); + AssemblyName = Assembly.Name.Name; + AssemblyVersion = Assembly.Name.Version; + } + + // Internal constructor used for certain tests. AssemblyDefinition is not initialized. + internal ExtensionAssembly(string filePath, bool fromWildCard, string assemblyName, Version version) + { + FilePath = filePath; + FromWildCard = fromWildCard; + AssemblyName = assemblyName; + AssemblyVersion = version; } public string FilePath { get; } public bool FromWildCard { get; } public AssemblyDefinition Assembly { get; } - public string AssemblyName - { - get { return Assembly.Name.Name; } - } - - public Version AssemblyVersion - { - get { return Assembly.Name.Version; } - } + public string AssemblyName { get; } - public ModuleDefinition MainModule - { - get { return Assembly.MainModule; } - } + public Version AssemblyVersion { get; } #if NETFRAMEWORK public RuntimeFramework TargetFramework diff --git a/src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs b/src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs new file mode 100644 index 000000000..96b3c153d --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs @@ -0,0 +1,25 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using NUnit.Engine.Extensibility; +using System.Collections.Generic; + +namespace NUnit.Engine.Internal +{ + /// + /// This is a simple utility class used by the ExtensionManager to keep track of ExtensionAssemblies. + /// It is a List of ExtensionAssemblies and also provides indices by file path and assembly name. + /// It allows writing tests to show that no duplicate extension assemblies are loaded. + /// + internal class ExtensionAssemblyTracker : List + { + public Dictionary ByPath = new Dictionary(); + public Dictionary ByName = new Dictionary(); + + public new void Add(ExtensionAssembly assembly) + { + base.Add(assembly); + ByPath.Add(assembly.FilePath, assembly); + ByName.Add(assembly.AssemblyName, assembly); + } + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index b033414cd..f8c1d8784 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -10,6 +10,7 @@ using NUnit.Engine.Internal.FileSystemAccess; using NUnit.Engine.Internal.FileSystemAccess.Default; using System.Linq; +using System.Diagnostics; namespace NUnit.Engine.Services { @@ -27,26 +28,21 @@ public class ExtensionManager private readonly List _extensionPoints = new List(); // Index to ExtensionPoints based on the Path - private readonly Dictionary _pathIndex = new Dictionary(); + private readonly Dictionary _extensionPointIndex = new Dictionary(); // List of ExtensionNodes for all extensions discovered. private List _extensions = new List(); private bool _extensionsAreLoaded; - // List of candidate ExtensionAssemblies, which should be examined for extensions. - // This list must be completely built before we examine any of the assemblies, since - // it's possible that the same assembly may be found in multiple versions. - private readonly List _candidateAssemblies = new List(); - + // AssemblyTracker is a List of candidate ExtensionAssemblies, with built-in indexing + // by file path and assembly name, eliminating the need to update indices separately. + private readonly ExtensionAssemblyTracker _assemblies = new ExtensionAssemblyTracker(); + // List of all extensionDirectories specified on command-line or in environment, - // used to ignore duplicate calls to FindExtensions. + // used to ignore duplicate calls to FindExtensionAssemblies. private readonly List _extensionDirectories = new List(); - // Dictionary containing all directory paths and assembly paths already processed, - // used to prevent processing a directory or assembly more than once. - private readonly Dictionary _visited = new Dictionary(); - public ExtensionManager() : this(new FileSystem()) { @@ -97,7 +93,7 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) foreach (ExtensionPointAttribute attr in assembly.GetCustomAttributes(typeof(ExtensionPointAttribute), false)) { - if (_pathIndex.ContainsKey(attr.Path)) + if (_extensionPointIndex.ContainsKey(attr.Path)) { string msg = string.Format( "The Path {0} is already in use for another extension point.", @@ -111,7 +107,7 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) }; _extensionPoints.Add(ep); - _pathIndex.Add(ep.Path, ep); + _extensionPointIndex.Add(ep.Path, ep); log.Info(" Found ExtensionPoint: Path={0}, Type={1}", ep.Path, ep.TypeName); } @@ -122,7 +118,7 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) { string path = attr.Path ?? "/NUnit/Engine/TypeExtensions/" + type.Name; - if (_pathIndex.ContainsKey(path)) + if (_extensionPointIndex.ContainsKey(path)) { string msg = string.Format( "The Path {0} is already in use for another extension point.", @@ -136,7 +132,7 @@ public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) }; _extensionPoints.Add(ep); - _pathIndex.Add(path, ep); + _extensionPointIndex.Add(path, ep); log.Info(" Found ExtensionPoint: Path={0}, Type={1}", ep.Path, ep.TypeName); } @@ -179,22 +175,16 @@ public void FindExtensionAssemblies(Assembly hostAssembly) ? new[] { "nunit-extension-*/**/tools/", "nunit-extension-*/**/tools/*/" } : new[] { "NUnit.Extension.*/**/tools/", "NUnit.Extension.*/**/tools/*/" }; - FindExtensionAssemblies(hostAssembly, extensionPatterns); - } - /// - /// We can only load extensions after all candidate assemblies are identified. - /// This method is called internally to ensure the load happens before any - /// extensions are used. - /// - private void EnsureExtensionsAreLoaded() - { - if (!_extensionsAreLoaded) + IDirectory startDir = _fileSystem.GetDirectory(AssemblyHelper.GetDirectoryName(hostAssembly)); + + while (startDir != null) { - _extensionsAreLoaded = true; + foreach (var pattern in extensionPatterns) + foreach (var dir in _directoryFinder.GetDirectories(startDir, pattern)) + ProcessDirectory(dir, true); - foreach (var candidate in _candidateAssemblies) - FindExtensionsInAssembly(candidate); + startDir = startDir.Parent; } } @@ -203,7 +193,7 @@ private void EnsureExtensionsAreLoaded() /// public IExtensionPoint GetExtensionPoint(string path) { - return _pathIndex.ContainsKey(path) ? _pathIndex[path] : null; + return _extensionPointIndex.ContainsKey(path) ? _extensionPointIndex[path] : null; } /// @@ -323,23 +313,18 @@ private ExtensionPoint DeduceExtensionPointFromType(TypeReference typeRef) } /// - /// Find candidate extension assemblies for a given host assembly, - /// using the provided list of patterns to direct the search using - /// a built-in algorithm. + /// We can only load extensions after all candidate assemblies are identified. + /// This method is called internally to ensure the load happens before any + /// extensions are used. /// - /// An assembly that supports extensions - /// A list of patterns used to identify potential candidates. - private void FindExtensionAssemblies(Assembly hostAssembly, string[] patterns) + private void EnsureExtensionsAreLoaded() { - IDirectory startDir = _fileSystem.GetDirectory(AssemblyHelper.GetDirectoryName(hostAssembly)); - - while (startDir != null) + if (!_extensionsAreLoaded) { - foreach (var pattern in patterns) - foreach (var dir in _directoryFinder.GetDirectories(startDir, pattern)) - ProcessDirectory(dir, true); + _extensionsAreLoaded = true; - startDir = startDir.Parent; + foreach (var candidate in _assemblies) + FindExtensionsInAssembly(candidate); } } @@ -354,20 +339,15 @@ private void ProcessDirectory(IDirectory startDir, bool fromWildCard) if (WasVisited(directoryName, fromWildCard)) { log.Warning($"Skipping directory '{directoryName}' because it was already visited."); + return; } - else - { - log.Info($"Scanning directory '{directoryName}' for extensions."); - MarkAsVisited(directoryName, fromWildCard); - if (ProcessAddinsFiles(startDir, fromWildCard) == 0) - { - foreach (var file in startDir.GetFiles("*.dll")) - { - ProcessCandidateAssembly(file.FullName, true); - } - } - } + log.Info($"Scanning directory '{directoryName}' for extensions."); + Visit(directoryName, fromWildCard); + + if (ProcessAddinsFiles(startDir, fromWildCard) == 0) + foreach (var file in startDir.GetFiles("*.dll")) + ProcessCandidateAssembly(file.FullName, true); } /// @@ -432,32 +412,29 @@ private void ProcessAddinsFile(IFile addinsFile, bool fromWildCard) private void ProcessCandidateAssembly(string filePath, bool fromWildCard) { - if (WasVisited(filePath, fromWildCard)) + // Did we already process this file? + if (_assemblies.ByPath.ContainsKey(filePath)) return; - MarkAsVisited(filePath, fromWildCard); - try { - var candidate = new ExtensionAssembly(filePath, fromWildCard); + var candidateAssembly = new ExtensionAssembly(filePath, fromWildCard); + string assemblyName = candidateAssembly.AssemblyName; - if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), candidate)) + // We never add assemblies unless the host can load them + if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), candidateAssembly)) return; - - for (var i = 0; i < _candidateAssemblies.Count; i++) + + // Do we already have a copy of the same assembly at a diffrent path? + if (_assemblies.ByName.ContainsKey(assemblyName)) { - var assembly = _candidateAssemblies[i]; + if (candidateAssembly.IsBetterVersionOf(_assemblies.ByName[assemblyName])) + _assemblies.ByName[assemblyName] = candidateAssembly; - if (candidate.IsDuplicateOf(assembly)) - { - if (candidate.IsBetterVersionOf(assembly)) - _candidateAssemblies[i] = candidate; - - return; - } + return; } - _candidateAssemblies.Add(candidate); + _assemblies.Add(candidateAssembly); } catch (BadImageFormatException e) { @@ -471,12 +448,15 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) } } + // Dictionary containing all directory paths already visited. + private readonly Dictionary _visited = new Dictionary(); + private bool WasVisited(string path, bool fromWildcard) { return _visited.ContainsKey($"path={path}_visited={fromWildcard}"); } - private void MarkAsVisited(string path, bool fromWildcard) + private void Visit(string path, bool fromWildcard) { _visited.Add($"path={path}_visited={fromWildcard}", null); } @@ -486,35 +466,35 @@ private void MarkAsVisited(string path, bool fromWildcard) /// For each extension, create an ExtensionNode and link it to the /// correct ExtensionPoint. Public for testing. /// - internal void FindExtensionsInAssembly(ExtensionAssembly assembly) + internal void FindExtensionsInAssembly(ExtensionAssembly extensionAssembly) { - log.Info($"Scanning {assembly.FilePath} for Extensions"); + log.Info($"Scanning {extensionAssembly.FilePath} for Extensions"); - if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), assembly)) + if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), extensionAssembly)) { - log.Info($"{assembly.FilePath} cannot be loaded on this runtime"); + log.Info($"{extensionAssembly.FilePath} cannot be loaded on this runtime"); return; } IRuntimeFramework assemblyTargetFramework = null; #if NETFRAMEWORK var currentFramework = RuntimeFramework.CurrentFramework; - assemblyTargetFramework = assembly.TargetFramework; + assemblyTargetFramework = extensionAssembly.TargetFramework; if (!currentFramework.CanLoad(assemblyTargetFramework)) { - if (!assembly.FromWildCard) + if (!extensionAssembly.FromWildCard) { - throw new NUnitEngineException($"Extension {assembly.FilePath} targets {assemblyTargetFramework.DisplayName}, which is not available."); + throw new NUnitEngineException($"Extension {extensionAssembly.FilePath} targets {assemblyTargetFramework.DisplayName}, which is not available."); } else { - log.Info($"Assembly {assembly.FilePath} targets {assemblyTargetFramework.DisplayName}, which is not available. Assembly found via wildcard."); + log.Info($"Assembly {extensionAssembly.FilePath} targets {assemblyTargetFramework.DisplayName}, which is not available. Assembly found via wildcard."); return; } } #endif - foreach (var extensionType in assembly.MainModule.GetTypes()) + foreach (var extensionType in extensionAssembly.Assembly.MainModule.GetTypes()) { CustomAttribute extensionAttr = extensionType.GetAttribute("NUnit.Engine.Extensibility.ExtensionAttribute"); @@ -534,7 +514,7 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) } } - var node = new ExtensionNode(assembly.FilePath, assembly.AssemblyVersion, extensionType.FullName, assemblyTargetFramework) + var node = new ExtensionNode(extensionAssembly.FilePath, extensionAssembly.AssemblyVersion, extensionType.FullName, assemblyTargetFramework) { Path = extensionAttr.GetNamedArgument("Path") as string, Description = extensionAttr.GetNamedArgument("Description") as string @@ -679,9 +659,9 @@ private System.Runtime.Versioning.FrameworkName GetTargetRuntime(string filePath public void Dispose() { // Make sure all assemblies release the underlying file streams. - foreach (var assembly in _candidateAssemblies) + foreach (var candidate in _assemblies) { - assembly.Dispose(); + candidate.Dispose(); } } } From 074d5f472e39ef84454446a822661edd5a230035 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 13 Dec 2024 00:10:42 -0800 Subject: [PATCH 134/190] Changes from second review --- src/NUnitConsole/nunit3-console/ConsoleOptions.cs | 3 --- src/NUnitConsole/nunit3-console/ConsoleRunner.cs | 7 +------ .../nunit.engine.core/Services/ExtensionManager.cs | 9 +++++---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs index 23b9922b8..2939dc804 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs @@ -110,9 +110,6 @@ public List ResultOutputSpecifications { get { - //if (noresult) - // return new List(); - if (resultOutputSpecifications.Count == 0) resultOutputSpecifications.Add( new OutputSpecification("TestResult.xml", CURRENT_DIRECTORY_ON_ENTRY)); diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index 2f6ffaede..606fec6d0 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -56,7 +56,7 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri Guard.ArgumentNotNull(_options = options, nameof(options)); Guard.ArgumentNotNull(_outWriter = writer, nameof(writer)); - // NOTE: Accessing Services triggerss the engine to initialize all services + // NOTE: Accessing Services triggers the engine to initialize all services _resultService = _engine.Services.GetService(); Guard.OperationValid(_resultService != null, "Internal Error: ResultService was not found"); @@ -74,10 +74,6 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri foreach (string extensionDirectory in _options.ExtensionDirectories) _extensionService.FindExtensionAssemblies(extensionDirectory); - // Trigger lazy loading of extensions - var dummy = _extensionService.Extensions; - - _workDirectory = options.WorkDirectory; if (_workDirectory != null) Directory.CreateDirectory(_workDirectory); @@ -90,7 +86,6 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri foreach (var node in _extensionService.GetExtensionNodes(EVENT_LISTENER_EXTENSION_PATH)) if (teamcityInstalled = node.TypeName == TEAMCITY_EVENT_LISTENER_FULLNAME) break; - //Guard.ArgumentValid(teamcityInstalled, "Option --teamcity specified but the extension is not installed.", "--teamCity"); if (!teamcityInstalled) throw new RequiredExtensionException(TEAMCITY_EVENT_LISTENER_NAME, "--teamcity"); } diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index f8c1d8784..a02d1b410 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -193,7 +193,7 @@ public void FindExtensionAssemblies(Assembly hostAssembly) /// public IExtensionPoint GetExtensionPoint(string path) { - return _extensionPointIndex.ContainsKey(path) ? _extensionPointIndex[path] : null; + return _extensionPointIndex.TryGetValue(path, out ExtensionPoint ep) ? ep : null; } /// @@ -425,10 +425,11 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), candidateAssembly)) return; - // Do we already have a copy of the same assembly at a diffrent path? - if (_assemblies.ByName.ContainsKey(assemblyName)) + // Do we already have a copy of the same assembly at a different path? + //if (_assemblies.ByName.ContainsKey(assemblyName)) + if (_assemblies.ByName.TryGetValue(assemblyName, out ExtensionAssembly existing)) { - if (candidateAssembly.IsBetterVersionOf(_assemblies.ByName[assemblyName])) + if (candidateAssembly.IsBetterVersionOf(existing)) _assemblies.ByName[assemblyName] = candidateAssembly; return; From fde1ede48b8b31ff26841baa753cb21c7cac33bc Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 22 Dec 2024 21:18:18 -0800 Subject: [PATCH 135/190] Use alpha label for dev builds --- GitVersion.yml | 2 +- README.md | 22 ++++----- build.cake | 125 +------------------------------------------------ 3 files changed, 14 insertions(+), 135 deletions(-) diff --git a/GitVersion.yml b/GitVersion.yml index 3b27dee15..e4a74f1aa 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -6,7 +6,7 @@ commits-since-version-source-padding: 5 branches: master: regex: ^(main|version4)$ - tag: dev + tag: alpha release: tag: pre pull-request: diff --git a/README.md b/README.md index a4f0bfa65..63a587c6e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NUnit 3 Console and Engine # +# NUnit 3 Console and Engine [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/81uhucr7tlq2kwup/branch/master?svg=true)](https://ci.appveyor.com/project/CharliePoole/nunit-console/branch/master) [![Azure Pipelines Build Status](https://nunit.visualstudio.com/NUnit/_apis/build/status/NUnit%20Engine%20&%20Console/NUnit%20Console%20CI?branchName=master)](https://nunit.visualstudio.com/NUnit/_build/latest?definitionId=13&branchName=master) [![NuGet Version and Downloads count](https://buildstats.info/nuget/NUnit.ConsoleRunner)](https://www.nuget.org/packages/NUnit.ConsoleRunner) @@ -6,7 +6,7 @@ NUnit is a unit-testing framework for all .NET languages. Initially ported from JUnit, the current production release, version 3, has been completely rewritten with many new features and support for a wide range of .NET platforms. -## Table of Contents ## +## Table of Contents - [Downloads](#downloads) - [Documentation](#documentation) @@ -14,7 +14,7 @@ NUnit is a unit-testing framework for all .NET languages. Initially ported from - [License](#license) - [NUnit Projects](#nunit-projects) -## Downloads ## +## Downloads The latest stable release of the NUnit Console is [available on NuGet](https://www.nuget.org/packages/NUnit.ConsoleRunner/), [Chocolatey](https://chocolatey.org/packages/nunit-console-runner), or can be [downloaded from GitHub](https://github.com/nunit/nunit-console/releases). Pre-release builds are [available on MyGet](https://www.myget.org/feed/nunit/package/nuget/NUnit.ConsoleRunner). @@ -25,11 +25,11 @@ The Console/Engine are available in various packages: - [NUnit.Engine](https://www.nuget.org/packages/NUnit.Engine/) & [NUnit.Engine.Api](https://www.nuget.org/packages/NUnit.Engine.Api/): Packages intended for custom runners integrating directly with the NUnit Engine. Development builds of all packages are available from our MyGet feed at https://www.myget.org/feed/Packages/nunit -## Documentation ## +## Documentation Documentation for all NUnit projects are available at [https://docs.nunit.org/](https://docs.nunit.org/). -## Contributing ## +## Contributing For more information on contributing to the NUnit project, please see [CONTRIBUTING.md](https://github.com/nunit/nunit-console/blob/master/CONTRIBUTING.md) and the [Developer Docs](https://github.com/nunit/docs/wiki/Team-Practices#technical-practices). @@ -37,31 +37,31 @@ NUnit 3.0 was created by [Charlie Poole](https://github.com/CharliePoole), [Rob Earlier versions of NUnit were developed by Charlie Poole, James W. Newkirk, Alexei A. Vorontsov, Michael C. Two and Philip A. Craig. -## License ## +## License NUnit is Open Source software and NUnit 3 is released under the [MIT license](https://github.com/nunit/docs/wiki/License). Earlier releases used the [NUnit license](http://www.nunit.org/nuget/license.html). Both of these licenses allow the use of NUnit in free and commercial applications and libraries without restrictions. -## NUnit Projects ## +## NUnit Projects NUnit is made up of several projects. When reporting issues, please try to report issues in the correct project. -### Core Projects ### +### Core Projects - [NUnit Test Framework](https://github.com/nunit/nunit) - The test framework used to write NUnit tests - [NUnit Console and Engine](https://github.com/nunit/nunit-console) - Runs unit tests from the command line and provides the engine that is used by other test runners to run NUnit tests -### Visual Studio Extensions ### +### Visual Studio Extensions - [NUnit 3 Visual Studio Adapter](https://github.com/nunit/nunit3-vs-adapter) - Visual Studio adapter for running NUnit 3 tests in Visual Studio and in VSTS/TFS builds - [NUnit Visual Studio Templates](https://github.com/nunit/nunit-vs-templates) - Project templates and snippets for writing unit tests in Visual Studio - [Visual Studio Test Generator](https://github.com/nunit/nunit-vs-testgenerator) - Generates NUnit tests in Visual Studio - [NUnit 2 Visual Studio Adapter](https://github.com/nunit/nunit-vs-adapter) - Visual Studio adapter for running older NUnit 2.x tests in Visual Studio and in VSTS/TFS builds -### Other Projects ### +### Other Projects - [NUnit Xamarin Runner](https://github.com/nunit/nunit.xamarin) - Runs NUnit 3 tests on mobile devices using the Xamarin framework -### NUnit Engine Extensions ### +### NUnit Engine Extensions - [NUnit 2 Driver](https://github.com/nunit/nunit-v2-framework-driver) - Allows the NUnit 3 engine to run NUnit 2 tests - [NUnit 2 Result Writer](https://github.com/nunit/nunit-v2-result-writer) - Writes test results in the legacy NUnit 2 format diff --git a/build.cake b/build.cake index 9dfb9e07e..9f39ad7a0 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.2.1-dev00002 +#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0-alpha.1 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -92,7 +92,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { checks: new PackageCheck[] { HasFiles("nunit.exe"), - HasSomeDirectory(".store/nunit.consolerunner.netcore/**/tools/net8.0/any") + HasDirectory(".store/nunit.consolerunner.netcore/**/tools/net8.0/any") .WithFiles(ENGINE_FILES).AndFiles(ConsoleFiles).AndFile("Microsoft.Extensions.DependencyModel.dll") }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory @@ -205,68 +205,6 @@ Task("TestNetCorePackage") NUnitConsoleRunnerNetCorePackage.RunPackageTests(); }); -// Adhoc code to check content of a dotnet standalone executable -// TODO: Incorporate this in the recipe itself - -private static ExtendedDirectoryCheck HasSomeDirectory(string pattern) => new ExtendedDirectoryCheck(pattern); - -public class ExtendedDirectoryCheck : PackageCheck -{ - private string _directoryPattern; - private List _files = new List(); - - public ExtendedDirectoryCheck(string directoryPattern) - { - // Assume it has no wildcard - checked in ApplyTo method - _directoryPattern = directoryPattern; - } - - public ExtendedDirectoryCheck WithFiles(params FilePath[] files) - { - _files.AddRange(files); - return this; - } - - public ExtendedDirectoryCheck AndFiles(params FilePath[] files) - { - return WithFiles(files); - } - - public ExtendedDirectoryCheck WithFile(FilePath file) - { - _files.Add(file); - return this; - } - - public ExtendedDirectoryCheck AndFile(FilePath file) - { - return AndFiles(file); - } - - public override bool ApplyTo(DirectoryPath testDirPath) - { - if (_directoryPattern.Contains('*') || _directoryPattern.Contains('?')) // Wildcard - { - var absDirPattern = testDirPath.Combine(_directoryPattern).ToString(); - foreach (var dir in _context.GetDirectories(absDirPattern)) - { - // Use first one found - return CheckFilesExist(_files.Select(file => dir.CombineWithFilePath(file))); - } - } - else // No wildcard - { - var absDirPath = testDirPath.Combine(_directoryPattern); - if (!CheckDirectoryExists(absDirPath)) - return false; - - return CheckFilesExist(_files.Select(file => absDirPath.CombineWithFilePath(file))); - } - - return false; - } -} - ////////////////////////////////////////////////////////////////////// // TEST RUNNERS ////////////////////////////////////////////////////////////////////// @@ -288,65 +226,6 @@ public class ConsoleRunnerSelfTester : TestRunner, IPackageTestRunner } } -////////////////////////////////////////////////////////////////////// -// ADDITIONAL TARGETS USED FOR RECOVERY AND DEBUGGING -////////////////////////////////////////////////////////////////////// - -// Some of these targets may be moved into the recipe itself in the future. - -// When a NuGet package was published successfully but the corresponding symbols -// package failed, use this target locally after correcting the error. -// TODO: This task is extemely complicated because it has to copy lots of code -// from the recipe. It would be simpler if it were integrated in the recipe. -// TODO: This has been tested on NUnit.ConsoleRunner, so the branches with either -// zero or one packages are speculative at this point. They will need testing -// if this is incorporated into the recipe. -Task("PublishSymbolsPackage") - .Description("Re-publish a specific symbols package to NuGet after a failure") - .Does(() => - { - if (!BuildSettings.ShouldPublishToNuGet) - Information("Nothing to publish to NuGet from this run."); - else if (CommandLineOptions.NoPush) - Information("NoPush option suppressing publication to NuGet"); - else - { - List packages; - - if (BuildSettings.Packages.Count == 0) - throw new Exception("No packages exist!"); - else if (BuildSettings.Packages.Count == 1) - { - if (BuildSettings.Packages[0].PackageType != PackageType.NuGet) - throw new Exception("The only package is not a NuGet package"); - - packages = BuildSettings.Packages; - } - else // count is > 1 - { - if (!CommandLineOptions.PackageSelector.Exists) - throw new Exception("Multiple packages exist. Specify a nuget package id using the '--where' option"); - - packages = new List(); - - foreach (var package in BuildSettings.Packages) - if (package.IsSelectedBy(CommandLineOptions.PackageSelector.Value)) - packages.Add(package); - - if (packages.Count > 1) - throw new Exception("The '--where' option selected multiple packages"); - - if (packages[0].PackageType != PackageType.NuGet) - throw new Exception("The selected package is a {package.PackageType} package. It must be a package for nuget.org."); - } - - // At this point we have a single NuGet package in packages - var packageName = $"{packages[0].PackageId}.{BuildSettings.PackageVersion}.snupkg"; - var packagePath = BuildSettings.PackageDirectory + packageName; - NuGetPush(packagePath, new NuGetPushSettings() { ApiKey = BuildSettings.NuGetApiKey, Source = BuildSettings.NuGetPushUrl }); - } - }); - ////////////////////////////////////////////////////////////////////// // EXECUTION ////////////////////////////////////////////////////////////////////// From 874f0ea8c886f4a5ee6b18938c6231026cb263ff Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 25 Dec 2024 14:06:02 -0800 Subject: [PATCH 136/190] Use latest build of recipe --- .config/dotnet-tools.json | 2 +- build.cake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index da200cdae..43e6f74ce 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "cake.tool": { - "version": "4.0.0", + "version": "5.0.0", "commands": [ "dotnet-cake" ] diff --git a/build.cake b/build.cake index 9f39ad7a0..adcc21236 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0-alpha.1 +#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0-alpha.2 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake From 9840d910a135e3bb5a25b140fce83bb87708ec6e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 25 Dec 2024 19:59:20 -0800 Subject: [PATCH 137/190] Update recipe --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index adcc21236..312cb9a4a 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0-alpha.2 +#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0-alpha.3 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake From 1cf27a4a02f81e639af26b50f6a20872bb08a5d5 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 26 Dec 2024 09:32:30 -0800 Subject: [PATCH 138/190] Update recipe to 1.3.0-alpha.6 --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 312cb9a4a..dcd742d0a 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0-alpha.3 +#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0-alpha.6 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake From e2ab6a8097732bf678a2fb191065365aff2dc857 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 26 Dec 2024 17:38:05 -0800 Subject: [PATCH 139/190] Update recipe to version 1.3.0 --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index dcd742d0a..b6681b8f7 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0-alpha.6 +#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake From b133b4ee5070e5c295edbeac3badc4a5c77cf9cd Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 26 Dec 2024 22:53:40 -0800 Subject: [PATCH 140/190] Use dependency model v8 for .net 8 agent --- src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index ad931a802..ac5cc099a 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -22,11 +22,16 @@ - + + + + + + From a2f391efc286db6759cdd082976f07cca866b55e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 10 Jan 2025 16:59:54 -0800 Subject: [PATCH 141/190] Update to latest recipe; fix format of zip file --- build.cake | 15 ++++++++++----- .../Services/ExtensionManager.cs | 11 ++++++++++- src/NUnitEngine/nunit.engine/nunit.engine.addins | 6 +----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/build.cake b/build.cake index b6681b8f7..a753542bc 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.3.0 +#load nuget:?package=NUnit.Cake.Recipe&version=1.3.1-alpha.1 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -121,10 +121,15 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasFiles("LICENSE.txt", "NOTICES.txt", "CHANGES.txt"), HasDirectory("bin/net462").WithFiles("nunit3-console.exe", "nunit3-console.exe.config", "nunit3-console.pdb").AndFiles(ENGINE_FILES).AndFiles(ENGINE_PDB_FILES), - HasDirectory("bin/net462/addins").WithFiles( - "nunit.core.dll", "nunit.core.interfaces.dll", "nunit.engine.api.dll", - "nunit.v2.driver.dll", "nunit-project-loader.dll", "nunit-v2-result-writer.dll", - "teamcity-event-listener.dll", "vs-project-loader.dll"), + HasDirectory("NUnit.Extension.NUnitProjectLoader.3.8.0"), + HasDirectory("NUnit.Extension.NUnitV2Driver.3.9.0"), + HasDirectory("NUnit.Extension.NUnitV2ResultWriter.3.8.0"), + HasDirectory("NUnit.Extension.TeamCityEventListener.1.0.7"), + HasDirectory("NUnit.Extension.VSProjectLoader.3.9.0"), + //HasDirectory("bin/net462/addins").WithFiles( + // "nunit.core.dll", "nunit.core.interfaces.dll", "nunit.engine.api.dll", + // "nunit.v2.driver.dll", "nunit-project-loader.dll", "nunit-v2-result-writer.dll", + // "teamcity-event-listener.dll", "vs-project-loader.dll"), HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index a02d1b410..1780eb6ec 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -384,6 +384,7 @@ private void ProcessAddinsFile(IFile addinsFile, bool fromWildCard) string entryDir = entry.DirectoryName; string entryFile = entry.FileName; + log.Debug($"Processing entry {entry.Text}"); if (entry.IsDirectory) { if (entry.IsFullyQualified) @@ -412,9 +413,14 @@ private void ProcessAddinsFile(IFile addinsFile, bool fromWildCard) private void ProcessCandidateAssembly(string filePath, bool fromWildCard) { + log.Debug($"Processing candidate assembly {filePath}"); + // Did we already process this file? if (_assemblies.ByPath.ContainsKey(filePath)) + { + log.Debug(" Skipping assembly already processed"); return; + } try { @@ -423,10 +429,12 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) // We never add assemblies unless the host can load them if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), candidateAssembly)) + { + log.Debug(" Unable to load this assembly"); return; + } // Do we already have a copy of the same assembly at a different path? - //if (_assemblies.ByName.ContainsKey(assemblyName)) if (_assemblies.ByName.TryGetValue(assemblyName, out ExtensionAssembly existing)) { if (candidateAssembly.IsBetterVersionOf(existing)) @@ -435,6 +443,7 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) return; } + log.Debug(" Adding this assembly"); _assemblies.Add(candidateAssembly); } catch (BadImageFormatException e) diff --git a/src/NUnitEngine/nunit.engine/nunit.engine.addins b/src/NUnitEngine/nunit.engine/nunit.engine.addins index be4c6fa29..4ddd66c2e 100644 --- a/src/NUnitEngine/nunit.engine/nunit.engine.addins +++ b/src/NUnitEngine/nunit.engine/nunit.engine.addins @@ -1,5 +1 @@ -addins/nunit.v2.driver.dll -addins/nunit-v2-result-writer.dll -addins/nunit-project-loader.dll -addins/vs-project-loader.dll -addins/teamcity-event-listener.dll +../../../bundled-extensions/NUnit.Extension.*/tools/**/ \ No newline at end of file From 1c4d532f5f1add83869591e5e469e73470d4f4db Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 18 Jan 2025 09:21:32 -0800 Subject: [PATCH 142/190] Drop agents for .NET 7.0 and .NET Core 3.1 --- .../workflows/NUnitConsoleAndEngine.CI.yml | 2 ++ build.cake | 12 --------- choco/nunit-console-runner.nuspec | 20 -------------- nuget/engine/nunit.engine.nuspec | 11 -------- nuget/runners/nunit.console-runner.nuspec | 26 ------------------- .../nunit-agent/nunit-agent.csproj | 2 +- .../nunit.engine/Services/AgentProcess.cs | 2 +- 7 files changed, 4 insertions(+), 71 deletions(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index d218973ba..c7991d975 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -31,12 +31,14 @@ jobs: with: fetch-depth: 0 + # Ensure all required runtimes are available - name: 🛠️ Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: | 3.1.x 6.0.x + 7.0.x 8.0.100 - name: 🔧 Install dotnet tools diff --git a/build.cake b/build.cake index a753542bc..b10527b19 100644 --- a/build.cake +++ b/build.cake @@ -63,17 +63,13 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasFiles("LICENSE.txt", "NOTICES.txt"), HasDirectory("tools").WithFiles("nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE) }, symbols: new PackageCheck[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), HasDirectory("tools/agents/net462").WithFiles(AGENT_PDB_FILES), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_PDB_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory @@ -105,9 +101,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { checks: new PackageCheck[] { HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt", "nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles(AGENT_FILES_NETCORE), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), - HasDirectory("tools/agents/net7.0").WithFiles(AGENT_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ChocolateyTestDirectory @@ -126,14 +120,8 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("NUnit.Extension.NUnitV2ResultWriter.3.8.0"), HasDirectory("NUnit.Extension.TeamCityEventListener.1.0.7"), HasDirectory("NUnit.Extension.VSProjectLoader.3.9.0"), - //HasDirectory("bin/net462/addins").WithFiles( - // "nunit.core.dll", "nunit.core.interfaces.dll", "nunit.engine.api.dll", - // "nunit.v2.driver.dll", "nunit-project-loader.dll", "nunit-v2-result-writer.dll", - // "teamcity-event-listener.dll", "vs-project-loader.dll"), - HasDirectory("bin/netcoreapp3.1").WithFiles(ENGINE_CORE_FILES).AndFiles(ENGINE_CORE_PDB_FILES), HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("bin/agents/net7.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index d5eededcc..1b05a1446 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -49,16 +49,6 @@ - - - - - - - - - - @@ -69,16 +59,6 @@ - - - - - - - - - - diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index 76dfd2651..fd455f97a 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -55,17 +55,6 @@ - - - - - - - - - - - diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 3489e8a10..ab7a0ccfd 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -40,19 +40,6 @@ - - - - - - - - - - - - - @@ -66,19 +53,6 @@ - - - - - - - - - - - - - diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index b7ee2341a..1289644ee 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -3,7 +3,7 @@ Exe nunit.agent - net462;netcoreapp3.1;net6.0;net7.0;net8.0 + net462;net6.0;net8.0 app.manifest ..\..\..\nunit.ico false diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index bbdc6f284..e84555837 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -127,7 +127,7 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re agentExtension = ".exe"; break; case RuntimeType.NetCore: - runtimeDir = major >= 8 ? "net8.0" : major == 7 ? "net7.0" : major == 6 ? "net6.0" : "netcoreapp3.1"; + runtimeDir = major >= 7 ? "net8.0" : "net6.0"; agentName = "nunit-agent"; agentExtension = ".dll"; break; From c1add162f950d7195f084f75c479f00112997924 Mon Sep 17 00:00:00 2001 From: mazharenko Date: Wed, 29 Jan 2025 17:55:43 +0500 Subject: [PATCH 143/190] fix determination of chocolatey --- src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 1780eb6ec..e50f0a0ac 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -170,7 +170,7 @@ public void FindExtensionAssemblies(Assembly hostAssembly) { log.Info($"FindExtensionAssemblies called for host {hostAssembly.FullName}"); - bool isChocolateyPackage = System.IO.File.Exists(Path.Combine(hostAssembly.Location, "VERIFICATION.txt")); + bool isChocolateyPackage = System.IO.File.Exists(Path.Combine(Path.GetDirectoryName(hostAssembly.Location)!, "VERIFICATION.txt")); string[] extensionPatterns = isChocolateyPackage ? new[] { "nunit-extension-*/**/tools/", "nunit-extension-*/**/tools/*/" } : new[] { "NUnit.Extension.*/**/tools/", "NUnit.Extension.*/**/tools/*/" }; From 3c06abb113d416812d8d36d62fbc3ad602ac347d Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 9 Feb 2025 20:26:23 -0800 Subject: [PATCH 144/190] Fix error in use of --extensionDirectory --- package-tests.cake | 4 +- .../nunit3-console/ConsoleRunner.cs | 4 +- .../Services/DriverService.cs | 40 ++++++++------ .../nunit.engine/Services/ProjectService.cs | 52 ++++++++++--------- 4 files changed, 56 insertions(+), 44 deletions(-) diff --git a/package-tests.cake b/package-tests.cake index 0f371e852..c39888913 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -198,7 +198,7 @@ AddToBothLists(new PackageTest( new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-8.0") } })); ////////////////////////////////////////////////////////////////////// -// RUN TESTS USING EACH OF OUR EXTENSIONS +// TESTS USING EACH OF OUR EXTENSIONS ////////////////////////////////////////////////////////////////////// // NUnit Project Loader Tests @@ -287,7 +287,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "V2FrameworkDriverTest", "Run mock-assembly-v2 using the V2 Driver out of process", - "v2-tests/net462/mock-assembly-v2.dll --list-extensions", + "v2-tests/net462/mock-assembly-v2.dll", new ExpectedResult("Failed") { Total = 28, diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index 606fec6d0..366409715 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -52,7 +52,7 @@ public class ConsoleRunner public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWriter writer) { - Guard.ArgumentNotNull(_engine = engine, nameof(engine)); + Guard.ArgumentNotNull(_engine = engine, nameof(engine)); Guard.ArgumentNotNull(_options = options, nameof(options)); Guard.ArgumentNotNull(_outWriter = writer, nameof(writer)); @@ -71,7 +71,7 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri foreach (string extensionDirectory in extensionPath.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) _extensionService.FindExtensionAssemblies(extensionDirectory); - foreach (string extensionDirectory in _options.ExtensionDirectories) + foreach (string extensionDirectory in _options.ExtensionDirectories) _extensionService.FindExtensionAssemblies(extensionDirectory); _workDirectory = options.WorkDirectory; diff --git a/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs b/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs index ad9a7b5fc..8220831b0 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs @@ -20,7 +20,8 @@ public class DriverService : Service, IDriverService { static ILogger log = InternalTrace.GetLogger("DriverService"); - readonly IList _factories = new List(); + private ExtensionService _extensionService; + private IList _factories; /// /// Get a driver suitable for use with a particular test assembly. @@ -55,6 +56,9 @@ public IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, string " test assemblies are not supported by this version of the engine"); } + if (_factories == null) + InitializeDriverFactories(); + try { using (var assemblyDef = AssemblyDefinition.ReadAssembly(assemblyPath)) @@ -104,20 +108,7 @@ public override void StartService() try { - var extensionService = ServiceContext.GetService(); - if (extensionService != null) - { - foreach (IDriverFactory factory in extensionService.GetExtensions()) - _factories.Add(factory); - -#if NETFRAMEWORK - var node = extensionService.GetExtensionNode("/NUnit/Engine/NUnitV2Driver"); - if (node != null) - _factories.Add(new NUnit2DriverFactory(node)); -#endif - } - - _factories.Add(new NUnit3DriverFactory()); + _extensionService = ServiceContext.GetService(); Status = ServiceStatus.Started; } @@ -127,5 +118,24 @@ public override void StartService() throw; } } + + private void InitializeDriverFactories() + { + _factories = new List(); + + if (_extensionService != null) + { + foreach (IDriverFactory factory in _extensionService.GetExtensions()) + _factories.Add(factory); + +#if NETFRAMEWORK + var node = _extensionService.GetExtensionNode("/NUnit/Engine/NUnitV2Driver"); + if (node != null) + _factories.Add(new NUnit2DriverFactory(node)); +#endif + } + + _factories.Add(new NUnit3DriverFactory()); + } } } diff --git a/src/NUnitEngine/nunit.engine/Services/ProjectService.cs b/src/NUnitEngine/nunit.engine/Services/ProjectService.cs index 10d0628f5..df65635a6 100644 --- a/src/NUnitEngine/nunit.engine/Services/ProjectService.cs +++ b/src/NUnitEngine/nunit.engine/Services/ProjectService.cs @@ -12,10 +12,14 @@ namespace NUnit.Engine.Services /// public class ProjectService : Service, IProjectService { - Dictionary _extensionIndex = new Dictionary(); + Dictionary _extensionIndex; + ExtensionService _extensionService; public bool CanLoadFrom(string path) { + if (_extensionIndex == null) + InitializeExtensionIndex(); + ExtensionNode node = GetNodeForPath(path); if (node != null) { @@ -84,40 +88,38 @@ public override void StartService() { try { - var extensionService = ServiceContext.GetService(); + _extensionService = ServiceContext.GetService(); - if (extensionService == null) - Status = ServiceStatus.Started; - else if (extensionService.Status != ServiceStatus.Started) - Status = ServiceStatus.Error; - else - { - Status = ServiceStatus.Started; - - foreach (var node in extensionService.GetExtensionNodes()) - { - foreach (string ext in node.GetValues("FileExtension")) - { - if (ext != null) - { - if (_extensionIndex.ContainsKey(ext)) - throw new NUnitEngineException(string.Format("ProjectLoader extension {0} is already handled by another extension.", ext)); - - _extensionIndex.Add(ext, node); - } - } - } - } + Status = _extensionService == null || _extensionService.Status == ServiceStatus.Started + ? ServiceStatus.Started : ServiceStatus.Error; } catch { - // TODO: Should we just ignore any addin that doesn't load? Status = ServiceStatus.Error; throw; } } } + private void InitializeExtensionIndex() + { + _extensionIndex = new Dictionary(); + + foreach (var node in _extensionService.GetExtensionNodes()) + { + foreach (string ext in node.GetValues("FileExtension")) + { + if (ext != null) + { + if (_extensionIndex.ContainsKey(ext)) + throw new NUnitEngineException(string.Format("ProjectLoader extension {0} is already handled by another extension.", ext)); + + _extensionIndex.Add(ext, node); + } + } + } + } + private IProject LoadFrom(string path) { if (File.Exists(path)) From 1761d3e283243c0a0eb266894933ce9030bb80f8 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 14 Feb 2025 10:58:17 -0800 Subject: [PATCH 145/190] Further work on 1613 --- build.cake | 47 +- choco/nunit-console-runner.nuspec | 7 + package-tests.cake | 757 ++++++++++-------- .../nunit3-console/ConsoleRunner.cs | 4 +- src/NUnitEngine/nunit-agent/Program.cs | 4 +- .../Services/DriverService.cs | 18 +- .../Services/ExtensionManager.cs | 32 +- .../nunit.engine/nunit.engine.addins | 1 - .../nunit.engine/nunit.engine.csproj | 6 - .../FakeExtensions/fake-extensions.addins | 3 + 10 files changed, 468 insertions(+), 411 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine/nunit.engine.addins create mode 100644 src/TestData/FakeExtensions/fake-extensions.addins diff --git a/build.cake b/build.cake index b10527b19..f56815e76 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.3.1-alpha.1 +#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.1 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -74,7 +74,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + $"NUnit.ConsoleRunner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), - tests: StandardRunnerTests), + tests: PackageTests.StandardRunnerTests), // NOTE: Must follow ConsoleRunner, upon which it depends NUnitConsoleNuGetPackage = new NuGetPackage( @@ -91,9 +91,8 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory(".store/nunit.consolerunner.netcore/**/tools/net8.0/any") .WithFiles(ENGINE_FILES).AndFiles(ConsoleFiles).AndFile("Microsoft.Extensions.DependencyModel.dll") }, - testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory - + $"NUnit.ConsoleRunner.NetCore.{BuildSettings.PackageVersion}/nunit.exe"), - tests: NetCoreRunnerTests), + testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + "nunit.exe"), + tests: PackageTests.NetCoreRunnerTests), NUnitConsoleRunnerChocolateyPackage = new ChocolateyPackage( id: "nunit-console-runner", @@ -106,7 +105,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ChocolateyTestDirectory + $"nunit-console-runner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), - tests: StandardRunnerTests), + tests: PackageTests.StandardRunnerTests), NUnitConsoleZipPackage = new ZipPackage( id: "NUnit.Console", @@ -126,7 +125,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), - tests: StandardRunnerTests, + tests: PackageTests.ZipRunnerTests, bundledExtensions: new [] { KnownExtensions.VSProjectLoader.NuGetPackage, KnownExtensions.NUnitProjectLoader.NuGetPackage, @@ -168,36 +167,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { }) }); -Task("BuildZipPackage") - .Does(() => - { - NUnitConsoleZipPackage.BuildPackage(); - }); - -Task("InstallZipPackage") - .Does(() => - { - NUnitConsoleZipPackage.InstallPackage(); - }); - -Task("VerifyZipPackage") - .Does(() => - { - NUnitConsoleZipPackage.VerifyPackage(); - }); - -Task("TestZipPackage") - .Does(() => - { - NUnitConsoleZipPackage.RunPackageTests(); - }); - -Task("TestNetCorePackage") - .Does(() => - { - NUnitConsoleRunnerNetCorePackage.RunPackageTests(); - }); - ////////////////////////////////////////////////////////////////////// // TEST RUNNERS ////////////////////////////////////////////////////////////////////// @@ -212,10 +181,10 @@ public class ConsoleRunnerSelfTester : TestRunner, IPackageTestRunner _executablePath = executablePath; } - public int RunPackageTest(string arguments) + public int RunPackageTest(string arguments, bool redirectOutput = false) { Console.WriteLine("Running package test"); - return base.RunTest(_executablePath, arguments); + return base.RunPackageTest(_executablePath, new ProcessSettings { Arguments = arguments, RedirectStandardOutput = redirectOutput }); } } diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 1b05a1446..2090d1ad0 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -28,6 +28,7 @@ + @@ -48,6 +49,8 @@ + + @@ -58,6 +61,8 @@ + + @@ -68,5 +73,7 @@ + + diff --git a/package-tests.cake b/package-tests.cake index c39888913..6dc4d8a1f 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -1,350 +1,455 @@ -// Tests run for all runner packages except NETCORE runner -var StandardRunnerTests = new List(); - -// Tests run for the NETCORE runner package -var NetCoreRunnerTests = new List(); - -// Method for adding to both lists -void AddToBothLists(PackageTest test) +public static class PackageTests { - StandardRunnerTests.Add(test); - NetCoreRunnerTests.Add(test); -} + // Tests run for the Standard runner packages (both nuget and chocolatey) + public static List StandardRunnerTests = new List(); -////////////////////////////////////////////////////////////////////// -// RUN MOCK-ASSEMBLY UNDER EACH RUNTIME -////////////////////////////////////////////////////////////////////// + // Tests run for the NETCORE runner package + public static List NetCoreRunnerTests = new List(); -class MockAssemblyExpectedResult : ExpectedResult -{ - public MockAssemblyExpectedResult(params string[] runtimes) : base("Failed") - { - int nCopies = runtimes.Length; - Total = 37 * nCopies; - Passed = 23 * nCopies; - Failed = 5 * nCopies; - Warnings = 1 * nCopies; - Inconclusive = 1 * nCopies; - Skipped = 7 * nCopies; - Assemblies = new ExpectedAssemblyResult[nCopies]; - for (int i = 0; i < nCopies; i++) - Assemblies[i] = new ExpectedAssemblyResult("mock-assembly.dll", runtimes[i]); - } -} + // Tests Run for the ZIP Package + public static List ZipRunnerTests = new List(); -StandardRunnerTests.Add(new PackageTest( - 1, "Net462Test", - "Run mock-assembly.dll under .NET 4.6.2", - "testdata/net462/mock-assembly.dll", - new MockAssemblyExpectedResult("net-4.6.2"))); - -AddToBothLists(new PackageTest( - 1, "Net80Test", - "Run mock-assembly.dll under .NET 8.0", - "testdata/net8.0/mock-assembly.dll", - new MockAssemblyExpectedResult("netcore-8.0"))); - -AddToBothLists(new PackageTest( - 1, "Net70Test", - "Run mock-assembly.dll under .NET 7.0", - "testdata/net7.0/mock-assembly.dll", - new MockAssemblyExpectedResult("netcore-7.0"))); - -AddToBothLists(new PackageTest( - 1, "Net60Test", - "Run mock-assembly.dll under .NET 6.0", - "testdata/net6.0/mock-assembly.dll", - new MockAssemblyExpectedResult("netcore-6.0"))); - -AddToBothLists(new PackageTest( - 1, "NetCore31Test", - "Run mock-assembly.dll under .NET Core 3.1", - "testdata/netcoreapp3.1/mock-assembly.dll", - new MockAssemblyExpectedResult("netcore-3.1"))); - -////////////////////////////////////////////////////////////////////// -// RUN MOCK-ASSEMBLY-X86 UNDER EACH RUNTIME -////////////////////////////////////////////////////////////////////// - -const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; -// TODO: Remove the limitation to Windows -bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); - -class MockAssemblyX86ExpectedResult : MockAssemblyExpectedResult -{ - public MockAssemblyX86ExpectedResult(params string[] runtimes) : base(runtimes) + static PackageTests() { - for (int i = 0; i < runtimes.Length; i++) - Assemblies[i] = new ExpectedAssemblyResult("mock-assembly-x86.dll", runtimes[i]); - } -} + // Wrapper to add tests to all three Lists + var AllLists = new ListWrapper(StandardRunnerTests, NetCoreRunnerTests, ZipRunnerTests); -// X86 is always available for .NET Framework -StandardRunnerTests.Add(new PackageTest( - 1, "Net462X86Test", - "Run mock-assembly-x86.dll under .NET 4.6.2", - "testdata/net462/mock-assembly-x86.dll", - new MockAssemblyX86ExpectedResult("net-4.6.2"))); + // Wrapper to add tests to the standard and netcore lists + var StandardAndNetCoreLists = new ListWrapper(StandardRunnerTests, NetCoreRunnerTests); -if (dotnetX86Available) -{ - // TODO: Make tests run on all build platforms - bool onAppVeyor = BuildSystem.IsRunningOnAppVeyor; - bool onGitHubActions = BuildSystem.IsRunningOnGitHubActions; - - if (!onAppVeyor) - StandardRunnerTests.Add(new PackageTest( - 1, "Net80X86Test", - "Run mock-assembly-x86.dll under .NET 8.0", - "testdata/net8.0/mock-assembly-x86.dll", - new MockAssemblyX86ExpectedResult("netcore-8.0"))); - - if (!onAppVeyor && !onGitHubActions) - StandardRunnerTests.Add(new PackageTest( - 1, "Net70X86Test", - "Run mock-assembly-x86.dll under .NET 7.0", - "testdata/net7.0/mock-assembly-x86.dll", - new MockAssemblyX86ExpectedResult("netcore-7.0"))); - - StandardRunnerTests.Add(new PackageTest( - 1, "Net60X86Test", - "Run mock-assembly-x86.dll under .NET 6.0", - "testdata/net6.0/mock-assembly-x86.dll", - new MockAssemblyX86ExpectedResult("netcore-6.0"))); - - if (!onAppVeyor && !onGitHubActions) - StandardRunnerTests.Add(new PackageTest( - 1, "NetCore31X86Test", - "Run mock-assembly-x86.dll under .NET Core 3.1", - "testdata/netcoreapp3.1/mock-assembly-x86.dll", - new MockAssemblyX86ExpectedResult("netcore-3.1"))); -} + // Wrapper to add tests to the standard and zip Lists + var StandardAndZipLists = new ListWrapper(StandardRunnerTests, ZipRunnerTests); -////////////////////////////////////////////////////////////////////// -// RUN MULTIPLE COPIES OF MOCK-ASSEMBLY -////////////////////////////////////////////////////////////////////// - -StandardRunnerTests.Add(new PackageTest( - 1, "Net462PlusNet462Test", - "Run two copies of mock-assembly together", - "testdata/net462/mock-assembly.dll testdata/net462/mock-assembly.dll", - new MockAssemblyExpectedResult("net-4.6.2", "net-4.6.2"))); - -StandardRunnerTests.Add(new PackageTest( - 1, "Net60PlusNet80Test", - "Run mock-assembly under .NET6.0 and 8.0 together", - "testdata/net6.0/mock-assembly.dll testdata/net8.0/mock-assembly.dll", - new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0"))); - -StandardRunnerTests.Add(new PackageTest( - 1, "Net462PlusNet60Test", - "Run mock-assembly under .Net Framework 4.6.2 and .Net 6.0 together", - "testdata/net462/mock-assembly.dll testdata/net6.0/mock-assembly.dll", - new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"))); - -////////////////////////////////////////////////////////////////////// -// ASP.NETCORE TESTS -////////////////////////////////////////////////////////////////////// - -AddToBothLists(new PackageTest( - 1, "Net60AspNetCoreTest", "Run test using AspNetCore targeting .NET 6.0", - "testdata/net6.0/aspnetcore-test.dll", new ExpectedResult("Passed") - { - Total = 3, Passed = 3, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, - Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-6.0") } - })); + ////////////////////////////////////////////////////////////////////// + // RUN MOCK-ASSEMBLY UNDER EACH RUNTIME + ////////////////////////////////////////////////////////////////////// -AddToBothLists(new PackageTest( - 1, "Net80AspNetCoreTest", "Run test using AspNetCore targeting .NET 8.0", - "testdata/net8.0/aspnetcore-test.dll", new ExpectedResult("Passed") - { - Total = 3, Passed = 3, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, - Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-8.0") } - })); + StandardAndZipLists.Add(new PackageTest(1, "Net462Test") + { + Description="Run mock-assembly.dll under .NET 4.6.2", + Arguments="testdata/net462/mock-assembly.dll", + ExpectedResult=new MockAssemblyExpectedResult("net-4.6.2") + }); -////////////////////////////////////////////////////////////////////// -// WINDOWS FORMS TESTS -////////////////////////////////////////////////////////////////////// + AllLists.Add(new PackageTest(1, "Net80Test") + { + Description = "Run mock-assembly.dll under .NET 8.0", + Arguments = "testdata/net8.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-8.0") + }); -AddToBothLists(new PackageTest( - 1, "Net60WindowsFormsTest", "Run test using windows forms under .NET 6.0", - "testdata/net6.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") - { - Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, - Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("windows-forms-test.dll", "netcore-6.0") } - })); - -// Runs under Net80 runner but not NetCore -StandardRunnerTests.Add(new PackageTest( - 1, "Net80WindowsFormsTest", "Run test using windows forms under .NET 8.0", - "testdata/net8.0-windows/windows-forms-test.dll", new ExpectedResult("Passed") - { - Total = 2, Passed = 2, Failed = 0, Warnings = 0, Inconclusive = 0, Skipped = 0, - Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("windows-forms-test.dll", "netcore-8.0") } - })); - -////////////////////////////////////////////////////////////////////// -// WPF TESTS -////////////////////////////////////////////////////////////////////// - -AddToBothLists(new PackageTest( - 1, "Net60WPFTest", "Run test using WPF under .NET 6.0", - "testdata/net6.0-windows/WpfTest.dll --trace=Debug", - new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-6.0") } })); - -AddToBothLists(new PackageTest( - 1, "Net80WPFTest", "Run test using WPF under .NET 8.0", - "testdata/net8.0-windows/WpfTest.dll --trace=Debug", - new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-8.0") } })); - -////////////////////////////////////////////////////////////////////// -// TESTS USING EACH OF OUR EXTENSIONS -////////////////////////////////////////////////////////////////////// - -// NUnit Project Loader Tests -StandardRunnerTests.Add(new PackageTest( - 1, "NUnitProjectTest", - "Run NUnit project with mock-assembly.dll built for .NET 4.6.2 and 6.0", - "../../MixedTests.nunit --config=Release", - new MockAssemblyExpectedResult("net-4.6.2", "net-6.0"), - KnownExtensions.NUnitProjectLoader)); - -NetCoreRunnerTests.Add(new PackageTest( - 1, "NUnitProjectTest", - "Run NUnit project with mock-assembly.dll built for .NET 6.0 and 8.0", - "../../NetCoreTests.nunit --config=Release", - new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0"), - KnownExtensions.NUnitProjectLoader)); - -// V2 Result Writer Test -AddToBothLists(new PackageTest( - 1, "V2ResultWriterTest", - "Run mock-assembly under .NET 6.0 and produce V2 output", - "testdata/net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", - new MockAssemblyExpectedResult("netcore-6.0"), - KnownExtensions.NUnitV2ResultWriter)); - -// VS Project Loader Tests -StandardRunnerTests.Add(new PackageTest( - 1, "VSProjectLoaderTest_Project", - "Run mock-assembly using the .csproj file", - "../../src/TestData/mock-assembly/mock-assembly.csproj --config=Release", - new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0"), - KnownExtensions.VSProjectLoader)); - -StandardRunnerTests.Add(new PackageTest( - 1, "VSProjectLoaderTest_Solution", - "Run mock-assembly using the .sln file", - "../../src/TestData/TestData.sln --config=Release --trace=Debug", - new ExpectedResult("Failed") - { - Total = 37 * 5, - Passed = 23 * 5, - Failed = 5 * 5, - Warnings = 1 * 5, - Inconclusive = 1 * 5, - Skipped = 7 * 5, - Assemblies = new ExpectedAssemblyResult[] - { - new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-3.1"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), - new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), - new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), - new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0"), - new ExpectedAssemblyResult("WpfApp.exe") + AllLists.Add(new PackageTest(1, "Net70Test") + { + Description = "Run mock-assembly.dll under .NET 7.0", + Arguments = "testdata/net7.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-7.0") + }); + + AllLists.Add(new PackageTest(1, "Net60Test") + { + Description = "Run mock-assembly.dll under .NET 6.0", + Arguments = "testdata/net6.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0") + }); + + AllLists.Add(new PackageTest(1, "NetCore31Test") + { + Description = "Run mock-assembly.dll under .NET Core 3.1", + Arguments = "testdata/netcoreapp3.1/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-3.1") + }); + + ////////////////////////////////////////////////////////////////////// + // RUN MOCK-ASSEMBLY-X86 UNDER EACH RUNTIME + ////////////////////////////////////////////////////////////////////// + + const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; + // TODO: Remove the limitation to Windows + bool dotnetX86Available = BuildSettings.IsRunningOnWindows && System.IO.File.Exists(DOTNET_EXE_X86); + + // X86 is always available for .NET Framework + StandardAndZipLists.Add(new PackageTest(1, "Net462X86Test") + { + Description = "Run mock-assembly-x86.dll under .NET 4.6.2", + Arguments = "testdata/net462/mock-assembly-x86.dll", + ExpectedResult = new MockAssemblyX86ExpectedResult("net-4.6.2") + }); + + if (dotnetX86Available) + { + // TODO: Make tests run on all build platforms + bool onGitHubActions = BuildSettings.IsRunningOnGitHubActions; + + StandardAndZipLists.Add(new PackageTest(1, "Net80X86Test") + { + Description = "Run mock-assembly-x86.dll under .NET 8.0", + Arguments = "testdata/net8.0/mock-assembly-x86.dll", + ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-8.0") + }); + + if (!onGitHubActions) + StandardAndZipLists.Add(new PackageTest(1, "Net70X86Test") + { + Description = "Run mock-assembly-x86.dll under .NET 7.0", + Arguments = "testdata/net7.0/mock-assembly-x86.dll", + ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-7.0") + }); + + StandardAndZipLists.Add(new PackageTest(1, "Net60X86Test") + { + Description = "Run mock-assembly-x86.dll under .NET 6.0", + Arguments = "testdata/net6.0/mock-assembly-x86.dll", + ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-6.0") + }); + + if (!onGitHubActions) + StandardAndZipLists.Add(new PackageTest(1, "NetCore31X86Test") + { + Description = "Run mock-assembly-x86.dll under .NET Core 3.1", + Arguments = "testdata/netcoreapp3.1/mock-assembly-x86.dll", + ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-3.1") + }); } - }, - KnownExtensions.VSProjectLoader)); - -// TeamCity Event Listener Test -StandardRunnerTests.Add(new PackageTest( - 1, "TeamCityListenerTest", - "Run mock-assembly with --teamcity enabled", - "testdata/net462/mock-assembly.dll --teamcity", - new MockAssemblyExpectedResult("net-4.6.2"), - KnownExtensions.TeamCityEventListener)); - -// V2 Framework Driver Tests -StandardRunnerTests.Add(new PackageTest( - 1, "V2FrameworkDriverTest", - "Run mock-assembly-v2 using the V2 Driver in process", - "v2-tests/net462/mock-assembly-v2.dll", - new ExpectedResult("Failed") - { - Total = 28, - Passed = 18, - Failed = 5, - Warnings = 0, - Inconclusive = 1, - Skipped = 4, - Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } - }, - KnownExtensions.NUnitV2Driver)); - -StandardRunnerTests.Add(new PackageTest( - 1, "V2FrameworkDriverTest", - "Run mock-assembly-v2 using the V2 Driver out of process", - "v2-tests/net462/mock-assembly-v2.dll", - new ExpectedResult("Failed") - { - Total = 28, - Passed = 18, - Failed = 5, - Warnings = 0, - Inconclusive = 1, - Skipped = 4, - Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } - }, - KnownExtensions.NUnitV2Driver)); - -////////////////////////////////////////////////////////////////////// -// SPECIAL CASES -////////////////////////////////////////////////////////////////////// - -StandardRunnerTests.Add(new PackageTest( - 1, "InvalidTestNameTest_Net462", - "Ensure we handle invalid test names correctly under .NET 4.6.2", - "testdata/net462/InvalidTestNames.dll --trace:Debug", - new ExpectedResult("Passed") + + ////////////////////////////////////////////////////////////////////// + // RUN MULTIPLE COPIES OF MOCK-ASSEMBLY + ////////////////////////////////////////////////////////////////////// + + StandardAndZipLists.Add(new PackageTest(1, "Net462PlusNet462Test") + { + Description = "Run two copies of mock-assembly together", + Arguments = "testdata/net462/mock-assembly.dll testdata/net462/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2", "net-4.6.2") + }); + + StandardAndZipLists.Add(new PackageTest(1, "Net60PlusNet80Test") + { + Description = "Run mock-assembly under .NET6.0 and 8.0 together", + Arguments = "testdata/net6.0/mock-assembly.dll testdata/net8.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0") + }); + + StandardAndZipLists.Add(new PackageTest(1, "Net462PlusNet60Test") + { + Description = "Run mock-assembly under .Net Framework 4.6.2 and .Net 6.0 together", + Arguments = "testdata/net462/mock-assembly.dll testdata/net6.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0") + }); + + ////////////////////////////////////////////////////////////////////// + // ASP.NETCORE TESTS + ////////////////////////////////////////////////////////////////////// + + AllLists.Add(new PackageTest(1, "Net60AspNetCoreTest") + { + Description = "Run test using AspNetCore targeting .NET 6.0", + Arguments = "testdata/net6.0/aspnetcore-test.dll", + ExpectedResult = new ExpectedResult("Passed") + { + Total = 3, + Passed = 3, + Failed = 0, + Warnings = 0, + Inconclusive = 0, + Skipped = 0, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-6.0") } + } + }); + + AllLists.Add(new PackageTest(1, "Net80AspNetCoreTest") + { + Description = "Run test using AspNetCore targeting .NET 8.0", + Arguments = "testdata/net8.0/aspnetcore-test.dll", + ExpectedResult = new ExpectedResult("Passed") + { + Total = 3, + Passed = 3, + Failed = 0, + Warnings = 0, + Inconclusive = 0, + Skipped = 0, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-8.0") } + } + }); + + ////////////////////////////////////////////////////////////////////// + // WINDOWS FORMS TESTS + ////////////////////////////////////////////////////////////////////// + + AllLists.Add(new PackageTest(1, "Net60WindowsFormsTest") + { + Description = "Run test using windows forms under .NET 6.0", + Arguments = "testdata/net6.0-windows/windows-forms-test.dll", + ExpectedResult = new ExpectedResult("Passed") + { + Total = 2, + Passed = 2, + Failed = 0, + Warnings = 0, + Inconclusive = 0, + Skipped = 0, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("windows-forms-test.dll", "netcore-6.0") } + } + }); + + StandardAndZipLists.Add(new PackageTest(1, "Net80WindowsFormsTest") + { + Description = "Run test using windows forms under .NET 8.0", + Arguments = "testdata/net8.0-windows/windows-forms-test.dll", + ExpectedResult = new ExpectedResult("Passed") + { + Total = 2, + Passed = 2, + Failed = 0, + Warnings = 0, + Inconclusive = 0, + Skipped = 0, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("windows-forms-test.dll", "netcore-8.0") } + } + }); + + ////////////////////////////////////////////////////////////////////// + // WPF TESTS + ////////////////////////////////////////////////////////////////////// + + AllLists.Add(new PackageTest(1, "Net60WPFTest") + { + Description = "Run test using WPF under .NET 6.0", + Arguments = "testdata/net6.0-windows/WpfTest.dll --trace=Debug", + ExpectedResult = new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-6.0") } } + }); + + AllLists.Add(new PackageTest(1, "Net80WPFTest") + { + Description = "Run test using WPF under .NET 8.0", + Arguments = "testdata/net8.0-windows/WpfTest.dll --trace=Debug", + ExpectedResult = new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-8.0") } } + }); + + ////////////////////////////////////////////////////////////////////// + // TESTS OF EXTENSION LISTING + ////////////////////////////////////////////////////////////////////// + + StandardAndNetCoreLists.Add(new PackageTest(1, "NoExtensionsInstalled") + { + Description = "List Extensions shows none installed", + Arguments = "--list-extensions", + OutputCheck = new OutputDoesNotContain("Extension:"), + }); + + StandardAndNetCoreLists.Add(new PackageTest(1, "ExtensionsInstalledFromAddedDirectory") + { + Description = "List Extensions shows extension from added directory", + Arguments = "--extensionDirectory ../../src/TestData/FakeExtensions --list-extensions", + OutputCheck = new OutputContains("Extension:", exactly: 5) + }); + + ZipRunnerTests.Add(new PackageTest(1, "BundledExtensionsInstalled") + { + Description = "List Extensions shows bundled extensions", + Arguments = "--list-extensions", + OutputCheck = new OutputContains("Extension:", exactly: 5) + }); + + ////////////////////////////////////////////////////////////////////// + // TESTS USING EACH OF OUR EXTENSIONS + ////////////////////////////////////////////////////////////////////// + + // NUnit Project Loader Tests + StandardAndZipLists.Add(new PackageTest(1, "NUnitProjectTest") + { + Description = "Run NUnit project with mock-assembly.dll built for .NET 4.6.2 and 6.0", + Arguments = "../../MixedTests.nunit --config=Release", + ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2", "net-6.0"), + ExtensionsNeeded = new [] { KnownExtensions.NUnitProjectLoader } + }); + + NetCoreRunnerTests.Add(new PackageTest(1, "NUnitProjectTest") + { + Description="Run NUnit project with mock-assembly.dll built for .NET 6.0 and 8.0", + Arguments="../../NetCoreTests.nunit --config=Release", + ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0"), + ExtensionsNeeded = new [] { KnownExtensions.NUnitProjectLoader } + }); + + // V2 Result Writer Test + AllLists.Add(new PackageTest(1, "V2ResultWriterTest") + { + Description = "Run mock-assembly under .NET 6.0 and produce V2 output", + Arguments = "testdata/net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", + ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0"), + ExtensionsNeeded = new[] { KnownExtensions.NUnitV2ResultWriter } + }); + + // VS Project Loader Tests + StandardAndZipLists.Add(new PackageTest(1, "VSProjectLoaderTest_Project") + { + Description = "Run mock-assembly using the .csproj file", + Arguments = "../../src/TestData/mock-assembly/mock-assembly.csproj --config=Release", + ExpectedResult = new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0"), + ExtensionsNeeded = new[] { KnownExtensions.VSProjectLoader } + }); + + StandardAndZipLists.Add(new PackageTest(1, "VSProjectLoaderTest_Solution") + { + Description = "Run mock-assembly using the .sln file", + Arguments = "../../src/TestData/TestData.sln --config=Release --trace=Debug", + ExpectedResult = new ExpectedResult("Failed") + { + Total = 37 * 5, + Passed = 23 * 5, + Failed = 5 * 5, + Warnings = 1 * 5, + Inconclusive = 1 * 5, + Skipped = 7 * 5, + Assemblies = new ExpectedAssemblyResult[] + { + new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-3.1"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), + new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), + new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), + new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0"), + new ExpectedAssemblyResult("WpfApp.exe") + } + }, + ExtensionsNeeded = new[] { KnownExtensions.VSProjectLoader } + }); + + // TeamCity Event Listener Test + StandardAndZipLists.Add(new PackageTest(1, "TeamCityListenerTest") + { + Description = "Run mock-assembly with --teamcity enabled", + Arguments = "testdata/net462/mock-assembly.dll --teamcity", + ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), + ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener } + }); + + // V2 Framework Driver Tests + StandardAndZipLists.Add(new PackageTest(1, "V2FrameworkDriverTest") + { + Description = "Run mock-assembly-v2 using the V2 Driver in process", + Arguments = "v2-tests/net462/mock-assembly-v2.dll", + ExpectedResult = new ExpectedResult("Failed") + { + Total = 28, + Passed = 18, + Failed = 5, + Warnings = 0, + Inconclusive = 1, + Skipped = 4, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } + }, + ExtensionsNeeded = new[] { KnownExtensions.NUnitV2Driver } + }); + + StandardAndZipLists.Add(new PackageTest(1, "V2FrameworkDriverTest") + { + Description = "Run mock-assembly-v2 using the V2 Driver out of process", + Arguments = "v2-tests/net462/mock-assembly-v2.dll", + ExpectedResult = new ExpectedResult("Failed") + { + Total = 28, + Passed = 18, + Failed = 5, + Warnings = 0, + Inconclusive = 1, + Skipped = 4, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } + }, + ExtensionsNeeded = new[] { KnownExtensions.NUnitV2Driver } + }); + + ////////////////////////////////////////////////////////////////////// + // SPECIAL CASES + ////////////////////////////////////////////////////////////////////// + + StandardAndZipLists.Add(new PackageTest(1, "InvalidTestNameTest_Net462") + { + Description = "Ensure we handle invalid test names correctly under .NET 4.6.2", + Arguments = "testdata/net462/InvalidTestNames.dll --trace:Debug", + ExpectedResult = new ExpectedResult("Passed") + { + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("InvalidTestNames.dll", "net-4.6.2") } + } + }); + + AllLists.Add(new PackageTest(1, "InvalidTestNameTest_Net60") + { + Description = "Ensure we handle invalid test names correctly under .NET 6.0", + Arguments = "testdata/net6.0/InvalidTestNames.dll --trace:Debug", + ExpectedResult = new ExpectedResult("Passed") + { + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("InvalidTestNames.dll", "netcore-6.0") } + } + }); + + AllLists.Add(new PackageTest(1, "InvalidTestNameTest_Net80") + { + Description = "Ensure we handle invalid test names correctly under .NET 8.0", + Arguments = "testdata/net8.0/InvalidTestNames.dll --trace:Debug", + ExpectedResult = new ExpectedResult("Passed") + { + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("InvalidTestNames.dll", "netcore-8.0") } + } + }); + + StandardAndZipLists.Add(new PackageTest(1, "AppContextBaseDirectory_NET80") + { + Description = "Test Setting the BaseDirectory to match test assembly location under .NET 8.0", + Arguments = "testdata/net8.0/AppContextTest.dll", + ExpectedResult = new ExpectedResult("Passed") + { + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("AppContextTest.dll", "netcore-8.0") } + } + }); + } + + #region Nested Classes + + class ListWrapper { - Assemblies = new ExpectedAssemblyResult[] + private List[] _lists; + + public ListWrapper(params List[] lists) { - new ExpectedAssemblyResult("InvalidTestNames.dll", "net-4.6.2") + _lists = lists; } - })); -AddToBothLists(new PackageTest( - 1, "InvalidTestNameTest_Net60", - "Ensure we handle invalid test names correctly under .NET 6.0", - "testdata/net6.0/InvalidTestNames.dll --trace:Debug", - new ExpectedResult("Passed") - { - Assemblies = new ExpectedAssemblyResult[] + public void Add(PackageTest test) { - new ExpectedAssemblyResult("InvalidTestNames.dll", "netcore-6.0") + foreach (var list in _lists) + list.Add(test); } - })); + } -AddToBothLists(new PackageTest( - 1, "InvalidTestNameTest_Net80", - "Ensure we handle invalid test names correctly under .NET 8.0", - "testdata/net8.0/InvalidTestNames.dll --trace:Debug", - new ExpectedResult("Passed") + class MockAssemblyExpectedResult : ExpectedResult { - Assemblies = new ExpectedAssemblyResult[] + public MockAssemblyExpectedResult(params string[] runtimes) : base("Failed") { - new ExpectedAssemblyResult("InvalidTestNames.dll", "netcore-8.0") + int nCopies = runtimes.Length; + Total = 37 * nCopies; + Passed = 23 * nCopies; + Failed = 5 * nCopies; + Warnings = 1 * nCopies; + Inconclusive = 1 * nCopies; + Skipped = 7 * nCopies; + Assemblies = new ExpectedAssemblyResult[nCopies]; + for (int i = 0; i < nCopies; i++) + Assemblies[i] = new ExpectedAssemblyResult("mock-assembly.dll", runtimes[i]); } - })); + } -StandardRunnerTests.Add(new PackageTest( - 1, "AppContextBaseDirectory_NET80", - "Test Setting the BaseDirectory to match test assembly location under .NET 8.0", - "testdata/net8.0/AppContextTest.dll", - new ExpectedResult("Passed") + class MockAssemblyX86ExpectedResult : MockAssemblyExpectedResult { - Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("AppContextTest.dll", "netcore-8.0") } - })); + public MockAssemblyX86ExpectedResult(params string[] runtimes) : base(runtimes) + { + for (int i = 0; i < runtimes.Length; i++) + Assemblies[i] = new ExpectedAssemblyResult("mock-assembly-x86.dll", runtimes[i]); + } + } + + #endregion +} diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index 366409715..606fec6d0 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -52,7 +52,7 @@ public class ConsoleRunner public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWriter writer) { - Guard.ArgumentNotNull(_engine = engine, nameof(engine)); + Guard.ArgumentNotNull(_engine = engine, nameof(engine)); Guard.ArgumentNotNull(_options = options, nameof(options)); Guard.ArgumentNotNull(_outWriter = writer, nameof(writer)); @@ -71,7 +71,7 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri foreach (string extensionDirectory in extensionPath.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) _extensionService.FindExtensionAssemblies(extensionDirectory); - foreach (string extensionDirectory in _options.ExtensionDirectories) + foreach (string extensionDirectory in _options.ExtensionDirectories) _extensionService.FindExtensionAssemblies(extensionDirectory); _workDirectory = options.WorkDirectory; diff --git a/src/NUnitEngine/nunit-agent/Program.cs b/src/NUnitEngine/nunit-agent/Program.cs index 99bb8d0c2..0f182ae5e 100644 --- a/src/NUnitEngine/nunit-agent/Program.cs +++ b/src/NUnitEngine/nunit-agent/Program.cs @@ -62,8 +62,8 @@ public static void Main(string[] args) } } - // var logName = $"nunit-agent_{pid}.log"; - // InternalTrace.Initialize(Path.Combine(workDirectory, logName), traceLevel); // See PR 1214 https://github.com/nunit/nunit-console/pull/1214/files + var logName = $"nunit-agent_{pid}.log"; + InternalTrace.Initialize(Path.Combine(workDirectory, logName), traceLevel); // See PR 1214 https://github.com/nunit/nunit-console/pull/1214/files log = InternalTrace.GetLogger(typeof(NUnitTestAgent)); log.Info("Agent process {0} starting", pid); diff --git a/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs b/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs index 8220831b0..13764b8bf 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs @@ -21,7 +21,7 @@ public class DriverService : Service, IDriverService static ILogger log = InternalTrace.GetLogger("DriverService"); private ExtensionService _extensionService; - private IList _factories; + private List _factories; /// /// Get a driver suitable for use with a particular test assembly. @@ -76,7 +76,8 @@ public IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, string foreach (var factory in _factories) { - log.Debug($"Trying {factory.GetType().Name}"); + string factoryName = factory.GetType().Name; + log.Debug($"Trying {factoryName}"); foreach (var reference in references) { @@ -87,6 +88,8 @@ public IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, string return factory.GetDriver(reference); #endif } + + log.Debug($"No driver found using {factoryName}"); } } } @@ -98,8 +101,8 @@ public IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, string if (skipNonTestAssemblies) return new SkippedAssemblyFrameworkDriver(assemblyPath); else - return new InvalidAssemblyFrameworkDriver(assemblyPath, string.Format("No suitable tests found in '{0}'.\n" + - "Either assembly contains no tests or proper test driver has not been found.", assemblyPath)); + return new InvalidAssemblyFrameworkDriver(assemblyPath, string.Format( + $"No suitable tests found in '{assemblyPath}'.\r\nEither assembly contains no tests or proper test driver has not been found.")); } public override void StartService() @@ -123,10 +126,11 @@ private void InitializeDriverFactories() { _factories = new List(); - if (_extensionService != null) + if (_extensionService == null) + log.Debug("ExtensionService is not available, no driver extensions will be loaded"); + else { - foreach (IDriverFactory factory in _extensionService.GetExtensions()) - _factories.Add(factory); + _factories.AddRange(_extensionService.GetExtensions()); #if NETFRAMEWORK var node = _extensionService.GetExtensionNode("/NUnit/Engine/NUnitV2Driver"); diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index e50f0a0ac..9837ae245 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -10,7 +10,6 @@ using NUnit.Engine.Internal.FileSystemAccess; using NUnit.Engine.Internal.FileSystemAccess.Default; using System.Linq; -using System.Diagnostics; namespace NUnit.Engine.Services { @@ -21,7 +20,6 @@ public class ExtensionManager static readonly Logger log = InternalTrace.GetLogger(typeof(ExtensionManager)); private readonly IFileSystem _fileSystem; - //private readonly IAddinsFileReader _addinsReader; private readonly IDirectoryFinder _directoryFinder; // List of all ExtensionPoints discovered @@ -154,10 +152,7 @@ public void FindExtensionAssemblies(string startDir) log.Info($"FindExtensionAssemblies examining extension directory {startDir}"); - // Create the list of possible extension assemblies, - // eliminating duplicates, start in the provided directory. - // In this top level directory, we only look at .addins files. - ProcessAddinsFiles(_fileSystem.GetDirectory(startDir), false); + ProcessDirectory(_fileSystem.GetDirectory(startDir), false); } } @@ -242,7 +237,9 @@ public IEnumerable GetExtensionNodes(bool includeDisabled = fa EnsureExtensionsAreLoaded(); var ep = GetExtensionPoint(typeof(T)); - if (ep != null) + if (ep == null) + log.Debug("There is no extension point of type {typeof(T).Name}"); + else foreach (var node in ep.Extensions) if (includeDisabled || node.Enabled) yield return node; @@ -631,27 +628,6 @@ internal bool CanLoadTargetFramework(Assembly runnerAsm, ExtensionAssembly exten } } - //string extensionFrameworkName = AssemblyDefinition.ReadAssembly(extensionAsm.FilePath).GetFrameworkName(); - //string runnerFrameworkName = AssemblyDefinition.ReadAssembly(runnerAsm.Location).GetFrameworkName(); - //if (runnerFrameworkName?.StartsWith(".NETStandard") == true) - //{ - // throw new NUnitEngineException($"{runnerAsm.FullName} test runner must target .NET Core or .NET Framework, not .NET Standard"); - //} - //else if (runnerFrameworkName?.StartsWith(".NETCoreApp") == true) - //{ - // if (extensionFrameworkName?.StartsWith(".NETStandard") != true && extensionFrameworkName?.StartsWith(".NETCoreApp") != true) - // { - // log.Info($".NET Core runners require .NET Core or .NET Standard extension for {extensionAsm.FilePath}"); - // return false; - // } - //} - //else if (extensionFrameworkName?.StartsWith(".NETCoreApp") == true) - //{ - // log.Info($".NET Framework runners cannot load .NET Core extension {extensionAsm.FilePath}"); - // return false; - //} - - //return true; } private System.Runtime.Versioning.FrameworkName GetTargetRuntime(string filePath) diff --git a/src/NUnitEngine/nunit.engine/nunit.engine.addins b/src/NUnitEngine/nunit.engine/nunit.engine.addins deleted file mode 100644 index 4ddd66c2e..000000000 --- a/src/NUnitEngine/nunit.engine/nunit.engine.addins +++ /dev/null @@ -1 +0,0 @@ -../../../bundled-extensions/NUnit.Extension.*/tools/**/ \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine/nunit.engine.csproj b/src/NUnitEngine/nunit.engine/nunit.engine.csproj index c8a16bcdf..c9dd0007f 100644 --- a/src/NUnitEngine/nunit.engine/nunit.engine.csproj +++ b/src/NUnitEngine/nunit.engine/nunit.engine.csproj @@ -30,10 +30,4 @@ - - - PreserveNewest - - - \ No newline at end of file diff --git a/src/TestData/FakeExtensions/fake-extensions.addins b/src/TestData/FakeExtensions/fake-extensions.addins new file mode 100644 index 000000000..0e9627ea7 --- /dev/null +++ b/src/TestData/FakeExtensions/fake-extensions.addins @@ -0,0 +1,3 @@ +# This file isn't copied anywhere but is used by one of our +# package tests to exercise the --extensionDirectory option +../../../bin/Release/fakes/**/FakeExtensions.dll \ No newline at end of file From eeccd37a5b04d3aa681a04e8f00aefbd8b791b16 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 22 Feb 2025 04:09:43 -0800 Subject: [PATCH 146/190] Eliminate default option provider and ignore missing extension under teamcity --- .../nunit3-console.tests/CommandLineTests.cs | 41 +++------------ .../nunit3-console.tests/ConsoleMocks.cs | 3 +- .../DefaultOptionsProviderTests.cs | 52 ------------------- .../nunit3-console/ConsoleOptions.cs | 5 +- .../nunit3-console/ConsoleRunner.cs | 16 ++++-- .../Options/DefaultOptionsProvider.cs | 19 ------- .../Options/IDefaultOptionsProvider.cs | 9 ---- src/NUnitConsole/nunit3-console/Program.cs | 2 +- 8 files changed, 21 insertions(+), 126 deletions(-) delete mode 100644 src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs delete mode 100644 src/NUnitConsole/nunit3-console/Options/DefaultOptionsProvider.cs delete mode 100644 src/NUnitConsole/nunit3-console/Options/IDefaultOptionsProvider.cs diff --git a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs index f2fda3363..7b9770b39 100644 --- a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs @@ -92,7 +92,7 @@ public void GetArgsFromFiles(string commandline, string files, params string[] e var fileSystem = new VirtualFileSystem(); fileSystem.SetupFiles(files); - var options = new ConsoleOptions(new DefaultOptionsProviderStub(false), fileSystem); + var options = new ConsoleOptions(fileSystem); // When var expandedArgs = options.PreParse(commandline.Split(' ')); @@ -106,7 +106,7 @@ public void GetArgsFromFiles(string commandline, string files, params string[] e [TestCase("--arg1 @ --arg2", "You must include a file name after @.")] public void GetArgsFromFiles_FailureTests(string args, string errorMessage) { - var options = new ConsoleOptions(new DefaultOptionsProviderStub(false), new VirtualFileSystem()); + var options = new ConsoleOptions(new VirtualFileSystem()); options.PreParse(args.Split(' ')); @@ -119,7 +119,7 @@ public void GetArgsFromFiles_NestingOverflow() var fileSystem = new VirtualFileSystem(); var lines = new string[] { "@file1.txt" }; fileSystem.SetupFile("file1.txt", lines); - var options = new ConsoleOptions(new DefaultOptionsProviderStub(false), fileSystem); + var options = new ConsoleOptions(fileSystem); var expectedErrors = new string[] { "Arguments file nesting exceeds maximum depth of 3." }; var arglist = options.PreParse(lines); @@ -431,7 +431,6 @@ public void ResultOptionWithFilePathAndTransform() IFileSystem fileSystem = GetFileSystemContainingFile(transformFile); ConsoleOptions options = new ConsoleOptions( - new DefaultOptionsProviderStub(false), fileSystem, "tests.dll", $"-result:results.xml;transform={transformFile}"); Assert.That(options.Validate(), Is.True); @@ -469,7 +468,6 @@ public void ResultOptionMayBeRepeated() IFileSystem fileSystem = GetFileSystemContainingFile(transformFile); ConsoleOptions options = new ConsoleOptions( - new DefaultOptionsProviderStub(false), fileSystem, "tests.dll", "-result:results.xml", "-result:nunit2results.xml;format=nunit2", $"-result:myresult.xml;transform={transformFile}"); Assert.That(options.Validate(), Is.True, "Should be valid"); @@ -521,7 +519,6 @@ public void MissingXsltFileRecordsError() const string missingXslt = "missing.xslt"; var options = new ConsoleOptions( - new DefaultOptionsProviderStub(false), new VirtualFileSystem(), "test.dll", $"-result:userspecifed.xml;transform={missingXslt}"); Assert.That(options.ResultOutputSpecifications, Has.Exactly(1).Items @@ -573,7 +570,6 @@ public void ExploreOptionWithFilePathAndTransform() string transformFile = Path.Combine(TestContext.CurrentContext.TestDirectory, "TextSummary.xslt"); IFileSystem fileSystem = GetFileSystemContainingFile(transformFile); ConsoleOptions options = new ConsoleOptions( - new DefaultOptionsProviderStub(false), fileSystem, "tests.dll", $"-explore:results.xml;transform={transformFile}"); Assert.That(options.Validate(), Is.True); @@ -599,14 +595,9 @@ public void ExploreOptionWithFilePathUsingEqualSign() Assert.That(options.ExploreOutputSpecifications[0].OutputPath, Is.EqualTo("C:/nunit/tests/bin/Debug/console-test.xml")); } - [Test] - [TestCase(true, null, true)] - [TestCase(false, null, false)] - [TestCase(true, false, true)] - [TestCase(false, false, false)] - [TestCase(true, true, true)] - [TestCase(false, true, true)] - public void ShouldSetTeamCityFlagAccordingToArgsAndDefaults(bool hasTeamcityInCmd, bool? defaultTeamcity, bool expectedTeamCity) + [TestCase(true, true)] + [TestCase(false, false)] + public void ShouldSetTeamCityFlagAccordingToArgsAndDefaults(bool hasTeamcityInCmd, bool expectedTeamCity) { // Given List args = new List { "tests.dll" }; @@ -615,15 +606,7 @@ public void ShouldSetTeamCityFlagAccordingToArgsAndDefaults(bool hasTeamcityInCm args.Add("--teamcity"); } - ConsoleOptions options; - if (defaultTeamcity.HasValue) - { - options = new ConsoleOptions(new DefaultOptionsProviderStub(defaultTeamcity.Value), new VirtualFileSystem(), args.ToArray()); - } - else - { - options = ConsoleMocks.Options(args.ToArray()); - } + ConsoleOptions options = ConsoleMocks.Options(args.ToArray()); // When var actualTeamCity = options.TeamCity; @@ -873,15 +856,5 @@ private static PropertyInfo GetPropertyInfo(string propertyName) Assert.That(property, Is.Not.Null, "The property '{0}' is not defined", propertyName); return property; } - - internal sealed class DefaultOptionsProviderStub : IDefaultOptionsProvider - { - public DefaultOptionsProviderStub(bool teamCity) - { - TeamCity = teamCity; - } - - public bool TeamCity { get; private set; } - } } } diff --git a/src/NUnitConsole/nunit3-console.tests/ConsoleMocks.cs b/src/NUnitConsole/nunit3-console.tests/ConsoleMocks.cs index 26ffdefb4..8b5c0813b 100644 --- a/src/NUnitConsole/nunit3-console.tests/ConsoleMocks.cs +++ b/src/NUnitConsole/nunit3-console.tests/ConsoleMocks.cs @@ -11,8 +11,7 @@ internal static class ConsoleMocks public static ConsoleOptions Options(params string[] args) { var mockFileSystem = Substitute.For(); - var mockDefaultsProvider = Substitute.For(); - return new ConsoleOptions(mockDefaultsProvider, mockFileSystem, args); + return new ConsoleOptions(mockFileSystem, args); } } } diff --git a/src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs b/src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs deleted file mode 100644 index 8584260d2..000000000 --- a/src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; - -using NUnit.ConsoleRunner.Options; -using NUnit.Framework; - -namespace NUnit.ConsoleRunner.Tests -{ - [TestFixture] - public sealed class DefaultOptionsProviderTests - { - private const string EnvironmentVariableTeamcityProjectName = "TEAMCITY_PROJECT_NAME"; - - [TearDown] - public void TearDown() - { - Environment.SetEnvironmentVariable(EnvironmentVariableTeamcityProjectName, null); - } - - [Test] - public void ShouldRetTeamCityAsTrueWhenHasEnvironmentVariable_TEAMCITY_PROJECT_NAME() - { - // Given - var provider = CreateInstance(); - - // When - Environment.SetEnvironmentVariable(EnvironmentVariableTeamcityProjectName, "Abc"); - - // Then - Assert.That(provider.TeamCity, Is.True); - } - - [Test] - public void ShouldRetTeamCityAsFalseWhenHasNotEnvironmentVariable_TEAMCITY_PROJECT_NAME() - { - // Given - var provider = CreateInstance(); - - // When - Environment.SetEnvironmentVariable(EnvironmentVariableTeamcityProjectName, string.Empty); - - // Then - Assert.That(provider.TeamCity, Is.False); - } - - private static DefaultOptionsProvider CreateInstance() - { - return new DefaultOptionsProvider(); - } - } -} \ No newline at end of file diff --git a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs index 2939dc804..648e76404 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs @@ -28,11 +28,8 @@ public class ConsoleOptions : OptionSet /// protected readonly IFileSystem _fileSystem; - internal ConsoleOptions(IDefaultOptionsProvider defaultOptionsProvider, IFileSystem fileSystem, params string[] args) + internal ConsoleOptions(IFileSystem fileSystem, params string[] args) { - // Apply default options - if (defaultOptionsProvider == null) throw new ArgumentNullException(nameof(defaultOptionsProvider)); - TeamCity = defaultOptionsProvider.TeamCity; _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); ConfigureOptions(); diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index 606fec6d0..01a72bdd5 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -29,6 +29,7 @@ public class ConsoleRunner private const string EVENT_LISTENER_EXTENSION_PATH = "/NUnit/Engine/TypeExtensions/ITestEventListener"; private const string TEAMCITY_EVENT_LISTENER_FULLNAME = "NUnit.Engine.Listeners.TeamCityEventListener"; private const string TEAMCITY_EVENT_LISTENER_NAME = "TeamCityEventListener"; + private const string TEAMCITY_PROJECT_NAME = "TEAMCITY_PROJECT_NAME"; private const string NUNIT_EXTENSION_DIRECTORIES = "NUNIT_EXTENSION_DIRECTORIES"; @@ -80,20 +81,25 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri else _workDirectory = null; - if (_options.TeamCity) + if (_options.TeamCity || RunningUnderTeamCity) { bool teamcityInstalled = false; foreach (var node in _extensionService.GetExtensionNodes(EVENT_LISTENER_EXTENSION_PATH)) if (teamcityInstalled = node.TypeName == TEAMCITY_EVENT_LISTENER_FULLNAME) break; - if (!teamcityInstalled) + + if (teamcityInstalled) + // Enable TeamCityEventListener immediately, before the console is redirected + _extensionService.EnableExtension("NUnit.Engine.Listeners.TeamCityEventListener", true); + else if (_options.TeamCity) throw new RequiredExtensionException(TEAMCITY_EVENT_LISTENER_NAME, "--teamcity"); } - - // Enable TeamCityEventListener immediately, before the console is redirected - _extensionService.EnableExtension("NUnit.Engine.Listeners.TeamCityEventListener", _options.TeamCity); } + private bool RunningUnderTeamCity => + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TEAMCITY_PROJECT_NAME")); + + /// /// Executes tests according to the provided commandline options. /// diff --git a/src/NUnitConsole/nunit3-console/Options/DefaultOptionsProvider.cs b/src/NUnitConsole/nunit3-console/Options/DefaultOptionsProvider.cs deleted file mode 100644 index 33bdd1e16..000000000 --- a/src/NUnitConsole/nunit3-console/Options/DefaultOptionsProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -namespace NUnit.ConsoleRunner.Options -{ - using System; - - internal sealed class DefaultOptionsProvider : IDefaultOptionsProvider - { - private const string EnvironmentVariableTeamcityProjectName = "TEAMCITY_PROJECT_NAME"; - - public bool TeamCity - { - get - { - return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnvironmentVariableTeamcityProjectName)); - } - } - } -} \ No newline at end of file diff --git a/src/NUnitConsole/nunit3-console/Options/IDefaultOptionsProvider.cs b/src/NUnitConsole/nunit3-console/Options/IDefaultOptionsProvider.cs deleted file mode 100644 index 543378afc..000000000 --- a/src/NUnitConsole/nunit3-console/Options/IDefaultOptionsProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -namespace NUnit.ConsoleRunner.Options -{ - internal interface IDefaultOptionsProvider - { - bool TeamCity { get; } - } -} \ No newline at end of file diff --git a/src/NUnitConsole/nunit3-console/Program.cs b/src/NUnitConsole/nunit3-console/Program.cs index 02a5ad179..7c4443583 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -22,7 +22,7 @@ namespace NUnit.ConsoleRunner public class Program { //static Logger log = InternalTrace.GetLogger(typeof(Runner)); - static readonly ConsoleOptions Options = new ConsoleOptions(new DefaultOptionsProvider(), new FileSystem()); + static readonly ConsoleOptions Options = new ConsoleOptions(new FileSystem()); private static ExtendedTextWriter _outWriter; // This has to be lazy otherwise NoColor command line option is not applied correctly From f9fd4a766718a51e5e0c132071bd620e2aa62f26 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 23 Feb 2025 08:59:25 -0800 Subject: [PATCH 147/190] Allow enabling and disabling extensions --- package-tests.cake | 17 ++++++-- .../nunit3-console.tests/CommandLineTests.cs | 43 +++++++++---------- .../ConsoleRunnerTests.cs | 11 ++++- .../nunit3-console/ConsoleOptions.cs | 33 +++++++++++--- .../nunit3-console/ConsoleRunner.cs | 39 ++++++++++++----- .../nunit3-console/ConsoleTests.nunit | 9 ++++ .../RequiredExtensionException.cs | 22 ++-------- .../nunit3-console/nunit3-console.csproj | 7 +++ 8 files changed, 118 insertions(+), 63 deletions(-) create mode 100644 src/NUnitConsole/nunit3-console/ConsoleTests.nunit diff --git a/package-tests.cake b/package-tests.cake index 6dc4d8a1f..7bfac7afa 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -319,12 +319,23 @@ public static class PackageTests }); // TeamCity Event Listener Test - StandardAndZipLists.Add(new PackageTest(1, "TeamCityListenerTest") + StandardAndZipLists.Add(new PackageTest(1, "TeamCityListenerTest1") { - Description = "Run mock-assembly with --teamcity enabled", + Description = "Run mock-assembly with --teamcity option", Arguments = "testdata/net462/mock-assembly.dll --teamcity", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), - ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener } + ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, + OutputCheck = new OutputContains("##teamcity") + }); + + // TeamCity Event Listener Test + StandardAndZipLists.Add(new PackageTest(1, "TeamCityListenerTest2") + { + Description = "Run mock-assembly with --enable teamcity option", + Arguments = "testdata/net462/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener", + ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), + ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, + OutputCheck = new OutputContains("##teamcity") }); // V2 Framework Driver Tests diff --git a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs index 7b9770b39..caf100636 100644 --- a/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs @@ -142,7 +142,6 @@ public void NoInputFiles() [TestCase("WaitBeforeExit", "wait")] [TestCase("NoHeader", "noheader|noh")] [TestCase("DisposeRunners", "dispose-runners")] - [TestCase("TeamCity", "teamcity")] [TestCase("SkipNonTestAssemblies", "skipnontestassemblies")] [TestCase("NoResult", "noresult")] #if NETFRAMEWORK @@ -289,6 +288,9 @@ public void CanRecognizeIntOptions(string propertyName, string pattern) [TestCase("--test-name-format")] [TestCase("--params")] [TestCase("--encoding")] + [TestCase("--extensionDirectory")] + [TestCase("--enable")] + [TestCase("--disable")] #if NETFRAMEWORK [TestCase("--process")] [TestCase("--domain")] @@ -595,26 +597,6 @@ public void ExploreOptionWithFilePathUsingEqualSign() Assert.That(options.ExploreOutputSpecifications[0].OutputPath, Is.EqualTo("C:/nunit/tests/bin/Debug/console-test.xml")); } - [TestCase(true, true)] - [TestCase(false, false)] - public void ShouldSetTeamCityFlagAccordingToArgsAndDefaults(bool hasTeamcityInCmd, bool expectedTeamCity) - { - // Given - List args = new List { "tests.dll" }; - if (hasTeamcityInCmd) - { - args.Add("--teamcity"); - } - - ConsoleOptions options = ConsoleMocks.Options(args.ToArray()); - - // When - var actualTeamCity = options.TeamCity; - - // Then - Assert.That(expectedTeamCity, Is.EqualTo(actualTeamCity)); - } - [Test] public void ShouldNotFailOnEmptyLine() { @@ -829,6 +811,7 @@ public void DeprecatedLabelsOptionsAreReplacedCorrectly(string oldOption, string Assert.That(options.DisplayTestLabels, Is.EqualTo(newOption)); } + [Test] public void UserExtensionDirectoryTest() { ConsoleOptions options = ConsoleMocks.Options("--extensionDirectory=/a/b/c"); @@ -836,6 +819,22 @@ public void UserExtensionDirectoryTest() Assert.That(options.ExtensionDirectories.Contains("/a/b/c")); } + [Test] + public void EnableExtensionTest() + { + ConsoleOptions options = ConsoleMocks.Options("--enable=NUnit.Engine.Listeners.TeamCityEventListener"); + Assert.That(options.Validate); + Assert.That(options.EnableExtensions.Contains("NUnit.Engine.Listeners.TeamCityEventListener")); + } + + [Test] + public void DisableExtensionTest() + { + ConsoleOptions options = ConsoleMocks.Options("--disable=NUnit.Engine.Listeners.TeamCityEventListener"); + Assert.That(options.Validate); + Assert.That(options.DisableExtensions.Contains("NUnit.Engine.Listeners.TeamCityEventListener")); + } + private static IFileSystem GetFileSystemContainingFile(string fileName) { var fileSystem = new VirtualFileSystem(); @@ -853,7 +852,7 @@ private static FieldInfo GetFieldInfo(string fieldName) private static PropertyInfo GetPropertyInfo(string propertyName) { PropertyInfo property = typeof(ConsoleOptions).GetProperty(propertyName); - Assert.That(property, Is.Not.Null, "The property '{0}' is not defined", propertyName); + Assert.That(property, Is.Not.Null, $"The property '{propertyName}' is not defined"); return property; } } diff --git a/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs b/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs index 17f038de4..7c45fa7ee 100644 --- a/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs +++ b/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs @@ -44,7 +44,16 @@ public void ThrowsRequiredExtensionExceptionWhenTeamcityOptionIsSpecifiedButNotA var ex = Assert.Throws( () => new ConsoleRunner(_testEngine, ConsoleMocks.Options("mock-assembly.dll", "--teamcity"), new ColorConsoleWriter())); - Assert.That(ex, Has.Message.Contains("teamcity")); + Assert.That(ex, Has.Message.EqualTo("Required extension 'NUnit.Engine.Listeners.TeamCityEventListener' is not installed.")); + } + + [Test] + public void ThrowsRequiredExtensionExceptionWhenEnableOptionSpecifiesUnavailableExtension() + { + var ex = Assert.Throws( + () => new ConsoleRunner(_testEngine, ConsoleMocks.Options("mock-assembly.dll", "--enable:Not.An.Extension"), new ColorConsoleWriter())); + + Assert.That(ex, Has.Message.EqualTo("Required extension 'Not.An.Extension' is not installed.")); } } diff --git a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs index 648e76404..7875a7eb6 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleOptions.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs @@ -47,6 +47,9 @@ internal ConsoleOptions(IFileSystem fileSystem, params string[] args) public bool ListExtensions { get; private set; } + public List EnableExtensions { get; private set; } = new List(); + public List DisableExtensions { get; private set; } = new List(); + // Additional directories to be used to search for user extensions public List ExtensionDirectories { get; } = new List(); @@ -83,8 +86,6 @@ internal ConsoleOptions(IFileSystem fileSystem, params string[] args) public bool NoColor { get; private set; } - public bool TeamCity { get; private set; } - public string OutFile { get; private set; } public bool OutFileSpecified { get { return OutFile != null; } } @@ -301,9 +302,6 @@ private void ConfigureOptions() this.Add("trace=", "Set internal trace {LEVEL}.\nValues: Off, Error, Warning, Info, Verbose (Debug)", v => InternalTraceLevel = parser.RequiredValue(v, "--trace", "Off", "Error", "Warning", "Info", "Verbose", "Debug")); - this.Add("teamcity", "Turns on use of TeamCity service messages. TeamCity engine extension is required.", - v => TeamCity = v != null); - this.Add("noheader|noh", "Suppress display of program information at start of run.", v => NoHeader = v != null); @@ -381,8 +379,29 @@ private void ConfigureOptions() this.Add("list-extensions", "List all extension points and the extensions for each.", v => ListExtensions = v != null); - this.Add("extensionDirectory=", "Specifies an additional directory to be examined for extensions. May be repeated.", - v => { ExtensionDirectories.Add(Path.GetFullPath(v)); }); + this.Add("extensionDirectory=", "Specifies an additional directory to be examined for extensions. May be repeated.", v => + { + string dir = parser.RequiredValue(v, "--extensionDirectory"); + if (dir != null) + ExtensionDirectories.Add(dir); + }); + + this.Add("teamcity", "Turns on use of TeamCity service messages. TeamCity engine extension is required.", + v => EnableExtensions.Add("NUnit.Engine.Listeners.TeamCityEventListener")); //TeamCity = v != null); + + this.Add("enable=", "Enables the specified extension. May be repeated.", v => + { + string extension = parser.RequiredValue(v, "--enable"); + if (!string.IsNullOrEmpty(extension)) + EnableExtensions.Add(extension); + }); + + this.Add("disable=", "Disables the specified extension. May be repeated.", v => + { + string extension = parser.RequiredValue(v, "--disable"); + if (!string.IsNullOrEmpty(extension)) + DisableExtensions.Add(extension); + }); this.AddNetFxOnlyOption("set-principal-policy=", "Set PrincipalPolicy for the test domain.", NetFxOnlyOption("set-principal-policy=", v => PrincipalPolicy = parser.RequiredValue(v, "--set-principal-policy", "UnauthenticatedPrincipal", "NoPrincipal", "WindowsPrincipal"))); diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index 01a72bdd5..0756d5fae 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -73,7 +73,7 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri _extensionService.FindExtensionAssemblies(extensionDirectory); foreach (string extensionDirectory in _options.ExtensionDirectories) - _extensionService.FindExtensionAssemblies(extensionDirectory); + _extensionService.FindExtensionAssemblies(extensionDirectory); _workDirectory = options.WorkDirectory; if (_workDirectory != null) @@ -81,24 +81,41 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri else _workDirectory = null; - if (_options.TeamCity || RunningUnderTeamCity) + // Attempt to enable extensions as requested by the user + foreach (string typeName in options.EnableExtensions) { - bool teamcityInstalled = false; - foreach (var node in _extensionService.GetExtensionNodes(EVENT_LISTENER_EXTENSION_PATH)) - if (teamcityInstalled = node.TypeName == TEAMCITY_EVENT_LISTENER_FULLNAME) - break; + // Throw if requested extension is not installed + if (!IsExtensionInstalled(typeName)) + throw new RequiredExtensionException(typeName); - if (teamcityInstalled) - // Enable TeamCityEventListener immediately, before the console is redirected - _extensionService.EnableExtension("NUnit.Engine.Listeners.TeamCityEventListener", true); - else if (_options.TeamCity) - throw new RequiredExtensionException(TEAMCITY_EVENT_LISTENER_NAME, "--teamcity"); + EnableExtension(typeName); } + + // Also enable TeamCity extension under TeamCity, if it is installed + if (RunningUnderTeamCity && IsExtensionInstalled(TEAMCITY_EVENT_LISTENER_FULLNAME)) + EnableExtension(TEAMCITY_EVENT_LISTENER_FULLNAME); + + // Disable extensions as requested by the user, ignoring any not installed + foreach (string typeName in options.DisableExtensions) + if (IsExtensionInstalled(typeName)) + DisableExtension(typeName); } private bool RunningUnderTeamCity => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TEAMCITY_PROJECT_NAME")); + private bool IsExtensionInstalled(string typeName) + { + foreach (var node in _extensionService.Extensions) + if (node.TypeName == typeName) return true; + + return false; + } + + private void EnableExtension(string name) => _extensionService.EnableExtension(name, true); + + private void DisableExtension(string name) => _extensionService.EnableExtension(name, false); + /// /// Executes tests according to the provided commandline options. diff --git a/src/NUnitConsole/nunit3-console/ConsoleTests.nunit b/src/NUnitConsole/nunit3-console/ConsoleTests.nunit new file mode 100644 index 000000000..be1fa8efe --- /dev/null +++ b/src/NUnitConsole/nunit3-console/ConsoleTests.nunit @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs b/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs index 38de0082a..d60873d2e 100644 --- a/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs +++ b/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs @@ -11,20 +11,12 @@ namespace NUnit.ConsoleRunner /// public class RequiredExtensionException : Exception { - private static string Message1(string extensionName) => $"Required extension {extensionName} is not installed."; - private static string Message2(string extensionName, string option) => $"Option {option} specified but {extensionName} is not installed."; + private static string BuildMessage(string extensionName) => $"Required extension '{extensionName}' is not installed."; /// /// Construct with the name of an extension /// - public RequiredExtensionException(string extensionName) : base(Message1(extensionName)) - { - } - - /// - /// Construct with the name of an extension and the command-line option requiring that extension. - /// - public RequiredExtensionException(string extensionName, string option) : base(Message2(extensionName, option)) + public RequiredExtensionException(string extensionName) : base(BuildMessage(extensionName)) { } @@ -32,15 +24,7 @@ public RequiredExtensionException(string extensionName, string option) : base(Me /// Construct with the name of an extension and inner exception /// public RequiredExtensionException(string extensionName, Exception innerException) - : base(Message1(extensionName), innerException) - { - } - - /// - /// Construct with the name of an extension, a command-line option and inner exception - /// - public RequiredExtensionException(string extensionName, string option, Exception innerException) - : base(Message2(extensionName, option), innerException) + : base(BuildMessage(extensionName), innerException) { } } diff --git a/src/NUnitConsole/nunit3-console/nunit3-console.csproj b/src/NUnitConsole/nunit3-console/nunit3-console.csproj index feef472f2..29eabe010 100644 --- a/src/NUnitConsole/nunit3-console/nunit3-console.csproj +++ b/src/NUnitConsole/nunit3-console/nunit3-console.csproj @@ -26,10 +26,17 @@ ..\..\..\nunit.ico + + + + + + PreserveNewest + From cb576a8e3c50a6a0009218658499a27f0ef18052 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 11 Mar 2025 16:38:44 -0700 Subject: [PATCH 148/190] Update recipe --- build.cake | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.cake b/build.cake index f56815e76..273848e3c 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.1 +#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.5 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -147,8 +147,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { }, symbols: new PackageCheck[] { HasDirectory("lib/net462").WithFiles(ENGINE_PDB_FILES), - HasDirectory("lib/netstandard2.0").WithFiles(ENGINE_PDB_FILES), - HasDirectory("lib/net6.0").WithFiles(ENGINE_PDB_FILES), HasDirectory("lib/net8.0").WithFiles(ENGINE_PDB_FILES), HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_PDB_FILES) }), From c5be118adb0ba867d99fc77fcdaee94a2b76b635 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 11 Mar 2025 17:20:54 -0700 Subject: [PATCH 149/190] WIP --- build.cake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.cake b/build.cake index 273848e3c..ec5fe070e 100644 --- a/build.cake +++ b/build.cake @@ -1,7 +1,7 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.5 +//#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.3 // Comment out above line and uncomment below for local tests of recipe changes -//#load ../NUnit.Cake.Recipe/recipe/*.cake +#load ../NUnit.Cake.Recipe/recipe/*.cake #load package-tests.cake @@ -117,7 +117,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("NUnit.Extension.NUnitProjectLoader.3.8.0"), HasDirectory("NUnit.Extension.NUnitV2Driver.3.9.0"), HasDirectory("NUnit.Extension.NUnitV2ResultWriter.3.8.0"), - HasDirectory("NUnit.Extension.TeamCityEventListener.1.0.7"), + HasDirectory("NUnit.Extension.TeamCityEventListener.1.0.9"), HasDirectory("NUnit.Extension.VSProjectLoader.3.9.0"), HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), From 33c3454a246b68b0c2c35050f77edb714ffbe233 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 11 Mar 2025 19:12:12 -0700 Subject: [PATCH 150/190] Update recipe and use teamcity listener 1.0.9 --- build.cake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.cake b/build.cake index ec5fe070e..c092549c0 100644 --- a/build.cake +++ b/build.cake @@ -1,7 +1,7 @@ // Load the recipe -//#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.3 +#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.6 // Comment out above line and uncomment below for local tests of recipe changes -#load ../NUnit.Cake.Recipe/recipe/*.cake +//#load ../NUnit.Cake.Recipe/recipe/*.cake #load package-tests.cake From 374a31a610d72f9b6d74dd70201270e16c13d6ce Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 12 Mar 2025 08:41:56 -0700 Subject: [PATCH 151/190] Add tests of teamcity extension under .NET Core --- package-tests.cake | 79 ++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/package-tests.cake b/package-tests.cake index 7bfac7afa..18c9bf969 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -26,35 +26,35 @@ public static class PackageTests StandardAndZipLists.Add(new PackageTest(1, "Net462Test") { - Description="Run mock-assembly.dll under .NET 4.6.2", + Description= "Run mock-assembly.dll targeting .NET 4.6.2", Arguments="testdata/net462/mock-assembly.dll", ExpectedResult=new MockAssemblyExpectedResult("net-4.6.2") }); AllLists.Add(new PackageTest(1, "Net80Test") { - Description = "Run mock-assembly.dll under .NET 8.0", + Description = "Run mock-assembly.dll targeting .NET 8.0", Arguments = "testdata/net8.0/mock-assembly.dll", ExpectedResult = new MockAssemblyExpectedResult("netcore-8.0") }); AllLists.Add(new PackageTest(1, "Net70Test") { - Description = "Run mock-assembly.dll under .NET 7.0", + Description = "Run mock-assembly.dll targeting .NET 7.0", Arguments = "testdata/net7.0/mock-assembly.dll", ExpectedResult = new MockAssemblyExpectedResult("netcore-7.0") }); AllLists.Add(new PackageTest(1, "Net60Test") { - Description = "Run mock-assembly.dll under .NET 6.0", + Description = "Run mock-assembly.dll targeting .NET 6.0", Arguments = "testdata/net6.0/mock-assembly.dll", ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0") }); AllLists.Add(new PackageTest(1, "NetCore31Test") { - Description = "Run mock-assembly.dll under .NET Core 3.1", + Description = "Run mock-assembly.dll targeting .NET Core 3.1", Arguments = "testdata/netcoreapp3.1/mock-assembly.dll", ExpectedResult = new MockAssemblyExpectedResult("netcore-3.1") }); @@ -70,7 +70,7 @@ public static class PackageTests // X86 is always available for .NET Framework StandardAndZipLists.Add(new PackageTest(1, "Net462X86Test") { - Description = "Run mock-assembly-x86.dll under .NET 4.6.2", + Description = "Run mock-assembly-x86.dll targeting .NET 4.6.2", Arguments = "testdata/net462/mock-assembly-x86.dll", ExpectedResult = new MockAssemblyX86ExpectedResult("net-4.6.2") }); @@ -82,7 +82,7 @@ public static class PackageTests StandardAndZipLists.Add(new PackageTest(1, "Net80X86Test") { - Description = "Run mock-assembly-x86.dll under .NET 8.0", + Description = "Run mock-assembly-x86.dll targeting .NET 8.0", Arguments = "testdata/net8.0/mock-assembly-x86.dll", ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-8.0") }); @@ -90,14 +90,14 @@ public static class PackageTests if (!onGitHubActions) StandardAndZipLists.Add(new PackageTest(1, "Net70X86Test") { - Description = "Run mock-assembly-x86.dll under .NET 7.0", + Description = "Run mock-assembly-x86.dll targeting .NET 7.0", Arguments = "testdata/net7.0/mock-assembly-x86.dll", ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-7.0") }); StandardAndZipLists.Add(new PackageTest(1, "Net60X86Test") { - Description = "Run mock-assembly-x86.dll under .NET 6.0", + Description = "Run mock-assembly-x86.dll targeting .NET 6.0", Arguments = "testdata/net6.0/mock-assembly-x86.dll", ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-6.0") }); @@ -105,7 +105,7 @@ public static class PackageTests if (!onGitHubActions) StandardAndZipLists.Add(new PackageTest(1, "NetCore31X86Test") { - Description = "Run mock-assembly-x86.dll under .NET Core 3.1", + Description = "Run mock-assembly-x86.dll targeting .NET Core 3.1", Arguments = "testdata/netcoreapp3.1/mock-assembly-x86.dll", ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-3.1") }); @@ -124,14 +124,14 @@ public static class PackageTests StandardAndZipLists.Add(new PackageTest(1, "Net60PlusNet80Test") { - Description = "Run mock-assembly under .NET6.0 and 8.0 together", + Description = "Run mock-assembly targeting .NET6.0 and 8.0 together", Arguments = "testdata/net6.0/mock-assembly.dll testdata/net8.0/mock-assembly.dll", ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0") }); StandardAndZipLists.Add(new PackageTest(1, "Net462PlusNet60Test") { - Description = "Run mock-assembly under .Net Framework 4.6.2 and .Net 6.0 together", + Description = "Run mock-assembly targeting .Net Framework 4.6.2 and .Net 6.0 together", Arguments = "testdata/net462/mock-assembly.dll testdata/net6.0/mock-assembly.dll", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0") }); @@ -178,7 +178,7 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "Net60WindowsFormsTest") { - Description = "Run test using windows forms under .NET 6.0", + Description = "Run test using windows forms targeting .NET 6.0", Arguments = "testdata/net6.0-windows/windows-forms-test.dll", ExpectedResult = new ExpectedResult("Passed") { @@ -194,7 +194,7 @@ public static class PackageTests StandardAndZipLists.Add(new PackageTest(1, "Net80WindowsFormsTest") { - Description = "Run test using windows forms under .NET 8.0", + Description = "Run test using windows forms targeting .NET 8.0", Arguments = "testdata/net8.0-windows/windows-forms-test.dll", ExpectedResult = new ExpectedResult("Passed") { @@ -214,14 +214,14 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "Net60WPFTest") { - Description = "Run test using WPF under .NET 6.0", + Description = "Run test using WPF targeting .NET 6.0", Arguments = "testdata/net6.0-windows/WpfTest.dll --trace=Debug", ExpectedResult = new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-6.0") } } }); AllLists.Add(new PackageTest(1, "Net80WPFTest") { - Description = "Run test using WPF under .NET 8.0", + Description = "Run test using WPF targeting .NET 8.0", Arguments = "testdata/net8.0-windows/WpfTest.dll --trace=Debug", ExpectedResult = new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-8.0") } } }); @@ -258,7 +258,7 @@ public static class PackageTests // NUnit Project Loader Tests StandardAndZipLists.Add(new PackageTest(1, "NUnitProjectTest") { - Description = "Run NUnit project with mock-assembly.dll built for .NET 4.6.2 and 6.0", + Description = "Run NUnit project with mock-assembly.dll targeting .NET 4.6.2 and 6.0", Arguments = "../../MixedTests.nunit --config=Release", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2", "net-6.0"), ExtensionsNeeded = new [] { KnownExtensions.NUnitProjectLoader } @@ -266,7 +266,7 @@ public static class PackageTests NetCoreRunnerTests.Add(new PackageTest(1, "NUnitProjectTest") { - Description="Run NUnit project with mock-assembly.dll built for .NET 6.0 and 8.0", + Description= "Run NUnit project with mock-assembly.dll targeting .NET 6.0 and 8.0", Arguments="../../NetCoreTests.nunit --config=Release", ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0"), ExtensionsNeeded = new [] { KnownExtensions.NUnitProjectLoader } @@ -275,7 +275,7 @@ public static class PackageTests // V2 Result Writer Test AllLists.Add(new PackageTest(1, "V2ResultWriterTest") { - Description = "Run mock-assembly under .NET 6.0 and produce V2 output", + Description = "Run mock-assembly targeting .NET 6.0 and produce V2 output", Arguments = "testdata/net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0"), ExtensionsNeeded = new[] { KnownExtensions.NUnitV2ResultWriter } @@ -318,26 +318,45 @@ public static class PackageTests ExtensionsNeeded = new[] { KnownExtensions.VSProjectLoader } }); - // TeamCity Event Listener Test - StandardAndZipLists.Add(new PackageTest(1, "TeamCityListenerTest1") + // TeamCity Event Listener Tests + StandardAndZipLists.Add(new PackageTest(1, "Net462TeamCityListenerTest1") { - Description = "Run mock-assembly with --teamcity option", - Arguments = "testdata/net462/mock-assembly.dll --teamcity", + Description = "Run mock-assembly targeting .NET 4.6.2 with --teamcity option", + Arguments = "testdata/net462/mock-assembly.dll --teamcity --trace:Debug", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") }); // TeamCity Event Listener Test - StandardAndZipLists.Add(new PackageTest(1, "TeamCityListenerTest2") + StandardAndZipLists.Add(new PackageTest(1, "Net462TeamCityListenerTest2") { - Description = "Run mock-assembly with --enable teamcity option", - Arguments = "testdata/net462/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener", + Description = "Run mock-assembly targeting .NET 4.6.2 with --enable teamcity option", + Arguments = "testdata/net462/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener --trace:Debug", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") }); + AllLists.Add(new PackageTest(1, "Net60TeamCityListenerTest1") + { + Description = "Run mock-assembly targeting .NET 6.0 with --teamcity option", + Arguments = "testdata/net6.0/mock-assembly.dll --teamcity --trace:Debug", + ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), + ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, + OutputCheck = new OutputContains("##teamcity") + }); + + // TeamCity Event Listener Test + AllLists.Add(new PackageTest(1, "Net60TeamCityListenerTest2") + { + Description = "Run mock-assembly targeting .NET 6.0 with --enable teamcity option", + Arguments = "testdata/net6.0/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener --trace:Debug", + ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), + ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, + OutputCheck = new OutputContains("##teamcity") + }); + // V2 Framework Driver Tests StandardAndZipLists.Add(new PackageTest(1, "V2FrameworkDriverTest") { @@ -379,7 +398,7 @@ public static class PackageTests StandardAndZipLists.Add(new PackageTest(1, "InvalidTestNameTest_Net462") { - Description = "Ensure we handle invalid test names correctly under .NET 4.6.2", + Description = "Ensure we handle invalid test names correctly targeting .NET 4.6.2", Arguments = "testdata/net462/InvalidTestNames.dll --trace:Debug", ExpectedResult = new ExpectedResult("Passed") { @@ -389,7 +408,7 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "InvalidTestNameTest_Net60") { - Description = "Ensure we handle invalid test names correctly under .NET 6.0", + Description = "Ensure we handle invalid test names correctly targeting .NET 6.0", Arguments = "testdata/net6.0/InvalidTestNames.dll --trace:Debug", ExpectedResult = new ExpectedResult("Passed") { @@ -399,7 +418,7 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "InvalidTestNameTest_Net80") { - Description = "Ensure we handle invalid test names correctly under .NET 8.0", + Description = "Ensure we handle invalid test names correctly targeting .NET 8.0", Arguments = "testdata/net8.0/InvalidTestNames.dll --trace:Debug", ExpectedResult = new ExpectedResult("Passed") { @@ -409,7 +428,7 @@ public static class PackageTests StandardAndZipLists.Add(new PackageTest(1, "AppContextBaseDirectory_NET80") { - Description = "Test Setting the BaseDirectory to match test assembly location under .NET 8.0", + Description = "Test Setting the BaseDirectory to match test assembly location targeting .NET 8.0", Arguments = "testdata/net8.0/AppContextTest.dll", ExpectedResult = new ExpectedResult("Passed") { From cb0e620fe795181ce8909f3f508dbce938af28f0 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 12 Mar 2025 12:40:31 -0700 Subject: [PATCH 152/190] Update recipe --- build.cake | 12 ++++++------ package-tests.cake | 37 ++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/build.cake b/build.cake index c092549c0..f6b3b027a 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.6 +#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.7 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -127,11 +127,11 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), tests: PackageTests.ZipRunnerTests, bundledExtensions: new [] { - KnownExtensions.VSProjectLoader.NuGetPackage, - KnownExtensions.NUnitProjectLoader.NuGetPackage, - KnownExtensions.NUnitV2Driver.NuGetPackage, - KnownExtensions.NUnitV2ResultWriter.NuGetPackage, - KnownExtensions.TeamCityEventListener.NuGetPackage + Extensions.VSProjectLoader.NuGetPackage, + Extensions.NUnitProjectLoader.NuGetPackage, + Extensions.NUnitV2Driver.NuGetPackage, + Extensions.NUnitV2ResultWriter.NuGetPackage, + Extensions.TeamCityEventListener.NuGetPackage }), // NOTE: Packages below this point have no direct tests diff --git a/package-tests.cake b/package-tests.cake index 18c9bf969..7a1c97643 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -1,3 +1,18 @@ +public static class Extensions +{ + // Extensions used in tests, with version specified. + public static ExtensionSpecifier NUnitV2Driver = new ExtensionSpecifier( + "NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver", "3.9.0"); + public static ExtensionSpecifier NUnitProjectLoader = new ExtensionSpecifier( + "NUnit.Extension.NUnitProjectLoader", "nunit-extension-nunit-project-loader", "3.8.0"); + public static ExtensionSpecifier VSProjectLoader = new ExtensionSpecifier( + "NUnit.Extension.VSProjectLoader", "nunit-extension-vs-project-loader", "3.9.0"); + public static ExtensionSpecifier NUnitV2ResultWriter = new ExtensionSpecifier( + "NUnit.Extension.NUnitV2ResultWriter", "nunit-extension-nunit-v2-result-writer", "3.8.0"); + public static ExtensionSpecifier TeamCityEventListener = new ExtensionSpecifier( + "NUnit.Extension.TeamCityEventListener", "nunit-extension-teamcity-event-listener", "1.0.9"); +} + public static class PackageTests { // Tests run for the Standard runner packages (both nuget and chocolatey) @@ -261,7 +276,7 @@ public static class PackageTests Description = "Run NUnit project with mock-assembly.dll targeting .NET 4.6.2 and 6.0", Arguments = "../../MixedTests.nunit --config=Release", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2", "net-6.0"), - ExtensionsNeeded = new [] { KnownExtensions.NUnitProjectLoader } + ExtensionsNeeded = new [] { Extensions.NUnitProjectLoader } }); NetCoreRunnerTests.Add(new PackageTest(1, "NUnitProjectTest") @@ -269,7 +284,7 @@ public static class PackageTests Description= "Run NUnit project with mock-assembly.dll targeting .NET 6.0 and 8.0", Arguments="../../NetCoreTests.nunit --config=Release", ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0"), - ExtensionsNeeded = new [] { KnownExtensions.NUnitProjectLoader } + ExtensionsNeeded = new [] { Extensions.NUnitProjectLoader } }); // V2 Result Writer Test @@ -278,7 +293,7 @@ public static class PackageTests Description = "Run mock-assembly targeting .NET 6.0 and produce V2 output", Arguments = "testdata/net6.0/mock-assembly.dll --result=TestResult.xml --result=NUnit2TestResult.xml;format=nunit2", ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0"), - ExtensionsNeeded = new[] { KnownExtensions.NUnitV2ResultWriter } + ExtensionsNeeded = new[] { Extensions.NUnitV2ResultWriter } }); // VS Project Loader Tests @@ -287,7 +302,7 @@ public static class PackageTests Description = "Run mock-assembly using the .csproj file", Arguments = "../../src/TestData/mock-assembly/mock-assembly.csproj --config=Release", ExpectedResult = new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0"), - ExtensionsNeeded = new[] { KnownExtensions.VSProjectLoader } + ExtensionsNeeded = new[] { Extensions.VSProjectLoader } }); StandardAndZipLists.Add(new PackageTest(1, "VSProjectLoaderTest_Solution") @@ -315,7 +330,7 @@ public static class PackageTests new ExpectedAssemblyResult("WpfApp.exe") } }, - ExtensionsNeeded = new[] { KnownExtensions.VSProjectLoader } + ExtensionsNeeded = new[] { Extensions.VSProjectLoader } }); // TeamCity Event Listener Tests @@ -324,7 +339,7 @@ public static class PackageTests Description = "Run mock-assembly targeting .NET 4.6.2 with --teamcity option", Arguments = "testdata/net462/mock-assembly.dll --teamcity --trace:Debug", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), - ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, + ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") }); @@ -334,7 +349,7 @@ public static class PackageTests Description = "Run mock-assembly targeting .NET 4.6.2 with --enable teamcity option", Arguments = "testdata/net462/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener --trace:Debug", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), - ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, + ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") }); @@ -343,7 +358,7 @@ public static class PackageTests Description = "Run mock-assembly targeting .NET 6.0 with --teamcity option", Arguments = "testdata/net6.0/mock-assembly.dll --teamcity --trace:Debug", ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), - ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, + ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") }); @@ -353,7 +368,7 @@ public static class PackageTests Description = "Run mock-assembly targeting .NET 6.0 with --enable teamcity option", Arguments = "testdata/net6.0/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener --trace:Debug", ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), - ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }, + ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") }); @@ -372,7 +387,7 @@ public static class PackageTests Skipped = 4, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } }, - ExtensionsNeeded = new[] { KnownExtensions.NUnitV2Driver } + ExtensionsNeeded = new[] { Extensions.NUnitV2Driver } }); StandardAndZipLists.Add(new PackageTest(1, "V2FrameworkDriverTest") @@ -389,7 +404,7 @@ public static class PackageTests Skipped = 4, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly-v2.dll", "net-4.6.2") } }, - ExtensionsNeeded = new[] { KnownExtensions.NUnitV2Driver } + ExtensionsNeeded = new[] { Extensions.NUnitV2Driver } }); ////////////////////////////////////////////////////////////////////// From 80c00c1d9e37ee61480bc2d0ee9b2ea2dff222a2 Mon Sep 17 00:00:00 2001 From: Mikkel Bundgaard Date: Wed, 12 Mar 2025 22:16:43 +0000 Subject: [PATCH 153/190] feat: Add .NET9 agent --- build.cake | 10 +++++++--- choco/nunit-console-runner.nuspec | 12 ++++++++++++ global.json | 2 +- nuget/runners/nunit.console-runner.nuspec | 13 +++++++++++++ src/NUnitEngine/nunit-agent/nunit-agent.csproj | 2 +- .../nunit.engine/Services/AgentProcess.cs | 2 +- 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/build.cake b/build.cake index f6b3b027a..b27eca365 100644 --- a/build.cake +++ b/build.cake @@ -64,13 +64,15 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools").WithFiles("nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE) + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE) }, symbols: new PackageCheck[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), HasDirectory("tools/agents/net462").WithFiles(AGENT_PDB_FILES), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_PDB_FILES_NETCORE) + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("tools/agents/net9.0").WithFiles(AGENT_PDB_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + $"NUnit.ConsoleRunner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), @@ -101,7 +103,8 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt", "nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), - HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE) + HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ChocolateyTestDirectory + $"nunit-console-runner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), @@ -122,6 +125,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("bin/agents/net9.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 2090d1ad0..0f2f8a30d 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -75,5 +75,17 @@ + + + + + + + + + + + + diff --git a/global.json b/global.json index 7eb9bc5b3..dcc8f8f12 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "9.0.100", "rollForward": "feature" } } \ No newline at end of file diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index ab7a0ccfd..7e637f6af 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -66,6 +66,19 @@ + + + + + + + + + + + + + diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index 1289644ee..7b9c302f6 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -3,7 +3,7 @@ Exe nunit.agent - net462;net6.0;net8.0 + net462;net6.0;net8.0;net9.0 app.manifest ..\..\..\nunit.ico false diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index e84555837..e9bf77464 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -127,7 +127,7 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re agentExtension = ".exe"; break; case RuntimeType.NetCore: - runtimeDir = major >= 7 ? "net8.0" : "net6.0"; + runtimeDir = major >= 9 ? "net9.0" : major >= 7 ? "net8.0" : "net6.0"; agentName = "nunit-agent"; agentExtension = ".dll"; break; From 39fd03ecbf2d21ce23c15871d57e84cdb23305fe Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 12 Mar 2025 23:58:19 -0700 Subject: [PATCH 154/190] Enable continued use of binaryformatter under .net 9.0 and add a package test --- GitVersion.yml | 2 +- build.cake | 5 ++- choco/nunit-console-runner.nuspec | 1 + nuget/runners/nunit.console-runner.nuspec | 1 + package-tests.cake | 42 +++++++++++-------- .../nunit.engine.core.tests.csproj | 2 +- .../nunit.engine.core.csproj | 8 +++- .../mock-assembly-x86.csproj | 2 +- .../mock-assembly/mock-assembly.csproj | 2 +- 9 files changed, 40 insertions(+), 25 deletions(-) diff --git a/GitVersion.yml b/GitVersion.yml index e4a74f1aa..acdf7df63 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 3.19.0 +next-version: 3.20.0 mode: ContinuousDelivery legacy-semver-padding: 5 build-metadata-padding: 5 diff --git a/build.cake b/build.cake index b27eca365..70695513f 100644 --- a/build.cake +++ b/build.cake @@ -65,7 +65,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE), - HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE) + HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE).WithFile("System.Runtime.Serialization.Formatters.dll") }, symbols: new PackageCheck[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), @@ -125,7 +125,8 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("bin/agents/net9.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("bin/agents/net9.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) + .AndFile("System.Runtime.Serialization.Formatters.dll") }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 0f2f8a30d..dab777ccb 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -85,6 +85,7 @@ + diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 7e637f6af..85772b506 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -78,6 +78,7 @@ + diff --git a/package-tests.cake b/package-tests.cake index 7a1c97643..be6c014fb 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -46,6 +46,13 @@ public static class PackageTests ExpectedResult=new MockAssemblyExpectedResult("net-4.6.2") }); + AllLists.Add(new PackageTest(1, "Net90Test") + { + Description = "Run mock-assembly.dll targeting .NET 9.0", + Arguments = "testdata/net9.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-9.0") + }); + AllLists.Add(new PackageTest(1, "Net80Test") { Description = "Run mock-assembly.dll targeting .NET 8.0", @@ -230,14 +237,14 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "Net60WPFTest") { Description = "Run test using WPF targeting .NET 6.0", - Arguments = "testdata/net6.0-windows/WpfTest.dll --trace=Debug", + Arguments = "testdata/net6.0-windows/WpfTest.dll", ExpectedResult = new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-6.0") } } }); AllLists.Add(new PackageTest(1, "Net80WPFTest") { Description = "Run test using WPF targeting .NET 8.0", - Arguments = "testdata/net8.0-windows/WpfTest.dll --trace=Debug", + Arguments = "testdata/net8.0-windows/WpfTest.dll", ExpectedResult = new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-8.0") } } }); @@ -301,22 +308,22 @@ public static class PackageTests { Description = "Run mock-assembly using the .csproj file", Arguments = "../../src/TestData/mock-assembly/mock-assembly.csproj --config=Release", - ExpectedResult = new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0"), + ExpectedResult = new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0", "netcore-9.0"), ExtensionsNeeded = new[] { Extensions.VSProjectLoader } }); StandardAndZipLists.Add(new PackageTest(1, "VSProjectLoaderTest_Solution") { Description = "Run mock-assembly using the .sln file", - Arguments = "../../src/TestData/TestData.sln --config=Release --trace=Debug", + Arguments = "../../src/TestData/TestData.sln --config=Release", ExpectedResult = new ExpectedResult("Failed") { - Total = 37 * 5, - Passed = 23 * 5, - Failed = 5 * 5, - Warnings = 1 * 5, - Inconclusive = 1 * 5, - Skipped = 7 * 5, + Total = 37 * 6, + Passed = 23 * 6, + Failed = 5 * 6, + Warnings = 1 * 6, + Inconclusive = 1 * 6, + Skipped = 7 * 6, Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), @@ -324,6 +331,7 @@ public static class PackageTests new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), + new ExpectedAssemblyResult("mock-assembly.dll", "netcore-9.0"), new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0"), @@ -337,7 +345,7 @@ public static class PackageTests StandardAndZipLists.Add(new PackageTest(1, "Net462TeamCityListenerTest1") { Description = "Run mock-assembly targeting .NET 4.6.2 with --teamcity option", - Arguments = "testdata/net462/mock-assembly.dll --teamcity --trace:Debug", + Arguments = "testdata/net462/mock-assembly.dll --teamcity", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") @@ -347,7 +355,7 @@ public static class PackageTests StandardAndZipLists.Add(new PackageTest(1, "Net462TeamCityListenerTest2") { Description = "Run mock-assembly targeting .NET 4.6.2 with --enable teamcity option", - Arguments = "testdata/net462/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener --trace:Debug", + Arguments = "testdata/net462/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") @@ -356,7 +364,7 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "Net60TeamCityListenerTest1") { Description = "Run mock-assembly targeting .NET 6.0 with --teamcity option", - Arguments = "testdata/net6.0/mock-assembly.dll --teamcity --trace:Debug", + Arguments = "testdata/net6.0/mock-assembly.dll --teamcity", ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") @@ -366,7 +374,7 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "Net60TeamCityListenerTest2") { Description = "Run mock-assembly targeting .NET 6.0 with --enable teamcity option", - Arguments = "testdata/net6.0/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener --trace:Debug", + Arguments = "testdata/net6.0/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener", ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, OutputCheck = new OutputContains("##teamcity") @@ -414,7 +422,7 @@ public static class PackageTests StandardAndZipLists.Add(new PackageTest(1, "InvalidTestNameTest_Net462") { Description = "Ensure we handle invalid test names correctly targeting .NET 4.6.2", - Arguments = "testdata/net462/InvalidTestNames.dll --trace:Debug", + Arguments = "testdata/net462/InvalidTestNames.dll", ExpectedResult = new ExpectedResult("Passed") { Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("InvalidTestNames.dll", "net-4.6.2") } @@ -424,7 +432,7 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "InvalidTestNameTest_Net60") { Description = "Ensure we handle invalid test names correctly targeting .NET 6.0", - Arguments = "testdata/net6.0/InvalidTestNames.dll --trace:Debug", + Arguments = "testdata/net6.0/InvalidTestNames.dll", ExpectedResult = new ExpectedResult("Passed") { Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("InvalidTestNames.dll", "netcore-6.0") } @@ -434,7 +442,7 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "InvalidTestNameTest_Net80") { Description = "Ensure we handle invalid test names correctly targeting .NET 8.0", - Arguments = "testdata/net8.0/InvalidTestNames.dll --trace:Debug", + Arguments = "testdata/net8.0/InvalidTestNames.dll", ExpectedResult = new ExpectedResult("Passed") { Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("InvalidTestNames.dll", "netcore-8.0") } diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 5a6350858..a78b9c67c 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine.Core.Tests - net462;netcoreapp3.1;net6.0;net8.0 + net462;netcoreapp3.1;net6.0;net8.0;net9.0 Exe true ..\..\nunit.snk diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index ac5cc099a..7df5a6aa2 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net462;netcoreapp3.1;net6.0;net8.0 + net462;netcoreapp3.1;net6.0;net8.0;net9.0 $(NoWarn);SYSLIB0011;SYSLIB0012 true ..\..\nunit.snk @@ -27,11 +27,15 @@ - + + + + + diff --git a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj index 58003c7be..876fe1c72 100644 --- a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net462;netcoreapp3.1;net6.0;net7.0;net8.0 + net462;netcoreapp3.1;net6.0;net7.0;net8.0;net9.0 ..\..\..\bin\$(Configuration)\testdata\ true ..\..\nunit.snk diff --git a/src/TestData/mock-assembly/mock-assembly.csproj b/src/TestData/mock-assembly/mock-assembly.csproj index 4c7613a41..459ee644e 100644 --- a/src/TestData/mock-assembly/mock-assembly.csproj +++ b/src/TestData/mock-assembly/mock-assembly.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net462;netcoreapp3.1;net6.0;net7.0;net8.0 + net462;netcoreapp3.1;net6.0;net7.0;net8.0;net9.0 ..\..\..\bin\$(Configuration)\testdata\ true ..\..\nunit.snk From 143b430f15043d0e3c0dc7c37c874a5a5b20d945 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 13 Mar 2025 18:51:29 -0700 Subject: [PATCH 155/190] Modify ITestEngineResult interface splitting StopRun into two methods --- .../Runners/DirectTestRunnerTests.cs | 4 +- .../Communication/Messages/MessageCode.cs | 24 ++++++++++++ .../Remoting/TestAgentRemotingTransport.cs | 16 ++++---- .../nunit.engine.core/ITestEngineRunner.cs | 11 ++++-- .../Runners/AbstractTestRunner.cs | 12 ++++-- .../Runners/AggregatingTestRunner.cs | 18 +++++++-- .../Runners/DirectTestRunner.cs | 14 +++++-- .../Runners/NotRunnableTestRunner.cs | 14 +++++-- .../Transports/Tcp/TestAgentTcpProxy.cs | 7 ++-- .../nunit.engine/Runners/MasterTestRunner.cs | 8 ++-- .../nunit.engine/Runners/ProcessRunner.cs | 37 +++++++++++++------ 11 files changed, 121 insertions(+), 44 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/Runners/DirectTestRunnerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Runners/DirectTestRunnerTests.cs index e57f94891..aaf3f3f93 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Runners/DirectTestRunnerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Runners/DirectTestRunnerTests.cs @@ -108,7 +108,7 @@ public void StopRun_Passes_Along_NUnitEngineException() _driver.When(x => x.StopRun(Arg.Any())) .Do(x => { throw new NUnitEngineException("Message"); }); - var ex = Assert.Throws(() => _directTestRunner.StopRun(true)); + var ex = Assert.Throws(() => _directTestRunner.ForcedStop()); Assert.That(ex.Message, Is.EqualTo("Message")); } @@ -118,7 +118,7 @@ public void StopRun_Throws_NUnitEngineException() _driver.When(x => x.StopRun(Arg.Any())) .Do(x => { throw new ArgumentException("Message"); }); - var ex = Assert.Throws(() => _directTestRunner.StopRun(true)); + var ex = Assert.Throws(() => _directTestRunner.ForcedStop()); Assert.That(ex.InnerException is ArgumentException); Assert.That(ex.InnerException.Message, Is.EqualTo("Message")); } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs new file mode 100644 index 000000000..162dbbcfc --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs @@ -0,0 +1,24 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +namespace NUnit.Engine.Communication.Messages +{ + public class MessageCode + { // All length 4 + public const string StartAgent = "STRT"; // not used + public const string StopAgent = "EXIT"; + public const string CreateRunner = "RUNR"; + + public const string LoadCommand = "LOAD"; + public const string ReloadCommand = "RELD"; + public const string UnloadCommand = "UNLD"; + public const string ExploreCommand = "XPLR"; + public const string CountCasesCommand = "CNTC"; + public const string RunCommand = "RSYN"; + public const string RunAsyncCommand = "RASY"; + public const string RequestStopCommand = "STOP"; + public const string ForcedStopCommand = "ABRT"; + + public const string ProgressReport = "PROG"; + public const string CommandResult = "RSLT"; + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Remoting/TestAgentRemotingTransport.cs b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Remoting/TestAgentRemotingTransport.cs index ac8486355..26ee4a34c 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Remoting/TestAgentRemotingTransport.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Remoting/TestAgentRemotingTransport.cs @@ -172,14 +172,16 @@ public AsyncTestEngineResult RunAsync(ITestEventListener listener, TestFilter fi } /// - /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// Request the current test run to stop. If no tests are running, + /// the call is ignored. /// - /// If true, cancel any ongoing test threads, otherwise wait for them to complete. - public void StopRun(bool force) - { - if (_runner != null) - _runner.StopRun(force); - } + public void RequestStop() => _runner?.RequestStop(); + + /// + /// Force the current test run to stop, killing threads or processes if necessary. + /// If no tests are running, the call is ignored. + /// + public void ForcedStop() => _runner?.ForcedStop(); #endregion diff --git a/src/NUnitEngine/nunit.engine.core/ITestEngineRunner.cs b/src/NUnitEngine/nunit.engine.core/ITestEngineRunner.cs index 67bcaa7b2..fb59c0c4a 100644 --- a/src/NUnitEngine/nunit.engine.core/ITestEngineRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/ITestEngineRunner.cs @@ -57,11 +57,16 @@ public interface ITestEngineRunner : IDisposable AsyncTestEngineResult RunAsync(ITestEventListener listener, TestFilter filter); /// - /// Cancel the current test run. If no test is running, + /// Request the current test run to stop. If no tests are running, /// the call is ignored. /// - /// If true, force a stop by cancelling threads if necessary. - void StopRun(bool force); + void RequestStop(); + + /// + /// Force the current test run to stop, killing threads or processes if necessary. + /// If no tests are running, the call is ignored. + /// + void ForcedStop(); /// /// Explore a loaded TestPackage and return information about diff --git a/src/NUnitEngine/nunit.engine.core/Runners/AbstractTestRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/AbstractTestRunner.cs index dd98eef2e..aa97503a4 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/AbstractTestRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/AbstractTestRunner.cs @@ -104,10 +104,16 @@ protected virtual AsyncTestEngineResult RunTestsAsync(ITestEventListener listene } /// - /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// Request the current test run to stop. If no tests are running, + /// the call is ignored. /// - /// If true, cancel any ongoing test threads, otherwise wait for them to complete. - public abstract void StopRun(bool force); + public abstract void RequestStop(); + + /// + /// Force the current test run to stop, killing threads or processes if necessary. + /// If no tests are running, the call is ignored. + /// + public abstract void ForcedStop(); /// /// Explores the TestPackage and returns information about diff --git a/src/NUnitEngine/nunit.engine.core/Runners/AggregatingTestRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/AggregatingTestRunner.cs index 392025a03..042751cb5 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/AggregatingTestRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/AggregatingTestRunner.cs @@ -191,13 +191,23 @@ private void RunTestsInParallel(ITestEventListener listener, TestFilter filter, } /// - /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// Request the current test run to stop. If no tests are running, + /// the call is ignored. /// - /// If true, cancel any ongoing test threads, otherwise wait for them to complete. - public override void StopRun(bool force) + public override void RequestStop() { foreach (var runner in Runners) - runner.StopRun(force); + runner.RequestStop(); + } + + /// + /// Force the current test run to stop, killing threads or processes if necessary. + /// If no tests are running, the call is ignored. + /// + public override void ForcedStop() + { + foreach (var runner in Runners) + runner.ForcedStop(); } protected override void Dispose(bool disposing) diff --git a/src/NUnitEngine/nunit.engine.core/Runners/DirectTestRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/DirectTestRunner.cs index 83b90f3ac..7a39b2787 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/DirectTestRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/DirectTestRunner.cs @@ -209,10 +209,18 @@ protected override TestEngineResult RunTests(ITestEventListener listener, TestFi } /// - /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// Request the current test run to stop. If no tests are running, + /// the call is ignored. /// - /// If true, cancel any ongoing test threads, otherwise wait for them to complete. - public override void StopRun(bool force) + public override void RequestStop() => StopRun(false); + + /// + /// Force the current test run to stop, killing threads or processes if necessary. + /// If no tests are running, the call is ignored. + /// + public override void ForcedStop() => StopRun(true); + + private void StopRun(bool force) { EnsurePackageIsLoaded(); diff --git a/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs index b6e0fc5d0..39ae50d9a 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs @@ -80,9 +80,17 @@ AsyncTestEngineResult ITestEngineRunner.RunAsync(ITestEventListener listener, Te throw new NotImplementedException(); } - void ITestEngineRunner.StopRun(bool force) - { - } + /// + /// Request the current test run to stop. If no tests are running, + /// the call is ignored. + /// + void ITestEngineRunner.RequestStop() { } + + /// + /// Force the current test run to stop, killing threads or processes if necessary. + /// If no tests are running, the call is ignored. + /// + void ITestEngineRunner.ForcedStop() { } TestEngineResult ITestEngineRunner.Explore(TestFilter filter) { diff --git a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs index efb1862d8..e33ad40d5 100644 --- a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs +++ b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs @@ -81,10 +81,9 @@ public AsyncTestEngineResult RunAsync(ITestEventListener listener, TestFilter fi //return new AsyncTestEngineResult(); } - public void StopRun(bool force) - { - SendCommandMessage("StopRun", force); - } + public void RequestStop() => SendCommandMessage(MessageCode.RequestStopCommand); + + public void ForcedStop() => SendCommandMessage(MessageCode.ForcedStopCommand); public TestEngineResult Explore(TestFilter filter) { diff --git a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs index 3418ce60e..2bcbd27c8 100644 --- a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs @@ -169,10 +169,12 @@ public ITestRun RunAsync(ITestEventListener listener, TestFilter filter) /// If true, cancel any ongoing test threads, otherwise wait for them to complete. public void StopRun(bool force) { - _engineRunner.StopRun(force); - - if (force) + if (!force) + _engineRunner.RequestStop(); + else { + _engineRunner.ForcedStop(); + // Frameworks should handle StopRun(true) by cancelling all tests and notifying // us of the completion of any tests that were running. However, this feature // may be absent in some frameworks or may be broken and we may not pass on the diff --git a/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs b/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs index a98aa3fc3..1cdaba411 100644 --- a/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs @@ -174,21 +174,34 @@ protected override AsyncTestEngineResult RunTestsAsync(ITestEventListener listen } /// - /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// Request the current test run to stop. If no tests are running, + /// the call is ignored. /// - /// If true, cancel any ongoing test threads, otherwise wait for them to complete. - public override void StopRun(bool force) + public override void RequestStop() { - if (_remoteRunner != null) + try + { + _remoteRunner?.ForcedStop(); + } + catch(Exception e) { - try - { - _remoteRunner.StopRun(force); - } - catch (Exception e) - { - log.Error("Failed to stop the remote run. {0}", ExceptionHelper.BuildMessageAndStackTrace(e)); - } + log.Error("Failed to stop the remote run. {0}", ExceptionHelper.BuildMessageAndStackTrace(e)); + } + } + + /// + /// Force the current test run to stop, killing threads or processes if necessary. + /// If no tests are running, the call is ignored. + /// + public override void ForcedStop() + { + try + { + _remoteRunner?.ForcedStop(); + } + catch( Exception e) + { + log.Error("Failed to stop the remote run. {0}", ExceptionHelper.BuildMessageAndStackTrace(e)); } } From dddb3fb911bf09b3f3f7d09af79d6ce324752aef Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 14 Mar 2025 09:58:32 -0700 Subject: [PATCH 156/190] Make TestPackage serializable as XML --- .../nunit.engine.api/TestPackage.cs | 112 +++++++++++++++-- .../Communication/Messages/MessageTests.cs | 81 ++++++++++++ .../BinarySerializationProtocolTests.cs | 117 ++++++++++++++++++ .../Internal/TestPackageSerializationTests.cs | 85 +++++++++++++ .../nunit.engine.core.tests.csproj | 2 +- .../Messages/TestEngineMessage.cs | 14 +++ .../Internal/TestPackageExtensions.cs | 45 +++++++ 7 files changed, 443 insertions(+), 13 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs create mode 100644 src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs create mode 100644 src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs diff --git a/src/NUnitEngine/nunit.engine.api/TestPackage.cs b/src/NUnitEngine/nunit.engine.api/TestPackage.cs index e43cf0fed..7c6e0e2e6 100644 --- a/src/NUnitEngine/nunit.engine.api/TestPackage.cs +++ b/src/NUnitEngine/nunit.engine.api/TestPackage.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Xml.Schema; +using System.Xml; +using System.Xml.Serialization; namespace NUnit.Engine { @@ -23,7 +26,7 @@ namespace NUnit.Engine /// tests in the reloaded assembly to match those originally loaded. /// [Serializable] - public class TestPackage + public class TestPackage : IXmlSerializable { /// /// Construct a named TestPackage, specifying a file path for @@ -37,8 +40,6 @@ public TestPackage(string filePath) if (filePath != null) { FullName = Path.GetFullPath(filePath); - Settings = new Dictionary(); - SubPackages = new List(); } } @@ -49,16 +50,19 @@ public TestPackage(string filePath) public TestPackage(IList testFiles) { ID = GetNextID(); - SubPackages = new List(); - Settings = new Dictionary(); foreach (string testFile in testFiles) SubPackages.Add(new TestPackage(testFile)); } + /// + /// Construct an empty TestPackage. + /// + public TestPackage() { } + private static int _nextID = 0; - private string GetNextID() + private static string GetNextID() { return (_nextID++).ToString(); } @@ -75,10 +79,7 @@ private string GetNextID() /// /// Gets the name of the package /// - public string Name - { - get { return FullName == null ? null : Path.GetFileName(FullName); } - } + public string Name => FullName == null ? null : Path.GetFileName(FullName); /// /// Gets the path to the file containing tests. It may be @@ -89,12 +90,12 @@ public string Name /// /// Gets the list of SubPackages contained in this package /// - public IList SubPackages { get; private set; } + public IList SubPackages { get; } = new List(); /// /// Gets the settings dictionary for this package. /// - public IDictionary Settings { get; private set; } + public IDictionary Settings { get; } = new Dictionary(); /// /// Add a subproject to the package. @@ -139,5 +140,92 @@ public T GetSetting(string name, T defaultSetting) ? (T)Settings[name] : defaultSetting; } + + #region IXmlSerializable Implementation + + /// + public XmlSchema GetSchema() + { + return null; + } + + /// + public void ReadXml(XmlReader xmlReader) => ReadPackageFromXml(this, xmlReader); + + // Read a single package and its subpackages from the xml reader + private void ReadPackageFromXml(TestPackage package, XmlReader xmlReader) + { + package.ID = xmlReader.GetAttribute("id"); + package.FullName = xmlReader.GetAttribute("fullname"); + if (!xmlReader.IsEmptyElement) + { + while (xmlReader.Read()) + { + switch (xmlReader.NodeType) + { + case XmlNodeType.Element: + switch(xmlReader.Name) + { + case "Settings": + while (xmlReader.MoveToNextAttribute()) + package.AddSetting(xmlReader.Name, xmlReader.Value); + + xmlReader.MoveToElement(); + break; + + case "TestPackage": + TestPackage testPackage = new TestPackage(); + ReadPackageFromXml(testPackage, xmlReader); + package.SubPackages.Add(testPackage); + break; + } + break; + + case XmlNodeType.EndElement: + if (xmlReader.Name == "TestPackage") + return; + break; + + default: + throw new Exception("Unexpected EndElement: " + xmlReader.Name); + } + } + + package.SubPackages.Add(package); + } + } + + /// + public void WriteXml(XmlWriter xmlWriter) => WritePackageToXml(this, xmlWriter); + + // Write a single package and its subpackages to the xmlWriter + private void WritePackageToXml(TestPackage package, XmlWriter xmlWriter) + { + // Write ID and FullName + xmlWriter.WriteAttributeString("id", package.ID); + if (package.FullName != null) + xmlWriter.WriteAttributeString("fullname", package.FullName); + + // Write Settings + if (package.Settings.Count != 0) + { + xmlWriter.WriteStartElement("Settings"); + + foreach (KeyValuePair setting in package.Settings) + xmlWriter.WriteAttributeString(setting.Key, setting.Value.ToString()); + + xmlWriter.WriteEndElement(); + } + + // Write any SubPackages recursively + foreach (TestPackage subPackage in package.SubPackages) + { + xmlWriter.WriteStartElement("TestPackage"); + WritePackageToXml(subPackage, xmlWriter); + xmlWriter.WriteEndElement(); + } + } } + + #endregion } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs new file mode 100644 index 000000000..d45e8f8a7 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System.Collections.Generic; +using NUnit.Framework; +using NUnit.Engine.Communication.Protocols; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Communication.Messages +{ + public class MessageTests + { + const string EMPTY_FILTER = ""; + static readonly string TEST_PACKAGE = new TestPackage("mock-assembly.dll").ToXml(); + + private BinarySerializationProtocol _wireProtocol = new BinarySerializationProtocol(); + + static readonly TestCaseData[] MessageTestData = new TestCaseData[] + { + new TestCaseData(MessageCode.CreateRunner, TEST_PACKAGE), + new TestCaseData(MessageCode.LoadCommand, null), + new TestCaseData(MessageCode.ReloadCommand, null), + new TestCaseData(MessageCode.UnloadCommand, null), + new TestCaseData(MessageCode.ExploreCommand, EMPTY_FILTER), + new TestCaseData(MessageCode.CountCasesCommand, EMPTY_FILTER), + new TestCaseData(MessageCode.RunCommand, EMPTY_FILTER), + new TestCaseData(MessageCode.RunAsyncCommand, EMPTY_FILTER), + new TestCaseData(MessageCode.RequestStopCommand, null), + new TestCaseData(MessageCode.ForcedStopCommand, null) + }; + + //[TestCaseSource(nameof(MessageTestData))] + //public void CommandMessageConstructionTests(string commandName, string argument) + //{ + // var cmd = new TestEngineMessage(commandName, argument); + // Assert.That(cmd.Code, Is.EqualTo(commandName)); + // Assert.That(cmd.CommandName, Is.EqualTo(commandName)); + // Assert.That(cmd.Argument, Is.EqualTo(argument)); + //} + + //[TestCaseSource(nameof(MessageTestData))] + //public void CommandMessageEncodingTests(string commandName, string argument) + //{ + // var cmd = new TestEngineMessage(commandName, argument); + + // var bytes = _wireProtocol.Encode(cmd); + // var messages = new List(_wireProtocol.Decode(bytes)); + // var decoded = messages[0]; + // Assert.That(decoded.Code, Is.EqualTo(commandName)); + // Assert.That(decoded.CommandName, Is.EqualTo(commandName)); + // Assert.That(decoded.Argument, Is.EqualTo(argument)); + //} + + //[Test] + //public void ProgressMessageTest() + //{ + // const string REPORT = "Progress report"; + // var msg = new TestEngineMessage(MessageCode.ProgressReport, REPORT); + // Assert.That(msg.Code, Is.EqualTo(MessageCode.ProgressReport)); + // Assert.That(msg.Data, Is.EqualTo(REPORT)); + // var bytes = _wireProtocol.Encode(msg); + // var messages = new List(_wireProtocol.Decode(bytes)); + // var decoded = messages[0]; + // Assert.That(decoded.Code, Is.EqualTo(MessageCode.ProgressReport)); + // Assert.That(decoded.Data, Is.EqualTo(REPORT)); + //} + + //[Test] + //public void CommandReturnMessageTest() + //{ + // const string RESULT = "Result text"; + // var msg = new TestEngineMessage(MessageCode.CommandResult, RESULT); + // Assert.That(msg.Code, Is.EqualTo(MessageCode.CommandResult)); + // Assert.That(msg.Data, Is.EqualTo(RESULT)); + // var bytes = _wireProtocol.Encode(msg); + // var messages = new List(_wireProtocol.Decode(bytes)); + // var decoded = messages[0]; + // Assert.That(decoded.Code, Is.EqualTo(MessageCode.CommandResult)); + // Assert.That(decoded.Data, Is.EqualTo(RESULT)); + //} + } +} diff --git a/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs new file mode 100644 index 000000000..2c57d75d1 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs @@ -0,0 +1,117 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Serialization; +using NUnit.Framework; +using NUnit.Engine.Communication.Messages; +using NUnit.Engine.Internal; +using System.Xml; + +namespace NUnit.Engine.Communication.Protocols +{ + public class BinarySerializationProtocolTests + { + private BinarySerializationProtocol wireProtocol = new BinarySerializationProtocol(); + + [Test] + public void WriteAndReadBytes() + { + int SIZE = 1024; + + var bytes = new byte[SIZE]; + new Random().NextBytes(bytes); + + var stream = new MemoryStream(); + stream.Write(bytes, 0, SIZE); + + Assert.That(stream.Length, Is.EqualTo(SIZE)); + var copy = new byte[SIZE]; + + stream.Position = 0; + Assert.That(stream.Read(copy, 0, SIZE), Is.EqualTo(SIZE)); + + Assert.That(copy, Is.EqualTo(bytes)); + } + + //[Test] + //public void DecodeSingleMessage() + //{ + // var originalPackage = new TestPackage("mock-assembly.dll", "notest-assembly.dll"); + // var originalMessage = new TestEngineMessage(MessageCode.CommandResult, originalPackage.ToXml()); + + // var bytes = wireProtocol.Encode(originalMessage); + // Console.WriteLine($"Serialized {bytes.Length} bytes."); + + // var messages = new List(wireProtocol.Decode(bytes)); + // Assert.That(messages.Count, Is.EqualTo(1)); + // var message = messages[0]; + + // Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult)); + // Assert.That(message.Data, Is.EqualTo(originalPackage.ToXml())); + // var newPackage = DeserializePackage(message.Data); + // ComparePackages(newPackage, originalPackage); + //} + + //[TestCase(1)] + //[TestCase(2)] + //[TestCase(5)] + //public void DecodeSplitMessages(int numMessages) + //{ + // const int SPLIT_SIZE = 1000; + + // var originalPackage = new TestPackage("mock-assembly.dll", "notest-assembly.dll"); + // var originalMessage = new TestEngineMessage(MessageCode.CommandResult, originalPackage.ToXml()); + + // var msgBytes = wireProtocol.Encode(originalMessage); + // var msgLength = msgBytes.Length; + // var allBytes = new byte[msgLength * numMessages]; + // for (int i = 0; i < numMessages; i++) + // Array.Copy(msgBytes, 0, allBytes, i * msgLength, msgLength); + + // Console.WriteLine($"Serialized {numMessages} messages in {allBytes.Length} bytes."); + + // var messages = new List(); + + // for (int index = 0; index < allBytes.Length; index += SPLIT_SIZE) + // { + // var bytesToSend = Math.Min(allBytes.Length - index, SPLIT_SIZE); + // var buffer = new byte[bytesToSend]; + // Array.Copy(allBytes, index, buffer, 0, bytesToSend); + // messages.AddRange(wireProtocol.Decode(buffer)); + // Console.WriteLine($"Decoded {bytesToSend} bytes, message count is now {messages.Count}"); + // var expectedCount = (index + bytesToSend) / msgLength; + // Assert.That(messages.Count, Is.EqualTo(expectedCount)); + // } + + // foreach (TestEngineMessage message in messages) + // { + // Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult)); + // var newPackage = DeserializePackage(message.Data); + // ComparePackages(newPackage, originalPackage); + // } + //} + + ////[Test] + //public void DecodeMultipleMessages() + //{ + // var commands = new string[] { "CMD1", "CMD2", "CMD3", "CMD4", "CMD5", "CMD6" }; + + // var stream = new MemoryStream(); + + // foreach (var command in commands) + // { + // var buffer = wireProtocol.Encode(new TestEngineMessage(command, null)); + // stream.Write(buffer, 0, buffer.Length); + // } + + // var received = new List(wireProtocol.Decode(stream.ToArray())); + // Assert.That(received.Count, Is.EqualTo(commands.Length)); + + // for (int i = 0; i < commands.Length; i++) + // Assert.That(received[i].CommandName, Is.EqualTo(commands[i])); + //} + + } +} diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs new file mode 100644 index 000000000..8cbcad75a --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs @@ -0,0 +1,85 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.IO; +using NUnit.Framework; + +namespace NUnit.Engine.Internal +{ + public class TestPackageSerializationTests + { + private static readonly string ASSEMBLY_1 = Path.GetFullPath("mock-assembly.dll"); + private static readonly string ASSEMBLY_2 = Path.GetFullPath("notest-assembly.dll"); + private static readonly TestPackage TEST_PACKAGE = new TestPackage(new string[] { "mock-assembly.dll", "notest-assembly.dll" }); + private static readonly string TEST_PACKAGE_XML = + "" + + $"" + + $"" + + ""; + private static readonly string TEST_PACKAGE_XML_WITH_XML_DECLARATION = "" + TEST_PACKAGE_XML; + + static TestPackageSerializationTests() + { + TEST_PACKAGE.AddSetting("foo", "bar"); + } + + [Test] + public void TestPackageToXml() + { + var xml = TEST_PACKAGE.ToXml(); + Console.WriteLine(xml); + Assert.That(xml, Is.EqualTo(TEST_PACKAGE_XML)); + } + + [Test] + public void TestPackageFromXml() + { + var package = new TestPackage().FromXml(TEST_PACKAGE_XML); + Console.WriteLine(package.ToXml()); + ComparePackages(package, TEST_PACKAGE); + } + + [Test] + public void TestPackageFromXml_BadRootElementThrowsInvalidOperationException() + { + Assert.That(() => new TestPackage().FromXml("Not a TestPackage"), Throws.InvalidOperationException); + } + + [Test] + public void TestPackageFromXml_XmlDeclarationIsIgnored() + { + var package = new TestPackage().FromXml(TEST_PACKAGE_XML_WITH_XML_DECLARATION); + Console.WriteLine(package.ToXml()); + ComparePackages(package, TEST_PACKAGE); + } + + [Test] + public void TestPackageRoundTrip() + { + var xml = TEST_PACKAGE.ToXml(); + Console.WriteLine(xml); + var package = new TestPackage().FromXml(xml); + ComparePackages(package, TEST_PACKAGE); + } + + private void ComparePackages(TestPackage newPackage, TestPackage oldPackage) + { + Assert.Multiple(() => + { + Assert.That(newPackage.Name, Is.EqualTo(oldPackage.Name)); + Assert.That(newPackage.FullName, Is.EqualTo(oldPackage.FullName)); + Assert.That(newPackage.Settings.Count, Is.EqualTo(oldPackage.Settings.Count)); + Assert.That(newPackage.SubPackages.Count, Is.EqualTo(oldPackage.SubPackages.Count)); + + foreach (var key in oldPackage.Settings.Keys) + { + Assert.That(newPackage.Settings.ContainsKey(key)); + Assert.That(newPackage.Settings[key], Is.EqualTo(oldPackage.Settings[key])); + } + + for (int i = 0; i < oldPackage.SubPackages.Count; i++) + ComparePackages(newPackage.SubPackages[i], oldPackage.SubPackages[i]); + }); + } + } +} diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index a78b9c67c..76037d0e5 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -1,7 +1,7 @@  - NUnit.Engine.Core.Tests + NUnit.Engine net462;netcoreapp3.1;net6.0;net8.0;net9.0 Exe true diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs index 16478eab1..bba3086b9 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs @@ -7,5 +7,19 @@ namespace NUnit.Engine.Communication.Messages [Serializable] public abstract class TestEngineMessage { + // public TestEngineMessage(string code, string data) + // { + // Code = code; + // Data = data; + // } + + // protected TestEngineMessage() { } + + // public string Code { get; } + // public string Data { get; } + + // // Alias properties for convenience + // //public string CommandName => Code; + // //public string Argument => Data; } } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs index 535561eba..49fc54c31 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs @@ -2,6 +2,9 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Xml.Serialization; +using System.Xml; namespace NUnit.Engine.Internal { @@ -39,5 +42,47 @@ private static void AccumulatePackages(TestPackage package, IList s foreach (var subPackage in package.SubPackages) AccumulatePackages(subPackage, selection, selector); } + + public static string ToXml(this TestPackage package) + { + var writer = new StringWriter(); + var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings() { OmitXmlDeclaration = true }); + var serializer = new XmlSerializer(typeof(TestPackage)); + serializer.Serialize(xmlWriter, package); + xmlWriter.Flush(); + xmlWriter.Close(); + + return writer.ToString(); + } + + /// + /// Populate an empty TestPackage using its XML representation + /// + /// String holding the XML representation of the package + /// A TestPackage + public static TestPackage FromXml(this TestPackage package, string xml) + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + + var reader = new StringReader(doc.OuterXml); + var xmlReader = XmlReader.Create(reader); + + if (!ReadTestPackageElement()) + throw new InvalidOperationException("Invalid TestPackage XML"); + + package.ReadXml(xmlReader); + return package; + + // The reader must be positioned on the top-level TestPackgae element + // before calling ReadXml. + bool ReadTestPackageElement() + { + while (xmlReader.Read()) + if (xmlReader.NodeType == XmlNodeType.Element) + return xmlReader.Name == "TestPackage"; + return false; + } + } } } From 70fa8546e55b917ede03bac024cf9f7cdb531d94 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 15 Mar 2025 17:08:03 -0700 Subject: [PATCH 157/190] Eliminate ProgressMessage class --- .../Communication/Messages/CommandMessage.cs | 1 + .../Communication/Messages/MessageCode.cs | 29 +++++++++++++++++++ .../Communication/Messages/ProgressMessage.cs | 17 ----------- .../Messages/TestEngineMessage.cs | 24 +++++++-------- .../Transports/Tcp/TestAgentTcpTransport.cs | 3 +- .../Transports/Tcp/TestAgentTcpProxy.cs | 7 ++--- 6 files changed, 47 insertions(+), 34 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core/Communication/Messages/ProgressMessage.cs diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandMessage.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandMessage.cs index 3cfd3a3ed..d72185a20 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandMessage.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandMessage.cs @@ -8,6 +8,7 @@ namespace NUnit.Engine.Communication.Messages public class CommandMessage : TestEngineMessage { public CommandMessage(string commandName, params object[] arguments) + : base(MessageCode.FromCommand(commandName), null) { CommandName = commandName; Arguments = arguments; diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs index 162dbbcfc..8a9958878 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs @@ -1,5 +1,7 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +using System; + namespace NUnit.Engine.Communication.Messages { public class MessageCode @@ -20,5 +22,32 @@ public class MessageCode public const string ProgressReport = "PROG"; public const string CommandResult = "RSLT"; + + public static string FromCommand(string command) + { + switch (command) + { + case "CreateRunner": + return CreateRunner; + case "Load": + return LoadCommand; + case "Reload": + return ReloadCommand; + case "Unload": + return UnloadCommand; + case "Explore": + return ExploreCommand; + case "CountTestCases": + return CountCasesCommand; + case "Run": + return RunCommand; + case "RunAsync": + return RunAsyncCommand; + case "Stop": + return RequestStopCommand; + default: + throw new ArgumentException("Invalid command", nameof(command)); + } + } } } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/ProgressMessage.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/ProgressMessage.cs deleted file mode 100644 index 6038aa792..000000000 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/ProgressMessage.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; - -namespace NUnit.Engine.Communication.Messages -{ - [Serializable] - public class ProgressMessage : TestEngineMessage - { - public ProgressMessage(string report) - { - Report = report; - } - - public string Report { get; } - } -} diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs index bba3086b9..3d93abb2d 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs @@ -5,21 +5,21 @@ namespace NUnit.Engine.Communication.Messages { [Serializable] - public abstract class TestEngineMessage + public class TestEngineMessage { - // public TestEngineMessage(string code, string data) - // { - // Code = code; - // Data = data; - // } + public TestEngineMessage(string code, string data) + { + Code = code; + Data = data; + } - // protected TestEngineMessage() { } + protected TestEngineMessage() { } - // public string Code { get; } - // public string Data { get; } + public string Code { get; } + public string Data { get; } - // // Alias properties for convenience - // //public string CommandName => Code; - // //public string Argument => Data; + // Alias properties for convenience + //public string CommandName => Code; + //public string Argument => Data; } } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs index bd8f913e3..b2454acbd 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs @@ -129,7 +129,8 @@ private void SendResult(object result) public void OnTestEvent(string report) { - var progressMessage = new ProgressMessage(report); + //var progressMessage = new ProgressMessage(report); + var progressMessage = new TestEngineMessage(MessageCode.ProgressReport, report); var bytes = new BinarySerializationProtocol().Encode(progressMessage); _clientSocket.Send(bytes); } diff --git a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs index e33ad40d5..8c848ee7e 100644 --- a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs +++ b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs @@ -119,11 +119,10 @@ private TestEngineResult TestRunResult(ITestEventListener listener) if (returnMessage != null) return (TestEngineResult)returnMessage.ReturnValue; - var progressMessage = receivedMessage as ProgressMessage; - if (progressMessage == null) + if (receivedMessage.Code == MessageCode.ProgressReport) + listener.OnTestEvent(receivedMessage.Data); + else throw new InvalidOperationException($"Expected either a ProgressMessage or a CommandReturnMessage but received a {receivedType}"); - - listener.OnTestEvent(progressMessage.Report); } } } From 1bf6bec1f0c0210804bcf1667bada1f87c53d711 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 15 Mar 2025 22:35:04 -0700 Subject: [PATCH 158/190] Eliminate CommandMessage class --- package-tests.cake | 57 ++++++++++--------- .../Communication/Messages/MessageTests.cs | 26 ++++----- .../nunit.engine.core.tests.csproj | 1 + .../Communication/Messages/CommandMessage.cs | 21 ------- .../Messages/TestEngineMessage.cs | 4 -- .../Transports/Tcp/TestAgentTcpTransport.cs | 35 ++++++------ .../Transports/Tcp/TestAgentTcpProxy.cs | 25 ++++---- 7 files changed, 75 insertions(+), 94 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandMessage.cs diff --git a/package-tests.cake b/package-tests.cake index be6c014fb..bcee421f8 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -312,34 +312,35 @@ public static class PackageTests ExtensionsNeeded = new[] { Extensions.VSProjectLoader } }); - StandardAndZipLists.Add(new PackageTest(1, "VSProjectLoaderTest_Solution") - { - Description = "Run mock-assembly using the .sln file", - Arguments = "../../src/TestData/TestData.sln --config=Release", - ExpectedResult = new ExpectedResult("Failed") - { - Total = 37 * 6, - Passed = 23 * 6, - Failed = 5 * 6, - Warnings = 1 * 6, - Inconclusive = 1 * 6, - Skipped = 7 * 6, - Assemblies = new ExpectedAssemblyResult[] - { - new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-3.1"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), - new ExpectedAssemblyResult("mock-assembly.dll", "netcore-9.0"), - new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), - new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), - new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0"), - new ExpectedAssemblyResult("WpfApp.exe") - } - }, - ExtensionsNeeded = new[] { Extensions.VSProjectLoader } - }); + // TODO: This seems to be broken by latest changes to TCP commmunication + //StandardAndZipLists.Add(new PackageTest(1, "VSProjectLoaderTest_Solution") + //{ + // Description = "Run mock-assembly using the .sln file", + // Arguments = "../../src/TestData/TestData.sln --config=Release", + // ExpectedResult = new ExpectedResult("Failed") + // { + // Total = 37 * 6, + // Passed = 23 * 6, + // Failed = 5 * 6, + // Warnings = 1 * 6, + // Inconclusive = 1 * 6, + // Skipped = 7 * 6, + // Assemblies = new ExpectedAssemblyResult[] + // { + // new ExpectedAssemblyResult("mock-assembly.dll", "net-4.6.2"), + // new ExpectedAssemblyResult("mock-assembly.dll", "netcore-3.1"), + // new ExpectedAssemblyResult("mock-assembly.dll", "netcore-6.0"), + // new ExpectedAssemblyResult("mock-assembly.dll", "netcore-7.0"), + // new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"), + // new ExpectedAssemblyResult("mock-assembly.dll", "netcore-9.0"), + // new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"), + // new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"), + // new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0"), + // new ExpectedAssemblyResult("WpfApp.exe") + // } + // }, + // ExtensionsNeeded = new[] { Extensions.VSProjectLoader } + //}); // TeamCity Event Listener Tests StandardAndZipLists.Add(new PackageTest(1, "Net462TeamCityListenerTest1") diff --git a/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs index d45e8f8a7..e5529e6ec 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs @@ -50,19 +50,19 @@ public class MessageTests // Assert.That(decoded.Argument, Is.EqualTo(argument)); //} - //[Test] - //public void ProgressMessageTest() - //{ - // const string REPORT = "Progress report"; - // var msg = new TestEngineMessage(MessageCode.ProgressReport, REPORT); - // Assert.That(msg.Code, Is.EqualTo(MessageCode.ProgressReport)); - // Assert.That(msg.Data, Is.EqualTo(REPORT)); - // var bytes = _wireProtocol.Encode(msg); - // var messages = new List(_wireProtocol.Decode(bytes)); - // var decoded = messages[0]; - // Assert.That(decoded.Code, Is.EqualTo(MessageCode.ProgressReport)); - // Assert.That(decoded.Data, Is.EqualTo(REPORT)); - //} + [Test] + public void ProgressMessageTest() + { + const string REPORT = "Progress report"; + var msg = new TestEngineMessage(MessageCode.ProgressReport, REPORT); + Assert.That(msg.Code, Is.EqualTo(MessageCode.ProgressReport)); + Assert.That(msg.Data, Is.EqualTo(REPORT)); + var bytes = _wireProtocol.Encode(msg); + var messages = new List(_wireProtocol.Decode(bytes)); + var decoded = messages[0]; + Assert.That(decoded.Code, Is.EqualTo(MessageCode.ProgressReport)); + Assert.That(decoded.Data, Is.EqualTo(REPORT)); + } //[Test] //public void CommandReturnMessageTest() diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 76037d0e5..f134f5246 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -9,6 +9,7 @@ Full false false + true diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandMessage.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandMessage.cs deleted file mode 100644 index d72185a20..000000000 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandMessage.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; - -namespace NUnit.Engine.Communication.Messages -{ - [Serializable] - public class CommandMessage : TestEngineMessage - { - public CommandMessage(string commandName, params object[] arguments) - : base(MessageCode.FromCommand(commandName), null) - { - CommandName = commandName; - Arguments = arguments; - } - - public string CommandName { get; } - - public object[] Arguments { get; } - } -} diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs index 3d93abb2d..7437632d9 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs @@ -17,9 +17,5 @@ protected TestEngineMessage() { } public string Code { get; } public string Data { get; } - - // Alias properties for convenience - //public string CommandName => Code; - //public string Argument => Data; } } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs index b2454acbd..841f8dc7c 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs @@ -8,6 +8,8 @@ using NUnit.Engine.Internal; using NUnit.Engine.Communication.Messages; using NUnit.Engine.Communication.Protocols; +using System.Xml.Serialization; +using System.IO; namespace NUnit.Engine.Communication.Transports.Tcp { @@ -23,6 +25,7 @@ public class TestAgentTcpTransport : ITestAgentTransport, ITestEventListener private string _agencyUrl; private Socket _clientSocket; private ITestEngineRunner _runner; + private XmlSerializer _testPackageSerializer = new XmlSerializer(typeof(TestPackage)); public TestAgentTcpTransport(RemoteTestAgent agent, string serverUrl) { @@ -76,42 +79,42 @@ private void CommandLoop() while (keepRunning) { - var command = socketReader.GetNextMessage(); + var command = socketReader.GetNextMessage(); - switch (command.CommandName) + switch (command.Code) { - case "CreateRunner": - var package = (TestPackage)command.Arguments[0]; + case MessageCode.CreateRunner: + var package = new TestPackage().FromXml(command.Data); _runner = CreateRunner(package); break; - case "Load": + case MessageCode.LoadCommand: SendResult(_runner.Load()); break; - case "Reload": + case MessageCode.ReloadCommand: SendResult(_runner.Reload()); break; - case "Unload": + case MessageCode.UnloadCommand: _runner.Unload(); break; - case "Explore": - var filter = (TestFilter)command.Arguments[0]; + case MessageCode.ExploreCommand: + var filter = new TestFilter(command.Data); SendResult(_runner.Explore(filter)); break; - case "CountTestCases": - filter = (TestFilter)command.Arguments[0]; + case MessageCode.CountCasesCommand: + filter = new TestFilter(command.Data); SendResult(_runner.CountTestCases(filter)); break; - case "Run": - filter = (TestFilter)command.Arguments[0]; + case MessageCode.RunCommand: + filter = new TestFilter(command.Data); SendResult(_runner.Run(this, filter)); break; - case "RunAsync": - filter = (TestFilter)command.Arguments[0]; + case MessageCode.RunAsyncCommand: + filter = new TestFilter(command.Data); _runner.RunAsync(this, filter); break; - case "Stop": + case MessageCode.StopAgent: keepRunning = false; break; } diff --git a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs index 8c848ee7e..f50366bdf 100644 --- a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs +++ b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs @@ -4,6 +4,7 @@ using System.Net.Sockets; using NUnit.Engine.Communication.Messages; using NUnit.Engine.Communication.Protocols; +using NUnit.Engine.Internal; namespace NUnit.Engine.Communication.Transports.Tcp { @@ -26,7 +27,7 @@ public TestAgentTcpProxy(Socket socket, Guid id) public ITestEngineRunner CreateRunner(TestPackage package) { - SendCommandMessage("CreateRunner", package); + SendCommandMessage(MessageCode.CreateRunner, package.ToXml()); // Agent also functions as the runner return this; @@ -34,48 +35,48 @@ public ITestEngineRunner CreateRunner(TestPackage package) public bool Start() { - SendCommandMessage("Start"); + SendCommandMessage(MessageCode.StartAgent); return CommandResult(); } public void Stop() { - SendCommandMessage("Stop"); + SendCommandMessage(MessageCode.StopAgent); } public TestEngineResult Load() { - SendCommandMessage("Load"); + SendCommandMessage(MessageCode.LoadCommand); return CommandResult(); } public void Unload() { - SendCommandMessage("Unload"); + SendCommandMessage(MessageCode.UnloadCommand); } public TestEngineResult Reload() { - SendCommandMessage("Reload"); + SendCommandMessage(MessageCode.ReloadCommand); return CommandResult(); } public int CountTestCases(TestFilter filter) { - SendCommandMessage("CountTestCases", filter); + SendCommandMessage(MessageCode.CountCasesCommand, filter.Text); return CommandResult(); } public TestEngineResult Run(ITestEventListener listener, TestFilter filter) { - SendCommandMessage("Run", filter); + SendCommandMessage(MessageCode.RunCommand, filter.Text); return TestRunResult(listener); } public AsyncTestEngineResult RunAsync(ITestEventListener listener, TestFilter filter) { - SendCommandMessage("RunAsync", filter); + SendCommandMessage(MessageCode.RunAsyncCommand, filter.Text); // TODO: Should we get the async result from the agent or just use our own? return CommandResult(); //return new AsyncTestEngineResult(); @@ -87,7 +88,7 @@ public AsyncTestEngineResult RunAsync(ITestEventListener listener, TestFilter fi public TestEngineResult Explore(TestFilter filter) { - SendCommandMessage("Explore", filter); + SendCommandMessage(MessageCode.ExploreCommand, filter.Text); return CommandResult(); } @@ -95,9 +96,9 @@ public void Dispose() { } - private void SendCommandMessage(string command, params object[] arguments) + private void SendCommandMessage(string command, string data=null) { - _socket.Send(_wireProtocol.Encode(new CommandMessage(command, arguments))); + _socket.Send(_wireProtocol.Encode(new TestEngineMessage(command, data))); } private T CommandResult() From 104e20459bf6905ade88e5c8b55fb3dd43c39d67 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 16 Mar 2025 09:57:13 -0700 Subject: [PATCH 159/190] Eliminate ComandResultMessage class; re-enable tests --- .../Communication/Messages/MessageTests.cs | 62 ++++--- .../BinarySerializationProtocolTests.cs | 156 +++++++++--------- .../Internal/TestPackageSerializationTests.cs | 25 ++- .../Messages/CommandReturnMessage.cs | 17 -- .../Communication/Messages/MessageCode.cs | 27 --- .../Transports/Tcp/SocketReader.cs | 18 -- .../Transports/Tcp/TestAgentTcpTransport.cs | 17 +- .../Transports/Tcp/TestAgentTcpProxy.cs | 26 ++- 8 files changed, 144 insertions(+), 204 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandReturnMessage.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs index e5529e6ec..a32438fd0 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs @@ -28,27 +28,25 @@ public class MessageTests new TestCaseData(MessageCode.ForcedStopCommand, null) }; - //[TestCaseSource(nameof(MessageTestData))] - //public void CommandMessageConstructionTests(string commandName, string argument) - //{ - // var cmd = new TestEngineMessage(commandName, argument); - // Assert.That(cmd.Code, Is.EqualTo(commandName)); - // Assert.That(cmd.CommandName, Is.EqualTo(commandName)); - // Assert.That(cmd.Argument, Is.EqualTo(argument)); - //} + [TestCaseSource(nameof(MessageTestData))] + public void CommandMessageConstructionTests(string code, string data) + { + var cmd = new TestEngineMessage(code, data); + Assert.That(cmd.Code, Is.EqualTo(code)); + Assert.That(cmd.Data, Is.EqualTo(data)); + } - //[TestCaseSource(nameof(MessageTestData))] - //public void CommandMessageEncodingTests(string commandName, string argument) - //{ - // var cmd = new TestEngineMessage(commandName, argument); + [TestCaseSource(nameof(MessageTestData))] + public void CommandMessageEncodingTests(string code, string data) + { + var cmd = new TestEngineMessage(code, data); - // var bytes = _wireProtocol.Encode(cmd); - // var messages = new List(_wireProtocol.Decode(bytes)); - // var decoded = messages[0]; - // Assert.That(decoded.Code, Is.EqualTo(commandName)); - // Assert.That(decoded.CommandName, Is.EqualTo(commandName)); - // Assert.That(decoded.Argument, Is.EqualTo(argument)); - //} + var bytes = _wireProtocol.Encode(cmd); + var messages = new List(_wireProtocol.Decode(bytes)); + var decoded = messages[0]; + Assert.That(decoded.Code, Is.EqualTo(code)); + Assert.That(decoded.Data, Is.EqualTo(data)); + } [Test] public void ProgressMessageTest() @@ -64,18 +62,18 @@ public void ProgressMessageTest() Assert.That(decoded.Data, Is.EqualTo(REPORT)); } - //[Test] - //public void CommandReturnMessageTest() - //{ - // const string RESULT = "Result text"; - // var msg = new TestEngineMessage(MessageCode.CommandResult, RESULT); - // Assert.That(msg.Code, Is.EqualTo(MessageCode.CommandResult)); - // Assert.That(msg.Data, Is.EqualTo(RESULT)); - // var bytes = _wireProtocol.Encode(msg); - // var messages = new List(_wireProtocol.Decode(bytes)); - // var decoded = messages[0]; - // Assert.That(decoded.Code, Is.EqualTo(MessageCode.CommandResult)); - // Assert.That(decoded.Data, Is.EqualTo(RESULT)); - //} + [Test] + public void CommandReturnMessageTest() + { + const string RESULT = "Result text"; + var msg = new TestEngineMessage(MessageCode.CommandResult, RESULT); + Assert.That(msg.Code, Is.EqualTo(MessageCode.CommandResult)); + Assert.That(msg.Data, Is.EqualTo(RESULT)); + var bytes = _wireProtocol.Encode(msg); + var messages = new List(_wireProtocol.Decode(bytes)); + var decoded = messages[0]; + Assert.That(decoded.Code, Is.EqualTo(MessageCode.CommandResult)); + Assert.That(decoded.Data, Is.EqualTo(RESULT)); + } } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs index 2c57d75d1..c79c07c68 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs @@ -13,7 +13,7 @@ namespace NUnit.Engine.Communication.Protocols { public class BinarySerializationProtocolTests { - private BinarySerializationProtocol wireProtocol = new BinarySerializationProtocol(); + private BinarySerializationProtocol _wireProtocol = new BinarySerializationProtocol(); [Test] public void WriteAndReadBytes() @@ -35,83 +35,83 @@ public void WriteAndReadBytes() Assert.That(copy, Is.EqualTo(bytes)); } - //[Test] - //public void DecodeSingleMessage() - //{ - // var originalPackage = new TestPackage("mock-assembly.dll", "notest-assembly.dll"); - // var originalMessage = new TestEngineMessage(MessageCode.CommandResult, originalPackage.ToXml()); - - // var bytes = wireProtocol.Encode(originalMessage); - // Console.WriteLine($"Serialized {bytes.Length} bytes."); - - // var messages = new List(wireProtocol.Decode(bytes)); - // Assert.That(messages.Count, Is.EqualTo(1)); - // var message = messages[0]; - - // Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult)); - // Assert.That(message.Data, Is.EqualTo(originalPackage.ToXml())); - // var newPackage = DeserializePackage(message.Data); - // ComparePackages(newPackage, originalPackage); - //} - - //[TestCase(1)] - //[TestCase(2)] - //[TestCase(5)] - //public void DecodeSplitMessages(int numMessages) - //{ - // const int SPLIT_SIZE = 1000; - - // var originalPackage = new TestPackage("mock-assembly.dll", "notest-assembly.dll"); - // var originalMessage = new TestEngineMessage(MessageCode.CommandResult, originalPackage.ToXml()); - - // var msgBytes = wireProtocol.Encode(originalMessage); - // var msgLength = msgBytes.Length; - // var allBytes = new byte[msgLength * numMessages]; - // for (int i = 0; i < numMessages; i++) - // Array.Copy(msgBytes, 0, allBytes, i * msgLength, msgLength); - - // Console.WriteLine($"Serialized {numMessages} messages in {allBytes.Length} bytes."); - - // var messages = new List(); - - // for (int index = 0; index < allBytes.Length; index += SPLIT_SIZE) - // { - // var bytesToSend = Math.Min(allBytes.Length - index, SPLIT_SIZE); - // var buffer = new byte[bytesToSend]; - // Array.Copy(allBytes, index, buffer, 0, bytesToSend); - // messages.AddRange(wireProtocol.Decode(buffer)); - // Console.WriteLine($"Decoded {bytesToSend} bytes, message count is now {messages.Count}"); - // var expectedCount = (index + bytesToSend) / msgLength; - // Assert.That(messages.Count, Is.EqualTo(expectedCount)); - // } - - // foreach (TestEngineMessage message in messages) - // { - // Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult)); - // var newPackage = DeserializePackage(message.Data); - // ComparePackages(newPackage, originalPackage); - // } - //} - - ////[Test] - //public void DecodeMultipleMessages() - //{ - // var commands = new string[] { "CMD1", "CMD2", "CMD3", "CMD4", "CMD5", "CMD6" }; - - // var stream = new MemoryStream(); - - // foreach (var command in commands) - // { - // var buffer = wireProtocol.Encode(new TestEngineMessage(command, null)); - // stream.Write(buffer, 0, buffer.Length); - // } - - // var received = new List(wireProtocol.Decode(stream.ToArray())); - // Assert.That(received.Count, Is.EqualTo(commands.Length)); - - // for (int i = 0; i < commands.Length; i++) - // Assert.That(received[i].CommandName, Is.EqualTo(commands[i])); - //} + [Test] + public void DecodeSingleMessage() + { + var originalPackage = new TestPackage(new[] { "mock-assembly.dll", "notest-assembly.dll" }); + var originalMessage = new TestEngineMessage(MessageCode.CommandResult, originalPackage.ToXml()); + + var bytes = _wireProtocol.Encode(originalMessage); + Console.WriteLine($"Serialized {bytes.Length} bytes."); + + var messages = new List(_wireProtocol.Decode(bytes)); + Assert.That(messages.Count, Is.EqualTo(1)); + var message = messages[0]; + + Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult)); + Assert.That(message.Data, Is.EqualTo(originalPackage.ToXml())); + //var newPackage = DeserializePackage(message.Data); + //ComparePackages(newPackage, originalPackage); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(5)] + public void DecodeSplitMessages(int numMessages) + { + const int SPLIT_SIZE = 1000; + + var originalPackage = new TestPackage(new[] { "mock-assembly.dll", "notest-assembly.dll" }); + var originalMessage = new TestEngineMessage(MessageCode.CommandResult, originalPackage.ToXml()); + + var msgBytes = _wireProtocol.Encode(originalMessage); + var msgLength = msgBytes.Length; + var allBytes = new byte[msgLength * numMessages]; + for (int i = 0; i < numMessages; i++) + Array.Copy(msgBytes, 0, allBytes, i * msgLength, msgLength); + + Console.WriteLine($"Serialized {numMessages} messages in {allBytes.Length} bytes."); + + var messages = new List(); + + for (int index = 0; index < allBytes.Length; index += SPLIT_SIZE) + { + var bytesToSend = Math.Min(allBytes.Length - index, SPLIT_SIZE); + var buffer = new byte[bytesToSend]; + Array.Copy(allBytes, index, buffer, 0, bytesToSend); + messages.AddRange(_wireProtocol.Decode(buffer)); + Console.WriteLine($"Decoded {bytesToSend} bytes, message count is now {messages.Count}"); + var expectedCount = (index + bytesToSend) / msgLength; + Assert.That(messages.Count, Is.EqualTo(expectedCount)); + } + + foreach (TestEngineMessage message in messages) + { + Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult)); + //var newPackage = DeserializePackage(message.Data); + //ComparePackages(newPackage, originalPackage); + } + } + + [Test] + public void DecodeMultipleMessages() + { + var commands = new string[] { "CMD1", "CMD2", "CMD3", "CMD4", "CMD5", "CMD6" }; + + var stream = new MemoryStream(); + + foreach (var command in commands) + { + var buffer = _wireProtocol.Encode(new TestEngineMessage(command, null)); + stream.Write(buffer, 0, buffer.Length); + } + + var received = new List(_wireProtocol.Decode(stream.ToArray())); + Assert.That(received.Count, Is.EqualTo(commands.Length)); + + for (int i = 0; i < commands.Length; i++) + Assert.That(received[i].Code, Is.EqualTo(commands[i])); + } } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs index 8cbcad75a..44bca376c 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs @@ -8,19 +8,25 @@ namespace NUnit.Engine.Internal { public class TestPackageSerializationTests { - private static readonly string ASSEMBLY_1 = Path.GetFullPath("mock-assembly.dll"); - private static readonly string ASSEMBLY_2 = Path.GetFullPath("notest-assembly.dll"); - private static readonly TestPackage TEST_PACKAGE = new TestPackage(new string[] { "mock-assembly.dll", "notest-assembly.dll" }); - private static readonly string TEST_PACKAGE_XML = - "" + - $"" + - $"" + - ""; - private static readonly string TEST_PACKAGE_XML_WITH_XML_DECLARATION = "" + TEST_PACKAGE_XML; + private const string ASSEMBLY_1 = "mock-assembly.dll"; + private const string ASSEMBLY_2 = "notest-assembly.dll"; + + private static readonly TestPackage TEST_PACKAGE; + private static readonly string TEST_PACKAGE_XML; + private static readonly string TEST_PACKAGE_XML_WITH_XML_DECLARATION; static TestPackageSerializationTests() { + TEST_PACKAGE = new TestPackage(new string[] { "mock-assembly.dll", "notest-assembly.dll" }); TEST_PACKAGE.AddSetting("foo", "bar"); + + TEST_PACKAGE_XML = + $"" + + $"" + + $"" + + ""; + + TEST_PACKAGE_XML_WITH_XML_DECLARATION = "" + TEST_PACKAGE_XML; } [Test] @@ -58,6 +64,7 @@ public void TestPackageRoundTrip() { var xml = TEST_PACKAGE.ToXml(); Console.WriteLine(xml); + Assert.That(xml, Is.EqualTo(TEST_PACKAGE_XML)); var package = new TestPackage().FromXml(xml); ComparePackages(package, TEST_PACKAGE); } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandReturnMessage.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandReturnMessage.cs deleted file mode 100644 index 808b6400b..000000000 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandReturnMessage.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; - -namespace NUnit.Engine.Communication.Messages -{ - [Serializable] - public class CommandReturnMessage : TestEngineMessage - { - public CommandReturnMessage(object returnValue) - { - ReturnValue = returnValue; - } - - public object ReturnValue { get; } - } -} diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs index 8a9958878..3ff682711 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs @@ -22,32 +22,5 @@ public class MessageCode public const string ProgressReport = "PROG"; public const string CommandResult = "RSLT"; - - public static string FromCommand(string command) - { - switch (command) - { - case "CreateRunner": - return CreateRunner; - case "Load": - return LoadCommand; - case "Reload": - return ReloadCommand; - case "Unload": - return UnloadCommand; - case "Explore": - return ExploreCommand; - case "CountTestCases": - return CountCasesCommand; - case "Run": - return RunCommand; - case "RunAsync": - return RunAsyncCommand; - case "Stop": - return RequestStopCommand; - default: - throw new ArgumentException("Invalid command", nameof(command)); - } - } } } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/SocketReader.cs b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/SocketReader.cs index 60223956e..b6272ee9d 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/SocketReader.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/SocketReader.cs @@ -48,23 +48,5 @@ public TestEngineMessage GetNextMessage() return _msgQueue.Dequeue(); } - - /// - /// Get the next message to arrive, which must be of the - /// specified message type. - /// - /// The expected message type - /// A message of type TMessage - /// A message of a different type was received - public TMessage GetNextMessage() where TMessage : TestEngineMessage - { - var receivedMessage = GetNextMessage(); - var expectedMessage = receivedMessage as TMessage; - - if (expectedMessage == null) - throw new InvalidOperationException($"Expected a {typeof(TMessage)} but received a {receivedMessage.GetType()}"); - - return expectedMessage; - } } } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs index 841f8dc7c..cf4ccd673 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Transports/Tcp/TestAgentTcpTransport.cs @@ -79,7 +79,7 @@ private void CommandLoop() while (keepRunning) { - var command = socketReader.GetNextMessage(); + var command = socketReader.GetNextMessage(); switch (command.Code) { @@ -88,25 +88,25 @@ private void CommandLoop() _runner = CreateRunner(package); break; case MessageCode.LoadCommand: - SendResult(_runner.Load()); + SendResult(_runner.Load().Xml.OuterXml); break; case MessageCode.ReloadCommand: - SendResult(_runner.Reload()); + SendResult(_runner.Reload().Xml.OuterXml); break; case MessageCode.UnloadCommand: _runner.Unload(); break; case MessageCode.ExploreCommand: var filter = new TestFilter(command.Data); - SendResult(_runner.Explore(filter)); + SendResult(_runner.Explore(filter).Xml.OuterXml); break; case MessageCode.CountCasesCommand: filter = new TestFilter(command.Data); - SendResult(_runner.CountTestCases(filter)); + SendResult(_runner.CountTestCases(filter).ToString()); break; case MessageCode.RunCommand: filter = new TestFilter(command.Data); - SendResult(_runner.Run(this, filter)); + SendResult(_runner.Run(this, filter).Xml.OuterXml); break; case MessageCode.RunAsyncCommand: @@ -123,16 +123,15 @@ private void CommandLoop() Stop(); } - private void SendResult(object result) + private void SendResult(string result) { - var resultMessage = new CommandReturnMessage(result); + var resultMessage = new TestEngineMessage(MessageCode.CommandResult, result); var bytes = new BinarySerializationProtocol().Encode(resultMessage); _clientSocket.Send(bytes); } public void OnTestEvent(string report) { - //var progressMessage = new ProgressMessage(report); var progressMessage = new TestEngineMessage(MessageCode.ProgressReport, report); var bytes = new BinarySerializationProtocol().Encode(progressMessage); _clientSocket.Send(bytes); diff --git a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs index f50366bdf..efdac42d9 100644 --- a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs +++ b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs @@ -36,7 +36,7 @@ public ITestEngineRunner CreateRunner(TestPackage package) public bool Start() { SendCommandMessage(MessageCode.StartAgent); - return CommandResult(); + return bool.Parse(CommandResult()); } public void Stop() @@ -47,7 +47,7 @@ public void Stop() public TestEngineResult Load() { SendCommandMessage(MessageCode.LoadCommand); - return CommandResult(); + return new TestEngineResult(CommandResult()); } public void Unload() @@ -58,13 +58,13 @@ public void Unload() public TestEngineResult Reload() { SendCommandMessage(MessageCode.ReloadCommand); - return CommandResult(); + return new TestEngineResult(CommandResult()); } public int CountTestCases(TestFilter filter) { SendCommandMessage(MessageCode.CountCasesCommand, filter.Text); - return CommandResult(); + return int.Parse(CommandResult()); } public TestEngineResult Run(ITestEventListener listener, TestFilter filter) @@ -78,8 +78,8 @@ public AsyncTestEngineResult RunAsync(ITestEventListener listener, TestFilter fi { SendCommandMessage(MessageCode.RunAsyncCommand, filter.Text); // TODO: Should we get the async result from the agent or just use our own? - return CommandResult(); - //return new AsyncTestEngineResult(); + //return CommandResult(); + return new AsyncTestEngineResult(); } public void RequestStop() => SendCommandMessage(MessageCode.RequestStopCommand); @@ -89,7 +89,7 @@ public AsyncTestEngineResult RunAsync(ITestEventListener listener, TestFilter fi public TestEngineResult Explore(TestFilter filter) { SendCommandMessage(MessageCode.ExploreCommand, filter.Text); - return CommandResult(); + return new TestEngineResult(CommandResult()); } public void Dispose() @@ -101,9 +101,9 @@ private void SendCommandMessage(string command, string data=null) _socket.Send(_wireProtocol.Encode(new TestEngineMessage(command, data))); } - private T CommandResult() + private string CommandResult() { - return (T)new SocketReader(_socket, _wireProtocol).GetNextMessage().ReturnValue; + return new SocketReader(_socket, _wireProtocol).GetNextMessage().Data; } // Return the result of a test run as a TestEngineResult. ProgressMessages @@ -114,16 +114,14 @@ private TestEngineResult TestRunResult(ITestEventListener listener) while (true) { var receivedMessage = rdr.GetNextMessage(); - var receivedType = receivedMessage.GetType(); - var returnMessage = receivedMessage as CommandReturnMessage; - if (returnMessage != null) - return (TestEngineResult)returnMessage.ReturnValue; + if (receivedMessage.Code == MessageCode.CommandResult) + return new TestEngineResult(receivedMessage.Data); if (receivedMessage.Code == MessageCode.ProgressReport) listener.OnTestEvent(receivedMessage.Data); else - throw new InvalidOperationException($"Expected either a ProgressMessage or a CommandReturnMessage but received a {receivedType}"); + throw new InvalidOperationException($"Expected either a ProgressMessage or a CommandReturnMessage but received a {receivedMessage.Code}"); } } } From 9cfc7b6754f4a7834746267e7899a028ced1aa70 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 16 Mar 2025 14:08:05 -0700 Subject: [PATCH 160/190] Refactor BinarySerializationProtocol --- .../BinarySerializationProtocolTests.cs | 27 ++++++- .../Protocols/BinarySerializationProtocol.cs | 80 ++++++++++++------- .../Protocols/ISerializationProtocol.cs | 2 +- 3 files changed, 77 insertions(+), 32 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs index c79c07c68..6dafef390 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs @@ -50,8 +50,8 @@ public void DecodeSingleMessage() Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult)); Assert.That(message.Data, Is.EqualTo(originalPackage.ToXml())); - //var newPackage = DeserializePackage(message.Data); - //ComparePackages(newPackage, originalPackage); + var newPackage = new TestPackage().FromXml(message.Data); + ComparePackages(newPackage, originalPackage); } [TestCase(1)] @@ -88,8 +88,8 @@ public void DecodeSplitMessages(int numMessages) foreach (TestEngineMessage message in messages) { Assert.That(message.Code, Is.EqualTo(MessageCode.CommandResult)); - //var newPackage = DeserializePackage(message.Data); - //ComparePackages(newPackage, originalPackage); + var newPackage = new TestPackage().FromXml(message.Data); + ComparePackages(newPackage, originalPackage); } } @@ -113,5 +113,24 @@ public void DecodeMultipleMessages() Assert.That(received[i].Code, Is.EqualTo(commands[i])); } + private void ComparePackages(TestPackage newPackage, TestPackage oldPackage) + { + Assert.Multiple(() => + { + Assert.That(newPackage.Name, Is.EqualTo(oldPackage.Name)); + Assert.That(newPackage.FullName, Is.EqualTo(oldPackage.FullName)); + Assert.That(newPackage.Settings.Count, Is.EqualTo(oldPackage.Settings.Count)); + Assert.That(newPackage.SubPackages.Count, Is.EqualTo(oldPackage.SubPackages.Count)); + + foreach (var key in oldPackage.Settings.Keys) + { + Assert.That(newPackage.Settings.ContainsKey(key)); + Assert.That(newPackage.Settings[key], Is.EqualTo(oldPackage.Settings[key])); + } + + for (int i = 0; i < oldPackage.SubPackages.Count; i++) + ComparePackages(newPackage.SubPackages[i], oldPackage.SubPackages[i]); + }); + } } } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs b/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs index 5995853af..2e63a3902 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs @@ -5,20 +5,29 @@ using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; +using System.Text; using NUnit.Engine.Communication.Messages; namespace NUnit.Engine.Communication.Protocols { /// - /// BinarySerializationProtocol serializes messages in the following format: + /// BinarySerializationProtocol serializes messages as an array of bytes in the following format: /// - /// [Message Length (4 bytes)][Serialized Message Content] - /// - /// The message content is in binary form as produced by the .NET BinaryFormatter. - /// Each message of length n is serialized as n + 4 bytes. + /// [] + /// + /// The length of the message is encoded as four bytes, from lowest to highest order. + /// + /// The message code is always four bytes and indicates the type of message. + /// + /// Messages taking an additional data argument encode it in the remaining bytes. The + /// argument length may therefore be calculated as overall length - 8 bytes. Messages + /// without an argument are serialized as 8 bytes. /// public class BinarySerializationProtocol : ISerializationProtocol { + private const int MSG_LENGTH_SIZE = 4; + private const int MSG_CODE_SIZE = 4; + /// /// Maximum length of a message. /// @@ -34,24 +43,30 @@ public class BinarySerializationProtocol : ISerializationProtocol /// /// Message to be serialized /// A byte[] containing the message, serialized as per the protocol. - public byte[] Encode(object message) + public byte[] Encode(TestEngineMessage message) { - //Serialize the message to a byte array - var serializedMessage = SerializeMessage(message); + string code = message.Code; + string data = message.Data; + Encoding utf8 = Encoding.UTF8; + + // TODO: Compile time check + if (utf8.GetByteCount(code) != MSG_CODE_SIZE) + throw new ArgumentException($"Invalid message code {code}"); + + int dataLength = message.Data != null ? utf8.GetByteCount(message.Data) : 0; + int messageLength = dataLength + MSG_CODE_SIZE; - //Check for message length - var messageLength = serializedMessage.Length; + //Check message length if (messageLength > MAX_MESSAGE_LENGTH) - { throw new Exception("Message is too big (" + messageLength + " bytes). Max allowed length is " + MAX_MESSAGE_LENGTH + " bytes."); - } //Create a byte array including the length of the message (4 bytes) and serialized message content - var bytes = new byte[messageLength + 4]; + var bytes = new byte[messageLength + MSG_LENGTH_SIZE]; WriteInt32(bytes, 0, messageLength); - Array.Copy(serializedMessage, 0, bytes, 4, messageLength); + utf8.GetBytes(code, 0, code.Length, bytes, MSG_LENGTH_SIZE); + if (dataLength > 0) + utf8.GetBytes(data, 0, data.Length, bytes, MSG_LENGTH_SIZE + MSG_CODE_SIZE); - //Return serialized message by this protocol return bytes; } @@ -139,7 +154,7 @@ private bool ReadSingleMessage(ICollection messages) //If stream has less than 4 bytes, that means we can not even read length of the message //So, return false to wait more for bytes from remorte application. - if (_receiveMemoryStream.Length < 4) + if (_receiveMemoryStream.Length < MSG_LENGTH_SIZE) { return false; } @@ -147,15 +162,13 @@ private bool ReadSingleMessage(ICollection messages) //Read length of the message var messageLength = ReadInt32(_receiveMemoryStream); if (messageLength > MAX_MESSAGE_LENGTH) - { throw new Exception("Message is too big (" + messageLength + " bytes). Max allowed length is " + MAX_MESSAGE_LENGTH + " bytes."); - } //If message is zero-length (It must not be but good approach to check it) if (messageLength == 0) { //if no more bytes, return immediately - if (_receiveMemoryStream.Length == 4) + if (_receiveMemoryStream.Length == MSG_LENGTH_SIZE) { _receiveMemoryStream = new MemoryStream(); //Clear the stream return false; @@ -164,12 +177,12 @@ private bool ReadSingleMessage(ICollection messages) //Create a new memory stream from current except first 4-bytes. var bytes = _receiveMemoryStream.ToArray(); _receiveMemoryStream = new MemoryStream(); - _receiveMemoryStream.Write(bytes, 4, bytes.Length - 4); + _receiveMemoryStream.Write(bytes, MSG_LENGTH_SIZE, bytes.Length - MSG_LENGTH_SIZE); return true; } //If all bytes of the message is not received yet, return to wait more bytes - if (_receiveMemoryStream.Length < (4 + messageLength)) + if (_receiveMemoryStream.Length < (MSG_LENGTH_SIZE + messageLength)) { _receiveMemoryStream.Position = _receiveMemoryStream.Length; return false; @@ -178,7 +191,15 @@ private bool ReadSingleMessage(ICollection messages) //Read bytes of serialized message and deserialize it var serializedMessageBytes = ReadByteArray(_receiveMemoryStream, messageLength); - messages.Add((TestEngineMessage)DeserializeMessage(serializedMessageBytes)); + Encoding utf8 = Encoding.UTF8; + + string code = utf8.GetString(serializedMessageBytes, 0, MSG_CODE_SIZE); + + string data = messageLength > MSG_CODE_SIZE + ? utf8.GetString(serializedMessageBytes, MSG_CODE_SIZE, messageLength - MSG_CODE_SIZE) + : null; + + messages.Add(new TestEngineMessage(code, data)); //Read remaining bytes to an array var remainingBytes = ReadByteArray(_receiveMemoryStream, (int)(_receiveMemoryStream.Length - (4 + messageLength))); @@ -188,7 +209,7 @@ private bool ReadSingleMessage(ICollection messages) _receiveMemoryStream.Write(remainingBytes, 0, remainingBytes.Length); //Return true to re-call this method to try to read next message - return (remainingBytes.Length > 4); + return (remainingBytes.Length > MSG_LENGTH_SIZE); } /// @@ -212,10 +233,15 @@ private static void WriteInt32(byte[] buffer, int startIndex, int number) private static int ReadInt32(Stream stream) { var buffer = ReadByteArray(stream, 4); - return ((buffer[0] << 24) | - (buffer[1] << 16) | - (buffer[2] << 8) | - (buffer[3]) + return ReadInt32(buffer, 0); + } + + private static int ReadInt32(byte[] buffer, int index) + { + return ((buffer[index] << 24) | + (buffer[index + 1] << 16) | + (buffer[index + 2] << 8) | + (buffer[index + 3]) ); } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Protocols/ISerializationProtocol.cs b/src/NUnitEngine/nunit.engine.core/Communication/Protocols/ISerializationProtocol.cs index 89b0511dc..9b704380f 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Protocols/ISerializationProtocol.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Protocols/ISerializationProtocol.cs @@ -12,7 +12,7 @@ public interface ISerializationProtocol /// /// Message to be serialized /// A byte[] containing the message, serialized as per the protocol. - byte[] Encode(object message); + byte[] Encode(TestEngineMessage message); /// /// Accept an array of bytes and deserialize the messages that are found. From 671710c83800c78c1de707f9a5296821d36e75c8 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 16 Mar 2025 14:33:26 -0700 Subject: [PATCH 161/190] Remove dependency on BinaryFormatter --- build.cake | 3 +- choco/nunit-console-runner.nuspec | 1 - nuget/runners/nunit.console-runner.nuspec | 1 - .../nunit-agent/nunit-agent.csproj | 1 - .../nunit.engine.core.tests.csproj | 1 - .../Protocols/BinarySerializationProtocol.cs | 76 +++++++++---------- .../nunit.engine.core.csproj | 4 - 7 files changed, 38 insertions(+), 49 deletions(-) diff --git a/build.cake b/build.cake index 70695513f..27e54d99e 100644 --- a/build.cake +++ b/build.cake @@ -65,7 +65,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE), - HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE).WithFile("System.Runtime.Serialization.Formatters.dll") + HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE) }, symbols: new PackageCheck[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), @@ -126,7 +126,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net9.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) - .AndFile("System.Runtime.Serialization.Formatters.dll") }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index dab777ccb..0f2f8a30d 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -85,7 +85,6 @@ - diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 85772b506..7e637f6af 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -78,7 +78,6 @@ - diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index 7b9c302f6..542dbc8d0 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -9,7 +9,6 @@ false ..\..\..\bin\$(Configuration)\agents\ false - true diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index f134f5246..76037d0e5 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -9,7 +9,6 @@ Full false false - true diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs b/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs index 2e63a3902..9fb8fe6fb 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; using System.Text; using NUnit.Engine.Communication.Messages; @@ -101,43 +99,43 @@ public void Reset() } } - /// - /// Serializes a message to a byte array to send to remote application. - /// - /// Message to be serialized - /// - /// A byte[] containing the message itself, without a prefixed - /// length, serialized according to the protocol. - /// - internal byte[] SerializeMessage(object message) - { - using (var memoryStream = new MemoryStream()) - { - new BinaryFormatter().Serialize(memoryStream, message); - return memoryStream.ToArray(); - } - } - - /// - /// Deserializes a message contained in a byte array. - /// - /// A byte[] containing just the message, without a length prefix - /// An object representing the message encoded in the byte array - internal object DeserializeMessage(byte[] bytes) - { - using (var memoryStream = new MemoryStream(bytes)) - { - try - { - return new BinaryFormatter().Deserialize(memoryStream); - } - catch (Exception exception) - { - Reset(); // reset the received memory stream before the exception is rethrown - otherwise the same erroneous message is received again and again - throw new SerializationException("error while deserializing message", exception); - } - } - } + ///// + ///// Serializes a message to a byte array to send to remote application. + ///// + ///// Message to be serialized + ///// + ///// A byte[] containing the message itself, without a prefixed + ///// length, serialized according to the protocol. + ///// + //internal byte[] SerializeMessage(object message) + //{ + // using (var memoryStream = new MemoryStream()) + // { + // new BinaryFormatter().Serialize(memoryStream, message); + // return memoryStream.ToArray(); + // } + //} + + ///// + ///// Deserializes a message contained in a byte array. + ///// + ///// A byte[] containing just the message, without a length prefix + ///// An object representing the message encoded in the byte array + //internal object DeserializeMessage(byte[] bytes) + //{ + // using (var memoryStream = new MemoryStream(bytes)) + // { + // try + // { + // return new BinaryFormatter().Deserialize(memoryStream); + // } + // catch (Exception exception) + // { + // Reset(); // reset the received memory stream before the exception is rethrown - otherwise the same erroneous message is received again and again + // throw new SerializationException("error while deserializing message", exception); + // } + // } + //} /// /// This method tries to read a single message and add to the messages collection. diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index 7df5a6aa2..6e22a717f 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -32,10 +32,6 @@ - - - - From a08196a8b1b4af5fdc87ad65f66a3740ce8e2ac5 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 17 Mar 2025 09:27:29 -0700 Subject: [PATCH 162/190] Changes from code review --- .../nunit.engine.api/TestPackage.cs | 42 +++++++++---------- .../Messages/TestEngineMessage.cs | 4 +- .../Protocols/BinarySerializationProtocol.cs | 38 ----------------- .../Internal/TestPackageExtensions.cs | 14 ++++--- 4 files changed, 28 insertions(+), 70 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.api/TestPackage.cs b/src/NUnitEngine/nunit.engine.api/TestPackage.cs index 7c6e0e2e6..6a743cf0f 100644 --- a/src/NUnitEngine/nunit.engine.api/TestPackage.cs +++ b/src/NUnitEngine/nunit.engine.api/TestPackage.cs @@ -150,13 +150,11 @@ public XmlSchema GetSchema() } /// - public void ReadXml(XmlReader xmlReader) => ReadPackageFromXml(this, xmlReader); - - // Read a single package and its subpackages from the xml reader - private void ReadPackageFromXml(TestPackage package, XmlReader xmlReader) + public void ReadXml(XmlReader xmlReader) { - package.ID = xmlReader.GetAttribute("id"); - package.FullName = xmlReader.GetAttribute("fullname"); + var currentPackage = this; + currentPackage.ID = xmlReader.GetAttribute("id"); + currentPackage.FullName = xmlReader.GetAttribute("fullname"); if (!xmlReader.IsEmptyElement) { while (xmlReader.Read()) @@ -167,16 +165,17 @@ private void ReadPackageFromXml(TestPackage package, XmlReader xmlReader) switch(xmlReader.Name) { case "Settings": + // We don't use AddSettings, which copies settings downward. + // Instead, each package handles it's own settings. while (xmlReader.MoveToNextAttribute()) - package.AddSetting(xmlReader.Name, xmlReader.Value); - + currentPackage.Settings.Add(xmlReader.Name, xmlReader.Value); xmlReader.MoveToElement(); break; case "TestPackage": - TestPackage testPackage = new TestPackage(); - ReadPackageFromXml(testPackage, xmlReader); - package.SubPackages.Add(testPackage); + TestPackage subPackage = new TestPackage(); + subPackage.ReadXml(xmlReader); + currentPackage.SubPackages.Add(subPackage); break; } break; @@ -191,37 +190,34 @@ private void ReadPackageFromXml(TestPackage package, XmlReader xmlReader) } } - package.SubPackages.Add(package); + throw new Exception("Invalid XML: TestPackage Element not terminated."); } } /// - public void WriteXml(XmlWriter xmlWriter) => WritePackageToXml(this, xmlWriter); - - // Write a single package and its subpackages to the xmlWriter - private void WritePackageToXml(TestPackage package, XmlWriter xmlWriter) + public void WriteXml(XmlWriter xmlWriter) { // Write ID and FullName - xmlWriter.WriteAttributeString("id", package.ID); - if (package.FullName != null) - xmlWriter.WriteAttributeString("fullname", package.FullName); + xmlWriter.WriteAttributeString("id", ID); + if (FullName != null) + xmlWriter.WriteAttributeString("fullname", FullName); // Write Settings - if (package.Settings.Count != 0) + if (Settings.Count != 0) { xmlWriter.WriteStartElement("Settings"); - foreach (KeyValuePair setting in package.Settings) + foreach (KeyValuePair setting in Settings) xmlWriter.WriteAttributeString(setting.Key, setting.Value.ToString()); xmlWriter.WriteEndElement(); } // Write any SubPackages recursively - foreach (TestPackage subPackage in package.SubPackages) + foreach (TestPackage subPackage in SubPackages) { xmlWriter.WriteStartElement("TestPackage"); - WritePackageToXml(subPackage, xmlWriter); + subPackage.WriteXml(xmlWriter); xmlWriter.WriteEndElement(); } } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs b/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs index 7437632d9..db5aed602 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Messages/TestEngineMessage.cs @@ -5,7 +5,7 @@ namespace NUnit.Engine.Communication.Messages { [Serializable] - public class TestEngineMessage + public sealed class TestEngineMessage { public TestEngineMessage(string code, string data) { @@ -13,8 +13,6 @@ public TestEngineMessage(string code, string data) Data = data; } - protected TestEngineMessage() { } - public string Code { get; } public string Data { get; } } diff --git a/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs b/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs index 9fb8fe6fb..4a824797d 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs @@ -99,44 +99,6 @@ public void Reset() } } - ///// - ///// Serializes a message to a byte array to send to remote application. - ///// - ///// Message to be serialized - ///// - ///// A byte[] containing the message itself, without a prefixed - ///// length, serialized according to the protocol. - ///// - //internal byte[] SerializeMessage(object message) - //{ - // using (var memoryStream = new MemoryStream()) - // { - // new BinaryFormatter().Serialize(memoryStream, message); - // return memoryStream.ToArray(); - // } - //} - - ///// - ///// Deserializes a message contained in a byte array. - ///// - ///// A byte[] containing just the message, without a length prefix - ///// An object representing the message encoded in the byte array - //internal object DeserializeMessage(byte[] bytes) - //{ - // using (var memoryStream = new MemoryStream(bytes)) - // { - // try - // { - // return new BinaryFormatter().Deserialize(memoryStream); - // } - // catch (Exception exception) - // { - // Reset(); // reset the received memory stream before the exception is rethrown - otherwise the same erroneous message is received again and again - // throw new SerializationException("error while deserializing message", exception); - // } - // } - //} - /// /// This method tries to read a single message and add to the messages collection. /// diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs index 49fc54c31..ebebb3729 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs @@ -46,12 +46,14 @@ private static void AccumulatePackages(TestPackage package, IList s public static string ToXml(this TestPackage package) { var writer = new StringWriter(); - var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings() { OmitXmlDeclaration = true }); - var serializer = new XmlSerializer(typeof(TestPackage)); - serializer.Serialize(xmlWriter, package); - xmlWriter.Flush(); - xmlWriter.Close(); + using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings() { OmitXmlDeclaration = true })) + { + xmlWriter.WriteStartElement(nameof(TestPackage)); + package.WriteXml(xmlWriter); + xmlWriter.WriteEndElement(); + } + return writer.ToString(); } @@ -80,7 +82,7 @@ bool ReadTestPackageElement() { while (xmlReader.Read()) if (xmlReader.NodeType == XmlNodeType.Element) - return xmlReader.Name == "TestPackage"; + return xmlReader.Name == nameof(TestPackage); return false; } } From 818e0a9414ce03afa6f1ab65d94a2191f1a6524f Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Tue, 18 Mar 2025 08:07:31 +0800 Subject: [PATCH 163/190] Simplify ReadXml and update test to proof settings are only added to appropriate package --- src/NUnitEngine/nunit.engine.api/TestPackage.cs | 9 ++++----- .../Internal/TestPackageSerializationTests.cs | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.api/TestPackage.cs b/src/NUnitEngine/nunit.engine.api/TestPackage.cs index 6a743cf0f..f9b8d369f 100644 --- a/src/NUnitEngine/nunit.engine.api/TestPackage.cs +++ b/src/NUnitEngine/nunit.engine.api/TestPackage.cs @@ -152,9 +152,8 @@ public XmlSchema GetSchema() /// public void ReadXml(XmlReader xmlReader) { - var currentPackage = this; - currentPackage.ID = xmlReader.GetAttribute("id"); - currentPackage.FullName = xmlReader.GetAttribute("fullname"); + ID = xmlReader.GetAttribute("id"); + FullName = xmlReader.GetAttribute("fullname"); if (!xmlReader.IsEmptyElement) { while (xmlReader.Read()) @@ -168,14 +167,14 @@ public void ReadXml(XmlReader xmlReader) // We don't use AddSettings, which copies settings downward. // Instead, each package handles it's own settings. while (xmlReader.MoveToNextAttribute()) - currentPackage.Settings.Add(xmlReader.Name, xmlReader.Value); + Settings.Add(xmlReader.Name, xmlReader.Value); xmlReader.MoveToElement(); break; case "TestPackage": TestPackage subPackage = new TestPackage(); subPackage.ReadXml(xmlReader); - currentPackage.SubPackages.Add(subPackage); + SubPackages.Add(subPackage); break; } break; diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs index 44bca376c..848ef6f3a 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs @@ -17,12 +17,13 @@ public class TestPackageSerializationTests static TestPackageSerializationTests() { - TEST_PACKAGE = new TestPackage(new string[] { "mock-assembly.dll", "notest-assembly.dll" }); + TEST_PACKAGE = new TestPackage(new string[] { ASSEMBLY_1, ASSEMBLY_2 }); TEST_PACKAGE.AddSetting("foo", "bar"); + TEST_PACKAGE.SubPackages[0].AddSetting("cpu", "x86"); TEST_PACKAGE_XML = $"" + - $"" + + $"" + $"" + ""; From c36dbf9d985b12ac9ce74f610249936da69cc554 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 8 Apr 2025 09:17:24 -0700 Subject: [PATCH 164/190] Update teamcity extension to version 1.0.10 --- build.cake | 12 ++++++++++-- .../nunit.console-runner-with-extensions.nuspec | 2 +- package-tests.cake | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/build.cake b/build.cake index 27e54d99e..9d560c76e 100644 --- a/build.cake +++ b/build.cake @@ -82,7 +82,15 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { NUnitConsoleNuGetPackage = new NuGetPackage( id: "NUnit.Console", source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner-with-extensions.nuspec", - checks: new PackageCheck[] { HasFile("LICENSE.txt") }), + checks: new PackageCheck[] { + HasFile("LICENSE.txt"), + // Check proper extension is in a sibling directory since we + // don't yet have the 'HasExtension' predicate. + HasDirectory("../NUnit.Extension.NUnitProjectLoader.3.8.0"), + HasDirectory("../NUnit.Extension.NUnitV2Driver.3.9.0"), + HasDirectory("../NUnit.Extension.NUnitV2ResultWriter.3.8.0"), + HasDirectory("../NUnit.Extension.TeamCityEventListener.1.0.10"), + HasDirectory("../NUnit.Extension.VSProjectLoader.3.9.0") }), NUnitConsoleRunnerNetCorePackage = new DotNetToolPackage( id: "NUnit.ConsoleRunner.NetCore", @@ -120,7 +128,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("NUnit.Extension.NUnitProjectLoader.3.8.0"), HasDirectory("NUnit.Extension.NUnitV2Driver.3.9.0"), HasDirectory("NUnit.Extension.NUnitV2ResultWriter.3.8.0"), - HasDirectory("NUnit.Extension.TeamCityEventListener.1.0.9"), + HasDirectory("NUnit.Extension.TeamCityEventListener.1.0.10"), HasDirectory("NUnit.Extension.VSProjectLoader.3.9.0"), HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), diff --git a/nuget/runners/nunit.console-runner-with-extensions.nuspec b/nuget/runners/nunit.console-runner-with-extensions.nuspec index 94ffdd643..f38482c47 100644 --- a/nuget/runners/nunit.console-runner-with-extensions.nuspec +++ b/nuget/runners/nunit.console-runner-with-extensions.nuspec @@ -36,7 +36,7 @@ - + diff --git a/package-tests.cake b/package-tests.cake index bcee421f8..c76f01798 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -1,6 +1,6 @@ public static class Extensions { - // Extensions used in tests, with version specified. + // Define the latest version of every extension bundled and/or used in tests. public static ExtensionSpecifier NUnitV2Driver = new ExtensionSpecifier( "NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver", "3.9.0"); public static ExtensionSpecifier NUnitProjectLoader = new ExtensionSpecifier( @@ -10,7 +10,7 @@ public static class Extensions public static ExtensionSpecifier NUnitV2ResultWriter = new ExtensionSpecifier( "NUnit.Extension.NUnitV2ResultWriter", "nunit-extension-nunit-v2-result-writer", "3.8.0"); public static ExtensionSpecifier TeamCityEventListener = new ExtensionSpecifier( - "NUnit.Extension.TeamCityEventListener", "nunit-extension-teamcity-event-listener", "1.0.9"); + "NUnit.Extension.TeamCityEventListener", "nunit-extension-teamcity-event-listener", "1.0.10"); } public static class PackageTests From 890beb6e5c0fc11922bfabe5e444b2b360654b7e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 10 Apr 2025 15:38:38 -0700 Subject: [PATCH 165/190] Update to recipe version 1.4.0-alpha.11 --- build.cake | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.cake b/build.cake index 9d560c76e..69212f5e3 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.7 +#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.11 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -86,11 +86,11 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasFile("LICENSE.txt"), // Check proper extension is in a sibling directory since we // don't yet have the 'HasExtension' predicate. - HasDirectory("../NUnit.Extension.NUnitProjectLoader.3.8.0"), - HasDirectory("../NUnit.Extension.NUnitV2Driver.3.9.0"), - HasDirectory("../NUnit.Extension.NUnitV2ResultWriter.3.8.0"), - HasDirectory("../NUnit.Extension.TeamCityEventListener.1.0.10"), - HasDirectory("../NUnit.Extension.VSProjectLoader.3.9.0") }), + HasDependency(Extensions.NUnitProjectLoader.NuGetPackage), + HasDependency(Extensions.NUnitV2Driver.NuGetPackage), + HasDependency(Extensions.NUnitV2ResultWriter.NuGetPackage), + HasDependency(Extensions.TeamCityEventListener.NuGetPackage), + HasDependency(Extensions.VSProjectLoader.NuGetPackage) }), NUnitConsoleRunnerNetCorePackage = new DotNetToolPackage( id: "NUnit.ConsoleRunner.NetCore", From 47363f0330af3ef8d2e5676418f87d1f359fad33 Mon Sep 17 00:00:00 2001 From: east1k Date: Wed, 21 May 2025 14:44:19 +0500 Subject: [PATCH 166/190] Fix issue https://github.com/nunit/nunit-console/issues/1680 Changes: - If the filter is "tests" and the operator is regex, take the value without normalization. - If the filter is "tests" and the operator is comparison, take the value using "GetTestName". - GetTestName takes the value to the end, rather than truncating at the first closing parenthesis. - GetTestName removes extra spaces everywhere except in quoted arguments. - GetTestName ignores escaped quotes. --- .../Services/TestSelectionParserTests.cs | 10 +++++ .../Services/TestSelectionParser.cs | 40 +++++-------------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs index 334e690f7..e86a7a29a 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs @@ -87,6 +87,16 @@ public void TestParser_InvalidInput(string input, Type type) new TestCaseData("test=My.Test.Fixture.Method(\"\")", "My.Test.Fixture.Method("<xyz>")"), new TestCaseData("test==Issue1510.TestSomething ( Option1 , \"ABC\" ) ", "Issue1510.TestSomething(Option1,"ABC")"), new TestCaseData("test==Issue1510.TestSomething ( Option1 , \"A B C\" ) ", "Issue1510.TestSomething(Option1,"A B C")"), + new TestCaseData("test == namespace.class(1).test1(1)", "namespace.class(1).test1(1)"), + new TestCaseData("test == \"namespace.class(1).test1(1)\"", "namespace.class(1).test1(1)"), + new TestCaseData("test == 'namespace.class(1).test1(1)'", "namespace.class(1).test1(1)"), + new TestCaseData("test =~ \"(namespace\\.test1\\(1\\)|namespace\\.test2\\(2\\))\"", "(namespace.test1(1)|namespace.test2(2))"), + new TestCaseData("test =~ '(namespace\\.test1\\(1\\)|namespace\\.test2\\(2\\))'", "(namespace.test1(1)|namespace.test2(2))"), + new TestCaseData("test =~ /(namespace\\.test1\\(1\\)|namespace\\.test2\\(2\\))/", "(namespace.test1(1)|namespace.test2(2))"), + new TestCaseData("test =~ \"(namespace1|namespace2)\\.test1\"", "(namespace1|namespace2).test1"), + new TestCaseData("test =~ '(namespace1|namespace2)\\.test1'", "(namespace1|namespace2).test1"), + new TestCaseData("test =~ /(namespace1|namespace2)\\.test1/", "(namespace1|namespace2).test1"), + new TestCaseData("test='My.Test.Fixture.Method(\" A \\\\\" B \\\\\" C \")'", "My.Test.Fixture.Method(" A \\" B \\" C ")"), // And Filter new TestCaseData("cat==Urgent && test=='My.Tests'", "UrgentMy.Tests"), new TestCaseData("cat==Urgent and test=='My.Tests'", "UrgentMy.Tests"), diff --git a/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs b/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs index 8873d172c..181315831 100644 --- a/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs +++ b/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs @@ -125,7 +125,9 @@ public string ParseFilterElement() { case "test": op = Expect(REL_OPS); - rhs = GetTestName(); + rhs = op == MATCH_OP || op == NOMATCH_OP + ? Expect(TokenKind.String, TokenKind.Word) + : GetTestName(); return EmitFilterElement(lhs, op, rhs); case "cat": @@ -163,35 +165,15 @@ private Token GetTestName() if (result.Kind == TokenKind.String) { - int index = result.Text.IndexOf('('); - - if (index < 0) - return result; - - // Remove white space around arguments - string testName = result.Text; - sb = new StringBuilder(testName.Substring(0, index).Trim()); - sb.Append('('); - bool done = false; - - while (++index < testName.Length && !done) + var inQuotes = false; + var inEscape = false; + foreach (var ch in result.Text) { - char ch = testName[index]; - switch (ch) - { - case '"': - sb.Append(ch); - while (++index < testName.Length && testName[index] != '"') - sb.Append(testName[index]); - sb.Append('"'); - break; - case ' ': - break; - default: - sb.Append(ch); - done = ch == ')'; - break; - } + if (ch == ' ' && !inQuotes) + continue; + sb.Append(ch); + inQuotes = (!inQuotes && ch == '"') || (inQuotes && ch != '"') || (inQuotes && ch == '"' && inEscape); + inEscape = inQuotes && !inEscape && ch == '\\'; } } else From df5aa6ccf2c62fe11b4f34ff7e3418cfedf129fb Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 23 May 2025 11:42:38 -0700 Subject: [PATCH 167/190] Ensure path to exe is quoted when running dotnet agents --- src/NUnitEngine/nunit.engine/Services/AgentProcess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index e9bf77464..ef8de440f 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -64,7 +64,7 @@ public AgentProcess(TestAgency agency, TestPackage package, Guid agentId) else if (TargetRuntime.Runtime == RuntimeType.NetCore) { StartInfo.FileName = "dotnet"; - StartInfo.Arguments = $"{AgentExePath} {AgentArgs}"; + StartInfo.Arguments = $"\"{AgentExePath}\" {AgentArgs}"; StartInfo.LoadUserProfile = loadUserProfile; // TODO: Remove the windows limitation and the use of a hard-coded path. From e4bd2744f5ee34f56173d836ddec9250bcf3f9d1 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 26 May 2025 17:13:45 -0700 Subject: [PATCH 168/190] Ensure that latest extension version is selected if multiple versions present --- NUnitConsole.sln | 21 ++- build.cake | 3 +- package-tests.cake | 16 +- src/Directory.Build.props | 2 +- .../nunit.engine.api/nunit.engine.api.csproj | 1 - .../Internal/ExtensionAssemblyTrackerTests.cs | 85 ++++++---- .../Services/ExtensionManagerTests.cs | 42 ++--- .../Extensibility/ExtensionAssembly.cs | 2 +- .../Internal/ExtensionAssemblyTracker.cs | 41 ++++- .../Services/ExtensionManager.cs | 63 +++---- .../{ => 1.0}/FakeExtensions.cs | 0 .../1.0/FakeExtensionsV1.csproj | 18 ++ .../FakeExtensions/2.0/FakeExtensions.cs | 157 ++++++++++++++++++ .../2.0/FakeExtensionsV2.csproj | 18 ++ .../FakeExtensions/Directory.Build.props | 11 ++ .../FakeExtensions/FakeExtensions.csproj | 15 -- .../FakeExtensions/fake-extensions.addins | 3 +- 17 files changed, 370 insertions(+), 128 deletions(-) rename src/TestData/FakeExtensions/{ => 1.0}/FakeExtensions.cs (100%) create mode 100644 src/TestData/FakeExtensions/1.0/FakeExtensionsV1.csproj create mode 100644 src/TestData/FakeExtensions/2.0/FakeExtensions.cs create mode 100644 src/TestData/FakeExtensions/2.0/FakeExtensionsV2.csproj create mode 100644 src/TestData/FakeExtensions/Directory.Build.props delete mode 100644 src/TestData/FakeExtensions/FakeExtensions.csproj diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 5150d355e..4204f4c9b 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -22,8 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution GitVersion.yml = GitVersion.yml global.json = global.json LICENSE.txt = LICENSE.txt - NetCoreTests.nunit = NetCoreTests.nunit MixedTests.nunit = MixedTests.nunit + NetCoreTests.nunit = NetCoreTests.nunit NOTICES.txt = NOTICES.txt NuGet.config = NuGet.config nunit.ico = nunit.ico @@ -148,7 +148,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppContextTest", "src\TestD EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly-v2", "src\TestData\mock-assembly-v2\mock-assembly-v2.csproj", "{AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FakeExtensions", "src\TestData\FakeExtensions\FakeExtensions.csproj", "{D6C217E0-BFB7-4C80-8D50-C969F46EBC59}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FakeExtensionsV1", "src\TestData\FakeExtensions\1.0\FakeExtensionsV1.csproj", "{3AA135F1-CF80-C1D9-89FF-1DF30F567CA1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FakeExtensionsV2", "src\TestData\FakeExtensions\2.0\FakeExtensionsV2.csproj", "{E3A8037B-83EF-CB79-3EED-492C89F47BF9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -232,10 +234,14 @@ Global {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78}.Release|Any CPU.Build.0 = Release|Any CPU - {D6C217E0-BFB7-4C80-8D50-C969F46EBC59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D6C217E0-BFB7-4C80-8D50-C969F46EBC59}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D6C217E0-BFB7-4C80-8D50-C969F46EBC59}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D6C217E0-BFB7-4C80-8D50-C969F46EBC59}.Release|Any CPU.Build.0 = Release|Any CPU + {3AA135F1-CF80-C1D9-89FF-1DF30F567CA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AA135F1-CF80-C1D9-89FF-1DF30F567CA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AA135F1-CF80-C1D9-89FF-1DF30F567CA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AA135F1-CF80-C1D9-89FF-1DF30F567CA1}.Release|Any CPU.Build.0 = Release|Any CPU + {E3A8037B-83EF-CB79-3EED-492C89F47BF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3A8037B-83EF-CB79-3EED-492C89F47BF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3A8037B-83EF-CB79-3EED-492C89F47BF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3A8037B-83EF-CB79-3EED-492C89F47BF9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -271,7 +277,8 @@ Global {58E18ACC-1F7E-4395-817E-E7EF943E0C77} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {E43A3E4B-B050-471B-B43C-0DF60FD44376} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} - {D6C217E0-BFB7-4C80-8D50-C969F46EBC59} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {3AA135F1-CF80-C1D9-89FF-1DF30F567CA1} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} + {E3A8037B-83EF-CB79-3EED-492C89F47BF9} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/build.cake b/build.cake index 69212f5e3..c513734e2 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.4.0-alpha.11 +#load nuget:?package=NUnit.Cake.Recipe&version=1.5.0-alpha.4 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake @@ -193,7 +193,6 @@ public class ConsoleRunnerSelfTester : TestRunner, IPackageTestRunner public int RunPackageTest(string arguments, bool redirectOutput = false) { - Console.WriteLine("Running package test"); return base.RunPackageTest(_executablePath, new ProcessSettings { Arguments = arguments, RedirectStandardOutput = redirectOutput }); } } diff --git a/package-tests.cake b/package-tests.cake index c76f01798..f2208c991 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -256,21 +256,23 @@ public static class PackageTests { Description = "List Extensions shows none installed", Arguments = "--list-extensions", - OutputCheck = new OutputDoesNotContain("Extension:"), + ExpectedOutput = new OutputCheck[] { DoesNotContain("Extension:") } }); StandardAndNetCoreLists.Add(new PackageTest(1, "ExtensionsInstalledFromAddedDirectory") { Description = "List Extensions shows extension from added directory", Arguments = "--extensionDirectory ../../src/TestData/FakeExtensions --list-extensions", - OutputCheck = new OutputContains("Extension:", exactly: 5) + ExpectedOutput = new OutputCheck[] { + Contains("Extension:", exactly: 5), + Contains("fakesv2", exactly: 5) } }); ZipRunnerTests.Add(new PackageTest(1, "BundledExtensionsInstalled") { Description = "List Extensions shows bundled extensions", Arguments = "--list-extensions", - OutputCheck = new OutputContains("Extension:", exactly: 5) + ExpectedOutput = new OutputCheck[] { Contains("Extension:", exactly: 5) } }); ////////////////////////////////////////////////////////////////////// @@ -349,7 +351,7 @@ public static class PackageTests Arguments = "testdata/net462/mock-assembly.dll --teamcity", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, - OutputCheck = new OutputContains("##teamcity") + ExpectedOutput = new OutputCheck[] { Contains("##teamcity") } }); // TeamCity Event Listener Test @@ -359,7 +361,7 @@ public static class PackageTests Arguments = "testdata/net462/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener", ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, - OutputCheck = new OutputContains("##teamcity") + ExpectedOutput = new[] { Contains("##teamcity") } }); AllLists.Add(new PackageTest(1, "Net60TeamCityListenerTest1") @@ -368,7 +370,7 @@ public static class PackageTests Arguments = "testdata/net6.0/mock-assembly.dll --teamcity", ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, - OutputCheck = new OutputContains("##teamcity") + ExpectedOutput = new[] { Contains("##teamcity") } }); // TeamCity Event Listener Test @@ -378,7 +380,7 @@ public static class PackageTests Arguments = "testdata/net6.0/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener", ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, - OutputCheck = new OutputContains("##teamcity") + ExpectedOutput = new[] { Contains("##teamcity") } }); // V2 Framework Driver Tests diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 983510678..ac43a9f29 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -10,7 +10,7 @@ - 3.99.0.0 + 3.0.0.0 3.99.0.0 3.99.0.0-VSIDE diff --git a/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj b/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj index aeeea093c..a0bda5810 100644 --- a/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj +++ b/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj @@ -15,7 +15,6 @@ NUnit Engine API ($(TargetFramework)) Defines the interfaces used to access the NUnit Engine 3.0.0.0 - 3.99.0.0 diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs index dc1e6bd18..a9ab79bce 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Reflection; using NUnit.Engine.Extensibility; using NUnit.Framework; @@ -14,7 +16,8 @@ public class ExtensionAssemblyTrackerTests private static readonly string THIS_ASSEMBLY_PATH = THIS_ASSEMBLY.Location; private static readonly string THIS_ASSEMBLY_NAME = THIS_ASSEMBLY.GetName().Name; private static readonly Version THIS_ASSEMBLY_VERSION = THIS_ASSEMBLY.GetName().Version; - private static readonly ExtensionAssembly TEST_EXTENSION_ASSEMBLY = + + private static readonly ExtensionAssembly TEST_EXTENSION_ASSEMBLY = new ExtensionAssembly(THIS_ASSEMBLY_PATH, false, THIS_ASSEMBLY_NAME, THIS_ASSEMBLY_VERSION); private ExtensionAssemblyTracker _tracker; @@ -28,52 +31,74 @@ public void CreateTracker() [Test] public void AddToList() { - _tracker.Add(TEST_EXTENSION_ASSEMBLY); + _tracker.AddOrUpdate(TEST_EXTENSION_ASSEMBLY); Assert.That(_tracker.Count, Is.EqualTo(1)); - Assert.That(_tracker[0].FilePath, Is.EqualTo(THIS_ASSEMBLY_PATH)); - Assert.That(_tracker[0].AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); - Assert.That(_tracker[0].AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); + var assembly = _tracker.Single(); + + Assert.That(assembly.FilePath, Is.EqualTo(THIS_ASSEMBLY_PATH)); + Assert.That(assembly.AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(assembly.AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); } [Test] - public void AddUpdatesNameIndex() + public void AddUpdatesPathIndex() { - _tracker.Add(TEST_EXTENSION_ASSEMBLY); + _tracker.AddOrUpdate(TEST_EXTENSION_ASSEMBLY); - Assert.That(_tracker.ByName.ContainsKey(THIS_ASSEMBLY_NAME)); - Assert.That(_tracker.ByName[THIS_ASSEMBLY_NAME].AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); - Assert.That(_tracker.ByName[THIS_ASSEMBLY_NAME].FilePath, Is.EqualTo(THIS_ASSEMBLY_PATH)); - Assert.That(_tracker.ByName[THIS_ASSEMBLY_NAME].AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); + Assert.That(_tracker.ContainsPath(THIS_ASSEMBLY_PATH)); } - [Test] - public void AddUpdatesPathIndex() - { - _tracker.Add(TEST_EXTENSION_ASSEMBLY); - Assert.That(_tracker.ByPath.ContainsKey(THIS_ASSEMBLY_PATH)); - Assert.That(_tracker.ByPath[THIS_ASSEMBLY_PATH].AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); - Assert.That(_tracker.ByPath[THIS_ASSEMBLY_PATH].FilePath, Is.EqualTo(THIS_ASSEMBLY_PATH)); - Assert.That(_tracker.ByPath[THIS_ASSEMBLY_PATH].AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); + private static IEnumerable TestCasesAddNewerAssemblyUpdatesExistingInformation() + { + yield return new TestCaseData(new Version(THIS_ASSEMBLY_VERSION.Major + 1, THIS_ASSEMBLY_VERSION.Minor, THIS_ASSEMBLY_VERSION.Build)); + yield return new TestCaseData(new Version(THIS_ASSEMBLY_VERSION.Major, THIS_ASSEMBLY_VERSION.Minor + 1, THIS_ASSEMBLY_VERSION.Build)); + yield return new TestCaseData(new Version(THIS_ASSEMBLY_VERSION.Major, THIS_ASSEMBLY_VERSION.Minor, THIS_ASSEMBLY_VERSION.Build + 1)); } - [Test] - public void AddDuplicatePathThrowsArgumentException() + [TestCaseSource(nameof(TestCasesAddNewerAssemblyUpdatesExistingInformation))] + public void AddNewerAssemblyUpdatesExistingInformation(Version newVersion) { - _tracker.Add(TEST_EXTENSION_ASSEMBLY); + _tracker.AddOrUpdate(TEST_EXTENSION_ASSEMBLY); + + string newAssemblyPath = "/path/to/new/assembly"; + var newerAssembly = new ExtensionAssembly(newAssemblyPath, false, THIS_ASSEMBLY_NAME, newVersion); + + _tracker.AddOrUpdate(newerAssembly); - Assert.That(() => - _tracker.Add(TEST_EXTENSION_ASSEMBLY), - Throws.TypeOf()); + Assert.That(_tracker.Count, Is.EqualTo(1)); + Assert.That(_tracker.ContainsPath(newAssemblyPath)); + + var assembly = _tracker.Single(); + Assert.That(assembly.FilePath, Is.EqualTo(newAssemblyPath)); + Assert.That(assembly.AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(assembly.AssemblyVersion, Is.EqualTo(newVersion)); } - [Test] - public void AddDuplicateAssemblyNameThrowsArgumentException() + private static IEnumerable AddNewerAssemblyUpdatesExistingInformationTestCases() + { + yield return new TestCaseData(new Version(THIS_ASSEMBLY_VERSION.Major - 1, THIS_ASSEMBLY_VERSION.Minor, THIS_ASSEMBLY_VERSION.Build)); + yield return new TestCaseData(new Version(THIS_ASSEMBLY_VERSION.Major, THIS_ASSEMBLY_VERSION.Minor - 1, THIS_ASSEMBLY_VERSION.Build)); + yield return new TestCaseData(new Version(THIS_ASSEMBLY_VERSION.Major, THIS_ASSEMBLY_VERSION.Minor, THIS_ASSEMBLY_VERSION.Build)); + } + + [TestCaseSource(nameof(AddNewerAssemblyUpdatesExistingInformationTestCases))] + public void AddOlderOrSameAssemblyDoesNotUpdateExistingInformation(Version newVersion) { - _tracker.Add(TEST_EXTENSION_ASSEMBLY); + _tracker.AddOrUpdate(TEST_EXTENSION_ASSEMBLY); + + string newAssemblyPath = "/path/to/new/assembly"; + var newerAssembly = new ExtensionAssembly(newAssemblyPath, false, THIS_ASSEMBLY_NAME, newVersion); + + _tracker.AddOrUpdate(newerAssembly); + + Assert.That(_tracker.Count, Is.EqualTo(1)); + Assert.That(_tracker.ContainsPath(newAssemblyPath)); - Assert.That(() => _tracker.Add(new ExtensionAssembly("Some/Other/Path", false, THIS_ASSEMBLY_NAME, THIS_ASSEMBLY_VERSION)), - Throws.TypeOf()); + var assembly = _tracker.Single(); + Assert.That(assembly.FilePath, Is.EqualTo(THIS_ASSEMBLY_PATH)); + Assert.That(assembly.AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(assembly.AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); } } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index ccf1382dd..375c755da 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -8,17 +8,19 @@ using System.Reflection; using System.Collections.Generic; using NUnit.Engine.Internal; -using NSubstitute; -using NUnit.Engine.Internal.FileSystemAccess; -using System.Diagnostics; -namespace NUnit.Engine.Services.Tests +namespace NUnit.Engine.Services { public class ExtensionManagerTests { - private ExtensionManager _extensionManager; + private static readonly Assembly THIS_ASSEMBLY = typeof(ExtensionManagerTests).Assembly; + private static readonly string THIS_ASSEMBLY_DIRECTORY = Path.GetDirectoryName(THIS_ASSEMBLY.Location); + private const string FAKE_EXTENSIONS_FILENAME = "FakeExtensions.dll"; + private static readonly string FAKE_EXTENSIONS_PARENT_DIRECTORY = + Path.Combine(new DirectoryInfo(THIS_ASSEMBLY_DIRECTORY).Parent.FullName, "fakesv2"); + private static readonly string FAKE_EXTENSIONS_SOURCE_DIRECTORY = + Path.Combine(new DirectoryInfo(THIS_ASSEMBLY_DIRECTORY).Parent.Parent.Parent.FullName, "src/TestData/FakeExtensions"); -#pragma warning disable 414 private static readonly string[] KnownExtensionPointPaths = { "/NUnit/Engine/TypeExtensions/IDriverFactory", "/NUnit/Engine/TypeExtensions/IProjectLoader", @@ -48,7 +50,8 @@ public class ExtensionManagerTests "NUnit.Engine.Tests.DummyDisabledExtension", "NUnit.Engine.Tests.V2DriverExtension" }; -#pragma warning restore 414 + + private ExtensionManager _extensionManager; [SetUp] public void CreateExtensionManager() @@ -58,12 +61,10 @@ public void CreateExtensionManager() // Find actual extension points. _extensionManager.FindExtensionPoints(typeof(CoreEngine).Assembly); _extensionManager.FindExtensionPoints(typeof(ITestEngine).Assembly); - // Find extensions directly in the their assemblies -#if NETCOREAPP - _extensionManager.FindExtensionsInAssembly(FakeExtensions("netstandard2.0")); -#else - _extensionManager.FindExtensionsInAssembly(FakeExtensions("net462")); -#endif + + // Find Fake Extensions using alternate start directory + _extensionManager.FindExtensionAssemblies(FAKE_EXTENSIONS_SOURCE_DIRECTORY); + _extensionManager.LoadExtensions(); } [Test] @@ -80,6 +81,15 @@ public void AllKnownExtensionsAreFound() Is.EquivalentTo(KnownExtensions)); } + [Test] + public void AllExtensionsUseTheLatestVersion() + { + // We have two builds of FakeExtensions. Version 2 + // should be used rather than 1 for all extensions. + foreach (var node in _extensionManager.Extensions) + Assert.That(node.AssemblyVersion.ToString(), Is.EqualTo("2.0.0.0")); + } + [Test] public void AllKnownExtensionsAreEnabledAsRequired() { @@ -298,12 +308,6 @@ private static string GetSiblingDirectory(string dir) return Path.Combine(file.Directory.Parent.FullName, dir); } - private static readonly Assembly THIS_ASSEMBLY = typeof(ExtensionManagerTests).Assembly; - private static readonly string THIS_ASSEMBLY_DIRECTORY = Path.GetDirectoryName(THIS_ASSEMBLY.Location); - private const string FAKE_EXTENSIONS_FILENAME = "FakeExtensions.dll"; - private static readonly string FAKE_EXTENSIONS_PARENT_DIRECTORY = - Path.Combine(new DirectoryInfo(THIS_ASSEMBLY_DIRECTORY).Parent.FullName, "fakes"); - /// /// Returns an ExtensionAssembly referring to a particular build of the fake test extensions /// assembly based on the argument provided. diff --git a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs index 774f10da5..d05f2c887 100644 --- a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs +++ b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs @@ -14,7 +14,7 @@ public ExtensionAssembly(string filePath, bool fromWildCard) { FilePath = filePath; FromWildCard = fromWildCard; - Assembly = GetAssemblyDefinition(); + Assembly = AssemblyDefinition.ReadAssembly(filePath); //GetAssemblyDefinition(); AssemblyName = Assembly.Name.Name; AssemblyVersion = Assembly.Name.Version; } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs b/src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs index 96b3c153d..0203274c2 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs @@ -1,25 +1,50 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt using NUnit.Engine.Extensibility; +using System.Collections; using System.Collections.Generic; namespace NUnit.Engine.Internal { /// /// This is a simple utility class used by the ExtensionManager to keep track of ExtensionAssemblies. - /// It is a List of ExtensionAssemblies and also provides indices by file path and assembly name. + /// It maps assemblies by there name nad keeps track of evealuated assembly paths. /// It allows writing tests to show that no duplicate extension assemblies are loaded. /// - internal class ExtensionAssemblyTracker : List + internal class ExtensionAssemblyTracker : IEnumerable { - public Dictionary ByPath = new Dictionary(); - public Dictionary ByName = new Dictionary(); + static readonly Logger log = InternalTrace.GetLogger(typeof(ExtensionAssemblyTracker)); - public new void Add(ExtensionAssembly assembly) + private readonly HashSet _evaluatedPaths = new HashSet(); + private readonly Dictionary _byName = new Dictionary(); + + public int Count => +_byName.Count; + + public IEnumerator GetEnumerator() => _byName.Values.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public bool ContainsPath(string path) => _evaluatedPaths.Contains(path); + + public void AddOrUpdate(ExtensionAssembly candidateAssembly) { - base.Add(assembly); - ByPath.Add(assembly.FilePath, assembly); - ByName.Add(assembly.AssemblyName, assembly); + string assemblyName = candidateAssembly.AssemblyName; + _evaluatedPaths.Add(candidateAssembly.FilePath); + + // Do we already have a copy of the same assembly at a different path? + if (_byName.TryGetValue(assemblyName, out ExtensionAssembly existing)) + { + if (candidateAssembly.IsBetterVersionOf(existing)) + { + _byName[assemblyName] = candidateAssembly; + log.Debug($"Newer version added for assembly: {assemblyName}"); + } + } + else + { + _byName[assemblyName] = candidateAssembly; + log.Debug($"Asembly added: {assemblyName}"); + } } } } diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 9837ae245..db1eea36d 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -57,7 +57,7 @@ internal ExtensionManager(IFileSystem fileSystem, IDirectoryFinder directoryFind _directoryFinder = directoryFinder; } - #region IExtensionManager Implementation + #region Public Properties and Methods /// /// Gets an enumeration of all ExtensionPoints in the engine. @@ -74,7 +74,7 @@ public IEnumerable Extensions { get { - EnsureExtensionsAreLoaded(); + LoadExtensions(); return _extensions.ToArray(); } @@ -205,7 +205,7 @@ public IEnumerable GetExtensions() /// public IEnumerable GetExtensionNodes(string path) { - EnsureExtensionsAreLoaded(); + LoadExtensions(); var ep = GetExtensionPoint(path); if (ep != null) @@ -220,7 +220,7 @@ public IEnumerable GetExtensionNodes(string path) /// public IExtensionNode GetExtensionNode(string path) { - EnsureExtensionsAreLoaded(); + LoadExtensions(); // TODO: Remove need for the cast var ep = GetExtensionPoint(path) as ExtensionPoint; @@ -234,7 +234,7 @@ public IExtensionNode GetExtensionNode(string path) /// If true, disabled nodes are included public IEnumerable GetExtensionNodes(bool includeDisabled = false) { - EnsureExtensionsAreLoaded(); + LoadExtensions(); var ep = GetExtensionPoint(typeof(T)); if (ep == null) @@ -250,14 +250,30 @@ public IEnumerable GetExtensionNodes(bool includeDisabled = fa /// public void EnableExtension(string typeName, bool enabled) { - EnsureExtensionsAreLoaded(); + LoadExtensions(); foreach (var node in _extensions) if (node.TypeName == typeName) node.Enabled = enabled; } - #endregion + /// + /// We can only load extensions after all candidate assemblies are identified. + /// This method may be called by the user after all "Find" calls are complete. + /// If the user fails to call it and subsequently tries to examine extensions + /// using other ExtensionManager properties or methods, it will be called + /// but calls not going through ExtensionManager may fail. + /// + public void LoadExtensions() + { + if (!_extensionsAreLoaded) + { + _extensionsAreLoaded = true; + + foreach (var candidate in _assemblies) + FindExtensionsInAssembly(candidate); + } + } /// /// Get an ExtensionPoint based on the required Type for extensions. @@ -283,6 +299,8 @@ public ExtensionPoint GetExtensionPoint(TypeReference type) return null; } + #endregion + /// /// Deduce the extension point based on the Type of an extension. /// Returns null if no extension point can be found that would @@ -309,22 +327,6 @@ private ExtensionPoint DeduceExtensionPointFromType(TypeReference typeRef) : null; } - /// - /// We can only load extensions after all candidate assemblies are identified. - /// This method is called internally to ensure the load happens before any - /// extensions are used. - /// - private void EnsureExtensionsAreLoaded() - { - if (!_extensionsAreLoaded) - { - _extensionsAreLoaded = true; - - foreach (var candidate in _assemblies) - FindExtensionsInAssembly(candidate); - } - } - /// /// Scans a directory for candidate addin assemblies. Note that assemblies in /// the directory are only scanned if no file of type .addins is found. If such @@ -413,7 +415,7 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) log.Debug($"Processing candidate assembly {filePath}"); // Did we already process this file? - if (_assemblies.ByPath.ContainsKey(filePath)) + if (_assemblies.ContainsPath(filePath)) { log.Debug(" Skipping assembly already processed"); return; @@ -422,7 +424,6 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) try { var candidateAssembly = new ExtensionAssembly(filePath, fromWildCard); - string assemblyName = candidateAssembly.AssemblyName; // We never add assemblies unless the host can load them if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), candidateAssembly)) @@ -430,18 +431,8 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) log.Debug(" Unable to load this assembly"); return; } - - // Do we already have a copy of the same assembly at a different path? - if (_assemblies.ByName.TryGetValue(assemblyName, out ExtensionAssembly existing)) - { - if (candidateAssembly.IsBetterVersionOf(existing)) - _assemblies.ByName[assemblyName] = candidateAssembly; - - return; - } - log.Debug(" Adding this assembly"); - _assemblies.Add(candidateAssembly); + _assemblies.AddOrUpdate(candidateAssembly); } catch (BadImageFormatException e) { diff --git a/src/TestData/FakeExtensions/FakeExtensions.cs b/src/TestData/FakeExtensions/1.0/FakeExtensions.cs similarity index 100% rename from src/TestData/FakeExtensions/FakeExtensions.cs rename to src/TestData/FakeExtensions/1.0/FakeExtensions.cs diff --git a/src/TestData/FakeExtensions/1.0/FakeExtensionsV1.csproj b/src/TestData/FakeExtensions/1.0/FakeExtensionsV1.csproj new file mode 100644 index 000000000..5ad014913 --- /dev/null +++ b/src/TestData/FakeExtensions/1.0/FakeExtensionsV1.csproj @@ -0,0 +1,18 @@ + + + + net462;netstandard2.0 + TestCentric.Extensibility + true + ..\..\..\nunit.snk + ..\..\..\..\bin\$(Configuration)\fakesv1 + 1.0.0.0 + 1.0.0 + FakeExtensions + + + + + + + diff --git a/src/TestData/FakeExtensions/2.0/FakeExtensions.cs b/src/TestData/FakeExtensions/2.0/FakeExtensions.cs new file mode 100644 index 000000000..fa76265d2 --- /dev/null +++ b/src/TestData/FakeExtensions/2.0/FakeExtensions.cs @@ -0,0 +1,157 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; +using NUnit.Engine.Extensibility; + +namespace NUnit.Engine.Tests +{ + [Extension] + public class DummyFrameworkDriverExtension : IDriverFactory + { +#if !NETFRAMEWORK + public IFrameworkDriver GetDriver(AssemblyName reference) +#else + public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) +#endif + { + throw new NotImplementedException(); + } + + public bool IsSupportedTestFramework(AssemblyName reference) + { + throw new NotImplementedException(); + } + } + + [Extension] + public class DummyProjectLoaderExtension : IProjectLoader + { + public bool CanLoadFrom(string path) + { + throw new NotImplementedException(); + } + + public IProject LoadFrom(string path) + { + throw new NotImplementedException(); + } + } + + [Extension] + public class DummyResultWriterExtension : IResultWriter + { + public void CheckWritability(string outputPath) + { + throw new NotImplementedException(); + } + + public void WriteResultFile(XmlNode resultNode, TextWriter writer) + { + throw new NotImplementedException(); + } + + public void WriteResultFile(XmlNode resultNode, string outputPath) + { + throw new NotImplementedException(); + } + } + + [Extension] + public class DummyEventListenerExtension : ITestEventListener + { + public void OnTestEvent(string report) + { + throw new NotImplementedException(); + } + } + + [Extension] + public class DummyServiceExtension : IService + { + public IServiceLocator ServiceContext + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } + + public ServiceStatus Status + { + get + { + throw new NotImplementedException(); + } + } + + public void StartService() + { + throw new NotImplementedException(); + } + + public void StopService() + { + throw new NotImplementedException(); + } + } + + [Extension] + public class V2DriverExtension : IFrameworkDriver + { + public string ID + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } + + public int CountTestCases(string filter) + { + throw new NotImplementedException(); + } + + public string Explore(string filter) + { + throw new NotImplementedException(); + } + + public string Load(string testAssemblyPath, IDictionary settings) + { + throw new NotImplementedException(); + } + + public string Run(ITestEventListener listener, string filter) + { + throw new NotImplementedException(); + } + + public void StopRun(bool force) + { + throw new NotImplementedException(); + } + } + + [Extension(Enabled=false)] + public class DummyDisabledExtension : ITestEventListener + { + public void OnTestEvent(string report) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/TestData/FakeExtensions/2.0/FakeExtensionsV2.csproj b/src/TestData/FakeExtensions/2.0/FakeExtensionsV2.csproj new file mode 100644 index 000000000..da2cbb7ac --- /dev/null +++ b/src/TestData/FakeExtensions/2.0/FakeExtensionsV2.csproj @@ -0,0 +1,18 @@ + + + + net462;netstandard2.0 + TestCentric.Extensibility + true + ..\..\..\nunit.snk + ..\..\..\..\bin\$(Configuration)\fakesv2 + 2.0.0.0 + 2.0.0 + FakeExtensions + + + + + + + diff --git a/src/TestData/FakeExtensions/Directory.Build.props b/src/TestData/FakeExtensions/Directory.Build.props new file mode 100644 index 000000000..e3e573fed --- /dev/null +++ b/src/TestData/FakeExtensions/Directory.Build.props @@ -0,0 +1,11 @@ + + + + true + 11 + true + NUnit Software + Copyright (c) 2022 Charlie Poole, Rob Prouse + + + \ No newline at end of file diff --git a/src/TestData/FakeExtensions/FakeExtensions.csproj b/src/TestData/FakeExtensions/FakeExtensions.csproj deleted file mode 100644 index 066d31608..000000000 --- a/src/TestData/FakeExtensions/FakeExtensions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net462;netstandard2.0 - TestCentric.Extensibility - true - ..\..\nunit.snk - ..\..\..\bin\$(Configuration)\fakes - - - - - - - diff --git a/src/TestData/FakeExtensions/fake-extensions.addins b/src/TestData/FakeExtensions/fake-extensions.addins index 0e9627ea7..4aeaaf80a 100644 --- a/src/TestData/FakeExtensions/fake-extensions.addins +++ b/src/TestData/FakeExtensions/fake-extensions.addins @@ -1,3 +1,4 @@ # This file isn't copied anywhere but is used by one of our # package tests to exercise the --extensionDirectory option -../../../bin/Release/fakes/**/FakeExtensions.dll \ No newline at end of file +../../../bin/Release/fakesV1/**/FakeExtensions.dll +../../../bin/Release/fakesV2/**/FakeExtensions.dll From a097bc2e1b59c809f8cde70402a7d3c6119d7ecf Mon Sep 17 00:00:00 2001 From: Stefan Beckers Date: Fri, 30 May 2025 10:25:13 +0200 Subject: [PATCH 169/190] Added manifest --- src/NUnitConsole/nunit3-console/nunit3-console.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NUnitConsole/nunit3-console/nunit3-console.csproj b/src/NUnitConsole/nunit3-console/nunit3-console.csproj index 29eabe010..87105df67 100644 --- a/src/NUnitConsole/nunit3-console/nunit3-console.csproj +++ b/src/NUnitConsole/nunit3-console/nunit3-console.csproj @@ -24,6 +24,7 @@ ..\..\..\nunit.ico + app.manifest From e02e29f91ea7110d83e1d17e9de2b4c81caa3a4c Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 16 Oct 2025 07:55:05 -0700 Subject: [PATCH 170/190] Fix crashing error in UnmanagedExecutableTestRunner --- .../IRuntimeFrameworkService.cs | 11 +----- .../Runners/NotRunnableTestRunner.cs | 18 ++++++--- .../Services/Fakes/FakeRuntimeService.cs | 3 +- .../Services/RuntimeFrameworkServiceTests.cs | 24 ++++++------ .../Services/DefaultTestRunnerFactory.cs | 6 ++- .../Services/RuntimeFrameworkService.cs | 38 ++++++++++--------- 6 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs index 9f7021092..7ef5fc3dc 100644 --- a/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs @@ -23,16 +23,9 @@ public interface IRuntimeFrameworkService /// /// Selects a target runtime framework for a TestPackage based on /// the settings in the package and the assemblies themselves. - /// The package RuntimeFramework setting may be updated as a - /// result and the selected runtime is returned. - /// - /// Note that if a package has subpackages, the subpackages may run on a different - /// framework to the top-level package. In future, this method should - /// probably not return a simple string, and instead require runners - /// to inspect the test package structure, to find all desired frameworks. + /// The package RuntimeFramework setting may be updated as a result. /// /// A TestPackage - /// The selected RuntimeFramework - string SelectRuntimeFramework(TestPackage package); + void SelectRuntimeFramework(TestPackage package); } } diff --git a/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs index 39ae50d9a..3e4a12502 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs @@ -42,10 +42,14 @@ public abstract class NotRunnableTestRunner : ITestEngineRunner public NotRunnableTestRunner(string assemblyPath, string message) { - _name = Escape(Path.GetFileName(assemblyPath)); - _fullname = Escape(Path.GetFullPath(assemblyPath)); - _message = Escape(message); - _type = new List { ".dll", ".exe" }.Contains(Path.GetExtension(assemblyPath)) ? "Assembly" : "Unknown"; + if (assemblyPath != null) + { + _name = Escape(Path.GetFileName(assemblyPath)); + _fullname = Escape(Path.GetFullPath(assemblyPath)); + _type = new List { ".dll", ".exe" }.Contains(Path.GetExtension(assemblyPath)) ? "Assembly" : "Unknown"; + } + if (message != null) + _message = Escape(message); } public string ID { get; set; } @@ -134,6 +138,10 @@ public class UnmanagedExecutableTestRunner : NotRunnableTestRunner { public UnmanagedExecutableTestRunner(string assemblyPath) : base (assemblyPath, "Unmanaged libraries or applications are not supported") - { } + { + _runstate = "NotRunnable"; + _result = "Failed"; + _label = "Invalid"; + } } } diff --git a/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs b/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs index 9d5558e7f..deb8850cc 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs @@ -9,9 +9,8 @@ bool IRuntimeFrameworkService.IsAvailable(string framework, bool x86) return true; } - string IRuntimeFrameworkService.SelectRuntimeFramework(TestPackage package) + void IRuntimeFrameworkService.SelectRuntimeFramework(TestPackage package) { - return string.Empty; } } } diff --git a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs index df4626746..c86814212 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs @@ -5,7 +5,7 @@ using System.IO; using NUnit.Framework; -namespace NUnit.Engine.Services.Tests +namespace NUnit.Engine.Services { public class RuntimeFrameworkServiceTests { @@ -32,17 +32,17 @@ public void ServiceIsStarted() Assert.That(_runtimeService.Status, Is.EqualTo(ServiceStatus.Started)); } - [TestCase("mock-assembly.dll", false)] - [TestCase("../agents/net462/nunit-agent.exe", false)] - [TestCase("../agents/net462/nunit-agent-x86.exe", true)] - [TestCase("../netstandard2.0/nunit.engine.api.dll", false)] - public void SelectRuntimeFramework(string assemblyName, bool runAsX86) + [TestCase("mock-assembly.dll", "net-4.6.2", false)] + [TestCase("../agents/net462/nunit-agent.exe", "net-4.6.2", false)] + [TestCase("../agents/net462/nunit-agent-x86.exe", "net-4.6.2", true)] + [TestCase("../netstandard2.0/nunit.engine.api.dll", "netcore-3.1", false)] + public void SelectRuntimeFramework(string assemblyName, string expectedRuntime, bool runAsX86) { var package = new TestPackage(Path.Combine(TestContext.CurrentContext.TestDirectory, assemblyName)); + + _runtimeService.SelectRuntimeFramework(package); - var returnValue = _runtimeService.SelectRuntimeFramework(package); - - Assert.That(package.GetSetting("TargetRuntimeFramework", ""), Is.EqualTo(returnValue)); + Assert.That(package.GetSetting("TargetRuntimeFramework", ""), Is.EqualTo(expectedRuntime)); Assert.That(package.GetSetting("RunAsX86", false), Is.EqualTo(runAsX86)); } @@ -55,9 +55,9 @@ public void AvailableFrameworks() Console.WriteLine("Available: {0}", framework.DisplayName); } - [TestCase("mono", 2, 0, "net-4.0")] - [TestCase("net", 2, 0, "net-4.0")] - [TestCase("net", 3, 5, "net-4.0")] + //[TestCase("mono", 2, 0, "net-4.0")] + //[TestCase("net", 2, 0, "net-4.0")] + //[TestCase("net", 3, 5, "net-4.0")] public void EngineOptionPreferredOverImageTarget(string framework, int majorVersion, int minorVersion, string requested) { diff --git a/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs b/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs index b53a66995..2aa52f02b 100644 --- a/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs +++ b/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs @@ -42,10 +42,14 @@ public ITestEngineRunner MakeTestRunner(TestPackage package) return InProcessTestRunnerFactory.MakeTestRunner(ServiceContext, package); } #else - // Unmanaged code is handled in-process irrespective of the process model if (package.GetSetting(InternalEnginePackageSettings.ImageTargetFrameworkName, "").StartsWith("Unmanaged,")) + { + if (package.SubPackages.Count > 0) + return new AggregatingTestRunner(ServiceContext, package); + return new UnmanagedExecutableTestRunner(package.FullName); + } ProcessModel processModel = (ProcessModel)System.Enum.Parse( typeof(ProcessModel), diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs index a977fbdb7..ae42a4680 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -92,16 +92,15 @@ private static bool RuntimesMatch(RuntimeType requested, RuntimeType available) /// /// A TestPackage /// A string representing the selected RuntimeFramework - public string SelectRuntimeFramework(TestPackage package) + public void SelectRuntimeFramework(TestPackage package) { // Evaluate package target framework ApplyImageData(package); - var targetFramework = SelectRuntimeFrameworkInner(package); - return targetFramework.ToString(); + SelectRuntimeFrameworkInner(package); } - private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) + private void SelectRuntimeFrameworkInner(TestPackage package) { foreach (var subPackage in package.SubPackages) { @@ -126,15 +125,13 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) throw new NUnitEngineException("Requested framework is not available: " + frameworkSetting); package.Settings[EnginePackageSettings.TargetRuntimeFramework] = frameworkSetting; - - return requestedFramework; } log.Debug($"No specific framework requested for {package.Name}"); string imageTargetFrameworkNameSetting = package.GetSetting(InternalEnginePackageSettings.ImageTargetFrameworkName, ""); - RuntimeType targetRuntime; - Version targetVersion; + RuntimeType targetRuntime = RuntimeType.Any; + Version targetVersion = new Version(0,0); if (string.IsNullOrEmpty(imageTargetFrameworkNameSetting)) { @@ -161,24 +158,29 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) targetVersion = new Version(3, 1); break; case "Unmanaged": - return null; + break; default: throw new NUnitEngineException("Unsupported Target Framework: " + imageTargetFrameworkNameSetting); } } - if (!IsAvailable(new RuntimeFramework(targetRuntime, targetVersion).Id, runAsX86)) + // All cases except Unmanaged set the runtime type + if (targetRuntime == RuntimeType.Any) + package.Settings[InternalEnginePackageSettings.ImageTargetFrameworkName] = "Unmanaged,Version=0.0"; + else { - log.Debug("Preferred version {0} is not installed or this NUnit installation does not support it", targetVersion); - if (targetVersion < currentFramework.FrameworkVersion) - targetVersion = currentFramework.FrameworkVersion; - } + if (!IsAvailable(new RuntimeFramework(targetRuntime, targetVersion).Id, runAsX86)) + { + log.Debug("Preferred version {0} is not installed or this NUnit installation does not support it", targetVersion); + if (targetVersion < currentFramework.FrameworkVersion) + targetVersion = currentFramework.FrameworkVersion; + } - RuntimeFramework targetFramework = new RuntimeFramework(targetRuntime, targetVersion); - package.Settings[EnginePackageSettings.TargetRuntimeFramework] = targetFramework.ToString(); + RuntimeFramework targetFramework = new RuntimeFramework(targetRuntime, targetVersion); + package.Settings[EnginePackageSettings.TargetRuntimeFramework] = targetFramework.ToString(); - log.Debug($"Test will use {targetFramework} for {package.Name}"); - return targetFramework; + log.Debug($"Test will use {targetFramework} for {package.Name}"); + } } public override void StartService() From fcfcab1d2530fa0bc78c1849e5f56f8bcdea60ce Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 16 Oct 2025 10:31:23 -0700 Subject: [PATCH 171/190] Add package test --- .config/dotnet-tools.json | 17 ++++- GitVersion.yml | 14 ++-- KnownExtensions.cake | 70 ++++++++++++++++++ NUnitConsole.sln | 1 + NuGet.config | 5 +- build.cake | 21 +++--- package-tests.cake | 25 +++++-- .../Services/ResultServiceTests.cs | 3 +- src/TestData/native-assembly/NativeTests.dll | Bin 0 -> 86528 bytes 9 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 KnownExtensions.cake create mode 100644 src/TestData/native-assembly/NativeTests.dll diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 43e6f74ce..be0c1e856 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -6,7 +6,22 @@ "version": "5.0.0", "commands": [ "dotnet-cake" - ] + ], + "rollForward": false + }, + "gitversion.tool": { + "version": "6.4.0", + "commands": [ + "dotnet-gitversion" + ], + "rollForward": false + }, + "gitreleasemanager.tool": { + "version": "0.20.0", + "commands": [ + "dotnet-gitreleasemanager" + ], + "rollForward": false } } } \ No newline at end of file diff --git a/GitVersion.yml b/GitVersion.yml index acdf7df63..ca6e99b51 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,13 +1,9 @@ -next-version: 3.20.0 mode: ContinuousDelivery -legacy-semver-padding: 5 -build-metadata-padding: 5 -commits-since-version-source-padding: 5 branches: - master: - regex: ^(main|version4)$ - tag: alpha + main: + regex: ^version3$ + label: alpha release: - tag: pre + label: pre pull-request: - tag: pr + label: pr diff --git a/KnownExtensions.cake b/KnownExtensions.cake new file mode 100644 index 000000000..b827b2721 --- /dev/null +++ b/KnownExtensions.cake @@ -0,0 +1,70 @@ +using System.Reflection; + +// Static class holding information about known extensions. +public static class KnownExtensions +{ + // Static Variables representing well-known Extensions with the latest tested version + public static ExtensionSpecifier NUnitV2Driver = new ExtensionSpecifier( + "NUnit.Extension.NUnitV2Driver", "nunit-extension-nunit-v2-driver", "3.9.0"); + public static ExtensionSpecifier NUnitProjectLoader = new ExtensionSpecifier( + "NUnit.Extension.NUnitProjectLoader", "nunit-extension-nunit-project-loader", "3.8.0"); + public static ExtensionSpecifier VSProjectLoader = new ExtensionSpecifier( + "NUnit.Extension.VSProjectLoader", "nunit-extension-vs-project-loader", "3.9.0"); + public static ExtensionSpecifier NUnitV2ResultWriter = new ExtensionSpecifier( + "NUnit.Extension.NUnitV2ResultWriter", "nunit-extension-nunit-v2-result-writer", "3.8.0"); + public static ExtensionSpecifier TeamCityEventListener = new ExtensionSpecifier( + "NUnit.Extension.TeamCityEventListener", "nunit-extension-teamcity-event-listener", "1.0.9"); + public static ExtensionSpecifier Net462PluggableAgent = new ExtensionSpecifier( + "NUnit.Extension.Net462PluggableAgent", "nunit-extension-net462-pluggable-agent", "4.1.0-alpha.3"); + public static ExtensionSpecifier Net80PluggableAgent = new ExtensionSpecifier( + "NUnit.Extension.Net80PluggableAgent", "nunit-extension-net80-pluggable-agent", "4.1.0-alpha.4"); + public static ExtensionSpecifier Net90PluggableAgent = new ExtensionSpecifier( + "NUnit.Extension.Net90PluggableAgent", "nunit-extension-net90-pluggable-agent", "4.1.0-alpha.3"); + + private static FieldInfo[] ExtensionFields => + typeof(KnownExtensions).GetFields(BindingFlags.Static | BindingFlags.Public).ToArray(); + + private static ExtensionSpecifier[] BundledAgents => + [ + Net462PluggableAgent, + Net80PluggableAgent, + Net90PluggableAgent + ]; + + public static IEnumerable BundledNuGetAgents => + BundledAgents.Select(a => a.NuGetPackage); + + public static IEnumerable BundledChocolateyAgents => + BundledAgents.Select(a => a.ChocoPackage); + + public static IEnumerable AllExtensions => + ExtensionFields.Select(f => (ExtensionSpecifier)f.GetValue("Value")).ToArray(); + + public static IEnumerable AllAgents => + AllExtensions.Where(ex => ex.NuGetId.EndsWith("PluggableAgent")); +} + +Task("InstallBundledAgents") + .Description("Installs just the agents we bundle with the GUI runner.") + .Does(() => + { + foreach (var agent in KnownExtensions.BundledNuGetAgents) + agent.Install(BuildSettings.ProjectDirectory + BIN_DIR); + }); + +Task("InstallAllAgents") + .Description("Installs all known agents.") + .Does(() => + { + foreach (var agent in KnownExtensions.AllAgents) + agent.NuGetPackage.Install(BuildSettings.ProjectDirectory + BIN_DIR); + }); + +Task("InstallAllExtensions") + .Description("Installs all known extensions, both agents and others.") + .Does(() => + { + foreach (var extension in KnownExtensions.AllExtensions) + extension.NuGetPackage.Install(BuildSettings.ProjectDirectory + BIN_DIR); + }); + diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 4204f4c9b..79df83dec 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution GitReleaseManager.yaml = GitReleaseManager.yaml GitVersion.yml = GitVersion.yml global.json = global.json + KnownExtensions.cake = KnownExtensions.cake LICENSE.txt = LICENSE.txt MixedTests.nunit = MixedTests.nunit NetCoreTests.nunit = NetCoreTests.nunit diff --git a/NuGet.config b/NuGet.config index fe24e8fb4..1f2ff0303 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,10 +1,7 @@  - + - - - \ No newline at end of file diff --git a/build.cake b/build.cake index c513734e2..648f8b8d2 100644 --- a/build.cake +++ b/build.cake @@ -1,9 +1,10 @@ // Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.5.0-alpha.4 +#load nuget:?package=NUnit.Cake.Recipe&version=1.6.0-alpha.5 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake #load package-tests.cake +#load KnownExtensions.cake // Initialize BuildSettings BuildSettings.Initialize( @@ -83,14 +84,14 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { id: "NUnit.Console", source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner-with-extensions.nuspec", checks: new PackageCheck[] { - HasFile("LICENSE.txt"), - // Check proper extension is in a sibling directory since we - // don't yet have the 'HasExtension' predicate. - HasDependency(Extensions.NUnitProjectLoader.NuGetPackage), - HasDependency(Extensions.NUnitV2Driver.NuGetPackage), - HasDependency(Extensions.NUnitV2ResultWriter.NuGetPackage), - HasDependency(Extensions.TeamCityEventListener.NuGetPackage), - HasDependency(Extensions.VSProjectLoader.NuGetPackage) }), + HasFile("LICENSE.txt") }), + //// Check proper extension is in a sibling directory since we + //// don't yet have the 'HasExtension' predicate. + //HasDependency(KnownExtensions.NUnitProjectLoader.NuGetPackage), + //HasDependency(KnownExtensions.NUnitV2Driver.NuGetPackage), + //HasDependency(KnownExtensions.NUnitV2ResultWriter.NuGetPackage), + //HasDependency(KnownExtensions.TeamCityEventListener.NuGetPackage), + //HasDependency(KnownExtensions.VSProjectLoader.NuGetPackage) }), NUnitConsoleRunnerNetCorePackage = new DotNetToolPackage( id: "NUnit.ConsoleRunner.NetCore", @@ -115,7 +116,7 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ChocolateyTestDirectory - + $"nunit-console-runner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), + + $"nunit-console-runner.{BuildSettings.ChocolateyPackageVersion}/tools/nunit3-console.exe"), tests: PackageTests.StandardRunnerTests), NUnitConsoleZipPackage = new ZipPackage( diff --git a/package-tests.cake b/package-tests.cake index f2208c991..e552207ea 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -110,6 +110,7 @@ public static class PackageTests }); if (!onGitHubActions) + { StandardAndZipLists.Add(new PackageTest(1, "Net70X86Test") { Description = "Run mock-assembly-x86.dll targeting .NET 7.0", @@ -117,20 +118,20 @@ public static class PackageTests ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-7.0") }); - StandardAndZipLists.Add(new PackageTest(1, "Net60X86Test") - { - Description = "Run mock-assembly-x86.dll targeting .NET 6.0", - Arguments = "testdata/net6.0/mock-assembly-x86.dll", - ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-6.0") - }); + StandardAndZipLists.Add(new PackageTest(1, "Net60X86Test") + { + Description = "Run mock-assembly-x86.dll targeting .NET 6.0", + Arguments = "testdata/net6.0/mock-assembly-x86.dll", + ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-6.0") + }); - if (!onGitHubActions) StandardAndZipLists.Add(new PackageTest(1, "NetCore31X86Test") { Description = "Run mock-assembly-x86.dll targeting .NET Core 3.1", Arguments = "testdata/netcoreapp3.1/mock-assembly-x86.dll", ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-3.1") }); + } } ////////////////////////////////////////////////////////////////////// @@ -461,6 +462,16 @@ public static class PackageTests Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("AppContextTest.dll", "netcore-8.0") } } }); + + AllLists.Add(new PackageTest(1, "UnmanagedAssemblyTest") + { + Description = "Attempt to run an unmanaged assembly fails gracefully", + Arguments = "../../src/TestData/native-assembly/NativeTests.dll", + ExpectedResult = new ExpectedResult("Failed:Invalid") + { + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("NativeTests.dll", "net-4.6.2") } + } + }); } #region Nested Classes diff --git a/src/NUnitEngine/nunit.engine.tests/Services/ResultServiceTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/ResultServiceTests.cs index 1c9872391..a81dd1c1a 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/ResultServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/ResultServiceTests.cs @@ -30,7 +30,8 @@ public void ServiceIsStarted() [Test] public void AvailableFormats() { - Assert.That(_resultService.Formats, Is.EquivalentTo(new string[] { "nunit3", "cases", "user" })); + // Changed from Is.Equivalent.To since developers may have installed other formats locally for testing + Assert.That(_resultService.Formats, Is.SupersetOf(new string[] { "nunit3", "cases", "user" })); } [TestCase("nunit3", null, ExpectedResult = "NUnit3XmlResultWriter")] diff --git a/src/TestData/native-assembly/NativeTests.dll b/src/TestData/native-assembly/NativeTests.dll new file mode 100644 index 0000000000000000000000000000000000000000..079799a77304230415c47a885fe1ee057253d4f7 GIT binary patch literal 86528 zcmeEv3t&^#{r^pxV!%R!f}%2wN(TsHp+%9lkODzaswq%FMN4T@QY>xFgNFm9mQG@b zx=qw=y2;ev+{UJEK8n+=JVvX-4V7(7nO4X2)^@W~ep+2M|Ig=~dy|{=$z%H4e=Xc| z&pqdRzVGwA=bq$UbC;5&D2fGFB%&yFxbx2;!d?QCq6|6b*&)i417A9~&NT0(a~FEb zeD*4D7hSPB$Q(V;`^Q#)p!1K;s&kFdFIaPaa!t)))evyAi z@r|PV9mRL-C7wHqed2k3S+R$-bje&!MVV(hS;@bB&DDBZlVU#iWYZ8u`6E)+%hVk< z2x~)}7en67iZVbv#_mcz5~)o5nH7gDB+4L=WyQ^rW>)S(;pb+Um9+$Z(5z%@<@gx= zd)%x%X;qZ;!DeMjG_iBUgM5cE*l2<(j?%JH}Ve% zr~~RGfls@lESumhDe@O7%Kj|mw*f92_t$V4{v3+pm>|KF2L~Y|9SlM?{v3)@ zKcP$lHlB_Q$fAtE{p?O~4!_&yC**j_x(oq%ySNjai*T#mUY~_oRe&S>)Xs1dR+N^N zKr23GZPehhJHa_7q^B2`t^he^-KD`z>jKW}^A-a{*!Drd(P8KohCc^VCV1WDl>oft zWMshRN(%0?JHgE`6gwf&ef(>FXN{sXpASE5-ghg46~hp`y$M0`&k;28@%)hpeuX;H zd@j@OejLG_>k!P~<5LXkh~?xf5v=Cp6Aad$hu}6CvH3hA_zFw36LZFR1b&t%8;;=c zXau)75UhlZ&6CeSU|WJ9#9$Ap-I$AD`!)pcjzaMBY6PF1gJ8~u2>wd$2N2&tKY}Mw z*P5>?K(O}-1piBrrZfau3lZEyqz$VPEM+i`AiEgsBq1xReseKVeQyGSZ!_j)@OyIg zotX#*^0Daw1m7k1U!R8H8(suksK47DL+}A5dcFq1=FJHHx)H(ITM-PjBPhv4@E8?) z(-jE*#S+WNLCe<>d^8Ke&as)%pL=dFLpQJX{5$>jo z5j;W-*@^El787MxkVflw5WGvh{G1HEgyygL8fMJA9>LkP%p-yq7Q2PPqkK%e34wAQ zg4gp9{LdByxep>(NRkaS?J1P<@3hAN7JKzh1iwQo&^(ulc#@iY|3(CZh;J;5*=f+P zk?sI0_FRJ8Plj$Ie=~@sIe=i_&kz(bHtj12#x6y$khv!_?HcOJnUA2IaDQiT4{5wb z#cpB7;vXZpl}zSci{L)G$~M9cAsaus27%>!2!2g`&$7hUZy>mfw!HS+2$m4v69j3c z4_UjC_qCsjYIG{DtPm3qB{311oxkd;Pm?t z+)4yxEO9eS>^>jC_1{8p2F-mptu=iyf@f*zz|{!aNa%{$2oABtTg11Pu^q%RX*hyB zifU!>Dm`KX#kzsHv&s-mC%dn)gp;g2PHSB|20=DK7Bj=lSOa5kpN?QKV@s%mnG+Cv zeg}eewD~6LDs>ow?-0R5KSiLNhTw{!2B5=oAEp zzlq>=qCb;HX2;k33M*{OKm@Z{o%5JBZxVu!&qMG{K3+sDYbz0~Ah2O5#G^4AS_3_`c2HY{E7F1i`Zu&`D0K zZb0x$8s)1*Uqret!ab9VVA4DUFA?b+7R$I1!5dUvF+obGzX)S<82pB9$wpfLmkjP1 zgkb1o1pBT=aBmiZrwR8H22(a5NTTG^XqmC}oCEhG`2Ot(9wxOnN$uy1{i_f`DPu*K zBPgGNU@>+2&07$RWyYL51Rpb2L4Wy>vDP0WxRZqHh-&zH1ot!cI-A$MK?F|_ukS>k83?lH;= zuxN4kc}FA?*@1C@Qb+P1{-p5wYeRp#ZnuG#V}jo&@#lx1&m@727=f#UdF`RRcJ+l1 zA`#yI`XTPlcF?Wf!%|tT>UB)|+jYAvU5*k2KfU00T3zaoK%i!i#YG}N`rl4O+DX4# zB8l?%MlZ#s-uTZLz4Ism=nm*C+(wPAwM*eZ7xJx)cY$e;E55-m8dJs2Q3)&n> zZC;E=&0JS+8S8PJ^?9V0@e^ zACU_Sw1)DIlmrJe?`mO;!-&Bc`yp1J^rWk-Pxhg`yTK~uH@$3-f1XM6)<-{w_!w5| z;%m|eooz7%K>AQ#O30QqG%#>)?QWDXwVWg)O2|13{$R@w6{O4$*V-VsOC|D}J=S3D za{vv683x032z5d`H5_h5G+#YKFY+WFLt7aJw?2+=ud~Axs%6Zz(i9FiB9gC~ zg(ZdsYq#Suw3T6SDe zGN|80O1$|(d2Jalse9pT)q9qJTNv^rkbugKRa-aN3B61EU17A}Cf$Bjkab5W z*HG&R2mq9orj<$IFqnhjPZa83wf+u*q=`JTHYCR=3q$Xv6nW={%4PX}+Jz7hq@ zY}a`0mUz$fQlEBOYwP`KH5*biy0XS}qFV{Ig zZ^K7r|7Asc)jnUH49mjiLg`vhnb9hgzN_2L1YRKYbx8UbdXQ0Y-5rs`XW>ffr) z7Y0U8MAhp0zd-1Jqo8dG*+LGBruw15T#K|vaBELiNbLiUMl19|{~rLQbWN%MWN>F} z3+{(Kx@`;kq<<@H+n%qUibe$@sq#NEx{Q1|zq?F)R}3_kSo_3+>>+hcS;V9ly7 zeJ5VzAF17md^Gaj53C9Dx+LN+TizcfRIwfJ_9$-;`uneF-@k16d-i`$OH9IZpV{cP|tql&WgkwI%*a*hDXP;_9 zw|Fx(-thx))J1;1}xw z-89ihQ@*Pa0LsH3nvTmKtZFHI(4~%T(skad{s<{6m6c*Nxq|3-jxWKTj~; z9f#8;hh4dLJY(9E=c)0RiEUe-`+hflEW=B(F0O^CIHVzE)T-KPua z2_1XXKPLU2-cj)@IA7?W?6tM2*?dtL-`h>x7apG zLL#N+J&(-9&_1s$y%W z=~{zUEUN`w5UahxVWm1794WkI)MLO=v(pwdamo^H+xu9>`aoYTglbZsuE)Z~K;Yl& zNHMisDfH_Wo#C%de6nI`%y5of2>j!x@tHuudgbq`&Z(K+lm zAD8{$Mkw8A>?#`JXf)gx|8lmcK0PO;_65JWWV574$#D>Ld>+Vi#F>tY(|y$`MT zBqQk6%{fg`)H9$yQK#y#7QEw|${VyX>y?om{Vg}y!u7)v|TmvnH@;dOI{Cjw;-a{d=JohGW zv4U}?W(P0@pfKYRFY@`^UVon3=PO#_KEBt!(JCtJse+iCv5vr0bjMUY8sE>M{rg|I zg)5x*zWN}T#pVLU=u$5AA(#a(V?u3gCEI=v<)(+jBY{PI7Ls5N+OE>@Aw`t-zJw@} zhSKVy$nzpIm@d_mJ`?I$3|c}x)qBVol=32hpp?{e__dTW1yJvU1mm+1FU&2+H*ag# z9g9kY{B1xae9K1hv+<;f-{5S=119Ep4Q@q!ZN48gYZ@aNuY4Rx$#PnKsT^C3kRzi} zbhhGEwym~4brWaC)f-xA!_-^B;;ANR}Xo3?DB_3`2?Q1q%njVog(1(Ai$c z3DFIW5xfKrIS&LagS3RtkF*t5cTg8 z#)e?=1gX3B>8W*$AyuxRS=AxGjUNVF%KTn>roJh*Px@(Bn?SIp-eT-Io&rz3o41en zCsQDWH5!^|R~-rR&Xu%!?1`nkf?jL|P|SdhJLa_ghrc00Wt`KBmIAB9YzI!ly(PIN z2^C3*sm*(T2b)sWeP6G_eB6E@x*nNd-5P7&t%=Qh=NC@ywqTyhp+e(HLFb`Ro~mwo zAri@Q9)f1b?xB{IA?KltS9Tr8gt9#Oj*s8^q2p^_)H)O zwU7Ip@#~R(>6A5RF+Se_GaK=w^kCpn%gM~Lp$KS2S9%Z0+MufCG*qJ+Vty^f356L3 z^Y*v6gYGuTqS)HzjFy=q%Pj7q%vo|auBzi3rEfzxP=B%)Zs^V}la)r`!Hm>w0aMI#X6x)IAs9xilF&7mMf<&0Tda>tO*y z=OQ5M&06O&<wV%@Z4$=VZ3&!7ZH5-P?B97%+%!Mp>pzPhP8TXfA}Dg9q@EN#YP0!ycK z#nPg#SUROAmPQGd4xlabR@BYuh@>9O^&vyv%WsiK0t^W5`O0*9gQ_J}ZXBs;$#bd&J<#A{LU28^tX|VP@ zz+k~ORA3F}4-1P&w6(3OZ8y`c;bC*;t7Sroa;q1yqxmdg_EC><`q=+J0_PDkA{Q$S zMjV^8`%l4$Hd)Gs3|uO1SMQ4@dXOjz7T3#y>yal1cCufna-m-s2K3q#N*hMpMikn- zaxFmg`O_xEk9+sW1He zrA4_4uJl zO_NzmFzX3czJvvY?3Mvx(Gt*gqX@^=M^meXy!%03wm0eY?@T-ViOMEcDF$&1P`hddKYU)D(jdUaKo%RNS%}KKUBrZiC>)x* zQk0-#`Z&+%RB^Qgp|9Tdd*coL6mo$HJJ#fabFw*zPTe#FTrvsIZw;vVWI*bs4gthH zuUMCMs7;VlSW)bP#lG~``RbxyvinQH@C39DD+!^{p26fgh5r2T3D=`y_$R zI}G}WuZkixs(*hr9&K)SXrtAkkNCe)C-CP%XGXT)0zK%ZGu!d_dufmGUSaJBtM_y$ z25(L0tL>tSSY*S|0-tWjQi|4$bZQ5#@0EopFN_FzkCGqH(eU%T9lfcfGe5nLAMS#b z*dt}sy}M6@`e@&Dfq$40fAWdMpZ_%ZkHh~6U8+aliQ21o{Zx_JM)fn0G0KqJD-EHa zUdHF}zbT5p2rJS_VeV10%JuWZ&wwV{KPz@;1oN6&28(HJsGup7Yr!W!&L)eALmwg9RXog-1* zhj`(<%HkVNKuat1(ewC5e)JYdZr(&G(3njHYgi2SuHnN0)<=cC9v&L-;8HJtl3kws zws@&D)Gz37hLxyReE11l=}>oya^plf^poxCpI}&hiCJ`q>ZuY@5nu@LGyoculgN=+ zdTLi6drI>KZALMfD2{u;(6{zO3<*rBo5YI-*{L3pohrG)_~}fot351Jzl~I_xf`M7 zw8@-XqB&Z+BK>6r(&d-;D+M;TncLFjvPE`!*A4hwc-h?7~ic8LuM$f z)T2eZ!B@9Bi<>GybNZ_98+MuL z2c7)d5Gyq})xepOBy!u;TUhV0lSi(dxFKVU6LBr~z;~cN|mrKFIHR0E1Xho!GDsE&uFC8e53p1)k)X zP_99}o!~-6zXqz%TdL2;_#IM>3cAfcanG%#KE=MWQaR2mvu8tkUqM0xZV`;GlYZ1y$=DvMif z@6==PJRS^Z?uN+S?3b;e_= z?@I~RGA|@T(S8vf+TX)uvNuiy{g?(a6Ez^tlhBq2;TSLePkbA>QPSw%vPX&Ef*NgX zgY72(KU-fZfqD>*h$h8I;a86eEp;bBANXI1)>p&7wln_g`+&b*w|}ScDF+qcwtF-7 z>_2dm)Mxl1SL!q7ti}KqujwkB>~qY*IHFj*W~@gt(KhN!q1T&#%C^l$U(wpR`S4S{ z5z;z~KQO7+wphq_SlUBBp)2uys7B*KS{8L;)(>&#-lHsM%9tio-=HB09h((E;8JjJizZ5LMiy z$#m=uYairo5GR;mZ-yd2tz?X|^{_~Hg~bdoU%mPvaw}&(8hgvhanCJKU;L@|1>JK& zS!v;de}Q#A_KAv3mi(6QlZQFR?z$QL3-WqlBI@0J+yBko^s|9AVZ7^?gnHi8Cgkne zpRvvqxuv_>D9YDE4PEzt7?x##4bi|63O@1|*rj{<1-{sZwh^G6xL-MTz(cz_jTUWG zHvuP>&}ufcn%1CEVJXq5@cmAWice>m5)z?cMca2gzeiRKR)|G=_K%+X$M+u;^`|?> z*z<#)6r-^q=r`*6ocTj`U!b z*4tjjfd}(N#)4bvyQPLcoFrj9dtZ2rX-o6p5uhSh08~syci=>IondiaqxMSB>`zhC zz+dwrY&~qbL_Gs>Z14C0MZ#!|!ePep)sMGPi`LYq_t(DSAIco{`$Dmn=7&%aEs|JG zwE8QxMLumM(T#FgNxV8f^(v&IIn2tP;+wAJXlsm~SBY&OuAiOpinNlEe*8>}RM(zWY7=eOBQ{jbUxo_6d>r9O|{QdYF8<`y5^i zH_lh5KOpK7a{_B=qrAr89K058LWFOL`7XFonn+u!bpw~~92WoCAOrx;2pi*G7f%CN z%ely_sW;R7%mzVWTJ4SUSpaE%8>*1LF)r`9mp=gv`bj_qbK|*?rWQ`op}zG!DBJnm z%VZZ^&X4!1-HEvd2m2xN`oR50fMJ7foL`h9O%p~kBi>hcs0$Ox=?c@t%ruZR`A?0i zU)t+Edc3S_`rB;bz-xqIES((q9i(dxESg}p320D$kL2D*LBl=eta@=o|0iNSFY=gX6IqUXCQ~a%r3K)Q5B|SY!#H9?KTm>>sF32HBQ!Y&k!& zlFe{rc&mJ`QU@aH2+7lFQJxsfS3eRA$POSdO7LXXc#4Xrx%iDphtmm|o4zCX7S2b7 z!ujSS``_$*!hZE2*Y)^_y;s4vV~i0JjO_rgU5}9jz5@~;<^$uQksbJ*iMakWuJ%^| z75bq`@omBq63A=66y2Ww0!{0;rpX-9jE%sCtkg}fvc--w^O*u?>ZTpYso5~h9Jo-+ z*P885ArY{L@~4d3|MI7yUEfY}rkq-U4O%I|0$WQVikJd3@kJiDcV)$+{xys09i-Mp z{RC<3qC$J)5uFA^ir==a9zMhU(J3L91q3(r*ig$`U71rr!2MCsc!t9BY2}$$beNi=HIr-hW2El%KE3h2v}L z|21-8t}C=uZ7lL5!lN^aYrpSCqMoFUOR7bZq1&iJLQ5wNHGhpJ75Y!zv^Zv7jfX50 zViS_njd{;Pe)r~GEX=E$RYR%X+u5uKkapB&y%+k|%^K=ti)xZFI$oD%y`^A1#xR;m z*Wma7VF1l{rx`gF+;^xKByy^ovq`pAgOJ}@%Pk|Y4s){8*4abl60$tq@*2_;$I@2s zVZBD}|K&YM!pgv~6R3BiB?5Bb4k8aE>Lmk_o*Ry3{zff)*K7=bX?3~xO0?fD05>qN$ZUNdAF=dqeV?> zH8e{qz83A=EbRn*g8z<03m=p(x1Ft7f_SW1QnSRjxLq|%tBFc)NVR5ZIntsvPCb>% zl0#&z*PX#K6vmv`ER8`<=Vs{?!Y4LMpTQIf&C+`!$h3T`Ey) z3_km2SF&}MR*t7tOg^k?0qJOsfVNikG|;dSu>b$n2#ETuow$$j%afmD{9-tAU*8cB zban*uj^KlTsBA+zOqCY*NPGtEop`yEnfI`b0hD>~_v0uIYPW_WVtGeSZmLS{mZ~#OI zvY5%3x^o$&dAC6zOKR=cV{B{9#bA)L2a{k9NhvnbKZti%$Vg}f*scM7D` zV5h()IO~$wi3i*9SiOfHQ#8kWhh+slaVPNcfj9q=*CNt=_2j{9x&Ii@9WjRvo^6@`Mle^z|zTq8ZLvz#pG ztr;6n?;Jvkp~IR~bq$Qzj9;X~&(zVnaU+k9w&;L~Gps{0hchg!L18W&U0u{D{}KRt z531GkM@16Vs4dRiOC)ics3nN{7`!IoJwQ1@dw%QDlZPE&UT^+odzmYM7z*PJin`-= zGB00;(x=gSJQnJi`99zmgB(dAGW<#GQ3MIRqbUGpQQtI5&K@X5(zu5?l4chne;y-} z##2EeY1~s8N#oAPNE(kYjim7$wMZK67D=N)BWdg}B58EMNE)3vlE(2?B#owwq;ZND zN#o!ll6FXh2Ss>5g!@IvV?iTnjUwD5!pB8;zX%@`;lm<)RD|0_I7;yFq=?sv@Hr8# z7wJ_Z^owvULTTr^{WzA=@O?tvZNuZ9H|8w7*o;j8=kR-Xxl49$GLuYd2HJ0zdK1)G zvuA_>MVtenkXB4h0({M$Veyc#XFi)r^{~jrcVHbg8#)w!a>&`yf?tcU=BxgoaB(yu zr{GhSNZ=&(NqBfmvibmuwCw8H)qx)?O(alt8_`%3KE^=4$o=`(1(AeY@jhe0qR>A> zuho9!Kd1I}|554GXj=Z&(AYdov|ZRoWcKk%L$+IjWe z*ynE<^}+57dAvZa((-OXSw2!9a1QuDgQ8+HaH2Qs`FS1cEigWORi3GaUH={V>Y`0T z1bR?6I}3#9+<=0E4seD~(weK-n~n;1Y{7xV1vc?RRO%WyBC1Bq6fULt2kdoXUzY`; zg7dJj06PV9Ui2|7XQ%^F6>&0dODc{jkg?=C^u7wIrzpPy&h5DVg6myeU5MK$3H+`6 z+>?Hu86VgGB6`S4&7M}+N$h@_=xi51NY*BQkZh%*E|x8YIH5OiQbv9AD6pVU2tlZ zgrP(Q-0?(zOcEk0pIe6DW07}UYK;_WH}w53_9a>}>RaZk*McP9nH4nEOjWp75$8yK zs>{ypi(lank8$@8N108bp}~SfI9x=15al8Thqf(2D~)4T9^zeEDX*<0_M2>pp4Q1U zCnQ1I*VW0sN4l|ZO%B;rZlMakCPh%C2~p>+Y(CK3iY}sc9%SUdyMd zzd%_kH(wor7@T?cN;o&iAitNEl-@+Dnn#}?T}k8k$~EkOO;-dR`~o;_ zgYz(5SC@W6ipf6!kmdjm7?(fhC5BY%0Tgr|uGw%{!D^M5Ase&*{xABJo*e|jxYEO+ zLy?_g3S6{%?gzvp6=?=#vM$(43cz0tYy}K-%`JE( zWXO8~T!5%nFIsZ2P=e#j;mu8Q$)qXYa~E0@*;;8E-PQ}Mjo2M>t56IO=mrlt598PB zzHm2>))m-0zb2>6%rVnRqSm#XW0ZCqvoOd(^R0diAtD8$>F#z1G4L6StK&b7M2z1I zBOgjP1(MY>Ze{mi&};Q)^z;-X^uB%DmZEwYI=OAj;-3XQYfZal&A^bQ_6V50Zg)m~ zY*gBle>dB6*laFF0&;%-PLMp`6RD5@RN-wBceo{$a6EY%Y{ysPF@%bHU|maXH_ z7i0DiOcAl`cH{h@1S;T%{V6^W60Kr~`j3tD(vAj`#e%Y(I!X;*hrS9X=>a<0U#~QI zY{UmEhDm_kcZ@*1eq{g7Ho8OIN+Xe*F*kc6@*rV`*fkR37@y-`8 zaID#72@VQg&XFa)3In*0;?hiZP z#Wo?WCks3GxmFHXsdzS@y2^odPRv+aPGc9KWV}#4g^8(m?+opfExi~yVaW9EF}Ly1 zANXH(4E(c^lKhL&^Z~TBLFYjs%OU&{EJkWyjzm#c6cNZ*-8u_hvG(ZzyBeo;LFl@a z#i5*RG@tU9Y0=E3Zh8}~Ztd%Vb3OMBG}~5M7Q58%Vab+zXtk4U4`?+wG&5z9rwu?6 zj7!u}VV~X8fhU(IeURC<$dirG<6`hAf^cm;#Vgq`hlEv>>#3XjWR3WrM@nc!(mcM+UZiy0STFlU!7bW zd5UZC>K_Eu`0L^9g&8khs}Aa_EsEFNu{}ZGa0m96=Z8{iMgKIc)`iBq=97qj!hBbL zB!JUE1vltPL&HmuCHm0$F7BpKME+-NnX!C}~8+Po$CUqH0! zg+ME2%pcyKG;>pZ;FIw49Y`Ga##OgpX1WXs?V;a3dp`>7Y&L)RNa(jUZzX;BaOi{E z%`*(Ze~baY7_Mb`3H_pgF1#Ki?q&2QoOJsXd5=6rVboK|>K=9gG~nE>I*Z=b)d_<3 z%1fzfWtFfk%J^t;XoGd!o5AaB;YAZ!iwp1`s=cP&=1s2Spacuh<8B|BmHPB0jGUm4C2Q@oH=k4UUJ_8%>`c0>WLbiMPKE!VFu4)u6?Tg0=jK?cf7@5H{5ALrAl#*9X9KJIr1i z38XGk53LiuX;1)&RlUdpG{McEDN1Cx1x;{{0+;lihZ=bRC|oNOvcnHB8S72Szno%4 zy=U8~zV(SfiaGJ9klWk4^Xb*D;xs6G?EuB>BwhHn*A@AxxqBi26xFGTr#g_#_zxULG zIalNCW-@(&09- z-hTzsyX~U8a-)A+n(#ksT>GcbtvLABQAbyk<|hD1lD+d2TPA>fJ8e*P-7NtHid>Eog8G&1;a=MVh-lI@KR-UeY6Iwy-K1uBOd}-`M zYcIiYVz8g#@G6F1DQ7s@!!V}=Vd<9PcZu8JP2%>IW#X2+RNT^*h}(q=al8M{O2e&Nkb*W%(au!GG3#8|4K5rkPek9@q=W|BnKCcA-81ZDGk(M!Dm^hypes|e zhgF?Wg;~7t3ZdVTQoob@LchtFV|~%uJq+z0txh=@7HXC1ntbDlP?yk$UFzdJ_&NOT z%g{$tulnFp*XqII10c+!Qs7x!{Ti}*Acq&tChkrxNU7On3J#|)M!gOv0Dgn6sLd5x zaJV7n=q9-$l$Dp_8;-FB=bL%9kfl0k*?Jd%TV9hYex>q6sMz3*qJ5Avg;#diA;QQ1 zoR8s0hNv6!)sdi=@X8GKiHEOav2nXxaN2yer9u?QNM_yYvQ0sG_ziUQYNN=~6 zu3Y^Lbpky^LMi0ev|?i==8bs!jSugRB;{vw+n=))Z!|ChOMNDvJIurLY$JksZ8(T0 z^_dw1ZfG2nlqU{8FKEM%PSf%jUCXNfe4*u(tQjfZ)5L^01v*c1SuZU}@rhTDmX>`u zapUN_0Ku<3y3vABPJ!T@t9_}@46Xh&?;1t%GaZYZOiY@Sjcrd53e#~2Q9Z@w)Br|8 zH0&D@BDLX*uAGxG5E4Z(a5@UUz)#7x5ipUaSHYiE{Sj1-;Tu_*q&|fQyg!uMd3Fk3 zl4R7kr9Lr8zA{qyO0Xl#*&eVwj@LphuTSbpFTk(P6UhB1sjuFE7qq9S=fEpak4fw# zwRlw|^0+C%$}yKjdA#EJ7J%Rj+M+zlLFlfFkX{M!$3Rb4hk~p)WFRn5{kSX=>8*SS z@apdX*aZ>b&sV>OJZS4&vC?`L7g^l}%SFd|=FC6t!@E8*W%0mUCn{Up)d6$ND>bS{NfzZlknD_Z@<*( z097ZH8#Ojk{q71~V?&_*zXA)e^`y3_!HB`vJVS%UA0$%Mo6WFC7D?8r~J&?tHiT$AAtw_m6WP3Oe_N z^7df|&7t|~U&Oo$jpMvNydWB~+Xlr~2iBL=r$tqO6QaD+qr6E)!eMXgOW*!;Z%#tKmO{ygjyBz%TdfTrYJ4Ey6W~IMJWbVBgkV z&nfsD*5Kr-9vr8gx9S`Y7p{EA@k{?XVsMd$WrzsAKool~K7KhU2IkG*0cg132tK?Y zFJ?1GWQUc3Z!q&Z@XLxw%wWw&d{i_Z#+kzSNnTN)BZGqmw$~XP9CV;<2s=A6_QNLC z8#+jzZr*VfA}wD56srv;YNr6M`o*D_Day{bZ6~um;Q;L+@(duIy>2CrGf%G%)DPe1 zovOb?#AmgfwxvS?@j5a7bsp{QSq5WAhGzmI!P%uhhe+`58W6 zg|KwX*89aRe3!UAP%dt@9&vl5MBIM547dC5ti2nz20?=p%21vq7~X=UaDgSb^*%U4tMqK1}bjk^`Udb+OhSQQ~YNr`X*m z#+@a=kot_Z`jhCO7*h|{tfU#)(!Z)NjShz|kpue?v0-o;2gvLEL)LJ_FydJ&c)DTN z5GJlStyKhtqZ=tpoq>PP1=tECmrV3p5z{B~+5WL_WcyJ)wb-|+iF+{ccFmTA-*Q8< zv2K_>`CDgT6%_e54Lg@{%wRgI*A6?6acvqbqRUx_b&d8|CMx+_opP&I6kmAil`p-n(Omf2m#bEG_vV9Tk8&8a%;diwkb zgW3UK-8w98y>VmCCd!{$%e`ECllfp$OAzOx@#j#BXBL-l-jEF*8kn_m$vDjAS-e4d zM%H0u6vnM3%T;D|Du|)ILAJxA4CKl%7mimdF4ZS6A}6($Um`_@JK)ka?TZ7aue8RX z@^q*`z@wn5U_JOq3iz2EO}{QyQeVy1R*u!PqP$()6>swW9C?c)`I=rTV1_G+AE7)0 z6**D&Q6nEmMA%~_8tB;j#l_-hH6i9&s{Kmv!Z!^mc++sX$Z>i0Gh?zLV`5AL!xt}> z`NIwQLo@0Nr5yQa1t|yKKLW|($nYg>l-jT*)porGGC>1b;8CDw9poT(+fMfki=_l> zRoD@#8yl`|LoD<=7q=#R#sI_vfi|iufGF}uK%5CA3qj}F>!WDps|A#85TO;N zT+;BN-y(7tIp`=nPXc4)Dbi4f5#_7$A@l$lz-JD>RX+o^$g3I^$Z}83^DJx%ghOHC z|7fQvaO=y>p_gkuF{j>&W!K1DNvHJE%sFApoYY#FH&R-&_VcCwK|7Yh8B6h-VlRev ze!Rb?{gl*OXCgP8J2Z!HPf~B2jHhjOs4uuFkG{NZl#I7CK1Rkn7#}a=3P$kT(q-Jj zc&3b78J{BKDU4^!xDD~rZ4Nw$3R4O)!;loK5>siRHu}UzfpcP}AEs9u>_k2tAOL`N z(0J^H;$JFM3$B+sKMr^O!i$RfkutzoKhRx-!_+(8(4~0zwMxDSk;n!~kK^rGJ`Yeo zf+p(T>!^)Ua7EMvzQo4Q%3g*v)&$QXxEFMbvMUF=)H|05#bJ8!2hV<)15zuoSv(NU z`K2@keHEp612MddKjOlqV3At77?`ooX3vO^51v(@@si_u^|hrZNcFP4uW6l^@q+eq zmCh8tsa!b{9(yIX1T2aSccHF90Bm)`xQCyS3)O+ z(nG&fKfD%-{(a<+Il14MWJ;}_0q+0|4hGKm*g*>GOeck^Mgs6f4G_K0rjY`wDeS~r z8UKYYaV>}hj)Yd*0l3Wo*hE4bTEltm+Ego_=sbY>ng+EA=zd5jXFD<9&!~be`Bhc` zEg}%Mi?dBE*1lKR&>nWSp*IX;W#JNp9j?&ol(6$KL`R8HQMCRU7&2nn@Cc`!hqHc* z{e;egNjtL+dT$PGXeS$_aO4^(K9uxGMr1L^iSytLnDETOO#)I)`!qNR!=er(?T98d zF=+~t{wJEGhE5N8$0O-mdXn={;kClMLe4gb^KcH#bB^zUolQyLJW6#IsKOfnPy^x+ zugw*@B3MYuY^_e$ac($5_00yRan~KrIw$qkEJU&NAtq{Bjew41jco8HJF<3q zKMKuiz_4go8bD|hVhozXIKTE1XcKP%L#wMozl#J8s7+T(orEO%chQu>XJB%r`E|s< z@FwjGC5ZN-rqvM}Tr60GmwBw0_}exIW5k+9(?h(k9Hg3|DA{Z@Z$?+#;A|sQ{Mv#n zP346Ki{dYsgCY_RG-cFhypZ~X;DF%umf)4v;G&dg#oNMNN@!kk6MlhIJnnp33C$Yb zH2E@jML>I-m=u~fys7%1WfktZC+`$MFTX95D*W#9lXrGrEW5qnz~+KCK~i`|0@did zp@oB+f&~Y$hV>@=@5WHx!Roi#rbkk%KdB#JQv6u(J6Qd;NceQw0JGwEphBhbIj4$` z6AcAi1CkWKJ+1%}QVj)?lt5}|@D^y6-=m6%CVi6r&NMU&T9`GwO+3H+R!kdU6Uj9j z_Mf!=D$2xPl}M0Dl-c=~qHAZCETbofAZqp7++*S&yiG9CXnu%y13()9@Zn7ACa!!+ zGXh?B0*i2hL~zCGzeT8%uXE=mw~2*MoBsxIXWEa`#)1X=aBx@1xv%E!0X0oZu%JHZ z+*7lmen4PO&4xWnV1}fF<%y`fSf#r}1%cpAcK;a?5KE&`K)VD)L$B?gyQcv4IAQwp zh1R7LK@9a#E<0(HMLc7F`#e-YMJ)WWW*HYW69pzb=YzT)_*|XCnc;5C`g%tZN!u>I zSbSWB{F*b8Rwu&eL|8AvJtAxr;XV=W7vTXB9u(mr5%P!8BWYtq$e&fmH=ZK&i;yRJ zM$*=buu6pGBJ_x`M1;#kxKxBoM7U6dE)mWX;T#dp5}`wc*&>`G!b}mSi*Td}hl!9I zpYSEA2)PLhU!jVSD@6F_RD|5Kfp1YoI8uaM+l-`f#S?OgkjtZyG_Dv%(zq%ZN#jaR zB#kRDku*+PBWdjDB5CYoBWdh$BWWCjMbbF!iKKCOBfSUqid+6Z0nc&kLnp#V`*HMZ zpkD+18tB(RzXtj>(651h4fJcEUjzLb=+{8+8o&>IH2;1&7Ah8wvW91*gl->!^~sDz z_0O{+k-8=4nr$LCh9?}h0ibzJF5d5iTd^G3TrQ%Gj20mpMf5OIfaoqD3SHiT*w=~D zavo5w2f-N1xL7#sApRM6gB8V8E-}q!`7vZEyZiE#Co;`YSd(WU3qN=^3NtrGlJMW2 zVSpb6@{}J8)KwAw_wn$$o;0{82)Fhe!2K`|PNzWp`;BVR%B^EL7t2+~l_UIvUBJ&J zyq)mVh{}#;SX3N*URa5 zc^s-Z{&Xakerg`ELtmc*XU$Ge2ha_3&eHAMmk9qdY*E)v_8R&Gzm4z?@^f#` z@D}N3HxfRb_*ZlZkDt73o&m|Cd?~Wrr7X9HmEoeea(Hvpd@L!_=tQYnF^=qJC1N|E4*Fe7p`Zdt6f&V%Uq$%Z! zPm{XP<@XjAS629o@F{`Uer}FEUF4jn6e=#oui&?^lwze)sX)9)DZ|~3G`n)Hl7lc^ zIS=7i@sIJuzhY&ovQ()8e5JAiVUe;5aHYUf4lF*z$0%!+DayslSir49da;1@0lpOU z?8<7S`IG=kmgC8Xw185Av`RcpR5Fzbpt=-L#R$n;G2jT{#`9WWbmOW7&UIaI7tbwm zmw?}?kT(}t%2BQubp61g|D7jjtQOpq0osn37m@`8C2HtA9aP+@TV10uefF%o!kLwU z3co!&$3Dhac9Xl%KX%N86UW*wvKKD6Nk>jxGr?a#SjDbn3tNnVe#(@az;7Wq84KNB z2px?DmlxqGR2CqwCu`xXO@j_*0S~p_l^(_jrwRDrFJHBfDV#C)vclYX^RAkiyKwGR zS0Xz1vMckPS1z<)XrDNCtUX6}DV=?AF$VnT-W32{=~MI~=FCv$DwpA&tIWf{s~~Hx zvJh!kDOYN#%)e5}Q=AAFqSS@BC@ZDS>4RrRSrHMhy9^)Ac&of_e_?Tv&z~K@a~0}^ z-+sX~DJ-7GUVGuh>nKg4zzZPj+$){);*sL%Ja~s&_zb3Kv06dj?ujS8 z{ujc^vW66(t~-_aD765!<{HF%=Q}giY#V?dn;C>JVm`px6C3f9T#nOBwhefG#b&aN z7;3T&KHX&d9@1Y&`p1Cz2=Lj+e;9c&DHLTtaJ3>&PxI&z+?Qza{iopbIlz1exb*BV zyT4%j=N0vFh0ap9cc0%|R)vWnG3aUgYylomyCUncKh6?epik{hrE_xfP|Ah0JD>4leG~dxCUCF*+Oz zE_2N&$aAk+?)JuTI%ZZ?6;zZ7O0&I1tK4fUy*D@(mH7fiTV_UP^hGfe@mem_K;q*wHytwC8nkcP2E>O=l;0(1f=-z zF91kF#hx;Nagu=Lc%Te+q`sx@b?USOM>cD5CsjAbCdHsw)twLt>AHPp!Y9}__&Qlq zQKIUZI@PGQsZ+bs?9{2WpD=NDF%4~x8fcoq<^q5wPvD1-F?&a$9MeZd(K;4IF*+Pa zXIAsK0m^DhOzAu%4;GFs9aNFcdspW8-=M%iR?#{LyCHmTb1!2bgVZPr@0=xDMg6;Z$>z#v1R& zD^4E2#~oA0Xl1m0O_47q+ytUn7!LxNG9Q#i3kdzKDMPyy@M}p?HM&bgX^n{7{`0PdY(m+KqRRu>VFP~tyFZ6&R{133L z0(g<#>-Gi8{lrHQa%w15;zot@0wEcwa7CR(p@yZaFM;FthCha zMWHT`Oc*q{2eG@&$^|Ov^+i#?r;AJH_s~A508~g^4S*u%mfHQ)1#QS0B}6GIA8%h* z8L<0cr1BE`sv_SFfO6Y?l>u+D&^ez3Ng*_!imIr4R1&}2zP!xOGC)7O(hFxUT2)o< zo@%!b8a%MrQ{=T5+c6m`!&D=E?d;iOvjz>G=_#sMA$niptt?^H()n5krI(l6Y26|y zt{9aYKsUl}W&U-v7ayhdh@leoW)+5;B?8Ye>Qh&AT)PnG<}o|vLZxvn1mWUZ3BRUY zt+8;I#0rB@9F2q~qZ^Wn-cHQN%(85i+dk9dMw{sLdMmwgxaW(0kv$w|&UVbZJh&Gt zHz4jrpT>E-7ya5GWiV0-(1#W&%h6vE){i_p#vkl8t3)5R4$t#3?wE-?M=0#miZEMd z&)D;y+Pbu z$ev(1tuKLJy1i_Pmamik9BqhD=c4p@^j+NFpjoKAQ|;MI9F!}%57ER_1ULc{t&m

*xE4;Uur z#ySz`;^;dE9?;K&`-y8g{HFrrxjf8|T*@r0H*hT}9ez1WtT+&65&U*Oe4OhGa{=kp zU}gcT1m0IppoG67czzk?=&Rs`7b( zT+JsQ@39is+SAM!%bS=K&Eo<4!*Cr&-eKe|W#%#d@uB)k0v+n3Lrb%qDtRCr;rVC8n^;ytz%Me*HiuSLPos!=w&IXm`Tt zxQsj}mepZe0l!cytEUmKM@y^H?u66xjd&Bw>M)If->#L_(}et5+CJQM+=T5?hY96% zJhsVbr-sM)VLI(NQcD|;yAeO(yRu^<4jq60y?YJJUodNd=_k(fS3dkLNA<#jukM=u z-uGy`nNyb*U}EE2ikECF-G%At=}Q-gwSlGc-B=`FS`om?*wQPxI6Psl&lhm}mWo?O z`lO|^+{*(imR^Y^v(;`c82Tnul`Pj-I2U(b{5wb9`on$=bgO|BtatM27N9?)UjzST z8u$$Fb9w!jiQO-54;tVvMD&oY@WfI6ajf_F%O)G)+0T1$fYH<1y3h}4HbI9drlhV`AFlHc4_UUJj>xM4&fjBw7dSCeuu=XKOacQzvs^Q{QZXfncpzk zDsd4%|IV<<_`^7RhtJfY`TeV%E|KSg^N&T`iel{Bp9 zOFUxUhq#dr;W+2x-H2zsfsQWka~dCfo_(*$b~P@R>neZ5eJ0yDT*mx5L;1ZwHrf7; zi~0S3|MxZE(%uKurI|&0$MrB?9z1Wx^(e0NQ%uUPp(f?>(@e?%vstmG!>gB>6O`7dCHQ%2@w>~L~q z7F^8O45dpxz6h;@1#ob&BbO|IU=cTeIKDJ>2-Qc~i0USq635sVq#sEp$^x||47OVL zQlsza{&j-)#u;5V-gk2U@3)`*9DaEkNC#=-yJ_@CapR6B&iM5#{?aHp{faNZAQdZd;{;pZ>O8&BAK-DFl zN*bga3w6Pc5eMWcgtgs9Fk}AoQH66Weajtli~z81JdE_0V^NI}Pi)gPc1a%Hrt2R4 z&9Id_3TLEyV*0iy>S)ABgL|(F%eWcbA@YrR*uyM$M)#oO=sxHkU<4lSb1uX_=R zIRQM%TYscq1OJH{Sh&Efl;T>C>zlZKhHD?LCR|5w*%o3Ripzm(5w0p+TX6jV*ORy! zaUH~U7*`uEbAeeo1(zL{1LcqB?`Ky4C$7wSX605~`*C^kyeQAC+=uIJT;s1aD;A{f zy4Xrs4W@4tT}o#WexfdzYD&|G~8!*V(w5W`m9sGU0wJuI0Gq z;2MQ%5U$fe=S2NI34VuMh5drKT!Iz;y~P z3$AvQYsK|8u0P^>6xUu{$NRT`W(>y+=<FI^|^6Z8(zZ}ct=Q=SO zy+(NiITJhO@Y~8qOw$X+-kQQy*zI4mLL1y(ox8*}qi}KhTohKmWu9KRBw^qM02IH6 zIdbq+=;n7MlB_5_3q|i`K0ethtSl|`;$s~ncIHne$A&3X-=;WoAr*NKQL3XZU7(d- zoHX6N93KT@r7=2%>m&UcPFHT>Vz973Y5EEr32;0plsT5^Rbp0g9DL*h$MN!U4fJum z!UUj?<$+aI$Doj0=MsAA9?Nu}+rQ%25YQ^=>A+G9S&mmBSI{oUCChJxEXSw}AqzxN zUOa9r)W|~AL{~*wMe#9dR&#gd7GsUWbhx*vv|iV2#(Lg|a(WFPZJIv)V!dAUamtJ~ zFE@8l`b5$2DW@k;?q$Iv)@-`s)^>Vr7$!Pst6LNF(OXI(XWwyXjfg_q9&rwbO%bWO}m zpEZH+AuY;uehYb3#mvfz5^xmYzOW$QL~oVuJBdhzS&DVP$2x zZ$ek6LkPR(D1}w{z?C1T<2yingIZjLj{`4bY*poIw=#z@^vZlgP&jk#+Jzo(?z7{=*#P$wR{CP zxO@HhDDwi50#}nMWRu(jDw+lX+)8!wnqnVD*8I{i4u{g9;Bcz)ax7bVly(b`tEeni zlo_VNQm@-xSh9SD@@;ZkMrU57d?yw!bSrmA4VP7FiYr|0!^ig({?bC9@^?z(DJ(53 zN5MkY_d}pmSy5WH0?tubQH1gRn!+OQiq(p8kHjiYv5025D^{0zD=SvvGyp}pHx2@4 zAFM6&7y5Y?hN8S>F7&!rz@(s~<>=odD=8+}*j*g(yY(ztNChvjFFxG_BIRBEsqn@? zS@8`hf<)y-q{^5(nZlP<09U2#B&7>-_G8m5a+t+&GrNtGtw~M;BH` z^OakZ3o4}VmP9eY1;8x6j!1?FbelcU4A-6+@bUx*m$$MQO`}qjyik`?Pf}KaXYHZV zJKJ64hwXIUFSLlyfn9Dd&LKfBTI?2w%z^lz&e-RcC_@aXsPpck5+o{rWa@%-u-+P8mYMNE%qlqQ3R-NptmMPuvUU!S-{2PWhF<yQo|Ut|;F# z5mIAJc|eEPzI9iWE1dIjfLq2SVgAHrfd1!x4g5!G;7OC&b`aMb?S1us6!ZTEX4MG3 z`%%ocQMi`iYQm-OVgEOf>qptIfiGJFCx6)t^waUF?|1ldjT$PR(fs1EN@aq5jzpH`WhI0}B?{eAC z*%zY$lfw1@*T#5y{PsXm(z{3>h>yYMAg^khS=qo%p#6V6Y9M?1+EqC7Q*I5<89i}A z`e?hm0tXY~gu$HA1q)|iIAyfm=f~Pr5zY#B=Zs$G_KluCZSbIM9LtJRa&e+05LEbb zMhCnVQ+-&U#CeDpuF?;~y%39CQ;U47Caj(~8mB8(l$B!LOxtrVsR5PUp6&Mre16WR zHEiegf-U1bQ4S@2Sa|W`d_?UTDPH%DIEKORE^&FwR%4#O!tIM@8nc~Z%?96X%yX}H zm*d<}ggK*&d{_sqyus}qZ4Z>?7IV=eXLM;%xz9b?eo+)p_C?)O$-XF#r|gTO!l8Kf zMY`Vb(4%GgGy660U#J1QiS4>_Px`OZ@0s}g#6=k!GM>*cWu|9lXFitsROaZ(7fx1k zF3u^;c_$}z+DFrsba|HDIq7@T-%tPg#K$Jio>V#Mok^(~V>28Xdo$k8I6d>+%!@LY zWp2!TcXHCjXI^Z-_|c1-E}1fA@09naJbme#moA!m%hY?P{&;FemM81)S=MZO_I=qO zWuKFC-!#QxQVxQbnG@fiIC9dMNkcL+fpbsBNtt6ZF9AQZGV?MEGAlCIWUkMw&b%%2 z&CGvfewg{MOv~hy$!U`(OwO1*W%9Jii>7Rxa?hpPFTHeX`P8*jv$L+qa%C;aD#|L$ zx-~15bywEbtnX(1KI@IFzh<>$9m(p*8lHW2_W9XkvL|FO&%QBxb@t8Kw`Jd%{b=?R z*-vNxGJ9wC!R&XlTeFX3f0mt`b9T=S+0)LSHg=lgGU0bOa5iANKYe5R*CxL*`S4`R#g?r6 ztmRpov+mBiH0P?EvYhIi@8#^uIhgbHXJ6SkvA>uDPuWKMrAXUS@2vQ_SqSGYB*QMIYBl<-!0t96UsrVr_F^fW*6 zQ+|to%>UNEHkcOJAQ{}ptTqLI4-N)Sm=}iOec`t79p-Z+oRp}KHbp(rn~eTev)nY9 zZqsj$n6qZwTx)N#Wp;(#Z9lYU?RVB$%fExp$6@>CSN%x{tfhyF1+j?m0JCm8ep6r&^|3y#3xG@38lUH{_l6M!a+0MQ_~8){}Ki z=j*usl`hsZwA6tv)xXgd`Yv6mm+F+R(RF&IUZdCPX1z(b>UQ0!yY&veM}MNf)SrY2 zR)38D_#r#WVdsa@?a`Cb*65As!{}ep$>>UxWAe=g^Nh*2H`)?A+s?BK?EQAFby~8V zF2dPr?o zr`1_?NtJrt-mBi*-gWv3y;c81f1%H?9%=oZ9@jH{*Dv$O{i}k)peASt_6Gk6RCp|` zN&GW$GV$-klxPm?@NRS@N=H|l8D`j|4ccY4+j4Q8SXef+E*CwTet_`$B*$Cj^cS-L{)Sz)zWgRr$?!Yo}^ZKj$Wi!Xa~JPduV_@rqAgF z{ahBwJ7p#N@qKwp{>p9|;eSKT-P)H> z(X41*w17Kub<`AXj)tT2(Ogq$R+!bM)$B5Pb}WN^dzQ1Ehhl+ZK>Pys!3S^;uEWQ% z3-{syJcYUJIG+~LJzVQ!^fg^CZu>kp@(=i*a`z1RU-@%`+2QZPzVO}fw^1@$!nHmf zb#QLHW_BC0pr;50khE ztMFc|#pPI!kFbtUU<+=>4tx=N@HO0pZ(={bht6Qu#J>KZD1<853J2jll;9G44FAST zJCsj~{!A~^XLOZxEvwggzUm)X19|)*NgiY*ASx`!)(qwNt!AsNtLviQnZGe zsgwGspI5*P+EGeVj*Q6yUYpBh1>>xib+TD@$}ZU_`{h2)kW=hc$IWx&ZlPP`PIt@P z0zd9gPl!aVsW%@9}4;WIK$5sT9k^EsAsHgjB-F66-6K*$x1iEMreb6 z#xe>Q*-5$Vnre1QGqy5{9>&mz1FX*oE8|cu#VC&o8A%4k3hwt-Y9mMRIenfdCMwyZ zIqbDQzPF4qHq+ol0`|)Nazw^5Pu#7#Vqy>alO{39<3x%>Avi63uAkqY__C52FTXA` LaG8Psn}PoUtSnpP literal 0 HcmV?d00001 From 284b6f5d1fb4095e4ecfb979b8514227cbb8f15a Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 30 Oct 2025 08:25:41 -0700 Subject: [PATCH 172/190] Add unit tests of netcore3.1 driver --- src/Directory.Build.props | 27 ++-- .../Drivers/NUnit3FrameworkDriverTests.cs | 8 +- .../Drivers/NUnitNetCore31DriverTests.cs | 149 ++++++++++++++++++ .../Drivers/NUnitNetStandardDriverTests.cs | 3 +- .../Internal/ExtensionAssemblyTrackerTests.cs | 1 + .../Drivers/NUnitNetStandardDriver.cs | 2 +- 6 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetCore31DriverTests.cs diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ac43a9f29..e77b82bd5 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,18 +1,17 @@ - - ..\..\..\bin\$(Configuration)\ - true - 11 - true - NUnit Software - Copyright (c) 2022 Charlie Poole, Rob Prouse - - - - 3.0.0.0 - 3.99.0.0 - 3.99.0.0-VSIDE - + + + 12 + strict + true + 3.99.0.0 + $(MSBuildThisFileDirectory)\..\bin\$(Configuration)\ + true + true + + NUnit Software + Copyright (c) 2022 Charlie Poole, Rob Prouse + \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnit3FrameworkDriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnit3FrameworkDriverTests.cs index 26a79fc5c..a0d424327 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnit3FrameworkDriverTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnit3FrameworkDriverTests.cs @@ -104,6 +104,8 @@ public void RunTestsAction_AfterLoad_ReturnsRunnableSuite() public void RunTestsAction_WithoutLoad_ThrowsInvalidOperationException() { var ex = Assert.Catch(() => _driver.Run(new NullListener(), TestFilter.Empty.Text)); + if (ex is TargetInvocationException) + ex = ex.InnerException; Assert.That(ex, Is.TypeOf()); Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); } @@ -118,12 +120,6 @@ public void RunTestsAction_WithInvalidFilterElement_ThrowsNUnitEngineException() Assert.That(ex, Is.TypeOf()); } - private static string GetSkipReason(XmlNode result) - { - var propNode = result.SelectSingleNode(string.Format("properties/property[@name='{0}']", PropertyNames.SkipReason)); - return propNode == null ? null : propNode.GetAttribute("value"); - } - private class CallbackEventHandler : System.Web.UI.ICallbackEventHandler { private string _result; diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetCore31DriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetCore31DriverTests.cs new file mode 100644 index 000000000..97767d43a --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetCore31DriverTests.cs @@ -0,0 +1,149 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETCOREAPP +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Xml; +using NUnit.Tests.Assemblies; +using NUnit.Framework; +using NUnit.Framework.Internal; +using System.Runtime.Loader; + +namespace NUnit.Engine.Drivers.Tests +{ + // Functional tests of the NUnitFrameworkDriver calling into the framework. + public class NUnitNetCore31DriverTests + { + private const string MOCK_ASSEMBLY = "mock-assembly.dll"; + private const string LOAD_MESSAGE = "Method called without calling Load first"; + + private IDictionary _settings = new Dictionary(); + + private NUnitNetCore31Driver _driver; + private string _mockAssemblyPath; + + [SetUp] + public void CreateDriver() + { + var assemblyName = typeof(NUnit.Framework.TestAttribute).Assembly.GetName(); + _mockAssemblyPath = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); + _driver = new NUnitNetCore31Driver(); + } + + [Test] + public void Load_GoodFile_ReturnsRunnableSuite() + { + var result = XmlHelper.CreateXmlNode(_driver.Load(_mockAssemblyPath, _settings)); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("Runnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo(MockAssembly.Tests.ToString())); + Assert.That(result.SelectNodes("test-suite").Count, Is.EqualTo(0), "Load result should not have child tests"); + } + + [Test] + public void Explore_AfterLoad_ReturnsRunnableSuite() + { + _driver.Load(_mockAssemblyPath, _settings); + var result = XmlHelper.CreateXmlNode(_driver.Explore(TestFilter.Empty.Text)); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("Runnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo(MockAssembly.Tests.ToString())); + Assert.That(result.SelectNodes("test-suite").Count, Is.GreaterThan(0), "Explore result should have child tests"); + } + + [Test] + public void ExploreTestsAction_WithoutLoad_ThrowsInvalidOperationException() + { + var ex = Assert.Catch(() => _driver.Explore(TestFilter.Empty.Text)); + if (ex is System.Reflection.TargetInvocationException) + ex = ex.InnerException; + Assert.That(ex, Is.TypeOf()); + Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); + } + + [Test] + public void CountTestsAction_AfterLoad_ReturnsCorrectCount() + { + _driver.Load(_mockAssemblyPath, _settings); + Assert.That(_driver.CountTestCases(TestFilter.Empty.Text), Is.EqualTo(MockAssembly.Tests)); + } + + [Test] + public void CountTestsAction_WithoutLoad_ThrowsInvalidOperationException() + { + var ex = Assert.Catch(() => _driver.CountTestCases(TestFilter.Empty.Text)); + if (ex is System.Reflection.TargetInvocationException) + ex = ex.InnerException; + Assert.That(ex, Is.TypeOf()); + Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); + } + + [Test] + public void RunTestsAction_AfterLoad_ReturnsRunnableSuite() + { + _driver.Load(_mockAssemblyPath, _settings); + var result = XmlHelper.CreateXmlNode(_driver.Run(new NullListener(), TestFilter.Empty.Text)); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("Runnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo(MockAssembly.Tests.ToString())); + Assert.That(result.GetAttribute("result"), Is.EqualTo("Failed")); + Assert.That(result.GetAttribute("passed"), Is.EqualTo(MockAssembly.PassedInAttribute.ToString())); + Assert.That(result.GetAttribute("failed"), Is.EqualTo(MockAssembly.Failed.ToString())); + Assert.That(result.GetAttribute("skipped"), Is.EqualTo(MockAssembly.Skipped.ToString())); + Assert.That(result.GetAttribute("inconclusive"), Is.EqualTo(MockAssembly.Inconclusive.ToString())); + Assert.That(result.SelectNodes("test-suite").Count, Is.GreaterThan(0), "Explore result should have child tests"); + } + + [Test] + public void RunTestsAction_WithoutLoad_ThrowsInvalidOperationException() + { + var ex = Assert.Catch(() => _driver.Run(new NullListener(), TestFilter.Empty.Text)); + if (ex is TargetInvocationException) + ex = ex.InnerException; + Assert.That(ex, Is.TypeOf()); + Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); + } + + [Test] + public void RunTestsAction_WithInvalidFilterElement_ThrowsNUnitEngineException() + { + _driver.Load(_mockAssemblyPath, _settings); + + var invalidFilter = "foo"; + var ex = Assert.Catch(() => _driver.Run(new NullListener(), invalidFilter)); + Assert.That(ex, Is.TypeOf()); + Assert.That(ex.InnerException, Is.TypeOf()); + } + + private class CallbackEventHandler : System.Web.UI.ICallbackEventHandler + { + private string _result; + + public string GetCallbackResult() + { + return _result; + } + + public void RaiseCallbackEvent(string eventArgument) + { + _result = eventArgument; + } + } + + public class NullListener : ITestEventListener + { + public void OnTestEvent(string testEvent) + { + // No action + } + } + } +} +#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetStandardDriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetStandardDriverTests.cs index ecf2edc7b..1a3c9532a 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetStandardDriverTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetStandardDriverTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Xml; using NUnit.Tests.Assemblies; using NUnit.Framework; @@ -106,7 +107,7 @@ public void RunTestsAction_AfterLoad_ReturnsRunnableSuite() public void RunTestsAction_WithoutLoad_ThrowsInvalidOperationException() { var ex = Assert.Catch(() => _driver.Run(new NullListener(), TestFilter.Empty.Text)); - if (ex is System.Reflection.TargetInvocationException) + if (ex is TargetInvocationException) ex = ex.InnerException; Assert.That(ex, Is.TypeOf()); Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs index a9ab79bce..eec4ad525 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs @@ -26,6 +26,7 @@ public class ExtensionAssemblyTrackerTests public void CreateTracker() { _tracker = new ExtensionAssemblyTracker(); + Console.WriteLine($"Current assembly version is {THIS_ASSEMBLY_VERSION}"); } [Test] diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs index 7826a1669..bc8e31a00 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs @@ -1,6 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if NETSTANDARD +#if NETSTANDARD2_0 using System; using System.Linq; using System.Collections.Generic; From a6a7b00ddb556f37b8ec4ad164b3797a2e1cbb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Pichler?= Date: Tue, 18 Nov 2025 12:05:46 +0100 Subject: [PATCH 173/190] Add .NET10 agent; Remove .NET6 agent --- build.cake | 16 ++++++------ choco/nunit-console-runner.nuspec | 24 ++++++++--------- global.json | 2 +- nuget/runners/nunit.console-runner.nuspec | 26 +++++++++---------- .../nunit-agent/nunit-agent.csproj | 2 +- .../nunit.engine.core.tests.csproj | 2 +- .../nunit.engine.core.csproj | 7 ++++- .../nunit.engine/Services/AgentProcess.cs | 2 +- .../mock-assembly-x86.csproj | 2 +- .../mock-assembly/mock-assembly.csproj | 2 +- 10 files changed, 45 insertions(+), 40 deletions(-) diff --git a/build.cake b/build.cake index 648f8b8d2..fd71db766 100644 --- a/build.cake +++ b/build.cake @@ -64,16 +64,16 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasFiles("LICENSE.txt", "NOTICES.txt"), HasDirectory("tools").WithFiles("nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE), - HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE) + HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net10.0").WithFiles(AGENT_FILES_NETCORE) }, symbols: new PackageCheck[] { HasDirectory("tools").WithFiles(ENGINE_PDB_FILES).AndFile("nunit3-console.pdb"), HasDirectory("tools/agents/net462").WithFiles(AGENT_PDB_FILES), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("tools/agents/net9.0").WithFiles(AGENT_PDB_FILES_NETCORE) + HasDirectory("tools/agents/net9.0").WithFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("tools/agents/net10.0").WithFiles(AGENT_PDB_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.NuGetTestDirectory + $"NUnit.ConsoleRunner.{BuildSettings.PackageVersion}/tools/nunit3-console.exe"), @@ -111,9 +111,9 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { checks: new PackageCheck[] { HasDirectory("tools").WithFiles("LICENSE.txt", "NOTICES.txt", "VERIFICATION.txt", "nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), - HasDirectory("tools/agents/net6.0").WithFiles(AGENT_FILES_NETCORE), HasDirectory("tools/agents/net8.0").WithFiles(AGENT_FILES_NETCORE), - HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE) + HasDirectory("tools/agents/net9.0").WithFiles(AGENT_FILES_NETCORE), + HasDirectory("tools/agents/net10.0").WithFiles(AGENT_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ChocolateyTestDirectory + $"nunit-console-runner.{BuildSettings.ChocolateyPackageVersion}/tools/nunit3-console.exe"), @@ -132,9 +132,9 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("NUnit.Extension.TeamCityEventListener.1.0.10"), HasDirectory("NUnit.Extension.VSProjectLoader.3.9.0"), HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), - HasDirectory("bin/agents/net6.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), HasDirectory("bin/agents/net8.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), - HasDirectory("bin/agents/net9.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) + HasDirectory("bin/agents/net9.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE), + HasDirectory("bin/agents/net10.0").WithFiles(AGENT_FILES_NETCORE).AndFiles(AGENT_PDB_FILES_NETCORE) }, testRunner: new ConsoleRunnerSelfTester(BuildSettings.ZipTestDirectory + $"NUnit.Console.{BuildSettings.PackageVersion}/bin/net462/nunit3-console.exe"), diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 0f2f8a30d..3cc584f44 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -52,18 +52,6 @@ - - - - - - - - - - - - @@ -87,5 +75,17 @@ + + + + + + + + + + + + diff --git a/global.json b/global.json index dcc8f8f12..393147ff6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100", + "version": "10.0.0", "rollForward": "feature" } } \ No newline at end of file diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 7e637f6af..8e3c1138b 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -40,19 +40,6 @@ - - - - - - - - - - - - - @@ -79,6 +66,19 @@ + + + + + + + + + + + + + diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index 542dbc8d0..1c005b161 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -3,7 +3,7 @@ Exe nunit.agent - net462;net6.0;net8.0;net9.0 + net462;net8.0;net9.0;net10.0 app.manifest ..\..\..\nunit.ico false diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 76037d0e5..938fe7ff3 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net462;netcoreapp3.1;net6.0;net8.0;net9.0 + net462;netcoreapp3.1;net6.0;net8.0;net9.0;net10.0 Exe true ..\..\nunit.snk diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index 6e22a717f..a57e0dc93 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -2,7 +2,7 @@ NUnit.Engine - net462;netcoreapp3.1;net6.0;net8.0;net9.0 + net462;netcoreapp3.1;net6.0;net8.0;net9.0;net10.0 $(NoWarn);SYSLIB0011;SYSLIB0012 true ..\..\nunit.snk @@ -32,6 +32,11 @@ + + + + + diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index ef8de440f..103d09beb 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -127,7 +127,7 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re agentExtension = ".exe"; break; case RuntimeType.NetCore: - runtimeDir = major >= 9 ? "net9.0" : major >= 7 ? "net8.0" : "net6.0"; + runtimeDir = major >= 10 ? "net10.0" : major >= 9 ? "net9.0" : "net8.0"; agentName = "nunit-agent"; agentExtension = ".dll"; break; diff --git a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj index 876fe1c72..383539b63 100644 --- a/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net462;netcoreapp3.1;net6.0;net7.0;net8.0;net9.0 + net462;netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 ..\..\..\bin\$(Configuration)\testdata\ true ..\..\nunit.snk diff --git a/src/TestData/mock-assembly/mock-assembly.csproj b/src/TestData/mock-assembly/mock-assembly.csproj index 459ee644e..d454c082d 100644 --- a/src/TestData/mock-assembly/mock-assembly.csproj +++ b/src/TestData/mock-assembly/mock-assembly.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net462;netcoreapp3.1;net6.0;net7.0;net8.0;net9.0 + net462;netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 ..\..\..\bin\$(Configuration)\testdata\ true ..\..\nunit.snk From ef4b3ea948e1d271de32f7943c4ea187e51ea657 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 19 Nov 2025 21:44:49 -0800 Subject: [PATCH 174/190] Add package tests of .NET 10.0 agent --- .../workflows/NUnitConsoleAndEngine.CI.yml | 4 ++- package-tests.cake | 31 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index c7991d975..f189fcb1d 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -39,7 +39,9 @@ jobs: 3.1.x 6.0.x 7.0.x - 8.0.100 + 8.0.x + 9.0.x + 10.0.x - name: 🔧 Install dotnet tools run: dotnet tool restore diff --git a/package-tests.cake b/package-tests.cake index e552207ea..52a0b1443 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -46,6 +46,13 @@ public static class PackageTests ExpectedResult=new MockAssemblyExpectedResult("net-4.6.2") }); + AllLists.Add(new PackageTest(1, "Net10Test") + { + Description = "Run mock-assembly.dll targeting .NET 10.0", + Arguments = "testdata/net10.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-10.0") + }); + AllLists.Add(new PackageTest(1, "Net90Test") { Description = "Run mock-assembly.dll targeting .NET 9.0", @@ -145,11 +152,11 @@ public static class PackageTests ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2", "net-4.6.2") }); - StandardAndZipLists.Add(new PackageTest(1, "Net60PlusNet80Test") + StandardAndZipLists.Add(new PackageTest(1, "Net60PlusNet80PlusNet10Test") { Description = "Run mock-assembly targeting .NET6.0 and 8.0 together", - Arguments = "testdata/net6.0/mock-assembly.dll testdata/net8.0/mock-assembly.dll", - ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0") + Arguments = "testdata/net6.0/mock-assembly.dll testdata/net8.0/mock-assembly.dll testdata/net10.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0", "netcore-10.0") }); StandardAndZipLists.Add(new PackageTest(1, "Net462PlusNet60Test") @@ -195,6 +202,22 @@ public static class PackageTests } }); + AllLists.Add(new PackageTest(1, "Net10AspNetCoreTest") + { + Description = "Run test using AspNetCore targeting .NET 8.0", + Arguments = "testdata/net8.0/aspnetcore-test.dll", + ExpectedResult = new ExpectedResult("Passed") + { + Total = 3, + Passed = 3, + Failed = 0, + Warnings = 0, + Inconclusive = 0, + Skipped = 0, + Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("aspnetcore-test.dll", "netcore-8.0", "netcore-10.0") } + } + }); + ////////////////////////////////////////////////////////////////////// // WINDOWS FORMS TESTS ////////////////////////////////////////////////////////////////////// @@ -311,7 +334,7 @@ public static class PackageTests { Description = "Run mock-assembly using the .csproj file", Arguments = "../../src/TestData/mock-assembly/mock-assembly.csproj --config=Release", - ExpectedResult = new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0", "netcore-9.0"), + ExpectedResult = new MockAssemblyExpectedResult("net462", "netcore-3.1", "netcore-6.0", "netcore-7.0", "netcore-8.0", "netcore-9.0", "netcore-10.0"), ExtensionsNeeded = new[] { Extensions.VSProjectLoader } }); From 185c9ccb606e52aaa4faf1264b2256b1ebc2777a Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 24 Nov 2025 02:27:26 -0800 Subject: [PATCH 175/190] Enhanced assembly resolution for windows and aspnetcore --- .../nunit.engine.core/DotNetHelper.cs | 122 ++++++++++----- .../Internal/TestAssemblyResolver.cs | 143 ++++++------------ .../nunit.engine/Services/AgentProcess.cs | 2 +- .../RuntimeLocators/NetCoreRuntimeLocator.cs | 96 +----------- 4 files changed, 139 insertions(+), 224 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs index bba07a275..6634601fa 100644 --- a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -2,65 +2,113 @@ using Microsoft.Win32; using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing.Drawing2D; using System.IO; +using System.Linq; using System.Runtime.InteropServices; namespace NUnit.Engine { public static class DotNet { - public static string GetInstallDirectory() => Environment.Is64BitProcess - ? GetX64InstallDirectory() : GetX86InstallDirectory(); + private const string X64_SUBKEY1 = @"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"; + private const string X64_SUBKEY2 = @"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x64\"; + private const string X86_SUBKEY1 = @"SOFTWARE\dotnet\SetUp\InstalledVersions\x86\InstallLocation\"; + private const string X86_SUBKEY2 = @"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"; - public static string GetInstallDirectory(bool x86) => x86 - ? GetX86InstallDirectory() : GetX64InstallDirectory(); + public static readonly string X64InstallDirectory; + public static readonly string X86InstallDirectory; + public static readonly List Runtimes; - private static string _x64InstallDirectory; - public static string GetX64InstallDirectory() + public class RuntimeInfo { - if (_x64InstallDirectory == null) - _x64InstallDirectory = Environment.GetEnvironmentVariable("DOTNET_ROOT"); + public string Name; + public Version Version; + public string Path; + + public RuntimeInfo(string name, string version, string path) + : this(name, new Version(version), path) { } - if (_x64InstallDirectory == null) + public RuntimeInfo(string name, Version version, string path) { -#if NETFRAMEWORK - if (Path.DirectorySeparatorChar == '\\') -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) -#endif - { - RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); - _x64InstallDirectory = (string)key?.GetValue("Path"); - } - else - _x64InstallDirectory = "/usr/shared/dotnet/"; + Name = name; + Version = version; + Path = path; } - - return _x64InstallDirectory; } - private static string _x86InstallDirectory; - public static string GetX86InstallDirectory() + ///

+ /// Static constructor initializes everything once + /// + static DotNet() { - if (_x86InstallDirectory == null) - _x86InstallDirectory = Environment.GetEnvironmentVariable("DOTNET_ROOT_X86"); +#pragma warning disable CA1416 + X64InstallDirectory = + Environment.GetEnvironmentVariable("DOTNET_ROOT") ?? ( + IsWindows + ? (string)Registry.LocalMachine.OpenSubKey(X64_SUBKEY1)?.GetValue("Path") ?? + (string)Registry.LocalMachine.OpenSubKey(X64_SUBKEY2)?.GetValue("Path") ?? @"C:\Program Files\dotnet" + : "/usr/shared/dotnet/"); + X86InstallDirectory = + Environment.GetEnvironmentVariable("DOTNET_ROOT_X86") ?? ( + IsWindows + ? (string)Registry.LocalMachine.OpenSubKey(X86_SUBKEY1)?.GetValue("InstallLocation") ?? + (string)Registry.LocalMachine.OpenSubKey(X86_SUBKEY2)?.GetValue("InstallLocation") ?? @"C:\Program Files (x86)\dotnet" + : "/usr/shared/dotnet/"); +#pragma warning restore CA1416 + Runtimes = new List(); + foreach (string line in DotnetCommand("--list-runtimes")) + { + string[] parts = line.Trim().Split([' '], 3); + Runtimes.Add(new RuntimeInfo(parts[0], parts[1], parts[2].Trim(['[', ']']))); + } + } + + /// + /// Get the correct install directory, depending on whether we need X86 or X64 architecture. + /// + /// Flag indicating whether the X86 architecture is needed + /// + public static string GetInstallDirectory(bool x86) => x86 + ? X86InstallDirectory : X64InstallDirectory; + + public static IEnumerable GetRuntimes(string name) => Runtimes.Where(r => r.Name == name); - if (_x86InstallDirectory == null) + private static IEnumerable DotnetCommand(string arguments) + { + var process = new Process { -#if NETFRAMEWORK - if (Path.DirectorySeparatorChar == '\\') -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) -#endif + StartInfo = new ProcessStartInfo { - RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); - _x86InstallDirectory = (string)key?.GetValue("InstallLocation"); + FileName = "dotnet", + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true } - else - _x86InstallDirectory = "/usr/shared/dotnet/"; + }; + + try + { + process.Start(); + } + catch (Exception) + { + // Failed to start dotnet command. Assume no versions are installed and just return + yield break; } - return _x86InstallDirectory; + while (!process.StandardOutput.EndOfStream) + yield return process.StandardOutput.ReadLine(); } + +#if NETFRAMEWORK + private static bool IsWindows => Path.DirectorySeparatorChar == '\\'; +#else + private static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); +#endif + } } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index 1d7059d05..00c2037a0 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -7,6 +7,7 @@ using Microsoft.Win32; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -22,20 +23,9 @@ internal sealed class TestAssemblyResolver : IDisposable private readonly AssemblyLoadContext _loadContext; - private static readonly string INSTALL_DIR; - private static readonly string WINDOWS_DESKTOP_DIR; - private static readonly string ASP_NET_CORE_DIR; - // Our Strategies for resolving references List ResolutionStrategies; - static TestAssemblyResolver() - { - INSTALL_DIR = DotNet.GetInstallDirectory(); - WINDOWS_DESKTOP_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.WindowsDesktop.App"); - ASP_NET_CORE_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.AspNetCore.App"); - } - public TestAssemblyResolver(AssemblyLoadContext loadContext, string testAssemblyPath) { _loadContext = loadContext; @@ -47,34 +37,37 @@ public TestAssemblyResolver(AssemblyLoadContext loadContext, string testAssembly private void InitializeResolutionStrategies(AssemblyLoadContext loadContext, string testAssemblyPath) { - // First, looking only at direct references by the test assembly, try to determine if - // this assembly is using WindowsDesktop (either SWF or WPF) and/or AspNetCore. + // Decide whether to try WindowsDeskTop and/or AspNetCore runtimes before any others. + // We base this on direct references only, so we will eventually try each of them + // later in case there are any indirect references. AssemblyDefinition assemblyDef = AssemblyDefinition.ReadAssembly(testAssemblyPath); - bool isWindowsDesktop = false; - bool isAspNetCore = false; + bool tryWindowsDesktopFirst = false; + bool tryAspNetCoreFirst = false; foreach (var reference in assemblyDef.MainModule.GetTypeReferences()) { string fn = reference.FullName; if (fn.StartsWith("System.Windows.") || fn.StartsWith("PresentationFramework")) - isWindowsDesktop = true; + tryWindowsDesktopFirst = true; if (fn.StartsWith("Microsoft.AspNetCore.")) - isAspNetCore = true; + tryAspNetCoreFirst = true; } // Initialize the list of ResolutionStrategies in the best order depending on // what we learned. ResolutionStrategies = new List(); - if (isWindowsDesktop && Directory.Exists(WINDOWS_DESKTOP_DIR)) - ResolutionStrategies.Add(new AdditionalDirectoryStrategy(WINDOWS_DESKTOP_DIR)); - if (isAspNetCore && Directory.Exists(ASP_NET_CORE_DIR)) - ResolutionStrategies.Add(new AdditionalDirectoryStrategy(ASP_NET_CORE_DIR)); + if (tryWindowsDesktopFirst) + ResolutionStrategies.Add(new WindowsDesktopStrategy()); + if (tryAspNetCoreFirst) + ResolutionStrategies.Add(new AspNetCoreStrategy()); + ResolutionStrategies.Add(new TrustedPlatformAssembliesStrategy()); ResolutionStrategies.Add(new RuntimeLibrariesStrategy(loadContext, testAssemblyPath)); - if (!isWindowsDesktop && Directory.Exists(WINDOWS_DESKTOP_DIR)) - ResolutionStrategies.Add(new AdditionalDirectoryStrategy(WINDOWS_DESKTOP_DIR)); - if (!isAspNetCore && Directory.Exists(ASP_NET_CORE_DIR)) - ResolutionStrategies.Add(new AdditionalDirectoryStrategy(ASP_NET_CORE_DIR)); + + if (!tryWindowsDesktopFirst) + ResolutionStrategies.Add(new WindowsDesktopStrategy()); + if (!tryAspNetCoreFirst) + ResolutionStrategies.Add(new AspNetCoreStrategy()); } public void Dispose() @@ -201,96 +194,58 @@ public override bool TryToResolve( } } - public class AdditionalDirectoryStrategy : ResolutionStrategy + public class AdditionalRuntimesStrategy : ResolutionStrategy { - private string _frameworkDirectory; + private IEnumerable _additionalRuntimes; - public AdditionalDirectoryStrategy(string frameworkDirectory) + public AdditionalRuntimesStrategy(string runtimeName) { - _frameworkDirectory = frameworkDirectory; + _additionalRuntimes = DotNet.GetRuntimes(runtimeName); } - public override bool TryToResolve( - AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) + public override bool TryToResolve(AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) { loadedAssembly = null; - if (assemblyName.Version == null) - return false; - - var versionDir = FindBestVersionDir(_frameworkDirectory, assemblyName.Version); - if (versionDir != null) - { - string candidate = Path.Combine(_frameworkDirectory, versionDir, assemblyName.Name + ".dll"); - if (File.Exists(candidate)) - { - loadedAssembly = loadContext.LoadFromAssemblyPath(candidate); - log.Info("'{0}' ({1}) assembly is loaded from AdditionalFrameworkDirectory {2} dependencies with best candidate version {3}", - assemblyName, - loadedAssembly.Location, - _frameworkDirectory, - versionDir); + DotNet.RuntimeInfo runtime; + if (!FindBestRuntime(assemblyName, out runtime)) + return false; - return true; - } - else - { - log.Debug("Best version dir for {0} is {1}, but there is no {2} file", _frameworkDirectory, versionDir, candidate); - return false; - } - } + string candidate = Path.Combine(runtime.Path, runtime.Version.ToString(), assemblyName.Name + ".dll"); + if (!File.Exists(candidate)) + return false; - return false; + loadedAssembly = loadContext.LoadFromAssemblyPath(candidate); + return true; } - } - #endregion + private bool FindBestRuntime(AssemblyName assemblyName, out DotNet.RuntimeInfo bestRuntime) + { + bestRuntime = null; + var targetVersion = assemblyName.Version; - #region HelperMethods + if (targetVersion is null) + return false; - private static string FindBestVersionDir(string libraryDir, Version targetVersion) - { - string target = targetVersion.ToString(); - Version bestVersion = new Version(0, 0); - foreach (var subdir in Directory.GetDirectories(libraryDir)) - { - Version version; - if (TryGetVersionFromString(Path.GetFileName(subdir), out version)) + foreach (var candidate in _additionalRuntimes) { - if (version >= targetVersion) - if (bestVersion.Major == 0 || bestVersion > version) - bestVersion = version; + if (candidate.Version >= targetVersion) + if (bestRuntime is null || bestRuntime.Version > candidate.Version) + bestRuntime = candidate; } - } - return bestVersion.Major > 0 - ? bestVersion.ToString() - : null; + return bestRuntime is not null; + } } - private static bool TryGetVersionFromString(string text, out Version newVersion) + public class WindowsDesktopStrategy : AdditionalRuntimesStrategy { - const string VERSION_CHARS = ".0123456789"; - - int len = 0; - foreach (char c in text) - { - if (VERSION_CHARS.IndexOf(c) >= 0) - len++; - else - break; - } + public WindowsDesktopStrategy() : base("Microsoft.WindowsDesktop.App") { } + } - try - { - newVersion = new Version(text.Substring(0, len)); - return true; - } - catch - { - newVersion = new Version(); - return false; - } + public class AspNetCoreStrategy : AdditionalRuntimesStrategy + { + public AspNetCoreStrategy() : base("Microsoft.AspNetCore.App") { } } #endregion diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 103d09beb..6348179dc 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -73,7 +73,7 @@ public AgentProcess(TestAgency agency, TestPackage package, Guid agentId) if (Path.DirectorySeparatorChar != '\\') throw new Exception("Running .NET Core as X86 is currently only supported on Windows"); - var x86_dotnet_exe = Path.Combine(DotNet.GetX86InstallDirectory(), "dotnet.exe"); + var x86_dotnet_exe = Path.Combine(DotNet.X86InstallDirectory, "dotnet.exe"); if (!File.Exists(x86_dotnet_exe)) throw new Exception("The X86 version of dotnet.exe is not installed"); diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs index 2418ff345..0390dca4a 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs @@ -1,10 +1,8 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt #if NETFRAMEWORK -using Microsoft.Win32; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; namespace NUnit.Engine.Services.RuntimeLocators @@ -15,100 +13,14 @@ public static IEnumerable FindRuntimes(bool x86) { List alreadyFound = new List(); - foreach (string dirName in GetRuntimeDirectories(x86)) + foreach (var runtime in DotNet.GetRuntimes("Microsoft.NETCore.App")) { - Version newVersion; - if (TryGetVersionFromString(dirName, out newVersion) && !alreadyFound.Contains(newVersion)) + if (!alreadyFound.Contains(runtime.Version)) { - alreadyFound.Add(newVersion); - yield return new RuntimeFramework(RuntimeType.NetCore, newVersion); + alreadyFound.Add(runtime.Version); + yield return new RuntimeFramework(RuntimeType.NetCore, runtime.Version); } } - - foreach (string line in GetRuntimeList()) - { - Version newVersion; - if (TryGetVersionFromString(line, out newVersion) && !alreadyFound.Contains(newVersion)) - { - alreadyFound.Add(newVersion); - yield return new RuntimeFramework(RuntimeType.NetCore, newVersion); - } - } - } - - private static IEnumerable GetRuntimeDirectories(bool x86) - { - string installDir = DotNet.GetInstallDirectory(x86); - - if (installDir != null && Directory.Exists(installDir) && - File.Exists(Path.Combine(installDir, "dotnet.exe"))) - { - string runtimeDir = Path.Combine(installDir, Path.Combine("shared", "Microsoft.NETCore.App")); - if (Directory.Exists(runtimeDir)) - foreach (var dir in new DirectoryInfo(runtimeDir).GetDirectories()) - yield return dir.Name; - } - } - - private static IEnumerable GetRuntimeList() - { - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = "dotnet", - Arguments = "--list-runtimes", - UseShellExecute = false, - RedirectStandardOutput = true, - CreateNoWindow = true - } - }; - - try - { - process.Start(); - } - catch (Exception) - { - // Failed to start dotnet command. Assume no versions are installed and just return - yield break; - } - - const string PREFIX = "Microsoft.NETCore.App "; - const int VERSION_START = 22; - - while (!process.StandardOutput.EndOfStream) - { - var line = process.StandardOutput.ReadLine(); - if (line.StartsWith(PREFIX)) - yield return line.Substring(VERSION_START); - } - } - - private static bool TryGetVersionFromString(string text, out Version newVersion) - { - const string VERSION_CHARS = ".0123456789"; - - int len = 0; - foreach (char c in text) - { - if (VERSION_CHARS.IndexOf(c) >= 0) - len++; - else - break; - } - - try - { - var fullVersion = new Version(text.Substring(0, len)); - newVersion = new Version(fullVersion.Major, fullVersion.Minor); - return true; - } - catch - { - newVersion = new Version(); - return false; - } } } } From 641e72bead44a306cb33d9f93b4473e6b9d5d823 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 25 Nov 2025 15:44:31 -0800 Subject: [PATCH 176/190] Revisions after review --- .../nunit.engine.core/DotNetHelper.cs | 87 ++++++++++--------- .../Internal/DirectoryFinder.cs | 2 +- .../Internal/TestAssemblyResolver.cs | 4 +- src/NUnitEngine/nunit.engine.core/OS.cs | 11 +++ .../nunit.engine.core/RuntimeFramework.cs | 2 +- .../nunit.engine/Services/AgentProcess.cs | 8 +- .../RuntimeLocators/NetCoreRuntimeLocator.cs | 2 +- 7 files changed, 65 insertions(+), 51 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core/OS.cs diff --git a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs index 6634601fa..49caa74c6 100644 --- a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -18,9 +18,30 @@ public static class DotNet private const string X86_SUBKEY1 = @"SOFTWARE\dotnet\SetUp\InstalledVersions\x86\InstallLocation\"; private const string X86_SUBKEY2 = @"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"; - public static readonly string X64InstallDirectory; - public static readonly string X86InstallDirectory; - public static readonly List Runtimes; +#pragma warning disable CA1416 + private static readonly Lazy _x64InstallDirectory = new Lazy(() => + Environment.GetEnvironmentVariable("DOTNET_ROOT") ?? ( + OS.IsWindows + ? (string) Registry.LocalMachine.OpenSubKey(X64_SUBKEY1)?.GetValue("Path") ?? + (string) Registry.LocalMachine.OpenSubKey(X64_SUBKEY2)?.GetValue("Path") ?? @"C:\Program Files\dotnet" + : "/usr/shared/dotnet/")); + private static readonly Lazy _x86InstallDirectory = new Lazy(() => + Environment.GetEnvironmentVariable("DOTNET_ROOT_X86") ?? ( + OS.IsWindows + ? (string) Registry.LocalMachine.OpenSubKey(X86_SUBKEY1)?.GetValue("InstallLocation") ?? + (string) Registry.LocalMachine.OpenSubKey(X86_SUBKEY2)?.GetValue("InstallLocation") ?? @"C:\Program Files (x86)\dotnet" + : "/usr/shared/dotnet/")); +#pragma warning restore CA1416 + + private static Lazy> _x64Runtimes = new Lazy>(() => [.. GetAllRuntimes(x86: false)]); + private static Lazy> _x86Runtimes = new Lazy>(() => [.. GetAllRuntimes(x86: true)]); + + public enum Architecture + { + Unspecified, + X64, + X86 + } public class RuntimeInfo { @@ -40,49 +61,42 @@ public RuntimeInfo(string name, Version version, string path) } /// - /// Static constructor initializes everything once + /// Get the correct install directory, depending on whether we need X86 or X64 architecture. /// - static DotNet() - { -#pragma warning disable CA1416 - X64InstallDirectory = - Environment.GetEnvironmentVariable("DOTNET_ROOT") ?? ( - IsWindows - ? (string)Registry.LocalMachine.OpenSubKey(X64_SUBKEY1)?.GetValue("Path") ?? - (string)Registry.LocalMachine.OpenSubKey(X64_SUBKEY2)?.GetValue("Path") ?? @"C:\Program Files\dotnet" - : "/usr/shared/dotnet/"); - X86InstallDirectory = - Environment.GetEnvironmentVariable("DOTNET_ROOT_X86") ?? ( - IsWindows - ? (string)Registry.LocalMachine.OpenSubKey(X86_SUBKEY1)?.GetValue("InstallLocation") ?? - (string)Registry.LocalMachine.OpenSubKey(X86_SUBKEY2)?.GetValue("InstallLocation") ?? @"C:\Program Files (x86)\dotnet" - : "/usr/shared/dotnet/"); -#pragma warning restore CA1416 - Runtimes = new List(); - foreach (string line in DotnetCommand("--list-runtimes")) - { - string[] parts = line.Trim().Split([' '], 3); - Runtimes.Add(new RuntimeInfo(parts[0], parts[1], parts[2].Trim(['[', ']']))); - } - } + /// Flag indicating whether the X86 architecture is needed + /// + public static string GetInstallDirectory(bool x86) => x86 + ? _x86InstallDirectory.Value : _x64InstallDirectory.Value; /// - /// Get the correct install directory, depending on whether we need X86 or X64 architecture. + /// Get the correct dotnet.exe, depending on whether we need X86 or X64 architecture. /// /// Flag indicating whether the X86 architecture is needed /// - public static string GetInstallDirectory(bool x86) => x86 - ? X86InstallDirectory : X64InstallDirectory; + public static string GetDotnetExecutable(bool x86) => Path.Combine(GetInstallDirectory(x86), "dotnet.exe"); + + public static IEnumerable GetRuntimes(string name, bool x86) + { + var runtimes = x86 ? _x86Runtimes.Value : _x64Runtimes.Value; + return runtimes.Where(r => r.Name == name); + } - public static IEnumerable GetRuntimes(string name) => Runtimes.Where(r => r.Name == name); + private static IEnumerable GetAllRuntimes(bool x86) + { + foreach (string line in DotnetCommand("--list-runtimes", x86: x86)) + { + string[] parts = line.Trim().Split([' '], 3); + yield return new RuntimeInfo(parts[0], parts[1], parts[2].Trim(['[', ']'])); + } + } - private static IEnumerable DotnetCommand(string arguments) + private static IEnumerable DotnetCommand(string arguments, bool x86 = false) { var process = new Process { StartInfo = new ProcessStartInfo { - FileName = "dotnet", + FileName = GetDotnetExecutable(x86), Arguments = arguments, UseShellExecute = false, RedirectStandardOutput = true, @@ -103,12 +117,5 @@ private static IEnumerable DotnetCommand(string arguments) while (!process.StandardOutput.EndOfStream) yield return process.StandardOutput.ReadLine(); } - -#if NETFRAMEWORK - private static bool IsWindows => Path.DirectorySeparatorChar == '\\'; -#else - private static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); -#endif - } } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/DirectoryFinder.cs b/src/NUnitEngine/nunit.engine.core/Internal/DirectoryFinder.cs index aaa1bdd2e..f21dc197d 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/DirectoryFinder.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/DirectoryFinder.cs @@ -31,7 +31,7 @@ public IEnumerable GetDirectories(IDirectory startDirectory, string Guard.ArgumentNotNull(startDirectory, nameof(startDirectory)); Guard.ArgumentNotNull(pattern, nameof(pattern)); - if (Path.DirectorySeparatorChar == '\\') + if (OS.IsWindows) pattern = pattern.Replace(Path.DirectorySeparatorChar, '/'); var dirList = new List { startDirectory }; diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index 00c2037a0..7e2ce2ff4 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -200,7 +200,7 @@ public class AdditionalRuntimesStrategy : ResolutionStrategy public AdditionalRuntimesStrategy(string runtimeName) { - _additionalRuntimes = DotNet.GetRuntimes(runtimeName); + _additionalRuntimes = DotNet.GetRuntimes(runtimeName, !Environment.Is64BitProcess); } public override bool TryToResolve(AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) @@ -222,7 +222,7 @@ public override bool TryToResolve(AssemblyLoadContext loadContext, AssemblyName private bool FindBestRuntime(AssemblyName assemblyName, out DotNet.RuntimeInfo bestRuntime) { bestRuntime = null; - var targetVersion = assemblyName.Version; + var targetVersion = new Version(assemblyName.Version.Major, assemblyName.Version.Minor, assemblyName.Version.Build); if (targetVersion is null) return false; diff --git a/src/NUnitEngine/nunit.engine.core/OS.cs b/src/NUnitEngine/nunit.engine.core/OS.cs new file mode 100644 index 000000000..0939c303e --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/OS.cs @@ -0,0 +1,11 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System.IO; + +namespace NUnit.Engine +{ + internal static class OS + { + public static bool IsWindows { get; } = Path.DirectorySeparatorChar == '\\'; + } +} diff --git a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs index bb1d8b111..508249bfb 100644 --- a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs +++ b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs @@ -336,7 +336,7 @@ public static string MonoExePath { get { - return MonoPrefix != null && Environment.OSVersion.Platform == PlatformID.Win32NT + return MonoPrefix != null && OS.IsWindows ? Path.Combine(MonoPrefix, "bin/mono.exe") : "mono"; } diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 6348179dc..32629ea26 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -63,21 +63,17 @@ public AgentProcess(TestAgency agency, TestPackage package, Guid agentId) } else if (TargetRuntime.Runtime == RuntimeType.NetCore) { - StartInfo.FileName = "dotnet"; + StartInfo.FileName = DotNet.GetDotnetExecutable(runAsX86); StartInfo.Arguments = $"\"{AgentExePath}\" {AgentArgs}"; StartInfo.LoadUserProfile = loadUserProfile; - // TODO: Remove the windows limitation and the use of a hard-coded path. if (runAsX86) { if (Path.DirectorySeparatorChar != '\\') throw new Exception("Running .NET Core as X86 is currently only supported on Windows"); - var x86_dotnet_exe = Path.Combine(DotNet.X86InstallDirectory, "dotnet.exe"); - if (!File.Exists(x86_dotnet_exe)) + if (!File.Exists(StartInfo.FileName)) throw new Exception("The X86 version of dotnet.exe is not installed"); - - StartInfo.FileName = x86_dotnet_exe; } } else diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs index 0390dca4a..45e188197 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs @@ -13,7 +13,7 @@ public static IEnumerable FindRuntimes(bool x86) { List alreadyFound = new List(); - foreach (var runtime in DotNet.GetRuntimes("Microsoft.NETCore.App")) + foreach (var runtime in DotNet.GetRuntimes("Microsoft.NETCore.App", x86)) { if (!alreadyFound.Contains(runtime.Version)) { From bb555d436dacffdac5c399381e161d14ca96468f Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Wed, 26 Nov 2025 09:59:44 +0800 Subject: [PATCH 177/190] Use OS.IsWindows in more places. --- .../nunit.engine.core/Internal/PathUtils.cs | 10 ++++------ src/NUnitEngine/nunit.engine.core/OS.cs | 2 +- src/NUnitEngine/nunit.engine/Services/AgentProcess.cs | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs b/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs index bec00090b..d919e1ac2 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs @@ -144,10 +144,10 @@ public static bool SamePathOrUnder( string path1, string path2 ) // if lengths are the same, check for equality if ( length1 == length2 ) - return string.Compare( path1, path2, RunningOnWindows ) == 0; + return string.Compare( path1, path2, OS.IsWindows ) == 0; // path 2 is longer than path 1: see if initial parts match - if ( string.Compare( path1, path2.Substring( 0, length1 ), RunningOnWindows ) != 0 ) + if ( string.Compare( path1, path2.Substring( 0, length1 ), OS.IsWindows) != 0 ) return false; // must match through or up to a directory separator boundary @@ -174,7 +174,7 @@ public static string Combine(string path1, params string[] morePaths) /// public static bool IsFullyQualifiedPath(string path ) { - return RunningOnWindows + return OS.IsWindows ? IsFullyQualifiedWindowsPath(path) : IsFullyQualifiedUnixPath(path); } @@ -250,8 +250,6 @@ private static bool IsValidDriveSpecifier(char c) return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); } - private static bool RunningOnWindows => DirectorySeparatorChar == '\\'; - private static string[] SplitPath(string path) { char[] separators = new char[] { DirectorySeparatorChar, AltDirectorySeparatorChar }; @@ -277,7 +275,7 @@ private static string[] SplitPath(string path) private static bool PathsEqual(string path1, string path2) { - if (RunningOnWindows) + if (OS.IsWindows) return path1.ToLower().Equals(path2.ToLower()); else return path1.Equals(path2); diff --git a/src/NUnitEngine/nunit.engine.core/OS.cs b/src/NUnitEngine/nunit.engine.core/OS.cs index 0939c303e..8108abfda 100644 --- a/src/NUnitEngine/nunit.engine.core/OS.cs +++ b/src/NUnitEngine/nunit.engine.core/OS.cs @@ -4,7 +4,7 @@ namespace NUnit.Engine { - internal static class OS + public static class OS { public static bool IsWindows { get; } = Path.DirectorySeparatorChar == '\\'; } diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 32629ea26..3c058fe64 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -69,7 +69,7 @@ public AgentProcess(TestAgency agency, TestPackage package, Guid agentId) if (runAsX86) { - if (Path.DirectorySeparatorChar != '\\') + if (OS.IsWindows) throw new Exception("Running .NET Core as X86 is currently only supported on Windows"); if (!File.Exists(StartInfo.FileName)) From 11fd7804a3bc629bb011df1b193fd4f864e6daaf Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Wed, 26 Nov 2025 10:10:09 +0800 Subject: [PATCH 178/190] Small cleanups --- .../nunit.engine.core/DotNetHelper.cs | 2 -- .../Internal/TestAssemblyResolver.cs | 24 ++++++++----------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs index 49caa74c6..a895b2d57 100644 --- a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -4,10 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing.Drawing2D; using System.IO; using System.Linq; -using System.Runtime.InteropServices; namespace NUnit.Engine { diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index 7e2ce2ff4..86593f169 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -4,10 +4,8 @@ using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.DependencyModel.Resolution; -using Microsoft.Win32; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -84,9 +82,8 @@ private Assembly OnResolving(AssemblyLoadContext loadContext, AssemblyName assem { if (loadContext == null) throw new ArgumentNullException("context"); - Assembly loadedAssembly; foreach (var strategy in ResolutionStrategies) - if (strategy.TryToResolve(loadContext, assemblyName, out loadedAssembly)) + if (strategy.TryToResolve(loadContext, assemblyName, out Assembly loadedAssembly)) return loadedAssembly; log.Info("Cannot resolve assembly '{0}'", assemblyName); @@ -114,7 +111,7 @@ private static bool TryLoadFromTrustedPlatformAssemblies( { // https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing loadedAssembly = null; - var trustedAssemblies = System.AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; + var trustedAssemblies = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; if (string.IsNullOrEmpty(trustedAssemblies)) { return false; @@ -142,19 +139,19 @@ bool FileMatchesAssembly(string fileName) => public class RuntimeLibrariesStrategy : ResolutionStrategy { - private DependencyContext _dependencyContext; - private readonly ICompilationAssemblyResolver _assemblyResolver; + private readonly DependencyContext _dependencyContext; + private readonly CompositeCompilationAssemblyResolver _assemblyResolver; public RuntimeLibrariesStrategy(AssemblyLoadContext loadContext, string testAssemblyPath) { _dependencyContext = DependencyContext.Load(loadContext.LoadFromAssemblyPath(testAssemblyPath)); - _assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[] - { + _assemblyResolver = new CompositeCompilationAssemblyResolver( + [ new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(testAssemblyPath)), new ReferenceAssemblyPathResolver(), new PackageCompilationAssemblyResolver() - }); + ]); } public override bool TryToResolve( @@ -196,7 +193,7 @@ public override bool TryToResolve( public class AdditionalRuntimesStrategy : ResolutionStrategy { - private IEnumerable _additionalRuntimes; + private readonly IEnumerable _additionalRuntimes; public AdditionalRuntimesStrategy(string runtimeName) { @@ -207,8 +204,7 @@ public override bool TryToResolve(AssemblyLoadContext loadContext, AssemblyName { loadedAssembly = null; - DotNet.RuntimeInfo runtime; - if (!FindBestRuntime(assemblyName, out runtime)) + if (!FindBestRuntime(assemblyName, out DotNet.RuntimeInfo runtime)) return false; string candidate = Path.Combine(runtime.Path, runtime.Version.ToString(), assemblyName.Name + ".dll"); @@ -222,7 +218,7 @@ public override bool TryToResolve(AssemblyLoadContext loadContext, AssemblyName private bool FindBestRuntime(AssemblyName assemblyName, out DotNet.RuntimeInfo bestRuntime) { bestRuntime = null; - var targetVersion = new Version(assemblyName.Version.Major, assemblyName.Version.Minor, assemblyName.Version.Build); + var targetVersion = assemblyName.Version; if (targetVersion is null) return false; From 2eb79b6861e64f24629922c82c2e158f1c815ea2 Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Wed, 26 Nov 2025 10:10:39 +0800 Subject: [PATCH 179/190] Use dotnet for non-Windows and add 4th digit to runtime versions --- src/NUnitEngine/nunit.engine.core/DotNetHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs index a895b2d57..af4e646c1 100644 --- a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -71,7 +71,7 @@ public static string GetInstallDirectory(bool x86) => x86 ///
/// Flag indicating whether the X86 architecture is needed /// - public static string GetDotnetExecutable(bool x86) => Path.Combine(GetInstallDirectory(x86), "dotnet.exe"); + public static string GetDotnetExecutable(bool x86) => Path.Combine(GetInstallDirectory(x86), OS.IsWindows ? "dotnet.exe" : "dotnet"); public static IEnumerable GetRuntimes(string name, bool x86) { @@ -84,7 +84,7 @@ private static IEnumerable GetAllRuntimes(bool x86) foreach (string line in DotnetCommand("--list-runtimes", x86: x86)) { string[] parts = line.Trim().Split([' '], 3); - yield return new RuntimeInfo(parts[0], parts[1], parts[2].Trim(['[', ']'])); + yield return new RuntimeInfo(parts[0], parts[1] + ".0", parts[2].Trim(['[', ']'])); } } From 3db130e4ac20ac4aac15c37ca4e4d056c35b6db2 Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Wed, 26 Nov 2025 10:11:03 +0800 Subject: [PATCH 180/190] Cleanup package references to prevent NU1510 error --- .../nunit.engine.core.tests/nunit.engine.core.tests.csproj | 4 ---- src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj | 7 ++++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index 938fe7ff3..34713640f 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj +++ b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj @@ -35,10 +35,6 @@ - - - - diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index a57e0dc93..3c78b8f34 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -22,18 +22,19 @@ + + + + - - - From dbe2d5ff27dd2fa4cf0e617b1428152b52636acd Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Wed, 26 Nov 2025 10:24:53 +0800 Subject: [PATCH 181/190] Fix build. There is an indirect reference through Castle.Core to a non-existing version of System.ComponentModel.TypeConverter If I add System.ComponentModel.TypeConverter locally, the compiler complains that it isn't used ... --- src/Directory.Build.props | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e77b82bd5..9eec7717d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -12,6 +12,7 @@ NUnit Software Copyright (c) 2022 Charlie Poole, Rob Prouse + $(NoWarn);NU1603 - \ No newline at end of file + From 652621a6b89b985161dbe135881e6c32ec132495 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 25 Nov 2025 21:39:19 -0800 Subject: [PATCH 182/190] Fix broken unit and package tests --- src/NUnitEngine/nunit.engine.core/DotNetHelper.cs | 2 +- .../nunit.engine.core/Internal/PathUtils.cs | 10 ++++++---- src/NUnitEngine/nunit.engine/Services/AgentProcess.cs | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs index af4e646c1..0318eb790 100644 --- a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -84,7 +84,7 @@ private static IEnumerable GetAllRuntimes(bool x86) foreach (string line in DotnetCommand("--list-runtimes", x86: x86)) { string[] parts = line.Trim().Split([' '], 3); - yield return new RuntimeInfo(parts[0], parts[1] + ".0", parts[2].Trim(['[', ']'])); + yield return new RuntimeInfo(parts[0], parts[1], parts[2].Trim(['[', ']'])); } } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs b/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs index d919e1ac2..a037b6b31 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs @@ -144,10 +144,10 @@ public static bool SamePathOrUnder( string path1, string path2 ) // if lengths are the same, check for equality if ( length1 == length2 ) - return string.Compare( path1, path2, OS.IsWindows ) == 0; + return string.Compare( path1, path2, RunningOnWindows ) == 0; // path 2 is longer than path 1: see if initial parts match - if ( string.Compare( path1, path2.Substring( 0, length1 ), OS.IsWindows) != 0 ) + if ( string.Compare( path1, path2.Substring( 0, length1 ), RunningOnWindows) != 0 ) return false; // must match through or up to a directory separator boundary @@ -174,11 +174,13 @@ public static string Combine(string path1, params string[] morePaths) /// public static bool IsFullyQualifiedPath(string path ) { - return OS.IsWindows + return RunningOnWindows ? IsFullyQualifiedWindowsPath(path) : IsFullyQualifiedUnixPath(path); } + private static bool RunningOnWindows => DirectorySeparatorChar == '\\'; + /// /// Returns a value that indicates whether the specified file path is fully qualified or not on Windows operating systems. /// @@ -275,7 +277,7 @@ private static string[] SplitPath(string path) private static bool PathsEqual(string path1, string path2) { - if (OS.IsWindows) + if (RunningOnWindows) return path1.ToLower().Equals(path2.ToLower()); else return path1.Equals(path2); diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 3c058fe64..3e206d1f5 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -69,7 +69,7 @@ public AgentProcess(TestAgency agency, TestPackage package, Guid agentId) if (runAsX86) { - if (OS.IsWindows) + if (!OS.IsWindows) throw new Exception("Running .NET Core as X86 is currently only supported on Windows"); if (!File.Exists(StartInfo.FileName)) From 05cc42e7b509c04bf8842321e3176ac84eb598c4 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 27 Nov 2025 01:28:24 -0800 Subject: [PATCH 183/190] Ignore .addins files in XML format --- GitVersion.yml | 1 + .../Internal/AddinsFileTests.cs | 19 +++++++ .../nunit.engine.core/DotNetHelper.cs | 7 --- .../nunit.engine.core/Internal/AddinsFile.cs | 52 ++++++++++++++----- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/GitVersion.yml b/GitVersion.yml index ca6e99b51..a2cba3226 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,3 +1,4 @@ +next-version: 3.21.0 mode: ContinuousDelivery branches: main: diff --git a/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs index 25d4363b1..628ea126b 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs @@ -48,6 +48,25 @@ public void Read_Stream() } } + [Test] + public void Read_AddinsFileIsNotOurs() + { + var content = new[] + { + "Anything", + "", + " We don't understand this", + "", + "More stuff" + }; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, content)))) + { + var result = AddinsFile.Read(stream); + Assert.That(result.Count, Is.Zero); + } + } + [Test] public void Read_InvalidEntry() { diff --git a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs index 0318eb790..a5da58431 100644 --- a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -34,13 +34,6 @@ public static class DotNet private static Lazy> _x64Runtimes = new Lazy>(() => [.. GetAllRuntimes(x86: false)]); private static Lazy> _x86Runtimes = new Lazy>(() => [.. GetAllRuntimes(x86: true)]); - public enum Architecture - { - Unspecified, - X64, - X86 - } - public class RuntimeInfo { public string Name; diff --git a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs index 6b090c318..51acc97ba 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs @@ -13,6 +13,8 @@ namespace NUnit.Engine.Internal { internal class AddinsFile : List { + static readonly Logger log = InternalTrace.GetLogger(typeof(AddinsFile)); + public static AddinsFile Read(IFile file) { if (file == null) @@ -33,29 +35,55 @@ public static AddinsFile Read(IFile file) /// If the executing system uses backslashes ('\') to separate directories, these will be substituted with slashes ('/'). internal static AddinsFile Read(Stream stream, string fullName = null) { + // Read the whole file first + var content = new List(); using (var reader = new StreamReader(stream)) + { + while (!reader.EndOfStream) + content.Add(reader.ReadLine().Trim()); + } + + // Create an empty AddinsFile, with no entries + var addinsFile = new AddinsFile(); + + // Ensure that this is actually an NUnit .addins file, since + // the extension is used by others. See, for example, + // https://github.com/nunit/nunit-console/issues/1761 + // TODO: Consider using an extension specific to NUnit for V4 + if (!IsNUnitAddinsFile(content)) { - var addinsFile = new AddinsFile(); + log.Warning($"Ignoring file {fullName} because it's not an NUnit .addins file"); + return addinsFile; + } - int lineNumber = 0; - while (!reader.EndOfStream) + // It's our file, so process it + int lineNumber = 0; + foreach (var line in content) + { + var entry = new AddinsFileEntry(++lineNumber, line); + if (entry.Text != "" && !entry.IsValid) { - var entry = new AddinsFileEntry(++lineNumber, reader.ReadLine()); - if (entry.Text != "" && !entry.IsValid) - { - string msg = $"Invalid Entry in {fullName ?? "addins file"}:\r\n {entry}"; - throw new InvalidOperationException(msg); - } - - addinsFile.Add(entry); + string msg = $"Invalid Entry in {fullName ?? "addins file"}:\r\n {entry}"; + throw new InvalidOperationException(msg); } - return addinsFile; + addinsFile.Add(entry); } + + return addinsFile; } private AddinsFile() { } + private static bool IsNUnitAddinsFile(List content) + { + foreach (var line in content) + if (line.Length > 0 && line[0] == '<') + return false; + + return true; + } + public override string ToString() { var sb = new StringBuilder("AddinsFile:"); From e5329d981f32e337694a707994a2341d5d591309 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 29 Nov 2025 02:12:27 -0800 Subject: [PATCH 184/190] Handle prerelease dotnet runtimes correctly --- .../DotNetHelperTests.cs | 98 ++++++++ .../nunit.engine.core/DotNetHelper.cs | 116 +++++++-- .../Internal/TestAssemblyResolver.cs | 236 ++++++++---------- 3 files changed, 309 insertions(+), 141 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core.tests/DotNetHelperTests.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/DotNetHelperTests.cs b/src/NUnitEngine/nunit.engine.core.tests/DotNetHelperTests.cs new file mode 100644 index 000000000..3b5f89b97 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/DotNetHelperTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.IO; +using System.Collections.Generic; +using NUnit.Framework; +using System.Linq; + +namespace NUnit.Engine +{ + public static class DotNetHelperTests + { + [Test] + public static void CanGetInstallDirectory([Values] bool x86) + { + string path = DotNet.GetInstallDirectory(x86); + Assert.That(Directory.Exists(path)); + Assert.That(File.Exists(Path.Combine(path, OS.IsWindows ? "dotnet.exe" : "dotnet"))); + } + + [Test] + public static void CanGetExecutable([Values] bool x86) + { + string path = DotNet.GetDotnetExecutable(x86); + Assert.That(File.Exists(path)); + Assert.That(Path.GetFileName(path), Is.EqualTo(OS.IsWindows ? "dotnet.exe" : "dotnet")); + } + + [Test] + public static void CanIssueDotNetCommand([Values] bool x86) + { + var output = DotNet.DotnetCommand("--help", x86); + Assert.That(output.Count(), Is.GreaterThan(0)); + } + + [TestCaseSource(nameof(RuntimeCases))] + public static void CanParseInputLine(string line, string name, string packageVersion, string path, + bool isPreRelease, Version version, string suffix) + { + DotNet.RuntimeInfo runtime = DotNet.RuntimeInfo.Parse(line); + Assert.That(runtime.Name, Is.EqualTo(name)); + Assert.That(runtime.PackageVersion, Is.EqualTo(packageVersion)); + Assert.That(runtime.Path, Is.EqualTo(path)); + Assert.That(runtime.IsPreRelease, Is.EqualTo(isPreRelease)); + Assert.That(runtime.Version, Is.EqualTo(version)); + Assert.That(runtime.PreReleaseSuffix, Is.EqualTo(suffix)); + } + + static TestCaseData[] RuntimeCases = [ + new TestCaseData("Microsoft.NETCore.App 8.0.22 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]", + "Microsoft.NETCore.App", "8.0.22", "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App", + false, new Version(8,0,22), null), + new TestCaseData("Microsoft.WindowsDesktop.App 9.0.11 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]", + "Microsoft.WindowsDesktop.App", "9.0.11", "C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App", + false, new Version(9,0,11), null), + new TestCaseData("Microsoft.AspNetCore.App 7.0.20 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]", + "Microsoft.AspNetCore.App", "7.0.20", "C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App", + false, new Version(7,0,20), null), + new TestCaseData("Microsoft.AspNetCore.App 9.0.0-rc.2.24474.3 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]", + "Microsoft.AspNetCore.App", "9.0.0-rc.2.24474.3", "C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App", + true, new Version(9,0,0), "rc.2.24474.3")]; + + [TestCase("Microsoft.NETCore.App", "8.0.0", "8.0.22")] + [TestCase("Microsoft.NETCore.App", "8.0.0.0", "8.0.22")] + [TestCase("Microsoft.NETCore.App", "8.0.0.100", "8.0.22")] + [TestCase("Microsoft.NETCore.App", "8.0.100", "9.0.11")] + [TestCase("Microsoft.AspNetCore.App", "5.0.0", "8.0.22")] // Rather than 8.0.2 + [TestCase("Microsoft.AspNetCore.App", "7.0.0", "8.0.22")] // Rather than 8.0.2 + [TestCase("Microsoft.AspNetCore.App", "8.0.0", "8.0.22")] // Rather than 8.0.2 + [TestCase("Microsoft.WindowsDesktop.App", "9.0.0", "9.0.11")] // Rather than the pre-release version + [TestCase("Microsoft.WindowsDesktop.App", "10.0.0", "10.0.0-rc.2.25502.107")] + public static void FindBestRuntimeTests(string runtimeName, string targetVersion, string expectedVersion) + { + var availableRuntimes = SimulatedListRuntimesOutput.Where(r => r.Name == runtimeName); + Assert.That(DotNet.FindBestRuntime(new Version(targetVersion), availableRuntimes, out DotNet.RuntimeInfo bestRuntime)); + Assert.That(bestRuntime, Is.Not.Null); + Assert.That(bestRuntime.PackageVersion, Is.EqualTo(expectedVersion)); + } + + static DotNet.RuntimeInfo[] SimulatedListRuntimesOutput = [ + DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 8.0.22 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 9.0.0-rc.2.24474.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 9.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 10.0.0-rc.2.25502.107 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 10.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 8.0.22 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 9.0.0-rc.2.24473.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 9.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 10.0.0-rc.2.25502.107 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 10.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 8.0.22 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 9.0.0-rc.2.24474.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 9.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]"), + DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 10.0.0-rc.2.25502.107 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]") ]; + //DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 10.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]") ]; + } +} diff --git a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs index a5da58431..213392c93 100644 --- a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -6,6 +6,8 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; +using System.Xml.Linq; namespace NUnit.Engine { @@ -34,20 +36,73 @@ public static class DotNet private static Lazy> _x64Runtimes = new Lazy>(() => [.. GetAllRuntimes(x86: false)]); private static Lazy> _x86Runtimes = new Lazy>(() => [.. GetAllRuntimes(x86: true)]); + /// + /// DotNet.RuntimeInfo holds information about a single installed runtime. + /// public class RuntimeInfo { - public string Name; - public Version Version; - public string Path; + /// + /// Gets the runtime name, e.g. Microsoft.NetCore.App, Microsoft.AspNetCore.App or Microsoft.WindowsDesktop.App. + /// + public string Name { get; } + + /// + /// Gets the package version as a string, possibly including a pre-release suffix. + /// + public string PackageVersion { get; } + + /// + /// Gets the path to the directory containing assemblies for this runtime + /// + public string Path { get; } + + /// + /// Gets a flag, which is true if this runtime is a pre-release, otherwise fales. + /// + public bool IsPreRelease { get; } - public RuntimeInfo(string name, string version, string path) - : this(name, new Version(version), path) { } + /// + /// Gets the three-part version of this runtime. + /// + public Version Version { get; } - public RuntimeInfo(string name, Version version, string path) + /// + /// Gets the pre-release suffix if IsPreRelease is true, otherwise null + /// + public string PreReleaseSuffix { get; } + + + /// + /// Constructs a Runtime instance. + /// + public RuntimeInfo(string name, string packageVersion, string path) { Name = name; - Version = version; + PackageVersion = packageVersion; Path = path; + + int dash = PackageVersion.IndexOf('-'); + IsPreRelease = dash > 0; + + if (IsPreRelease) + { + Version = new Version(packageVersion.Substring(0, dash)); + PreReleaseSuffix = packageVersion.Substring(dash + 1); + } + else + Version = new Version(packageVersion); + } + + /// + /// Parses a single line from the --list-runtimes display to create + /// an instance of DotNet.RuntimeInfo. + /// + /// Line from execution of dotnet --list-runtimes + /// A DotNet.RuntimeInfo + public static RuntimeInfo Parse(string line) + { + string[] parts = line.Trim().Split([' '], 3); + return new RuntimeInfo(parts[0], parts[1], parts[2].Trim(['[', ']'])); } } @@ -55,7 +110,6 @@ public RuntimeInfo(string name, Version version, string path) /// Get the correct install directory, depending on whether we need X86 or X64 architecture. ///
/// Flag indicating whether the X86 architecture is needed - /// public static string GetInstallDirectory(bool x86) => x86 ? _x86InstallDirectory.Value : _x64InstallDirectory.Value; @@ -63,25 +117,57 @@ public static string GetInstallDirectory(bool x86) => x86 /// Get the correct dotnet.exe, depending on whether we need X86 or X64 architecture. ///
/// Flag indicating whether the X86 architecture is needed - /// - public static string GetDotnetExecutable(bool x86) => Path.Combine(GetInstallDirectory(x86), OS.IsWindows ? "dotnet.exe" : "dotnet"); + public static string GetDotnetExecutable(bool x86) => + Path.Combine(GetInstallDirectory(x86), OS.IsWindows ? "dotnet.exe" : "dotnet"); + /// + /// Gets an enumeration of all installed runtimes matching the specified name and x86 flag. + /// + /// Name of the requested runtime + /// Flag indicating whether the X86 architecture is needed public static IEnumerable GetRuntimes(string name, bool x86) { var runtimes = x86 ? _x86Runtimes.Value : _x64Runtimes.Value; return runtimes.Where(r => r.Name == name); } - private static IEnumerable GetAllRuntimes(bool x86) + /// + /// Finds the "best" runtime for a particular asssembly version among those installed. + /// May return null, if no suitable runtime is available. + /// + /// The version of assembly sought. + /// Name of the requested runtime + /// Flag indicating whether the X86 architecture is needed + /// Output variable set to the runtime that was found or null + /// True if a runtime was found, otherwise false + public static bool FindBestRuntime(Version targetVersion, string name, bool x86, out RuntimeInfo bestRuntime) => + FindBestRuntime(targetVersion, GetRuntimes(name, x86), out bestRuntime); + + // Internal method used to facilitate testing + internal static bool FindBestRuntime(Version targetVersion, IEnumerable availableRuntimes, out RuntimeInfo bestRuntime) { - foreach (string line in DotnetCommand("--list-runtimes", x86: x86)) + bestRuntime = null; + + if (targetVersion is null) + return false; + + foreach (var candidate in availableRuntimes) { - string[] parts = line.Trim().Split([' '], 3); - yield return new RuntimeInfo(parts[0], parts[1], parts[2].Trim(['[', ']'])); + if (candidate.Version >= targetVersion) + if (bestRuntime is null || candidate.Version.Major == bestRuntime.Version.Major) + bestRuntime = candidate; } + + return bestRuntime is not null; + } + + private static IEnumerable GetAllRuntimes(bool x86) + { + foreach (string line in DotnetCommand("--list-runtimes", x86: x86)) + yield return RuntimeInfo.Parse(line); } - private static IEnumerable DotnetCommand(string arguments, bool x86 = false) + internal static IEnumerable DotnetCommand(string arguments, bool x86 = false) { var process = new Process { diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index 86593f169..b3710bcc3 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -53,19 +53,19 @@ private void InitializeResolutionStrategies(AssemblyLoadContext loadContext, str // Initialize the list of ResolutionStrategies in the best order depending on // what we learned. ResolutionStrategies = new List(); - + if (tryWindowsDesktopFirst) - ResolutionStrategies.Add(new WindowsDesktopStrategy()); + ResolutionStrategies.Add(new WindowsDesktopStrategy(false)); if (tryAspNetCoreFirst) - ResolutionStrategies.Add(new AspNetCoreStrategy()); + ResolutionStrategies.Add(new AspNetCoreStrategy(false)); ResolutionStrategies.Add(new TrustedPlatformAssembliesStrategy()); ResolutionStrategies.Add(new RuntimeLibrariesStrategy(loadContext, testAssemblyPath)); if (!tryWindowsDesktopFirst) - ResolutionStrategies.Add(new WindowsDesktopStrategy()); + ResolutionStrategies.Add(new WindowsDesktopStrategy(false)); if (!tryAspNetCoreFirst) - ResolutionStrategies.Add(new AspNetCoreStrategy()); + ResolutionStrategies.Add(new AspNetCoreStrategy(false)); } public void Dispose() @@ -89,162 +89,146 @@ private Assembly OnResolving(AssemblyLoadContext loadContext, AssemblyName assem log.Info("Cannot resolve assembly '{0}'", assemblyName); return null; } + } + + public abstract class ResolutionStrategy + { + public abstract bool TryToResolve( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly); + } - #region Nested ResolutionStrategy Classes + public class TrustedPlatformAssembliesStrategy : ResolutionStrategy + { + private static readonly Logger log = InternalTrace.GetLogger(typeof(TrustedPlatformAssembliesStrategy)); - public abstract class ResolutionStrategy + public override bool TryToResolve( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) { - public abstract bool TryToResolve( - AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly); + return TryLoadFromTrustedPlatformAssemblies(loadContext, assemblyName, out loadedAssembly); } - public class TrustedPlatformAssembliesStrategy : ResolutionStrategy + private static bool TryLoadFromTrustedPlatformAssemblies( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) { - public override bool TryToResolve( - AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) + // https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing + loadedAssembly = null; + var trustedAssemblies = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; + if (string.IsNullOrEmpty(trustedAssemblies)) { - return TryLoadFromTrustedPlatformAssemblies(loadContext, assemblyName, out loadedAssembly); + return false; } - private static bool TryLoadFromTrustedPlatformAssemblies( - AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) + var separator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ";" : ":"; + foreach (var assemblyPath in trustedAssemblies.Split(separator)) { - // https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing - loadedAssembly = null; - var trustedAssemblies = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; - if (string.IsNullOrEmpty(trustedAssemblies)) - { - return false; - } - - var separator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ";" : ":"; - foreach (var assemblyPath in trustedAssemblies.Split(separator)) + var fileName = Path.GetFileNameWithoutExtension(assemblyPath); + if (FileMatchesAssembly(fileName) && File.Exists(assemblyPath)) { - var fileName = Path.GetFileNameWithoutExtension(assemblyPath); - if (FileMatchesAssembly(fileName) && File.Exists(assemblyPath)) - { - loadedAssembly = loadContext.LoadFromAssemblyPath(assemblyPath); - log.Info("'{0}' assembly is loaded from trusted path '{1}'", assemblyPath, loadedAssembly.Location); + loadedAssembly = loadContext.LoadFromAssemblyPath(assemblyPath); + log.Info("'{0}' assembly is loaded from trusted path '{1}'", assemblyPath, loadedAssembly.Location); - return true; - } + return true; } + } - return false; + return false; - bool FileMatchesAssembly(string fileName) => - string.Equals(fileName, assemblyName.Name, StringComparison.InvariantCultureIgnoreCase); - } + bool FileMatchesAssembly(string fileName) => + string.Equals(fileName, assemblyName.Name, StringComparison.InvariantCultureIgnoreCase); } + } - public class RuntimeLibrariesStrategy : ResolutionStrategy - { - private readonly DependencyContext _dependencyContext; - private readonly CompositeCompilationAssemblyResolver _assemblyResolver; + public class RuntimeLibrariesStrategy : ResolutionStrategy + { + private static readonly Logger log = InternalTrace.GetLogger(typeof(RuntimeLibrariesStrategy)); - public RuntimeLibrariesStrategy(AssemblyLoadContext loadContext, string testAssemblyPath) - { - _dependencyContext = DependencyContext.Load(loadContext.LoadFromAssemblyPath(testAssemblyPath)); - - _assemblyResolver = new CompositeCompilationAssemblyResolver( - [ - new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(testAssemblyPath)), - new ReferenceAssemblyPathResolver(), - new PackageCompilationAssemblyResolver() - ]); - } + private readonly DependencyContext _dependencyContext; + private readonly CompositeCompilationAssemblyResolver _assemblyResolver; - public override bool TryToResolve( - AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) + public RuntimeLibrariesStrategy(AssemblyLoadContext loadContext, string testAssemblyPath) + { + _dependencyContext = DependencyContext.Load(loadContext.LoadFromAssemblyPath(testAssemblyPath)); + + _assemblyResolver = new CompositeCompilationAssemblyResolver( + [ + new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(testAssemblyPath)), + new ReferenceAssemblyPathResolver(), + new PackageCompilationAssemblyResolver() + ]); + } + + public override bool TryToResolve( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) + { + foreach (var library in _dependencyContext.RuntimeLibraries) { - foreach (var library in _dependencyContext.RuntimeLibraries) + var wrapper = new CompilationLibrary( + library.Type, + library.Name, + library.Version, + library.Hash, + library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths), + library.Dependencies, + library.Serviceable); + + var assemblies = new List(); + _assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies); + + foreach (var assemblyPath in assemblies) { - var wrapper = new CompilationLibrary( - library.Type, - library.Name, - library.Version, - library.Hash, - library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths), - library.Dependencies, - library.Serviceable); - - var assemblies = new List(); - _assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies); - - foreach (var assemblyPath in assemblies) + if (assemblyName.Name == Path.GetFileNameWithoutExtension(assemblyPath)) { - if (assemblyName.Name == Path.GetFileNameWithoutExtension(assemblyPath)) - { - loadedAssembly = loadContext.LoadFromAssemblyPath(assemblyPath); - log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", - assemblyName, - loadedAssembly.Location, - library.Name); - - return true; - } + loadedAssembly = loadContext.LoadFromAssemblyPath(assemblyPath); + log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies", + assemblyName, + loadedAssembly.Location, + library.Name); + + return true; } } - - loadedAssembly = null; - return false; } - } - - public class AdditionalRuntimesStrategy : ResolutionStrategy - { - private readonly IEnumerable _additionalRuntimes; - - public AdditionalRuntimesStrategy(string runtimeName) - { - _additionalRuntimes = DotNet.GetRuntimes(runtimeName, !Environment.Is64BitProcess); - } - - public override bool TryToResolve(AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) - { - loadedAssembly = null; - if (!FindBestRuntime(assemblyName, out DotNet.RuntimeInfo runtime)) - return false; - - string candidate = Path.Combine(runtime.Path, runtime.Version.ToString(), assemblyName.Name + ".dll"); - if (!File.Exists(candidate)) - return false; + loadedAssembly = null; + return false; + } + } - loadedAssembly = loadContext.LoadFromAssemblyPath(candidate); - return true; - } + public class AdditionalRuntimesStrategy : ResolutionStrategy + { + private string _runtimeName; + private bool _x86; - private bool FindBestRuntime(AssemblyName assemblyName, out DotNet.RuntimeInfo bestRuntime) - { - bestRuntime = null; - var targetVersion = assemblyName.Version; + public AdditionalRuntimesStrategy(string runtimeName, bool x86) + { + _runtimeName = runtimeName; + _x86 = x86; + } - if (targetVersion is null) - return false; + public override bool TryToResolve(AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) + { + loadedAssembly = null; - foreach (var candidate in _additionalRuntimes) - { - if (candidate.Version >= targetVersion) - if (bestRuntime is null || bestRuntime.Version > candidate.Version) - bestRuntime = candidate; - } + if (!DotNet.FindBestRuntime(assemblyName.Version, _runtimeName, _x86, out DotNet.RuntimeInfo runtime)) + return false; - return bestRuntime is not null; - } - } + string candidate = Path.Combine(runtime.Path, runtime.Version.ToString(), assemblyName.Name + ".dll"); + if (!File.Exists(candidate)) + return false; - public class WindowsDesktopStrategy : AdditionalRuntimesStrategy - { - public WindowsDesktopStrategy() : base("Microsoft.WindowsDesktop.App") { } + loadedAssembly = loadContext.LoadFromAssemblyPath(candidate); + return true; } + } - public class AspNetCoreStrategy : AdditionalRuntimesStrategy - { - public AspNetCoreStrategy() : base("Microsoft.AspNetCore.App") { } - } + public class WindowsDesktopStrategy : AdditionalRuntimesStrategy + { + public WindowsDesktopStrategy(bool x86) : base("Microsoft.WindowsDesktop.App", x86) { } + } - #endregion + public class AspNetCoreStrategy : AdditionalRuntimesStrategy + { + public AspNetCoreStrategy(bool x86) : base("Microsoft.AspNetCore.App", x86) { } } } #endif From d7332a2c2b6c0051e2953df24a9906147ffd4902 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 6 Dec 2025 07:33:12 -0800 Subject: [PATCH 185/190] Prepare for release --- src/NUnitEngine/nunit.engine.api/ITestRunner.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.api/ITestRunner.cs b/src/NUnitEngine/nunit.engine.api/ITestRunner.cs index f67ccac2b..2ca8eeb6d 100644 --- a/src/NUnitEngine/nunit.engine.api/ITestRunner.cs +++ b/src/NUnitEngine/nunit.engine.api/ITestRunner.cs @@ -21,9 +21,9 @@ public interface ITestRunner : IDisposable ///
/// An XmlNode representing the loaded package. /// - /// This method is normally optional, since Explore and Run call - /// it automatically when necessary. The method is kept in order - /// to make it easier to convert older programs that use it. + /// This method is optional, since Explore and Run both load the + /// tests automatically when necessary. Programs needing a correct + /// test case count may still need to call it in advance. /// XmlNode Load(); From 6572a7b9b44ffadab0eb65ced99661521df4343b Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 12 Dec 2025 11:03:11 -0800 Subject: [PATCH 186/190] Eliminate use of our custom AssemblyLoadContext --- NetCoreTests.nunit | 2 - package-tests.cake | 8 ++-- .../Drivers/NUnitNetCore31Driver.cs | 17 ++++++- .../Internal/TestAssemblyLoadContext.cs | 2 +- .../Internal/TestAssemblyResolver.cs | 45 +++++++++++++++++-- .../Runners/DirectTestRunner.cs | 1 + 6 files changed, 62 insertions(+), 13 deletions(-) diff --git a/NetCoreTests.nunit b/NetCoreTests.nunit index 093078013..67fd0b9b3 100644 --- a/NetCoreTests.nunit +++ b/NetCoreTests.nunit @@ -1,11 +1,9 @@  - - \ No newline at end of file diff --git a/package-tests.cake b/package-tests.cake index 52a0b1443..6d76eba15 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -261,14 +261,14 @@ public static class PackageTests AllLists.Add(new PackageTest(1, "Net60WPFTest") { Description = "Run test using WPF targeting .NET 6.0", - Arguments = "testdata/net6.0-windows/WpfTest.dll", + Arguments = "testdata/net6.0-windows/WpfTest.dll --trace:Debug", ExpectedResult = new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-6.0") } } }); AllLists.Add(new PackageTest(1, "Net80WPFTest") { Description = "Run test using WPF targeting .NET 8.0", - Arguments = "testdata/net8.0-windows/WpfTest.dll", + Arguments = "testdata/net8.0-windows/WpfTest.dll --trace:Debug", ExpectedResult = new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("WpfTest.dll", "netcore-8.0") } } }); @@ -314,9 +314,9 @@ public static class PackageTests NetCoreRunnerTests.Add(new PackageTest(1, "NUnitProjectTest") { - Description= "Run NUnit project with mock-assembly.dll targeting .NET 6.0 and 8.0", + Description= "Run NUnit project with mock-assembly.dll targeting 8.0", Arguments="../../NetCoreTests.nunit --config=Release", - ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0"), + ExpectedResult = new MockAssemblyExpectedResult("netcore-8.0"), ExtensionsNeeded = new [] { Extensions.NUnitProjectLoader } }); diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs index 10abde4cf..337085f11 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs @@ -8,6 +8,7 @@ using NUnit.Engine.Internal; using System.Reflection; using NUnit.Engine.Extensibility; +using System.Runtime.Loader; namespace NUnit.Engine.Drivers { @@ -38,7 +39,8 @@ public class NUnitNetCore31Driver : IFrameworkDriver Assembly _frameworkAssembly; object _frameworkController; Type _frameworkControllerType; - TestAssemblyLoadContext _assemblyLoadContext; + AssemblyLoadContext _assemblyLoadContext; + TestAssemblyResolver _testAssemblyResolver; /// /// An id prefix that will be passed to the test framework and used as part of the @@ -54,11 +56,17 @@ public class NUnitNetCore31Driver : IFrameworkDriver /// An XML string representing the loaded test public string Load(string assemblyPath, IDictionary settings) { + //if (assemblyPath.EndsWith("WpfTest.dll")) + // System.Diagnostics.Debugger.Launch(); + log.Debug($"Loading {assemblyPath}"); var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; assemblyPath = Path.GetFullPath(assemblyPath); //AssemblyLoadContext requires an absolute path - _assemblyLoadContext = new TestAssemblyLoadContext(assemblyPath); + //_assemblyLoadContext = new TestAssemblyLoadContext(assemblyPath); + //_assemblyLoadContext = AssemblyLoadContext.Default; + _assemblyLoadContext = new AssemblyLoadContext(assemblyPath); + _testAssemblyResolver = new TestAssemblyResolver(_assemblyLoadContext, assemblyPath); try { @@ -104,6 +112,11 @@ public string Load(string assemblyPath, IDictionary settings) return ExecuteMethod(LOAD_METHOD) as string; } + private Assembly _assemblyLoadContext_Resolving(AssemblyLoadContext arg1, AssemblyName arg2) + { + throw new NotImplementedException(); + } + /// /// Counts the number of test cases for the loaded test assembly /// diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs index 3dd54cd52..50d41fdca 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs @@ -1,6 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if NETCOREAPP3_1_OR_GREATER +#if NETCOREAPP3_1_OR_GREATER && false using System.Reflection; using System.Runtime.InteropServices; diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index b3710bcc3..58216a908 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -11,6 +11,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Loader; +using System.Xml.Linq; using TestCentric.Metadata; namespace NUnit.Engine.Internal @@ -20,6 +21,8 @@ internal sealed class TestAssemblyResolver : IDisposable private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyResolver)); private readonly AssemblyLoadContext _loadContext; + private readonly string _basePath; + private readonly AssemblyDependencyResolver _assemblyDependencyResolver; // Our Strategies for resolving references List ResolutionStrategies; @@ -27,6 +30,11 @@ internal sealed class TestAssemblyResolver : IDisposable public TestAssemblyResolver(AssemblyLoadContext loadContext, string testAssemblyPath) { _loadContext = loadContext; + _basePath = Path.GetDirectoryName(testAssemblyPath); + _assemblyDependencyResolver = new AssemblyDependencyResolver(testAssemblyPath); +#if NET8_0_OR_GREATER + AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", _basePath); +#endif InitializeResolutionStrategies(loadContext, testAssemblyPath); @@ -73,19 +81,48 @@ public void Dispose() _loadContext.Resolving -= OnResolving; } - public Assembly Resolve(AssemblyLoadContext context, AssemblyName assemblyName) - { - return OnResolving(context, assemblyName); - } + //public Assembly Resolve(AssemblyLoadContext context, AssemblyName assemblyName) + //{ + // return OnResolving(context, assemblyName); + //} private Assembly OnResolving(AssemblyLoadContext loadContext, AssemblyName assemblyName) { if (loadContext == null) throw new ArgumentNullException("context"); + //var runtimeResolverPath = _assemblyDependencyResolver.ResolveAssemblyToPath(assemblyName); + //if (!string.IsNullOrEmpty(runtimeResolverPath) && File.Exists(runtimeResolverPath)) + //{ + // var loadedAssembly = _loadContext.LoadFromAssemblyPath(runtimeResolverPath); + // if (loadedAssembly != null) + // { + // log.Info($"Assembly {assemblyName} ({loadedAssembly}) is loaded using the deps.json info"); + // return loadedAssembly; + // } + //} + foreach (var strategy in ResolutionStrategies) if (strategy.TryToResolve(loadContext, assemblyName, out Assembly loadedAssembly)) return loadedAssembly; + // Load assemblies that are dependencies, and in the same folder as the test assembly, + // but are not fully specified in test assembly deps.json file. This happens when the + // dependencies reference in the csproj file has CopyLocal=false, and for example, the + // reference is a projectReference and has the same output directory as the parent. + foreach (var extension in new string[] { ".dll", ".exe" }) + { + string assemblyPath = Path.Combine(_basePath, assemblyName.Name + extension); + if (File.Exists(assemblyPath)) + { + var loadedAssembly = _loadContext.LoadFromAssemblyPath(assemblyPath); + if (loadedAssembly != null) + { + log.Info($"Assembly {assemblyName.Name} ({assemblyPath}) is loaded using base path"); + return loadedAssembly; + } + } + } + log.Info("Cannot resolve assembly '{0}'", assemblyName); return null; } diff --git a/src/NUnitEngine/nunit.engine.core/Runners/DirectTestRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/DirectTestRunner.cs index 7a39b2787..1f4e2f157 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/DirectTestRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/DirectTestRunner.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; From aa0fd1105a8c8f2dc1b776354c17d73e501f283b Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 24 Dec 2025 23:16:30 -0800 Subject: [PATCH 187/190] Eliminate double-loading of assemblies --- package-tests.cake | 18 +-- .../nunit3-console.tests.csproj | 4 - .../Drivers/NUnitNetCore31Driver.cs | 23 +-- .../Internal/AssemblyHelper.cs | 26 ++++ .../Internal/TestAssemblyLoadContext.cs | 143 ------------------ 5 files changed, 48 insertions(+), 166 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs diff --git a/package-tests.cake b/package-tests.cake index 6d76eba15..2447a773d 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -476,15 +476,15 @@ public static class PackageTests } }); - StandardAndZipLists.Add(new PackageTest(1, "AppContextBaseDirectory_NET80") - { - Description = "Test Setting the BaseDirectory to match test assembly location targeting .NET 8.0", - Arguments = "testdata/net8.0/AppContextTest.dll", - ExpectedResult = new ExpectedResult("Passed") - { - Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("AppContextTest.dll", "netcore-8.0") } - } - }); + //StandardAndZipLists.Add(new PackageTest(1, "AppContextBaseDirectory_NET80") + //{ + // Description = "Test Setting the BaseDirectory to match test assembly location targeting .NET 8.0", + // Arguments = "testdata/net8.0/AppContextTest.dll", + // ExpectedResult = new ExpectedResult("Passed") + // { + // Assemblies = new ExpectedAssemblyResult[] { new ExpectedAssemblyResult("AppContextTest.dll", "netcore-8.0") } + // } + //}); AllLists.Add(new PackageTest(1, "UnmanagedAssemblyTest") { diff --git a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj index 2ad1519ba..c44294def 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -21,10 +21,6 @@ - - - - PreserveNewest diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs index 337085f11..5ad6b431e 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs @@ -5,10 +5,10 @@ using System.Linq; using System.Collections.Generic; using System.IO; -using NUnit.Engine.Internal; using System.Reflection; -using NUnit.Engine.Extensibility; using System.Runtime.Loader; +using NUnit.Engine.Internal; +using NUnit.Engine.Extensibility; namespace NUnit.Engine.Drivers { @@ -56,21 +56,24 @@ public class NUnitNetCore31Driver : IFrameworkDriver /// An XML string representing the loaded test public string Load(string assemblyPath, IDictionary settings) { - //if (assemblyPath.EndsWith("WpfTest.dll")) - // System.Diagnostics.Debugger.Launch(); - log.Debug($"Loading {assemblyPath}"); var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; assemblyPath = Path.GetFullPath(assemblyPath); //AssemblyLoadContext requires an absolute path - //_assemblyLoadContext = new TestAssemblyLoadContext(assemblyPath); - //_assemblyLoadContext = AssemblyLoadContext.Default; - _assemblyLoadContext = new AssemblyLoadContext(assemblyPath); - _testAssemblyResolver = new TestAssemblyResolver(_assemblyLoadContext, assemblyPath); try { - _testAssembly = _assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + _testAssembly = AssemblyHelper.FindLoadedAssemblyByPath(assemblyPath); + + if (_testAssembly != null) + _assemblyLoadContext = AssemblyLoadContext.GetLoadContext(_testAssembly); + else + { + _assemblyLoadContext = new AssemblyLoadContext(Path.GetFileNameWithoutExtension(assemblyPath)); + _testAssembly = _assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + } + + _testAssemblyResolver = new TestAssemblyResolver(_assemblyLoadContext, assemblyPath); } catch (Exception e) { diff --git a/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs b/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs index df68aadff..bdad8aed7 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs @@ -2,7 +2,12 @@ using System; using System.IO; +using System.Linq; using System.Reflection; +#if NETCOREAPP3_1_OR_GREATER +using System.Runtime.Loader; +#endif + namespace NUnit.Engine.Internal { /// @@ -69,5 +74,26 @@ public static string GetAssemblyPathFromCodeBase(string codeBase) return codeBase.Substring(start); } + + // For assemblies already loaded by MTP (net core and above) or by means + public static Assembly FindLoadedAssemblyByPath(string assemblyPath) + { + var full = Path.GetFullPath(assemblyPath); + + return AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => + !a.IsDynamic && + !string.IsNullOrEmpty(a.Location) && + StringComparer.OrdinalIgnoreCase.Equals(Path.GetFullPath(a.Location), full)); + } + + public static Assembly FindLoadedAssemblyByName(AssemblyName assemblyName) + { + return AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => + !a.IsDynamic && + !string.IsNullOrEmpty(a.Location) && + a.GetName() == assemblyName); + } } } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs deleted file mode 100644 index 50d41fdca..000000000 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -#if NETCOREAPP3_1_OR_GREATER && false - -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Loader; -using System.IO; -using System; -using System.Linq; - -namespace NUnit.Engine.Internal -{ - internal sealed class TestAssemblyLoadContext : AssemblyLoadContext - { - private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyLoadContext)); - - private readonly string _basePath; - private readonly TestAssemblyResolver _resolver; - private readonly System.Runtime.Loader.AssemblyDependencyResolver _runtimeResolver; - - public TestAssemblyLoadContext(string testAssemblyPath) - { - _resolver = new TestAssemblyResolver(this, testAssemblyPath); - _basePath = Path.GetDirectoryName(testAssemblyPath); - _runtimeResolver = new AssemblyDependencyResolver(testAssemblyPath); -#if NET8_0_OR_GREATER - AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", _basePath); -#endif - } - - protected override Assembly Load(AssemblyName name) - { - log.Debug("Loading {0} assembly", name); - - var loadedAssembly = base.Load(name); - if (loadedAssembly != null) - { - log.Info("Assembly {0} ({1}) is loaded using default base.Load()", name, GetAssemblyLocationInfo(loadedAssembly)); - return loadedAssembly; - } - - var runtimeResolverPath = _runtimeResolver.ResolveAssemblyToPath(name); - if (string.IsNullOrEmpty(runtimeResolverPath) == false && - File.Exists(runtimeResolverPath)) - { - loadedAssembly = LoadFromAssemblyPath(runtimeResolverPath); - } - - if (loadedAssembly != null) - { - log.Info("Assembly {0} ({1}) is loaded using the deps.json info", name, GetAssemblyLocationInfo(loadedAssembly)); - return loadedAssembly; - } - - loadedAssembly = _resolver.Resolve(this, name); - if (loadedAssembly != null) - { - log.Info("Assembly {0} ({1}) is loaded using the TestAssembliesResolver", name, GetAssemblyLocationInfo(loadedAssembly)); - - return loadedAssembly; - } - - // Load assemblies that are dependencies, and in the same folder as the test assembly, - // but are not fully specified in test assembly deps.json file. This happens when the - // dependencies reference in the csproj file has CopyLocal=false, and for example, the - // reference is a projectReference and has the same output directory as the parent. - foreach (var extension in new string[] { ".dll", ".exe" }) - { - string assemblyPath = Path.Combine(_basePath, name.Name + extension); - if (File.Exists(assemblyPath)) - { - loadedAssembly = LoadFromAssemblyPath(assemblyPath); - break; - } - } - - if (loadedAssembly != null) - { - log.Info("Assembly {0} ({1}) is loaded using base path", name, GetAssemblyLocationInfo(loadedAssembly)); - return loadedAssembly; - } - - return loadedAssembly; - } - - protected override IntPtr LoadUnmanagedDll(string name) - { - log.Debug("Loading {0} unmanaged dll", name); - - IntPtr loadedDllHandle = base.LoadUnmanagedDll(name); - if (loadedDllHandle != IntPtr.Zero) - { - log.Info("Unmanaged DLL {0} is loaded using default base.LoadUnmanagedDll()", name); - return loadedDllHandle; - } - - string runtimeResolverPath = _runtimeResolver.ResolveUnmanagedDllToPath(name); - if (string.IsNullOrEmpty(runtimeResolverPath) == false && - File.Exists(runtimeResolverPath)) - { - loadedDllHandle = LoadUnmanagedDllFromPath(runtimeResolverPath); - } - - if (loadedDllHandle != IntPtr.Zero) - { - log.Info("Unmanaged DLL {0} ({1}) is loaded using the deps.json info", name, runtimeResolverPath); - return loadedDllHandle; - } - - string unmanagedDllPath = Path.Combine(_basePath, name + ".dll"); - if (File.Exists(unmanagedDllPath)) - { - loadedDllHandle = LoadUnmanagedDllFromPath(unmanagedDllPath); - } - - if (loadedDllHandle != IntPtr.Zero) - { - log.Info("Unmanaged DLL {0} ({1}) is loaded using base path", name, unmanagedDllPath); - return loadedDllHandle; - } - - return IntPtr.Zero; - } - - private static string GetAssemblyLocationInfo(Assembly assembly) - { - if (assembly.IsDynamic) - { - return $"Dynamic {assembly.FullName}"; - } - - if (string.IsNullOrEmpty(assembly.Location)) - { - return $"No location for {assembly.FullName}"; - } - - return $"{assembly.FullName} from {assembly.Location}"; - } - } -} - -#endif From 89c2f57c0951d90af1fb844ebf752d635cc9758e Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 25 Dec 2025 04:35:50 -0800 Subject: [PATCH 188/190] Change next release to 3.22.0 --- GitVersion.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GitVersion.yml b/GitVersion.yml index a2cba3226..e5da2dfd2 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 3.21.0 +next-version: 3.22.0 mode: ContinuousDelivery branches: main: From 3bfe696b56d42955061990d9389183d4956f49c8 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 30 Dec 2025 10:59:05 -0800 Subject: [PATCH 189/190] Fix problems in assemlby loading --- .../DotNetHelperTests.cs | 2 +- .../nunit.engine.core/DotNetHelper.cs | 3 +- .../Drivers/NUnitNetCore31Driver.cs | 15 ++++++---- .../Internal/AssemblyHelper.cs | 10 +------ .../Internal/TestAssemblyResolver.cs | 30 ++++++++----------- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core.tests/DotNetHelperTests.cs b/src/NUnitEngine/nunit.engine.core.tests/DotNetHelperTests.cs index 3b5f89b97..c3de2cbe3 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/DotNetHelperTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/DotNetHelperTests.cs @@ -63,7 +63,7 @@ public static void CanParseInputLine(string line, string name, string packageVer [TestCase("Microsoft.NETCore.App", "8.0.0", "8.0.22")] [TestCase("Microsoft.NETCore.App", "8.0.0.0", "8.0.22")] [TestCase("Microsoft.NETCore.App", "8.0.0.100", "8.0.22")] - [TestCase("Microsoft.NETCore.App", "8.0.100", "9.0.11")] + [TestCase("Microsoft.NETCore.App", "8.0.100", "8.0.22")] [TestCase("Microsoft.AspNetCore.App", "5.0.0", "8.0.22")] // Rather than 8.0.2 [TestCase("Microsoft.AspNetCore.App", "7.0.0", "8.0.22")] // Rather than 8.0.2 [TestCase("Microsoft.AspNetCore.App", "8.0.0", "8.0.22")] // Rather than 8.0.2 diff --git a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs index 213392c93..571b3fc6f 100644 --- a/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -153,7 +153,8 @@ internal static bool FindBestRuntime(Version targetVersion, IEnumerable= targetVersion) + if (candidate.Version.Major > targetVersion.Major || + candidate.Version.Major == targetVersion.Major && candidate.Version.Minor >= candidate.Version.Minor) if (bestRuntime is null || candidate.Version.Major == bestRuntime.Version.Major) bestRuntime = candidate; } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs index 5ad6b431e..70525872e 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs @@ -1,5 +1,7 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +//#define USE_DEFAULT_ASSEMBLY_LOAD_CONTEXT + #if NETCOREAPP3_1_OR_GREATER using System; using System.Linq; @@ -66,11 +68,19 @@ public string Load(string assemblyPath, IDictionary settings) _testAssembly = AssemblyHelper.FindLoadedAssemblyByPath(assemblyPath); if (_testAssembly != null) + { _assemblyLoadContext = AssemblyLoadContext.GetLoadContext(_testAssembly); + log.Debug($" Already loaded in context {_assemblyLoadContext}"); + } else { +#if USE_DEFAULT_ASSEMBLY_LOAD_CONTEXT + _assemblyLoadContext = AssemblyLoadContext.Default; +#else _assemblyLoadContext = new AssemblyLoadContext(Path.GetFileNameWithoutExtension(assemblyPath)); +#endif _testAssembly = _assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + log.Debug($" Loaded into new context {_assemblyLoadContext}"); } _testAssemblyResolver = new TestAssemblyResolver(_assemblyLoadContext, assemblyPath); @@ -115,11 +125,6 @@ public string Load(string assemblyPath, IDictionary settings) return ExecuteMethod(LOAD_METHOD) as string; } - private Assembly _assemblyLoadContext_Resolving(AssemblyLoadContext arg1, AssemblyName arg2) - { - throw new NotImplementedException(); - } - /// /// Counts the number of test cases for the loaded test assembly /// diff --git a/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs b/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs index bdad8aed7..b402fe970 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -86,14 +87,5 @@ public static Assembly FindLoadedAssemblyByPath(string assemblyPath) !string.IsNullOrEmpty(a.Location) && StringComparer.OrdinalIgnoreCase.Equals(Path.GetFullPath(a.Location), full)); } - - public static Assembly FindLoadedAssemblyByName(AssemblyName assemblyName) - { - return AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => - !a.IsDynamic && - !string.IsNullOrEmpty(a.Location) && - a.GetName() == assemblyName); - } } } diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index 58216a908..a824099a2 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyModel.Resolution; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -52,7 +53,7 @@ private void InitializeResolutionStrategies(AssemblyLoadContext loadContext, str foreach (var reference in assemblyDef.MainModule.GetTypeReferences()) { string fn = reference.FullName; - if (fn.StartsWith("System.Windows.") || fn.StartsWith("PresentationFramework")) + if (fn.StartsWith("System.Windows.") || fn.StartsWith("PresentationFramework") || fn == "WindowsBase") tryWindowsDesktopFirst = true; if (fn.StartsWith("Microsoft.AspNetCore.")) tryAspNetCoreFirst = true; @@ -81,25 +82,18 @@ public void Dispose() _loadContext.Resolving -= OnResolving; } - //public Assembly Resolve(AssemblyLoadContext context, AssemblyName assemblyName) - //{ - // return OnResolving(context, assemblyName); - //} - private Assembly OnResolving(AssemblyLoadContext loadContext, AssemblyName assemblyName) { - if (loadContext == null) throw new ArgumentNullException("context"); - - //var runtimeResolverPath = _assemblyDependencyResolver.ResolveAssemblyToPath(assemblyName); - //if (!string.IsNullOrEmpty(runtimeResolverPath) && File.Exists(runtimeResolverPath)) - //{ - // var loadedAssembly = _loadContext.LoadFromAssemblyPath(runtimeResolverPath); - // if (loadedAssembly != null) - // { - // log.Info($"Assembly {assemblyName} ({loadedAssembly}) is loaded using the deps.json info"); - // return loadedAssembly; - // } - //} + var runtimeResolverPath = _assemblyDependencyResolver.ResolveAssemblyToPath(assemblyName); + if (!string.IsNullOrEmpty(runtimeResolverPath) && File.Exists(runtimeResolverPath)) + { + var loadedAssembly = _loadContext.LoadFromAssemblyPath(runtimeResolverPath); + if (loadedAssembly != null) + { + log.Info($"Assembly {assemblyName} ({loadedAssembly}) is loaded using the deps.json info"); + return loadedAssembly; + } + } foreach (var strategy in ResolutionStrategies) if (strategy.TryToResolve(loadContext, assemblyName, out Assembly loadedAssembly)) From cc13b2a87d1a397ffd2947f11ed2cfd6b6947729 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 31 Dec 2025 18:07:51 -0800 Subject: [PATCH 190/190] Fix problems in loading --- NuGet.config | 1 + build.cake | 4 ++-- nuget/engine/nunit.engine.api.nuspec | 4 ++-- nuget/engine/nunit.engine.nuspec | 4 ++-- nuget/runners/nunit.console-runner-with-extensions.nuspec | 4 ++-- nuget/runners/nunit.console-runner.netcore.nuspec | 5 ++--- nuget/runners/nunit.console-runner.nuspec | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/NuGet.config b/NuGet.config index 1f2ff0303..0f0d51301 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,6 +1,7 @@  + diff --git a/build.cake b/build.cake index fd71db766..019cbc7df 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ -// Load the recipe -#load nuget:?package=NUnit.Cake.Recipe&version=1.6.0-alpha.5 +// Load the recipe +#load nuget:?package=NUnit.Cake.Recipe&version=1.6.0-alpha.8 // Comment out above line and uncomment below for local tests of recipe changes //#load ../NUnit.Cake.Recipe/recipe/*.cake diff --git a/nuget/engine/nunit.engine.api.nuspec b/nuget/engine/nunit.engine.api.nuspec index ff327573f..93e93342a 100644 --- a/nuget/engine/nunit.engine.api.nuspec +++ b/nuget/engine/nunit.engine.api.nuspec @@ -10,7 +10,7 @@ https://nunit.org https://cdn.rawgit.com/nunit/resources/master/images/icon/nunit_256.png - images\nunit_256.png + nunit_256.png false The NUnit Test Engine Api is used by runners to interface with the NUnit Engine in order to discover and execute tests. This package includes the nunit.engine.api, which is normally the only assembly referenced by runners as well as by engine extensions. It is not intended for direct use by users who simply want to run tests using NUnit. @@ -21,10 +21,10 @@ + - \ No newline at end of file diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index fd455f97a..5907d96db 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -10,7 +10,7 @@ https://nunit.org https://cdn.rawgit.com/nunit/resources/master/images/icon/nunit_256.png - images\nunit_256.png + nunit_256.png false The NUnit Test Engine is used by runners to discover and execute tests. This package includes nunit.engine and related assemblies, for use by runner programs. It is not intended for direct use by users who simply want to run tests. @@ -34,6 +34,7 @@ + @@ -65,6 +66,5 @@ - \ No newline at end of file diff --git a/nuget/runners/nunit.console-runner-with-extensions.nuspec b/nuget/runners/nunit.console-runner-with-extensions.nuspec index f38482c47..ef18ed587 100644 --- a/nuget/runners/nunit.console-runner-with-extensions.nuspec +++ b/nuget/runners/nunit.console-runner-with-extensions.nuspec @@ -10,7 +10,7 @@ https://nunit.org https://cdn.rawgit.com/nunit/resources/master/images/icon/nunit_256.png - images\nunit_256.png + nunit_256.png false Console runner for the NUnit 3 unit-testing framework with selected extensions. @@ -42,6 +42,6 @@ - + diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index d1aa3df2b..cec7d93a1 100644 --- a/nuget/runners/nunit.console-runner.netcore.nuspec +++ b/nuget/runners/nunit.console-runner.netcore.nuspec @@ -10,7 +10,7 @@ https://nunit.org https://cdn.rawgit.com/nunit/resources/master/images/icon/nunit_256.png - images\nunit_256.png + nunit_256.png false .NET Core build of the console runner for the NUnit unit-testing framework. @@ -29,6 +29,7 @@ + @@ -46,7 +47,5 @@ - - diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 8e3c1138b..f01c00567 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -10,7 +10,7 @@ https://nunit.org https://cdn.rawgit.com/nunit/resources/master/images/icon/nunit_256.png - images\nunit_256.png + nunit_256.png false Console runner for the NUnit 3 unit-testing framework, without any extensions. @@ -26,6 +26,7 @@ + @@ -90,6 +91,5 @@ -