From afc6962021c22e505545c17ebacd00bc51c7dcc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 25 Feb 2021 17:58:22 +0000 Subject: [PATCH] Add initial implementation of Unit Test framework (#2) Kudos to @Ellerbach who re-started this project and pulled it through! --- LICENSE.md | 21 + NuGet.Config | 7 + README.md | 80 +- assets/nf-logo.png | Bin 0 -> 4129 bytes assets/test-discovered.jpg | Bin 0 -> 57944 bytes assets/test-integration-vs-failed.jpg | Bin 0 -> 12189 bytes assets/test-integration-vs.jpg | Bin 0 -> 74325 bytes assets/test-nuget-test-framework.jpg | Bin 0 -> 41512 bytes assets/test-success.jpg | Bin 0 -> 60825 bytes azure-pipelines.yml | 321 +++ azure-pipelines/update-dependencies.ps1 | 118 + config/SignClient.json | 14 + config/filelist.txt | 1 + nanoFramework.TestAdapter.sln | 31 + nanoFramework.TestFramework.sln | 40 + poc/ConsoleApp3/App.config | 6 - poc/ConsoleApp3/ConsoleApp3.csproj | 53 - poc/ConsoleApp3/Program.cs | 24 - poc/ConsoleApp3/Properties/AssemblyInfo.cs | 36 - poc/NFApp3.sln | 69 - poc/NFApp3/NFApp3.nfproj | 56 - poc/NFApp3/NFUnitTest1.cs | 14 - poc/NFApp3/Program.cs | 18 - poc/NFApp3/babel-test-proj.babel | 58 - poc/NFApp3/local.runsettings | 7 - poc/NFApp3/nanoFramework.runsettings | 9 - poc/NFApp3/packages.config | 6 - poc/NFUnit Test Demo.sln | 27 + poc/TestOfTestFramework/NFUnitTest.nfproj | 59 + .../Properties/AssemblyInfo.cs | 6 + poc/TestOfTestFramework/Test.cs | 172 ++ .../TestOfTestFramework.cs | 172 ++ poc/TestOfTestFramework/nano.runsettings | 13 + poc/TestOfTestFramework/packages.config | 5 + .../nanoFramework.MSTest.TestAdapter.props | 7 +- .../Nuget.nanoFramework.TestAdapter.nuproj | 52 - .../Nuget.nanoFramework.TestFramework.nuproj | 52 - source/TestAdapter/AssemblyResolver.cs | 682 ----- source/TestAdapter/Assertions/Assert.cs | 2371 ----------------- .../Attributes/DeploymentItemAttribute.cs | 55 - .../TestAdapter/Attributes/ExecutionScope.cs | 25 - .../Attributes/ExpectedExceptionAttribute.cs | 147 - .../ExpectedExceptionBaseAttribute.cs | 122 - .../TestAdapter/Attributes/IgnoreAttribute.cs | 41 - .../TestAdapter/Attributes/OwnerAttribute.cs | 33 - .../Attributes/PriorityAttribute.cs | 33 - .../Attributes/TestCategoryBaseAttribute.cs | 42 - .../Attributes/TestPropertyAttribute.cs | 43 - source/TestAdapter/Attributes/TestResult.cs | 98 - source/TestAdapter/Attributes/VsAttributes.cs | 115 - source/TestAdapter/Constants.cs | 92 - .../TestAdapter/Deployment/DeploymentItem.cs | 162 -- .../Deployment/TestRunDirectories.cs | 79 - source/TestAdapter/Directory.Build.props | 9 + source/TestAdapter/Discover.cs | 236 ++ .../Discovery/AssemblyEnumerator.cs | 317 --- .../Discovery/AssemblyEnumeratorWrapper.cs | 187 -- .../Discovery/TestMethodValidator.cs | 79 - .../TestAdapter/Discovery/TypeEnumerator.cs | 174 -- source/TestAdapter/Discovery/TypeValidator.cs | 144 - .../Discovery/UnitTestDiscoverer.cs | 186 -- .../Exceptions/AssertFailedException.cs | 43 - .../Exceptions/AssertInconclusiveException.cs | 43 - .../Exceptions/UnitTestAssertException.cs | 42 - .../Execution/LogMessageListener.cs | 148 - .../TestAdapter/Execution/TestAssemblyInfo.cs | 245 -- .../Execution/TestCaseDiscoverySink.cs | 45 - source/TestAdapter/Execution/TestClassInfo.cs | 352 --- .../Execution/TestExecutionManager.cs | 518 ---- .../TestAdapter/Execution/TestMethodInfo.cs | 712 ----- .../Execution/TestRunCancellationToken.cs | 77 - .../Execution/ThreadSafeStringWriter.cs | 93 - source/TestAdapter/Execution/TypeCache.cs | 735 ----- .../TestAdapter/Execution/UnitTestRunner.cs | 187 -- source/TestAdapter/Executor.cs | 320 +++ .../Extensions/ExceptionExtensions.cs | 144 - .../Extensions/MethodInfoExtensions.cs | 165 -- .../Extensions/TestCaseExtensions.cs | 43 - .../Extensions/TestContextExtensions.cs | 27 - .../Extensions/UnitTestOutcomeExtensions.cs | 72 - source/TestAdapter/Friends.cs | 8 - source/TestAdapter/Helpers/ReflectHelper.cs | 712 ----- .../TestAdapter/Helpers/StackTraceHelper.cs | 255 -- .../Helpers/UnitTestOutcomeHelper.cs | 56 - source/TestAdapter/Helpers/UtfHelper.cs | 66 - .../Interfaces/IAdapterTraceLogger.cs | 41 - .../Interfaces/IAssemblyLoadContext.cs | 30 - .../TestAdapter/Interfaces/IFileOperations.cs | 90 - .../Interfaces/IPlatformServiceProvider.cs | 126 - .../Interfaces/IReflectionOperations.cs | 39 - .../Interfaces/ISettingsProvider.cs | 35 - source/TestAdapter/Interfaces/ITestContext.cs | 74 - .../TestAdapter/Interfaces/ITestDeployment.cs | 50 - source/TestAdapter/Interfaces/ITestMethod.cs | 76 - source/TestAdapter/Interfaces/ITestSource.cs | 37 - .../TestAdapter/Interfaces/ITestSourceHost.cs | 38 - .../TestAdapter/Interfaces/ITraceListener.cs | 27 - .../Interfaces/ITraceListenerManager.cs | 35 - source/TestAdapter/LogMessenger.cs | 97 + source/TestAdapter/Logger.cs | 63 - source/TestAdapter/MSTest.TestAdapter.csproj | 199 -- source/TestAdapter/MSTestSettings.cs | 522 ---- .../Navigation/DiaNavigationData.cs | 32 - source/TestAdapter/Navigation/DiaSession.cs | 115 - .../Navigation/FullSymbolReader.cs | 521 ---- .../Navigation/Helpers/FileHelper.cs | 138 - .../Navigation/Helpers/IProcessHelper.cs | 117 - .../Helpers/PlatformArchitecture.cs | 21 - .../Navigation/Helpers/ProcessHelper.cs | 216 -- source/TestAdapter/Navigation/IFileHelper.cs | 140 - .../TestAdapter/Navigation/INavigationData.cs | 29 - .../Navigation/INavigationSession.cs | 24 - .../TestAdapter/Navigation/ISymbolReader.cs | 42 - .../TestAdapter/Navigation/NativeMethods.cs | 644 ----- .../Navigation/PlatformAssemblyLoadContext.cs | 26 - .../Navigation/PortablePdbReader.cs | 173 -- .../Navigation/PortableSymbolReader.cs | 141 - .../Navigation/ProcessStartInfoExtensions.cs | 24 - .../ObjectModel/AdapterSettingsException.cs | 18 - source/TestAdapter/ObjectModel/ITestMethod.cs | 42 - .../ObjectModel/StackTraceInformation.cs | 52 - .../ObjectModel/TestFailedException.cs | 52 - source/TestAdapter/ObjectModel/TestMethod.cs | 135 - .../ObjectModel/TestMethodOptions.cs | 42 - .../ObjectModel/TypeInspectionException.cs | 32 - .../ObjectModel/UnitTestElement.cs | 131 - .../ObjectModel/UnitTestOutcome.cs | 67 - .../TestAdapter/ObjectModel/UnitTestResult.cs | 217 -- source/TestAdapter/PlatformServiceProvider.cs | 223 -- source/TestAdapter/Properties/AssemblyInfo.cs | 26 - .../Resources/Resource.Designer.cs | 1353 ---------- source/TestAdapter/Resources/Resource.resx | 560 ---- .../TestAdapter/RunConfigurationSettings.cs | 153 -- .../Services/AdapterTraceLogger.cs | 49 - .../Services/AssemblyLoadWorker.cs | 264 -- .../Services/DiaSessionOperations.cs | 121 - source/TestAdapter/Services/FileOperations.cs | 173 -- .../Services/MSTestSettingsProvider.cs | 361 --- .../Services/RecursiveDirectoryPath.cs | 51 - .../Services/ReflectionOperations.cs | 82 - .../Services/TestContextImplementation.cs | 494 ---- source/TestAdapter/Services/TestDeployment.cs | 245 -- source/TestAdapter/Services/TestSource.cs | 68 - source/TestAdapter/Services/TestSourceHost.cs | 376 --- source/TestAdapter/Services/TraceListener.cs | 44 - .../Services/TraceListenerManager.cs | 81 - source/TestAdapter/Settings.cs | 97 + source/TestAdapter/SettingsProvider.cs | 39 + source/TestAdapter/TestContext.cs | 94 - source/TestAdapter/TestDiscoverer.cs | 73 - source/TestAdapter/TestExecutor.cs | 118 - source/TestAdapter/TestMethodFilter.cs | 153 -- source/TestAdapter/TestObjectHelper.cs | 29 + source/TestAdapter/TestSettings.cs | 508 ---- source/TestAdapter/TestsConstants.cs | 24 + .../Utilities/AppDomainUtilities.cs | 258 -- .../TestAdapter/Utilities/AppDomainWrapper.cs | 27 - .../TestAdapter/Utilities/AssemblyUtility.cs | 273 -- .../Utilities/DeploymentItemUtility.cs | 256 -- .../Utilities/DeploymentUtility.cs | 743 ------ source/TestAdapter/Utilities/FileUtility.cs | 357 --- source/TestAdapter/Utilities/IAppDomain.cs | 35 - .../TestAdapter/Utilities/IAssemblyUtility.cs | 27 - .../Utilities/ReflectionUtility.cs | 337 --- source/TestAdapter/Utilities/XmlUtilities.cs | 175 -- source/TestAdapter/key.snk | Bin 0 -> 596 bytes .../nanoFramework.TestAdapter.csproj | 27 + source/TestAdapter/packages.config | 7 - .../Extensions/LogHelperExtensions.cs | 41 - .../Extensions/TestCaseExtensions.cs | 27 - .../Properties/AssemblyInfo.cs | 28 - .../SettingsProvider.cs | 30 - .../TestDiscoverer.cs | 98 - .../TestAdapter_v1_light-wip/TestExecutor.cs | 272 -- .../nanoFramework.TestAdapter.csproj | 88 - .../TestAdapter_v1_light-wip/packages.config | 7 - source/TestFramework/Friends.cs | 8 - .../TestFramework/Properties/AssemblyInfo.cs | 29 +- source/TestFramework/TestExtensions.cs | 132 + source/TestFramework/TestFramework.cs | 738 +++++ source/TestFramework/key.snk | Bin 0 -> 596 bytes .../nanoFramework.TestFramework.nfproj | 60 + source/TestFramework/packages.config | 3 +- .../TestFrameworkShared/CleanupAttribute.cs | 18 + source/TestFrameworkShared/SetupAttribute.cs | 18 + .../TestClassAttribute.cs | 8 +- .../TestFrameworkShared.projitems | 17 + .../TestFrameworkShared.shproj} | 4 +- .../TestMethodAttribute.cs | 8 +- source/TestInterface/Constants.cs | 19 - .../TestInterface/Properties/AssemblyInfo.cs | 28 - source/TestInterface/RegExHelper.cs | 15 - .../Resources/StringResources.Designer.cs | 117 - .../Resources/StringResources.resx | 138 - source/TestInterface/TestCase.cs | 21 - source/TestInterface/TestExecutionManager.cs | 16 - source/TestInterface/UnitTestDiscoverer.cs | 55 - source/TestInterface/nFTestSettings.cs | 60 - .../nanoFramework.TestInterface.csproj | 70 - .../TestPlatform.Shared.projitems | 15 - source/UnitTestLauncher/Program.cs | 86 + .../Properties/AssemblyInfo.cs | 21 + source/UnitTestLauncher/key.snk | Bin 0 -> 596 bytes .../nanoFramework.UnitTestLauncher.nfproj} | 30 +- source/UnitTestLauncher/packages.config | 5 + .../Properties/AssemblyInfo.cs | 33 - source/VSIXTestAdapter/VSIXTestAdapter.csproj | 63 - .../source.extension.vsixmanifest | 25 - source/key.snk | Bin 596 -> 0 bytes source/nanoFramework.TestPlatform.sln | 77 - source/package.nuspec | 37 + source/packages.config | 11 - source/runsettings/nano.runsettings | 13 + version.json | 25 + 214 files changed, 3174 insertions(+), 24725 deletions(-) create mode 100644 LICENSE.md create mode 100644 NuGet.Config create mode 100644 assets/nf-logo.png create mode 100644 assets/test-discovered.jpg create mode 100644 assets/test-integration-vs-failed.jpg create mode 100644 assets/test-integration-vs.jpg create mode 100644 assets/test-nuget-test-framework.jpg create mode 100644 assets/test-success.jpg create mode 100644 azure-pipelines.yml create mode 100644 azure-pipelines/update-dependencies.ps1 create mode 100644 config/SignClient.json create mode 100644 config/filelist.txt create mode 100644 nanoFramework.TestAdapter.sln create mode 100644 nanoFramework.TestFramework.sln delete mode 100644 poc/ConsoleApp3/App.config delete mode 100644 poc/ConsoleApp3/ConsoleApp3.csproj delete mode 100644 poc/ConsoleApp3/Program.cs delete mode 100644 poc/ConsoleApp3/Properties/AssemblyInfo.cs delete mode 100644 poc/NFApp3.sln delete mode 100644 poc/NFApp3/NFApp3.nfproj delete mode 100644 poc/NFApp3/NFUnitTest1.cs delete mode 100644 poc/NFApp3/Program.cs delete mode 100644 poc/NFApp3/babel-test-proj.babel delete mode 100644 poc/NFApp3/local.runsettings delete mode 100644 poc/NFApp3/nanoFramework.runsettings delete mode 100644 poc/NFApp3/packages.config create mode 100644 poc/NFUnit Test Demo.sln create mode 100644 poc/TestOfTestFramework/NFUnitTest.nfproj rename poc/{NFApp3 => TestOfTestFramework}/Properties/AssemblyInfo.cs (80%) create mode 100644 poc/TestOfTestFramework/Test.cs create mode 100644 poc/TestOfTestFramework/TestOfTestFramework.cs create mode 100644 poc/TestOfTestFramework/nano.runsettings create mode 100644 poc/TestOfTestFramework/packages.config delete mode 100644 source/Nuget.nanoFramework.TestAdapter/Nuget.nanoFramework.TestAdapter.nuproj delete mode 100644 source/Nuget.nanoFramework.TestFramework/Nuget.nanoFramework.TestFramework.nuproj delete mode 100644 source/TestAdapter/AssemblyResolver.cs delete mode 100644 source/TestAdapter/Assertions/Assert.cs delete mode 100644 source/TestAdapter/Attributes/DeploymentItemAttribute.cs delete mode 100644 source/TestAdapter/Attributes/ExecutionScope.cs delete mode 100644 source/TestAdapter/Attributes/ExpectedExceptionAttribute.cs delete mode 100644 source/TestAdapter/Attributes/ExpectedExceptionBaseAttribute.cs delete mode 100644 source/TestAdapter/Attributes/IgnoreAttribute.cs delete mode 100644 source/TestAdapter/Attributes/OwnerAttribute.cs delete mode 100644 source/TestAdapter/Attributes/PriorityAttribute.cs delete mode 100644 source/TestAdapter/Attributes/TestCategoryBaseAttribute.cs delete mode 100644 source/TestAdapter/Attributes/TestPropertyAttribute.cs delete mode 100644 source/TestAdapter/Attributes/TestResult.cs delete mode 100644 source/TestAdapter/Attributes/VsAttributes.cs delete mode 100644 source/TestAdapter/Constants.cs delete mode 100644 source/TestAdapter/Deployment/DeploymentItem.cs delete mode 100644 source/TestAdapter/Deployment/TestRunDirectories.cs create mode 100644 source/TestAdapter/Directory.Build.props create mode 100644 source/TestAdapter/Discover.cs delete mode 100644 source/TestAdapter/Discovery/AssemblyEnumerator.cs delete mode 100644 source/TestAdapter/Discovery/AssemblyEnumeratorWrapper.cs delete mode 100644 source/TestAdapter/Discovery/TestMethodValidator.cs delete mode 100644 source/TestAdapter/Discovery/TypeEnumerator.cs delete mode 100644 source/TestAdapter/Discovery/TypeValidator.cs delete mode 100644 source/TestAdapter/Discovery/UnitTestDiscoverer.cs delete mode 100644 source/TestAdapter/Exceptions/AssertFailedException.cs delete mode 100644 source/TestAdapter/Exceptions/AssertInconclusiveException.cs delete mode 100644 source/TestAdapter/Exceptions/UnitTestAssertException.cs delete mode 100644 source/TestAdapter/Execution/LogMessageListener.cs delete mode 100644 source/TestAdapter/Execution/TestAssemblyInfo.cs delete mode 100644 source/TestAdapter/Execution/TestCaseDiscoverySink.cs delete mode 100644 source/TestAdapter/Execution/TestClassInfo.cs delete mode 100644 source/TestAdapter/Execution/TestExecutionManager.cs delete mode 100644 source/TestAdapter/Execution/TestMethodInfo.cs delete mode 100644 source/TestAdapter/Execution/TestRunCancellationToken.cs delete mode 100644 source/TestAdapter/Execution/ThreadSafeStringWriter.cs delete mode 100644 source/TestAdapter/Execution/TypeCache.cs delete mode 100644 source/TestAdapter/Execution/UnitTestRunner.cs create mode 100644 source/TestAdapter/Executor.cs delete mode 100644 source/TestAdapter/Extensions/ExceptionExtensions.cs delete mode 100644 source/TestAdapter/Extensions/MethodInfoExtensions.cs delete mode 100644 source/TestAdapter/Extensions/TestCaseExtensions.cs delete mode 100644 source/TestAdapter/Extensions/TestContextExtensions.cs delete mode 100644 source/TestAdapter/Extensions/UnitTestOutcomeExtensions.cs delete mode 100644 source/TestAdapter/Friends.cs delete mode 100644 source/TestAdapter/Helpers/ReflectHelper.cs delete mode 100644 source/TestAdapter/Helpers/StackTraceHelper.cs delete mode 100644 source/TestAdapter/Helpers/UnitTestOutcomeHelper.cs delete mode 100644 source/TestAdapter/Helpers/UtfHelper.cs delete mode 100644 source/TestAdapter/Interfaces/IAdapterTraceLogger.cs delete mode 100644 source/TestAdapter/Interfaces/IAssemblyLoadContext.cs delete mode 100644 source/TestAdapter/Interfaces/IFileOperations.cs delete mode 100644 source/TestAdapter/Interfaces/IPlatformServiceProvider.cs delete mode 100644 source/TestAdapter/Interfaces/IReflectionOperations.cs delete mode 100644 source/TestAdapter/Interfaces/ISettingsProvider.cs delete mode 100644 source/TestAdapter/Interfaces/ITestContext.cs delete mode 100644 source/TestAdapter/Interfaces/ITestDeployment.cs delete mode 100644 source/TestAdapter/Interfaces/ITestMethod.cs delete mode 100644 source/TestAdapter/Interfaces/ITestSource.cs delete mode 100644 source/TestAdapter/Interfaces/ITestSourceHost.cs delete mode 100644 source/TestAdapter/Interfaces/ITraceListener.cs delete mode 100644 source/TestAdapter/Interfaces/ITraceListenerManager.cs create mode 100644 source/TestAdapter/LogMessenger.cs delete mode 100644 source/TestAdapter/Logger.cs delete mode 100644 source/TestAdapter/MSTest.TestAdapter.csproj delete mode 100644 source/TestAdapter/MSTestSettings.cs delete mode 100644 source/TestAdapter/Navigation/DiaNavigationData.cs delete mode 100644 source/TestAdapter/Navigation/DiaSession.cs delete mode 100644 source/TestAdapter/Navigation/FullSymbolReader.cs delete mode 100644 source/TestAdapter/Navigation/Helpers/FileHelper.cs delete mode 100644 source/TestAdapter/Navigation/Helpers/IProcessHelper.cs delete mode 100644 source/TestAdapter/Navigation/Helpers/PlatformArchitecture.cs delete mode 100644 source/TestAdapter/Navigation/Helpers/ProcessHelper.cs delete mode 100644 source/TestAdapter/Navigation/IFileHelper.cs delete mode 100644 source/TestAdapter/Navigation/INavigationData.cs delete mode 100644 source/TestAdapter/Navigation/INavigationSession.cs delete mode 100644 source/TestAdapter/Navigation/ISymbolReader.cs delete mode 100644 source/TestAdapter/Navigation/NativeMethods.cs delete mode 100644 source/TestAdapter/Navigation/PlatformAssemblyLoadContext.cs delete mode 100644 source/TestAdapter/Navigation/PortablePdbReader.cs delete mode 100644 source/TestAdapter/Navigation/PortableSymbolReader.cs delete mode 100644 source/TestAdapter/Navigation/ProcessStartInfoExtensions.cs delete mode 100644 source/TestAdapter/ObjectModel/AdapterSettingsException.cs delete mode 100644 source/TestAdapter/ObjectModel/ITestMethod.cs delete mode 100644 source/TestAdapter/ObjectModel/StackTraceInformation.cs delete mode 100644 source/TestAdapter/ObjectModel/TestFailedException.cs delete mode 100644 source/TestAdapter/ObjectModel/TestMethod.cs delete mode 100644 source/TestAdapter/ObjectModel/TestMethodOptions.cs delete mode 100644 source/TestAdapter/ObjectModel/TypeInspectionException.cs delete mode 100644 source/TestAdapter/ObjectModel/UnitTestElement.cs delete mode 100644 source/TestAdapter/ObjectModel/UnitTestOutcome.cs delete mode 100644 source/TestAdapter/ObjectModel/UnitTestResult.cs delete mode 100644 source/TestAdapter/PlatformServiceProvider.cs delete mode 100644 source/TestAdapter/Properties/AssemblyInfo.cs delete mode 100644 source/TestAdapter/Resources/Resource.Designer.cs delete mode 100644 source/TestAdapter/Resources/Resource.resx delete mode 100644 source/TestAdapter/RunConfigurationSettings.cs delete mode 100644 source/TestAdapter/Services/AdapterTraceLogger.cs delete mode 100644 source/TestAdapter/Services/AssemblyLoadWorker.cs delete mode 100644 source/TestAdapter/Services/DiaSessionOperations.cs delete mode 100644 source/TestAdapter/Services/FileOperations.cs delete mode 100644 source/TestAdapter/Services/MSTestSettingsProvider.cs delete mode 100644 source/TestAdapter/Services/RecursiveDirectoryPath.cs delete mode 100644 source/TestAdapter/Services/ReflectionOperations.cs delete mode 100644 source/TestAdapter/Services/TestContextImplementation.cs delete mode 100644 source/TestAdapter/Services/TestDeployment.cs delete mode 100644 source/TestAdapter/Services/TestSource.cs delete mode 100644 source/TestAdapter/Services/TestSourceHost.cs delete mode 100644 source/TestAdapter/Services/TraceListener.cs delete mode 100644 source/TestAdapter/Services/TraceListenerManager.cs create mode 100644 source/TestAdapter/Settings.cs create mode 100644 source/TestAdapter/SettingsProvider.cs delete mode 100644 source/TestAdapter/TestContext.cs delete mode 100644 source/TestAdapter/TestDiscoverer.cs delete mode 100644 source/TestAdapter/TestExecutor.cs delete mode 100644 source/TestAdapter/TestMethodFilter.cs create mode 100644 source/TestAdapter/TestObjectHelper.cs delete mode 100644 source/TestAdapter/TestSettings.cs create mode 100644 source/TestAdapter/TestsConstants.cs delete mode 100644 source/TestAdapter/Utilities/AppDomainUtilities.cs delete mode 100644 source/TestAdapter/Utilities/AppDomainWrapper.cs delete mode 100644 source/TestAdapter/Utilities/AssemblyUtility.cs delete mode 100644 source/TestAdapter/Utilities/DeploymentItemUtility.cs delete mode 100644 source/TestAdapter/Utilities/DeploymentUtility.cs delete mode 100644 source/TestAdapter/Utilities/FileUtility.cs delete mode 100644 source/TestAdapter/Utilities/IAppDomain.cs delete mode 100644 source/TestAdapter/Utilities/IAssemblyUtility.cs delete mode 100644 source/TestAdapter/Utilities/ReflectionUtility.cs delete mode 100644 source/TestAdapter/Utilities/XmlUtilities.cs create mode 100644 source/TestAdapter/key.snk create mode 100644 source/TestAdapter/nanoFramework.TestAdapter.csproj delete mode 100644 source/TestAdapter/packages.config delete mode 100644 source/TestAdapter_v1_light-wip/Extensions/LogHelperExtensions.cs delete mode 100644 source/TestAdapter_v1_light-wip/Extensions/TestCaseExtensions.cs delete mode 100644 source/TestAdapter_v1_light-wip/Properties/AssemblyInfo.cs delete mode 100644 source/TestAdapter_v1_light-wip/SettingsProvider.cs delete mode 100644 source/TestAdapter_v1_light-wip/TestDiscoverer.cs delete mode 100644 source/TestAdapter_v1_light-wip/TestExecutor.cs delete mode 100644 source/TestAdapter_v1_light-wip/nanoFramework.TestAdapter.csproj delete mode 100644 source/TestAdapter_v1_light-wip/packages.config delete mode 100644 source/TestFramework/Friends.cs create mode 100644 source/TestFramework/TestExtensions.cs create mode 100644 source/TestFramework/TestFramework.cs create mode 100644 source/TestFramework/key.snk create mode 100644 source/TestFramework/nanoFramework.TestFramework.nfproj create mode 100644 source/TestFrameworkShared/CleanupAttribute.cs create mode 100644 source/TestFrameworkShared/SetupAttribute.cs rename source/{TestPlatform.Shared => TestFrameworkShared}/TestClassAttribute.cs (88%) create mode 100644 source/TestFrameworkShared/TestFrameworkShared.projitems rename source/{TestPlatform.Shared/TestPlatform.Shared.shproj => TestFrameworkShared/TestFrameworkShared.shproj} (87%) rename source/{TestPlatform.Shared => TestFrameworkShared}/TestMethodAttribute.cs (72%) delete mode 100644 source/TestInterface/Constants.cs delete mode 100644 source/TestInterface/Properties/AssemblyInfo.cs delete mode 100644 source/TestInterface/RegExHelper.cs delete mode 100644 source/TestInterface/Resources/StringResources.Designer.cs delete mode 100644 source/TestInterface/Resources/StringResources.resx delete mode 100644 source/TestInterface/TestCase.cs delete mode 100644 source/TestInterface/TestExecutionManager.cs delete mode 100644 source/TestInterface/UnitTestDiscoverer.cs delete mode 100644 source/TestInterface/nFTestSettings.cs delete mode 100644 source/TestInterface/nanoFramework.TestInterface.csproj delete mode 100644 source/TestPlatform.Shared/TestPlatform.Shared.projitems create mode 100644 source/UnitTestLauncher/Program.cs create mode 100644 source/UnitTestLauncher/Properties/AssemblyInfo.cs create mode 100644 source/UnitTestLauncher/key.snk rename source/{TestFramework/TestFramework.nfproj => UnitTestLauncher/nanoFramework.UnitTestLauncher.nfproj} (66%) create mode 100644 source/UnitTestLauncher/packages.config delete mode 100644 source/VSIXTestAdapter/Properties/AssemblyInfo.cs delete mode 100644 source/VSIXTestAdapter/VSIXTestAdapter.csproj delete mode 100644 source/VSIXTestAdapter/source.extension.vsixmanifest delete mode 100644 source/key.snk delete mode 100644 source/nanoFramework.TestPlatform.sln create mode 100644 source/package.nuspec delete mode 100644 source/packages.config create mode 100644 source/runsettings/nano.runsettings create mode 100644 version.json diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..94286c9 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) .NET Foundation and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..ef0bacf --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/README.md b/README.md index 4474548..9a68790 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,80 @@ -# nanoFramework.TestPlatform nanoFramework Unit Test platform +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![NuGet](https://img.shields.io/nuget/dt/nanoframework.TestFramework.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoframework.TestFramework/) [![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/master/CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T) + +![nanoFramework logo](https://github.com/nanoframework/Home/blob/master/resources/logo/nanoFramework-repo-logo.png) + +----- + +# Welcome to the **nanoFramework** Unit Test Framework repository! + +## Build status + +| Component | Build Status | NuGet Package | +|:-|---|---| +| nanoframework.TestFramework | [![Build Status](https://dev.azure.com/nanoframework/nanoframework.TestFramework/_apis/build/status/nanoFramework.TestFramework?repoName=nanoframework%2FnanoFramework.TestFramework&branchName=master)](https://dev.azure.com/nanoframework/nanoframework.TestFramework/_build/latest?definitionId=67&repoName=nanoframework%2FnanoFramework.TestFramework&branchName=master) | [![NuGet](https://img.shields.io/nuget/v/nanoframework.TestFramework.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoframework.TestFramework/) | +| nanoframework.TestFramework (preview) | [![Build Status](https://dev.azure.com/nanoframework/nanoframework.TestFramework/_apis/build/status/nanoFramework.TestFramework?repoName=nanoframework%2FnanoFramework.TestFramework&branchName=develop)](https://dev.azure.com/nanoframework/nanoframework.TestFramework/_build/latest?definitionId=67&repoName=nanoframework%2FnanoFramework.TestFramework&branchName=develop) | [![](https://badgen.net/badge/NuGet/preview/D7B023?icon=https://simpleicons.now.sh/azuredevops/fff)](https://dev.azure.com/nanoframework/feed/_packaging?_a=package&feed=sandbox&package=nanoframework.TestFramework&protocolType=NuGet&view=overview) | + +## What is the .NET nanoFramework Test Framework + +nanoFramework TestFramework it's a Unit Test framework dedicated to .NET nanoFramework! It has all the benefits of what you're used to when using Microsoft Test platform for .NET or XUnit or any other! + +The framework includes multiple elements that are distributed in a single NuGet package! + +- `nanoFramework.TestFramework` which contains the attributes to decorate your code and the `Assert` classes to check that you're code is properly doing what's expected. +- `nanoFramework.UnitTestLauncher` which is the engine launching and managing the Unit Tests. +- `nanoFramework.TestAdapter` which is the Visual Studio Test platform adapter, allowing to have the test integration in Visual Studio. + +The integration looks like that: + +![test integration](assets/test-integration-vs.jpg) + +And the integration will point you up to your code for successful or failed tests: + +![test integration failed](assets/test-integration-vs-failed.jpg) + +## Usage of .NET nanoFramework Test Framework + +Simply add the `nanoFramework.TestFramework` nuget to your project and you're good to go! + +![test nuget](assets/test-nuget-test-framework.jpg) + +Once you'll build your project, the tests will be automatically discovered: + +![test discovered](assets/test-discovered.jpg) + +You can then run all the tests and you'll get the result: + +![test success](assets/test-success.jpg) + +To have more details on usage of the framework, please refer to the detailed [documentation here](https://docs.nanoframework.net/). + +## What you'll find in this repository + +This repository contains the source of the core elements. You'll find them in `sources` directory. The Visual Studio projects in the root directory will open those elements. + +## Sample pack + +You can find on our samples repo a [sample pack](https://github.com/nanoframework/Samples/tree/master/samples/UnitTest) with projects demoing how to use the Unit Test Framework. + +## Feedback and documentation + +For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home). + +Join our Discord community [here](https://discord.gg/gCyBu8T). + +## Credits + +The list of contributors to this project can be found at [CONTRIBUTORS](https://github.com/nanoframework/Home/blob/master/CONTRIBUTORS.md). + +## License + +The .NET **nanoFramework** Test Framework is licensed under the [MIT license](LICENSE.md). + +## Code of Conduct + +This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. +For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). + +### .NET Foundation + +This project is supported by the [.NET Foundation](https://dotnetfoundation.org). \ No newline at end of file diff --git a/assets/nf-logo.png b/assets/nf-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6f576caa11431ae1f032fde71fd1ec0bd049ed39 GIT binary patch literal 4129 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 JDbnoI{{Xpk+?xOZ literal 0 HcmV?d00001 diff --git a/assets/test-discovered.jpg b/assets/test-discovered.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8671cc77a4bc534a9cdc5e02b0429ebc1fb435a2 GIT binary patch literal 57944 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 literal 0 HcmV?d00001 diff --git a/assets/test-integration-vs.jpg b/assets/test-integration-vs.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2104fb8d68a3a7a848adf560ba4e6022c8bee2ca GIT binary patch literal 74325 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 Y+Nzm9Ha0gr&GY}C|L4BJfqxVK1F8c&J^%m! literal 0 HcmV?d00001 diff --git a/assets/test-success.jpg b/assets/test-success.jpg new file mode 100644 index 0000000000000000000000000000000000000000..40c9e8544c3898091568e6052164785790eecea0 GIT binary patch literal 60825 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 $null + +Write-Debug "Commit changed files" + +git commit -m "$prTitle ***NO_CI***" -m "$commitMessage" > $null + +Write-Debug "Push changes" + +git -c http.extraheader="AUTHORIZATION: $auth" push --set-upstream origin $newBranchName > $null + +# start PR +# we are hardcoding to 'develop' branch to have a fixed one +# this is very important for tags (which don't have branch information) +# considering that the base branch can be changed at the PR ther is no big deal about this +$prRequestBody = @{title="$prTitle";body="$commitMessage";head="$newBranchName";base="develop"} | ConvertTo-Json +$githubApiEndpoint = "https://api.github.com/repos/nanoframework/nf-Visual-Studio-extension/pulls" +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +$headers = @{} +$headers.Add("Authorization","$auth") +$headers.Add("Accept","application/vnd.github.symmetra-preview+json") + +try +{ + $result = Invoke-RestMethod -Method Post -UserAgent [Microsoft.PowerShell.Commands.PSUserAgent]::InternetExplorer -Uri $githubApiEndpoint -Header $headers -ContentType "application/json" -Body $prRequestBody + 'Started PR with dependencies update...' | Write-Host -NoNewline + 'OK' | Write-Host -ForegroundColor Green +} +catch +{ + $result = $_.Exception.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($result) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + + throw "Error starting PR: $responseBody" +} diff --git a/config/SignClient.json b/config/SignClient.json new file mode 100644 index 0000000..482177d --- /dev/null +++ b/config/SignClient.json @@ -0,0 +1,14 @@ +{ + "SignClient": { + "AzureAd": { + "AADInstance": "https://login.microsoftonline.com/", + "ClientId": "c248d68a-ba6f-4aa9-8a68-71fe872063f8", + "TenantId": "16076fdc-fcc1-4a15-b1ca-32c9a255900e" + }, + "Service": { + "Url": "https://codesign.dotnetfoundation.org/", + "ResourceId": "https://SignService/3c30251f-36f3-490b-a955-520addb85001" + } + } + } + \ No newline at end of file diff --git a/config/filelist.txt b/config/filelist.txt new file mode 100644 index 0000000..dcf95ee --- /dev/null +++ b/config/filelist.txt @@ -0,0 +1 @@ +**/nanoFramework.* diff --git a/nanoFramework.TestAdapter.sln b/nanoFramework.TestAdapter.sln new file mode 100644 index 0000000..a9018fe --- /dev/null +++ b/nanoFramework.TestAdapter.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31005.135 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nanoFramework.TestAdapter", "source\TestAdapter\nanoFramework.TestAdapter.csproj", "{7B3026CA-1CBB-4F28-AC42-29CD22710877}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "TestFrameworkShared", "source\TestFrameworkShared\TestFrameworkShared.shproj", "{55F048B5-6739-43C5-A93D-DB61DB8E912F}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + source\TestFrameworkShared\TestFrameworkShared.projitems*{55f048b5-6739-43c5-a93d-db61db8e912f}*SharedItemsImports = 13 + source\TestFrameworkShared\TestFrameworkShared.projitems*{7b3026ca-1cbb-4f28-ac42-29cd22710877}*SharedItemsImports = 5 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7B3026CA-1CBB-4F28-AC42-29CD22710877}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B3026CA-1CBB-4F28-AC42-29CD22710877}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B3026CA-1CBB-4F28-AC42-29CD22710877}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B3026CA-1CBB-4F28-AC42-29CD22710877}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8DC0B70D-F42F-4257-A336-60E93BE828D2} + EndGlobalSection +EndGlobal diff --git a/nanoFramework.TestFramework.sln b/nanoFramework.TestFramework.sln new file mode 100644 index 0000000..65e308d --- /dev/null +++ b/nanoFramework.TestFramework.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31005.135 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.UnitTestLauncher", "source\UnitTestLauncher\nanoFramework.UnitTestLauncher.nfproj", "{897FC4EA-823D-4343-8CC6-B6F28C3FF91E}" +EndProject +Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.TestFramework", "source\TestFramework\nanoFramework.TestFramework.nfproj", "{D66A7774-AFAB-466B-9CFB-3485B02F4AF4}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "TestFrameworkShared", "source\TestFrameworkShared\TestFrameworkShared.shproj", "{55F048B5-6739-43C5-A93D-DB61DB8E912F}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + source\TestFrameworkShared\TestFrameworkShared.projitems*{55f048b5-6739-43c5-a93d-db61db8e912f}*SharedItemsImports = 13 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {897FC4EA-823D-4343-8CC6-B6F28C3FF91E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {897FC4EA-823D-4343-8CC6-B6F28C3FF91E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {897FC4EA-823D-4343-8CC6-B6F28C3FF91E}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {897FC4EA-823D-4343-8CC6-B6F28C3FF91E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {897FC4EA-823D-4343-8CC6-B6F28C3FF91E}.Release|Any CPU.Build.0 = Release|Any CPU + {897FC4EA-823D-4343-8CC6-B6F28C3FF91E}.Release|Any CPU.Deploy.0 = Release|Any CPU + {D66A7774-AFAB-466B-9CFB-3485B02F4AF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D66A7774-AFAB-466B-9CFB-3485B02F4AF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D66A7774-AFAB-466B-9CFB-3485B02F4AF4}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {D66A7774-AFAB-466B-9CFB-3485B02F4AF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D66A7774-AFAB-466B-9CFB-3485B02F4AF4}.Release|Any CPU.Build.0 = Release|Any CPU + {D66A7774-AFAB-466B-9CFB-3485B02F4AF4}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {45FCB25A-0B73-4A11-B3AC-29A48E46AB9C} + EndGlobalSection +EndGlobal diff --git a/poc/ConsoleApp3/App.config b/poc/ConsoleApp3/App.config deleted file mode 100644 index 8324aa6..0000000 --- a/poc/ConsoleApp3/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/poc/ConsoleApp3/ConsoleApp3.csproj b/poc/ConsoleApp3/ConsoleApp3.csproj deleted file mode 100644 index a1e4c70..0000000 --- a/poc/ConsoleApp3/ConsoleApp3.csproj +++ /dev/null @@ -1,53 +0,0 @@ - - - - - Debug - AnyCPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0} - Exe - ConsoleApp3 - ConsoleApp3 - v4.6 - 512 - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/poc/ConsoleApp3/Program.cs b/poc/ConsoleApp3/Program.cs deleted file mode 100644 index d504f7b..0000000 --- a/poc/ConsoleApp3/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace ConsoleApp3 -{ - class Program - { - static void Main(string[] args) - { - DateTime alarmTime = new DateTime(2018, 10, 29, 23, 30, 15); - - // c - Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; - - Console.WriteLine($"Set alarm time to {alarmTime.ToString("s")}"); - - } - } -} diff --git a/poc/ConsoleApp3/Properties/AssemblyInfo.cs b/poc/ConsoleApp3/Properties/AssemblyInfo.cs deleted file mode 100644 index 4dd9589..0000000 --- a/poc/ConsoleApp3/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ConsoleApp3")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ConsoleApp3")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("cd1a924a-d2b8-4e51-bd01-ea71e61976d0")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/poc/NFApp3.sln b/poc/NFApp3.sln deleted file mode 100644 index 544066d..0000000 --- a/poc/NFApp3.sln +++ /dev/null @@ -1,69 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28010.2019 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "NFApp3", "NFApp3\NFApp3.nfproj", "{78E81FA7-C1BF-4F65-A45D-9736CA479649}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp3", "ConsoleApp3\ConsoleApp3.csproj", "{CD1A924A-D2B8-4E51-BD01-EA71E61976D0}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|ARM = Debug|ARM - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|ARM = Release|ARM - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|Any CPU.Build.0 = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|ARM.ActiveCfg = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|ARM.Build.0 = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|ARM.Deploy.0 = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|x64.ActiveCfg = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|x64.Build.0 = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|x64.Deploy.0 = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|x86.ActiveCfg = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|x86.Build.0 = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Debug|x86.Deploy.0 = Debug|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|Any CPU.ActiveCfg = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|Any CPU.Build.0 = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|Any CPU.Deploy.0 = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|ARM.ActiveCfg = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|ARM.Build.0 = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|ARM.Deploy.0 = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|x64.ActiveCfg = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|x64.Build.0 = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|x64.Deploy.0 = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|x86.ActiveCfg = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|x86.Build.0 = Release|Any CPU - {78E81FA7-C1BF-4F65-A45D-9736CA479649}.Release|x86.Deploy.0 = Release|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Debug|ARM.ActiveCfg = Debug|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Debug|ARM.Build.0 = Debug|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Debug|x64.ActiveCfg = Debug|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Debug|x64.Build.0 = Debug|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Debug|x86.ActiveCfg = Debug|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Debug|x86.Build.0 = Debug|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Release|Any CPU.Build.0 = Release|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Release|ARM.ActiveCfg = Release|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Release|ARM.Build.0 = Release|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Release|x64.ActiveCfg = Release|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Release|x64.Build.0 = Release|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Release|x86.ActiveCfg = Release|Any CPU - {CD1A924A-D2B8-4E51-BD01-EA71E61976D0}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {78B87301-5203-4FB7-A6B0-758075F129BA} - EndGlobalSection -EndGlobal diff --git a/poc/NFApp3/NFApp3.nfproj b/poc/NFApp3/NFApp3.nfproj deleted file mode 100644 index 0203224..0000000 --- a/poc/NFApp3/NFApp3.nfproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - $(MSBuildToolsPath)..\..\..\nanoFramework\v1.0\ - - - - - Debug - AnyCPU - {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - 78e81fa7-c1bf-4f65-a45d-9736ca479649 - Exe - Properties - 512 - NFApp3 - NFApp3 - v1.0 - true - true - True - - - - - - - - - - - - - - - ..\packages\nanoFramework.CoreLibrary.1.0.1-preview127\lib\mscorlib.dll - True - True - - - ..\packages\nanoFramework.TestFramework.1.0.0-preview010\lib\nanoFramework.TestPlatform.TestFramework.dll - True - True - - - - - - - - - - - - \ No newline at end of file diff --git a/poc/NFApp3/NFUnitTest1.cs b/poc/NFApp3/NFUnitTest1.cs deleted file mode 100644 index 67af1b9..0000000 --- a/poc/NFApp3/NFUnitTest1.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; - -namespace UnitTestProject1_mstest -{ - [TestClass] - public class UnitTest11 - { - [TestMethod] - public void TestMethod11() - { - } - } -} diff --git a/poc/NFApp3/Program.cs b/poc/NFApp3/Program.cs deleted file mode 100644 index dacd20f..0000000 --- a/poc/NFApp3/Program.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Threading; - -namespace NFApp3 -{ - public class Program - { - public static void Main() - { - // need this? - //Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI(); - - //Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments); - - Thread.Sleep(Timeout.Infinite); - } - } -} diff --git a/poc/NFApp3/babel-test-proj.babel b/poc/NFApp3/babel-test-proj.babel deleted file mode 100644 index f10fd5b..0000000 --- a/poc/NFApp3/babel-test-proj.babel +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - bin\Debug\NFApp3.exe - \[mscorlib\]System.Runtime.CompilerServices.CompilerGeneratedAttribute;\[mscorlib\]System.Diagnostics.DebuggerDisplayAttribute;\[mscorlib\]System.Diagnostics.DebuggerBrowsableAttribute;\[mscorlib\]System.Diagnostics.DebuggerNonUserCodeAttribute;\[mscorlib\]System.Diagnostics.DebuggerHiddenAttribute;\[mscorlib\]System.Diagnostics.DebuggerStepThroughAttribute;false - false - goto=true;switch=true;case=true;if=true;call=true;value=false;token=false;underflow=false;true - false - false - false - false - None - false - true - false - true - false - false - false - 3 - false - false - true - false - false - false - true - true - true - true - true - true - false - false - false - false - true - true - true - false - false - false - false - true - false - 1 - true - - - - - \ No newline at end of file diff --git a/poc/NFApp3/local.runsettings b/poc/NFApp3/local.runsettings deleted file mode 100644 index 3a70adc..0000000 --- a/poc/NFApp3/local.runsettings +++ /dev/null @@ -1,7 +0,0 @@ - - - - X86 - .NETFramework,Version=v4.6.1 - - diff --git a/poc/NFApp3/nanoFramework.runsettings b/poc/NFApp3/nanoFramework.runsettings deleted file mode 100644 index 2f2162f..0000000 --- a/poc/NFApp3/nanoFramework.runsettings +++ /dev/null @@ -1,9 +0,0 @@ - - - X86 - False - Framework40 - True - 10000 - - \ No newline at end of file diff --git a/poc/NFApp3/packages.config b/poc/NFApp3/packages.config deleted file mode 100644 index c4482f5..0000000 --- a/poc/NFApp3/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/poc/NFUnit Test Demo.sln b/poc/NFUnit Test Demo.sln new file mode 100644 index 0000000..65fea44 --- /dev/null +++ b/poc/NFUnit Test Demo.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31019.35 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "NFUnitTest", "TestOfTestFramework\NFUnitTest.nfproj", "{FBD29C49-D7DC-425E-BAD1-1AE63484A6CD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FBD29C49-D7DC-425E-BAD1-1AE63484A6CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBD29C49-D7DC-425E-BAD1-1AE63484A6CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBD29C49-D7DC-425E-BAD1-1AE63484A6CD}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {FBD29C49-D7DC-425E-BAD1-1AE63484A6CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBD29C49-D7DC-425E-BAD1-1AE63484A6CD}.Release|Any CPU.Build.0 = Release|Any CPU + {FBD29C49-D7DC-425E-BAD1-1AE63484A6CD}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {280DA13F-AFF7-4D5D-BA7F-BFC5E8F19995} + EndGlobalSection +EndGlobal diff --git a/poc/TestOfTestFramework/NFUnitTest.nfproj b/poc/TestOfTestFramework/NFUnitTest.nfproj new file mode 100644 index 0000000..323a91b --- /dev/null +++ b/poc/TestOfTestFramework/NFUnitTest.nfproj @@ -0,0 +1,59 @@ + + + + + + + $(MSBuildToolsPath)..\..\..\nanoFramework\v1.0\ + + + + Debug + AnyCPU + {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + fbd29c49-d7dc-425e-bad1-1ae63484a6cd + Library + Properties + 512 + NFUnitTest + NFUnitTest + False + true + UnitTest + v4.0 + + + + $(MSBuildProjectDirectory)\nano.runsettings + + + + + + + + ..\packages\nanoFramework.CoreLibrary.1.10.1-preview.9\lib\mscorlib.dll + True + True + + + ..\packages\nanoFramework.TestFramework.1.0.25\lib\nanoFramework.TestFramework.dll + True + True + + + ..\packages\nanoFramework.TestFramework.1.0.25\lib\nanoFramework.UnitTestLauncher.exe + True + True + + + + + + + + + + + + \ No newline at end of file diff --git a/poc/NFApp3/Properties/AssemblyInfo.cs b/poc/TestOfTestFramework/Properties/AssemblyInfo.cs similarity index 80% rename from poc/NFApp3/Properties/AssemblyInfo.cs rename to poc/TestOfTestFramework/Properties/AssemblyInfo.cs index 262254d..61f731f 100644 --- a/poc/NFApp3/Properties/AssemblyInfo.cs +++ b/poc/TestOfTestFramework/Properties/AssemblyInfo.cs @@ -31,3 +31,9 @@ using System.Runtime.InteropServices; // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] + +///////////////////////////////////////////////////////////////// +// This attribute is mandatory when building Interop libraries // +// update this whenever the native assembly signature changes // +[assembly: AssemblyNativeVersion("1.0.0.0")] +///////////////////////////////////////////////////////////////// diff --git a/poc/TestOfTestFramework/Test.cs b/poc/TestOfTestFramework/Test.cs new file mode 100644 index 0000000..0591cd5 --- /dev/null +++ b/poc/TestOfTestFramework/Test.cs @@ -0,0 +1,172 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using System; +using System.Diagnostics; + +namespace nanoFramework.TestFramework.Test +{ + [TestClass] + public class TestOfTest + { + [TestMethod] + public void TestRaisesException() + { + Debug.WriteLine("Test will raise exception"); + Assert.Trows(typeof(Exception), ThrowMe); + } + + private void ThrowMe() + { + throw new Exception("Test failed and it's a shame"); + } + + [TestMethod] + public void TestCheckAllEqual() + { + Debug.WriteLine("Test will check that all the Equal are actually equal"); + // Arrange + byte bytea = 42; byte byteb = 42; + char chara = (char)42; char charb = (char)42; + sbyte sbytea = 42; sbyte sbyteb = 42; + int inta = 42; int intb = 42; + uint uinta = 42; uint uintb = 42; + long longa = 42; long longb = 42; + ulong ulonga = 42; ulong ulongb = 42; + bool boola = true; bool boolb = true; + short shorta = 42; short shortb = 42; + ushort ushorta = 42; ushort ushortb = 42; + float floata = 42; float floatb = 42; + int[] intArraya = new int[5] { 1, 2, 3, 4, 5 }; + int[] intArrayb = new int[5] { 1, 2, 3, 4, 5 }; + object obja = new object(); object objb = obja; + string stra = "42"; string strb = "42"; + byte[] arrayempty = new byte[0]; + // Assert + Assert.True(boola); + Assert.Equal(bytea, byteb); + Assert.Equal(chara, charb); + Assert.Equal(sbytea, sbyteb); + Assert.Equal(inta, intb); + Assert.Equal(uinta, uintb); + Assert.Equal(longa, longb); + Assert.Equal(ulonga, ulongb); + Assert.Equal(boola, boolb); + Assert.Equal(shorta, shortb); + Assert.Equal(ushorta, ushortb); + Assert.Equal(floata, floatb); + Assert.Equal(intArraya, intArrayb); + Assert.Equal(stra, strb); + Assert.Same(obja, objb); + Assert.Empty(arrayempty); + } + + [TestMethod] + public void TestCheckAllNotEqual() + { + Debug.WriteLine("Test will check that all the NotEqual are actually equal"); + // Arrange + byte bytea = 42; byte byteb = 43; + char chara = (char)42; char charb = (char)43; + sbyte sbytea = 42; sbyte sbyteb = 43; + int inta = 42; int intb = 43; + uint uinta = 42; uint uintb = 43; + long longa = 42; long longb = 43; + ulong ulonga = 42; ulong ulongb = 43; + bool boola = true; bool boolb = false; + short shorta = 42; short shortb = 43; + ushort ushorta = 42; ushort ushortb = 43; + float floata = 42; float floatb = 43; + int[] intArraya = new int[5] { 1, 2, 3, 4, 5 }; + int[] intArrayb = new int[5] { 1, 2, 3, 4, 6 }; + int[] intArraybis = new int[4] { 1, 2, 3, 4 }; + int[] intArrayter = null; + object obja = new object(); object objb = new object(); + string stra = "42"; string strb = "43"; + // Assert + Assert.False(boolb); + Assert.NotEqual(bytea, byteb); + Assert.NotEqual(chara, charb); + Assert.NotEqual(sbytea, sbyteb); + Assert.NotEqual(inta, intb); + Assert.NotEqual(uinta, uintb); + Assert.NotEqual(longa, longb); + Assert.NotEqual(ulonga, ulongb); + Assert.NotEqual(boola, boolb); + Assert.NotEqual(shorta, shortb); + Assert.NotEqual(ushorta, ushortb); + Assert.NotEqual(floata, floatb); + Assert.NotEqual(intArraya, intArrayb); + Assert.NotEqual(intArraya, intArraybis); + Assert.NotEqual(intArraya, intArrayter); + Assert.NotEqual(stra, strb); + Assert.NotSame(obja, objb); + Assert.NotEmpty(intArraya); + } + + [TestMethod] + public void TestNullEmpty() + { + Debug.WriteLine("Test null, not null, types"); + // Arrange + object objnull = null; + object objnotnull = new object(); + Type typea = typeof(int); + Type typeb = typeof(int); + Type typec = typeof(long); + // Assert + Assert.Null(objnull); + Assert.NotNull(objnotnull); + Assert.IsType(typea, typeb); + Assert.IsNotType(typea, typec); + } + + [TestMethod] + public void TestStringComparison() + { + Debug.WriteLine("Test string, Contains, EndsWith, StartWith"); + // Arrange + string tocontains = "this text contains and end with contains"; + string startcontains = "contains start this text"; + string contains = "contains"; + string doesnotcontains = "this is totally something else"; + string empty = string.Empty; + string stringnull = null; + // Assert + Assert.Contains(contains, tocontains); + Assert.DoesNotContains(contains, doesnotcontains); + Assert.DoesNotContains(contains, empty); + Assert.DoesNotContains(contains, stringnull); + Assert.StartsWith(contains, startcontains); + Assert.EndsWith(contains, tocontains); + } + + [Setup] + public void RunSetup() + { + Debug.WriteLine("Setup"); + } + + public void Nothing() + { + Debug.WriteLine("Nothing and should not be called"); + } + + [Cleanup] + public void Cleanup() + { + Debug.WriteLine("Cleanup"); + } + } + + public class SomthingElse + { + public void NothingReally() + { + Debug.WriteLine("Test failed: This would never get through"); + } + } +} diff --git a/poc/TestOfTestFramework/TestOfTestFramework.cs b/poc/TestOfTestFramework/TestOfTestFramework.cs new file mode 100644 index 0000000..999cd72 --- /dev/null +++ b/poc/TestOfTestFramework/TestOfTestFramework.cs @@ -0,0 +1,172 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using System; +using System.Diagnostics; + +namespace nanoFramework.TestFramework.Test +{ + [TestClass] + public class TestOfTest + { + [TestMethod] + public void TestRaisesException() + { + Debug.WriteLine("Test will raise exception"); + Assert.Trows(typeof(Exception), ThrowMe); + } + + private void ThrowMe() + { + throw new Exception("Test failed and it's a shame"); + } + + [TestMethod] + public void TestCheckAllEqual() + { + Debug.WriteLine("Test will check that all the Equal are actually equal"); + // Arrange + byte bytea = 42; byte byteb = 42; + char chara = (char)42; char charb = (char)42; + sbyte sbytea = 42; sbyte sbyteb = 42; + int inta = 42; int intb = 42; + uint uinta = 42; uint uintb = 42; + long longa = 42; long longb = 42; + ulong ulonga = 42; ulong ulongb = 42; + bool boola = true; bool boolb = true; + short shorta = 42; short shortb = 42; + ushort ushorta = 42; ushort ushortb = 42; + float floata = 42; float floatb = 42; + int[] intArraya = new int[5] { 1, 2, 3, 4, 5 }; + int[] intArrayb = new int[5] { 1, 2, 3, 4, 5 }; + object obja = new object(); object objb = obja; + string stra = "42"; string strb = "42"; + byte[] arrayempty = new byte[0]; + // Assert + Assert.True(boola); + Assert.Equal(bytea, byteb); + Assert.Equal(chara, charb); + Assert.Equal(sbytea, sbyteb); + Assert.Equal(inta, intb); + Assert.Equal(uinta, uintb); + Assert.Equal(longa, longb); + Assert.Equal(ulonga, ulongb); + Assert.Equal(boola, boolb); + Assert.Equal(shorta, shortb); + Assert.Equal(ushorta, ushortb); + Assert.Equal(floata, floatb); + Assert.Equal(intArraya, intArrayb); + Assert.Equal(stra, strb); + Assert.Same(obja, objb); + Assert.Empty(arrayempty); + } + + [TestMethod] + public void TestCheckAllNotEqual() + { + Debug.WriteLine("Test will check that all the NotEqual are actually equal"); + // Arrange + byte bytea = 42; byte byteb = 43; + char chara = (char)42; char charb = (char)43; + sbyte sbytea = 42; sbyte sbyteb = 43; + int inta = 42; int intb = 43; + uint uinta = 42; uint uintb = 43; + long longa = 42; long longb = 43; + ulong ulonga = 42; ulong ulongb = 43; + bool boola = true; bool boolb = false; + short shorta = 42; short shortb = 43; + ushort ushorta = 42; ushort ushortb = 43; + float floata = 42; float floatb = 43; + int[] intArraya = new int[5] { 1, 2, 3, 4, 5 }; + int[] intArrayb = new int[5] { 1, 2, 3, 4, 6 }; + int[] intArraybis = new int[4] { 1, 2, 3, 4 }; + int[] intArrayter = null; + object obja = new object(); object objb = new object(); + string stra = "42"; string strb = "43"; + // Assert + Assert.False(boolb); + Assert.NotEqual(bytea, byteb); + Assert.NotEqual(chara, charb); + Assert.NotEqual(sbytea, sbyteb); + Assert.NotEqual(inta, intb); + Assert.NotEqual(uinta, uintb); + Assert.NotEqual(longa, longb); + Assert.NotEqual(ulonga, ulongb); + Assert.NotEqual(boola, boolb); + Assert.NotEqual(shorta, shortb); + Assert.NotEqual(ushorta, ushortb); + Assert.NotEqual(floata, floatb); + Assert.NotEqual(intArraya, intArrayb); + Assert.NotEqual(intArraya, intArraybis); + Assert.NotEqual(intArraya, intArrayter); + Assert.NotEqual(stra, strb); + Assert.NotSame(obja, objb); + Assert.NotEmpty(intArraya); + } + + [TestMethod] + public void TestNullEmpty() + { + Debug.WriteLine("Test null, not null, types"); + // Arrange + object objnull = null; + object objnotnull = new object(); + Type typea = typeof(int); + Type typeb = typeof(int); + Type typec = typeof(long); + // Assert + Assert.Null(objnull); + Assert.NotNull(objnotnull); + Assert.IsType(typea, typeb); + Assert.IsNotType(typea, typec); + } + + [TestMethod] + public void TestStringComparison() + { + Debug.WriteLine("Test string, Contains, EndsWith, StartWith"); + // Arrange + string tocontains = "this text contains and end with contains"; + string startcontains = "contains start this text"; + string contains = "contains"; + string doesnotcontains = "this is totally something else"; + string empty = string.Empty; + string stringnull = null; + // Assert + Assert.Contains(contains, tocontains); + Assert.DoesNotContains(contains, doesnotcontains); + Assert.DoesNotContains(contains, empty); + Assert.DoesNotContains(contains, stringnull); + Assert.StartsWith(contains, startcontains); + Assert.EndsWith(contains, tocontains); + } + + [Setup] + public void RunSetup() + { + Debug.WriteLine("Setup"); + } + + public void Nothing() + { + Debug.WriteLine("Nothing and should not be called"); + } + + [Cleanup] + public void Cleanup() + { + Debug.WriteLine("Cleanup"); + } + } + + public class SomthingElse + { + public void NothingReally() + { + Debug.WriteLine("Test failed: This would never get thru"); + } + } +} diff --git a/poc/TestOfTestFramework/nano.runsettings b/poc/TestOfTestFramework/nano.runsettings new file mode 100644 index 0000000..62f0a00 --- /dev/null +++ b/poc/TestOfTestFramework/nano.runsettings @@ -0,0 +1,13 @@ + + + + + 1 + .\TestResults + 120000 + Framework40 + + + None + + \ No newline at end of file diff --git a/poc/TestOfTestFramework/packages.config b/poc/TestOfTestFramework/packages.config new file mode 100644 index 0000000..dcab08b --- /dev/null +++ b/poc/TestOfTestFramework/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/source/Build/nanoFramework.MSTest.TestAdapter.props b/source/Build/nanoFramework.MSTest.TestAdapter.props index a5fe68c..811d687 100644 --- a/source/Build/nanoFramework.MSTest.TestAdapter.props +++ b/source/Build/nanoFramework.MSTest.TestAdapter.props @@ -1,8 +1,9 @@ + - + - - nanoFramework.TestPlatform.TestAdapter.dll + + nanoFramework.TestPlatform.dll PreserveNewest False diff --git a/source/Nuget.nanoFramework.TestAdapter/Nuget.nanoFramework.TestAdapter.nuproj b/source/Nuget.nanoFramework.TestAdapter/Nuget.nanoFramework.TestAdapter.nuproj deleted file mode 100644 index d59dd02..0000000 --- a/source/Nuget.nanoFramework.TestAdapter/Nuget.nanoFramework.TestAdapter.nuproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - - Debug - AnyCPU - - - Release - AnyCPU - - - - - [14.0.0] - net461 - - - - - build\_common\nanoFramework.TestPlatform.TestAdapter.dll - - - build\nanoFramework10\nanoFramework.MSTest.TestAdapter.props - - - - 9607C081-2415-465C-8424-1D8C28D4C0AC - - - ..\packages\NuProj.0.20.4-beta\tools\ - - - - nanoFramework.TestAdapter - 1.0.4 - nanoFramework.TestAdapter - jassimoes - jassimoes - nanoFramework.TestAdapter - nanoFramework.TestAdapter - - - - - - - Copyright © jassimoes - nanoFramework.TestAdapter - - - \ No newline at end of file diff --git a/source/Nuget.nanoFramework.TestFramework/Nuget.nanoFramework.TestFramework.nuproj b/source/Nuget.nanoFramework.TestFramework/Nuget.nanoFramework.TestFramework.nuproj deleted file mode 100644 index 5d9fb83..0000000 --- a/source/Nuget.nanoFramework.TestFramework/Nuget.nanoFramework.TestFramework.nuproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - - Debug - AnyCPU - - - Release - AnyCPU - - - - - lib\nanoFramework.TestPlatform.TestFramework.dll - - - lib\nanoFramework.TestPlatform.TestFramework.pdb - - - lib\nanoFramework.TestPlatform.TestFramework.pdbx - - - lib\nanoFramework.TestPlatform.TestFramework.pe - - - - 9d39a999-0427-4238-b882-4878e3edb9ad - - - ..\packages\NuProj.0.20.4-beta\tools\ - - - - nanoFramework.TestFramework - 1.0.0-preview010 - nanoFramework.TestFramework - jassimoes - jassimoes - nanoFramework.TestFramework - nanoFramework.TestFramework - - - - - - - Copyright © jassimoes - nanoFramework.TestFramework - - - \ No newline at end of file diff --git a/source/TestAdapter/AssemblyResolver.cs b/source/TestAdapter/AssemblyResolver.cs deleted file mode 100644 index 19d863c..0000000 --- a/source/TestAdapter/AssemblyResolver.cs +++ /dev/null @@ -1,682 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Reflection; - - /// - /// Helps resolve MSTestFramework assemblies for CLR loader. - /// The idea is that Unit Test Adapter creates App Domain for running tests and sets AppBase to tests dir. - /// Since we don't want to put our assemblies to GAC and they are not in tests dir, we use custom way to resolve them. - /// - public class AssemblyResolver : MarshalByRefObject, IDisposable - { - - /// - /// This will have the list of all directories read from runsettings. - /// - private Queue directoryList; - - /// - /// The directories to look for assemblies to resolve. - /// - private List searchDirectories; - - /// - /// Dictionary of Assemblies discovered to date. - /// - private Dictionary resolvedAssemblies = new Dictionary(); - - /// - /// Dictionary of Reflection-Only Assemblies discovered to date. - /// - private Dictionary reflectionOnlyResolvedAssemblies = new Dictionary(); - - /// - /// lock for the loaded assemblies cache. - /// - private object syncLock = new object(); - - private bool disposed; - - /// - /// Initializes a new instance of the class. - /// - /// - /// A list of directories for resolution path - /// - /// - /// If there are additonal paths where a recursive search is required - /// call AddSearchDirectoryFromRunSetting method with that list. - /// - public AssemblyResolver( - IList directories - ) - { - if (directories == null || directories.Count == 0) - { - throw new ArgumentNullException("directories"); - } - - this.searchDirectories = new List(directories); - this.directoryList = new Queue(); - - AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(this.OnResolve); - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(this.ReflectionOnlyOnResolve); - } - - /// - /// Finalizes an instance of the class. - /// - ~AssemblyResolver() - { - this.Dispose(false); - } - - /// - /// The dispose. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Returns object to be used for controlling lifetime, null means infinite lifetime. - /// - /// - /// The . - /// - public override object InitializeLifetimeService() - { - return null; - } - - /// - /// It will add a list of search directories path with property recursive/non-recursive in assembly resolver . - /// - /// - /// The recursive Directory Path. - /// - public void AddSearchDirectoriesFromRunSetting( - List recursiveDirectoryPath - ) - { - // Enqueue elements from the list in Queue - if (recursiveDirectoryPath == null) - { - return; - } - - foreach (var recPath in recursiveDirectoryPath) - { - this.directoryList.Enqueue(recPath); - } - } - - /// - /// Assembly Resolve event handler for App Domain - called when CLR loader cannot resolve assembly. - /// - /// The sender App Domain. - /// The args. - /// The . - internal Assembly ReflectionOnlyOnResolve( - object sender, - ResolveEventArgs args - ) - { - return this.OnResolveInternal(sender, args, true); - } - - /// - /// Assembly Resolve event handler for App Domain - called when CLR loader cannot resolve assembly. - /// - /// The sender App Domain. - /// The args. - /// The . - internal Assembly OnResolve( - object sender, - ResolveEventArgs args - ) - { - return this.OnResolveInternal(sender, args, false); - } - - /// - /// Adds the subdirectories of the provided path to the collection. - /// - /// Path go get subdirectories for. - /// The search Directories. - internal void AddSubdirectories( - string path, - List searchDirectories - ) - { - Debug.Assert(!string.IsNullOrEmpty(path), "'path' cannot be null or empty."); - Debug.Assert(searchDirectories != null, "'searchDirectories' cannot be null."); - - // If the directory exists, get it's subdirectories - if (this.DoesDirectoryExist(path)) - { - // Get the directories in the path provided. - var directories = this.GetDirectories(path); - - // Add each directory and its subdirectories to the collection. - foreach (var directory in directories) - { - searchDirectories.Add(directory); - - this.AddSubdirectories(directory, searchDirectories); - } - } - } - - /// - /// The dispose. - /// - /// - /// The disposing. - /// - protected virtual void Dispose( - bool disposing - ) - { - if (!this.disposed) - { - if (disposing) - { - // cleanup Managed resourceslike calling dispose on other managed object created. - AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(this.OnResolve); - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= new ResolveEventHandler(this.ReflectionOnlyOnResolve); - } - - // cleanup native resources - this.disposed = true; - } - } - - /// - /// Verifies if a directory exists. - /// - /// The path to the directory. - /// True if the directory exists. - /// Only present for unit testing scenarios. - protected virtual bool DoesDirectoryExist( - string path - ) - { - return Directory.Exists(path); - } - - /// - /// Gets the directories from a path. - /// - /// The path to the directory. - /// A list of directories in path. - /// Only present for unit testing scenarios. - protected virtual string[] GetDirectories( - string path - ) - { - return Directory.GetDirectories(path); - } - - protected virtual bool DoesFileExist( - string filePath - ) - { - return File.Exists(filePath); - } - - protected virtual Assembly LoadAssemblyFrom( - string path - ) - { - return Assembly.LoadFrom(path); - } - - protected virtual Assembly ReflectionOnlyLoadAssemblyFrom( - string path - ) - { - return Assembly.ReflectionOnlyLoadFrom(path); - } - - /// - /// It will search for a particular assembly in the given list of directory. - /// - /// The search Directorypaths. - /// The name. - /// Indicates whether this is called under a Reflection Only Load context. - /// The . - protected virtual Assembly SearchAssembly( - List searchDirectorypaths, - string name, - bool isReflectionOnly - ) - { - if (searchDirectorypaths == null || searchDirectorypaths.Count == 0) - { - return null; - } - - // args.Name is like: "Microsoft.VisualStudio.TestTools.Common, Version=[VersionMajor].0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a". - AssemblyName requestedName = null; - - try - { - // Can throw ArgumentException, FileLoadException if arg is empty/wrong format, etc. Should not return null. - requestedName = new AssemblyName(name); - } - catch (Exception ex) - { - this.SafeLog( - name, - () => - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info( - "AssemblyResolver: {0}: Failed to create assemblyName. Reason:{1} ", - name, - ex); - } - }); - - return null; - } - - Debug.Assert(requestedName != null && !string.IsNullOrEmpty(requestedName.Name), "AssemblyResolver.OnResolve: requested is null or name is empty!"); - - foreach (var dir in searchDirectorypaths) - { - if (string.IsNullOrEmpty(dir)) - { - continue; - } - - this.SafeLog( - name, - () => - { - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("AssemblyResolver: Searching assembly: {0} in the directory: {1}", requestedName.Name, dir); - } - }); - - foreach (var extension in new string[] { ".dll", ".exe" }) - { - var assemblyPath = Path.Combine(dir, requestedName.Name + extension); - - var assembly = this.SearchAndLoadAssembly(assemblyPath, name, requestedName, isReflectionOnly); - if (assembly != null) - { - return assembly; - } - } - } - - return null; - } - - /// - /// Verifies that found assembly name matches requested to avoid security issues. - /// Looks only at PublicKeyToken and Version, empty matches anything. - /// - /// The requested Name. - /// The found Name. - /// The . - private static bool RequestedAssemblyNameMatchesFound( - AssemblyName requestedName, - AssemblyName foundName - ) - { - Debug.Assert(requestedName != null, "requested assembly name should not be null."); - Debug.Assert(foundName != null, "found assembly name should not be null."); - - var requestedPublicKey = requestedName.GetPublicKeyToken(); - if (requestedPublicKey != null) - { - var foundPublicKey = foundName.GetPublicKeyToken(); - if (foundPublicKey == null) - { - return false; - } - - for (var i = 0; i < requestedPublicKey.Length; ++i) - { - if (requestedPublicKey[i] != foundPublicKey[i]) - { - return false; - } - } - } - - return requestedName.Version == null || requestedName.Version.Equals(foundName.Version); - } - - /// - /// Assembly Resolve event handler for App Domain - called when CLR loader cannot resolve assembly. - /// - /// The sender App Domain. - /// The args. - /// Indicates whether this is called under a Reflection Only Load context. - /// The . - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "senderAppDomain", Justification = "This is an event handler.")] - private Assembly OnResolveInternal( - object senderAppDomain, - ResolveEventArgs args, - bool isReflectionOnly - ) - { - if (string.IsNullOrEmpty(args?.Name)) - { - Debug.Assert(false, "AssemblyResolver.OnResolve: args.Name is null or empty."); - return null; - } - - this.SafeLog( - args.Name, - () => - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("AssemblyResolver: Resolving assembly: {0}.", args.Name); - } - }); - - string assemblyNameToLoad = AppDomain.CurrentDomain.ApplyPolicy(args.Name); - - this.SafeLog( - assemblyNameToLoad, - () => - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("AssemblyResolver: Resolving assembly after applying policy: {0}.", assemblyNameToLoad); - } - }); - - lock (this.syncLock) - { - // Since both normal and reflection only cache are accessed in same block, putting only one lock should be sufficient. - Assembly assembly = null; - - if (this.TryLoadFromCache(assemblyNameToLoad, isReflectionOnly, out assembly)) - { - return assembly; - } - - assembly = this.SearchAssembly(this.searchDirectories, assemblyNameToLoad, isReflectionOnly); - - if (assembly != null) - { - return assembly; - } - - if (this.directoryList != null && this.directoryList.Any()) - { - // required assembly is not present in searchDirectories?? - // see, if we can find it in user specified search directories. - while (assembly == null && this.directoryList.Count > 0) - { - // instead of loading whole saerch directory in one time, we are adding directory on the basis of need - var currentNode = this.directoryList.Dequeue(); - - List increamentalSearchDirectory = new List(); - - if (this.DoesDirectoryExist(currentNode.DirectoryPath)) - { - increamentalSearchDirectory.Add(currentNode.DirectoryPath); - - if (currentNode.IncludeSubDirectories) - { - // Add all its sub-directory in depth first search order. - this.AddSubdirectories(currentNode.DirectoryPath, increamentalSearchDirectory); - } - - // Add this directory list in this.searchDirectories so that when we will try to resolve some other - // assembly, then it will look in this whole directory first. - this.searchDirectories.AddRange(increamentalSearchDirectory); - - assembly = this.SearchAssembly(increamentalSearchDirectory, assemblyNameToLoad, isReflectionOnly); - } - else - { - // generate warning that path does not exist. - this.SafeLog( - assemblyNameToLoad, - () => - { - if (EqtTrace.IsWarningEnabled) - { - EqtTrace.Warning( - "The Directory: {0}, does not exist", - currentNode.DirectoryPath); - } - }); - } - } - - if (assembly != null) - { - return assembly; - } - } - - // Try for default load for System dlls that can't be found in search paths. Needs to loaded just by name. - try - { - if (isReflectionOnly) - { - // Put it in the resolved assembly cache so that if the Load call below - // triggers another assembly resolution, then we dont end up in stack overflow. - this.reflectionOnlyResolvedAssemblies[assemblyNameToLoad] = null; - - assembly = Assembly.ReflectionOnlyLoad(assemblyNameToLoad); - - if (assembly != null) - { - this.reflectionOnlyResolvedAssemblies[assemblyNameToLoad] = assembly; - } - } - else - { - // Put it in the resolved assembly cache so that if the Load call below - // triggers another assembly resolution, then we dont end up in stack overflow. - this.resolvedAssemblies[assemblyNameToLoad] = null; - - assembly = Assembly.Load(assemblyNameToLoad); - - if (assembly != null) - { - this.resolvedAssemblies[assemblyNameToLoad] = assembly; - } - } - - return assembly; - } - catch (Exception ex) - { - this.SafeLog( - args?.Name, - () => - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info( - "AssemblyResolver: {0}: Failed to load assembly. Reason:{1} ", - assemblyNameToLoad, - ex); - } - }); - } - - return assembly; - } - } - - /// - /// Load assembly from cache if available. - /// - /// The assembly Name. - /// Indicates if this is a reflection-only context. - /// The assembly. - /// The . - private bool TryLoadFromCache( - string assemblyName, - bool isReflectionOnly, - out Assembly assembly - ) - { - bool isFoundInCache = false; - - if (isReflectionOnly) - { - isFoundInCache = this.reflectionOnlyResolvedAssemblies.TryGetValue(assemblyName, out assembly); - } - else - { - isFoundInCache = this.resolvedAssemblies.TryGetValue(assemblyName, out assembly); - } - - if (isFoundInCache) - { - this.SafeLog( - assemblyName, - () => - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("AssemblyResolver: Resolved: {0}.", assemblyName); - } - }); - return true; - } - - return false; - } - - /// - /// Call logger APIs safely. We do not want a stackoverflow when objectmodel assembly itself - /// is being resolved and an EqtTrace message prompts the load of the same dll again. - /// CLR does not trigger a load when the EqtTrace messages are in a lamda expression. Leaving it that way - /// to preserve readability instead of creating wrapper functions. - /// - /// The assembly being rsolved. - /// The logger function. - private void SafeLog( - string assemblyName, - Action loggerAction - ) - { - if (!string.IsNullOrEmpty(assemblyName) && !assemblyName.StartsWith(Constants.LoggerAssemblyName)) - { - loggerAction.Invoke(); - } - } - - /// - /// Search for assembly and if exists then load. - /// - /// The assembly Path. - /// The assembly Name. - /// The requested Name. - /// Indicates whether this is called under a Reflection Only Load context. - /// The . - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom", Justification = "The assembly location is fugred out from the configuration that the user passes in.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private Assembly SearchAndLoadAssembly( - string assemblyPath, - string assemblyName, - AssemblyName requestedName, - bool isReflectionOnly - ) - { - try - { - if (!this.DoesFileExist(assemblyPath)) - { - return null; - } - - var foundName = AssemblyName.GetAssemblyName(assemblyPath); - - if (!RequestedAssemblyNameMatchesFound(requestedName, foundName)) - { - return null; // File exists but version/public key is wrong. Try next extension. - } - - Assembly assembly; - - if (isReflectionOnly) - { - assembly = this.ReflectionOnlyLoadAssemblyFrom(assemblyPath); - this.reflectionOnlyResolvedAssemblies[assemblyName] = assembly; - } - else - { - assembly = this.LoadAssemblyFrom(assemblyPath); - this.resolvedAssemblies[assemblyName] = assembly; - } - - this.SafeLog( - assemblyName, - () => - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("AssemblyResolver: Resolved assembly: {0}. ", assemblyName); - } - }); - return assembly; - } - catch (FileLoadException ex) - { - this.SafeLog( - assemblyName, - () => - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("AssemblyResolver: Failed to load assembly: {0}. Reason:{1} ", assemblyName, ex); - } - }); - - // Rethrow FileLoadException, because this exception means that the assembly - // was found, but could not be loaded. This will allow us to report a more - // specific error message to the user for things like access denied. - throw; - } - catch (Exception ex) - { - // For all other exceptions, try the next extension. - this.SafeLog( - assemblyName, - () => - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("AssemblyResolver: Failed to load assembly: {0}. Reason:{1} ", assemblyName, ex); - } - }); - } - - return null; - } - } -} diff --git a/source/TestAdapter/Assertions/Assert.cs b/source/TestAdapter/Assertions/Assert.cs deleted file mode 100644 index 5f34b22..0000000 --- a/source/TestAdapter/Assertions/Assert.cs +++ /dev/null @@ -1,2371 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Reflection; - using System.Threading.Tasks; - - /// - /// A collection of helper classes to test various conditions within - /// unit tests. If the condition being tested is not met, an exception - /// is thrown. - /// - public sealed class Assert - { - private static Assert that; - - #region Singleton constructor - - private Assert() - { - } - - /// - /// Gets the singleton instance of the Assert functionality. - /// - /// - /// Users can use this to plug-in custom assertions through C# extension methods. - /// For instance, the signature of a custom assertion provider could be "public static void IsOfType<T>(this Assert assert, object obj)" - /// Users could then use a syntax similar to the default assertions which in this case is "Assert.That.IsOfType<Dog>(animal);" - /// More documentation is at "https://github.com/Microsoft/testfx-docs". - /// - public static Assert That - { - get - { - if (that == null) - { - that = new Assert(); - } - - return that; - } - } - - #endregion - - #region Boolean - - /// - /// Tests whether the specified condition is true and throws an exception - /// if the condition is false. - /// - /// - /// The condition the test expects to be true. - /// - /// - /// Thrown if is false. - /// - public static void IsTrue(bool condition) - { - IsTrue(condition, string.Empty, null); - } - - /// - /// Tests whether the specified condition is true and throws an exception - /// if the condition is false. - /// - /// - /// The condition the test expects to be true. - /// - /// - /// The message to include in the exception when - /// is false. The message is shown in test results. - /// - /// - /// Thrown if is false. - /// - public static void IsTrue(bool condition, string message) - { - IsTrue(condition, message, null); - } - - /// - /// Tests whether the specified condition is true and throws an exception - /// if the condition is false. - /// - /// - /// The condition the test expects to be true. - /// - /// - /// The message to include in the exception when - /// is false. The message is shown in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is false. - /// - public static void IsTrue(bool condition, string message, params object[] parameters) - { - if (!condition) - { - HandleFail("Assert.IsTrue", message, parameters); - } - } - - /// - /// Tests whether the specified condition is false and throws an exception - /// if the condition is true. - /// - /// - /// The condition the test expects to be false. - /// - /// - /// Thrown if is true. - /// - public static void IsFalse(bool condition) - { - IsFalse(condition, string.Empty, null); - } - - /// - /// Tests whether the specified condition is false and throws an exception - /// if the condition is true. - /// - /// - /// The condition the test expects to be false. - /// - /// - /// The message to include in the exception when - /// is true. The message is shown in test results. - /// - /// - /// Thrown if is true. - /// - public static void IsFalse(bool condition, string message) - { - IsFalse(condition, message, null); - } - - /// - /// Tests whether the specified condition is false and throws an exception - /// if the condition is true. - /// - /// - /// The condition the test expects to be false. - /// - /// - /// The message to include in the exception when - /// is true. The message is shown in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is true. - /// - public static void IsFalse(bool condition, string message, params object[] parameters) - { - if (condition) - { - HandleFail("Assert.IsFalse", message, parameters); - } - } - - #endregion - - #region Null - - /// - /// Tests whether the specified object is null and throws an exception - /// if it is not. - /// - /// - /// The object the test expects to be null. - /// - /// - /// Thrown if is not null. - /// - public static void IsNull(object value) - { - IsNull(value, string.Empty, null); - } - - /// - /// Tests whether the specified object is null and throws an exception - /// if it is not. - /// - /// - /// The object the test expects to be null. - /// - /// - /// The message to include in the exception when - /// is not null. The message is shown in test results. - /// - /// - /// Thrown if is not null. - /// - public static void IsNull(object value, string message) - { - IsNull(value, message, null); - } - - /// - /// Tests whether the specified object is null and throws an exception - /// if it is not. - /// - /// - /// The object the test expects to be null. - /// - /// - /// The message to include in the exception when - /// is not null. The message is shown in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is not null. - /// - public static void IsNull(object value, string message, params object[] parameters) - { - if (value != null) - { - HandleFail("Assert.IsNull", message, parameters); - } - } - - /// - /// Tests whether the specified object is non-null and throws an exception - /// if it is null. - /// - /// - /// The object the test expects not to be null. - /// - /// - /// Thrown if is null. - /// - public static void IsNotNull(object value) - { - IsNotNull(value, string.Empty, null); - } - - /// - /// Tests whether the specified object is non-null and throws an exception - /// if it is null. - /// - /// - /// The object the test expects not to be null. - /// - /// - /// The message to include in the exception when - /// is null. The message is shown in test results. - /// - /// - /// Thrown if is null. - /// - public static void IsNotNull(object value, string message) - { - IsNotNull(value, message, null); - } - - /// - /// Tests whether the specified object is non-null and throws an exception - /// if it is null. - /// - /// - /// The object the test expects not to be null. - /// - /// - /// The message to include in the exception when - /// is null. The message is shown in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is null. - /// - public static void IsNotNull(object value, string message, params object[] parameters) - { - if (value == null) - { - HandleFail("Assert.IsNotNull", message, parameters); - } - } - - #endregion - - #region AreSame - - /// - /// Tests whether the specified objects both refer to the same object and - /// throws an exception if the two inputs do not refer to the same object. - /// - /// - /// The first object to compare. This is the value the test expects. - /// - /// - /// The second object to compare. This is the value produced by the code under test. - /// - /// - /// Thrown if does not refer to the same object - /// as . - /// - public static void AreSame(object expected, object actual) - { - AreSame(expected, actual, string.Empty, null); - } - - /// - /// Tests whether the specified objects both refer to the same object and - /// throws an exception if the two inputs do not refer to the same object. - /// - /// - /// The first object to compare. This is the value the test expects. - /// - /// - /// The second object to compare. This is the value produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is not the same as . The message is shown - /// in test results. - /// - /// - /// Thrown if does not refer to the same object - /// as . - /// - public static void AreSame(object expected, object actual, string message) - { - AreSame(expected, actual, message, null); - } - - /// - /// Tests whether the specified objects both refer to the same object and - /// throws an exception if the two inputs do not refer to the same object. - /// - /// - /// The first object to compare. This is the value the test expects. - /// - /// - /// The second object to compare. This is the value produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is not the same as . The message is shown - /// in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if does not refer to the same object - /// as . - /// - public static void AreSame(object expected, object actual, string message, params object[] parameters) - { - if (!ReferenceEquals(expected, actual)) - { - string finalMessage = message; - - ValueType valExpected = expected as ValueType; - if (valExpected != null) - { - ValueType valActual = actual as ValueType; - if (valActual != null) - { - finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreSameGivenValues, - message == null ? string.Empty : ReplaceNulls(message)); - } - } - - HandleFail("Assert.AreSame", finalMessage, parameters); - } - } - - /// - /// Tests whether the specified objects refer to different objects and - /// throws an exception if the two inputs refer to the same object. - /// - /// - /// The first object to compare. This is the value the test expects not - /// to match . - /// - /// - /// The second object to compare. This is the value produced by the code under test. - /// - /// - /// Thrown if refers to the same object - /// as . - /// - public static void AreNotSame(object notExpected, object actual) - { - AreNotSame(notExpected, actual, string.Empty, null); - } - - /// - /// Tests whether the specified objects refer to different objects and - /// throws an exception if the two inputs refer to the same object. - /// - /// - /// The first object to compare. This is the value the test expects not - /// to match . - /// - /// - /// The second object to compare. This is the value produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is the same as . The message is shown in - /// test results. - /// - /// - /// Thrown if refers to the same object - /// as . - /// - public static void AreNotSame(object notExpected, object actual, string message) - { - AreNotSame(notExpected, actual, message, null); - } - - /// - /// Tests whether the specified objects refer to different objects and - /// throws an exception if the two inputs refer to the same object. - /// - /// - /// The first object to compare. This is the value the test expects not - /// to match . - /// - /// - /// The second object to compare. This is the value produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is the same as . The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if refers to the same object - /// as . - /// - public static void AreNotSame(object notExpected, object actual, string message, params object[] parameters) - { - if (ReferenceEquals(notExpected, actual)) - { - HandleFail("Assert.AreNotSame", message, parameters); - } - } - - #endregion - - #region AreEqual - - /// - /// Tests whether the specified values are equal and throws an exception - /// if the two values are not equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The type of values to compare. - /// - /// - /// The first value to compare. This is the value the tests expects. - /// - /// - /// The second value to compare. This is the value produced by the code under test. - /// - /// - /// Thrown if is not equal to . - /// - public static void AreEqual(T expected, T actual) - { - AreEqual(expected, actual, string.Empty, null); - } - - /// - /// Tests whether the specified values are equal and throws an exception - /// if the two values are not equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The type of values to compare. - /// - /// - /// The first value to compare. This is the value the tests expects. - /// - /// - /// The second value to compare. This is the value produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is not equal to . The message is shown in - /// test results. - /// - /// - /// Thrown if is not equal to - /// . - /// - public static void AreEqual(T expected, T actual, string message) - { - AreEqual(expected, actual, message, null); - } - - /// - /// Tests whether the specified values are equal and throws an exception - /// if the two values are not equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The type of values to compare. - /// - /// - /// The first value to compare. This is the value the tests expects. - /// - /// - /// The second value to compare. This is the value produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is not equal to . The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is not equal to - /// . - /// - public static void AreEqual(T expected, T actual, string message, params object[] parameters) - { - if (!object.Equals(expected, actual)) - { - string finalMessage; - if (actual != null && expected != null && !actual.GetType().Equals(expected.GetType())) - { - // This is for cases like: Assert.AreEqual(42L, 42) -> Expected: <42>, Actual: <42> - finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreEqualDifferentTypesFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - ReplaceNulls(expected), - expected.GetType().FullName, - ReplaceNulls(actual), - actual.GetType().FullName); - } - else - { - finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreEqualFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - ReplaceNulls(expected), - ReplaceNulls(actual)); - } - - HandleFail("Assert.AreEqual", finalMessage, parameters); - } - } - - /// - /// Tests whether the specified values are unequal and throws an exception - /// if the two values are equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The type of values to compare. - /// - /// - /// The first value to compare. This is the value the test expects not - /// to match . - /// - /// - /// The second value to compare. This is the value produced by the code under test. - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(T notExpected, T actual) - { - AreNotEqual(notExpected, actual, string.Empty, null); - } - - /// - /// Tests whether the specified values are unequal and throws an exception - /// if the two values are equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The type of values to compare. - /// - /// - /// The first value to compare. This is the value the test expects not - /// to match . - /// - /// - /// The second value to compare. This is the value produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is equal to . The message is shown in - /// test results. - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(T notExpected, T actual, string message) - { - AreNotEqual(notExpected, actual, message, null); - } - - /// - /// Tests whether the specified values are unequal and throws an exception - /// if the two values are equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The type of values to compare. - /// - /// - /// The first value to compare. This is the value the test expects not - /// to match . - /// - /// - /// The second value to compare. This is the value produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is equal to . The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(T notExpected, T actual, string message, params object[] parameters) - { - if (object.Equals(notExpected, actual)) - { - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreNotEqualFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - ReplaceNulls(notExpected), - ReplaceNulls(actual)); - HandleFail("Assert.AreNotEqual", finalMessage, parameters); - } - } - - /// - /// Tests whether the specified objects are equal and throws an exception - /// if the two objects are not equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The first object to compare. This is the object the tests expects. - /// - /// - /// The second object to compare. This is the object produced by the code under test. - /// - /// - /// Thrown if is not equal to - /// . - /// - public static void AreEqual(object expected, object actual) - { - AreEqual(expected, actual, string.Empty, null); - } - - /// - /// Tests whether the specified objects are equal and throws an exception - /// if the two objects are not equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The first object to compare. This is the object the tests expects. - /// - /// - /// The second object to compare. This is the object produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is not equal to . The message is shown in - /// test results. - /// - /// - /// Thrown if is not equal to - /// . - /// - public static void AreEqual(object expected, object actual, string message) - { - AreEqual(expected, actual, message, null); - } - - /// - /// Tests whether the specified objects are equal and throws an exception - /// if the two objects are not equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The first object to compare. This is the object the tests expects. - /// - /// - /// The second object to compare. This is the object produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is not equal to . The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is not equal to - /// . - /// - public static void AreEqual(object expected, object actual, string message, params object[] parameters) - { - AreEqual(expected, actual, message, parameters); - } - - /// - /// Tests whether the specified objects are unequal and throws an exception - /// if the two objects are equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The first object to compare. This is the value the test expects not - /// to match . - /// - /// - /// The second object to compare. This is the object produced by the code under test. - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(object notExpected, object actual) - { - AreNotEqual(notExpected, actual, string.Empty, null); - } - - /// - /// Tests whether the specified objects are unequal and throws an exception - /// if the two objects are equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The first object to compare. This is the value the test expects not - /// to match . - /// - /// - /// The second object to compare. This is the object produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is equal to . The message is shown in - /// test results. - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(object notExpected, object actual, string message) - { - AreNotEqual(notExpected, actual, message, null); - } - - /// - /// Tests whether the specified objects are unequal and throws an exception - /// if the two objects are equal. Different numeric types are treated - /// as unequal even if the logical values are equal. 42L is not equal to 42. - /// - /// - /// The first object to compare. This is the value the test expects not - /// to match . - /// - /// - /// The second object to compare. This is the object produced by the code under test. - /// - /// - /// The message to include in the exception when - /// is equal to . The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(object notExpected, object actual, string message, params object[] parameters) - { - AreNotEqual(notExpected, actual, message, parameters); - } - - /// - /// Tests whether the specified floats are equal and throws an exception - /// if they are not equal. - /// - /// - /// The first float to compare. This is the float the tests expects. - /// - /// - /// The second float to compare. This is the float produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by more than . - /// - /// - /// Thrown if is not equal to - /// . - /// - public static void AreEqual(float expected, float actual, float delta) - { - AreEqual(expected, actual, delta, string.Empty, null); - } - - /// - /// Tests whether the specified floats are equal and throws an exception - /// if they are not equal. - /// - /// - /// The first float to compare. This is the float the tests expects. - /// - /// - /// The second float to compare. This is the float produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by more than . - /// - /// - /// The message to include in the exception when - /// is different than by more than - /// . The message is shown in test results. - /// - /// - /// Thrown if is not equal to - /// . - /// - public static void AreEqual(float expected, float actual, float delta, string message) - { - AreEqual(expected, actual, delta, message, null); - } - - /// - /// Tests whether the specified floats are equal and throws an exception - /// if they are not equal. - /// - /// - /// The first float to compare. This is the float the tests expects. - /// - /// - /// The second float to compare. This is the float produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by more than . - /// - /// - /// The message to include in the exception when - /// is different than by more than - /// . The message is shown in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is not equal to - /// . - /// - public static void AreEqual(float expected, float actual, float delta, string message, params object[] parameters) - { - if (float.IsNaN(expected) || float.IsNaN(actual) || float.IsNaN(delta)) - { - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreEqualDeltaFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - expected.ToString(CultureInfo.CurrentCulture.NumberFormat), - actual.ToString(CultureInfo.CurrentCulture.NumberFormat), - delta.ToString(CultureInfo.CurrentCulture.NumberFormat)); - HandleFail("Assert.AreEqual", finalMessage, parameters); - } - - if (Math.Abs(expected - actual) > delta) - { - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreEqualDeltaFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - expected.ToString(CultureInfo.CurrentCulture.NumberFormat), - actual.ToString(CultureInfo.CurrentCulture.NumberFormat), - delta.ToString(CultureInfo.CurrentCulture.NumberFormat)); - HandleFail("Assert.AreEqual", finalMessage, parameters); - } - } - - /// - /// Tests whether the specified floats are unequal and throws an exception - /// if they are equal. - /// - /// - /// The first float to compare. This is the float the test expects not to - /// match . - /// - /// - /// The second float to compare. This is the float produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by at most . - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(float notExpected, float actual, float delta) - { - AreNotEqual(notExpected, actual, delta, string.Empty, null); - } - - /// - /// Tests whether the specified floats are unequal and throws an exception - /// if they are equal. - /// - /// - /// The first float to compare. This is the float the test expects not to - /// match . - /// - /// - /// The second float to compare. This is the float produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by at most . - /// - /// - /// The message to include in the exception when - /// is equal to or different by less than - /// . The message is shown in test results. - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(float notExpected, float actual, float delta, string message) - { - AreNotEqual(notExpected, actual, delta, message, null); - } - - /// - /// Tests whether the specified floats are unequal and throws an exception - /// if they are equal. - /// - /// - /// The first float to compare. This is the float the test expects not to - /// match . - /// - /// - /// The second float to compare. This is the float produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by at most . - /// - /// - /// The message to include in the exception when - /// is equal to or different by less than - /// . The message is shown in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(float notExpected, float actual, float delta, string message, params object[] parameters) - { - if (Math.Abs(notExpected - actual) <= delta) - { - var finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreNotEqualDeltaFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - notExpected.ToString(CultureInfo.CurrentCulture.NumberFormat), - actual.ToString(CultureInfo.CurrentCulture.NumberFormat), - delta.ToString(CultureInfo.CurrentCulture.NumberFormat)); - HandleFail("Assert.AreNotEqual", finalMessage, parameters); - } - } - - /// - /// Tests whether the specified doubles are equal and throws an exception - /// if they are not equal. - /// - /// - /// The first double to compare. This is the double the tests expects. - /// - /// - /// The second double to compare. This is the double produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by more than . - /// - /// - /// Thrown if is not equal to - /// . - /// - public static void AreEqual(double expected, double actual, double delta) - { - AreEqual(expected, actual, delta, string.Empty, null); - } - - /// - /// Tests whether the specified doubles are equal and throws an exception - /// if they are not equal. - /// - /// - /// The first double to compare. This is the double the tests expects. - /// - /// - /// The second double to compare. This is the double produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by more than . - /// - /// - /// The message to include in the exception when - /// is different than by more than - /// . The message is shown in test results. - /// - /// - /// Thrown if is not equal to . - /// - public static void AreEqual(double expected, double actual, double delta, string message) - { - AreEqual(expected, actual, delta, message, null); - } - - /// - /// Tests whether the specified doubles are equal and throws an exception - /// if they are not equal. - /// - /// - /// The first double to compare. This is the double the tests expects. - /// - /// - /// The second double to compare. This is the double produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by more than . - /// - /// - /// The message to include in the exception when - /// is different than by more than - /// . The message is shown in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is not equal to . - /// - public static void AreEqual(double expected, double actual, double delta, string message, params object[] parameters) - { - if (double.IsNaN(expected) || double.IsNaN(actual) || double.IsNaN(delta)) - { - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreEqualDeltaFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - expected.ToString(CultureInfo.CurrentCulture.NumberFormat), - actual.ToString(CultureInfo.CurrentCulture.NumberFormat), - delta.ToString(CultureInfo.CurrentCulture.NumberFormat)); - HandleFail("Assert.AreEqual", finalMessage, parameters); - } - - if (Math.Abs(expected - actual) > delta) - { - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreEqualDeltaFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - expected.ToString(CultureInfo.CurrentCulture.NumberFormat), - actual.ToString(CultureInfo.CurrentCulture.NumberFormat), - delta.ToString(CultureInfo.CurrentCulture.NumberFormat)); - HandleFail("Assert.AreEqual", finalMessage, parameters); - } - } - - /// - /// Tests whether the specified doubles are unequal and throws an exception - /// if they are equal. - /// - /// - /// The first double to compare. This is the double the test expects not to - /// match . - /// - /// - /// The second double to compare. This is the double produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by at most . - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(double notExpected, double actual, double delta) - { - AreNotEqual(notExpected, actual, delta, string.Empty, null); - } - - /// - /// Tests whether the specified doubles are unequal and throws an exception - /// if they are equal. - /// - /// - /// The first double to compare. This is the double the test expects not to - /// match . - /// - /// - /// The second double to compare. This is the double produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by at most . - /// - /// - /// The message to include in the exception when - /// is equal to or different by less than - /// . The message is shown in test results. - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(double notExpected, double actual, double delta, string message) - { - AreNotEqual(notExpected, actual, delta, message, null); - } - - /// - /// Tests whether the specified doubles are unequal and throws an exception - /// if they are equal. - /// - /// - /// The first double to compare. This is the double the test expects not to - /// match . - /// - /// - /// The second double to compare. This is the double produced by the code under test. - /// - /// - /// The required accuracy. An exception will be thrown only if - /// is different than - /// by at most . - /// - /// - /// The message to include in the exception when - /// is equal to or different by less than - /// . The message is shown in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(double notExpected, double actual, double delta, string message, params object[] parameters) - { - if (Math.Abs(notExpected - actual) <= delta) - { - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreNotEqualDeltaFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - notExpected.ToString(CultureInfo.CurrentCulture.NumberFormat), - actual.ToString(CultureInfo.CurrentCulture.NumberFormat), - delta.ToString(CultureInfo.CurrentCulture.NumberFormat)); - HandleFail("Assert.AreNotEqual", finalMessage, parameters); - } - } - - /// - /// Tests whether the specified strings are equal and throws an exception - /// if they are not equal. The invariant culture is used for the comparison. - /// - /// - /// The first string to compare. This is the string the tests expects. - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// Thrown if is not equal to . - /// - public static void AreEqual(string expected, string actual, bool ignoreCase) - { - Assert.AreEqual(expected, actual, ignoreCase, string.Empty, null); - } - - /// - /// Tests whether the specified strings are equal and throws an exception - /// if they are not equal. The invariant culture is used for the comparison. - /// - /// - /// The first string to compare. This is the string the tests expects. - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// The message to include in the exception when - /// is not equal to . The message is shown in - /// test results. - /// - /// - /// Thrown if is not equal to . - /// - public static void AreEqual(string expected, string actual, bool ignoreCase, string message) - { - AreEqual(expected, actual, ignoreCase, message, null); - } - - /// - /// Tests whether the specified strings are equal and throws an exception - /// if they are not equal. The invariant culture is used for the comparison. - /// - /// - /// The first string to compare. This is the string the tests expects. - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// The message to include in the exception when - /// is not equal to . The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is not equal to . - /// - public static void AreEqual(string expected, string actual, bool ignoreCase, string message, params object[] parameters) - { - AreEqual(expected, actual, ignoreCase, CultureInfo.InvariantCulture, message, parameters); - } - - /// - /// Tests whether the specified strings are equal and throws an exception - /// if they are not equal. - /// - /// - /// The first string to compare. This is the string the tests expects. - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// A CultureInfo object that supplies culture-specific comparison information. - /// - /// - /// Thrown if is not equal to . - /// - public static void AreEqual(string expected, string actual, bool ignoreCase, CultureInfo culture) - { - AreEqual(expected, actual, ignoreCase, culture, string.Empty, null); - } - - /// - /// Tests whether the specified strings are equal and throws an exception - /// if they are not equal. - /// - /// - /// The first string to compare. This is the string the tests expects. - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// A CultureInfo object that supplies culture-specific comparison information. - /// - /// - /// The message to include in the exception when - /// is not equal to . The message is shown in - /// test results. - /// - /// - /// Thrown if is not equal to . - /// - public static void AreEqual(string expected, string actual, bool ignoreCase, CultureInfo culture, string message) - { - AreEqual(expected, actual, ignoreCase, culture, message, null); - } - - /// - /// Tests whether the specified strings are equal and throws an exception - /// if they are not equal. - /// - /// - /// The first string to compare. This is the string the tests expects. - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// A CultureInfo object that supplies culture-specific comparison information. - /// - /// - /// The message to include in the exception when - /// is not equal to . The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is not equal to . - /// - public static void AreEqual(string expected, string actual, bool ignoreCase, CultureInfo culture, string message, params object[] parameters) - { - CheckParameterNotNull(culture, "Assert.AreEqual", "culture", string.Empty); - if (CompareInternal(expected, actual, ignoreCase, culture) != 0) - { - string finalMessage; - - // Comparison failed. Check if it was a case-only failure. - if (!ignoreCase && - CompareInternal(expected, actual, ignoreCase, culture) == 0) - { - finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreEqualCaseFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - ReplaceNulls(expected), - ReplaceNulls(actual)); - } - else - { - finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreEqualFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - ReplaceNulls(expected), - ReplaceNulls(actual)); - } - - HandleFail("Assert.AreEqual", finalMessage, parameters); - } - } - - /// - /// Tests whether the specified strings are unequal and throws an exception - /// if they are equal. The invariant culture is used for the comparison. - /// - /// - /// The first string to compare. This is the string the test expects not to - /// match . - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(string notExpected, string actual, bool ignoreCase) - { - AreNotEqual(notExpected, actual, ignoreCase, string.Empty, null); - } - - /// - /// Tests whether the specified strings are unequal and throws an exception - /// if they are equal. The invariant culture is used for the comparison. - /// - /// - /// The first string to compare. This is the string the test expects not to - /// match . - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// The message to include in the exception when - /// is equal to . The message is shown in - /// test results. - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(string notExpected, string actual, bool ignoreCase, string message) - { - AreNotEqual(notExpected, actual, ignoreCase, message, null); - } - - /// - /// Tests whether the specified strings are unequal and throws an exception - /// if they are equal. The invariant culture is used for the comparison. - /// - /// - /// The first string to compare. This is the string the test expects not to - /// match . - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// The message to include in the exception when - /// is equal to . The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(string notExpected, string actual, bool ignoreCase, string message, params object[] parameters) - { - AreNotEqual(notExpected, actual, ignoreCase, CultureInfo.InvariantCulture, message, parameters); - } - - /// - /// Tests whether the specified strings are unequal and throws an exception - /// if they are equal. - /// - /// - /// The first string to compare. This is the string the test expects not to - /// match . - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// A CultureInfo object that supplies culture-specific comparison information. - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(string notExpected, string actual, bool ignoreCase, CultureInfo culture) - { - AreNotEqual(notExpected, actual, ignoreCase, culture, string.Empty, null); - } - - /// - /// Tests whether the specified strings are unequal and throws an exception - /// if they are equal. - /// - /// - /// The first string to compare. This is the string the test expects not to - /// match . - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// A CultureInfo object that supplies culture-specific comparison information. - /// - /// - /// The message to include in the exception when - /// is equal to . The message is shown in - /// test results. - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(string notExpected, string actual, bool ignoreCase, CultureInfo culture, string message) - { - AreNotEqual(notExpected, actual, ignoreCase, culture, message, null); - } - - /// - /// Tests whether the specified strings are unequal and throws an exception - /// if they are equal. - /// - /// - /// The first string to compare. This is the string the test expects not to - /// match . - /// - /// - /// The second string to compare. This is the string produced by the code under test. - /// - /// - /// A Boolean indicating a case-sensitive or insensitive comparison. (true - /// indicates a case-insensitive comparison.) - /// - /// - /// A CultureInfo object that supplies culture-specific comparison information. - /// - /// - /// The message to include in the exception when - /// is equal to . The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is equal to . - /// - public static void AreNotEqual(string notExpected, string actual, bool ignoreCase, CultureInfo culture, string message, params object[] parameters) - { - CheckParameterNotNull(culture, "Assert.AreNotEqual", "culture", string.Empty); - if (CompareInternal(notExpected, actual, ignoreCase, culture) == 0) - { - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.AreNotEqualFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - ReplaceNulls(notExpected), - ReplaceNulls(actual)); - HandleFail("Assert.AreNotEqual", finalMessage, parameters); - } - } - - #endregion - - #region Type - - /// - /// Tests whether the specified object is an instance of the expected - /// type and throws an exception if the expected type is not in the - /// inheritance hierarchy of the object. - /// - /// - /// The object the test expects to be of the specified type. - /// - /// - /// The expected type of . - /// - /// - /// Thrown if is null or - /// is not in the inheritance hierarchy - /// of . - /// - public static void IsInstanceOfType(object value, Type expectedType) - { - IsInstanceOfType(value, expectedType, string.Empty, null); - } - - /// - /// Tests whether the specified object is an instance of the expected - /// type and throws an exception if the expected type is not in the - /// inheritance hierarchy of the object. - /// - /// - /// The object the test expects to be of the specified type. - /// - /// - /// The expected type of . - /// - /// - /// The message to include in the exception when - /// is not an instance of . The message is - /// shown in test results. - /// - /// - /// Thrown if is null or - /// is not in the inheritance hierarchy - /// of . - /// - public static void IsInstanceOfType(object value, Type expectedType, string message) - { - IsInstanceOfType(value, expectedType, message, null); - } - - /// - /// Tests whether the specified object is an instance of the expected - /// type and throws an exception if the expected type is not in the - /// inheritance hierarchy of the object. - /// - /// - /// The object the test expects to be of the specified type. - /// - /// - /// The expected type of . - /// - /// - /// The message to include in the exception when - /// is not an instance of . The message is - /// shown in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is null or - /// is not in the inheritance hierarchy - /// of . - /// - public static void IsInstanceOfType(object value, Type expectedType, string message, params object[] parameters) - { - if (expectedType == null || value == null) - { - HandleFail("Assert.IsInstanceOfType", message, parameters); - } - - var elementTypeInfo = value.GetType().GetTypeInfo(); - var expectedTypeInfo = expectedType.GetTypeInfo(); - if (!expectedTypeInfo.IsAssignableFrom(elementTypeInfo)) - { - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.IsInstanceOfFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - expectedType.ToString(), - value.GetType().ToString()); - HandleFail("Assert.IsInstanceOfType", finalMessage, parameters); - } - } - - /// - /// Tests whether the specified object is not an instance of the wrong - /// type and throws an exception if the specified type is in the - /// inheritance hierarchy of the object. - /// - /// - /// The object the test expects not to be of the specified type. - /// - /// - /// The type that should not be. - /// - /// - /// Thrown if is not null and - /// is in the inheritance hierarchy - /// of . - /// - public static void IsNotInstanceOfType(object value, Type wrongType) - { - IsNotInstanceOfType(value, wrongType, string.Empty, null); - } - - /// - /// Tests whether the specified object is not an instance of the wrong - /// type and throws an exception if the specified type is in the - /// inheritance hierarchy of the object. - /// - /// - /// The object the test expects not to be of the specified type. - /// - /// - /// The type that should not be. - /// - /// - /// The message to include in the exception when - /// is an instance of . The message is shown - /// in test results. - /// - /// - /// Thrown if is not null and - /// is in the inheritance hierarchy - /// of . - /// - public static void IsNotInstanceOfType(object value, Type wrongType, string message) - { - IsNotInstanceOfType(value, wrongType, message, null); - } - - /// - /// Tests whether the specified object is not an instance of the wrong - /// type and throws an exception if the specified type is in the - /// inheritance hierarchy of the object. - /// - /// - /// The object the test expects not to be of the specified type. - /// - /// - /// The type that should not be. - /// - /// - /// The message to include in the exception when - /// is an instance of . The message is shown - /// in test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Thrown if is not null and - /// is in the inheritance hierarchy - /// of . - /// - public static void IsNotInstanceOfType(object value, Type wrongType, string message, params object[] parameters) - { - if (wrongType == null || value == null) - { - HandleFail("Assert.IsNotInstanceOfType", message, parameters); - } - - var elementTypeInfo = value.GetType().GetTypeInfo(); - var expectedTypeInfo = wrongType.GetTypeInfo(); - if (expectedTypeInfo.IsAssignableFrom(elementTypeInfo)) - { - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.IsNotInstanceOfFailMsg, - message == null ? string.Empty : ReplaceNulls(message), - wrongType.ToString(), - value.GetType().ToString()); - HandleFail("Assert.IsNotInstanceOfType", finalMessage, parameters); - } - } - - #endregion - - #region Fail - - /// - /// Throws an AssertFailedException. - /// - /// - /// Always thrown. - /// - public static void Fail() - { - Fail(string.Empty, null); - } - - /// - /// Throws an AssertFailedException. - /// - /// - /// The message to include in the exception. The message is shown in - /// test results. - /// - /// - /// Always thrown. - /// - public static void Fail(string message) - { - Fail(message, null); - } - - /// - /// Throws an AssertFailedException. - /// - /// - /// The message to include in the exception. The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Always thrown. - /// - public static void Fail(string message, params object[] parameters) - { - HandleFail("Assert.Fail", message, parameters); - } - - #endregion - - #region Inconclusive - - /// - /// Throws an AssertInconclusiveException. - /// - /// - /// Always thrown. - /// - public static void Inconclusive() - { - Inconclusive(string.Empty, null); - } - - /// - /// Throws an AssertInconclusiveException. - /// - /// - /// The message to include in the exception. The message is shown in - /// test results. - /// - /// - /// Always thrown. - /// - public static void Inconclusive(string message) - { - Inconclusive(message, null); - } - - /// - /// Throws an AssertInconclusiveException. - /// - /// - /// The message to include in the exception. The message is shown in - /// test results. - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Always thrown. - /// - public static void Inconclusive(string message, params object[] parameters) - { - string finalMessage = string.Empty; - if (!string.IsNullOrEmpty(message)) - { - if (parameters == null) - { - finalMessage = ReplaceNulls(message); - } - else - { - finalMessage = string.Format(CultureInfo.CurrentCulture, ReplaceNulls(message), parameters); - } - } - - throw new AssertInconclusiveException(string.Format(CultureInfo.CurrentCulture, Resource.AssertionFailed, "Assert.Inconclusive", finalMessage)); - } - - #endregion - - #region Equals Assertion - - /// - /// Static equals overloads are used for comparing instances of two types for reference - /// equality. This method should not be used for comparison of two instances for - /// equality. This object will always throw with Assert.Fail. Please use - /// Assert.AreEqual and associated overloads in your unit tests. - /// - /// Object A - /// Object B - /// False, always. - [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "We want to compare 'object A' with 'object B', so it makes sense to have 'obj' in the parameter name")] - public static new bool Equals(object objA, object objB) - { - Fail(Resource.DoNotUseAssertEquals); - return false; - } - - #endregion Equals Assertion - - #region ThrowsException - - /// - /// Tests whether the code specified by delegate throws exact given exception of type (and not of derived type) - /// and throws - /// - /// AssertFailedException - /// - /// if code does not throws exception or throws exception of type other than . - /// - /// - /// Delegate to code to be tested and which is expected to throw exception. - /// - /// - /// Type of exception expected to be thrown. - /// - /// - /// Thrown if does not throws exception of type . - /// - /// - /// The exception that was thrown. - /// - public static T ThrowsException(Action action) - where T : Exception - { - return ThrowsException(action, string.Empty, null); - } - - /// - /// Tests whether the code specified by delegate throws exact given exception of type (and not of derived type) - /// and throws - /// - /// AssertFailedException - /// - /// if code does not throws exception or throws exception of type other than . - /// - /// - /// Delegate to code to be tested and which is expected to throw exception. - /// - /// - /// The message to include in the exception when - /// does not throws exception of type . - /// - /// - /// Type of exception expected to be thrown. - /// - /// - /// Thrown if does not throws exception of type . - /// - /// - /// The exception that was thrown. - /// - public static T ThrowsException(Action action, string message) - where T : Exception - { - return ThrowsException(action, message, null); - } - - /// - /// Tests whether the code specified by delegate throws exact given exception of type (and not of derived type) - /// and throws - /// - /// AssertFailedException - /// - /// if code does not throws exception or throws exception of type other than . - /// - /// - /// Delegate to code to be tested and which is expected to throw exception. - /// - /// - /// Type of exception expected to be thrown. - /// - /// - /// Thrown if does not throws exception of type . - /// - /// - /// The exception that was thrown. - /// - public static T ThrowsException(Func action) - where T : Exception - { - return ThrowsException(action, string.Empty, null); - } - - /// - /// Tests whether the code specified by delegate throws exact given exception of type (and not of derived type) - /// and throws - /// - /// AssertFailedException - /// - /// if code does not throws exception or throws exception of type other than . - /// - /// - /// Delegate to code to be tested and which is expected to throw exception. - /// - /// - /// The message to include in the exception when - /// does not throws exception of type . - /// - /// - /// Type of exception expected to be thrown. - /// - /// - /// Thrown if does not throws exception of type . - /// - /// - /// The exception that was thrown. - /// - public static T ThrowsException(Func action, string message) - where T : Exception - { - return ThrowsException(action, message, null); - } - - /// - /// Tests whether the code specified by delegate throws exact given exception of type (and not of derived type) - /// and throws - /// - /// AssertFailedException - /// - /// if code does not throws exception or throws exception of type other than . - /// - /// - /// Delegate to code to be tested and which is expected to throw exception. - /// - /// - /// The message to include in the exception when - /// does not throws exception of type . - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Type of exception expected to be thrown. - /// - /// - /// Thrown if does not throw exception of type . - /// - /// - /// The exception that was thrown. - /// - public static T ThrowsException(Func action, string message, params object[] parameters) - where T : Exception - { - return ThrowsException(() => { action(); }, message, parameters); - } - - /// - /// Tests whether the code specified by delegate throws exact given exception of type (and not of derived type) - /// and throws - /// - /// AssertFailedException - /// - /// if code does not throws exception or throws exception of type other than . - /// - /// - /// Delegate to code to be tested and which is expected to throw exception. - /// - /// - /// The message to include in the exception when - /// does not throws exception of type . - /// - /// - /// An array of parameters to use when formatting . - /// - /// - /// Type of exception expected to be thrown. - /// - /// - /// Thrown if does not throws exception of type . - /// - /// - /// The exception that was thrown. - /// - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - public static T ThrowsException(Action action, string message, params object[] parameters) - where T : Exception - { - var finalMessage = string.Empty; - - if (action == null) - { - throw new ArgumentNullException("action"); - } - - if (message == null) - { - throw new ArgumentNullException("message"); - } - - try - { - action(); - } - catch (Exception ex) - { - if (!typeof(T).Equals(ex.GetType())) - { - finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.WrongExceptionThrown, - ReplaceNulls(message), - typeof(T).Name, - ex.GetType().Name, - ex.Message, - ex.StackTrace); - HandleFail("Assert.ThrowsException", finalMessage, parameters); - } - - return (T)ex; - } - - finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.NoExceptionThrown, - ReplaceNulls(message), - typeof(T).Name); - HandleFail("Assert.ThrowsException", finalMessage, parameters); - - // This will not hit, but need it for compiler. - return null; - } - - /// - /// Tests whether the code specified by delegate throws exact given exception of type (and not of derived type) - /// and throws - /// - /// AssertFailedException - /// - /// if code does not throws exception or throws exception of type other than . - /// - /// - /// Delegate to code to be tested and which is expected to throw exception. - /// - /// - /// Type of exception expected to be thrown. - /// - /// - /// Thrown if does not throws exception of type . - /// - /// - /// The executing the delegate. - /// - public static async Task ThrowsExceptionAsync(Func action) - where T : Exception - { - return await ThrowsExceptionAsync(action, string.Empty, null); - } - - /// - /// Tests whether the code specified by delegate throws exact given exception of type (and not of derived type) - /// and throws AssertFailedException if code does not throws exception or throws exception of type other than . - /// - /// Delegate to code to be tested and which is expected to throw exception. - /// - /// The message to include in the exception when - /// does not throws exception of type . - /// - /// Type of exception expected to be thrown. - /// - /// Thrown if does not throws exception of type . - /// - /// - /// The executing the delegate. - /// - public static async Task ThrowsExceptionAsync(Func action, string message) - where T : Exception - { - return await ThrowsExceptionAsync(action, message, null); - } - - /// - /// Tests whether the code specified by delegate throws exact given exception of type (and not of derived type) - /// and throws AssertFailedException if code does not throws exception or throws exception of type other than . - /// - /// Delegate to code to be tested and which is expected to throw exception. - /// - /// The message to include in the exception when - /// does not throws exception of type . - /// - /// - /// An array of parameters to use when formatting . - /// - /// Type of exception expected to be thrown. - /// - /// Thrown if does not throws exception of type . - /// - /// - /// The executing the delegate. - /// - public static async Task ThrowsExceptionAsync(Func action, string message, params object[] parameters) - where T : Exception - { - var finalMessage = string.Empty; - - if (action == null) - { - throw new ArgumentNullException("action"); - } - - if (message == null) - { - throw new ArgumentNullException("message"); - } - - try - { - await action(); - } - catch (Exception ex) - { - if (!typeof(T).Equals(ex.GetType())) - { - finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.WrongExceptionThrown, - ReplaceNulls(message), - typeof(T).Name, - ex.GetType().Name, - ex.Message, - ex.StackTrace); - HandleFail("Assert.ThrowsException", finalMessage, parameters); - } - - return (T)ex; - } - - finalMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.NoExceptionThrown, - ReplaceNulls(message), - typeof(T).Name); - HandleFail("Assert.ThrowsException", finalMessage, parameters); - - // This will not hit, but need it for compiler. - return null; - } - - #endregion - - #region Helpers - - /// - /// Replaces null characters ('\0') with "\\0". - /// - /// - /// The string to search. - /// - /// - /// The converted string with null characters replaced by "\\0". - /// - /// - /// This is only public and still present to preserve compatibility with the V1 framework. - /// - public static string ReplaceNullChars(string input) - { - if (string.IsNullOrEmpty(input)) - { - return input; - } - - return input.Replace("\0", "\\0"); - } - - /// - /// Helper function that creates and throws an AssertionFailedException - /// - /// - /// name of the assertion throwing an exception - /// - /// - /// message describing conditions for assertion failure - /// - /// - /// The parameters. - /// - internal static void HandleFail(string assertionName, string message, params object[] parameters) - { - string finalMessage = string.Empty; - if (!string.IsNullOrEmpty(message)) - { - if (parameters == null) - { - finalMessage = ReplaceNulls(message); - } - else - { - finalMessage = string.Format(CultureInfo.CurrentCulture, ReplaceNulls(message), parameters); - } - } - - throw new AssertFailedException(string.Format(CultureInfo.CurrentCulture, Resource.AssertionFailed, assertionName, finalMessage)); - } - - /// - /// Checks the parameter for valid conditions - /// - /// - /// The parameter. - /// - /// - /// The assertion Name. - /// - /// - /// parameter name - /// - /// - /// message for the invalid parameter exception - /// - /// - /// The parameters. - /// - internal static void CheckParameterNotNull(object param, string assertionName, string parameterName, string message, params object[] parameters) - { - if (param == null) - { - HandleFail(assertionName, string.Format(CultureInfo.CurrentCulture, Resource.NullParameterToAssert, parameterName, message), parameters); - } - } - - /// - /// Safely converts an object to a string, handling null values and null characters. - /// Null values are converted to "(null)". Null characters are converted to "\\0". - /// - /// - /// The object to convert to a string. - /// - /// - /// The converted string. - /// - [SuppressMessage("ReSharper", "RedundantToStringCall", Justification = "We are ensuring ToString() isn't overloaded in a way to misbehave")] - internal static string ReplaceNulls(object input) - { - // Use the localized "(null)" string for null values. - if (input == null) - { - return Resource.Common_NullInMessages.ToString(); - } - else - { - // Convert it to a string. - string inputString = input.ToString(); - - // Make sure the class didn't override ToString and return null. - if (inputString == null) - { - return Resource.Common_ObjectString.ToString(); - } - - return ReplaceNullChars(inputString); - } - } - - private static int CompareInternal(string expected, string actual, bool ignoreCase, CultureInfo culture) - { - return string.Compare(expected, actual, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); - } - - #endregion - } -} diff --git a/source/TestAdapter/Attributes/DeploymentItemAttribute.cs b/source/TestAdapter/Attributes/DeploymentItemAttribute.cs deleted file mode 100644 index 58a97e2..0000000 --- a/source/TestAdapter/Attributes/DeploymentItemAttribute.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - - /// - /// Used to specify deployment item (file or directory) for per-test deployment. - /// Can be specified on test class or test method. - /// Can have multiple instances of the attribute to specify more than one item. - /// The item path can be absolute or relative, if relative, it is relative to RunConfig.RelativePathRoot. - /// - /// - /// [DeploymentItem("file1.xml")] - /// [DeploymentItem("file2.xml", "DataFiles")] - /// [DeploymentItem("bin\Debug")] - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] - public sealed class DeploymentItemAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The file or directory to deploy. The path is relative to the build output directory. The item will be copied to the same directory as the deployed test assemblies. - public DeploymentItemAttribute(string path) - { - this.Path = path; - this.OutputDirectory = string.Empty; - } - - /// - /// Initializes a new instance of the class - /// - /// The relative or absolute path to the file or directory to deploy. The path is relative to the build output directory. The item will be copied to the same directory as the deployed test assemblies. - /// The path of the directory to which the items are to be copied. It can be either absolute or relative to the deployment directory. All files and directories identified by will be copied to this directory. - public DeploymentItemAttribute(string path, string outputDirectory) - { - this.Path = path; - this.OutputDirectory = outputDirectory; - } - - /// - /// Gets the path of the source file or folder to be copied. - /// - public string Path { get; } - - /// - /// Gets the path of the directory to which the item is copied. - /// - public string OutputDirectory { get; } - } -} diff --git a/source/TestAdapter/Attributes/ExecutionScope.cs b/source/TestAdapter/Attributes/ExecutionScope.cs deleted file mode 100644 index 7973501..0000000 --- a/source/TestAdapter/Attributes/ExecutionScope.cs +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - /// - /// Parallel execution mode. - /// - public enum ExecutionScope - { - /// - /// Each thread of execution will be handed a TestClass worth of tests to execute. - /// Within the TestClass, the test methods will execute serially. - /// - ClassLevel = 0, - - /// - /// Each thread of execution will be handed TestMethods to execute. - /// - MethodLevel = 1, - } -} \ No newline at end of file diff --git a/source/TestAdapter/Attributes/ExpectedExceptionAttribute.cs b/source/TestAdapter/Attributes/ExpectedExceptionAttribute.cs deleted file mode 100644 index cffd062..0000000 --- a/source/TestAdapter/Attributes/ExpectedExceptionAttribute.cs +++ /dev/null @@ -1,147 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Diagnostics; - using System.Globalization; - using System.Reflection; - - /// - /// Attribute that specifies to expect an exception of the specified type - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public sealed class ExpectedExceptionAttribute : ExpectedExceptionBaseAttribute - { - #region Constructors - - /// - /// Initializes a new instance of the class with the expected type - /// - /// Type of the expected exception - public ExpectedExceptionAttribute(Type exceptionType) - : this(exceptionType, string.Empty) - { - } - - /// - /// Initializes a new instance of the class with - /// the expected type and the message to include when no exception is thrown by the test. - /// - /// Type of the expected exception - /// - /// Message to include in the test result if the test fails due to not throwing an exception - /// - public ExpectedExceptionAttribute(Type exceptionType, string noExceptionMessage) - : base(noExceptionMessage) - { - if (exceptionType == null) - { - throw new ArgumentNullException("exceptionType"); - } - - if (!typeof(Exception).GetTypeInfo().IsAssignableFrom(exceptionType.GetTypeInfo())) - { - throw new ArgumentException( - Resource.UTF_ExpectedExceptionTypeMustDeriveFromException, - "exceptionType"); - } - - this.ExceptionType = exceptionType; - } - - #endregion - - #region Properties - - /// - /// Gets a value indicating the Type of the expected exception - /// - public Type ExceptionType - { - get; - private set; - } - - /// - /// Gets or sets a value indicating whether to allow types derived from the type of the expected exception to - /// qualify as expected - /// - public bool AllowDerivedTypes - { - get; - set; - } - - /// - /// Gets the message to include in the test result if the test fails due to not throwing an exception - /// - protected internal override string NoExceptionMessage - { - get - { - return string.Format( - CultureInfo.CurrentCulture, - Resource.UTF_TestMethodNoException, - this.ExceptionType.FullName, - this.SpecifiedNoExceptionMessage); - } - } - - #endregion - - #region Methods - - /// - /// Verifies that the type of the exception thrown by the unit test is expected - /// - /// The exception thrown by the unit test - protected internal override void Verify(Exception exception) - { - Debug.Assert(exception != null, "'exception' is null"); - - Type thrownExceptionType = exception.GetType(); - if (this.AllowDerivedTypes) - { - if (!this.ExceptionType.GetTypeInfo().IsAssignableFrom(thrownExceptionType.GetTypeInfo())) - { - // If the exception is an AssertFailedException or an AssertInconclusiveException, then rethrow it to - // preserve the test outcome and error message - this.RethrowIfAssertException(exception); - - string message = string.Format( - CultureInfo.CurrentCulture, - Resource.UTF_TestMethodWrongExceptionDerivedAllowed, - thrownExceptionType.FullName, - this.ExceptionType.FullName, - UtfHelper.GetExceptionMsg(exception)); - throw new Exception(message); - } - } - else - { - if (thrownExceptionType != this.ExceptionType) - { - // If the exception is an AssertFailedException or an AssertInconclusiveException, then rethrow it to - // preserve the test outcome and error message - this.RethrowIfAssertException(exception); - - string message = string.Format( - CultureInfo.CurrentCulture, - Resource.UTF_TestMethodWrongException, - thrownExceptionType.FullName, - this.ExceptionType.FullName, - UtfHelper.GetExceptionMsg(exception)); - throw new Exception(message); - } - } - } - - #endregion - } -} diff --git a/source/TestAdapter/Attributes/ExpectedExceptionBaseAttribute.cs b/source/TestAdapter/Attributes/ExpectedExceptionBaseAttribute.cs deleted file mode 100644 index e9721b4..0000000 --- a/source/TestAdapter/Attributes/ExpectedExceptionBaseAttribute.cs +++ /dev/null @@ -1,122 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Diagnostics; - using System.Globalization; - - /// - /// Base class for attributes that specify to expect an exception from a unit test - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public abstract class ExpectedExceptionBaseAttribute : Attribute - { - #region Constructors - - /// - /// Initializes a new instance of the class with a default no-exception message - /// - protected ExpectedExceptionBaseAttribute() - : this(string.Empty) - { - this.SpecifiedNoExceptionMessage = string.Empty; - } - - /// - /// Initializes a new instance of the class with a no-exception message - /// - /// - /// Message to include in the test result if the test fails due to not throwing an - /// exception - /// - protected ExpectedExceptionBaseAttribute(string noExceptionMessage) - { - this.SpecifiedNoExceptionMessage = - noExceptionMessage == null ? - string.Empty : - noExceptionMessage.Trim(); - } - - #endregion - - #region Properties - - // Todo: Test Context needs to be put in here for source compat. - - /// - /// Gets the message to include in the test result if the test fails due to not throwing an exception - /// - protected internal virtual string NoExceptionMessage - { - get - { - Debug.Assert(this.SpecifiedNoExceptionMessage != null, "'noExceptionMessage' is null"); - - if (string.IsNullOrEmpty(this.SpecifiedNoExceptionMessage)) - { - // Provide a default message when none was provided by a derived class - return GetDefaultNoExceptionMessage(this.GetType().FullName); - } - - return this.SpecifiedNoExceptionMessage; - } - } - - /// - /// Gets the message to include in the test result if the test fails due to not throwing an exception - /// - protected string SpecifiedNoExceptionMessage { get; private set; } - - #endregion - - #region Methods - - /// - /// Gets the default no-exception message - /// - /// The ExpectedException attribute type name - /// The default no-exception message - internal static string GetDefaultNoExceptionMessage(string expectedExceptionAttributeTypeName) - { - return string.Format( - CultureInfo.CurrentCulture, - Resource.UTF_TestMethodNoExceptionDefault, - expectedExceptionAttributeTypeName); - } - - /// - /// Determines whether the exception is expected. If the method returns, then it is - /// understood that the exception was expected. If the method throws an exception, then it - /// is understood that the exception was not expected, and the thrown exception's message - /// is included in the test result. The class can be used for - /// convenience. If is used and the assertion fails, - /// then the test outcome is set to Inconclusive. - /// - /// The exception thrown by the unit test - protected internal abstract void Verify(Exception exception); - - /// - /// Rethrow the exception if it is an AssertFailedException or an AssertInconclusiveException - /// - /// The exception to rethrow if it is an assertion exception - protected void RethrowIfAssertException(Exception exception) - { - if (exception is AssertFailedException) - { - throw new AssertFailedException(exception.Message); - } - else if (exception is AssertInconclusiveException) - { - throw new AssertInconclusiveException(exception.Message); - } - } - - #endregion - } -} diff --git a/source/TestAdapter/Attributes/IgnoreAttribute.cs b/source/TestAdapter/Attributes/IgnoreAttribute.cs deleted file mode 100644 index 9d7665d..0000000 --- a/source/TestAdapter/Attributes/IgnoreAttribute.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - - /// - /// The ignore attribute. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] - public sealed class IgnoreAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - public IgnoreAttribute() - : this(string.Empty) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Message specifies reason for ignoring. - /// - public IgnoreAttribute(string message) - { - this.IgnoreMessage = message; - } - - /// - /// Gets the owner. - /// - public string IgnoreMessage { get; private set; } - } -} diff --git a/source/TestAdapter/Attributes/OwnerAttribute.cs b/source/TestAdapter/Attributes/OwnerAttribute.cs deleted file mode 100644 index 1519183..0000000 --- a/source/TestAdapter/Attributes/OwnerAttribute.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - - /// - /// Test Owner - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class OwnerAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The owner. - /// - public OwnerAttribute(string owner) - { - this.Owner = owner; - } - - /// - /// Gets the owner. - /// - public string Owner { get; } - } -} diff --git a/source/TestAdapter/Attributes/PriorityAttribute.cs b/source/TestAdapter/Attributes/PriorityAttribute.cs deleted file mode 100644 index bce7135..0000000 --- a/source/TestAdapter/Attributes/PriorityAttribute.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - - /// - /// Priority attribute; used to specify the priority of a unit test. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class PriorityAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The priority. - /// - public PriorityAttribute(int priority) - { - this.Priority = priority; - } - - /// - /// Gets the priority. - /// - public int Priority { get; } - } -} diff --git a/source/TestAdapter/Attributes/TestCategoryBaseAttribute.cs b/source/TestAdapter/Attributes/TestCategoryBaseAttribute.cs deleted file mode 100644 index 0225282..0000000 --- a/source/TestAdapter/Attributes/TestCategoryBaseAttribute.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.Collections.Generic; - - /// - /// Base class for the "Category" attribute - /// - /// - /// The reason for this attribute is to let the users create their own implementation of test categories. - /// - test framework (discovery, etc) deals with TestCategoryBaseAttribute. - /// - The reason that TestCategories property is a collection rather than a string, - /// is to give more flexibility to the user. For instance the implementation may be based on enums for which the values can be OR'ed - /// in which case it makes sense to have single attribute rather than multiple ones on the same test. - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] - public abstract class TestCategoryBaseAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// Applies the category to the test. The strings returned by TestCategories - /// are used with the /category command to filter tests - /// - protected TestCategoryBaseAttribute() - { - } - - /// - /// Gets the test category that has been applied to the test. - /// - public abstract IList TestCategories - { - get; - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/Attributes/TestPropertyAttribute.cs b/source/TestAdapter/Attributes/TestPropertyAttribute.cs deleted file mode 100644 index 883a4b8..0000000 --- a/source/TestAdapter/Attributes/TestPropertyAttribute.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - - /// - /// The test property attribute. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class TestPropertyAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The name. - /// - /// - /// The value. - /// - public TestPropertyAttribute(string name, string value) - { - // NOTE : DONT THROW EXCEPTIONS FROM HERE IT WILL CRASH GetCustomAttributes() call - this.Name = name; - this.Value = value; - } - - /// - /// Gets the name. - /// - public string Name { get; } - - /// - /// Gets the value. - /// - public string Value { get; } - } -} diff --git a/source/TestAdapter/Attributes/TestResult.cs b/source/TestAdapter/Attributes/TestResult.cs deleted file mode 100644 index 6c48749..0000000 --- a/source/TestAdapter/Attributes/TestResult.cs +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using System; - using System.Collections.Generic; - - /// - /// TestResult object to be returned to adapter. - /// - public class TestResult - { - /// - /// Initializes a new instance of the class. - /// - public TestResult() - { - this.DatarowIndex = -1; - } - - /// - /// Gets or sets the display name of the result. Useful when returning multiple results. - /// If null then Method name is used as DisplayName. - /// - public string DisplayName { get; set; } - - /// - /// Gets or sets the outcome of the test execution. - /// - public UnitTestOutcome Outcome { get; set; } - - /// - /// Gets or sets the exception thrown when test is failed. - /// - public Exception TestFailureException { get; set; } - - /// - /// Gets or sets the output of the message logged by test code. - /// - public string LogOutput { get; set; } - - /// - /// Gets or sets the output of the message logged by test code. - /// - public string LogError { get; set; } - - /// - /// Gets or sets the debug traces by test code. - /// - public string DebugTrace { get; set; } - - /// - /// Gets or sets the debug traces by test code. - /// - public string TestContextMessages { get; set; } - - /// - /// Gets or sets the execution id of the result. - /// - public Guid ExecutionId { get; set; } - - /// - /// Gets or sets the parent execution id of the result. - /// - public Guid ParentExecId { get; set; } - - /// - /// Gets or sets the inner results count of the result. - /// - public int InnerResultsCount { get; set; } - - /// - /// Gets or sets the duration of test execution. - /// - public TimeSpan Duration { get; set; } - - /// - /// Gets or sets the data row index in data source. Set only for results of individual - /// run of data row of a data driven test. - /// - public int DatarowIndex { get; set; } - - /// - /// Gets or sets the return value of the test method. (Currently null always). - /// - public object ReturnValue { get; set; } - - /// - /// Gets or sets the result files attached by the test. - /// - public IList ResultFiles { get; set; } - } -} diff --git a/source/TestAdapter/Attributes/VsAttributes.cs b/source/TestAdapter/Attributes/VsAttributes.cs deleted file mode 100644 index a6e51a2..0000000 --- a/source/TestAdapter/Attributes/VsAttributes.cs +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.Diagnostics.CodeAnalysis; - - /// - /// The test initialize attribute. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class TestInitializeAttribute : Attribute - { - } - - /// - /// The test cleanup attribute. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class TestCleanupAttribute : Attribute - { - } - - /// - /// The class initialize attribute. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class ClassInitializeAttribute : Attribute - { - } - - /// - /// The class cleanup attribute. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class ClassCleanupAttribute : Attribute - { - } - - /// - /// The assembly initialize attribute. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class AssemblyInitializeAttribute : Attribute - { - } - - /// - /// The assembly cleanup attribute. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class AssemblyCleanupAttribute : Attribute - { - } - - - /// - /// Timeout attribute; used to specify the timeout of a unit test. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class TimeoutAttribute : Attribute - { - #region Constructor - - /// - /// Initializes a new instance of the class. - /// - /// - /// The timeout. - /// - public TimeoutAttribute(int timeout) - { - this.Timeout = timeout; - } - - /// - /// Initializes a new instance of the class with a preset timeout - /// - /// - /// The timeout - /// - public TimeoutAttribute(TestTimeout timeout) - { - this.Timeout = (int)timeout; - } - - #endregion - - #region Properties - - /// - /// Gets the timeout. - /// - public int Timeout { get; } - - #endregion - } - - /// - /// Enumeration for timeouts, that can be used with the class. - /// The type of the enumeration must match - /// - [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Justification = "Compat reasons")] - public enum TestTimeout - { - /// - /// The infinite. - /// - Infinite = int.MaxValue - } -} diff --git a/source/TestAdapter/Constants.cs b/source/TestAdapter/Constants.cs deleted file mode 100644 index 14b338b..0000000 --- a/source/TestAdapter/Constants.cs +++ /dev/null @@ -1,92 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using System; -using System.Collections.Generic; - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - internal class Constants - { - internal const string NFExecutorUriString = "executor://nanoFrameworkTestExecutor/v1"; - - internal static readonly Uri ExecutorUri = new Uri(NFExecutorUriString); - - /// - /// The assembly name of the dll containing logger APIs(EqtTrace) from the TestPlatform. - /// - /// - /// The reason we have this is because the AssemblyResolver itself logs information during resolution. - /// If the resolver is called for the assembly containing the logger APIs, we do not log so as to prevent a stack overflow. - /// - internal const string LoggerAssemblyName = "Microsoft.VisualStudio.TestPlatform.ObjectModel"; - - internal const string TargetFrameworkAttributeFullName = "System.Runtime.Versioning.TargetFrameworkAttribute"; - - internal const string DotNetnanoFrameWorkStringPrefix = ".NETnanoFramework,Version="; - - internal const string TargetFrameworkName = "TargetFrameworkName"; - - internal const string TargetFramework_nanoFramework = ".NETnanoFramework"; - - internal const string TestClassAttributeFullName = "Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute"; - - internal const string TestMethodAttributeFullName = "Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute"; - - - internal static readonly TestProperty TestClassNameProperty = TestProperty.Register("TestDiscoverer.TestClassName", TestClassNameLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase)); - -#pragma warning disable CS0618 // Type or member is obsolete - internal static readonly TestProperty TestCategoryProperty = TestProperty.Register("TestDiscoverer.TestCategory", TestCategoryLabel, typeof(string[]), TestPropertyAttributes.Hidden | TestPropertyAttributes.Trait, typeof(TestCase)); -#pragma warning restore CS0618 // Type or member is obsolete - - internal static readonly TestProperty PriorityProperty = TestProperty.Register("TestDiscoverer.Priority", PriorityLabel, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase)); - - internal static readonly TestProperty DeploymentItemsProperty = TestProperty.Register("MSTestDiscoverer.DeploymentItems", DeploymentItemsLabel, typeof(KeyValuePair[]), TestPropertyAttributes.Hidden, typeof(TestCase)); - - internal static readonly TestProperty ExecutionIdProperty = TestProperty.Register("ExecutionId", ExecutionIdLabel, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult)); - - internal static readonly TestProperty ParentExecIdProperty = TestProperty.Register("ParentExecId", ParentExecIdLabel, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult)); - - internal static readonly TestProperty InnerResultsCountProperty = TestProperty.Register("InnerResultsCount", InnerResultsCountLabel, typeof(int), TestPropertyAttributes.Hidden, typeof(TestResult)); - - #region Private Constants - - /// - /// These are the Test properties used by the adapter, which essentially correspond - /// to attributes on tests, and may be available in command line/TeamBuild to filter tests. - /// These Property names should not be localized. - /// - private const string TestClassNameLabel = "ClassName"; - private const string IsAsyncLabel = "IsAsync"; - private const string TestCategoryLabel = "TestCategory"; - private const string PriorityLabel = "Priority"; - private const string DeploymentItemsLabel = "DeploymentItems"; - private const string DoNotParallelizeLabel = "DoNotParallelize"; - private const string ExecutionIdLabel = "ExecutionId"; - private const string ParentExecIdLabel = "ParentExecId"; - private const string InnerResultsCountLabel = "InnerResultsCount"; - - private const string TestRunId = "__Tfs_TestRunId__"; - private const string TestPlanId = "__Tfs_TestPlanId__"; - private const string TestCaseId = "__Tfs_TestCaseId__"; - private const string TestPointId = "__Tfs_TestPointId__"; - private const string TestConfigurationId = "__Tfs_TestConfigurationId__"; - private const string TestConfigurationName = "__Tfs_TestConfigurationName__"; - private const string IsInLabEnvironment = "__Tfs_IsInLabEnvironment__"; - private const string BuildConfigurationId = "__Tfs_BuildConfigurationId__"; - private const string BuildDirectory = "__Tfs_BuildDirectory__"; - private const string BuildFlavor = "__Tfs_BuildFlavor__"; - private const string BuildNumber = "__Tfs_BuildNumber__"; - private const string BuildPlatform = "__Tfs_BuildPlatform__"; - private const string BuildUri = "__Tfs_BuildUri__"; - private const string TfsServerCollectionUrl = "__Tfs_TfsServerCollectionUrl__"; - private const string TfsTeamProject = "__Tfs_TeamProject__"; - - #endregion - } -} diff --git a/source/TestAdapter/Deployment/DeploymentItem.cs b/source/TestAdapter/Deployment/DeploymentItem.cs deleted file mode 100644 index eb826db..0000000 --- a/source/TestAdapter/Deployment/DeploymentItem.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Deployment -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Diagnostics; - using System.Globalization; - - /// - /// Specifies type of deployment item origin, where the item comes from. - /// - [Serializable] - internal enum DeploymentItemOriginType - { - /// - /// A per test deployment item. - /// - PerTestDeployment, - - /// - /// A test storage. - /// - TestStorage, - - /// - /// A dependency item. - /// - Dependency, - - /// - /// A satellite assembly. - /// - Satellite, - } - - /// - /// The deployment item for a test class or a test method. - /// - [Serializable] - internal class DeploymentItem - { - /// - /// Initializes a new instance of the class. - /// - /// Absolute or relative to test assembly. - internal DeploymentItem(string sourcePath) - : this(sourcePath, string.Empty) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Absolute or relative to test assembly. - /// Relative to the deployment directory. string.Empty means deploy to - /// deployment directory itself. - /// - internal DeploymentItem( - string sourcePath, - string relativeOutputDirectory - ) - : this(sourcePath, relativeOutputDirectory, DeploymentItemOriginType.PerTestDeployment) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Absolute or relative to test assembly. - /// Relative to the deployment directory. string.Empty means deploy to deployment directory itself. - /// Origin of this deployment directory. - internal DeploymentItem( - string sourcePath, - string relativeOutputDirectory, - DeploymentItemOriginType originType - ) - { - Debug.Assert(!string.IsNullOrEmpty(sourcePath), "sourcePath"); - Debug.Assert(relativeOutputDirectory != null, "relativeOutputDirectory"); - - this.SourcePath = sourcePath; - this.RelativeOutputDirectory = relativeOutputDirectory; - this.OriginType = originType; - } - - /// - /// Gets the full path to the 'source' deployment item - /// - internal string SourcePath - { - get; private set; - } - - /// - /// Gets the directory in which item should be deployed. Relative to the deployment root. - /// - internal string RelativeOutputDirectory - { - get; private set; - } - - /// - /// Gets the origin of deployment item. - /// - internal DeploymentItemOriginType OriginType { get; private set; } - - #region Object - overrides - - /// - /// Equals implementation. - /// - /// The object. - /// True if the two objects are equal. - public override bool Equals( - object obj - ) - { - DeploymentItem otherItem = obj as DeploymentItem; - if (otherItem == null) - { - return false; - } - - Debug.Assert(this.SourcePath != null, "SourcePath"); - Debug.Assert(this.RelativeOutputDirectory != null, "RelativeOutputDirectory"); - return this.SourcePath.Equals(otherItem.SourcePath, StringComparison.OrdinalIgnoreCase) && - this.RelativeOutputDirectory.Equals(otherItem.RelativeOutputDirectory, StringComparison.OrdinalIgnoreCase); - } - - /// - /// GetHashCode implementation. - /// - /// The hash code value. - public override int GetHashCode() - { - Debug.Assert(this.SourcePath != null, "SourcePath"); - Debug.Assert(this.RelativeOutputDirectory != null, "RelativeOutputDirectory"); - return this.SourcePath.GetHashCode() + this.RelativeOutputDirectory.GetHashCode(); - } - - /// - /// Deployment item description. - /// - /// The value. - public override string ToString() - { - Debug.Assert(this.SourcePath != null, "SourcePath"); - Debug.Assert(this.RelativeOutputDirectory != null, "RelativeOutputDirectory"); - - return - string.IsNullOrEmpty(this.RelativeOutputDirectory) ? - string.Format(CultureInfo.CurrentCulture, Resource.DeploymentItem, this.SourcePath) : - string.Format(CultureInfo.CurrentCulture, Resource.DeploymentItemWithOutputDirectory, this.SourcePath, this.RelativeOutputDirectory); - } - - #endregion - } -} diff --git a/source/TestAdapter/Deployment/TestRunDirectories.cs b/source/TestAdapter/Deployment/TestRunDirectories.cs deleted file mode 100644 index 59d9772..0000000 --- a/source/TestAdapter/Deployment/TestRunDirectories.cs +++ /dev/null @@ -1,79 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Deployment -{ - using System; - using System.Diagnostics; - using System.IO; - - /// - /// The test run directories. - /// - [Serializable] - public class TestRunDirectories - { - /// - /// The default deployment root directory. We do not want to localize it. - /// - internal const string DefaultDeploymentRootDirectory = "TestResults"; - - /// - /// The deployment in directory suffix. - /// - internal const string DeploymentInDirectorySuffix = "In"; - - /// - /// The deployment out directory suffix. - /// - internal const string DeploymentOutDirectorySuffix = "Out"; - - public TestRunDirectories(string rootDirectory) - { - Debug.Assert(!string.IsNullOrEmpty(rootDirectory), "rootDirectory"); - - this.RootDeploymentDirectory = rootDirectory; - } - - /// - /// Gets or sets the root deployment directory - /// - public string RootDeploymentDirectory { get; set; } - - /// - /// Gets the In directory - /// - public string InDirectory - { - get - { - return Path.Combine(this.RootDeploymentDirectory, DeploymentInDirectorySuffix); - } - } - - /// - /// Gets the Out directory - /// - public string OutDirectory - { - get - { - return Path.Combine(this.RootDeploymentDirectory, DeploymentOutDirectorySuffix); - } - } - - /// - /// Gets In\MachineName directory - /// - public string InMachineNameDirectory - { - get - { - return Path.Combine(Path.Combine(this.RootDeploymentDirectory, DeploymentInDirectorySuffix), Environment.MachineName); - } - } - } -} diff --git a/source/TestAdapter/Directory.Build.props b/source/TestAdapter/Directory.Build.props new file mode 100644 index 0000000..95a6843 --- /dev/null +++ b/source/TestAdapter/Directory.Build.props @@ -0,0 +1,9 @@ + + + + + 3.3.37 + all + + + \ No newline at end of file diff --git a/source/TestAdapter/Discover.cs b/source/TestAdapter/Discover.cs new file mode 100644 index 0000000..c647b80 --- /dev/null +++ b/source/TestAdapter/Discover.cs @@ -0,0 +1,236 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using nanoFramework.TestAdapter; +using nanoFramework.TestFramework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace nanoFramework.TestPlatform.TestAdapter +{ + /// + /// A Test Discoverer class + /// + [DefaultExecutorUri(TestsConstants.NanoExecutor)] + [FileExtension(".exe")] + [FileExtension(".dll")] + public class TestDiscoverer : ITestDiscoverer + { + private LogMessenger _logger; + private List _testCases; + + /// + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + _testCases = new List(); + + var settingsProvider = discoveryContext.RunSettings.GetSettings(TestsConstants.SettingsName) as SettingsProvider; + + _logger = new LogMessenger( + logger, + settingsProvider); + + if (settingsProvider != null) + { + _logger.LogMessage( + "Getting ready to discover tests...", + Settings.LoggingLevel.Detailed); + + _logger.LogMessage( + "Settings parsed", + Settings.LoggingLevel.Verbose); + } + else + { + _logger.LogMessage( + "Getting ready to discover tests...", + Settings.LoggingLevel.Detailed); + + _logger.LogMessage( + "No settings for nanoFramework adapter", + Settings.LoggingLevel.Verbose); + } + + foreach (var source in sources) + { + _logger.LogMessage( + $" New file processed: {source}", + Settings.LoggingLevel.Detailed); + + if (!File.Exists(source)) + { + _logger.LogMessage( + $" File doesn't exist: {source}", + Settings.LoggingLevel.Detailed); + + continue; + } + + var cases = FindTestCases(source); + if (cases.Count > 0) + { + _logger.LogMessage( + $" Adding {cases.Count} new tests", + Settings.LoggingLevel.Detailed); + + _testCases.AddRange(cases); + } + } + + foreach (var testCase in _testCases) + { + discoverySink.SendTestCase(testCase); + } + + _logger.LogMessage( + "Finished adding files", + Settings.LoggingLevel.Detailed); + } + + /// + /// Find tests cases based on the source file + /// + /// the link on a file + /// A list of test cases + public static List FindTestCases(string source) + { + List testCases = new List(); + + var nfprojSources = FindNfprojSources(source); + if (nfprojSources.Length == 0) + { + return testCases; + } + + var allCsFils = GetAllCsFileNames(nfprojSources); + + Assembly test = Assembly.LoadFile(source); + AppDomain.CurrentDomain.AssemblyResolve += App_AssemblyResolve; + AppDomain.CurrentDomain.Load(test.GetName()); + + Type[] allTypes = test.GetTypes(); + foreach (var type in allTypes) + { + if (type.IsClass) + { + var typeAttribs = type.GetCustomAttributes(true); + foreach (var typeAttrib in typeAttribs) + { + if (typeof(TestClassAttribute).FullName == typeAttrib.GetType().FullName) + { + var methods = type.GetMethods(); + // First we look at Setup + foreach (var method in methods) + { + var attribs = method.GetCustomAttributes(true); + + foreach (var attrib in attribs) + { + if (attrib.GetType().FullName == typeof(SetupAttribute).FullName || + attrib.GetType().FullName == typeof(TestMethodAttribute).FullName || + attrib.GetType().FullName == typeof(CleanupAttribute).FullName) + { + var testCase = GetFileNameAndLineNumber(allCsFils, type, method); + testCase.Source = source; + testCase.ExecutorUri = new Uri(TestsConstants.NanoExecutor); + testCase.FullyQualifiedName = $"{type.FullName}.{testCase.DisplayName}"; + testCase.Traits.Add(new Trait("Type", attrib.GetType().Name.Replace("Attribute",""))); + testCases.Add(testCase); + } + } + } + + } + } + } + } + + return testCases; + } + + private static Assembly App_AssemblyResolve(object sender, ResolveEventArgs args) + { + string dllName = args.Name.Split(new[] { ',' })[0] + ".dll"; + string path = Path.GetDirectoryName(args.RequestingAssembly.Location); + return Assembly.LoadFrom(Path.Combine(path, dllName)); + } + + private static string[] GetAllCsFileNames(FileInfo[] nfprojSources) + { + List allCsFiles = new List(); + foreach (var nfproj in nfprojSources) + { + var csFiles = Directory.GetFiles(Path.GetDirectoryName(nfproj.FullName), "*.cs", SearchOption.AllDirectories); + // Get rid of those in /bin / obj + var csFilesClean = csFiles.Where(m => !(m.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}") || m.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}"))); + allCsFiles.AddRange(csFilesClean); + } + + return allCsFiles.ToArray(); + } + + private static FileInfo[] FindNfprojSources(string source) + { + if (Path.GetDirectoryName(source) == null) + { + return new FileInfo[0]; + } + + // Find all the potential *.cs files present at same level or above a nfproj file, + // if no nfproj file, then we will skip this source + var mainDirectory = new DirectoryInfo(Path.GetDirectoryName(source)); + var nfproj = mainDirectory.GetFiles("*.nfproj"); + if (nfproj.Length == 0) + { + var ret = FindNfprojSources(mainDirectory.Parent.FullName); + return ret; + } + + return nfproj; + } + + private static TestCase GetFileNameAndLineNumber(string[] csFiles, Type className, MethodInfo method) + { + var clName = className.Name; + var methodName = method.Name; + TestCase flret = new TestCase(); + foreach (var csFile in csFiles) + { + StreamReader sr = new StreamReader(csFile); + var allFile = sr.ReadToEnd(); + if (allFile.Contains($"class {clName}")) + { + if (allFile.Contains($" {methodName}(")) + { + // We found it! + int lineNum = 1; + foreach (var line in allFile.Split('\r')) + { + if (line.Contains($" {methodName}(")) + { + flret.CodeFilePath = csFile; + flret.LineNumber = lineNum; + flret.DisplayName = method.Name; + return flret; + } + + lineNum++; + } + } + } + } + + return flret; + } + } +} + diff --git a/source/TestAdapter/Discovery/AssemblyEnumerator.cs b/source/TestAdapter/Discovery/AssemblyEnumerator.cs deleted file mode 100644 index 68bfbc3..0000000 --- a/source/TestAdapter/Discovery/AssemblyEnumerator.cs +++ /dev/null @@ -1,317 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Security; - using System.Text; - - - /// - /// Enumerates through all types in the assembly in search of valid test methods. - /// - internal class AssemblyEnumerator : MarshalByRefObject - { - /// - /// Initializes a new instance of the class. - /// - public AssemblyEnumerator() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The settings for the session. - /// Use this constructor when creating this object in a new app domain so the settings for this app domain are set. - public AssemblyEnumerator(TestSettings settings) - { - // Populate the settings into the domain(Desktop workflow) performing discovery. - // This would just be resettings the settings to itself in non desktop workflows. - TestSettings.PopulateSettings(settings); - } - - /// - /// Returns object to be used for controlling lifetime, null means infinite lifetime. - /// - /// - /// The . - /// - [SecurityCritical] - public override object InitializeLifetimeService() - { - return null; - } - - /// - /// Enumerates through all types in the assembly in search of valid test methods. - /// - /// The assembly file name. - /// Contains warnings if any, that need to be passed back to the caller. - /// A collection of Test Elements. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Catching a generic exception since it is a requirement to not abort discovery in case of any errors.")] - internal ICollection EnumerateAssembly( - string assemblyFileName, - out ICollection warnings) - { - Debug.Assert(!string.IsNullOrWhiteSpace(assemblyFileName), "Invalid assembly file name."); - - var warningMessages = new List(); - var tests = new List(); - - Type testClassAttrib = null; - Type testMethodAttrib = null; - - - // load test attributes from TestFramework nanoFramework assembly - var testFrameworkAssembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly( - Path.GetDirectoryName(assemblyFileName) + "\\nanoFramework.TestPlatform.TestFramework.dll", - isReflectionOnly: true); - - if(testFrameworkAssembly == null) - { - // If we fail to load the TestFramework assembly abort the discovery because that won't be possible - string message = string.Format( - CultureInfo.CurrentCulture, - "Fail to load nanoFramework.TestPlatform.TestFramework assembly."); - - warningMessages.Add(message); - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Fail to load nanoFramework.TestPlatform.TestFramework assembly."); - - warningMessages.Add(message); - } - - var testFrameworkTypes = this.GetTypes(testFrameworkAssembly, assemblyFileName, null); - - foreach (var type in testFrameworkTypes) - { - if (type == null) - { - continue; - } - - string typeFullName = null; - - try - { - typeFullName = type.FullName; - - if(typeFullName == Constants.TestClassAttributeFullName) - { - testClassAttrib = type; - continue; - } - else if (typeFullName == Constants.TestMethodAttributeFullName) - { - testMethodAttrib = type; - continue; - } - } - catch (Exception exception) - { - // If we fail to discover the test attributes abort the discovery because that won't be possible - string message = string.Format( - CultureInfo.CurrentCulture, - "Exception occurred when searching test attributes in nanoFramework.TestPlatform.TestFramework assembly. {0}", - exception.Message); - - warningMessages.Add(message); - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Could not find the test attributes in nanoFramework.TestPlatform.TestFramework assembly. {0}", exception); - - warningMessages.Add(message); - } - } - - if (testClassAttrib != null && testMethodAttrib != null) - { - // we have test attributes so we are good to go - // always need to load the source assembly in reflection only context - Assembly assembly; - assembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly( - assemblyFileName, - isReflectionOnly: true); - - var types = this.GetTypes(assembly, assemblyFileName, warningMessages); - - foreach (var type in types) - { - if (type == null) - { - continue; - } - - string typeFullName = null; - - try - { - ICollection warningsFromTypeEnumerator; - - typeFullName = type.FullName; - var unitTestCases = this.GetTypeEnumerator(type, assemblyFileName, testClassAttrib, testMethodAttrib).Enumerate(out warningsFromTypeEnumerator); - - if (warningsFromTypeEnumerator != null) - { - warningMessages.AddRange(warningsFromTypeEnumerator); - } - - if (unitTestCases != null) - { - tests.AddRange(unitTestCases); - } - } - catch (Exception exception) - { - // If we fail to discover type from a class, then don't abort the discovery - // Move to the next type. - string message = string.Format( - CultureInfo.CurrentCulture, - Resource.CouldNotInspectTypeDuringDiscovery, - typeFullName, - assemblyFileName, - exception.Message); - warningMessages.Add(message); - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo( - "AssemblyEnumerator: Exception occurred while enumerating type {0}. {1}", - typeFullName, - exception); - } - } - } - else - { - // If we fail to discover the test attributes abort the discovery because that won't be possible - string message = string.Format( - CultureInfo.CurrentCulture, - "Fail to find test attributes in nanoFramework.TestPlatform.TestFramework assembly."); - - warningMessages.Add(message); - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Fail to find test attributes in nanoFramework.TestPlatform.TestFramework assembly."); - - warningMessages.Add(message); - - } - - warnings = warningMessages; - return tests; - } - - /// - /// Gets the types defined in an assembly. - /// - /// The reflected assembly. - /// The file name of the assembly. - /// Contains warnings if any, that need to be passed back to the caller. - /// Gets the types defined in the provided assembly. - internal Type[] GetTypes( - Assembly assembly, - string assemblyFileName, - ICollection warningMessages) - { - var types = new List(); - try - { - types.AddRange(assembly.DefinedTypes.Select(typeinfo => typeinfo.AsType())); - } - catch (ReflectionTypeLoadException ex) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning( - "TestExecutor.TryGetTests: Failed to discover tests from {0}. Reason:{1}", - assemblyFileName, - ex); - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning("Exceptions thrown from the Loader :"); - - if (ex.LoaderExceptions != null) - { - // If not able to load all type, log a warning and continue with loaded types. - var message = string.Format( - CultureInfo.CurrentCulture, - Resource.TypeLoadFailed, - assemblyFileName, - this.GetLoadExceptionDetails(ex)); - - warningMessages?.Add(message); - - foreach (var loaderEx in ex.LoaderExceptions) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning("{0}", loaderEx); - } - } - - return ex.Types; - } - - return types.ToArray(); - } - - /// - /// Formats load exception as multiline string, each line contains load error message. - /// - /// The exception. - /// Returns loader exceptions as a multiline string. - internal string GetLoadExceptionDetails(ReflectionTypeLoadException ex) - { - Debug.Assert(ex != null, "exception should not be null."); - - var map = new Dictionary(StringComparer.OrdinalIgnoreCase); // Exception -> null. - var errorDetails = new StringBuilder(); - - if (ex.LoaderExceptions != null) - { - // Loader exceptions can contain duplicates, leave only unique exceptions. - foreach (var loaderException in ex.LoaderExceptions) - { - Debug.Assert(loaderException != null, "loader exception should not be null."); - var line = string.Format(CultureInfo.CurrentCulture, Resource.EnumeratorLoadTypeErrorFormat, loaderException.GetType(), loaderException.Message); - if (!map.ContainsKey(line)) - { - map.Add(line, null); - errorDetails.AppendLine(line); - } - } - } - else - { - errorDetails.AppendLine(ex.Message); - } - - return errorDetails.ToString(); - } - - /// - /// Returns an instance of the class. - /// - /// The type to enumerate. - /// The reflected assembly name. - /// a TypeEnumerator instance. - internal virtual TypeEnumerator GetTypeEnumerator( - Type type, - string assemblyFileName, - Type testClassAttrib, - Type testMethodAttrib) - { - var reflectHelper = new ReflectHelper(); - var typevalidator = new TypeValidator(reflectHelper, testClassAttrib); - var testMethodValidator = new TestMethodValidator(reflectHelper, testMethodAttrib); - - return new TypeEnumerator(type, assemblyFileName, reflectHelper, typevalidator, testMethodValidator); - } - } -} diff --git a/source/TestAdapter/Discovery/AssemblyEnumeratorWrapper.cs b/source/TestAdapter/Discovery/AssemblyEnumeratorWrapper.cs deleted file mode 100644 index afec6b8..0000000 --- a/source/TestAdapter/Discovery/AssemblyEnumeratorWrapper.cs +++ /dev/null @@ -1,187 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Text; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities; - - /// - /// Enumerates through an assembly to get a set of test methods. - /// - internal class AssemblyEnumeratorWrapper - { - /// - /// Assembly name for UTF - /// - //private static readonly AssemblyName UnitTestFrameworkAssemblyName = - // typeof(TestMethodAttribute).GetTypeInfo().Assembly.GetName(); - - /// - /// Gets test elements from an assembly. - /// - /// The assembly file name. - /// The run Settings. - /// Contains warnings if any, that need to be passed back to the caller. - /// A collection of test elements. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Catching a generic exception since it is a requirement to not abort discovery in case of any errors.")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#", Justification = "This is only for internal use.")] - internal ICollection GetTests( - string assemblyFileName, - IRunSettings runSettings, - out ICollection warnings) - { - warnings = new List(); - - if (string.IsNullOrEmpty(assemblyFileName)) - { - return null; - } - - var fullFilePath = PlatformServiceProvider.Instance.FileOperations.GetFullFilePath(assemblyFileName); - - try - { - if (!PlatformServiceProvider.Instance.FileOperations.DoesFileExist(fullFilePath)) - { - var message = string.Format( - CultureInfo.CurrentCulture, - Resource.TestAssembly_FileDoesNotExist, - fullFilePath); - throw new FileNotFoundException(message); - } - - // check - // Setup app-domain - var appDomainSetup = new AppDomainSetup(); - - // get target framework from assembly - var targetFrameworkVersion = AppDomainUtilities.GetTargetFrameworkVersionString(assemblyFileName); - - // sanity check for nanoFramework being the target framework of this assembly - if (!targetFrameworkVersion.StartsWith(Constants.TargetFramework_nanoFramework)) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning( - $"TestDiscoverer.TryGetTests: Failed to discover tests from {fullFilePath}. Reason: this assembly is not targeting .NET nanoFramework."); - - var message = string.Format( - CultureInfo.CurrentCulture, - Resource.TestAssembly_AssemblyDiscoveryFailure, - fullFilePath, - "Reason: this assembly is not targeting .NET nanoFramework."); - warnings.Add(message); - - return null; - } - - // TODO - //if (!PlatformServiceProvider.Instance.TestSource.IsAssemblyReferenced( - // UnitTestFrameworkAssemblyName, - // fullFilePath)) - //{ - // return null; - //} - - // Load the assembly in isolation if required. - return this.GetTestsInIsolation(fullFilePath, runSettings, out warnings); - } - catch (FileNotFoundException ex) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning( - "TestDiscoverer.TryGetTests: Failed to discover tests from {0}. Reason:{1}", - fullFilePath, - ex); - - var message = string.Format( - CultureInfo.CurrentCulture, - Resource.TestAssembly_AssemblyDiscoveryFailure, - fullFilePath, - ex.Message); - warnings.Add(message); - - return null; - } - catch (ReflectionTypeLoadException ex) - { - var message = string.Format( - CultureInfo.CurrentCulture, - Resource.TestAssembly_AssemblyDiscoveryFailure, - fullFilePath, - ex.Message); - warnings.Add(message); - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning( - "MSPhoneTestDiscoverer.TryGetTests: Failed to discover tests from {0}. Reason:{1}", - assemblyFileName, - ex); - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning("Exceptions thrown from the Loader :"); - - if (ex.LoaderExceptions != null) - { - foreach (var loaderEx in ex.LoaderExceptions) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning("{0}", loaderEx); - } - } - - return null; - } - catch (BadImageFormatException) - { - // Ignore BadImageFormatException when loading native dll in managed adapter. - return null; - } - catch (Exception ex) - { - // Catch all exceptions, if discoverer fails to load the dll then let caller continue with other sources. - // Discover test doesn't work if there is a managed C++ project in solution - // Assembly.Load() fails to load the managed cpp executable, with FileLoadException. It can load the dll - // successfully though. This is known CLR issue. - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning( - "TestDiscoverer.TryGetTests: Failed to discover tests from {0}. Reason:{1}", - assemblyFileName, - ex); - var message = ex is FileNotFoundException fileNotFoundEx - ? fileNotFoundEx.Message - : string.Format( - CultureInfo.CurrentCulture, - Resource.TestAssembly_AssemblyDiscoveryFailure, - fullFilePath, - ex.Message); - warnings.Add(message); - return null; - } - } - - private ICollection GetTestsInIsolation( - string fullFilePath, - IRunSettings runSettings, - out ICollection warnings) - { - using (var isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(fullFilePath, runSettings, frameworkHandle: null)) - { - // Create an instance of a type defined in adapter so that adapter gets loaded in the child app domain - var assemblyEnumerator = isolationHost.CreateInstanceForType( - typeof(AssemblyEnumerator), new object[] { TestSettings.CurrentSettings }) as AssemblyEnumerator; - - // After loading adapter reset the child-domain's appbase to point to test source location - isolationHost.UpdateAppBaseToTestSourceLocation(); - - return assemblyEnumerator.EnumerateAssembly(fullFilePath, out warnings); - } - } - } -} diff --git a/source/TestAdapter/Discovery/TestMethodValidator.cs b/source/TestAdapter/Discovery/TestMethodValidator.cs deleted file mode 100644 index e5ef605..0000000 --- a/source/TestAdapter/Discovery/TestMethodValidator.cs +++ /dev/null @@ -1,79 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Reflection; - - /// - /// Determines if a method is a valid test method. - /// - internal class TestMethodValidator - { - private readonly ReflectHelper reflectHelper; - private readonly Type _testMethodAttrib; - - /// - /// Initializes a new instance of the class. - /// - /// An instance to reflection helper for type information. - internal TestMethodValidator( - ReflectHelper reflectHelper, - Type testMethodAttrib) - { - this.reflectHelper = reflectHelper; - _testMethodAttrib = testMethodAttrib; - } - - /// - /// Determines if a method is a valid test method. - /// - /// The reflected method. - /// The reflected type. - /// Contains warnings if any, that need to be passed back to the caller. - /// Return true if a method is a valid test method. - internal virtual bool IsValidTestMethod( - MethodInfo testMethodInfo, - Type type, - ICollection warnings) - { - if (!this.reflectHelper.IsAttributeDefined(testMethodInfo, _testMethodAttrib, false)) - { - return false; - } - - // Generic method Definitions are not valid. - if (testMethodInfo.IsGenericMethodDefinition) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorGenericTestMethod, testMethodInfo.DeclaringType.FullName, testMethodInfo.Name); - warnings.Add(message); - return false; - } - - // Todo: Decide wheter parameter count matters. - // The isGenericMethod check below id to verify that there are no closed generic methods slipping through. - // Closed generic methods being GenericMethod and open being GenericMethod. - var isValidTestMethod = testMethodInfo.IsPublic && !testMethodInfo.IsAbstract && !testMethodInfo.IsStatic - && !testMethodInfo.IsGenericMethod - && testMethodInfo.IsVoidOrTaskReturnType(); - - if (!isValidTestMethod) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorIncorrectTestMethodSignature, type.FullName, testMethodInfo.Name); - warnings.Add(message); - return false; - } - - return true; - } - } -} diff --git a/source/TestAdapter/Discovery/TypeEnumerator.cs b/source/TestAdapter/Discovery/TypeEnumerator.cs deleted file mode 100644 index 91b3ab5..0000000 --- a/source/TestAdapter/Discovery/TypeEnumerator.cs +++ /dev/null @@ -1,174 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using System.Threading.Tasks; - - - /// - /// Enumerates through the type looking for Valid Test Methods to execute. - /// - internal class TypeEnumerator - { - private readonly Type type; - private readonly string assemblyName; - private readonly TypeValidator typeValidator; - private readonly TestMethodValidator testMethodValidator; - private readonly ReflectHelper reflectHelper; - - /// - /// Initializes a new instance of the class. - /// - /// The reflected type. - /// The name of the assembly being reflected. - /// An instance to reflection helper for type information. - /// The validator for test classes. - /// The validator for test methods. - internal TypeEnumerator(Type type, string assemblyName, ReflectHelper reflectHelper, TypeValidator typeValidator, TestMethodValidator testMethodValidator) - { - this.type = type; - this.assemblyName = assemblyName; - this.reflectHelper = reflectHelper; - this.typeValidator = typeValidator; - this.testMethodValidator = testMethodValidator; - } - - /// - /// Walk through all methods in the type, and find out the test methods - /// - /// Contains warnings if any, that need to be passed back to the caller. - /// list of test cases. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#", Justification = "This is only for internal use.")] - internal virtual ICollection Enumerate(out ICollection warnings) - { - warnings = new Collection(); - - if (!this.typeValidator.IsValidTestClass(this.type, warnings)) - { - return null; - } - - // If test class is valid, then get the tests - return this.GetTests(warnings); - } - - /// - /// Gets a list of valid tests in a type. - /// - /// Contains warnings if any, that need to be passed back to the caller. - /// List of Valid Tests. - internal Collection GetTests(ICollection warnings) - { - var tests = new Collection(); - - // Test class is already valid. Verify methods. - foreach (var method in this.type.GetRuntimeMethods()) - { - var isMethodDeclaredInTestTypeAssembly = this.reflectHelper.IsMethodDeclaredInSameAssemblyAsType(method, this.type); - var enableMethodsFromOtherAssemblies = TestSettings.CurrentSettings.EnableBaseClassTestMethodsFromOtherAssemblies; - - if (!isMethodDeclaredInTestTypeAssembly && !enableMethodsFromOtherAssemblies) - { - continue; - } - - if (this.testMethodValidator.IsValidTestMethod(method, this.type, warnings)) - { - tests.Add(this.GetTestFromMethod(method, isMethodDeclaredInTestTypeAssembly, warnings)); - } - } - - return tests; - } - - /// - /// Gets a UnitTestElement from a MethodInfo object filling it up with appropriate values. - /// - /// The reflected method. - /// True if the reflected method is declared in the same assembly as the current type. - /// Contains warnings if any, that need to be passed back to the caller. - /// Returns a UnitTestElement. - internal UnitTestElement GetTestFromMethod( - MethodInfo method, - bool isDeclaredInTestTypeAssembly, - ICollection warnings) - { - Debug.Assert(this.type.AssemblyQualifiedName != null, "AssemblyQualifiedName for method is null."); - - // This allows void returning async test method to be valid test method. Though they will be executed similar to non-async test method. - var isAsync = ReflectHelper.MatchReturnType(method, typeof(Task)); - - var testMethod = new TestMethod(method.Name, this.type.FullName, this.assemblyName, isAsync); - - if (!method.DeclaringType.FullName.Equals(this.type.FullName)) - { - testMethod.DeclaringClassFullName = method.DeclaringType.FullName; - } - - if (!isDeclaredInTestTypeAssembly) - { - testMethod.DeclaringAssemblyName = - PlatformServiceProvider.Instance.FileOperations.GetAssemblyPath( - method.DeclaringType.GetTypeInfo().Assembly); - } - - var testElement = new UnitTestElement(testMethod); - - // TODO currently nanoFramework does not support async - //// Get compiler generated type name for async test method (either void returning or task returning). - //var asyncTypeName = method.GetAsyncTypeName(); - //testElement.AsyncTypeName = asyncTypeName; - - testElement.TestCategory = this.reflectHelper.GetCategories(method); - - var traits = this.reflectHelper.GetTestPropertiesAsTraits(method); - - var ownerTrait = this.reflectHelper.GetTestOwnerAsTraits(method); - if (ownerTrait != null) - { - traits = traits.Concat(new[] { ownerTrait }); - } - - testElement.Priority = this.reflectHelper.GetPriority(method); - - // nanoFramework test adapter does not support Priority attribute - if (testElement.Priority != null) - { - // issue a warning about this - warnings.Add($"{this.type.FullName} in {this.assemblyName} has Priority attribute set. Ignoring it because nanoFramework test adapter does support this."); - - // REset it to null - testElement.Priority = null; - } - - var priorityTrait = this.reflectHelper.GetTestPriorityAsTraits(testElement.Priority); - if (priorityTrait != null) - { - traits = traits.Concat(new[] { priorityTrait }); - } - - testElement.Traits = traits.ToArray(); - - // TODO - // Get Deployment items if any. - //testElement.DeploymentItems = PlatformServiceProvider.Instance.TestDeployment.GetDeploymentItems( - // method, - // this.type, - // warnings); - - return testElement; - } - } -} diff --git a/source/TestAdapter/Discovery/TypeValidator.cs b/source/TestAdapter/Discovery/TypeValidator.cs deleted file mode 100644 index 7c37702..0000000 --- a/source/TestAdapter/Discovery/TypeValidator.cs +++ /dev/null @@ -1,144 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Reflection; - - /// - /// Determines whether a type is a valid test class for this adapter. - /// - internal class TypeValidator - { - // Setting this to a string representation instead of a typeof(TestContext).FullName - // since the later would require a load of the Test Framework extension assembly at this point. - // TODO adjust after settling the assembly name - private const string TestContextFullName = "nanoFramework.TestPlatform.MSTest.TestAdapter.TestContext"; - private readonly ReflectHelper reflectHelper; - private readonly Type _testClassAttrib; - - /// - /// Initializes a new instance of the class. - /// - /// An instance to reflection helper for type information. - internal TypeValidator( - ReflectHelper reflectHelper, - Type testClassAttrib) - { - this.reflectHelper = reflectHelper; - - _testClassAttrib = testClassAttrib; - } - - /// - /// Determines if a type is a valid test class for this adapter. - /// - /// The reflected type. - /// Contains warnings if any, that need to be passed back to the caller. - /// Return true if it is a valid test class. - internal virtual bool IsValidTestClass( - Type type, - ICollection warnings) - { - if (type.GetTypeInfo().IsClass && (this.reflectHelper.IsAttributeDefined(type, _testClassAttrib, false))) - { - var isPublic = type.GetTypeInfo().IsPublic || (type.GetTypeInfo().IsNested && type.GetTypeInfo().IsNestedPublic); - - // non-public class - if (!isPublic) - { - var warning = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorNonPublicTestClass, type.FullName); - warnings.Add(warning); - return false; - } - - // Generic class - if (type.GetTypeInfo().IsGenericTypeDefinition && !type.GetTypeInfo().IsAbstract) - { - // In IDE generic classes that are not abstract are treated as not runnable. Keep consistence. - var warning = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorNonPublicTestClass, type.FullName); - warnings.Add(warning); - return false; - } - - // Class is not valid if the testContext property is incorrect - if (!this.HasCorrectTestContextSignature(type)) - { - var warning = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInValidTestContextSignature, type.FullName); - warnings.Add(warning); - return false; - } - - // Abstract test classes can be base classes for derived test classes. - // There is no way to see if there are derived test classes. - // Thus if a test class is abstract, just ignore all test methods from it - // (they will be visible in derived classes). No warnings (such as test method, deployment item, - // etc attribute is defined on the class) will be generated for this class: - // What we do is: - // - report the class as "not valid" test class. This will cause to skip enumerating tests from it. - // - Do not generate warnings/do not create NOT RUNNABLE tests. - if (type.GetTypeInfo().IsAbstract) - { - return false; - } - - return true; - } - - return false; - } - - /// - /// Determines if the type has a valid TestContext property definition. - /// - /// The reflected type. - /// Returns true if type has a valid TestContext property definition. - internal bool HasCorrectTestContextSignature(Type type) - { - Debug.Assert(type != null, "HasCorrectTestContextSignature type is null"); - - var propertyInfoEnumerable = type.GetTypeInfo().DeclaredProperties; - var propertyInfo = new List(); - - foreach (var pinfo in propertyInfoEnumerable) - { - // PropertyType.FullName can be null if the property is a generic type. - if (TestContextFullName.Equals(pinfo.PropertyType.FullName, StringComparison.Ordinal)) - { - propertyInfo.Add(pinfo); - } - } - - if (propertyInfo.Count == 0) - { - return true; - } - - foreach (var pinfo in propertyInfo) - { - var setInfo = pinfo.SetMethod; - if (setInfo == null) - { - // we have a getter, but not a setter. - return false; - } - - if (setInfo.IsPrivate || setInfo.IsStatic || setInfo.IsAbstract) - { - return false; - } - } - - return true; - } - } -} diff --git a/source/TestAdapter/Discovery/UnitTestDiscoverer.cs b/source/TestAdapter/Discovery/UnitTestDiscoverer.cs deleted file mode 100644 index 43c4204..0000000 --- a/source/TestAdapter/Discovery/UnitTestDiscoverer.cs +++ /dev/null @@ -1,186 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System.Collections.Generic; - using System.Globalization; - - internal class UnitTestDiscoverer - { - private readonly AssemblyEnumeratorWrapper assemblyEnumeratorWrapper; - - internal UnitTestDiscoverer() - { - this.assemblyEnumeratorWrapper = new AssemblyEnumeratorWrapper(); - this.TestMethodFilter = new TestMethodFilter(); - } - - /// - /// Gets or sets method filter for filtering tests - /// - private TestMethodFilter TestMethodFilter { get; set; } - - /// - /// Discovers the tests available from the provided sources. - /// - /// The sources. - /// The logger. - /// The discovery Sink. - /// The discovery context. - internal void DiscoverTests( - IEnumerable sources, - IMessageLogger logger, - ITestCaseDiscoverySink discoverySink, - IDiscoveryContext discoveryContext) - { - foreach (var source in sources) - { - this.DiscoverTestsInSource(source, logger, discoverySink, discoveryContext); - } - } - - /// - /// Get the tests from the parameter source - /// - /// The source. - /// The logger. - /// The discovery Sink. - /// The discovery context. - internal virtual void DiscoverTestsInSource( - string source, - IMessageLogger logger, - ITestCaseDiscoverySink discoverySink, - IDiscoveryContext discoveryContext) - { - ICollection warnings; - - var testElements = this.assemblyEnumeratorWrapper.GetTests(source, discoveryContext?.RunSettings, out warnings); - - // log the warnings - foreach (var warning in warnings) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo( - "TestDiscoverer: Warning during discovery from {0}. {1} ", - source, - warning); - var message = string.Format(CultureInfo.CurrentCulture, Resource.DiscoveryWarning, source, warning); - logger.SendMessage(TestMessageLevel.Warning, message); - } - - // No tests found => nothing to do - if (testElements == null || testElements.Count == 0) - { - return; - } - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo( - "TestDiscoverer: Found {0} tests from source {1}", - testElements.Count, - source); - - this.SendTestCases(source, testElements, discoverySink, discoveryContext, logger); - } - - internal void SendTestCases( - string source, - IEnumerable testElements, - ITestCaseDiscoverySink discoverySink, - IDiscoveryContext discoveryContext, - IMessageLogger logger) - { - var shouldCollectSourceInformation = TestSettings.RunConfigurationSettings.CollectSourceInformation; - - var navigationSessions = new Dictionary(); - try - { - if (shouldCollectSourceInformation) - { - navigationSessions.Add(source, PlatformServiceProvider.Instance.FileOperations.CreateNavigationSession(source)); - } - - // Get filter expression and skip discovery in case filter expression has parsing error. - bool filterHasError = false; - ITestCaseFilterExpression filterExpression = this.TestMethodFilter.GetFilterExpression(discoveryContext, logger, out filterHasError); - if (filterHasError) - { - return; - } - - foreach (var testElement in testElements) - { - var testCase = testElement.ToTestCase(); - - // Filter tests based on test case filters - if (filterExpression != null && filterExpression.MatchTestCase(testCase, (p) => this.TestMethodFilter.PropertyValueProvider(testCase, p)) == false) - { - continue; - } - - object testNavigationSession; - if (shouldCollectSourceInformation) - { - string testSource = testElement.TestMethod.DeclaringAssemblyName ?? source; - - if (!navigationSessions.TryGetValue(testSource, out testNavigationSession)) - { - testNavigationSession = PlatformServiceProvider.Instance.FileOperations.CreateNavigationSession(testSource); - navigationSessions.Add(testSource, testNavigationSession); - } - - if (testNavigationSession != null) - { - var className = testElement.TestMethod.DeclaringClassFullName - ?? testElement.TestMethod.FullClassName; - - var methodName = testElement.TestMethod.Name; - - // TODO currently nanoFramework does not support async - //// If it is async test method use compiler generated type and method name for navigation data. - //if (!string.IsNullOrEmpty(testElement.AsyncTypeName)) - //{ - // className = testElement.AsyncTypeName; - - // // compiler generated method name is "MoveNext". - // methodName = "MoveNext"; - //} - - int minLineNumber; - string fileName; - - PlatformServiceProvider.Instance.FileOperations.GetNavigationData( - testNavigationSession, - className, - methodName, - out minLineNumber, - out fileName); - - if (!string.IsNullOrEmpty(fileName)) - { - testCase.LineNumber = minLineNumber; - testCase.CodeFilePath = fileName; - } - } - } - - discoverySink.SendTestCase(testCase); - } - } - finally - { - foreach (object navigationSession in navigationSessions.Values) - { - PlatformServiceProvider.Instance.FileOperations.DisposeNavigationSession(navigationSession); - } - } - } - } -} diff --git a/source/TestAdapter/Exceptions/AssertFailedException.cs b/source/TestAdapter/Exceptions/AssertFailedException.cs deleted file mode 100644 index fe0744a..0000000 --- a/source/TestAdapter/Exceptions/AssertFailedException.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - - /// - /// AssertFailedException class. Used to indicate failure for a test case - /// - public partial class AssertFailedException : UnitTestAssertException - { - /// - /// Initializes a new instance of the class. - /// - /// The message. - /// The exception. - public AssertFailedException(string msg, Exception ex) - : base(msg, ex) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message. - public AssertFailedException(string msg) - : base(msg) - { - } - - /// - /// Initializes a new instance of the class. - /// - public AssertFailedException() - : base() - { - } - } -} diff --git a/source/TestAdapter/Exceptions/AssertInconclusiveException.cs b/source/TestAdapter/Exceptions/AssertInconclusiveException.cs deleted file mode 100644 index ce7dfea..0000000 --- a/source/TestAdapter/Exceptions/AssertInconclusiveException.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - - /// - /// The assert inconclusive exception. - /// - public partial class AssertInconclusiveException : UnitTestAssertException - { - /// - /// Initializes a new instance of the class. - /// - /// The message. - /// The exception. - public AssertInconclusiveException(string msg, Exception ex) - : base(msg, ex) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message. - public AssertInconclusiveException(string msg) - : base(msg) - { - } - - /// - /// Initializes a new instance of the class. - /// - public AssertInconclusiveException() - : base() - { - } - } -} diff --git a/source/TestAdapter/Exceptions/UnitTestAssertException.cs b/source/TestAdapter/Exceptions/UnitTestAssertException.cs deleted file mode 100644 index fe31fc4..0000000 --- a/source/TestAdapter/Exceptions/UnitTestAssertException.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - - /// - /// Base class for Framework Exceptions. - /// - public abstract partial class UnitTestAssertException : Exception - { - /// - /// Initializes a new instance of the class. - /// - protected UnitTestAssertException() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message. - /// The exception. - protected UnitTestAssertException(string msg, Exception ex) - : base(msg, ex) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message. - protected UnitTestAssertException(string msg) - : base(msg) - { - } - } -} diff --git a/source/TestAdapter/Execution/LogMessageListener.cs b/source/TestAdapter/Execution/LogMessageListener.cs deleted file mode 100644 index 40e788d..0000000 --- a/source/TestAdapter/Execution/LogMessageListener.cs +++ /dev/null @@ -1,148 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Execution -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Logging; - using System; - using System.Globalization; - using System.IO; - - /// - /// Listens for log messages and Debug.WriteLine - /// Note that this class is not thread-safe and thus should only be used when unit tests are being run serially. - /// - public class LogMessageListener : IDisposable - { - private static LogMessageListener activeRedirector; - private readonly LogMessageListener previousRedirector; - private readonly TextWriter redirectLoggerOut; - private readonly TextWriter redirectStdErr; - private readonly bool captureDebugTraces; - - /// - /// Trace listener to capture Trace.WriteLines in the test cases - /// - private ITraceListener traceListener; - - /// - /// Trace listener Manager to perform operation on tracelistener objects. - /// - private ITraceListenerManager traceListenerManager; - - /// - /// Initializes a new instance of the class. - /// - /// Captures debug traces if true. - public LogMessageListener(bool captureDebugTraces) - { - this.captureDebugTraces = captureDebugTraces; - - // Cache the original output/error streams and replace it with the own stream. - this.redirectLoggerOut = new ThreadSafeStringWriter(CultureInfo.InvariantCulture); - this.redirectStdErr = new ThreadSafeStringWriter(CultureInfo.InvariantCulture); - - Logger.OnLogMessage += this.redirectLoggerOut.WriteLine; - - // Cache the previous redirector if any and replace the trace listener. - this.previousRedirector = activeRedirector; - - if (this.captureDebugTraces) - { - this.traceListener = PlatformServiceProvider.Instance.GetTraceListener(new ThreadSafeStringWriter(CultureInfo.InvariantCulture)); - this.traceListenerManager = PlatformServiceProvider.Instance.GetTraceListenerManager(this.redirectLoggerOut, this.redirectStdErr); - - // If there was a previous LogMessageListener active, remove its - // TraceListener (it will be restored when this one is disposed). - if (this.previousRedirector != null && this.previousRedirector.traceListener != null) - { - this.traceListenerManager.Remove(this.previousRedirector.traceListener); - } - - this.traceListenerManager.Add(this.traceListener); - } - - activeRedirector = this; - } - - ~LogMessageListener() - { - this.Dispose(false); - } - - /// - /// Gets logger output - /// - public string StandardOutput => this.redirectLoggerOut.ToString(); - - /// - /// Gets 'Error' Output from the redirected stream - /// - public string StandardError => this.redirectStdErr.ToString(); - - /// - /// Gets 'Trace' Output from the redirected stream - /// - public string DebugTrace - { - get - { - return (this.traceListener == null || this.traceListener.GetWriter() == null) ? - string.Empty : this.traceListener.GetWriter().ToString(); - } - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - Logger.OnLogMessage -= this.redirectLoggerOut.WriteLine; - Logger.OnLogMessage -= this.redirectStdErr.WriteLine; - - this.redirectLoggerOut.Dispose(); - this.redirectStdErr.Dispose(); - - if (this.captureDebugTraces) - { - try - { - this.traceListenerManager.Remove(this.traceListener); - - // Restore the previous LogMessageListener's TraceListener (if there was one) - if (this.previousRedirector != null && this.previousRedirector.traceListener != null) - { - this.traceListenerManager.Add(this.previousRedirector.traceListener); - } - } - catch (Exception e) - { - // Catch all exceptions since Dispose should not throw. - PlatformServiceProvider.Instance.AdapterTraceLogger.LogError( - "ConsoleOutputRedirector.Dispose threw exception: {0}", - e); - } - - if (this.traceListener != null) - { - // Dispose trace manager and listeners - this.traceListenerManager.Dispose(this.traceListener); - this.traceListenerManager = null; - this.traceListener = null; - } - } - - activeRedirector = this.previousRedirector; - } - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/Execution/TestAssemblyInfo.cs b/source/TestAdapter/Execution/TestAssemblyInfo.cs deleted file mode 100644 index 87058a4..0000000 --- a/source/TestAdapter/Execution/TestAssemblyInfo.cs +++ /dev/null @@ -1,245 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Execution -{ - using Extensions; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using ObjectModel; - using System; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Reflection; - - /// - /// Defines TestAssembly Info object - /// - public class TestAssemblyInfo - { - private MethodInfo assemblyCleanupMethod; - - private MethodInfo assemblyInitializeMethod; - private object assemblyInfoExecuteSyncObject; - - /// - /// Initializes a new instance of the class. - /// - internal TestAssemblyInfo() - { - this.assemblyInfoExecuteSyncObject = new object(); - } - - /// - /// Gets AssemblyInitialize method for the assembly. - /// - public MethodInfo AssemblyInitializeMethod - { - get - { - return this.assemblyInitializeMethod; - } - - internal set - { - if (this.assemblyInitializeMethod != null) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiAssemblyInit, this.assemblyInitializeMethod.DeclaringType.FullName); - throw new TypeInspectionException(message); - } - - this.assemblyInitializeMethod = value; - } - } - - /// - /// Gets AssemblyCleanup method for the assembly. - /// - public MethodInfo AssemblyCleanupMethod - { - get - { - return this.assemblyCleanupMethod; - } - - internal set - { - if (this.assemblyCleanupMethod != null) - { - string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiAssemblyClean, this.assemblyCleanupMethod.DeclaringType.FullName); - throw new TypeInspectionException(message); - } - - this.assemblyCleanupMethod = value; - } - } - - /// - /// Gets a value indicating whether AssemblyInitialize has been executed. - /// - public bool IsAssemblyInitializeExecuted { get; internal set; } - - /// - /// Gets the assembly initialization exception. - /// - public Exception AssemblyInitializationException { get; internal set; } - - /// - /// Gets a value indicating whether this assembly has an executable AssemblyCleanup method. - /// - public bool HasExecutableCleanupMethod - { - get - { - // If no assembly cleanup, then continue with the next one. - if (this.AssemblyCleanupMethod == null) - { - return false; - } - - // If assembly initialization was successful, then only call assembly cleanup. - if (this.AssemblyInitializationException != null) - { - return false; - } - - return true; - } - } - - /// - /// Runs assembly initialize method. - /// - /// The test context. - /// Throws a test failed exception if the initialization method throws an exception. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - public void RunAssemblyInitialize(TestContext testContext) - { - // No assembly initialize => nothing to do. - if (this.AssemblyInitializeMethod == null) - { - return; - } - - if (testContext == null) - { - throw new NullReferenceException(Resource.TestContextIsNull); - } - - // If assembly initialization is not done, then do it. - if (!this.IsAssemblyInitializeExecuted) - { - // Aquiring a lock is usually a costly operation which does not need to be - // performed every time if the assembly init is already executed. - lock (this.assemblyInfoExecuteSyncObject) - { - // Perform a check again. - if (!this.IsAssemblyInitializeExecuted) - { - try - { - this.AssemblyInitializeMethod.InvokeAsSynchronousTask(null, testContext); - } - catch (Exception ex) - { - this.AssemblyInitializationException = ex; - } - finally - { - this.IsAssemblyInitializeExecuted = true; - } - } - } - } - - // If assemblyInitialization was successful, then dont do anything - if (this.AssemblyInitializationException == null) - { - return; - } - - // Cache and return an already created TestFailedException. - if (this.AssemblyInitializationException is TestFailedException) - { - throw this.AssemblyInitializationException; - } - - var realException = this.AssemblyInitializationException.InnerException - ?? this.AssemblyInitializationException; - - var outcome = UnitTestOutcome.Failed; - string errorMessage = null; - StackTraceInformation stackTraceInfo = null; - if (!realException.TryGetUnitTestAssertException(out outcome, out errorMessage, out stackTraceInfo)) - { - var exception = realException.GetType().ToString(); - var message = StackTraceHelper.GetExceptionMessage(realException); - errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_AssemblyInitMethodThrows, - this.AssemblyInitializeMethod.DeclaringType.FullName, - this.AssemblyInitializeMethod.Name, - exception, - message); - stackTraceInfo = StackTraceHelper.GetStackTraceInformation(realException); - } - - var testFailedException = new TestFailedException(outcome, errorMessage, stackTraceInfo); - this.AssemblyInitializationException = testFailedException; - - throw testFailedException; - } - - /// - /// Run assembly cleanup methods - /// - /// - /// Any exception that can be thrown as part of a assembly cleanup as warning messages. - /// - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - public string RunAssemblyCleanup() - { - if (this.AssemblyCleanupMethod == null) - { - return null; - } - - lock (this.assemblyInfoExecuteSyncObject) - { - try - { - this.AssemblyCleanupMethod.InvokeAsSynchronousTask(null); - - return null; - } - catch (Exception ex) - { - var realException = ex.InnerException ?? ex; - - string errorMessage; - - // special case AssertFailedException to trim off part of the stack trace - if (realException is AssertFailedException || - realException is AssertInconclusiveException) - { - errorMessage = realException.Message; - } - else - { - errorMessage = StackTraceHelper.GetExceptionMessage(realException); - } - - return string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_AssemblyCleanupMethodWasUnsuccesful, - this.AssemblyCleanupMethod.DeclaringType.Name, - this.AssemblyCleanupMethod.Name, - errorMessage, - StackTraceHelper.GetStackTraceInformation(realException)?.ErrorStackTrace); - } - } - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/Execution/TestCaseDiscoverySink.cs b/source/TestAdapter/Execution/TestCaseDiscoverySink.cs deleted file mode 100644 index 89313f3..0000000 --- a/source/TestAdapter/Execution/TestCaseDiscoverySink.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Execution -{ - using System.Collections.Generic; - using System.Collections.ObjectModel; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - - /// - /// The test case discovery sink. - /// - internal class TestCaseDiscoverySink : ITestCaseDiscoverySink - { - /// - /// Initializes a new instance of the class. - /// - public TestCaseDiscoverySink() - { - this.Tests = new Collection(); - } - - /// - /// Gets the tests. - /// - public ICollection Tests { get; private set; } - - /// - /// Sends the test case. - /// - /// The discovered test. - public void SendTestCase(TestCase discoveredTest) - { - if (discoveredTest != null) - { - this.Tests.Add(discoveredTest); - } - } - } -} diff --git a/source/TestAdapter/Execution/TestClassInfo.cs b/source/TestAdapter/Execution/TestClassInfo.cs deleted file mode 100644 index 6679105..0000000 --- a/source/TestAdapter/Execution/TestClassInfo.cs +++ /dev/null @@ -1,352 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Reflection; - using Extensions; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Execution; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using ObjectModel; - using UnitTestOutcome = nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel.UnitTestOutcome; - - /// - /// Defines the TestClassInfo object - /// - public class TestClassInfo - { - private MethodInfo classCleanupMethod; - private MethodInfo classInitializeMethod; - private MethodInfo testCleanupMethod; - private MethodInfo testInitializeMethod; - private object testClassExecuteSyncObject; - - /// - /// Initializes a new instance of the class. - /// - /// Underlying test class type. - /// Constructor for the test class. - /// Reference to the property in test class. - /// Test class attribute. - /// Parent assembly info. - internal TestClassInfo( - Type type, - ConstructorInfo constructor, - PropertyInfo testContextProperty, - TestClassAttribute classAttribute, - TestAssemblyInfo parent) - { - Debug.Assert(type != null, "Type should not be null"); - Debug.Assert(constructor != null, "Constructor should not be null"); - Debug.Assert(parent != null, "Parent should not be null"); - Debug.Assert(classAttribute != null, "ClassAtribute should not be null"); - - this.ClassType = type; - this.Constructor = constructor; - this.TestContextProperty = testContextProperty; - this.BaseTestInitializeMethodsQueue = new Queue(); - this.BaseTestCleanupMethodsQueue = new Queue(); - this.Parent = parent; - this.ClassAttribute = classAttribute; - this.testClassExecuteSyncObject = new object(); - } - - /// - /// Gets the class attribute. - /// - public TestClassAttribute ClassAttribute { get; private set; } - - /// - /// Gets the class type. - /// - public Type ClassType { get; private set; } - - /// - /// Gets the constructor. - /// - public ConstructorInfo Constructor { get; private set; } - - /// - /// Gets the test context property. - /// - public PropertyInfo TestContextProperty { get; private set; } - - /// - /// Gets the parent . - /// - public TestAssemblyInfo Parent { get; private set; } - - /// - /// Gets the class initialize method. - /// - public MethodInfo ClassInitializeMethod - { - get - { - return this.classInitializeMethod; - } - - internal set - { - if (this.classInitializeMethod != null) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiClassInit, this.ClassType.FullName); - throw new TypeInspectionException(message); - } - - this.classInitializeMethod = value; - } - } - - /// - /// Gets a value indicating whether class initialize has executed. - /// - public bool IsClassInitializeExecuted { get; internal set; } - - /// - /// Gets the exception thrown during method invocation. - /// - public Exception ClassInitializationException { get; internal set; } - - /// - /// Gets the class cleanup method. - /// - public MethodInfo ClassCleanupMethod - { - get - { - return this.classCleanupMethod; - } - - internal set - { - if (this.classCleanupMethod != null) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiClassClean, this.ClassType.FullName); - throw new TypeInspectionException(message); - } - - this.classCleanupMethod = value; - } - } - - /// - /// Gets a value indicating whether this class info has a executable cleanup method. - /// - public bool HasExecutableCleanupMethod - { - get - { - // If no class cleanup, then continue with the next one. - if (this.ClassCleanupMethod == null) - { - return false; - } - - // If class initialization was successful, then only call class cleanup. - if (this.ClassInitializationException != null) - { - return false; - } - - return true; - } - } - - /// - /// Gets the test initialize method. - /// - public MethodInfo TestInitializeMethod - { - get - { - return this.testInitializeMethod; - } - - internal set - { - if (this.testInitializeMethod != null) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiInit, this.ClassType.FullName); - throw new TypeInspectionException(message); - } - - this.testInitializeMethod = value; - } - } - - /// - /// Gets the test cleanup method. - /// - public MethodInfo TestCleanupMethod - { - get - { - return this.testCleanupMethod; - } - - internal set - { - if (this.testCleanupMethod != null) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiClean, this.ClassType.FullName); - throw new TypeInspectionException(message); - } - - this.testCleanupMethod = value; - } - } - - /// - /// Gets a queue of test initialize methods to call for this type. - /// - public Queue BaseTestInitializeMethodsQueue { get; private set; } - - /// - /// Gets a queue of test cleanup methods to call for this type. - /// - public Queue BaseTestCleanupMethodsQueue { get; private set; } - - /// - /// Runs the class initialize method. - /// - /// The test context. - /// Throws a test failed exception if the initialization method throws an exception. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - public void RunClassInitialize(TestContext testContext) - { - // If no class initialize return - if (this.ClassInitializeMethod == null) - { - return; - } - - if (testContext == null) - { - throw new NullReferenceException(Resource.TestContextIsNull); - } - - // If class initialization is not done, then do it. - if (!this.IsClassInitializeExecuted) - { - // Aquiring a lock is usually a costly operation which does not need to be - // performed every time if the class init is already executed. - lock (this.testClassExecuteSyncObject) - { - // Perform a check again. - if (!this.IsClassInitializeExecuted) - { - try - { - this.ClassInitializeMethod.InvokeAsSynchronousTask(null, testContext); - } - catch (Exception ex) - { - this.ClassInitializationException = ex; - } - finally - { - this.IsClassInitializeExecuted = true; - } - } - } - } - - // If classInitialization was successful, then dont do anything - if (this.ClassInitializationException == null) - { - return; - } - - if (this.ClassInitializationException is TestFailedException) - { - throw this.ClassInitializationException; - } - - // Fail the current test if it was a failure. - var realException = this.ClassInitializationException.InnerException ?? this.ClassInitializationException; - - var outcome = UnitTestOutcome.Failed; - string errorMessage = null; - StackTraceInformation exceptionStackTraceInfo = null; - if (!realException.TryGetUnitTestAssertException(out outcome, out errorMessage, out exceptionStackTraceInfo)) - { - errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_ClassInitMethodThrows, - this.ClassType.FullName, - this.ClassInitializeMethod.Name, - realException.GetType().ToString(), - StackTraceHelper.GetExceptionMessage(realException)); - - exceptionStackTraceInfo = realException.TryGetStackTraceInformation(); - } - - var testFailedException = new TestFailedException(outcome, errorMessage, exceptionStackTraceInfo); - this.ClassInitializationException = testFailedException; - - throw testFailedException; - } - - /// - /// Run class cleanup methods - /// - /// - /// Any exception that can be thrown as part of a class cleanup as warning messages. - /// - [SuppressMessageAttribute("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - public string RunClassCleanup() - { - if (this.ClassCleanupMethod == null) - { - return null; - } - - if (this.IsClassInitializeExecuted || this.ClassInitializeMethod == null) - { - try - { - this.ClassCleanupMethod.InvokeAsSynchronousTask(null); - - return null; - } - catch (Exception exception) - { - var realException = exception.InnerException ?? exception; - - string errorMessage; - - // special case AssertFailedException to trim off part of the stack trace - if (realException is AssertFailedException || - realException is AssertInconclusiveException) - { - errorMessage = realException.Message; - } - else - { - errorMessage = StackTraceHelper.GetExceptionMessage(realException); - } - - return string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_ClassCleanupMethodWasUnsuccesful, - this.ClassType.Name, - this.ClassCleanupMethod.Name, - errorMessage, - StackTraceHelper.GetStackTraceInformation(realException)?.ErrorStackTrace); - } - } - - return null; - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/Execution/TestExecutionManager.cs b/source/TestAdapter/Execution/TestExecutionManager.cs deleted file mode 100644 index 15c8473..0000000 --- a/source/TestAdapter/Execution/TestExecutionManager.cs +++ /dev/null @@ -1,518 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Execution -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - - - /// - /// Class responsible for execution of tests at assembly level and sending tests via framework handle - /// - public class TestExecutionManager - { - /// - /// Specifies whether the test run is canceled or not - /// - private TestRunCancellationToken cancellationToken; - - /// - /// Dictionary for test run parameters - /// - private IDictionary sessionParameters; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Need to over-write the keys in dictionary.")] - public TestExecutionManager() - { - this.TestMethodFilter = new TestMethodFilter(); - this.sessionParameters = new Dictionary(); - } - - /// - /// Gets or sets method filter for filtering tests - /// - private TestMethodFilter TestMethodFilter { get; set; } - - /// - /// Gets or sets a value indicating whether any test executed has failed. - /// - private bool HasAnyTestFailed { get; set; } - - /// - /// Runs the tests. - /// - /// Tests to be run. - /// Context to use when executing the tests. - /// Handle to the framework to record results and to do framework operations. - /// Test run cancellation tokenn - public void RunTests( - IEnumerable tests, - IRunContext runContext, - IFrameworkHandle frameworkHandle, - TestRunCancellationToken runCancellationToken) - { - Debug.Assert(tests != null, "tests"); - Debug.Assert(runContext != null, "runContext"); - Debug.Assert(frameworkHandle != null, "frameworkHandle"); - Debug.Assert(runCancellationToken != null, "runCancellationToken"); - - this.cancellationToken = runCancellationToken; - - //var isDeploymentDone = PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, runContext, frameworkHandle); - - //// Placing this after deployment since we need information post deployment that we pass in as properties. - //this.CacheSessionParameters(runContext, frameworkHandle); - - //// Execute the tests - //this.ExecuteTests(tests, runContext, frameworkHandle, isDeploymentDone); - - //if (!this.HasAnyTestFailed) - //{ - // PlatformServiceProvider.Instance.TestDeployment.Cleanup(); - //} - } - - public void RunTests( - IEnumerable sources, - IRunContext runContext, - IFrameworkHandle frameworkHandle, - TestRunCancellationToken cancellationToken) - { - this.cancellationToken = cancellationToken; - - var discoverySink = new TestCaseDiscoverySink(); - - var tests = new List(); - - // deploy everything first. - foreach (var source in sources) - { - if (this.cancellationToken.Canceled) - { - break; - } - - var logger = (IMessageLogger)frameworkHandle; - - // discover the tests - this.GetUnitTestDiscoverer().DiscoverTestsInSource(source, logger, discoverySink, runContext); - tests.AddRange(discoverySink.Tests); - - // Clear discoverSinksTests so that it just stores test for one source at one point of time - discoverySink.Tests.Clear(); - } - - bool isDeploymentDone = PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, runContext, frameworkHandle); - - // Placing this after deployment since we need information post deployment that we pass in as properties. - this.CacheSessionParameters(runContext, frameworkHandle); - - // Run tests. - this.ExecuteTests(tests, runContext, frameworkHandle, isDeploymentDone); - - if (!this.HasAnyTestFailed) - { - PlatformServiceProvider.Instance.TestDeployment.Cleanup(); - } - } - - /// - /// Execute the parameter tests - /// - /// Tests to execute. - /// The run context. - /// Handle to record test start/end/results. - /// Indicates if deployment is done. - internal virtual void ExecuteTests( - IEnumerable tests, - IRunContext runContext, - IFrameworkHandle frameworkHandle, - bool isDeploymentDone) - { - var testsBySource = from test in tests - group test by test.Source into testGroup - select new { Source = testGroup.Key, Tests = testGroup }; - - foreach (var group in testsBySource) - { - this.ExecuteTestsInSource(group.Tests, runContext, frameworkHandle, group.Source, isDeploymentDone); - } - } - - internal virtual UnitTestDiscoverer GetUnitTestDiscoverer() - { - return new UnitTestDiscoverer(); - } - - internal void SendTestResults( - TestCase test, - UnitTestResult[] unitTestResults, - DateTimeOffset startTime, - DateTimeOffset endTime, - ITestExecutionRecorder testExecutionRecorder) - { - if (!(unitTestResults?.Length > 0)) - { - return; - } - - foreach (var unitTestResult in unitTestResults) - { - if (test == null) - { - continue; - } - - var testResult = unitTestResult.ToTestResult(test, startTime, endTime, MSTestSettings.CurrentSettings.MapInconclusiveToFailed); - - if (unitTestResult.DatarowIndex >= 0) - { - testResult.DisplayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, test.DisplayName, unitTestResult.DatarowIndex); - } - - testExecutionRecorder.RecordEnd(test, testResult.Outcome); - - if (testResult.Outcome == TestOutcome.Failed) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("TestExecutor:Test {0} failed. ErrorMessage:{1}, ErrorStackTrace:{2}.", testResult.TestCase.FullyQualifiedName, testResult.ErrorMessage, testResult.ErrorStackTrace); - this.HasAnyTestFailed = true; - } - - try - { - testExecutionRecorder.RecordResult(testResult); - } - catch (TestCanceledException) - { - // Ignore this exception - } - } - } - - private static bool MatchTestFilter( - ITestCaseFilterExpression filterExpression, - TestCase test, - TestMethodFilter testMethodFilter) - { - if (filterExpression != null && filterExpression.MatchTestCase(test, p => testMethodFilter.PropertyValueProvider(test, p)) == false) - { - // Skip test if not fitting filter criteria. - return false; - } - - return true; - } - - /// - /// Execute the parameter tests present in parameter source - /// - /// Tests to execute. - /// The run context. - /// Handle to record test start/end/results. - /// The test container for the tests. - /// Indicates if deployment is done. - private void ExecuteTestsInSource( - IEnumerable tests, - IRunContext runContext, - IFrameworkHandle frameworkHandle, - string source, - bool isDeploymentDone) - { - Debug.Assert(!string.IsNullOrEmpty(source), "Source cannot be empty"); - - if (isDeploymentDone) - { - source = Path.Combine(PlatformServiceProvider.Instance.TestDeployment.GetDeploymentDirectory(), Path.GetFileName(source)); - } - - using (var isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(source, runContext?.RunSettings, frameworkHandle)) - { - // Create an instance of a type defined in adapter so that adapter gets loaded in the child app domain - var testRunner = isolationHost.CreateInstanceForType( - typeof(UnitTestRunner), - new object[] { MSTestSettings.CurrentSettings }) as UnitTestRunner; - - // After loading adapter reset the chils-domain's appbase to point to test source location - isolationHost.UpdateAppBaseToTestSourceLocation(); - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Created unit-test runner {0}", source); - - // Default test set is filtered tests based on user provided filter criteria - IEnumerable testsToRun = Enumerable.Empty(); - var filterExpression = this.TestMethodFilter.GetFilterExpression(runContext, frameworkHandle, out var filterHasError); - if (filterHasError) - { - // Bail out without processing everything else below. - return; - } - - testsToRun = tests.Where(t => MatchTestFilter(filterExpression, t, this.TestMethodFilter)); - - // this is done so that appropriate values of testcontext properties are set at source level - // and are merged with session level parameters - var sourceLevelParameters = PlatformServiceProvider.Instance.SettingsProvider.GetProperties(source); - - if (this.sessionParameters != null && this.sessionParameters.Count > 0) - { - sourceLevelParameters = sourceLevelParameters.Concat(this.sessionParameters).ToDictionary(x => x.Key, x => x.Value); - } - - //var sourceSettingsProvider = isolationHost.CreateInstanceForType( - // typeof(TestAssemblySettingsProvider), - // null) as TestAssemblySettingsProvider; - - //var sourceSettings = sourceSettingsProvider.GetSettings(source); - //var parallelWorkers = sourceSettings.Workers; - //var parallelScope = sourceSettings.Scope; - - //if (MSTestSettings.CurrentSettings.ParallelizationWorkers.HasValue) - //{ - // // The runsettings value takes precedence over an assembly level setting. Reset the level. - // parallelWorkers = MSTestSettings.CurrentSettings.ParallelizationWorkers.Value; - //} - - //if (MSTestSettings.CurrentSettings.ParallelizationScope.HasValue) - //{ - // // The runsettings value takes precedence over an assembly level setting. Reset the level. - // parallelScope = MSTestSettings.CurrentSettings.ParallelizationScope.Value; - //} - - //if (!MSTestSettings.CurrentSettings.DisableParallelization && sourceSettings.CanParallelizeAssembly && parallelWorkers > 0) - //{ - // // Parallelization is enabled. Let's do further classification for sets. - // var logger = (IMessageLogger)frameworkHandle; - // logger.SendMessage( - // TestMessageLevel.Informational, - // string.Format(CultureInfo.CurrentCulture, Resource.TestParallelizationBanner, source, parallelWorkers, parallelScope)); - - // // Create test sets for execution, we can execute them in parallel based on parallel settings - // IEnumerable> testsets = Enumerable.Empty>(); - - // // Parallel and not parallel sets. - // testsets = testsToRun.GroupBy(t => t.GetPropertyValue(TestAdapter.Constants.DoNotParallelizeProperty, false)); - - // var parallelizableTestSet = testsets.FirstOrDefault(g => g.Key == false); - // var nonparallelizableTestSet = testsets.FirstOrDefault(g => g.Key == true); - - // if (parallelizableTestSet != null) - // { - // ConcurrentQueue> queue = null; - - // // Chunk the sets into further groups based on parallel level - // switch (parallelScope) - // { - // case ExecutionScope.MethodLevel: - // queue = new ConcurrentQueue>(parallelizableTestSet.Select(t => new[] { t })); - // break; - // case ExecutionScope.ClassLevel: - // queue = new ConcurrentQueue>(parallelizableTestSet.GroupBy(t => t.GetPropertyValue(TestAdapter.Constants.TestClassNameProperty) as string)); - // break; - // } - - // var tasks = new List(); - - // for (int i = 0; i < parallelWorkers; i++) - // { - // tasks.Add(Task.Factory.StartNew( - // () => - // { - // while (!queue.IsEmpty) - // { - // if (this.cancellationToken != null && this.cancellationToken.Canceled) - // { - // // if a cancellation has been requested, do not queue any more test runs. - // break; - // } - - // if (queue.TryDequeue(out IEnumerable testSet)) - // { - // this.ExecuteTestsWithTestRunner(testSet, runContext, frameworkHandle, source, sourceLevelParameters, testRunner); - // } - // } - // }, - // CancellationToken.None, - // TaskCreationOptions.LongRunning, - // TaskScheduler.Default)); - // } - - // Task.WaitAll(tasks.ToArray()); - // } - - // // Queue the non parallel set - // if (nonparallelizableTestSet != null) - // { - // this.ExecuteTestsWithTestRunner(nonparallelizableTestSet, runContext, frameworkHandle, source, sourceLevelParameters, testRunner); - // } - //} - //else - { - this.ExecuteTestsWithTestRunner(testsToRun, runContext, frameworkHandle, source, sourceLevelParameters, testRunner); - } - - this.RunCleanup(frameworkHandle, testRunner); - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo( - "Executed tests belonging to source {0}", - source); - } - } - - private void ExecuteTestsWithTestRunner( - IEnumerable tests, - IRunContext runContext, - ITestExecutionRecorder testExecutionRecorder, - string source, - IDictionary sourceLevelParameters, - UnitTestRunner testRunner) - { - foreach (var currentTest in tests) - { - if (this.cancellationToken != null && this.cancellationToken.Canceled) - { - break; - } - - var unitTestElement = currentTest.ToUnitTestElement(source); - testExecutionRecorder.RecordStart(currentTest); - - var startTime = DateTimeOffset.Now; - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo( - "Executing test {0}", - unitTestElement.TestMethod.Name); - - // Run single test passing test context properties to it. - //var tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(currentTest); - //var testContextProperties = this.GetTestContextProperties(tcmProperties, sourceLevelParameters); - //var unitTestResult = testRunner.RunSingleTest(unitTestElement.TestMethod, testContextProperties); - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo( - "Executed test {0}", - unitTestElement.TestMethod.Name); - - var endTime = DateTimeOffset.Now; - - //this.SendTestResults(currentTest, unitTestResult, startTime, endTime, testExecutionRecorder); - this.SendTestResults(currentTest, new UnitTestResult[] { new UnitTestResult(UnitTestOutcome.NotFound, string.Format(CultureInfo.CurrentCulture, Resource.TestNotFound, currentTest.DisplayName) )}, startTime, endTime, testExecutionRecorder); - } - } - - /// - /// Get test context properties. - /// - /// Tcm properties. - /// Source level parameters. - /// Test context properties. - private IDictionary GetTestContextProperties( - IDictionary tcmProperties, - IDictionary sourceLevelParameters) - { - var testContextProperties = new Dictionary(); - - // Add tcm properties. - foreach (var propertyPair in tcmProperties) - { - testContextProperties[propertyPair.Key.Id] = propertyPair.Value; - } - - // Add source level parameters. - foreach (var propertyPair in sourceLevelParameters) - { - testContextProperties[propertyPair.Key] = propertyPair.Value; - } - - return testContextProperties; - } - - private void RunCleanup( - ITestExecutionRecorder testExecutionRecorder, - UnitTestRunner testRunner) - { - // All cleanups (Class and Assembly) run at the end of test execution. Failures in these cleanup - // methods will be reported as Warnings. - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executing cleanup methods."); - // var cleanupResult = testRunner.RunCleanup(); - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executed cleanup methods."); - //if (cleanupResult != null) - //{ - // IList warnings = cleanupResult.Warnings; - - // // Do not attach the standard output and error messages to any test result. It is not - // // guaranteed that a test method of same class would have run last. We will end up - // // adding stdout to test method of another class. - // this.LogWarnings(testExecutionRecorder, warnings); - //} - } - - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle errors in user specified run parameters")] - private void CacheSessionParameters( - IRunContext runContext, - ITestExecutionRecorder testExecutionRecorder) - { - if (!string.IsNullOrEmpty(runContext?.RunSettings?.SettingsXml)) - { - try - { - //var testRunParameters = RunSettingsUtilities.GetTestRunParameters(runContext.RunSettings.SettingsXml); - //if (testRunParameters != null) - //{ - // // Clear sessionParameters to prevent key collisions of test run parameters in case - // // "Keep Test Execution Engine Alive" is selected in VS. - // this.sessionParameters.Clear(); - // foreach (var kvp in testRunParameters) - // { - // this.sessionParameters.Add(kvp); - // } - //} - } - catch (Exception ex) - { - testExecutionRecorder.SendMessage(TestMessageLevel.Error, ex.Message); - } - } - } - - /// - /// Log the parameter warnings on the parameter logger - /// - /// Handle to record test start/end/results/messages. - /// Any warnings during run operation. - private void LogWarnings( - ITestExecutionRecorder testExecutionRecorder, - IEnumerable warnings) - { - if (warnings == null) - { - return; - } - - Debug.Assert(testExecutionRecorder != null, "Logger should not be null"); - - // log the warnings - foreach (string warning in warnings) - { - testExecutionRecorder.SendMessage(TestMessageLevel.Warning, warning); - } - } - } -} diff --git a/source/TestAdapter/Execution/TestMethodInfo.cs b/source/TestAdapter/Execution/TestMethodInfo.cs deleted file mode 100644 index aebdf83..0000000 --- a/source/TestAdapter/Execution/TestMethodInfo.cs +++ /dev/null @@ -1,712 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Execution -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Reflection; - using System.Text; - - /// - /// Defines the TestMethod Info object - /// - public class TestMethodInfo : TestAdapter.ITestMethod - { - /// - /// Specifies the timeout when it is not set in a test case - /// - public const int TimeoutWhenNotSet = 0; - - private object[] arguments; - - internal TestMethodInfo( - MethodInfo testMethod, - TestClassInfo parent, - TestMethodOptions testmethodOptions) - { - Debug.Assert(testMethod != null, "TestMethod should not be null"); - Debug.Assert(parent != null, "Parent should not be null"); - - this.TestMethod = testMethod; - this.Parent = parent; - this.TestMethodOptions = testmethodOptions; - } - - /// - /// Gets a value indicating whether timeout is set. - /// - public bool IsTimeoutSet => this.TestMethodOptions.Timeout != TimeoutWhenNotSet; - - /// - /// Gets the reason why the test is not runnable - /// - public string NotRunnableReason { get; internal set; } - - /// - /// Gets a value indicating whether test is runnnable - /// - public bool IsRunnable => string.IsNullOrEmpty(this.NotRunnableReason); - - public ParameterInfo[] ParameterTypes => this.TestMethod.GetParameters(); - - public Type ReturnType => this.TestMethod.ReturnType; - - public string TestClassName => this.Parent.ClassType.FullName; - - public string TestMethodName => this.TestMethod.Name; - - public MethodInfo MethodInfo => this.TestMethod; - - public object[] Arguments => this.arguments; - - /// - /// Gets testMethod referred by this object - /// - internal MethodInfo TestMethod { get; private set; } - - /// - /// Gets the parent class Info object - /// - internal TestClassInfo Parent { get; private set; } - - /// - /// Gets the options for the test method in this environment. - /// - internal TestMethodOptions TestMethodOptions { get; private set; } - - public Attribute[] GetAllAttributes(bool inherit) - { - return ReflectHelper.GetCustomAttributes(this.TestMethod, inherit) as Attribute[]; - } - - public TAttributeType[] GetAttributes(bool inherit) - where TAttributeType : Attribute - { - Attribute[] attributeArray = ReflectHelper.GetCustomAttributes(this.TestMethod, typeof(TAttributeType), inherit); - - TAttributeType[] tAttributeArray = attributeArray as TAttributeType[]; - if (tAttributeArray != null) - { - return tAttributeArray; - } - - List tAttributeList = new List(); - if (attributeArray != null) - { - foreach (Attribute attribute in attributeArray) - { - TAttributeType tAttribute = attribute as TAttributeType; - if (tAttribute != null) - { - tAttributeList.Add(tAttribute); - } - } - } - - return tAttributeList.ToArray(); - } - - /// - /// Execute test method. Capture failures, handle async and return result. - /// - /// - /// Arguments to pass to test method. (E.g. For data driven) - /// - /// Result of test method invocation. - public virtual TestResult Invoke(object[] arguments) - { - Stopwatch watch = new Stopwatch(); - TestResult result = null; - - // check if arguments are set for data driven tests - if (arguments == null) - { - arguments = this.Arguments; - } - - using (LogMessageListener listener = new LogMessageListener(this.TestMethodOptions.CaptureDebugTraces)) - { - watch.Start(); - try - { - //if (this.IsTimeoutSet) - //{ - // result = this.ExecuteInternalWithTimeout(arguments); - //} - //else - { - result = this.ExecuteInternal(arguments); - } - } - finally - { - // Handle logs & debug traces. - watch.Stop(); - - if (result != null) - { - result.Duration = watch.Elapsed; - result.DebugTrace = listener.DebugTrace; - result.LogOutput = listener.StandardOutput; - result.LogError = listener.StandardError; - result.TestContextMessages = this.TestMethodOptions.TestContext.GetAndClearDiagnosticMessages(); - result.ResultFiles = this.TestMethodOptions.TestContext.GetResultFiles(); - } - } - } - - return result; - } - - internal void SetArguments(object[] arguments) - { - this.arguments = arguments; - } - - /// - /// Execute test without timeout. - /// - /// Arguments to be passed to the method. - /// The result of the execution. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private TestResult ExecuteInternal(object[] arguments) - { - Debug.Assert(this.TestMethod != null, "UnitTestExecuter.DefaultTestMethodInvoke: testMethod = null."); - - var result = new TestResult(); - - // TODO remove dry violation with TestMethodRunner - var classInstance = this.CreateTestClassInstance(result); - var testContextSetup = false; - bool isExceptionThrown = false; - bool hasTestInitializePassed = false; - Exception testRunnerException = null; - - try - { - try - { - if (classInstance != null && this.SetTestContext(classInstance, result)) - { - // For any failure after this point, we must run TestCleanup - testContextSetup = true; - - if (this.RunTestInitializeMethod(classInstance, result)) - { - hasTestInitializePassed = true; - // TODO - //PlatformServiceProvider.Instance.ThreadOperations.ExecuteWithAbortSafety( - // () => this.TestMethod.InvokeAsSynchronousTask(classInstance, arguments)); - result.Outcome = UnitTestOutcome.Passed; - } - } - } - catch (Exception ex) - { - isExceptionThrown = true; - - if (this.IsExpectedException(ex, result)) - { - // Expected Exception was thrown, so Pass the test - result.Outcome = UnitTestOutcome.Passed; - } - else if (result.TestFailureException == null) - { - // This block should not throw. If it needs to throw, then handling of - // ThreadAbortException will need to be revisited. See comment in RunTestMethod. - result.TestFailureException = this.HandleMethodException( - ex, - this.TestClassName, - this.TestMethodName); - } - - if (result.Outcome != UnitTestOutcome.Passed) - { - if (ex is AssertInconclusiveException || ex.InnerException is AssertInconclusiveException) - { - result.Outcome = UnitTestOutcome.Inconclusive; - } - else - { - result.Outcome = UnitTestOutcome.Failed; - } - } - } - - // if we get here, the test method did not throw the exception - // if the user specified that the test was going to throw an exception, and - // it did not, we should fail the test - // We only perform this check if the test initialize passes and the test method is actually run. - if (hasTestInitializePassed && !isExceptionThrown && this.TestMethodOptions.ExpectedException != null) - { - result.TestFailureException = new TestFailedException( - UnitTestOutcome.Failed, - this.TestMethodOptions.ExpectedException.NoExceptionMessage); - result.Outcome = UnitTestOutcome.Failed; - } - } - catch (Exception exception) - { - testRunnerException = exception; - } - - // Set the current tests outcome before cleanup so it can be used in the cleanup logic. - this.TestMethodOptions.TestContext.SetOutcome(result.Outcome); - - // TestCleanup can potentially be a long running operation which should'nt ideally be in a finally block. - // Pulling it out so extension writers can abort custom cleanups if need be. Having this in a finally block - // does not allow a threadabort exception to be raised within the block but throws one after finally is executed - // crashing the process. This was blocking writing an extension for Dynamic Timeout in VSO. - if (classInstance != null && testContextSetup) - { - this.RunTestCleanupMethod(classInstance, result); - } - - if (testRunnerException != null) - { - throw testRunnerException; - } - - return result; - } - - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private bool IsExpectedException(Exception ex, TestResult result) - { - Exception realException = this.GetRealException(ex); - - // if the user specified an expected exception, we need to check if this - // exception was thrown. If it was thrown, we should pass the test. In - // case a different exception was thrown, the test is seen as failure - if (this.TestMethodOptions.ExpectedException != null) - { - Exception exceptionFromVerify; - try - { - // If the expected exception attribute's Verify method returns, then it - // considers this exception as expected, so the test passed - this.TestMethodOptions.ExpectedException.Verify(realException); - return true; - } - catch (Exception verifyEx) - { - var isTargetInvocationError = verifyEx is TargetInvocationException; - if (isTargetInvocationError && verifyEx.InnerException != null) - { - exceptionFromVerify = verifyEx.InnerException; - } - else - { - // Verify threw an exception, so the expected exception attribute does not - // consider this exception to be expected. Include the exception message in - // the test result. - exceptionFromVerify = verifyEx; - } - } - - // See if the verification exception (thrown by the expected exception - // attribute's Verify method) is an AssertInconclusiveException. If so, set - // the test outcome to Inconclusive. - result.TestFailureException = new TestFailedException( - exceptionFromVerify is AssertInconclusiveException ? UnitTestOutcome.Inconclusive : UnitTestOutcome.Failed, - exceptionFromVerify.TryGetMessage(), - realException.TryGetStackTraceInformation()); - return false; - } - else - { - return false; - } - } - - private Exception GetRealException(Exception ex) - { - if (ex is TargetInvocationException) - { - Debug.Assert(ex.InnerException != null, "Inner exception of TargetInvocationException is null. This should occur because we should have caught this case above."); - - // Our reflected call will typically always get back a TargetInvocationException - // containing the real exception thrown by the test method as its inner exception - return ex.InnerException; - } - else - { - return ex; - } - } - - /// - /// Handles the exception that is thrown by a test method. The exception can either - /// be expected or not expected - /// - /// Exception that was thrown - /// The class name. - /// The method name. - /// Test framework exception with details. - private Exception HandleMethodException(Exception ex, string className, string methodName) - { - Debug.Assert(ex != null, "exception should not be null."); - - var isTargetInvocationException = ex is TargetInvocationException; - if (isTargetInvocationException && ex.InnerException == null) - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.UTA_FailedToGetTestMethodException, className, methodName); - return new TestFailedException(UnitTestOutcome.Error, errorMessage); - } - - // Get the real exception thrown by the test method - Exception realException = this.GetRealException(ex); - string exceptionMessage = null; - StackTraceInformation exceptionStackTraceInfo = null; - var outcome = UnitTestOutcome.Failed; - - if (realException.TryGetUnitTestAssertException(out outcome, out exceptionMessage, out exceptionStackTraceInfo)) - { - return new TestFailedException(outcome.ToUnitTestOutcome(), exceptionMessage, exceptionStackTraceInfo, realException); - } - else - { - string errorMessage; - - // Handle special case of UI objects in TestMethod to suggest UITestMethod - if (realException.HResult == -2147417842) - { - errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_WrongThread, - string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestMethodThrows, className, methodName, StackTraceHelper.GetExceptionMessage(realException))); - } - else - { - errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_TestMethodThrows, - className, - methodName, - StackTraceHelper.GetExceptionMessage(realException)); - } - - StackTraceInformation stackTrace = null; - - // For ThreadAbortException (that can be thrown only by aborting a thread as there's no public constructor) - // there's no inner exception and exception itself contains reflection-related stack trace - // (_RuntimeMethodHandle.InvokeMethodFast <- _RuntimeMethodHandle.Invoke <- UnitTestExecuter.RunTestMethod) - // which has no meaningful info for the user. Thus, we do not show call stack for ThreadAbortException. - if (realException.GetType().Name != "ThreadAbortException") - { - stackTrace = StackTraceHelper.GetStackTraceInformation(realException); - } - - return new TestFailedException(UnitTestOutcome.Failed, errorMessage, stackTrace, realException); - } - } - - /// - /// Runs TestCleanup methods of parent TestClass and base classes. - /// - /// Instance of TestClass. - /// Instance of TestResult. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private void RunTestCleanupMethod(object classInstance, TestResult result) - { - Debug.Assert(classInstance != null, "classInstance != null"); - Debug.Assert(result != null, "result != null"); - - var testCleanupMethod = this.Parent.TestCleanupMethod; - try - { - try - { - // Test cleanups are called in the order of discovery - // Current TestClass -> Parent -> Grandparent - testCleanupMethod?.InvokeAsSynchronousTask(classInstance, null); - var baseTestCleanupQueue = new Queue(this.Parent.BaseTestCleanupMethodsQueue); - while (baseTestCleanupQueue.Count > 0) - { - testCleanupMethod = baseTestCleanupQueue.Dequeue(); - testCleanupMethod?.InvokeAsSynchronousTask(classInstance, null); - } - } - finally - { - (classInstance as IDisposable)?.Dispose(); - } - } - catch (Exception ex) - { - var cleanupOutcome = UnitTestOutcome.Failed; - var cleanupError = new StringBuilder(); - var cleanupStackTrace = new StringBuilder(); - StackTraceInformation cleanupStackTraceInfo = null; - - TestFailedException testFailureException = result.TestFailureException as TestFailedException; - testFailureException.TryGetTestFailureExceptionMessageAndStackTrace(cleanupError, cleanupStackTrace); - - if (cleanupStackTrace.Length > 0) - { - cleanupStackTrace.Append(Resource.UTA_CleanupStackTrace); - cleanupStackTrace.Append(Environment.NewLine); - } - - Exception realException = ex.GetInnerExceptionOrDefault(); - string exceptionMessage = null; - StackTraceInformation realExceptionStackTraceInfo = null; - - // special case UnitTestAssertException to trim off part of the stack trace - if (!realException.TryGetUnitTestAssertException(out cleanupOutcome, out exceptionMessage, out realExceptionStackTraceInfo)) - { - cleanupOutcome = UnitTestOutcome.Failed; - exceptionMessage = this.GetTestCleanUpExceptionMessage(testCleanupMethod, realException); - realExceptionStackTraceInfo = realException.TryGetStackTraceInformation(); - } - - cleanupError.Append(exceptionMessage); - if (realExceptionStackTraceInfo != null) - { - cleanupStackTrace.Append(realExceptionStackTraceInfo.ErrorStackTrace); - cleanupStackTraceInfo = cleanupStackTraceInfo ?? realExceptionStackTraceInfo; - } - - UnitTestOutcome outcome = testFailureException == null ? cleanupOutcome : cleanupOutcome.GetMoreImportantOutcome(result.Outcome); - StackTraceInformation finalStackTraceInfo = cleanupStackTraceInfo != null ? - new StackTraceInformation( - cleanupStackTrace.ToString(), - cleanupStackTraceInfo.ErrorFilePath, - cleanupStackTraceInfo.ErrorLineNumber, - cleanupStackTraceInfo.ErrorColumnNumber) : - new StackTraceInformation(cleanupStackTrace.ToString()); - - result.Outcome = outcome; - result.TestFailureException = new TestFailedException(outcome.ToUnitTestOutcome(), cleanupError.ToString(), finalStackTraceInfo); - } - } - - private string GetTestCleanUpExceptionMessage(MethodInfo testCleanupMethod, Exception exception) - { - if (testCleanupMethod != null) - { - return string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_CleanupMethodThrows, - this.TestClassName, - testCleanupMethod?.Name, - exception.GetType().ToString(), - StackTraceHelper.GetExceptionMessage(exception)); - } - else - { - return string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_CleanupMethodThrowsGeneralError, - this.TestClassName, - StackTraceHelper.GetExceptionMessage(exception)); - } - } - - /// - /// Runs TestInitialize methods of parent TestClass and the base classes. - /// - /// Instance of TestClass. - /// Instance of TestResult. - /// True if the TestInitialize method(s) did not throw an exception. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private bool RunTestInitializeMethod(object classInstance, TestResult result) - { - Debug.Assert(classInstance != null, "classInstance != null"); - Debug.Assert(result != null, "result != null"); - - MethodInfo testInitializeMethod = null; - try - { - // TestInitialize methods for base classes are called in reverse order of discovery - // Grandparent -> Parent -> Child TestClass - var baseTestInitializeStack = new Stack(this.Parent.BaseTestInitializeMethodsQueue); - while (baseTestInitializeStack.Count > 0) - { - testInitializeMethod = baseTestInitializeStack.Pop(); - testInitializeMethod?.InvokeAsSynchronousTask(classInstance, null); - } - - testInitializeMethod = this.Parent.TestInitializeMethod; - testInitializeMethod?.InvokeAsSynchronousTask(classInstance, null); - - return true; - } - catch (Exception ex) - { - var innerException = ex.GetInnerExceptionOrDefault(); - string exceptionMessage = null; - StackTraceInformation exceptionStackTraceInfo = null; - var outcome = UnitTestOutcome.Failed; - - if (innerException.TryGetUnitTestAssertException(out outcome, out exceptionMessage, out exceptionStackTraceInfo)) - { - result.Outcome = outcome; - result.TestFailureException = new TestFailedException( - UnitTestOutcome.Failed, - exceptionMessage, - exceptionStackTraceInfo); - } - else - { - var stackTrace = StackTraceHelper.GetStackTraceInformation(innerException); - var errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_InitMethodThrows, - this.TestClassName, - testInitializeMethod?.Name, - StackTraceHelper.GetExceptionMessage(innerException)); - - result.Outcome = UnitTestOutcome.Failed; - result.TestFailureException = new TestFailedException(UnitTestOutcome.Failed, errorMessage, stackTrace); - } - } - - return false; - } - - /// - /// Sets the on . - /// - /// - /// Reference to instance of TestClass. - /// - /// - /// Reference to instance of . - /// - /// - /// True if there no exceptions during set context operation. - /// - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private bool SetTestContext(object classInstance, TestResult result) - { - Debug.Assert(classInstance != null, "classInstance != null"); - Debug.Assert(result != null, "result != null"); - - try - { - if (this.Parent.TestContextProperty != null && this.Parent.TestContextProperty.CanWrite) - { - this.Parent.TestContextProperty.SetValue(classInstance, this.TestMethodOptions.TestContext); - } - - return true; - } - catch (Exception ex) - { - var stackTraceInfo = StackTraceHelper.GetStackTraceInformation(ex.GetInnerExceptionOrDefault()); - var errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_TestContextSetError, - this.TestClassName, - StackTraceHelper.GetExceptionMessage(ex.GetInnerExceptionOrDefault())); - - result.Outcome = UnitTestOutcome.Failed; - result.TestFailureException = new TestFailedException(UnitTestOutcome.Failed, errorMessage, stackTraceInfo); - } - - return false; - } - - /// - /// Creates an instance of TestClass. The TestMethod is invoked on this instance. - /// - /// - /// Reference to the for this TestMethod. - /// Outcome and TestFailureException are updated based on instance creation. - /// - /// - /// An instance of the TestClass. Returns null if there are errors during class instantiation. - /// - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private object CreateTestClassInstance(TestResult result) - { - object classInstance = null; - try - { - classInstance = this.Parent.Constructor.Invoke(null); - } - catch (Exception ex) - { - // In most cases, exception will be TargetInvocationException with real exception wrapped - // in the InnerException; or user code throws an exception - var actualException = ex.InnerException ?? ex; - var exceptionMessage = StackTraceHelper.GetExceptionMessage(actualException); - var stackTraceInfo = StackTraceHelper.GetStackTraceInformation(actualException); - var errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_InstanceCreationError, - this.TestClassName, - exceptionMessage); - - result.Outcome = UnitTestOutcome.Failed; - result.TestFailureException = new TestFailedException(UnitTestOutcome.Failed, errorMessage, stackTraceInfo); - } - - return classInstance; - } - - ///// - ///// Execute test with a timeout - ///// - ///// The arguments to be passed. - ///// The result of execution. - //[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - //private TestResult ExecuteInternalWithTimeout(object[] arguments) - //{ - // Debug.Assert(this.IsTimeoutSet, "Timeout should be set"); - - // TestResult result = null; - // Exception failure = null; - - // Action executeAsyncAction = () => - // { - // try - // { - // result = this.ExecuteInternal(arguments); - // } - // catch (Exception ex) - // { - // failure = ex; - // } - // }; - - // if (PlatformServiceProvider.Instance.ThreadOperations.Execute(executeAsyncAction, this.TestMethodOptions.Timeout)) - // { - // if (failure != null) - // { - // throw failure; - // } - - // Debug.Assert(result != null, "no timeout, no failure result should not be null"); - // return result; - // } - // else - // { - // // Timed out - - // // If the method times out, then - // // - // // 1. If the test is stuck, then we can get CannotUnloadAppDomain exception. - // // - // // Which are handled as follows: - - // // - // // For #1, we are now restarting the execution process if adapter fails to unload app-domain. - // string errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Timeout, this.TestMethodName); - // MSTest.TestResult timeoutResult = new nanoFramework.TestPlatform.MSTest.TestResult() { Outcome = nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel.UnitTestOutcome.Timeout, TestFailureException = new TestFailedException(UnitTestOutcome.Timeout, errorMessage) }; - // return timeoutResult; - // } - //} - } -} diff --git a/source/TestAdapter/Execution/TestRunCancellationToken.cs b/source/TestAdapter/Execution/TestRunCancellationToken.cs deleted file mode 100644 index fc1ed3d..0000000 --- a/source/TestAdapter/Execution/TestRunCancellationToken.cs +++ /dev/null @@ -1,77 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.Diagnostics; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - - /// - /// Cancellation token supporting cancellation of a test run. - /// - public class TestRunCancellationToken - { - /// - /// Stores whether the test run is cancelled or not. - /// - private bool cancelled; - - /// - /// Callback to be invoked when cancelled. - /// - private Action registeredCallback; - - /// - /// Gets a value indicating whether the test run is cancelled. - /// - public bool Canceled - { - get - { - return this.cancelled; - } - - private set - { - this.cancelled = value; - if (this.cancelled) - { - this.registeredCallback?.Invoke(); - } - } - } - - /// - /// Cancels the execution of a test run. - /// - public void Cancel() - { - this.Canceled = true; - } - - /// - /// Registers a callback method to be invoked when cancelled. - /// - /// Callback delegate for handling cancellation. - public void Register(Action callback) - { - ValidateArg.NotNull(callback, "callback"); - - Debug.Assert(this.registeredCallback == null, "Callback delegate is already registered, use a new cancellationToken"); - - this.registeredCallback = callback; - } - - /// - /// Unregister the callback method. - /// - public void Unregister() - { - this.registeredCallback = null; - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/Execution/ThreadSafeStringWriter.cs b/source/TestAdapter/Execution/ThreadSafeStringWriter.cs deleted file mode 100644 index f889d8c..0000000 --- a/source/TestAdapter/Execution/ThreadSafeStringWriter.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Execution -{ - using System; - using System.IO; - - /// - /// StringWriter which has thread safe ToString(). - /// - internal class ThreadSafeStringWriter : StringWriter - { - private readonly object lockObject = new object(); - - /// - /// Initializes a new instance of the class. - /// - /// - /// The format provider. - /// - public ThreadSafeStringWriter(IFormatProvider formatProvider) - : base(formatProvider) - { - } - - /// - public override string ToString() - { - lock (this.lockObject) - { - try - { - return base.ToString(); - } - catch (ObjectDisposedException) - { - return default(string); - } - } - } - - /// - public override void Write(char value) - { - lock (this.lockObject) - { - InvokeBaseClass(() => base.Write(value)); - } - } - - /// - public override void Write(string value) - { - lock (this.lockObject) - { - InvokeBaseClass(() => base.Write(value)); - } - } - - /// - public override void Write(char[] buffer, int index, int count) - { - lock (this.lockObject) - { - InvokeBaseClass(() => base.Write(buffer, index, count)); - } - } - - /// - protected override void Dispose(bool disposing) - { - lock (this.lockObject) - { - InvokeBaseClass(() => base.Dispose(disposing)); - } - } - - private static void InvokeBaseClass(Action action) - { - try - { - action(); - } - catch (ObjectDisposedException) - { - } - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/Execution/TypeCache.cs b/source/TestAdapter/Execution/TypeCache.cs deleted file mode 100644 index ad6e8ee..0000000 --- a/source/TestAdapter/Execution/TypeCache.cs +++ /dev/null @@ -1,735 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Execution -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Reflection; - using System.Security; - - /// - /// Defines type cache which reflects upon a type and cache its test artifacts - /// - internal class TypeCache : MarshalByRefObject - { - /// - /// Test context property name - /// - private const string TestContextPropertyName = "TestContext"; - - /// - /// Predefined test Attribute names. - /// - private static readonly string[] PredefinedNames = new string[] { "Priority", "TestCategory", "Owner" }; - - /// - /// Helper for reflection API's. - /// - private ReflectHelper reflectionHelper; - - /// - /// Assembly info cache - /// - private Dictionary testAssemblyInfoCache; - - /// - /// ClassInfo cache - /// - private Dictionary classInfoCache; - - private object assemblyInfoSyncObject; - private object classInfoSyncObject; - - /// - /// Initializes a new instance of the class. - /// - internal TypeCache() - : this(new ReflectHelper()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// An instance to the object. - internal TypeCache(ReflectHelper reflectionHelper) - { - this.reflectionHelper = reflectionHelper; - this.testAssemblyInfoCache = new Dictionary(); - this.classInfoCache = new Dictionary(StringComparer.Ordinal); - this.assemblyInfoSyncObject = new object(); - this.classInfoSyncObject = new object(); - } - - /// - /// Gets Class Info cache which has cleanup methods to execute - /// - public IEnumerable ClassInfoListWithExecutableCleanupMethods - { - get - { - return this.classInfoCache.Values.Where(classInfo => classInfo.HasExecutableCleanupMethod).ToList(); - } - } - - /// - /// Gets Assembly Info cache which has cleanup methods to execute - /// - public IEnumerable AssemblyInfoListWithExecutableCleanupMethods - { - get - { - return this.testAssemblyInfoCache.Values.Where(assemblyInfo => assemblyInfo.HasExecutableCleanupMethod).ToList(); - } - } - - /// - /// Gets the set of cached assembly info values. - /// - public IEnumerable AssemblyInfoCache - { - get - { - return this.testAssemblyInfoCache.Values.ToList(); - } - } - - /// - /// Gets the set of cached class info values. - /// - public IEnumerable ClassInfoCache - { - get - { - return this.classInfoCache.Values.ToList(); - } - } - - /// - /// Get the test method info corresponding to the parameter test Element - /// - /// The test Method. - /// The test Context. - /// Indicates whether the test method should capture debug traces. - /// The . - public TestMethodInfo GetTestMethodInfo(TestMethod testMethod, ITestContext testContext, bool captureDebugTraces) - { - if (testMethod == null) - { - throw new ArgumentNullException("testMethod"); - } - - if (testContext == null) - { - throw new ArgumentNullException("testContext"); - } - - // Get the classInfo (This may throw as GetType calls assembly.GetType(..,true);) - var testClassInfo = this.GetClassInfo(testMethod); - - if (testClassInfo == null) - { - // This means the class containing the test method could not be found. - // Return null so we return a not found result. - return null; - } - - // Get the testMethod - return this.ResolveTestMethod(testMethod, testClassInfo, testContext, captureDebugTraces); - } - - /// - /// Returns object to be used for controlling lifetime, null means infinite lifetime. - /// - /// - /// The . - /// - [SecurityCritical] - public override object InitializeLifetimeService() - { - return null; - } - - #region ClassInfo creation and cache logic. - - /// - /// Gets the classInfo corresponding to the unit test. - /// - /// The test Method. - /// The . - private TestClassInfo GetClassInfo(TestMethod testMethod) - { - Debug.Assert(testMethod != null, "test method is null"); - - var typeName = testMethod.FullClassName; - - TestClassInfo classInfo; - - if (!this.classInfoCache.TryGetValue(typeName, out classInfo)) - { - // Aquiring a lock is usually a costly operation which does not need to be - // performed every time if the type is found in the cache. - lock (this.classInfoSyncObject) - { - // Perform a check again. - if (!this.classInfoCache.TryGetValue(typeName, out classInfo)) - { - // Load the class type - Type type = this.LoadType(typeName, testMethod.AssemblyName); - - if (type == null) - { - // This means the class containing the test method could not be found. - // Return null so we return a not found result. - return null; - } - - // Get the classInfo - classInfo = this.CreateClassInfo(type, testMethod); - - // Use the full type name for the cache. - this.classInfoCache.Add(typeName, classInfo); - } - } - } - - return classInfo; - } - - /// - /// Loads the parameter type from the parameter assembly - /// - /// The type Name. - /// The assembly Name. - /// The . - /// Thrown when there is a type load exception from the assembly. - private Type LoadType(string typeName, string assemblyName) - { - try - { - // Attempt to load the assembly using the full type name (includes assembly) - // This call will load the assembly from the first location it is - // found in (i.e. GAC, current directory, path) - // If this fails, we will try to load the type from the assembly - // location in the Out directory. - var t = Type.GetType(typeName); - - if (t == null) - { - var assembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(assemblyName, isReflectionOnly: false); - - // Attempt to load the type from the test assembly. - // Allow this call to throw if the type can't be loaded. - t = assembly.GetType(typeName); - } - - return t; - } - catch (TypeLoadException) - { - // This means the class containing the test method could not be found. - // Return null so we return a not found result. - return null; - } - catch (Exception ex) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TypeLoadError, typeName, ex); - throw new TypeInspectionException(message); - } - } - - /// - /// Create the class Info - /// - /// The class Type. - /// The test Method. - /// The . - private TestClassInfo CreateClassInfo(Type classType, TestMethod testMethod) - { - var constructors = classType.GetTypeInfo().DeclaredConstructors; - var constructor = constructors.FirstOrDefault(ctor => ctor.GetParameters().Length == 0 && ctor.IsPublic); - - if (constructor == null) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_NoDefaultConstructor, testMethod.FullClassName); - throw new TypeInspectionException(message); - } - - var testContextProperty = this.ResolveTestContext(classType); - - var assemblyInfo = this.GetAssemblyInfo(classType); - - var classInfo = new TestClassInfo(classType, constructor, testContextProperty, this.reflectionHelper.GetDerivedAttribute(classType, false), assemblyInfo); - - var testInitializeAttributeType = typeof(TestInitializeAttribute); - var testCleanupAttributeType = typeof(TestCleanupAttribute); - var classInitializeAttributeType = typeof(ClassInitializeAttribute); - var classCleanupAttributeType = typeof(ClassCleanupAttribute); - - // List of instance methods present in the type as well its base type - // which is used to decide whether TestInitialize/TestCleanup methods - // present in the base type should be used or not. They are not used if - // the method is overridden in the derived type. - var instanceMethods = new Dictionary(); - - foreach (var methodInfo in classType.GetTypeInfo().DeclaredMethods) - { - // Update test initialize/cleanup method - this.UpdateInfoIfTestInitializeOrCleanupMethod(classInfo, methodInfo, isBase: false, instanceMethods: instanceMethods, testInitializeAttributeType: testInitializeAttributeType, testCleanupAttributeType: testCleanupAttributeType); - - if (this.IsAssemblyOrClassInitializeMethod(methodInfo, classInitializeAttributeType)) - { - // update class initialize method - classInfo.ClassInitializeMethod = methodInfo; - } - else if (this.IsAssemblyOrClassCleanupMethod(methodInfo, classCleanupAttributeType)) - { - // update class cleanup method - classInfo.ClassCleanupMethod = methodInfo; - } - } - - var baseType = classType.GetTypeInfo().BaseType; - while (baseType != null) - { - foreach (var methodInfo in baseType.GetTypeInfo().DeclaredMethods) - { - if (methodInfo.IsPublic && !methodInfo.IsStatic) - { - // Update test initialize/cleanup method from base type. - this.UpdateInfoIfTestInitializeOrCleanupMethod(classInfo, methodInfo, true, instanceMethods, testInitializeAttributeType, testCleanupAttributeType); - } - } - - baseType = baseType.GetTypeInfo().BaseType; - } - - return classInfo; - } - - /// - /// Resolves the test context property - /// - /// The class Type. - /// The for TestContext property. Null if not defined. - private PropertyInfo ResolveTestContext(Type classType) - { - try - { - var testContextProperty = classType.GetRuntimeProperty(TestContextPropertyName); - if (testContextProperty == null) - { - // that's okay may be the property was not defined - return null; - } - - // check if testContextProperty is of correct type - if (!testContextProperty.PropertyType.FullName.Equals(typeof(TestContext).FullName, StringComparison.Ordinal)) - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestContextTypeMismatchLoadError, classType.FullName); - throw new TypeInspectionException(errorMessage); - } - - return testContextProperty; - } - catch (AmbiguousMatchException ex) - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestContextLoadError, classType.FullName, ex.Message); - throw new TypeInspectionException(errorMessage); - } - } - - #endregion - - #region AssemblyInfo creation and cache logic. - - /// - /// Get the assembly info for the parameter type - /// - /// The type. - /// The instance. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Discoverer should continue with remaining sources.")] - private TestAssemblyInfo GetAssemblyInfo(Type type) - { - var assembly = type.GetTypeInfo().Assembly; - TestAssemblyInfo assemblyInfo; - - if (!this.testAssemblyInfoCache.TryGetValue(assembly, out assemblyInfo)) - { - // Aquiring a lock is usually a costly operation which does not need to be - // performed every time if the assembly is found in the cache. - lock (this.assemblyInfoSyncObject) - { - if (this.testAssemblyInfoCache.TryGetValue(assembly, out assemblyInfo)) - { - return assemblyInfo; - } - - var assemblyInitializeType = typeof(AssemblyInitializeAttribute); - var assemblyCleanupType = typeof(AssemblyCleanupAttribute); - - assemblyInfo = new TestAssemblyInfo(); - - var types = new AssemblyEnumerator().GetTypes(assembly, assembly.FullName, null); - - foreach (var t in types) - { - if (t == null) - { - continue; - } - - try - { - // Only examine test classes for test attributes - if (!this.reflectionHelper.IsAttributeDefined(t, typeof(TestClassAttribute), inherit: true)) - { - continue; - } - } - catch (Exception ex) - { - // If we fail to discover type from an assembly, then do not abort. Pick the next type. - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning( - "TypeCache: Exception occured while checking whether type {0} is a test class or not. {1}", - t.FullName, - ex); - - continue; - } - - // Enumerate through all methods and identify the Assembly Init and cleanup methods. - foreach (var methodInfo in t.GetTypeInfo().DeclaredMethods) - { - if (this.IsAssemblyOrClassInitializeMethod(methodInfo, assemblyInitializeType)) - { - assemblyInfo.AssemblyInitializeMethod = methodInfo; - } - else if (this.IsAssemblyOrClassCleanupMethod(methodInfo, assemblyCleanupType)) - { - assemblyInfo.AssemblyCleanupMethod = methodInfo; - } - } - } - - this.testAssemblyInfoCache.Add(assembly, assemblyInfo); - } - } - - return assemblyInfo; - } - - /// - /// Verify if a given method is an Assembly or Class Initialize method. - /// - /// The method info. - /// The initialization attribute type. - /// True if its an initialization method. - private bool IsAssemblyOrClassInitializeMethod(MethodInfo methodInfo, Type initializeAttributeType) - { - if (!this.reflectionHelper.IsAttributeDefined(methodInfo, initializeAttributeType, false)) - { - return false; - } - - if (!methodInfo.HasCorrectClassOrAssemblyInitializeSignature()) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyInitializeMethodHasWrongSignature, methodInfo.DeclaringType.FullName, methodInfo.Name); - throw new TypeInspectionException(message); - } - - return true; - } - - /// - /// Verify if a given method is an Assembly or Class cleanup method. - /// - /// The method info. - /// The cleanup attribute type. - /// True if its a cleanup method. - private bool IsAssemblyOrClassCleanupMethod(MethodInfo methodInfo, Type cleanupAttributeType) - { - if (!this.reflectionHelper.IsAttributeDefined(methodInfo, cleanupAttributeType, false)) - { - return false; - } - - if (!methodInfo.HasCorrectClassOrAssemblyCleanupSignature()) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ClassOrAssemblyCleanupMethodHasWrongSignature, methodInfo.DeclaringType.FullName, methodInfo.Name); - throw new TypeInspectionException(message); - } - - return true; - } - - #endregion - - /// - /// Update the classInfo if the parameter method is a testInitialize/cleanup method - /// - /// The class Info. - /// The method Info. - /// If this needs to validate in base class or not. - /// The instance Methods. - /// The test Initialize Attribute Type. - /// The test Cleanup Attribute Type. - private void UpdateInfoIfTestInitializeOrCleanupMethod( - TestClassInfo classInfo, - MethodInfo methodInfo, - bool isBase, - Dictionary instanceMethods, - Type testInitializeAttributeType, - Type testCleanupAttributeType) - { - var hasTestInitialize = this.reflectionHelper.IsAttributeDefined(methodInfo, testInitializeAttributeType, inherit: false); - var hasTestCleanup = this.reflectionHelper.IsAttributeDefined(methodInfo, testCleanupAttributeType, inherit: false); - - if (!hasTestCleanup && !hasTestInitialize) - { - if (methodInfo.HasCorrectTestInitializeOrCleanupSignature()) - { - instanceMethods[methodInfo.Name] = null; - } - - return; - } - - if (!methodInfo.HasCorrectTestInitializeOrCleanupSignature()) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestInitializeAndCleanupMethodHasWrongSignature, methodInfo.DeclaringType.FullName, methodInfo.Name); - throw new TypeInspectionException(message); - } - - if (hasTestInitialize) - { - if (!isBase) - { - classInfo.TestInitializeMethod = methodInfo; - } - else - { - if (!instanceMethods.ContainsKey(methodInfo.Name)) - { - classInfo.BaseTestInitializeMethodsQueue.Enqueue(methodInfo); - } - } - } - - if (hasTestCleanup) - { - if (!isBase) - { - classInfo.TestCleanupMethod = methodInfo; - } - else - { - if (!instanceMethods.ContainsKey(methodInfo.Name)) - { - classInfo.BaseTestCleanupMethodsQueue.Enqueue(methodInfo); - } - } - } - - instanceMethods[methodInfo.Name] = null; - } - - /// - /// Resolve the test method. The function will try to - /// find a function that has the method name with 0 parameters. If the function - /// cannot be found, or a function is found that returns non-void, the result is - /// set to error. - /// - /// The test Method. - /// The test Class Info. - /// The test Context. - /// Indicates whether the test method should capture debug traces. - /// - /// The TestMethodInfo for the given test method. Null if the test method could not be found. - /// - private TestMethodInfo ResolveTestMethod(TestMethod testMethod, TestClassInfo testClassInfo, ITestContext testContext, bool captureDebugTraces) - { - Debug.Assert(testMethod != null, "testMethod is Null"); - Debug.Assert(testClassInfo != null, "testClassInfo is Null"); - - var methodInfo = this.GetMethodInfoForTestMethod(testMethod, testClassInfo); - if (methodInfo == null) - { - // Means the specified test method could not be found. - return null; - } - - var expectedExceptionAttribute = this.reflectionHelper.ResolveExpectedExceptionHelper(methodInfo, testMethod); - var timeout = this.GetTestTimeout(methodInfo, testMethod); - - var testMethodOptions = new TestMethodOptions() { Timeout = timeout, Executor = this.GetTestMethodAttribute(methodInfo, testClassInfo), ExpectedException = expectedExceptionAttribute, TestContext = testContext, CaptureDebugTraces = captureDebugTraces }; - var testMethodInfo = new TestMethodInfo(methodInfo, testClassInfo, testMethodOptions); - - this.SetCustomProperties(testMethodInfo, testContext); - - return testMethodInfo; - } - - /// - /// Provides the Test Method Extension Attribute of the TestClass. - /// - /// The method info. - /// The test class info. - /// Test Method Attribute - private TestMethodAttribute GetTestMethodAttribute(MethodInfo methodInfo, TestClassInfo testClassInfo) - { - // Get the derived TestMethod attribute from reflection - var testMethodAttribute = this.reflectionHelper.GetDerivedAttribute(methodInfo, false); - - // Get the derived TestMethod attribute from Extended TestClass Attribute - // If the extended TestClass Attribute doesn't have extended TestMethod attribute then base class returns back the original testMethod Attribute - testMethodAttribute = testClassInfo.ClassAttribute.GetTestMethodAttribute(testMethodAttribute) ?? testMethodAttribute; - - return testMethodAttribute; - } - - /// - /// Resolves a method by using the method name. - /// - /// The test Method. - /// The test Class Info. - /// The . - private MethodInfo GetMethodInfoForTestMethod(TestMethod testMethod, TestClassInfo testClassInfo) - { - var methodsInClass = testClassInfo.ClassType.GetRuntimeMethods().ToArray(); - - var testMethodInfo = - methodsInClass.Where(method => method.Name.Equals(testMethod.Name)) - .FirstOrDefault(method => method.HasCorrectTestMethodSignature(true)); - - // if correct method is not found, throw appropriate - // exception about what is wrong. - if (testMethodInfo == null) - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.UTA_MethodDoesNotExists, testMethod.FullClassName, testMethod.Name); - throw new TypeInspectionException(errorMessage); - } - - return testMethodInfo; - } - - /// - /// Gets the test timeout for the parameter test method - /// - /// The method Info. - /// The test Method. - /// The timeout value if defined. 0 if not defined. - private int GetTestTimeout(MethodInfo methodInfo, TestMethod testMethod) - { - Debug.Assert(methodInfo != null, "TestMethod should be non-null"); - var timeoutAttribute = this.reflectionHelper.GetAttribute(methodInfo); - var globalTimeout = MSTestSettings.CurrentSettings.TestTimeout; - - if (timeoutAttribute != null) - { - if (!methodInfo.HasCorrectTimeout()) - { - var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, testMethod.FullClassName, testMethod.Name); - throw new TypeInspectionException(message); - } - - return timeoutAttribute.Timeout; - } - else if (globalTimeout > 0) - { - return globalTimeout; - } - - return TestMethodInfo.TimeoutWhenNotSet; - } - - /// - /// Set custom properties - /// - /// The test Method Info. - /// The test Context. - private void SetCustomProperties(TestMethodInfo testMethodInfo, ITestContext testContext) - { - Debug.Assert(testMethodInfo != null, "testMethodInfo is Null"); - Debug.Assert(testMethodInfo.TestMethod != null, "testMethodInfo.TestMethod is Null"); - - var attributes = testMethodInfo.TestMethod.GetCustomAttributes(typeof(TestPropertyAttribute), false); - Debug.Assert(attributes != null, "attributes is null"); - - foreach (TestPropertyAttribute attribute in attributes) - { - if (!this.ValidateAndAssignTestProperty(testMethodInfo, testContext, attribute.Name, attribute.Value)) - { - break; - } - } - } - - /// - /// Validates If a Custom test property is valid and then adds it to the TestContext property list. - /// - /// The test method info. - /// The test context. - /// The property name. - /// The property value. - /// True if its a valid Test Property. - private bool ValidateAndAssignTestProperty( - TestMethodInfo testMethodInfo, - ITestContext testContext, - string propertyName, - string propertyValue) - { - if (PredefinedNames.Any(predefinedProp => predefinedProp == propertyName)) - { - testMethodInfo.NotRunnableReason = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_ErrorPredefinedTestProperty, - testMethodInfo.TestMethod.DeclaringType.FullName, - testMethodInfo.TestMethod.Name, - propertyName); - - return false; - } - - if (string.IsNullOrEmpty(propertyName)) - { - testMethodInfo.NotRunnableReason = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_ErrorTestPropertyNullOrEmpty, - testMethodInfo.TestMethod.DeclaringType.FullName, - testMethodInfo.TestMethod.Name); - - return false; - } - - object existingValue; - if (testContext.TryGetPropertyValue(propertyName, out existingValue)) - { - // Do not add to the test context because it would conflict with an already existing value. - // We were at one point reporting a warning here. However with extensibility centered around TestProperty where - // users can have multiple WorkItemAttributes(say) we cannot throw a warning here. Users would have multiple of these attributes - // so that it shows up in reporting rather than seeing them in TestContext properties. - } - else - { - testContext.AddProperty(propertyName, propertyValue); - } - - return true; - } - } -} diff --git a/source/TestAdapter/Execution/UnitTestRunner.cs b/source/TestAdapter/Execution/UnitTestRunner.cs deleted file mode 100644 index 459b6d4..0000000 --- a/source/TestAdapter/Execution/UnitTestRunner.cs +++ /dev/null @@ -1,187 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Execution -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Security; - - /// - /// The runner that runs a single unit test. Also manages the assembly and class cleanup methods at the end of the run. - /// - internal class UnitTestRunner : MarshalByRefObject - { - /// - /// Type cache - /// - //private readonly TypeCache typeCache; - - /// - /// Initializes a new instance of the class. - /// - /// Specifies adapter settings that need to be instantiated in the domain running these tests. - public UnitTestRunner(MSTestSettings settings) - : this(settings, new ReflectHelper()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Specifies adapter settings. - /// The reflect Helper. - internal UnitTestRunner(MSTestSettings settings, ReflectHelper reflectHelper) - { - //this.typeCache = new TypeCache(reflectHelper); - - // Populate the settings into the domain(Desktop workflow) performing discovery. - // This would just be resettings the settings to itself in non desktop workflows. - MSTestSettings.PopulateSettings(settings); - } - - /// - /// Returns object to be used for controlling lifetime, null means infinite lifetime. - /// - /// - /// The . - /// - [SecurityCritical] - public override object InitializeLifetimeService() - { - return null; - } - - /// - /// Runs a single test. - /// - /// The test Method. - /// The test context properties. - /// The . - internal UnitTestResult[] RunSingleTest( - nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel.TestMethod testMethod, - IDictionary testContextProperties) - { - if (testMethod == null) - { - throw new ArgumentNullException("testMethod"); - } - - try - { - using (var writer = new ThreadSafeStringWriter(CultureInfo.InvariantCulture)) - { - var properties = new Dictionary(testContextProperties); - var testContext = PlatformServiceProvider.Instance.GetTestContext(testMethod, writer, properties); - testContext.SetOutcome(UnitTestOutcome.InProgress); - - // Get the testMethod - //var testMethodInfo = this.typeCache.GetTestMethodInfo(testMethod, testContext, MSTestSettings.CurrentSettings.CaptureDebugTraces); - - // If the specified TestMethod could not be found, return a NotFound result. - //if (testMethodInfo == null) - { - return new UnitTestResult[] { new UnitTestResult(UnitTestOutcome.NotFound, string.Format(CultureInfo.CurrentCulture, Resource.TestNotFound, testMethod.Name)) }; - } - - // If test cannot be executed, then bail out. - //if (!testMethodInfo.IsRunnable) - { - //return new UnitTestResult[] { new UnitTestResult(UnitTestOutcome.NotRunnable, testMethodInfo.NotRunnableReason) }; - } - - //return new TestMethodRunner(testMethodInfo, testMethod, testContext, MSTestSettings.CurrentSettings.CaptureDebugTraces).Execute(); - } - } - catch (TypeInspectionException ex) - { - // Catch any exception thrown while inspecting the test method and return failure. - return new UnitTestResult[] { new UnitTestResult(UnitTestOutcome.Failed, "this is a message"/*ex.Message*/) }; - } - } - - ///// - ///// Runs the class cleanup method. - ///// It returns any error information during the execution of the cleanup method - ///// - ///// The . - //internal RunCleanupResult RunCleanup() - //{ - // // No cleanup methods to execute, then return. - // var assemblyInfoCache = this.typeCache.AssemblyInfoListWithExecutableCleanupMethods; - // var classInfoCache = this.typeCache.ClassInfoListWithExecutableCleanupMethods; - // if (!assemblyInfoCache.Any() && !classInfoCache.Any()) - // { - // return null; - // } - - // var result = new RunCleanupResult { Warnings = new List() }; - - // using (var redirector = new LogMessageListener(MSTestSettings.CurrentSettings.CaptureDebugTraces)) - // { - // try - // { - // this.RunClassCleanupMethods(classInfoCache, result.Warnings); - // this.RunAssemblyCleanup(assemblyInfoCache, result.Warnings); - // } - // finally - // { - // // Replacing the null character with a string.replace should work. - // // If this does not work for a specific dotnet version a custom function doing the same needs to be put in place. - // result.StandardOut = redirector.StandardOutput?.Replace("\0", "\\0"); - // result.StandardError = redirector.StandardError?.Replace("\0", "\\0"); - // result.DebugTrace = redirector.DebugTrace?.Replace("\0", "\\0"); - // } - // } - - // return result; - //} - - ///// - ///// Run assembly cleanup methods - ///// - ///// The assembly Info Cache. - ///// The warnings. - //private void RunAssemblyCleanup(IEnumerable assemblyInfoCache, IList warnings) - //{ - // foreach (var assemblyInfo in assemblyInfoCache) - // { - // Debug.Assert(assemblyInfo.HasExecutableCleanupMethod, "HasExecutableCleanupMethod should be true."); - - // var warning = assemblyInfo.RunAssemblyCleanup(); - // if (warning != null) - // { - // warnings.Add(warning); - // } - // } - //} - - ///// - ///// Run class cleanup methods - ///// - ///// The class Info Cache. - ///// The warnings. - //private void RunClassCleanupMethods(IEnumerable classInfoCache, IList warnings) - //{ - // foreach (var classInfo in classInfoCache) - // { - // Debug.Assert(classInfo.HasExecutableCleanupMethod, "HasExecutableCleanupMethod should be true."); - - // var warning = classInfo.RunClassCleanup(); - // if (warning != null) - // { - // warnings.Add(warning); - // } - // } - //} - } -} \ No newline at end of file diff --git a/source/TestAdapter/Executor.cs b/source/TestAdapter/Executor.cs new file mode 100644 index 0000000..042fed6 --- /dev/null +++ b/source/TestAdapter/Executor.cs @@ -0,0 +1,320 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using nanoFramework.TestAdapter; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; + +namespace nanoFramework.TestPlatform.TestAdapter +{ + /// + /// An Executor class + /// + [ExtensionUri(TestsConstants.NanoExecutor)] + class Executor : ITestExecutor + { + private const string TestPassed = "Test passed: "; + private const string TestFailed = "Test failed: "; + private const string Exiting = "Exiting."; + private const string Done = "Done."; + private Settings _settings; + private LogMessenger _logger; + private Process _nanoClr; + + private IFrameworkHandle _frameworkHandle = null; + + /// + public void Cancel() + { + if (!_nanoClr.HasExited) + { + _logger.LogMessage( + "Canceling to test process. Attempting to kill nanoCLR process...", + Settings.LoggingLevel.Verbose); + + _nanoClr.Kill(); + // Wait 5 seconds maximum + _nanoClr.WaitForExit(5000); + } + + return; + } + + /// + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + var settingsProvider = runContext.RunSettings.GetSettings(TestsConstants.SettingsName) as SettingsProvider; + + _logger = new LogMessenger(frameworkHandle, settingsProvider); + + if (settingsProvider != null) + { + _settings = settingsProvider.Settings; + + _logger.LogMessage( + "Getting ready to run tests...", + Settings.LoggingLevel.Detailed); + + _logger.LogMessage( + "Settings parsed", + Settings.LoggingLevel.Verbose); + } + else + { + _logger.LogMessage( + "Getting ready to run tests...", + Settings.LoggingLevel.Detailed); + + _logger.LogMessage( + "No settings for nanoFramework adapter", + Settings.LoggingLevel.Verbose); + } + + var uniqueSources = tests.Select(m => m.Source).Distinct(); + + _logger.LogMessage( + "Test sources enumerated", + Settings.LoggingLevel.Verbose); + + foreach (var source in uniqueSources) + { + var groups = tests.Where(m => m.Source == source); + + _logger.LogMessage( + $"Test group is '{source}'", + Settings.LoggingLevel.Detailed); + + var results = RunTest(groups.ToList()); + + foreach (var result in results) + { + frameworkHandle.RecordResult(result); + } + } + } + + /// + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + foreach (var source in sources) + { + _logger.LogMessage( + $"Finding test cases for '{source}'...", + Settings.LoggingLevel.Detailed); + + var testsCases = TestDiscoverer.FindTestCases(source); + + RunTests(testsCases, runContext, frameworkHandle); + } + } + + private List RunTest(List tests) + { + _logger.LogMessage( + "Setting up test runner...", + Settings.LoggingLevel.Detailed); + + int runTimeout = 10000; + if (_settings != null) + { + runTimeout = _settings.TestTimeOutSeconds * 1000; + } + + _logger.LogMessage( + $"Timeout set to {runTimeout}ms", + Settings.LoggingLevel.Verbose); + + List results = new List(); + + foreach (var test in tests) + { + TestResult result = new TestResult(test) { Outcome = TestOutcome.None }; + results.Add(result); + } + + _logger.LogMessage( + "Processing assemblies to load into test runner...", + Settings.LoggingLevel.Verbose); + + var source = tests.First().Source; + var nfUnitTestLauncherLocation = source.Replace(Path.GetFileName(source), "nanoFramework.UnitTestLauncher.pe"); + var workingDirectory = Path.GetDirectoryName(nfUnitTestLauncherLocation); + var mscorlibLocation = source.Replace(Path.GetFileName(source), "mscorlib.pe"); + var nfTestFrameworkLocation = source.Replace(Path.GetFileName(source), "nanoFramework.TestFramework.pe"); + var nfAssemblyUnderTestLocation = source.Replace(".dll", ".pe"); + + // prepare the process start of the WIN32 nanoCLR + _nanoClr = new Process(); + + AutoResetEvent outputWaitHandle = new AutoResetEvent(false); + AutoResetEvent errorWaitHandle = new AutoResetEvent(false); + StringBuilder output = new StringBuilder(); + StringBuilder error = new StringBuilder(); + + try + { + // prepare parameters to load nanoCLR, include: + // 1. unit test launcher + // 2. mscorlib + // 3. test framework + // 4. test application + string parameter = $"-load {nfUnitTestLauncherLocation} -load {mscorlibLocation} -load {nfTestFrameworkLocation} -load {nfAssemblyUnderTestLocation}"; + + _logger.LogMessage( + "Launching process with nanoCLR...", + Settings.LoggingLevel.Verbose); + + _nanoClr.StartInfo = new ProcessStartInfo(TestObjectHelper.GetNanoClrLocation(), parameter) + { + WorkingDirectory = workingDirectory, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + + // launch nanoCLR + if (!_nanoClr.Start()) + { + results.First().Outcome = TestOutcome.Failed; + results.First().ErrorMessage = "Failed to start nanoCLR"; + + _logger.LogPanicMessage( + "Failed to start nanoCLR!"); + } + + _nanoClr.OutputDataReceived += (sender, e) => + { + if (e.Data == null) + { + outputWaitHandle.Set(); + } + else + { + output.AppendLine(e.Data); + } + }; + + _nanoClr.ErrorDataReceived += (sender, e) => + { + if (e.Data == null) + { + errorWaitHandle.Set(); + } + else + { + error.AppendLine(e.Data); + } + }; + + _nanoClr.Start(); + + _nanoClr.BeginOutputReadLine(); + _nanoClr.BeginErrorReadLine(); + + _logger.LogMessage( + $"nanoCLR started @ process ID: {_nanoClr.Id}", + Settings.LoggingLevel.Detailed); + + + // wait for exit, no worries about the outcome + _nanoClr.WaitForExit(runTimeout); + + var outputStrings = Regex.Split(output.ToString(), @"((\r)+)?(\n)+((\r)+)?").Where(m => !string.IsNullOrEmpty(m)); + + _logger.LogMessage( + "Parsing test results...", + Settings.LoggingLevel.Verbose); + + foreach (var line in outputStrings) + { + if (line.Contains(TestPassed)) + { + // Format is "Test passed: MethodName, ticks"; + // We do get split with space if the coma is missing, happens time to time + string method = line.Substring(line.IndexOf(TestPassed) + TestPassed.Length).Split(',')[0].Split(' ')[0]; + string ticks = line.Substring(line.IndexOf(TestPassed) + TestPassed.Length + method.Length + 2); + long ticksNum = 0; + + try + { + ticksNum = Convert.ToInt64(ticks); + } + catch (Exception) + { + // We won't do anything + } + + // Find the test + var res = results.Where(m => m.TestCase.DisplayName == method); + if (res.Any()) + { + res.First().Duration = TimeSpan.FromTicks(ticksNum); + res.First().Outcome = TestOutcome.Passed; + } + } + else if (line.Contains(TestFailed)) + { + // Format is "Test passed: MethodName, Exception message"; + string method = line.Substring(line.IndexOf(TestFailed) + TestFailed.Length).Split(',')[0].Split(' ')[0]; + string exception = line.Substring(line.IndexOf(TestFailed) + TestPassed.Length + method.Length + 2); + + // Find the test + var res = results.Where(m => m.TestCase.DisplayName == method); + if (res.Any()) + { + res.First().ErrorMessage = exception; + res.First().Outcome = TestOutcome.Failed; + } + } + } + + if (!output.ToString().Contains(Done)) + { + results.First().Outcome = TestOutcome.Failed; + results.First().ErrorMessage = output.ToString(); + } + + var notPassedOrFailed = results.Where(m => m.Outcome != TestOutcome.Failed && m.Outcome != TestOutcome.Passed); + if (notPassedOrFailed.Any()) + { + notPassedOrFailed.First().ErrorMessage = output.ToString(); + } + + } + catch (Exception ex) + { + _logger.LogMessage( + $"Fatal exception when processing test results: >>>{ex.Message}\r\n{output}\r\n{error}", + Settings.LoggingLevel.Detailed); + + results.First().Outcome = TestOutcome.Failed; + results.First().ErrorMessage = $"Fatal exception when processing test results. Set logging to 'Detailed' for details."; + } + finally + { + if (!_nanoClr.HasExited) + { + _logger.LogMessage( + "Attempting to kill nanoCLR process...", + Settings.LoggingLevel.Verbose); + + _nanoClr.Kill(); + _nanoClr.WaitForExit(runTimeout); + } + } + + return results; + } + } +} diff --git a/source/TestAdapter/Extensions/ExceptionExtensions.cs b/source/TestAdapter/Extensions/ExceptionExtensions.cs deleted file mode 100644 index 0bc21f2..0000000 --- a/source/TestAdapter/Extensions/ExceptionExtensions.cs +++ /dev/null @@ -1,144 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Execution; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Diagnostics; - using System.Globalization; - using System.Text; - using UTF = Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Extension methods for the exception class. - /// - internal static class ExceptionExtensions - { - /// - /// Get the InnerException if available, else return the current Exception. - /// - /// The exception. - /// - /// An instance. - /// - internal static Exception GetInnerExceptionOrDefault(this Exception exception) - { - return exception?.InnerException ?? exception; - } - - /// - /// Get the exception message if available, empty otherwise. - /// - /// An object - /// Exception message - internal static string TryGetMessage(this Exception exception) - { - if (exception == null) - { - return string.Format(CultureInfo.CurrentCulture, Resource.UTF_FailedToGetExceptionMessage, "null"); - } - - // It is safe to retrieve an exception message, it should not throw in any case. - return exception.Message ?? string.Empty; - } - - /// - /// Returns an exception message with all inner exceptions messages. - /// - /// The exception. - /// Custom exception message that includes inner exceptions. - internal static string GetExceptionMessage(this Exception exception) - { - Debug.Assert(exception != null, "Exception is null"); - - if (exception == null) - { - return string.Empty; - } - - var exceptionString = exception.Message; - var inner = exception.InnerException; - while (inner != null) - { - exceptionString += Environment.NewLine + inner.Message; - inner = inner.InnerException; - } - - return exceptionString; - } - - - /// - /// Gets the for an exception. - /// - /// An instance. - /// StackTraceInformation for the exception - internal static StackTraceInformation TryGetStackTraceInformation(this Exception exception) - { - if (!string.IsNullOrEmpty(exception?.StackTrace)) - { - return StackTraceHelper.CreateStackTraceInformation(exception, false, exception.StackTrace); - } - - return null; - } - - /// - /// Checks whether exception is an Assert exception - /// - /// An instance. - /// Adapter's Outcome depending on type of assertion. - /// Exception message. - /// StackTraceInformation for the exception - /// True, if Assert exception. False, otherwise. - internal static bool TryGetUnitTestAssertException(this Exception exception, out UnitTestOutcome outcome, out string exceptionMessage, out StackTraceInformation exceptionStackTrace) - { - if (exception is UnitTestAssertException) - { - outcome = exception is AssertInconclusiveException ? - UnitTestOutcome.Inconclusive : UnitTestOutcome.Failed; - - exceptionMessage = exception.TryGetMessage(); - exceptionStackTrace = exception.TryGetStackTraceInformation(); - return true; - } - else - { - outcome = UnitTestOutcome.Failed; - exceptionMessage = null; - exceptionStackTrace = null; - return false; - } - } - - /// - /// Gets exception message and stack trace for TestFailedException. - /// - /// An instance. - /// Appends TestFailedException message to this message. - /// Appends TestFailedExeption stacktrace to this stackTrace - internal static void TryGetTestFailureExceptionMessageAndStackTrace(this TestFailedException testFailureException, StringBuilder message, StringBuilder stackTrace) - { - if (testFailureException != null) - { - if (!string.IsNullOrEmpty(testFailureException.Message)) - { - message.Append(testFailureException.Message); - message.AppendLine(); - } - - if (testFailureException.StackTraceInformation != null && !string.IsNullOrEmpty(testFailureException.StackTraceInformation.ErrorStackTrace)) - { - stackTrace.Append(testFailureException.StackTraceInformation.ErrorStackTrace); - stackTrace.Append(Environment.NewLine); - } - } - } - } -} diff --git a/source/TestAdapter/Extensions/MethodInfoExtensions.cs b/source/TestAdapter/Extensions/MethodInfoExtensions.cs deleted file mode 100644 index 2bf58b6..0000000 --- a/source/TestAdapter/Extensions/MethodInfoExtensions.cs +++ /dev/null @@ -1,165 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using System.Runtime.CompilerServices; - using System.Threading.Tasks; - - internal static class MethodInfoExtensions - { - /// - /// Verifies that the class initialize has the correct signature - /// - /// The method to verify. - /// True if the method has the right Assembly/Class initialize signature. - internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInfo method) - { - Debug.Assert(method != null, "method should not be null."); - - ParameterInfo[] parameters = method.GetParameters(); - - return - method.IsStatic && - method.IsPublic && - (parameters.Length == 1) && - parameters[0].ParameterType == typeof(TestContext) && - method.IsVoidOrTaskReturnType(); - } - - /// - /// Verifies that the class cleanup has the correct signature - /// - /// The method to verify. - /// True if the method has the right Assembly/Class cleanup signature. - internal static bool HasCorrectClassOrAssemblyCleanupSignature(this MethodInfo method) - { - Debug.Assert(method != null, "method should not be null."); - - return - method.IsStatic && - method.IsPublic && - (method.GetParameters().Length == 0) && - method.IsVoidOrTaskReturnType(); - } - - /// - /// Verifies that the test Initiailize/cleanup has the correct signature - /// - /// The method to verify. - /// True if the method has the right test init/cleanup signature. - internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo method) - { - Debug.Assert(method != null, "method should not be null."); - - return - !method.IsStatic && - method.IsPublic && - (method.GetParameters().Length == 0) && - method.IsVoidOrTaskReturnType(); - } - - /// - /// Verifies that the test method has the correct signature - /// - /// The method to verify. - /// Indicates whether parameter lenght is to be ignored. - /// True if the method has the right test method signature. - internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool ignoreParameterLength) - { - Debug.Assert(method != null, "method should not be null."); - - return - !method.IsAbstract && - !method.IsStatic && - !method.IsGenericMethod && - method.IsPublic && - (method.GetParameters().Length == 0 || ignoreParameterLength) && - method.IsVoidOrTaskReturnType(); // Match return type Task for async methods only. Else return type void. - } - - /// - /// Checks whether test method has correct Timeout attribute. - /// - /// The method to verify. - /// True if the method has the right test timeout signature. - internal static bool HasCorrectTimeout(this MethodInfo method) - { - Debug.Assert(method != null, "method should not be null."); - - // There should be one and only one TimeoutAttribute. - var attributes = ReflectHelper.GetCustomAttributes(method, typeof(TimeoutAttribute), false); - if (attributes?.Length != 1) - { - return false; - } - - // Timeout cannot be less than 0. - var attribute = attributes[0] as TimeoutAttribute; - - return !(attribute?.Timeout < 0); - } - - /// - /// Check is return type is void for non async and Task for async methods. - /// - /// The method to verify. - /// True if the method has a void/task return type.. - internal static bool IsVoidOrTaskReturnType(this MethodInfo method) - { - return method.GetAsyncTypeName() == null ? ReflectHelper.MatchReturnType(method, typeof(void)) - : ReflectHelper.MatchReturnType(method, typeof(Task)); - } - - /// - /// For async methods compiler generates different type and method. - /// Gets the compiler generated type name for given async test method. - /// - /// The method to verify. - /// Compiler generated type name for given async test method.. - internal static string GetAsyncTypeName(this MethodInfo method) - { - var asyncStateMachineAttribute = ReflectHelper.GetCustomAttributes(method, typeof(AsyncStateMachineAttribute), false).FirstOrDefault() as AsyncStateMachineAttribute; - - return asyncStateMachineAttribute?.StateMachineType?.FullName; - } - - /// - /// Invoke a as a synchronous . - /// - /// - /// instance. - /// - /// - /// Instance of the on which methodInfo is invoked. - /// - /// - /// Arguments for the methodInfo invoke. - /// - internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object classInstance, params object[] parameters) - { - var methodParameters = methodInfo.GetParameters(); - - // check if testmethod expected parameter values but no testdata was provided, - // throw error with appropriate message. - if (methodParameters != null && methodParameters.Length > 0 && parameters == null) - { - throw new TestFailedException(ObjectModel.UnitTestOutcome.Error, Resource.UTA_TestMethodExpectedParameters); - } - - var task = methodInfo.Invoke(classInstance, parameters) as Task; - - // If methodInfo is an Async method, wait for returned task - task?.GetAwaiter().GetResult(); - } - } -} diff --git a/source/TestAdapter/Extensions/TestCaseExtensions.cs b/source/TestAdapter/Extensions/TestCaseExtensions.cs deleted file mode 100644 index a1bf243..0000000 --- a/source/TestAdapter/Extensions/TestCaseExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - - /// - /// Extension Methods for TestCase Class - /// - internal static class TestCaseExtensions - { - /// - /// The to unit test element. - /// - /// The test case. - /// The source. If deployed this is the full path of the source in the deployment directory. - /// The converted . - internal static UnitTestElement ToUnitTestElement(this TestCase testCase, string source) - { - // TODO currently nanoFramework does not support async - //var isAsync = (testCase.GetPropertyValue(Constants.AsyncTestProperty) as bool?) ?? false; - var isAsync = false; - - var testClassName = testCase.GetPropertyValue(TestAdapter.Constants.TestClassNameProperty) as string; - - TestMethod testMethod = new TestMethod(testCase.DisplayName, testClassName, source, isAsync); - - UnitTestElement testElement = new UnitTestElement(testMethod) - { - IsAsync = isAsync, - TestCategory = testCase.GetPropertyValue(TestAdapter.Constants.TestCategoryProperty) as string[], - Priority = testCase.GetPropertyValue(TestAdapter.Constants.PriorityProperty) as int? - }; - - return testElement; - } - } -} diff --git a/source/TestAdapter/Extensions/TestContextExtensions.cs b/source/TestAdapter/Extensions/TestContextExtensions.cs deleted file mode 100644 index 88afcb0..0000000 --- a/source/TestAdapter/Extensions/TestContextExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions -{ - internal static class TestContextExtensions - { - /// - /// Returns diagnostic messages written to test context and clears from this instance. - /// - /// The test context instance. - /// The diagnostic messages. - internal static string GetAndClearDiagnosticMessages(this ITestContext testContext) - { - var messages = testContext.GetDiagnosticMessages(); - - testContext.ClearDiagnosticMessages(); - - return messages; - } - } -} diff --git a/source/TestAdapter/Extensions/UnitTestOutcomeExtensions.cs b/source/TestAdapter/Extensions/UnitTestOutcomeExtensions.cs deleted file mode 100644 index 3929352..0000000 --- a/source/TestAdapter/Extensions/UnitTestOutcomeExtensions.cs +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions -{ - public static class UnitTestOutcomeExtensions - { - /// - /// Converts the test framework's UnitTestOutcome object to adapter's UnitTestOutcome object. - /// - /// The test framework's UnitTestOutcome object. - /// The adapter's UnitTestOutcome object. - public static UnitTestOutcome ToUnitTestOutcome(this UnitTestOutcome frameworkTestOutcome) - { - UnitTestOutcome outcome = UnitTestOutcome.Passed; - - switch (frameworkTestOutcome) - { - case UnitTestOutcome.Failed: - outcome = UnitTestOutcome.Failed; - break; - - case UnitTestOutcome.Inconclusive: - outcome = UnitTestOutcome.Inconclusive; - break; - - case UnitTestOutcome.InProgress: - outcome = UnitTestOutcome.InProgress; - break; - - case UnitTestOutcome.Passed: - outcome = UnitTestOutcome.Passed; - break; - - case UnitTestOutcome.Timeout: - outcome = UnitTestOutcome.Timeout; - break; - - case UnitTestOutcome.NotRunnable: - outcome = UnitTestOutcome.NotRunnable; - break; - - case UnitTestOutcome.Unknown: - default: - outcome = UnitTestOutcome.Error; - break; - } - - return outcome; - } - - /// - /// Returns more important outcome of two. - /// - /// First outcome that needs to be compared. - /// Second outcome that needs to be compared. - /// Outcome which has higher importance. - internal static UnitTestOutcome GetMoreImportantOutcome( - this UnitTestOutcome outcome1, - UnitTestOutcome outcome2) - { - var unitTestOutcome1 = outcome1.ToUnitTestOutcome(); - var unitTestOutcome2 = outcome2.ToUnitTestOutcome(); - return unitTestOutcome1 < unitTestOutcome2 ? outcome1 : outcome2; - } - } -} diff --git a/source/TestAdapter/Friends.cs b/source/TestAdapter/Friends.cs deleted file mode 100644 index 3dd01e8..0000000 --- a/source/TestAdapter/Friends.cs +++ /dev/null @@ -1,8 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/source/TestAdapter/Helpers/ReflectHelper.cs b/source/TestAdapter/Helpers/ReflectHelper.cs deleted file mode 100644 index ce4e031..0000000 --- a/source/TestAdapter/Helpers/ReflectHelper.cs +++ /dev/null @@ -1,712 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Execution; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Reflection; - using System.Security; - - internal class ReflectHelper : MarshalByRefObject - { - /// - /// Contains the memberInfo Vs the name/type of the attributes defined on that member. (FYI: - MemberInfo denotes properties, fields, methods, events) - /// - private Dictionary> attributeCache = new Dictionary>(); - - /// - /// Checks to see if the parameter memberInfo contains the parameter attribute or not. - /// - /// Member/Type to test - /// Attribute to search for - /// Look through inheritance or not - /// True if the attribute of the specified type is defined. - public virtual bool IsAttributeDefined( - MemberInfo memberInfo, - Type attributeType, - bool inherit - ) - { - if (memberInfo == null) - { - throw new ArgumentNullException(nameof(memberInfo)); - } - - if (attributeType == null) - { - throw new ArgumentNullException(nameof(attributeType)); - } - - Debug.Assert(attributeType != null, "attrbiuteType should not be null."); - - // Get attributes defined on the member from the cache. - Dictionary attributes = this.GetAttributes(memberInfo, inherit); - if (attributes == null) - { - // If we could not obtain all attributes from cache, just get the one we need. - var specificAttributes = GetCustomAttributes(memberInfo, attributeType, inherit); - var requiredAttributeQualifiedName = attributeType.AssemblyQualifiedName; - - return specificAttributes.Any(a => string.Equals(a.GetType().AssemblyQualifiedName, requiredAttributeQualifiedName)); - } - - string nameToFind = attributeType.AssemblyQualifiedName; - if (attributes.ContainsKey(nameToFind)) - { - return true; - } - - return false; - } - - /// - /// Checks to see if the parameter memberInfo contains the parameter attribute or not. - /// - /// Member/Type to test - /// Attribute to search for - /// Look throug inheritence or not - /// True if the specified attribute is defined on the type. - public virtual bool IsAttributeDefined( - Type type, - Type attributeType, - bool inherit - ) - { - return this.IsAttributeDefined((MemberInfo)type.GetTypeInfo(), attributeType, inherit); - } - - /// - /// Returns true when specified class/member has attribute derived from specific attribute. - /// - /// The type. - /// The base attribute type. - /// Should look at inheritance tree. - /// An object derived from Attribute that corresponds to the instance of found attribute. - public virtual bool HasAttributeDerivedFrom( - Type type, - Type baseAttributeType, - bool inherit - ) - { - return this.HasAttributeDerivedFrom((MemberInfo)type.GetTypeInfo(), baseAttributeType, inherit); - } - - /// - /// Returns true when specified class/member has attribute derived from specific attribute. - /// - /// The member info. - /// The base attribute type. - /// Should look at inheritance tree. - /// An object derived from Attribute that corresponds to the instance of found attribute. - public bool HasAttributeDerivedFrom( - MemberInfo memberInfo, - Type baseAttributeType, - bool inherit - ) - { - if (memberInfo == null) - { - throw new ArgumentNullException(nameof(memberInfo)); - } - - if (baseAttributeType == null) - { - throw new ArgumentNullException(nameof(baseAttributeType)); - } - - // Get all attributes on the member. - Dictionary attributes = this.GetAttributes(memberInfo, inherit); - if (attributes == null) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning("ReflectHelper.HasAttributeDerivedFrom: Failed to get attribute cache. Ignoring attribute inheritance and falling into 'type defines Attribute model', so that we have some data."); - - return this.IsAttributeDefined(memberInfo, baseAttributeType, inherit); - } - - // Try to find the attribute that is derived from baseAttrType. - foreach (object attribute in attributes.Values) - { - Debug.Assert(attribute != null, "ReflectHeler.DefinesAttributeDerivedFrom: internal error: wrong value in the attrs dictionary."); - - Type attributeType = attribute.GetType(); - if (attributeType.GetTypeInfo().IsSubclassOf(baseAttributeType)) - { - return true; - } - } - - return false; - } - - /// - /// Resolves the expected exception attribute. The function will try to - /// get all the expected exception attributes defined for a testMethod. - /// - /// The MethodInfo instance. - /// The test method. - /// - /// The expected exception attribute found for this test. Null if not found. - /// - public virtual ExpectedExceptionBaseAttribute ResolveExpectedExceptionHelper( - MethodInfo methodInfo, - TestMethod testMethod - ) - { - Debug.Assert(methodInfo != null, "MethodInfo should be non-null"); - - // Get the expected exception attribute - ExpectedExceptionBaseAttribute[] expectedExceptions; - try - { - expectedExceptions = GetCustomAttributes(methodInfo, typeof(ExpectedExceptionBaseAttribute), true).OfType().ToArray(); - } - catch (Exception ex) - { - // If construction of the attribute throws an exception, indicate that there was an - // error when trying to run the test - string errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_ExpectedExceptionAttributeConstructionException, - testMethod.FullClassName, - testMethod.Name, - StackTraceHelper.GetExceptionMessage(ex)); - throw new TypeInspectionException(errorMessage); - } - - if (expectedExceptions == null || expectedExceptions.Length == 0) - { - return null; - } - - // Verify that there is only one attribute (multiple attributes derived from - // ExpectedExceptionBaseAttribute are not allowed on a test method) - if (expectedExceptions.Length > 1) - { - string errorMessage = string.Format( - CultureInfo.CurrentCulture, - Resource.UTA_MultipleExpectedExceptionsOnTestMethod, - testMethod.FullClassName, - testMethod.Name); - throw new TypeInspectionException(errorMessage); - } - - // Set the expected exception attribute to use for this test - ExpectedExceptionBaseAttribute expectedException = expectedExceptions[0]; - - return expectedException; - } - - /// - /// Returns object to be used for controlling lifetime, null means infinite lifetime. - /// - /// - /// The . - /// - [SecurityCritical] - public override object InitializeLifetimeService() - { - return null; - } - - /// - /// Match return type of method. - /// - /// The method to inspect. - /// The return type to match. - /// True if there is a match. - internal static bool MatchReturnType( - MethodInfo method, - Type returnType - ) - { - if (method == null) - { - throw new ArgumentNullException(nameof(method)); - } - - if (returnType == null) - { - throw new ArgumentNullException(nameof(returnType)); - } - - return method.ReturnType.Equals(returnType); - } - - /// - /// Get custom attributes on a member for both normal and reflection only load. - /// - /// Memeber for which attributes needs to be retrieved. - /// Type of attribute to retrieve. - /// If inheritied type of attribute. - /// All attributes of give type on member. - internal static Attribute[] GetCustomAttributes( - MemberInfo memberInfo, - Type type, - bool inherit - ) - { - if (memberInfo == null) - { - return null; - } - - var attributesArray = PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes( - memberInfo, - type, - inherit); - - return attributesArray.OfType().ToArray(); - } - - /// - /// Get custom attributes on a member for both normal and reflection only load. - /// - /// Memeber for which attributes needs to be retrieved. - /// If inheritied type of attribute. - /// All attributes of give type on member. - internal static object[] GetCustomAttributes( - MemberInfo memberInfo, - bool inherit - ) - { - if (memberInfo == null) - { - return null; - } - - var attributesArray = PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes( - memberInfo, - inherit); - - return attributesArray.ToArray(); - } - - /// - /// Returns the first attribute of the specified type or null if no attribute - /// of the specified type is set on the method. - /// - /// The type of attribute to return. - /// The method on which the attribute is defined. - /// The attribute or null if none exists. - internal AttributeType GetAttribute( - MethodInfo method - ) - where AttributeType : class - { - if (this.IsAttributeDefined(method, typeof(AttributeType), false)) - { - object[] attributes = GetCustomAttributes(method, typeof(AttributeType), false); - Debug.Assert(attributes.Length == 1, "Should only be one attribute."); - return attributes[0] as AttributeType; - } - - return null; - } - - /// - /// Returns the attribute of the specified type. Null if no attribute of the specified type is defined. - /// - /// The attribute type. - /// The method to inspect. - /// Attribute of the specified type. Null if not found. - internal Attribute GetAttribute( - Type attributeType, - MethodInfo method - ) - { - if (this.IsAttributeDefined(method, attributeType, false)) - { - object[] attributes = GetCustomAttributes(method, attributeType, false); - Debug.Assert(attributes.Length == 1, "Should only be one attribute."); - return attributes[0] as Attribute; - } - - return null; - } - - /// - /// Returns true when the method is delcared in the assembly where the type is declared. - /// - /// The method to check for. - /// The type declared in the assembly to check. - /// True if the method is declared in the assembly where the type is declared. - internal virtual bool IsMethodDeclaredInSameAssemblyAsType( - MethodInfo method, - Type type - ) - { - return method.DeclaringType.GetTypeInfo().Assembly.Equals(type.GetTypeInfo().Assembly); - } - - /// - /// Get categories applied to the test method - /// - /// The member to inspect. - /// Categories defined. - internal virtual string[] GetCategories( - MemberInfo categoryAttributeProvider - ) - { - var categories = this.GetCustomAttributesRecursively(categoryAttributeProvider, typeof(TestCategoryBaseAttribute)); - List testCategories = new List(); - - if (categories != null) - { - foreach (TestCategoryBaseAttribute category in categories) - { - testCategories.AddRange(category.TestCategories); - } - } - - return testCategories.ToArray(); - } - - /// - /// Gets custom attributes at the class and assembly for a method. - /// - /// Method Info or Member Info or a Type - /// What type of CustomAttribute you need. For instance: TestCategory, Owner etc., - /// The categories of the specified type on the method. - internal IEnumerable GetCustomAttributesRecursively( - MemberInfo attributeProvider, - Type type - ) - { - var categories = this.GetCustomAttributes(attributeProvider, typeof(TestCategoryBaseAttribute)); - if (categories != null) - { - categories = categories.Concat(this.GetCustomAttributes(attributeProvider.DeclaringType.GetTypeInfo(), typeof(TestCategoryBaseAttribute))).ToArray(); - } - - if (categories != null) - { - categories = categories.Concat(this.GetCustomAttributeForAssembly(attributeProvider, typeof(TestCategoryBaseAttribute))).ToArray(); - } - - if (categories != null) - { - return categories; - } - - return Enumerable.Empty(); - } - - /// - /// Gets the custom attributes on the assembly of a member info - /// NOTE: having it as separate virtual method, so that we can extend it for testing. - /// - /// The member to inspect. - /// The attribute type to find. - /// Custom attributes defined. - internal virtual Attribute[] GetCustomAttributeForAssembly( - MemberInfo memberInfo, - Type type - ) - { - return - PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes( - memberInfo.DeclaringType.GetTypeInfo().Assembly, - type).OfType().ToArray(); - } - - /// - /// Gets the custom attributes of the provided type on a memberInfo - /// - /// The member to reflect on. - /// The attribute type. - /// Attributes defined. - internal virtual Attribute[] GetCustomAttributes( - MemberInfo attributeProvider, - Type type - ) - { - return GetCustomAttributes(attributeProvider, type, true); - } - - /// - /// KeyValue pairs that are provided by TestOwnerAttribute of the given test method. - /// - /// The member to inspect. - /// The owner trait. - internal virtual Trait GetTestOwnerAsTraits( - MemberInfo ownerAttributeProvider - ) - { - string owner = this.GetOwner(ownerAttributeProvider); - - if (string.IsNullOrEmpty(owner)) - { - return null; - } - else - { - return new Trait("Owner", owner); - } - } - - /// - /// KeyValue pairs that are provided by TestPriorityAttributes of the given test method. - /// - /// The priority - /// The corresponding trait. - internal virtual Trait GetTestPriorityAsTraits( - int? testPriority - ) - { - if (testPriority == null) - { - return null; - } - else - { - return new Trait("Priority", ((int)testPriority).ToString(CultureInfo.InvariantCulture)); - } - } - - /// - /// Priority if any set for test method. Will return priority if attribute is applied to TestMethod - /// else null; - /// - /// The member to inspect. - /// Priority value if defined. Null otherwise. - internal virtual int? GetPriority( - MemberInfo priorityAttributeProvider - ) - { - var priorityAttribute = GetCustomAttributes(priorityAttributeProvider, typeof(PriorityAttribute), true); - - if (priorityAttribute == null || priorityAttribute.Length != 1) - { - return null; - } - - return (priorityAttribute[0] as PriorityAttribute).Priority; - } - - /// - /// Priority if any set for test method. Will return priority if attribute is applied to TestMethod - /// else null; - /// - /// The member to inspect. - /// Priority value if defined. Null otherwise. - internal virtual string GetIgnoreMessage( - MemberInfo ignoreAttributeProvider - ) - { - var ignoreAttribute = GetCustomAttributes(ignoreAttributeProvider, typeof(IgnoreAttribute), true); - - if (!ignoreAttribute.Any()) - { - return null; - } - - return (ignoreAttribute?.FirstOrDefault() as IgnoreAttribute).IgnoreMessage; - } - - /// - /// KeyValue pairs that are provided by TestPropertyAttributes of the given test method. - /// - /// The member to inspect. - /// List of traits. - internal virtual IEnumerable GetTestPropertiesAsTraits( - MemberInfo testPropertyProvider - ) - { - var testPropertyAttributes = this.GetTestPropertyAttributes(testPropertyProvider); - - foreach (TestPropertyAttribute testProperty in testPropertyAttributes) - { - Trait testPropertyPair; - if (testProperty.Name == null) - { - testPropertyPair = new Trait(string.Empty, testProperty.Value); - } - else - { - testPropertyPair = new Trait(testProperty.Name, testProperty.Value); - } - - yield return testPropertyPair; - } - } - - /// - /// Get attribute defined on a method which is of given type of subtype of given type. - /// - /// The attribute type. - /// The member to inspect. - /// Look at inheritance chain. - /// An instance of the attribute. - internal AttributeType GetDerivedAttribute( - MemberInfo memberInfo, bool inherit - ) - where AttributeType : Attribute - { - Dictionary attributes = this.GetAttributes(memberInfo, inherit); - if (attributes == null) - { - return null; - } - - // Try to find the attribute that is derived from baseAttrType. - foreach (object attribute in attributes.Values) - { - Debug.Assert(attribute != null, "ReflectHeler.DefinesAttributeDerivedFrom: internal error: wrong value in the attrs dictionary."); - - Type attributeType = attribute.GetType(); - if (attributeType.Equals(typeof(AttributeType)) || attributeType.GetTypeInfo().IsSubclassOf(typeof(AttributeType))) - { - return attribute as AttributeType; - } - } - - return null; - } - - /// - /// Get attribute defined on a method which is of given type of subtype of given type. - /// - /// The attribute type. - /// The type to inspect. - /// Look at inheritance chain. - /// An instance of the attribute. - internal AttributeType GetDerivedAttribute( - Type type, - bool inherit - ) - where AttributeType : Attribute - { - var attributes = GetCustomAttributes(type.GetTypeInfo(), inherit); - if (attributes == null) - { - return null; - } - - // Try to find the attribute that is derived from baseAttrType. - foreach (object attribute in attributes) - { - Debug.Assert(attribute != null, "ReflectHeler.DefinesAttributeDerivedFrom: internal error: wrong value in the attrs dictionary."); - - Type attributeType = attribute.GetType(); - if (attributeType.Equals(typeof(AttributeType)) || attributeType.GetTypeInfo().IsSubclassOf(typeof(AttributeType))) - { - return attribute as AttributeType; - } - } - - return null; - } - - /// - /// Returns owner if attribute is applied to TestMethod, else null; - /// - /// The member to inspect. - /// owner if attribute is applied to TestMethod, else null; - private string GetOwner( - MemberInfo ownerAttributeProvider - ) - { - var ownerAttribute = GetCustomAttributes(ownerAttributeProvider, typeof(OwnerAttribute), true).ToArray(); - - if (ownerAttribute == null || ownerAttribute.Length != 1) - { - return null; - } - - return (ownerAttribute[0] as OwnerAttribute).Owner; - } - - /// - /// Return TestProperties attributes applied to TestMethod - /// - /// The member to inspect. - /// TestProperty attributes if defined. Empty otherwise. - private IEnumerable GetTestPropertyAttributes( - MemberInfo propertyAttributeProvider - ) - { - return GetCustomAttributes(propertyAttributeProvider, typeof(TestPropertyAttribute), true); - } - - /// - /// Get the Attributes (TypeName/TypeObject) for a given member. - /// - /// The member to inspect. - /// Look at inheritance chain. - /// attributes defined. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private Dictionary GetAttributes( - MemberInfo memberInfo, - bool inherit - ) - { - // If the information is cached, then use it otherwise populate the cache using - // the reflection APIs. - Dictionary attributes; - lock (this.attributeCache) - { - if (!this.attributeCache.TryGetValue(memberInfo, out attributes)) - { - // Populate the cache - attributes = new Dictionary(); - - object[] customAttributesArray = null; - try - { - customAttributesArray = GetCustomAttributes(memberInfo, inherit); - } - catch (Exception ex) - { - // Get the exception description - string description; - try - { - // Can throw if the Message or StackTrace properties throw exceptions - description = ex.ToString(); - } - catch (Exception ex2) - { - description = - ex.GetType().FullName + - ": (Failed to get exception description due to an exception of type " + - ex2.GetType().FullName + ')'; - } - - PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning( - "Getting custom attributes for type {0} threw exception (will ignore and use the reflection way): {1}", - memberInfo.GetType().FullName, - description); - - // Since we cannot check by attribute names, do it in reflection way. - // Note 1: this will not work for different version of assembly but it is better than nothing. - // Note 2: we cannot cache this because we don't know if there are other attributes defined. - return null; - } - - Debug.Assert(customAttributesArray != null, "attributes should not be null."); - - foreach (object customAttribute in customAttributesArray) - { - Type attrType = customAttribute.GetType(); - attributes[attrType.AssemblyQualifiedName] = customAttribute; - } - - this.attributeCache.Add(memberInfo, attributes); - } - } - - return attributes; - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/Helpers/StackTraceHelper.cs b/source/TestAdapter/Helpers/StackTraceHelper.cs deleted file mode 100644 index b96f520..0000000 --- a/source/TestAdapter/Helpers/StackTraceHelper.cs +++ /dev/null @@ -1,255 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Execution -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Text; - using System.Text.RegularExpressions; - - /// - /// Provides helper methods to parse stack trace. - /// - internal static class StackTraceHelper - { - /// - /// Type that need to be excluded. - /// - private static List typesToBeExcluded; - - /// - /// Gets the types whose methods should be ignored in the reported call stacks. - /// This is used to remove our stack that the user will not care about. - /// - private static List TypeToBeExcluded - { - get - { - if (typesToBeExcluded == null) - { - typesToBeExcluded = new List(); - typesToBeExcluded.Add(typeof(Assert).Namespace); - typesToBeExcluded.Add(typeof(TestExecutor).Namespace); - } - - return typesToBeExcluded; - } - } - - /// - /// Gets the stack trace for an exception, including all stack traces for inner - /// exceptions. - /// - /// - /// The exception. - /// - /// - /// The for the provided exception. - /// - internal static StackTraceInformation GetStackTraceInformation(Exception ex) - { - Debug.Assert(ex != null, "exception should not be null."); - - Stack stackTraces = new Stack(); - - for (Exception curException = ex; - curException != null; - curException = curException.InnerException) - { - // TODO:Fix the shadow stack-trace used in Private Object - // (Look-in Assertion.cs in the UnitTestFramework assembly) - - // Sometimes the stacktrace can be null, but the inner stacktrace - // contains information. We are not interested in null stacktraces - // so we simply ignore this case - try - { - if (curException.StackTrace != null) - { - stackTraces.Push(curException.StackTrace); - } - } - catch (Exception e) - { - // curException.StackTrace can throw exception, Although MSDN doc doesn't say that. - try - { - // try to get stacktrace - if (e.StackTrace != null) - { - stackTraces.Push(e.StackTrace); - } - } - catch (Exception) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.LogError( - "StackTraceHelper.GetStackTraceInformation: Failed to get stacktrace info."); - } - } - } - - StringBuilder result = new StringBuilder(); - bool first = true; - while (stackTraces.Count != 0) - { - result.Append( - string.Format( - CultureInfo.CurrentCulture, - "{0} {1}{2}", - first ? string.Empty : (Resource.UTA_EndOfInnerExceptionTrace + Environment.NewLine), - stackTraces.Pop(), - Environment.NewLine)); - first = false; - } - - return CreateStackTraceInformation(ex, true, result.ToString()); - } - - /// - /// Removes all stack frames that refer to nanoFramework.TestPlatform.TestTools.UnitTesting.Assertion - /// - /// - /// The stack Trace. - /// - /// - /// The trimmed stack trace removing traces of the framework and adapter from the stack. - /// - internal static string TrimStackTrace(string stackTrace) - { - Debug.Assert(stackTrace != null && stackTrace.Length > 0, "stack trace should be non-empty."); - - StringBuilder result = new StringBuilder(stackTrace.Length); - string[] stackFrames = Regex.Split(stackTrace, Environment.NewLine); - - foreach (string stackFrame in stackFrames) - { - if (string.IsNullOrEmpty(stackFrame)) - { - continue; - } - - // Add the frame to the result if it does not refer to - // the assertion class in the test framework - bool hasReference = HasReferenceToUTF(stackFrame); - if (!hasReference) - { - result.Append(stackFrame); - result.Append(Environment.NewLine); - } - } - - return result.ToString(); - } - - /// - /// Gets the exception messages, including the messages for all inner exceptions - /// recursively - /// - /// - /// The exception. - /// - /// - /// The aggregated exception message that considers inner exceptions. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - internal static string GetExceptionMessage(Exception ex) - { - Debug.Assert(ex != null, "exception should not be null."); - - StringBuilder result = new StringBuilder(); - bool first = true; - for (Exception curException = ex; - curException != null; - curException = curException.InnerException) - { - // Get the exception message. Need to check for errors because the Message property - // may have been overridden by the exception type in user code. - string msg; - try - { - msg = curException.Message; - } - catch - { - msg = string.Format(CultureInfo.CurrentCulture, Resource.UTF_FailedToGetExceptionMessage, curException.GetType()); - } - - result.Append( - string.Format( - CultureInfo.CurrentCulture, - "{0}{1}: {2}", - first ? string.Empty : " ---> ", - curException.GetType(), - msg)); - first = false; - } - - return result.ToString(); - } - - /// - /// Create stack trace information - /// - /// - /// The exception. - /// - /// - /// Whether the inner exception needs to be checked too. - /// - /// - /// The stack Trace String. - /// - /// - /// The . - /// - internal static StackTraceInformation CreateStackTraceInformation( - Exception ex, - bool checkInnerExceptions, - string stackTraceString) - { - if (checkInnerExceptions && ex.InnerException != null) - { - return CreateStackTraceInformation(ex.InnerException, checkInnerExceptions, stackTraceString); - } - - var stackTrace = StackTraceHelper.TrimStackTrace(stackTraceString); - - if (!string.IsNullOrEmpty(stackTrace)) - { - return new StackTraceInformation(stackTrace, null, 0, 0); - } - - return null; - } - - /// - /// Returns whether the parameter stackFrame has reference to UTF - /// - /// - /// The stack Frame. - /// - /// - /// True if the framework or the adapter methods are in the stack frame. - /// - internal static bool HasReferenceToUTF(string stackFrame) - { - foreach (var type in TypeToBeExcluded) - { - if (stackFrame.IndexOf(type, StringComparison.Ordinal) > -1) - { - return true; - } - } - - return false; - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/Helpers/UnitTestOutcomeHelper.cs b/source/TestAdapter/Helpers/UnitTestOutcomeHelper.cs deleted file mode 100644 index 0e34361..0000000 --- a/source/TestAdapter/Helpers/UnitTestOutcomeHelper.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Helpers -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - - internal static class UnitTestOutcomeHelper - { - /// - /// Converts the parameter unitTestOutcome to testOutcome - /// - /// The unit Test Outcome. - /// Should map inconclusive to failed. - /// The Test platforms outcome. - internal static TestOutcome ToTestOutcome(UnitTestOutcome unitTestOutcome, bool mapInconclusiveToFailed) - { - switch (unitTestOutcome) - { - case UnitTestOutcome.Passed: - return TestOutcome.Passed; - - case UnitTestOutcome.Failed: - case UnitTestOutcome.Error: - case UnitTestOutcome.Timeout: - return TestOutcome.Failed; - - case UnitTestOutcome.NotRunnable: - return TestOutcome.None; - - case UnitTestOutcome.Ignored: - return TestOutcome.Skipped; - - case UnitTestOutcome.Inconclusive: - { - if (mapInconclusiveToFailed) - { - return TestOutcome.Failed; - } - - return TestOutcome.Skipped; - } - - case UnitTestOutcome.NotFound: - return TestOutcome.NotFound; - - default: - return TestOutcome.None; - } - } - } -} diff --git a/source/TestAdapter/Helpers/UtfHelper.cs b/source/TestAdapter/Helpers/UtfHelper.cs deleted file mode 100644 index d7d75de..0000000 --- a/source/TestAdapter/Helpers/UtfHelper.cs +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Text; - - /// - /// Provides helper functionality for the unit test framework - /// - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - internal static class UtfHelper - { - /// - /// Gets the exception messages, including the messages for all inner exceptions - /// recursively - /// - /// Exception to get messages for - /// string with error message information - internal static string GetExceptionMsg(Exception ex) - { - Debug.Assert(ex != null, "exception is null"); - - StringBuilder result = new StringBuilder(); - bool first = true; - for (Exception curException = ex; - curException != null; - curException = curException.InnerException) - { - // Get the exception message. Need to check for errors because the Message property - // may have been overridden by the exception type in user code. - string msg; - try - { - msg = curException.Message; - } - catch - { - msg = string.Format( - CultureInfo.CurrentCulture, - Resource.UTF_FailedToGetExceptionMessage, - curException.GetType()); - } - - result.Append( - string.Format( - CultureInfo.CurrentCulture, - "{0}{1}: {2}", - first ? string.Empty : " ---> ", - curException.GetType(), - msg)); - first = false; - } - - return result.ToString(); - } - } -} diff --git a/source/TestAdapter/Interfaces/IAdapterTraceLogger.cs b/source/TestAdapter/Interfaces/IAdapterTraceLogger.cs deleted file mode 100644 index 0a76f07..0000000 --- a/source/TestAdapter/Interfaces/IAdapterTraceLogger.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices.Interface -{ - /// - /// A service to log any trace messages from the adapter that would be shown in *.TpTrace files. - /// - public interface IAdapterTraceLogger - { - /// - /// Log an error in a given format. - /// - /// The format. - /// The args. - void LogError( - string format, - params object[] args); - - /// - /// Log a warning in a given format. - /// - /// The format. - /// The args. - void LogWarning( - string format, - params object[] args); - - /// - /// Log an information message in a given format. - /// - /// The format. - /// The args. - void LogInfo( - string format, - params object[] args); - } -} diff --git a/source/TestAdapter/Interfaces/IAssemblyLoadContext.cs b/source/TestAdapter/Interfaces/IAssemblyLoadContext.cs deleted file mode 100644 index 4de7017..0000000 --- a/source/TestAdapter/Interfaces/IAssemblyLoadContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interfaces -{ - using System.Reflection; - - /// - /// Abstraction for Assembly Methods - /// - public interface IAssemblyLoadContext - { - /// - /// Loads assembly from given path - /// - /// Assembly path - /// Assembly from given path - Assembly LoadAssemblyFromPath(string assemblyPath); - - /// - /// Gets Assembly Name from given path - /// - /// Assembly path - /// AssemblyName from given path - AssemblyName GetAssemblyNameFromPath(string assemblyPath); - } -} diff --git a/source/TestAdapter/Interfaces/IFileOperations.cs b/source/TestAdapter/Interfaces/IFileOperations.cs deleted file mode 100644 index 123e10a..0000000 --- a/source/TestAdapter/Interfaces/IFileOperations.cs +++ /dev/null @@ -1,90 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interface -{ - using System.Reflection; - - /// - /// This service is responsible for any file based operations. - /// - public interface IFileOperations - { - /// - /// Loads an assembly into the current context. - /// - /// - /// The name of the assembly. - /// - /// - /// Indicates whether this should be a reflection only load. - /// - /// - /// A handle to the loaded assembly. - /// - /// - /// A platform can choose how it wants the assembly loaded. (ReflectionOnlyLoad/Load etc). - /// - Assembly LoadAssembly(string assemblyName, bool isReflectionOnly); - - /// - /// Gets the path to the .DLL of the assembly. - /// - /// The assembly. - /// Path to the .DLL of the assembly. - string GetAssemblyPath(Assembly assembly); - - /// - /// Verify if a file exists in the current context. - /// - /// The assembly file name. - /// true if the file exists. - bool DoesFileExist(string assemblyFileName); - - /// - /// Creates a Navigation session for the source file. - /// This is used to get file path and line number information for its components. - /// - /// The source file. - /// A Navigation session instance for the current platform. - /// - /// Unfortunately we cannot use INavigationSession introduced in Object Model in Dev14 update-2 because - /// the adapter needs to work with older VS versions as well where this new type would not be defined resulting in a type not found exception. - /// - object CreateNavigationSession(string source); - - /// - /// Get's the navigation data for a navigation session. - /// - /// The navigation session. - /// The class name. - /// The method name. - /// The min line number. - /// The file name. - /// - /// Unfortunately we cannot use INavigationSession introduced in Object Model in Dev14 update-2 because - /// the adapter needs to work with older VS versions as well where this new type would not be defined resulting in a type not found exception. - /// - void GetNavigationData(object navigationSession, string className, string methodName, out int minLineNumber, out string fileName); - - /// - /// Dispose's the navigation session instance. - /// - /// The navigation session. - /// - /// Unfortunately we cannot use INavigationSession introduced in Object Model in Dev14 update-2 because - /// the adapter needs to work with older VS versions as well where this new type would not be defined resulting in a type not found exception. - /// - void DisposeNavigationSession(object navigationSession); - - /// - /// Gets the full file path of an assembly file. - /// - /// The file name. - /// The full file path. - string GetFullFilePath(string assemblyFileName); - } -} diff --git a/source/TestAdapter/Interfaces/IPlatformServiceProvider.cs b/source/TestAdapter/Interfaces/IPlatformServiceProvider.cs deleted file mode 100644 index cce8d63..0000000 --- a/source/TestAdapter/Interfaces/IPlatformServiceProvider.cs +++ /dev/null @@ -1,126 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices.Interface; - using System.Collections.Generic; - using System.IO; - - /// - /// The definition of a PlatformServiceProvider with a hook to all the services. - /// - internal interface IPlatformServiceProvider - { - ///// - ///// Gets an instance to the platform service validator for test sources. - ///// - ITestSource TestSource { get; } - - ///// - ///// Gets an instance to the platform service to data drive a test. - ///// - //ITestDataSource TestDataSource { get; } - - /// - /// Gets an instance to the platform service for file operations. - /// - IFileOperations FileOperations { get; } - - /// - /// Gets an instance to the platform service for trace logging. - /// - IAdapterTraceLogger AdapterTraceLogger { get; } - - /// - /// Gets an instance of the test deployment service. - /// - ITestDeployment TestDeployment { get; } - - /// - /// Gets an instance to the platform service for a Settings Provider. - /// - ISettingsProvider SettingsProvider { get; } - - ///// - ///// Gets an instance to the platform service for thread operations. - ///// - //IThreadOperations ThreadOperations { get; } - - /// - /// Gets an instance to the platform service for reflection operations specific to a platform. - /// - IReflectionOperations ReflectionOperations { get; } - - /// - /// Creates an instance to the platform service for a test source host. - /// - /// - /// The source. - /// - /// - /// The run Settings for the session. - /// - /// - /// The handle to the the test platform. - /// - /// - /// Returns the host for the source provided. - /// - ITestSourceHost CreateTestSourceHost( - string source, - Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter.IRunSettings runSettings, - Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter.IFrameworkHandle frameworkHandle); - - /// - /// Gets an instance to the platform service listener who monitors trace and debug output - /// on provided text writer. - /// - /// - /// The text Writer. - /// - /// - /// The . - /// - ITraceListener GetTraceListener(TextWriter textWriter); - - /// - /// Gets an instance to the platform service trace-listener manager which updates the output/error streams - /// with redirected streams and performs operations on the listener provided as argument. - /// - /// - /// The redirected output stream writer. - /// - /// - /// The redirected error stream writer. - /// - /// - /// The manager for trace listeners. - /// - ITraceListenerManager GetTraceListenerManager(TextWriter outputWriter, TextWriter errorWriter); - - /// - /// Gets the TestContext object for a platform. - /// - /// - /// The test method. - /// - /// - /// The writer instance for logging. - /// - /// - /// The default set of properties the test context needs to be filled with. - /// - /// - /// The instance. - /// - /// - /// This was required for compatibility reasons since the TestContext object that the V1 adapter had for desktop is not .Net Core compliant. - /// - ITestContext GetTestContext(ObjectModel.ITestMethod testMethod, StringWriter writer, IDictionary properties); - } -} diff --git a/source/TestAdapter/Interfaces/IReflectionOperations.cs b/source/TestAdapter/Interfaces/IReflectionOperations.cs deleted file mode 100644 index 2dba4e8..0000000 --- a/source/TestAdapter/Interfaces/IReflectionOperations.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interface -{ - using System; - using System.Reflection; - - /// - /// This service is responsible for platform specific reflection operations. - /// - public interface IReflectionOperations - { - /// - /// Gets all the custom attributes adorned on a member. - /// - /// The member. - /// True to inspect the ancestors of element; otherwise, false. - /// The list of attributes on the member. Empty list if none found. - object[] GetCustomAttributes(MemberInfo memberInfo, bool inherit); - - /// - /// Gets all the custom attributes of a given type adorned on a member. - /// - /// The member info. - /// The attribute type. - /// True to inspect the ancestors of element; otherwise, false. - /// The list of attributes on the member. Empty list if none found. - object[] GetCustomAttributes(MemberInfo memberInfo, Type type, bool inherit); - - /// - /// Gets all the custom attributes of a given type on an assembly. - /// - /// The assembly. - /// The attribute type. - /// The list of attributes of the given type on the member. Empty list if none found. - object[] GetCustomAttributes(Assembly assembly, Type type); - } -} diff --git a/source/TestAdapter/Interfaces/ISettingsProvider.cs b/source/TestAdapter/Interfaces/ISettingsProvider.cs deleted file mode 100644 index e112759..0000000 --- a/source/TestAdapter/Interfaces/ISettingsProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interface -{ - using System.Collections.Generic; - using System.Xml; - - /// - /// To read settings from the runsettings xml for the corresponding platform service. - /// - public interface ISettingsProvider - { - /// - /// Load settings from the xml reader instance which are specific - /// for the corresponding platform service. - /// - /// - /// Reader that can be used to read current node and all its descendants, - /// to load the settings from. - void Load(XmlReader reader); - - /// - /// The set of properties/settings specific to the platform, that will be surfaced to the user through the test context. - /// - /// - /// source is used to find application base directory used for setting test context properties. - /// - /// Properties specific to the platform. - IDictionary GetProperties(string source); - } -} diff --git a/source/TestAdapter/Interfaces/ITestContext.cs b/source/TestAdapter/Interfaces/ITestContext.cs deleted file mode 100644 index 8037b03..0000000 --- a/source/TestAdapter/Interfaces/ITestContext.cs +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interface -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using System.Collections.Generic; - - /// - /// Operations on the TestContext object that is implemented differently for each platform. - /// - public interface ITestContext - { - /// - /// Gets the inner test context object. - /// - TestContext Context { get; } - - /// - /// Returns whether property with parameter name is present. - /// - /// The property Name. - /// The property Value. - /// True if the property is present. - bool TryGetPropertyValue(string propertyName, out object propertyValue); - - /// - /// Adds the parameter name/value pair to property bag. - /// - /// The property Name. - /// The property Value. - void AddProperty(string propertyName, string propertyValue); - - /// - /// Sets the outcome of a Test Method in the TestContext. - /// - /// The outcome. - void SetOutcome(UnitTestOutcome outcome); - - /// - /// Set data row for particular run of TestMethod. - /// - /// data row - void SetDataRow(object dataRow); - - /// - /// Set connection for TestContext - /// - /// db Connection. - void SetDataConnection(object dbConnection); - - /// - /// Gets the attached Result files - /// - /// - /// The list of result files. - /// - IList GetResultFiles(); - - /// - /// Gets the diagnostics messages written to TestContext.WriteLine() - /// - /// The test context messages added so far. - string GetDiagnosticMessages(); - - /// - /// Clears the previous testContext writeline messages. - /// - void ClearDiagnosticMessages(); - } -} diff --git a/source/TestAdapter/Interfaces/ITestDeployment.cs b/source/TestAdapter/Interfaces/ITestDeployment.cs deleted file mode 100644 index 0139f41..0000000 --- a/source/TestAdapter/Interfaces/ITestDeployment.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interface -{ - using System; - using System.Collections.Generic; - using System.Reflection; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - - /// - /// The TestDeployment interface. - /// - public interface ITestDeployment - { - /// - /// Deploy deployment items for the specified test cases. - /// - /// The test cases. - /// The run context. - /// The framework handle. - /// True if deployment is done. - bool Deploy(IEnumerable testCases, IRunContext runContext, IFrameworkHandle frameworkHandle); - - /// - /// Gets the set of deployment items on a method and its corresponding class. - /// - /// The method. - /// The type. - /// The warnings. - /// A KeyValuePair of deployment items. - KeyValuePair[] GetDeploymentItems(MethodInfo method, Type type, ICollection warnings); - - /// - /// Cleanup deployment item directories. - /// - void Cleanup(); - - /// - /// Gets the deployment output directory where the source file along with all its dependencies is dropped. - /// - /// The deployment output directory. - string GetDeploymentDirectory(); - } -} diff --git a/source/TestAdapter/Interfaces/ITestMethod.cs b/source/TestAdapter/Interfaces/ITestMethod.cs deleted file mode 100644 index c99b972..0000000 --- a/source/TestAdapter/Interfaces/ITestMethod.cs +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.Reflection; - - /// - /// TestMethod for execution. - /// - public partial interface ITestMethod - { - /// - /// Gets the name of test method. - /// - string TestMethodName { get; } - - /// - /// Gets the name of test class. - /// - string TestClassName { get; } - - /// - /// Gets the return type of test method. - /// - Type ReturnType { get; } - - /// - /// Gets the arguments with which test method is invoked. - /// - object[] Arguments { get; } - - /// - /// Gets the parameters of test method. - /// - ParameterInfo[] ParameterTypes { get; } - - /// - /// Gets the methodInfo for test method. - /// - /// - /// This is just to retrieve additional information about the method. - /// Do not directly invoke the method using MethodInfo. Use ITestMethod.Invoke instead. - /// - MethodInfo MethodInfo { get; } - - /// - /// Get all attributes of the test method. - /// - /// - /// Whether attribute defined in parent class is valid. - /// - /// - /// All attributes. - /// - Attribute[] GetAllAttributes(bool inherit); - - /// - /// Get attribute of specific type. - /// - /// System.Attribute type. - /// - /// Whether attribute defined in parent class is valid. - /// - /// - /// The attributes of the specified type. - /// - AttributeType[] GetAttributes(bool inherit) - where AttributeType : Attribute; - } - -} diff --git a/source/TestAdapter/Interfaces/ITestSource.cs b/source/TestAdapter/Interfaces/ITestSource.cs deleted file mode 100644 index 1f8484e..0000000 --- a/source/TestAdapter/Interfaces/ITestSource.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interface -{ - using System.Collections.Generic; - using System.Reflection; - - /// - /// This platform service is responsible for any data or operations to validate - /// the test sources provided to the adapter. - /// - public interface ITestSource - { - /// - /// Gets the set of valid extensions for sources targeting this platform. - /// - IEnumerable ValidSourceExtensions { get; } - - /// - /// Verifies if the assembly provided is referenced by the source. - /// - /// The assembly name. - /// The source. - /// True if the assembly is referenced. - bool IsAssemblyReferenced(AssemblyName assemblyName, string source); - - /// - /// Gets the set of sources (dll's/exe's) that contain tests. - /// - /// Sources given to the adapter. - /// Sources that contains tests. - IEnumerable GetTestSources(IEnumerable sources); - } -} diff --git a/source/TestAdapter/Interfaces/ITestSourceHost.cs b/source/TestAdapter/Interfaces/ITestSourceHost.cs deleted file mode 100644 index bfba100..0000000 --- a/source/TestAdapter/Interfaces/ITestSourceHost.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interface -{ - using System; - - /// - /// A host that loads the test source.This can be in isolation for desktop using an AppDomain or just loading the source in the current context. - /// - public interface ITestSourceHost : IDisposable - { - /// - /// Sets up the isolation host. - /// - void SetupHost(); - - /// - /// Creates an instance of a given type in the test source host. - /// - /// The type that needs to be created in the host. - /// The arguments to pass to the constructor. - /// This array of arguments must match in number, order, and type the parameters of the constructor to invoke. - /// Pass in null for a constructor with no arguments. - /// - /// An instance of the type created in the host. - /// If a type is to be created in isolation then it needs to be a MarshalByRefObject. - object CreateInstanceForType(Type type, object[] args); - - /// - /// Updates child-domain's appbase to point to test source location - /// - void UpdateAppBaseToTestSourceLocation(); - } -} diff --git a/source/TestAdapter/Interfaces/ITraceListener.cs b/source/TestAdapter/Interfaces/ITraceListener.cs deleted file mode 100644 index 83371d5..0000000 --- a/source/TestAdapter/Interfaces/ITraceListener.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interface -{ - using System.IO; - - /// - /// Operations on the TraceListener object that is implemented differently for each platform. - /// - public interface ITraceListener - { - /// - /// Gets the text writer that receives the tracing or debugging output. - /// - /// The writer instance. - TextWriter GetWriter(); - - /// - /// Disposes this TraceListener object. - /// - void Dispose(); - } -} diff --git a/source/TestAdapter/Interfaces/ITraceListenerManager.cs b/source/TestAdapter/Interfaces/ITraceListenerManager.cs deleted file mode 100644 index 16e2ae6..0000000 --- a/source/TestAdapter/Interfaces/ITraceListenerManager.cs +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Interface -{ - using System.IO; - - /// - /// Manager to perform operations on the TraceListener object passed as parameter. - /// These operations are implemented differently for each platform service. - /// - public interface ITraceListenerManager - { - /// - /// Adds the arguement traceListener object to TraceListenerCollection. - /// - /// The trace listener instance. - void Add(ITraceListener traceListener); - - /// - /// Removes the arguement traceListener object from TraceListenerCollection. - /// - /// The trace listener instance. - void Remove(ITraceListener traceListener); - - /// - /// Disposes the traceListener object passed as arguement. - /// - /// The trace listener instance. - void Dispose(ITraceListener traceListener); - } -} diff --git a/source/TestAdapter/LogMessenger.cs b/source/TestAdapter/LogMessenger.cs new file mode 100644 index 0000000..bb3c159 --- /dev/null +++ b/source/TestAdapter/LogMessenger.cs @@ -0,0 +1,97 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using nanoFramework.TestPlatform.TestAdapter; + +namespace nanoFramework.TestAdapter +{ + internal class LogMessenger + { + private IFrameworkHandle _frameworkHandle = null; + private IMessageLogger _logger = null; + private Settings _settings = null; + + /// + /// A log messenger to log during the discovery and executor process + /// + /// The framework handle + /// The settings provider + public LogMessenger( + IFrameworkHandle frameworkHandle, + SettingsProvider provider) + { + _frameworkHandle = frameworkHandle; + if (provider != null) + { + _settings = provider.Settings; + } + } + /// + /// A log messenger to log during the discovery and executor process + /// + /// A platform logger + /// The settings provider + + public LogMessenger( + IMessageLogger logger, + SettingsProvider provider) + { + _logger = logger; + + if (provider != null) + { + _settings = provider.Settings; + } + } + + /// + /// Log a panic message + /// + /// The message to log + public void LogPanicMessage( + string message) + { + LogMessage( + message, + Settings.LoggingLevel.Error, + true); + } + + /// + /// Log a message + /// + /// The message to log + /// The log level + /// Is it a panic message + public void LogMessage( + string message, + Settings.LoggingLevel logLevel, + bool panicMessage = false) + { + if (logLevel >= _settings?.Logging) + { + _frameworkHandle?.SendMessage( + TestMessageLevel.Informational, + $"[nanoTestAdapter]: {message}"); + + _logger?.SendMessage( + TestMessageLevel.Informational, + $"[nanoTestAdapter]: {message}"); + } + else if (panicMessage) + { + _frameworkHandle?.SendMessage( + TestMessageLevel.Error, + $"[nanoTestAdapter] **PANIC**: {message}"); + _logger?.SendMessage( + TestMessageLevel.Error, + $"[nanoTestAdapter] **PANIC**: {message}"); + } + } + } +} diff --git a/source/TestAdapter/Logger.cs b/source/TestAdapter/Logger.cs deleted file mode 100644 index 0b634b7..0000000 --- a/source/TestAdapter/Logger.cs +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Logging -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Reflection; - - /// - /// Enables users to log/write traces from unit tests for diagnostics. - /// - public class Logger - { - /// - /// Handler for LogMessage. - /// - /// Message to log. - public delegate void LogMessageHandler(string message); - - /// - /// Event to listen. Raised when unit test writer writes some message. - /// Mainly to consume by adapter. - /// - public static event LogMessageHandler OnLogMessage; - - /// - /// API for test writer to call to Log messages. - /// - /// String format with placeholders. - /// Parameters for placeholders. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - public static void LogMessage(string format, params object[] args) - { - if (OnLogMessage != null) - { - if (format == null) - { - throw new ArgumentNullException("format"); - } - - string message = string.Format(CultureInfo.InvariantCulture, format, args); - - // Making sure all event handlers are called in sunc on same thread. - foreach (var invoker in OnLogMessage.GetInvocationList()) - { - try - { - invoker.GetMethodInfo().Invoke(invoker.Target, new object[] { message }); - } - catch (Exception) - { - // Catch and ignore all exceptions thrown by event handlers. - } - } - } - } - } -} diff --git a/source/TestAdapter/MSTest.TestAdapter.csproj b/source/TestAdapter/MSTest.TestAdapter.csproj deleted file mode 100644 index 613d2b5..0000000 --- a/source/TestAdapter/MSTest.TestAdapter.csproj +++ /dev/null @@ -1,199 +0,0 @@ - - - - - Debug - AnyCPU - {EB97BF7E-4D42-4110-9B10-155F11ECA3E6} - Library - Properties - nanoFramework.TestPlatform.MSTest.TestAdapter - nanoFramework.TestPlatform.TestAdapter - v4.6.1 - - bin\$(Configuration) - - - true - full - false - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - TRACE - prompt - 4 - false - - - true - - - ..\key.snk - - - - ..\packages\Microsoft.VisualStudio.TestPlatform.ObjectModel.14.0.0\lib\net20\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - - - ..\packages\Newtonsoft.Json.11.0.2\lib\net40\Newtonsoft.Json.dll - - - - ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll - - - - ..\packages\System.Reflection.Metadata.1.6.0\lib\netstandard2.0\System.Reflection.Metadata.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resource.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ResXFileCodeGenerator - Resource.Designer.cs - Designer - - - - - - - - \ No newline at end of file diff --git a/source/TestAdapter/MSTestSettings.cs b/source/TestAdapter/MSTestSettings.cs deleted file mode 100644 index 9f58c3f..0000000 --- a/source/TestAdapter/MSTestSettings.cs +++ /dev/null @@ -1,522 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.Globalization; - using System.IO; - using System.Xml; - using System.Xml.Linq; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - - /// - /// Adapter Settings for the run - /// - [Serializable] - public class MSTestSettings - { - /// - /// The settings name. - /// - public const string SettingsName = "MSTest"; - - /// - /// The alias to the default settings name. - /// - public const string SettingsNameAlias = "MSTestV2"; - - private const string ParallelizeSettingsName = "Parallelize"; - - /// - /// Member variable for Adapter settings - /// - private static MSTestSettings currentSettings; - - /// - /// Member variable for RunConfiguration settings - /// - private static RunConfigurationSettings runConfigurationSettings; - - /// - /// Initializes a new instance of the class. - /// - public MSTestSettings() - { - this.CaptureDebugTraces = true; - this.MapInconclusiveToFailed = false; - this.EnableBaseClassTestMethodsFromOtherAssemblies = true; - this.ForcedLegacyMode = false; - this.TestSettingsFile = null; - this.DisableParallelization = false; - this.TestTimeout = 0; - } - - /// - /// Gets the current settings. - /// - public static MSTestSettings CurrentSettings - { - get - { - if (currentSettings == null) - { - currentSettings = new MSTestSettings(); - } - - return currentSettings; - } - - private set - { - currentSettings = value; - } - } - - /// - /// Gets the current configuration settings. - /// - public static RunConfigurationSettings RunConfigurationSettings - { - get - { - if (runConfigurationSettings == null) - { - runConfigurationSettings = new RunConfigurationSettings(); - } - - return runConfigurationSettings; - } - - private set - { - runConfigurationSettings = value; - } - } - - /// - /// Gets a value indicating whether capture debug traces. - /// - public bool CaptureDebugTraces { get; private set; } - - /// - /// Gets a value indicating whether user wants the adapter to run in legacy mode or not. - /// Default is False. - /// - public bool ForcedLegacyMode { get; private set; } - - /// - /// Gets the path to settings file. - /// - public string TestSettingsFile { get; private set; } - - /// - /// Gets a value indicating whether an inconclusive result be mapped to failed test. - /// - public bool MapInconclusiveToFailed { get; private set; } - - /// - /// Gets a value indicating whether to enable discovery of test methods from base classes in a different assembly from the inheriting test class. - /// - public bool EnableBaseClassTestMethodsFromOtherAssemblies { get; private set; } - - /// - /// Gets the number of threads/workers to be used for parallelization. - /// - public int? ParallelizationWorkers { get; private set; } - - /// - /// Gets the scope of parallelization. - /// - public ExecutionScope? ParallelizationScope { get; private set; } - - /// - /// Gets a value indicating whether the assembly can be parallelized. - /// - /// - /// This is also re-used to disable parallelization on format errors - /// - public bool DisableParallelization { get; private set; } - - /// - /// Gets specified global test case timeout - /// - public int TestTimeout { get; private set; } - - /// - /// Populate settings based on existing settings object. - /// - /// The existing settings object. - public static void PopulateSettings( - MSTestSettings settings - ) - { - CurrentSettings.CaptureDebugTraces = settings.CaptureDebugTraces; - CurrentSettings.ForcedLegacyMode = settings.ForcedLegacyMode; - CurrentSettings.TestSettingsFile = settings.TestSettingsFile; - CurrentSettings.MapInconclusiveToFailed = settings.MapInconclusiveToFailed; - CurrentSettings.EnableBaseClassTestMethodsFromOtherAssemblies = settings.EnableBaseClassTestMethodsFromOtherAssemblies; - CurrentSettings.ParallelizationWorkers = settings.ParallelizationWorkers; - CurrentSettings.ParallelizationScope = settings.ParallelizationScope; - CurrentSettings.DisableParallelization = settings.DisableParallelization; - CurrentSettings.TestTimeout = settings.TestTimeout; - } - - /// - /// Populate adapter settings from the context - /// - /// - /// The discovery context that contains the runsettings. - /// - public static void PopulateSettings( - IDiscoveryContext context - ) - { - RunConfigurationSettings = RunConfigurationSettings.PopulateSettings(context); - - if (context == null || context.RunSettings == null || string.IsNullOrEmpty(context.RunSettings.SettingsXml)) - { - // This will contain default adapter settings - CurrentSettings = new MSTestSettings(); - return; - } - - var aliasSettings = GetSettings(context.RunSettings.SettingsXml, SettingsNameAlias); - - // If a user specifies MSTestV2 in the runsettings, then prefer that over the v1 settings. - if (aliasSettings != null) - { - CurrentSettings = aliasSettings; - } - else - { - var settings = GetSettings(context.RunSettings.SettingsXml, SettingsName); - - if (settings != null) - { - CurrentSettings = settings; - } - else - { - CurrentSettings = new MSTestSettings(); - } - } - - SetGlobalSettings(context.RunSettings.SettingsXml, CurrentSettings); - } - - /// - /// Get the MSTestV1 adapter settings from the context - /// - /// The logger for messages. - /// Returns true if test settings is provided.. - public static bool IsLegacyScenario( - IMessageLogger logger - ) - { - if (!string.IsNullOrEmpty(CurrentSettings.TestSettingsFile)) - { - logger.SendMessage(TestMessageLevel.Warning, Resource.LegacyScenariosNotSupportedWarning); - return true; - } - - return false; - } - - /// - /// Gets the adapter specific settings from the xml. - /// - /// The xml with the settings passed from the test platform. - /// The name of the adapter settings to fetch - Its either MSTest or MSTestV2 - /// The settings if found. Null otherwise. - internal static MSTestSettings GetSettings( - string runsettingsXml, - string settingName - ) - { - using (var stringReader = new StringReader(runsettingsXml)) - { - XmlReader reader = XmlReader.Create(stringReader, XmlRunSettingsUtilities.ReaderSettings); - - // read to the fist child - XmlReaderUtilities.ReadToRootNode(reader); - reader.ReadToNextElement(); - - // Read till we reach nodeName element or reach EOF - while (!string.Equals(reader.Name, settingName, StringComparison.OrdinalIgnoreCase) - && - !reader.EOF) - { - reader.SkipToNextElement(); - } - - if (!reader.EOF) - { - // read nodeName element. - return ToSettings(reader.ReadSubtree()); - } - } - - return null; - } - - /// - /// Resets any settings loaded. - /// - internal static void Reset() - { - MSTestSettings.CurrentSettings = null; - MSTestSettings.RunConfigurationSettings = null; - } - - /// - /// Convert the parameter xml to TestSettings - /// - /// Reader to load the settings from. - /// An instance of the class - private static MSTestSettings ToSettings( - XmlReader reader - ) - { - ValidateArg.NotNull(reader, "reader"); - - // Expected format of the xml is: - - // - // - // true - // false - // false - // 5000 - // - // 4 - // TestClass - // - // - // - // (or) - // - // - // true - // ..\..\Local.testsettings - // true - // - MSTestSettings settings = new MSTestSettings(); - - // Read the first element in the section which is either "MSTest"/"MSTestV2" - reader.ReadToNextElement(); - - if (!reader.IsEmptyElement) - { - reader.Read(); - - while (reader.NodeType == XmlNodeType.Element) - { - bool result; - string elementName = reader.Name.ToUpperInvariant(); - switch (elementName) - { - case "CAPTURETRACEOUTPUT": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.CaptureDebugTraces = result; - } - - break; - } - - case "ENABLEBASECLASSTESTMETHODSFROMOTHERASSEMBLIES": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.EnableBaseClassTestMethodsFromOtherAssemblies = result; - } - - break; - } - - case "FORCEDLEGACYMODE": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.ForcedLegacyMode = result; - } - - break; - } - - case "MAPINCONCLUSIVETOFAILED": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.MapInconclusiveToFailed = result; - } - - break; - } - - case "SETTINGSFILE": - { - string fileName = reader.ReadInnerXml(); - - if (!string.IsNullOrEmpty(fileName)) - { - settings.TestSettingsFile = fileName; - } - - break; - } - - case "PARALLELIZE": - { - SetParallelSettings(reader.ReadSubtree(), settings); - reader.SkipToNextElement(); - - break; - } - - case "TESTTIMEOUT": - { - if (int.TryParse(reader.ReadInnerXml(), out int testTimeout) && testTimeout > 0) - { - settings.TestTimeout = testTimeout; - } - - break; - } - - default: - { - PlatformServiceProvider.Instance.SettingsProvider.Load(reader.ReadSubtree()); - reader.SkipToNextElement(); - - break; - } - } - } - } - - return settings; - } - - private static void SetParallelSettings( - XmlReader reader, - MSTestSettings settings - ) - { - reader.Read(); - if (!reader.IsEmptyElement) - { - // Read the first child. - reader.Read(); - - while (reader.NodeType == XmlNodeType.Element) - { - string elementName = reader.Name.ToUpperInvariant(); - switch (elementName) - { - case "WORKERS": - { - var value = reader.ReadInnerXml(); - if (int.TryParse(value, out int parallelWorkers)) - { - if (parallelWorkers == 0) - { - settings.ParallelizationWorkers = Environment.ProcessorCount; - } - else if (parallelWorkers > 0) - { - settings.ParallelizationWorkers = parallelWorkers; - } - else - { - throw new AdapterSettingsException( - string.Format( - CultureInfo.CurrentCulture, - Resource.InvalidParallelWorkersValue, - value)); - } - } - else - { - throw new AdapterSettingsException( - string.Format( - CultureInfo.CurrentCulture, - Resource.InvalidParallelWorkersValue, - value)); - } - - break; - } - - case "SCOPE": - { - var value = reader.ReadInnerXml(); - if (Enum.TryParse(value, out ExecutionScope scope)) - { - settings.ParallelizationScope = scope; - } - else - { - throw new AdapterSettingsException( - string.Format( - CultureInfo.CurrentCulture, - Resource.InvalidParallelScopeValue, - value, - string.Join(", ", Enum.GetNames(typeof(ExecutionScope))))); - } - - break; - } - - default: - { - throw new AdapterSettingsException( - string.Format( - CultureInfo.CurrentCulture, - Resource.InvalidSettingsXmlElement, - ParallelizeSettingsName, - reader.Name)); - } - } - } - } - - // If any of these properties are not set, resort to the defaults. - if (!settings.ParallelizationWorkers.HasValue) - { - settings.ParallelizationWorkers = Environment.ProcessorCount; - } - - if (!settings.ParallelizationScope.HasValue) - { - settings.ParallelizationScope = ExecutionScope.ClassLevel; - } - } - - private static void SetGlobalSettings(string runsettingsXml, MSTestSettings settings) - { - var runconfigElement = XDocument.Parse(runsettingsXml)?.Element("RunSettings")?.Element("RunConfiguration"); - - if (runconfigElement == null) - { - return; - } - - var disableParallelizationString = runconfigElement.Element("DisableParallelization")?.Value; - if (bool.TryParse(disableParallelizationString, out bool disableParallelization)) - { - settings.DisableParallelization = disableParallelization; - } - } - } -} diff --git a/source/TestAdapter/Navigation/DiaNavigationData.cs b/source/TestAdapter/Navigation/DiaNavigationData.cs deleted file mode 100644 index 655cc12..0000000 --- a/source/TestAdapter/Navigation/DiaNavigationData.cs +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation; - using System.Diagnostics.CodeAnalysis; - - /// - /// A struct that stores the infomation needed by the navigation: file name, line number, column number. - /// - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Dia is a specific name.")] - public class DiaNavigationData : INavigationData - { - public string FileName { get; set; } - - public int MinLineNumber { get; set; } - - public int MaxLineNumber { get; set; } - - public DiaNavigationData(string fileName, int minLineNumber, int maxLineNumber) - { - this.FileName = fileName; - this.MinLineNumber = minLineNumber; - this.MaxLineNumber = maxLineNumber; - } - } - -} \ No newline at end of file diff --git a/source/TestAdapter/Navigation/DiaSession.cs b/source/TestAdapter/Navigation/DiaSession.cs deleted file mode 100644 index 7293d1d..0000000 --- a/source/TestAdapter/Navigation/DiaSession.cs +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation; - using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; - using System.Diagnostics.CodeAnalysis; - using System.IO; - - /// - /// The class that enables us to get debug information from both managed and native binaries. - /// - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", - Justification = "Dia is a specific name.")] - public class DiaSession : INavigationSession - { - /// - /// Characters that should be stripped off the end of test names. - /// - private static readonly char[] TestNameStripChars = { '(', ')', ' ' }; - - /// - /// The symbol reader. - /// - private readonly ISymbolReader symbolReader; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The binary path. - /// - public DiaSession(string binaryPath) - : this(binaryPath, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The binary path is assembly path Ex: \path\to\bin\Debug\simpleproject.dll - /// - /// - /// search path. - /// - public DiaSession(string binaryPath, string searchPath) - : this(binaryPath, searchPath, GetSymbolReader(binaryPath)) - { - } - - internal DiaSession(string binaryPath, string searchPath, ISymbolReader symbolReader) - { - this.symbolReader = symbolReader; - ValidateArg.NotNullOrEmpty(binaryPath, "binaryPath"); - this.symbolReader.CacheSymbols(binaryPath, searchPath); - } - - /// - /// Dispose symbol reader - /// - public void Dispose() - { - this.symbolReader?.Dispose(); - } - - /// - /// Gets the navigation data for a method declared in a type. - /// - /// The declaring type name. - /// The method name. - /// The for that method. - /// Leaving this method in place to preserve back compatibility. - public DiaNavigationData GetNavigationData(string declaringTypeName, string methodName) - { - return (DiaNavigationData)this.GetNavigationDataForMethod(declaringTypeName, methodName); - } - - /// - /// Gets the navigation data for a method declared in a type. - /// - /// The declaring type name. - /// The method name. - /// The for that method. - public INavigationData GetNavigationDataForMethod(string declaringTypeName, string methodName) - { - ValidateArg.NotNullOrEmpty(declaringTypeName, "declaringTypeName"); - ValidateArg.NotNullOrEmpty(methodName, "methodName"); - methodName = methodName.TrimEnd(TestNameStripChars); - return this.symbolReader.GetNavigationData(declaringTypeName, methodName); - } - - private static ISymbolReader GetSymbolReader(string binaryPath) - { - var pdbFilePath = Path.ChangeExtension(binaryPath, ".pdb"); - - // For remote scenario, also look for pdb in current directory, (esp for UWP) - // The alternate search path should be an input from Adapters, but since it is not so currently adding a HACK - pdbFilePath = !File.Exists(pdbFilePath) ? Path.Combine(Directory.GetCurrentDirectory(), Path.GetFileName(pdbFilePath)) : pdbFilePath; - using (var stream = new FileHelper().GetStream(pdbFilePath, FileMode.Open, FileAccess.Read)) - { - if (PortablePdbReader.IsPortable(stream)) - { - return new PortableSymbolReader(); - } - - return new FullSymbolReader(); - } - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/Navigation/FullSymbolReader.cs b/source/TestAdapter/Navigation/FullSymbolReader.cs deleted file mode 100644 index d334ba4..0000000 --- a/source/TestAdapter/Navigation/FullSymbolReader.cs +++ /dev/null @@ -1,521 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Runtime.InteropServices; - - /// - /// To get method's file name, startline and endline from desktop assembly file. - /// - internal class FullSymbolReader : ISymbolReader - { - /// - /// To check isDisposed - /// - private bool isDisposed; - - private IDiaDataSource source; - private IDiaSession session; - - /// - /// Holds type symbols avaiable in the source. - /// - private Dictionary typeSymbols = new Dictionary(); - - /// - /// Holds method symbols for all types in the source. - /// Methods in different types can have same name, hence seprated dicitionary is created for each type. - /// Bug: Method overrides in same type are not handled (not a regression) - /// ToDo(Solution): Use method token along with as identifier, this will always give unique method.The adapters would need to send this token to us to correct the behavior. - /// - private Dictionary> methodSymbols = new Dictionary>(); - - /// - /// dispose caches - /// - public void Dispose() - { - this.Dispose(true); - - // Use SupressFinalize in case a subclass - // of this type implements a finalizer. - GC.SuppressFinalize(this); - } - - /// - /// Cache symbols from binary path - /// - /// - /// The binary path is assembly path Ex: \path\to\bin\Debug\simpleproject.dll - /// - /// - /// search path. - /// - public void CacheSymbols(string binaryPath, string searchPath) - { - try - { - if(OpenSession(binaryPath, searchPath)) - { - this.PopulateCacheForTypeAndMethodSymbols(); - } - } - catch (COMException) - { - this.Dispose(); - throw; - } - } - - /// - /// Gets Navigation data from caches - /// - /// - /// Type name Ex: MyNameSpace.MyType - /// - /// - /// Method name in declaringTypeName Ex: Method1 - /// - /// - /// . - /// Returns INavigationData which contains filename and linenumber. - /// - public INavigationData GetNavigationData(string declaringTypeName, string methodName) - { - INavigationData navigationData = null; - IDiaSymbol methodSymbol = null; - - IDiaSymbol typeSymbol = this.GetTypeSymbol(declaringTypeName, SymTagEnum.SymTagCompiland); - if (typeSymbol != null) - { - methodSymbol = this.GetMethodSymbol(typeSymbol, methodName); - } - else - { - // May be a managed C++ test assembly... - string fullMethodName = declaringTypeName.Replace(".", "::"); - fullMethodName = fullMethodName + "::" + methodName; - - methodSymbol = this.GetTypeSymbol(fullMethodName, SymTagEnum.SymTagFunction); - } - - if (methodSymbol != null) - { - navigationData = this.GetSymbolNavigationData(methodSymbol); - } - - return navigationData; - } - - private bool OpenSession(string filename, string searchPath) - { - try - { - // Activate the DIA data source COM object - this.source = DiaSourceObject.GetDiaSourceObject(); - - if (this.source == null) - { - return false; - } - - // UWP(App model) scenario - if (!Path.IsPathRooted(filename)) - { - filename = Path.Combine(Directory.GetCurrentDirectory(), filename); - if (string.IsNullOrEmpty(searchPath)) - { - searchPath = Directory.GetCurrentDirectory(); - } - } - - // Load the data for the executable - int hResult = this.source.LoadDataForExe(filename, searchPath, IntPtr.Zero); - if (HResult.Failed(hResult)) - { - throw new COMException(string.Format(Resource.FailedToCreateDiaSession, hResult)); - } - - // Open the session and return it - if (HResult.Failed(this.source.OpenSession(out this.session))) - { - return false; - } - } - catch (COMException) - { - throw; - } - finally - { - if (this.source != null) - { - Marshal.FinalReleaseComObject(this.source); - } - } - - return true; - } - - private DiaNavigationData GetSymbolNavigationData(IDiaSymbol symbol) - { - ValidateArg.NotNull(symbol, "symbol"); - - DiaNavigationData navigationData = new DiaNavigationData(null, int.MaxValue, int.MinValue); - - IDiaEnumLineNumbers lines = null; - - try - { - // Get the address section - if (HResult.Failed(symbol.GetAddressSection(out uint section))) - { - return navigationData; - } - - // Get the address offset - if (HResult.Failed(symbol.GetAddressOffset(out uint offset))) - { - return navigationData; - } - - // Get the length of the symbol - if (HResult.Failed(symbol.GetLength(out long length))) - { - return navigationData; - } - - this.session.FindLinesByAddress(section, offset, (uint)length, out lines); - - while (true) - { - lines.GetNext(1, out IDiaLineNumber lineNumber, out uint celt); - - if (celt != 1) - { - break; - } - - IDiaSourceFile sourceFile = null; - try - { - lineNumber.GetSourceFile(out sourceFile); - - // Get startline - lineNumber.GetLineNumber(out uint startLine); - - // Get endline - lineNumber.GetLineNumberEnd(out uint endLine); - - // The magic hex constant below works around weird data reported from GetSequencePoints. - // The constant comes from ILDASM's source code, which performs essentially the same test. - const uint Magic = 0xFEEFEE; - if (startLine >= Magic || endLine >= Magic) - { - continue; - } - - sourceFile.GetFilename(out string srcFileName); - - navigationData.FileName = srcFileName; - navigationData.MinLineNumber = Math.Min(navigationData.MinLineNumber, (int)startLine); - navigationData.MaxLineNumber = Math.Max(navigationData.MaxLineNumber, (int)endLine); - } - finally - { - ReleaseComObject(ref sourceFile); - ReleaseComObject(ref lineNumber); - } - } - } - finally - { - ReleaseComObject(ref lines); - } - - return navigationData; - } - - private void PopulateCacheForTypeAndMethodSymbols() - { - IDiaEnumSymbols enumTypeSymbols = null; - IDiaSymbol global = null; - try - { - this.session.GetGlobalScope(out global); - global.FindChildren(SymTagEnum.SymTagCompiland, null, 0, out enumTypeSymbols); - - // NOTE:: - // If foreach loop is used instead of Enumerator iterator, for some reason it leaves - // the reference to pdb active, which prevents pdb from being rebuilt (in VS IDE scenario). - enumTypeSymbols.GetNext(1, out IDiaSymbol typeSymbol, out uint celtTypeSymbol); - while (celtTypeSymbol == 1 && typeSymbol != null) - { - typeSymbol.GetName(out string name); - this.typeSymbols[name] = typeSymbol; - - IDiaEnumSymbols enumMethodSymbols = null; - try - { - Dictionary methodSymbolsForType = new Dictionary(); - typeSymbol.FindChildren(SymTagEnum.SymTagFunction, null, 0, out enumMethodSymbols); - - enumMethodSymbols.GetNext(1, out IDiaSymbol methodSymbol, out uint celtMethodSymbol); - while (celtMethodSymbol == 1 && methodSymbol != null) - { - methodSymbol.GetName(out string methodName); - UpdateMethodSymbolCache(methodName, methodSymbol, methodSymbolsForType); - enumMethodSymbols.GetNext(1, out methodSymbol, out celtMethodSymbol); - } - - this.methodSymbols[name] = methodSymbolsForType; - } - catch (Exception ex) - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error( - "Ignoring the exception while iterating method symbols:{0} for type:{1}", - ex, - name); - } - } - finally - { - ReleaseComObject(ref enumMethodSymbols); - } - - enumTypeSymbols.GetNext(1, out typeSymbol, out celtTypeSymbol); - } - } - catch (Exception ex) - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("Ignoring the exception while iterating type symbols:{0}", ex); - } - } - finally - { - ReleaseComObject(ref enumTypeSymbols); - ReleaseComObject(ref global); - } - } - - private IDiaSymbol GetTypeSymbol(string typeName, SymTagEnum symTag) - { - ValidateArg.NotNullOrEmpty(typeName, "typeName"); - - IDiaEnumSymbols enumSymbols = null; - IDiaSymbol typeSymbol = null; - IDiaSymbol global = null; - - try - { - typeName = typeName.Replace('+', '.'); - if (this.typeSymbols.ContainsKey(typeName)) - { - return this.typeSymbols[typeName]; - } - - this.session.GetGlobalScope(out global); - global.FindChildren(symTag, typeName, 0, out enumSymbols); - - enumSymbols.GetNext(1, out typeSymbol, out uint celt); - -#if DEBUG - if (typeSymbol == null) - { - IDiaEnumSymbols enumAllSymbols = null; - try - { - global.FindChildren(symTag, null, 0, out enumAllSymbols); - List children = new List(); - - while (true) - { - enumAllSymbols.GetNext(1, out IDiaSymbol childSymbol, out uint fetchedCount); - if (fetchedCount == 0 || childSymbol == null) - { - break; - } - - childSymbol.GetName(out string childSymbolName); - children.Add(childSymbolName); - ReleaseComObject(ref childSymbol); - } - - Debug.Assert(children.Count > 0); - } - finally - { - ReleaseComObject(ref enumAllSymbols); - } - } - -#endif - } - finally - { - ReleaseComObject(ref enumSymbols); - ReleaseComObject(ref global); - } - - if (typeSymbol != null) - { - this.typeSymbols[typeName] = typeSymbol; - } - - return typeSymbol; - } - - private IDiaSymbol GetMethodSymbol(IDiaSymbol typeSymbol, string methodName) - { - ValidateArg.NotNull(typeSymbol, "typeSymbol"); - ValidateArg.NotNullOrEmpty(methodName, "methodName"); - - IDiaEnumSymbols enumSymbols = null; - IDiaSymbol methodSymbol = null; - Dictionary methodSymbolsForType; - - try - { - typeSymbol.GetName(out string symbolName); - if (this.methodSymbols.ContainsKey(symbolName)) - { - methodSymbolsForType = this.methodSymbols[symbolName]; - if (methodSymbolsForType.ContainsKey(methodName)) - { - return methodSymbolsForType[methodName]; - } - } - else - { - methodSymbolsForType = new Dictionary(); - this.methodSymbols[symbolName] = methodSymbolsForType; - } - - typeSymbol.FindChildren(SymTagEnum.SymTagFunction, methodName, 0, out enumSymbols); - - enumSymbols.GetNext(1, out methodSymbol, out uint celtFetched); - -#if DEBUG - if (methodSymbol == null) - { - IDiaEnumSymbols enumAllSymbols = null; - try - { - typeSymbol.FindChildren(SymTagEnum.SymTagFunction, null, 0, out enumAllSymbols); - List children = new List(); - - while (true) - { - enumAllSymbols.GetNext(1, out IDiaSymbol childSymbol, out uint fetchedCount); - if (fetchedCount == 0 || childSymbol == null) - { - break; - } - - childSymbol.GetName(out string childSymbolName); - children.Add(childSymbolName); - ReleaseComObject(ref childSymbol); - } - - Debug.Assert(children.Count > 0); - } - finally - { - ReleaseComObject(ref enumAllSymbols); - } - } - -#endif - } - finally - { - ReleaseComObject(ref enumSymbols); - } - - if (methodSymbol != null) - { - methodSymbolsForType[methodName] = methodSymbol; - } - - return methodSymbol; - } - - /// - /// Update the method symbol cache. - /// - private static void UpdateMethodSymbolCache(string methodName, IDiaSymbol methodSymbol, Dictionary methodSymbolCache) - { - Debug.Assert(!string.IsNullOrEmpty(methodName), "MethodName cannot be empty."); - Debug.Assert(methodSymbol != null, "Method symbol cannot be null."); - Debug.Assert(methodSymbolCache != null, "Method symbol cache cannot be null."); - - // #827589, In case a type has overloaded methods, then there could be a method already in the - // cache which should be disposed. - if (methodSymbolCache.TryGetValue(methodName, out IDiaSymbol oldSymbol)) - { - ReleaseComObject(ref oldSymbol); - } - - methodSymbolCache[methodName] = methodSymbol; - } - - private static void ReleaseComObject(ref T obj) - where T : class - { - if (obj != null) - { - Marshal.FinalReleaseComObject(obj); - obj = null; - } - } - - private void Dispose(bool disposing) - { - if (!this.isDisposed) - { - if (disposing) - { - foreach (Dictionary methodSymbolsForType in this.methodSymbols.Values) - { - foreach (IDiaSymbol methodSymbol in methodSymbolsForType.Values) - { - IDiaSymbol symToRelease = methodSymbol; - ReleaseComObject(ref symToRelease); - } - - methodSymbolsForType.Clear(); - } - - this.methodSymbols.Clear(); - this.methodSymbols = null; - foreach (IDiaSymbol typeSymbol in this.typeSymbols.Values) - { - IDiaSymbol symToRelease = typeSymbol; - ReleaseComObject(ref symToRelease); - } - - this.typeSymbols.Clear(); - this.typeSymbols = null; - ReleaseComObject(ref this.session); - ReleaseComObject(ref this.source); - } - - this.isDisposed = true; - } - } - } -} diff --git a/source/TestAdapter/Navigation/Helpers/FileHelper.cs b/source/TestAdapter/Navigation/Helpers/FileHelper.cs deleted file mode 100644 index 4f603ea..0000000 --- a/source/TestAdapter/Navigation/Helpers/FileHelper.cs +++ /dev/null @@ -1,138 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers -{ - using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - - /// - /// The file helper. - /// - public class FileHelper : IFileHelper - { - private static readonly Version DefaultFileVersion = new Version(0, 0); - - /// - public DirectoryInfo CreateDirectory(string path) - { - return Directory.CreateDirectory(path); - } - - /// - public string GetCurrentDirectory() - { - return Directory.GetCurrentDirectory(); - } - - /// - public bool Exists(string path) - { - return File.Exists(path); - } - - /// - public bool DirectoryExists(string path) - { - return Directory.Exists(path); - } - - /// - public Stream GetStream(string filePath, FileMode mode, FileAccess access = FileAccess.ReadWrite) - { - return new FileStream(filePath, mode, access); - } - - /// - public Stream GetStream(string filePath, FileMode mode, FileAccess access, FileShare share) - { - return new FileStream(filePath, mode, access, share); - } - - - /// - public IEnumerable EnumerateFiles( - string directory, - SearchOption searchOption, - params string[] endsWithSearchPatterns) - { - if (endsWithSearchPatterns == null || endsWithSearchPatterns.Length == 0) - { - return Enumerable.Empty(); - } - - var files = Directory.EnumerateFiles(directory, "*", searchOption); - - return files.Where( - file => endsWithSearchPatterns.Any( - pattern => file.EndsWith(pattern, StringComparison.OrdinalIgnoreCase))); - } - - /// - public FileAttributes GetFileAttributes(string path) - { - return new FileInfo(path).Attributes; - } - - /// - public Version GetFileVersion(string path) - { - var currentFileVersion = FileVersionInfo.GetVersionInfo(path)?.FileVersion; - return Version.TryParse(currentFileVersion, out var currentVersion) ? currentVersion : DefaultFileVersion; - } - - /// - public void CopyFile(string sourcePath, string destinationPath) - { - File.Copy(sourcePath, destinationPath); - } - - /// - public void MoveFile(string sourcePath, string destinationPath) - { - File.Move(sourcePath, destinationPath); - } - - /// - public void WriteAllTextToFile(string filePath, string content) - { - File.WriteAllText(filePath, content); - } - - /// - public string GetFullPath(string path) - { - return Path.GetFullPath(path); - } - - /// - public void DeleteEmptyDirectroy(string dirPath) - { - try - { - if (Directory.Exists(dirPath) && Directory.GetFiles(dirPath).Length == 0 - && Directory.GetDirectories(dirPath).Length == 0) - { - Directory.Delete(dirPath, true); - } - } - catch - { - // ignored - } - } - - /// - public string[] GetFiles(string path, string searchPattern, SearchOption searchOption) - { - return Directory.GetFiles(path, searchPattern, searchOption); - } - } -} diff --git a/source/TestAdapter/Navigation/Helpers/IProcessHelper.cs b/source/TestAdapter/Navigation/Helpers/IProcessHelper.cs deleted file mode 100644 index 13aeeb2..0000000 --- a/source/TestAdapter/Navigation/Helpers/IProcessHelper.cs +++ /dev/null @@ -1,117 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces -{ - using System; - using System.Collections.Generic; - - /// - /// Interface for any process related functionality. This is needed for clean unit-testing. - /// - public interface IProcessHelper - { - /// - /// Launches the process with the given arguments. - /// - /// The full file name of the process. - /// The command-line arguments. - /// The working directory for this process. - /// Environment variables to set while bootstrapping the process. - /// Call back for to read error stream data - /// Call back for on process exit - /// The process created. - object LaunchProcess(string processPath, string arguments, string workingDirectory, IDictionary environmentVariables, Action errorCallback, Action exitCallBack); - - /// - /// Gets the current process file path. - /// - /// The current process file path. - string GetCurrentProcessFileName(); - - /// - /// Gets the current process location. - /// - /// The current process location. - string GetCurrentProcessLocation(); - - /// - /// Gets the location of test engine. - /// - /// Location of test engine. - string GetTestEngineDirectory(); - - /// - /// Gets the location of native dll's, depending on current process architecture.. - /// - /// Location of native dll's - string GetNativeDllDirectory(); - - /// - /// Gets current process architecture - /// - /// Process Architecture - PlatformArchitecture GetCurrentProcessArchitecture(); - - /// - /// Gets the process id of test engine. - /// - /// process id of test engine. - int GetCurrentProcessId(); - - /// - /// Gets the process id of input process. - /// - /// process parameter - /// process id. - int GetProcessId(object process); - - /// - /// Gets the process name for given process id. - /// - /// process id - /// Name of process - string GetProcessName(int processId); - - /// - /// False if process has not exited, True otherwise. Set exitCode only if process has exited. - /// - /// process parameter - /// return value of exitCode - /// False if process has not exited, True otherwise - bool TryGetExitCode(object process, out int exitCode); - - /// - /// Sets the process exit callback. - /// - /// - /// The process id. - /// - /// - /// Callback on process exit. - /// - void SetExitCallback(int processId, Action callbackAction); - - /// - /// Terminates a process. - /// - /// Reference of process to terminate. - void TerminateProcess(object process); - - /// - /// Wait for process to exit - /// - /// Reference to process - void WaitForProcessExit(object process); - - /// - /// Gets the process handle for given process Id. - /// - /// process id - /// Process Handle - IntPtr GetProcessHandle(int processId); - } -} diff --git a/source/TestAdapter/Navigation/Helpers/PlatformArchitecture.cs b/source/TestAdapter/Navigation/Helpers/PlatformArchitecture.cs deleted file mode 100644 index e0de00a..0000000 --- a/source/TestAdapter/Navigation/Helpers/PlatformArchitecture.cs +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.PlatformAbstractions -{ - using System; - - /// - /// Available architectures for test platform. - /// - public enum PlatformArchitecture - { - X86, - X64, - ARM, - ARM64 - } -} diff --git a/source/TestAdapter/Navigation/Helpers/ProcessHelper.cs b/source/TestAdapter/Navigation/Helpers/ProcessHelper.cs deleted file mode 100644 index d011dd2..0000000 --- a/source/TestAdapter/Navigation/Helpers/ProcessHelper.cs +++ /dev/null @@ -1,216 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.PlatformAbstractions -{ - using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Reflection; - - - public partial class ProcessHelper : IProcessHelper - { - /// - public string GetCurrentProcessLocation() - { - return Path.GetDirectoryName(this.GetCurrentProcessFileName()); - } - - public IntPtr GetProcessHandle(int processId) - { - return Process.GetProcessById(processId).Handle; - } - } - - - /// - /// Helper class to deal with process related functionality. - /// - public partial class ProcessHelper : IProcessHelper - { - /// - public object LaunchProcess(string processPath, string arguments, string workingDirectory, IDictionary envVariables, Action errorCallback, Action exitCallBack) - { - var process = new Process(); - try - { - process.StartInfo.UseShellExecute = false; - process.StartInfo.CreateNoWindow = true; - process.StartInfo.WorkingDirectory = workingDirectory; - - process.StartInfo.FileName = processPath; - process.StartInfo.Arguments = arguments; - process.StartInfo.RedirectStandardError = true; - process.EnableRaisingEvents = true; - - if (envVariables != null) - { - foreach (var kvp in envVariables) - { - process.StartInfo.AddEnvironmentVariable(kvp.Key, kvp.Value); - } - } - - if (errorCallback != null) - { - process.ErrorDataReceived += (sender, args) => errorCallback(sender as Process, args.Data); - } - - if (exitCallBack != null) - { - process.Exited += (sender, args) => - { - // Call WaitForExit without again to ensure all streams are flushed, - var p = sender as Process; - try - { - // Add timeout to avoid indefinite waiting on child process exit. - p.WaitForExit(500); - } - catch (InvalidOperationException) - { - } - - // If exit callback has code that access Process object, ensure that the exceptions handling should be done properly. - exitCallBack(p); - }; - } - - // EqtTrace.Verbose("ProcessHelper: Starting process '{0}' with command line '{1}'", processPath, arguments); - process.Start(); - - if (errorCallback != null) - { - process.BeginErrorReadLine(); - } - } - catch (Exception) - { - process.Dispose(); - process = null; - - // EqtTrace.Error("TestHost Object {0} failed to launch with the following exception: {1}", processPath, exception.Message); - throw; - } - - return process as object; - } - - /// - public string GetCurrentProcessFileName() - { - return Process.GetCurrentProcess().MainModule.FileName; - } - - /// - public string GetTestEngineDirectory() - { - return Path.GetDirectoryName(typeof(ProcessHelper).GetTypeInfo().Assembly.Location); - } - - /// - public int GetCurrentProcessId() - { - return Process.GetCurrentProcess().Id; - } - - /// - public string GetProcessName(int processId) - { - return Process.GetProcessById(processId).ProcessName; - } - - /// - public bool TryGetExitCode(object process, out int exitCode) - { - var proc = process as Process; - try - { - if (proc != null && proc.HasExited) - { - exitCode = proc.ExitCode; - return true; - } - } - catch (InvalidOperationException) - { - } - - exitCode = 0; - return false; - } - - /// - public void SetExitCallback(int processId, Action callbackAction) - { - try - { - var process = Process.GetProcessById(processId); - process.EnableRaisingEvents = true; - process.Exited += (sender, args) => callbackAction?.Invoke(sender); - } - catch (ArgumentException) - { - // Process.GetProcessById() throws ArgumentException if process is not running(identifier might be expired). - // Invoke callback immediately. - callbackAction?.Invoke(null); - } - } - - /// - public void TerminateProcess(object process) - { - var proc = process as Process; - try - { - if (proc != null && !proc.HasExited) - { - proc.Kill(); - } - } - catch (InvalidOperationException) - { - } - } - - /// - public int GetProcessId(object process) - { - var proc = process as Process; - return proc?.Id ?? -1; - } - - /// - public PlatformArchitecture GetCurrentProcessArchitecture() - { - if (IntPtr.Size == 8) - { - return PlatformArchitecture.X64; - } - - return PlatformArchitecture.X86; - } - - /// - public string GetNativeDllDirectory() - { - return Path.Combine(this.GetCurrentProcessLocation(), "ComComponents", this.GetCurrentProcessArchitecture().ToString().ToLower()); - } - - /// - public void WaitForProcessExit(object process) - { - var proc = process as Process; - if (proc != null || !proc.HasExited) - { - proc.WaitForExit(); - } - } - } -} diff --git a/source/TestAdapter/Navigation/IFileHelper.cs b/source/TestAdapter/Navigation/IFileHelper.cs deleted file mode 100644 index 6c24a7a..0000000 --- a/source/TestAdapter/Navigation/IFileHelper.cs +++ /dev/null @@ -1,140 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces -{ - using System; - using System.Collections.Generic; - using System.IO; - - /// - /// The FileHelper interface. - /// - public interface IFileHelper - { - /// - /// Creates a directory. - /// - /// Path of the directory. - /// for the created directory. - DirectoryInfo CreateDirectory(string path); - - /// - /// Gets the current directory - /// - /// Current directory - string GetCurrentDirectory(); - - /// - /// Exists utility to check if file exists (case sensitive). - /// - /// The path of file. - /// True if file exists . - bool Exists(string path); - - /// - /// Exists utility to check if directory exists (case sensitive). - /// - /// The path of file. - /// True if directory exists . - bool DirectoryExists(string path); - - /// - /// Gets a stream for the file. - /// - /// Path to the file. - /// for file operations. - /// for file operations. - /// A that supports read/write on the file. - Stream GetStream(string filePath, FileMode mode, FileAccess access = FileAccess.ReadWrite); - - /// - /// Gets a stream for the file. - /// - /// Path to the file. - /// for file operations. - /// for file operations. - /// for file operations. - /// A that supports read/write on the file. - Stream GetStream(string filePath, FileMode mode, FileAccess access, FileShare share); - - /// - /// Enumerates files which match a pattern (case insensitive) in a directory. - /// - /// Parent directory to search. - /// for directory. - /// Patterns used to select files using String.EndsWith - /// List of files matching the pattern. - IEnumerable EnumerateFiles(string directory, SearchOption searchOption, params string[] endsWithSearchPatterns); - - /// - /// Gets attributes of a file. - /// - /// Full path of the file. - /// Attributes of the file. - FileAttributes GetFileAttributes(string path); - - /// - /// Gets the version information of the file. - /// - /// Full path to the file. - /// File Version information of the file. - Version GetFileVersion(string path); - - /// - /// Copy a file in the file system. - /// - /// Full path of the file. - /// Target path for the file. - void CopyFile(string sourcePath, string destinationPath); - - /// - /// Moves a file in the file system. - /// - /// Full path of the file. - /// Target path for the file. - void MoveFile(string sourcePath, string destinationPath); - - /// - /// The write all text to file. - /// - /// - /// The file Path. - /// - /// - /// The content. - /// - void WriteAllTextToFile(string filePath, string content); - - /// - /// Gets full path if relative path is specified. - /// - /// - /// The path. - /// - /// - /// Full path. - /// - string GetFullPath(string path); - - /// - /// Helper for deleting a directory. It deletes the directory only if its empty. - /// - /// - /// The directory path. - /// - void DeleteEmptyDirectroy(string directoryPath); - - /// - /// Gets all files in directory based on search pattern - /// - /// Directory Path - /// Search pattern - /// Search option - /// string[] - string[] GetFiles(string path, string searchPattern, SearchOption searchOption); - } -} diff --git a/source/TestAdapter/Navigation/INavigationData.cs b/source/TestAdapter/Navigation/INavigationData.cs deleted file mode 100644 index 132b13b..0000000 --- a/source/TestAdapter/Navigation/INavigationData.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation -{ - /// - /// Stores the navigation data associated with the .exe/.dll file - /// - public interface INavigationData - { - /// - /// Gets or sets the file name of the file containing the method being navigated. - /// - string FileName { get; set; } - - /// - /// Gets or sets the min line number of the method being navigated in the file. - /// - int MinLineNumber { get; set; } - - /// - /// Gets or sets the max line number of the method being navigated in the file. - /// - int MaxLineNumber { get; set; } - } -} diff --git a/source/TestAdapter/Navigation/INavigationSession.cs b/source/TestAdapter/Navigation/INavigationSession.cs deleted file mode 100644 index 153566a..0000000 --- a/source/TestAdapter/Navigation/INavigationSession.cs +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation -{ - using System; - - /// - /// Manages the debug data associated with the .exe/.dll file - /// - public interface INavigationSession : IDisposable - { - /// - /// Gets the navigation data for a method. - /// - /// The declaring type name. - /// The method name. - /// The to get to the method. - INavigationData GetNavigationDataForMethod(string declaringTypeName, string methodName); - } -} diff --git a/source/TestAdapter/Navigation/ISymbolReader.cs b/source/TestAdapter/Navigation/ISymbolReader.cs deleted file mode 100644 index 4c6ac57..0000000 --- a/source/TestAdapter/Navigation/ISymbolReader.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation -{ - using System; - - /// - /// Caches filename and linenumber for symbols in assembly. - /// - internal interface ISymbolReader : IDisposable - { - /// - /// Cache symbols from binary path - /// - /// - /// The binary path is assembly path Ex: \path\to\bin\Debug\simpleproject.dll - /// - /// - /// search path. - /// - void CacheSymbols(string binaryPath, string searchPath); - - /// - /// Gets Navigation data from caches - /// - /// - /// Type name Ex: MyNameSpace.MyType - /// - /// - /// Method name in declaringTypeName Ex: Method1 - /// - /// - /// . - /// Returns INavigationData which contains file name and line number. - /// - INavigationData GetNavigationData(string declaringTypeName, string methodName); - } -} diff --git a/source/TestAdapter/Navigation/NativeMethods.cs b/source/TestAdapter/Navigation/NativeMethods.cs deleted file mode 100644 index 42d0274..0000000 --- a/source/TestAdapter/Navigation/NativeMethods.cs +++ /dev/null @@ -1,644 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using PlatformAbstractions; - using System; - using System.IO; - using System.Runtime.InteropServices; - internal static class HResult - { - public static bool Failed(int hr) - { - return hr < 0; - } - - public static bool Succeeded(int hr) - { - return !Failed(hr); - } - } - - /// - /// Some GUID constants we use to instantiate COM objects. - /// - internal static class Guids - { - internal static Guid CLSID_DiaSource = new Guid("79F1BB5F-B66E-48E5-B6A9-1545C323CA3D"); - } - - /// - /// DIA's IDiaEnumLineNumbers used for enumerating a symbol's line numbers. - /// - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("FE30E878-54AC-44f1-81BA-39DE940F6052")] - internal interface IDiaEnumLineNumbers - { - int Stub1(); - - [PreserveSig] - int GetCount(out uint count); - - [PreserveSig] - int GetItem(uint index, out IDiaLineNumber line); - - [PreserveSig] - int GetNext(uint celt, out IDiaLineNumber rgelt, out uint pceltFetched); - - int Stub5(); - - int Stub6(); - - int Stub7(); - } - - /// - /// DIA's IDiaLineNumber used for retrieving line information. - /// - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("B388EB14-BE4D-421d-A8A1-6CF7AB057086")] - internal interface IDiaLineNumber - { - int Stub1(); - - [PreserveSig] - int GetSourceFile(out IDiaSourceFile file); - - [PreserveSig] - int GetLineNumber(out uint line); - - [PreserveSig] - int GetLineNumberEnd(out uint line); - - [PreserveSig] - int GetColumnNumber(out uint line); - - int Stub6(); - - int Stub7(); - - int Stub8(); - - int Stub9(); - - int Stub10(); - - int Stub11(); - - int Stub12(); - - int Stub13(); - - int Stub14(); - } - - /// - /// DIA's IDiaSession used for locating symbols. - /// - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("2F609EE1-D1C8-4E24-8288-3326BADCD211")] - internal interface IDiaSession - { - int Stub1(); - - int Stub2(); - - [PreserveSig] - int GetGlobalScope(out IDiaSymbol diaSymbol); - - int Stub4(); - - int Stub5(); - - int Stub6(); - - int Stub7(); - - int Stub8(); - - int Stub9(); - - int Stub10(); - - int Stub11(); - - int Stub12(); - - int Stub13(); - - [PreserveSig] - int FindSymbolByToken(uint token, SymTagEnum tag, out IDiaSymbol symbol); - - int Stub15(); - - int Stub16(); - - int Stub17(); - - int Stub18(); - - int Stub19(); - - int Stub20(); - - int Stub21(); - - [PreserveSig] - int FindLinesByAddress(uint section, uint offset, uint length, out IDiaEnumLineNumbers enumerator); - - int Stub23(); - - int Stub24(); - - int Stub25(); - - int Stub26(); - - int Stub27(); - } - - /// - /// DIA's IDiaSourceFile used for getting source filenames. - /// - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("A2EF5353-F5A8-4eb3-90D2-CB526ACB3CDD")] - internal interface IDiaSourceFile - { - int Stub1(); - - [PreserveSig] - int GetFilename([MarshalAs(UnmanagedType.BStr)] out string filename); - - int Stub3(); - - int Stub4(); - - int Stub5(); - } - - /// - /// Represents the DIA symbol tags. - /// - internal enum SymTagEnum : uint - { - SymTagNull, - SymTagExe, - SymTagCompiland, - SymTagCompilandDetails, - SymTagCompilandEnv, - SymTagFunction, - SymTagBlock, - SymTagData, - SymTagAnnotation, - SymTagLabel, - SymTagPublicSymbol, - SymTagUDT, - SymTagEnum, - SymTagFunctionType, - SymTagPointerType, - SymTagArrayType, - SymTagBaseType, - SymTagTypedef, - SymTagBaseClass, - SymTagFriend, - SymTagFunctionArgType, - SymTagFuncDebugStart, - SymTagFuncDebugEnd, - SymTagUsingNamespace, - SymTagVTableShape, - SymTagVTable, - SymTagCustom, - SymTagThunk, - SymTagCustomType, - SymTagManagedType, - SymTagDimension - } - - /// - /// DIA's IDiaSymbol used for getting the address of function symbols. - /// - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("cb787b2f-bd6c-4635-ba52-933126bd2dcd")] - internal interface IDiaSymbol - { - int Stub1(); - - [PreserveSig] - int GetSymTag(out SymTagEnum tag); - - int GetName(out string name); - - int Stub4(); - - int Stub5(); - - int Stub6(); - - int Stub7(); - - int Stub8(); - - [PreserveSig] - int GetAddressSection(out uint section); - - [PreserveSig] - int GetAddressOffset(out uint offset); - - int Stub11(); - - int Stub12(); - - int Stub13(); - - int Stub14(); - - [PreserveSig] - int GetLength(out long length); - - int Stub16(); - - int Stub17(); - - int Stub18(); - - int Stub19(); - - int Stub20(); - - int Stub21(); - - int Stub22(); - - int Stub23(); - - int Stub24(); - - int Stub25(); - - int Stub26(); - - int Stub27(); - - int Stub28(); - - int Stub29(); - - int Stub30(); - - int Stub31(); - - int Stub32(); - - int Stub33(); - - int Stub34(); - - int Stub35(); - - int Stub36(); - - int Stub37(); - - int Stub38(); - - int Stub39(); - - int Stub40(); - - int Stub41(); - - int Stub42(); - - int Stub43(); - - int Stub44(); - - int Stub45(); - - int Stub46(); - - int Stub47(); - - int Stub48(); - - int Stub49(); - - int Stub50(); - - int Stub51(); - - int Stub52(); - - int Stub53(); - - int Stub54(); - - int Stub55(); - - int Stub56(); - - int Stub57(); - - int Stub58(); - - int Stub59(); - - int Stub60(); - - int Stub61(); - - int Stub62(); - - int Stub63(); - - int Stub64(); - - int Stub65(); - - int Stub66(); - - int Stub67(); - - int Stub68(); - - int Stub69(); - - int Stub70(); - - int Stub71(); - - int Stub72(); - - int Stub73(); - - int Stub74(); - - int Stub75(); - - int Stub76(); - - int Stub77(); - - int Stub78(); - - int Stub79(); - - int Stub80(); - - int Stub81(); - - int Stub82(); - - [PreserveSig] - int FindChildren(SymTagEnum tag, string str, int flags, out IDiaEnumSymbols symbol); - - int Stub84(); - - int Stub85(); - - int Stub86(); - - int Stub87(); - - int Stub88(); - - int Stub89(); - - int Stub90(); - - int Stub91(); - - int Stub92(); - - int Stub93(); - - int Stub94(); - - int Stub95(); - - int Stub96(); - - int Stub97(); - - int Stub98(); - - int Stub99(); - - int Stub100(); - - int Stub101(); - - int Stub102(); - - int Stub103(); - - int Stub104(); - - int Stub105(); - - int Stub106(); - - int Stub107(); - - int Stub108(); - - int Stub109(); - - int Stub110(); - - int Stub111(); - - int Stub112(); - - int Stub113(); - - int Stub114(); - - int Stub115(); - - int Stub116(); - - int Stub117(); - - int Stub118(); - - int Stub119(); - - int Stub120(); - - int Stub121(); - - int Stub122(); - - int Stub123(); - - int Stub124(); - - int Stub125(); - - int Stub126(); - - int Stub127(); - - int Stub128(); - - int Stub129(); - - int Stub130(); - - int Stub131(); - - int Stub132(); - - int Stub133(); - - int Stub134(); - - int Stub135(); - - int Stub136(); - - int Stub137(); - - int Stub138(); - - int Stub139(); - - int Stub140(); - - int Stub141(); - - int Stub142(); - - int Stub143(); - - int Stub144(); - - int Stub145(); - - int Stub146(); - - int Stub147(); - - int Stub148(); - - int Stub149(); - - int Stub150(); - - int Stub151(); - - int Stub152(); - - int Stub153(); - - int Stub154(); - - int Stub155(); - } - - // The definition for DiaSource COM object is present InternalApis\vctools\inc\dia2.h - // The GUID here must match what is present in dia2.h - [ComImport, CoClass(typeof(DiaSourceClass)), Guid("79F1BB5F-B66E-48E5-B6A9-1545C323CA3D")] - internal interface DiaSource : IDiaDataSource - { - } - - // The definition for DiaSourceClass COM object is present InternalApis\vctools\inc\dia2.h - // The GUID here must match what is present in dia2.h - [ComImport, ClassInterface((short)0), Guid("E6756135-1E65-4D17-8576-610761398C3C")] - internal class DiaSourceClass - { - } - - internal static class DiaSourceObject - { - [DllImport("kernel32.dll", SetLastError = true)] - public static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, int dwFlags); - - public static IDiaDataSource GetDiaSourceObject() - { - var nativeDllDirectory = new ProcessHelper().GetNativeDllDirectory(); - - IntPtr modHandle = LoadLibraryEx(Path.Combine(nativeDllDirectory, "msdia140.dll"), IntPtr.Zero, 0); - - if (modHandle == IntPtr.Zero) - { - throw new COMException(string.Format(Resource.FailedToLoadMsDia)); - } - - var diaSourceClassGuid = new Guid("{E6756135-1E65-4D17-8576-610761398C3C}"); - var comClassFactory = (IClassFactory)DllGetClassObject(diaSourceClassGuid, new Guid("00000001-0000-0000-C000-000000000046")); - - Guid iDataDataSourceGuid = new Guid("79F1BB5F-B66E-48E5-B6A9-1545C323CA3D"); - comClassFactory.CreateInstance(null, ref iDataDataSourceGuid, out object comObject); - return (comObject as IDiaDataSource); - } - - #region private - - [ComImport, ComVisible(false), Guid("00000001-0000-0000-C000-000000000046"), - InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - private interface IClassFactory - { - void CreateInstance( - [MarshalAs(UnmanagedType.Interface)] object aggregator, - ref Guid refiid, - [MarshalAs(UnmanagedType.Interface)] out object createdObject); - - void LockServer(bool incrementRefCount); - } - - [return: MarshalAs(UnmanagedType.Interface)] - [DllImport("msdia140.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] - internal static extern object DllGetClassObject( - [In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, - [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid); - - #endregion - } - - /// - /// DIA's IDiaDataSource used for opening symbols. - /// - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("79F1BB5F-B66E-48E5-B6A9-1545C323CA3D")] - internal interface IDiaDataSource - { - int Stub1(); - - int Stub2(); - - int Stub3(); - - [PreserveSig] - int LoadDataForExe( - [MarshalAs(UnmanagedType.LPWStr)] string executable, - [MarshalAs(UnmanagedType.LPWStr)] string searchPath, - IntPtr callback); - - int Stub5(); - - [PreserveSig] - int OpenSession(out IDiaSession session); - } - - [ComImport, Guid("CAB72C48-443B-48F5-9B0B-42F0820AB29A"), InterfaceType(1)] - internal interface IDiaEnumSymbols - { - int Stub1(); - - [PreserveSig] - int GetCount(out uint count); - - [PreserveSig] - int GetItem(uint index, out IDiaSymbol symbol); - - int GetNext(uint index, out IDiaSymbol symbol, out uint pceltFetched); - - int Stub5(); - - int Stub6(); - - int Stub7(); - } -} diff --git a/source/TestAdapter/Navigation/PlatformAssemblyLoadContext.cs b/source/TestAdapter/Navigation/PlatformAssemblyLoadContext.cs deleted file mode 100644 index 530cae1..0000000 --- a/source/TestAdapter/Navigation/PlatformAssemblyLoadContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.PlatformAbstractions -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interfaces; - using System.Reflection; - - /// - public class PlatformAssemblyLoadContext : IAssemblyLoadContext - { - /// - public AssemblyName GetAssemblyNameFromPath(string assemblyPath) - { - return AssemblyName.GetAssemblyName(assemblyPath); - } - - public Assembly LoadAssemblyFromPath(string assemblyPath) - { - return Assembly.LoadFrom(assemblyPath); - } - } -} diff --git a/source/TestAdapter/Navigation/PortablePdbReader.cs b/source/TestAdapter/Navigation/PortablePdbReader.cs deleted file mode 100644 index b06d5f3..0000000 --- a/source/TestAdapter/Navigation/PortablePdbReader.cs +++ /dev/null @@ -1,173 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using System; - using System.IO; - using System.Reflection; - using System.Reflection.Metadata; - using System.Reflection.Metadata.Ecma335; - - /// - /// The portable pdb reader. - /// - internal class PortablePdbReader : IDisposable - { - /// - /// Use to get method token - /// - private static readonly PropertyInfo MethodInfoMethodTokenProperty = - typeof(MethodInfo).GetProperty("MetadataToken"); - - /// - /// Metadata reader provider from portable pdb stream - /// To get Metadate reader - /// - private MetadataReaderProvider provider; - - /// - /// Metadata reader from portable pdb stream - /// To get method debug info from mehthod info - /// - private MetadataReader reader; - - /// - /// Initializes a new instance of the class. - /// - /// - /// Portable pdb stream - /// - /// - /// Raises Exception on given stream is not portable pdb stream - /// - public PortablePdbReader(Stream stream) - { - if (!IsPortable(stream)) - { - throw new Exception("Given stream is not portable stream"); - } - - this.provider = MetadataReaderProvider.FromPortablePdbStream(stream); - this.reader = this.provider.GetMetadataReader(); - } - - /// - /// Dispose Metadata reader - /// - public void Dispose() - { - this.provider?.Dispose(); - this.provider = null; - this.reader = null; - } - - /// - /// Gets dia navigation data from Metadata reader - /// - /// - /// Method info. - /// - /// - /// The . - /// - public DiaNavigationData GetDiaNavigationData(MethodInfo methodInfo) - { - if (methodInfo == null) - { - return null; - } - - var handle = GetMethodDebugInformationHandle(methodInfo); - - return this.GetDiaNavigationData(handle); - } - - /// - /// Checks gives stream is from portable pdb or not - /// - /// - /// Stream. - /// - /// - /// The . - /// - internal static bool IsPortable(Stream stream) - { - // First four bytes should be 'BSJB' - var result = (stream.ReadByte() == 'B') && (stream.ReadByte() == 'S') && (stream.ReadByte() == 'J') - && (stream.ReadByte() == 'B'); - stream.Position = 0; - return result; - } - - internal static MethodDebugInformationHandle GetMethodDebugInformationHandle(MethodInfo methodInfo) - { - var methodToken = (int)MethodInfoMethodTokenProperty.GetValue(methodInfo); - var handle = ((MethodDefinitionHandle)MetadataTokens.Handle(methodToken)).ToDebugInformationHandle(); - return handle; - } - - private static void GetMethodMinAndMaxLineNumber( - MethodDebugInformation methodDebugDefinition, - out int minLineNumber, - out int maxLineNumber) - { - minLineNumber = int.MaxValue; - maxLineNumber = int.MinValue; - var orderedSequencePoints = methodDebugDefinition.GetSequencePoints(); - foreach (var sequencePoint in orderedSequencePoints) - { - if (sequencePoint.IsHidden) - { - // Special sequence point with startLine is Magic number 0xFEEFEE - // Magic number comes from Potable CodeGen source code - continue; - } - minLineNumber = Math.Min(minLineNumber, sequencePoint.StartLine); - maxLineNumber = Math.Max(maxLineNumber, sequencePoint.StartLine); - } - } - - private DiaNavigationData GetDiaNavigationData(MethodDebugInformationHandle handle) - { - if (this.reader == null) - { - throw new ObjectDisposedException(nameof(PortablePdbReader)); - } - - DiaNavigationData diaNavigationData = null; - try - { - var methodDebugDefinition = this.reader.GetMethodDebugInformation(handle); - var fileName = this.GetMethodFileName(methodDebugDefinition); - int minLineNumber, maxLineNumber; - GetMethodMinAndMaxLineNumber(methodDebugDefinition, out minLineNumber, out maxLineNumber); - - diaNavigationData = new DiaNavigationData(fileName, minLineNumber, maxLineNumber); - } - catch (BadImageFormatException exception) - { - EqtTrace.Error("failed to get dia navigation data: {0}", exception); - } - - return diaNavigationData; - } - - private string GetMethodFileName(MethodDebugInformation methodDebugDefinition) - { - var fileName = string.Empty; - if (!methodDebugDefinition.Document.IsNil) - { - var document = this.reader.GetDocument(methodDebugDefinition.Document); - fileName = this.reader.GetString(document.Name); - } - - return fileName; - } - } -} diff --git a/source/TestAdapter/Navigation/PortableSymbolReader.cs b/source/TestAdapter/Navigation/PortableSymbolReader.cs deleted file mode 100644 index 6a667fb..0000000 --- a/source/TestAdapter/Navigation/PortableSymbolReader.cs +++ /dev/null @@ -1,141 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; - using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; - using System; - using System.Collections.Generic; - using System.IO; - using System.Reflection; - - /// - /// The portable symbol reader. - /// - internal class PortableSymbolReader : ISymbolReader - { - /// - /// Key in first dict is Type FullName - /// Key in second dict is method name - /// - private Dictionary> methodsNavigationDataForType = - new Dictionary>(); - - /// - /// The cache symbols. - /// - /// - /// The binary path. - /// - /// - /// The search path. - /// - public void CacheSymbols(string binaryPath, string searchPath) - { - this.PopulateCacheForTypeAndMethodSymbols(binaryPath); - } - - /// - /// The dispose. - /// - public void Dispose() - { - foreach (var methodsNavigationData in this.methodsNavigationDataForType.Values) - { - methodsNavigationData.Clear(); - } - - this.methodsNavigationDataForType.Clear(); - this.methodsNavigationDataForType = null; - } - - /// - /// The get navigation data. - /// - /// - /// The declaring type name. - /// - /// - /// The method name. - /// - /// - /// The . - /// - public INavigationData GetNavigationData(string declaringTypeName, string methodName) - { - INavigationData navigationData = null; - if (this.methodsNavigationDataForType.ContainsKey(declaringTypeName)) - { - var methodDict = this.methodsNavigationDataForType[declaringTypeName]; - if (methodDict.ContainsKey(methodName)) - { - navigationData = methodDict[methodName]; - } - } - - return navigationData; - } - - /// - /// The populate cache for type and method symbols. - /// - /// - /// The binary path. - /// - private void PopulateCacheForTypeAndMethodSymbols(string binaryPath) - { - try - { - var pdbFilePath = Path.ChangeExtension(binaryPath, ".pdb"); - using (var pdbReader = new PortablePdbReader(new FileHelper().GetStream(pdbFilePath, FileMode.Open, FileAccess.Read))) - { - // At this point, the assembly should be already loaded into the load context. We query for a reference to - // find the types and cache the symbol information. Let the loader follow default lookup order instead of - // forcing load from a specific path. - var asm = Assembly.Load(new PlatformAssemblyLoadContext().GetAssemblyNameFromPath(binaryPath)); - - foreach (var type in asm.GetTypes()) - { - // Get declared method infos - var methodInfoList = type.GetTypeInfo().DeclaredMethods; - var methodsNavigationData = new Dictionary(); - - foreach (var methodInfo in methodInfoList) - { - var diaNavigationData = pdbReader.GetDiaNavigationData(methodInfo); - if (diaNavigationData != null) - { - methodsNavigationData[methodInfo.Name] = diaNavigationData; - } - else - { - EqtTrace.Error( - string.Format( - "Unable to find source information for method: {0} type: {1}", - methodInfo.Name, - type.FullName)); - } - } - - if (methodsNavigationData.Count != 0) - { - this.methodsNavigationDataForType[type.FullName] = methodsNavigationData; - } - } - } - } - catch (Exception ex) - { - EqtTrace.Error("PortableSymbolReader: Failed to load symbols for binary: {0}", binaryPath); - EqtTrace.Error(ex); - this.Dispose(); - throw; - } - } - } -} diff --git a/source/TestAdapter/Navigation/ProcessStartInfoExtensions.cs b/source/TestAdapter/Navigation/ProcessStartInfoExtensions.cs deleted file mode 100644 index 62a3378..0000000 --- a/source/TestAdapter/Navigation/ProcessStartInfoExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace Microsoft.VisualStudio.TestPlatform.PlatformAbstractions -{ - using System.Diagnostics; - - public static class ProcessStartInfoExtensions - { - /// - /// Add environment variable that apply to this process and child processes. - /// - /// The process start info - /// Environment Variable name. - /// Environment Variable value. - public static void AddEnvironmentVariable(this ProcessStartInfo startInfo, string name, string value) - { - startInfo.EnvironmentVariables[name] = value; - } - } -} diff --git a/source/TestAdapter/ObjectModel/AdapterSettingsException.cs b/source/TestAdapter/ObjectModel/AdapterSettingsException.cs deleted file mode 100644 index 38745ae..0000000 --- a/source/TestAdapter/ObjectModel/AdapterSettingsException.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - using System; - - internal class AdapterSettingsException : Exception - { - internal AdapterSettingsException(string message) - : base(message) - { - } - } -} diff --git a/source/TestAdapter/ObjectModel/ITestMethod.cs b/source/TestAdapter/ObjectModel/ITestMethod.cs deleted file mode 100644 index 4c1986a..0000000 --- a/source/TestAdapter/ObjectModel/ITestMethod.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - using System; - using System.Reflection; - - /// - /// TestMethod structure that is shared between adapter and platform services only. - /// - public partial interface ITestMethod - { - /// - /// Gets the name of the test method - /// - string Name { get; } - - /// - /// Gets the full class name of the test method - /// - string FullClassName { get; } - - /// - /// Gets the declaring class full name. This will be used while getting navigation data. - /// - string DeclaringClassFullName { get; } - - /// - /// Gets the name of the test assembly - /// - string AssemblyName { get; } - - /// - /// Gets a value indicating whether test method is async - /// - bool IsAsync { get; } - } -} diff --git a/source/TestAdapter/ObjectModel/StackTraceInformation.cs b/source/TestAdapter/ObjectModel/StackTraceInformation.cs deleted file mode 100644 index 85221e6..0000000 --- a/source/TestAdapter/ObjectModel/StackTraceInformation.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - using System; - using System.Diagnostics; - - [Serializable] - internal class StackTraceInformation - { - public StackTraceInformation(string stackTrace) - : this(stackTrace, null, 0, 0) - { - } - - public StackTraceInformation(string stackTrace, string filePath, int lineNumber, int columnNumber) - { - Debug.Assert(!string.IsNullOrEmpty(stackTrace), "StackTrace message should not be empty"); - Debug.Assert(lineNumber >= 0, "Line number should be greater than or equal to 0"); - Debug.Assert(columnNumber >= 0, "Column number should be greater than or equal to 0"); - - this.ErrorStackTrace = stackTrace; - this.ErrorFilePath = filePath; - this.ErrorLineNumber = lineNumber; - this.ErrorColumnNumber = columnNumber; - } - - /// - /// Gets stack Trace associated with the test failure - /// - public string ErrorStackTrace { get; private set; } - - /// - /// Gets source code FilePath where the error occurred - /// - public string ErrorFilePath { get; private set; } - - /// - /// Gets line number in the source code file where the error occurred. - /// - public int ErrorLineNumber { get; private set; } - - /// - /// Gets column number in the source code file where the error occurred. - /// - public int ErrorColumnNumber { get; private set; } - } -} diff --git a/source/TestAdapter/ObjectModel/TestFailedException.cs b/source/TestAdapter/ObjectModel/TestFailedException.cs deleted file mode 100644 index fecc081..0000000 --- a/source/TestAdapter/ObjectModel/TestFailedException.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - using System; - using System.Diagnostics; - - /// - /// Internal class to indicate Test Execution failure - /// - [Serializable] - internal class TestFailedException : Exception - { - public TestFailedException(UnitTestOutcome outcome, string errorMessage) - : this(outcome, errorMessage, null, null) - { - } - - public TestFailedException(UnitTestOutcome outcome, string errorMessage, StackTraceInformation stackTraceInformation) - : this(outcome, errorMessage, stackTraceInformation, null) - { - } - - public TestFailedException(UnitTestOutcome outcome, string errorMessage, Exception realException) - : this(outcome, errorMessage, null, realException) - { - } - - public TestFailedException(UnitTestOutcome outcome, string errorMessage, StackTraceInformation stackTraceInformation, Exception realException) - : base(errorMessage, realException) - { - Debug.Assert(!string.IsNullOrEmpty(errorMessage), "ErrorMessage should not be empty"); - - this.Outcome = outcome; - this.StackTraceInformation = stackTraceInformation; - } - - /// - /// Gets stack trace information associated with the test failure - /// - public StackTraceInformation StackTraceInformation { get; private set; } - - /// - /// Gets outcome of the test case - /// - public UnitTestOutcome Outcome { get; private set; } - } -} diff --git a/source/TestAdapter/ObjectModel/TestMethod.cs b/source/TestAdapter/ObjectModel/TestMethod.cs deleted file mode 100644 index 7ae7b7f..0000000 --- a/source/TestAdapter/ObjectModel/TestMethod.cs +++ /dev/null @@ -1,135 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - using System; - using System.Diagnostics; - using System.Reflection; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - - /// - /// TestMethod contains information about a unit test method that needs to be executed - /// - [Serializable] - public sealed class TestMethod : ITestMethod - { - #region Fields - - /// - /// Member field for the property 'DeclaringClassFullName' - /// - private string declaringClassFullName = null; - - /// - /// Member field for the property 'DeclaringAssemblyName' - /// - private string declaringAssemblyName = null; - - #endregion - - public TestMethod(string name, string fullClassName, string assemblyName, bool isAsync) - { - if (string.IsNullOrEmpty(assemblyName)) - { - throw new ArgumentNullException(nameof(assemblyName)); - } - - Debug.Assert(!string.IsNullOrEmpty(name), "TestName cannot be empty"); - Debug.Assert(!string.IsNullOrEmpty(fullClassName), "Full className cannot be empty"); - - this.Name = name; - this.FullClassName = fullClassName; - this.AssemblyName = assemblyName; - this.IsAsync = isAsync; - } - - /// - /// Gets the name of the test method - /// - public string Name { get; private set; } - - /// - /// Gets the full classname of the test method - /// - public string FullClassName { get; private set; } - - /// - /// Gets or sets the declaring class full name. This will be used while getting navigation data. - /// This will be null if AssemblyName is same as DeclaringAssemblyName. - /// Reason to set to null in the above case is to minimise the transfer of data across appdomains and not have a perf hit. - /// - public string DeclaringAssemblyName - { - get - { - return this.declaringAssemblyName; - } - - set - { - Debug.Assert(value != this.AssemblyName, "DeclaringAssemblyName should not be the same as AssemblyName."); - this.declaringAssemblyName = value; - } - } - - /// - /// Gets or sets the declaring class full name. This will be used while getting navigation data. - /// This will be null if FullClassName is same as DeclaringClassFullName. - /// Reason to set to null in the above case is to minimise the transfer of data across appdomains and not have a perf hit. - /// - public string DeclaringClassFullName - { - get - { - return this.declaringClassFullName; - } - - set - { - Debug.Assert(value != this.FullClassName, "DeclaringClassFullName should not be the same as FullClassName."); - this.declaringClassFullName = value; - } - } - - /// - /// Gets the name of the test assembly - /// - public string AssemblyName { get; private set; } - - /// - /// Gets a value indicating whether specifies test method is async - /// - public bool IsAsync { get; private set; } - - public string TestMethodName => throw new NotImplementedException(); - - public string TestClassName => throw new NotImplementedException(); - - public Type ReturnType => throw new NotImplementedException(); - - public object[] Arguments => throw new NotImplementedException(); - - public ParameterInfo[] ParameterTypes => throw new NotImplementedException(); - - public MethodInfo MethodInfo => throw new NotImplementedException(); - - public Attribute[] GetAllAttributes(bool inherit) - { - throw new NotImplementedException(); - } - - public AttributeType[] GetAttributes(bool inherit) where AttributeType : Attribute - { - throw new NotImplementedException(); - } - - public TestResult Invoke(object[] arguments) - { - throw new NotImplementedException(); - } - } -} diff --git a/source/TestAdapter/ObjectModel/TestMethodOptions.cs b/source/TestAdapter/ObjectModel/TestMethodOptions.cs deleted file mode 100644 index 949cf9f..0000000 --- a/source/TestAdapter/ObjectModel/TestMethodOptions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - - /// - /// A fascade service for options passed to a test method. - /// - internal class TestMethodOptions - { - /// - /// Gets or sets the timeout specified for a test method. - /// - internal int Timeout { get; set; } - - /// - /// Gets or sets the ExpectedException attribute adorned on a test method. - /// - internal ExpectedExceptionBaseAttribute ExpectedException { get; set; } - - /// - /// Gets or sets the testcontext passed into the test method. - /// - internal ITestContext TestContext { get; set; } - - /// - /// Gets or sets a value indicating whether debug traces should be captured when running the test. - /// - internal bool CaptureDebugTraces { get; set; } - - /// - /// Gets or sets the test method executor that invokes the test. - /// - internal TestMethodAttribute Executor { get; set; } - } -} diff --git a/source/TestAdapter/ObjectModel/TypeInspectionException.cs b/source/TestAdapter/ObjectModel/TypeInspectionException.cs deleted file mode 100644 index 768f512..0000000 --- a/source/TestAdapter/ObjectModel/TypeInspectionException.cs +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - using System; - - /// - /// Internal class to indicate type inspection failure - /// - [Serializable] - internal class TypeInspectionException : Exception - { - public TypeInspectionException() - : base() - { - } - - public TypeInspectionException(string message) - : base(message) - { - } - - public TypeInspectionException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} diff --git a/source/TestAdapter/ObjectModel/UnitTestElement.cs b/source/TestAdapter/ObjectModel/UnitTestElement.cs deleted file mode 100644 index c84c09c..0000000 --- a/source/TestAdapter/ObjectModel/UnitTestElement.cs +++ /dev/null @@ -1,131 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - - /// - /// The unit test element. - /// - [Serializable] - internal class UnitTestElement - { - /// - /// Initializes a new instance of the class. - /// - /// The test method. - /// Thrown when method is null. - public UnitTestElement( - TestMethod testMethod - ) - { - if (testMethod == null) - { - throw new ArgumentNullException("testMethod"); - } - - Debug.Assert(testMethod.FullClassName != null, "Full className cannot be empty"); - this.TestMethod = testMethod; - } - - /// - /// Gets the test method which should be executed as part of this test case - /// - public TestMethod TestMethod { get; private set; } - - /// - /// Gets or sets a value indicating whether the unit test should be ignored at run-time - /// - public bool Ignored { get; set; } - - /// - /// Gets or sets a value indicating whether it is a async test - /// - public bool IsAsync { get; set; } - - /// - /// Gets or sets the test categories for test method. - /// - public string[] TestCategory { get; set; } - - /// - /// Gets or sets the traits for test method. - /// - public Trait[] Traits { get; set; } - - /// - /// Gets or sets the priority of the test method, if any. - /// - public int? Priority { get; set; } - - /// - /// Gets or sets the deployment items for the test method. - /// - public KeyValuePair[] DeploymentItems { get; set; } - - // TODO currently nanoFramework does not support async - ///// - ///// Gets or sets the compiler generated type name for async test method. - ///// - //internal string AsyncTypeName { get; set; } - - /// - /// Convert the UnitTestElement instance to an Object Model testCase instance. - /// - /// An instance of . - internal TestCase ToTestCase() - { - var fullName = string.Format( - CultureInfo.InvariantCulture, - "{0}.{1}", - this.TestMethod.FullClassName, - this.TestMethod.Name); - - TestCase testCase = new TestCase(fullName, TestAdapter.Constants.ExecutorUri, this.TestMethod.AssemblyName); - testCase.DisplayName = this.TestMethod.Name; - - testCase.SetPropertyValue(TestAdapter.Constants.TestClassNameProperty, this.TestMethod.FullClassName); - - // TODO currently nanoFramework does not support async - //// Many of the tests will not be async, so there is no point in sending extra data - //if (this.IsAsync) - //{ - // testCase.SetPropertyValue(TestAdapter.Constants.AsyncTestProperty, this.IsAsync); - //} - - // Set only if some test category is present - if (this.TestCategory != null && this.TestCategory.Length > 0) - { - testCase.SetPropertyValue(TestAdapter.Constants.TestCategoryProperty, this.TestCategory); - } - - // Set priority if present - if (this.Priority != null) - { - testCase.SetPropertyValue(TestAdapter.Constants.PriorityProperty, this.Priority.Value); - } - - if (this.Traits != null) - { - testCase.Traits.AddRange(this.Traits); - } - - // The list of items to deploy before running this test. - if (this.DeploymentItems != null && this.DeploymentItems.Length > 0) - { - testCase.SetPropertyValue(TestAdapter.Constants.DeploymentItemsProperty, this.DeploymentItems); - } - - return testCase; - } - } -} diff --git a/source/TestAdapter/ObjectModel/UnitTestOutcome.cs b/source/TestAdapter/ObjectModel/UnitTestOutcome.cs deleted file mode 100644 index 8bfe830..0000000 --- a/source/TestAdapter/ObjectModel/UnitTestOutcome.cs +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - /// - /// Outcome of a test - /// - public enum UnitTestOutcome : int - { - /// - /// There was a system error while we were trying to execute a test. - /// - Error, - - /// - /// Test was executed, but there were issues. - /// Issues may involve exceptions or failed assertions. - /// - Failed, - - /// - /// The test timed out - /// - Timeout, - - /// - /// Test has completed, but we can't say if it passed or failed. - /// (Used in Assert.InConclusive scenario) - /// - Inconclusive, - - /// - /// Test had it chance for been executed but was not, as Ignore == true. - /// - Ignored, - - /// - /// Test cannot be executed. - /// - NotRunnable, - - /// - /// Test was executed w/o any issues. - /// - Passed, - - /// - /// The specific test cannot be found. - /// - NotFound, - - /// - /// When test is handed over to runner for execution, it goes into progress state. - /// It is added so that the right status can be set in TestContext. - /// - InProgress, - - /// - /// Test is in an unknown state - /// - Unknown, - } -} diff --git a/source/TestAdapter/ObjectModel/UnitTestResult.cs b/source/TestAdapter/ObjectModel/UnitTestResult.cs deleted file mode 100644 index 72baf01..0000000 --- a/source/TestAdapter/ObjectModel/UnitTestResult.cs +++ /dev/null @@ -1,217 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using Constants = TestAdapter.Constants; - - [Serializable] - public class UnitTestResult - { - /// - /// Initializes a new instance of the class. - /// - internal UnitTestResult() - { - this.DatarowIndex = -1; - } - - /// - /// Initializes a new instance of the class. - /// - /// The test failed exception. - internal UnitTestResult(TestFailedException testFailedException) - : this() - { - this.Outcome = testFailedException.Outcome; - this.ErrorMessage = testFailedException.Message; - - if (testFailedException.StackTraceInformation != null) - { - this.ErrorStackTrace = testFailedException.StackTraceInformation.ErrorStackTrace; - this.ErrorLineNumber = testFailedException.StackTraceInformation.ErrorLineNumber; - this.ErrorFilePath = testFailedException.StackTraceInformation.ErrorFilePath; - this.ErrorColumnNumber = testFailedException.StackTraceInformation.ErrorColumnNumber; - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The outcome. - /// The error message. - internal UnitTestResult(UnitTestOutcome outcome, string errorMessage) - : this() - { - this.Outcome = outcome; - this.ErrorMessage = errorMessage; - } - - /// - /// Gets the display name for the result - /// - public string DisplayName { get; internal set; } - - /// - /// Gets the outcome of the result - /// - public UnitTestOutcome Outcome { get; internal set; } - - /// - /// Gets the errorMessage of the result - /// - public string ErrorMessage { get; internal set; } - - /// - /// Gets the stackTrace of the result - /// - public string ErrorStackTrace { get; internal set; } - - /// - /// Gets the execution id of the result - /// - public Guid ExecutionId { get; internal set; } - - /// - /// Gets the parent execution id of the result - /// - public Guid ParentExecId { get; internal set; } - - /// - /// Gets the inner results count of the result - /// - public int InnerResultsCount { get; internal set; } - - /// - /// Gets the duration of the result - /// - public TimeSpan Duration { get; internal set; } - - /// - /// Gets the standard output of the result - /// - public string StandardOut { get; internal set; } - - /// - /// Gets the Standard Error of the result - /// - public string StandardError { get; internal set; } - - /// - /// Gets the debug trace of the result - /// - public string DebugTrace { get; internal set; } - - /// - /// Gets additional information messages generated by TestContext.WriteLine. - /// - public string TestContextMessages { get; internal set; } - - /// - /// Gets the source code FilePath where the error was thrown. - /// - public string ErrorFilePath { get; internal set; } - - /// - /// Gets the line number in the source code file where the error was thrown. - /// - public int ErrorLineNumber { get; private set; } - - /// - /// Gets the column number in the source code file where the error was thrown. - /// - public int ErrorColumnNumber { get; private set; } - - /// - /// Gets data row index in data source. Set only for results of individual - /// run of data row of a data driven test. - /// - public int DatarowIndex { get; internal set; } - - /// - /// Gets the result files attached by the test. - /// - public IList ResultFiles { get; internal set; } - - /// - /// Convert parameter unitTestResult to testResult - /// - /// The test Case. - /// The start Time. - /// The end Time. - /// Indication to map inconclusive tests to failed. - /// The . - internal TestResult ToTestResult(TestCase testCase, DateTimeOffset startTime, DateTimeOffset endTime, bool mapInconclusiveToFailed) - { - Debug.Assert(testCase != null, "testCase"); - - //var testResult = new TestResult(testCase) - // { - // DisplayName = this.DisplayName, - // Duration = this.Duration, - // ErrorMessage = this.ErrorMessage, - // ErrorStackTrace = this.ErrorStackTrace, - // Outcome = UnitTestOut - // comeHelper.ToTestOutcome(this.Outcome, mapInconclusiveToFailed), - // StartTime = startTime, - // EndTime = endTime - //}; - - var testResult = new TestResult(testCase); - - testResult.SetPropertyValue(Constants.ExecutionIdProperty, this.ExecutionId); - testResult.SetPropertyValue(Constants.ParentExecIdProperty, this.ParentExecId); - testResult.SetPropertyValue(Constants.InnerResultsCountProperty, this.InnerResultsCount); - - if (!string.IsNullOrEmpty(this.StandardOut)) - { - TestResultMessage message = new TestResultMessage(TestResultMessage.StandardOutCategory, this.StandardOut); - testResult.Messages.Add(message); - } - - if (!string.IsNullOrEmpty(this.StandardError)) - { - TestResultMessage message = new TestResultMessage(TestResultMessage.StandardErrorCategory, this.StandardError); - testResult.Messages.Add(message); - } - - if (!string.IsNullOrEmpty(this.DebugTrace)) - { - string debugTraceMessagesinStdOut = string.Format(CultureInfo.InvariantCulture, "\n\n{0}\n{1}", Resource.DebugTraceBanner, this.DebugTrace); - TestResultMessage debugTraceMessage = new TestResultMessage(TestResultMessage.StandardOutCategory, debugTraceMessagesinStdOut); - testResult.Messages.Add(debugTraceMessage); - } - - if (!string.IsNullOrEmpty(this.TestContextMessages)) - { - string testContextMessagesInStdOut = string.Format(CultureInfo.InvariantCulture, "\n\n{0}\n{1}", Resource.TestContextMessageBanner, this.TestContextMessages); - TestResultMessage testContextMessage = new TestResultMessage(TestResultMessage.StandardOutCategory, testContextMessagesInStdOut); - testResult.Messages.Add(testContextMessage); - } - - if (this.ResultFiles != null && this.ResultFiles.Count > 0) - { - AttachmentSet attachmentSet = new AttachmentSet(Constants.ExecutorUri, Resource.AttachmentSetDisplayName); - foreach (var resultFile in this.ResultFiles) - { - string pathToResultFile = PlatformServiceProvider.Instance.FileOperations.GetFullFilePath(resultFile); - UriDataAttachment attachment = new UriDataAttachment(new Uri(pathToResultFile), resultFile); - attachmentSet.Attachments.Add(attachment); - } - - testResult.Attachments.Add(attachmentSet); - } - - return testResult; - } - } -} diff --git a/source/TestAdapter/PlatformServiceProvider.cs b/source/TestAdapter/PlatformServiceProvider.cs deleted file mode 100644 index 66c8342..0000000 --- a/source/TestAdapter/PlatformServiceProvider.cs +++ /dev/null @@ -1,223 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System.Collections.Generic; - using System.IO; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices; - using nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices.Interface; - - /// - /// The main service provider class that exposes all the platform services available. - /// - internal class PlatformServiceProvider : IPlatformServiceProvider - { - private static IPlatformServiceProvider instance; - private ITestSource testSource; - private IFileOperations fileOperations; - private IAdapterTraceLogger traceLogger; - private ITestDeployment testDeployment; - private ISettingsProvider settingsProvider; - //private ITestDataSource testDataSource; - //private IThreadOperations threadOperations; - private IReflectionOperations reflectionOperations; - - /// - /// Initializes a new instance of the class - a singleton. - /// - private PlatformServiceProvider() - { - } - - /// - /// Gets an instance to the platform service validator for test sources. - /// - public ITestSource TestSource - { - get - { - return this.testSource ?? (this.testSource = new TestSource()); - } - } - - ///// - ///// Gets an instance to the platform service validator for data sources for tests. - ///// - //public ITestDataSource TestDataSource - //{ - // get - // { - // return this.testDataSource ?? (this.testDataSource = new TestDataSource()); - // } - //} - - /// - /// Gets an instance to the platform service for file operations. - /// - public IFileOperations FileOperations - { - get - { - return this.fileOperations ?? (this.fileOperations = new FileOperations()); - } - } - - /// - /// Gets an instance to the platform service for trace logging. - /// - public IAdapterTraceLogger AdapterTraceLogger - { - get - { - return this.traceLogger ?? (this.traceLogger = new AdapterTraceLogger()); - } - } - - /// - /// Gets an instance of the test deployment service. - /// - public ITestDeployment TestDeployment - { - get - { - return this.testDeployment ?? (this.testDeployment = new TestDeployment()); - } - } - - /// - /// Gets an instance to the platform service for a Settings Provider. - /// - public ISettingsProvider SettingsProvider - { - get - { - return this.settingsProvider ?? (this.settingsProvider = new MSTestSettingsProvider()); - } - } - - ///// - ///// Gets an instance to the platform service for thread operations. - ///// - //public IThreadOperations ThreadOperations - //{ - // get - // { - // return this.threadOperations ?? (this.threadOperations = new ThreadOperations()); - // } - //} - - /// - /// Gets an instance to the platform service for reflection operations specific to a platform. - /// - public IReflectionOperations ReflectionOperations - { - get - { - return this.reflectionOperations ?? (this.reflectionOperations = new ReflectionOperations()); - } - } - - /// - /// Gets or sets the instance for the platform service. - /// - internal static IPlatformServiceProvider Instance - { - get - { - return instance ?? (instance = new PlatformServiceProvider()); - } - - set - { - instance = value; - } - } - - /// - /// Creates an instance to the platform service for a test source host. - /// - /// - /// The source. - /// - /// - /// The run Settings for the session. - /// - /// - /// The handle to the the test platform. - /// - /// - /// Returns the host for the source provided. - /// - public ITestSourceHost CreateTestSourceHost( - string source, - Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter.IRunSettings runSettings, - Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter.IFrameworkHandle frameworkHandle) - { - var testSourceHost = new TestSourceHost(source, runSettings, frameworkHandle); - testSourceHost.SetupHost(); - - return testSourceHost; - } - - /// - /// Gets an instance to the platform service listener who monitors trace and debug output - /// on provided text writer. - /// - /// - /// The text Writer. - /// - /// - /// The . - /// - public ITraceListener GetTraceListener(TextWriter textWriter) - { - return new TraceListenerWrapper(textWriter); - } - - /// - /// Gets an instance to the platform service trace-listener manager which updates the output/error streams - /// with redirected streams and performs operations on the listener provided as argument. - /// - /// - /// The redirected output stream writer. - /// - /// - /// The redirected error stream writer. - /// - /// - /// The manager for trace listeners. - /// - public ITraceListenerManager GetTraceListenerManager(TextWriter outputWriter, TextWriter errorWriter) - { - return new TraceListenerManager(outputWriter, errorWriter); - } - - /// - /// Gets the TestContext object for a platform. - /// - /// - /// The test method. - /// - /// - /// The writer instance for logging. - /// - /// - /// The default set of properties the test context needs to be filled with. - /// - /// - /// The instance. - /// - /// - /// This was required for compatibility reasons since the TestContext object that the V1 adapter had for desktop is not .Net Core compliant. - /// - public ITestContext GetTestContext(ObjectModel.ITestMethod testMethod, StringWriter writer, IDictionary properties) - { - return new TestContextImplementation(testMethod, writer, properties); - } - } -} diff --git a/source/TestAdapter/Properties/AssemblyInfo.cs b/source/TestAdapter/Properties/AssemblyInfo.cs deleted file mode 100644 index 0320b72..0000000 --- a/source/TestAdapter/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("nanoFramework.TestPlatform.MSTest.TestAdapter")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("nanoFramework TestPlatform TestAdapter")] -[assembly: AssemblyCopyright("Copyright © nanoFrameowrk Project contributors 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/source/TestAdapter/Resources/Resource.Designer.cs b/source/TestAdapter/Resources/Resource.Designer.cs deleted file mode 100644 index 2cf3eed..0000000 --- a/source/TestAdapter/Resources/Resource.Designer.cs +++ /dev/null @@ -1,1353 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resource { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resource() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("nanoFramework.TestPlatform.MSTest.TestAdapter.Resources.Resource", typeof(Resource).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Access string has invalid syntax.. - /// - internal static string AccessStringInvalidSyntax { - get { - return ResourceManager.GetString("AccessStringInvalidSyntax", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The expected collection contains {1} occurrence(s) of <{2}>. The actual collection contains {3} occurrence(s). {0}. - /// - internal static string ActualHasMismatchedElements { - get { - return ResourceManager.GetString("ActualHasMismatchedElements", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Duplicate item found:<{1}>. {0}. - /// - internal static string AllItemsAreUniqueFailMsg { - get { - return ResourceManager.GetString("AllItemsAreUniqueFailMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expected:<{1}>. Case is different for actual value:<{2}>. {0}. - /// - internal static string AreEqualCaseFailMsg { - get { - return ResourceManager.GetString("AreEqualCaseFailMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expected a difference no greater than <{3}> between expected value <{1}> and actual value <{2}>. {0}. - /// - internal static string AreEqualDeltaFailMsg { - get { - return ResourceManager.GetString("AreEqualDeltaFailMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expected:<{1} ({2})>. Actual:<{3} ({4})>. {0}. - /// - internal static string AreEqualDifferentTypesFailMsg { - get { - return ResourceManager.GetString("AreEqualDifferentTypesFailMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expected:<{1}>. Actual:<{2}>. {0}. - /// - internal static string AreEqualFailMsg { - get { - return ResourceManager.GetString("AreEqualFailMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expected a difference greater than <{3}> between expected value <{1}> and actual value <{2}>. {0}. - /// - internal static string AreNotEqualDeltaFailMsg { - get { - return ResourceManager.GetString("AreNotEqualDeltaFailMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expected any value except:<{1}>. Actual:<{2}>. {0}. - /// - internal static string AreNotEqualFailMsg { - get { - return ResourceManager.GetString("AreNotEqualFailMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Do not pass value types to AreSame(). Values converted to Object will never be the same. Consider using AreEqual(). {0}. - /// - internal static string AreSameGivenValues { - get { - return ResourceManager.GetString("AreSameGivenValues", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} failed. {1}. - /// - internal static string AssertionFailed { - get { - return ResourceManager.GetString("AssertionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to async TestMethod with UITestMethodAttribute are not supported. Either remove async or use TestMethodAttribute.. - /// - internal static string AsyncUITestMethodNotSupported { - get { - return ResourceManager.GetString("AsyncUITestMethodNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MSTestAdapterV2. - /// - internal static string AttachmentSetDisplayName { - get { - return ResourceManager.GetString("AttachmentSetDisplayName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Both collections are empty. {0}. - /// - internal static string BothCollectionsEmpty { - get { - return ResourceManager.GetString("BothCollectionsEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Both collection contain same elements.. - /// - internal static string BothCollectionsSameElements { - get { - return ResourceManager.GetString("BothCollectionsSameElements", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Both collection references point to the same collection object. {0}. - /// - internal static string BothCollectionsSameReference { - get { - return ResourceManager.GetString("BothCollectionsSameReference", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Both collections contain the same elements. {0}. - /// - internal static string BothSameElements { - get { - return ResourceManager.GetString("BothSameElements", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not find file '{0}'.. - /// - internal static string CannotFindFile { - get { - return ResourceManager.GetString("CannotFindFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}({1}). - /// - internal static string CollectionEqualReason { - get { - return ResourceManager.GetString("CollectionEqualReason", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter should not be null or empty.. - /// - internal static string Common_CannotBeNullOrEmpty { - get { - return ResourceManager.GetString("Common_CannotBeNullOrEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter should not be null or empty.. - /// - internal static string Common_CannotBeNullOrEmpty1 { - get { - return ResourceManager.GetString("Common_CannotBeNullOrEmpty1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter must be greater than zero.. - /// - internal static string Common_MustBeGreaterThanZero { - get { - return ResourceManager.GetString("Common_MustBeGreaterThanZero", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to (null). - /// - internal static string Common_NullInMessages { - get { - return ResourceManager.GetString("Common_NullInMessages", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to (object). - /// - internal static string Common_ObjectString { - get { - return ResourceManager.GetString("Common_ObjectString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to String '{0}' does not contain string '{1}'. {2}.. - /// - internal static string ContainsFail { - get { - return ResourceManager.GetString("ContainsFail", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MSTestAdapter failed to discover tests in class '{0}' of assembly '{1}' because {2}.. - /// - internal static string CouldNotInspectTypeDuringDiscovery { - get { - return ResourceManager.GetString("CouldNotInspectTypeDuringDiscovery", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MSTestAdapter failed to discover tests in class '{0}' of assembly '{1}'. Reason {2}.. - /// - internal static string CouldNotInspectTypeDuringDiscovery1 { - get { - return ResourceManager.GetString("CouldNotInspectTypeDuringDiscovery1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} (Data Row {1}). - /// - internal static string DataDrivenResultDisplayName { - get { - return ResourceManager.GetString("DataDrivenResultDisplayName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ({1}). - /// - internal static string DataDrivenResultDisplayName1 { - get { - return ResourceManager.GetString("DataDrivenResultDisplayName1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Debug Trace:. - /// - internal static string DebugTraceBanner { - get { - return ResourceManager.GetString("DebugTraceBanner", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test Run deployment issue: Bad deployment item: '{0}': output directory '{1}' specifies the item to be deployed outside deployment root directory which is not allowed.. - /// - internal static string DeploymentErrorBadDeploymentItem { - get { - return ResourceManager.GetString("DeploymentErrorBadDeploymentItem", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test Run deployment issue: Failed to access output directory '{1}' specified by deployment item '{0}', the item will not be deployed: {2}: {3}. - /// - internal static string DeploymentErrorFailedToAccesOutputDirectory { - get { - return ResourceManager.GetString("DeploymentErrorFailedToAccesOutputDirectory", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test Run deployment issue: Failed to access the file '{0}': {1}: {2}. - /// - internal static string DeploymentErrorFailedToAccessFile { - get { - return ResourceManager.GetString("DeploymentErrorFailedToAccessFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test Run deployment issue: Failed to copy file '{0}' to '{1}': {2}: {3}. - /// - internal static string DeploymentErrorFailedToCopyWithOverwrite { - get { - return ResourceManager.GetString("DeploymentErrorFailedToCopyWithOverwrite", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test Run deployment issue: Failed to deploy dependencies for test storage '{0}': {1}. - /// - internal static string DeploymentErrorFailedToDeployDependencies { - get { - return ResourceManager.GetString("DeploymentErrorFailedToDeployDependencies", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test Run deployment issue: Failed to get the file for {0}: {1}: {2}. - /// - internal static string DeploymentErrorFailedToGetFileForDeploymentItem { - get { - return ResourceManager.GetString("DeploymentErrorFailedToGetFileForDeploymentItem", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test Run deployment issue: an error occurred while getting satellite assemblies for {0}: {1}: {2}. - /// - internal static string DeploymentErrorGettingSatellite { - get { - return ResourceManager.GetString("DeploymentErrorGettingSatellite", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to deployment item '{0}'. - /// - internal static string DeploymentItem { - get { - return ResourceManager.GetString("DeploymentItem", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid deployment item: the specified path '{0}' or output directory '{1}' contains illegal characters.. - /// - internal static string DeploymentItemContainsInvalidCharacters { - get { - return ResourceManager.GetString("DeploymentItemContainsInvalidCharacters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid deployment item: the output directory cannot be null.. - /// - internal static string DeploymentItemOutputDirectoryCannotBeNull { - get { - return ResourceManager.GetString("DeploymentItemOutputDirectoryCannotBeNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid deployment item: the specified output directory '{0}' is not relative.. - /// - internal static string DeploymentItemOutputDirectoryMustBeRelative { - get { - return ResourceManager.GetString("DeploymentItemOutputDirectoryMustBeRelative", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid deployment item: the path must contain at least one character.. - /// - internal static string DeploymentItemPathCannotBeNullOrEmpty { - get { - return ResourceManager.GetString("DeploymentItemPathCannotBeNullOrEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to deployment item '{0}' (output directory '{1}'). - /// - internal static string DeploymentItemWithOutputDirectory { - get { - return ResourceManager.GetString("DeploymentItemWithOutputDirectory", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [MSTest][Discovery][{0}] {1}. - /// - internal static string DiscoveryWarning { - get { - return ResourceManager.GetString("DiscoveryWarning", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Assert.Equals should not be used for Assertions. Please use Assert.AreEqual & overloads instead.. - /// - internal static string DoNotUseAssertEquals { - get { - return ResourceManager.GetString("DoNotUseAssertEquals", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Method {0} must match the expected signature: public static {1} {0}({2}).. - /// - internal static string DynamicDataDisplayName { - get { - return ResourceManager.GetString("DynamicDataDisplayName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Property or method {0} on {1} does not return IEnumerable<object[]>.. - /// - internal static string DynamicDataIEnumerableNull { - get { - return ResourceManager.GetString("DynamicDataIEnumerableNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Value returned by property or method {0} shouldn't be null.. - /// - internal static string DynamicDataValueNull { - get { - return ResourceManager.GetString("DynamicDataValueNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The number of elements in the collections do not match. Expected:<{1}>. Actual:<{2}>.{0}. - /// - internal static string ElementNumbersDontMatch { - get { - return ResourceManager.GetString("ElementNumbersDontMatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Element at index {0} do not match.. - /// - internal static string ElementsAtIndexDontMatch { - get { - return ResourceManager.GetString("ElementsAtIndexDontMatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Element at index {1} is not of expected type. Expected type:<{2}>. Actual type:<{3}>.{0}. - /// - internal static string ElementTypesAtIndexDontMatch { - get { - return ResourceManager.GetString("ElementTypesAtIndexDontMatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Element at index {1} is (null). Expected type:<{2}>.{0}. - /// - internal static string ElementTypesAtIndexDontMatch2 { - get { - return ResourceManager.GetString("ElementTypesAtIndexDontMatch2", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to String '{0}' does not end with string '{1}'. {2}.. - /// - internal static string EndsWithFail { - get { - return ResourceManager.GetString("EndsWithFail", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}: {1}. - /// - internal static string EnumeratorLoadTypeErrorFormat { - get { - return ResourceManager.GetString("EnumeratorLoadTypeErrorFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid argument- EqualsTester can't use nulls.. - /// - internal static string EqualsTesterInvalidArgs { - get { - return ResourceManager.GetString("EqualsTesterInvalidArgs", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot convert object of type {0} to {1}.. - /// - internal static string ErrorInvalidCast { - get { - return ResourceManager.GetString("ErrorInvalidCast", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test '{0}' exceeded execution timeout period.. - /// - internal static string Execution_Test_Timeout { - get { - return ResourceManager.GetString("Execution_Test_Timeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to Create msdia Session COM HResult '{0}'.. - /// - internal static string FailedToCreateDiaSession { - get { - return ResourceManager.GetString("FailedToCreateDiaSession", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to load msdia. - /// - internal static string FailedToLoadMsDia { - get { - return ResourceManager.GetString("FailedToLoadMsDia", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The internal object referenced is no longer valid.. - /// - internal static string InternalObjectNotValid { - get { - return ResourceManager.GetString("InternalObjectNotValid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid value '{0}' specified for 'Scope'. Supported scopes are {1}.. - /// - internal static string InvalidParallelScopeValue { - get { - return ResourceManager.GetString("InvalidParallelScopeValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid value '{0}' specified for 'Workers'. The value should be a non-negative integer.. - /// - internal static string InvalidParallelWorkersValue { - get { - return ResourceManager.GetString("InvalidParallelWorkersValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter '{0}' is invalid. {1}.. - /// - internal static string InvalidParameterToAssert { - get { - return ResourceManager.GetString("InvalidParameterToAssert", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The property {0} has type {1}; expected type {2}.. - /// - internal static string InvalidPropertyType { - get { - return ResourceManager.GetString("InvalidPropertyType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid settings '{0}'. Unexpected XmlAttribute: '{1}'.. - /// - internal static string InvalidSettingsXmlAttribute { - get { - return ResourceManager.GetString("InvalidSettingsXmlAttribute", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid settings '{0}'. Unexpected XmlElement: '{1}'.. - /// - internal static string InvalidSettingsXmlElement { - get { - return ResourceManager.GetString("InvalidSettingsXmlElement", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MSTestAdapter encountered an unexpected element '{0}' in its settings '{1}'. Remove this element and try again.. - /// - internal static string InvalidSettingsXmlElement1 { - get { - return ResourceManager.GetString("InvalidSettingsXmlElement1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} Expected type:<{1}>. Actual type:<{2}>.. - /// - internal static string IsInstanceOfFailMsg { - get { - return ResourceManager.GetString("IsInstanceOfFailMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to String '{0}' does not match pattern '{1}'. {2}.. - /// - internal static string IsMatchFail { - get { - return ResourceManager.GetString("IsMatchFail", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wrong Type:<{1}>. Actual type:<{2}>. {0}. - /// - internal static string IsNotInstanceOfFailMsg { - get { - return ResourceManager.GetString("IsNotInstanceOfFailMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to String '{0}' matches pattern '{1}'. {2}.. - /// - internal static string IsNotMatchFail { - get { - return ResourceManager.GetString("IsNotMatchFail", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Warning : A testsettings file or a vsmdi file is not supported with the MSTest V2 Adapter.. - /// - internal static string LegacyScenariosNotSupportedWarning { - get { - return ResourceManager.GetString("LegacyScenariosNotSupportedWarning", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test Run deployment issue: The assembly or module '{0}' was not found. Reason: {1}. - /// - internal static string MissingDeploymentDependency { - get { - return ResourceManager.GetString("MissingDeploymentDependency", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test Run deployment issue: The assembly or module '{0}' was not found.. - /// - internal static string MissingDeploymentDependencyWithoutReason { - get { - return ResourceManager.GetString("MissingDeploymentDependencyWithoutReason", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No test data source specified. Atleast one TestDataSource is required with DataTestMethodAttribute.. - /// - internal static string NoDataRow { - get { - return ResourceManager.GetString("NoDataRow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No exception thrown. {1} exception was expected. {0}. - /// - internal static string NoExceptionThrown { - get { - return ResourceManager.GetString("NoExceptionThrown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter '{0}' is invalid. The value cannot be null. {1}.. - /// - internal static string NullParameterToAssert { - get { - return ResourceManager.GetString("NullParameterToAssert", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Different number of elements.. - /// - internal static string NumberOfElementsDiff { - get { - return ResourceManager.GetString("NumberOfElementsDiff", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to - /// The constructor with the specified signature could not be found. You might need to regenerate your private accessor, - /// or the member may be private and defined on a base class. If the latter is true, you need to pass the type - /// that defines the member into PrivateObject's constructor. - /// . - /// - internal static string PrivateAccessorConstructorNotFound { - get { - return ResourceManager.GetString("PrivateAccessorConstructorNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to - /// The member specified ({0}) could not be found. You might need to regenerate your private accessor, - /// or the member may be private and defined on a base class. If the latter is true, you need to pass the type - /// that defines the member into PrivateObject's constructor. - /// . - /// - internal static string PrivateAccessorMemberNotFound { - get { - return ResourceManager.GetString("PrivateAccessorMemberNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Running tests in any of the provided sources is not supported for the selected platform. - /// - internal static string SourcesNotSupported { - get { - return ResourceManager.GetString("SourcesNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to String '{0}' does not start with string '{1}'. {2}.. - /// - internal static string StartsWithFail { - get { - return ResourceManager.GetString("StartsWithFail", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to discover tests from assembly {0}. Reason:{1}. - /// - internal static string TestAssembly_AssemblyDiscoveryFailure { - get { - return ResourceManager.GetString("TestAssembly_AssemblyDiscoveryFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to File does not exist: {0}. - /// - internal static string TestAssembly_FileDoesNotExist { - get { - return ResourceManager.GetString("TestAssembly_FileDoesNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to TestContext cannot be Null.. - /// - internal static string TestContextIsNull { - get { - return ResourceManager.GetString("TestContextIsNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to TestContext Messages:. - /// - internal static string TestContextMessageBanner { - get { - return ResourceManager.GetString("TestContextMessageBanner", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test method {0} was not found.. - /// - internal static string TestNotFound { - get { - return ResourceManager.GetString("TestNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MSTest Executor: Test Parallelization enabled for {0} (Workers: {1}, Scope: {2}).. - /// - internal static string TestParallelizationBanner { - get { - return ResourceManager.GetString("TestParallelizationBanner", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}_{1} {2}. - /// - internal static string TestRunName { - get { - return ResourceManager.GetString("TestRunName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to load types from the test source '{0}'. Some or all of the tests in this source may not be discovered.. - /// - internal static string TypeLoadFailed { - get { - return ResourceManager.GetString("TypeLoadFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Assembly Cleanup method {0}.{1} failed. Error Message: {2}. StackTrace: {3}. - /// - internal static string UTA_AssemblyCleanupMethodWasUnsuccesful { - get { - return ResourceManager.GetString("UTA_AssemblyCleanupMethodWasUnsuccesful", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Assembly Initialization method {0}.{1} threw exception. {2}: {3}. Aborting test execution.. - /// - internal static string UTA_AssemblyInitMethodThrows { - get { - return ResourceManager.GetString("UTA_AssemblyInitMethodThrows", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Class Cleanup method {0}.{1} failed. Error Message: {2}. Stack Trace: {3}. - /// - internal static string UTA_ClassCleanupMethodWasUnsuccesful { - get { - return ResourceManager.GetString("UTA_ClassCleanupMethodWasUnsuccesful", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Class Initialization method {0}.{1} threw exception. {2}: {3}.. - /// - internal static string UTA_ClassInitMethodThrows { - get { - return ResourceManager.GetString("UTA_ClassInitMethodThrows", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Method {0}.{1} has wrong signature. The method must be static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be Task.. - /// - internal static string UTA_ClassOrAssemblyCleanupMethodHasWrongSignature { - get { - return ResourceManager.GetString("UTA_ClassOrAssemblyCleanupMethodHasWrongSignature", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Method {0}.{1} has wrong signature. The method must be static, public, does not return a value and should take a single parameter of type TestContext. Additionally, if you are using async-await in method then return-type must be Task.. - /// - internal static string UTA_ClassOrAssemblyInitializeMethodHasWrongSignature { - get { - return ResourceManager.GetString("UTA_ClassOrAssemblyInitializeMethodHasWrongSignature", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to TestCleanup method {0}.{1} threw exception. {2}: {3}.. - /// - internal static string UTA_CleanupMethodThrows { - get { - return ResourceManager.GetString("UTA_CleanupMethodThrows", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error calling Test Cleanup method for test class {0}: {1}. - /// - internal static string UTA_CleanupMethodThrowsGeneralError { - get { - return ResourceManager.GetString("UTA_CleanupMethodThrowsGeneralError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to TestCleanup Stack Trace. - /// - internal static string UTA_CleanupStackTrace { - get { - return ResourceManager.GetString("UTA_CleanupStackTrace", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Data source '{0}' cannot be found in the test configuration settings. - /// - internal static string UTA_DataSourceConfigurationSectionMissing { - get { - return ResourceManager.GetString("UTA_DataSourceConfigurationSectionMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to --- End of inner exception stack trace ---. - /// - internal static string UTA_EndOfInnerExceptionTrace { - get { - return ResourceManager.GetString("UTA_EndOfInnerExceptionTrace", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The unit test adapter failed to connect to the data source or to read the data. For more information on troubleshooting this error, see "Troubleshooting Data-Driven Unit Tests" (http://go.microsoft.com/fwlink/?LinkId=62412) in the MSDN Library. Error details: {0}. - /// - internal static string UTA_ErrorDataConnectionFailed { - get { - return ResourceManager.GetString("UTA_ErrorDataConnectionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA015: A generic method cannot be a test method. {0}.{1} has invalid signature. - /// - internal static string UTA_ErrorGenericTestMethod { - get { - return ResourceManager.GetString("UTA_ErrorGenericTestMethod", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA007: Method {1} defined in class {0} does not have correct signature. Test method marked with the [TestMethod] attribute must be non-static, public, return-type as void and should not take any parameter. Example: public void Test.Class1.Test(). Additionally, if you are using async-await in test method then return-type must be Task. Example: public async Task Test.Class1.Test2(). - /// - internal static string UTA_ErrorIncorrectTestMethodSignature { - get { - return ResourceManager.GetString("UTA_ErrorIncorrectTestMethodSignature", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA031: class {0} does not have valid TestContext property. TestContext must be of type TestContext, must be non-static, public and must not be read-only. For example: public TestContext TestContext.. - /// - internal static string UTA_ErrorInValidTestContextSignature { - get { - return ResourceManager.GetString("UTA_ErrorInValidTestContextSignature", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA054: {0}.{1} has invalid Timeout attribute. The timeout must be a valid integer value and cannot be less than 0.. - /// - internal static string UTA_ErrorInvalidTimeout { - get { - return ResourceManager.GetString("UTA_ErrorInvalidTimeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA014: {0}: Cannot define more than one method with the AssemblyCleanup attribute inside an assembly.. - /// - internal static string UTA_ErrorMultiAssemblyClean { - get { - return ResourceManager.GetString("UTA_ErrorMultiAssemblyClean", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA013: {0}: Cannot define more than one method with the AssemblyInitialize attribute inside an assembly.. - /// - internal static string UTA_ErrorMultiAssemblyInit { - get { - return ResourceManager.GetString("UTA_ErrorMultiAssemblyInit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA026: {0}: Cannot define more than one method with the ClassCleanup attribute inside a class.. - /// - internal static string UTA_ErrorMultiClassClean { - get { - return ResourceManager.GetString("UTA_ErrorMultiClassClean", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA025: {0}: Cannot define more than one method with the ClassInitialize attribute inside a class.. - /// - internal static string UTA_ErrorMultiClassInit { - get { - return ResourceManager.GetString("UTA_ErrorMultiClassInit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA024: {0}: Cannot define more than one method with the TestCleanup attribute.. - /// - internal static string UTA_ErrorMultiClean { - get { - return ResourceManager.GetString("UTA_ErrorMultiClean", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA018: {0}: Cannot define more than one method with the TestInitialize attribute.. - /// - internal static string UTA_ErrorMultiInit { - get { - return ResourceManager.GetString("UTA_ErrorMultiInit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA001: TestClass attribute defined on non-public class {0}. - /// - internal static string UTA_ErrorNonPublicTestClass { - get { - return ResourceManager.GetString("UTA_ErrorNonPublicTestClass", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA023: {0}: Cannot define predefined property {2} on method {1}.. - /// - internal static string UTA_ErrorPredefinedTestProperty { - get { - return ResourceManager.GetString("UTA_ErrorPredefinedTestProperty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA022: {0}.{1}: The custom property "{2}" is already defined. Using "{3}" as value.. - /// - internal static string UTA_ErrorTestPropertyAlreadyDefined { - get { - return ResourceManager.GetString("UTA_ErrorTestPropertyAlreadyDefined", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UTA021: {0}: Null or empty custom property defined on method {1}. The custom property must have a valid name.. - /// - internal static string UTA_ErrorTestPropertyNullOrEmpty { - get { - return ResourceManager.GetString("UTA_ErrorTestPropertyNullOrEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Exception thrown while executing test. If using extension of TestMethodAttribute then please contact vendor. Error message: {0}. - /// - internal static string UTA_ExecuteThrewException { - get { - return ResourceManager.GetString("UTA_ExecuteThrewException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The ExpectedException attribute defined on test method {0}.{1} threw an exception during construction. - ///{2}. - /// - internal static string UTA_ExpectedExceptionAttributeConstructionException { - get { - return ResourceManager.GetString("UTA_ExpectedExceptionAttributeConstructionException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to obtain the exception thrown by test method {0}.{1}.. - /// - internal static string UTA_FailedToGetTestMethodException { - get { - return ResourceManager.GetString("UTA_FailedToGetTestMethodException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initialization method {0}.{1} threw exception. {2}.. - /// - internal static string UTA_InitMethodThrows { - get { - return ResourceManager.GetString("UTA_InitMethodThrows", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to create instance of class {0}. Error: {1}.. - /// - internal static string UTA_InstanceCreationError { - get { - return ResourceManager.GetString("UTA_InstanceCreationError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Method {0}.{1} does not exist.. - /// - internal static string UTA_MethodDoesNotExists { - get { - return ResourceManager.GetString("UTA_MethodDoesNotExists", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The test method {0}.{1} has multiple attributes derived from ExpectedExceptionBaseAttribute defined on it. Only one such attribute is allowed.. - /// - internal static string UTA_MultipleExpectedExceptionsOnTestMethod { - get { - return ResourceManager.GetString("UTA_MultipleExpectedExceptionsOnTestMethod", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to get default constructor for class {0}.. - /// - internal static string UTA_NoDefaultConstructor { - get { - return ResourceManager.GetString("UTA_NoDefaultConstructor", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error in executing test. No result returned by extension. If using extension of TestMethodAttribute then please contact vendor.. - /// - internal static string UTA_NoTestResult { - get { - return ResourceManager.GetString("UTA_NoTestResult", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to find property {0}.TestContext. Error:{1}.. - /// - internal static string UTA_TestContextLoadError { - get { - return ResourceManager.GetString("UTA_TestContextLoadError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to set TestContext property for the class {0}. Error: {1}.. - /// - internal static string UTA_TestContextSetError { - get { - return ResourceManager.GetString("UTA_TestContextSetError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0}.TestContext has incorrect type.. - /// - internal static string UTA_TestContextTypeMismatchLoadError { - get { - return ResourceManager.GetString("UTA_TestContextTypeMismatchLoadError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be Task.. - /// - internal static string UTA_TestInitializeAndCleanupMethodHasWrongSignature { - get { - return ResourceManager.GetString("UTA_TestInitializeAndCleanupMethodHasWrongSignature", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Only data driven test methods can have parameters. Did you intend to use [DataRow] or [DynamicData]?. - /// - internal static string UTA_TestMethodExpectedParameters { - get { - return ResourceManager.GetString("UTA_TestMethodExpectedParameters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test method {0}.{1} threw exception: - ///{2}. - /// - internal static string UTA_TestMethodThrows { - get { - return ResourceManager.GetString("UTA_TestMethodThrows", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to get type {0}. Error: {1}.. - /// - internal static string UTA_TypeLoadError { - get { - return ResourceManager.GetString("UTA_TypeLoadError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}. If you are using UI objects in test consider using [UITestMethod] attribute instead of [TestMethod] to execute test in UI thread.. - /// - internal static string UTA_WrongThread { - get { - return ResourceManager.GetString("UTA_WrongThread", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The expected exception type must be System.Exception or a type derived from System.Exception.. - /// - internal static string UTF_ExpectedExceptionTypeMustDeriveFromException { - get { - return ResourceManager.GetString("UTF_ExpectedExceptionTypeMustDeriveFromException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to (Failed to get the message for an exception of type {0} due to an exception.). - /// - internal static string UTF_FailedToGetExceptionMessage { - get { - return ResourceManager.GetString("UTF_FailedToGetExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to (Failed to get the message for an exception of type {0} due to an exception.). - /// - internal static string UTF_FailedToGetExceptionMessage1 { - get { - return ResourceManager.GetString("UTF_FailedToGetExceptionMessage1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test method did not throw expected exception {0}. {1}. - /// - internal static string UTF_TestMethodNoException { - get { - return ResourceManager.GetString("UTF_TestMethodNoException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test method did not throw an exception. An exception was expected by attribute {0} defined on the test method.. - /// - internal static string UTF_TestMethodNoExceptionDefault { - get { - return ResourceManager.GetString("UTF_TestMethodNoExceptionDefault", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test method threw exception {0}, but exception {1} was expected. Exception message: {2}. - /// - internal static string UTF_TestMethodWrongException { - get { - return ResourceManager.GetString("UTF_TestMethodWrongException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test method threw exception {0}, but exception {1} or a type derived from it was expected. Exception message: {2}. - /// - internal static string UTF_TestMethodWrongExceptionDerivedAllowed { - get { - return ResourceManager.GetString("UTF_TestMethodWrongExceptionDerivedAllowed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Threw exception {2}, but exception {1} was expected. {0} - ///Exception Message: {3} - ///Stack Trace: {4}. - /// - internal static string WrongExceptionThrown { - get { - return ResourceManager.GetString("WrongExceptionThrown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wrong number of objects for permutation. Should be greater than zero.. - /// - internal static string WrongNumberOfObjects { - get { - return ResourceManager.GetString("WrongNumberOfObjects", resourceCulture); - } - } - } -} diff --git a/source/TestAdapter/Resources/Resource.resx b/source/TestAdapter/Resources/Resource.resx deleted file mode 100644 index f3bbe66..0000000 --- a/source/TestAdapter/Resources/Resource.resx +++ /dev/null @@ -1,560 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - MSTestAdapterV2 - - - The parameter should not be null or empty. - - - The parameter must be greater than zero. - - - MSTestAdapter failed to discover tests in class '{0}' of assembly '{1}' because {2}. - - - MSTestAdapter failed to discover tests in class '{0}' of assembly '{1}'. Reason {2}. - - - {0} (Data Row {1}) - - - Debug Trace: - - - [MSTest][Discovery][{0}] {1} - - - {0}: {1} - - - Test '{0}' exceeded execution timeout period. - - - Invalid value '{0}' specified for 'Scope'. Supported scopes are {1}. - 'Scope' is a setting name that shouldn't be localized. - - - Invalid value '{0}' specified for 'Workers'. The value should be a non-negative integer. - `Workers` is a setting name that shouldn't be localized. - - - Invalid settings '{0}'. Unexpected XmlAttribute: '{1}'. - - - Invalid settings '{0}'. Unexpected XmlElement: '{1}'. - - - Warning : A testsettings file or a vsmdi file is not supported with the MSTest V2 Adapter. - - - Running tests in any of the provided sources is not supported for the selected platform - - - Failed to discover tests from assembly {0}. Reason:{1} - - - File does not exist: {0} - - - TestContext cannot be Null. - - - TestContext Messages: - - - Test method {0} was not found. - - - MSTest Executor: Test Parallelization enabled for {0} (Workers: {1}, Scope: {2}). - - - Unable to load types from the test source '{0}'. Some or all of the tests in this source may not be discovered. - - - Assembly Cleanup method {0}.{1} failed. Error Message: {2}. StackTrace: {3} - - - Assembly Initialization method {0}.{1} threw exception. {2}: {3}. Aborting test execution. - - - Class Cleanup method {0}.{1} failed. Error Message: {2}. Stack Trace: {3} - - - Class Initialization method {0}.{1} threw exception. {2}: {3}. - - - Method {0}.{1} has wrong signature. The method must be static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be Task. - - - Method {0}.{1} has wrong signature. The method must be static, public, does not return a value and should take a single parameter of type TestContext. Additionally, if you are using async-await in method then return-type must be Task. - - - TestCleanup method {0}.{1} threw exception. {2}: {3}. - - - Error calling Test Cleanup method for test class {0}: {1} - - - TestCleanup Stack Trace - - - --- End of inner exception stack trace --- - - - UTA015: A generic method cannot be a test method. {0}.{1} has invalid signature - - - UTA007: Method {1} defined in class {0} does not have correct signature. Test method marked with the [TestMethod] attribute must be non-static, public, return-type as void and should not take any parameter. Example: public void Test.Class1.Test(). Additionally, if you are using async-await in test method then return-type must be Task. Example: public async Task Test.Class1.Test2() - - - UTA031: class {0} does not have valid TestContext property. TestContext must be of type TestContext, must be non-static, public and must not be read-only. For example: public TestContext TestContext. - - - UTA054: {0}.{1} has invalid Timeout attribute. The timeout must be a valid integer value and cannot be less than 0. - - - UTA014: {0}: Cannot define more than one method with the AssemblyCleanup attribute inside an assembly. - - - UTA013: {0}: Cannot define more than one method with the AssemblyInitialize attribute inside an assembly. - - - UTA026: {0}: Cannot define more than one method with the ClassCleanup attribute inside a class. - - - UTA025: {0}: Cannot define more than one method with the ClassInitialize attribute inside a class. - - - UTA024: {0}: Cannot define more than one method with the TestCleanup attribute. - - - UTA018: {0}: Cannot define more than one method with the TestInitialize attribute. - - - UTA001: TestClass attribute defined on non-public class {0} - - - UTA023: {0}: Cannot define predefined property {2} on method {1}. - - - UTA022: {0}.{1}: The custom property "{2}" is already defined. Using "{3}" as value. - - - UTA021: {0}: Null or empty custom property defined on method {1}. The custom property must have a valid name. - - - Exception thrown while executing test. If using extension of TestMethodAttribute then please contact vendor. Error message: {0} - - - The ExpectedException attribute defined on test method {0}.{1} threw an exception during construction. -{2} - - - Failed to obtain the exception thrown by test method {0}.{1}. - - - Initialization method {0}.{1} threw exception. {2}. - - - Unable to create instance of class {0}. Error: {1}. - - - Method {0}.{1} does not exist. - - - The test method {0}.{1} has multiple attributes derived from ExpectedExceptionBaseAttribute defined on it. Only one such attribute is allowed. - - - Unable to get default constructor for class {0}. - - - Error in executing test. No result returned by extension. If using extension of TestMethodAttribute then please contact vendor. - - - Unable to find property {0}.TestContext. Error:{1}. - - - Unable to set TestContext property for the class {0}. Error: {1}. - - - The {0}.TestContext has incorrect type. - - - Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be Task. - - - Only data driven test methods can have parameters. Did you intend to use [DataRow] or [DynamicData]? - - - Test method {0}.{1} threw exception: -{2} - - - Unable to get type {0}. Error: {1}. - - - {0}. If you are using UI objects in test consider using [UITestMethod] attribute instead of [TestMethod] to execute test in UI thread. - - - (Failed to get the message for an exception of type {0} due to an exception.) - - - Access string has invalid syntax. - - - The expected collection contains {1} occurrence(s) of <{2}>. The actual collection contains {3} occurrence(s). {0} - - - Duplicate item found:<{1}>. {0} - - - Expected:<{1}>. Case is different for actual value:<{2}>. {0} - - - Expected a difference no greater than <{3}> between expected value <{1}> and actual value <{2}>. {0} - - - Expected:<{1} ({2})>. Actual:<{3} ({4})>. {0} - - - Expected:<{1}>. Actual:<{2}>. {0} - - - Expected a difference greater than <{3}> between expected value <{1}> and actual value <{2}>. {0} - - - Expected any value except:<{1}>. Actual:<{2}>. {0} - - - Do not pass value types to AreSame(). Values converted to Object will never be the same. Consider using AreEqual(). {0} - - - {0} failed. {1} - - - async TestMethod with UITestMethodAttribute are not supported. Either remove async or use TestMethodAttribute. - - - Both collections are empty. {0} - - - Both collection contain same elements. - - - Both collection references point to the same collection object. {0} - - - Both collections contain the same elements. {0} - - - {0}({1}) - - - (null) - - - (object) - - - String '{0}' does not contain string '{1}'. {2}. - - - {0} ({1}) - - - Assert.Equals should not be used for Assertions. Please use Assert.AreEqual & overloads instead. - - - Method {0} must match the expected signature: public static {1} {0}({2}). - - - Property or method {0} on {1} does not return IEnumerable<object[]>. - - - Value returned by property or method {0} shouldn't be null. - - - The number of elements in the collections do not match. Expected:<{1}>. Actual:<{2}>.{0} - - - Element at index {0} do not match. - - - Element at index {1} is not of expected type. Expected type:<{2}>. Actual type:<{3}>.{0} - - - Element at index {1} is (null). Expected type:<{2}>.{0} - - - String '{0}' does not end with string '{1}'. {2}. - - - Invalid argument- EqualsTester can't use nulls. - - - Cannot convert object of type {0} to {1}. - - - The internal object referenced is no longer valid. - - - The parameter '{0}' is invalid. {1}. - - - The property {0} has type {1}; expected type {2}. - - - {0} Expected type:<{1}>. Actual type:<{2}>. - - - String '{0}' does not match pattern '{1}'. {2}. - - - Wrong Type:<{1}>. Actual type:<{2}>. {0} - - - String '{0}' matches pattern '{1}'. {2}. - - - No test data source specified. Atleast one TestDataSource is required with DataTestMethodAttribute. - - - No exception thrown. {1} exception was expected. {0} - - - The parameter '{0}' is invalid. The value cannot be null. {1}. - - - Different number of elements. - - - - The constructor with the specified signature could not be found. You might need to regenerate your private accessor, - or the member may be private and defined on a base class. If the latter is true, you need to pass the type - that defines the member into PrivateObject's constructor. - - - - - The member specified ({0}) could not be found. You might need to regenerate your private accessor, - or the member may be private and defined on a base class. If the latter is true, you need to pass the type - that defines the member into PrivateObject's constructor. - - - - String '{0}' does not start with string '{1}'. {2}. - - - The expected exception type must be System.Exception or a type derived from System.Exception. - - - (Failed to get the message for an exception of type {0} due to an exception.) - - - Test method did not throw expected exception {0}. {1} - - - Test method did not throw an exception. An exception was expected by attribute {0} defined on the test method. - - - Test method threw exception {0}, but exception {1} was expected. Exception message: {2} - - - Test method threw exception {0}, but exception {1} or a type derived from it was expected. Exception message: {2} - - - Threw exception {2}, but exception {1} was expected. {0} -Exception Message: {3} -Stack Trace: {4} - - - Could not find file '{0}'. - - - The parameter should not be null or empty. - - - Test Run deployment issue: Bad deployment item: '{0}': output directory '{1}' specifies the item to be deployed outside deployment root directory which is not allowed. - - - Test Run deployment issue: Failed to access output directory '{1}' specified by deployment item '{0}', the item will not be deployed: {2}: {3} - - - Test Run deployment issue: Failed to access the file '{0}': {1}: {2} - - - Test Run deployment issue: Failed to copy file '{0}' to '{1}': {2}: {3} - - - Test Run deployment issue: Failed to deploy dependencies for test storage '{0}': {1} - - - Test Run deployment issue: Failed to get the file for {0}: {1}: {2} - - - Test Run deployment issue: an error occurred while getting satellite assemblies for {0}: {1}: {2} - - - deployment item '{0}' - - - Invalid deployment item: the specified path '{0}' or output directory '{1}' contains illegal characters. - - - Invalid deployment item: the output directory cannot be null. - - - Invalid deployment item: the specified output directory '{0}' is not relative. - - - Invalid deployment item: the path must contain at least one character. - - - deployment item '{0}' (output directory '{1}') - - - MSTestAdapter encountered an unexpected element '{0}' in its settings '{1}'. Remove this element and try again. - - - Test Run deployment issue: The assembly or module '{0}' was not found. Reason: {1} - - - Test Run deployment issue: The assembly or module '{0}' was not found. - - - {0}_{1} {2} - - - Data source '{0}' cannot be found in the test configuration settings - - - The unit test adapter failed to connect to the data source or to read the data. For more information on troubleshooting this error, see "Troubleshooting Data-Driven Unit Tests" (http://go.microsoft.com/fwlink/?LinkId=62412) in the MSDN Library. Error details: {0} - - - Wrong number of objects for permutation. Should be greater than zero. - - - Failed to Create msdia Session COM HResult '{0}'. - - - Failed to load msdia - - \ No newline at end of file diff --git a/source/TestAdapter/RunConfigurationSettings.cs b/source/TestAdapter/RunConfigurationSettings.cs deleted file mode 100644 index 71ba57c..0000000 --- a/source/TestAdapter/RunConfigurationSettings.cs +++ /dev/null @@ -1,153 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.IO; - using System.Xml; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; - - public class RunConfigurationSettings - { - /// - /// The settings name. - /// - public const string SettingsName = "RunConfiguration"; - - /// - /// Initializes a new instance of the class. - /// - public RunConfigurationSettings() - { - this.CollectSourceInformation = true; - } - - /// - /// Gets a value indicating whether source information needs to be collected or not. - /// - public bool CollectSourceInformation { get; private set; } - - /// - /// Populate adapter settings from the context - /// - /// - /// The discovery context that contains the runsettings. - /// - /// Populated RunConfigurationSettings from the discovery context. - public static RunConfigurationSettings PopulateSettings(IDiscoveryContext context) - { - if (context == null || context.RunSettings == null || string.IsNullOrEmpty(context.RunSettings.SettingsXml)) - { - // This will contain default configuration settings - return new RunConfigurationSettings(); - } - - var settings = GetSettings(context.RunSettings.SettingsXml, SettingsName); - - if (settings != null) - { - return settings; - } - - return new RunConfigurationSettings(); - } - - /// - /// Gets the configuration settings from the xml. - /// - /// The xml with the settings passed from the test platform. - /// The name of the settings to fetch. - /// The settings if found. Null otherwise. - internal static RunConfigurationSettings GetSettings(string runsettingsXml, string settingName) - { - using (var stringReader = new StringReader(runsettingsXml)) - { - XmlReader reader = XmlReader.Create(stringReader, XmlRunSettingsUtilities.ReaderSettings); - - // read to the fist child - XmlReaderUtilities.ReadToRootNode(reader); - reader.ReadToNextElement(); - - // Read till we reach nodeName element or reach EOF - while (!string.Equals(reader.Name, settingName, StringComparison.OrdinalIgnoreCase) - && - !reader.EOF) - { - reader.SkipToNextElement(); - } - - if (!reader.EOF) - { - // read nodeName element. - return ToSettings(reader.ReadSubtree()); - } - } - - return null; - } - - /// - /// Convert the parameter xml to TestSettings - /// - /// Reader to load the settings from. - /// An instance of the class - private static RunConfigurationSettings ToSettings(XmlReader reader) - { - ValidateArg.NotNull(reader, "reader"); - - // Expected format of the xml is: - - // - // - // - // true - // - // - RunConfigurationSettings settings = new RunConfigurationSettings(); - - // Read the first element in the section - reader.ReadToNextElement(); - - if (!reader.IsEmptyElement) - { - reader.Read(); - - while (reader.NodeType == XmlNodeType.Element) - { - bool result; - string elementName = reader.Name.ToUpperInvariant(); - switch (elementName) - { - case "COLLECTSOURCEINFORMATION": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.CollectSourceInformation = result; - PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo( - "CollectSourceInformation value Found : {0} ", - result); - } - - break; - } - - default: - { - reader.SkipToNextElement(); - break; - } - } - } - } - - return settings; - } - } -} diff --git a/source/TestAdapter/Services/AdapterTraceLogger.cs b/source/TestAdapter/Services/AdapterTraceLogger.cs deleted file mode 100644 index 5b81285..0000000 --- a/source/TestAdapter/Services/AdapterTraceLogger.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices.Interface; - - /// - /// The trace logger for .Net Core. - /// - public class AdapterTraceLogger : IAdapterTraceLogger - { - /// - /// Log an error in a given format. - /// - /// The format. - /// The args. - /// This is currently not implemented. - public void LogError(string format, params object[] args) - { - // Do Nothing. - } - - /// - /// Log a warning in a given format. - /// - /// The format. - /// The args. - /// This is currently not implemented. - public void LogWarning(string format, params object[] args) - { - // Do Nothing. - } - - /// - /// Log an information message in a given format. - /// - /// The format. - /// The args. - /// This is currently not implemented. - public void LogInfo(string format, params object[] args) - { - // Do Nothing. - } - } -} diff --git a/source/TestAdapter/Services/AssemblyLoadWorker.cs b/source/TestAdapter/Services/AssemblyLoadWorker.cs deleted file mode 100644 index 5e090e0..0000000 --- a/source/TestAdapter/Services/AssemblyLoadWorker.cs +++ /dev/null @@ -1,264 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Reflection; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities; - - /// - /// Utility function for Assembly related info - /// The caller is supposed to create AppDomain and create instance of given class in there. - /// - internal class AssemblyLoadWorker : MarshalByRefObject - { - private IAssemblyUtility assemblyUtility; - - public AssemblyLoadWorker() - : this(new AssemblyUtility()) - { - } - - internal AssemblyLoadWorker(IAssemblyUtility assemblyUtility) - { - this.assemblyUtility = assemblyUtility; - } - - /// - /// Returns the full path to the dependent assemblies of the parameter managed assembly recursively. - /// It does not report GAC assemblies. - /// - /// Path to the assembly file to load from. - /// The warnings. - /// Full path to dependent assemblies. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - public string[] GetFullPathToDependentAssemblies(string assemblyPath, out IList warnings) - { - Debug.Assert(!string.IsNullOrEmpty(assemblyPath), "assemblyPath"); - - warnings = new List(); - Assembly assembly = null; - try - { - // First time we load in LoadFromContext to avoid issues. - assembly = this.assemblyUtility.ReflectionOnlyLoadFrom(assemblyPath); - } - catch (Exception ex) - { - warnings.Add(ex.Message); - return new string[0]; // Otherwise just return no dependencies. - } - - Debug.Assert(assembly != null, "assembly"); - - List result = new List(); - List visitedAssemblies = new List(); - - visitedAssemblies.Add(assembly.FullName); - - this.ProcessChildren(assembly, result, visitedAssemblies, warnings); - - return result.ToArray(); - } - - /// - /// initialize the lifetime service. - /// - /// The . - public override object InitializeLifetimeService() - { - // Infinite. - return null; - } - - /// - /// Get the target dotNet framework string for the assembly - /// - /// Path of the assembly file - /// String representation of the the target dotNet framework e.g. .NETFramework,Version=v4.0 - internal string GetTargetFrameworkVersionStringFromPath(string path) - { - if (File.Exists(path)) - { - try - { - Assembly a = this.assemblyUtility.ReflectionOnlyLoadFrom(path); - return this.GetTargetFrameworkStringFromAssembly(a); - } - catch (BadImageFormatException) - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("AssemblyHelper:GetTargetFrameworkVersionString() caught BadImageFormatException. Falling to native binary."); - } - } - catch (Exception ex) - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("AssemblyHelper:GetTargetFrameworkVersionString() Returning default. Unhandled exception: {0}.", ex); - } - } - } - - return string.Empty; - } - - /// - /// Get the target dot net framework string for the assembly - /// - /// Assembly from which target framework has to find - /// String representation of the the target dot net framework e.g. .NETFramework,Version=v4.0 - private string GetTargetFrameworkStringFromAssembly(Assembly assembly) - { - string dotNetVersion = string.Empty; - foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(assembly)) - { - if (data?.NamedArguments?.Count > 0) - { - var declaringType = data.NamedArguments[0].MemberInfo.DeclaringType; - if (declaringType != null) - { - string attributeName = declaringType.FullName; - if (string.Equals( - attributeName, - Constants.TargetFrameworkAttributeFullName, - StringComparison.OrdinalIgnoreCase)) - { - dotNetVersion = data.ConstructorArguments[0].Value.ToString(); - break; - } - } - } - } - - return dotNetVersion; - } - - /// - /// Processes references, modules, satellites. - /// Fills parameter results. - /// - /// The assembly. - /// The result. - /// The visited Assemblies. - /// The warnings. - private void ProcessChildren(Assembly assembly, IList result, IList visitedAssemblies, IList warnings) - { - Debug.Assert(assembly != null, "assembly"); - foreach (AssemblyName reference in assembly.GetReferencedAssemblies()) - { - this.GetDependentAssembliesInternal(reference.FullName, result, visitedAssemblies, warnings); - } - - // Take care of .netmodule's. - var modules = new Module[0]; - try - { - modules = assembly.GetModules(); - } - catch (FileNotFoundException e) - { - string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependency, e.FileName, e.Message); - warnings.Add(warning); - return; - } - - // Assembly.GetModules() returns all modules including main one. - if (modules.Length > 1) - { - // The modules must be in the same directory as assembly that references them. - foreach (Module m in modules) - { - // Module.Name ~ MyModule.netmodule. Module.FullyQualifiedName ~ C:\dir\MyModule.netmodule. - string shortName = m.Name; - - // Note that "MyModule" may contain dots: - int dotIndex = shortName.LastIndexOf('.'); - if (dotIndex > 0) - { - shortName = shortName.Substring(0, dotIndex); - } - - if (string.Equals(shortName, assembly.GetName().Name, StringComparison.OrdinalIgnoreCase)) - { - // This is main assembly module. - continue; - } - - if (visitedAssemblies.Contains(m.Name)) - { - continue; - } - - visitedAssemblies.Add(m.Name); - - if (!File.Exists(m.FullyQualifiedName)) - { - string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependencyWithoutReason, m.FullyQualifiedName); - warnings.Add(warning); - continue; - } - - result.Add(m.FullyQualifiedName); - } - } - } - - /// - /// Loads in Load Context. Fills private members. - /// - /// Full or partial assembly name passed to Assembly.Load. - /// The result. - /// The visited Assemblies. - /// The warnings. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private void GetDependentAssembliesInternal(string assemblyString, IList result, IList visitedAssemblies, IList warnings) - { - Debug.Assert(!string.IsNullOrEmpty(assemblyString), "assemblyString"); - - if (visitedAssemblies.Contains(assemblyString)) - { - return; - } - - visitedAssemblies.Add(assemblyString); - - Assembly assembly = null; - try - { - string postPolicyAssembly = AppDomain.CurrentDomain.ApplyPolicy(assemblyString); - Debug.Assert(!string.IsNullOrEmpty(postPolicyAssembly), "postPolicyAssembly"); - - assembly = this.assemblyUtility.ReflectionOnlyLoad(postPolicyAssembly); - visitedAssemblies.Add(assembly.FullName); // Just in case. - } - catch (Exception ex) - { - string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependency, assemblyString, ex.Message); - warnings.Add(warning); - return; - } - - // As soon as we find GAC or internal assembly we do not look further. - if (assembly.GlobalAssemblyCache) - { - return; - } - - result.Add(assembly.Location); - - this.ProcessChildren(assembly, result, visitedAssemblies, warnings); - } - } -} diff --git a/source/TestAdapter/Services/DiaSessionOperations.cs b/source/TestAdapter/Services/DiaSessionOperations.cs deleted file mode 100644 index 8ef355a..0000000 --- a/source/TestAdapter/Services/DiaSessionOperations.cs +++ /dev/null @@ -1,121 +0,0 @@ -///// -//// Copyright (c) 2018 The nanoFramework project contributors -//// Portions Copyright (c) Microsoft Corporation. All rights reserved. -//// See LICENSE file in the project root for full license information. -//// - -//namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -//{ -// using System; -// using System.Reflection; - -// internal static class DiaSessionOperations -// { -// private static MethodInfo methodGetNavigationData; -// private static PropertyInfo propertyFileName; -// private static PropertyInfo propertyMinLineNumber; -// private static Type typeDiaSession; -// private static Type typeDiaNavigationData; - -// /// -// /// Initializes static members of the class. -// /// -// /// Initializes DiaSession. -// static DiaSessionOperations() -// { -// const string diaSessionTypeName = "Microsoft.VisualStudio.TestPlatform.ObjectModel.DiaSession, Microsoft.VisualStudio.TestPlatform.ObjectModel"; -// const string diaNavigationDataTypeName = "Microsoft.VisualStudio.TestPlatform.ObjectModel.DiaNavigationData, Microsoft.VisualStudio.TestPlatform.ObjectModel"; - -// Initialize(diaSessionTypeName, diaNavigationDataTypeName); -// } - -// /// -// /// Creates a Navigation session for the source file. -// /// This is used to get file path and line number information for its components. -// /// -// /// The source file. -// /// A Navigation session instance for the current platform. -// internal static object CreateNavigationSession(string source) -// { -// // Create instance only when DiaSession is found in Object Model. -// if (typeDiaSession != null && typeDiaNavigationData != null) -// { -// var messageFormatOnException = string.Join("TestDiscoverer:DiaSession: Could not create diaSession for source:", source, ". Reason:{0}"); -// return SafeInvoke(() => Activator.CreateInstance(typeDiaSession, source), messageFormatOnException); -// } - -// return null; -// } - -// /// -// /// Get's the navigation data for a navigation session. -// /// -// /// The navigation session. -// /// The class name. -// /// The method name. -// /// The min line number. -// /// The file name. -// internal static void GetNavigationData(object navigationSession, string className, string methodName, out int minLineNumber, out string fileName) -// { -// // Set default values. -// fileName = null; -// minLineNumber = -1; - -// // Get navigation data only when DiaSession is found in Object Model. -// if (typeDiaSession != null && typeDiaNavigationData != null) -// { -// var messageFormatOnException = string.Join("TestDiscoverer:DiaSession: Could not get navigation data for class:", className, ". Reason:{0}"); -// var data = SafeInvoke(() => methodGetNavigationData.Invoke(navigationSession, new object[] { className, methodName }), messageFormatOnException); - -// if (data != null) -// { -// fileName = (string)propertyFileName?.GetValue(data); -// minLineNumber = (int)(propertyMinLineNumber?.GetValue(data) ?? -1); -// } -// } -// } - -// /// -// /// Dispose's the navigation session instance. -// /// -// /// The navigation session. -// internal static void DisposeNavigationSession(object navigationSession) -// { -// var diaSession = navigationSession as IDisposable; -// diaSession?.Dispose(); -// } - -// /// -// /// 1. Initializes DiaSession. -// /// 2. Assists in Unit Testing. -// /// -// /// Type name of DiaSession class. -// /// Type name of DiaNavigationData class. -// internal static void Initialize(string diaSession, string diaNavigationData) -// { -// typeDiaSession = Type.GetType(diaSession, false); -// typeDiaNavigationData = Type.GetType(diaNavigationData, false); - -// if (typeDiaSession != null && typeDiaNavigationData != null) -// { -// methodGetNavigationData = typeDiaSession.GetRuntimeMethod("GetNavigationData", new[] { typeof(string), typeof(string) }); -// propertyFileName = typeDiaNavigationData.GetRuntimeProperty("FileName"); -// propertyMinLineNumber = typeDiaNavigationData.GetRuntimeProperty("MinLineNumber"); -// } -// } - -// private static object SafeInvoke(Func action, string messageFormatOnException = null) -// { -// try -// { -// return action.Invoke(); -// } -// catch (Exception) -// { -// // todo : Add EqtTrace -// } - -// return null; -// } -// } -//} diff --git a/source/TestAdapter/Services/FileOperations.cs b/source/TestAdapter/Services/FileOperations.cs deleted file mode 100644 index 167fd4d..0000000 --- a/source/TestAdapter/Services/FileOperations.cs +++ /dev/null @@ -1,173 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using System; - using System.IO; - using System.Reflection; - - - /// - /// This service is responsible for any file based operations. - /// - public class FileOperations : IFileOperations - { - /// - /// Loads an assembly into the current context. - /// - /// The name of the assembly. - /// - /// Indicates whether this should be a reflection only load. - /// - /// A handle to the loaded assembly. - public Assembly LoadAssembly(string assemblyFileName, bool isReflectionOnly) - { - //if (isReflectionOnly) - //{ - // return Assembly.ReflectionOnlyLoadFrom(assemblyFileName); - //} - //else - //{ - // return Assembly.LoadFrom(assemblyFileName); - //} - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(assemblyFileName); - return Assembly.Load(new AssemblyName(fileNameWithoutExtension)); - - } - - /// - /// Gets the path to the .DLL of the assembly. - /// - /// The assembly. - /// Path to the .DLL of the assembly. - public string GetAssemblyPath(Assembly assembly) - { - //return assembly.Location; - return null; // TODO: what are the options here? - } - - /// - /// Verify if a file exists in the current context. - /// - /// The assembly file name. - /// true if the file exists. - public bool DoesFileExist(string assemblyFileName) - { - //return (SafeInvoke(() => File.Exists(assemblyFileName)) as bool?) ?? false; - var fileExists = true;// false; - - //try - //{ - // var fileNameWithoutPath = Path.GetFileName(assemblyFileName); - // var searchTask = Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(fileNameWithoutPath).AsTask(); - // searchTask.Wait(); - // fileExists = searchTask.Result != null; - //} - //catch (Exception) - //{ - // // ignore - //} - - return fileExists; - - } - - /// - /// Creates a Navigation session for the source file. - /// This is used to get file path and line number information for its components. - /// - /// The source file. - /// A Navigation session instance for the current platform. - public object CreateNavigationSession(string source) - { - //var messageFormatOnException = - // string.Join("MSTestDiscoverer:DiaSession: Could not create diaSession for source:", source, ". Reason:{0}"); - //return SafeInvoke(() => new DiaSession(source), messageFormatOnException) as DiaSession; - //return DiaSessionOperations.CreateNavigationSession(source); - - return new DiaSession(source); - } - - /// - /// Get's the navigation data for a navigation session. - /// - /// The navigation session. - /// The class name. - /// The method name. - /// The min line number. - /// The file name. - public void GetNavigationData(object navigationSession, string className, string methodName, out int minLineNumber, out string fileName) - { - //fileName = null; - //minLineNumber = -1; - - //var diasession = navigationSession as DiaSession; - //var navigationData = diasession?.GetNavigationData(className, methodName); - - //if (navigationData != null) - //{ - // minLineNumber = navigationData.MinLineNumber; - // fileName = navigationData.FileName; - //} - //DiaSessionOperations.GetNavigationData(navigationSession, className, methodName, out minLineNumber, out fileName); - - DiaSession diaSession = navigationSession as DiaSession; - var navData = diaSession.GetNavigationData(className, methodName); - - minLineNumber = navData.MinLineNumber; - fileName = navData.FileName; - } - - /// - /// Dispose's the navigation session instance. - /// - /// The navigation session. - public void DisposeNavigationSession(object navigationSession) - { - //var diasession = navigationSession as DiaSession; - //diasession?.Dispose(); - // DiaSessionOperations.DisposeNavigationSession(navigationSession); - - - DiaSession diaSession = navigationSession as DiaSession; - diaSession.Dispose(); - } - - /// - /// Gets the full file path of an assembly file. - /// - /// The file name. - /// The full file path. - public string GetFullFilePath(string assemblyFileName) - { - //return (SafeInvoke(() => Path.GetFullPath(assemblyFileName)) as string) ?? assemblyFileName; - return assemblyFileName; - - } - - //private static object SafeInvoke(Func action, string messageFormatOnException = null) - //{ - // try - // { - // return action.Invoke(); - // } - // catch (Exception exception) - // { - // if (string.IsNullOrEmpty(messageFormatOnException)) - // { - // messageFormatOnException = "{0}"; - // } - - // EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, messageFormatOnException, exception.Message); - // } - - // return null; - //} - } -} diff --git a/source/TestAdapter/Services/MSTestSettingsProvider.cs b/source/TestAdapter/Services/MSTestSettingsProvider.cs deleted file mode 100644 index a0354c6..0000000 --- a/source/TestAdapter/Services/MSTestSettingsProvider.cs +++ /dev/null @@ -1,361 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.IO; - using System.Xml; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - - - /// - /// Class to read settings from the runsettings xml for the desktop. - /// - public class MSTestSettingsProvider : ISettingsProvider - { - /// - /// Member variable for Adapter settings - /// - private static MSTestAdapterSettings settings; - - /// - /// Gets settings provided to the adapter. - /// - public static MSTestAdapterSettings Settings - { - get - { - if (settings == null) - { - settings = new MSTestAdapterSettings(); - } - - return settings; - } - } - - /// - /// Load the settings from the reader. - /// - /// Reader to load the settings from. - public void Load(XmlReader reader) - { - ValidateArg.NotNull(reader, "reader"); - - settings = MSTestAdapterSettings.ToSettings(reader); - } - - public IDictionary GetProperties(string source) - { - return TestDeployment.GetDeploymentInformation(source); - } - - /// - /// Reset the settings to its default. - /// Used for testing purposes. - /// - internal static void Reset() - { - settings = null; - } - } - - /// - /// Adapter Settings for the run - /// -#pragma warning disable SA1402 - public class MSTestAdapterSettings -#pragma warning restore SA1402 - { - /// - /// Initializes a new instance of the class. - /// - public MSTestAdapterSettings() - { - this.DeleteDeploymentDirectoryAfterTestRunIsComplete = true; - this.DeploymentEnabled = true; - this.SearchDirectories = new List(); - } - - /// - /// Gets a value indicating whether deployment is enable or not. - /// - public bool DeploymentEnabled { get; private set; } - - /// - /// Gets a value indicating whether deployment directory has to be deleted after test run. - /// - public bool DeleteDeploymentDirectoryAfterTestRunIsComplete { get; private set; } - - /// - /// Gets list of paths recursive or non recursive paths. - /// - protected List SearchDirectories { get; private set; } - - /// - /// Convert the parameter xml to TestSettings - /// - /// Reader to load the settings from. - /// An instance of the class - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Reviewed. Suppression is OK here.")] - public static MSTestAdapterSettings ToSettings(XmlReader reader) - { - ValidateArg.NotNull(reader, "reader"); - - // Expected format of the xml is: - - // - // - // true - // true - // - // - // - // ...// by default includeSubDirectories is false - // - // - MSTestAdapterSettings settings = MSTestSettingsProvider.Settings; - - if (!reader.IsEmptyElement) - { - reader.Read(); - - while (reader.NodeType == XmlNodeType.Element) - { - bool result; - string elementName = reader.Name.ToUpperInvariant(); - switch (elementName) - { - case "ASSEMBLYRESOLUTION": - { - settings.ReadAssemblyResolutionPath(reader); - break; - } - - case "DEPLOYMENTENABLED": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.DeploymentEnabled = result; - } - - break; - } - - case "DELETEDEPLOYMENTDIRECTORYAFTERTESTRUNISCOMPLETE": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.DeleteDeploymentDirectoryAfterTestRunIsComplete = result; - } - - break; - } - - default: - { - reader.Skip(); - break; - } - } - } - } - - return settings; - } - - public static bool IsAppDomainCreationDisabled(string settingsXml) - { - bool disableAppDomain = false; - if (!string.IsNullOrEmpty(settingsXml)) - { - StringReader stringReader = new StringReader(settingsXml); - XmlReader reader = XmlReader.Create(stringReader, XmlRunSettingsUtilities.ReaderSettings); - - if (reader.ReadToFollowing("DisableAppDomain")) - { - bool.TryParse(reader.ReadInnerXml(), out disableAppDomain); - } - } - - return disableAppDomain; - } - - /// - /// Returns the array of path with recursive property true/false from comma separated paths - /// for ex: paths = c:\a\b;e:\balh\foo;%SystemDrive%\SomeDirectory and recursive = true - /// it will return an list {{c:\a\b, true}, {e:\balh\foo, true}, {c:\somedirectory, true}} - /// - /// the base directory for relative path. - /// RecursiveDirectoryPath information. - public List GetDirectoryListWithRecursiveProperty(string baseDirectory) - { - List directoriesList = new List(); - - foreach (RecursiveDirectoryPath recPath in this.SearchDirectories) - { - // If path has environment variable, then resolve it - string directorypath = this.ResolveEnvironmentVariableAndReturnFullPathIfExist(recPath.DirectoryPath, baseDirectory); - - if (!string.IsNullOrEmpty(directorypath)) - { - directoriesList.Add(new RecursiveDirectoryPath(directorypath, recPath.IncludeSubDirectories)); - } - } - - return directoriesList; - } - - /// - /// Gets the full path and expands any environment variables contained in the path. - /// - /// The path to be expanded. - /// The base directory for the path which is not rooted path - /// The expanded path. - internal string ResolveEnvironmentVariableAndReturnFullPathIfExist(string path, string baseDirectory) - { - // Trim begining and trailing white space from the path. - path = path.Trim(' ', '\t'); - - if (!string.IsNullOrEmpty(path)) - { - string warningMessage = null; - - // Expand any environment variables in the path. - path = this.ExpandEnvironmentVariables(path); - - // If the path is a relative path, expand it relative to the base directory - if (!Path.IsPathRooted(path)) - { - if (!string.IsNullOrEmpty(baseDirectory)) - { - path = Path.Combine(baseDirectory, path); - } - else - { - warningMessage = string.Format("The Directory: {0}, has following problem: {1}", path, "This is not an absolute path. A base directory should be provided for this to be used as a relative path."); - - if (EqtTrace.IsWarningEnabled) - { - EqtTrace.Warning(warningMessage); - } - - return null; - } - } - - try - { - // Get the full path. - // This will cleanup the path converting any "..\" to the appropariate value - // and convert any alternative directory seperators to "\" - path = Path.GetFullPath(path); - } - catch (Exception e) - { - warningMessage = e.Message; - } - - if (!string.IsNullOrEmpty(warningMessage)) - { - warningMessage = string.Format("The Directory: {0}, has following problem: {1}", path, warningMessage); - - if (EqtTrace.IsWarningEnabled) - { - EqtTrace.Warning(warningMessage); - } - - return null; - } - - if (this.DoesDirectoryExist(path)) - { - return path; - } - - // generate warning that path doesnot exist. - EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, string.Format("The Directory: {0}, does not exist.", path)); - } - - return null; - } - - /// - /// Verifies if a directory exists. - /// - /// path - /// True if directory exists. - /// Only present for unit testing scenarios. - protected virtual bool DoesDirectoryExist(string path) - { - return Directory.Exists(path); - } - - /// - /// Expands any environment variables in the path provided. - /// - /// path - /// expanded string - /// Only present for unit testing scenarios. - protected virtual string ExpandEnvironmentVariables(string path) - { - return Environment.ExpandEnvironmentVariables(path); - } - - private void ReadAssemblyResolutionPath(XmlReader reader) - { - ValidateArg.NotNull(reader, "reader"); - - // Expected format of the xml is: - - // - // - // - // - // ...// by default includeSubDirectories is false - // - bool empty = reader.IsEmptyElement; - reader.Read(); - - if (!empty) - { - while (reader.NodeType == XmlNodeType.Element) - { - if (string.Compare("Directory", reader.Name, StringComparison.OrdinalIgnoreCase) == 0) - { - string recursiveAttribute = reader.GetAttribute("includeSubDirectories"); - - // read the path specified - string path = reader.GetAttribute("path"); - - if (!string.IsNullOrEmpty(path)) - { - // Do we have to look in sub directory for dependent dll. - var includeSubDirectories = string.Compare(recursiveAttribute, "true", StringComparison.OrdinalIgnoreCase) == 0; - this.SearchDirectories.Add(new RecursiveDirectoryPath(path, includeSubDirectories)); - } - } - else - { - string message = string.Format(CultureInfo.CurrentCulture, Resource.InvalidSettingsXmlElement, reader.Name, "AssemblyResolution"); - throw new SettingsException(message); - } - - // Move to the next element under tag AssemblyResolution - reader.Read(); - } - } - - // go to the end of the element. - reader.ReadEndElement(); - } - } -} diff --git a/source/TestAdapter/Services/RecursiveDirectoryPath.cs b/source/TestAdapter/Services/RecursiveDirectoryPath.cs deleted file mode 100644 index 750925b..0000000 --- a/source/TestAdapter/Services/RecursiveDirectoryPath.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using System; - using System.Diagnostics.CodeAnalysis; - - /// - /// Mstest settings in runsettings look like this - /// - /// - /// - /// - /// ...// by default includeSubDirectories is false - /// - /// - /// - /// For each directory we need to have two info 1) path 2) includeSubDirectories - /// - [Serializable] - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1603:DocumentationMustContainValidXml", Justification = "Reviewed. Suppression is OK here.")] - public class RecursiveDirectoryPath : MarshalByRefObject - { - /// - /// Initializes a new instance of the class. - /// - /// The directory path. - /// - /// True if to include subdirectory else false - /// - public RecursiveDirectoryPath(string dirPath, bool includeSubDirectories) - { - this.DirectoryPath = dirPath; - this.IncludeSubDirectories = includeSubDirectories; - } - - /// - /// Gets the directory path. - /// - public string DirectoryPath { get; private set; } - - /// - /// Gets a value indicating whether to include sub directories. - /// - public bool IncludeSubDirectories { get; private set; } - } -} diff --git a/source/TestAdapter/Services/ReflectionOperations.cs b/source/TestAdapter/Services/ReflectionOperations.cs deleted file mode 100644 index 9b710d9..0000000 --- a/source/TestAdapter/Services/ReflectionOperations.cs +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities; - using System; - using System.Reflection; - -#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName - - /// - /// This service is responsible for platform specific reflection operations. - /// - /// - /// The test platform triggers discovery of test assets built for all architectures including ARM on desktop. In such cases we would need to load - /// these sources in a reflection only context. Since Reflection-Only context currently is primarily prevalent in .Net Framework only, this service is required - /// so that some operations like fetching attributes in a reflection only context can be performed. - /// - public class ReflectionOperations : IReflectionOperations - { - private ReflectionUtility reflectionUtility; - - /// - /// Initializes a new instance of the class. - /// - public ReflectionOperations() - { - this.reflectionUtility = new ReflectionUtility(); - } - - /// - /// Gets all the custom attributes adorned on a member. - /// - /// The member. - /// True to inspect the ancestors of element; otherwise, false. - /// The list of attributes on the member. Empty list if none found. - public object[] GetCustomAttributes( - MemberInfo memberInfo, - bool inherit - ) - { - return this.reflectionUtility.GetCustomAttributes(memberInfo, inherit); - } - - /// - /// Gets all the custom attributes of a given type adorned on a member. - /// - /// The member info. - /// The attribute type. - /// True to inspect the ancestors of element; otherwise, false. - /// The list of attributes on the member. Empty list if none found. - public object[] GetCustomAttributes( - MemberInfo memberInfo, - Type type, - bool inherit - ) - { - return this.reflectionUtility.GetCustomAttributes(memberInfo, type, inherit); - } - - /// - /// Gets all the custom attributes of a given type on an assembly. - /// - /// The assembly. - /// The attribute type. - /// The list of attributes of the given type on the member. Empty list if none found. - public object[] GetCustomAttributes( - Assembly assembly, - Type type - ) - { - return this.reflectionUtility.GetCustomAttributes(assembly, type); - } - } - -#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName -} diff --git a/source/TestAdapter/Services/TestContextImplementation.cs b/source/TestAdapter/Services/TestContextImplementation.cs deleted file mode 100644 index 713cf2c..0000000 --- a/source/TestAdapter/Services/TestContextImplementation.cs +++ /dev/null @@ -1,494 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using System; - using System.Collections.Generic; - using System.Data; - using System.Data.Common; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - - //using UTF = nanoFramework.TestPlatform.MSTest.TestAdapter; - - /// - /// Internal implementation of TestContext exposed to the user. - /// The virtual string properties of the TestContext are retreived from the property dictionary - /// like GetProperty<string>("TestName") or GetProperty<string>("FullyQualifiedTestClassName"); - /// - public class TestContextImplementation : TestContext, ITestContext - { - /// - /// List of result files associated with the test - /// - private IList testResultFiles; - - /// - /// Properties - /// - private IDictionary properties; - - /// - /// Unit test outcome - /// - private UnitTestOutcome outcome; - - /// - /// Writer on which the messages given by the user should be written - /// - private StringWriter stringWriter; - - /// - /// Specifies whether the writer is disposed or not - /// - private bool stringWriterDisposed = false; - - /// - /// Test Method - /// - private ITestMethod testMethod; - - /// - /// DB connection for test context - /// - private DbConnection dbConnection; - - /// - /// Data row for TestContext - /// - private DataRow dataRow; - - /// - /// Initializes a new instance of the class. - /// - /// The test method. - /// The writer where diagnostic messages are written to. - /// Properties/configuration passed in. - public TestContextImplementation(ITestMethod testMethod, StringWriter stringWriter, IDictionary properties) - { - Debug.Assert(testMethod != null, "TestMethod is not null"); - Debug.Assert(stringWriter != null, "StringWriter is not null"); - Debug.Assert(properties != null, "properties is not null"); - - this.testMethod = testMethod; - this.stringWriter = stringWriter; - this.properties = new Dictionary(properties); - - this.InitializeProperties(); - - this.testResultFiles = new List(); - } - - #region TestContext impl - - /// - public override UnitTestOutcome CurrentTestOutcome - { - get - { - return this.outcome; - } - } - - /// - //public override DbConnection DataConnection - //{ - // get - // { - // return this.dbConnection; - // } - //} - - ///// - //public override DataRow DataRow - //{ - // get - // { - // return this.dataRow; - // } - //} - - ///// - //public override IDictionary Properties - //{ - // get - // { - // return this.properties as IDictionary; - // } - //} - - /// - //public override string TestRunDirectory - //{ - // get - // { - // return this.GetStringPropertyValue(TestContextPropertyStrings.TestRunDirectory); - // } - //} - - ///// - //public override string DeploymentDirectory - //{ - // get - // { - // return this.GetStringPropertyValue(TestContextPropertyStrings.DeploymentDirectory); - // } - //} - - ///// - //public override string ResultsDirectory - //{ - // get - // { - // return this.GetStringPropertyValue(TestContextPropertyStrings.ResultsDirectory); - // } - //} - - ///// - //public override string TestRunResultsDirectory - //{ - // get - // { - // return this.GetStringPropertyValue(TestContextPropertyStrings.TestRunResultsDirectory); - // } - //} - - /// - //[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Justification = "TestResultsDirectory is what we need.")] - //public override string TestResultsDirectory - //{ - // get - // { - // // In MSTest, it is actually "In\697105f7-004f-42e8-bccf-eb024870d3e9\User1", but - // // we are setting it to "In" only because MSTest does not create this directory. - // return this.GetStringPropertyValue(TestContextPropertyStrings.TestResultsDirectory); - // } - //} - - /// - //public override string TestDir - //{ - // get - // { - // return this.GetStringPropertyValue(TestContextPropertyStrings.TestDir); - // } - //} - - ///// - //public override string TestDeploymentDir - //{ - // get - // { - // return this.GetStringPropertyValue(TestContextPropertyStrings.TestDeploymentDir); - // } - //} - - ///// - //public override string TestLogsDir - //{ - // get - // { - // return this.GetStringPropertyValue(TestContextPropertyStrings.TestLogsDir); - // } - //} - - /// - public override string FullyQualifiedTestClassName - { - get - { - return this.GetStringPropertyValue(TestContextPropertyStrings.FullyQualifiedTestClassName); - } - } - - /// - public override string TestName - { - get - { - return this.GetStringPropertyValue(TestContextPropertyStrings.TestName); - } - } - - public TestContext Context - { - get - { - return this as TestContext; - } - } - - TestContext ITestContext.Context => throw new NotImplementedException(); - - public override IDictionary Properties => throw new NotImplementedException(); - - /// - //public override void AddResultFile(string fileName) - //{ - // if (string.IsNullOrEmpty(fileName)) - // { - // throw new ArgumentException(Resource.Common_CannotBeNullOrEmpty, "fileName"); - // } - - // this.testResultFiles.Add(Path.GetFullPath(fileName)); - //} - - ///// - //public override void BeginTimer(string timerName) - //{ - // throw new NotSupportedException(); - //} - - ///// - //public override void EndTimer(string timerName) - //{ - // throw new NotSupportedException(); - //} - - /// - /// When overridden in a derived class, used to write trace messages while the - /// test is running. - /// - /// The formatted string that contains the trace message. - public override void WriteLine(string message) - { - if (this.stringWriterDisposed) - { - return; - } - - try - { - var msg = message?.Replace("\0", "\\0"); - this.stringWriter.WriteLine(msg); - } - catch (ObjectDisposedException) - { - this.stringWriterDisposed = true; - } - } - - /// - /// When overridden in a derived class, used to write trace messages while the - /// test is running. - /// - /// The string that contains the trace message. - /// Arguments to add to the trace message. - public override void WriteLine(string format, params object[] args) - { - if (this.stringWriterDisposed) - { - return; - } - - try - { - string message = string.Format(CultureInfo.CurrentCulture, format?.Replace("\0", "\\0"), args); - this.stringWriter.WriteLine(message); - } - catch (ObjectDisposedException) - { - this.stringWriterDisposed = true; - } - } - - /// - /// Set the unit-test outcome - /// - /// The test outcome. - public void SetOutcome(UnitTestOutcome outcome) - { - this.outcome = ToUTF(outcome); - } - - /// - /// Set data row for particular run of TestMethod. - /// - /// data row. - public void SetDataRow(object dataRow) - { - this.dataRow = dataRow as DataRow; - } - - /// - /// Set connection for TestContext - /// - /// db Connection. - public void SetDataConnection(object dbConnection) - { - this.dbConnection = dbConnection as DbConnection; - } - - /// - /// Returns whether property with parameter name is present or not - /// - /// The property name. - /// The property value. - /// True if found. - public bool TryGetPropertyValue(string propertyName, out object propertyValue) - { - if (this.properties == null) - { - propertyValue = null; - return false; - } - - return this.properties.TryGetValue(propertyName, out propertyValue); - } - - /// - /// Adds the parameter name/value pair to property bag - /// - /// The property name. - /// The property value. - public void AddProperty(string propertyName, string propertyValue) - { - if (this.properties == null) - { - this.properties = new Dictionary(); - } - - this.properties.Add(propertyName, propertyValue); - } - - /// - /// Result files attached - /// - /// Results files generated in run. - public IList GetResultFiles() - { - if (this.testResultFiles.Count() == 0) - { - return null; - } - - List results = this.testResultFiles.ToList(); - - // clear the result files to handle data driven tests - this.testResultFiles.Clear(); - - return results; - } - - /// - /// Gets messages from the testContext writeLines - /// - /// The test context messages added so far. - public string GetDiagnosticMessages() - { - return this.stringWriter.ToString(); - } - - /// - /// Clears the previous testContext writeline messages. - /// - public void ClearDiagnosticMessages() - { - var sb = this.stringWriter.GetStringBuilder(); - sb.Remove(0, sb.Length); - } - - #endregion - - /// - /// Converts the parameter outcome to UTF outcome - /// - /// The UTF outcome. - /// test outcome - private static UnitTestOutcome ToUTF( - UnitTestOutcome outcome - ) - { - switch (outcome) - { - case UnitTestOutcome.Error: - { - return UnitTestOutcome.Error; - } - - case UnitTestOutcome.Failed: - { - return UnitTestOutcome.Failed; - } - - case UnitTestOutcome.Inconclusive: - { - return UnitTestOutcome.Inconclusive; - } - - case UnitTestOutcome.Passed: - { - return UnitTestOutcome.Passed; - } - - case UnitTestOutcome.Timeout: - { - return UnitTestOutcome.Timeout; - } - - case UnitTestOutcome.InProgress: - { - return UnitTestOutcome.InProgress; - } - - default: - { - Debug.Fail("Unknown outcome " + outcome); - return UnitTestOutcome.Unknown; - } - } - } - - /// - /// Helper to safely fetch a property value. - /// - /// Property Name - /// Property value - private string GetStringPropertyValue( - string propertyName - ) - { - object propertyValue = null; - this.properties.TryGetValue(propertyName, out propertyValue); - return propertyValue as string; - } - - /// - /// Helper to initialize the properties. - /// - private void InitializeProperties() - { - this.properties[TestContextPropertyStrings.FullyQualifiedTestClassName] = this.testMethod.FullClassName; - this.properties[TestContextPropertyStrings.TestName] = this.testMethod.Name; - } - } - -#pragma warning disable SA1402 // File may only contain a single class - - /// - /// Test Context Property Names. - /// - internal static class TestContextPropertyStrings -#pragma warning restore SA1402 // File may only contain a single class - { - public static readonly string TestRunDirectory = "TestRunDirectory"; - public static readonly string DeploymentDirectory = "DeploymentDirectory"; - public static readonly string ResultsDirectory = "ResultsDirectory"; - public static readonly string TestRunResultsDirectory = "TestRunResultsDirectory"; - public static readonly string TestResultsDirectory = "TestResultsDirectory"; - public static readonly string TestDir = "TestDir"; - public static readonly string TestDeploymentDir = "TestDeploymentDir"; - public static readonly string TestLogsDir = "TestLogsDir"; - - public static readonly string FullyQualifiedTestClassName = "FullyQualifiedTestClassName"; - public static readonly string TestName = "TestName"; - } -} diff --git a/source/TestAdapter/Services/TestDeployment.cs b/source/TestAdapter/Services/TestDeployment.cs deleted file mode 100644 index 56adc8f..0000000 --- a/source/TestAdapter/Services/TestDeployment.cs +++ /dev/null @@ -1,245 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Reflection; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Deployment; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities; - - /// - /// The test deployment. - /// - public class TestDeployment : ITestDeployment - { - #region Service Utility Variables - - private DeploymentItemUtility deploymentItemUtility; - private DeploymentUtility deploymentUtility; - private FileUtility fileUtility; - private MSTestAdapterSettings adapterSettings; - - #endregion - - /// - /// Initializes a new instance of the class. - /// - public TestDeployment() - : this(new DeploymentItemUtility(new ReflectionUtility()), new DeploymentUtility(), new FileUtility()) - { - } - - /// - /// Initializes a new instance of the class. Used for unit tests. - /// - /// The deployment item utility. - /// The deployment utility. - /// The file utility. - internal TestDeployment( - DeploymentItemUtility deploymentItemUtility, - DeploymentUtility deploymentUtility, - FileUtility fileUtility - ) - { - this.deploymentItemUtility = deploymentItemUtility; - this.deploymentUtility = deploymentUtility; - this.fileUtility = fileUtility; - this.adapterSettings = null; - RunDirectories = null; - } - - /// - /// Gets the current run directories for this session. - /// - /// - /// This is intialized at the beginning of a run session when Deploy is called. - /// Leaving this as a static varaible since the testContext needs to be filled in with this information. - /// - internal static TestRunDirectories RunDirectories - { - get; - private set; - } - - /// - /// The get deployment items. - /// - /// The method. - /// The type. - /// The warnings. - /// A string of deployment items. - public KeyValuePair[] GetDeploymentItems( - MethodInfo method, - Type type, - ICollection warnings - ) - { - return this.deploymentItemUtility.GetDeploymentItems(method, this.deploymentItemUtility.GetClassLevelDeploymentItems(type, warnings), warnings); - } - - /// - /// The cleanup. - /// - public void Cleanup() - { - // Delete the deployment directory - if (RunDirectories != null && this.adapterSettings.DeleteDeploymentDirectoryAfterTestRunIsComplete) - { - EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "Deleting deployment directory {0}", RunDirectories.RootDeploymentDirectory); - - this.fileUtility.DeleteDirectories(RunDirectories.RootDeploymentDirectory); - - EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "Deleted deployment directory {0}", RunDirectories.RootDeploymentDirectory); - } - } - - /// - /// Gets the deployment output directory where the source file along with all its dependencies is dropped. - /// - /// The deployment output directory. - public string GetDeploymentDirectory() - { - return RunDirectories?.OutDirectory; - } - - /// - /// Deploy files related to the list of tests specified. - /// - /// The tests. - /// The run context. - /// The framework handle. - /// Return true if deployment is done. - public bool Deploy( - IEnumerable tests, - IRunContext runContext, - IFrameworkHandle frameworkHandle - ) - { - Debug.Assert(tests != null, "tests"); - - // Reset runDirectories before doing deployment, so that older values of runDirectories is not picked - // even if test host is kept alive. - RunDirectories = null; - - this.adapterSettings = MSTestSettingsProvider.Settings; - bool canDeploy = this.CanDeploy(); - var hasDeploymentItems = tests.Any(test => this.deploymentItemUtility.HasDeploymentItems(test)); - - // deployment directories should not be created in this case,simply return - if (!canDeploy && hasDeploymentItems) - { - return false; - } - - RunDirectories = this.deploymentUtility.CreateDeploymentDirectories(runContext); - - // Deployment directories are created but deployment will not happen. - // This is added just to keep consistency with MSTestv1 behaviour. - if (!hasDeploymentItems) - { - return false; - } - - var isDeploymentDone = false; - - using (new SuspendCodeCoverage()) - { - // Group the tests by source - var testsBySource = from test in tests - group test by test.Source into testGroup - select new { Source = testGroup.Key, Tests = testGroup }; - - var runDirectories = RunDirectories; - foreach (var group in testsBySource) - { - // do the deployment - isDeploymentDone = this.deploymentUtility.Deploy(@group.Tests, @group.Source, runContext, frameworkHandle, ref runDirectories) || isDeploymentDone; - } - - // Update the runDirectories - RunDirectories = runDirectories; - } - - return true; - } - - internal static IDictionary GetDeploymentInformation( - string source - ) - { - var properties = new Dictionary(); - - var applicationBaseDirectory = string.Empty; - - // Run directories can be null in win8. - if (RunDirectories == null && !string.IsNullOrEmpty(source)) - { - // applicationBaseDirectory is set at source level - applicationBaseDirectory = Path.GetDirectoryName(source); - } - - properties[TestContextPropertyStrings.TestRunDirectory] = RunDirectories != null - ? RunDirectories.RootDeploymentDirectory - : applicationBaseDirectory; - properties[TestContextPropertyStrings.DeploymentDirectory] = RunDirectories != null - ? RunDirectories.OutDirectory - : applicationBaseDirectory; - properties[TestContextPropertyStrings.ResultsDirectory] = RunDirectories != null - ? RunDirectories.InDirectory - : applicationBaseDirectory; - properties[TestContextPropertyStrings.TestRunResultsDirectory] = RunDirectories != null - ? RunDirectories.InMachineNameDirectory - : applicationBaseDirectory; - properties[TestContextPropertyStrings.TestResultsDirectory] = RunDirectories != null - ? RunDirectories.InDirectory - : applicationBaseDirectory; - properties[TestContextPropertyStrings.TestDir] = RunDirectories != null - ? RunDirectories.RootDeploymentDirectory - : applicationBaseDirectory; - properties[TestContextPropertyStrings.TestDeploymentDir] = RunDirectories != null - ? RunDirectories.OutDirectory - : applicationBaseDirectory; - properties[TestContextPropertyStrings.TestLogsDir] = RunDirectories != null - ? RunDirectories.InMachineNameDirectory - : applicationBaseDirectory; - - return properties; - } - - /// - /// Reset the static varaible to default values. Used only for testing purposes. - /// - internal static void Reset() - { - RunDirectories = null; - } - - /// - /// Returns whether deployment can happen or not - /// - /// True if deployment can be done. - private bool CanDeploy() - { - if (!this.adapterSettings.DeploymentEnabled) - { - EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "TestExecutor: CanDeploy is false."); - return false; - } - - return true; - } - } -} diff --git a/source/TestAdapter/Services/TestSource.cs b/source/TestAdapter/Services/TestSource.cs deleted file mode 100644 index d109d5e..0000000 --- a/source/TestAdapter/Services/TestSource.cs +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using System.Collections.Generic; - using System.Reflection; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - - /// - /// This platform service is responsible for any data or operations to validate - /// the test sources provided to the adapter. - /// - public class TestSource : ITestSource - { - /// - /// Gets the set of valid extensions for sources targeting this platform. - /// - public IEnumerable ValidSourceExtensions - { - get - { - // Since desktop Platform service would also discover other platform tests on dekstop, - // this extension list needs to be updated with all platforms supported file extensions. - return new List - { - ".dll", - ".exe" - }; - } - } - - /// - /// Verifies if the assembly provided is referenced by the source. - /// - /// The assembly name. - /// The source. - /// True if the assembly is referenced. - public bool IsAssemblyReferenced(AssemblyName assemblyName, string source) - { - // This loads the dll in a different app domain. We can optimize this to load in the current domain since this code ould be run in a new app domain anyway. - bool? utfReference = AssemblyHelper.DoesReferencesAssembly(source, assemblyName); - - // If no reference to UTF don't run discovery. Take conservative approach. If not able to find proceed with discovery. - if (utfReference.HasValue && utfReference.Value == false) - { - return false; - } - - return true; - } - - /// - /// Gets the set of sources (dll's/exe's) that contain tests. If a source is a package(appx), return the file(dll/exe) that contains tests from it. - /// - /// Sources given to the adapter. - /// Sources that contains tests. . - public IEnumerable GetTestSources(IEnumerable sources) - { - return sources; - } - } -} diff --git a/source/TestAdapter/Services/TestSourceHost.cs b/source/TestAdapter/Services/TestSourceHost.cs deleted file mode 100644 index c3303ee..0000000 --- a/source/TestAdapter/Services/TestSourceHost.cs +++ /dev/null @@ -1,376 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities; - - /// - /// A host that loads the test source.This can be in isolation for desktop using an AppDomain or just loading the source in the current context. - /// - public class TestSourceHost : ITestSourceHost - { - /// - /// Child AppDomain used to discover/execute tests - /// - private AppDomain domain; - - /// - /// Assembly resolver used in the current app-domain - /// - private AssemblyResolver parentDomainAssemblyResolver; - - /// - /// Assembly resolver used in the new child app-domain created for discovery/execution - /// - private AssemblyResolver childDomainAssemblyResolver; - - /// - /// Determines whether child-appdomain needs to be created based on DisableAppDomain Flag set in runsettings - /// - private bool isAppDomainCreationDisabled; - - private string sourceFileName; - private IRunSettings runSettings; - private IFrameworkHandle frameworkHandle; - - private string currentDirectory = null; - private IAppDomain appDomain; - - private string targetFrameworkVersion; - - /// - /// Initializes a new instance of the class. - /// - /// The source file name. - /// The run-settings provided for this session. - /// The handle to the test platform. - public TestSourceHost( - string sourceFileName, - IRunSettings runSettings, - IFrameworkHandle frameworkHandle - ) - : this(sourceFileName, runSettings, frameworkHandle, new AppDomainWrapper()) - { - } - - internal TestSourceHost( - string sourceFileName, - IRunSettings runSettings, - IFrameworkHandle frameworkHandle, - IAppDomain appDomain - ) - { - this.sourceFileName = sourceFileName; - this.runSettings = runSettings; - this.frameworkHandle = frameworkHandle; - - this.appDomain = appDomain; - - // Set the environment context. - this.SetContext(sourceFileName); - - // Set isAppDomainCreationDisabled flag - // this has to be always true because we need an app domain to load nanoFramework assemblies - this.isAppDomainCreationDisabled = false; //Settings != null) && MSTestAdapterSettings.IsAppDomainCreationDisabled(this.runSettings.SettingsXml); - } - - internal AppDomain AppDomain - { - get - { - return this.domain; - } - } - - /// - /// Setup the isolation host. - /// - public void SetupHost() - { - List resolutionPaths = this.GetResolutionPaths(this.sourceFileName, true);// VSInstallationUtilities.IsCurrentProcessRunningInPortableMode()); - - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("DesktopTestSourceHost.SetupHost(): Creating assembly resolver with resolution paths {0}.", string.Join(",", resolutionPaths.ToArray())); - } - - // Case when DisableAppDomain setting is present in runsettings and no child-appdomain needs to be created - if (this.isAppDomainCreationDisabled) - { - this.parentDomainAssemblyResolver = new AssemblyResolver(resolutionPaths); - this.AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(this.parentDomainAssemblyResolver, Path.GetDirectoryName(this.sourceFileName)); - } - - // Create child-appdomain and set assembly resolver on it - else - { - // Setup app-domain - var appDomainSetup = new AppDomainSetup(); - this.targetFrameworkVersion = this.GetTargetFrameworkVersionString(this.sourceFileName); - AppDomainUtilities.SetAppDomainFrameworkVersionBasedOnTestSource(appDomainSetup, this.targetFrameworkVersion); - - // Temporarily set appbase to the location from where adapter should be picked up from. We will later reset this to test source location - // once adapter gets loaded in the child app domain. - appDomainSetup.ApplicationBase = Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location); - - var configFile = this.GetConfigFileForTestSource(this.sourceFileName); - AppDomainUtilities.SetConfigurationFile(appDomainSetup, configFile); - - EqtTrace.Info("DesktopTestSourceHost.SetupHost(): Creating app-domain for source {0} with application base path {1}.", this.sourceFileName, appDomainSetup.ApplicationBase); - - string domainName = string.Format("TestSourceHost: Enumerating source ({0})", this.sourceFileName); - this.domain = this.appDomain.CreateDomain(domainName, null, appDomainSetup); - - // Load objectModel before creating assembly resolver otherwise in 3.5 process, we run into a recurive assembly resolution - // which is trigged by AppContainerUtilities.AttachEventToResolveWinmd method. - EqtTrace.SetupRemoteEqtTraceListeners(this.domain); - - // Add an assembly resolver in the child app-domain... - Type assemblyResolverType = typeof(AssemblyResolver); - - EqtTrace.Info("DesktopTestSourceHost.SetupHost(): assemblyenumerator location: {0} , fullname: {1} ", assemblyResolverType.Assembly.Location, assemblyResolverType.FullName); - - var resolver = AppDomainUtilities.CreateInstance( - this.domain, - assemblyResolverType, - new object[] { resolutionPaths }); - - EqtTrace.Info( - "DesktopTestSourceHost.SetupHost(): resolver type: {0} , resolve type assembly: {1} ", - resolver.GetType().FullName, - resolver.GetType().Assembly.Location); - - this.childDomainAssemblyResolver = (AssemblyResolver)resolver; - - this.AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(this.childDomainAssemblyResolver, Path.GetDirectoryName(this.sourceFileName)); - } - } - - /// - /// Creates an instance of a given type in the test source host. - /// - /// The type that needs to be created in the host. - /// The arguments to pass to the constructor. - /// This array of arguments must match in number, order, and type the parameters of the constructor to invoke. - /// Pass in null for a constructor with no arguments. - /// - /// An instance of the type created in the host. - /// If a type is to be created in isolation then it needs to be a MarshalByRefObject. - public object CreateInstanceForType( - Type type, - object[] args - ) - { - // Honour DisableAppDomain setting if it is present in runsettings - if (this.isAppDomainCreationDisabled) - { - return Activator.CreateInstance(type, args); - } - - return AppDomainUtilities.CreateInstance(this.domain, type, args); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (this.parentDomainAssemblyResolver != null) - { - this.parentDomainAssemblyResolver.Dispose(); - this.parentDomainAssemblyResolver = null; - } - - if (this.childDomainAssemblyResolver != null) - { - this.childDomainAssemblyResolver.Dispose(); - this.childDomainAssemblyResolver = null; - } - - if (this.domain != null) - { - try - { - this.appDomain.Unload(this.domain); - } - catch (Exception exception) - { - // This happens usually when a test spawns off a thread and fails to clean it up. - EqtTrace.Error("DesktopTestSourceHost.Dispose(): The app domain running tests could not be unloaded. Exception: {0}", exception); - - if (this.frameworkHandle != null) - { - // Let the test platform know that it should tear down the test host process - // since we we have issues in unloading appdomain. We do so to avoid any assembly locking issues. - this.frameworkHandle.EnableShutdownAfterTestRun = true; - - EqtTrace.Verbose("DesktopTestSourceHost.Dispose(): Notifying the test platform that the test host process should be shut down because the app domain running tests could not be unloaded successfully."); - } - } - - this.domain = null; - } - - this.ResetContext(); - - GC.SuppressFinalize(this); - } - - /// - /// Updates child-domain's appbase to point to test source location. - /// - public void UpdateAppBaseToTestSourceLocation() - { - // Simply return if no child-appdomain was created - if (this.isAppDomainCreationDisabled) - { - return; - } - - // After adapter has been loaded, reset child-appdomains appbase. - this.domain.SetData("APPBASE", Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location)); - - EqtTrace.Info("DesktopTestSourceHost.UpdateAppBaseToTestSourceLocation(): Updating domain's appbase path for source {0} to {1}.", this.sourceFileName, this.domain.SetupInformation.ApplicationBase); - } - - /// - /// Gets the probing paths to load the test assembly dependencies. - /// - /// - /// The source File Name. - /// - /// - /// True if running in portable mode else false. - /// - /// - /// A list of path. - /// - internal virtual List GetResolutionPaths( - string sourceFileName, - bool isPortableMode - ) - { - List resolutionPaths = new List(); - - //Add path of test assembly in resolution path. - resolutionPaths.Add(Path.GetDirectoryName(sourceFileName)); - - // Adding adapter folder to resolution paths - if (!resolutionPaths.Contains(Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location))) - { - resolutionPaths.Add(Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location)); - } - - // Adding TestPlatform folder to resolution paths - if (!resolutionPaths.Contains(Path.GetDirectoryName(typeof(AssemblyHelper).Assembly.Location))) - { - resolutionPaths.Add(Path.GetDirectoryName(typeof(AssemblyHelper).Assembly.Location)); - } - - return resolutionPaths; - } - - internal virtual string GetTargetFrameworkVersionString( - string sourceFileName - ) - { - return AppDomainUtilities.GetTargetFrameworkVersionString(sourceFileName); - } - - private string GetConfigFileForTestSource( - string sourceFileName - ) - { - return new DeploymentUtility().GetConfigFile(sourceFileName); - } - - /// - /// Sets context required for running tests. - /// - /// - /// source parameter used for setting context - /// - private void SetContext( - string source - ) - { - if (string.IsNullOrEmpty(source)) - { - return; - } - - Exception setWorkingDirectoryException = null; - this.currentDirectory = Environment.CurrentDirectory; - - try - { - Environment.CurrentDirectory = Path.GetDirectoryName(source); - EqtTrace.Info("TestExecutor: Changed the working directory to {0}", Environment.CurrentDirectory); - } - catch (IOException ex) - { - setWorkingDirectoryException = ex; - } - catch (System.Security.SecurityException ex) - { - setWorkingDirectoryException = ex; - } - - if (setWorkingDirectoryException != null) - { - EqtTrace.Error("TestExecutor.SetWorkingDirectory: Failed to set the working directory to '{0}'. {1}", Path.GetDirectoryName(source), setWorkingDirectoryException); - } - } - - /// - /// Resets the context as it was before calling SetContext() - /// - private void ResetContext() - { - if (!string.IsNullOrEmpty(this.currentDirectory)) - { - Environment.CurrentDirectory = this.currentDirectory; - } - } - - private void AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver( - AssemblyResolver assemblyResolver, - string baseDirectory - ) - { - // Check if user specified any adapter settings - MSTestAdapterSettings adapterSettings = MSTestSettingsProvider.Settings; - - if (adapterSettings != null) - { - try - { - var additionalSearchDirectories = adapterSettings.GetDirectoryListWithRecursiveProperty(baseDirectory); - if (additionalSearchDirectories?.Count > 0) - { - assemblyResolver.AddSearchDirectoriesFromRunSetting(additionalSearchDirectories); - } - } - catch (Exception exception) - { - EqtTrace.Error( - "DesktopTestSourceHost.AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(): Exception hit while trying to set assembly resolver for domain. Exception : {0} \n Message : {1}", - exception, - exception.Message); - } - } - } - - } -} diff --git a/source/TestAdapter/Services/TraceListener.cs b/source/TestAdapter/Services/TraceListener.cs deleted file mode 100644 index c9ac406..0000000 --- a/source/TestAdapter/Services/TraceListener.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using System.Diagnostics; - using System.IO; - - /// - /// Internal implementation of TraceListener exposed to the user. - /// The virtual operations of the TraceListener are implemented here - /// like Close(), Dispose() etc. - /// - public class TraceListenerWrapper : TextWriterTraceListener, ITraceListener - { - // Summary: - // Initializes a new instance of an object that derives from System.Diagnostics.TextWriterTraceListener - // Class and initializes TextWriterTraceListener object using the specified writer as recipient of the - // tracing or debugging output. - public TraceListenerWrapper(TextWriter textWriter) - : base(textWriter) - { - } - - // Summary: - // Wrapper over Dispose() of System.Diagnostics.TextWriterTraceListener object - public new void Dispose() - { - base.Dispose(); - } - - // Summary: - // Gets the text writer of System.Diagnostics.TextWriterTraceListener.Writer - // that receives the tracing or debugging output. - public TextWriter GetWriter() - { - return this.Writer; - } - } -} diff --git a/source/TestAdapter/Services/TraceListenerManager.cs b/source/TestAdapter/Services/TraceListenerManager.cs deleted file mode 100644 index c7ae693..0000000 --- a/source/TestAdapter/Services/TraceListenerManager.cs +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.Interface; - using System; - using System.Diagnostics; - using System.IO; - -#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName - - /// - /// Internal implementation of TraceListenerManager exposed to the user. - /// Responsible for performing Add(), Remove(), Close(), Dispose() operations on traceListener object. - /// - public class TraceListenerManager : ITraceListenerManager - { - /// - /// Original output stream - /// - private TextWriter origStdOut; - - /// - /// Original error stream - /// - private TextWriter origStdErr; - - /// - /// Initializes a new instance of the class. - /// - /// A writer instance to log output messages. - /// A writer instance to log error messages. - public TraceListenerManager(TextWriter outputWriter, TextWriter errorWriter) - { - this.origStdOut = Console.Out; - this.origStdErr = Console.Error; - - // Update the output/error streams with redirected streams - Console.SetOut(outputWriter); - Console.SetError(errorWriter); - } - - /// - /// Adds the arguement traceListener object to System.Diagnostics.TraceListenerCollection. - /// - /// The trace listener instance. - public void Add(ITraceListener traceListener) - { - // NOTE: Listeners will not get Debug events in dotnet core due to platform limitation. - // Refer https://github.com/Microsoft/testfx/pull/218 for more details. - Trace.Listeners.Add(traceListener as TextWriterTraceListener); - } - - /// - /// Removes the arguement traceListener object from System.Diagnostics.TraceListenerCollection. - /// - /// The trace listener instance. - public void Remove(ITraceListener traceListener) - { - Trace.Listeners.Remove(traceListener as TextWriterTraceListener); - } - - /// - /// Wrapper over Dispose() of ITraceListener. - /// Also resets the standard output/error streams. - /// - /// The trace listener instance. - public void Dispose(ITraceListener traceListener) - { - traceListener.Dispose(); - Console.SetOut(this.origStdOut); - Console.SetError(this.origStdErr); - } - } - -#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName -} diff --git a/source/TestAdapter/Settings.cs b/source/TestAdapter/Settings.cs new file mode 100644 index 0000000..af46540 --- /dev/null +++ b/source/TestAdapter/Settings.cs @@ -0,0 +1,97 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using System; +using System.Xml; + +namespace nanoFramework.TestPlatform.TestAdapter +{ + /// + /// Settings for the nanoFramweork tests + /// + public class Settings + { + /// + /// How long maximum the tests can run. + /// + /// Make sure to adjust the default value in .runsettings first + public int TestTimeOutSeconds { get; set; } = 60; + + /// + /// True to run the tests on real hardware + /// + public bool IsRealHarware { get; set; } = false; + + /// + /// The serial port number to run the tests on a real hardware + /// + public string RealHarwarePort { get; set; } = string.Empty; + + /// + /// Level of logging for test execution. + /// + public LoggingLevel Logging { get; set; } = LoggingLevel.None; + + /// + /// Get settings from an XML node + /// + /// + /// + public static Settings Extract(XmlNode node) + { + Settings settings = new Settings(); + + if (node.Name == TestsConstants.SettingsName) + { + var timeout = node.SelectSingleNode(nameof(TestTimeOutSeconds))?.FirstChild; + if (timeout != null && timeout.NodeType == XmlNodeType.Text) + { + if (int.TryParse(timeout.Value, out int timeoutNum)) + { + settings.TestTimeOutSeconds = timeoutNum; + } + } + + var isrealhard = node.SelectSingleNode(nameof(IsRealHarware))?.FirstChild; + if (isrealhard != null && isrealhard.NodeType == XmlNodeType.Text) + { + settings.IsRealHarware = isrealhard.Value.ToLower() == "true" ? true : false; + } + + var realhardport = node.SelectSingleNode(nameof(RealHarwarePort))?.FirstChild; + if (realhardport != null && realhardport.NodeType == XmlNodeType.Text) + { + settings.RealHarwarePort = realhardport.Value; + } + + var loggingLevel = node.SelectSingleNode(nameof(Logging))?.FirstChild; + if (loggingLevel != null && loggingLevel.NodeType == XmlNodeType.Text) + { + if (Enum.TryParse(loggingLevel.Value, out LoggingLevel logging)) + { + settings.Logging = logging; + } + } + } + + return settings; + } + + /// + /// The log level + /// + public enum LoggingLevel + { + None = 0, + + Detailed = 1, + + Verbose = 2, + + Error = 3 + } + } +} diff --git a/source/TestAdapter/SettingsProvider.cs b/source/TestAdapter/SettingsProvider.cs new file mode 100644 index 0000000..8dc4a9c --- /dev/null +++ b/source/TestAdapter/SettingsProvider.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using System.Xml; + +namespace nanoFramework.TestPlatform.TestAdapter +{ + /// + /// Setting Provider class + /// + [SettingsName(TestsConstants.SettingsName)] + public class SettingsProvider : ISettingsProvider + { + #region Properties + + /// + /// Settings + /// + public Settings Settings { get; private set; } + + #endregion // Properties + + /// + /// Loading the XML elements + /// + /// + public void Load(XmlReader reader) + { + var xml = new XmlDocument(); + reader.Read(); + Settings = Settings.Extract(xml.ReadNode(reader)); + } + } +} diff --git a/source/TestAdapter/TestContext.cs b/source/TestAdapter/TestContext.cs deleted file mode 100644 index d6a53f3..0000000 --- a/source/TestAdapter/TestContext.cs +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - - /// - /// TestContext class. This class should be fully abstract and not contain any - /// members. The adapter will implement the members. Users in the framework should - /// only access this via a well-defined interface. - /// - public abstract class TestContext - { - /// - /// Gets test properties for a test. - /// - public abstract IDictionary Properties { get; } - - /// - /// Gets Fully-qualified name of the class containing the test method currently being executed - /// - /// - /// This property can be useful in attributes derived from ExpectedExceptionBaseAttribute. - /// Those attributes have access to the test context, and provide messages that are included - /// in the test results. Users can benefit from messages that include the fully-qualified - /// class name in addition to the name of the test method currently being executed. - /// - public virtual string FullyQualifiedTestClassName - { - get - { - return this.GetProperty("FullyQualifiedTestClassName"); - } - } - - /// - /// Gets the Name of the test method currently being executed - /// - public virtual string TestName - { - get - { - return this.GetProperty("TestName"); - } - } - - /// - /// Gets the current test outcome. - /// - public virtual UnitTestOutcome CurrentTestOutcome => UnitTestOutcome.Unknown; - - /// - /// Used to write trace messages while the test is running - /// - /// formatted message string - public abstract void WriteLine(string message); - - /// - /// Used to write trace messages while the test is running - /// - /// format string - /// the arguments - public abstract void WriteLine(string format, params object[] args); - - private T GetProperty(string name) - where T : class - { - object o; - - if (!this.Properties.TryGetValue(name, out o)) - { - return null; - } - - if (o != null && !(o is T)) - { - // If o has a value, but it's not the right type - Debug.Assert(false, "How did an invalid value get in here?"); - throw new InvalidCastException(string.Format(CultureInfo.CurrentCulture, Resource.InvalidPropertyType, name, o.GetType(), typeof(T))); - } - - return (T)o; - } - } -} diff --git a/source/TestAdapter/TestDiscoverer.cs b/source/TestAdapter/TestDiscoverer.cs deleted file mode 100644 index d808635..0000000 --- a/source/TestAdapter/TestDiscoverer.cs +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Discovery; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.IO; - using System.Linq; - - [FileExtension(".exe")] - [FileExtension(".dll")] - [DefaultExecutorUri(Constants.NFExecutorUriString)] - public class TestDiscoverer : ITestDiscoverer - { - /// - /// ITestDiscover, Given a list of test sources this method pulls out the test cases - /// - /// List of test sources passed from client (Client can be VS or command line) - /// Context and runSettings for current run. Discoverer pulls out the tests based on current context - /// Used to relay messages to registered loggers - /// Callback used to notify client upon discovery of test cases - public void DiscoverTests( - IEnumerable sources, - IDiscoveryContext discoveryContext, - IMessageLogger logger, - ITestCaseDiscoverySink discoverySink) - { - ValidateArg.NotNull(sources, "sources"); - ValidateArg.NotNull(discoverySink, "discoverySink"); - ValidateArg.NotNull(logger, "logger"); - - // Populate the runsettings. - try - { - MSTestSettings.PopulateSettings(discoveryContext); - } - catch (AdapterSettingsException ex) - { - logger.SendMessage(TestMessageLevel.Error, ex.Message); - return; - } - - new UnitTestDiscoverer().DiscoverTests(sources, logger, discoverySink, discoveryContext); - } - - /// - /// Verifies if the sources are valid for the target platform. - /// - /// The test sources - /// Sources cannot be null. - /// True if the source has a valid extension for the current platform. - internal bool AreValidSources(IEnumerable sources) - { - // ValidSourceExtensions is always expected to return a non-null list. - return - sources.Any( - source => - PlatformServiceProvider.Instance.TestSource.ValidSourceExtensions.Any( - extension => - string.Compare(Path.GetExtension(source), extension, StringComparison.OrdinalIgnoreCase) == 0)); - } - } -} diff --git a/source/TestAdapter/TestExecutor.cs b/source/TestAdapter/TestExecutor.cs deleted file mode 100644 index 6dcce1d..0000000 --- a/source/TestAdapter/TestExecutor.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Net.NetworkInformation; - using System.Runtime.InteropServices; - using System.Text; - using System.Threading; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Execution; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using Newtonsoft.Json; - - [ExtensionUri(TestAdapter.Constants.NFExecutorUriString)] - public class TestExecutor : ITestExecutor - { - /// - /// Token for cancelling the test run. - /// - private TestRunCancellationToken cancellationToken = null; - - /// - /// Gets or sets the ms test execution manager. - /// - public TestExecutionManager TestExecutionManager { get; protected set; } - - /// - /// Gets discoverer used for validating the sources. - /// - private TestDiscoverer TestDiscoverer { get; } - - /// - /// Initializes a new instance of the class. - /// - public TestExecutor() - { - TestExecutionManager = new TestExecutionManager(); - TestDiscoverer = new TestDiscoverer(); - } - - public void RunTests( - IEnumerable tests, - IRunContext runContext, - IFrameworkHandle frameworkHandle) - { - ValidateArg.NotNull(frameworkHandle, "frameworkHandle"); - ValidateArg.NotNullOrEmpty(tests, "tests"); - - if (!this.TestDiscoverer.AreValidSources(from test in tests select test.Source)) - { - throw new NotSupportedException(); - } - - // Populate the runsettings. - try - { - MSTestSettings.PopulateSettings(runContext); - } - catch (AdapterSettingsException ex) - { - frameworkHandle.SendMessage(TestMessageLevel.Error, ex.Message); - return; - } - - this.cancellationToken = new TestRunCancellationToken(); - this.TestExecutionManager.RunTests(tests, runContext, frameworkHandle, this.cancellationToken); - this.cancellationToken = null; - } - - - public void RunTests( - IEnumerable sources, - IRunContext runContext, - IFrameworkHandle frameworkHandle) - { - ValidateArg.NotNull(frameworkHandle, "frameworkHandle"); - ValidateArg.NotNullOrEmpty(sources, "sources"); - - if (!this.TestDiscoverer.AreValidSources(sources)) - { - throw new NotSupportedException(); - } - - // Populate the runsettings. - try - { - MSTestSettings.PopulateSettings(runContext); - } - catch (AdapterSettingsException ex) - { - frameworkHandle.SendMessage(TestMessageLevel.Error, ex.Message); - return; - } - - sources = PlatformServiceProvider.Instance.TestSource.GetTestSources(sources); - this.cancellationToken = new TestRunCancellationToken(); - this.TestExecutionManager.RunTests(sources, runContext, frameworkHandle, this.cancellationToken); - - this.cancellationToken = null; - } - - public void Cancel() - { - this.cancellationToken?.Cancel(); - } - } -} diff --git a/source/TestAdapter/TestMethodFilter.cs b/source/TestAdapter/TestMethodFilter.cs deleted file mode 100644 index 65c4393..0000000 --- a/source/TestAdapter/TestMethodFilter.cs +++ /dev/null @@ -1,153 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - - internal class TestMethodFilter - { - /// - /// Supported properties for filtering - /// - private readonly Dictionary supportedProperties; - - internal TestMethodFilter() - { - this.supportedProperties = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - [Constants.TestCategoryProperty.Label] = Constants.TestCategoryProperty, - [Constants.PriorityProperty.Label] = Constants.PriorityProperty, - [TestCaseProperties.FullyQualifiedName.Label] = TestCaseProperties.FullyQualifiedName, - [TestCaseProperties.DisplayName.Label] = TestCaseProperties.DisplayName, - [Constants.TestClassNameProperty.Label] = Constants.TestClassNameProperty - }; - } - - /// - /// Returns ITestCaseFilterExpression for TestProperties supported by adapter. - /// - /// The current context of the run. - /// Handler to report test messages/start/end and results. - /// Indicates that the filter is unsupported/has an error. - /// A filter expression. - internal ITestCaseFilterExpression GetFilterExpression( - IDiscoveryContext context, - IMessageLogger logger, - out bool filterHasError - ) - { - filterHasError = false; - ITestCaseFilterExpression filter = null; - if (context != null) - { - try - { - filter = (context is IRunContext) ? this.GetTestCaseFilterFromRunContext(context as IRunContext) : this.GetTestCaseFilterFromDiscoveryContext(context, logger); - } - catch (TestPlatformFormatException ex) - { - filterHasError = true; - logger.SendMessage(TestMessageLevel.Error, ex.Message); - } - } - - return filter; - } - - /// - /// Provides TestProperty for property name 'propertyName' as used in filter. - /// - /// The property name. - /// a TestProperty instance. - internal TestProperty PropertyProvider( - string propertyName - ) - { - TestProperty testProperty; - this.supportedProperties.TryGetValue(propertyName, out testProperty); - Debug.Assert(testProperty != null, "Invalid property queried"); - return testProperty; - } - - /// - /// Provides value of TestProperty corresponding to property name 'propertyName' as used in filter. - /// - /// The current test case. - /// Property name. - /// The property value. - internal object PropertyValueProvider( - TestCase currentTest, - string propertyName - ) - { - if (currentTest != null && propertyName != null) - { - TestProperty testProperty; - if (this.supportedProperties.TryGetValue(propertyName, out testProperty)) - { - // Test case might not have defined this property. In that case GetPropertyValue() - // would return default value. For filtering, if property is not defined return null. - if (currentTest.Properties.Contains(testProperty)) - { - return currentTest.GetPropertyValue(testProperty); - } - } - } - - return null; - } - - /// - /// Gets filter expression from run context. - /// - /// Run context - /// Filter expression. - private ITestCaseFilterExpression GetTestCaseFilterFromRunContext( - IRunContext context - ) - { - return context.GetTestCaseFilter(this.supportedProperties.Keys, this.PropertyProvider); - } - - /// - /// Gets filter expression from discovery context. - /// - /// Discovery context - /// The logger to log exception messages too. - /// Filter expression. - private ITestCaseFilterExpression GetTestCaseFilterFromDiscoveryContext( - IDiscoveryContext context, - IMessageLogger logger - ) - { - try - { - // GetTestCaseFilter is present in DiscoveryContext but not in IDiscoveryContext interface. - MethodInfo methodGetTestCaseFilter = context.GetType().GetRuntimeMethod("GetTestCaseFilter", new[] { typeof(IEnumerable), typeof(Func) }); - return (ITestCaseFilterExpression)methodGetTestCaseFilter?.Invoke(context, new object[] { this.supportedProperties.Keys, (Func)this.PropertyProvider }); - } - catch (Exception ex) - { - if (ex is TargetInvocationException) - { - throw ex.InnerException; - } - - logger.SendMessage(TestMessageLevel.Warning, ex.Message); - } - - return null; - } - } -} \ No newline at end of file diff --git a/source/TestAdapter/TestObjectHelper.cs b/source/TestAdapter/TestObjectHelper.cs new file mode 100644 index 0000000..1ca6797 --- /dev/null +++ b/source/TestAdapter/TestObjectHelper.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using System.IO; +using System.Reflection; + +namespace nanoFramework.TestPlatform.TestAdapter +{ + /// + /// Test object helper to find path + /// + public static class TestObjectHelper + { + /// + /// Get the execution directory for the nanoCLR.exe + /// + /// + public static string GetNanoClrLocation() + { + var thisAssemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var nanoClrFullPath = Path.Combine(thisAssemblyDir, "nanoFramework.nanoCLR.exe"); + + return nanoClrFullPath; + } + } +} diff --git a/source/TestAdapter/TestSettings.cs b/source/TestAdapter/TestSettings.cs deleted file mode 100644 index 084b6ed..0000000 --- a/source/TestAdapter/TestSettings.cs +++ /dev/null @@ -1,508 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter -{ - using System; - using System.Globalization; - using System.IO; - using System.Xml; - using System.Xml.Linq; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; - using nanoFramework.TestPlatform.MSTest.TestAdapter.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - - /// - /// Adapter Settings for the run - /// - [Serializable] - public class TestSettings - { - /// - /// The settings name. - /// - public const string SettingsName = "MSTest"; - - /// - /// The alias to the default settings name. - /// - public const string SettingsNameAlias = "MSTestV2"; - - private const string ParallelizeSettingsName = "Parallelize"; - - /// - /// Member variable for Adapter settings - /// - private static TestSettings currentSettings; - - /// - /// Member variable for RunConfiguration settings - /// - private static RunConfigurationSettings runConfigurationSettings; - - /// - /// Initializes a new instance of the class. - /// - public TestSettings() - { - this.CaptureDebugTraces = true; - this.MapInconclusiveToFailed = false; - this.EnableBaseClassTestMethodsFromOtherAssemblies = true; - this.ForcedLegacyMode = false; - this.TestSettingsFile = null; - this.DisableParallelization = false; - this.TestTimeout = 0; - } - - /// - /// Gets the current settings. - /// - public static TestSettings CurrentSettings - { - get - { - if (currentSettings == null) - { - currentSettings = new TestSettings(); - } - - return currentSettings; - } - - private set - { - currentSettings = value; - } - } - - /// - /// Gets the current configuration settings. - /// - public static RunConfigurationSettings RunConfigurationSettings - { - get - { - if (runConfigurationSettings == null) - { - runConfigurationSettings = new RunConfigurationSettings(); - } - - return runConfigurationSettings; - } - - private set - { - runConfigurationSettings = value; - } - } - - /// - /// Gets a value indicating whether capture debug traces. - /// - public bool CaptureDebugTraces { get; private set; } - - /// - /// Gets a value indicating whether user wants the adapter to run in legacy mode or not. - /// Default is False. - /// - public bool ForcedLegacyMode { get; private set; } - - /// - /// Gets the path to settings file. - /// - public string TestSettingsFile { get; private set; } - - /// - /// Gets a value indicating whether an inconclusive result be mapped to failed test. - /// - public bool MapInconclusiveToFailed { get; private set; } - - /// - /// Gets a value indicating whether to enable discovery of test methods from base classes in a different assembly from the inheriting test class. - /// - public bool EnableBaseClassTestMethodsFromOtherAssemblies { get; private set; } - - /// - /// Gets the number of threads/workers to be used for parallelization. - /// - public int? ParallelizationWorkers { get; private set; } - - /// - /// Gets the scope of parallelization. - /// - public ExecutionScope? ParallelizationScope { get; private set; } - - /// - /// Gets a value indicating whether the assembly can be parallelized. - /// - /// - /// This is also re-used to disable parallelization on format errors - /// - public bool DisableParallelization { get; private set; } - - /// - /// Gets specified global test case timeout - /// - public int TestTimeout { get; private set; } - - /// - /// Populate settings based on existing settings object. - /// - /// The existing settings object. - public static void PopulateSettings(TestSettings settings) - { - CurrentSettings.CaptureDebugTraces = settings.CaptureDebugTraces; - CurrentSettings.ForcedLegacyMode = settings.ForcedLegacyMode; - CurrentSettings.TestSettingsFile = settings.TestSettingsFile; - CurrentSettings.MapInconclusiveToFailed = settings.MapInconclusiveToFailed; - CurrentSettings.EnableBaseClassTestMethodsFromOtherAssemblies = settings.EnableBaseClassTestMethodsFromOtherAssemblies; - CurrentSettings.ParallelizationWorkers = settings.ParallelizationWorkers; - CurrentSettings.ParallelizationScope = settings.ParallelizationScope; - CurrentSettings.DisableParallelization = settings.DisableParallelization; - CurrentSettings.TestTimeout = settings.TestTimeout; - } - - /// - /// Populate adapter settings from the context - /// - /// - /// The discovery context that contains the runsettings. - /// - public static void PopulateSettings(IDiscoveryContext context) - { - RunConfigurationSettings = RunConfigurationSettings.PopulateSettings(context); - - if (context == null || context.RunSettings == null || string.IsNullOrEmpty(context.RunSettings.SettingsXml)) - { - // This will contain default adapter settings - CurrentSettings = new TestSettings(); - return; - } - - var aliasSettings = GetSettings(context.RunSettings.SettingsXml, SettingsNameAlias); - - // If a user specifies MSTestV2 in the runsettings, then prefer that over the v1 settings. - if (aliasSettings != null) - { - CurrentSettings = aliasSettings; - } - else - { - var settings = GetSettings(context.RunSettings.SettingsXml, SettingsName); - - if (settings != null) - { - CurrentSettings = settings; - } - else - { - CurrentSettings = new TestSettings(); - } - } - - SetGlobalSettings(context.RunSettings.SettingsXml, CurrentSettings); - } - - /// - /// Get the MSTestV1 adapter settings from the context - /// - /// The logger for messages. - /// Returns true if test settings is provided.. - public static bool IsLegacyScenario(IMessageLogger logger) - { - if (!string.IsNullOrEmpty(CurrentSettings.TestSettingsFile)) - { - logger.SendMessage(TestMessageLevel.Warning, Resource.LegacyScenariosNotSupportedWarning); - return true; - } - - return false; - } - - /// - /// Gets the adapter specific settings from the xml. - /// - /// The xml with the settings passed from the test platform. - /// The name of the adapter settings to fetch - Its either MSTest or MSTestV2 - /// The settings if found. Null otherwise. - internal static TestSettings GetSettings(string runsettingsXml, string settingName) - { - using (var stringReader = new StringReader(runsettingsXml)) - { - XmlReader reader = XmlReader.Create(stringReader, XmlRunSettingsUtilities.ReaderSettings); - - // read to the fist child - XmlReaderUtilities.ReadToRootNode(reader); - reader.ReadToNextElement(); - - // Read till we reach nodeName element or reach EOF - while (!string.Equals(reader.Name, settingName, StringComparison.OrdinalIgnoreCase) - && - !reader.EOF) - { - reader.SkipToNextElement(); - } - - if (!reader.EOF) - { - // read nodeName element. - return ToSettings(reader.ReadSubtree()); - } - } - - return null; - } - - /// - /// Resets any settings loaded. - /// - internal static void Reset() - { - TestSettings.CurrentSettings = null; - TestSettings.RunConfigurationSettings = null; - } - - /// - /// Convert the parameter xml to TestSettings - /// - /// Reader to load the settings from. - /// An instance of the class - private static TestSettings ToSettings(XmlReader reader) - { - ValidateArg.NotNull(reader, "reader"); - - // Expected format of the xml is: - - // - // - // true - // false - // false - // 5000 - // - // 4 - // TestClass - // - // - // - // (or) - // - // - // true - // ..\..\Local.testsettings - // true - // - TestSettings settings = new TestSettings(); - - // Read the first element in the section which is either "MSTest"/"MSTestV2" - reader.ReadToNextElement(); - - if (!reader.IsEmptyElement) - { - reader.Read(); - - while (reader.NodeType == XmlNodeType.Element) - { - bool result; - string elementName = reader.Name.ToUpperInvariant(); - switch (elementName) - { - case "CAPTURETRACEOUTPUT": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.CaptureDebugTraces = result; - } - - break; - } - - case "ENABLEBASECLASSTESTMETHODSFROMOTHERASSEMBLIES": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.EnableBaseClassTestMethodsFromOtherAssemblies = result; - } - - break; - } - - case "FORCEDLEGACYMODE": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.ForcedLegacyMode = result; - } - - break; - } - - case "MAPINCONCLUSIVETOFAILED": - { - if (bool.TryParse(reader.ReadInnerXml(), out result)) - { - settings.MapInconclusiveToFailed = result; - } - - break; - } - - case "SETTINGSFILE": - { - string fileName = reader.ReadInnerXml(); - - if (!string.IsNullOrEmpty(fileName)) - { - settings.TestSettingsFile = fileName; - } - - break; - } - - case "PARALLELIZE": - { - SetParallelSettings(reader.ReadSubtree(), settings); - reader.SkipToNextElement(); - - break; - } - - case "TESTTIMEOUT": - { - if (int.TryParse(reader.ReadInnerXml(), out int testTimeout) && testTimeout > 0) - { - settings.TestTimeout = testTimeout; - } - - break; - } - - //default: - // { - // PlatformServiceProvider.Instance.SettingsProvider.Load(reader.ReadSubtree()); - // reader.SkipToNextElement(); - - // break; - // } - } - } - } - - return settings; - } - - private static void SetParallelSettings(XmlReader reader, TestSettings settings) - { - reader.Read(); - if (!reader.IsEmptyElement) - { - // Read the first child. - reader.Read(); - - while (reader.NodeType == XmlNodeType.Element) - { - string elementName = reader.Name.ToUpperInvariant(); - switch (elementName) - { - case "WORKERS": - { - var value = reader.ReadInnerXml(); - if (int.TryParse(value, out int parallelWorkers)) - { - if (parallelWorkers == 0) - { - settings.ParallelizationWorkers = Environment.ProcessorCount; - } - else if (parallelWorkers > 0) - { - settings.ParallelizationWorkers = parallelWorkers; - } - else - { - throw new AdapterSettingsException( - string.Format( - CultureInfo.CurrentCulture, - Resource.InvalidParallelWorkersValue, - value)); - } - } - else - { - throw new AdapterSettingsException( - string.Format( - CultureInfo.CurrentCulture, - Resource.InvalidParallelWorkersValue, - value)); - } - - break; - } - - case "SCOPE": - { - var value = reader.ReadInnerXml(); - if (Enum.TryParse(value, out ExecutionScope scope)) - { - settings.ParallelizationScope = scope; - } - else - { - throw new AdapterSettingsException( - string.Format( - CultureInfo.CurrentCulture, - Resource.InvalidParallelScopeValue, - value, - string.Join(", ", Enum.GetNames(typeof(ExecutionScope))))); - } - - break; - } - - default: - { - throw new AdapterSettingsException( - string.Format( - CultureInfo.CurrentCulture, - Resource.InvalidSettingsXmlElement, - ParallelizeSettingsName, - reader.Name)); - } - } - } - } - - // If any of these properties are not set, resort to the defaults. - if (!settings.ParallelizationWorkers.HasValue) - { - settings.ParallelizationWorkers = Environment.ProcessorCount; - } - - if (!settings.ParallelizationScope.HasValue) - { - settings.ParallelizationScope = ExecutionScope.ClassLevel; - } - } - - private static void SetGlobalSettings(string runsettingsXml, TestSettings settings) - { - var runconfigElement = XDocument.Parse(runsettingsXml)?.Element("RunSettings")?.Element("RunConfiguration"); - - if (runconfigElement == null) - { - return; - } - - var disableParallelizationString = runconfigElement.Element("DisableParallelization")?.Value; - if (bool.TryParse(disableParallelizationString, out bool disableParallelization)) - { - settings.DisableParallelization = disableParallelization; - } - } - } -} diff --git a/source/TestAdapter/TestsConstants.cs b/source/TestAdapter/TestsConstants.cs new file mode 100644 index 0000000..01624c9 --- /dev/null +++ b/source/TestAdapter/TestsConstants.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +namespace nanoFramework.TestPlatform.TestAdapter +{ + /// + /// Project wide constants + /// + public static class TestsConstants + { + /// + /// Executor name for nanoFrmaework + /// + public const string NanoExecutor = "executor://nanoFrameworkTestExecutor"; + + /// + /// Settings property XML node name + /// + public const string SettingsName = "nanoFrameworkAdapter"; + } +} diff --git a/source/TestAdapter/Utilities/AppDomainUtilities.cs b/source/TestAdapter/Utilities/AppDomainUtilities.cs deleted file mode 100644 index 5b21373..0000000 --- a/source/TestAdapter/Utilities/AppDomainUtilities.cs +++ /dev/null @@ -1,258 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.IO; - using System.Reflection; - - internal static class AppDomainUtilities - { - private const string ObjectModelVersionBuiltAgainst = "11.0.0.0"; - - private static Version defaultVersion = new Version(); - private static Version version10 = new Version("1.0"); - - private static XmlUtilities xmlUtilities = null; - - /// - /// Gets or sets the Xml Utilities instance. - /// - internal static XmlUtilities XmlUtilities - { - get - { - if (xmlUtilities == null) - { - xmlUtilities = new XmlUtilities(); - } - - return xmlUtilities; - } - - set - { - xmlUtilities = value; - } - } - - /// - /// Get target framework version string from the given dll - /// - /// - /// The path of the dll - /// - /// - /// Framework string - /// Todo: Need to add components/E2E tests to cover these scenarios. - /// - internal static string GetTargetFrameworkVersionString( - string testSourcePath - ) - { - AppDomainSetup appDomainSetup = new AppDomainSetup(); - - appDomainSetup.LoaderOptimization = LoaderOptimization.MultiDomainHost; - - AppDomainUtilities.SetConfigurationFile(appDomainSetup, new DeploymentUtility().GetConfigFile(testSourcePath)); - - if (File.Exists(testSourcePath)) - { - AppDomain appDomain = null; - - try - { - appDomain = AppDomain.CreateDomain("Framework Version String Domain", null, appDomainSetup); - - // Wire the eqttrace logs in this domain to the current domain. - EqtTrace.SetupRemoteEqtTraceListeners(appDomain); - - // Add an assembly resolver to resolve ObjectModel or any Test Platform dependencies. - // Not moving to IMetaDataImport APIs because the time taken for this operation is <20 ms and - // IMetaDataImport needs COM registration which is not a guarantee in Dev15. - var assemblyResolverType = typeof(AssemblyResolver); - - var resolutionPaths = new List{ Path.GetDirectoryName(typeof(TestCase).Assembly.Location) }; - resolutionPaths.Add(Path.GetDirectoryName(testSourcePath)); - - AppDomainUtilities.CreateInstance( - appDomain, - assemblyResolverType, - new object[] { resolutionPaths }); - - var assemblyLoadWorker = - (AssemblyLoadWorker)AppDomainUtilities.CreateInstance( - appDomain, - typeof(AssemblyLoadWorker), - null); - - return assemblyLoadWorker.GetTargetFrameworkVersionStringFromPath(testSourcePath); - } - catch (Exception exception) - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error(exception); - } - } - finally - { - if (appDomain != null) - { - AppDomain.Unload(appDomain); - } - } - } - - return string.Empty; - } - - /// - /// Set configuration file on the parameter appDomain. - /// - /// The app Domain Setup. - /// The test Source Config File. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - internal static void SetConfigurationFile( - AppDomainSetup appDomainSetup, - string testSourceConfigFile - ) - { - if (!string.IsNullOrEmpty(testSourceConfigFile)) - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("UnitTestAdapter: Using configuration file {0} to setup appdomain for test source {1}.", testSourceConfigFile, Path.GetFileNameWithoutExtension(testSourceConfigFile)); - } - - appDomainSetup.ConfigurationFile = Path.GetFullPath(testSourceConfigFile); - - try - { - // Add redirection of the built 11.0 Object Model assembly to the current version if that is not 11.0 - var currentVersionOfObjectModel = typeof(TestCase).Assembly.GetName().Version.ToString(); - if (!string.Equals(currentVersionOfObjectModel, ObjectModelVersionBuiltAgainst)) - { - var assemblyName = typeof(TestCase).Assembly.GetName(); - var configurationBytes = - XmlUtilities.AddAssemblyRedirection( - testSourceConfigFile, - assemblyName, - ObjectModelVersionBuiltAgainst, - assemblyName.Version.ToString()); - appDomainSetup.SetConfigurationBytes(configurationBytes); - } - } - catch (Exception ex) - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("Exception hit while adding binding redirects to test source config file. Exception : {0}", ex); - } - } - } - else - { - // Use the current domains configuration setting. - appDomainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; - } - } - - internal static object CreateInstance( - AppDomain appDomain, - Type type, - object[] arguments - ) - { - Debug.Assert(appDomain != null, "appDomain is null"); - Debug.Assert(type != null, "type is null"); - - var typeAssemblyLocation = type.Assembly.Location; - var fullFilePath = typeAssemblyLocation == null ? null : Path.Combine(appDomain.SetupInformation.ApplicationBase, Path.GetFileName(typeAssemblyLocation)); - - if (fullFilePath == null || File.Exists(fullFilePath)) - { - // If the assembly exists in the app base directory, load it from there itself. - // Even if it does not exist, Create the type in the default Load Context and let the CLR resolve the assembly path. - // This would load the assembly in the Default Load context. - return appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName, - false, - BindingFlags.Default, - null, - arguments, - null, - null); - } - else - { - // This means that the file is not present in the app base directory. Load it from Path instead. - // NOTE: We expect that all types that we are creating from here are types we know the location for. - // This would load the assembly in the Load-From context. - // While the above if condition is satisfied for most common cases, there could be a case where the adapter dlls - // do not get copied over to where the test assembly is, in which case we load them from where the parent AppDomain is picking them up from. - return appDomain.CreateInstanceFromAndUnwrap( - typeAssemblyLocation, - type.FullName, - false, - BindingFlags.Default, - null, - arguments, - null, - null); - } - } - - /// - /// Set the target framework for app domain as nanoFrameowrk - /// - /// AppdomainSetup for app domain creation - /// The target framework version of the test source. - internal static void SetAppDomainFrameworkVersionBasedOnTestSource( - AppDomainSetup setup, - string frameworkVersionString - ) - { - PropertyInfo pInfo = typeof(AppDomainSetup).GetProperty(Constants.TargetFrameworkName); - if (pInfo != null) - { - pInfo.SetValue(setup, frameworkVersionString, null); - } - } - - /// - /// Get the Version for the target framework version string - /// - /// Target framework string - /// Framework Version - internal static Version GetTargetFrameworkVersionFromVersionString( - string version - ) - { - try - { - if (version.Length > TestAdapter.Constants.DotNetnanoFrameWorkStringPrefix.Length + 1) - { - string versionPart = version.Substring(TestAdapter.Constants.DotNetnanoFrameWorkStringPrefix.Length + 1); - return new Version(versionPart); - } - } - catch (FormatException ex) - { - EqtTrace.Warning(string.Format("AppDomainUtilities.GetTargetFrameworkVersionFromVersionString: Could not create version object from version string '{0}' due to error '{1}':", version, ex.Message)); - } - - return defaultVersion; - } - } -} diff --git a/source/TestAdapter/Utilities/AppDomainWrapper.cs b/source/TestAdapter/Utilities/AppDomainWrapper.cs deleted file mode 100644 index 4c7a0d3..0000000 --- a/source/TestAdapter/Utilities/AppDomainWrapper.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using System; - using System.Security.Policy; - - /// - /// Abstraction over the AppDomain APIs. - /// - internal class AppDomainWrapper : IAppDomain - { - public AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup info) - { - return AppDomain.CreateDomain(friendlyName, securityInfo, info); - } - - public void Unload(AppDomain appDomain) - { - AppDomain.Unload(appDomain); - } - } -} diff --git a/source/TestAdapter/Utilities/AssemblyUtility.cs b/source/TestAdapter/Utilities/AssemblyUtility.cs deleted file mode 100644 index c52ac4b..0000000 --- a/source/TestAdapter/Utilities/AssemblyUtility.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Reflection; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.PlatformServices; - - /// - /// Utility for assembly specific functionality. - /// - internal class AssemblyUtility : IAssemblyUtility - { - private static Dictionary cultures; - private readonly string[] assemblyExtensions = new string[] { ".dll", ".exe" }; - - /// - /// Gets all supported culture names in Keys. The Values are always null. - /// - private static Dictionary Cultures - { - get - { - if (cultures == null) - { - cultures = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var info in CultureInfo.GetCultures(CultureTypes.AllCultures)) - { - cultures.Add(info.Name, null); - } - } - - return cultures; - } - } - - /// - /// Loads an assembly into the reflection-only context, given its path. - /// - /// The path of the file that contains the manifest of the assembly. - /// The loaded assembly. - public Assembly ReflectionOnlyLoadFrom(string assemblyPath) - { - return Assembly.ReflectionOnlyLoadFrom(assemblyPath); - } - - /// - /// Loads an assembly into the reflection-only context, given its display name. - /// - /// The display name of the assembly, as returned by the System.Reflection.AssemblyName.FullName property. - /// The loaded assembly. - public Assembly ReflectionOnlyLoad(string assemblyString) - { - return Assembly.ReflectionOnlyLoad(assemblyString); - } - - /// - /// Whether file extension is an assembly file extension. - /// Returns true for .exe and .dll, otherwise false. - /// - /// Extension containing leading dot, e.g. ".exe". - /// Path.GetExtension() returns extension with leading dot. - /// True if this is an assembly extension. - internal bool IsAssemblyExtension(string extensionWithLeadingDot) - { - foreach (var realExtension in this.assemblyExtensions) - { - if (string.Equals(extensionWithLeadingDot, realExtension, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - - /// - /// Determines whether given file is managed assembly. Does not load the assembly. Does not check file extension. - /// Performance: takes ~0.1 seconds on 2x CPU P4. - /// - /// The path to the assembly. - /// True if managed assembly. - internal bool IsAssembly(string path) - { - Debug.Assert(!string.IsNullOrEmpty(path), "path"); - try - { - // AssemblyName.GetAssemblyName: causes the file to be opened and closed, but the assembly is not added to this domain. - // Also if there are dependencies, they are never loaded. - AssemblyName.GetAssemblyName(path); - return true; - } - catch (FileLoadException) - { - // This is an executable image but not an assembly. - } - catch (BadImageFormatException) - { - // Happens when file is not a DLL/EXE, etc. - } - - // If file cannot be found we will throw. - // If there's anything else like SecurityException - we just pass exception through. - return false; - } - - /// - /// Returns satellite assemblies. Returns full canonicalized paths. - /// If the file is not an assembly returns empty list. - /// - /// The assembly to get satellites for. - /// List of satellite assemblies. - internal virtual List GetSatelliteAssemblies(string assemblyPath) - { - if (!this.IsAssemblyExtension(Path.GetExtension(assemblyPath)) || !this.IsAssembly(assemblyPath)) - { - EqtTrace.ErrorIf( - EqtTrace.IsErrorEnabled, - "AssemblyUtilities.GetSatelliteAssemblies: the specified file '{0}' is not managed assembly.", - assemblyPath); - Debug.Fail("AssemblyUtilities.GetSatelliteAssemblies: the file '" + assemblyPath + "' is not an assembly."); - - // If e.g. this is unmanaged dll, we don't care about the satellites. - return new List(); - } - - assemblyPath = Path.GetFullPath(assemblyPath); - var assemblyDir = Path.GetDirectoryName(assemblyPath); - var satellites = new List(); - - // Directory.Exists for 266 dirs takes 9ms while Path.GetDirectories can take up to 80ms on 10k dirs. - foreach (string dir in Cultures.Keys) - { - var dirPath = Path.Combine(assemblyDir, dir); - if (!Directory.Exists(dirPath)) - { - continue; - } - - // Check if the satellite exists in this dir. - // We check filenames like: MyAssembly.dll -> MyAssembly.resources.dll. - // Suprisingly, but both DLL and EXE are found by resource manager. - foreach (var extension in this.assemblyExtensions) - { - // extension contains leading dot. - string satellite = Path.ChangeExtension(Path.GetFileName(assemblyPath), "resources" + extension); - string satellitePath = Path.Combine(assemblyDir, Path.Combine(dir, satellite)); - - // We don't use Assembly.LoadFrom/Assembly.GetSatelliteAssebmlies because this is rather slow - // (1620ms for 266 cultures when directories do not exist). - if (File.Exists(satellitePath)) - { - // If the satellite found is not a managed assembly we do not report it as a reference. - if (!this.IsAssembly(satellitePath)) - { - EqtTrace.ErrorIf( - EqtTrace.IsErrorEnabled, - "AssemblyUtilities.GetSatelliteAssemblies: found assembly '{0}' installed as satellite but it's not managed assembly.", - satellitePath); - continue; - } - - // If both .exe and .dll exist we return both silently. - satellites.Add(satellitePath); - } - } - } - - return satellites; - } - - /// - /// Returns the dependent assemblies of the parameter assembly. - /// - /// Path to assembly to get dependencies for. - /// Config file to use while trying to resolve dependencies. - /// The warnings. - /// The . - internal virtual string[] GetFullPathToDependentAssemblies(string assemblyPath, string configFile, out IList warnings) - { - Debug.Assert(!string.IsNullOrEmpty(assemblyPath), "assemblyPath"); - - EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: start."); - - AppDomainSetup setupInfo = new AppDomainSetup(); - setupInfo.ApplicationBase = Path.GetDirectoryName(Path.GetFullPath(assemblyPath)); - - Debug.Assert(string.IsNullOrEmpty(configFile) || File.Exists(configFile), "Config file is specified but does not exist: {0}", configFile); - - //AppDomainUtilities.SetConfigurationFile(setupInfo, configFile); - - EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: Using config file: '{0}'.", setupInfo.ConfigurationFile); - - setupInfo.LoaderOptimization = LoaderOptimization.MultiDomainHost; - - AppDomain appDomain = null; - try - { - appDomain = AppDomain.CreateDomain("Dependency finder domain", null, setupInfo); - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("AssemblyDependencyFinder.GetDependentAssemblies: Created AppDomain."); - } - - var assemblyResolverType = typeof(AssemblyResolver); - - EqtTrace.SetupRemoteEqtTraceListeners(appDomain); - - // This has to be LoadFrom, otherwise we will have to use AssemblyResolver to find self. - using ( - AssemblyResolver resolver = - (AssemblyResolver)AppDomainUtilities.CreateInstance( - appDomain, - assemblyResolverType, - new object[] { this.GetResolutionPaths() })) - { - // This has to be Load, otherwise Serialization of argument types will not work correctly. - AssemblyLoadWorker worker = - (AssemblyLoadWorker)AppDomainUtilities.CreateInstance(appDomain, typeof(AssemblyLoadWorker), null); - - EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: loaded the worker."); - - return worker.GetFullPathToDependentAssemblies(assemblyPath, out warnings); - } - } - finally - { - if (appDomain != null) - { - EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: unloading AppDomain..."); - AppDomain.Unload(appDomain); - EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: unloading AppDomain succeeded."); - } - } - } - - /// - /// Gets the resolution paths for app domain creation. - /// - /// The of resolution paths. - internal IList GetResolutionPaths() - { - // Use dictionary to ensure we get a list of unique paths, but keep a list as the - // dictionary does not guarantee order. - Dictionary resolutionPathsDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - List resolutionPaths = new List(); - - // Add the path of the currently executing assembly (use Uri(CodeBase).LocalPath as Location can be on shadow dir). - string currentlyExecutingAssembly = Path.GetDirectoryName(Path.GetFullPath(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath)); - resolutionPaths.Add(currentlyExecutingAssembly); - resolutionPathsDictionary[currentlyExecutingAssembly] = null; - - // Add the application base for this domain. - if (!resolutionPathsDictionary.ContainsKey(AppDomain.CurrentDomain.BaseDirectory)) - { - resolutionPaths.Add(AppDomain.CurrentDomain.BaseDirectory); - resolutionPathsDictionary[AppDomain.CurrentDomain.BaseDirectory] = null; - } - - return resolutionPaths; - } - } -} diff --git a/source/TestAdapter/Utilities/DeploymentItemUtility.cs b/source/TestAdapter/Utilities/DeploymentItemUtility.cs deleted file mode 100644 index 28f089c..0000000 --- a/source/TestAdapter/Utilities/DeploymentItemUtility.cs +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Reflection; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Deployment; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - - /// - /// The deployment utility. - /// - internal class DeploymentItemUtility - { - private ReflectionUtility reflectionUtility; - - /// - /// A cache for class level deployment items. - /// - private Dictionary> classLevelDeploymentItems; - - /// - /// Initializes a new instance of the class. - /// - /// The reflect helper. - internal DeploymentItemUtility(ReflectionUtility reflectionUtility) - { - this.reflectionUtility = reflectionUtility; - this.classLevelDeploymentItems = new Dictionary>(); - } - - /// - /// Get the class level deployment items. - /// - /// The type. - /// The warnings. - /// The of deployment items on a class. - internal IList GetClassLevelDeploymentItems(Type type, ICollection warnings) - { - if (!this.classLevelDeploymentItems.ContainsKey(type)) - { - var deploymentItemAttributes = this.reflectionUtility.GetCustomAttributes( - type.GetTypeInfo(), - typeof(DeploymentItemAttribute)); - - this.classLevelDeploymentItems[type] = this.GetDeploymentItems(deploymentItemAttributes, warnings); - } - - return this.classLevelDeploymentItems[type]; - } - - /// - /// The get deployment items. - /// The method. - /// The class level deployment items. - /// The warnings. - /// The .of deployment item information. - internal KeyValuePair[] GetDeploymentItems(MethodInfo method, IList classLevelDeploymentItems, ICollection warnings) - { - var testLevelDeploymentItems = this.GetDeploymentItems(this.reflectionUtility.GetCustomAttributes(method, typeof(DeploymentItemAttribute)), warnings); - - return this.ToKeyValuePairs(this.Concat(testLevelDeploymentItems, classLevelDeploymentItems)); - } - - /// - /// Checks if parameters are valid to create deployment item. - /// - /// The source Path. - /// The relative Output Directory. - /// The warning message if it is an invalid deployment item. - /// Returns true if it is a valid deployment item. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Internal method.")] - internal bool IsValidDeploymentItem(string sourcePath, string relativeOutputDirectory, out string warning) - { - if (string.IsNullOrEmpty(sourcePath)) - { - warning = Resource.DeploymentItemPathCannotBeNullOrEmpty; - return false; - } - - if (relativeOutputDirectory == null) - { - warning = Resource.DeploymentItemOutputDirectoryCannotBeNull; - return false; - } - - if (sourcePath.IndexOfAny(System.IO.Path.GetInvalidPathChars()) != -1 || - relativeOutputDirectory.IndexOfAny(System.IO.Path.GetInvalidPathChars()) != -1) - { - warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentItemContainsInvalidCharacters, sourcePath, relativeOutputDirectory); - return false; - } - - if (System.IO.Path.IsPathRooted(relativeOutputDirectory)) - { - warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentItemOutputDirectoryMustBeRelative, relativeOutputDirectory); - return false; - } - - warning = string.Empty; - return true; - } - - /// - /// Returns whether there are any deployment items defined on the test - /// - /// The test Case. - /// True if has deployment items. - internal bool HasDeploymentItems(TestCase testCase) - { - var deploymentItems = this.GetDeploymentItems(testCase); - - return deploymentItems != null && deploymentItems.Length > 0; - } - - internal IList GetDeploymentItems(IEnumerable tests) - { - List allDeploymentItems = new List(); - - foreach (var test in tests) - { - KeyValuePair[] items = this.GetDeploymentItems(test); - - if (items == null || items.Length == 0) - { - continue; - } - - IList deploymentItemsToBeAdded = this.FromKeyValuePairs(items); - - foreach (var deploymentItemToBeAdded in deploymentItemsToBeAdded) - { - this.AddDeploymentItem(allDeploymentItems, deploymentItemToBeAdded); - } - } - - return allDeploymentItems; - } - - internal void AddDeploymentItem(IList deploymentItemList, DeploymentItem deploymentItem) - { - Debug.Assert(deploymentItemList != null, "DeploymentItem list cannot be null"); - Debug.Assert(deploymentItem != null, "DeploymentItem cannot be null"); - - if (!deploymentItemList.Contains(deploymentItem)) - { - deploymentItemList.Add(deploymentItem); - } - } - - private IList GetDeploymentItems(object[] deploymentItemAttributes, ICollection warnings) - { - var deploymentItems = new List(); - - foreach (DeploymentItemAttribute deploymentItemAttribute in deploymentItemAttributes) - { - string warning; - if (this.IsValidDeploymentItem(deploymentItemAttribute.Path, deploymentItemAttribute.OutputDirectory, out warning)) - { - this.AddDeploymentItem(deploymentItems, new DeploymentItem(deploymentItemAttribute.Path, deploymentItemAttribute.OutputDirectory)); - } - else - { - warnings.Add(warning); - } - } - - return deploymentItems; - } - - private IList Concat(IList deploymentItemList1, IList deploymentItemList2) - { - if (deploymentItemList1 == null && deploymentItemList2 == null) - { - return null; - } - - if (deploymentItemList1 == null) - { - return deploymentItemList2; - } - - if (deploymentItemList2 == null) - { - return deploymentItemList1; - } - - IList result = new List(deploymentItemList1); - - foreach (var item in deploymentItemList2) - { - this.AddDeploymentItem(result, item); - } - - return result; - } - - /// - /// Returns the deployment items defined on the test - /// - /// The test Case. - /// The . - private KeyValuePair[] GetDeploymentItems(TestCase testCase) - { - return - testCase.GetPropertyValue(TestAdapter.Constants.DeploymentItemsProperty) as - KeyValuePair[]; - } - - private KeyValuePair[] ToKeyValuePairs(IList deploymentItemList) - { - if (deploymentItemList == null || deploymentItemList.Count == 0) - { - return null; - } - - IList> result = new List>(); - - foreach (var deploymentItem in deploymentItemList) - { - if (deploymentItem != null) - { - result.Add(new KeyValuePair(deploymentItem.SourcePath, deploymentItem.RelativeOutputDirectory)); - } - } - - return result.ToArray(); - } - - private IList FromKeyValuePairs(KeyValuePair[] deploymentItemsData) - { - if (deploymentItemsData == null || deploymentItemsData.Length == 0) - { - return null; - } - - IList result = new List(); - - foreach (var deploymentItemData in deploymentItemsData) - { - this.AddDeploymentItem(result, new DeploymentItem(deploymentItemData.Key, deploymentItemData.Value)); - } - - return result; - } - } -} diff --git a/source/TestAdapter/Utilities/DeploymentUtility.cs b/source/TestAdapter/Utilities/DeploymentUtility.cs deleted file mode 100644 index 24c7e8d..0000000 --- a/source/TestAdapter/Utilities/DeploymentUtility.cs +++ /dev/null @@ -1,743 +0,0 @@ -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Security; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Deployment; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - - internal class DeploymentUtility - { - private const string TestAssemblyConfigFileExtension = ".config"; - private const string NetAppConfigFile = "App.Config"; - - /// - /// Prefix for deployment folder to avoid confusions with other folders (like trx attachments). - /// - private const string DeploymentFolderPrefix = "Deploy"; - - private DeploymentItemUtility deploymentItemUtility; - private FileUtility fileUtility; - private AssemblyUtility assemblyUtility; - - internal DeploymentUtility() - : this(new DeploymentItemUtility(new ReflectionUtility()), new AssemblyUtility(), new FileUtility()) - { - } - - internal DeploymentUtility( - DeploymentItemUtility deploymentItemUtility, - AssemblyUtility assemblyUtility, - FileUtility fileUtility - ) - { - this.deploymentItemUtility = deploymentItemUtility; - this.assemblyUtility = assemblyUtility; - this.fileUtility = fileUtility; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "4#", Justification = "Used internally.")] - internal bool Deploy( - IEnumerable tests, - string source, - IRunContext runContext, - ITestExecutionRecorder testExecutionRecorder, - ref TestRunDirectories testRunDirectories - ) - { - IList deploymentItems = this.deploymentItemUtility.GetDeploymentItems(tests); - - // we just deploy source if there are no deployment items for current source but there are deployment items for other sources - return this.Deploy(source, runContext, testExecutionRecorder, deploymentItems, ref testRunDirectories); - } - - /// - /// Create deployment directories - /// - /// The run context. - /// TestRunDirectories instance. - internal TestRunDirectories CreateDeploymentDirectories( - IRunContext runContext - ) - { - var tempDirectory = this.GetTestResultsDirectory(runContext); - var rootDeploymentDirectory = this.GetRootDeploymentDirectory(tempDirectory); - - var result = new TestRunDirectories(rootDeploymentDirectory); - var inDirectory = result.InDirectory; - var outDirectory = result.OutDirectory; - var inMachineDirectory = result.InMachineNameDirectory; - - this.fileUtility.CreateDirectoryIfNotExists(rootDeploymentDirectory); - this.fileUtility.CreateDirectoryIfNotExists(inDirectory); - this.fileUtility.CreateDirectoryIfNotExists(outDirectory); - this.fileUtility.CreateDirectoryIfNotExists(inMachineDirectory); - - return result; - } - - internal string GetConfigFile( - string testSource - ) - { - string configFile = null; - - if (this.fileUtility.DoesFileExist(testSource + TestAssemblyConfigFileExtension)) - { - // Path to config file cannot be bad: storage is already checked, and extension is valid. - configFile = testSource + TestAssemblyConfigFileExtension; - } - else - { - var netAppConfigFile = Path.Combine(Path.GetDirectoryName(testSource), NetAppConfigFile); - if (this.fileUtility.DoesFileExist(netAppConfigFile)) - { - configFile = netAppConfigFile; - } - } - - return configFile; - } - - /// - /// Log the parameter warnings on the parameter logger - /// - /// Execution recorder. - /// Warnings. - private static void LogWarnings( - ITestExecutionRecorder testExecutionRecorder, - IEnumerable warnings - ) - { - if (warnings == null) - { - return; - } - - Debug.Assert(testExecutionRecorder != null, "Logger should not be null"); - - // log the warnings - foreach (string warning in warnings) - { - testExecutionRecorder.SendMessage(TestMessageLevel.Warning, warning); - } - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "5#", Justification = "Used internally.")] - private bool Deploy( - string source, - IRunContext runContext, - ITestExecutionRecorder testExecutionRecorder, - IList deploymentItems, - ref TestRunDirectories testRunDirectories - ) - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("TestExecutor: Found that deployment items for source {0} are: ", source); - - foreach (var item in deploymentItems) - { - EqtTrace.Info("TestExecutor: SourcePath: - {0}", item.SourcePath); - } - } - - // Do the deployment. - IEnumerable warnings; - - var runDirectories = testRunDirectories ?? this.CreateDeploymentDirectories(runContext); - - ValidateArg.NotNull(runDirectories, "runDirectories"); - EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "TestExecutor: Using deployment directory {0} for source {1}.", runDirectories.OutDirectory, source); - - this.Deploy(new List(deploymentItems), source, runDirectories.OutDirectory, out warnings); - - // Log warnings - LogWarnings(testExecutionRecorder, warnings); - - return deploymentItems != null && deploymentItems.Count > 0; - } - - /// - /// Does the deployment of parameter deployment items & the testSource to the parameter directory. - /// - /// The deployment item. - /// The test source. - /// The deployment directory. - /// Warnings. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private void Deploy( - IList deploymentItems, - string testSource, - string deploymentDirectory, - out IEnumerable deploymentWarnings - ) - { - Debug.Assert(!string.IsNullOrEmpty(deploymentDirectory), "Deployment directory is null/empty"); - Debug.Assert(this.fileUtility.DoesDirectoryExist(deploymentDirectory), "Deployment directory " + deploymentDirectory + " does not exist"); - Debug.Assert(!string.IsNullOrEmpty(testSource), "TestSource directory is null/empty"); - Debug.Assert(this.fileUtility.DoesFileExist(testSource), "TestSource " + testSource + " does not exist."); - - testSource = Path.GetFullPath(testSource); - - var warnings = new List(); - - // Get the referenced assemblies. - this.ProcessNewStorage(testSource, deploymentItems, warnings); - - // Get the satellite assemblies - var satelliteItems = this.GetSatellites(deploymentItems, testSource, warnings); - foreach (var satelliteItem in satelliteItems) - { - this.deploymentItemUtility.AddDeploymentItem(deploymentItems, satelliteItem); - } - - // Maps relative to Out dir destination -> source and used to determine if there are conflicted items. - var destToSource = new Dictionary(StringComparer.OrdinalIgnoreCase); - - // Copy the deployment items. (As deployment item can correspond to directories as well, so each deployment item may map to n files) - foreach (var deploymentItem in deploymentItems) - { - Debug.Assert(deploymentItem != null, "deploymentItem should not be null."); - - // Validate the output directory. - if (!this.IsOutputDirectoryValid(deploymentItem, deploymentDirectory, warnings)) - { - continue; - } - - // Get the files corresponding to this deployment item - bool itemIsDirectory; - var deploymentItemFiles = this.GetFullPathToFilesCorrespondingToDeploymentItem(deploymentItem, testSource, warnings, out itemIsDirectory); - if (deploymentItemFiles == null) - { - continue; - } - - var fullPathToDeploymentItemSource = this.GetFullPathToDeploymentItemSource(deploymentItem.SourcePath, testSource); - - // Note: source is already rooted. - foreach (var deploymentItemFile in deploymentItemFiles) - { - Debug.Assert(Path.IsPathRooted(deploymentItemFile), "File " + deploymentItemFile + " is not rooted"); - - // List of files to deploy, by default, just itemFile. - var filesToDeploy = new List(1); - filesToDeploy.Add(deploymentItemFile); - - // Find dependencies of test deployment items and deploy them at the same time as master file. - if (deploymentItem.OriginType == DeploymentItemOriginType.PerTestDeployment - && - this.assemblyUtility.IsAssemblyExtension(Path.GetExtension(deploymentItemFile))) - { - this.AddDependenciesOfDeploymentItem(deploymentItemFile, filesToDeploy, warnings); - } - - foreach (var fileToDeploy in filesToDeploy) - { - Debug.Assert(Path.IsPathRooted(fileToDeploy), "File " + fileToDeploy + " is not rooted"); - - // Ignore the test platform files. - var tempFile = Path.GetFileName(fileToDeploy); - var assemblyName = Path.GetFileName(Assembly.GetExecutingAssembly().Location); - if (tempFile.Equals(assemblyName, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - string relativeDestination; - if (itemIsDirectory) - { - // Deploy into subdirectory of deployment (Out) dir. - Debug.Assert(fileToDeploy.StartsWith(fullPathToDeploymentItemSource, StringComparison.Ordinal), "Somehow source is outside original dir."); - relativeDestination = this.fileUtility.TryConvertPathToRelative(fileToDeploy, fullPathToDeploymentItemSource); - } - else - { - // Deploy just to the deployment (Out) dir. - relativeDestination = Path.GetFileName(fileToDeploy); - } - - relativeDestination = Path.Combine(deploymentItem.RelativeOutputDirectory, relativeDestination); // Ignores empty arg1. - var destination = Path.Combine(deploymentDirectory, relativeDestination); - try - { - destination = Path.GetFullPath(destination); - } - catch (Exception e) - { - var warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorFailedToAccessFile, destination, e.GetType(), e.Message); - warnings.Add(warning); - - continue; - } - - if (!destToSource.ContainsKey(relativeDestination)) - { - destToSource.Add(relativeDestination, fileToDeploy); - - // Now, finally we can copy the file... - string warning; - destination = this.fileUtility.CopyFileOverwrite(fileToDeploy, destination, out warning); - if (!string.IsNullOrEmpty(warning)) - { - warnings.Add(warning); - } - - if (string.IsNullOrEmpty(destination)) - { - continue; - } - - // We clear the attributes so that e.g. you can write to the copies of files originally under SCC. - this.fileUtility.SetAttributes(destination, FileAttributes.Normal); - - // Deploy PDB for line number info in stack trace. - this.fileUtility.FindAndDeployPdb(destination, relativeDestination, fileToDeploy, destToSource); - } - else if ( - !string.Equals( - fileToDeploy, - destToSource[relativeDestination], - StringComparison.OrdinalIgnoreCase)) - { - EqtTrace.WarningIf( - EqtTrace.IsWarningEnabled, - "Conflict during copiyng file: '{0}' and '{1}' are from different origins although they might be the same.", - fileToDeploy, - destToSource[relativeDestination]); - } - } // foreach fileToDeploy. - } // foreach itemFile. - } - - deploymentWarnings = warnings; - } - - private void AddDependenciesOfDeploymentItem( - string deploymentItemFile, - IList filesToDeploy, - IList warnings - ) - { - var dependencies = new List(); - - this.AddDependencies(deploymentItemFile, null, dependencies, warnings); - - foreach (var dependencyItem in dependencies) - { - Debug.Assert(Path.IsPathRooted(dependencyItem.SourcePath), "Path of the dependency " + dependencyItem.SourcePath + " is not rooted."); - - // Add dependencies to filesToDeploy. - filesToDeploy.Add(dependencyItem.SourcePath); - } - } - - /// - /// Process test storage and add dependant assemblies to dependencyDeploymentItems. - /// - /// The test source. - /// The config file. - /// Deployment items. - /// Warnigns. - private void AddDependencies - (string testSource, - string configFile, - IList deploymentItems, - IList warnings - ) - { - Debug.Assert(!string.IsNullOrEmpty(testSource), "testSource should not be null or empty."); - - // config file can be null. - Debug.Assert(deploymentItems != null, "deploymentItems should not be null."); - Debug.Assert(Path.IsPathRooted(testSource), "path should be rooted."); - - // Note: if this is not an assembly we simply return empty array, also: - // we do recursive search and report missing. - IList warningList; - string[] references = this.assemblyUtility.GetFullPathToDependentAssemblies(testSource, configFile, out warningList); - if (warningList != null && warningList.Count > 0) - { - warnings = warnings.Concat(warningList).ToList(); - } - - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("DeploymentManager: Source:{0} has following references", testSource); - } - - foreach (string reference in references) - { - DeploymentItem deploymentItem = new DeploymentItem(reference, string.Empty, DeploymentItemOriginType.Dependency); - this.deploymentItemUtility.AddDeploymentItem(deploymentItems, deploymentItem); - - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("DeploymentManager: Reference:{0} ", reference); - } - } - } - - /// - /// Get files corresponding to parameter deployment item. - /// - /// Deployment Item. - /// The test source. - /// Warnings. - /// Is this a directory. - /// Paths to items to deploy. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private string[] GetFullPathToFilesCorrespondingToDeploymentItem( - DeploymentItem deploymentItem, - string testSource, - IList warnings, - out bool isDirectory - ) - { - Debug.Assert(deploymentItem != null, "deploymentItem should not be null."); - Debug.Assert(!string.IsNullOrEmpty(testSource), "testsource should not be null or empty."); - - try - { - string directory; - isDirectory = this.IsDeploymentItemSourceADirectory(deploymentItem, testSource, out directory); - - if (isDirectory) - { - return this.fileUtility.AddFilesFromDirectory(directory, false).ToArray(); - } - - string fileName; - if (!this.IsDeploymentItemSourceAFile(deploymentItem.SourcePath, testSource, out fileName)) - { - // If file/directory is not found, then try removing the prefix and see if it is present. - string fileOrDirNameOnly = - Path.GetFileName( - deploymentItem.SourcePath.TrimEnd( - new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar })); - if (!this.IsDeploymentItemSourceAFile(fileOrDirNameOnly, testSource, out fileName)) - { - string message = string.Format(CultureInfo.CurrentCulture, Resource.CannotFindFile, fileName); - throw new FileNotFoundException(message, fileName); - } - } - - return new[] { fileName }; - } - catch (Exception e) - { - warnings.Add(string.Format( - CultureInfo.CurrentCulture, - Resource.DeploymentErrorFailedToGetFileForDeploymentItem, - deploymentItem, - e.GetType(), - e.Message)); - - isDirectory = false; - return null; - } - } - - private bool IsDeploymentItemSourceAFile( - string deploymentItemSourcePath, - string testSource, - out string file - ) - { - file = this.GetFullPathToDeploymentItemSource(deploymentItemSourcePath, testSource); - - return this.fileUtility.DoesFileExist(file); - } - - private bool IsDeploymentItemSourceADirectory( - DeploymentItem deploymentItem, - string testSource, - out string resultDirectory - ) - { - resultDirectory = null; - - string directory = this.GetFullPathToDeploymentItemSource(deploymentItem.SourcePath, testSource); - directory = directory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - - if (this.fileUtility.DoesDirectoryExist(directory)) - { - resultDirectory = directory; - return true; - } - - return false; - } - - private IEnumerable GetSatellites( - IEnumerable deploymentItems, - string testSource, - IList warnings - ) - { - List satellites = new List(); - foreach (DeploymentItem item in deploymentItems) - { - // We do not care about deployment items which are directories because in that case we deploy all files underneath anyway. - string path = null; - try - { - path = this.GetFullPathToDeploymentItemSource(item.SourcePath, testSource); - path = Path.GetFullPath(path); - - if (string.IsNullOrEmpty(path) || !this.assemblyUtility.IsAssemblyExtension(Path.GetExtension(path)) - || !this.fileUtility.DoesFileExist(path) || !this.assemblyUtility.IsAssembly(path)) - { - continue; - } - } - catch (ArgumentException ex) - { - EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex); - } - catch (SecurityException ex) - { - EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex); - } - catch (IOException ex) - { - // This covers PathTooLongException. - EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex); - } - catch (NotSupportedException ex) - { - EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex); - } - - // Note: now Path operations with itemPath should not result in any exceptions. - // path is already canonicalized. - - // If we cannot access satellite due to security, etc, we report warning. - try - { - string itemDir = Path.GetDirectoryName(path).TrimEnd( - new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); - List itemSatellites = this.assemblyUtility.GetSatelliteAssemblies(path); - foreach (string satellite in itemSatellites) - { - Debug.Assert(!string.IsNullOrEmpty(satellite), "DeploymentManager.DoDeployment: got empty satellite!"); - Debug.Assert( - satellite.IndexOf(itemDir, StringComparison.OrdinalIgnoreCase) == 0, - "DeploymentManager.DoDeployment: Got satellite that does not start with original item path"); - - string satelliteDir = Path.GetDirectoryName(satellite).TrimEnd( - new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); - - Debug.Assert(!string.IsNullOrEmpty(satelliteDir), "DeploymentManager.DoDeployment: got empty satellite dir!"); - Debug.Assert(satelliteDir.Length > itemDir.Length + 1, "DeploymentManager.DoDeployment: wrong satellite dir lenght!"); - - string localeDir = satelliteDir.Substring(itemDir.Length + 1); - Debug.Assert(!string.IsNullOrEmpty(localeDir), "DeploymentManager.DoDeployment: got empty dir name for satellite dir!"); - - string relativeOutputDir = Path.Combine(item.RelativeOutputDirectory, localeDir); - - // Now finally add the item! - DeploymentItem satelliteItem = new DeploymentItem(satellite, relativeOutputDir, DeploymentItemOriginType.Satellite); - this.deploymentItemUtility.AddDeploymentItem(satellites, satelliteItem); - } - } - catch (ArgumentException ex) - { - EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex); - string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorGettingSatellite, item, ex.GetType(), ex.GetExceptionMessage()); - warnings.Add(warning); - } - catch (SecurityException ex) - { - EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex); - string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorGettingSatellite, item, ex.GetType(), ex.GetExceptionMessage()); - warnings.Add(warning); - } - catch (IOException ex) - { - // This covers PathTooLongException. - EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex); - string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorGettingSatellite, item, ex.GetType(), ex.GetExceptionMessage()); - warnings.Add(warning); - } - } - - return satellites; - } - - private string GetFullPathToDeploymentItemSource( - string deploymentItemSourcePath, - string testSource - ) - { - if (Path.IsPathRooted(deploymentItemSourcePath)) - { - return deploymentItemSourcePath; - } - - return Path.Combine(Path.GetDirectoryName(testSource), deploymentItemSourcePath); - } - - /// - /// Validate the output directory for the parameter deployment item. - /// - /// The deployment item. - /// The deployment directory. - /// Warnings. - /// True if valid. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private bool IsOutputDirectoryValid( - DeploymentItem deploymentItem, - string deploymentDirectory, - IList warnings - ) - { - Debug.Assert(deploymentItem != null, "deploymentItem should not be null."); - Debug.Assert(!string.IsNullOrEmpty(deploymentDirectory), "deploymentDirectory should not be null or empty."); - Debug.Assert(warnings != null, "warnings should not be null."); - - // Check that item.output dir does not go outside deployment Out dir, otherwise you can erase any file! - string outputDir = deploymentDirectory; - try - { - outputDir = Path.GetFullPath(Path.Combine(deploymentDirectory, deploymentItem.RelativeOutputDirectory)); - - // convert the short path to full length path (like joe~1.dom to joe.domain) and the comparison - // startsWith in the next loop will work for the matching paths. - deploymentDirectory = Path.GetFullPath(deploymentDirectory); - } - catch (Exception e) - { - string warning = string.Format( - CultureInfo.CurrentCulture, - Resource.DeploymentErrorFailedToAccesOutputDirectory, - deploymentItem.SourcePath, - outputDir, - e.GetType(), - e.GetExceptionMessage()); - - warnings.Add(warning); - return false; - } - - if (!outputDir.StartsWith(deploymentDirectory, StringComparison.OrdinalIgnoreCase)) - { - string warning = string.Format( - CultureInfo.CurrentCulture, - Resource.DeploymentErrorBadDeploymentItem, - deploymentItem.SourcePath, - deploymentItem.RelativeOutputDirectory); - warnings.Add(warning); - - return false; - } - - return true; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private void ProcessNewStorage( - string testSource, - IList deploymentItems, - IList warnings - ) - { - // Add deployment items and process .config files only for storages we have not processed before. - string errorMessage; - if (!this.deploymentItemUtility.IsValidDeploymentItem(testSource, string.Empty, out errorMessage)) - { - warnings.Add(errorMessage); - return; - } - - this.deploymentItemUtility.AddDeploymentItem(deploymentItems, new DeploymentItem(testSource, string.Empty, DeploymentItemOriginType.TestStorage)); - - // Deploy .config file if exists, only for assemlbies, i.e. DLL and EXE. - // First check .config, then if not found check for App.Config - // and deploy AppConfig to .config. - if (this.assemblyUtility.IsAssemblyExtension(Path.GetExtension(testSource))) - { - var configFile = this.AddTestSourceConfigFileIfExists(testSource, deploymentItems); - - // Deal with test dependencies: update dependencyDeploymentItems and missingDependentAssemblies. - try - { - // We look for dependent assemblies only for DLL and EXE's. - this.AddDependencies(testSource, configFile, deploymentItems, warnings); - } - catch (Exception e) - { - string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorFailedToDeployDependencies, testSource, e); - warnings.Add(warning); - } - } - } - - /// - /// Get the parent test results directory where deployment will be done. - /// - /// The run context. - /// The test results directory. - private string GetTestResultsDirectory( - IRunContext runContext - ) - { - var tempDirectory = (!string.IsNullOrEmpty(runContext?.TestRunDirectory)) ? - runContext.TestRunDirectory : null; - - if (string.IsNullOrEmpty(tempDirectory)) - { - tempDirectory = Path.GetFullPath(Path.Combine(Path.GetTempPath(), TestRunDirectories.DefaultDeploymentRootDirectory)); - } - - return tempDirectory; - } - - /// - /// Get root deployment directory - /// - /// The base directory. - /// Root deployment directory. - private string GetRootDeploymentDirectory( - string baseDirectory - ) - { - string dateTimeSufix = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", DateTimeFormatInfo.InvariantInfo); - string directoryName = string.Format(CultureInfo.CurrentCulture, Resource.TestRunName, DeploymentFolderPrefix, Environment.UserName, dateTimeSufix); - directoryName = this.fileUtility.ReplaceInvalidFileNameCharacters(directoryName); - - return this.fileUtility.GetNextIterationDirectoryName(baseDirectory, directoryName); - } - - private string AddTestSourceConfigFileIfExists( - string testSource, - IList deploymentItems - ) - { - string configFile = this.GetConfigFile(testSource); - - if (string.IsNullOrEmpty(configFile) == false) - { - this.deploymentItemUtility.AddDeploymentItem(deploymentItems, new DeploymentItem(configFile)); - } - - return configFile; - } - } -} diff --git a/source/TestAdapter/Utilities/FileUtility.cs b/source/TestAdapter/Utilities/FileUtility.cs deleted file mode 100644 index fc0a8c5..0000000 --- a/source/TestAdapter/Utilities/FileUtility.cs +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Extensions; - using nanoFramework.TestPlatform.MSTest.TestAdapter.Resources; - - internal class FileUtility - { - private AssemblyUtility assemblyUtility; - - internal FileUtility() - { - this.assemblyUtility = new AssemblyUtility(); - } - - internal virtual void CreateDirectoryIfNotExists( - string directory - ) - { - Debug.Assert(!string.IsNullOrEmpty(directory), "directory"); - - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); // Creates subdir chain if necessary. - } - } - - /// - /// Replaces the invalid file/path characters from the parameter file name with '_' - /// - /// The file Name. - /// The fileName devoid of any invalid characters. - internal string ReplaceInvalidFileNameCharacters( - string fileName - ) - { - Debug.Assert(!string.IsNullOrEmpty(fileName), "fileName"); - - return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, ch) => current.Replace(ch, '_')); - } - - /// - /// Checks whether directory with specified name exists in the specified directory. - /// If it exits, adds [1],[2]... to the directory name and checks again. - /// Returns full directory name (full path) of the iteration when the file does not exist. - /// - /// The directory where to check. - /// The original directory (that we would add [1],[2],.. in the end of if needed) name to check. - /// A unique directory name. - internal virtual string GetNextIterationDirectoryName( - string parentDirectoryName, - string originalDirectoryName - ) - { - Debug.Assert(!string.IsNullOrEmpty(parentDirectoryName), "parentDirectoryName"); - Debug.Assert(!string.IsNullOrEmpty(originalDirectoryName), "originalDirectoryName"); - - uint iteration = 0; - do - { - string tryMe; - if (iteration == 0) - { - tryMe = originalDirectoryName; - } - else - { - tryMe = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", originalDirectoryName, iteration.ToString(CultureInfo.InvariantCulture)); - } - - string tryMePath = Path.Combine(parentDirectoryName, tryMe); - - if (!File.Exists(tryMePath) && !Directory.Exists(tryMePath)) - { - return tryMePath; - } - - ++iteration; - } - while (iteration != uint.MaxValue); - - // Return the original path in case file does not exist and let it fail. - return Path.Combine(parentDirectoryName, originalDirectoryName); - } - - /// - /// Copies source file to destination file. - /// - /// Path to source file. - /// Path to destination file. - /// warnings to be reported. - /// - /// Returns destination on full success, - /// Returns empty string on error when specified to continue the run on error, - /// throw on error when specified to abort the run on error. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - internal virtual string CopyFileOverwrite( - string source, - string destination, - out string warning - ) - { - Debug.Assert(!string.IsNullOrEmpty(source), "source should not be null."); - Debug.Assert(!string.IsNullOrEmpty(destination), "destination should not be null."); - - try - { - string destinationDirectory = Path.GetDirectoryName(destination); - if (!string.IsNullOrEmpty(destinationDirectory) && File.Exists(source) && !Directory.Exists(destinationDirectory)) - { - Directory.CreateDirectory(destinationDirectory); - } - - // Overwrite - Debug.Assert(Path.IsPathRooted(source), "DeploymentManager: source path " + source + " must be rooted!"); - File.Copy(source, destination, true); - - warning = null; - return Path.GetFullPath(destination); - } - catch (Exception e) - { - warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorFailedToCopyWithOverwrite, source, destination, e.GetType(), e.GetExceptionMessage()); - - return string.Empty; - } - } - - /// - /// For given file checks if it is a binary, then finds and deploys PDB for line number info in call stack. - /// - /// - /// Returns deployed destination pdb file path if everything is Ok, otherwise null. - /// - /// The file we need to find PDBs for (we care only about binaries). - /// Destination relative to the root of deployment dir. - /// Original file of destinationFile, i.e. the file copied to deployment dir. - /// destToSource map. - internal string FindAndDeployPdb( - string destinationFile, - string relativeDestination, - string sourceFile, - Dictionary destToSource - ) - { - Debug.Assert(!string.IsNullOrEmpty(destinationFile), "destination should not be null or empty."); - Debug.Assert(!string.IsNullOrEmpty(relativeDestination), "relative destination path should not be null or empty."); - Debug.Assert(!string.IsNullOrEmpty(sourceFile), "sourceFile should not be null or empty."); - Debug.Assert(destToSource != null, "destToSource should not be null."); - - if (!this.assemblyUtility.IsAssemblyExtension(Path.GetExtension(destinationFile))) - { - return null; - } - - string pdbSource = this.GetSymbolsFileName(sourceFile); - if (string.IsNullOrEmpty(pdbSource)) - { - return null; - } - - string pdbDestination = null; - string relativePdbDestination = null; - - try - { - pdbDestination = Path.Combine(Path.GetDirectoryName(destinationFile), Path.GetFileName(pdbSource)); - relativePdbDestination = Path.Combine( - Path.GetDirectoryName(relativeDestination), Path.GetFileName(pdbDestination)); - } - catch (ArgumentException ex) - { - EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "Error while trying to locate pdb for deployed assembly '{0}': {1}", destinationFile, ex); - return null; - } - - // If already processed, do nothing. - if (!destToSource.ContainsKey(relativePdbDestination)) - { - if (this.DoesFileExist(pdbSource)) - { - string warning; - pdbDestination = this.CopyFileOverwrite(pdbSource, pdbDestination, out warning); - if (!string.IsNullOrEmpty(pdbDestination)) - { - destToSource.Add(relativePdbDestination, pdbSource); - return pdbDestination; - } - } - } - else if (!string.Equals(pdbSource, destToSource[relativePdbDestination], StringComparison.OrdinalIgnoreCase)) - { - EqtTrace.WarningIf( - EqtTrace.IsWarningEnabled, - "Conflict during copiyng PDBs for line number info: '{0}' and '{1}' are from different origins although they might be the same.", - pdbSource, - destToSource[relativePdbDestination]); - } - - return null; - } - - internal virtual List AddFilesFromDirectory( - string directoryPath, - bool ignoreIOExceptions - ) - { - var fileContents = new List(); - - try - { - var files = this.GetFilesInADirectory(directoryPath); - fileContents.AddRange(files); - } - catch (IOException) - { - if (!ignoreIOExceptions) - { - throw; - } - } - - foreach (var subDirectoryPath in this.GetDirectoriesInADirectory(directoryPath)) - { - var subDirectoryContents = this.AddFilesFromDirectory(subDirectoryPath, true); - if (subDirectoryContents?.Count > 0) - { - fileContents.AddRange(subDirectoryContents); - } - } - - return fileContents; - } - - internal string TryConvertPathToRelative( - string path, - string rootDir - ) - { - Debug.Assert(!string.IsNullOrEmpty(path), "path should not be null or empty."); - Debug.Assert(!string.IsNullOrEmpty(rootDir), "rootDir should not be null or empty."); - - if (Path.IsPathRooted(path) && path.StartsWith(rootDir, StringComparison.OrdinalIgnoreCase)) - { - return path.Substring(rootDir.Length).TrimStart(Path.DirectorySeparatorChar); - } - - return path; - } - - /// - /// The function goes among the subdirectories of the specified one and clears all of - /// them. - /// - /// The root directory to clear. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - internal virtual void DeleteDirectories( - string filePath - ) - { - Debug.Assert(filePath != null, "filePath"); - - try - { - var root = new DirectoryInfo(filePath); - root.Delete(true); - } - catch (Exception ex) - { - EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, "DeploymentManager.DeleteDirectories failed for the directory '{0}': {1}", filePath, ex); - } - } - - internal virtual bool DoesDirectoryExist( - string deploymentDirectory - ) - { - return Directory.Exists(deploymentDirectory); - } - - internal virtual bool DoesFileExist( - string testSource - ) - { - return File.Exists(testSource); - } - - internal virtual void SetAttributes( - string path, - FileAttributes fileAttributes - ) - { - File.SetAttributes(path, fileAttributes); - } - - internal virtual string[] GetFilesInADirectory( - string directoryPath - ) - { - return Directory.GetFiles(directoryPath); - } - - internal virtual string[] GetDirectoriesInADirectory( - string directoryPath - ) - { - return Directory.GetDirectories(directoryPath); - } - - /// - /// Returns either PDB file name from inside compiled binary or null if this cannot be done. - /// Does not throw. - /// - /// path to symbols file. - /// Pdb file name or null if non-existent. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")] - private string GetSymbolsFileName( - string path - ) - { - if (string.IsNullOrEmpty(path) || path.IndexOfAny(Path.GetInvalidPathChars()) != -1) - { - if (EqtTrace.IsWarningEnabled) - { - EqtTrace.Warning("Path is either null or invalid. Path = '{0}'", path); - } - - return null; - } - - string pdbFile = Path.ChangeExtension(path, ".pdb"); - if (File.Exists(pdbFile)) - { - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("Pdb file found for path '{0}'", path); - } - - return pdbFile; - } - - return null; - } - } -} diff --git a/source/TestAdapter/Utilities/IAppDomain.cs b/source/TestAdapter/Utilities/IAppDomain.cs deleted file mode 100644 index 82d3483..0000000 --- a/source/TestAdapter/Utilities/IAppDomain.cs +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using System; - using System.Security.Policy; - - /// - /// This interface is an abstraction over the AppDomain APIs - /// - internal interface IAppDomain - { - /// - /// Unloads the specified application domain. - /// - /// An application domain to unload. - void Unload(AppDomain appDomain); - - /// - /// Creates a new application domain using the specified name, evidence, and application domain setup information. - /// - /// The friendly name of the domain. - /// Evidence that establishes the identity of the code that runs in the application domain. Pass null to use the evidence of the current application domain. - /// An object that contains application domain initialization information. - /// The newly created application domain. - AppDomain CreateDomain( - string friendlyName, - Evidence securityInfo, - AppDomainSetup info); - } -} diff --git a/source/TestAdapter/Utilities/IAssemblyUtility.cs b/source/TestAdapter/Utilities/IAssemblyUtility.cs deleted file mode 100644 index e79f1d1..0000000 --- a/source/TestAdapter/Utilities/IAssemblyUtility.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using System.Reflection; - - internal interface IAssemblyUtility - { - /// - /// Loads an assembly into the reflection-only context, given its path. - /// - /// The path of the file that contains the manifest of the assembly. - /// The loaded assembly. - Assembly ReflectionOnlyLoadFrom(string assemblyPath); - - /// - /// Loads an assembly into the reflection-only context, given its display name. - /// - /// The display name of the assembly, as returned by the System.Reflection.AssemblyName.FullName property. - /// The loaded assembly. - Assembly ReflectionOnlyLoad(string assemblyString); - } -} \ No newline at end of file diff --git a/source/TestAdapter/Utilities/ReflectionUtility.cs b/source/TestAdapter/Utilities/ReflectionUtility.cs deleted file mode 100644 index 224ee52..0000000 --- a/source/TestAdapter/Utilities/ReflectionUtility.cs +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - - /// - /// Utility for reflection API's - /// - internal class ReflectionUtility - { - /// - /// Gets the custom attributes of the provided type on a memberInfo - /// - /// The member to reflect on. - /// The attribute type. - /// The vale of the custom attribute. - internal virtual object[] GetCustomAttributes( - MemberInfo attributeProvider, - Type type - ) - { - return this.GetCustomAttributes(attributeProvider, type, true); - } - - /// - /// Gets all the custom attributes adorned on a member. - /// - /// The member. - /// True to inspect the ancestors of element; otherwise, false. - /// The list of attributes on the member. Empty list if none found. - internal object[] GetCustomAttributes( - MemberInfo memberInfo, - bool inherit - ) - { - return this.GetCustomAttributes(memberInfo, type: null, inherit: inherit); - } - - /// - /// Get custom attributes on a member for both normal and reflection only load. - /// - /// Member for which attributes needs to be retrieved. - /// Type of attribute to retrieve. - /// If inherited type of attribute. - /// All attributes of give type on member. - internal object[] GetCustomAttributes( - MemberInfo memberInfo, - Type type, - bool inherit - ) - { - if (memberInfo == null) - { - return null; - } - - bool shouldGetAllAttributes = type == null; - - if (!this.IsReflectionOnlyLoad(memberInfo)) - { - if (shouldGetAllAttributes) - { - return memberInfo.GetCustomAttributes(inherit); - } - else - { - return memberInfo.GetCustomAttributes(type, inherit); - } - } - else - { - List nonUniqueAttributes = new List(); - Dictionary uniqueAttributes = new Dictionary(); - - var inheritanceThreshold = 10; - var inheritanceLevel = 0; - - if (inherit && memberInfo.MemberType == MemberTypes.TypeInfo) - { - // This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeType type, RuntimeType caType, bool inherit) - var tempTypeInfo = memberInfo as TypeInfo; - - do - { - var attributes = CustomAttributeData.GetCustomAttributes(tempTypeInfo); - this.AddNewAttributes( - attributes, - shouldGetAllAttributes, - type, - uniqueAttributes, - nonUniqueAttributes); - tempTypeInfo = tempTypeInfo.BaseType?.GetTypeInfo(); - inheritanceLevel++; - } - while (tempTypeInfo != null && tempTypeInfo != typeof(object).GetTypeInfo() - && inheritanceLevel < inheritanceThreshold); - } - else if (inherit && memberInfo.MemberType == MemberTypes.Method) - { - // This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeMethodInfo method, RuntimeType caType, bool inherit). - var tempMethodInfo = memberInfo as MethodInfo; - - do - { - var attributes = CustomAttributeData.GetCustomAttributes(tempMethodInfo); - this.AddNewAttributes( - attributes, - shouldGetAllAttributes, - type, - uniqueAttributes, - nonUniqueAttributes); - var baseDefinition = tempMethodInfo.GetBaseDefinition(); - - if (baseDefinition != null) - { - if (string.Equals( - string.Concat(tempMethodInfo.DeclaringType.FullName, tempMethodInfo.Name), - string.Concat(baseDefinition.DeclaringType.FullName, baseDefinition.Name))) - { - break; - } - } - - tempMethodInfo = baseDefinition; - inheritanceLevel++; - } - while (tempMethodInfo != null && inheritanceLevel < inheritanceThreshold); - } - else - { - // Ideally we should not be reaaching here. We only query for attributes on types/methods currently. - // Return the attributes that CustomAttributeData returns in this cases not considering inheritance. - var firstLevelAttributes = - CustomAttributeData.GetCustomAttributes(memberInfo); - this.AddNewAttributes(firstLevelAttributes, shouldGetAllAttributes, type, uniqueAttributes, nonUniqueAttributes); - } - - nonUniqueAttributes.AddRange(uniqueAttributes.Values); - return nonUniqueAttributes.ToArray(); - } - } - - internal object[] GetCustomAttributes( - Assembly assembly, - Type type - ) - { - if (assembly.ReflectionOnly) - { - List customAttributes = new List(); - customAttributes.AddRange(CustomAttributeData.GetCustomAttributes(assembly)); - - List attributesArray = new List(); - - foreach (var attribute in customAttributes) - { - if (this.IsTypeInheriting(attribute.Constructor.DeclaringType, type) - || attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals( - type.AssemblyQualifiedName)) - { - Attribute attributeInstance = CreateAttributeInstance(attribute); - if (attributeInstance != null) - { - attributesArray.Add(attributeInstance); - } - } - } - - return attributesArray.ToArray(); - } - else - { - return assembly.GetCustomAttributes(type).ToArray(); - } - } - - /// - /// Create instance of the attribute for reflection only load. - /// - /// The attribute data. - /// An attribute. - private static Attribute CreateAttributeInstance( - CustomAttributeData attributeData - ) - { - object attribute = null; - try - { - // Create instance of attribute. For some case, constructor param is returned as ReadOnlyCollection - // instead of array. So convert it to array else constructor invoke will fail. - Type attributeType = Type.GetType(attributeData.Constructor.DeclaringType.AssemblyQualifiedName); - - List constructorParameters = new List(); - List constructorArguments = new List(); - foreach (var parameter in attributeData.ConstructorArguments) - { - Type parameterType = Type.GetType(parameter.ArgumentType.AssemblyQualifiedName); - constructorParameters.Add(parameterType); - if (parameterType.IsArray) - { - IEnumerable enumerable = parameter.Value as IEnumerable; - if (enumerable != null) - { - ArrayList list = new ArrayList(); - foreach (var item in enumerable) - { - if (item is CustomAttributeTypedArgument) - { - list.Add(((CustomAttributeTypedArgument)item).Value); - } - else - { - list.Add(item); - } - } - - constructorArguments.Add(list.ToArray(parameterType.GetElementType())); - } - else - { - constructorArguments.Add(parameter.Value); - } - } - else - { - constructorArguments.Add(parameter.Value); - } - } - - ConstructorInfo constructor = attributeType.GetConstructor(constructorParameters.ToArray()); - attribute = constructor.Invoke(constructorArguments.ToArray()); - - foreach (var namedArgument in attributeData.NamedArguments) - { - attributeType.GetProperty(namedArgument.MemberInfo.Name).SetValue(attribute, namedArgument.TypedValue.Value, null); - } - } - - // If not able to create instance of attribute ignore attribute. (May happen for custom user defined attributes). - catch (BadImageFormatException) - { - } - catch (FileLoadException) - { - } - catch (TypeLoadException) - { - } - - return attribute as Attribute; - } - - private void AddNewAttributes( - IList customAttributes, - bool shouldGetAllAttributes, - Type type, - Dictionary uniqueAttributes, - List nonUniqueAttributes - ) - { - foreach (var attribute in customAttributes) - { - if (shouldGetAllAttributes - || (this.IsTypeInheriting(attribute.Constructor.DeclaringType, type) - || attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals( - type.AssemblyQualifiedName))) - { - Attribute attributeInstance = CreateAttributeInstance(attribute); - if (attributeInstance != null) - { - var attributeUsageAttribute = - this.GetCustomAttributes( - attributeInstance.GetType().GetTypeInfo(), - typeof(AttributeUsageAttribute), - true).FirstOrDefault() as AttributeUsageAttribute; - - if (attributeUsageAttribute != null && !attributeUsageAttribute.AllowMultiple) - { - if (!uniqueAttributes.ContainsKey(attributeInstance.GetType().FullName)) - { - uniqueAttributes.Add(attributeInstance.GetType().FullName, attributeInstance); - } - } - else - { - nonUniqueAttributes.Add(attributeInstance); - } - } - } - } - } - - /// - /// Check whether the member is loaded in a reflection only context. - /// - /// The member Info. - /// True if the member is loaded in a reflection only context. - private bool IsReflectionOnlyLoad( - MemberInfo memberInfo - ) - { - if (memberInfo != null) - { - return memberInfo.Module.Assembly.ReflectionOnly; - } - - return false; - } - - private bool IsTypeInheriting( - Type type1, - Type type2 - ) - { - while (type1 != null) - { - if (type1.AssemblyQualifiedName.Equals(type2.AssemblyQualifiedName)) - { - return true; - } - - type1 = type1.GetTypeInfo().BaseType; - } - - return false; - } - } -} diff --git a/source/TestAdapter/Utilities/XmlUtilities.cs b/source/TestAdapter/Utilities/XmlUtilities.cs deleted file mode 100644 index 50bdb3f..0000000 --- a/source/TestAdapter/Utilities/XmlUtilities.cs +++ /dev/null @@ -1,175 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -namespace nanoFramework.TestPlatform.MSTest.TestAdapter.Utilities -{ - using System; - using System.Diagnostics; - using System.IO; - using System.Reflection; - using System.Text; - using System.Xml; - - internal class XmlUtilities - { - private const string XmlNamespace = "urn:schemas-microsoft-com:asm.v1"; - - /// - /// Adds assembly redirection and converts the resulting config file to a byte array. - /// - /// The config File. - /// The assembly name. - /// The old version. - /// The new version. - /// A byte array of the config file with the redirections added. - internal byte[] AddAssemblyRedirection( - string configFile, - AssemblyName assemblyName, - string oldVersion, - string newVersion - ) - { - var doc = this.GetXmlDocument(configFile); - - var configurationElement = FindOrCreateElement(doc, doc, "configuration"); - var assemblyBindingSection = FindOrCreateAssemblyBindingSection(doc, configurationElement); - AddAssemblyBindingRedirect(doc, assemblyBindingSection, assemblyName, oldVersion, newVersion); - using (var ms = new MemoryStream()) - { - doc.Save(ms); - return ms.ToArray(); - } - } - - /// - /// Gets the Xml document from the config file. This is virtual for unit testing. - /// - /// The config file. - /// An XmlDocument. - internal virtual XmlDocument GetXmlDocument( - string configFile - ) - { - var doc = new XmlDocument(); - if (!string.IsNullOrEmpty(configFile?.Trim())) - { - using (var xmlReader = new XmlTextReader(configFile)) - { - xmlReader.DtdProcessing = DtdProcessing.Prohibit; - xmlReader.XmlResolver = null; - doc.Load(xmlReader); - } - } - - return doc; - } - - private static XmlElement FindOrCreateElement( - XmlDocument doc, - XmlNode parent, - string name - ) - { - var ret = parent[name]; - - if (ret != null) - { - return ret; - } - - ret = doc.CreateElement(name, parent.NamespaceURI); - parent.AppendChild(ret); - return ret; - } - - private static XmlElement FindOrCreateAssemblyBindingSection( - XmlDocument doc, - XmlElement configurationElement - ) - { - // Each section must be created with the xmlns specified so that - // we don't end up with xmlns="" on each element. - - // Find or create the runtime section (this one should not have an xmlns on it). - var runtimeSection = FindOrCreateElement(doc, configurationElement, "runtime"); - - // Use the assemblyBinding section if it exists; otherwise, create one. - var assemblyBindingSection = runtimeSection["assemblyBinding"]; - if (assemblyBindingSection != null) - { - return assemblyBindingSection; - } - - assemblyBindingSection = doc.CreateElement("assemblyBinding", XmlNamespace); - runtimeSection.AppendChild(assemblyBindingSection); - return assemblyBindingSection; - } - - /// - /// Add an assembly binding redirect entry to the config file. - /// - /// The doc. - /// The assembly Binding Section. - /// The assembly Name. - /// The from Version. - /// The to Version. - private static void AddAssemblyBindingRedirect( - XmlDocument doc, - XmlElement assemblyBindingSection, - AssemblyName assemblyName, - string fromVersion, - string toVersion) - { - Debug.Assert(assemblyName != null, "assemblyName should not be null."); - if (assemblyName == null) - { - throw new ArgumentNullException("assemblyName"); - } - - // Convert the public key token into a string. - StringBuilder publicKeyTokenString = null; - var publicKeyToken = assemblyName.GetPublicKeyToken(); - if (publicKeyToken != null) - { - publicKeyTokenString = new StringBuilder(publicKeyToken.GetLength(0) * 2); - for (var i = 0; i < publicKeyToken.GetLength(0); i++) - { - publicKeyTokenString.AppendFormat( - System.Globalization.CultureInfo.InvariantCulture, - "{0:x2}", - new object[] { publicKeyToken[i] }); - } - } - - // Get the culture as a string. - var cultureString = assemblyName.CultureInfo.ToString(); - if (string.IsNullOrEmpty(cultureString)) - { - cultureString = "neutral"; - } - - // Add the dependentAssembly section. - var dependentAssemblySection = doc.CreateElement("dependentAssembly", XmlNamespace); - assemblyBindingSection.AppendChild(dependentAssemblySection); - - // Add the assemblyIdentity element. - var assemblyIdentityElement = doc.CreateElement("assemblyIdentity", XmlNamespace); - assemblyIdentityElement.SetAttribute("name", assemblyName.Name); - if (publicKeyTokenString != null) - { - assemblyIdentityElement.SetAttribute("publicKeyToken", publicKeyTokenString.ToString()); - } - - assemblyIdentityElement.SetAttribute("culture", cultureString); - dependentAssemblySection.AppendChild(assemblyIdentityElement); - - var bindingRedirectElement = doc.CreateElement("bindingRedirect", XmlNamespace); - bindingRedirectElement.SetAttribute("oldVersion", fromVersion); - bindingRedirectElement.SetAttribute("newVersion", toVersion); - dependentAssemblySection.AppendChild(bindingRedirectElement); - } - } -} diff --git a/source/TestAdapter/key.snk b/source/TestAdapter/key.snk new file mode 100644 index 0000000000000000000000000000000000000000..67c9bb0ad77fd9cfb31a5fe1f8e4f6537f8883f8 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096IAgVrqn?0oVUK<}}z@wqRV>QE=V9G(P zv&4>n;N)r`28$iZ}__~(k83t)%SuJd!((DT{8XK)X~rK64E`*F3l z_5^K&AM;wi;^44^3SyC?622Htvk|)gU+)k0iCdaqB2%u@FR*KYku>(Zol_KusD7d= zA40qu=-~L94PXsGJ#eO@FlCr&O~0PO!6o-*GQq`zy7)g4B)IFgLhI0vfH=+L6i;`a zn(iWy{`8{{oj}vGf9S9M=48~NC!$coEAg@m^H3NXd_JrnT}HOd0AR%m^g zLg4+2jC7g-iYgo9N=!v@nv=I99O}eXu|AyULz5*Z*LDuQt4pP~UX|Zf`1Y#v4}kU8;^ac8l5!?oeMbz%wA_dHw*Ud81*FK}w1r zw3t~`4#bY+anQY5XU!i9#lc>IiLAxX~!c5@{0(|Buf%3XVh2FE%G?X~1 z7e5uWO?|rx0b=vcr3gpq3{-d$I8fk&Qrx<8j#tO3HOsxjGMJn4IT0*|11Oqu{gBd3 z&+6?KA|)R~alAn@8a!Y9Eq$TwFM@mu$U=dJ?PzCQY8jWLBlv^YEFIb8Fvhr*vMTOc zG+a5pRFEs9BT_>6MCF{WdN&DTsp^F!N=e1<>cRNkafH_Fr3`v9f%5G2(}LQ$j`vCM iwz?1qR;-^j3D_^H<{zl%^r6ivhR`zlIvhi1+jkY8izavg literal 0 HcmV?d00001 diff --git a/source/TestAdapter/nanoFramework.TestAdapter.csproj b/source/TestAdapter/nanoFramework.TestAdapter.csproj new file mode 100644 index 0000000..50841dd --- /dev/null +++ b/source/TestAdapter/nanoFramework.TestAdapter.csproj @@ -0,0 +1,27 @@ + + + + net4.6 + true + key.snk + + + + + + 1.6.1-preview.223 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 3.3.37 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/source/TestAdapter/packages.config b/source/TestAdapter/packages.config deleted file mode 100644 index 55c4f05..0000000 --- a/source/TestAdapter/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/source/TestAdapter_v1_light-wip/Extensions/LogHelperExtensions.cs b/source/TestAdapter_v1_light-wip/Extensions/LogHelperExtensions.cs deleted file mode 100644 index cb57ba8..0000000 --- a/source/TestAdapter_v1_light-wip/Extensions/LogHelperExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - -namespace nanoFramework.TestPlatform.TestAdapter -{ - public static class LogHelperExtensions - { - #region IMessageLogger extensions - - public static void InformationalMessage(this IMessageLogger logger, string message) - { - logger.SendMessage(TestMessageLevel.Informational, message); - } - - public static void ErrorMessage(this IMessageLogger logger, string message) - { - logger.SendMessage(TestMessageLevel.Error, message); - } - - #endregion - - - #region IFrameworkHandle extensions - - public static void InformationalMessage(this IFrameworkHandle frameworkHandle, string message) - { - frameworkHandle.SendMessage(TestMessageLevel.Informational, message); - } - public static void ErrorMessage(this IFrameworkHandle frameworkHandle, string message) - { - frameworkHandle.SendMessage(TestMessageLevel.Error, message); - } - - #endregion - } -} diff --git a/source/TestAdapter_v1_light-wip/Extensions/TestCaseExtensions.cs b/source/TestAdapter_v1_light-wip/Extensions/TestCaseExtensions.cs deleted file mode 100644 index ce472ba..0000000 --- a/source/TestAdapter_v1_light-wip/Extensions/TestCaseExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase; - -namespace nanoFramework.TestPlatform.TestAdapter -{ - public static class TestCaseExtensions - { - public static VSTestCase ToVSTestCase(this TestInterface.TestCase testCase) - { - var vsTestCase = new TestCase(testCase.Name, TestExecutor.ExecutorUri, testCase.Source); - vsTestCase.CodeFilePath = testCase.Filename; - vsTestCase.LineNumber = testCase.Line; - - foreach (var tag in testCase.Tags) - { - vsTestCase.Traits.Add(new Trait("Tag", tag)); - } - - return vsTestCase; - } - } -} diff --git a/source/TestAdapter_v1_light-wip/Properties/AssemblyInfo.cs b/source/TestAdapter_v1_light-wip/Properties/AssemblyInfo.cs deleted file mode 100644 index 508863d..0000000 --- a/source/TestAdapter_v1_light-wip/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("nanoFramework.TestAdapter")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("nanoFramework.TestAdapter")] -[assembly: AssemblyCopyright("Copyright © 2018 The nanoFramework project contributors")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.3.0")] -[assembly: AssemblyFileVersion("1.0.3.0")] diff --git a/source/TestAdapter_v1_light-wip/SettingsProvider.cs b/source/TestAdapter_v1_light-wip/SettingsProvider.cs deleted file mode 100644 index 7cf7d82..0000000 --- a/source/TestAdapter_v1_light-wip/SettingsProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using nanoFramework.TestPlatform.TestInterface; -using System.Xml; - -namespace nanoFramework.TestPlatform.TestAdapter -{ - [SettingsName(nFTestSettings.SettingsName)] - public class SettingsProvider : ISettingsProvider - { - public nFTestSettings nFTestSettings { get; private set; } - - #region ISettingsProvider - - public void Load(XmlReader reader) - { - var xml = new XmlDocument(); - reader.Read(); - nFTestSettings = nFTestSettings.Extract(xml.ReadNode(reader)); - } - - #endregion - - } -} diff --git a/source/TestAdapter_v1_light-wip/TestDiscoverer.cs b/source/TestAdapter_v1_light-wip/TestDiscoverer.cs deleted file mode 100644 index 33e0b92..0000000 --- a/source/TestAdapter_v1_light-wip/TestDiscoverer.cs +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -using nanoFramework.TestPlatform.TestInterface; -using nanoFramework.TestPlatform.TestInterface.Resources; -using System; -using System.Collections.Generic; - -namespace nanoFramework.TestPlatform.TestAdapter -{ - [DefaultExecutorUri(TestInterface.Constants.NFExecutorUriString)] - [FileExtension(".exe")] - [FileExtension(".dll")] - public class TestDiscoverer : ITestDiscoverer - { - private IDiscoveryContext _discoveryContext = null; - private IMessageLogger _logger = null; - private ITestCaseDiscoverySink _discoverySink = null; - private nFTestSettings _settings = new nFTestSettings(); - - #region ITestDiscoverer - - public void DiscoverTests( - IEnumerable sources, - IDiscoveryContext discoveryContext, - IMessageLogger logger, - ITestCaseDiscoverySink discoverySink - ) - { - _discoveryContext = discoveryContext ?? throw new ArgumentNullException(nameof(discoveryContext)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _discoverySink = discoverySink ?? throw new ArgumentNullException(nameof(discoverySink)); - - logger.SendMessage(TestMessageLevel.Informational, "Hello from nF DiscoverTests"); - - //// Retrieve nanoFramework specific settings - //if (!PopulateSettings()) - //{ - // _logger.ErrorMessage(StringResources.SettingsMissingDiscoveryWarning); - // return; - //} - - // Check if adapter is disabled - if (_settings.Disabled) - { - _logger.InformationalMessage(StringResources.TestAdapterDisabled); - return; - } - - // start discovery - _logger.InformationalMessage(StringResources.StartingDiscovery); - - DiscoverTests(sources); - - // done with discovery - _logger.InformationalMessage(StringResources.DiscoveryCompleted); - } - - #endregion - - - private void DiscoverTests(IEnumerable sources) - { - var discoverer = new UnitTestDiscoverer(_settings); - - var testCases = discoverer.DiscoverTests(sources); - //if (!string.IsNullOrEmpty(discoverer.Log)) - //{ - // _logger.InformationalMessage($"Discover log:{Environment.NewLine}{discoverer.Log}"); - //} - - // Add testcases to discovery sink - //LogDebug(TestMessageLevel.Informational, "Start adding test cases to discovery sink"); - foreach (var test in testCases) - { - _discoverySink.SendTestCase(test.ToVSTestCase()); - //LogDebug(TestMessageLevel.Informational, $" {testcase.Name}"); - } - //LogDebug(TestMessageLevel.Informational, "Finished adding test cases to discovery sink"); - } - - private bool PopulateSettings() - { - // Populate our test setting with whatever is coming in the runsettings - - var settingsprovider = _discoveryContext?.RunSettings?.GetSettings(nFTestSettings.SettingsName) as SettingsProvider; - - _settings = settingsprovider?.nFTestSettings; - - return _settings != null; - } - } -} diff --git a/source/TestAdapter_v1_light-wip/TestExecutor.cs b/source/TestAdapter_v1_light-wip/TestExecutor.cs deleted file mode 100644 index 0a07942..0000000 --- a/source/TestAdapter_v1_light-wip/TestExecutor.cs +++ /dev/null @@ -1,272 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -using nanoFramework.TestPlatform.TestInterface; -using nanoFramework.TestPlatform.TestInterface.Resources; -using System; -using System.Collections.Generic; -using System.IO; -using TestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase; - -namespace nanoFramework.TestPlatform.TestAdapter -{ - [ExtensionUri(TestInterface.Constants.NFExecutorUriString)] - - public class TestExecutor : ITestExecutor - { - public static readonly Uri ExecutorUri = new Uri(TestInterface.Constants.NFExecutorUriString); - - private bool _cancelled = false; - private IRunContext _runContext = null; - private IFrameworkHandle _frameworkHandle = null; - private nFTestSettings _settings = new nFTestSettings(); - - - #region ITestExecutor - - public void Cancel() - { - _cancelled = true; - //_executor.Cancel(); - } - - public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) - { - _cancelled = false; - _runContext = runContext; - _frameworkHandle = frameworkHandle; - - _frameworkHandle.SendMessage(TestMessageLevel.Error, "Hello from nF RunTests1"); - - //// Retrieve nanoFramework specific settings - //if (!PopulateSettings()) - //{ - // _frameworkHandle.ErrorMessage(StringResources.SettingsMissingDiscoveryWarning); - // return; - //} - - // Check if adapter is disabled - if (_settings.Disabled) - { - _frameworkHandle.InformationalMessage(StringResources.TestAdapterDisabled); - return; - } - - // start execution - _frameworkHandle.InformationalMessage(StringResources.StartingExecution); - - RunTests(tests); - - // done with execution - _frameworkHandle.InformationalMessage(StringResources.ExecutionCompleted); - - } - - public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) - { - _cancelled = false; - _frameworkHandle = frameworkHandle; - _runContext = runContext; - - _frameworkHandle.SendMessage(TestMessageLevel.Error, "Hello from nF RunTests2"); - - //// Retrieve nanoFramework specific settings - //if (!PopulateSettings()) - //{ - // _frameworkHandle.ErrorMessage(StringResources.SettingsMissingDiscoveryWarning); - // return; - //} - - // Check if adapter is disabled - if (_settings.Disabled) - { - _frameworkHandle.InformationalMessage(StringResources.TestAdapterDisabled); - return; - } - - - // start discovery - _frameworkHandle.InformationalMessage(StringResources.StartingDiscovery); - - var tests = DiscoverTests(sources); - - // done with discovery - _frameworkHandle.InformationalMessage(StringResources.DiscoveryCompleted); - - // run tests - _frameworkHandle.InformationalMessage(StringResources.StartingExecution); - - RunTests(tests); - - // done with discovery - _frameworkHandle.InformationalMessage(StringResources.ExecutionCompleted); - - } - - #endregion - - private bool PopulateSettings() - { - // Populate our test setting with whatever is coming in the runsettings - SettingsProvider settingsprovider = null; - bool found = false; - try - { - - var sP = _runContext?.RunSettings?.GetSettings(nFTestSettings.SettingsName); - var sP1 = sP as SettingsProvider; - - settingsprovider = sP1; - - found = true; - } - catch (Exception ex) - { - _settings = new nFTestSettings(); - } - - //_executor = new Catch2Interface.Executor(_settings, _runContext.SolutionDirectory, _runContext.TestRunDirectory); - - return found; - } - - - private List DiscoverTests(IEnumerable sources) - { - var tests = new List(); - - var discoverer = new UnitTestDiscoverer(_settings); - - var testCases = discoverer.DiscoverTests(sources); - //if (!string.IsNullOrEmpty(discoverer.Log)) - //{ - // _logger.InformationalMessage($"Discover log:{Environment.NewLine}{discoverer.Log}"); - //} - - // Add testcases to discovery sink - //LogDebug(TestMessageLevel.Informational, "Start adding test cases to discovery sink"); - foreach (var test in testCases) - { - tests.Add(test.ToVSTestCase()); - //LogDebug(TestMessageLevel.Informational, $" {testcase.Name}"); - } - //LogDebug(TestMessageLevel.Informational, "Finished adding test cases to discovery sink"); - - return tests; - } - - private void RunTests(IEnumerable tests) - { - //_executor.InitTestRuns(); - - foreach (var test in tests) - { - if (_cancelled) break; - - _frameworkHandle.RecordStart(test); - - var result = RunTest(test); - - _frameworkHandle.RecordResult(result); - } - } - private TestResult RunTest(TestCase test) - { - //LogVerbose(TestMessageLevel.Informational, $"Run test: {test.FullyQualifiedName}"); - _frameworkHandle.InformationalMessage($"Source: {test.Source}"); - _frameworkHandle.InformationalMessage($"SolutionDirectory: {_runContext.SolutionDirectory}"); - _frameworkHandle.InformationalMessage($"TestRunDirectory: {_runContext.TestRunDirectory}"); - - TestResult result = new TestResult(test); - - // Check if file exists - if (!File.Exists(test.Source)) - { - result.Outcome = TestOutcome.NotFound; - } - - // Run test - if (_runContext.IsBeingDebugged) - { - _frameworkHandle.InformationalMessage("Start debug run."); - //_frameworkHandle - // .LaunchProcessWithDebuggerAttached(test.Source - // , null - // , _executor.GenerateCommandlineArguments(test.DisplayName, true) - // , null); - - // Do not process output in Debug mode - result.Outcome = TestOutcome.None; - } - else - { - result.Outcome = TestOutcome.Passed; - - - //var testresult = _executor.Run(test.DisplayName, test.Source); - - //if (!string.IsNullOrEmpty(_executor.Log)) - //{ - // LogNormal(TestMessageLevel.Informational, $"Executor log:{Environment.NewLine}{_executor.Log}"); - //} - - // Process test results - //switch (testresult.Outcome) - //{ - // case Catch2Interface.TestOutcomes.Timedout: - // LogVerbose(TestMessageLevel.Warning, "Time out"); - // result.Outcome = TestOutcome.Skipped; - // result.ErrorMessage = testresult.ErrorMessage; - // result.Messages.Add(new TestResultMessage(TestResultMessage.StandardOutCategory, testresult.StandardOut)); - // result.Duration = testresult.Duration; - // break; - // case Catch2Interface.TestOutcomes.Cancelled: - // result.Outcome = TestOutcome.None; - // break; - // case Catch2Interface.TestOutcomes.Skipped: - // result.Outcome = TestOutcome.Skipped; - // result.ErrorMessage = testresult.ErrorMessage; - // break; - // default: - // if (testresult.Outcome == Catch2Interface.TestOutcomes.Passed) - // { - // result.Outcome = TestOutcome.Passed; - // } - // else - // { - // result.Outcome = TestOutcome.Failed; - // } - // result.Duration = testresult.Duration; - // result.ErrorMessage = testresult.ErrorMessage; - // result.ErrorStackTrace = testresult.ErrorStackTrace; - - // if (!string.IsNullOrEmpty(testresult.StandardOut)) - // { - // result.Messages.Add(new TestResultMessage(TestResultMessage.StandardOutCategory, testresult.StandardOut)); - // } - - // if (!string.IsNullOrEmpty(testresult.StandardError)) - // { - // result.Messages.Add(new TestResultMessage(TestResultMessage.StandardErrorCategory, testresult.StandardError)); - // } - - // if (!string.IsNullOrEmpty(testresult.AdditionalInfo)) - // { - // result.Messages.Add(new TestResultMessage(TestResultMessage.AdditionalInfoCategory, testresult.AdditionalInfo)); - // } - // break; - //} - } - - _frameworkHandle.InformationalMessage($"Finished test: {test.FullyQualifiedName}"); - - return result; - } - - } -} diff --git a/source/TestAdapter_v1_light-wip/nanoFramework.TestAdapter.csproj b/source/TestAdapter_v1_light-wip/nanoFramework.TestAdapter.csproj deleted file mode 100644 index 5feec50..0000000 --- a/source/TestAdapter_v1_light-wip/nanoFramework.TestAdapter.csproj +++ /dev/null @@ -1,88 +0,0 @@ - - - - - Debug - AnyCPU - {DFFFE110-DC78-4AE4-B314-3FBBC228F77F} - Library - Properties - nanoFramework.TestPlatform.TestAdapter - nanoFramework.TestPlatform.TestAdapter - v4.6 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - - - ..\key.snk - - - - ..\packages\Microsoft.TestPlatform.ObjectModel.15.8.0\lib\net451\Microsoft.TestPlatform.CoreUtilities.dll - - - ..\packages\Microsoft.TestPlatform.ObjectModel.15.8.0\lib\net451\Microsoft.TestPlatform.PlatformAbstractions.dll - - - ..\packages\Microsoft.TestPlatform.ObjectModel.15.8.0\lib\net451\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - - - - ..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - - - - - ..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll - - - - ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - - - - - - - - - - - - - - - - - - - - - - - {727be6e6-631d-4117-9ce1-26bb275b1777} - nanoFramework.TestInterface - - - - \ No newline at end of file diff --git a/source/TestAdapter_v1_light-wip/packages.config b/source/TestAdapter_v1_light-wip/packages.config deleted file mode 100644 index d79f5ea..0000000 --- a/source/TestAdapter_v1_light-wip/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/source/TestFramework/Friends.cs b/source/TestFramework/Friends.cs deleted file mode 100644 index 4d929e3..0000000 --- a/source/TestFramework/Friends.cs +++ /dev/null @@ -1,8 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("nanoFramework.TestPlatform.TestAdapter, PublicKey=00240000048000009400000006020000002400005253413100040000010001002197150f2f6408342ff40ed30d17b0405e1b1af122b1fc378594e830169a175f4a44a0f4a346920e82109e92f4f1e204d95f80c5f5eb805332f4eca866dc1d904eca1f9527d46affbc8271afe75c91a9f16a0252b44bbbbe2ad89b2390e5b0b34682b0e8b5e0f04ce6ef488be9dc2aafab01740726a1e3ee06080db1beed3aaf")] diff --git a/source/TestFramework/Properties/AssemblyInfo.cs b/source/TestFramework/Properties/AssemblyInfo.cs index 262254d..487c824 100644 --- a/source/TestFramework/Properties/AssemblyInfo.cs +++ b/source/TestFramework/Properties/AssemblyInfo.cs @@ -1,33 +1,20 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("CSharp.BlankApplication")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("CSharp.BlankApplication")] -[assembly: AssemblyCopyright("Copyright © ")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] +[assembly: AssemblyTitle("nanoFramework Test Framework")] +[assembly: AssemblyCompany("nanoFramework Contributors")] +[assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +///////////////////////////////////////////////////////////////// +// This attribute is mandatory when building Interop libraries // +// update this whenever the native assembly signature changes // +[assembly: AssemblyNativeVersion("1.0.0.0")] +///////////////////////////////////////////////////////////////// diff --git a/source/TestFramework/TestExtensions.cs b/source/TestFramework/TestExtensions.cs new file mode 100644 index 0000000..0595798 --- /dev/null +++ b/source/TestFramework/TestExtensions.cs @@ -0,0 +1,132 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using System; + +namespace nanoFramework.TestFramework +{ + /// + /// Extension for array element comparison + /// + public static class TestExtensions + { + /// + /// Compare the sequence of 2 arrays + /// + /// First array + /// Second array + /// True if the sequence is the same + public static bool SequenceEqual(this Array a, Array b) + { + if ((a == null) || (b == null)) + { + return false; + } + + if (a.Length != b.Length) + { + return false; + } + + + for (int i = 0; i < a.Length; i++) + { + object obja = a.GetValue(i); + object objb = b.GetValue(i); + var typea = obja.GetType(); + var typeb = objb.GetType(); + if (typea != typeb) + { + return false; + } + + switch (typea.FullName) + { + case "System.Int32": + if ((int)obja != (int)objb) + { + return false; + } + break; + case "System.UInt32": + if ((uint)obja != (uint)objb) + { + return false; + } + break; + case "System.Byte": + if ((byte)obja != (byte)objb) + { + return false; + } + break; + case "System.SByte": + if ((sbyte)obja != (sbyte)objb) + { + return false; + } + break; + case "System.Int16": + if ((short)obja != (short)objb) + { + return false; + } + break; + case "System.UInt16": + if ((ushort)obja != (ushort)objb) + { + return false; + } + break; + case "System.Int64": + if ((long)obja != (long)objb) + { + return false; + } + break; + case "System.UInt64": + if ((ulong)obja != (ulong)objb) + { + return false; + } + break; + case "System.Char": + if ((char)obja != (char)objb) + { + return false; + } + break; + case "System.Double": + if ((double)obja != (double)objb) + { + return false; + } + break; + case "System.Boolean": + if ((bool)obja != (bool)objb) + { + return false; + } + break; + case "System.Single": + if ((float)obja != (float)objb) + { + return false; + } + break; + default: + if (obja != objb) + { + return false; + } + break; + } + } + + return true; + } + } +} diff --git a/source/TestFramework/TestFramework.cs b/source/TestFramework/TestFramework.cs new file mode 100644 index 0000000..bb6e1be --- /dev/null +++ b/source/TestFramework/TestFramework.cs @@ -0,0 +1,738 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using System; +using System.Collections; + +namespace nanoFramework.TestFramework +{ + /// + /// A static Assert class as a helper for tests + /// + public class Assert + { + + #region true/false + + /// + /// Check if a condition is true + /// + /// The condition to check + /// Raises an exception if the condition is not true + public static void True(bool condition) + { + if (condition) + { + return; + } + + throw new Exception($"{condition} is not true"); + } + + /// + /// Check if a condition is false + /// + /// The condition to check + /// Raises an exception if the condition is not false + public static void False(bool condition) + { + if (!condition) + { + return; + } + + throw new Exception($"{condition} is not false"); + } + + #endregion + + #region Equal + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(bool a, bool b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(int a, int b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(Array a, Array b) + { + if (a.SequenceEqual(b)) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(uint a, uint b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(short a, short b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(ushort a, ushort b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(long a, long b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(ulong a, ulong b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(byte a, byte b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(char a, char b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(sbyte a, sbyte b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(double a, double b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(float a, float b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void Equal(string a, string b) + { + if (a == b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + #endregion + + #region NotEqual + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(bool a, bool b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(int a, int b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(Array a, Array b) + { + if (!a.SequenceEqual(b)) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are not equal + public static void NotEqual(uint a, uint b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(short a, short b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(ushort a, ushort b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(long a, long b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(ulong a, ulong b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(byte a, byte b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(char a, char b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(sbyte a, sbyte b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(double a, double b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(float a, float b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + /// + /// Check if both elements are not equal + /// + /// First element + /// Second element + /// Raises an exception if both elements are equal + public static void NotEqual(string a, string b) + { + if (a != b) + { + return; + } + + throw new Exception($"{a} is not equal to {b}"); + } + + #endregion + + #region string + + /// + /// Check if a string is included in another string + /// + /// The expected string + /// The actual string to check + /// Raises an exception if the expected string is not included in the actual + public static void Contains(string expected, string actual) + { + if (actual.IndexOf(expected) >= 0) + { + return; + } + + throw new Exception($"{actual} does not contains {expected}"); + } + + /// + /// Check if a string does not contains another string + /// + /// The expected string + /// The actual string to check + /// Raises an exception if the expected string is included in the actual + public static void DoesNotContains(string expected, string actual) + { + if (actual == null) + { + return; + } + + if (actual.IndexOf(expected) < 0) + { + return; + } + + throw new Exception($"{actual} does not contains {expected}"); + } + + /// + /// Check is a string ends with another string + /// + /// The expected string + /// The actual string to check + /// Raises an exception if the expected string is not at the end of the actual string + public static void EndsWith(string expected, string actual) + { + // We have to take the last index as the text can contains multiple times the same word + if (actual.LastIndexOf(expected) == actual.Length - expected.Length) + { + return; + } + + throw new Exception($"{actual} does not ends with {expected}"); + } + + /// + /// Check is a string starts with another string + /// + /// The expected string + /// The actual string to check + /// Raises an exception if the expected string is not at the end of the actual string + public static void StartsWith(string expected, string actual) + { + if (actual.IndexOf(expected) == 0) + { + return; + } + + throw new Exception($"{actual} does not starts with {expected}"); + } + + #endregion + + #region collection + + /// + /// Check if a collection is empty + /// + /// The collection to check + /// Raises an exception if the collection is not empty + public static void Empty(ICollection collection) + { + if (collection.Count == 0) + { + return; + } + + throw new Exception($"{collection} is not empty"); + } + + /// + /// Check if a collection is empty + /// + /// The collection to check + /// Raises an exception if the collection is not empty + public static void NotEmpty(ICollection collection) + { + if (collection.Count > 0) + { + return; + } + + throw new Exception($"{collection} is not empty"); + } + + #endregion region + + #region types, objects + + /// + /// Check if an object is equal to a type + /// + /// The type to check + /// The object to check + /// Raises an exception if the types are not equal + public static void IsType(Type type, object obj) + { + if (type == obj.GetType()) + { + return; + } + + throw new Exception($"{obj} is not type of {type}"); + } + + /// + /// Check if an object is not equal to a type + /// + /// The type to check + /// The object to check + /// Raises an exception if the types are equal + public static void IsNotType(Type type, object obj) + { + if (type != obj.GetType()) + { + return; + } + + throw new Exception($"{obj} is not type of {type}"); + } + + /// + /// Check if an object is the same as another + /// + /// The first object + /// The second object + /// Raises an exception if the objects are not the same + public static void Same(object a, object b) + { + if (a.Equals(b)) + { + return; + } + + throw new Exception($"{a} is not the same as {b}"); + } + + /// + /// Check if an object is not the same as another + /// + /// The first object + /// The second object + /// Raises an exception if the objects are the same + public static void NotSame(object a, object b) + { + if (!a.Equals(b)) + { + return; + } + + throw new Exception($"{a} is the same as {b}"); + } + + /// + /// Check if an object is null + /// + /// The object to check + /// Raises an exception if the object is not null + public static void Null(object obj) + { + if (obj == null) + { + return; + } + + throw new Exception($"{obj} is null"); + } + + /// + /// Check if an object is not null + /// + /// The object to check + /// Raises an exception if the object is null/exception> + public static void NotNull(object obj) + { + if (obj != null) + { + return; + } + + throw new Exception($"{obj} is not null"); + } + + /// + /// Check if an exception is raised and matching a type + /// + /// The exception to be raised + /// The method to execute + public static void Trows(Type exceptionType, Action action) + { + try + { + action.Invoke(); + } + catch (Exception ex) + { + if (ex.GetType() == exceptionType) + { + return; + } + + throw new Exception($"An exception {ex.GetType()} has been thrown but is not type {exceptionType}"); + } + + throw new Exception($"No exception has been thrown"); + } + + #endregion + } +} diff --git a/source/TestFramework/key.snk b/source/TestFramework/key.snk new file mode 100644 index 0000000000000000000000000000000000000000..67c9bb0ad77fd9cfb31a5fe1f8e4f6537f8883f8 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096IAgVrqn?0oVUK<}}z@wqRV>QE=V9G(P zv&4>n;N)r`28$iZ}__~(k83t)%SuJd!((DT{8XK)X~rK64E`*F3l z_5^K&AM;wi;^44^3SyC?622Htvk|)gU+)k0iCdaqB2%u@FR*KYku>(Zol_KusD7d= zA40qu=-~L94PXsGJ#eO@FlCr&O~0PO!6o-*GQq`zy7)g4B)IFgLhI0vfH=+L6i;`a zn(iWy{`8{{oj}vGf9S9M=48~NC!$coEAg@m^H3NXd_JrnT}HOd0AR%m^g zLg4+2jC7g-iYgo9N=!v@nv=I99O}eXu|AyULz5*Z*LDuQt4pP~UX|Zf`1Y#v4}kU8;^ac8l5!?oeMbz%wA_dHw*Ud81*FK}w1r zw3t~`4#bY+anQY5XU!i9#lc>IiLAxX~!c5@{0(|Buf%3XVh2FE%G?X~1 z7e5uWO?|rx0b=vcr3gpq3{-d$I8fk&Qrx<8j#tO3HOsxjGMJn4IT0*|11Oqu{gBd3 z&+6?KA|)R~alAn@8a!Y9Eq$TwFM@mu$U=dJ?PzCQY8jWLBlv^YEFIb8Fvhr*vMTOc zG+a5pRFEs9BT_>6MCF{WdN&DTsp^F!N=e1<>cRNkafH_Fr3`v9f%5G2(}LQ$j`vCM iwz?1qR;-^j3D_^H<{zl%^r6ivhR`zlIvhi1+jkY8izavg literal 0 HcmV?d00001 diff --git a/source/TestFramework/nanoFramework.TestFramework.nfproj b/source/TestFramework/nanoFramework.TestFramework.nfproj new file mode 100644 index 0000000..b788b18 --- /dev/null +++ b/source/TestFramework/nanoFramework.TestFramework.nfproj @@ -0,0 +1,60 @@ + + + + $(MSBuildToolsPath)..\..\..\nanoFramework\v1.0\ + + + + Debug + AnyCPU + {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + d66a7774-afab-466b-9cfb-3485b02f4af4 + Library + Properties + 512 + nanoFramework.TestFramework + nanoFramework.TestFramework + v1.0 + + + true + + + key.snk + + + false + + + + + + + + + + + + ..\..\packages\nanoFramework.CoreLibrary.1.10.1-preview.9\lib\mscorlib.dll + True + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. + + + + \ No newline at end of file diff --git a/source/TestFramework/packages.config b/source/TestFramework/packages.config index 1c08da0..e4a4549 100644 --- a/source/TestFramework/packages.config +++ b/source/TestFramework/packages.config @@ -1,4 +1,5 @@  - + + \ No newline at end of file diff --git a/source/TestFrameworkShared/CleanupAttribute.cs b/source/TestFrameworkShared/CleanupAttribute.cs new file mode 100644 index 0000000..54907b9 --- /dev/null +++ b/source/TestFrameworkShared/CleanupAttribute.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using System; + +namespace nanoFramework.TestFramework +{ + /// + /// Clean up attribute typically used to clean up after the tests, it will always been called the last after all the Test Method run. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CleanupAttribute : Attribute + { + } +} diff --git a/source/TestFrameworkShared/SetupAttribute.cs b/source/TestFrameworkShared/SetupAttribute.cs new file mode 100644 index 0000000..cbc7480 --- /dev/null +++ b/source/TestFrameworkShared/SetupAttribute.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using System; + +namespace nanoFramework.TestFramework +{ + /// + /// Setup attribute, will always be launched first by the launcher, typically used to setup hardware or classes that has to be used in all the tests. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class SetupAttribute : Attribute + { + } +} diff --git a/source/TestPlatform.Shared/TestClassAttribute.cs b/source/TestFrameworkShared/TestClassAttribute.cs similarity index 88% rename from source/TestPlatform.Shared/TestClassAttribute.cs rename to source/TestFrameworkShared/TestClassAttribute.cs index a007c7d..4ec9d78 100644 --- a/source/TestPlatform.Shared/TestClassAttribute.cs +++ b/source/TestFrameworkShared/TestClassAttribute.cs @@ -1,13 +1,13 @@ // -// Copyright (c) 2018 The nanoFramework project contributors +// Copyright (c) .NET Foundation and Contributors // Portions Copyright (c) Microsoft Corporation. All rights reserved. // See LICENSE file in the project root for full license information. // -namespace Microsoft.VisualStudio.TestTools.UnitTesting -{ - using System; +using System; +namespace nanoFramework.TestFramework +{ /// /// The test class attribute. /// diff --git a/source/TestFrameworkShared/TestFrameworkShared.projitems b/source/TestFrameworkShared/TestFrameworkShared.projitems new file mode 100644 index 0000000..5370617 --- /dev/null +++ b/source/TestFrameworkShared/TestFrameworkShared.projitems @@ -0,0 +1,17 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 55f048b5-6739-43c5-a93d-db61db8e912f + + + TestFrameworkShared + + + + + + + + \ No newline at end of file diff --git a/source/TestPlatform.Shared/TestPlatform.Shared.shproj b/source/TestFrameworkShared/TestFrameworkShared.shproj similarity index 87% rename from source/TestPlatform.Shared/TestPlatform.Shared.shproj rename to source/TestFrameworkShared/TestFrameworkShared.shproj index 5ce84ba..729b57b 100644 --- a/source/TestPlatform.Shared/TestPlatform.Shared.shproj +++ b/source/TestFrameworkShared/TestFrameworkShared.shproj @@ -1,13 +1,13 @@ - beaf6aed-9605-4d23-9dbc-805436bd5bf0 + 55f048b5-6739-43c5-a93d-db61db8e912f 14.0 - + diff --git a/source/TestPlatform.Shared/TestMethodAttribute.cs b/source/TestFrameworkShared/TestMethodAttribute.cs similarity index 72% rename from source/TestPlatform.Shared/TestMethodAttribute.cs rename to source/TestFrameworkShared/TestMethodAttribute.cs index 5b3f30e..85a7801 100644 --- a/source/TestPlatform.Shared/TestMethodAttribute.cs +++ b/source/TestFrameworkShared/TestMethodAttribute.cs @@ -1,13 +1,13 @@ // -// Copyright (c) 2018 The nanoFramework project contributors +// Copyright (c) .NET Foundation and Contributors // Portions Copyright (c) Microsoft Corporation. All rights reserved. // See LICENSE file in the project root for full license information. // -namespace Microsoft.VisualStudio.TestTools.UnitTesting -{ - using System; +using System; +namespace nanoFramework.TestFramework +{ /// /// The test method attribute. /// diff --git a/source/TestInterface/Constants.cs b/source/TestInterface/Constants.cs deleted file mode 100644 index a24a6f8..0000000 --- a/source/TestInterface/Constants.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace nanoFramework.TestPlatform.TestInterface -{ - public static class Constants - { - public const string NFExecutorUriString = "executor://nanoFrameworkTestExecutor"; - - } -} diff --git a/source/TestInterface/Properties/AssemblyInfo.cs b/source/TestInterface/Properties/AssemblyInfo.cs deleted file mode 100644 index 9d2b778..0000000 --- a/source/TestInterface/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("nanoFramework.TestInterface")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("nanoFramework.TestInterface")] -[assembly: AssemblyCopyright("Copyright © 2018 The nanoFramework project contributors")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/source/TestInterface/RegExHelper.cs b/source/TestInterface/RegExHelper.cs deleted file mode 100644 index 683721e..0000000 --- a/source/TestInterface/RegExHelper.cs +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using System.Text.RegularExpressions; - -namespace nanoFramework.TestPlatform.TestInterface -{ - public static class RegExHelper - { - public static readonly Regex Regex_TrueFalse = new Regex(@"^(?i:true)$|^(?i:false)$", RegexOptions.Singleline); - - } -} diff --git a/source/TestInterface/Resources/StringResources.Designer.cs b/source/TestInterface/Resources/StringResources.Designer.cs deleted file mode 100644 index b07941c..0000000 --- a/source/TestInterface/Resources/StringResources.Designer.cs +++ /dev/null @@ -1,117 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace nanoFramework.TestPlatform.TestInterface.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class StringResources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal StringResources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("nanoFramework.TestPlatform.TestInterface.Resources.StringResources", typeof(StringResources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Test discovery completed.. - /// - public static string DiscoveryCompleted { - get { - return ResourceManager.GetString("DiscoveryCompleted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Test executiobcompleted.. - /// - public static string ExecutionCompleted { - get { - return ResourceManager.GetString("ExecutionCompleted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Couldn't find nanoFramework test adapter settings. Most likely this is an indication that you haven't select or provide a runsettings files in the Test configuration.. - /// - public static string SettingsMissingDiscoveryWarning { - get { - return ResourceManager.GetString("SettingsMissingDiscoveryWarning", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Starting test discovery.... - /// - public static string StartingDiscovery { - get { - return ResourceManager.GetString("StartingDiscovery", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Starting test execution.... - /// - public static string StartingExecution { - get { - return ResourceManager.GetString("StartingExecution", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to nanoFramework Test Adapter is disabled.. - /// - public static string TestAdapterDisabled { - get { - return ResourceManager.GetString("TestAdapterDisabled", resourceCulture); - } - } - } -} diff --git a/source/TestInterface/Resources/StringResources.resx b/source/TestInterface/Resources/StringResources.resx deleted file mode 100644 index 7e188b4..0000000 --- a/source/TestInterface/Resources/StringResources.resx +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Test discovery completed. - - - Test executiobcompleted. - - - Couldn't find nanoFramework test adapter settings. Most likely this is an indication that you haven't select or provide a runsettings files in the Test configuration. - - - Starting test discovery... - - - Starting test execution... - - - nanoFramework Test Adapter is disabled. - - \ No newline at end of file diff --git a/source/TestInterface/TestCase.cs b/source/TestInterface/TestCase.cs deleted file mode 100644 index 31b2782..0000000 --- a/source/TestInterface/TestCase.cs +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -using System.Collections.Generic; - -namespace nanoFramework.TestPlatform.TestInterface -{ - // This class is a replacement for Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase. - // The goal is to have the TestInterface assembly independent from Microsoft.TestPlatform.ObjectModel package. - public class TestCase - { - public string Name { get; set; } = string.Empty; - public string Source { get; set; } = string.Empty; - public string Filename { get; set; } = string.Empty; - public int Line { get; set; } = -1; - public List Tags { get; set; } = new List(); - } -} \ No newline at end of file diff --git a/source/TestInterface/TestExecutionManager.cs b/source/TestInterface/TestExecutionManager.cs deleted file mode 100644 index b723974..0000000 --- a/source/TestInterface/TestExecutionManager.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace nanoFramework.TestPlatform.TestInterface -{ - public class TestExecutionManager - { - //public TestResult RunTests(sources, runContext, frameworkHandle, this.cancellationToken) - //{ - - //} - } -} diff --git a/source/TestInterface/UnitTestDiscoverer.cs b/source/TestInterface/UnitTestDiscoverer.cs deleted file mode 100644 index 3ebd026..0000000 --- a/source/TestInterface/UnitTestDiscoverer.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace nanoFramework.TestPlatform.TestInterface -{ - public class UnitTestDiscoverer - { - private nFTestSettings _settings; - - public UnitTestDiscoverer(nFTestSettings settings) - { - _settings = settings ?? new nFTestSettings(); - - } - - public List DiscoverTests(IEnumerable sources) - { - var tests = new List(); - - // Retrieve test cases for each provided source - foreach (var source in sources) - { - //LogVerbose($"Source: {source}{Environment.NewLine}"); - //if (!File.Exists(source)) - //{ - // LogVerbose($" File not found.{Environment.NewLine}"); - //} - //else if (CheckSource(source)) - //{ - // var foundtests = ExtractTestCases(source); - // LogVerbose($" Testcase count: {foundtests.Count}{Environment.NewLine}"); - // tests.AddRange(foundtests); - //} - //else - //{ - // LogVerbose($" Invalid source.{Environment.NewLine}"); - //} - //LogDebug($" Accumulated Testcase count: {tests.Count}{Environment.NewLine}"); - - tests.Add(new TestCase() { Filename = "filename", Line = 10, Source = "source", Name = "test name" }); - } - - return tests; - } - } -} diff --git a/source/TestInterface/nFTestSettings.cs b/source/TestInterface/nFTestSettings.cs deleted file mode 100644 index a1b2647..0000000 --- a/source/TestInterface/nFTestSettings.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2018 The nanoFramework project contributors -// See LICENSE file in the project root for full license information. -// - -using System.Xml; - -namespace nanoFramework.TestPlatform.TestInterface -{ -#pragma warning disable IDE1006 // Naming Styles // following project name with lower case - public class nFTestSettings -#pragma warning restore IDE1006 // Naming Styles - { - /// - /// The settings name - /// - public const string SettingsName = "NanoFrameworkAdapter"; - - - #region properties - - public bool Disabled { get; private set; } - - #endregion - - - /// - /// Initializes a new instance of class. - /// - public nFTestSettings() - { - Disabled = false; - } - - public static nFTestSettings Extract(XmlNode node) - { - nFTestSettings settings = new nFTestSettings(); - - // Make sure we have the correct node, and extract settings - if (node.Name == SettingsName) - { - // Check if test adapter is disabled - var disabled = node.Attributes["disabled"]?.Value; - if (disabled != null && TestInterface.RegExHelper.Regex_TrueFalse.IsMatch(disabled)) - { - settings.Disabled = TestInterface.RegExHelper.Regex_TrueFalse.IsMatch(disabled); - } - - if (settings.Disabled) - { - // is disabled, return now, don't bother with parsing the rest of it - return settings; - } - } - - return settings; - } - - } -} diff --git a/source/TestInterface/nanoFramework.TestInterface.csproj b/source/TestInterface/nanoFramework.TestInterface.csproj deleted file mode 100644 index 44c02b6..0000000 --- a/source/TestInterface/nanoFramework.TestInterface.csproj +++ /dev/null @@ -1,70 +0,0 @@ - - - - - Debug - AnyCPU - {727BE6E6-631D-4117-9CE1-26BB275B1777} - Library - Properties - nanoFramework.TestPlatform.TestInterface - nanoFramework.TestPlatform.TestInterface - v4.6 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - - - ..\key.snk - - - - - - - - - - - - - - - - - - True - True - StringResources.resx - - - - - - - - PublicResXFileCodeGenerator - StringResources.Designer.cs - - - - \ No newline at end of file diff --git a/source/TestPlatform.Shared/TestPlatform.Shared.projitems b/source/TestPlatform.Shared/TestPlatform.Shared.projitems deleted file mode 100644 index b5972fb..0000000 --- a/source/TestPlatform.Shared/TestPlatform.Shared.projitems +++ /dev/null @@ -1,15 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - beaf6aed-9605-4d23-9dbc-805436bd5bf0 - - - TestPlatform.Shared - - - - - - \ No newline at end of file diff --git a/source/UnitTestLauncher/Program.cs b/source/UnitTestLauncher/Program.cs new file mode 100644 index 0000000..87e0d48 --- /dev/null +++ b/source/UnitTestLauncher/Program.cs @@ -0,0 +1,86 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using System; +using System.Diagnostics; +using System.Reflection; + +namespace nanoFramework.TestFramework +{ + /// + /// This class is a unit test launcher for nanoFramework + /// + public class UnitTestLauncher + { + /// + /// Main function + /// + public static void Main() + { + Assembly test = Assembly.Load("NFUnitTest"); + + Type[] allTypes = test.GetTypes(); + + foreach (var type in allTypes) + { + if (type.IsClass) + { + var typeAttribs = type.GetCustomAttributes(true); + + foreach (var typeAttrib in typeAttribs) + { + if (typeof(TestClassAttribute) == typeAttrib.GetType()) + { + var methods = type.GetMethods(); + + // First we look at Setup + RunTest(methods, typeof(SetupAttribute)); + + // then we run the tests + RunTest(methods, typeof(TestMethodAttribute)); + + // last we handle Cleanup + RunTest(methods, typeof(CleanupAttribute)); + } + } + } + } + } + + private static void RunTest( + MethodInfo[] methods, + Type attribToRun) + { + long dt; + long totalTicks; + + foreach (var method in methods) + { + var attribs = method.GetCustomAttributes(true); + + foreach (var attrib in attribs) + { + if (attribToRun == attrib.GetType()) + { + try + { + dt = DateTime.UtcNow.Ticks; + method.Invoke(null, null); + totalTicks = DateTime.UtcNow.Ticks - dt; + + Debug.WriteLine($"Test passed: {method.Name}, {totalTicks}"); + } + catch (Exception ex) + { + Debug.WriteLine($"Test failed: {method.Name}, {ex.Message}"); + } + + } + } + } + } + } +} diff --git a/source/UnitTestLauncher/Properties/AssemblyInfo.cs b/source/UnitTestLauncher/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..aa084d8 --- /dev/null +++ b/source/UnitTestLauncher/Properties/AssemblyInfo.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("nanoFramework Test Framework")] +[assembly: AssemblyCompany("nanoFramework Contributors")] +[assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +///////////////////////////////////////////////////////////////// +// This attribute is mandatory when building Interop libraries // +// update this whenever the native assembly signature changes // +[assembly: AssemblyNativeVersion("1.0.0.0")] +///////////////////////////////////////////////////////////////// diff --git a/source/UnitTestLauncher/key.snk b/source/UnitTestLauncher/key.snk new file mode 100644 index 0000000000000000000000000000000000000000..67c9bb0ad77fd9cfb31a5fe1f8e4f6537f8883f8 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096IAgVrqn?0oVUK<}}z@wqRV>QE=V9G(P zv&4>n;N)r`28$iZ}__~(k83t)%SuJd!((DT{8XK)X~rK64E`*F3l z_5^K&AM;wi;^44^3SyC?622Htvk|)gU+)k0iCdaqB2%u@FR*KYku>(Zol_KusD7d= zA40qu=-~L94PXsGJ#eO@FlCr&O~0PO!6o-*GQq`zy7)g4B)IFgLhI0vfH=+L6i;`a zn(iWy{`8{{oj}vGf9S9M=48~NC!$coEAg@m^H3NXd_JrnT}HOd0AR%m^g zLg4+2jC7g-iYgo9N=!v@nv=I99O}eXu|AyULz5*Z*LDuQt4pP~UX|Zf`1Y#v4}kU8;^ac8l5!?oeMbz%wA_dHw*Ud81*FK}w1r zw3t~`4#bY+anQY5XU!i9#lc>IiLAxX~!c5@{0(|Buf%3XVh2FE%G?X~1 z7e5uWO?|rx0b=vcr3gpq3{-d$I8fk&Qrx<8j#tO3HOsxjGMJn4IT0*|11Oqu{gBd3 z&+6?KA|)R~alAn@8a!Y9Eq$TwFM@mu$U=dJ?PzCQY8jWLBlv^YEFIb8Fvhr*vMTOc zG+a5pRFEs9BT_>6MCF{WdN&DTsp^F!N=e1<>cRNkafH_Fr3`v9f%5G2(}LQ$j`vCM iwz?1qR;-^j3D_^H<{zl%^r6ivhR`zlIvhi1+jkY8izavg literal 0 HcmV?d00001 diff --git a/source/TestFramework/TestFramework.nfproj b/source/UnitTestLauncher/nanoFramework.UnitTestLauncher.nfproj similarity index 66% rename from source/TestFramework/TestFramework.nfproj rename to source/UnitTestLauncher/nanoFramework.UnitTestLauncher.nfproj index cac55a0..5b436fc 100644 --- a/source/TestFramework/TestFramework.nfproj +++ b/source/UnitTestLauncher/nanoFramework.UnitTestLauncher.nfproj @@ -1,5 +1,5 @@ - + $(MSBuildToolsPath)..\..\..\nanoFramework\v1.0\ @@ -8,40 +8,42 @@ Debug AnyCPU {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 554ad6a9-97c7-4610-bac9-cd1fd16bd40c - Library + 897fc4ea-823d-4343-8cc6-b6f28c3ff91e + Exe Properties 512 - nanoFramework.TestPlatform.TestFramework - nanoFramework.TestPlatform.TestFramework + nanoFramework.TestFramework + nanoFramework.UnitTestLauncher v1.0 - bin\$(Configuration)\nanoFramework.TestPlatform.TestFramework.xml true - ..\key.snk + key.snk false - + - - ..\packages\nanoFramework.CoreLibrary.1.0.1-preview127\lib\mscorlib.dll + + + + + + + + + ..\..\packages\nanoFramework.CoreLibrary.1.10.1-preview.9\lib\mscorlib.dll True True - - - - diff --git a/source/UnitTestLauncher/packages.config b/source/UnitTestLauncher/packages.config new file mode 100644 index 0000000..e4a4549 --- /dev/null +++ b/source/UnitTestLauncher/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/source/VSIXTestAdapter/Properties/AssemblyInfo.cs b/source/VSIXTestAdapter/Properties/AssemblyInfo.cs deleted file mode 100644 index 818fbbe..0000000 --- a/source/VSIXTestAdapter/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("nanoFramework Unit Test Visual Studio extension")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("VSIXTestAdapter")] -[assembly: AssemblyCopyright("Copyright © 2018 The nanoFramework project contributors")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.1.0.0")] -[assembly: AssemblyFileVersion("0.1.0.0")] diff --git a/source/VSIXTestAdapter/VSIXTestAdapter.csproj b/source/VSIXTestAdapter/VSIXTestAdapter.csproj deleted file mode 100644 index 150a1c2..0000000 --- a/source/VSIXTestAdapter/VSIXTestAdapter.csproj +++ /dev/null @@ -1,63 +0,0 @@ - - - - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {D58D316D-C2A0-44B6-8563-F0D2FDF921D6} - Library - Properties - nanoFrameworkVSTestAdapter - nanoFrameworkVSTestAdapter - v4.6 - false - false - false - false - false - false - Program - $(DevEnvDir)devenv.exe - /rootsuffix Exp - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - Designer - - - - - - \ No newline at end of file diff --git a/source/VSIXTestAdapter/source.extension.vsixmanifest b/source/VSIXTestAdapter/source.extension.vsixmanifest deleted file mode 100644 index 18c0121..0000000 --- a/source/VSIXTestAdapter/source.extension.vsixmanifest +++ /dev/null @@ -1,25 +0,0 @@ - - - - - nanoFramework Test Adapter - Visual Studio 2017 extension providing unit test for nanoFramework solutions. - http://www.nanoframework.net - nanoFramework, unit test, unit testing, test adapter, netnf, netmf - - - - - - - - - - - - - - - - - diff --git a/source/key.snk b/source/key.snk deleted file mode 100644 index f61c2ecc845a4a3cff1f473d91626b497fc4d71a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096YmlY2$WC%1b^bXSv7qCEH8yfKwr@-^z5i++#Qfk${&>{)N22{f^o0s zT#>2qY64QUOS`@**qbAe<*>6xg0Sed;P6c5??{X3+$yiD0dxl@q2ulb2o15m?K-b3 z1#&!X2VLG{SK^LDH9RM5^YI>Jo(3E|2tp=}J$V?47>|O6me(ojA+Tp?f3OlB1qMiJPxSvW#;DE=NfEAFWvJ*x{qO42 z<3N1oztWh>0Q+i9L&6u35ENTILYBtYl1HiML5mDyh)JpvpoGp@5>!R152B4S{c#mA z9TE)@^$stx@a%cK07=`W5Sc1e8Za%{WhyJYRsVqUg7lw zSz-+$#j~1)4wHb1Y`f3uGv4o%sCz>nq`}AECun{h{I+26+Vw|EIi#p17S@l3dUE6N z^$C>L;@MFI@dY#d(R?z5(8N#OrV_gtU$I2>1TGzrU=;MH$)Epv8>E(-rMoLJbk>+B1l%Z<43yc3= + + + nanoFramework.TestFramework + $version$ + nanoFramework.TestFramework + nanoFramework project contributors + nanoFramework project contributors,dotnetfoundation + false + LICENSE.md + + true + https://github.com/nanoFramework.TestPlatform + https://secure.gravatar.com/avatar/97d0e092247f0716db6d4b47b7d1d1ad + images\nf-logo.png + + Copyright (c) .NET Foundation and Contributors + This package includes the Test Framework to be used for Unit Tests in .NET nanoFramework C# projects. + nanoFramework C# csharp netmf netnf unittest + + + + + + + + + + + + + + + + + + diff --git a/source/packages.config b/source/packages.config deleted file mode 100644 index 0eca3dc..0000000 --- a/source/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/source/runsettings/nano.runsettings b/source/runsettings/nano.runsettings new file mode 100644 index 0000000..62f0a00 --- /dev/null +++ b/source/runsettings/nano.runsettings @@ -0,0 +1,13 @@ + + + + + 1 + .\TestResults + 120000 + Framework40 + + + None + + \ No newline at end of file diff --git a/version.json b/version.json new file mode 100644 index 0000000..a4d13b8 --- /dev/null +++ b/version.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "1.0", + "assemblyVersion": { + "precision": "build" + }, + "versionHeightOffset": 3, + "semVer1NumericIdentifierPadding": 3, + "nuGetPackageVersion": { + "semVer": 2.0 + }, + "publicReleaseRefSpec": [ + "^refs/heads/master$", + "^refs/heads/v\\d+(?:\\.\\d+)?$" + ], + "cloudBuild": { + "setAllVariables": true, + "buildNumber": null + }, + "release": { + "branchName": "release-v{version}", + "firstUnstableTag": "preview", + "versionIncrement": "build" + } +}