diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 31e896e98..be0c1e856 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,10 +3,25 @@ "isRoot": true, "tools": { "cake.tool": { - "version": "2.0.0", + "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/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml new file mode 100644 index 000000000..f189fcb1d --- /dev/null +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -0,0 +1,82 @@ +name: NUnitConsoleAndEngine.CI + +on: + workflow_dispatch: + pull_request: + push: + 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 + +jobs: + ContinuousIntegration: + name: Continuous Integration + runs-on: windows-latest + + 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 }} + + steps: + - name: ⤵️ Checkout Source + uses: actions/checkout@v4 + 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.x + 9.0.x + 10.0.x + + - name: 🔧 Install dotnet tools + run: dotnet tool restore + + - name: 🍰 Run cake + 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 }} + + # 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 + + - 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 + + - 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() + with: + name: "Test Results" + path: test-results diff --git a/.github/workflows/PublishDraftRelease.yml b/.github/workflows/PublishDraftRelease.yml new file mode 100644 index 000000000..98136bc08 --- /dev/null +++ b/.github/workflows/PublishDraftRelease.yml @@ -0,0 +1,33 @@ +name: Publish Draft Release on GitHub + +on: + workflow_dispatch: + inputs: + packageVersion: + description: "Package version for release (must match an existing milestone)" + 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.packageVersion }}" diff --git a/.gitignore b/.gitignore index 24420f779..5fc1321fc 100644 --- a/.gitignore +++ b/.gitignore @@ -176,7 +176,9 @@ TestResult.xml testCaseCollection.xml deploy lib +build-results test-results +build-results/ package images MockAssemblyResult.xml diff --git a/GitVersion.yml b/GitVersion.yml index af1579785..e5da2dfd2 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,12 +1,10 @@ -next-version: 3.14.0 +next-version: 3.22.0 mode: ContinuousDelivery -legacy-semver-padding: 5 -build-metadata-padding: 5 -commits-since-version-source-padding: 5 branches: - master: - tag: dev + 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/MixedTests.nunit b/MixedTests.nunit new file mode 100644 index 000000000..9c26ee71b --- /dev/null +++ b/MixedTests.nunit @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 375b8a565..79df83dec 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -1,16 +1,16 @@  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 .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore - 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 @@ -20,16 +20,17 @@ 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 - header-check.cake = header-check.cake + global.json = global.json + KnownExtensions.cake = KnownExtensions.cake LICENSE.txt = LICENSE.txt - NetFXTests.nunit = NetFXTests.nunit + MixedTests.nunit = MixedTests.nunit + NetCoreTests.nunit = NetCoreTests.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 + PLATFORM_SUPPORT.md = PLATFORM_SUPPORT.md README.md = README.md - test-results.cake = test-results.cake VERSIONING.md = VERSIONING.md EndProjectSection EndProject @@ -43,7 +44,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 @@ -55,11 +55,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 @@ -71,24 +66,19 @@ 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\nunit.agent.addins = nuget\runners\nunit.agent.addins + 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.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}" -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 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 EndProjectSection EndProject @@ -102,37 +92,66 @@ 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 EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{9BCA00E2-D072-424B-A6DF-5BECF719C1FB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "zip", "zip", "{20005864-BE82-412D-99BF-288E2D8370E9}" ProjectSection(SolutionItems) = preProject - cake\constants.cake = cake\constants.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 + zip\nunit.bundle.addins = zip\nunit.bundle.addins + zip\nunit.bundle.agent.addins = zip\nunit.bundle.agent.addins EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "msi", "msi", "{0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A}" +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 - msi\resources\nunit.bundle.addins = msi\resources\nunit.bundle.addins + .github\workflows\NUnitConsoleAndEngine.CI.yml = .github\workflows\NUnitConsoleAndEngine.CI.yml EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "zip", "zip", "{20005864-BE82-412D-99BF-288E2D8370E9}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemplates", "{50371E48-BEC3-4D53-BD37-F3A6149CFD0D}" ProjectSection(SolutionItems) = preProject - zip\nunit.bundle.addins = zip\nunit.bundle.addins + .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 -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}" +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 +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}" + 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 +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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "src\TestData\WpfApp\WpfApp.csproj", "{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}" +EndProject +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 +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}") = "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 @@ -164,14 +183,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 @@ -180,14 +191,58 @@ 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 + {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 + {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 + {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 + {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 + {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 @@ -202,18 +257,29 @@ 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} - {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} + {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} + {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} + {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} + {AD40CA55-35CC-41CA-85F5-8FDA4ECAFF78} = {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/NetFXTests.nunit b/NetCoreTests.nunit similarity index 51% rename from NetFXTests.nunit rename to NetCoreTests.nunit index d7c02b751..67fd0b9b3 100644 --- a/NetFXTests.nunit +++ b/NetCoreTests.nunit @@ -1,11 +1,9 @@  - - + - - + \ No newline at end of file diff --git a/NuGet.config b/NuGet.config index fe24e8fb4..0f0d51301 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,10 +1,8 @@  - + + - - - \ No newline at end of file diff --git a/PLATFORM_SUPPORT.md b/PLATFORM_SUPPORT.md new file mode 100644 index 000000000..18c2b8362 --- /dev/null +++ b/PLATFORM_SUPPORT.md @@ -0,0 +1,47 @@ +# Platform Support Lifecycle + +"Platform Support" for this project includes three somewhat different things: +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 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 currently (July 3, 2024) supply the following agents with the console runner: +* .NET Framework 4.6.2 +* ,NET Core 3.1 +* .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 | Microsoft
End of Support | Agent Retirement | Notes | +| -------------------- | --------------- | --------------------- | --- | +| .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 | 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 | + +## 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 +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. + diff --git a/README.md b/README.md index 4ebdb0bdc..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,20 +14,22 @@ 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). 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. -## Documentation ## +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/). -## 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). @@ -35,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/appveyor.yml b/appveyor.yml deleted file mode 100644 index 5eefdac67..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,27 +0,0 @@ -image: Visual Studio 2022 - -# do not trigger build for branches intended to test azure pipeline only -branches: - except: - - /^azure-/ - -build_script: - - ps: .\build.ps1 --target=Appveyor --configuration=Release - -# disable built-in tests. -test: off - -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 \ No newline at end of file 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: diff --git a/build.cake b/build.cake index 96350d17b..019cbc7df 100644 --- a/build.cake +++ b/build.cake @@ -1,785 +1,205 @@ -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/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 - -// Install Tools -#tool NuGet.CommandLine&version=6.0.0 -#tool dotnet:?package=GitVersion.Tool&version=5.6.3 -#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); - - Information("Initializing PackageDefinitions"); - InitializePackageDefinitions(context); - - if (BuildSystem.IsRunningOnAppVeyor) - AppVeyor.UpdateBuildVersion(ProductVersion + "-" + AppVeyor.Environment.Build.Number); -}); - -Teardown(context => -{ - // Executed AFTER the last task. - DisplayUnreportedErrors(); +// 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 + +#load package-tests.cake +#load KnownExtensions.cake + +// Initialize BuildSettings +BuildSettings.Initialize( + Context, + title: "NUnit Console and Engine", + githubRepository: "nunit-console", + solutionFile: "NUnitConsole.sln", + exemptFiles: new[] { "Options.cs", "ProcessUtils.cs", "ProcessUtilsTests.cs" }); + +////////////////////////////////////////////////////////////////////// +// 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" }; +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"}; + +////////////////////////////////////////////////////////////////////// +// INDIVIDUAL PACKAGE DEFINITIONS +////////////////////////////////////////////////////////////////////// + +PackageDefinition NUnitConsoleNuGetPackage; +PackageDefinition NUnitConsoleRunnerNuGetPackage; +PackageDefinition NUnitConsoleRunnerNetCorePackage; +PackageDefinition NUnitConsoleRunnerNet80Package; +PackageDefinition NUnitEnginePackage; +PackageDefinition NUnitEngineApiPackage; +PackageDefinition NUnitConsoleRunnerChocolateyPackage; +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("nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), + HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), + HasDirectory("tools/agents/net8.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/net8.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"), + tests: PackageTests.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") }), + //// 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", + source: BuildSettings.NuGetDirectory + "runners/nunit.console-runner.netcore.nuspec", + checks: new PackageCheck[] + { + HasFiles("nunit.exe"), + 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.exe"), + tests: PackageTests.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", "nunit3-console.exe", "nunit3-console.exe.config").AndFiles(ENGINE_FILES), + HasDirectory("tools/agents/net462").WithFiles(AGENT_FILES), + HasDirectory("tools/agents/net8.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"), + tests: PackageTests.StandardRunnerTests), + + NUnitConsoleZipPackage = new ZipPackage( + id: "NUnit.Console", + 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("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"), + HasDirectory("bin/agents/net462").WithFiles(AGENT_FILES).AndFiles(AGENT_PDB_FILES), + 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/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"), + tests: PackageTests.ZipRunnerTests, + bundledExtensions: new [] { + Extensions.VSProjectLoader.NuGetPackage, + Extensions.NUnitProjectLoader.NuGetPackage, + Extensions.NUnitV2Driver.NuGetPackage, + Extensions.NUnitV2ResultWriter.NuGetPackage, + Extensions.TeamCityEventListener.NuGetPackage + }), + + // 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/net462").WithFiles(ENGINE_FILES), + HasDirectory("lib/net8.0").WithFiles(ENGINE_FILES).AndFile("Microsoft.Extensions.DependencyModel.dll"), + HasDirectory("contentFiles/any/agents/net462").WithFiles(AGENT_FILES) + }, + symbols: new PackageCheck[] { + HasDirectory("lib/net462").WithFiles(ENGINE_PDB_FILES), + HasDirectory("lib/net8.0").WithFiles(ENGINE_PDB_FILES), + HasDirectory("contentFiles/any/agents/net462").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/net462").WithFile("nunit.engine.api.dll"), + HasDirectory("lib/netstandard2.0").WithFile("nunit.engine.api.dll"), + }, + symbols: new PackageCheck[] { + HasDirectory("lib/net462").WithFile("nunit.engine.api.pdb"), + HasDirectory("lib/netstandard2.0").WithFile("nunit.engine.api.pdb") + }) }); ////////////////////////////////////////////////////////////////////// -// CLEANING +// TEST RUNNERS ////////////////////////////////////////////////////////////////////// -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 -////////////////////////////////////////////////////////////////////// - -Task("Build") - .Description("Builds the engine and console") - .IsDependentOn("CheckHeaders") - .IsDependentOn("Clean") - .Does(() => - { - if (IsRunningOnWindows()) - BuildSolution(); - else - BuildEachProjectSeparately(); - }); - -public void BuildSolution() +// Use the console runner we just built to run package tests +public class ConsoleRunnerSelfTester : TestRunner, IPackageTestRunner { - MSBuild(SOLUTION_FILE, CreateMSBuildSettings("Build").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") - .WithProperty("TargetFramework", "netstandard2.0") - .WithProperty("PublishDir", BIN_DIR + "netstandard2.0")); + private string _executablePath; - DisplayBanner("Publishing ENGINE Project for NETSTANDARD2.0"); - MSBuild(ENGINE_PROJECT, CreateMSBuildSettings("Publish") - .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" }) + public ConsoleRunnerSelfTester(string executablePath) { - DisplayBanner($"Publishing AGENT Project for {framework.ToUpper()}"); - MSBuild(AGENT_PROJECT, CreateMSBuildSettings("Publish") - .WithProperty("TargetFramework", framework) - .WithProperty("PublishDir", BIN_DIR + "agents/" + framework)); + _executablePath = executablePath; } -} - -private void BuildEachProjectSeparately() -{ - DotNetRestore(SOLUTION_FILE); - - BuildProject(ENGINE_PROJECT); - BuildProject(CONSOLE_PROJECT); - BuildProject(AGENT_PROJECT); - 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(MOCK_ASSEMBLY_X86_PROJECT, "net35", "net40", "netcoreapp2.1", "netcoreapp3.1"); - BuildProject(NOTEST_PROJECT, "net35", "netcoreapp2.1", "netcoreapp3.1"); - - - DisplayBanner("Publish .NET Core & Standard projects"); - - MSBuild(ENGINE_PROJECT, CreateMSBuildSettings("Publish") - .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") - .WithProperty("TargetFramework", "netcoreapp2.1") - .WithProperty("PublishDir", BIN_DIR + "netcoreapp2.1")); - MSBuild(ENGINE_CORE_TESTS_PROJECT, CreateMSBuildSettings("Publish") - .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, params string[] targetFrameworks) -{ - if (targetFrameworks.Length == 0) + public int RunPackageTest(string arguments, bool redirectOutput = false) { - DisplayBanner($"Building {System.IO.Path.GetFileName(project)}"); - DotNetMSBuild(project, CreateDotNetMSBuildSettings("Build")); - } - else - { - foreach (var framework in targetFrameworks) - { - DisplayBanner($"Building {System.IO.Path.GetFileName(project)} for {framework}"); - if (framework == "net35") - MSBuild(project, CreateMSBuildSettings("Build").WithProperty("TargetFramework", framework)); - else - DotNetMSBuild(project, CreateDotNetMSBuildSettings("Build").WithProperty("TargetFramework", framework)); - } + return base.RunPackageTest(_executablePath, new ProcessSettings { Arguments = arguments, RedirectStandardOutput = redirectOutput }); } } -////////////////////////////////////////////////////////////////////// -// BUILD C++ TESTS -////////////////////////////////////////////////////////////////////// - -Task("BuildCppTestFiles") - .Description("Builds the C++ mock test assemblies") - .WithCriteria(IsRunningOnWindows) - .Does(() => - { - MSBuild( - SOURCE_DIR + "NUnitEngine/mock-cpp-clr/mock-cpp-clr-x86.vcxproj", - CreateMSBuildSettings("Build").WithProperty("Platform", "x86")); - - MSBuild( - SOURCE_DIR + "NUnitEngine/mock-cpp-clr/mock-cpp-clr-x64.vcxproj", - CreateMSBuildSettings("Build").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, "netcoreapp2.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, "netcoreapp2.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"); - }); - -////////////////////////////////////////////////////////////////////// -// 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 -////////////////////////////////////////////////////////////////////// - -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(() => - { - if (!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(() => - { - if (!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(() => - { - if (!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(() => - { - bool isDirectTarget = Target == "CreateDraftRelease"; - - if (isDirectTarget && !HasArgument("productVersion")) - throw new Exception("Must specify --productVersion with the CreateDraftRelease target."); - - if (IsReleaseBranch || isDirectTarget) - { - string milestone = IsReleaseBranch - ? _buildVersion.BranchName.Substring(8) - : ProductVersion; - 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"); - } - }); - -////////////////////////////////////////////////////////////////////// -// CREATE A PRODUCTION RELEASE -////////////////////////////////////////////////////////////////////// - -Task("CreateProductionRelease") - .Does(() => - { - if (IsProductionRelease) - { - string token = EnvironmentVariable(GITHUB_ACCESS_TOKEN); - string tagName = ProductVersion; - - 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 - { - Information("Skipping CreateProductionRelease because this is not a production release"); - } - }); - -////////////////////////////////////////////////////////////////////// -// TASK TARGETS -////////////////////////////////////////////////////////////////////// - -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"); - ////////////////////////////////////////////////////////////////////// // EXECUTION ////////////////////////////////////////////////////////////////////// -RunTarget(Target); +Build.Run() diff --git a/build.cmd b/build.cmd index d060b75b8..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 %* +dotnet cake %* diff --git a/cake/build-settings.cake b/cake/build-settings.cake deleted file mode 100644 index 6aaf67882..000000000 --- a/cake/build-settings.cake +++ /dev/null @@ -1,23 +0,0 @@ -#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)); - - Target = context.TargetTask.Name; - Configuration = context.Argument("configuration", "Release"); - } - - public string Target { get; } - public string Configuration { get; } -} \ No newline at end of file diff --git a/cake/constants.cake b/cake/constants.cake deleted file mode 100644 index 4c3b713bc..000000000 --- a/cake/constants.cake +++ /dev/null @@ -1,85 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// RUNTIME CONSTANTS AND VARIABLES USED AS CONSTANTS -////////////////////////////////////////////////////////////////////// - -// 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. - -// 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"; - -// 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"; -// 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"; - -// Package sources for nuget restore -var PACKAGE_SOURCE = new string[] -{ - "https://www.nuget.org/api/v2", - "https://www.myget.org/F/nunit/api/v2" -}; - -// Extensions we bundle -var BUNDLED_EXTENSIONS = new[] -{ - "NUnit.Extension.VSProjectLoader", - "NUnit.Extension.NUnitProjectLoader", - "NUnit.Extension.NUnitV2Driver", - "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/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/package-checks.cake b/cake/package-checks.cake deleted file mode 100644 index 6c19f51b9..000000000 --- a/cake/package-checks.cake +++ /dev/null @@ -1,227 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// LISTS OF FILES USED IN CHECKING PACKAGES -////////////////////////////////////////////////////////////////////// - -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"}; -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", "nunit3-console.dll.config" }; - -////////////////////////////////////////////////////////////////////// -// 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) -{ - Console.WriteLine("\nPackage Name: " + System.IO.Path.GetFileName(package)); - - if (!FileExists(package)) - { - WriteError("Package was not found!"); - return false; - } - - 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!"); - - return allPassed; - } - 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 bool ApplyChecks(string dir, PackageCheck[] checks) -{ - bool allOK = true; - - foreach (var check in checks) - allOK &= check.Apply(dir); - - return allOK; -} - -public abstract class PackageCheck -{ - public abstract bool Apply(string dir); -} - -public class FileCheck : PackageCheck -{ - string[] _paths; - - public FileCheck(string[] paths) - { - _paths = paths; - } - - 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 class DirectoryCheck : PackageCheck -{ - private string _path; - private List _files = new List(); - - public DirectoryCheck(string path) - { - _path = path; - } - - public DirectoryCheck WithFiles(params string[] files) - { - _files.AddRange(files); - return this; - } - - public DirectoryCheck AndFiles(params string[] files) - { - return WithFiles(files); - } - - public DirectoryCheck WithFile(string file) - { - _files.Add(file); - return this; - } - - public DirectoryCheck AndFile(string 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); - -private DirectoryCheck HasDirectory(string dir) => new DirectoryCheck(dir); - -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); -} diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake deleted file mode 100644 index 93db0d309..000000000 --- a/cake/package-definitions.cake +++ /dev/null @@ -1,361 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// INDIVIDUAL PACKAGE DEFINITIONS -////////////////////////////////////////////////////////////////////// - -PackageDefinition NUnitConsoleNuGetPackage; -PackageDefinition NUnitConsoleRunnerNuGetPackage; -PackageDefinition NUnitConsoleRunnerNet60Package; -PackageDefinition NUnitEnginePackage; -PackageDefinition NUnitEngineApiPackage; -PackageDefinition NUnitConsoleRunnerChocolateyPackage; -PackageDefinition NUnitConsoleMsiPackage; -PackageDefinition NUnitConsoleZipPackage; - -public void InitializePackageDefinitions(ICakeContext context) -{ - const string DOTNET_EXE_X86 = @"C:\Program Files (x86)\dotnet\dotnet.exe"; - bool dotnetX86Available = IsRunningOnWindows() && System.IO.File.Exists(DOTNET_EXE_X86); - - // Tests run for all runner packages except NETCORE runner - var StandardRunnerTests = new List - { - Net35Test, - Net35X86Test, - Net40Test, - Net40X86Test, - Net35PlusNet40Test, - NetCore21Test, - NetCore31Test, - Net50Test, - Net60Test, - NetCore21PlusNetCore31Test, - NetCore21PlusNetCore31PlusNet50PlusNet60Test, - Net40PlusNet60Test - }; - - if (dotnetX86Available) - { - StandardRunnerTests.Add(NetCore21X86Test); - StandardRunnerTests.Add(NetCore31X86Test); - } - - // Tests run for the NETCORE runner package - var NetCoreRunnerTests = new List - { - NetCore21Test, - NetCore31Test, - Net50Test, - Net60Test, - NetCore21PlusNetCore31Test, - NetCore21PlusNetCore31PlusNet50PlusNet60Test - }; - - AllPackages.AddRange(new PackageDefinition[] { - - NUnitConsoleNuGetPackage = new NuGetPackage( - context: context, - id: "NUnit.Console", - version: ProductVersion, - 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, - 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.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") - }, - 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) - }, - executable: "tools/nunit3-console.exe", - tests: StandardRunnerTests), - - NUnitConsoleRunnerNet60Package = 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/net6.0/any").WithFiles(CONSOLE_FILES_NETCORE).AndFiles(ENGINE_FILES).AndFile("nunit.console.nuget.addins") - }, - symbols: new PackageCheck[] { - HasDirectory("tools/net6.0/any").WithFile("nunit3-console.pdb").AndFiles(ENGINE_PDB_FILES) - }, - executable: "tools/net6.0/any/nunit3-console.exe", - tests: NetCoreRunnerTests), - - NUnitConsoleRunnerChocolateyPackage = new ChocolateyPackage( - context: context, - id: "nunit-console-runner", - 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") - }, - executable: "tools/nunit3-console.exe", - tests: StandardRunnerTests), - - NUnitConsoleMsiPackage = new MsiPackage( - context: context, - id: "NUnit.Console", - version: 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.Concat(new[] { NUnitProjectTest })), - - NUnitConsoleZipPackage = new ZipPackage( - context: context, - id: "NUnit.Console", - version: ProductVersion, - 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/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), - 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) - }, - executable: "bin/net20/nunit3-console.exe", - tests: StandardRunnerTests.Concat(new[] { NUnitProjectTest })), - - // NOTE: Packages below this point have no direct tests - - NUnitEnginePackage = new NuGetPackage( - context: context, - id: "NUnit.Engine", - version: ProductVersion, - 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: ProductVersion, - 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-tester.cake b/cake/package-tester.cake deleted file mode 100644 index a3ee24f4d..000000000 --- a/cake/package-tester.cake +++ /dev/null @@ -1,125 +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); - } - } - - 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 e28e90434..000000000 --- a/cake/package-tests.cake +++ /dev/null @@ -1,122 +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 Net60Test = new PackageTest( - "Net60Test", - "Run mock-assembly.dll under .NET 6.0", - "net6.0/mock-assembly.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 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 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", - "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", - MockAssemblyExpectedResult(4)); - -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/test-results.cake b/cake/test-results.cake deleted file mode 100644 index dc34081ac..000000000 --- a/cake/test-results.cake +++ /dev/null @@ -1,192 +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 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"); - } - - public XmlNode Xml { 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 #904 is fixed. - return s == null ? 0 : int.Parse(s); - } -} - -public class TestReport -{ - public PackageTest Test; - public ActualResult Result; - public List Errors; - - public TestReport(PackageTest test, ActualResult result) - { - 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}"); - } - - public void Display(int index) - { - Console.WriteLine($"\n{index}. {Test.Description}"); - Console.WriteLine($" Args: {Test.Arguments}\n"); - - foreach (var error in Errors) - Console.WriteLine(error); - - Console.WriteLine(Errors.Count == 0 - ? " SUCCESS: Test Result matches expected result!" - : "\n ERROR: Test Result not as expected!"); - } - - // 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}"); - } - - 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(TestReport report) - { - _reports.Add(report); - } - - public bool ReportResults() - { - DisplayBanner($"Test Results for {_packageName}"); - - Console.WriteLine("\nTest Environment"); - Console.WriteLine($" OS Version: {Environment.OSVersion.VersionString}"); - Console.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); - } - - return hasErrors; - } -} diff --git a/cake/utilities.cake b/cake/utilities.cake deleted file mode 100644 index 803c04a3a..000000000 --- a/cake/utilities.cake +++ /dev/null @@ -1,256 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// 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) -{ - var settings = new MSBuildSettings() - .SetConfiguration(Configuration) - .SetVerbosity(Verbosity.Minimal) - .WithProperty("Version", ProductVersion) - .WithProperty("ApiFileVersion", 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) -{ - return new DotNetMSBuildSettings() - .SetConfiguration(Configuration) - .WithProperty("Version", ProductVersion) - .WithProperty("ApiFileVersion", 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 = $"\"{NET60_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 -////////////////////////////////////////////////////////////////////// - -public void CopyPackageContents(DirectoryPath packageDir, DirectoryPath outDir) -{ - var files = GetFiles(packageDir + "/tools/*").Concat(GetFiles(packageDir + "/tools/net20/*")); - 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."); -} - -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 deleted file mode 100644 index 12f0c9813..000000000 --- a/cake/versioning.cake +++ /dev/null @@ -1,114 +0,0 @@ -using System.Text.RegularExpressions; - -public class BuildVersion -{ - private ISetupContext _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(ISetupContext context) - { - _context = context; - _gitVersion = context.GitVersion(); - - BranchName = _gitVersion.BranchName; - IsReleaseBranch = BranchName.StartsWith("release-"); - - string productVersion = context.HasArgument("productVersion") - ? context.Argument("productVersion") - : CalculateProductVersion(); - - int dash = productVersion.IndexOf('-'); - IsPreRelease = dash > 0; - - string versionPart = productVersion; - string suffix = ""; - string label = ""; - - if (IsPreRelease) - { - versionPart = productVersion.Substring(0, dash); - suffix = productVersion.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; - - ProductVersion = productVersion; - AssemblyVersion = SemVer + ".0"; - AssemblyFileVersion = SemVer; - AssemblyInformationalVersion = productVersion; - } - - public string BranchName { get; } - public bool IsReleaseBranch { get; } - - public string ProductVersion { 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 CalculateProductVersion() - { - 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/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index f8f321b5e..3cc584f44 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -28,69 +28,64 @@ + - - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + 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.choco.addins deleted file mode 100644 index 905420e97..000000000 --- a/choco/nunit.choco.addins +++ /dev/null @@ -1 +0,0 @@ -../../nunit-extension-*/tools/ # find extensions installed under chocolatey diff --git a/global.json b/global.json index 69d60238d..393147ff6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.101", - "rollForward": "patch" + "version": "10.0.0", + "rollForward": "feature" } } \ No newline at end of file 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 2c7ea627c..000000000 Binary files a/msi/nunit/banner.bmp and /dev/null differ diff --git a/msi/nunit/console-files.wxi b/msi/nunit/console-files.wxi deleted file mode 100644 index 061f28c17..000000000 --- a/msi/nunit/console-files.wxi +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - diff --git a/msi/nunit/dialog.bmp b/msi/nunit/dialog.bmp deleted file mode 100644 index f67e6fd33..000000000 Binary files a/msi/nunit/dialog.bmp and /dev/null differ diff --git a/msi/nunit/engine-files.wxi b/msi/nunit/engine-files.wxi deleted file mode 100644 index 457244bd6..000000000 --- a/msi/nunit/engine-files.wxi +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 1f4eab58e..000000000 --- a/msi/nunit/runner-directories.wxi +++ /dev/null @@ -1,23 +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 704b08394..000000000 Binary files a/msi/resources/License.rtf and /dev/null differ diff --git a/msi/resources/nunit.agent.addins b/msi/resources/nunit.agent.addins deleted file mode 100644 index 1558d5f75..000000000 --- a/msi/resources/nunit.agent.addins +++ /dev/null @@ -1 +0,0 @@ -../../ # refer to the directory containing the engine and runner diff --git a/msi/resources/nunit.bundle.addins b/msi/resources/nunit.bundle.addins deleted file mode 100644 index be4c6fa29..000000000 --- a/msi/resources/nunit.bundle.addins +++ /dev/null @@ -1,5 +0,0 @@ -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 diff --git a/msi/resources/nunit.ico b/msi/resources/nunit.ico deleted file mode 100644 index 7073c2487..000000000 Binary files a/msi/resources/nunit.ico and /dev/null differ 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.api.nuspec b/nuget/engine/nunit.engine.api.nuspec index 568d00b16..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.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 271016e96..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. @@ -19,7 +19,7 @@ nunit test testing tdd engine Copyright (c) 2021 Charlie Poole, Rob Prouse - + @@ -34,65 +34,37 @@ + - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - \ No newline at end of file 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-with-extensions.nuspec b/nuget/runners/nunit.console-runner-with-extensions.nuspec index 660e55bad..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. @@ -32,16 +32,16 @@ - - - - - + + + + + - + diff --git a/nuget/runners/nunit.console-runner.netcore.nuspec b/nuget/runners/nunit.console-runner.netcore.nuspec index 1540808f3..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. @@ -21,30 +21,31 @@ 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/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 032d4d088..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,85 +26,70 @@ - - - - - - - - - - - - - + - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/nuget/runners/nunit.console.nuget.addins b/nuget/runners/nunit.console.nuget.addins deleted file mode 100644 index c5495ff20..000000000 --- a/nuget/runners/nunit.console.nuget.addins +++ /dev/null @@ -1,6 +0,0 @@ -../../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/ diff --git a/package-tests.cake b/package-tests.cake new file mode 100644 index 000000000..2447a773d --- /dev/null +++ b/package-tests.cake @@ -0,0 +1,545 @@ +public static class Extensions +{ + // 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( + "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.10"); +} + +public static class PackageTests +{ + // Tests run for the Standard runner packages (both nuget and chocolatey) + public static List StandardRunnerTests = new List(); + + // Tests run for the NETCORE runner package + public static List NetCoreRunnerTests = new List(); + + // Tests Run for the ZIP Package + public static List ZipRunnerTests = new List(); + + static PackageTests() + { + // Wrapper to add tests to all three Lists + var AllLists = new ListWrapper(StandardRunnerTests, NetCoreRunnerTests, ZipRunnerTests); + + // Wrapper to add tests to the standard and netcore lists + var StandardAndNetCoreLists = new ListWrapper(StandardRunnerTests, NetCoreRunnerTests); + + // Wrapper to add tests to the standard and zip Lists + var StandardAndZipLists = new ListWrapper(StandardRunnerTests, ZipRunnerTests); + + ////////////////////////////////////////////////////////////////////// + // RUN MOCK-ASSEMBLY UNDER EACH RUNTIME + ////////////////////////////////////////////////////////////////////// + + StandardAndZipLists.Add(new PackageTest(1, "Net462Test") + { + 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, "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", + 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", + Arguments = "testdata/net8.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-8.0") + }); + + AllLists.Add(new PackageTest(1, "Net70Test") + { + 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 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 targeting .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 targeting .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 targeting .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 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 targeting .NET 6.0", + Arguments = "testdata/net6.0/mock-assembly-x86.dll", + ExpectedResult = new MockAssemblyX86ExpectedResult("netcore-6.0") + }); + + 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") + }); + } + } + + ////////////////////////////////////////////////////////////////////// + // 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, "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 testdata/net10.0/mock-assembly.dll", + ExpectedResult = new MockAssemblyExpectedResult("netcore-6.0", "netcore-8.0", "netcore-10.0") + }); + + StandardAndZipLists.Add(new PackageTest(1, "Net462PlusNet60Test") + { + 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") + }); + + ////////////////////////////////////////////////////////////////////// + // 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") } + } + }); + + 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 + ////////////////////////////////////////////////////////////////////// + + AllLists.Add(new PackageTest(1, "Net60WindowsFormsTest") + { + Description = "Run test using windows forms targeting .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 targeting .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 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 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") } } + }); + + ////////////////////////////////////////////////////////////////////// + // TESTS OF EXTENSION LISTING + ////////////////////////////////////////////////////////////////////// + + StandardAndNetCoreLists.Add(new PackageTest(1, "NoExtensionsInstalled") + { + Description = "List Extensions shows none installed", + Arguments = "--list-extensions", + 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", + 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", + ExpectedOutput = new OutputCheck[] { Contains("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 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 [] { Extensions.NUnitProjectLoader } + }); + + NetCoreRunnerTests.Add(new PackageTest(1, "NUnitProjectTest") + { + Description= "Run NUnit project with mock-assembly.dll targeting 8.0", + Arguments="../../NetCoreTests.nunit --config=Release", + ExpectedResult = new MockAssemblyExpectedResult("netcore-8.0"), + ExtensionsNeeded = new [] { Extensions.NUnitProjectLoader } + }); + + // V2 Result Writer Test + AllLists.Add(new PackageTest(1, "V2ResultWriterTest") + { + 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[] { Extensions.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", "netcore-9.0", "netcore-10.0"), + 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") + { + Description = "Run mock-assembly targeting .NET 4.6.2 with --teamcity option", + Arguments = "testdata/net462/mock-assembly.dll --teamcity", + ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), + ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, + ExpectedOutput = new OutputCheck[] { Contains("##teamcity") } + }); + + // TeamCity Event Listener Test + 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", + ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"), + ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, + ExpectedOutput = new[] { Contains("##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", + ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), + ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, + ExpectedOutput = new[] { Contains("##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", + ExpectedResult = new MockAssemblyExpectedResult("net-6.0"), + ExtensionsNeeded = new[] { Extensions.TeamCityEventListener }, + ExpectedOutput = new[] { Contains("##teamcity") } + }); + + // 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[] { Extensions.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[] { Extensions.NUnitV2Driver } + }); + + ////////////////////////////////////////////////////////////////////// + // SPECIAL CASES + ////////////////////////////////////////////////////////////////////// + + 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", + 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 targeting .NET 6.0", + Arguments = "testdata/net6.0/InvalidTestNames.dll", + 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 targeting .NET 8.0", + Arguments = "testdata/net8.0/InvalidTestNames.dll", + 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 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") + { + 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 + + class ListWrapper + { + private List[] _lists; + + public ListWrapper(params List[] lists) + { + _lists = lists; + } + + public void Add(PackageTest test) + { + foreach (var list in _lists) + list.Add(test); + } + } + + 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]); + } + } + + 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]); + } + } + + #endregion +} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2168a2e85..9eec7717d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,18 +1,18 @@ - - ..\..\..\bin\$(Configuration)\ - true - 7 - true - NUnit Software - Copyright (c) 2022 Charlie Poole, Rob Prouse - + + + 12 + strict + true + 3.99.0.0 + $(MSBuildThisFileDirectory)\..\bin\$(Configuration)\ + true + true + + NUnit Software + Copyright (c) 2022 Charlie Poole, Rob Prouse + $(NoWarn);NU1603 + - - 3.99.0.0 - 3.99.0.0 - 3.99.0.0-VSIDE - - - \ No newline at end of file + 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..caf100636 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 { @@ -92,21 +92,21 @@ 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(' ')); // 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.")] [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); @@ -142,9 +142,9 @@ public void NoInputFiles() [TestCase("WaitBeforeExit", "wait")] [TestCase("NoHeader", "noheader|noh")] [TestCase("DisposeRunners", "dispose-runners")] - [TestCase("TeamCity", "teamcity")] [TestCase("SkipNonTestAssemblies", "skipnontestassemblies")] -#if NET35 + [TestCase("NoResult", "noresult")] +#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,10 @@ public void CanRecognizeIntOptions(string propertyName, string pattern) [TestCase("--test-name-format")] [TestCase("--params")] [TestCase("--encoding")] -#if NET35 + [TestCase("--extensionDirectory")] + [TestCase("--enable")] + [TestCase("--disable")] +#if NETFRAMEWORK [TestCase("--process")] [TestCase("--domain")] [TestCase("--framework")] @@ -317,7 +320,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() { @@ -403,10 +406,10 @@ 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); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -417,10 +420,10 @@ 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); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -430,14 +433,13 @@ 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); 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); @@ -468,7 +470,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"); @@ -479,12 +480,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,21 +503,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); - } - - [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)); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -524,7 +511,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); } @@ -534,11 +521,10 @@ 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 - .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,10 +545,10 @@ 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); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -574,10 +560,10 @@ 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); + Assert.That(spec.Transform, Is.Null); } [Test] @@ -586,7 +572,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); @@ -594,7 +579,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); @@ -612,39 +597,6 @@ 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) - { - // Given - List args = new List { "tests.dll" }; - if (hasTeamcityInCmd) - { - 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()); - } - - // When - var actualTeamCity = options.TeamCity; - - // Then - Assert.That(expectedTeamCity, Is.EqualTo(actualTeamCity)); - } - [Test] public void ShouldNotFailOnEmptyLine() { @@ -859,6 +811,30 @@ 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"); + Assert.That(options.Validate); + 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(); @@ -869,25 +845,15 @@ 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 '{propertyName}' is not defined"); 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 8f26a6c4c..8b5c0813b 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 { @@ -10,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/ConsoleRunnerTests.cs b/src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs index cbc18bbe3..7c45fa7ee 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,52 @@ 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 ThrowsRequiredExtensionExceptionWhenTeamcityOptionIsSpecifiedButNotAvailable() + { + var ex = Assert.Throws( + () => new ConsoleRunner(_testEngine, ConsoleMocks.Options("mock-assembly.dll", "--teamcity"), new ColorConsoleWriter())); + + 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.")); } } internal class FakeResultService : Service, IResultService { + public bool ThrowsUnauthorizedAccessException; + public string[] Formats { get @@ -43,25 +71,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.tests/DefaultOptionsProviderTests.cs b/src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs deleted file mode 100644 index 51280294b..000000000 --- a/src/NUnitConsole/nunit3-console.tests/DefaultOptionsProviderTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using NUnit.Common; -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.tests/MakeTestPackageTests.cs b/src/NUnitConsole/nunit3-console.tests/MakeTestPackageTests.cs index d65d451b9..3f3173f64 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")] @@ -65,11 +65,11 @@ 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); } -#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/OutputSpecificationTests.cs b/src/NUnitConsole/nunit3-console.tests/OutputSpecificationTests.cs index d79e8af23..871c53f5c 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,33 +30,33 @@ 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); + Assert.That(spec.Transform, Is.Null); } [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); + Assert.That(spec.Transform, Is.Null); } [Test] 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/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/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 3b33d3e17..c44294def 100644 --- a/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj +++ b/src/NUnitConsole/nunit3-console.tests/nunit3-console.tests.csproj @@ -2,7 +2,9 @@ NUnit.ConsoleRunner.Tests - net35;net6.0 + net462;net8.0 + Exe + NUnit.Engine.Tests.Program 1685 Full @@ -14,16 +16,11 @@ - - + + + - - - - - - PreserveNewest @@ -40,7 +37,7 @@ - + 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/ConsoleOptions.cs b/src/NUnitConsole/nunit3-console/ConsoleOptions.cs index 82c616e15..7875a7eb6 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 @@ -20,19 +21,15 @@ 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 /// 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(); @@ -40,7 +37,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; } @@ -48,11 +45,19 @@ internal ConsoleOptions(IDefaultOptionsProvider defaultOptionsProvider, IFileSys public bool ShowVersion { get; private set; } + 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(); + // 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(); @@ -81,8 +86,6 @@ internal ConsoleOptions(IDefaultOptionsProvider defaultOptionsProvider, IFileSys 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; } } @@ -98,14 +101,13 @@ 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 (resultOutputSpecifications.Count == 0) resultOutputSpecifications.Add( new OutputSpecification("TestResult.xml", CURRENT_DIRECTORY_ON_ENTRY)); @@ -114,7 +116,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; } } @@ -152,15 +154,13 @@ 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; } - 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() { @@ -273,7 +273,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 => { @@ -302,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); @@ -382,6 +379,30 @@ 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 => + { + 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"))); @@ -393,7 +414,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 +424,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."); @@ -412,11 +433,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 6e11ec932..0756d5fae 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -5,11 +5,13 @@ using System.IO; using System.Xml; using NUnit.Common; +using NUnit.ConsoleRunner.Options; using NUnit.ConsoleRunner.Utilities; using NUnit.Engine; using NUnit.Engine.Extensibility; using System.Runtime.InteropServices; using System.Text; +using System.Linq; namespace NUnit.ConsoleRunner { @@ -24,6 +26,13 @@ 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_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"; + public static readonly int OK = 0; public static readonly int INVALID_ARG = -1; public static readonly int INVALID_ASSEMBLY = -2; @@ -44,23 +53,70 @@ 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)); + // NOTE: Accessing Services triggers the engine to initialize all services _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.FindExtensionAssemblies(extensionDirectory); + + foreach (string extensionDirectory in _options.ExtensionDirectories) + _extensionService.FindExtensionAssemblies(extensionDirectory); + + _workDirectory = options.WorkDirectory; + if (_workDirectory != null) + Directory.CreateDirectory(_workDirectory); + else + _workDirectory = null; + + // Attempt to enable extensions as requested by the user + foreach (string typeName in options.EnableExtensions) + { + // Throw if requested extension is not installed + if (!IsExtensionInstalled(typeName)) + throw new RequiredExtensionException(typeName); + + 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); + } - // 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")); + + 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. /// @@ -132,52 +188,51 @@ 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) - { - 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"; @@ -257,7 +312,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); @@ -269,7 +324,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) @@ -296,9 +351,17 @@ 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]) + foreach (var ep in _extensionService.ExtensionPoints) { _outWriter.WriteLabelLine(" Extension Point: ", ep.Path); foreach (var node in ep.Extensions) 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/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 1e5ded4e7..e85baf5c9 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 { @@ -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); @@ -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/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/OptionsUtils/DefaultOptionsProvider.cs b/src/NUnitConsole/nunit3-console/OptionsUtils/DefaultOptionsProvider.cs deleted file mode 100644 index 70d0e6f13..000000000 --- a/src/NUnitConsole/nunit3-console/OptionsUtils/DefaultOptionsProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -namespace NUnit.Common -{ - 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/OptionsUtils/IDefaultOptionsProvider.cs b/src/NUnitConsole/nunit3-console/OptionsUtils/IDefaultOptionsProvider.cs deleted file mode 100644 index 8362735cf..000000000 --- a/src/NUnitConsole/nunit3-console/OptionsUtils/IDefaultOptionsProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -namespace NUnit.Common -{ - 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 c9c5d6d3e..7c4443583 100644 --- a/src/NUnitConsole/nunit3-console/Program.cs +++ b/src/NUnitConsole/nunit3-console/Program.cs @@ -2,19 +2,18 @@ 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; + using NUnit.Common; +using NUnit.ConsoleRunner.Options; using NUnit.Engine; -#if !NET20 -using System.Linq; -#endif - -using NUnit.Options; - namespace NUnit.ConsoleRunner { /// @@ -23,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 @@ -106,13 +105,22 @@ 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 { 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); @@ -159,9 +167,12 @@ 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 NET20 +#if NETFRAMEWORK object[] configurationAttributes = entryAssembly.GetCustomAttributes(typeof(AssemblyConfigurationAttribute), false); #else var configurationAttributes = entryAssembly.GetCustomAttributes().ToArray(); @@ -184,68 +195,116 @@ 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(); + OutWriter.WriteLine(ColorStyle.SectionHeader, "Description:"); +#if NETFRAMEWORK + 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(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(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, "Description:"); - 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(); - //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, "Operation:"); + 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(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) diff --git a/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs b/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs new file mode 100644 index 000000000..d60873d2e --- /dev/null +++ b/src/NUnitConsole/nunit3-console/RequiredExtensionException.cs @@ -0,0 +1,31 @@ +// 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 BuildMessage(string extensionName) => $"Required extension '{extensionName}' is not installed."; + + /// + /// Construct with the name of an extension + /// + public RequiredExtensionException(string extensionName) : base(BuildMessage(extensionName)) + { + } + + /// + /// Construct with the name of an extension and inner exception + /// + public RequiredExtensionException(string extensionName, Exception innerException) + : base(BuildMessage(extensionName), innerException) + { + } + } +} 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/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/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(); + } +} diff --git a/src/NUnitConsole/nunit3-console/nunit3-console.csproj b/src/NUnitConsole/nunit3-console/nunit3-console.csproj index 657ed62a3..87105df67 100644 --- a/src/NUnitConsole/nunit3-console/nunit3-console.csproj +++ b/src/NUnitConsole/nunit3-console/nunit3-console.csproj @@ -4,24 +4,40 @@ Exe NUnit.ConsoleRunner nunit3-console - net20;net6.0 + net462;net6.0;net8.0 Major + portable + 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 ..\..\..\nunit.ico + app.manifest + + + + + + PreserveNewest + 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-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 4c98c0591..0f182ae5e 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; @@ -61,7 +63,7 @@ public static void Main(string[] args) } var logName = $"nunit-agent_{pid}.log"; - InternalTrace.Initialize(Path.Combine(workDirectory, logName), traceLevel); + 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); @@ -71,12 +73,12 @@ 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}"); -#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/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 + + + diff --git a/src/NUnitEngine/nunit-agent/nunit-agent.csproj b/src/NUnitEngine/nunit-agent/nunit-agent.csproj index aeca8e54a..1c005b161 100644 --- a/src/NUnitEngine/nunit-agent/nunit-agent.csproj +++ b/src/NUnitEngine/nunit-agent/nunit-agent.csproj @@ -3,11 +3,12 @@ Exe nunit.agent - net20;net40;netcoreapp3.1;net5.0;net6.0 + net462;net8.0;net9.0;net10.0 app.manifest ..\..\..\nunit.ico false ..\..\..\bin\$(Configuration)\agents\ + false @@ -16,14 +17,20 @@ Agent used to run tests out of process - + - + + + + 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 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/IExtensionService.cs b/src/NUnitEngine/nunit.engine.api/IExtensionService.cs index 56ab7a7a8..6f2cc7f12 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 FindExtensionAssemblies(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.api/IRuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs index 0e48ebe6d..7ef5fc3dc 100644 --- a/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs @@ -16,22 +16,16 @@ 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 /// 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.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(); 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.api/TestPackage.cs b/src/NUnitEngine/nunit.engine.api/TestPackage.cs index e43cf0fed..f9b8d369f 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,87 @@ public T GetSetting(string name, T defaultSetting) ? (T)Settings[name] : defaultSetting; } + + #region IXmlSerializable Implementation + + /// + public XmlSchema GetSchema() + { + return null; + } + + /// + public void ReadXml(XmlReader xmlReader) + { + ID = xmlReader.GetAttribute("id"); + FullName = xmlReader.GetAttribute("fullname"); + if (!xmlReader.IsEmptyElement) + { + while (xmlReader.Read()) + { + switch (xmlReader.NodeType) + { + case XmlNodeType.Element: + 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()) + Settings.Add(xmlReader.Name, xmlReader.Value); + xmlReader.MoveToElement(); + break; + + case "TestPackage": + TestPackage subPackage = new TestPackage(); + subPackage.ReadXml(xmlReader); + SubPackages.Add(subPackage); + break; + } + break; + + case XmlNodeType.EndElement: + if (xmlReader.Name == "TestPackage") + return; + break; + + default: + throw new Exception("Unexpected EndElement: " + xmlReader.Name); + } + } + + throw new Exception("Invalid XML: TestPackage Element not terminated."); + } + } + + /// + public void WriteXml(XmlWriter xmlWriter) + { + // Write ID and FullName + xmlWriter.WriteAttributeString("id", ID); + if (FullName != null) + xmlWriter.WriteAttributeString("fullname", FullName); + + // Write Settings + if (Settings.Count != 0) + { + xmlWriter.WriteStartElement("Settings"); + + foreach (KeyValuePair setting in Settings) + xmlWriter.WriteAttributeString(setting.Key, setting.Value.ToString()); + + xmlWriter.WriteEndElement(); + } + + // Write any SubPackages recursively + foreach (TestPackage subPackage in SubPackages) + { + xmlWriter.WriteStartElement("TestPackage"); + subPackage.WriteXml(xmlWriter); + xmlWriter.WriteEndElement(); + } + } } + + #endregion } diff --git a/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj b/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj index 2bfa72d4f..a0bda5810 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 @@ -15,10 +15,9 @@ 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/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.tests/Communication/Messages/MessageTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs new file mode 100644 index 000000000..a32438fd0 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Communication/Messages/MessageTests.cs @@ -0,0 +1,79 @@ +// 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 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 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(code)); + Assert.That(decoded.Data, Is.EqualTo(data)); + } + + [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..6dafef390 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Communication/Protocols/BinarySerializationProtocolTests.cs @@ -0,0 +1,136 @@ +// 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(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 = new TestPackage().FromXml(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 = new TestPackage().FromXml(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])); + } + + 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/DotNetHelperTests.cs b/src/NUnitEngine/nunit.engine.core.tests/DotNetHelperTests.cs new file mode 100644 index 000000000..c3de2cbe3 --- /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", "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 + [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.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/Extensibility/ExtensionAssemblyTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs index 4a5a6121b..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() { @@ -54,7 +41,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/AddinsFileReaderTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileReaderTests.cs deleted file mode 100644 index 0c46db71e..000000000 --- a/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileReaderTests.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using NUnit.Engine.Internal.FileSystemAccess; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace NUnit.Engine.Internal.Tests -{ - /// - /// Tests the implementation of . - /// - [TestFixture] - public class AddinsFileReaderTests - { - [Test] - public void Read_IFile_Null() - { - var reader = new AddinsFileReader(); - - Assert.That(() => reader.Read((IFile)null), Throws.ArgumentNullException); - } - - [Test] - public void Read_Stream() - { - var input = string.Join(Environment.NewLine, 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" - }); - - IEnumerable result; - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(input))) - { - // Act - result = AddinsFileReader.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")); - } - - [Test] - [Platform("win")] - public void Read_Stream_TransformBackslash_Windows() - { - var input = string.Join(Environment.NewLine, new string[] - { - "c:\\windows\\absolute\\directory" - }); - - IEnumerable result; - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(input))) - { - // Act - result = AddinsFileReader.Read(stream); - } - - Assert.That(result, Has.Count.EqualTo(1)); - Assert.That(result, Contains.Item("c:/windows/absolute/directory")); - } - - [Test] - [Platform("linux,macosx,unix")] - public void Read_Stream_TransformBackslash_NonWindows() - { - IEnumerable result; - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("this/is/a\\ path\\ with\\ spaces/"))) - { - // Act - result = AddinsFileReader.Read(stream); - } - - Assert.That(result, Has.Count.EqualTo(1)); - Assert.That(result, Contains.Item("this/is/a\\ path\\ with\\ spaces/")); - } - } -} 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/AddinsFileTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs new file mode 100644 index 000000000..628ea126b --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/AddinsFileTests.cs @@ -0,0 +1,112 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using NUnit.Engine.Internal.FileSystemAccess; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace NUnit.Engine.Internal.Tests +{ + /// + /// Tests the implementation of . + /// + [TestFixture] + public class AddinsFileTests + { + [Test] + public void Read_IFile_Null() + { + Assert.That(() => AddinsFile.Read((IFile)null), Throws.ArgumentNullException); + } + + [Test] + public void Read_Stream() + { + var content = new[] + { + "# 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" + }; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, content)))) + { + var result = AddinsFile.Read(stream); + + 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] + 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() + { + 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() + { + var content = "c:\\windows\\absolute\\directory"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + { + var result = AddinsFile.Read(stream); + + 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() + { + var content = "this/is/a\\ path\\ with\\ spaces/"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + { + var result = AddinsFile.Read(stream); + + 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/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/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/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/ExtensionAssemblyTrackerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs new file mode 100644 index 000000000..eec4ad525 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/ExtensionAssemblyTrackerTests.cs @@ -0,0 +1,105 @@ +// 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; + +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(); + Console.WriteLine($"Current assembly version is {THIS_ASSEMBLY_VERSION}"); + } + + [Test] + public void AddToList() + { + _tracker.AddOrUpdate(TEST_EXTENSION_ASSEMBLY); + + Assert.That(_tracker.Count, Is.EqualTo(1)); + 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 AddUpdatesPathIndex() + { + _tracker.AddOrUpdate(TEST_EXTENSION_ASSEMBLY); + + Assert.That(_tracker.ContainsPath(THIS_ASSEMBLY_PATH)); + } + + 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)); + } + + [TestCaseSource(nameof(TestCasesAddNewerAssemblyUpdatesExistingInformation))] + public void AddNewerAssemblyUpdatesExistingInformation(Version newVersion) + { + _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)); + + 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)); + } + + 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.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)); + + 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/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 44cc4799c..485aeade8 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] @@ -139,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/Internal/TestPackageSerializationTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs new file mode 100644 index 000000000..848ef6f3a --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Internal/TestPackageSerializationTests.cs @@ -0,0 +1,93 @@ +// 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 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[] { ASSEMBLY_1, ASSEMBLY_2 }); + TEST_PACKAGE.AddSetting("foo", "bar"); + TEST_PACKAGE.SubPackages[0].AddSetting("cpu", "x86"); + + TEST_PACKAGE_XML = + $"" + + $"" + + $"" + + ""; + + TEST_PACKAGE_XML_WITH_XML_DECLARATION = "" + TEST_PACKAGE_XML; + } + + [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); + Assert.That(xml, Is.EqualTo(TEST_PACKAGE_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/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.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/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.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.core.tests/Services/ExtensionManagerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs index d0bd6a3f3..375c755da 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionManagerTests.cs @@ -8,16 +8,19 @@ using System.Reflection; using System.Collections.Generic; using NUnit.Engine.Internal; -using NSubstitute; -using NUnit.Engine.Internal.FileSystemAccess; -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", @@ -37,39 +40,64 @@ public class ExtensionManagerTests }; private static readonly int[] KnownExtensionPointCounts = { 1, 1, 1, 2, 1, 1 }; -#pragma warning restore 414 + + 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" + }; + + private ExtensionManager _extensionManager; [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); - _extensionManager.FindExtensionsInAssembly(new ExtensionAssembly(GetType().Assembly.Location, false)); + // Find Fake Extensions using alternate start directory + _extensionManager.FindExtensionAssemblies(FAKE_EXTENSIONS_SOURCE_DIRECTORY); + _extensionManager.LoadExtensions(); } [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); + Assert.That(_extensionManager.ExtensionPoints.Select(ep => ep.Path), + Is.EquivalentTo(KnownExtensionPointPaths)); + } - service.StartService(); + [Test] + public void AllKnownExtensionsAreFound() + { + Assert.That(_extensionManager.Extensions.Select(ext => ext.TypeName), + Is.EquivalentTo(KnownExtensions)); + } - fileSystem.Received().GetDirectory(workingDir); + [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 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] @@ -78,7 +106,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)); } @@ -89,30 +117,11 @@ 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)); } -#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, @@ -150,10 +159,10 @@ 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 2.1 version of the extensions from the .NET 3.5 tests - var assemblyName = Path.Combine(GetSiblingDirectory("netcoreapp2.1"), "nunit.engine.core.tests.dll"); + // 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); Console.WriteLine($"{assemblyName} does exist"); @@ -166,25 +175,43 @@ 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), + 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), + Assert.That(() => _extensionManager.CanLoadTargetFramework(combo.RunnerAssembly, combo.ExtensionAssembly), Is.False); } - [TestCaseSource(nameof(InvalidRunnerCombos))] + //[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 @@ -222,7 +249,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 } @@ -235,14 +262,14 @@ 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 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 @@ -257,7 +284,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 +297,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 @@ -281,476 +308,16 @@ private static string GetSiblingDirectory(string dir) return Path.Combine(file.Directory.Parent.FullName, dir); } - [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() + /// + /// 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) { - // 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, "**/./"); + return new ExtensionAssembly( + Path.Combine(FAKE_EXTENSIONS_PARENT_DIRECTORY, Path.Combine(tfm, FAKE_EXTENSIONS_FILENAME)), false); } } } 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..19263fe9d --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/ExtensionServiceTests.cs @@ -0,0 +1,121 @@ +// 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; +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() + { + Assembly hostAssembly = typeof(ExtensionService).Assembly; + + _extensionService.StartService(); + + _extensionManager.ReceivedWithAnyArgs().FindExtensionPoints(typeof(ExtensionService).Assembly, typeof(ITestEngine).Assembly); + _extensionManager.Received().FindExtensionAssemblies(hostAssembly); + Assert.That(_extensionService.Status, Is.EqualTo(ServiceStatus.Started)); + } + + [Test] + public void StartServiceInitializesExtensionManagerUsingAdditionalDirectories() + { + Assembly hostAssembly = typeof(ExtensionService).Assembly; + _extensionService.StartService(); + + var tempPath = Path.GetTempPath(); + _extensionService.FindExtensionAssemblies(tempPath); + + _extensionManager.Received().FindExtensionAssemblies(tempPath); + 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.tests/nunit.engine.core.tests.csproj b/src/NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj index ed35a1169..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 @@ -1,13 +1,14 @@  - NUnit.Engine.Core.Tests - net35;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0 + NUnit.Engine + net462;netcoreapp3.1;net6.0;net8.0;net9.0;net10.0 Exe true ..\..\nunit.snk Full false + false @@ -16,24 +17,27 @@ Tests of nunit.engine.core assembly - - - + + + - + + + + + + + - - - - - + + 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 3cfd3a3ed..000000000 --- a/src/NUnitEngine/nunit.engine.core/Communication/Messages/CommandMessage.cs +++ /dev/null @@ -1,20 +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) - { - CommandName = commandName; - Arguments = arguments; - } - - public string CommandName { get; } - - public object[] Arguments { get; } - } -} 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 new file mode 100644 index 000000000..3ff682711 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Communication/Messages/MessageCode.cs @@ -0,0 +1,26 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; + +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/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 16478eab1..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,15 @@ namespace NUnit.Engine.Communication.Messages { [Serializable] - public abstract class TestEngineMessage + public sealed class TestEngineMessage { + public TestEngineMessage(string code, string data) + { + Code = code; + Data = data; + } + + 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 5995853af..4a824797d 100644 --- a/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs +++ b/src/NUnitEngine/nunit.engine.core/Communication/Protocols/BinarySerializationProtocol.cs @@ -3,22 +3,29 @@ 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; 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 +41,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}"); - //Check for message length - var messageLength = serializedMessage.Length; + int dataLength = message.Data != null ? utf8.GetByteCount(message.Data) : 0; + int messageLength = dataLength + MSG_CODE_SIZE; + + //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; } @@ -86,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. /// @@ -139,7 +114,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 +122,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 +137,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 +151,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 +169,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 +193,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. 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/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 bd8f913e3..cf4ccd673 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": - SendResult(_runner.Load()); + case MessageCode.LoadCommand: + SendResult(_runner.Load().Xml.OuterXml); break; - case "Reload": - SendResult(_runner.Reload()); + case MessageCode.ReloadCommand: + SendResult(_runner.Reload().Xml.OuterXml); break; - case "Unload": + case MessageCode.UnloadCommand: _runner.Unload(); break; - case "Explore": - var filter = (TestFilter)command.Arguments[0]; - SendResult(_runner.Explore(filter)); + case MessageCode.ExploreCommand: + var filter = new TestFilter(command.Data); + SendResult(_runner.Explore(filter).Xml.OuterXml); break; - case "CountTestCases": - filter = (TestFilter)command.Arguments[0]; - SendResult(_runner.CountTestCases(filter)); + case MessageCode.CountCasesCommand: + filter = new TestFilter(command.Data); + SendResult(_runner.CountTestCases(filter).ToString()); break; - case "Run": - filter = (TestFilter)command.Arguments[0]; - SendResult(_runner.Run(this, filter)); + case MessageCode.RunCommand: + filter = new TestFilter(command.Data); + SendResult(_runner.Run(this, filter).Xml.OuterXml); 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; } @@ -120,16 +123,16 @@ 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.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/DotNetHelper.cs b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs new file mode 100644 index 000000000..571b3fc6f --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/DotNetHelper.cs @@ -0,0 +1,199 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +namespace NUnit.Engine +{ + public static class DotNet + { + 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\"; + +#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)]); + + /// + /// DotNet.RuntimeInfo holds information about a single installed runtime. + /// + public class RuntimeInfo + { + /// + /// 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; } + + /// + /// Gets the three-part version of this runtime. + /// + public Version Version { get; } + + /// + /// 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; + 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(['[', ']'])); + } + } + + /// + /// 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; + + /// + /// 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"); + + /// + /// 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); + } + + /// + /// 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) + { + bestRuntime = null; + + if (targetVersion is null) + return false; + + foreach (var candidate in availableRuntimes) + { + 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; + } + + 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); + } + + internal static IEnumerable DotnetCommand(string arguments, bool x86 = false) + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = GetDotnetExecutable(x86), + Arguments = arguments, + 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; + } + + while (!process.StandardOutput.EndOfStream) + yield return process.StandardOutput.ReadLine(); + } + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs index 6bc490cc8..8d6994587 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, new[] { domain }, null, null) as IFrameworkDriver; } } } 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 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/Drivers/NUnitNetCore31Driver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs index 3dfacd130..70525872e 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs @@ -1,12 +1,15 @@ // 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; using System.Collections.Generic; using System.IO; -using NUnit.Engine.Internal; using System.Reflection; +using System.Runtime.Loader; +using NUnit.Engine.Internal; using NUnit.Engine.Extensibility; namespace NUnit.Engine.Drivers @@ -38,7 +41,8 @@ public class NUnitNetCore31Driver : IFrameworkDriver Assembly _frameworkAssembly; object _frameworkController; Type _frameworkControllerType; - CustomAssemblyLoadContext _assemblyLoadContext; + AssemblyLoadContext _assemblyLoadContext; + TestAssemblyResolver _testAssemblyResolver; /// /// An id prefix that will be passed to the test framework and used as part of the @@ -58,17 +62,28 @@ 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); - }; try { - _testAssembly = _assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + _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); } catch (Exception e) { diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs index 275ac2aef..bc8e31a00 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs @@ -1,13 +1,13 @@ // 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; 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..d05f2c887 100644 --- a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs +++ b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs @@ -1,8 +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 Mono.Cecil; +using TestCentric.Metadata; using NUnit.Engine.Internal; namespace NUnit.Engine.Extensibility @@ -13,27 +14,27 @@ 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; + } + + // 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/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/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/Internal/AddinsFile.cs b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs new file mode 100644 index 000000000..51acc97ba --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFile.cs @@ -0,0 +1,113 @@ +// 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 + { + static readonly Logger log = InternalTrace.GetLogger(typeof(AddinsFile)); + + 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, file.FullName); + } + } + + /// + /// 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, 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)) + { + log.Warning($"Ignoring file {fullName} because it's not an NUnit .addins file"); + return addinsFile; + } + + // 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) + { + string msg = $"Invalid Entry in {fullName ?? "addins file"}:\r\n {entry}"; + throw new InvalidOperationException(msg); + } + + 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:"); + 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..3bf6470d5 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/AddinsFileEntry.cs @@ -0,0 +1,50 @@ +// 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 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; + 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/AssemblyHelper.cs b/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs index df68aadff..b402fe970 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/AssemblyHelper.cs @@ -1,8 +1,14 @@ // 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; +#if NETCOREAPP3_1_OR_GREATER +using System.Runtime.Loader; +#endif + namespace NUnit.Engine.Internal { /// @@ -69,5 +75,17 @@ 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)); + } } } 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/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/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/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/ExtensionAssemblyTracker.cs b/src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs new file mode 100644 index 000000000..0203274c2 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/ExtensionAssemblyTracker.cs @@ -0,0 +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 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 : IEnumerable + { + static readonly Logger log = InternalTrace.GetLogger(typeof(ExtensionAssemblyTracker)); + + 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) + { + 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/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/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/Internal/PathUtils.cs b/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs index b7d0c2220..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, 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,22 @@ 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); + } + + private static bool RunningOnWindows => DirectorySeparatorChar == '\\'; + + /// + /// 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 +221,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 +252,9 @@ private static bool IsValidDriveSpecifier(char c) return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); } - private static bool IsWindows() - { - return PathUtils.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/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs new file mode 100644 index 000000000..a824099a2 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -0,0 +1,265 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETCOREAPP3_1_OR_GREATER + +using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.DependencyModel.Resolution; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.Xml.Linq; +using TestCentric.Metadata; + +namespace NUnit.Engine.Internal +{ + 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; + + 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); + + _loadContext.Resolving += OnResolving; + } + + private void InitializeResolutionStrategies(AssemblyLoadContext loadContext, string testAssemblyPath) + { + // 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 tryWindowsDesktopFirst = false; + bool tryAspNetCoreFirst = false; + foreach (var reference in assemblyDef.MainModule.GetTypeReferences()) + { + string fn = reference.FullName; + if (fn.StartsWith("System.Windows.") || fn.StartsWith("PresentationFramework") || fn == "WindowsBase") + tryWindowsDesktopFirst = true; + if (fn.StartsWith("Microsoft.AspNetCore.")) + tryAspNetCoreFirst = true; + } + + // Initialize the list of ResolutionStrategies in the best order depending on + // what we learned. + ResolutionStrategies = new List(); + + if (tryWindowsDesktopFirst) + ResolutionStrategies.Add(new WindowsDesktopStrategy(false)); + if (tryAspNetCoreFirst) + ResolutionStrategies.Add(new AspNetCoreStrategy(false)); + + ResolutionStrategies.Add(new TrustedPlatformAssembliesStrategy()); + ResolutionStrategies.Add(new RuntimeLibrariesStrategy(loadContext, testAssemblyPath)); + + if (!tryWindowsDesktopFirst) + ResolutionStrategies.Add(new WindowsDesktopStrategy(false)); + if (!tryAspNetCoreFirst) + ResolutionStrategies.Add(new AspNetCoreStrategy(false)); + } + + public void Dispose() + { + _loadContext.Resolving -= OnResolving; + } + + private Assembly OnResolving(AssemblyLoadContext loadContext, AssemblyName assemblyName) + { + 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; + } + } + + public abstract class ResolutionStrategy + { + public abstract bool TryToResolve( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly); + } + + public class TrustedPlatformAssembliesStrategy : ResolutionStrategy + { + private static readonly Logger log = InternalTrace.GetLogger(typeof(TrustedPlatformAssembliesStrategy)); + + public override bool TryToResolve( + AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) + { + return TryLoadFromTrustedPlatformAssemblies(loadContext, assemblyName, out loadedAssembly); + } + + private static bool TryLoadFromTrustedPlatformAssemblies( + 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 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 = 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 static readonly Logger log = InternalTrace.GetLogger(typeof(RuntimeLibrariesStrategy)); + + private readonly DependencyContext _dependencyContext; + private readonly CompositeCompilationAssemblyResolver _assemblyResolver; + + 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) + { + 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 AdditionalRuntimesStrategy : ResolutionStrategy + { + private string _runtimeName; + private bool _x86; + + public AdditionalRuntimesStrategy(string runtimeName, bool x86) + { + _runtimeName = runtimeName; + _x86 = x86; + } + + public override bool TryToResolve(AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly) + { + loadedAssembly = null; + + if (!DotNet.FindBestRuntime(assemblyName.Version, _runtimeName, _x86, 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 = loadContext.LoadFromAssemblyPath(candidate); + return true; + } + } + + public class WindowsDesktopStrategy : AdditionalRuntimesStrategy + { + public WindowsDesktopStrategy(bool x86) : base("Microsoft.WindowsDesktop.App", x86) { } + } + + public class AspNetCoreStrategy : AdditionalRuntimesStrategy + { + public AspNetCoreStrategy(bool x86) : base("Microsoft.AspNetCore.App", x86) { } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestPackageExtensions.cs index 535561eba..ebebb3729 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,49 @@ 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(); + + using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings() { OmitXmlDeclaration = true })) + { + xmlWriter.WriteStartElement(nameof(TestPackage)); + package.WriteXml(xmlWriter); + xmlWriter.WriteEndElement(); + } + + 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 == nameof(TestPackage); + 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..8108abfda --- /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 +{ + public static class OS + { + public static bool IsWindows { get; } = Path.DirectorySeparatorChar == '\\'; + } +} 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..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; @@ -209,10 +210,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/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/Runners/NotRunnableTestRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs new file mode 100644 index 000000000..3e4a12502 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs @@ -0,0 +1,147 @@ +// 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) + { + 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; } + + 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(); + } + + /// + /// 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) + { + 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") + { + _runstate = "NotRunnable"; + _result = "Failed"; + _label = "Invalid"; + } + } +} diff --git a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs index 33fc7116b..508249bfb 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"); @@ -201,8 +201,13 @@ 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); + 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"); @@ -309,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. @@ -345,31 +336,13 @@ 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"; } } - /// - /// 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 @@ -543,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.core/Services/DriverService.cs b/src/NUnitEngine/nunit.engine.core/Services/DriverService.cs index 8472e6fd2..13764b8bf 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; @@ -20,7 +20,8 @@ public class DriverService : Service, IDriverService { static ILogger log = InternalTrace.GetLogger("DriverService"); - readonly IList _factories = new List(); + private ExtensionService _extensionService; + private List _factories; /// /// Get a driver suitable for use with a particular test assembly. @@ -48,9 +49,16 @@ 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"); } + if (_factories == null) + InitializeDriverFactories(); + try { using (var assemblyDef = AssemblyDefinition.ReadAssembly(assemblyPath)) @@ -68,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) { @@ -79,6 +88,8 @@ public IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, string return factory.GetDriver(reference); #endif } + + log.Debug($"No driver found using {factoryName}"); } } } @@ -90,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() @@ -100,20 +111,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; } @@ -123,5 +121,25 @@ public override void StartService() throw; } } + + private void InitializeDriverFactories() + { + _factories = new List(); + + if (_extensionService == null) + log.Debug("ExtensionService is not available, no driver extensions will be loaded"); + else + { + _factories.AddRange(_extensionService.GetExtensions()); + +#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.core/Services/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs index 70f1546d9..db1eea36d 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionManager.cs @@ -2,65 +2,62 @@ using System; using System.Collections.Generic; +using System.IO; using System.Reflection; -using Mono.Cecil; +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 -using Path = NUnit.Engine.Internal.Backports.Path; -#else -using Path = System.IO.Path; -#endif +using System.Linq; namespace NUnit.Engine.Services { - public sealed class ExtensionManager : IDisposable + public class ExtensionManager { - static readonly Logger log = InternalTrace.GetLogger(typeof(ExtensionService)); - static readonly Version ENGINE_VERSION = typeof(ExtensionService).Assembly.GetName().Version; + static readonly Version CURRENT_ENGINE_VERSION = Assembly.GetExecutingAssembly().GetName().Version; + + 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 private readonly List _extensionPoints = new List(); - private readonly Dictionary _pathIndex = new Dictionary(); - private readonly List _extensions = new List(); - private readonly List _assemblies = new List(); + // Index to ExtensionPoints based on the Path + private readonly Dictionary _extensionPointIndex = new Dictionary(); + + // List of ExtensionNodes for all extensions discovered. + private List _extensions = new List(); + + private bool _extensionsAreLoaded; + + // 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 FindExtensionAssemblies. + private readonly List _extensionDirectories = 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; } - 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 Public Properties and Methods /// /// Gets an enumeration of all ExtensionPoints in the engine. @@ -75,139 +72,235 @@ public IEnumerable ExtensionPoints /// public IEnumerable Extensions { - get { return _extensions.ToArray(); } + get + { + LoadExtensions(); + + return _extensions.ToArray(); + } } /// - /// Enable or disable an extension + /// Find the extension points in a loaded assembly. /// - public void EnableExtension(string typeName, bool enabled) + public virtual void FindExtensionPoints(params Assembly[] targetAssemblies) { - foreach (var node in _extensions) - if (node.TypeName == typeName) - node.Enabled = enabled; + foreach (var assembly in targetAssemblies) + { + log.Info("FindExtensionPoints scanning {0} assembly", assembly.GetName().Name); + + foreach (ExtensionPointAttribute attr in assembly.GetCustomAttributes(typeof(ExtensionPointAttribute), false)) + { + if (_extensionPointIndex.ContainsKey(attr.Path)) + { + string msg = string.Format( + "The Path {0} is already in use for another extension point.", + attr.Path); + throw new NUnitEngineException(msg); + } + + var ep = new ExtensionPoint(attr.Path, attr.Type) + { + Description = attr.Description, + }; + + _extensionPoints.Add(ep); + _extensionPointIndex.Add(ep.Path, ep); + + log.Info(" Found ExtensionPoint: Path={0}, Type={1}", ep.Path, ep.TypeName); + } + + foreach (Type type in assembly.GetExportedTypes()) + { + foreach (TypeExtensionPointAttribute attr in type.GetCustomAttributes(typeof(TypeExtensionPointAttribute), false)) + { + string path = attr.Path ?? "/NUnit/Engine/TypeExtensions/" + type.Name; + + if (_extensionPointIndex.ContainsKey(path)) + { + string msg = string.Format( + "The Path {0} is already in use for another extension point.", + attr.Path); + throw new NUnitEngineException(msg); + } + + var ep = new ExtensionPoint(path, type) + { + Description = attr.Description, + }; + + _extensionPoints.Add(ep); + _extensionPointIndex.Add(path, ep); + + log.Info(" Found ExtensionPoint: Path={0}, Type={1}", ep.Path, ep.TypeName); + } + } + } } /// - /// Get an ExtensionPoint based on its unique identifying path. + /// Find extension assemblies starting from a given base directory, + /// and using the contained '.addins' files to direct the search. /// - public ExtensionPoint GetExtensionPoint(string path) + /// Path to the initial directory. + public void FindExtensionAssemblies(string startDir) { - return _pathIndex.ContainsKey(path) ? _pathIndex[path] : null; + // Ignore a call for a directory we have already used + if (!_extensionDirectories.Contains(startDir)) + { + _extensionDirectories.Add(startDir); + + log.Info($"FindExtensionAssemblies examining extension directory {startDir}"); + + ProcessDirectory(_fileSystem.GetDirectory(startDir), false); + } } /// - /// Get an ExtensionPoint based on the required Type for extensions. + /// Find ExtensionAssemblies for a host assembly using + /// a built-in algorithm that searches in certain known locations. /// - public ExtensionPoint GetExtensionPoint(Type type) + /// An assembly that supports NUnit extensions. + public void FindExtensionAssemblies(Assembly hostAssembly) { - foreach (var ep in _extensionPoints) - if (ep.TypeName == type.FullName) - return ep; + log.Info($"FindExtensionAssemblies called for host {hostAssembly.FullName}"); - return null; + 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/*/" }; + + + IDirectory startDir = _fileSystem.GetDirectory(AssemblyHelper.GetDirectoryName(hostAssembly)); + + while (startDir != null) + { + foreach (var pattern in extensionPatterns) + foreach (var dir in _directoryFinder.GetDirectories(startDir, pattern)) + ProcessDirectory(dir, true); + + startDir = startDir.Parent; + } } /// - /// Get an ExtensionPoint based on a Cecil TypeReference. + /// Get an ExtensionPoint based on its unique identifying path. /// - public ExtensionPoint GetExtensionPoint(TypeReference type) + public IExtensionPoint GetExtensionPoint(string path) { - foreach (var ep in _extensionPoints) - if (ep.TypeName == type.FullName) - return ep; + return _extensionPointIndex.TryGetValue(path, out ExtensionPoint ep) ? ep : null; + } - return null; + /// + /// Get extension objects for all nodes of a given type + /// + public IEnumerable GetExtensions() + { + foreach (var node in GetExtensionNodes()) + yield return (T)node.ExtensionObject; } - public IEnumerable GetExtensionNodes(string path) + /// + /// Get all ExtensionNodes for a path + /// + public IEnumerable GetExtensionNodes(string path) { + LoadExtensions(); + var ep = GetExtensionPoint(path); if (ep != null) foreach (var node in ep.Extensions) yield return node; } - public ExtensionNode GetExtensionNode(string path) + /// + /// Get the first or only ExtensionNode for a given ExtensionPoint + /// + /// The identifying path for an ExtensionPoint + /// + public IExtensionNode GetExtensionNode(string path) { - var ep = GetExtensionPoint(path); + LoadExtensions(); + + // 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) { + LoadExtensions(); + 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; } - public IEnumerable GetExtensions() + /// + /// Enable or disable an extension + /// + public void EnableExtension(string typeName, bool enabled) { - foreach (var node in GetExtensionNodes()) - yield return (T)node.ExtensionObject; + LoadExtensions(); + + foreach (var node in _extensions) + if (node.TypeName == typeName) + node.Enabled = enabled; } /// - /// Find the extension points in a loaded assembly. + /// 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 FindExtensionPoints(params Assembly[] targetAssemblies) + public void LoadExtensions() { - foreach (var assembly in targetAssemblies) + if (!_extensionsAreLoaded) { - log.Info("Scanning {0} assembly for extension points", assembly.GetName().Name); - - foreach (ExtensionPointAttribute attr in assembly.GetCustomAttributes(typeof(ExtensionPointAttribute), false)) - { - if (_pathIndex.ContainsKey(attr.Path)) - { - string msg = string.Format( - "The Path {0} is already in use for another extension point.", - attr.Path); - throw new NUnitEngineException(msg); - } - - var ep = new ExtensionPoint(attr.Path, attr.Type) - { - Description = attr.Description, - }; - - _extensionPoints.Add(ep); - _pathIndex.Add(ep.Path, ep); - - log.Info(" Found Path={0}, Type={1}", ep.Path, ep.TypeName); - } + _extensionsAreLoaded = true; - foreach (Type type in assembly.GetExportedTypes()) - { - foreach (TypeExtensionPointAttribute attr in type.GetCustomAttributes(typeof(TypeExtensionPointAttribute), false)) - { - string path = attr.Path ?? "/NUnit/Engine/TypeExtensions/" + type.Name; + foreach (var candidate in _assemblies) + FindExtensionsInAssembly(candidate); + } + } - if (_pathIndex.ContainsKey(path)) - { - string msg = string.Format( - "The Path {0} is already in use for another extension point.", - attr.Path); - throw new NUnitEngineException(msg); - } + /// + /// 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; - var ep = new ExtensionPoint(path, type) - { - Description = attr.Description, - }; + return null; + } - _extensionPoints.Add(ep); - _pathIndex.Add(path, ep); + /// + /// 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; - log.Info(" Found Path={0}, Type={1}", ep.Path, ep.TypeName); - } - } - } + 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 @@ -234,17 +327,6 @@ private ExtensionPoint DeduceExtensionPointFromType(TypeReference typeRef) : null; } - /// - /// Find candidate extension assemblies starting from a - /// given base directory. - /// - /// - private void FindExtensionAssemblies(IDirectory startDir) - { - // First check the directory itself - ProcessAddinsFiles(startDir, false); - } - /// /// 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 @@ -253,23 +335,18 @@ private void FindExtensionAssemblies(IDirectory startDir) 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."); + 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); } /// @@ -280,14 +357,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(file, fromWildCard); + addinsFileCount++; } + return addinsFileCount; } @@ -297,78 +372,67 @@ 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 _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); - if (entry.EndsWith("/")) + bool isWild = fromWildCard || entry.IsPattern; + IDirectory baseDir = addinsFile.Parent; + string entryDir = entry.DirectoryName; + string entryFile = entry.FileName; + + log.Debug($"Processing entry {entry.Text}"); + 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 Backports.Tuple GetBaseDirAndPattern(IDirectory baseDir, string path) - { - if (Path.IsPathFullyQualified(path)) - { - if (path.EndsWith("/")) - { - return new Backports.Tuple(_fileSystem.GetDirectory(path), string.Empty); - } - else - { - return new Backports.Tuple(_fileSystem.GetDirectory(System.IO.Path.GetDirectoryName(path)), System.IO.Path.GetFileName(path)); - } - } - else - { - return new Backports.Tuple(baseDir, path); - } - } - private void ProcessCandidateAssembly(string filePath, bool fromWildCard) { - if (WasVisited(filePath, fromWildCard)) - return; + log.Debug($"Processing candidate assembly {filePath}"); - MarkAsVisited(filePath, fromWildCard); + // Did we already process this file? + if (_assemblies.ContainsPath(filePath)) + { + log.Debug(" Skipping assembly already processed"); + return; + } try { - var candidate = new ExtensionAssembly(filePath, fromWildCard); - - if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), candidate)) - return; + var candidateAssembly = new ExtensionAssembly(filePath, fromWildCard); - for (var i = 0; i < _assemblies.Count; i++) + // We never add assemblies unless the host can load them + if (!CanLoadTargetFramework(Assembly.GetEntryAssembly(), candidateAssembly)) { - var assembly = _assemblies[i]; - - if (candidate.IsDuplicateOf(assembly)) - { - if (candidate.IsBetterVersionOf(assembly)) - _assemblies[i] = candidate; - - return; - } + log.Debug(" Unable to load this assembly"); + return; } - _assemblies.Add(candidate); + _assemblies.AddOrUpdate(candidateAssembly); } catch (BadImageFormatException e) { @@ -382,16 +446,17 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) } } + // Dictionary containing all directory paths already visited. private readonly Dictionary _visited = new Dictionary(); - private bool WasVisited(string filePath, bool fromWildcard) + private bool WasVisited(string path, bool fromWildcard) { - return _visited.ContainsKey($"path={ filePath }_visited={fromWildcard}"); + return _visited.ContainsKey($"path={path}_visited={fromWildcard}"); } - private void MarkAsVisited(string filePath, bool fromWildcard) + private void Visit(string path, bool fromWildcard) { - _visited.Add($"path={ filePath }_visited={fromWildcard}", null); + _visited.Add($"path={path}_visited={fromWildcard}", null); } /// @@ -399,46 +464,55 @@ private void MarkAsVisited(string filePath, 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 type in assembly.MainModule.GetTypes()) + foreach (var extensionType in extensionAssembly.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) + { + if (new Version(versionArg) > CURRENT_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(extensionAssembly.FilePath, extensionAssembly.AssemblyVersion, extensionType.FullName, assemblyTargetFramework) { Path = extensionAttr.GetNamedArgument("Path") as string, Description = extensionAttr.GetNamedArgument("Description") as string @@ -447,9 +521,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; @@ -466,12 +540,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); } @@ -479,12 +553,13 @@ 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( "Unable to locate ExtensionPoint for Type {0}. The Path {1} cannot be found.", - type.FullName, + extensionType.FullName, node.Path); throw new NUnitEngineException(msg); } @@ -494,46 +569,76 @@ 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) + + } + + 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() { // Make sure all assemblies release the underlying file streams. - foreach (var assembly in _assemblies) + foreach (var candidate in _assemblies) { - assembly.Dispose(); + candidate.Dispose(); } } } diff --git a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs index b4aa6ee62..d4f55929a 100644 --- a/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine.core/Services/ExtensionService.cs @@ -1,21 +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 Mono.Cecil; 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 -using Path = NUnit.Engine.Internal.Backports.Path; -#else -using Path = System.IO.Path; -#endif +using System.Collections.Generic; +using System.Reflection; namespace NUnit.Engine.Services { @@ -33,59 +22,72 @@ public ExtensionService() _extensionManager = new ExtensionManager(); } - internal ExtensionService(IAddinsFileReader addinsReader, IFileSystem fileSystem) - : this(addinsReader, fileSystem, new DirectoryFinder(fileSystem)) + public ExtensionService(ExtensionManager extensionManager) + { + _extensionManager = extensionManager; + } + + 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); } + #region IExtensionService Implementation + + /// public IEnumerable ExtensionPoints => _extensionManager.ExtensionPoints; + /// public IEnumerable Extensions => _extensionManager.Extensions; - /// - /// Get an ExtensionPoint based on its unique identifying path. - /// + /// + public void FindExtensionAssemblies(string initialDirectory) + { + _extensionManager.FindExtensionAssemblies(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 ExtensionNode GetExtensionNode(string path) => _extensionManager.GetExtensionNode(path); + public IExtensionNode GetExtensionNode(string path) => _extensionManager.GetExtensionNode(path); public IEnumerable GetExtensionNodes() => _extensionManager.GetExtensionNodes(); public override void StartService() { + Assembly thisAssembly = Assembly.GetExecutingAssembly(); + Assembly apiAssembly = typeof(ITestEngine).Assembly; + try { - _extensionManager.FindExtensionPoints( - Assembly.GetExecutingAssembly(), - typeof(ITestEngine).Assembly); - - var thisAssembly = Assembly.GetExecutingAssembly(); - _extensionManager.FindExtensions(AssemblyHelper.GetDirectoryName(thisAssembly)); + _extensionManager.FindExtensionPoints(thisAssembly, apiAssembly); + _extensionManager.FindExtensionAssemblies(thisAssembly); Status = ServiceStatus.Started; } diff --git a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj index b6316e459..3c78b8f34 100644 --- a/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj +++ b/src/NUnitEngine/nunit.engine.core/nunit.engine.core.csproj @@ -1,13 +1,14 @@  - + NUnit.Engine - net20;netstandard2.0;netcoreapp3.1;net5.0;net6.0 + net462;netcoreapp3.1;net6.0;net8.0;net9.0;net10.0 $(NoWarn);SYSLIB0011;SYSLIB0012 true ..\..\nunit.snk portable true + true @@ -16,17 +17,29 @@ Common code used by both the engine and agents - + - - - + + + - + + + + + + + + + + + + + - + 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/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/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 b5b4ed323..33e7ee8a5 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) @@ -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/Fakes/FakeRuntimeService.cs b/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs index 9b181e991..deb8850cc 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs @@ -4,14 +4,13 @@ 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; } - string IRuntimeFrameworkService.SelectRuntimeFramework(TestPackage package) + void IRuntimeFrameworkService.SelectRuntimeFramework(TestPackage package) { - return string.Empty; } } } 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..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")] @@ -41,7 +42,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/Services/RuntimeFrameworkServiceTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs index 659c378af..c86814212 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs @@ -2,13 +2,10 @@ #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 +namespace NUnit.Engine.Services { public class RuntimeFrameworkServiceTests { @@ -35,16 +32,17 @@ public void ServiceIsStarted() Assert.That(_runtimeService.Status, Is.EqualTo(ServiceStatus.Started)); } - [TestCase("mock-assembly.dll", false)] - [TestCase("../agents/net20/nunit-agent.exe", false)] - [TestCase("../agents/net20/nunit-agent-x86.exe", true)] - 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)); } @@ -57,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) { @@ -76,8 +74,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.tests/Services/TestSelectionParserTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs index a067bc972..e86a7a29a 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TestSelectionParserTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +using NUnit.Framework; using System; +using System.Collections; using System.Collections.Generic; -using System.Text; using System.Xml; -using NUnit.Framework; namespace NUnit.Engine.Tests { @@ -18,46 +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("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))] @@ -66,5 +39,94 @@ 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")"), + 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"), + // 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.tests/Services/TokenizerTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TokenizerTests.cs index 29c826a76..babc0dd5f 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 @@ -45,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() { @@ -75,6 +106,16 @@ 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.String, "Issue1510.TestSomething(Option1,\"ABC\")"))); + Assert.That(tokenizer.NextToken(), Is.EqualTo(new Token(TokenKind.Eof))); + } + [Test] public void StringsMayContainEscapedQuoteChar() { 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..9fbcc825e --- /dev/null +++ b/src/NUnitEngine/nunit.engine.tests/Transport/Tcp/TcpServerTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +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.That(client.Connected, "Client is not connected to server"); + } + } + + [Test, Platform(Exclude = "Linux")] + 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.That(clients[i].Connected, $"Client {i+1} is not connected to server"); + } + } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj index e586bc682..08b96fc58 100644 --- a/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj +++ b/src/NUnitEngine/nunit.engine.tests/nunit.engine.tests.csproj @@ -2,12 +2,13 @@ NUnit.Engine.Tests - net35;netcoreapp2.1 + net462;net8.0 Exe true ..\..\nunit.snk Full false + false @@ -17,23 +18,19 @@ - - - + + + - - + + - - - - - + diff --git a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TcpServer.cs index 359704296..cc72a6635 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 = "TcpListenerTread", + 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) { diff --git a/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs b/src/NUnitEngine/nunit.engine/Communication/Transports/Tcp/TestAgentTcpProxy.cs index efb1862d8..efdac42d9 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,76 +35,75 @@ public ITestEngineRunner CreateRunner(TestPackage package) public bool Start() { - SendCommandMessage("Start"); - return CommandResult(); + SendCommandMessage(MessageCode.StartAgent); + return bool.Parse(CommandResult()); } public void Stop() { - SendCommandMessage("Stop"); + SendCommandMessage(MessageCode.StopAgent); } public TestEngineResult Load() { - SendCommandMessage("Load"); - return CommandResult(); + SendCommandMessage(MessageCode.LoadCommand); + return new TestEngineResult(CommandResult()); } public void Unload() { - SendCommandMessage("Unload"); + SendCommandMessage(MessageCode.UnloadCommand); } public TestEngineResult Reload() { - SendCommandMessage("Reload"); - return CommandResult(); + SendCommandMessage(MessageCode.ReloadCommand); + return new TestEngineResult(CommandResult()); } public int CountTestCases(TestFilter filter) { - SendCommandMessage("CountTestCases", filter); - return CommandResult(); + SendCommandMessage(MessageCode.CountCasesCommand, filter.Text); + return int.Parse(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(); + //return CommandResult(); + 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) { - SendCommandMessage("Explore", filter); - return CommandResult(); + SendCommandMessage(MessageCode.ExploreCommand, filter.Text); + return new TestEngineResult(CommandResult()); } 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() + 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,17 +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; - var progressMessage = receivedMessage as ProgressMessage; - if (progressMessage == null) - throw new InvalidOperationException($"Expected either a ProgressMessage or a CommandReturnMessage but received a {receivedType}"); + if (receivedMessage.Code == MessageCode.CommandResult) + return new TestEngineResult(receivedMessage.Data); - listener.OnTestEvent(progressMessage.Report); + if (receivedMessage.Code == MessageCode.ProgressReport) + listener.OnTestEvent(receivedMessage.Data); + else + throw new InvalidOperationException($"Expected either a ProgressMessage or a CommandReturnMessage but received a {receivedMessage.Code}"); } } } diff --git a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs index 41734a93d..2bcbd27c8 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 @@ -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 @@ -331,14 +333,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) @@ -376,8 +374,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/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)); } } diff --git a/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs b/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs index 5bd7f6830..ce623a191 100644 --- a/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs +++ b/src/NUnitEngine/nunit.engine/Runners/TestEventDispatcher.cs @@ -21,13 +21,21 @@ 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); } } +#if NET6_0 || NET8_0 + [Obsolete] +#endif public override object InitializeLifetimeService() { return null; 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 diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 6411d810e..3e206d1f5 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.Arguments = $"{AgentExePath} {AgentArgs}"; + 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 != '\\') + if (!OS.IsWindows) 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"; - 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 @@ -105,7 +101,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,12 +118,12 @@ 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; case RuntimeType.NetCore: - runtimeDir = major >= 6 ? "net6.0" : major == 5 ? "net5.0" : "netcoreapp3.1"; + runtimeDir = major >= 10 ? "net10.0" : major >= 9 ? "net9.0" : "net8.0"; agentName = "nunit-agent"; agentExtension = ".dll"; break; diff --git a/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs b/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs index 5fdb79de4..2aa52f02b 100644 --- a/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs +++ b/src/NUnitEngine/nunit.engine/Services/DefaultTestRunnerFactory.cs @@ -42,6 +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/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)) 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 bd5cecf99..ae42a4680 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -4,12 +4,11 @@ using System; using System.Collections.Generic; using System.IO; -using Mono.Cecil; +using System.Runtime.Versioning; using NUnit.Common; using NUnit.Engine.Internal; -#if NET20 -using FrameworkName = NUnit.Engine.Compatibility.FrameworkName; -#endif +using NUnit.Engine.Services.RuntimeLocators; +using TestCentric.Metadata; namespace NUnit.Engine.Services { @@ -17,33 +16,35 @@ 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(); + private List _availableX86Runtimes = new List(); /// /// Gets a list of available runtimes. /// - public IList AvailableRuntimes - { - get { return _availableRuntimes; } - } + 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 RuntimeFramework.AvailableFrameworks) + var runtimes = x86 ? _availableX86Runtimes : _availableRuntimes; + foreach (var framework in runtimes) if (FrameworksMatch(requestedFramework, framework)) return true; @@ -91,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) { @@ -112,6 +112,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) { @@ -120,23 +121,21 @@ 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; - - 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)) { - // Assume .NET Framework + // Assume .NET Framework 2.0 targetRuntime = currentFramework.Runtime; targetVersion = package.GetSetting(InternalEnginePackageSettings.ImageRuntimeVersion, new Version(2, 0)); } @@ -148,31 +147,56 @@ 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; + case "Unmanaged": break; default: throw new NUnitEngineException("Unsupported Target Framework: " + imageTargetFrameworkNameSetting); } - - targetVersion = frameworkName.Version; } - if (!new RuntimeFramework(targetRuntime, targetVersion).IsAvailable) + // 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() + { + try + { + FindAvailableRuntimes(); + } + catch + { + Status = ServiceStatus.Error; + throw; + } + + Status = ServiceStatus.Started; + } /// /// Returns the best available framework that matches a target framework. @@ -194,8 +218,28 @@ public RuntimeFramework GetBestAvailableFramework(RuntimeFramework target) return result; } + 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); + } + + var monoRuntimes = MonoRuntimeLocator.FindRuntimes(); + _availableRuntimes.AddRange(monoRuntimes); + _availableX86Runtimes.AddRange(monoRuntimes); + + _availableRuntimes.AddRange(NetCoreRuntimeLocator.FindRuntimes(x86: false)); + _availableX86Runtimes.AddRange(NetCoreRuntimeLocator.FindRuntimes(x86: true)); + } + /// - /// 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. /// /// @@ -243,26 +287,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()) - { - requiresX86 = true; - log.Debug($"Assembly {packageName} will be run x86"); - } - - if (assembly.HasAttribute("NUnit.Framework.TestAssemblyDirectoryResolveAttribute")) + using (var assembly = AssemblyDefinition.ReadAssembly(packageName)) { - 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/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..45e188197 --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs @@ -0,0 +1,27 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using System.IO; + +namespace NUnit.Engine.Services.RuntimeLocators +{ + public static class NetCoreRuntimeLocator + { + public static IEnumerable FindRuntimes(bool x86) + { + List alreadyFound = new List(); + + foreach (var runtime in DotNet.GetRuntimes("Microsoft.NETCore.App", x86)) + { + if (!alreadyFound.Contains(runtime.Version)) + { + alreadyFound.Add(runtime.Version); + yield return new RuntimeFramework(RuntimeType.NetCore, runtime.Version); + } + } + } + } +} +#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..0cfe65ede 100644 --- a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs +++ b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs @@ -55,14 +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:"; - foreach (var runtime in RuntimeFramework.AvailableFrameworks) + string msg = $"The {targetRuntime} framework is not available for X86={runAsX86}.\r\nAvailable frameworks:"; + // HACK + var service = _runtimeService as RuntimeFrameworkService; + var availableRuntimes = runAsX86 ? service.AvailableX86Runtimes : service.AvailableRuntimes; + foreach (var runtime in availableRuntimes) msg += $" {runtime}"; throw new ArgumentException(msg); } diff --git a/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs b/src/NUnitEngine/nunit.engine/Services/TestSelectionParser.cs index ef3fb4cf8..181315831 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,20 +118,31 @@ 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 = op == MATCH_OP || op == NOMATCH_OP + ? Expect(TokenKind.String, TokenKind.Word) + : GetTestName(); + return EmitFilterElement(lhs, op, rhs); + case "cat": case "method": case "class": case "name": - case "test": case "namespace": - Token op = lhs.Text == "id" - ? Expect(EQ_OPS) - : Expect(REL_OPS); - Token rhs = Expect(TokenKind.String, TokenKind.Word); + case "partition": + 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: @@ -141,6 +154,55 @@ 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) + { + var inQuotes = false; + var inEscape = false; + foreach (var ch in result.Text) + { + if (ch == ' ' && !inQuotes) + continue; + sb.Append(ch); + inQuotes = (!inQuotes && ch == '"') || (inQuotes && ch != '"') || (inQuotes && ch == '"' && inEscape); + inEscape = inQuotes && !inEscape && ch == '\\'; + } + } + 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 }; diff --git a/src/NUnitEngine/nunit.engine/nunit.engine.addins b/src/NUnitEngine/nunit.engine/nunit.engine.addins deleted file mode 100644 index be4c6fa29..000000000 --- a/src/NUnitEngine/nunit.engine/nunit.engine.addins +++ /dev/null @@ -1,5 +0,0 @@ -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 diff --git a/src/NUnitEngine/nunit.engine/nunit.engine.csproj b/src/NUnitEngine/nunit.engine/nunit.engine.csproj index e05330dd6..c9dd0007f 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;net8.0 true ..\..\nunit.snk portable @@ -15,7 +15,7 @@ Provides for loading, exploring and running NUnit tests - + @@ -30,10 +30,4 @@ - - - PreserveNewest - - - \ No newline at end of file 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\ + + + + + + + + + + + diff --git a/src/NUnitEngine/nunit.engine.core.tests/DummyExtensions.cs b/src/TestData/FakeExtensions/1.0/FakeExtensions.cs similarity index 100% rename from src/NUnitEngine/nunit.engine.core.tests/DummyExtensions.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/fake-extensions.addins b/src/TestData/FakeExtensions/fake-extensions.addins new file mode 100644 index 000000000..4aeaaf80a --- /dev/null +++ b/src/TestData/FakeExtensions/fake-extensions.addins @@ -0,0 +1,4 @@ +# This file isn't copied anywhere but is used by one of our +# package tests to exercise the --extensionDirectory option +../../../bin/Release/fakesV1/**/FakeExtensions.dll +../../../bin/Release/fakesV2/**/FakeExtensions.dll 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..7ac8e0a38 --- /dev/null +++ b/src/TestData/InvalidTestNames/InvalidTestNames.csproj @@ -0,0 +1,13 @@ + + + + net462;net6.0;net8.0 + ..\..\..\bin\$(Configuration)\testdata\ + Library + + + + + + + diff --git a/src/TestData/TestData.sln b/src/TestData/TestData.sln new file mode 100644 index 000000000..ac4378dac --- /dev/null +++ b/src/TestData/TestData.sln @@ -0,0 +1,37 @@ + +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\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 +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 + 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 + {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 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1BD3B7CF-501E-4806-870A-328B37C21752} + EndGlobalSection +EndGlobal 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..dd182afd4 --- /dev/null +++ b/src/TestData/WpfApp/WpfApp.csproj @@ -0,0 +1,10 @@ + + + + WinExe + net8.0-windows + ..\..\..\bin\$(Configuration)\testdata\ + true + + + diff --git a/src/TestData/aspnetcore-test/AspNetCoreTest.cs b/src/TestData/aspnetcore-test/AspNetCoreTest.cs new file mode 100644 index 000000000..65cfd1422 --- /dev/null +++ b/src/TestData/aspnetcore-test/AspNetCoreTest.cs @@ -0,0 +1,32 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System.Reflection; +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(); + } + + [Test] + public void LoadAspNetCore() + { + Assembly.Load("Microsoft.AspNetCore, Version=8.0.0.0, Culture=Neutral, PublicKeyToken=adb9793829ddae60"); + } + } +} \ 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..c6d0f546f --- /dev/null +++ b/src/TestData/aspnetcore-test/aspnetcore-test.csproj @@ -0,0 +1,14 @@ + + + + net6.0;net8.0 + ..\..\..\bin\$(Configuration)\testdata\ + Library + false + + + + + + + diff --git a/src/TestData/mock-assembly-v2/MockAssembly.cs b/src/TestData/mock-assembly-v2/MockAssembly.cs new file mode 100644 index 000000000..bf764e8d3 --- /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 NUnit.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 diff --git a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj similarity index 63% rename from src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj rename to src/TestData/mock-assembly-x86/mock-assembly-x86.csproj index 9d83868e6..383539b63 100644 --- a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/TestData/mock-assembly-x86/mock-assembly-x86.csproj @@ -2,11 +2,13 @@ NUnit.Tests - net35;net40;netcoreapp2.1;netcoreapp3.1 + net462;netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + ..\..\..\bin\$(Configuration)\testdata\ true ..\..\nunit.snk x86 false + false @@ -15,9 +17,14 @@ Assembly used in testing the engine - - + + + + + + + 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 60% rename from src/NUnitEngine/mock-assembly/mock-assembly.csproj rename to src/TestData/mock-assembly/mock-assembly.csproj index f60671084..d454c082d 100644 --- a/src/NUnitEngine/mock-assembly/mock-assembly.csproj +++ b/src/TestData/mock-assembly/mock-assembly.csproj @@ -2,10 +2,12 @@ NUnit.Tests - net35;net40;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0 + net462;netcoreapp3.1;net6.0;net7.0;net8.0;net9.0;net10.0 + ..\..\..\bin\$(Configuration)\testdata\ true ..\..\nunit.snk false + false @@ -14,8 +16,12 @@ Assembly used in testing the engine - - + + + + + + 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/TestData/native-assembly/NativeTests.dll b/src/TestData/native-assembly/NativeTests.dll new file mode 100644 index 000000000..079799a77 Binary files /dev/null and b/src/TestData/native-assembly/NativeTests.dll differ 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 51% rename from src/NUnitEngine/notest-assembly/notest-assembly.csproj rename to src/TestData/notest-assembly/notest-assembly.csproj index 8721a24dd..76194de1c 100644 --- a/src/NUnitEngine/notest-assembly/notest-assembly.csproj +++ b/src/TestData/notest-assembly/notest-assembly.csproj @@ -2,8 +2,10 @@ notest_assembly - net35;netcoreapp2.1;netcoreapp3.1 + net462;netstandard2.0;netcoreapp3.1 + ..\..\..\bin\$(Configuration)\testdata\ false + false @@ -12,8 +14,12 @@ Assembly with NonTestAssembly attribute used in testing the engine - - + + + + + + \ No newline at end of file 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..5e16e9243 --- /dev/null +++ b/src/TestData/windows-forms-test/windows-forms-test.csproj @@ -0,0 +1,14 @@ + + + + net6.0-windows;net8.0-windows + ..\..\..\bin\$(Configuration)\testdata\ + true + false + + + + + + + diff --git a/src/TestData/wpf-test/WpfTest.cs b/src/TestData/wpf-test/WpfTest.cs new file mode 100644 index 000000000..64624c647 --- /dev/null +++ b/src/TestData/wpf-test/WpfTest.cs @@ -0,0 +1,32 @@ +// 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; + +// Test which resolves issue #1203 +namespace Test1 +{ + [TestFixture] + public class WPFTest : IWeakEventListener + { + [Test] + public void AssertPass() + { + Assert.Pass(); + } + + [Test, Apartment(System.Threading.ApartmentState.STA)] + public void CreateCheckBox() + { + CheckBox checkbox; + checkbox = new CheckBox(); + } + + public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + throw new NotImplementedException(); + } + } +} \ 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..9df05e4b0 --- /dev/null +++ b/src/TestData/wpf-test/WpfTest.csproj @@ -0,0 +1,13 @@ + + + + net6.0-windows;net8.0-windows + ..\..\..\bin\$(Configuration)\testdata\ + true + + + + + + + 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